• 【C语言】指针由浅入深全方位详解


    目录

    指针

    野指针

    二级指针 

    指针数组 

    字符指针 

    数组指针 

    数组参数,指针参数 

    函数指针 

    函数指针数组

    回调函数 

    练习题 

    代码仓库 


    指针

    1. 指针定义

    1. 指针是内存中一个最小单元的编号,也就是地址。

    2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。

    3. 我们可以通过 &(取地址操作符) 取出变量的内存起始地址,把地址可以存放到一个变量中,这个变量就是指针变量。

    4. 指针的大小在32位平台是4个字节,在64位平台是8个字节

    1. int a = 100;
    2. int * pa = &a;
    3. //*表示pa是指针变量。
    4. //int表示 1.pa指向的类型是int 2.pa解引用的时候访问的对象大小是sizeof(int)。

    2. 指针类型

    1. 指针类型可以决定指针解引用的时候访问多少个字节(指针的权限)

    int* 的指针解引用访问4个字节。

    char* 的指针解引用访问1个字节

    .

    2. 指针类型决定指针加1减1操作时的步长

    整型指针+1跳过4个字节,字符指针+1跳过1个字节

    3. 指针运算 

    1. 指针加减整数

    1. int arr[10] = {0};
    2. int* p = &arr[0];
    3. for(int i=0; i<sizeof(arr)/sizeof(arr[0]); i++)
    4. {
    5. *p = i;
    6. p = p + 1; //指针加1,加一个sizeof(int)
    7. }
    8. p = arr;
    9. for(int i=0; i<sizeof(arr)/sizeof(arr[0]); i++) { printf("&d ", *(p+i)); }
    10. //*(p+i) == arr[i]
    11. //*(arr+i) == arr[i] == *(i+arr) == i[arr]

    2. 指针减指针 

    1. int arr[10] = {0};
    2. printf("%d\n", &arr[9] - &arr[0]); //等于9
    3. //指针减指针的绝对值是指针和指针之间的元素个数
    4. //指针和指针相减的前提是两个指针指向了同一块空间

    3. 指针的关系运算 

    • 地址是有大小的,指针的关系运算就是比较指针的大小。
    • 标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

    4. 指针与数组的联系

    1. 数组是一块连续的空间,可以存放1个或多个类型相同的数据。

    2. 数组名是数组首元素地址,数组名==地址==指针。
    3. 因为数组是连续存放的,所以通过指针就可以遍历访问整个数组。


    野指针

    1. 概念

    野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。

    2. 野指针成因

    1. //1.指针未初始化
    2. int *p;//局部变量指针未初始化,默认为随机值
    3. *p = 20;
    4. //2.指针越界访问
    5. int arr[10] = {0};
    6. int *p = arr;
    7. int i = 0;
    8. for(i=0; i<=11; i++)
    9. {
    10. //当指针指向的范围超出数组arr的范围时,p就是野指针
    11. *(p++) = i;
    12. }
    13. //3.指针指向的空间释放

    3. 如何规避野指针? 

    1.明确知道指针应该初始化为谁的地址就直接初始化,不知道的就初始化为NULL。

    2.小心指针越界。

    3.指针指向的空间释放后,及时置NULL。

    4.避免返回局部变量的地址。

    5.指针使用前检查有效性。


    二级指针 

    1. 指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?

    pp是二级指针变量,用来存放一级指针变量的地址

    1. int a = 10;
    2. int* p = &a;
    3. int** pp = &p;

    p是指针变量,是变量就有地址

    pp存放p的地址


    指针数组 

    1. 类比

    整型数组 - 存放整型的数组
    字符数组 - 存放字符的数组
    指针数组 - 存放指针(地址)的数组

    .

    int* arr1[10]; //存放整形指针的数组

    char* arr2[4]; //存放字符指针的数组

    char** arr3[5];//存放二级字符指针的数组

    2. 使用指针数组模拟二维数组

    真正的二维数组是连续存放的


    字符指针 

    1. char* 的两种使用方式

    1. //在指针的类型中我们知道有一种指针类型为字符指针char*
    2. int main()
    3. {
    4. char ch = 'w';
    5. char *pc = &ch;
    6. *pc = 'w';
    7. return 0;
    8. }
    9. //还有一种使用方式如下:
    10. int main()
    11. {
    12. //这里本质是把字符串hello bit.首字符的地址放到了pstr中
    13. const char* pstr = "hello bit.";
    14. printf("%s\n", pstr);
    15. return 0;
    16. }

    2. 面试题

    1. //输出结果?
    2. #include
    3. int main()
    4. {
    5. char str1[] = "hello bit.";
    6. char str2[] = "hello bit.";
    7. const char *str3 = "hello bit.";
    8. const char *str4 = "hello bit.";
    9. if(str1 ==str2)
    10. printf("str1 and str2 are same\n");
    11. else
    12. printf("str1 and str2 are not same\n");
    13. if(str3 ==str4)
    14. printf("str3 and str4 are same\n");
    15. else
    16. printf("str3 and str4 are not same\n");
    17. return 0;
    18. }

    str1和str2不同,原因如下

    1. str1和str2是两个不同的数组意味着不同的空间

    2. str1, str2是数组名也就是首元素地址,空间不同地址自然不同

    .

    str3和str4相同,原因如下

    1. "hello bit." 是一个常量字符串在内存的常量区,然后首元素地址给str3

    2. 因为常量字符串无法改变,所以它可以再次将首元素地址给str4

    3. 这里str3和str4指向的是一个同一个常量字符串。

    .

    C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。


    数组指针 

    1. 类比

    整型指针 - 指向整型变量的指针,存放整型变量的地址的指针变量

    字符指针 - 指向字符变量的指针,存放字符变量的地址的指针变量

    数组指针 - 指向数组的指针,存放数组的地址的指针变量

    2. 指针数组与数组指针 

    p1, p2分别是什么?

    int* p1[10];

    int (*p2)[10];

    解释:p1是指针数组,p2是数组指针。

    p2先和 * 结合,说明p2是一个指针变量,然后指针指向的是一个大小为10个整型的数组。

    所以p2是一个指针,指向一个数组,叫数组指针。

    这里要注意:[ ]的优先级要高于 * 号的,所以必须加上()来保证p2先和 * 结合。

    3. &数组名 与 数组名 

    &arr与arr,输出的值是一样的

    但是类型不一样

    实际上:&arr表示的是整个数组的地址,而不是数组首元素的地址。

    本例中&arr 的类型是:int(*)[10],是一种数组指针类型。

    .

    数组的地址+1,跳过整个数组的大小,所以&arr+1相对于&arr的差值是40。

    .

    数组地址的存放

    3. 数组指针一般用在二维数组传参

    1. void print_arr2(int (*arr)[5], int row, int col)
    2. {
    3. int i = 0;
    4. for(i=0; i
    5. {
    6. for(j=0; j
    7. {
    8. printf("%d ", arr[i][j]);
    9. }
    10. printf("\n");
    11. }
    12. }
    13. int main()
    14. {
    15. int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
    16. print_arr2(arr, 3, 5);
    17. return 0;
    18. }

    print_arr2(arr, 3, 5);
    1. 数组名arr,表示首元素的地址。
    2. 但是二维数组的首元素是二维数组的第一行。
    3. 所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址,需要一个数组指针来接收。


    数组参数,指针参数 

    1. 一维数组传参

    例子1

    1. void test(int arr[]){}//这个没问题
    2. void test(int arr[10]){}//这个也没问题,可见对[]内的数字没有要求
    3. void test(int *arr){}//传入数组名是首元素地址,用指针接收也没问题
    4. int main()
    5. {
    6. int arr[10] = {0}; //一维整型数组
    7. test(arr);
    8. }

    例子2

    1. void test2(int *arr[20]){} //这个没问题
    2. void test2(int **arr){} //传入数组名是首元素地址也就是一个一级指针的地址,所以用二级指针接收
    3. int main()
    4. {
    5. int *arr2[20] = {0}; //一维指针数组
    6. test2(arr2);
    7. }

    2. 二维数组传参  

    例子1

    1. void test(int arr[3][5]){} //正确
    2. void test(int arr[][]){} //错误
    3. void test(int arr[][5]){} //正确
    4. //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
    5. //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
    6. void test(int *arr){} //错误
    7. void test(int* arr[5]){} //错误
    8. void test(int (*arr)[5]){} //正确
    9. void test(int **arr){} //错误
    10. //传入了一个整型一维数组的地址,需要一个类型为整型一维数组的指针接收
    11. int main()
    12. {
    13. int arr[3][5] = {0};
    14. test(arr); //传入数组名也就是首元素地址,二维数组首元素地址是一个一维数组的地址
    15. }

    3. 一级指针传参

    例子1

    1. void print(int *p, int sz)
    2. {
    3. int i = 0;
    4. for(i=0; iprintf("%d\n", *(p+i));
    5. }
    6. int main()
    7. {
    8. int arr[10] = {1,2,3,4,5,6,7,8,9};
    9. int *p = arr;
    10. int sz = sizeof(arr)/sizeof(arr[0]);
    11. print(p, sz); //一级指针变量p,传给函数
    12. return 0;
    13. }

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

    4.  二级指针传参

    例子1

    1. void test(int** ptr) printf("num = %d\n", **ptr);
    2. int main()
    3. {
    4. int n = 10;
    5. int*p = &n;
    6. int **pp = &p;
    7. test(pp); //pp是二级指针变量
    8. test(&p); //&p是一级指针地址
    9. return 0;
    10. }

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


    函数指针 

    1. 类比

    数组指针 - 指向数组的指针

    函数指针 - 指向函数的指针

    2. 函数名 和 &函数名一样,都是函数的地址 

    函数地址的存放

    1. int (*pf)(int, int) = &Add; //pf是函数指针变量,前面返回值,后面参数
    2. int (*)(int, int) //去掉名字是函数指针类型

    3. 函数指针变量调用函数

    1. int Add(int x, int y){return x+y;}
    2. int main()
    3. {
    4. //int (*pf)(int, int) = &Add;
    5. int (*pf)(int, int) = Add; //这两个写法一样
    6. int a = Add(3, 5);
    7. int b = (*pf)(3, 5);
    8. int c = pf(3, 5); //这三个一样,可以不写 *
    9. return 0;
    10. }

    4. 有趣的代码 

    1. //代码1
    2. (*(void(*)())0)();
    3. //1. void(*)()是无返回值无参数的函数指针类型
    4. //2. *(void(*)())0 对0进行强制转换,然后解引用
    5. //3. (*(void(*)())0)() 调用0地址处的这个函数
    6. //代码2
    7. void (*signal(int, void(*)(int)))(int);
    8. //1. signal(int, void(*)(int)) 这是一个函数,参数是整型和函数指针类型
    9. //2. void (*)(int) 把刚刚的函数去掉剩下就是返回类型
    10. //3. 所以这是一个函数声明
    11. 代码2可读性不强,我们可以用typedef
    12. typedef void (*pf_t)(int); //将void (*)(int) 重命名为pf_t
    13. void (*signal(int, void(*)(int)))(int); //原代码
    14. pf_t signal(int, pf_t); //重命名后

    函数指针数组

    1. 类比

    int* arr[5] //整型指针数组

    char* arr[5] //字符指针数组

    函数指针数组:数组的每个元素都是函数指针类型

    2. 用函数指针实现计算器 

    1. int add(int a, int b) { return a + b; }
    2. int sub(int a, int b) { return a - b; }
    3. int mul(int a, int b) { return a * b; }
    4. int div(int a, int b) { return a / b; }
    5. void menu() { printf("0.exit, 1.add, 2.sub, 3.mul, 4.div\n"); }
    6. int main()
    7. {
    8. int (*pf_arr[])(int, int) = { NULL, add, sub, mul, div }; //函数指针数组,转移表
    9. int input, a, b;
    10. int sz = sizeof pf_arr / sizeof pf_arr[0];
    11. while (1)
    12. {
    13. menu();
    14. scanf("%d", &input);
    15. if (input == 0) break;
    16. else if (input > 0 && input <= sz)
    17. {
    18. scanf("%d %d", &a, &b);
    19. printf("%d\n", pf_arr[input](a, b));
    20. }
    21. else printf("input err\n");
    22. }
    23. return 0;
    24. }

    3. 指向函数指针数组的指针 

    指向函数指针数组的指针是一个指针,这个指针指向一个数组,这个数组的元素都是函数指针。 如何定义?

    1. int (*pf)(int, int); //函数指针
    2. int (*pf_arr[4])(int, int); //函数指针数组
    3. int (*(*p_pf_arr)[4])(int, int) = &pf_arr; //函数指针数组的地址
    4. //p_pf_arr就是指向函数指针数组的指针

    回调函数 

    1. 什么是回调函数

    回调函数就是一个通过函数指针调用的函数。

    如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

    回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

    2. 利用回调函数实现计算器 

    1. int add(int a, int b) { return a + b; }
    2. int sub(int a, int b) { return a - b; }
    3. int mul(int a, int b) { return a * b; }
    4. int div(int a, int b) { return a / b; }
    5. void menu() { printf("0.exit, 1.add, 2.sub, 3.mul, 4.div\n"); }
    6. int Cal(int (*pf)(int, int))
    7. {
    8. int a, b;
    9. scanf("%d %d", &a, &b);
    10. printf("%d\n", pf(a, b));
    11. }
    12. int main()
    13. {
    14. int input = 1;
    15. while (input)
    16. {
    17. menu();
    18. scanf("%d", &input);
    19. switch (input)
    20. {
    21. case 0:
    22. break;
    23. case 1:
    24. Cal(add);
    25. break;
    26. case 2:
    27. Cal(sub);
    28. break;
    29. case 3:
    30. Cal(mul);
    31. break;
    32. case 4:
    33. Cal(div);
    34. break;
    35. }
    36. }
    37. return 0;
    38. }

    3. qsort函数

    qsort函数特点:

    1. 采用快速排序的方法

    2. 适合任意类型数据的排序

    .

    qsort函数参数

    void qsort(void* base, //指向需要排序的数组第一个元素

                     size_t num, //排序个数

                     size_t size, //一个元素的大小

                     int (*cmp)(const void*, const void*) //函数指针类型,指向的函数能比较base中元素

                    );

    .

    qsort使用

    1. //void*的指针可以接收任意类型的地址
    2. //但是不能直接解引用和指针运算
    3. //需要强制转换成对应类型
    4. int cmp_int(const void* v1, const void* v2) { return *(int*)v1 - *(int*)v2; }
    5. int main()
    6. {
    7. int arr[] = { 2, 8, 9, 6, 4, 5, 1 };
    8. int sz = sizeof arr / sizeof arr[0];
    9. qsort(arr, sz, sizeof arr[0], cmp_int);
    10. return 0;
    11. }

    4. 模拟实现qsort函数

    冒泡排序思想:两两相邻的元素比较,不满足条件就交换

    1. //冒泡排序
    2. int main()
    3. {
    4. int arr[] = { 2, 8, 9, 6, 4, 5, 1 };
    5. int sz = sizeof arr / sizeof arr[0];
    6. int tmp;
    7. //需要比较sz-1趟
    8. for (int i = 0; i < sz - 1; i++)
    9. {
    10. //每一趟完成后少一个数
    11. for (int j = 0; j-1-i; j++)
    12. {
    13. if (arr[j] > arr[j + 1])
    14. {
    15. tmp = arr[j];
    16. arr[j] = arr[j + 1];
    17. arr[j + 1] = tmp;
    18. }
    19. }
    20. }
    21. return 0;
    22. }

    利用冒泡排序的思想模拟实现qsort函数

    问题1:第一个参数如何接收不同类型。

    解决1:void*的指针解决并给出个数和每个的大小

    问题2:不同类型元素比较方式可能不同

    解决2:将两个元素的比较方法作为函数参数传递

    问题3:不同的数据类型进行交换时不一样

    解决3:利用char*一次只交换一个字节,循环目标字节大小次即可

    1. //比较
    2. int cmp_int(const void* v1, const void* v2) { return *(int*)v1 - *(int*)v2; }
    3. //交换
    4. void swap(char* p1, char* p2, int sz)
    5. {
    6. char tmp;
    7. for (int i = 0; i < sz; i++)
    8. {
    9. tmp = *p1;
    10. *p1 = *p2;
    11. *p2 = tmp;
    12. p1++;
    13. p2++;
    14. }
    15. }
    16. //排序
    17. void bubble_sort(void* base, int num, int sz, int (*cmp)(const void*, const void*))
    18. {
    19. //冒泡思想
    20. for (int i = 0; i < num - 1; i++)
    21. {
    22. for (int j = 0; j < num - 1 - i; j++)
    23. {
    24. //不同类型比较方式不一样所以不能写死
    25. if (cmp((char*)base + j * sz, (char*)base + (j + 1) * sz) > 0)
    26. {
    27. //不同类型交换方式不一样所以不能写死
    28. swap((char*)base + j * sz, (char*)base + (j + 1) * sz, sz);
    29. }
    30. }
    31. }
    32. }
    33. int main()
    34. {
    35. int arr[] = { 2, 8, 9, 6, 4, 5, 1 };
    36. int num = sizeof arr / sizeof arr[0];
    37. bubble_sort(arr, num, sizeof arr[0], cmp_int);
    38. return 0;
    39. }

    练习题 

    数组名表示数组首元素的地址,
    但有两个例外:
    1. sizeof(数组名),这里的数组名表示整个数组,求整个数组的大小
    2. &数组名,这里的数组名表示整个数组

    输出结果?(默认地址为4字节)

    1. int a[] = {1,2,3,4};
    2. printf("%d\n",sizeof(a)); //整个数组
    3. printf("%d\n",sizeof(a+0)); //首元素地址加0
    4. printf("%d\n",sizeof(*a)); //首元素地址解引用
    5. printf("%d\n",sizeof(a+1)); //首元素地址加1
    6. printf("%d\n",sizeof(a[1])); //第二个元素
    7. printf("%d\n",sizeof(&a)); //整个数组的地址
    8. printf("%d\n",sizeof(*&a)); //整个数组的地址解引用
    9. printf("%d\n",sizeof(&a+1)); //整个数组的地址加1
    10. printf("%d\n",sizeof(&a[0])); //第一个元素的地址
    11. printf("%d\n",sizeof(&a[0]+1)); //第一个元素的地址加1

    答:16 4 4 4 4 4 16 4 4 4

     输出结果?(默认地址为4字节)

    1. char arr[] = {'a','b','c','d','e','f'};
    2. printf("%d\n", sizeof(arr)); //整个数组
    3. printf("%d\n", sizeof(arr+0)); //首元素地址加0
    4. printf("%d\n", sizeof(*arr)); //首元素地址解引用
    5. printf("%d\n", sizeof(arr[1])); //第二个元素
    6. printf("%d\n", sizeof(&arr)); //数组的地址
    7. printf("%d\n", sizeof(&arr+1)); //数组地址加1
    8. printf("%d\n", sizeof(&arr[0]+1)); //首元素地址加1
    9. //strlen统计\0之前的字符个数
    10. printf("%d\n", strlen(arr)); //随机值,无法确定\0
    11. printf("%d\n", strlen(arr+0)); //和上一个一样
    12. //strlen需要传入一个地址,当'a'传进去会转成ASCII码值进行地址访问
    13. printf("%d\n", strlen(*arr)); //异常访问
    14. printf("%d\n", strlen(arr[1])); //异常访问
    15. printf("%d\n", strlen(&arr)); //随机值
    16. printf("%d\n", strlen(&arr+1)); //随机值
    17. printf("%d\n", strlen(&arr[0]+1)); //随机值

    答:6 4 1 1 4 4 4

     输出结果?(地址默认为4)

    1. char arr[] = "abcdef";
    2. printf("%d\n", sizeof(arr)); //整个数组大小,字符串后面还有'\0'
    3. printf("%d\n", sizeof(arr+0)); //首元素地址
    4. printf("%d\n", sizeof(*arr)); //首元素
    5. printf("%d\n", sizeof(arr[1])); //第二个元素
    6. printf("%d\n", sizeof(&arr)); //数组地址
    7. printf("%d\n", sizeof(&arr+1)); //数组地址加1
    8. printf("%d\n", sizeof(&arr[0]+1)); //第一个元素地址加1
    9. printf("%d\n", strlen(arr)); //从首元素开始到'\0'之前
    10. printf("%d\n", strlen(arr+0)); //和上一个一样
    11. printf("%d\n", strlen(*arr)); //异常访问
    12. printf("%d\n", strlen(arr[1])); //异常访问
    13. printf("%d\n", strlen(&arr)); //从首元素开始到'\0'之前
    14. printf("%d\n", strlen(&arr+1)); //随机值,因为把'\0'跳过了
    15. printf("%d\n", strlen(&arr[0]+1)); //从第二个元素开始到'\0'之前

    答:7 4 1 1 4 4 4

    6 6 异常 异常 6 随机 5

     输出结果?(地址默认为4)

    1. //这里本质是把字符串"abcdef"首字符的地址放到了p中
    2. char *p = "abcdef";
    3. printf("%d\n", sizeof(p)); //首字符地址
    4. printf("%d\n", sizeof(p+1)); //第二个字符的地址
    5. printf("%d\n", sizeof(*p)); //首字符
    6. printf("%d\n", sizeof(p[0])); //首字符
    7. printf("%d\n", sizeof(&p)); //p的地址
    8. printf("%d\n", sizeof(&p+1)); //p的地址加1
    9. printf("%d\n", sizeof(&p[0]+1)); //首字符地址加1
    10. printf("%d\n", strlen(p)); //从首字符开始到'\0'
    11. printf("%d\n", strlen(p+1)); //从第二个字符开始到'\0'
    12. printf("%d\n", strlen(*p)); //异常访问
    13. printf("%d\n", strlen(p[0])); //异常访问
    14. printf("%d\n", strlen(&p)); //随机值
    15. printf("%d\n", strlen(&p+1)); //随机值
    16. printf("%d\n", strlen(&p[0]+1)); //从第二个字符开始到'\0'

    答:4 4 1 1 4 4 4

    6 5 异常 异常 随机 随机 5

     输出结果?(地址默认为4)

    1. int a[3][4] = {0};
    2. printf("%d\n",sizeof(a)); //整个数组大小
    3. printf("%d\n",sizeof(a[0][0])); //第一个元素
    4. printf("%d\n",sizeof(a[0])); //a[0]相当于第一行这个一维数组的数组名
    5. printf("%d\n",sizeof(a[0]+1)); //第一行这个一维数组的首元素地址加1
    6. printf("%d\n",sizeof(*(a[0]+1))); //第一行的第二个元素
    7. printf("%d\n",sizeof(a+1)); //第二行的地址
    8. printf("%d\n",sizeof(*(a+1))); //第二行的首元素地址
    9. printf("%d\n",sizeof(&a[0]+1)); //第一行的地址加1
    10. printf("%d\n",sizeof(*(&a[0]+1))); //第二行的地址解引用
    11. printf("%d\n",sizeof(*a)); //二维数组首元素地址解引用
    12. printf("%d\n",sizeof(a[3])); //越界了,但sizeof只看它类型就得出结果了

    答:48 4 16 4 4 4 16 4 16 16 16

    解析:数组名先看是否满足两个例外之一,不满足才是首元素地址。

    程序的结果是什么?

    1. int main()
    2. {
    3. int a[5] = { 1, 2, 3, 4, 5 };
    4. int *ptr = (int *)(&a + 1);
    5. printf( "%d,%d", *(a + 1), *(ptr - 1));
    6. return 0;
    7. }

    答:2,5

    假设p 的值为0x100000。 如下表表达式的值分别为多少?

    已知,结构体Test类型的变量大小是20个字节

    1. struct Test
    2. {
    3. int Num;
    4. char *pcName;
    5. short sDate;
    6. char cha[2];
    7. short sBa[4];
    8. }*p = (struct Test*)0x100000;
    9. int main()
    10. {
    11. printf("%p\n", p + 0x1);
    12. printf("%p\n", (unsigned long)p + 0x1); //注意这里不是指针
    13. printf("%p\n", (unsigned int*)p + 0x1);
    14. return 0;
    15. }

    答:00100014 00100001 00100004

    解析:第二个转成无符号长整形加1就是单纯的加1

     代码结果?

    1. int main()
    2. {
    3. int a[4] = { 1, 2, 3, 4 };
    4. int *ptr1 = (int *)(&a + 1);
    5. int *ptr2 = (int *)((int)a + 1);
    6. printf( "%x,%x", ptr1[-1], *ptr2);
    7. return 0;
    8. }

    答:4,2000000

    代码结果?

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

    答:1

    解析:二维数组对于行的初始化需要大括号,图中只是逗号表达式

    代码结果?

    1. int main()
    2. {
    3. int a[5][5];
    4. int(*p)[4];
    5. p = a;
    6. printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    7. return 0;
    8. }

    答:fffffffc,-4

    解析:%p -- 内存是什么值就打印什么值,用十六进制

    代码结果?

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

     答:10,5

    代码结果?

    1. int main()
    2. {
    3. char *a[] = {"work","at","alibaba"}; //放的是字符串首元素地址
    4. char**pa = a;
    5. pa++;
    6. printf("%s\n", *pa);
    7. return 0;
    8. }

    答:at

    解析:a是首元素地址,所以pa也是首元素地址,pa++,*pa就是第二个元素

    代码结果?

    1. int main()
    2. {
    3. char *c[] = {"ENTER","NEW","POINT","FIRST"};
    4. char**cp[] = {c+3,c+2,c+1,c};
    5. char***cpp = cp;
    6. printf("%s\n", **++cpp);
    7. printf("%s\n", *--*++cpp+3);
    8. printf("%s\n", *cpp[-2]+3);
    9. printf("%s\n", cpp[-1][-1]+1);
    10. return 0;
    11. }

    答:POINT ER ST EW

    答:C

    解析:A有点模糊,口头语上指针是指针变量 

    答:C

    解析:如果指针是4个字节的话,意味着地址是由32位组成,则可以形成2的32次方个地址,就可以管理2的32次方字节的空间

     代码运行结果?

    答:0 0 3 4 5

     

    答:C

    解析:指针比较大小就是地址比较大小

     在小端机器中,代码运行结果?

    答:11223300

    解析:%x 按十六进制打印

     代码运行结果?

    答:6 ,12

     

    答:A

    解析:D是数组指针

     

    答:B

    解析:点操作符优先级比星号高

     代码运行结果?

    答:wang

      

    答:D

    解析:stu是通过前面的结构体类型创建的变量

     

    答:D

     

    答:B

     

    答:A

    解析:free释放之后只是把指向的空间回收了,指针变量需要手动置空。

     

    答:C

       

    答:C

    解析:数组指针 - 指向数组的指针

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

    答:C

    解析:看变量名和谁结合,[ ]优先级比*高。

    答:C

    答:A

    解析:把变量名去掉就能得到返回类型,这里因为函数指针返回函数指针,所以需要把函数指针整体当作变量名去掉。

    答:B

    解析:

    答:D

    解析:&arr表示整个数组的地址,类型是int (*)[10]。

     

    答:A

    解析:arr表示数组首元素地址,&arr表示整个数组的地址。

    答:C

    解析:首先这是一个数组,其次元素类型是int*。

    答:B

    解析:1. 返回值和参数类型需要和原函数相同。2. &fun和fun等价都是函数的地址。

     

    答:C

    解析:A,p和[ ]先结合所以是数组不是指针。

               B,p先和( )结合变成函数。

    答: D

    解析:回调函数是调用函数指针指向的函数。

    答:BD

    解析:这是一个存放字符指针的数组,字符串存放的是首字符的地址。

     答:A

    解析:&aa是整个数组的地址,aa是第一行的地址,*aa是第一行的数组名也就是第一行的首元素地址。

    答:C

    解析:首先这是一个二维数组,传入首元素地址是第一行的地址。

    思路:s1是AABCD,那么我们在后面追一个s1就得到AABCDAABCD,这个字符串就包含了s1所有旋转的可能性,再用strstr找。

    1. int check(char* s1, char* s2)
    2. {
    3. //在s1后面追加s1
    4. int len1 = strlen(s1);
    5. strncat(s1, s1, len1);
    6. //同时要保证原本的s1和s2长度相等
    7. int len2 = strlen(s2);
    8. if (len1 != len2) return 0;
    9. //在加长后的s1里面找是否有s2
    10. if (strstr(s1, s2) == NULL) return 0;
    11. else return 1;
    12. }
    13. int main()
    14. {
    15. char s1[20] = "abcd";
    16. char s2[] = "cdab";
    17. if (check(s1, s2)) printf("是\n");
    18. else printf("不是\n");
    19. return 0;
    20. }

     

    思路:以k为分界,左边逆序,右边逆序,整体逆序。

    1. void reverse(char* left, char* right)
    2. {
    3. while (left < right)
    4. {
    5. char tmp = *left;
    6. *left = *right;
    7. *right = tmp;
    8. left++;
    9. right--;
    10. }
    11. }
    12. void StringLeftHanded(char* str, int k)
    13. {
    14. int len = strlen(str);
    15. k %= len;
    16. //1. 左边逆序
    17. reverse(str, str + k - 1);
    18. //2. 右边逆序
    19. reverse(str + k, str + len - 1);
    20. //3. 整体逆序
    21. reverse(str, str + len - 1);
    22. }
    23. int main()
    24. {
    25. char str[5] = "abcd";
    26. StringLeftHanded(str, 2);
    27. return 0;
    28. }

     

    思路:

    1. #define ROW 3
    2. #define COL 3
    3. void Yang(int (*arr)[COL], int *row, int *col, int key)
    4. {
    5. while (*col>=0 && *row
    6. {
    7. //我比这一行最大的都大
    8. if (key > arr[*row][*col]) (*row)++;
    9. //我比这一列最小的都小
    10. else if (key < arr[*row][*col]) (*col)--;
    11. //相等
    12. else return;
    13. }
    14. //走出来意味着没相等
    15. *row = *col = -1;
    16. return;
    17. }
    18. int main()
    19. {
    20. int arr[ROW][COL] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    21. int key;
    22. scanf("%d", &key);
    23. int row = 0;
    24. int col = COL-1;
    25. Yang(arr, &row, &col, key);
    26. if (row == -1) printf("不存在\n");
    27. else printf("存在,下标为(%d,%d)\n", row, col);
    28. return 0;
    29. }

    1. //思路:要换到一直没有空瓶
    2. int main()
    3. {
    4. //输入金额,1金额等于1瓶
    5. int n;
    6. scanf("%d", &n);
    7. //判断喝多少瓶
    8. int sum = 0, flag = 0;
    9. while (n)
    10. {
    11. sum += n;
    12. if (n % 2 == 0) n /= 2; //两空瓶等于1瓶
    13. else
    14. {
    15. flag += n % 2; //不能整除先把余数存起来再除2
    16. n /= 2;
    17. }
    18. if (flag >= 2) //余出来的空瓶超过两瓶就可以换了
    19. {
    20. flag -= 2;
    21. sum++;
    22. }
    23. }
    24. printf("%d\n", sum);
    25. return 0;
    26. }

     

    1. void print(int* _arr, int _sz) { for(int i=0; i<_sz; i++) printf("%d ", *(_arr+i)); }
    2. int main()
    3. {
    4. int arr[] = { 1, 2, 3, 4 };
    5. int sz = sizeof(arr) / sizeof(arr[0]);
    6. print(arr, sz);
    7. return 0;
    8. }

     

    1. //思路:分上下两部分
    2. int main()
    3. {
    4. //输入上部分的行
    5. int line;
    6. scanf("%d", &line);
    7. //打印上部分行
    8. for(int i=0; i
    9. {
    10. //打印空格
    11. for(int j=0; j-1-i; j++) printf(" ");
    12. //打印星
    13. for(int j=0; j2+1; j++) printf("*");
    14. printf("\n");
    15. }
    16. //打印下部分行
    17. for(int i=1; i
    18. {
    19. //打印空格
    20. for(int j=0; jprintf(" ");
    21. //打印星
    22. for(int k=(line*2-1)-(2*i); k>0; k--) printf("*");
    23. printf("\n");
    24. }
    25. return 0;
    26. }

     

    1. int get(int num, int count)
    2. {
    3. if (num == 0) return 0;
    4. int a = num % 10;
    5. num /= 10;
    6. return pow(a, count) + get(num, count); //pow求次方
    7. }
    8. int main()
    9. {
    10. for (int i = 0; i <= 100000; i++)
    11. {
    12. //求i的位数
    13. int cot = 1;
    14. for (int data = i; data > 9; data /= 10) cot++; //不能在循环内改变i
    15. //判断
    16. if(get(i, cot) == i) printf("%d ", i);
    17. }
    18. return 0;
    19. }

     

    1. //前面一项乘10加2等于后面一项
    2. int main()
    3. {
    4. //输入目标数字和项数
    5. int a, n;
    6. scanf("%d %d", &a, &n);
    7. //每项求和
    8. int sum = 0, k = 0;
    9. for (int i = 0; i < n; i++)
    10. {
    11. k = k * 10 + a;
    12. sum += k;
    13. }
    14. printf("%d\n", sum);
    15. return 0;
    16. }

    代码仓库 

    Pointer/Pointer/main.c · 林宇恒/code_c - 码云 - 开源中国 (gitee.com)

  • 相关阅读:
    android中使用opengl(.jni文件使用)
    10-千奇百怪的排序算法
    tomcat面试和Spring的面试题
    C语言--结构体(内容超级详细)
    Redis之SDS底层原理解读
    基于形态学重建和过滤改进FCM算法实现图像分割
    uniapp微信小程序局部刷新,无感刷新,修改哪条数据刷新哪条
    Ubuntu中Python3找不到_sqlite3模块
    LeetCode题解:1720. 解码异或后的数组,异或,JavaScript,详细注释
    解析PR曲线与目标检测中的mAP指标
  • 原文地址:https://blog.csdn.net/m0_71164215/article/details/140370108