第二组:
charp[],p[][],p[][][];
分别为一维,二维和三维char型数组,即数组,数组的数组,<数组的数组>的数组。
可以如下的方式进行初始化:
charpp[3]="ab";
charpp1[3][3]={"ab"};
charpp2[3][3][3]={{"ab"}};
现在我们尝试使用第二组三个数组名对应为第一组三个指针赋值,直接赋值的结果如下:
p=pp;//正确
p1=pp1;//错误
p2=pp2;//错误
为什么p1和p2的赋值会出错呢?
原因是数组名为给指针赋值的规则不是递归的,即数组的数组可以为数组的指针赋值,而不可以为指针的指针赋值。
这里先了解到这个抽象的规则,下面讲完第三组表达式,等我们知道数组的指针和指针的数组如何书写后再对这一问题举例说明。
第三组:
char*p[],*p[][],**p[],**p[][],*(*p)[],(**p)[],(**p)[][];
这一类表达式的解析方法如下:
首先把整个表达式分为三部分,
数据类型和星号部分+p或括号内内容部分+中括号部分
如:
char*(*p)[]分为?
char*,(*p)和[]
?
?
“char*”表示最内层存储的数据类型“(*p)”表示最外层指针“[]”表示中间层数组(维数=中括号数目),因此上式表示一个一维数组的指针p,数组中的元素的数据类型是指针char*。
同理,char(**p)[][]表示,一个二维数组的指针的指针,数组元素的数据类型是char。
这里如果表达式中间没有括号(如**p[]),则实际上是一个数组,如果最右没有中括号(如**p),则实际上是一个指针。
下面通过赋值表达式来理解这些表达式的含义:
charc='a';
char*pc=&c;
char*p[3],*p1[3][3],**p2[3],**p3[3][3],*(*p4)[3],(**p5)[3],(**p6)[3][3],(*(*p7))[3];
p[1]=pc;
p1[0][0]=pc;
p2[0]=&pc;
p3[0][0]=&pc;
(*p4)[0]=pc;
(**p5)[0]=c;
(**p6)[0][0]=c;
(**p7)[0]=c;
注意,(*(*p7))[3]和(**p5)[3]是等价的。
这里再继续上一小节讲一下数组名给指针赋值的问题。
事实上可以对等赋值的数组和指针关系如下(——>表示“赋值给”):
数组——>指针:
p[]——>*p
指针的数组——>指针的指针:
*p[]——>**p
指针的指针的数组的——>指针的指针的指针:
**p[]——>***p
。
。
。
。
。
。
或
数组的数组——>数组的指针:
p[][]——>(*p)[]
数组的数组的数组的——>数组的数组的指针:
p[][][]——>(*p)[][]
总之,最外层的数组可以转换指针,往内层不递归。
(3)关于上述表达式的长度
求一个表达式的“长度”,首先分清表达式实际表示的是一个数组还是一个指针;如果是指针,则长度为4byte;如果为数组则要计算实际存储的总元素个数和元素的数据类型。
另外要注意要求的是数组元素个数还是数组总字节数;
如:
*(*p)[3][3]
由上文可知上式表示一个指针,因此长度为4byte;而
**p3[3][3]
表示一个二维数组,数组元素类型为指针的指针,因此长度为3*3*4=36;
注意,标准C中sizeof函数求得的是总字节数而非数组长度。
(4)关于函数的指针返回值和指针参数
指针作为返回值要注意的地方是不要返回局部数据的指针。
如:
char*fun(void)
{
chari='a';
return(&i);
}
调用函数fun得不到值'a',原因是函数返回后,局部数据(在栈中)被析构,数据内存被回收,指针指向的数据没有意义;
可以改为:
char*fun(void)
{
chari='a';
char*p=(char*)malloc(5);
If(p!
=NULL){p[0]=i,p[1]='\0';}
return(p);
}
这里使用malloc分配了内存(在堆中)在函数返回后依然有效。
这里还没完,因为有一天我使用了下面的代码:
char*fun(void)
{
char*p="abc";
return(p);
}
发现虽然p定义为局部变量,但返回也是正确的。
还记得上面讲到的常量字符串存储位置吗?
指针p实际指向了静态数据区,这里的char*p相当于constchar*p,这样的数据在函数返回后是不会被立刻回收掉的。
指针作为参数主要用于修改指针指向的数据内容,但修改指针的值无效,
如
char*fun(char*p)
{
chari='a';
p=(char*)malloc(5);
p[0]=i;
returnp;
}
因为传递的是一个指针副本(形参指针和实参指针的值相同,但不是同一个指针),不会影响调用方的实参值。
(诡异的vs2012貌似可以通过形参将实参的值改掉!
不过还是不要冒这个险为好了)。
(5)关于const修饰符
const修饰符用于指针时也非常纠结。
首先要分清constchar*p和char*constp。
constchar*p是指向const对象的指针,即对象是只读的,而指针不是。
使用const对象的指针要注意两点:
一是不能将其赋值给非const对象的指针,
如:
constchar*p;
char*p1=p;//不允许的
当然,直接使用非const指针指向const对象也是不合法的,
如:
constcharc;
char*p1=&c;//不允许的,
这是要避免通过上述p1改变const对象的值。
二是可以将非const对象的地址赋值给指向const对象的指针,但是试图使用这个指针改变变量的值是非法的,
如:
charc='a';
constchar*p=&c;//允许的
*p='b';//不允许的
char*constp是const指针,即指向对象可编辑,但指针本身不可修改,这一点类似于数组。
如:
charc='a';
char*constp=&c;
*p='b';//允许的
p++;//不允许的
区分两者的方法是,看const是否靠近指针名,如果是则为const指针,否则为const对象。
这个助记方法的前提是char要和*号靠在一起,因为constchar*p=charconst*p。
另外,还有constchar*constp,自然是指向const对象的const指针了。
(6)关于指针函数
首先注意指针函数和函数指针的区别,前者是指“返回指针的函数”,这在上文中有提到,而后者是指“指向函数的指针”。
函数指针的定义方法为,将“函数名”替换为“(*函数指针名)”,
如:
指向一个声明为voidfun(inta)的函数指针可以定义为void(*pFun)(inta)或void(*pFun)(int),注意这里函数指针pFun只能指向和fun有相同返回类型(void)和参数类型(int)的一类函数,另外定义中()也不是摆设,去掉()会被看做是返回值为void*类型的函数声明。
举个例子:
voidfun(inta)
{
cout<}
intmain()
{
void(*pFun)(int);
pFun=&fun;//?
(1)
*(pFun)
(1);//
(2)
}
事实上,上式中的?
(1)
(2)行做如下几种替换也是正确的:
a、pFun=fun;
pFun
(1);
b、pFun=&fun;
pFun
(1);
c、pFun=fun;
*(pFun)
(1);
如果有什么疑问的话,可以接着尝试用如下方式直接调用函数fun:
(*fun)
(1);
运行的结果也是正确的!
这要怎么解释呢?
其实,fun不仅仅作为函数名,它同pFun一样也是一个函数指针,只不过是函数指针常量。
为了书写方便,c语言开发者允许将函数指针调用直接写成类似fun()的形式,同样函数指针变量赋值也可以写成类似pFun=&fun的形式。
值得注意的是,函数声明的格式还是比较严格的,如:
voidfun(int);//不能写成void(*fun)(int)。
同样,
void(*pFun)(int);//不能写成voidpFun(int)。
为了方便,我们还可以定义函数指针类型,针对上述例子的定义方法如下:
typedefvoid(*PFUN)(int);
这样一来我们就可以用
PFUNpFun;
来声明一个函数指针了。
有了函数指针之后,函数的参数也可以设为某一类函数类型。
如:
typedefvoid(*PFUN)(int);
voidfun(inta)
{
cout<}
voidtopfun(PFUNf1,inta)
{
f1(a);
}
intmain()
{
topfun(fun,1);
return1;
}