什么是指针
指针是C语言是一种特殊的数据类型,可以定义指针变量,里面存储的是整数,代表的是内存编号。
每个整数对应一个字节(8个二进制位),通过这种编号可以访问对应的内存,具体访问多少个字节由指针的类型决定。
32位系统一个指针变量大小4字节
64位系统一个指针变量大小8字节
为什么使用指针
从理论上讲指针可以访问任何任何位置的内存,但绝大多数的内存我们没有访问权限,因此非常容易产生段错误,建议只在合适的时候才使用指针。
1、函数之间协调工作时需要共享局部变量
2、使用指针可以提高传参效率,函数之间需要传递较多字节数的变量(如结构体)时,传递变量的地址效率更高。
3、堆内存无法取名字(无法与标识符建立映射关系),必须与指针变量配合。
使用指针需要注意的问题
1、空指针:指针值为NULL的指针叫空指针,不能运行解引用,一旦解引用空指针就会产生段错误。
NULL在大多数系统的值为0,该地址储存操作系统重启的数据。
NULL也被当作错误标志,如果函数的返回值是指针类型,当它的值是NULL时说明执行出现错误。
如何避免空指针产生的段错误:对来历不明的指针进行解引用前要先判断是否为空
2、野指针:指针变量的值是不确定的,随机的,未知的,这种指针被称为野指针。
对野指针进行解引用的后果:一切正常 (运气好)、段错误 (大概率)、脏数据 (堆内存申请的越多,脏数据可能性越大)。
如何避免野指针产生的错误:
野指针是无法被判断出来的,使用野指针不一定立即出错,所以危险性比空指针更大。
所有的野指针都是人为制造出来的,只有不制造野指针,才能避免使用野指针。
可能产生野指针的情况:
a、定义指针变量时没有初始化。
b、函数返回了局部变量的地址。
c、资源已经被释放,但是指针变量还在继续使用而没有及时置空。
指针运算
指针变量存储的是代表内存编号的整数,使用%p占位符可以以十六进制显示指针变量的值。
理论上凡是整型数据可以使用的运算符,指针变量都可以使用,但是只有以下运算是有意义的:
指针+整数n 相当于指针变量向后移动n个元素的宽度,移动了sizeof(类型)*n个字节
指针-整数n 相当于指针变量向前 移动n个元素的宽度,移动了sizeof(类型)*n个字节
指针-指针 两个指针之间相隔多少个元素,结果是(指针-指针)/sizeof(类型)
*万能指针(void )
1、不能解引用,必须先转换成其他类型指针才可以解引用访问内存。
2、运算是以1字节为单位。
3、它可以和任意类型的指针进行自动类型转换(C语言)。一般用作函数的参数和返回值。
当作函数的返回值和参数不确定是什么类型的指针,可以使用它。
指针与数组名
相同点:
1、数组名其实是一个特殊类型的常指针(数组空间的首地址),不能修改它的值。可以使用解引用的方式访问数组的元素。
反过来指针也可以使用[]进行解引用。
int arr[5] = {1,2,3,4,5};
for(int i=0; i<5; i++)
{
printf("%d", *(arr+i)); //打印的是数组arr中第一到第五个元素,arr[i] <=> *(arr+i)
}
不同点:
1、数组名是常量,指针是变量
2、指针有自己的存储空间,里面存的是内存地址。
数组名没有自己的存储空间(它自己就是地址)。
3、指针与目标内存是指向关系
数组名与目标内存是映射关系
4、对数组名取地址结果还是数组名的值
指针数组与数组指针
1、指针数组:
首先是个数组,由指针变量组成的数组。
可以配合堆内存,模拟不规则的二维数组,可以根据需要节约内存
int* arr[n]; //类似普通数组
2、数组指针:
首先是个指针,专门用于储存数组的地址。
可以配合堆内存,模拟规则的二维数组,使用方便。
注意:数组指针,一旦做加1,加的是整个数组的宽度。
int (*p)[n]; //指针p指向一个长度为10,存储int类型的指针
指针与const
const在*之前是保护目标
const在*之后是保护指针
const int* p; // 保护目标,不能通过指针修改目标
int const* p; // 同上
int* const p; // 保护指针变量不被修改
const int* const p; // 既保护指针变量也保护目标
int const* const p; // 同上
函数指针
c代码会被编译器翻译成二进制指令存储在代码段中。
一个函数就是一块代码段中的数据,函数名就是这块数据的首地址。
可以说函数名就是个特殊的地址,是个常量指针。
可以定义指针变量存储函数的地址,专门指向函数的指针变量叫做函数指针。
使用函数指针,可以把函数当作一段数据,在函数之间传递,从而实现回调模式
//返回值类型 (*函数名_fp)(形参列表);
//函数指针
typedef void (*FP)(int, int );
二级指针
一级指针(普通指针),用于存储普通变量的地址。
二级指针用于存储指针变量的地址。
跨函数共享一级指针时可以使用。