//个人感觉c语言最难的一部分
指针定义 定义指针变量是一种特殊的变量,存储的是地址信息,定义方式 数据类型 *指针名。因为指针是一个变量,所以指针名就是地址的别名,指针名可以直接当地址使用,这个地址就是指针变量存储的地址。
注意:不同配置电脑,不同操作系统下,指针所占空间的大小有区别,可能4个字节,可能8个字节以实测为准。
类型指针要分不同的类型,作用就在于规定该指针所指向区域的大小,不同类型的指针,虽然地址值相同,但是如果类型不同,指针偏移时,偏移的空间大小也不同。比如int *p;char *p;int (*p)[3],三个指针+1后偏移的大小分别是4字节,1字节和12字节。
指针类型只和偏移量有关,但是指向同一个地址的不同类型指针的值是一样的,赋值的时候用哪个都可以。赋值成功之后,指针偏移取决于此时指针的类型。
运算符优先级https://blog.csdn.net/changexhao/article/details/82556761
不同的运算符优先级顺序,导致指针的类型各不相同。
比如
| int *a | 指针,指向整型数 |
| int **a | 二级指针,指向一个指针,被指向的指针指向整型数 |
| int (*a)[10] | 指针,指向含有10个int类型数据的数组 |
| int (*a)(int ) | 指针,指向一个函数,返回值为int型,含有一个int型参数 |
| int(**a)[10] | 二级指针,指向一个指针,被指向的指针指向含有10个整型数的数组 |
| int*(*a)[10] | 指针,指向含有10个整型指针的数组 |
| int(*(*a)(int,int))(int) | 指针,指向一个函数,该函数返回值类型是个函数指针,且含有2个int型参数,返回值指针指向另外一个函数,这个函数有一个int参数,返回值类型为int型。 |
| int a[10] | 数组,含有10个整型数 |
| int *a[10] | 数组,含有10个整型指针 |
| int (*a[10])(int ) | 数组,含有10个函数指针,函数返回值为int型,且含有一个int型参数 |
注:用了指针后很容易出现Segmentation fault 段错误,一般是访问野指针或者空指针引起的,若出现此类错误,我们需要检查访问指针的相关代码是否访问了非法内存。
指针作用有了指针之后,变量的访问方式便多了一种。一种方式是通过变量名直接访问所存储的变量值,第二种是方式是间接访问,指针变量p存储所访问变量的地址,通过*p运算取内容,访问到变量值。注意:*运算符在定义时表示指针,除此之外*运算符表示取地址的里面内容。
所以有了指针,在函数传参时,传入指针就是传入地址,通过*指针便可以操作该地址内的数据,操作之后,就算不返回数据,函数结束后,那片地址的数据还是发生了变化。比如两数交换,可以直接在主函数中,通过变量赋值的方法,交换数据,也可以在自定义函数中,传入两变量的地址,通过*指针,直接修改变量的值,交换数据,函数结束后,虽然形参指针的空间被释放了,但是两变量的值,还是改变了。
指针偏移首先指针指向数组时,指针可以当成数组来用,访问数据时,可以用指针偏移或者下标法来访问数据,指针偏移访问数据比下标法效率高得多,下标法更形象,各有好处。其次数组名虽然是个地址,但是是个常量表达式,不能自增或者自减改变自身的值,只有变量才能改变自身的值。
一维数组和二维数组的偏移指针偏移时,偏移的是指针自身类型的空间大小,所以:
数组名+1偏移的大小,取决于数组名的类型,这个数组名指向区域占多大,则偏移多大。一维数组int a[4],a=&a[0],所以a的类型是int,a+1偏移一个int类型的大小。二维数组int a[2][5],a=&a[0],因为a[0]是一个含5个int类型的一维数组,所以a的类型是int *[5],a+1偏移5个int类型大小。
数组名取地址+1偏移的大小,取决于取地址后的指针类型。二维数组int a[2][5],&a,类型是int*[2][5],&a+1偏移10个int类型。&a[0],类型是int *[5],&a[0]+1偏移5个int类型。&a[0][0], 类型是int *,&a[0][0]+1偏移1个int类型。一维数组int a[4], &a,类型是int*[4].&a+1偏移4个int类型,&a[0],类型是int*,&a[0]+1偏移一个int类型。
注意:数组名和指针不互用的情况:eg:int a[5],int*p=a,sizeof(a)和sizeof(p)值不同和数组名不可以自加,指针名可以自加。
二维数组的下标法与指针偏移的转换对于二维数组int a [3][4],a[i]=*(a+i),a[i][j]=*(a[i]+j)=*(*(a+i)+j)。
指针分类 数组指针二维数组传参是后,形参一般是一维数组指针,因为二维数组名类型是一维数组指针,,一维数组指针+-就会得到二维数组行地址的数据,一维数组指针取内容就会得到二维数组列地址的数据,再取一次内容就会进入到数据进行修改。
对于指针数组的指针,数组存的是指针,而数组名代表首元素地址,那么数组名的类型就是一个二级指针类型,传参的时候,形参用二维指针来承接,仍然可以修改指针数组里存储的指针值。
函数指针函数指针类似java中的多态。函数指针,能指向同类型的函数(返回值相同,参数类型和个数相同)。主函数运行时,能根据不同的情况,让函数指针指向不同的函数,运行结果各不相同。有了函数指针,函数调用就有了两种方式,第一种函数名直接调用,第二种函数指针赋值为函数名或者&函数名。调用时,需要先对指针取内容进入到函数,再传参调用。
为什么为函数指针赋值可以赋值函数名或者&函数名呢?
函数和数组都分配有一段存储空间,以地址的角度,函数名数组名都是一个空间首地址,函数指针获取了地址后,取内容才能跳转到函数,运行函数。
对于函数int a(),函数名取地址再用,此时函数名代表的是整个函数这个对象a,取地址得到函数这个对象的首地址&a,类型是int(*)(),值等于函数入口空间的地址。函数名直接用,代表函数这个对象,也代表的是函数入口空间的地址,类型是int (),这个类型本质上是个*,值等于函数这个对象的首地址。函数名取内容再用,此时函数名代表函数这个对象的首地址,取内容就得到函数这个对象,类型是int(),等价于函数名。
二级指针二级指针管理着一块地址,这块地址里面放着一些元素的地址,二级指针可以通过取内容,让这块地址里面放其他元素的地址。
二级指针就是指向指针的指针,使用二级指针的名字就是使用一级指针的地址,经常通过*指针得到一级指针,或者改值为其他的一级指针。再*一次指针就可以得到一级指针的内容。
当函数的返回值为一级指针(一个元素的地址)时,我们可以在主函数中创建一个一级指针来承接返回值。
比如:当函数没有返回值,但是函数里面malloc了一个数组的首地址int *m,如何将这块地址存起来,通过指针的特性,在主函数中也定义一个一级指针int *p和二级指针int **pp,pp=&p,传参传&p或者pp(类型是个二级指针),将m赋值给*二级指针(就是p),最后m和p都指向了那块数组,在主函数中也可以通过p或者*pp访问到这块数组了。这样二级指针pp的值没有改变,即他管理的地址还是p的地址没变,但是这块地址存储的一级指针(不可访问的元素的地址)变成了m指向的地址(可访问元素的地址)。
注意:二级指针不能简单的指向二维数组,int scores[3][4]; int **p=scores; *p与*scores的值不一样,*p是野指针会段错误。需要int *p2=(int *)scores; int **p3=&p2。这样**p3与**scores值相同。
指针函数和指针数组指针函数时返回值为指针(各种指针都可以)的函数,传入参数,返回给我们需要的指针。
指针数组是一个存储指针(各种指针)的数组。存储的指针可以是函数指针,或者数组指针,或者普通指针等等。
注意:数组拷贝的问题,在函数里面直接开的数组不能被主函数使用,因为函数运行完毕,数组也会消失。要么把数组传进来依次赋值,要么直接在函数里面malloc空间,函数传参二级指针,在主函数里面就可以引用这片数组了。
#include#include #include void copy1(int **cp1){ int *p=(int *)malloc(5*sizeof(int)); for(int i=0;i<5;i++){ p[i]=i+1; } *cp1=p; } void copy2(int *cp2){ int p[5]={0}; for(int i=0;i<5;i++){ p[i]=i+1; cp2[i]=p[i]; } } int main(){ int cp2[5]={0}; int *cp1; copy1(&cp1); copy2(cp2); printf("cp1n"); for(int i=0;i<5;i++){ printf("%d ",cp1[i]); } printf("ncp2n"); for(int i=0;i<5;i++){ printf("%d ",cp2[i]); } }
作业案例
1,矩阵的删改查
#include#include void shan(int (*p)[4],int h,int l){ h=4; l=4; for(int i=0;i 2,给定成绩,输入学生序号,查所有成绩和查询有不及格科目的学生并且输出不及格的科目
#include#include #include int* data(int (*p)[4],int loc){ return *(p+loc-1); } void allprt(int *p){ for(int i=0;i<4;i++){ char a[5]; switch(i){ case 0: strcpy(a,"语文 "); break; case 1: strcpy(a,"数学 "); break; case 2: strcpy(a,"英语 "); break; case 3: strcpy(a,"物理 "); break; } printf("%s:%d ",a,*(p+i)); } puts(""); } void bjgprt(int (*bjgscore)[4],int **bjgrs,int len){ for(int i=0;i


