什么是指针:
1、指针是一种数据类型,使用它可以定义指针变量,简短指针。
2、指针变量中存储的是代表便内存地址的整数,每个整数只对应一个字节。
3、当通过指针去访问内存时,具体访问多个字节由它的类型决定,也就说指针变量中存储的是一块内存的首地址。
为什么使用指针:
1、跨函数共享变量
全局变量在函数之间共享的问题有:浪费内存、命名冲突、有被破坏的风险,尽量少用。
由于函数只能返回一个数据,而使用指针可以从被调用返回多个数据。
2、提高函数的传参效率
由于C语言的函数传参是单向值传递,就是实参给形参赋值(把实参的内存拷贝给形参),所以在传参时参数的字节数越多耗时越长,如果只传递变量的地址,那么只需要拷贝4/8字节,字节数越多的变量越值得使用指针传递。
注意:这样做作的缺点是变量有被函数破坏的风险,可以使用const加以保护。
3、配合堆内存使用
当我们定义一个变量时,系统就会为变量分配一块内存,它变量名建议联系,之后再使用变量名时,就相当于访问对应的内存。
使用向系统申请堆内存时,系统只会返回内存块的首地址,无法用一个变量名与内存建立联系(堆内存无法取名字),所以使用堆内存必须与指针配合。
如何使用指针:
定义指针变量:
类型* 指针变量名p;
1、指针变量与普通变量的用法不同,一般指针变量以p结尾,加以区分。
2、指针变量不能连续定义,一个*只能定义出一个指针变量。
int* p1,p2; // p1是指针变量,p2是int类型变量
int *p1,*p2; // p1、p2都是指针变量
typedef int* intp; // 给int*取一个类型名
intp p1,p2; // p1、p2都是指针变量
3、指针变量的默认值与普通变量一样是随机的,为了安全一般要初始化,如果不知道该赋值什么,建议初始化为NULL,也就是空指针,如果不初始化这就是野指针。
4、指针的类型决定的指针解引用时访问内存的字节数,还决定了指针变量的进步值。
给指针变量赋值:
可以定义指针变量时初始化,也可以指针变量定义完成后再赋值。
// 获取变量的地址赋值给指针变量
int* p = #
// 向系统申请内存,把获取到的内存块首地址赋值给指针变量
int* p = malloc;
解引用:*指针变量;
int *p = #
*p <=> num; // *p就等价于num
根据指针变量中存储的内存地址去访问对应的内存块,具体访问多少个字节,由指针变量的类型决定。
如果之前赋值的是非法的内存地址,此时会出现段错误。
产生段错误的原因:
1、对非法的内存地址解引用,指针变量中存储的内存地址不在maps文件范围内。
2、指针变量存储的是text内存段的地址,并通过解引用去修改内存的内容,也会产生段错误。
使用指针要注意的问题:
空指针:指针变量的值是NULL,操作系统规则该地址不能解引用,如果函数的返回值是指针类型,当返回NULL是表示函数执行出错,所以NULL也是一种错误标志。
如何避免空指针产生的段错误:
准则:对来历不明的指针解引用前要进行判断。
1、函数的参数
2、函数的返回值
3、全局的指针变量
判断空指针的方法:
if(!p) // 错误写法
{
1、不是所有系统的NULL都是0
2、可能让人误指针变量是布尔类型的变量
}
野指针:指向的内存区域不明,不知道是不是合法的内存地址,这种指针被称为野指针。
对野指针解引用的后果:
1、一切正常
2、脏数据
3、段错误
注意:指针产生的错误具有随机性、潜伏性、隐藏性,出现的错误很难重现,因此它比空指针的危害更大,并且无法判断是否是野指针。
如何避免野指针产生的危害:
准则:所有的野指针都是程序员制造出来的,不制造野指针就能避免使用野指针。
1、定义指针变量时要初始化,即使初始化为空指针也比野指针要好。
2、函数不反回局部变量的地址,因为随着函数的执行结束,属于局部变量的内存就会被释放,当执行其它函数时这块内存会被再次分配出去,因此返回局部变量的地址容易造成脏数据。
3、当堆内存被释放后,配合堆内存使用的指针要即时赋值为空。
指针的运算:
指针变量中存储的是内存编号,其它本质就是整数,理论上整型数据能使用运算符指针变量都可以使用,但只有以下运算才有意义:
指针+整数 指针代码内存编号的整数+整数*进步值
指针-整数 指针代码内存编号的整数-整数*进步值
指针加减一个整数,就相当于指针变量以进步值为单位前后移动。
指针-指针 (指针代码内存编号的整数-指针代码内存编号的整数)/进步值
指针减指针运算可以计算出两个地址之间相隔多少个元素,并且只有类型相同的指针变量才能相减,否则就会有编译错误。
通用指针:
指的是void类型的指针变量,它能与任意类型的指针变量进行类型转换,也被称为万能指针。
如果一个函数的参数可能是任意类型的指针变量,可以用void类型指针兼容所有类型的实参指针。
void类型的指针进步值是1,它不能直接解引用,必须先转换成其它类型指针再解引用。
指针与数组名:
数组名其实就是个常量地址,类型 数组名[n],那么数组名就是 类型* 的地址。
当我们使用数组作为函数的参数时,函数的形参就是指针变量,所以它的长度信息才丢失。
arr[n] <=> *(arr+n) 这两种语法就是等价的,如果指针变量指向一块连续内存,指针变量可以当数组使用。
指针与数组名的相同点:
1、它们都是地址,代表一块连接的内存。
3、它们都可以使用*,[]进行解引用,遍历一块连续内存。
指针与数组名的不同点:
1、指针是变量而数组名是常量
2、指针变量有自己的存储空间用于存储内存地址,而数组名没有,它就是地址。
3、指针变量与目标内存是指向关系,而数组名与目标内存是映射关系。
指针数组:
就是由指针组成的数组,它的身份是数组,成员是指针。
类型* 数组名[数组长度];
使用它能构造出不规则的二维数组:
int arr1[7] = {7,1,2,3,4,5,6};
int arr2[8] = {8,1,2,3,4,5,6,7};
int arr3[5] = {5,1,2,3,4};
int arr4[9] = {9,1,2,3,4,5,6,7,8};
int arr5[3] = {3,1,2};
int* arr[5] = {arr1,arr2,arr3,arr4,arr5};
for(int i=0; i<5; i++)
{
for(int j=1; j<arr[i][0]; j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
数组指针:
专门指向数组的指针,它的进步值是整个数组的长度。
类型 (*指针变量名) [长度]
使用它可以把一块连接的内存当作二维数组使用:
int arr[20];
int (*p)[5] = (void*)arr;
for(int i=0; i<4; i++)
{
for(int j=0; j<5; j++)
{
printf("%d ",p[i][j]);
}
printf("\n");
}
int arr[7];
int* p <=> arr; // 一维数组的数组名是普通指针
int (*p)[7] <=> &arr;
arr[i] <=> *(arr+i);
int arr1[3][4];
arr1[i][j] <=> *(*(arr1+i)+j)
int (*p)[4] <=> arr1; // 二维数组的数组名是数组指针
int* p <=> *arr1;
指针函数:
返回值是指针类型的函数就叫作指针函数,当返回值是NULL/0xffffffff,表示函数执行出错。
函数指针:
前提:函数就一段代码,这段代码会被翻译成二进制指令存储在代码段中,函数名就是个地址,表示函数的二进制指令在代码段的位置。
什么是函数指针:就是专门指针函数的指针变量,使用函数指针可以把函数像数据一样在函数之间进行传递。
如何定义函数指针:
1、照抄函数声明。
2、用小括号包含函数名
3、在函数名的前面加*,并在函数名末尾添加_fp。
使用函数:
1、用函数名给函数指针赋值。
2、函数指针(实参) 就可以调用函数了。
函数指针的作用大多数情况下用于实现回调模式,先实现的代码调用后实现的函数,例如:
void qsort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *));
该函数是早期标准库程序实现的,它无法知道如何判定元素的大小,所以就留一个函数指针的参数,让我们提供一个自己实现的对数组元素进行比较函数。
练习4、参考qsort函数,实现一个通用的选择排序函数。
void select_sort(void *base, size_t nmemb, size_t size,
int (*compar)(const void *, const void *))
{
char tmp[size];
for(int i=0; i<nmemb-1; i++)
{
int min = i;
for(int j=i+1; j<nmemb; j++)
{
if(compar(base+min*size,base+j*size))
min = j;
}
if(min != i)
{
memcpy(tmp,base+i*size,size);
memcpy(base+i*size,base+min*size,size);
memcpy(base+min*size,tmp,size);
}
}
}
二级指针:
专指向普通指针的指针变量,它里面存储的是指针变量的地址。
普通指针变量有三项用处:
1、跨函数共享变量
2、提高函数的传参效率
3、配合堆内存使用
二级指针只有一个用处,就是跨函数共享指针变量。
类型** 指针变量名pp = &普通指针变量;
int num = 1234;
int* p = #
int** pp = &p;
*pp <=> p;
**pp <=> *p <=> num;