指针进阶

  • 学习重点
  • 知识回顾
  • 1.字符指针
  • 2.指针数组
  • 3.数组指针
      • 3.1 定义
      • 3.2 &数组名与数组名
  • 4.数组参数 指针参数
      • 4.1 一维数组传参
      • 4.2 二维数组传参
      • 4.3 一级指针传参
      • 4.4 二级指针传参
  • 5.函数指针
  • 6.函数指针数组
      • 1.函数指针的数组定义:
      • 2.函数指针数组的用途:转移表
  • 7.回调函数
      • 1.qsort的实现以及使用
        • 1.1 qsort的原理:
        • 2.最普通的排序
        • 3.测试qsort排序整型数组
        • 4.测试qsort排序结构体数据
        • 5.自制qsort
  • 8. 指针和数组笔试题

针对于指针这一块区域,很多人都很头疼,但是又没有什么好办法,在这篇文章,我会带着大家一点一点分析指针,过程虽然会很痛苦,但是一旦学懂之后发现其实也没啥的,加油!冲冲冲!


学习重点

  1. 字符指针
  2. 数组指针
  3. 指针数组
  4. 数组传参和指针传参
  5. 函数指针
  6. 函数指针数组
  7. 指向函数指针数组的指针
  8. 回调函数
  9. 指针和数组面试题的解析

知识回顾

  1. 指针的作用: 可以通过指针间接访问内存

    内存编号是从0开始记录的,一般用十六进制数字表示

    可以利用指针变量保存地址

  2. 指针变量的定义和使用

    指针变量定义语法: 数据类型 * 变量名;

    指针变量可以通过” * “操作符,操作指针变量指向的内存空间,这个过程称为解引用

  3. 指针所占内存空间:在32位平台下是四个字节,在64位平台是8个字节

  4. 空指针:指针变量指向内存中编号为0的空间

    野指针:指针变量指向非法的内存空间

  5. 指针±整数:int 4个字节 char 1个字节

    指针-指针:中间的元素个数

  6. 二级指针

1.字符指针

  1. 字符指针的使用:

    //实例1#include int main(){char ch = 'w';char *pc = &ch;*pc = 'w';return 0;}
    //实例2#include int main(){ char* p="abcdef"; printf("%s\n",p);}
    //实例三#include int main(){char* p = "abcdef";*p = 'w';printf("%s\n", p);return 0;}

    解析:

那么有一道来自《剑指Offer》的题目,让我们look look吧

#includeint main(){char arr1[] = "abcdef";char arr2[] = "abcdef";const char* str1 = "abcdef";const char* str2 = "abcdef";if (arr1 == arr2)printf("arr1==arr2\n");elseprintf("arr1!=arr2\n");if (str1 == str2)printf("str1==str2\n");elseprintf("str1!=str2\n");return 0;}答案是arr1!=arr2str1==str2,做对了没?

那么接下来我在强调一下大家可能会出现错误的地方

1.int *pa,pb;请问pa pb是什么类型的?答案:pa->int* pb->int2.typedef int* PINTPINT pa,pb;答案:pa->int* pb->int*3.#define pint int*pint pa,pb;答案:pa->int* pb->int结论:推荐不要去连续定义指针

2.指针数组

整型数组-存放整型的数组

字符数组-存放字符的数组

指针数组->存放指针的数组

下面有几个例子,我们来看一下:

1.int* arr1[10]; //整形指针的数组:arr1先与[]结合,代表是10个元素的数组,每个数组都是int*类型的char *arr2[4]; //一级字符指针的数组:把字符串的首字母存入数组中char **arr3[5];//二级字符指针的数组备注:数组的类型 去掉数组名与元素个数
2.int a = 10;int* p = &a;int** pp = &p;//二级指针int** arr2[4] = {0};//指针数组,存放二级指针
3.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]);//*(*(arr+i)+j)}printf("\n");}return 0;}//相当于模拟二维数组备注:arr[i] 相当于找到每一行的起始地址arr[i][j]是找到当前行后面的数*(*(arr+i)+j)的含义:arr+i:找到行数*(arr+i):对找到的行进行解引用操作*(arr+i)+j:在当前行找到每个元素*(*(arr+i)+j):对每个元素进行解引用操作

4.int main(){char* arr[] = { "abcdef", "qwer", "zhangsan" };int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 0; i < sz; i++){printf("%s\n", arr[i]);}return 0;}

3.数组指针

3.1 定义

整形指针-指向整型的指针

字符指针-指向字符的指针

数组指针->应该是一种指针,是指向数组的指针

判断一下,下面代码哪个是数组指针?

int *p1[10];//指针数组-p1先与[]结合,指向数组元素为10的数组,数组每个元素是int类型int (*p2)[10];//数组指针-p2先和*结合,说明p2是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p2是一个指针,指向一个数组,叫数组指针。注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

3.2 &数组名与数组名

arr是数组名,数组名表示数组首元素的地址。类型:int*

&arr 表示的是数组的地址,而不是数组首元素的地址。类型:数组指针

演示1:

int arr[10] = { 0 };//数组名->首元素地址printf("%p\n", arr);//027CFA8Cprintf("%p\n", arr+1);//027CFA90//首元素地址printf("%p\n", &(arr[0]));//027CFA8Cprintf("%p\n", &(arr[0])+1);//027CFA90//数组的地址->放到数组指针里面printf("%p\n", &arr);//027CFA8Cprintf("%p\n", &arr+1);//027CFAB4


演示2:数组指针的类型就是去掉名字即可

char arr[5];char (*pa)[5] = &arr;int* parr[6];int* (*pp)[6] = &parr;


演示3:数组指针的用途

//数组指针有什么⽤?int main(){int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;int i = 0;for (i = 0; i < 10; i++){printf("%d ", *(p + i));//1 2 3 4 5 6 7 8 9 10}return 0;}//数组指针很少应⽤于⼀维数组int main(){int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int(*p)[10] = &arr;int i = 0;for (i = 0; i < 10; i++){printf("%d ", *((*p) + i));}return 0;}//数组指针常应⽤于⼆维数组void print(int a[3][5], int r, int c){int i = 0;int j = 0;for (i = 0; i < r; i++){for (j = 0; j < c; j++){printf("%d ", a[i][j]);}printf("\n");}}//int(*p)[5]是数组指针void print(int(*p)[5], int r, int c){int i = 0;for (i = 0; i < r; i++){int j = 0;for (j = 0; j < c; j++){//*(p+i) 相当于拿到了⼆维数组的第i⾏,也相当于第i⾏的数组名//数组名表示⾸元素的地址,其实也是第i⾏第⼀个元素的地址printf("%d ", *(*(p + i) + j));////p是第⼀⾏的地址//p+i是第i⾏的地址//*(p+i) 是第i⾏第⼀个元素的地址//}printf("\n");}}int main(){int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };print(arr, 3, 5);return 0;}


演示4:

int main(){int arr[10];int i = 0;arr[i] == *(arr + i) == p[i] == *(p + i);int* p = arr;//p和arr是一回事*(p + i);return 0;}


练习: 看看下面代码的意思

1.int arr[5];//arr是⼀个整型数组,有5个元素,每个元素都是int类型的2.int *parr1[10];//parr1是⼀个数组,数组有10个元素,每个元素的类型是int*,所以parr1是指针数组3.int (*parr2)[10];//parr2和*结合,说明parr2是⼀个指针,该指针指向⼀个数组,数组是10个元素,每个元素都是int类型。parr2是数组指针4.int (*parr3[10])[5];//parr3和[]结合,说明parr3是⼀个数组,数组是10个元素,数组的每个元素是⼀种数组指针,类型是int (*)[5],该类型的指针指向的数组有5个int类型的元素

第4题的图解

类型的判断:

  • 指针数组->去掉数组名和元素个数
  • 数组指针->去掉名字

数组名其实就是个地址,举个例子

int a=10;int arr[5];

我们的变量以及数组名在编译器处理过之后就是地址

我们在看这样一句代码

int (*p)[5];//这是个数组指针

4.数组参数 指针参数

4.1 一维数组传参

#include void test(int arr[])//数组传参的时候,形参用指针接受也是没问题的{ }void test(int arr[10])//本质:其实并不会新创建一个数组,哪怕你形参元素个数写错了也无所谓{ }void test(int* arr)//本质:一维数组传参,传过去的是地址,地址用指针接收没问题{ }void test2(int* arr[20])//数组传参,形参写成数组没问题,20个元素每个元素都是int*(指针类型){ }void test2(int** arr)//(地址->类型:)一级指针,用二级指针接收没问题{ }int main(){int arr[10] = { 0 };int* arr2[20] = { 0 };test(arr);test2(arr2);}

4.2 二维数组传参

void test(int arr[3][5])//ok-数组传参,形参部分写成数组没问题{}void test(int arr[][])//err-形参部分的二维数组行可省,列不可省{}void test(int arr[][5])//ok{}//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。//这样才方便运算。void test(int* arr)//err-二维数组传参,传过来的是第一行的地址,是int*的,直接用int*接受是不可以的{}void test(int* arr[5])//err-传过来的是地址,应该用数组指针而不是指针数组来接收{}void test(int(*arr)[5])//ok-传过来的是第一行的地址,用数组指针接受没问题{}void test(int** arr)//err-只有传过来⼀级指针的地址时 才⽤⼆级指针接收{}int main(){int arr[3][5] = { 0 };test(arr);//数组名->首元素的地址,在二维数组中,首元素为第一行,所以传的是第一行的地址}

4.3 一级指针传参

一级指针传参,一级指针接收

#include 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);return 0;}

思考:当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

void test(int* p){}int main(){int a = 10;int* ptr = &a;int arr[10] = { 0 };test(&a);//整个数组test(ptr);//整型指针test(arr);//数组名return 0;}

4.4 二级指针传参

#include void test(int** ptr){printf("num = %d\n", **ptr);}int main(){int n = 10;int* p = &n;//一级指针int** pp = &p;//二级指针,保存的是p的地址test(pp);test(&p);//一级指针变量的地址return 0;}

思考:当函数的参数为二级指针的时候,可以接收什么参数?

例子1:

void test(char** p){}int main(){char ch = 'w';char* p = &ch;char** pp = &p;char* arr[5];//指针数组test(&p);//一级指针变量的地址test(pp);//二级指针(存放一级指针变量的地址)test(arr);//数组名-指针数组是char*类型的,用char**接收没问题return 0;}

例子2:二维数组的类型名->类型(*)[列数]

//可以通过这种方法检测两边类型是否相同//报错信息:"int*"与"int(*)[5]的间接级别不同int main(){int arr[3][5];int p = arr;//err,arr是第一行的地址,是int(*)[5]类型的,应该用int* p来接收return 0;}

5.函数指针

函数指针:指向函数的指针

首先看一段代码:

int Add(int x, int y){return x + y;}void test(char* str){}int main(){//下面这两个相同,都是函数的地址//注意:函数是没有首元素的,不能说首元素地址//printf("%p\n", &Add);//printf("%p\n", Add);//int arr[5];//void (*pt)(char*) = test;//test函数的函数指针//int pf(int, int) = &Add;//err->这样就成函数了//int (* pf)(int, int) = &Add;//pf是函数指针int (*pf)(int, int) = Add;//pf存放的是Add的地址//下面两种写法都可以->*是个摆设//int sum = (*pf)(2,3);//(*pf)->找到函数 (2,3)->函数调用 e.g.Add(2,3);int sum = pf(2, 3);//pf从此和Add一样,可以替换//int sum = Add(2, 3);//int sum = *pf(2, 3);//err,先执行pf(2,3);在执行解引用操作printf("%d\n", sum);return 0;}

函数指针到底是怎么回事呢” />

阅读两段有趣的代码:

//代码1(*(void (*)())0)();//代码2void (*signal(int , void(*)(int)))(int);

代码一:

代码二:

6.函数指针数组

函数指针数组:函数指针数组-存放函数指针的数组,每个元素都是函数指针类型

1.函数指针的数组定义:

int (*parr1[10])();//parr1先和[]结合,说明parr1是数组,数组的内容是什么呢?是 int (*)() 类型的函数指针。

2.函数指针数组的用途:转移表

例子:计算器

//版本1#includeint 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("***** 1. add 2. sub *****\n");printf("***** 3. mul 4. div *****\n");printf("***** 0. exit *****\n");printf("**********************************\n");}int main(){int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:printf("输⼊2个操作数:>");scanf("%d %d", &x, &y);ret = Add(x, y);printf("ret = %d\n", ret);break;case 2:printf("输⼊2个操作数:>");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("ret = %d\n", ret);break;case 3:printf("输⼊2个操作数:>");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("ret = %d\n", ret);break;case 4:printf("输⼊2个操作数:>");scanf("%d %d", &x, &y);ret = Div(x, y);printf("ret = %d\n", ret);break;case 0:printf("退出计算器\n");break;default:printf("选择错误\n");break;}} while (input);return 0;}
//版本2:转移表#includeint 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("***** 1. add 2. sub *****\n");printf("***** 3. mul 4. div *****\n");printf("***** 0. exit *****\n");printf("**********************************\n");}int main(){int input = 0;int x = 0;int y = 0;int ret = 0;int (*pfArr[5])(int, int) = { 0, Add, Sub, Mul, Div };//pfArr是⼀个函数指针的数组,也叫转移表do{menu();printf("请选择:>");scanf("%d", &input);if (input == 0){printf("退出计算器\n");break;}else if (input >= 1 && input ");scanf("%d %d", &x, &y);ret = pfArr[input](x, y);printf("ret = %d\n", ret);}else{printf("选择错误\n");}} while (input);return 0;}

7.回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。

1.qsort的实现以及使用

1.1 qsort的原理:

//void qsort(void* base,// size_t num,// size_t width,// int(* compare)(const void* e1, const void* e2)// );

2.最普通的排序

#include void bubble_sort(int arr[], int sz){//趟数:sz-1int i = 0;for (i = 0; i < sz - 1; i++){//每⼀趟冒泡排序的过程//确定的⼀趟排序中⽐较的对数:sz-1-iint j = 0;for (j = 0; j  arr[j + 1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}}void print_arr(int arr[], int sz){int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");}int main(){int arr[] = { 9,8,7,6,5,4,3,2,1 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz);print_arr(arr, sz);return 0;}

3.测试qsort排序整型数组

#include#includevoid print_arr(int arr[], int sz){int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");}//⽐较e1和e2指向的元素//void*-无具体类型指针int cmp_int(const void* e1, const void* e2){//默认升序,想要降序的话,把e1和e2交换位置return *(int*)e1 - *(int*)e2;//e1直接解引用不行,要先把e1强转成int*类型,再解引用}//测试qsort排序整型数组void test1(){int arr[] = { 1,4,2,6,5,3,7,9,0,8 };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_int);print_arr(arr, sz);}int main(){test1();return 0;}

4.测试qsort排序结构体数据

#include#include#includestruct Stu{char name[20];int age;float score;};//排序成绩int cmp_stu_by_score(const void* e1, const void* e2){//把e1 e2强转成结构体类型if (((struct Stu*)e1)->score > ((struct Stu*)e2)->score){return 1;}else if (((struct Stu*)e1)->score score){return -1;}else{return 0;}}//排序年龄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);//引头文件#include}//打印void print_stu(struct Stu arr[], int sz){int i = 0;for (i = 0; i < sz; i++){printf("%s %d %f\n", arr[i].name, arr[i].age, arr[i].score);}printf("\n");}void test2(){struct Stu arr[] = { {"zhangsan",20,87.5f},{"lisi",22,99.0f},{"wangwu", 10, 68.5f} };//按照成绩来排序int sz = sizeof(arr) / sizeof(arr[0]);//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_score);//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);print_stu(arr, sz);}int main(){test2();return 0;}

5.自制qsort

//自制qsort排序整型数组#include#include#include//排序数字int cmp_int(const void* e1, const void* e2){return *(int*)e1 - *(int*)e2;}void print_arr1(int arr[], int sz){int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");}void Swap(char* buf1, char* buf2, int width){int i = 0;//一对一对字符交换for (i = 0; i < width; i++)//交换width对字节{char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}}//⾃制qsort:并未通过宽度推断类型,用宽度计算偏移量void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2)){int i = 0;for (i = 0; i < sz - 1; i++){int j = 0;for (j = 0; j  arr[j + 1])//一次操作一个字节if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0){//两个元素的交换Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);//void*并不知道会操作几个字节,所以我们要把偏移量width传进去}}}}void test3(){int arr[] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);print_arr1(arr, sz);}int main(){test3();return 0;}
//测试自制qsort排序结构体数据#include#include#includestruct Stu{char name[20];int age;float score;};//排序成绩int cmp_stu_by_score(const void* e1, const void* e2){if (((struct Stu*)e1)->score > ((struct Stu*)e2)->score){return 1;}else if (((struct Stu*)e1)->score score){return -1;}else{return 0;}}//排序年龄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 print_stu2(struct Stu arr[], int sz){int i = 0;for (i = 0; i < sz; i++){printf("%s %d %f\n", arr[i].name, arr[i].age, arr[i].score);}printf("\n");}void Swap(char* buf1, char* buf2, int width){int i = 0;for (i = 0; i < width; i++){char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}}//⾃制qsortvoid bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2)){int i = 0;for (i = 0; i < sz - 1; i++){int j = 0;for (j = 0; j  0){//两个元素的交换Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);}}}}void test4(){struct Stu arr[] = { {"zhangsan",20,87.5f},{"lisi",22,99.0f},{"wangwu", 10, 68.5f} };//按照成绩来排序int sz = sizeof(arr) / sizeof(arr[0]);//⾃制qsort的调⽤//bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_socre);//bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);print_stu2(arr, sz);}int main(){test4();return 0;}

那么接下来我们从不同角度思考问题:

  • 实现排序的作者:他并不知道用户想排序什么类型的数据
  • 使用者:
    1. 知道待排序的数据类型
    2. 知道待排序的数据的比较方法(>或者strcmp)

那么接下来再重新看这块代码

//void qsort(void* base,不知道排序什么类型 , 就用void*// size_t num,元素个数// size_t width,void*不知道类型,就不知道⼤⼩,⽆法知道⼀次 操作⼏个字节,所以我们要给出// int(* compare)(const void* e1, const void* e2)把不同的操作抽象成函数// );

8. 指针和数组笔试题

数组名:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。

练习1:

#includeint main(){int a[] = { 1,2,3,4 };printf("%d\n", sizeof(a));//数组名a单独放在sizeof内部,计算的整个数组的⼤⼩,单位是字节,4*4 = 16printf("%d\n", sizeof(a + 0));//a表示的⾸元素的地址,a+0还是数组⾸元素的地址,是地址⼤⼩4/8printf("%d\n", sizeof(*a));//a表示的⾸元素的地址,*a就是对⾸元素的地址的解引⽤,就是⾸元素,⼤⼩是4个字节printf("%d\n", sizeof(a + 1));//a表示的⾸元素的地址,a+1是第⼆个元素的地址,是地址,⼤⼩就4/8个字节printf("%d\n", sizeof(a[1]));//a[1]是数组的第⼆个元素,⼤⼩是4个字节printf("%d\n", sizeof(&a)); //&a 表示是数组的地址,数组的地址也是地址,地址⼤⼩就是4/8字节printf("%d\n", sizeof(*&a));//可以理解为*和&抵消效果,*&a相当于a,sizeof(a)是16//&a -> int(*)[4] 数组的地址放在数组指针中//&a是数组的地址,它的类型是int(*)[4]数组指针,如果解引⽤,访问的就是4个int的数组,⼤⼩是16个字节printf("%d\n", sizeof(&a + 1));//&a是数组的地址,&a+1 跳过整个数组后的地址,是地址就是4/8printf("%d\n", sizeof(&a[0]));//&a[0]取出数组第⼀个元素的地址,是地址就是4/8printf("%d\n", sizeof(&a[0] + 1));//&a[0]+1就是第⼆个元素的地址,是地址⼤⼩就是4/8个字节//&a[0] - 地址的类型:int*return 0;}

sizeof只关注占⽤空间的⼤⼩,单位是字节 ->南北通吃
sizeof不关注类型
sizeof是操作符,不是函数

strlen关注的字符串中\0的位置,计算的是\0之前出现了多少个字符
strlen指针对字符串
strlen是库函数

练习2:

#include#include int main(){//字符数组char arr[] = { 'a','b','c','d','e','f' };printf("%d\n", sizeof(arr));//arr作为数组名单独放在sizeof内部,计算的整个数组的⼤⼩,单位是字节,6printf("%d\n", sizeof(arr + 0));//arr就是⾸元素的地址,arr+0还是⾸元素的地址,地址⼤⼩就是4/8printf("%d\n", sizeof(*arr));//arr就是⾸元素的地址,*arr就是⾸元素,是⼀个字符,⼤⼩是⼀个字节,1printf("%d\n", sizeof(arr[1]));//arr[1]就是数组的第⼆个元素,是⼀个字符,⼤⼩是1个字节printf("%d\n", sizeof(&arr));//&arr取出的是数组的地址,数组的地址也是地址,地址就是4/8个字节printf("%d\n", sizeof(&arr + 1));//&arr取出的是数组的地址,&arr+1,跳过了整个数组,&arr+1还是地址,地址就是4/8个字节printf("%d\n", sizeof(&arr[0] + 1));//&arr[0]是第⼀个元素的地址,&arr[0]+1就是第⼆个元素的地址,地址就是4/8个字节printf("%d\n", strlen(arr));//arr是⾸元素的地址,但是arr数组中没有\0,计算的时候就不知道什么时候停⽌,结果是:随机值printf("%d\n", strlen(arr + 0));//arr是⾸元素的地址,arr+0还是⾸元素的地址,结果是:随机值printf("%d\n", strlen(*arr)); //err,strlen需要的是⼀个地址,从这个地址开始向后找字符,直到\0,统计字符的个数。//但是*arr是数组的⾸元素,也就是'a',这是传给strlen的就是'a'的ascii码值97,strlen函数会把97作为起始地址,统计字符串,会形成内存访问冲突printf("%d\n", strlen(arr[1]));//err 和上⼀个⼀样,内存访问冲突printf("%d\n", strlen(&arr));//&arr是arr数组的地址,虽然类型和strlen的参数类型有所差异,但是传参过去后,还是从第⼀个字符的位置向后数字符,结果还是随机值。printf("%d\n", strlen(&arr + 1));//随机值 数组地址+1->跳过整个数组printf("%d\n", strlen(&arr[0] + 1));//随机值 数组元素+1->跳过这个元素return 0;}

练习3:

int main(){char arr[] = "abcdef";printf("%d\n", strlen(arr));//arr是⾸元素的地址,向后寻找直到遇⻅\0,结果为6printf("%d\n", strlen(arr + 0));//arr就是⾸元素的地址,arr+0还是⾸元素的地址,结果同上printf("%d\n", strlen(*arr));//err,strlen需要的是⼀个地址,从这个地址开始向后找字符,直到\0,统计字符的个数。但是* arr是数组的⾸元素,也就是'a', 这是传给strlen的就是'a'的ascii码值97,strlen函数会把97作为起始地址,统计字符串,会形成内存访问冲突printf("%d\n", strlen(arr[1]));//err 和上⼀个⼀样,内存访问冲突printf("%d\n", strlen(&arr));//&arr是arr数组的地址,从第⼀个字符的位置向后数字符,结果是6printf("%d\n", strlen(&arr + 1));//数组地址+1->跳过整个数组 随机值printf("%d\n", strlen(&arr[0] + 1));//数组元素地址+1->跳过这个元素->跳到了b,向后数在\0之前有5个元素printf("%d\n", sizeof(arr));//数组名a单独放在sizeof内部,计算的整个数组的⼤⼩,单位是字节,有7个元素,1*7=7printf("%d\n", sizeof(arr + 0));//⽆单独的sizeof,⽆&->⾸元素地址,+0还是⾸元素地址,4/8个字节printf("%d\n", sizeof(*arr));//arr就是⾸元素的地址,*arr就是⾸元素,是⼀个字符,⼤⼩是⼀个字节,1//*arr->*(arr + 0)->arr[0]printf("%d\n", sizeof(arr[1]));//arr[1]就是数组的第⼆个元素,是⼀个字符,⼤⼩是1个字节printf("%d\n", sizeof(&arr));//&arr取出的是数组的地址,数组的地址也是地址,地址就是4/8个字节printf("%d\n", sizeof(&arr + 1));//&arr取出的是数组的地址,&arr+1,跳过了整个数组,&arr+1还是地址,地址就是4/8个字节printf("%d\n", sizeof(&arr[0] + 1));//&arr[0]是第⼀个元素的地址,&arr[0]+1就是第⼆个元素的地址,地址就是4/8个字节return 0;}

练习4:

#include#includeint main(){char* p = "abcdef";printf("%d\n", sizeof(p)); //p是⼀个指针变量,sizeof(p)计算的就是指针变量的⼤⼩,4 / 8个字节printf("%d\n", sizeof(p + 1));//p是指针变量,是存放地址的,p+1也是地址,地址⼤⼩就是4/8字节printf("%d\n", sizeof(*p));//p是char*的指针,解引⽤访问⼀个字节,即*p访问1个字节printf("%d\n", sizeof(p[0]));//p[0]--> *(p+0) -> *p ⾸元素:1个字节printf("%d\n", sizeof(&p));//&p也是地址,是地址就是4/8字节,&p是⼆级指针printf("%d\n", sizeof(&p + 1)); //&p是地址, + 1后还是地址,是地址就是4 / 8字节&p + 1,是p的地址+1,在内存中跳过p变量后的地址printf("%d\n", sizeof(&p[0] + 1));//p[0]就是a,&p[0]就是a的地址,&p[0]+1就是b的地址,是地址就是4/8字节//p[0]-- > * (p + 0)->*pprintf("%d\n", strlen(p));//p中存放的是'a'的地址,strlen(p)就是从'a'的位置向后求字符串的⻓度,⻓度是6printf("%d\n", strlen(p + 1));//p+1是'b'的地址,从b的位置开始求字符串⻓度是5printf("%d\n", strlen(*p));//err,strlen需要的是⼀个地址,从这个地址开始向后找字符,直到\0,统计字符的个数。但是* arr是数组的⾸元素,也就是'a', 这是传给strlen的就是'a'的ascii码值97,strlen函数会把97作为起始地址,统计字符串,会形成内存访问冲突printf("%d\n", strlen(p[0]));//err 和上⼀个⼀样,内存访问冲突printf("%d\n", strlen(&p));//随机值 向后找不到\0printf("%d\n", strlen(&p + 1));//随机值 同上printf("%d\n", strlen(&p[0] + 1));//p[0] -> *(p+0) -> *p ->'a' ,&p[0]就是⾸字符的地址,&p[0]+1就是第⼆个字符的地址//从第2个字符的位置向后数字符串,⻓度是5return 0;}

练习5:

int main(){//⼆维数组int a[3][4] = { 0 };printf("%d\n", sizeof(a));//数组名单独放在sizeof内部,计算的是整个数组的⼤⼩ 3*4*4=48printf("%d\n", sizeof(a[0][0]));//⼀个元素 -> 4个字节printf("%d\n", sizeof(a[0]));//a[0]表示第⼀⾏的数组名,a[0]作为数组名单独放在sizeof内部,计算的是第⼀⾏的⼤⼩(整个数组的⼤⼩)16printf("%d\n", sizeof(a[0] + 1));//a[0]作为第⼀⾏的数组名,没有&,没有单独放在sizeof内部,所以a[0]表示的就是⾸元素的地址,即a[0][0]的地址,a[0] + 1就是第⼀⾏第⼆个元素的地址,是地址就是4 / 8printf("%p\n", &a[0][0]);//02D8FB94printf("%p\n", a[0] + 1);//02D8FB98printf("%d\n", sizeof(*(a[0] + 1)));//a[0] + 1是第⼀⾏第⼆个元素的地址,对他进⾏解引⽤,就访问到了该元素,⼤⼩是4个字节printf("%d\n", sizeof(a + 1));//a是⼆维数组数组名,没有&,没有单独放在sizeof内部,a表示数组⾸元素(第⼀⾏的地址),a+1跳到了第⼆⾏的地址,是类型为int(*)[4]的数组指针,是地址就是4 / 8个字节。printf("%p\n", &a[0][0]);//008FF764printf("%p\n", &a + 1);//008FF794 跳过整个⼆维数组 还是地址4/8printf("%p\n", &a[0][0]);//0094FA4C 第⼀⾏地址printf("%p\n", a + 1);//0094FA5C 第⼆⾏地址printf("%d\n", sizeof(*(a + 1)));//a+1是第⼆⾏的地址,*(a+1)就是第⼆⾏,相当于第⼆⾏的数组名,*(a + 1)->a[1], sizeof(*(a + 1))计算的是第⼆⾏的⼤⼩,16字节printf("%d\n", sizeof(&a[0] + 1));//a[0]是第⼀⾏的地址,&a[0]是第⼀⾏的地址,&a[0]+1是第⼆⾏的地址,是地址就是4/8字节printf("%d\n", sizeof(*(&a[0] + 1)));//&a[0] + 1是第⼆⾏,*(&a[0] + 1))是对第⼆⾏解引⽤,相当于拿到了第⼆⾏的数组名,相当于第⼆⾏,也就是a[1], sizeof(a[1])⼤⼩是16字节printf("%d\n", sizeof(*a));//a是⼆维数组数组名,没有&,没有单独放在sizeof内部,a表示⾸元素地址,*a就是⼆维数组的⾸元素,也就是第⼀⾏,sizeof(*a)就是16个字节。* a->*(a + 0)->a[0]printf("%d\n", sizeof(a[3]));//感觉a[3]越界了,但是没关系,压根不会去计算括号内的东⻄,回去退到括号内的类型,括号⾥的类型是int[4], ⼤⼩是16个字节return 0;}

练习6:

int main(){int a[5] = { 1, 2, 3, 4, 5 };int* ptr = (int*)(&a + 1);printf("%d,%d", *(a + 1), *(ptr - 1));return 0;}//程序的结果是什么?5 6

练习7:

struct Test{int Num;char* pcName;short sDate;char cha[2];short sBa[4];}*p;//假设p 的值为0x100000。 如下表表达式的值分别为多少?//已知,结构体Test类型的变量⼤⼩是20个字节int main(){p = (struct Test*)0x100000;printf("%p\n", p + 0x1);//00100014printf("%p\n", (unsigned long)p + 0x1);//00100001printf("%p\n", (unsigned int*)p + 0x1);//00100004注意:0x1是1printf("%x\n", p + 0x1);//0x1->1;p+0x1->p+1 结构体指针+!跳过整个结构体,20个字节,20转换成16进制是14,0x100000 + 14->0x100014,打印的结果是10014printf("%x\n", (unsigned long)p + 0x1);//把p转化成unsigned long形式,p就是⼀个数字,0x100000不再是地址,0x100000 + 1->0x100001,打印的结果是100001printf("%x\n", (unsigned int*)p + 0x1);//整形指针+1->跳过四个字节,0x100000 + 4->0x100004, 打印的结果是100004//%p 以地址的形式打印,⾼位0不会省略//%x 就是打印16进制,⾼位0会省略return 0;}

练习8:坑!

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;}

练习9:

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

练习10:

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;}

练习11:

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;}

练习12:

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

练习13:难度略大

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;}


肝了一天才将这篇博客完成,希望大家多多支持,喜欢的话请一件三连熬!
有不懂的私聊博主即可,或者在评论区表达你的问题