七、指针
指针是一种数据类型,就像int和float,int装整型数据,float装浮点型数据,指针装地址型数据
可以简单的理解指针就是地址。比如一个人买了一间房子,这个房子可以是故宫,可以是中南海,可以是白宫,可以是唐宁街,可以是别墅,猪圈等。而指针式可以理解为房产证,房产证里面写着房子的具体地址。根据房产证的地址,我们就能找到房子了。所以操作指针的目的,就是为了通过房产证里面的地址来找到房子进而来操作房子。直接把房产证中个地址改了,并不会修改房子本身的地址,只是修改了指向的房子而已。
二、基本数据类型指针
char, short, int, long, long long, float, double
int *p; // 形式:类型+*+变量名; // int 表示p装的地址对应的空间的数据类型 // *表示p是一个指针变量 // p是指针的名字指针就是装地址的变量,变量就要赋值,即一定要装一块空间的地址,或者说指向一块空间,才能被使用。就像int a;,如果没有被初始化,没有赋值,这东西啥也不能干。指针变量也是同理,不装地址的情况下,啥都不能干,也叫野指针
#include<stdio.h> int main(void) { int a = 12; int b = 24; char c = 'a'; int *p; p = &a; // 指向某个地址,就是内容装哪块地址,就指向哪一块空间 printf("%p, %p, %p\n", p, &a, &p); // 010FF73C, 010FF73C, 010FF718 p = &b; // 重指向 printf("%p, %p, %p\n", p, &b, &p); // 010FF730, 010FF730, 010FF718 p = &c; /* 重指向 警告: Visual Studio: warning C4133: “=”: 从“char *”到“int *”的类型不兼容 DevC++: [Error] cannot convert 'char*' to 'int*' in assignment 所以类型一定要对应上,因为类型会决定指定的读写方式 */ printf("%p, %p, %p\n", p, &c, &p); // 010FF727, 010FF727, 010FF718 return 0; }
指针里面存放的是地址,直接操作地址意义不大,就是把房子地址改了,但是本身的房子还在那里。所以指针的核心作用是通过地址来操作地址所指向的数据。
操作数据主要分为对数据的读写操作。
/* 读数据 读指针的数据主要分为两种数据。一种是指针指向的数据,一种是指针变量自身的地址。 */ /* 1. 读取指针指向的数据 用 *+地址。这个地址一定是合法地址,
即我们申请了的地址,如定义的变量、数组等 2. 此时的 * 是一个内存操作符,还有人叫解引用运算符。是获取指针指向的
数据内容,和定义一个指针类型的 * 是不一样,定义时候属于一种标记。 3. 总结: 一个指针指向一个变量,*这个指针,就是变量本身。 */ #include<stdio.h> int main(void) { int a = 12; int *p1 = &a; int *p2; p2 = &a; *p2 = 23; // 1. 一种是指针指向的数据 printf("%d\n", *p1); // 12 printf("%d\n", *p2); // 12 printf("%d\n", *(&a)); // 12 // 2. 读取指针变量本身地址 printf("%p\n", &p1); // 000000000062FE38 printf("%d\n", &p1); // 6487608 return 0; }
2.4 类型决定内存操作
2.4.1 指针的基本运算
#include<stdio.h> int main(void) { int a = 10; double b = 9.9; char c = '@'; int *pa = &a; double *pb = &b; char *pc = &c; // 初始值 printf("&a=%p, &b=%p, &c=%p\n", &a, &b, &c);// &a=000000000062FE34, &b=000000000062FE28, &c=000000000062FE27 printf("pa=%p, pb=%p, pc=%p\n", pa, pb, pc);// &a=000000000062FE34, &b=000000000062FE28, &c=000000000062FE27 // 加法运算 pa++; pb++; pc++; printf("pa=%p, pb=%p, pc=%p\n", pa, pb, pc);// pa=000000000062FE38, pb=000000000062FE30, pc=000000000062FE28 //减法运算 pa -= 2; pb -= 2; pc -= 2; printf("pa=%p, pb=%p, pc=%p\n", pa, pb, pc);// pa=000000000062FE30, pb=000000000062FE20, pc=000000000062FE26 return 0; } /* 1. 从运算结果可以看出:pa、pb、pc 每次加 1,它们的地址分别增加 4、8、1,正好是
int、double、char 类型的长度;减 2 时,地址分别减少 8、16、2,正好是 int、double、char 类型长度的 2 倍。 2. 以int类型为例。int占据4个字节,当指针pa指向int类型的时候,int占据4个字节的地址,当pa+1的时候,
如果内存往后移动1个字节的话,那么pa+1所占据的内存就是 a内存的后面3个字节长度加上 1 个新的空间,
这样组成的新的内存空间意义不大,不清楚数据具体会成为什么,所以pa+1所指向的就是增加了一个 int类型所占据的长度。 3. 由此我们可以引申出来,如果指针操作的内存大小与所指向的数据的内存大小不一样,那么很有可能会出现问题的。 */
所指向的空间是什么类型,那么*p 就一次操作多大的内存空间
一个小范围的指针可以指向大范围的空间,并且操作不异常,虽然结果不太对
一个大范围的指针不可以指向小范围的空间,否则操作异常,结果更不对#include<stdio.h> int main(void) { double a = 123.456; int *p = &a; // Dev-C++中编译就会报错,VS中编译警告,可以执行 printf("%lf, %lf\n", a, *p); *p = 789; printf("%lf, %lf\n", a, *p); char c = 'a'; printf("%c\n", c); p = &c; /* *p = 'b'; // 运行直接报错了 printf("%c, %c\n", c, *p); */ return 0; } /* double类型一次性占据8个字节,int类型一次性操作4个字节。当int类型的指针指向8个字节的double类型时候,
每次操作double类型的4个字节(一般的字节),重新给double赋值的时候,也只是操作double的4个字节。所以
从现象看,double类型的前面部分数据是对的,后面是错误的(数据跟大小端存储有关系)。 同理,操作char类型的时候。char类型的字节是1个字节,但是int类型的指针操作了4个字节,超过了char的大小,所以就会直接会报错。 */
三、 二级指针
3.1 二级指针的定义
二级指针就是指向指针的指针,也就是装地址的地址。 普通变量的地址用一级指针来装。一级指针变量的地址,用二级指针来装,同理二级指针地址用三级指针装
#include<stdio.h> int main(void) { int a = 12; int *p = &a; // 想要存放p的地址,该怎么处理? int *p1 = &p; // warning C4047: “初始化”:“int *”与“int **”的间接级别不同 int *p2 = p; // 直接用p来也是不行的。因为p是变量名,直接用p代表着是值传递,传递的是p的值,也就是 p=&a, 所以等价于 int *p2 = &a; /* 因为 a是普通变量, 装普通变量的时候 用 p来存储,又因为是地址,所以用int *来定义,所以 int *p。
p为指针变量,要装指针变量,要用 *p3来表示,又因为是装地址 int *,所以就是 int * *p3, 即 int **p3 一个*表示一级指针,指向的是变量的地址 两个*表示的是二级指针,指向的是相应指针变量的地址,如果要取到变量的值,就需要两个*进行取值 */ int **p3 = &p; return 0; }
#include<stdio.h> int main(void) { int a = 12; int *p = &a; int **q = &p; printf("a = %d\n",a); // a = 12 printf("&a = %p\n",&a); // &a = 000000000062FE4C printf("p = %p\n",p); // p = 000000000062FE4C printf("&p = %p\n",&p); // &p = 000000000062FE40 printf("*p = %d\n",*p); // *p = 12 printf("q = %p\n",q); // q = 000000000062FE40 printf("&q = %d\n",&q); // &q = 6487608 printf("*q = %d\n",*q); // *q = 6487628 printf("**q = %d\n",**q); // **q = 12 }// 从上面的例子可以得出如下结论 q = &p; *q = p = &a; **q = *p = a;https://blog.csdn.net/weixin_42616791/article/details/106737245
四、一维数组与指针
4.1 指针遍历数组
为什么普通类型指针可以遍历数组?
1、数组空间是连续的
2、地址加减是加一个类型的大小#include <stdio.h> int main() { int arr[] = { 99, 15, 100, 888, 252 }; int len = sizeof(arr) / sizeof(int); //求数组长度 int i; int *p; p = &arr[0]; // 1. 遍历数组场景一 p+1 printf("%p, %p ,%p, %p, %p\n", arr, arr+1, arr+2, arr+3, arr+4); printf("%p, %p, %p, %p, %p\n", p, p+1, p+2, p+3, p+4); printf("%p, %p, %p, %p, %p\n", &arr[0], &arr[1], &arr[2], &arr[3], &arr[4]); /* 上述三个打印中,打印出来的结果是一样的。说明 数组名+1 == 指针+1 == 对应下标数组地址 是一样的,改变的都是一个数的字节大小。 根据 地址前面加上 *,即 *(&arr[1]) 我们就能获得1号元素的数据,同理 即 *(arr+1), *(p+1)就能获取下标1的数据。加上()是因为*的优先级高于+ */ // 2. 遍历数组场景二 *(p+i) for(i=0; i<len; i++) { printf("%d ", *(arr+i) ); // 99 15 100 888 252 *(arr+i)等价于arr[i] } printf("\n"); for(i=0; i<len; i++) { printf("%d ", *(p+i));// 99 15 100 888 252 *(p+i) == *(arr+i) == *(&arr[0]+i) } printf("\n"); /* arr 数组名代表数组的首地址。arr+1代表增加了4个字节的长度,因为数组的元素是连续的, 所以arr+1就是第一个元素的地址。
*(arr+1)就是这个地址的值了。同样,p存放的是是数组首地址,所以 *(p+1) == *(&arr[0]+1) == *(arr+1) */ // 3. 遍历数组场景三 (*p)++ for(i=0; i<len; i++) { printf("%d ", (*p)++); // 99 100 101 102 103 } printf("\n"); /* 使用(*p)++得出的结果是连续的数字。因为第一次循环先计算 *p == 99, 然后 ++,第二次循环将 ++ 的值输出。 */ // 4. 遍历数组场景四 *p++ for(i=0; i<len; i++) { printf("%d ", *p++); // 104 15 100 888 252 *p++ == *(p++) } /* 使用*p++在输出的结果和*(p+i)是一样的,但是运算结束以后,p指向的地址发生了改变,因为p++以后,
p的值发生了改变,所以指向的地址也发生了改变 */ // 5. 指针赋值 p = &arr[0]; // 重新初始化,因为步骤4已经改了p最后的指向了 *(p+2) = 13; for(i=0; i<len; i++) { printf("%d ", *(p+i)); // 104 15 13 888 252 } return 0; }
#include <stdio.h> int main() { int arr[] = { 99, 15, 100, 888, 252 }; int len = sizeof(arr) / sizeof(int); int *p = &arr[0]; int i; for(i=0; i<len; i++) { printf("%d ", arr[i]); printf("%d ", p[i]); printf("%d ", *(p+i)); printf("%d ", *(i+p)); printf("%d\n", i[p]); /* 99 99 99 99 99 15 15 15 15 15 100 100 100 100 100 888 888 888 888 888 252 252 252 252 252 */ } return 0; } /* 1. 从上面的运算结果中我们看出来 arr[i]与p[i], *(p+i), *(i+p), i[p]的结果是一样的。 2. arr[i]取值没有什么疑问,是数组的正常下标取值。 3. 在 4.1.1 的步骤二中,我们可以隐约感觉出来 arr和p有某种相等的关系。其实arr数组名
代表数组的首地址,p=&arr[0]也是数组的首地址,所以我们就感觉可以这样获取数据 4. arr[i] 本质就是 地址+[偏移量]。推理出 p[i]是指针的下标运算。 准确地说,下标运算
不是数组专属,只是一个运算符。数组可以用这个运算符是因为它符合了下标运算符的使用条件,地址+[偏移量],指针也是完全可以的 5. *(p+i)的值与 p[i]的值是相等的.执行的原理都是 :地址+偏移量找到目标地址,然后操作目标地址空间(读、写、取地址)。
但是意义不一样: *(p+i)混合运算表达式,包含三个运算符, p[2]只有一个下标运算符 6. 同理: arr[i] == *(arr+i)。所以在某种程度上 arr==p, 但是 arr是不能被赋值,
只能参与运算, p既可以参与运算,也可以被赋值,指向新的地址。 7. *(p+i) == p[i] == *(i+p) == i[p] 8. 将 *(p+i) == p[i] == A, 所以 *(A+j)==A[j]==p[i][j] 所以 *(*(p+i) + j) == p[i][j] */
4.2 指针数组 (装地址的数组)
#include<stdio.h> int main() { int b = 4, c = 3, d = 7, e = 8, f = 1; int* a[5] = { &b,&c,&d,&e,&f }; printf("%d\n",d); // 7 *a[2] = 13; printf("%d\n", d); // 13 return 0; }
// 指针数组的拉链结构(数组中的每一个元素放置的另一个数组的地址 #include<stdio.h> #include<stdlib.h> int main(void) { int b[3] = {1,2,3}; int c[2] = {4,5}; int d[4] = {6,7,8,9}; int e[5] = {10,11,12,13,14}; int f[2] = {15,16}; int* a[5] = {&b[0],c,d,e,f}; // 数组名代表数组的首地址 // 查找 d 的 8 d[2] printf("%d\n", a[2][2]); // a[2] 代表 d的地址 , a[2][2] == d[2] system("pause"); return 0; } // 【注意】 // 1.访问格式跟二维数组一样,意义也是一样的 // 2.但是内存结构是不一样的,或者说本质不一样 // 3.每个小数组大小可以不同,但是元素类型必须相同
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { int arr1[] = { 1, 2, 3, 4, 5 }; int arr2[] = { 2, 3, 4, 5, 6 }; int arr3[] = { 3, 4, 5, 6, 7 }; int * arr[] = { arr1, arr2, arr3 }; int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 5; j++) { printf("%d ", *(arr[i] + j)); } printf("\n"); for (j = 0; j < 5; j++) { printf("%d ", arr[i][j]); } printf("\n"); } return 0; }
4.3 数组指针(指向数组的指针)
#include<stdio.h> int main(void) { int a = 15; printf("%p, %p\n", &a, &a+1); // 000000000062FE14, 000000000062FE18 int b[5] = {3,4,5,6,7}; printf("%p, %p\n", &b,&b+1); // 000000000062FE30, 000000000062FE44 return 0; } /* 1. int a; int类型的地址 &a+1 的时候增加的是 一个 int 类型的字节的大小。 2. int b[5]; int 数组类型的 &b+1 的大小根据 &a+1 的理论 应该是加了整个数组的大小20各字节 说明&b这个地址类型是其所表示的数组那么大(字节数)。 3. 存储int类型的地址 我们用 int *即 int指针来装。 同理存储 int[] 类型的地址就叫做数组类型的地址,简称数组地址,用数组指针来装, 即 int []* */
int a[5]; int (*p)[5] = &a; /* (*p): p是一个指针,一定要加这个小括号,否则p就先跟[ ]结合,就是数组了 int [5]: p所指向的类型,数组类型, int [5]与int [7] ,数组元素个数不同,那就是不同类型的数组。一定要记住,不是所有的数组指针都是一样的 合在一起: int [5](*p) 这应该是通常的组合模型,但是C语言不让这样写,得int (*p)[5]这样写 */#include<stdio.h> int main(void) { int a[5] = {3,4,5,6,7}; int (*p1)[5] = &a; // 没问题 // int (*p2)[6] = &a; //报错 /* 此时 *p1 == a; *p1[2] == 5 ??? 这样是不对的。p1是数组指针, p1[2]==*(p1+2), p1加上两个数组的大小,此时已经越界了。 */ // 数组指针 // *p1 指针 int[5] 组类型 ---> int [5]*p1 ---> int *p1[5] (这样就成了指针数组) ---> int (*p1)[5] int (*p1)[5] 中括号的优先级高于指针 printf("%d\n",(*p1)[0]); // 3 return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { char* arr[5]; char* (*pa)[5] = &arr; // (*pa)的*说明pa是指针, [5]:pa指向的数组是5个元素, char * 代表pa指向的数组元素是 char*类型 return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 }; int(*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p //但是我们一般很少这样写代码 int i = 0; for (i = 0; i < 10; i++) { printf("%d\n", (*p)[i]); // *pa == arr printf("%d\n", *((*p) + i)); } return 0; }
指针数组 数组指针 含义 装地址的数组(指针的数组 ) 指向数组的指针(数组的指针) 表示方式 int *p[5] int (*p)[5] 用处 用的不多 函数参数
五、 二维数组与指针
5.1 二维数组与指针分析
int a[2][3] = {{1,2,3}, {4,5,6}}
二维数组主要有三个地址: &a, &a[0], &a[0][0], 对于(&a[1]) 、(&a[0][1])也是地址,暂不考虑。
1. &a[0][0]: 二维数组第一个小数组的第一个元素的地址。对应的数据类型是 int类型。
所以存储这个数据的地址应该为 int *p = &a[0][0];
2. &a[0]: 二维数组的首元素的地址, 等价于 数组名 a;对应的是二维数组的第一个元素,这个是一个元素是3个的一 维数组,所以对应的指针应该是一维数组的数组指针 int (*p)[3] = &a[0];
2. &a: 根据上面的可以推倒,这个是二维数组的地址, 对应的指针应该是 int(*p)[2][3] = &a;
#include<stdio.h> int main(void) { int a[2][3] = {{1,2,3}, {4,5,6}}; int *p1 = &a[0][0]; // p1指向这个元素地址, *p1就是这个元素本身。取元素的地址 *p1 = a[0][0] for(int i=0; i<6; i++) { printf("%d ",*(p1+i)); } printf("\n"); int (*p2)[3] = &a[0]; // 取一维数组的地址 *p2 = a[0]; *(p2+i) = p2[i] = a[i] for (int j=0; j<2; j++) { for(int k = 0; k<3; k++) { printf("%d ",(*(p2+j))[k]); // (*(p2+j))[k] == *(*(p+i) + j) == p[i][j] // 参见:4.1.2 深入理解下标运算 //printf("%d ",*(*(p2+j) + k)); // 和上面一样 } } printf("\n"); int (*p3)[2][3] = &a; // 取二维数组的地址 *p3 = a for (int m=0; m<2; m++) { for(int n=0; n<3; n++) { printf("%d ", (*p3)[m][n]); } } return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> void print1(int arr[3][5], int x, int y) { int i = 0; int j = 0; for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } void print2(int(*parr)[5], int x, int y) { int i = 0; int j = 0; for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { printf("%d ", *(*(parr + i) + j)); // parr + i:指向第i行地址 ,*(parr+i)指向该行首元素的地址, *(parr+i)+j指向该行第j列的元素地址 // *(parr+i) == parr[i] -- > *(*(parr + i) + j) == *(parr+i)[j] == parr[i][j] == arr[i][j] } printf("\n"); } } int main() { int arr[3][5] = { { 1, 2, 3, 4, 5 }, { 3, 4, 5, 6, 7 }, { 5, 6, 7, 8, 9 } }; // print1(arr, 3, 5); // print1(&arr[0][0], 3, 5); 也是一个意思 print2(arr, 3, 5); // arr 是数组名,就是首元素地址,首元素就是第一行的元素,所以arr是数组指针 return 0; }
int arr1[5]; // arr1是一个5个元素的整形数组 int *arr2[10];// arr2是一个数组,数组中有10个元素,每个元素的类型是 int *类型, arr2是指针数组 int (*arr3)[10];// arr3是一个指针, 该指针指向了一个数组,数组有10个元素,每个元素的类型是 int类型, arr3是一个数组指针 int (*arr4[10])[5];// arr4是一个数组,该数组有10个元素,每个元素是一个数组指针, 该数组指针指向的数组有5个元素,每个元素是 int类型
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> void test1(int arr[]) // true 传数组可以直接用数组接收 {} void test1(int arr[10]) // true 传数组可以直接用数组接收 {} void test1(int *arr) // true 数组名代表数组首元素地址,用地址接收没问题 {} void test2(int *arr[20]) // true 数组指针当参数,可以用数组指针接收 {} void test2(int *arr[]) // true 数组指针当参数,可以用数组指针接收 {} void test2(int **arr) // true 指针数组的首元素地址是一级指针,一级指针的地址传过来,用二级指针接收, {} int main() { int arr[10] = {0}; int *arr2[20] = {0}; test1(arr); // 传数组名可以直接用数组,或者首元素地址接收 test2(arr2); // 传指针数组可以用数组指针,数组指针首元素地址接收 return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> void test(int arr[3][5]) // true, 二维数组传参,用二维数组接收 {} void test(int arr[][]) // false, 列必须要有 {} void test(int arr[][5]) // true, 行可以没有,列一定要有 {} //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。 //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。 //这样才方便运算。 void test(int *arr) // false, 二维数组的数组名,表示首元素,也就是第一行的地址 {} void test(int* arr[5]) // false, 传过来的是地址,接收的参数时数组,数组元素是 int * {} void test(int(*arr)[5]) // ture, 二维数组首元素地市,接收第一行元素也就是数组的地址,需要用数组指针 {} void test(int **arr) // false, 数组的地址没办法放在二级指针,二级指针存放的是一级指针的地址 {} int main() { int arr[3][5] = { 0 }; test(arr); return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> void print(int *p, int sz) // 指针传参,用指针接收 { int i = 0; for (i = 0; i<sz; i++) { printf("%d\n", *(p + i)); } } int main() { int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int *p = arr; int sz = sizeof(arr) / sizeof(arr[0]); //一级指针p,传给函数 print(p, sz); // true return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> void test(int** ptr) // 二级指针传参,参数直接用二级指针 { printf("num = %d\n", **ptr); } int main() { int n = 10; int*p = &n; int **pp = &p; int *arr[10]; test(pp); // true test(&p); // true test(arr); // true return 0; }
七、函数指针
7.1 函数指针表示方式
// 数组指针; 指向数组的指针 // 函数指针: 指向函数的指针#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int Add(int x, int y) { int z = 0; z = x + y; return z; } int main() { int a = 10; int b = 20; int ret = Add(a, b); printf("rett = %d\n", ret); printf("%p\n", Add); // 函数名和 &函数名是一样的 printf("%p\n", &Add); return 0; } // 输出的是两个地址,这两个地址是 test 函数的地址。 // 那我们的函数的地址要想保存起来,怎么保存?
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int Add(int x, int y) { int z = 0; z = x + y; return z; } int main() { int a = 10; int b = 20; int ret = Add(a, b); printf("rett = %d\n", ret); // ret = 30 int (*pa)(int, int) = Add; // 存放函数的地址 // *pa == Add; int ret2 = (*pa)(30, 40); printf("rett = %d\n", ret2); // ret2 = 70 return 0; }#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> void Print(char *str) { printf("%s\n", str); } int main(void) { void(*p)(char *) = Print; (*p)("Hello World"); // Hello World return 0; }(*(void (*)())0)(); // void (*)() : 是一个函数指针类型 // (void (*)())0 : 将0强制转换成函数指针类型 (void (*)()), 0就是一个函数的地址 // *(void (*)())0 : 解引用 // (*(void (*)())0)() : 调用函数 void (*signal(int , void(*)(int)))(int); // void(*)(int) : 函数指针作为第二个参数 // signal(int , void(*)(int)) : signal函数名, 参书有两个 (int, void(*)(int)) // void (*)(int) : 这个是上面一行函数的返回类型, 是一个函数指针类型,说明 signal(int , void(*)(int)) 的返回类型是函数指针类型 // 总结: signal 是一个函数声明, signal函数的参数有两个,第一个是 int, 第二个是函数指针,该函数指针指向的函数的参数时int,返回类型是 void。 signal函数的返回类型也是一个函数指针,该函数指针指向的函数参数时 int, 返回类型是 void // 简单写如下: /* typedef void(* pfun_t)(int); 将函数指针类型 void (*)(int) 重命名为 pfun_t pfun_t signal(int, pfun_t); 转换之后即为这个样子 */#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int Add(int x, int y) { int z = 0; z = x + y; return z; } int main() { int(*pa)(int, int) = Add; // 存放函数的地址 printf("%d\n", (pa)(30, 40)); // 70 printf("%d\n", (*pa)(30, 40)); // 70 printf("%d\n", (**pa)(30, 40)); // 70 printf("%d\n", (***pa)(30, 40)); // 70 // pa是一个函数指针,解引用和不解应用得到最后的结果都是一样, **, ***意义不大,不建议用, 所以对于函数指针来说, *只是一个摆设 return 0; }
7.2 函数指针数组
把函数的地址存到一个数组中,那这个数组就叫函数指针数组 int (*parr1[10])();#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int Mul(int x, int y) { return x * y; } int Div(int x, int y) { return x / y; } int main() { int(*pa)(int, int) = Add; // pa 存放一个函数的函数指针 // 需要一个数组来存放4个函数的地址,这个就是函数指针数组 int(*parr[4])(int, int) = { Add, Sub, Mul, Div }; // parr1 先和 [] 结合,说明 parr1是数组,parr就是一个函数指针数组, int i = 0; for (i = 0; i < 4; i++) { printf("%d ", parr[i](2,3)); // 5 -1 6 0 } return 0; }
// 1. 函数指针数组的指向 // 一个函数声明是 char * my_strcpy(char * dest, const char* src); // 1). 写一个函数指针pf,能够指向 my_stecpy // 2). 写一个函数指针数组 pfArr, 能够存放 4 个 my_strcpy 函数的地址 // 1). char * (*pf)(char* , const char* ) // 2). char * (*pfArr[])(char* , const char* )// 2. 转移表, 计算器 // 2.1) 普通的switch case 方法 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int Mul(int x, int y) { return x * y; } int Div(int x, int y) { return x / y; } void menu() { printf("**********************\n"); printf("*** 0.exit 1. add ***\n"); printf("*** 2.sub 3. mul ***\n"); printf("*** 4.div **********\n"); printf("**********************\n"); } int main() { int x = 0; int y = 0; int input = 0; int ret = 0; do { menu(); printf("请输入选项:>>>"); scanf("%d", &input); switch (input) { case 0: printf("退出\n"); break; case 1: printf("请输入两个操作数:>>>"); scanf("%d%d", &x, &y); ret = Add(x, y); printf("ret = %d\n", ret); break; case 2: printf("请输入两个操作数:>>>"); scanf("%d%d", &x, &y); ret = Sub(x, y); printf("ret = %d\n", ret); break; case 3: printf("请输入两个操作数:>>>"); scanf("%d%d", &x, &y); ret = Mul(x, y); printf("ret = %d\n", ret); break; case 4: printf("请输入两个操作数:>>>"); scanf("%d%d", &x, &y); ret = Div(x, y); printf("ret = %d\n", ret); break; default: printf("输入错误,请重新输入\n"); break; } } while (input); return 0; }
// 2.2) 用函数指针数组解决, 使用数组指针数组能大量减少代码的冗余, 函数指针数组又称为转移表 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int Mul(int x, int y) { return x * y; } int Div(int x, int y) { return x / y; } void menu() { printf("**********************\n"); printf("*** 0.exit 1. add ***\n"); printf("*** 2.sub 3. mul ***\n"); printf("*** 4.div **********\n"); printf("**********************\n"); } int main() { int x = 0; int y = 0; int input = 0; int ret = 0; do { menu(); int(*pfArr[])(int, int) = { 0, Add, Sub, Mul, Div }; printf("请输入选项:>>>"); scanf("%d", &input); if (input >= 1 && input <= 4) { printf("请输入两个操作数:>>>"); scanf("%d%d", &x, &y); ret = pfArr[input](x, y); printf("ret = %d\n", ret); } else if (input == 0) { printf("退出\n"); } else { printf("请输入合理范围内的数字\n"); } } while (input); return 0; }
// 指向函数指针数组的指针是一个 指针 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int Mul(int x, int y) { return x * y; } int Div(int x, int y) { return x / y; } int main() { int arr[10] = { 0 }; // 数组 int(*parr)[10] = &arr; // 数组指针, 就是将 数组 arr前面加个 * int(*pf)(int, int); // 函数指针 int(*pArr[5])(int, int) = { 0, Add, Sub, Mul, Div };// pArr 是一个函数指针数组 int(*(*pfArr)[5])(int, int) = &pArr; // pfArr 是一个数组指针,指针指向的数组有4个元素, // 指向的数组的每个元素的类型是函数指针 int (*)(int, int), 相当于在上面一行的 pArr前面加上 * return 0; }
7.4 回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
可以理解为将函数名(函数地址)当成参数传给其他函数,在其他函数中使用这个地址调用这个函数,实现这个函数的功能
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int Add(int x, int y) { return x + y; } int Sub(int x, int y) { return x - y; } int Mul(int x, int y) { return x * y; } int Div(int x, int y) { return x / y; } void Calc(int(*pf)(int, int)) { int x = 0; int y = 0; int ret = 0; printf("请输入两个操作数:>>>"); scanf("%d%d", &x, &y); ret = pf(x, y); printf("ret = %d\n", ret); } void menu() { printf("**********************\n"); printf("*** 0.exit 1. add ***\n"); printf("*** 2.sub 3. mul ***\n"); printf("*** 4.div **********\n"); printf("**********************\n"); } int main() { int x = 0; int y = 0; int input = 0; int ret = 0; do { menu(); printf("请输入选项:>>>"); scanf("%d", &input); switch (input) { case 0: printf("退出\n"); break; case 1: Calc(Add); break; case 2: Calc(Sub); break; case 3: Calc(Mul); break; case 4: Calc(Div); break; default: printf("输入错误,请重新输入\n"); break; } } while (input); return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> void Print(char *str) { printf("%s\n", str); } void test(void(*p)(char *)) { printf("test\n"); p("wangyong"); } int main() { test(Print); return 0; }
// qsort --- 库函数 使用的底层是 快速排序 #include<stdlib.h> // void qsort(void *base, size_t num, size_t width, int (* cmp)(const void *e1,const void *e2)); // void* 参见 空指针内容,代表可以接受任意类型的地址 #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<stdlib.h> #include<string.h> void bubble_sort(int arr[], int sz) { int i = 0; int j = 0; int tmp = 0; for (i = 0; i < sz - 1; i++) // 趟数 { for (j = 0; j < sz - 1 - i; j++) // 两个比较元素 的对数 { if (arr[j]>arr[j + 1]) { tmp = arr[j + 1]; arr[j + 1] = arr[j]; arr[j] = tmp; } } } } struct Stu { char name[20]; int age; }; // 比较两个整形元素 int cmp_int(const void* e1, const void* e2) { // 比较两个整形的值 // e1, e2都是 void,不能直接 *e1, *e2解引用操作,强制类型转换为 int *类型之后进行比较 return *(int*)e1 - *(int*)e2; } // 排序整型 void sort_int() { int i = 0; int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_int); for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } // 比较两个浮点型规则 int cmp_float(const void* e1, const void* e2) { // return *(float*)e1 - *(float*)e2; // 这里会警告,因为返回的是浮点型,函数应该返回整型 if (*(float*)e1 == *(float*)e2) { return 0; } else if (*(float*)e1 > *(float*)e2) { return 1; } else { return -1; } } // 排序浮点型 void sort_float() { int i = 0; float f[] = { 9.0, 8.0, 7.0, 6.0, 5.0, 4.0 }; int sz = sizeof(f) / sizeof(f[0]); qsort(f, sz, sizeof(f[0]), cmp_float); for (i = 0; i < sz; i++) { printf("%f ", f[i]); } } // 结构体按照年龄排序规则 int cmp_stu_by_age(const void* e1, const void* e2) { return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age; } // 结构体按照姓名排序规则 int cmp_stu_by_name(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); } // 排序结构体 void sort_struct() { struct Stu s[3] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 25 } }; int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), cmp_stu_by_age); qsort(s, sz, sizeof(s[0]), cmp_stu_by_name); } int main() { int i = 0; int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; int sz = sizeof(arr) / sizeof(arr[0]); // 曾经的冒泡排序, 原本的冒泡排序只能排序整形数字,没办法比较浮点型以及字符等其他的数据,所以可以使用 qsort bubble_sort(arr, sz); /*for (i = 0; i < sz; i++) { printf("%d ", arr[i]); }*/ // void qsort(void *base, size_t num, size_t width, int (* cmp)(const void *e1,const void *e2)); //int (* cmp)(const void *e1,const void *e2) 当e1<e2的时候,返回<0.e1==e2,返回==0, e1>e2,返回>0的数字 /* qsort: 第一个参数: 待排序数据的首元素地址 第二个参数: 待排序数据的元素个数 第三个参数: 待排序数据的每个元素的大小,单位是字节 第四个参数: 是函数指针,比较两个元素的所用函数的地址,这个函数使用者自己实现 函数指针的两个参数是: 待比较两个元素的地址 */ sort_int(); sort_float(); sort_struct(); return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<stdlib.h> #include<string.h> struct Stu { char name[20]; int age; }; // 比较两个整形元素 int cmp_int(const void* e1, const void* e2) { // 比较两个整形的值 // e1, e2都是 void,不能直接 *e1, *e2解引用操作,强制类型转换为 int *类型之后进行比较 return *(int*)e1 - *(int*)e2; } // 排序整型 void sort_int() { int i = 0; int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; int sz = sizeof(arr) / sizeof(arr[0]); qsort(arr, sz, sizeof(arr[0]), cmp_int); for (i = 0; i < sz; i++) { printf("%d ", arr[i]); } printf("\n"); } // 比较两个浮点型规则 int cmp_float(const void* e1, const void* e2) { // return *(float*)e1 - *(float*)e2; // 这里会警告,因为返回的是浮点型,函数应该返回整型 if (*(float*)e1 == *(float*)e2) { return 0; } else if (*(float*)e1 > *(float*)e2) { return 1; } else { return -1; } } // 排序浮点型 void sort_float() { int i = 0; float f[] = { 9.0, 8.0, 7.0, 6.0, 5.0, 4.0 }; int sz = sizeof(f) / sizeof(f[0]); qsort(f, sz, sizeof(f[0]), cmp_float); for (i = 0; i < sz; i++) { printf("%f ", f[i]); } } // 结构体按照年龄排序规则 int cmp_stu_by_age(const void* e1, const void* e2) { return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age; } // 结构体按照姓名排序规则 int cmp_stu_by_name(const void* e1, const void* e2) { return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name); } // 排序结构体 void sort_struct() { struct Stu s[3] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 25 } }; int sz = sizeof(s) / sizeof(s[0]); qsort(s, sz, sizeof(s[0]), cmp_stu_by_age); qsort(s, sz, sizeof(s[0]), cmp_stu_by_name); } void Swap(char * buff1, char* buff2, int width) // 如果没有width参数,char* 每次只能交换一个字节,需要交换整个数据所在的所有字节 { int i = 0; for (i = 0; i < width; i++) { char tmp = *buff1; *buff1 = *buff2; *buff2 = tmp; buff1++; buff2++; } } // 模仿 qsort 来修改冒泡排序 // 排序各种类型的冒泡排序,实现bubble_sort2函数的程序员, 他是否知道未来排序的数据类型? // 如果不知道待比较的两个元素的数据类型 void bubble_sort2(void *base, int sz, int width, int (*cmp)(void *e1, void *e2)) { int i = 0; int j = 0; for (i = 0; i < sz - 1; i++) { // 每一层比较趟数 for (j = 0; j < sz - 1 - i; j++) { // 两个元素的比较 // cmp 函数接收用 void*来接收低地址, 因为不清楚传过来的具体的数据类型,但是我们在比较两个数的时候 // 不清楚是具体的 char类型,int类型,float类型, struct类型等的时候,没办法将元素直接类型转换。 // 此时,我们将每个数据类型都转换成 char*类型,因为函数传过来的时候将 width传过来了, char *之后再加上 width // 这样就能跳转到下一个操作数据进行比较 if (cmp((char *)base+j*width, (char*)base+(j+1)*width) > 0) { // 交换 Swap((char *)base + j*width, (char*)base + (j + 1)*width, width); } } } } // 数组使用 bubble_sort2 排序 void array_bubble_sort2() { int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 }; int sz = sizeof(arr) / sizeof(arr[0]); // 使用bubble_sort2的程序员一定知道自己排序的是什么数据 // 就应该知道如何比较待排序数据中的元素 bubble_sort2(arr, sz, sizeof(arr[0]), cmp_int); int i = 0; for (i = 0; i < sz; i++) { printf("%d ",arr[i]); } } // 结构体使用 bubble_sort2 排序 void struct_bubble_sort2() { struct Stu s[3] = { { "zhangsan", 20 }, { "lisi", 30 }, { "wangwu", 25 } }; int sz = sizeof(s) / sizeof(s[0]); // 使用bubble_sort2的程序员一定知道自己排序的是什么数据 // 就应该知道如何比较待排序数据中的元素 bubble_sort2(s, sz, sizeof(s[0]), cmp_stu_by_age); bubble_sort2(s, sz, sizeof(s[0]), cmp_stu_by_name); } int main() { array_bubble_sort2(); struct_bubble_sort2(); return 0; }
八、指针的大小
1、32位的系统支持32位的软件 64位系统支持32 64位的软件
2、32位的程序/软件是4字节指针,64位的程序是8字节指针。
所以,32位系统最大支持4字节指针,64位系统最大支持8字节指针
3、程序的位数由什么决定呢?
由我们的开发环境(编译器)决定
sizeof(指针变量名字)
64bit程序(8字节)
32bit程序(4字节)32位的地址,最大就是4字节装,就够了,你给分个5字节,没用。用不上,有一个字节空间浪费,64位同理。 4字节 == 4*8 == 32位; 最大数 == 2^32-1
#include<stdio.h> int main(void) { int *p1; short *p2; float* p3; double* p4; int(*p5)[2]; int(*p6)[2][3]; printf(" %d, %d, %d, %d, %d, %d\n", sizeof(p1), sizeof(p2), sizeof(p3), sizeof(p4), sizeof(p5), sizeof(p6)); printf(" %d, %d, %d, %d, %d, %d\n", sizeof(int *), sizeof(short *), sizeof(float *), sizeof(double *), sizeof(int (*)[2]), sizeof(int (*)[2][3])); /* 32位: 4, 4, 4, 4, 4, 4 64位: 8, 8, 8, 8, 8, 8 */ return 0; }
#include <stdio.h> #include <string.h> int main(){ char str[] = "https://www.cnblogs.com/wangyong123/articles/13689673.html"; int len = strlen(str), i; // == int len = strlen(str); int i; strlen的长度不包括'\0' printf("%s\n", str); // %s获取字符串 for(i=0; i<len; i++){ // for 循环输出字符串 printf("%c", str[i]); } printf("\n"); return 0; }
#include <stdio.h> #include <string.h> int main(){ char str[] = "https://www.cnblogs.com/wangyong123/articles/13689673.html"; char *p = str; // char *p = &str[0]; /* char *p = &str; 错误。一个是char指针,一个是数组地址。 char (*p)[] = &str; 错误。 char (*p)[] 与char (*p)[59]不是一个类型 char (*p)[59] = &str; 完美 */ int i; int len = strlen(str); // 方式一: %s printf("%s\n", str); // "https://www.cnblogs.com/wangyong123/articles/13689673.html" printf("\n"); // 方式二: p[i] for(i=0; i<len; i++) { printf("%c",p[i]); // "https://www.cnblogs.com/wangyong123/articles/13689673.html" } printf("\n"); // 方式三: *(p+i) for(i=0; i<len; i++) { printf("%c",*(p+i)); // "https://www.cnblogs.com/wangyong123/articles/13689673.html" } printf("\n"); return 0; }
除了字符数组以外,c语言还支持另外一种表示字符的方法,就是直接使用一个指针指向字符串 1. char *str = "https://www.cnblogs.com/wangyong123/articles/13689673.html"; 2. char *str; str = "https://www.cnblogs.com/wangyong123/articles/13689673.html";#include <stdio.h> #include <string.h> int main(){ char *str = "https://www.cnblogs.com/wangyong123/articles/13689673.html"; int i; int len =strlen(str); // 方式一: %s直接输出 printf("%s\n", str); // "https://www.cnblogs.com/wangyong123/articles/13689673.html" printf("\n"); // 方式二: str[i] for(i=0; i<len; i++) { printf("%c", str[i]); //"https://www.cnblogs.com/wangyong123/articles/13689673.html" } printf("\n"); // 方式三: *(str+i) for(i=0; i<len; i++) { printf("%c", *(str+i)); // "https://www.cnblogs.com/wangyong123/articles/13689673.html" } return 0; }#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { char arr1[] = "Wangyong"; char arr2[] = "Wangyong"; char *p1 = "Wangyong"; char *p2 = "Wangyong"; if (arr1 == arr2) { printf("arr1 == arr2\n"); } else { printf("arr1 != arr2\n"); // 用相同的常量字符串去初始化 // 不同的数组的时候就会开辟出不同的内存块。所以arr1和arr2不同 } if (p1 == p2) // p1 == p2 因为 p1 和 p2 指向的都是常量字符串,常量字符串在内存中不能修改, // 所以 p1 与 p2指向相同的地址,所以是 p1 == p2 { printf("p1 == p2\n"); } else { printf("p1 != p2\n"); } return 0; }
字符数组和使用一个指针指向字符串是非常相似的。那么这两种表示字符串的区别是什么?
1. 字符数组和指针字符串的区别是内存中的存储区域不一样,字符数组存储在全局数据区或栈区,指针指向的字符串存储在字符常量区。全局区和栈区的字符串(包括其它数据)有读取和写入的权限,而常量区的字符串只有读取权限,没有写入权限。
2. 内存权限不同导致一个明显的结果就是,字符数组在定义后可以读取和修改每个字符,而对于指针字符串来说,一旦被定义
后就只能读取而不能修改,任何对它的赋值都是错误的。
#include <stdio.h> int main() { char * str = "Hello World!"; str = "Wang Yong"; //正确,可以更改指针变量本身的指向 str[3] = 'N'; //错误,不能修改指向的字符串中的字符,指针字符串指向的数据存储在字符常量区,不能修改。 return 0; }在编程过程中,如果只涉及到对字符串的读取,那么字符数组和字符串常量都可以满足要求;如果有写入(修改)的操作,
那么只能使用字符数组,不能使用字符串常量。// 获取用户输入的字符串就是一个典型的写入操作,只能使用字符数组,不能使用字符串常量,请看下面的代码: #include <stdio.h> int main() { char str[30]; gets(str);// 正确 printf("%s\n", str); char *ptr; gets(ptr); //错误,指针指向的字符串存储在常量字符区,不能写入,键盘输入是写入(输入)操作。 printf("%s\n", str); return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int main() { int a = -10; char c = 'w'; int *pa = &a; // 数据类型完美吻合 char *pc = &a; // 可以存储但是会报警告 // 有没有一种数据类型可以接受任意类型的地址? void *po = &a; // 没有报错,没有警告, 没有具体类型的指针 // *po = 0; // 报错, 因为void*类型的指针解引用的时候不知道访问几个字节,所以不能进行解引用操作 po = &c; // 没毛病 return 0; }
10.2 野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
10.2.2 野指针形成原因
#include <stdio.h> int main() { int *p;//局部变量指针未初始化,默认为随机值 *p = 20; return 0; }
#include <stdio.h> int main() { int arr[10] = {0}; int *p = arr; int i = 0; for(i=0; i<=11; i++) { //当指针指向的范围超出数组arr的范围时,p就是野指针 *(p++) = i; } return 0; }
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放即使置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
#include <stdio.h> int main() { int *p = NULL; int a = 10; p = &a; if(p != NULL) { *p = 20; } return 0; }
1. 声明的时候有*,表示指针变量。 int * p;
2. *+地址,表示地址操作符。 *(p+i)
3. 数字*数字,表示乘法。 *2
4. 注释。 /* */
十二、指针题目练习
1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。 3. 除此之外所有的数组名都表示首元素的地址。
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { // 数组名是数组首地址,但有两处是例外 // 1. sizeof(数组名) // 2. &数组名 // 这两处代表整个数组 int a[] = { 1, 2, 3, 4 }; printf("%d\n", sizeof(a)); // 16 sizeof(数组名),计算数组的总大小,单位是字节 -- 16 printf("%d\n", sizeof(a + 0)); // 4 a是数组名,是首元素地址, a+0 还是首元素地址,地址韩式 4个字节 printf("%d\n", sizeof(*a)); // 4 a是数组名,是首元素地址, *a == 1就是首元素 是整形, sizeof(*a) == 4 printf("%d\n", sizeof(a + 1)); // 4 a是数组名,是首元素地址,a + 1和a+0意义是一样的,a+1代表第二个元素的地址 printf("%d\n", sizeof(a[1])); // 4 第二个元素大小 printf("%d\n", sizeof(&a)); // 4 此时 &a取出的是数组的地址,但是数组的地址也是地址,地址的大小就是 4 个字节 printf("%d\n", sizeof(*&a)); // 16 &a是数组的地址,*&a取数组地址之后又解引用访问数组,sizeof 计算的就是数组的阿晓,相当于 sizeof(a),所以是 16 printf("%d\n", sizeof(&a + 1)); // 4 &a是数组的地址, &a+1虽然地址跳过整个数组,但是还是地址 printf("%d\n", sizeof(&a[0])); // 4 第一个元素的地址 printf("%d\n", sizeof(&a[0] + 1)); // 4 第二个元素的地址 return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' }; printf("%d\n", sizeof(arr)); // 6 sizeof(数组名),计算的是数组的总大小 6个字节 printf("%d\n", sizeof(arr + 0)); // 4 arr是首元素的地址, arr + 0 还是首元素的地址 printf("%d\n", sizeof(*arr)); // 1 arr是首元素的地址, *arr == 'a' 占一个字节 printf("%d\n", sizeof(arr[1])); // 1 arr[1]是第二个字符 printf("%d\n", sizeof(&arr)); // 4 &arr虽然是数组的地址,但是还是地址 printf("%d\n", sizeof(&arr + 1)); // 4 &arr + 1 就是跳过整个数组,但是还是地址 printf("%d\n", sizeof(&arr[0] + 1)); // 4 &arr[0] + 1就是第二个元素的地址 printf("%d\n", strlen(arr)); // 随机值, 找不到 '\0' printf("%d\n", strlen(arr + 0)); // 随机值, arr+0与 arr是一样的 //printf("%d\n", strlen(*arr)); // 代码有问题 *arr == 'a' //printf("%d\n", strlen(arr[1])); // 代码有问题 printf("%d\n", strlen(&arr)); // 随机值, &arr 取得是数组的地址,也还是数组的首地址,和 strlen(arr) 和strlen(arr+0)的随机值应该是一样的 printf("%d\n", strlen(&arr + 1)); // 随机值, 比上面一行的随机值小 6 printf("%d\n", strlen(&arr[0] + 1)); // 随机值, 比上上面那个随机值小 1 return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { char arr[] = "abcdef"; // 自动添加 计算sizeof 添加'\0', strlen计算的时候查找'\0'截止,所以计算的时候不添加 printf("%d\n", sizeof(arr)); // 7 sizeof(arr) 计算的是数组的大小, 单位是 7 printf("%d\n", sizeof(arr + 0)); // 4 arr 代表首元素地址 + 0 还是首元素地址,所以计算的还是地址的大小 printf("%d\n", sizeof(*arr)); // 1 arr 是首元素地址, *arr == 'a' printf("%d\n", sizeof(arr[1])); // 1 arr[1] == 'b' printf("%d\n", sizeof(&arr)); // 4 &arr 虽然是数组的地址,还是还是地址, sizeof(&arr) == 4 printf("%d\n", sizeof(&arr + 1)); // 4 原理同上 printf("%d\n", sizeof(&arr[0] + 1)); // 4 &arr[0] + 1 也就是 &arr[1],还是地址 printf("%d\n", strlen(arr)); // 6 计算 strlen的时候遇到 '\0' 的时候停止, '\0'不计算 printf("%d\n", strlen(arr + 0)); // 6 arr+0 == arr // printf("%d\n", strlen(*arr)); // 代码有问题 *arr == 'a' 在ascii中是 97, 访问内存地址为 97的位置,非法访问,报错 // printf("%d\n", strlen(arr[1])); // 代码有问题, 原理同上 printf("%d\n", strlen(&arr)); // 6 &数组名 代表整个数组 &arr是数组的指针存放在数组指针, char(*p)[7] = &arr; strlen的参数 是 const char *,强行赋值没关系。 printf("%d\n", strlen(&arr + 1)); // 随机值, 因为跳过了整个数组,再往后查找,就随机找到 '\0' 才停止 printf("%d\n", strlen(&arr[0] + 1)); // 5 &arr[0] + 1 == &a[1] 往后数一共5个字符 return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { char *p = "abcdef"; printf("%d\n", sizeof(p)); // 4 p是一个地址,计算指针变量的大小 printf("%d\n", sizeof(p + 1)); // 4 p+1计算的是字符 'b' 的地址 printf("%d\n", sizeof(*p)); // 1 *p == 'a' printf("%d\n", sizeof(p[0])); // 1 p[0] == *(p + 0) == *p == 'a' printf("%d\n", sizeof(&p)); // 4 &p是地址,地址占据4个字节 printf("%d\n", sizeof(&p + 1)); // 4 还是地址 printf("%d\n", sizeof(&p[0] + 1)); // 4 &p[0] + 1 == p + 1 == 'b' printf("%d\n", strlen(p)); // 6 求 strlen 不算 '\0' printf("%d\n", strlen(p + 1)); // 5 p+1从'b'开始往后找'\0' // printf("%d\n", strlen(*p)); // 报错 // printf("%d\n", strlen(p[0])); // 报错 printf("%d\n", strlen(&p)); // 随机值 printf("%d\n", strlen(&p + 1)); // 随机值 printf("%d\n", strlen(&p[0] + 1)); // 5 &p[0] + 1 == 'b' return 0; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { int a[3][4] = { 0 }; printf("%d\n", sizeof(a)); // 48 3 * 4 * 4 == 48 printf("%d\n", sizeof(a[0][0])); // 4 printf("%d\n", sizeof(a[0])); // 16 a[0]相当于第一行的作为一维数组的数组名, sizeof(arr[0])把数组名单独放在sizeof内,计算的是第一行的大小 printf("%d\n", sizeof(a[0] + 1)); // 4 a[0]代表第一行首元素地址,其实就是就是第一行第一个元素的地址,所以 +1以后是第一行第二个元素的地址 printf("%d\n", sizeof(*(a[0] + 1))); // 4 *(a[0]+1)是第一行第二个元素的值,是int类型,所以是 4 printf("%d\n", sizeof(a + 1)); // 4 a是二维数组数组名,没有sizeof(数组名),也没有&(数组名),所以a是首元素地址, 而把二维数组看成一维护数组是时,二维数组的首元素是他的第一行。a+1 == &a[0]+1 == &a[1], 计算的是地址的大小 printf("%d\n", sizeof(*(a + 1))); // 16 *(a+1) == *(&a[1]) == a[1] 计算的是二维数组第二行第的大小 printf("%d\n", sizeof(&a[0] + 1)); // 4 &a[0]+1 == &a[1] 计算的是第二行的地址 printf("%d\n", sizeof(*(&a[0] + 1))); // 16 *(&a[0] + 1)) 是解引用第二行的一维数组,计算第二行的大小 printf("%d\n", sizeof(*a)); // 16 *a == *(&a[0]) a是首元素地址-第一行的地址, *a就是第一行,所以计算的就是第一行一维数组的大小, printf("%d\n", sizeof(a[3])); // 16 计算sizeof的时候内部的表达式并不会参与计算。 所以 a[3]也就是第四行数组的大小。 return 0; }