1. 简述
也许从现在开始,才真正进入C语言的世界。指针是C语言中最重要的概念之一,也是最简单的语法同时也是最难学习的语法之一。指针体现了C语言中语法的“简单、高效”特点,指针是C语言的灵魂。
指针是一个值,代表的是内存地址本身。每个内存单元都有一个唯一的地址,而指针就代表这个地址。但是指针不可以直接操作,而是要存储到指针变量中才可以使用。
指针也是一种数据类型,也可以用于声明变量保存指针,这就是指针变量。普通变量存储的是一个具体的数值,而指针变量存储的是内存地址。
2. 声明指针(变量)
指针变量可以声明为任何类型的变量,指针变量的声明由三部分组成:指针类型、指针变量名、变量值(指针)所指向的变量的数据类型,使用*符号来声明,声明形式为:类型说明符 * 变量名:
int *a;
上面示例中声明了一个变量名为a的指针变量,它存储的是int类型变量的地址,也就是说int*是一个指向int类型的值的指针,而a指向的内存地址存储的是int类型的值,
*符号可以在类型说明符和变量名之间的任意地方,都是有效合法的,就看个人规范和习惯;如果要声明多个同类型的指针变量且在同一行,那么*符号只能跟在变量名前:
//下面三种声明都是合法的
int* a;
int * a;
int *a;
//声明同类型的指针变量在同一行
int *a,*b;//正确
int *a,b;//错误,只有a是int*类型,b是整型
当然,指针变量还可以指向一个指针,也就是指针的指针,成为二级指针,也可以声明三级指针、四级指针,不过这里主要以一级指针为主:
int **a;//声明为二级指针
3. 指针变量初始化
指针变量声明后,编译器会为该指针变量分配一块内存空间,并初始化一个随机值。指针变量存储的是内存地址,所以这个随机值就是一个随机地址,如果不进行显式初始化,随便读取和写入这个内存地址是一种很危险的行为。建议将声明的指针变量先初始化为NULL,就不会指向其他地址了。
int *p;//随机地址
*p = 5;//错误
//建议设置为NULL
int *p = NULL;//地址为0,无法写入的,但不会指向随机地址了
指针变量初始化,就是要把一个地址赋值给指针变量,这个地址必须是已经分配好的内存地址,不能随意写入一个值。
int *p;//指向随机地址
int a = 5;//已经分配好内存地址了
p = &a;//&符号表示获取变量a的地址,后面会讲到
4. 取址&和取值*
&是一个地址运算符,在变量名前使用&操作符,可以获取到该变量所在的内存地址。
int a = 1;//声明普通变量
int *p = NULL;//声明普通变量,并初始化为NULL
p = &a;//将地址赋值给指针变量
printf("变量a的地址是 %p\n", &a);//%p占位符打印地址
声明变量时,*符号用于声明一个指针变量;*符号还可以作为指针的取值,从指针指向的内存地址中读取数据(值),这个操作也称为“解引用”。
int a = 5;
int *p = NULL;
p = &a;
int b = *p;//解引用,取出变量a的值
printf("变量a的地址是 %p\n", &a);//%p占位符打印地址
printf("变量b的值是 %d\n", b);//取出指针变量p指向的变量a地址存储的值
&取址运算符和*取值符是互逆运算,先取地址再解引用,和原值的值是一样的
int a = 5;
a == *(&a);//相等
5. 指针运算
指针是一个内存地址,内存地址是从0开始的,所以它是一个无符号整型数值,是可以参与运算的。虽然同样是数值,但运算规则和普通的数值不同,通常用于与整数的运算。
- 指针与整数的运算,指针与整数相加或者相减,表示偏移n个长度的指针指向
- 指针与指针的减法运算,返回两个指针之间相隔多少个数据单位(根据类型大小不同),高地址减低地址返回正数,低地址减高地址返回负数
- 指针与指针的比较运算,比较两个指针哪个更大一些,返回true或者false
- 指针与指针不能相加运算,也不能做乘除运算,否则会报错
//指针与整数相加减
int *p;
int a = 5;
p = &a;
printf("指针变量p的值是 %p\n", p);
p += 1;
printf("指针变量p的值是 %p\n", p);//p偏移了1个单位,表示指向了下一个地址
//指针与指针相减
int *p1;
int *p2;
int a = 5;
int b = 5;
p1 = &a;
p2 = &b;
printf("p1的值是 %p\n", p1);
printf("p2的值是 %p\n", p2);
printf("%td\n", p2-p1);
//指针与指针比较
if(p1 < p2){
printf("p1小于p2");
}
上面的示例中,指针与整数计算,根据与整数的计算值来确定偏移的字节大小,比如指针变量加1,并不是在地址加1,而是偏移一个int*指针类型所占用的字节大小,也就是偏移4个字节。如果设定整数为m,以m为单位,指针变量指向的数据类型占用n个字节大小,所以偏移的字节大小就为m * n。
指针与指针的减法运算,返回的不是字节大小,而是一个以数据类型为单位的长度,比如int*类型,相隔4个字节,那就是返回1。
7. 野指针
野指针是指指针指向了一个不确定的地址,且地址值是随机的,尽量避免野指针的使用。
野指针的常见情形有下面五种:
- 声明指针变量之后没有对它进行初始化,也没有赋值为NULL;
- 指针所指向的变量已经被销毁,仍使用该指针访问该变量,出现运行错误;
- 指针被free()或者delete()之后,没有赋值为NULL,此时指针指向的就是“垃圾”内存;
- 指针指向的数组超出范围导致越界访问
- 指针运算规则使用错误。
8. 总结
- 指针是一个内存地址,应放在指针变量中使用;
- 指针变量声明后必须要初始化后才能使用,或者指定为NULL,否则就是野指针;
- 指针变量初始化必须是编译器分配好的地址,不能随意指定一个值;
- 指针使用&符号获取变量的地址,使用*符号获取指针变量所指向地址的值,&与*是互逆运算;
- 指针可以与整数相加减、与指针相减、与指针比较运算,但不能进行相加;
- 应当避免野指针的使用。