C/C++中指针是灵活多变,可以指指向任意地址,但是地址有可以存储任意对象,因此指针与其它对象的定义结合在一起,功能和作用是不同的,很容易混淆,尤其是const、数组和函数。
下面分别介绍一下const、数组、函数与指针一起时在如何使用,有点像绕口令。
1、const与指针
const用来修饰不变量,const与指针结合在一起,有多种称谓和定义,例如: 常量指针、指针常量(指向常量的指针)、指向常量的常量指针。
中文中,两个或多个次名次在一起的时候,通常最后一个词才是关键词,表示整个词组的意义,而前面都是修饰性的。这样看来看,就比较容易搞清楚各种称谓的意义。
常量指针(const指针),根据上面的分词定主语,表示这是一个指针,是一个常量指针,也就是说,指针是一个常量,给这个指针赋值一个地址之后,不能再改变指针的指向,但是可以修改地址所存的对象(值)。
指针常量(指向常量的指针),表示一个指向常量的指针,也就是说,指针是所指的地址是可以变化的,但是每个可以赋值给这个指针的地址所存的对象是常量。
指向常量的常量指针,这个是非常明确的,指针是常量类型的, 指针所指的地址存储的对象也是常量。
如何定义和使用者三种指针呢,直接来例子吧:
int a = 1; //定义一个变量;
const int b = 2; // 定义一个常量;
const int c = 3;
const int * pa = &b; // 定义一个指向常量的指针;
pa = &c; //OK, pa是一个指向常量的指针,c和b都是常量,其地址也是常量;
pa = (int*)c; //OK, pa是一个指向常量的指针,c和b都是常量;
pa=(int*)100; //OK, pa是一个指向常量的指针, (int*)100是一个常量地址
int const * pb = &c; //OK, pb也是一个指向常量的指针,
int * const pc = &a; //OK, pc是一个常量指针,
pc=&a + 1; //Error,pc是一个常量指针,赋值之后,不能修改指针所指,一辈子只能指向a的地址;
const int* const pd = &b; //OK, pd是一个指向常量的常量指针, b是常量, b的地址也是常量;
我们看到红色字体这四个指针的定义,前面两个(pa,pb)都是指向常量的指针,pc是一个常量指针,pd是一个指向常量的常量指针;
很多人很难区分pa,pb,pc这三种定义,有一个简单办法,那就是以 * 号来分割,const在 * 号左边的是表示修饰指针所指的地址存储的值,而const在*号右边的表示修饰指针本身。
2、数组与指针
数组与指针,在大家的印象中,和日常工作中经常使用指针来操作数组, 但是数组和指针还会结合在一起定义使用,因此就出现了指针数组和数组指针。
从概念上区分,是很简单的,还是根据修饰词和关键词来判断。
指针数组:它本身是一个数组,数组里面的元素是指针;
数组指针:它本身是一个指针,是指向数组的指针;
下面看看这两种情况怎么定义和使用:
short (*paa)[20];
short *pbb[20];
如何区分指针数组和数组指针呢,也很简单,"*", "[]", "()"都是运算符, 而运算符有优先级和结合性,而优先级从高到低分别是"()", "[]", "*"。
这样很容易区分上面paa和pbb的意义, paa先与*结合,说明paa是一个指针,然后与[]结合,就paa是一个指向数组的指针;
而pbb没有使用"()", 那么pbb先与[]结合,说明pbb是一个数组,然后再与*结合,说明pbb是一个数组,数组元素是指针。
从使用上来说,指针数组,可以看成是一个一般的数组,其元素的类型变成某某指针。
而数组指针,通常用来访问数组的,一般我们用指针访问访问一维数组, 那么数组指针,根据其数组的维数,通常是访问更高一维的数组,因为指针可以表示一维, 但是也可以访问同维度的数组,但是这个时候,指针取值相当于数组定义时数组的名称。
需要注意的是指针数组在进行 +1 和自增操作的时候,地址偏移的长度是数组的维数和类型相关。
short aa[20] = {10, 20, 30, 40, 50, 60};
short (*paa)[20] = &aa; //paa是一个数组指针,指向的是一维数组,paa相当于一个二维数组, 因此paa相当于aa的地址,而*paa相当于数组名称
cout<<"*paa[1]="<<*paa[1]<<endl; //注意结合性这里,paa[]先结合,因此这里paa[]表示数组的第一维,也就是paa指针所表示的那一维,但是paa指向的是一个一维数组,因此这里访问是非法的
cout<<"(*paa)[1]="<<(*paa)[1]<<endl; //这里*paa表示数组aa,因此(*paa)[1]等同于aa[1];
下面是通过一个指向一维数组的指针来访问二维数组
short bb[3][20] = {{1,2,3,4,5,6},{10, 20, 30, 40, 50, 60},{100, 200, 300, 400, 500, 600}};
paa = bb;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 20; j++)
{
cout<<paa[i][j]<<",";
}
cout<<endl;
}
3、函数与指针
函数和指针的结合在概念上与前面的数组、常量与指针结合类似,存在函数指针和指针函数两个称谓,采用同样的分析方法,我们来看一下他们概念上的区别。
指针函数,本质上是一个函数,只是这个函数的返回值类型一定是某某指针类型,在编码中,经常使用从函数中返回一块内存地址;
函数指针,本质是一个指针,但是这个指针是指向函数的,也许不好理解,我们通常的指针指向的是某个变量的地址,指针本来就是为了给程序员方便的访问地址而出现的,因此指针指向函数也是很自然的事情。
函数指针在较大型的C项目或者用C来实现面向对象编程的时候常用的,也有人说C用函数指针可以实现“多态”,应该说是模仿吧。我们不发散了,下面看看是如何定义使用。
指针函数:
int* func(); //返回值为int* 类型的指针函数;
char* getName(struct Person* p); //返回值为char*类型的指针函数;
函数指针:
int (*pFunc1)(); //返回值为int类型的函数指针,这里实际上是定义一个类型,函数没有输入参数
char (*pFunc2)(struct Person*p); //返回值为char类型的函数指针,也是定义一个类型, struct Person*p 是输入参数;
从上面的定义上,很容易区分,关键还是看"*"和名称后面的"()"操作符级别和结合性, 由于"()"操作级别比"*"高,但是前面的"()"可以改变优先级。
使用指针函数需要注意两点:
1)指针函数返回的是一个地址,也就是一块内存,要注意不能返回局部变量的地址,也就是栈的地址,否则可能出现不可预料的问题;
2)指针寒暑返回的地址, 如果是全局变量的地址,那么函数就是不可重入的,就是线程不安全的;
前面的pFunc1和pFunc2都是定义一个函数指针的类型,在编程中,通常通过typedef 来定义类型,就像定义结构体一样。
例如: typedef int* (*PFUNC)(int a, const char* str );
定义了一个函数指针类型,它可以用来表示一种函数的签名,两个参数,分别是int和const char*类型,返回的值是int*, 注意哦,返回的是int*,那么也说它是一个指针函数了, 不要想那么复杂,下面来看如何用这个类型来定义一个函数指针,例如:
PFUNC pf;
如果你的代码中定义了两个个如下的函数
int* testABC(int a, const char* str){...};
int* testDEF(int a, const char* str){...};
那么可以将testABC和testDEF赋值给pf,如下
pf = testABC; //pf=&testABC 也可以,C语言规定函数名会被转换为指向这个函数的指针
(*pf)(100, "abc");
pf=testDEF;
(*pf)(200, "def");