笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)

? 写在前面 大家对于指针恐怕都不陌生!
没学过C语言那也一定听过指针吧,指针是C最强的优势,学不明白也就成了劣势!大家不必害怕,指针并没有那么恐怖,掌握了指针,让你的C语言更上一层楼!
bug郭和你一起将指针进阶学习一遍,一起加油!
【笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)】笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片


目录点击跳转

  • :alarm_clock: 写在前面
  • :zzz: 本章介绍
  • 字符指针
  • 数组指针
    • 数组指针的使用
  • 数组参数、指针参数
    • 一维数组传参
    • 二维数组传参
    • 一级指针传参
    • 二级指针传参
  • 函数指针
  • 函数指针数组
  • 指向函数指针数组的指针
  • 回调函数
    • `qsort`函数的使用:
    • `my_qsort`函数模拟实现
  • 指针和数组面试题解
    • 指针笔试题

本章介绍 可能有伙伴就要问了,咋一来就进阶指针!
不要慌问题不大,bug郭之前就写个一篇博客,介绍指针基础知识!
有兴趣的伙伴可以点击查看C语言指针,楼下大爷都能学会的小细节(和bug郭一起学C系列),建议收藏!
大家都复习完了指针基础吧,那我们就开始指针进阶的学习吧!
指针基础的一些概念:
  1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  3. 指针是有类型,指针的类型决定了指针的+-
  4. 整数的步长,指针解引用操作的时候的权限。
  5. 指针的运算。
本章重点
6. 字符指针
7. 数组指针
8. 指针数组
9. 数组传参和指针传参
10. 函数指针
11. 函数指针数组
12. 指向函数指针数组的指针
13. 回调函数
14. 指针和数组面试题的解析
字符指针 字符指针顾名思义就是一个指针变量,指针指向的空间存放的是一个字符!
char* 字符指针
//基本用法 int main() {char ch = 'c'; char* pc = &ch; *pc = 'w'; return 0; }

这种基本的用法,bug就不介绍了,相信大家都会!
//进阶 int main() { char* pstr = "abcdef"; //pstr字符指针存了字符串,第一个字符(a)的地址 printf("%s",pstr); return 0; }

代码char* pstr = "abcdef"; 特别容易让我们以为是把字符串abcedf放到字符指针pstr里了,但是本质是把字符串abcdef 首字符的地址放到了pstr中。
笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

我们可以知道通过字符指针pstr我们可以找到字符串abedef
为啥我们不直接创建一个字符串变量,而要用这种方式,有何不同呢?
//测试 #include int main() {char str1[] = "hello world."; char str2[] = "hello world."; char *str3 = "hello world."; char *str4 = "hello world."; if(str1 ==str2) printf("str1 and str2 are same\n"); else printf("str1 and str2 are not same\n"); if(str3 ==str4) printf("str3 and str4 are same\n"); else printf("str3 and str4 are not same\n"); return 0; }

输出结果
笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

可以看到,字符数组str1str2不相同,因为str1str2是数组名,而数组名就是第一个数组的地址。str1str2分别开辟了两个数组空间,只不过它们存放的内容一样而已!
str3str4它们都是指向的同一块空间,因为它们指向的是字符串常量hello world.
这里str3str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,
当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1str2不同,str3str4不同。
笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

数组指针 数组指针,我们首先要明确的就是,数组指针是指针而不是数组,它是指向数组的指针!
//判断数组指针 int* arr1[3]; //指针数组 int (*arr2)[3]; //数组指针

我们之前学过C语言操作符,建议收藏,我们知道操作符[]的优先级高于*
所以arr2是数组指针,而arr1是指针数组。
int (arr2*)[3] : arr2是一个指针,指向的对象是整型数组,数组的元素个数为3
数组指针的使用
#include int main() { int arr[4] = { 1,2,3,4 }; int(*parr)[4] = &arr; //数组指针存放数组arr的地址! return 0; }

大家肯定很少见代码这么写吧,数组指针很少这样使用!
我们已经知道了数组名就是,数组的首元素地址,而取地址数组名是数组的地址 。
&arrarr有啥区别呢?
#include int main() { int arr[4] = { 1,2,3,4 }; int(*parr)[4] = &arr; printf("arr :%p\n",arr); printf("&arr:%p\n", &arr); return 0; }

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片
居然都是第一个元素的地址!
但是我们知道,指针的类型决定了指针加减的步长!
#include int main() { int arr[4] = { 1,2,3,4 }; int(*parr)[4] = &arr; printf("arr :%p\n",arr); printf("&arr:%p\n", &arr); printf("arr+1 :%p\n", arr+1); //整型指针加1,加一个整型类型大小 printf("&arr+1:%p\n", &arr+1); //数组指针加1,加一个数组类型大小 return 0; }

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片
可以看到,数组指针和首元素地址,指针的类型不同
数组名arr:指针类型是整型 指针加减1,步长为整型大小(4bit)
&数组名:指针类型是数组 指针加减1,步长为数组大小(16bit)
数组指针正确使用
#include void print_arr1(int arr[3][5], int row, int col) {int i = 0; for(i=0; i

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片
学了数组指针,是不是发现有点懵了!
//捋一捋 int *arr1[3]; //指针数组 //数组个数是3,元素是int指针类型的数据 int (*arr2)[3]; //数组指针 //指针,指向数组,且数组的类型是int类型,且元素个数为3 int* (*arr3)[3]; //数组指针 //指针,指向数组,数组元素是int*类型,且元素个数为3 int (*arr4[3])[3]; //数组指针指针 //指针,指向一个数组指针,数组指针的类型是int(*) [3] 指向数组且为为int类型,元素个数为3 ......

就捋到吧,再捋下去就更懵了,兄弟们慢慢学,你可以了的!
数组参数、指针参数 在写代码的时候难免要把数组或者指针传给函数,那函数的参数该如何设计呢?
一维数组传参
#include void test(int arr[])//ok? { }//int arr[] 接收就是以int * arr形式接收,因为*arr等价与 arr[] void test(int arr[10])//ok? { }//int arr[10] 同上在形参中都是一个整型指针,形参中的数组长度无意义 void test(int* arr)//ok? { } //整型指针接收数组名就是首元素地址也就是整型指针 void test2(int* arr[20])//ok? { } //int* arr[20]等价于 int* arr[]等价于int**arr 即二级指针 //而实参就是一个指向整型指针的指针也就是二级指针 void test2(int** arr)//ok? { } //二级指针接收 int main() {int arr[10] = { 0 }; int* arr2[20] = { 0 }; test(arr); test2(arr2); }

二维数组传参
void test(int arr[3][5])//ok? { }//二维数组传参二维数组接收 void test(int arr[][])//ok? { } //error 不知道二维数组中一维数组中元素个数 void test(int arr[][5])//ok? { } //可以省略行不能省略列 //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。 //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。 //这样才方便运算。 void test(int *arr)//ok? { } //error 二维数组的数组名就是首元素地址,即一维数组的地址, // 也就是数组指针,应该用数组指针接收 void test(int* arr[5])//ok? { }//error,指针数组,实参是数组指针 void test(int (*arr)[5])//ok? { }//实参为数组指针与形参类型相同 void test(int **arr)//ok? { } //error int main() {int arr[3][5] = { 0}; test(arr); }

我们来总结一下!
  • 二维数组的数组名就是首元素地址,而二维数组的元素就是一维数组,所以数组名的类型就是数组指针。
  • 当二维数组数组名传参,形参接收时,数组的行可以省略,列不能省略,如果省略了列,我们就无法知道当指针加减跳过几个字节。
    笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
    文章图片
一级指针传参
#include void print(int *p, int sz)//一级指针传参,一级指针接收 {int i = 0; for(i=0; i; i++) {printf("%d\n", *(p+i)); } } //void print(int p[],int sz) //数组接收,也即一级指针接收,不提倡这样写 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); return 0; }

思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
//以int型指针为例 void test(int* p) {} int main() { int x=0; int* px=&x; int arr[10]; test(&x); //整型地址 test(px); //一级指针 test(arr); //一维数组名,即首元素地址,int* return 0; }

二级指针传参
void test(char** p ) {} int main() { char ch = 'c'; char* pc = &ch; char* *ppc = &pc; char* arr[3]; test(&pc); //一级指针的地址,即二级指针 test(ppc); //二级指针 test(arr); //数组名,首元素地址,首元素为一级指针,所以为二级指针 return 0; }

思考:
当函数的参数为二级指针的时候,可以接收什么参数?
其实和上面一样,你们可以思考一下!
函数指针 首先看一段代码:
#include void test() {printf("hehe\n"); } int main() {printf("%p\n",test); //函数名 就是函数地址 printf("%p\n", &test); //&函数名 也是函数地址return 0; }

运行结果
笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片
那么如何将test()函数指针保存起来呢?
void test() { printf("hehe\n"); } //下面pfun1和pfun2哪个有能力存放test函数的地址? void (*pfun1)(); //函数指针类型 void *pfun2(); //函数,函数的返回值是void*

函数指针类型
指针都是有类型的
整型指针 int*
数组指针 int (*)[]
函数指针 返回值 (*)(参数....)
#include int Add(int x, int y) { return x + y; } int main() { int (*pf)(int, int) = Add; int sum = (*pf)(3, 5); //对函数指针解引用 printf("sum = %d", sum); sum = pf(3, 5); //因为 Add和&Add相同,即Add等价于 pf printf("sum = %d", sum); return 0; }

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片
有趣的代码
//代码1 (*(void (*)())0)(); //void (*)()为函数指针 //(void (*)())0 将0强制类型抓换成函数指针的地址 //(*(void (*)())0)() *地址,调用0地址处的这个函数 //函数的返回值空,参数为空

//代码2 void (*signal(int , void(*)(int)))(int); //void(*)(int) 函数指针,返回值void,参数int //void (*signal(int , void(*)(int)))(int) // signal是函数名 //返回值是void(*)(int) // 参数int 和函数指针 void(*)(int) //这是一个函数的声明

当我们看到代码2很难看懂这个代码!
可以简化吗?
void (*signal(int , void(*)(int)))(int); //既然这个函数的返回值类型是 void(*)(int) //那我们可以写成 // void(*)(int) signal(int , void(*)(int)); //但是这样会语法错误 error

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片
函数指针类型重命名
笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片
简化
typedef void(*ptr_t) (int); //正确的类型重命名 ptr_t signal(int, ptr_t); //简化

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

上面的代码出自《C陷阱和缺陷》
有兴趣的伙伴可以尝试阅读!
函数指针数组 数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组
比如:
int* arr[10]; //整型指针数组

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[10]])(); // int *parr2[10](); int (*)() parr3[10];

答案是:parr1 ·parr1· 先和[] 结合,说明parr1是数组,数组的内容是什么呢? 是int (*)()类型的函数指针。
函数指针数组的用途:
转移表
例子:(计算器)
#include 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 (*pf[4])(int, int) = { add,sub,mul,div }; int sz = sizeof(pf) / sizeof(pf[1]); int i = 0; for (i = 0; i < 4; i++) {//实现5和3的加减乘除 printf("%d\n", pf[i](5,3)); } return 0; }

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

//计算器优化 #include void menu() {printf("*************************\n"); printf("1:add2:sub\n"); printf("3:mul4:div\n"); printf("*************************\n"); printf("请选择:"); } 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; } void Calc(int (*pf)(int ,int)) {int x, y; printf("请输入两个操作数\n"); scanf("%d%d", &x, &y); printf("%d\n", pf(x, y)); } int main() {int x, y; int input = 1; int ret = 0; int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表 do {menu(); //菜单 scanf("%d", &input); switch (input) {case 1: Calc(add); break; case 2: Calc(sub); break; case 3:Calc(mul); break; case 4:Calc(div); break; case 0: printf("退出\n"); break; default: printf("输出有误,请重新输入\n"); break; } } while (input); return 0; }

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

指向函数指针数组的指针 看到这个指针是不是感觉头都大了,没事我们慢慢分析!
int* arr[3]; //整型指针数组 int*(*parr)[3]=&arr; //整型指针数组的地址放入parr //parr就是指向整型指针数组的指针 //而我们只需要将整型指针换成函数指针就ok了 int(*pf)(int,int); //函数指针 int(*pfarr[3])(int,int); //函数指针数组 int(*(*ppfarr)[3])(int,int)=&pfarr; //&pfarr //ppfarr是指针,指针指向一个数组,数组的类型为函数指针 //元素个数为3,函数的返回值为int,参数为int

博主就不带大家套娃了,就学到这里了!
回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
刚刚的计算器优化后就是回调函数,依赖于函数指针。
我们把加减乘除函数的函数指针,传参的形式,传给另一方调用,回调该加减乘除的函数,所以为回调函数!
qsort函数的使用: 笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

这是MSDN中对qsort函数的解释!
我给大家介绍一下!
qsort
作用
Performs a quick sort.
qsort是快速排序函数库 and 中,我们可以通过qsort函数实现任意类型数据的排序!
函数声明
void qsort( void *base, size_t num, size_t width, int (__cdecl *compare )(const void *elem1, const void *elem2 ) );
返回值
void
参数
void *base
待排序的内容的首地址
size_t num
待排序元素的个数
size_t width
待排序元素的大小
int (__cdecl *compare )(const void *elem1, const void *elem2 )
自己定义一个排序的方式,例如一个结构体数组,结构体元素,按照结构体里的什么类型的数据的类型排序,所以需要自己定义一个campare排序方式!
然后将函数指针传参到qsot中!
//qsort函数使用实例 #include struct peo {char name[10]; char numb[12]; }; //自己定义一个排序的方式 int compare_char(const void* e1, const void* e2) { return strcmp(((struct peo*)e1)->numb, ((struct peo*)e2)->numb); //按照peo中numb排序 }int main() { //创建一个结构体数组 struct peo peo1[3] = { { "张三","15779770910"},{ "李四","17370126640"},{ "王五","12669781134"} }; int sz = sizeof(peo1) / sizeof(peo1[1]); qsort(peo1, sz, sizeof(peo1[1]), compare_char); for (int i = 0; i < sz; i++) {printf("%s\t%s\n",peo1[i].name,peo1[i].numb); } return 0; }

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片
看完qsort函数使用演示,是不是学会了。
my_qsort函数模拟实现 模仿qsort的功能实现一个通用的冒泡排序!
我们通过qsort函数的使用,我们尝试一下模拟实现qosrt函数。
//模拟实现qsort //模仿qsort的功能实现一个通用的冒泡排序//void qsort(void* base, size_t num, size_t width, //int(__cdecl* compare)(const void* elem1, const void* elem2)); #include typedef struct student { char name[5]; int score; }Student; int compare_score(const void*e1,const void* e2) { return ((Student*)e1)->score - ((Student*)e2)->score; } int compare_name(const void* e1, const void* e2) { return strcmp(((Student*)e1)->name, ((Student*)e2)->name); } void swap(void* e1, void* e2, size_t width) { //将元素一个一个字节交换 int i = 0; for (i = 0; i < width; i++) {char tmp; tmp = *((char*)e1+i); *((char*)e1+i) = *((char*)e2+i); *((char*)e2+i) = tmp; } } void my_qsort(void* base, size_t num, size_t width, int(*compare)(const void* e1, const void* e2)) { int i = 0, j = 0,flag = 1; //趟数 for (i = 0; i < num-1; i++) {//每趟比较次数 for (j = 0; j < num - i - 1; j++) {//比较 //因为我们无法得知实参的类型,无法得知指针加减的步长 //所以我们强制转换成char*类型指针,通过width元素大小进行访问 if (compare((char*)base + width * j, (char*)base + (j + 1) * width)>0) {//交换 swap((char*)base + j * width, (char*)base + (j + 1) * width, width); flag = 0; }} if (flag == 1) break; } } int main() { Student student[5] = { { "李四",65},{ "张三",78},{ "王五",56},{ "老王",100},{ "小李",98} }; my_qsort(student, sizeof(student) / sizeof(student[1]),sizeof(student[0]),compare_score); int i = 0; for (i = 0; i < 5; i++) {printf("%s %d\n", student[i].name, student[i].score); } return 0; }

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

指针和数组面试题解 题目
//一维数组 int a[] = { 1,2,3,4}; printf("%d\n",sizeof(a)); printf("%d\n",sizeof(a+0)); printf("%d\n",sizeof(*a)); printf("%d\n",sizeof(a+1)); printf("%d\n",sizeof(a[1])); printf("%d\n",sizeof(&a)); printf("%d\n",sizeof(*&a)); printf("%d\n",sizeof(&a+1)); printf("%d\n",sizeof(&a[0])); printf("%d\n",sizeof(&a[0]+1));

解析
//一维数组 int a[] = { 1,2,3,4}; printf("%d\n",sizeof(a)); // 16 //sizeof数组名就是计算整个数组空间大小 printf("%d\n",sizeof(a+0)); // 4/8 //a+0说明数组名a表示首元素地址,加0后还是首元素地址, //地址就是指针,而指针大小统一为4/8 printf("%d\n",sizeof(*a)); //4 //解引用数组名,该处的数组名表示首元素地址, //*后就是第一个元素 int型所以为4 printf("%d\n",sizeof(a+1)); // 4/8 //解析同a+0,a+1代表第二个元素的地址,指针大小4/8 printf("%d\n",sizeof(a[1])); // 4 //a[1]为int 故为4 printf("%d\n",sizeof(&a)); // 4/8 //&数组名,得到了整个数组的地址,而指针大小4/8 printf("%d\n",sizeof(*&a)); // 16 //&a得到整个数组地址,而*&a得到整个数组 16 printf("%d\n",sizeof(&a+1)); // 4/8 //&数组名,得到整个数组的地址,&a+1还是数组地址, //而指针大小4/8 printf("%d\n",sizeof(&a[0])); // 4/8 //&a[0]得到第一个元素的地址,指针4/8 printf("%d\n",sizeof(&a[0]+1)); // 4/8 //&a[0]得到第一个元素的地址, //&a[0]+1第二个元素地址,而指针大小4/8

运行结果
笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片
题目
//字符数组 char arr[] = { 'a','b','c','d','e','f'}; printf("%d\n", sizeof(arr)); printf("%d\n", sizeof(arr+0)); printf("%d\n", sizeof(*arr)); printf("%d\n", sizeof(arr[1])); printf("%d\n", sizeof(&arr)); printf("%d\n", sizeof(&arr+1)); printf("%d\n", sizeof(&arr[0]+1)); printf("%d\n", strlen(arr)); printf("%d\n", strlen(arr+0)); printf("%d\n", strlen(*arr)); printf("%d\n", strlen(arr[1])); printf("%d\n", strlen(&arr)); printf("%d\n", strlen(&arr+1)); printf("%d\n", strlen(&arr[0]+1)); char arr[] = "abcdef"; printf("%d\n", sizeof(arr)); printf("%d\n", sizeof(arr+0)); printf("%d\n", sizeof(*arr)); printf("%d\n", sizeof(arr[1])); printf("%d\n", sizeof(&arr)); printf("%d\n", sizeof(&arr+1)); printf("%d\n", sizeof(&arr[0]+1)); printf("%d\n", strlen(arr)); printf("%d\n", strlen(arr+0)); printf("%d\n", strlen(*arr)); printf("%d\n", strlen(arr[1])); printf("%d\n", strlen(&arr)); printf("%d\n", strlen(&arr+1)); printf("%d\n", strlen(&arr[0]+1)); char *p = "abcdef"; printf("%d\n", sizeof(p)); printf("%d\n", sizeof(p+1)); printf("%d\n", sizeof(*p)); printf("%d\n", sizeof(p[0])); printf("%d\n", sizeof(&p)); printf("%d\n", sizeof(&p+1)); printf("%d\n", sizeof(&p[0]+1)); printf("%d\n", strlen(p)); printf("%d\n", strlen(p+1)); 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));

解析
//字符数组 char arr[] = { 'a','b','c','d','e','f'}; printf("%d\n", sizeof(arr)); // 6 //sizeof数组名,就是整个数组 数组大小为6 printf("%d\n", sizeof(arr+0)); // 4/8 //arr+0,数组名就是首元素地址,arr+0也是首元素地址 //指针大小4/8 printf("%d\n", sizeof(*arr)); // 1 //解引用数组名,此时的数组名代表首元素地址, //解引用后得到第一个元素,元素大小1 printf("%d\n", sizeof(arr[1])); // 1 //arr[1]表示第一个元素,元素大小1 printf("%d\n", sizeof(&arr)); // 4/8 //&数组名,代表数组地址,指针大小4/8 printf("%d\n", sizeof(&arr+1)); //&arr 数组地址,&arr+1也是数组地址 //而指针变量大小4/8 printf("%d\n", sizeof(&arr[0]+1)); // 4/8 //&arr[0]第一个元素地址,&arr[0]+1第二个元素地址 printf("%d\n", strlen(arr)); //随机值 //stlen函数遇到'\0'停止计数 printf("%d\n", strlen(arr+0)); // 随机值 //同上 printf("%d\n", strlen(*arr)); // 报错 // stlen函数参数接收的是指针,*arr是第一个元素 printf("%d\n", strlen(arr[1])); //报错 //同上 printf("%d\n", strlen(&arr)); // 随机值 //&arr数组的地址,遇到'\0'停止计数 printf("%d\n", strlen(&arr+1)); // 随机值—6 //&arr+1 整个数组的地址加一跳过,数组大小 //而数组大小为6个字节,所以长度为随机值-6 printf("%d\n", strlen(&arr[0]+1)); //随机值-1 //&arr[0]+1得到数组第二个元素地址,从第一个元素计数 //计数结果为随机值,故从第二个为随机值-1

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片
笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

char arr[] = "abcdef"; //字符串数组,数组大小要加上'\0' printf("%d\n", sizeof(arr)); // 7 //sizeof arr arr表示整个数组 printf("%d\n", sizeof(arr+0)); // 4/8 //首元素地址加0还是首元素地址,指针大小为4/8 printf("%d\n", sizeof(*arr)); // 1 //*arr得到第一个元素,元素大小为 1bit printf("%d\n", sizeof(arr[1])); // 1 //同上 printf("%d\n", sizeof(&arr)); // 4/8 //&arr整个数组的地址,指针大小4/8 printf("%d\n", sizeof(&arr+1)); // 4/8 //&arr+1数组指针,指针大小4/8 printf("%d\n", sizeof(&arr[0]+1)); // 4/8 //&arr[0]第一个元素地址,&arr[0]+1第二个元素地址 //而指针大小4/8 printf("%d\n", strlen(arr)); // 6 //arr数组存到是字符串,字符串中含有'\0' printf("%d\n", strlen(arr+0)); // 6 // 同上 printf("%d\n", strlen(*arr)); // 报错 //*arr得到首元素,而strlen函数参数为指针类型 printf("%d\n", strlen(arr[1])); // 报错 //同上 printf("%d\n", strlen(&arr)); // 6 // &arr得到整个数组地址 printf("%d\n", strlen(&arr+1)); // 随机值 //&arr+1跳过整个数组 printf("%d\n", strlen(&arr[0]+1)); // 5 //&arr[0]+1得到第二个元素地址

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

char *p = "abcdef"; //const修饰的字符常量 printf("%d\n", sizeof(p)); // 4/8 //p是字符指针,指针大小4/8 printf("%d\n", sizeof(p+1)); // 4/8 //p+1字符指针,指针大小4/8 printf("%d\n", sizeof(*p)); // 1 //*p得到第一个元素,元素类型为char printf("%d\n", sizeof(p[0])); // 1 //同上 printf("%d\n", sizeof(&p)); // 4/8 //&p为字符指针,指针大小4/8 printf("%d\n", sizeof(&p+1)); //4/8 //&p+1为第二个字符地址,字符指针,指针大小4/8 printf("%d\n", sizeof(&p[0]+1)); // 4/8 //同上 printf("%d\n", strlen(p)); // 随机值 //p指针,所指空间,'\0'位置未知 printf("%d\n", strlen(p+1)); // 随机值-1 //p+1第二个字符地址 printf("%d\n", strlen(*p)); // 报错 // *p是字符,strlen参数为指针类型 printf("%d\n", strlen(p[0])); // 报错 //同上 printf("%d\n", strlen(&p)); // 另一随机值 // &p是二级指针, printf("%d\n", strlen(&p+1)); // 又另一随机值 //&p二级指针 ,&p+1后还是二级指针 printf("%d\n", strlen(&p[0]+1)); // 随机值—1 //&p[0]+1第二个字符地址

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

//二维数组 int a[3][4] = { 0 }; printf("%d\n", sizeof(a)); //48 //sizeof数组名.为整个数组 printf("%d\n", sizeof(a[0][0])); //4 //a[0][0]为第一元素 printf("%d\n", sizeof(a[0])); //16 //a[0]得到二维数组第一行 一行有4个元素 printf("%d\n", sizeof(a[0] + 1)); //4/8 //a[0]第一行地址,a[0]+1第二行地址 //第二行地址,数组地址,指针大小4/8 printf("%d\n", sizeof(*(a[0] + 1))); //4 //a[0]首元素地址,即第一行第一个元素地址, //加一得到第二个元素地址,解引用得到第二个元素 printf("%d\n", sizeof(a + 1)); //4/8 //a为第一行地址,a+1第二行地址 printf("%d\n", sizeof(*(a + 1))); //16 //a+1第二行地址,解引用后得到第二行 printf("%d\n", sizeof(&a[0] + 1)); //4/8 //&a[0]+1第二行地址 printf("%d\n", sizeof(*(&a[0] + 1))); //16 //&a[0]+1第二行地址 解引用得到第二行 printf("%d\n", sizeof(*a)); //16 //a第一行地址,解引用后得到第一行 printf("%d\n", sizeof(a[3])); //16 //数组越界,无第四行,但是根据二维数组前面行的大小 //sizeof认为a[3]相同大小

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

总结:
数组名的意义:
1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表示首元素的地址。
指针笔试题
笔试题1
int main() {int a[5] = { 1, 2, 3, 4, 5 }; int *ptr = (int *)(&a + 1); printf( "%d,%d", *(a + 1), *(ptr - 1)); return 0; } //程序的结果是什么?

解析
int main() {int a[5] = { 1, 2, 3, 4, 5 }; int* ptr = (int*)(&a + 1); //&a+1得到数组指针加一,指向数组末尾 //将数组指针强制类型转换成int*存放在ptr中 printf("%d,%d", *(a + 1), *(ptr - 1)); // 2,5 //a+1 a首元素地址,加一后得到第二个元素地址 // *(a + 1)解引用后得到 2 //ptr指针类型为int* ptr指向数组末尾 //ptr-1得到数组最后一个元素地址 // *(ptr - 1) 解引用得到最后一个元素5 return 0; }

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

笔试题2
//这里已知结构体的大小是20个字节 struct Test {int Num; char *pcName; short sDate; char cha[2]; short sBa[4]; }*p; //假设p 的值为0x100000。 如下表表达式的值分别为多少? int main() {printf("%p\n", p + 0x1); printf("%p\n", (unsigned long)p + 0x1); printf("%p\n", (unsigned int*)p + 0x1); return 0; }

解析
//这里已知结构体的大小是20个字节 struct Test {int Num; char* pcName; short sDate; char cha[2]; short sBa[4]; }*p; //假设p 的值为0x100000。 如下表表达式的值分别为多少? int main() {p = (struct Test*)0x100000; printf("%#08x\n", p + 0x1); //0x00100020 //p是结构体指针类型,p+1跳过一个结构体大小 //结构体大小为20字节,所以跳过20 //0x100000+20 = 0x00100020 printf("%#08x\n", (unsigned long)p + 0x1); //0x100001 //(unsigned long)p 将0x100000强制类型转换 //转换成unsigned long 后 + 0x1就是直接加 //0x100000 + 0x1 = 0x100001 printf("%#08x\n", (unsigned int*)p + 0x1); //0x100004 //(unsigned int*)p 转换成 (unsigned int*)p // 加 1跳过一个unsigned int类型大小 4 //0x100000 + 4 =0x100004 return 0; }

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

笔试题3
int main() {int a[4] = { 1, 2, 3, 4 }; int *ptr1 = (int *)(&a + 1); int *ptr2 = (int *)((int)a + 1); printf( "%x,%x", ptr1[-1], *ptr2); return 0; }

解析
int main() {int a[4] = { 1, 2, 3, 4 }; int* ptr1 = (int*)(&a + 1); //&a得到数组地址,&a+1跳过一个数组 //(int*)(&a+1)将数组指针强制转换 int* ptr2 = (int*)((int)a + 1); //(int)a 将a数组首元素地址强制转换int //(int)a+1后,a的地址码 + 1 //(int*)(int)a+1 转换成int*指针 printf("%x,%x", ptr1[-1], *ptr2); //4 , 2000000 //ptr1-1减去int类型大小,向前移动一个int型 //ptr2 指向了整个数组的第二个字节,访问一个int型 return 0; }

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片
笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

笔试题4
#include int main() {int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int *p; p = a[0]; printf( "%d", p[0]); return 0; }

解析
int main() {int a[3][2] = { (0, 1), (2, 3), (4, 5) }; //逗号表达式,初始化为{1,3,5} int* p; p = a[0]; //将a[0]第一行地址存入int*类型的p中 //即类型进行了转换成了int* printf("%d", p[0]); // 1 //此时的 p 是第一行第一个元素的地址 //p[0]就是 1 return 0; }

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

笔试题5
int main() {int a[5][5]; int(*p)[4]; p = a; printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]); return 0; }

解析
int main() {int a[5][5]; int(*p)[4]; //p为int [4] 类型的数组指针 p = a; //a为首元素地址,第一行的地址 //a为 int [5]数组指针 //将 a 赋值给 p printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]); // fffffffc -4 // &p[4][2] - &a[4][2] = -4 return 0; }

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

笔试题6
int main() {int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int *ptr1 = (int *)(&aa + 1); int *ptr2 = (int *)(*(aa + 1)); printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1)); return 0; }

解析
int main() {int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int* ptr1 = (int*)(&aa + 1); //&aa+1 跳过一个数组大小,数组指针 // aa+1指向数组的末尾 //(int*)(&aa + 1)转换成int*指针 int* ptr2 = (int*)(*(aa + 1)); //aa+1首元素地址 + 1 指向第二个元素地址 // 而这里的首元素地址就是第一行地址 // 加一后指向第二行地址 //(int*)(*(aa + 1))转换成 int*指针 printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1)); //10,5 //ptr1-1数组末尾指针-1 ptr1是int*指针 // 所以减一得到 最后一个元素地址 // 解引用后得到元素 10 //ptr2-1第二行数组地址-1 // 因为prt2是int*指针类型-1 跳过一个int类型 //*(ptr2-1)得到第一行最后一个元素 5 return 0; }

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

笔试题7
#include int main() {char *a[] = { "work","at","alibaba"}; char**pa = a; pa++; printf("%s\n", *pa); return 0; }

解析
int main() {char* a[] = { "work","at","alibaba" }; char** pa = a; pa++; //pa++ 后指向char* "at" printf("%s\n", *pa); // at return 0; }

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

笔试题8
int main() {char *c[] = { "ENTER","NEW","POINT","FIRST"}; char**cp[] = { c+3,c+2,c+1,c}; char***cpp = cp; printf("%s\n", **++cpp); printf("%s\n", *--*++cpp+3); printf("%s\n", *cpp[-2]+3); printf("%s\n", cpp[-1][-1]+1); return 0; }

解析
int main() {char* c[] = { "ENTER","NEW","POINT","FIRST" }; char** cp[] = { c + 3,c + 2,c + 1,c }; char*** cpp = cp; printf("%s\n", **++cpp); //POINT printf("%s\n", *-- * ++cpp + 3); //ER printf("%s\n", *cpp[-2] + 3); //ST printf("%s\n", cpp[-1][-1] + 1); //EW return 0; }

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

笔记|深入理解C语言指针——挑战C指针笔试题 (和bug郭一起学C系列)
文章图片

C语言指针完结,欢迎大佬的评论,感谢支持,点赞,收藏!

    推荐阅读