• 指针进阶(3)


    9. 模拟实现排序函数

    这里我们使用冒泡排序算法,模拟实现一个排序函数,可以排序任意类型的数据。

    这段代码可以排序整型数据,我们需要在这段代码的基础上进行改进,使得它可以排序任意类型的数据。

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. void bubble_sort(int arr[], int sz)
    4. {
    5. //冒泡排序趟数
    6. int i = 0;
    7. for (i = 0; i < sz - 1; i++)
    8. {
    9. int j = 0;
    10. for ( j = 0; j < sz - 1 - i; j++)
    11. {
    12. if (arr[j] < arr[j + 1])
    13. {
    14. int temp = arr[j];
    15. arr[j] = arr[j + 1];
    16. arr[j + 1] = temp;
    17. }
    18. }
    19. }
    20. }
    21. void print(int arr[], int sz)
    22. {
    23. int i = 0;
    24. for (i = 0; i < sz; i++)
    25. {
    26. printf("%d ", arr[i]);
    27. }
    28. printf("\n");
    29. }
    30. void test1()
    31. {
    32. int arr[] = { 1,2,3,4,5,6,7,8,9,10 };//升序
    33. int sz = sizeof(arr) / sizeof(arr[0]);
    34. print(arr, sz);//排序前打印
    35. bubble_sort(arr,sz);//排序成降序
    36. print(arr, sz);//排序后打印
    37. }
    38. int main()
    39. {
    40. test1();//排序整型数据
    41. }

    那么我们要做的就是将bubble_arr里的int arr[]改为指针的形式,并且是void*类型,这样就可以接收任意类型的数据,再将int sz改为size_t类型,仅仅有元素个数还不够,我们还需要知道一个元素的大小,这样可以方便我们访问,所以我们加一个size_t size,最后我们再加一个比较函数int (*cmp)(const void* e1,const void* e2),这是一个函数指针,所以我们需要根据自己的需求写一个比较函数。

    e1是一个指针,存放了一个要比较的元素的地址
    e2是一个指针,存放了一个要比较的元素的地址
    e1指向的元素>e2指向的元素,返回>0的数字
    e1指向的元素==e2指向的元素,返回0
    e1指向的元素

    1.如果我们要比较整型数据,我们就写一个cmp_int,将const void*强转成(int*)再解引用,然后两个元素进行相减,结果作为这个函数的返回值。由于base的类型是void*,所以不能进行+1,-1,所以我们再将base的类型强转成char*,然后加上j*size,就可以进行每个元素的访问了。我们在进行排序的时候需要将元素交换,所以写一个交换函数swap,将元素的地址和大小传过去就行了。

    2.如果我们要比较结构体数据的首字母,我们写一个cmp_stu_by_name作为比较函数,字符串的比较我们用strcmp,返回值是>0,<0或者0。

    1. #define _CRT_SECURE_NO_WARNINGS 1
    2. #include
    3. void swap(char* buf1, char* buf2, size_t size)
    4. {
    5. int i = 0;
    6. for (i = 0; i < size; i++)
    7. {
    8. char tmp = *buf1;
    9. *buf1 = *buf2;
    10. *buf2 = tmp;
    11. buf1++;
    12. buf2++;
    13. }
    14. }
    15. void bubble_sort(void* base, size_t num, size_t size, int (*cmp)(const void* e1, const void*e2))
    16. {
    17. //冒泡排序的趟数
    18. int i = 0;
    19. for (i = 0; i < num - 1; i++)
    20. {
    21. //一趟冒泡排序
    22. int j = 0;
    23. for (j = 0; j < num - 1 - i; j++)
    24. {
    25. //if (arr[j] > arr[j + 1])
    26. if(cmp((char*)base + j * size, (char*)base + (j + 1) * size)>0)
    27. {
    28. //交换
    29. swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
    30. }
    31. }
    32. }
    33. }
    34. int cmp_int(const void*e1, const void*e2)
    35. {
    36. return *(int*)e1 - *(int*)e2;
    37. }
    38. void test1()
    39. {
    40. int arr[] = { 0,1,2,3,4,5,6,7,8,9 };//升序
    41. //排序为降序
    42. int sz = sizeof(arr) / sizeof(arr[0]);
    43. print_arr(arr, sz);
    44. bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
    45. print_arr(arr, sz);
    46. }
    47. struct Stu
    48. {
    49. char name[20];//20
    50. int age;//4
    51. };
    52. int cmp_stu_by_age(const void* e1, const void*e2)
    53. {
    54. return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
    55. }
    56. int cmp_stu_by_name(const void* e1, const void* e2)
    57. {
    58. return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
    59. }
    60. void test2()
    61. {
    62. struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 15}};
    63. int sz = sizeof(arr) / sizeof(arr[0]);//3
    64. bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
    65. }
    66. int main()
    67. {
    68. //整型数据/字符数据/结构体数据...
    69. //可以使用qsort函数对数据进行排序
    70. //测试bubble_sort,排序整型数据
    71. test1();
    72. //测试bubble_sort,排序结构体的数据
    73. //test2();
    74. return 0;
    75. }

    10. 指针和数组笔试题解析

    一维数组:

    1. int a[] = {1,2,3,4};
    2. printf("%d\n",sizeof(a));

    数组名的理解:
    数组名是数组首元素的地址
    但是有2个例外:
    1. sizeof(数组名),这里的数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
    2. &数组名,这里的数组名表示整个数组,&数组名取出的是数组的地址

    所以答案是4*4=16。

    1. int a[] = {1,2,3,4};
    2. printf("%d\n",sizeof(a+0));

    a并非单独放在sizeof内部,也没有&,所以数组名a是数组首元素的地址,a+0还是首元素的地址。

    只要是地址,大小就是4/8,单位是byte。

    1. int a[] = {1,2,3,4};
    2. printf("%d\n",sizeof(*a));

    a并非单独放在sizeof内部,也没有&,所以数组名a是数组首元素的地址.*a == *(a+0) == a[0]。

    1. int a[] = {1,2,3,4};
    2. printf("%d\n",sizeof(a+1));

    a并非单独放在sizeof内部,也没有&,所以数组名a是数组首元素的地址,a+1就是第二个元素的地址。a+1 == &a[1] 是第2个元素的地址,是地址就是4/8个字节。

    1. int a[] = {1,2,3,4};
    2. printf("%d\n",sizeof(a[1]));

    a[1]就是数组的第二个元素,这里计算的就是第二个元素的大小,单位是字节 - 4

    1. int a[] = {1,2,3,4};
    2. printf("%d\n", sizeof(&a));

    &a - 是取出数组的地址,但是数组的地址也是地址,是地址就是4/8个Byte。

    数组的地址 和 数组首元素的地址 的本质区别是类型的区别,并非大小的区别
        a  -- int*             int * p = a;
       &a -- int (*)[4]     int (*p)[4] = &a;

    1. int a[] = { 1,2,3,4 };
    2. printf("%d\n", sizeof(*&a));

    对数组指针解引用访问一个数组的大小,单位是字节。

    sizeof(*&a) --- sizeof(a) //16

    1. int a[] = { 1,2,3,4 };
    2. printf("%d\n", sizeof(&a + 1));

    &a数组的地址,&a+1还是地址,是地址就是4/8个字节。

    1. int a[] = { 1,2,3,4 };
    2. printf("%d\n", sizeof(&a[0]));

    &a[0]是首元素的地址, 计算的是地址的大小 4/8 个字节。

    1. int a[] = { 1,2,3,4 };
    2. printf("%d\n", sizeof(&a[0] + 1));

    /&a[0]是首元素的地址,&a[0]+1就是第二个元素的地址,大小4/8个字节。

    字符数组

    1. char arr[] = { 'a','b','c','d','e','f' };
    2. printf("%d\n", strlen(arr));

    随机值,arr是首元素的地址.

    1. char arr[] = { 'a','b','c','d','e','f' };
    2. printf("%d\n", strlen(arr + 0));

    随机值,arr是首元素的地址, arr+0还是首元素的地址。

    1. char arr[] = { 'a','b','c','d','e','f' };
    2. printf("%d\n", strlen(*arr));

    err(发生错误),arr是首元素的地址, *arr就是首元素 - 'a' - 97。
    站在strlen的角度,认为传参进去的'a'-97就是地址,97作为地址,直接进行访问,就是非法访问。

    1. char arr[] = { 'a','b','c','d','e','f' };
    2. printf("%d\n", strlen(arr[1]));

    err, 'b' - 98。

    1. char arr[] = { 'a','b','c','d','e','f' };
    2. printf("%d\n", strlen(&arr));

    随机值
    &arr -- char (*)[6]
    const char*。

    1. char arr[] = { 'a','b','c','d','e','f' };
    2. printf("%d\n", strlen(&arr + 1));

    随机值。

    1. char arr[] = { 'a','b','c','d','e','f' };
    2. printf("%d\n", strlen(&arr[0] + 1));

    随机值。

    1. char arr[] = { 'a','b','c','d','e','f' };
    2. printf("%d\n", sizeof(arr));

    6,数组名arr单独放在sizeof内部,计算的是整个数组的大小,单位是字节。

    1. char arr[] = { 'a','b','c','d','e','f' };
    2. printf("%d\n", sizeof(arr + 0));

    arr是首元素的地址==&arr[0],是地址就是4/8个字节。

    char* 
        指针变量的大小和类型无关,不管什么类型的指针变量,大小都是4/8个字节。
        指针变量是用来存放地址的,地址存放需要多大空间,指针变量的大小就是几个字节。
        32位环境下,地址是32个二进制位,需要4个字节,所以指针变量的大小就是4个字节。
        64位环境下,地址是64个二进制位,需要8个字节,所以指针变量的大小就是8个字节。
       不要在门缝里看指针,把指针给看扁了。

    1. char arr[] = { 'a','b','c','d','e','f' };
    2. printf("%d\n", sizeof(*arr));

    arr是首元素的地址,*arr就是首元素,大小就是1Byte。

    1. char arr[] = { 'a','b','c','d','e','f' };
    2. printf("%d\n", sizeof(arr[1]));

    1.

    1. char arr[] = { 'a','b','c','d','e','f' };
    2. printf("%d\n", sizeof(&arr));

    &arr是数组的地址,sizeof(&arr)就是4/8个字节。

    1. char arr[] = { 'a','b','c','d','e','f' };
    2. printf("%d\n", sizeof(&arr + 1));

    &arr+1 是跳过数组后的地址,是地址就是4/8个字节。

    1. char arr[] = { 'a','b','c','d','e','f' };
    2. printf("%d\n", sizeof(&arr[0] + 1));

    第二个元素的地址,是地址就是4/8Byte。

    1. char arr[] = "abcdef";
    2. printf("%d\n", sizeof(arr));

    这个字符串创建的时候末尾必定有一个\0,所以答案是7.

    1. char arr[] = "abcdef";
    2. printf("%d\n", sizeof(arr + 0));

    arr+0就是第一个元素地址,就是4/8。

    1. char arr[] = "abcdef";
    2. printf("%d\n", sizeof(*arr));

     arr既没有单独放到sizeof内部,也没有&,所以是首元素地址,就是a的地址,*arr就是取出a的地址,大小就是一个字节。

    1. char arr[] = "abcdef";
    2. printf("%d\n", sizeof(arr[1]));

    arr[1]就是第二个元素的地址,所以是1.

    1. char arr[] = "abcdef";
    2. printf("%d\n", sizeof(&arr));

    &arr就是取出数组的地址,是地址就是4/8个字节。

    1. char arr[] = "abcdef";
    2. printf("%d\n", sizeof(&arr + 1));

    &arr+1,就是跳过整个数组,但也是地址,就是4/8个字节。

    1. char arr[] = "abcdef";
    2. printf("%d\n", sizeof(&arr[0] + 1));

    &arr[0]就是取出第一个元素的地址,+1就是第二个元素的地址,就是4/8个字节。

    1. char arr[] = "abcdef";
    2. printf("%d\n", strlen(arr));

    这个数组名虽然是单独放在strlen内部,但是和sizeof是有区别的,所以代表的是首元素地址,就是a的地址,统计的是\0之前的字符,就是6.

    1. char arr[] = "abcdef";
    2. printf("%d\n", strlen(arr + 0));

    arr+0代表的是首元素的地址,也是6.

    1. char arr[] = "abcdef";
    2. printf("%d\n", strlen(*arr));

    对首元素的地址进行解引用,就是a,ASCLL值是97,所以会报错。

    1. char arr[] = "abcdef";
    2. printf("%d\n", strlen(arr[1]));

    arr[1]是第二个元素,也会报错。

    1. char arr[] = "abcdef";
    2. printf("%d\n", strlen(&arr));

    取出的是arr的地址,所以是6.

    1. char arr[] = "abcdef";
    2. printf("%d\n", strlen(&arr + 1));

    跳过整个数组,把\0也跳过了,因为不知道啥时候遇到\0,所以是随机值。

    1. char arr[] = "abcdef";
    2. printf("%d\n", strlen(&arr[0] + 1));

    取出的是第二个元素的地址,所以是5.

    1. char* p = "abcdef";
    2. printf("%d\n", sizeof(p));

     把a的地址交给了p,p是一个指针变量,所以sizeof(p)就是4/8.

    1. char* p = "abcdef";
    2. printf("%d\n", sizeof(p + 1));

    char*指针加1就向后跳过一个字节,所以p指向了b,也是地址,所以是4/8.

    1. char* p = "abcdef";
    2. printf("%d\n", sizeof(*p));

    p是char*的指针,所以解引用访问一个字节,就是1.

    1. char* p = "abcdef";
    2. printf("%d\n", sizeof(p[0]));

    p[0]是第一个元素,所以也是1.

    1. char* p = "abcdef";
    2. printf("%d\n", sizeof(&p));

    &p也是地址,地址就是4/8个字节。

    1. char* p = "abcdef";
    2. printf("%d\n", sizeof(&p + 1));

    &p+1也是地址,所以也是4/8个字节。

    1. char* p = "abcdef";
    2. printf("%d\n", sizeof(&p[0] + 1));

    &p[0]+1就是第二个元素的地址,地址就是4/8个字节。

    1. char* p = "abcdef";
    2. printf("%d\n", strlen(p));

    遇到\0就停止,所以是6.

    1. char* p = "abcdef";
    2. printf("%d\n", strlen(p + 1));

    从第二个元素开始,所以是5.

    1. char* p = "abcdef";
    2. printf("%d\n", strlen(*p));

    *p就是a,97,所以会出错。

    1. char* p = "abcdef";
    2. printf("%d\n", strlen(p[0]));

    p[0]就是第一个元素,也是97,会出错。

    1. char* p = "abcdef";
    2. printf("%d\n", strlen(&p));

    &p拿到的是随机值。

    1. char* p = "abcdef";
    2. printf("%d\n", strlen(&p + 1));

    &p+1也是随机值。

    1. char* p = "abcdef";
    2. printf("%d\n", strlen(&p[0] + 1));

    &p[0]+1,就是第二个元素的地址,所以是5.


    今天的分享到这里就结束啦!谢谢老铁们的阅读,让我们下期再见。

  • 相关阅读:
    torch.nn.functional.grid_sample(F.grid_sample)函数的说明 & 3D空间中的点向图像投影的易错点
    Java开发学习(二十三)----SpringMVC入门案例、工作流程解析及设置bean加载控制
    RabbitMQ 学习(一)-- 概念和安装
    微信小程序开发学习—Day1
    化工管理杂志化工管理杂志社化工管理编辑部2023年第30期目录
    使用pyvista显示有透明度信息的点云数据
    万字深剖进程地址空间(全程干货)
    将ESP工作为AP路由模式并当成服务器
    django-rest-framework 基础三 认证、权限和频率
    【PaLM2】PaLM2 大语言模型与 Bard 使用体验
  • 原文地址:https://blog.csdn.net/2301_79035870/article/details/132945115