C语言指针的用法
学习 C 语言的指针既简单又有趣。通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配,没有指针是无法执行的。所以,想要成为一名优秀的 C 程序员,学习指针是很有必要的。下面是相关的知识,欢迎阅读。
(1)关于指针与数组的存储
a、指针和数组在内存中的存储形式
数组p[N]创建时,对应着内存中一个数组空间的分配,其地址和容量在数组生命周期内一般不可改变。数组名p本身是一个常量,即分配数组空间的地址值,这个值在编译时会替换成一个常数,在运行时没有任何内存空间来存储这个值,它和数组长度一起存在于代码中(应该是符号表中),在链接时已经制定好了;而指针*p创建时,对应内存中这个指针变量的空间分配,至于这个空间内填什么值即这个指针变量的值是多少,要看它在程序中被如何初始化,这也决定了指针指向哪一块内存地址。
b、指针和数组的赋值与初始化
根据上文,一般情况下,数组的地址不能修改,内容可以修改;而指针的内容可以修改,指针指向的内容也可以修改,但这之前要为指针初始化。
如:
int p[5];
p=p+1; 是不允许的
而p[0]=1; 是可以的;
//
int *p;
p=p+1; 是允许的
p[0]=1; 是不允许的,因为指针没有初始化;
//
int i;
int *p=&i;
p[0]=1; 是允许的;
对于字符指针还有比较特殊的情况。
如:
char * p="abc";
p[0]='d'; 是不允许的
为什么初始化了的字符指针不能改变其指向的内容呢?这是因为p指向的是“常量”字符串,字符串"abc"实际是存储在程序的静态存储区的,因此内容不能改变。这里常量字符串的地址确定在先,将指针指向其在后。
而
char p[]="abc";
p[0]='d'; 是允许的
这是因为,这个初始化实际上是把常量直接赋值给数组,即写到为数组分配的内存空间。这里数组内存分配在先,赋值在后。
(2)关于一些表达式的含义
char *p, **p, ***p;
char p[],p[][],p[][][];
char *p[],*p[][],**p[],**p[][],*(*p)[],(**p)[],(**p)[][];
能清晰地知道以上表达式的含义吗?(知道的去死!)
第一组:char *p, **p, ***p;
分别为char指针;char*指针,即指向char*类型数据地址的指针;char**指针,即指向char**类型数据的指针;他们都是占4字节空间的指针。
如:
char c='a';
char *p=&c;
char **p1=&p;
char ***p2=&p1;
cout<<***p2<<endl;< p="">
第二组:char p[],p[][],p[][][];
分别为一维,二维和三维char型数组,即数组,数组的数组,<数组的数组>的数组。可以如下的方式进行初始化:
char pp[3]="ab";
char pp1[3][3]={"ab"};
char pp2[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),则实际上是一个指针。下面通过赋值表达式来理解这些表达式的含义:
char c='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)
{
char i='a';
return (&i);
}
调用函数fun得不到值'a',原因是函数返回后,局部数据(在栈中)被析构,数据内存被回收,指针指向的数据没有意义;
可以改为:
char * fun(void)
{
char i='a';
char *p=(char *)malloc(5);
If(p!=NULL) {p[0]=i, p[1]='