1.指针
上一篇我在分析我自己写的程序错误里面发现是指针的问题,我觉得有必要在复习一下指针,毕竟指针是C语言系列的难点。
指针是C语言的精髓,但是很多初学者往往对于指针的概念并不深刻,以至于学完之后随着时间的推移对指针的概念和使用越加模糊,感觉指针难以掌握,今天我就再回顾下指针的概念和使用。
2.什么是指针
指针的本质是存放变量地址的变量,简单的说变量p中存储的是变量a的地址,那么p就可以称为是变量a的指针,或者说p指向a。当我们访问a变量的时候其实是程序先根据a取得a对应的地址,再到这个地址对应的存储空间中拿到a的值,这种方式我们称之为“直接引用”;而当我们通过p取得a的时候首先要先根据p转换成p对应的存储地址,再根据这个地址到其对应的存储空间中拿到存储内容,它的内容其实就是a的地址,然后根据这个地址到对应的存储空间中取得对应的内容,这个内容就是a的值,这种通过p找到a对应地址再取值的方式成为“间接引用”。
2.1示例代码如下:
int a =10;//a的地址是 ffeedd01 int *p ; *p=a;//此时p的值就为a的地址 ffeedd01
2.2指针的赋值
#include <stdio.h> int main(int argc, const char * argv[]) { int a=10; int *p; p=&a; //也可以直接给指针变量赋值:int *p=&a; printf("a=%d,p=%d\n",a,*p); //结果:a=10,p=10 *p=20; printf("a=%d,*p=%d\n",a,*p); //结果:a=20,p=20 int b=8; char c= 1; int *q=&c; printf("c=%d,q=%d\n", c, *q); //结果:c=1,q=2049,为什么q的值不是1呢? return 0; }
注意:
*int *p;中的*只是表示p变量是一个指针变量;而打印*p的时候,*p中的*是操作符,表示p指针指向的变量的存储空间(当前存储就是10),同时我们也看到了*p==a;修改了*p也就是修改了p指向的存储空间的内容,也就修改了a,所以第二次打印a=20;
*指针所指向的类型必须和定义指针时声明的类型相同;上面指针q定义成了int型而指向了char型,结果输出*q打印出了2049,(假设在16位编译器下,指针长度为2字节)
int b=8; //地址ffeedd0c //地址ffeedd0d char c= 1; //地址ffeedd0b int *q=&c; //ffeedd0b
由于局部变量是存储在stack堆栈里面的,所以先存储b再存储c、q,当打印*q的时候,其实就是以q指向的地址对应的空间开始取两个字节的数据(因为定义q的时候它指向的是int型,在16位编译器下int类型的长度为2个字节),刚好定义的b和c空间连续,所以就取到b的其中一个字节,最后*p二进制存储为“0000100000000001”,十进制表示就是2049;
*指针变量占用的空间和它所指向的变量类型无关。
3.数组和指针
数组的存储是连续的,数组名也就是数组的起始地址,这样一来数组和指针就有了关系:
#include <stdio.h> void changeValue(int a[]){ a[0]=2; } void changeValue2(int *p){ p[0]=3; } int main(int argc, const char * argv[]) { int a[]={1,2,3}; int *p=&a[0]; //等价于:*p=a;//p指向数组的起始地址//p指向a[0],p+1指向a[1],以此类推,所以我们通过指针也可以取出数组元素 for(int i=0;i<3;++i){ //printf("a[%d]=%d\n",i,a[i]); printf("a[%d]=%d\n",i,*(p+i)); } /*输出结果: a[0]=1 a[1]=2 a[2]=3 */ changeValue(p); //等价于:changeValue(a) for(int i=0;i<3;++i){ printf("a[%d]=%d\n",i,a[i]); } /*输出结果: a[0]=2 a[1]=2 a[2]=3 */ changeValue2(a); //等价于:changeValue2(p) for(int i=0;i<3;++i){ printf("a[%d]=%d\n",i,a[i]); } /*输出结果: a[0]=3 a[1]=2 a[2]=3 */ return 0; }
从上面的例子我们可以得出如下结论:
*数组名a等于&a[0]等于p;
*如果p指向一个数组,那么p+1指向数组的下一个元素,同时注意p+1移动的长度并不固定,具体需要根据p指向的数据类型而定;
*不管函数的形参为数组还是指针,实参都可以使用数组名或指针;
4.字符串和指针
在C语言中字符串就是字符数组,则字符串和数组的关系如下:
#include <stdio.h> int main(int argc, const char * argv[]) { char a[]="itheima"; printf("%s\n",a);//结果:itheimachar b[]="itheima"; char *p=b; printf("b=%s,p=%s\n",b,p);//结果:b=itheima,p=itheima //指针存储的是地址,而数组名存储的也是地址,既然字符数组可以表示字符串,那么指向字符的指针同样也可以,如下方式可以更简单的定义一个字符串 char *c="itheima"; //等价于char c[]="itheima"; printf("c=%s\n",c); //结果:c=itheima return 0; }
5.函数指针
我们先来研究一下返回指针类型数据的函数,指针类型也是C语言的数据类型,下面以一个字符串转换为大写字符的程序为例:
#include <stdio.h> char * toUpper(char *a){ char *b=a; //保留最初地址,因为后面的循环会改变字符串最初地址 int len=‘a‘-‘A‘; //大小写ASCII码差值相等 while (*a!=‘\0‘) { //字符是否结束 if(*a>‘a‘&&*a<‘z‘){//如果是小写字符 *(a++) -= len; //*a表示数组对应的字符(-32变为小写),a++代表移动到下一个字符 } } return b; } int main(int argc, const char * argv[]) { char a[]="itheima"; char *p=toUpper(a); printf("%s\n",p); //结果:ITHEIMA return 0; }
注意:大家都是知道函数只能有一个返回值,如果需要返回多个值,那么我们怎么办呢,其实只需要将指针作为函数参数传递就可以了:
#include <stdio.h> int operate(int a,int b,int *c){ *c=a-b; return a+b; } int main(int argc, const char * argv[]) { int a=1,b=2,c,d; d=operate(a, b, &c); printf("a+b=%d,a-b=%d\n",d,c);//结果:a+b=3,a-b=-1 return 0; }
难点:函数也是需要在内存中存储的,函数也有一个起始地址,函数名就是函数的起始地址。函数指针定义的形式:返回值类型 (*指针变量名)(形参1,形参2),函数指针其实等同于这个函数,函数的操作都可以通过指针来完成。既然指针作为C语言的数据类型,可以作为参数、作为返回值,那么当然函数指针同样可以作为函数的参数和返回值:
#include <stdio.h> int sum(int a,int b){ return a+b; } int sub(int a,int b){ return a-b; } //函数指针作为参数进行传递 int operate(int a,int b,int (*p)(int,int)){ return p(a,b); } int main(int argc, const char * argv[]) { int a=1,b=2; int (*p)(int ,int)=sum;//函数名就是函数首地址 int c=p(a,b); printf("a+b=%d\n",c); //结果:a+b=3 //函数作为参数传递 printf("%d\n",operate(a, b, sum)); //结果:3 printf("%d\n",operate(a, b, sub)); //结果:-1 return 0; }
虽然我知道这个函数指针这个概念,但是我现在还没遇到需要使用函数指针的场合。但是函数指针可以作为函数参数进行传递,实在太强大了。