建设网站怎么制作,甘肃企业网站建设,如何做实体店的网站,企业服务专区C 进阶 — 指针的使用 
主要内容 
1、字符指针 
2、数组指针 
3、指针数组 
4、数组传参和指针传参 
5、函数指针 
6、函数指针数组 
7、指向函数指针数组的指针 
8、 回调函数 
9、指针和数组练习题 
前节回顾 
1、指针就是个变量#xff0c;用来存放地址#xff0c;地址唯一…C 进阶 — 指针的使用 
主要内容 
1、字符指针 
2、数组指针 
3、指针数组 
4、数组传参和指针传参 
5、函数指针 
6、函数指针数组 
7、指向函数指针数组的指针 
8、 回调函数 
9、指针和数组练习题 
前节回顾 
1、指针就是个变量用来存放地址地址唯一标识一块内存空间 
2、指针大小是固定的 4/8 个字节32 位平台/64 位平台 
3、指针有类型指针的类型决定了指针的 ± 整数的步长和指针解引用操作时的解释 
4、指针的运算 
一 字符指针 
在指针的类型中我们知道有一种指针类型为字符指针 char* 
//使用方式一
int main()
{char ch  w;char *pc  ch;*pc  x;return 0;
}//使用方式二
int main()
{const char* pstr  HELLO WORLD; //如何理解该行printf(%s\n, pstr);return 0;
}代码 const char* pstr  HELLO WORLD; 是把字符串 HELLO WORLD 首字符的地址放到了 pstr 中可不能理解成把字符串 HELLO WORLD 放到字符指针 pstr 里了 
练习题 
下面代码的最终输出是 
#include stdio.h
int main()
{char str1[]  HELLO WORLD;char str2[]  HELLO WORLD;const char *str3  HELLO WORLD;const char *str4  HELLO WORLD;if(str1  str2) //这里比较的是数组首地址printf(str1 and str2 are same\n);elseprintf(str1 and str2 are not same\n); //√if(str3  str4) //这里比较的是 STR 存放的地址printf(str3 and str4 are same\n); //√elseprintf(str3 and str4 are not same\n);return 0;
}这里 str3 和 str4 指向的是一个同一个常量字符串。C/C 会把常量字符串存储到单独的一个内存区域当几个指针指向同一个字符串的时候他们实际会指向同一块内存 
但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以 str1 和 str2 不同str3 和 str4 相同 
这里可以结合内存分配会更好理解不同的数组在栈区分配的内存空间是不同的而两个 str 指针在栈区的内存空间当然也不同可是它们内存空间的值相同指向常量区的同一个位置 
二 指针数组 
指针数组是一个存放指针的数组操作符 [] 的优先级大于 * 
//复习 下面的数组表示的意义
int* arr1[10];		 //整形指针数组
char *arr2[4]; 		 //一级字符指针数组
char **arr3[5];		 //二级字符指针数组三 数组指针 
3.1 数组指针定义 
数组指针是指针即指向数组的指针 
// p1, p2 分别是什么
int *p1[10];  //指针数组, 表示十个元素的整型指针数组
int (*p2)[10]; //数组指针, 表示指向十个整型元素数组的指针//[] 优先级要高于 * 号, 加上 () 保证结合律3.2  数组名和 数组名 
int arr[10]; 
arr 和 arr 分别的含义是 arr 是数组名数组名表示数组首元素的地址。那 arr 数组名是  
#include stdio.h
int main()
{int arr[10]  {0};printf(%p\n, arr);printf(%p\n, arr);return 0;
}打印的内容是一样的即值相同但它们代表的含义却并不相同 #include stdio.h
int main()
{int arr[10]  { 0 };printf(arr     %p\n, arr);printf(arr    %p\n, arr);printf(arr1   %p\n, arr1);printf(arr1  %p\n, arr1);return 0;
}//打印结果
arr     000000D344CFF7D8
arr    000000D344CFF7D8
arr1   000000D344CFF7DC
arr1  000000D344CFF800arr 和 arr虽然值相同但意义不同。arr 表示的是 数组的地址而不是数组首元素的地址 
本例中 arr 的类型是 int(*)[10] 是一种数组指针类型。数组的地址  1跳过整个数组的大小所以 arr1 相对于 arr 的差值是 40 
3.3 数组指针的使用 
//代码一
#include stdio.h
int main()
{int arr[10]  {1,2,3,4,5,6,7,8,9,0};int (*p)[10]  arr;//把数组 arr 的地址赋值给数组指针变量 preturn 0;
}//代码二
#include stdio.h//普通二维数组的打印
void print_arr1(int arr[3][5], int row, int col)
{for(int i0; irow; i){for(int j0; jcol; j)printf(%d , arr[i][j]);printf(\n);}
}void print_arr2(int (*arr)[5], int row, int col)
{for(int i0; irow; i){for(int j0; jcol; j)printf(%d , arr[i][j]);printf(\n);}
}int main()
{int arr[3][5]  {1,2,3,4,5,6,7,8,9,10};print_arr1(arr, 3, 5);//数组名 arr, 表示首元素的地址, 但二维数组的首元素是二维数组的第一行. 所以这里传递的 arr, 其实相当于第一行的地址, 是一维数组的地址. 可以数组指针来接收print_arr2(arr, 3, 5); //传递的是第一行的地址, 这里联想二维数组的内存分布会好理解return 0;
}练习解释下面代码的意思 
int arr[5];				//有五个元素的整型数组
int *parr1[10];			//有十个整型指针元素的数组
int (*parr2)[10];   	//指向十个整型元素数组的指针
int (*parr3[10])[5];	//有十个数组指针元素的数组(数组指针指向有五个元素的整型数组)四 数组参数、指针参数 
4.1 一维数组传参 
#include stdio.h
void test(int arr[]) {}     	// √
void test(int arr[10]) {}   	// √
void test(int *arr) {}      	// √
void test2(int *arr[20]) {} 	// √
void test2(int **arr) {}    	// √int main()
{int arr[10]  {0};   //数组int *arr2[20]  {0}; //指针数组test(arr);test2(arr2);
}第一个 test() 函数传参是一个数组第一个 test() 函数用一个没有元素个数的数组来接收没问题一维数组传参函数形参可以省略数组元素个数 
第二个 test() 函数加上了数组元素个数自然也没问题 
第三个 test() 函数用一级指针来接收一维数组也是没什么问题数组名就是数组首元素的地址 
第一个 test2() 函数参数是一个指针数组就是一个存储一级指针的数组。函数用一个相同结构的指针数组来接收没问题这里也可以省略数组的元素个数 
第二个 test2() 函数用一个二级指针来接收也可以因为数组名是首元素地址而数组里面元素存放的也是一个地址地址的地址用一个二级指针来接收没问题 
总结一下一维数组传参函数的形参用相同的结构来接收没问题数组元素个数可以省略函数的形参用指针来接收就需要考虑指针和地址之间的关系了 
4.2 二维数组传参 
void test(int arr[3][5]) {}   			//√
void test(int arr[][]) {}				//×
void test(int arr[][5]) {}				//√
void test(int *arr) {}					//√
void test(int* arr[5]) {}				//×
void test(int (*arr)[5]) {}				//√
void test(int **arr) {}					//×int main()
{int arr[3][5]  {0};test(arr);return 0;
}第一个 test() 函数的传参是一个二维数组第一个 test() 函数用相同结构的二维数组接收没问题 
第二个test() 函数用省略了元素个数的二维数组来接收二维数组初始化时可以省略行数不能忽略列数 
第三个 test() 函数省略了行数没有省略列数没问题 
第四个 test() 函数用一级指针来接收二维数组是可行的数组名是首元素地址地址用一级指针接收没问题 
第五个 test() 函数用一维指针数组来接收不行指针数组本质是数组用数组来接收数组需要用相同结构的数组 
第六个 test() 函数用数组指针来接收二维数组是可以的因为数组名是首元素地址就是第一行的地址相当于是一个一维数组的地址而数组指针也是一个一级指针可以用来接收数组的地址并且每一行有5个元素数组指针也是接收 5 个元素 
第七个 test() 函数用二级指针来接收二维数组不行因为数组名是首元素地址是第一行的地址而二级指针是需要接收地址的地址匹配不上 
4.3 一级指针传参 
#include stdio.h
void print(int *p, int sz)
{for(int i  0; i  sz; i){printf(%d\n, *(pi));}
}
int main()
{int arr[10]  {1,2,3,4,5,6,7,8,9};int *p  arr;int sz  sizeof(arr)/sizeof(arr[0]);print(p, sz); //一级指针 p传给函数return 0;
}//思考: 当一个函数参数部分为一级指针时, 函数能接收什么参数 ?
void test1(int *p) {} //test1 函数能接收什么参数
void test2(char* p) {} //test2 函数能接收什么参数//地址传参用一级指针
//数组名, 指针, 地址4.4 二级指针传参 
#include stdio.h
void test(int** ptr)
{printf(num  %d\n, **ptr); 
}
int main()
{int n  10;int*p  n;int **pp  p;test(pp);test(p);return 0;
}//思考: 当函数的参数为二级指针时, 可以接收什么参数 ?
void test(char **p) {}int main()
{char c  b;char*pc  c;char**ppc  pc;char* arr[10];test(pc);test(ppc);test(arr); //√return 0;
}//地址的地址传参用二级指针总结指针传参只需要判断是地址还是地址的地址只要是地址就可以用一级指针接收是地址的地址就可以用二级指针来接收。相反用一级指针来接收就需要传地址二级指针来接收就需要传地址的地址 
五 函数指针 
顾名思义就是一个指针指向函数 
#include stdio.h
void test() {}int main()
{printf(%p\n, test);  // 00007FF7AFAB1159printf(%p\n, test); // 00007FF7AFAB1159return 0;
}上述是 test 函数的地址 那如何保存函数地址  
// pfun1 和 pfun2 哪个可以存放 test 函数的地址
void (*pfun1)();
void *pfun2();// pfun1 可以, pfun1 先和 * 结合说明 pfun1 是指针, 指针指向的是一个函数指向的函数无参数返回值类型为 void函数指针经典代码 
C 陷阱和缺陷中提及该代码解释下述两行代码 
(*(void (*)())0)();  // 代码一
void ( *signal(int , void(*)(int)) )(int);  //代码二代码一 
void (*)() 中的 * 表明该类型为函数指针 (它指向的函数参数为空返回值为空)(void (*)()) 0 表示把 0 地址强转为上述的函数指针类型*(void (*)())0 表示对函数指针解引用 (使用函数指针的方式调用函数 (*ptr)() ) 
综上上述代码意思为把 0 地址强转为指向参数为空返回值为空类型的函数指针并进行函数调用 
代码二 
void(*)(int) 和上述一样是一个函数指针类型 (它指针的函数参数类型为 int返回值为空 )signal(int , void(*)(int)) 表明 signal 是一个函数参数一类型是 int参数二类型是函数指针void ( *signal(int , void(*)(int)) )(int); 表示 signal 的返回类型为 void(*)(int) 
综上上述代码意思为signal 是一个函数参数一类型为 int参数二类型是函数指针且类型为 void(*)(int)返回值的类型为 void(*)(int) 
代码二如何简化 
typedef void(*func_ptr)(int);
func_ptr signal(int, func_ptr);六 函数指针数组 
数组是一个存放相同类型数据的存储空间指针数组比如 int *arr[10]; /数组的每个元素是 int*。把一组函数地址存到一个数组中这个数组就叫函数指针数组 
函数指针数组的定义例如 int (*parr[10])(); 
parr先和 [] 结合说明 parr 是数组数组的内容是 int (*)() 类型的函数指针 
函数指针数组的用途转移表 
#include stdio.h
int add(int a, int b) { return a  b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return a / b; }int main()
{int x, y;int input  1;int ret  0;do{printf( *************************\n );printf(  1:add 2:sub \n );printf(  3:mul 4:div \n );printf( *************************\n );printf( 请选择 );scanf( %d, input);switch (input){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;case 0:printf(退出程序\n);breark;default:printf( 选择错误\n );break;}} while (input);return 0;
}将上述计算器例子用函数指针数组改写 
#include stdio.h
int add(int a, int b) { return a  b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return a / b; }int main()
{int x, y;int input  1;int ret  0;do{printf( *************************\n );printf(  1:add 2:sub \n );printf(  3:mul 4:div \n );printf( *************************\n );int(*arr[5])(int x, int y)  {0, add, sub, mul, div}; //转移表printf( 请选择 );scanf( %d, input);if (input  1  input  4){printf( 输入操作数 );scanf( %d %d, x, y);ret  (*arr[input])(x,y);}elseprintf( 输入有误\n );printf( ret  %d\n, ret);} while (input);return 0;
}七 指向函数指针数组的指针 
指向函数指针数组的指针是一个 指针。它指向一个数组数组的元素都是函数指针 
void test(const char* str)
{printf(%s\n, str);
}int main()
{//函数指针 pfunvoid (*pfun)(const char*)  test;//函数指针的数组 pfunArrvoid (*pfunArr[5])(const char* str);pfunArr[0]  test;//指向函数指针数组 pfunArr 的指针 ppfunArrvoid (*(*ppfunArr)[5])(const char*)  pfunArr;return 0;
}//在函数指针的数组的类型最里面加上 (*) 即可表明是指向它的指针类型八 回调函数 
回调函数就是一个通过函数指针调用的函数。把函数指针地址作为参数传递给另一个函数当这个指针被用来调用其所指向的函数时就称为回调 
回调函数不是由该函数的实现方直接调用而是在特定的事件或条件发生时由另外的一方调用的用于对该事件或条件进行响应 
演示一下 qsort 函数的使用 
#include stdio.h//qosrt 函数的使用者得实现一个比较函数
int int_cmp(const void * p1, const void * p2)
{return (*( int *)p1 - *(int *) p2);
}
int main()
{int arr[]  { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i  0;qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);for (i  0; i sizeof(arr) / sizeof(arr[0]); i)printf( %d , arr[i]);return 0;
}使用回调函数模拟实现 qsort采用冒泡的方式 
#include stdio.h
int int_cmp(const void * p1, const void * p2)
{return (*( int *)p1 - *(int *) p2);
}void _swap(void *p1, void * p2, int size)
{for (int i  0; i size; i) //循环 swap 的内存空间大小{char tmp  *((char *)p1  i);*(( char *)p1  i)  *((char *) p2  i);*(( char *)p2  i)  tmp;}
}void bubble(void *base, int count , int size, int(*cmp )(void *, void *))
{for (int i  0; i count - 1; i){for (int j  0; j count - i - 1; j){if (cmp ((char *) base  j * size , (char *)base  (j  1) * size)  0) //转成 char * 指针, 进行指针偏移{_swap(( char *)base  j * size, (char *)base  (j  1) * size, size);}}}
}int main()
{int arr[]  { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };//char *arr[]  {aaaa,dddd,cccc,bbbb};int i  0;bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);for (i  0; i sizeof(arr) / sizeof(arr[0]); i)printf( %d , arr[i]);return 0;
}九 指针和数组练习题 
通常情况下数组名就是数组首元素的地址但是有两个特殊情况 
1、sizeof(数组名) —— 表示整个数组计算的是整个数组的大小单位是字 
2、  数组名 —— 数组名表示整个数组取出的是整个数组的地址 
数组练习题 
一维数组 
int a[]  {1,2,3,4};
printf(%d\n,sizeof(a));		
//数组有 4 个元素每个元素的类型都是 int, int 类型占 4 个字节, 所以打印出来的值是 16printf(%d\n,sizeof(a0));
//(数组名  0) 并不是单独的数组名, 那么此时表示的就不是整个数组(数组名  0) 表示的就是首元素的地址, 所以打印出来的值是 8 (64 位下)printf(%d\n,sizeof(*a));		
//不符合两种特殊情况, 所以 a 是首元素地址, *a就是首元素. 因为元素类型是 int, 所以打印值是 4printf(%d\n,sizeof(a1));
//sizeof(a1) 中的 a 是数组首元素地址, 因为  1 跳过 1 个整型. 所以a  1 是第二个元素的地址, 所以打印值是 8 (64位下)printf(%d\n,sizeof(a[1]));	
//a[1] 表示第二个元素, 所以它的大小就是 4 个字节printf(%d\n,sizeof(a));		
//sizeof(a) 中 a 是数组的地址, 数组的地址也是一个地址, 只要是地址它的大小就是 8 个字节 (64 位下)
//数组的地址和首元素的地址只在类型上存在差别, 数组的地址为 int(*)[4],首元素地址的类型为 int *, 类型的差异仅仅决定了 - 操作跳过几个地址. 因为它们都是指针所以 sizeof 的值都是一样的printf(%d\n,sizeof(*a));
//* 和  符号相互抵消, 该代码等价于 sizeof(a), 所以打印值是 16printf(%d\n, sizeof(a  1));
//a  1 跳过了整个数组, 指向了数值最后一个元素的下一个元素. 因为指针只是指向了最后一个元素的下一个元素, 并没有解引用, 所以不存在指针的越界访问. a  1 表示的仍然是一个指针, 打印值是 8(64 位下)printf(%d\n,sizeof(a[0]));
//a[0] 表示取出数组首元素地址, 所以打印值应该是 8(64位)printf(%d\n,sizeof(a[0]1));
//a[0]  1 表示数组第二个元素地址, 所以值是 8(64位)字符数组 
char arr[]  {a,b,c,d,e,f};
printf(%d\n, sizeof(arr));
//数组名单独放到 sizeof 内部, 计算整个数组的大小 单位字节, 所以打印值应该是 6
printf(%d\n, sizeof(arr0));
//arr0 不满足两种特殊情况, arr 表示首元素地址, 所以打印出来的值应该是 8 (64位)
printf(%d\n, sizeof(*arr));
//*arr 表示对数组首元素地址进行解引用, 所以表示的是首元素, 大小为 1 个字节
printf(%d\n, sizeof(arr[1]));
//arr[1] 是第二个元素, 大小是 1 个字节
printf(%d\n, sizeof(arr));
//arr 表示整个数组的地址, 数组的地址也是一个地址, 所以打印值是 8
printf(%d\n, sizeof(arr1));
//arr  1 表示跳过整个数组, 指向数组最后一个元素下一个元素的地址, 所以打印值是 8
printf(%d\n, sizeof(arr[0]1));
//arr[0]1 表示数组第二个元素地址, 所以打印值是 8printf(%d\n, strlen(arr));
//arr 是首元素地址, strlen 在求长度时只有遇到 \0 才会结束. 原数组中没有 \0 , 所以打印值是一个随机值
printf(%d\n, strlen(arr0));
//arr0 表示数组首元素地址, 原数组中没有 \0 , 所以打印值是一个随机值
printf(%d\n, strlen(*arr));
//*arr 表示数组首元素 a, 因 strlen 必须传入地址(入参是指针类型) , 字符 a ASCII 码值是 97. 此时 strlen 函数会把 97 当作一个地址, 那么在打印时会存在多种情况 : 1. 打印随机值 2. 访问到不允许访问的数据, 程序崩溃
printf(%d\n, strlen(arr[1]));
//arr[1] 表示第二个元素 b, 其余同上
printf(%d\n, strlen(arr));
//arr 表示取出整个数组的地址, 数组的地址也是从数组的第一个元素的地址开始的, 此时打印值是一个随机值
printf(%d\n, strlen(arr1));
//arr  1 指向数组最后一个元素下一个元素的地址, 得到一个随机值, 但与第一行打印中的随机值存在差异 (随机值少 6, 因为跳过了 6 个元素)
printf(%d\n, strlen(arr[0]1));
//arr[0]  1 表示数组第二个元素地址, 从 b 开始向后统计的, 打印值是一个随机值 (比第一行打印的随机值小 1)char arr[]  abcdef;
printf(%d\n, sizeof(arr));
//arr 取出的是数组中所有的元素, 所以打印值是 7
printf(%d\n, sizeof(arr0));
//arr  0 中 arr 表示数组首元素, arr  0 表示数组首元素地址, 所以打印值是 8
printf(%d\n, sizeof(*arr));
//*arr 表示数组首元素, 大小是一个字节
printf(%d\n, sizeof(arr[1]));
//arr[1] 表示第二个元素, 大小是一个字节
printf(%d\n, sizeof(arr));
//arr 表示取出的是数组的地址, 是地址就是 8 个字节
printf(%d\n, sizeof(arr1));
//arr  1 表示跳过了整个数组, 它还是一个地址, 是地址就是 8 个字节
printf(%d\n, sizeof(arr[0]1));
//arr[0]  1 表示的是第二个元素的地址, 大小是 8 个字节
printf(%d\n, strlen(arr));
//取出的是数组首元素地址, 所以打印值是 6
printf(%d\n, strlen(arr0));
//取出的是数组首元素地址, 所以打印值是 6
printf(%d\n, strlen(*arr));
//取出的是首元素 a, a 的 ASCII码值是 97此时 strlen 会把 97 当作一个地址, 所以打印值是一个随机值或报错
printf(%d\n, strlen(arr[1]));
//取出的是首元素 b, b 的 ASCII码值是 98此时 strlen 会把 98 当作一个地址, 所以打印值是一个随机值或报错
printf(%d\n, strlen(arr));
//取出来的是数组的地址, 数组的地址也是首元素的地址, 所以打印值是 6
printf(%d\n, strlen(arr1));
// 1 跳过了整个数组, 此时指向的是数组最后一个元素下一个元素的地址, 所以打印值是一个随机值
printf(%d\n, strlen(arr[0]1));
//第二个元素的地址, 所以打印值是 5char *p  abcdef;
printf(%d\n, sizeof(p));
//p 是一个指针变量, 是首元素地址. 计算的就是一个指针变量的大小, 所以打印值是 8
printf(%d\n, sizeof(p1));
//是第二个元素的地址, 所以打印值是 8
printf(%d\n, sizeof(*p));
//p 类型是 char*, *p 是 char 类型, 所以打印值是 1
printf(%d\n, sizeof(p[0]));
//表示首元素 a, 打印值是 1
printf(%d\n, sizeof(p));
//表示一个二级指针变量, 所以打印值是 8
printf(%d\n, sizeof(p1));
//表示一个二级指针变量, p  1 表示跳过 p 指针变量后的地址, 所以打印值是 8
printf(%d\n, sizeof(p[0]1));
//表示数组第二个元素地址, 所以打印值是 8
printf(%d\n, strlen(p));
//取出的是首元素地址, 所以打印值是 6
printf(%d\n, strlen(p1));
//取出的是第二个元素地址, 所以打印值是 5
printf(%d\n, strlen(*p));
//*p 取出的是第一个元素, a 的 ASCII 码值为 97, strlen 会把 97 当作一个地址, 所以打印值是一个随机值或报错
printf(%d\n, strlen(p[0]));
//*p 取出的是第一个元素, a 的 ASCII 码值为 97, strlen 会把 97 当作一个地址, 所以打印值是一个随机值或报错
printf(%d\n, strlen(p));
//表示一个二级指针变量, 所以打印值是一个随机值
printf(%d\n, strlen(p1));
//表示一个二级指针变量所以打印值是一个随机值
printf(%d\n, strlen(p[0]1));
//表示第二个元素地址, 所以打印值是 5二维数组 
int a[3][4]  {0};
printf(%d\n,sizeof(a));
//二维数组的数组名表示数组的大小, 打印值是 48
printf(%d\n,sizeof(a[0][0]));
//表示第一行的第一个元素, 所以打印值是 4
printf(%d\n,sizeof(a[0]));
//表示第一行所有元素, 所以打印出值是 16 (a[0] 是第一行的数组名, 数组名单独放到 sizeof 内部, 计算是第一行数组的总大小)
printf(%d\n,sizeof(a[0]1));
//数组名没有单独放到 sizeof 内部, 所以数组名 a[0] 就是数组首元素 a[0][0] 的地址, 1 后是 a[0][1] 的地址, 所以打印值是 8
printf(%d\n,sizeof(*(a[0]1)));
//表示第一行的第二个元素, 所以打印值是 4
printf(%d\n,sizeof(a1));
//数组名没有单独放到 sizeof 的内部, a 表示数组首元素地址是二维数组的首元素的地址, 也就是第一行的地址(a[0]). 1 跳过一行就是第二行的地址, 是一个数组指针变量, 所以打印值是 8
printf(%d\n,sizeof(*(a1)));
//表示 a[1] 第二行所有元素, 所以打印值是 16
printf(%d\n,sizeof(a[0]1));
//a[0] 是第一行数组名, a[0] 取出的就是数组的地址, 第一行的地址. 所以 1 就是第二行的地址, 所以打印值是 8
printf(%d\n,sizeof(*(a[0]1)));
//表示 a[1] 第二行所有元素, 所以打印值是 16
printf(%d\n,sizeof(*a));
//a 作为数组名没有单独放到 sizeof 内部, a 表示数组首元素地址, 是二维数组首元素的地址, 也就是第一行的地址, *a 就是第一行所有元素, 所以打印值是 16
printf(%d\n,sizeof(a[3]));
//a[3] 表示第四行数组名, 因为 sizeof 并不会计算, 也没有访问. 所以不存在越界访问, 所以打印值是 16. a[3]无需真实存在, 仅仅是通过类型的推断算出的长度总结 数组名的意义 
1、sizeof(数组名)这里的数组名表示整个数组计算的是整个数组的大小。 
2、数组名这里的数组名表示整个数组取出的是整个数组的地址。 
3、除此之外所有的数组名都表示首元素的地址 
指针练习题 
以下程序的结果是什么 
int main()
{int a[5]  { 1, 2, 3, 4, 5 };int *ptr  (int *)(a  1); //a 表示的是数组的地址printf( %d,%d, *(a  1), *(ptr - 1)); //2 5return 0;
}*(a1) 输出 2 a 数组名为数组首元素地址再加一解引用就是数组第二位元素 
*(ptr - 1) 输出5a 是取整个数组的地址然后加一跳过了数组 aptr 指向数组元素 5 的后面再强制转换成 int*本来类型应该为 int(*)[5]  然后 ptr - 1 解引用就指向数组元素 5 
struct Test
{int Num;	  //4char *pcName; //4short sDate;  //2char cha[2];  //2short sBa[4]; //8
}*p;
//假设 p 的值为 0x100000, 如下表表达式的值分别为多少 ?
//已知结构体 Test 类型的变量大小是 20 个字节
int main()
{printf(%p\n, p  0x1);  // 20 - 2 进制 1100 - 16 进制  14//0x100014printf(%p\n, (unsigned long)p  0x1);//0x100001printf(%p\n, (unsigned int*)p  0x1);//0x100004return 0;
}考察 指针 ± 整数结构体指针 1 会跳过一个结构体所以 1 就会跳过20 个字节。 p  0x1 表示 0x100000  20  0x100014因为打印的值是一个地址所以要补满8位所以打印出来的值应该是 00100014 
在 (unsigned long) p  0x1 中p 被强制类型转换为 unsigned long 类型此时 p 就不是一个指针变量了所以此时整型值 1 就是 1 本身所以打印出来的值应该是 00100001 
在 (unsigned int*)p  0x1 中p 被强制类型转换为 unsigned int* 类型所以1 就会跳过 4 个字节所以 p  0x1 就表示 0x100000  4  0x100004因为打印的值是一个地址要补满 8 位所以打印值是 00100004 int main()
{int a[4]  { 1, 2, 3, 4 };int *ptr1  (int *)(a  1); //a 整个数组的地址int *ptr2  (int *)((int)a  1); //数组首元素地址值转整型1 printf( %x,%x, ptr1[-1], *ptr2); //%x 用于格式化的输出符号, 以十六进制形式输出整数//4, 2000000(显示数据时也需要倒着读)return 0;
}数组首元素地址值转整型  1 在小端机器上以十六进制显示 
01 00 00 00 02 00 00 00 
*ptr2 的内容就是 00 00 00 02 这部分显示数据时也需要倒着读即为 2000000 
#include stdio.h
int main()
{int a[3][2]  { (0, 1), (2, 3), (4, 5) };int *p;p  a[0]; //a[0] 是第一个行的数组名, 数组名表示首元素的地址, 即 a[0][0] 的地址printf( %d, p[0]); //*(p0)  *preturn 0;
}注意第一行代码中的二维数组并不是如下形式 
int a[3][2]  { {0, 1}, {2, 3}, {4, 5} }; 
它是用括号连接起来的表示的是一个逗号表达式逗号表达式从左向右依次计算最后一个计算的值就是表达式的取值所以数组的真实情况应该如下 
int a[3][2]  { 1,3,5 }; 
因为数组是三行两列的所以数据 1 3 放到第一行数据 5 放到第二行第一列其它的三个位置上放的都是0 。因为 a[0] 是第一行的数组名数组名表示首元素的地址其实就是 a[0 ][0] 的地址 p[0]  *(p0)  *p 所以打印值是 1 
int main()
{int a[5][5]; //二维数组, 5 行 5 列int(*p)[4];p  a; //a 表示 a[0] , 类型为 int(*)[5]printf( %p,%d\n, p[4][2] - a[4][2], p[4][2] - a[4][2]); // p[4][2]  p  4 后再找第三个元素return 0;
}首先创建了一个 5 行 5 列的数组再创建了一个数组指针变量p 指向的是 4 个整型元素的地址 
接着进行了 p[4][2] - a[4][2] , p[4][2] - a[4][2] 两个操作指针-指针得到的是两个指针之间的元素的个数 
分析一下a 的类型是 int(*)[5]p 的类型是 int(*)[4] 
当把 a 赋予 p 时两者的首地址都是指向 a 数组中的第一行的第一个元素两者会有类型的差异所以 ± 整数二者跳过的字节数不同 
a 每次 1 跳过的是 5 个整型而 p 每次 1 跳过的是 4 个整型我们画图分析如下 由图可知两个指针相减得到的值是 -4 所以 %d 打印出来的值是 -4  
而 %p 是打印地址 -4 在内存中是以补码的形式存放的 
-4 的原码为10000000000000000000000000000100 
-4 的补码是111111111111111111111111111111111100 
所以 %p 此时就把 -4 的补码当作一个地址打印出来把它的值换算成 16 进制得到的是FFFFFFFC 
所以打印值是 FFFFFFFC 和 -4 
int main()
{int aa[2][5]  { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int *ptr1  (int *)(aa  1); //aa  1 整个数组int *ptr2  (int *)(*(aa  1)); //a[1][0]  6printf( %d,%d, *(ptr1 - 1), *(ptr2 - 1));return 0;
}*(aa1) 等价于 aa[1]aa[1] 是第二行的数组名因为数组名表示的是首元素的地址所以 aa[1]  aa[1][0] 
由图可知打印值分别是 10 和 5 
#include stdio.h
int main()
{char *a[]  {work,at,alibaba};char**pa  a;pa; //pa[1]printf(%s\n, *pa); return 0;
}a 是一个字符指针数组数组里面一共有三个元素数组的每个元素都是 char* 类型。因为二级指针变量 pa 被赋予了 aa 是一个数组名表示的就是首元素的地址 pa 即为 pa[1] 因此打印结果是 at 
int main()
{char *c[]  {ENTER,NEW,POINT,FIRST};char**cp[]  {c3,c2,c1,c};char***cpp  cp;printf(%s\n, **cpp);printf(%s\n, *--*cpp3);printf(%s\n, *cpp[-2]3);printf(%s\n, cpp[-1][-1]1);return 0;
}题目比较复杂试着画图像来分析 因为指针优先级的问题 cpp 会率先进行此时 cpp 的指向就会改变 此时*cpp 拿到的值就是c2所以**cpp 表示的值就是*(c2)此时打印出来的值就是 POINT 
第二个代码* -- *  cpp  3 
这个代码中的优先级是最低的所以应该先计算 cpp 
因为上一个代码已经进行了 cpp 的操作指向了c2此时在进行  操作指向的应该是 c1  
再依照优先级顺序进行解引用操作此时拿到的是c1原代码就可以转化为*--(c1)3 
接下来应该要执行 – 操作因为 – 的对象是 c1所以在执行完–操作以后c1的值会变成 c 现在的原代码相当于 *c3所以*c3打印出来的值应该是 ER 
第三个代码* cpp [-2]  3 
先把代码转换一下* *(cpp-2)  3 
应该先算(cpp-2)此时应该拿到的是 c3再对 (c3) 解引用拿到的是 FIRST 处的地址再进行 3 操作所以打印出来的值应该是 ST 
第四个代码cpp[-1][-1]  1 
再来把代码转换一下*(*(cpp - 1) - 1)  1 
此时的逻辑和之前的代码一模一样所以打印值是 EW