指针详解2

运行环境以Dev-C++、Visual Studio 2022、MacOS的命令行和Xcode为主

1.数组名的实质

  • 数组名就是数组首元素的地址,但是有两个意外

    • sizeof(数组名)sizeof中单独存放数组名,此处的数组名表示整个数组,计算的是整个数组的大小,单位为字节

    • &数组名,此处的数组名表示整个数组,取出的是整个数组的地址,该地址与数组首元素的地址有区别,具体参考下文代码

    // 代码1:分别打印数组名和数组首元素的地址,结果一样,数组名就是数组第一个元素的地址
    #include <stdio.h>
    
    int main(int argc, const char * argv[]) {
        int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        printf("&arr[0] = %p\n", &arr[0]);
        printf("arr     = %p\n", arr);
        
        return 0;
    }
    

    image

    // 代码2:由于sizeof中单独放数组名,表示的是整个数组,计算数组占用内存的大小,共 40B
    #include <stdio.h>
    
    int main(int argc, const char * argv[]) {
        int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        printf("%zd\n", sizeof(arr));
        
        return 0;
    }
    

    image

    // 代码3:数组首元素的地址、数组名和数组的地址在数值上一样
    #include <stdio.h>
    
    int main(int argc, const char * argv[]) {
        int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        printf("&arr[0] = %p\n", &arr[0]);
        printf("arr     = %p\n", arr);
        printf("&arr    = %p\n", &arr);
    
        return 0;
    }
    

    image

    // 代码4:数组首元素的地址、数组名和数组的地址分别加 1 的区别
    #include <stdio.h>
    int main(int argc, const char * argv[]) {
        int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        
        // 两者相差4个字节,&arr[0] 是首元素的地址,加 1 就是跳过一个元素
        printf("    &arr[0] = %p\n", &arr[0]);
        printf("&arr[0] + 1 = %p\n", &arr[0] + 1);
        
        // 两者相差4个字节,arr 也是数组首元素的地址,加 1 就是跳过一个元素
        printf("        arr = %p\n", arr);
        printf("    arr + 1 = %p\n", arr + 1);
    
        // 两者相差40个字节,因为 &arr 是数组的地址,加 1 操作是跳过整个数组
        // 十进制40的十六进制为28,16fdf280 + 00000028 = 16fdf2a8
        printf("       &arr = %p\n", &arr);
        printf("   &arr + 1 = %p\n", &arr + 1);
        
        return 0;
    }
    

    image

2.利用指针访问数组

  • 在积累了前面的知识后,结合数组的特点,可以很方便地使用指针访问数组

  • 编译器在处理数组元素访问的时候,通过首元素的地址加偏移量求得元素的地址,然后解引用来访问

#include <stdio.h>

int main(int argc, const char * argv[]) {
    int arr[10] = {0};
    int sz = sizeof(arr) / sizeof(arr[0]);
    
    int i = 0;
    int *p = &arr[0];
    // int *p = arr;    // 这里的数组名就是数组首元素的地址,两者等价

    // 输入
    for (i = 0; i < sz; i++) {
        scanf("%d", p + i);
        // scanf("%d", arr + i);
    }
    
    // 输出
    for (i = 0; i < sz; i++) {
        printf("%d ", *(p + i));
        // printf("%d", p[i]);    // p[i] 本质上等价于 *(p + i)。类似地,arr[i] 等价于 *(arr + i)
        // printf("%d", *(arr + i));
        // printf("%d", arr[i]);
        // printf("%d", i[arr]);    // [] 作为运算符,i 和 arr 是它两边的运算数,类比加法运算,[] 满足交换律
    }
    printf("\n");
    
    return 0;
}

3.一维数组传参的实质

  • 3.1 数组名作为函数参数

    • 之前的代码通常在自定义函数外部求数组元素的个数。如果将数组传给一个自定义函数,理论上在函数内部也能求出数组的元素个数。参考以下代码
    #include <stdio.h>
    
    void test1(int arr[]) {
        int sz1 = sizeof(arr) / sizeof(arr[0]);
        printf("sz1 = %zd\n", sz1);
    }
    
    void test2(int arr[10]) {
        int sz2 = sizeof(arr) / sizeof(arr[0]);
        printf("sz2 = %zd\n", sz2);
    }
    
    int main(int argc, const char * argv[]) {
        int arr[10] = {0};
        int sz = sizeof(arr) / sizeof(arr[0]);
        
        printf("sz = %zd\n", sz);
        test1(arr);
        test2(arr);
        
        return 0;
    }
    
    //
    

    image

    • 观察运行结果发现在函数内部并没有获得正确的数组元素个数

    • 数组名是数组首元素的地址,那么在数组传参时传递的是数组名,数组传参在本质上传递的是数组首元素的地址

    • 理论上,函数形参的部分应使用指针变量来接收首元素的地址,函数内部的sizeof(arr)计算的是一个地址的大小(单位是字节)而不是数组的大小(单位是字节)

    • 正是因为函数的参数部分本质是指针,所以在函数内部无法求得数组元素的个数

  • 3.2 指针作为函数参数

    • 一维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式
    #include <stdio.h>
    
    void test1(int arr[]) {
        printf("%zd\n", sizeof(arr));
    }
    
    void test2(int *arr) {
        printf("%zd\n", sizeof(arr));
    }
    
    int main(int argc, const char * argv[]) {
          int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
          test1(arr);
          test2(arr);
          
          return 0;
    }
    

    image

4.冒泡排序

  • 核心思想:升序排序时,两两相邻的元素进行比较,如果前面的比后面的大,那就交换这两个数。详细的过程可参考排序专题

  • 代码实现

// 方法1
#include <stdio.h>

void bubble_sort(int arr[], int sz) {
    int i = 0;
 
    for (i = 0; i < sz - 1; i++) {
        int j = 0;
        for (j = 0; j < sz - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                int temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

int main(int argc, const char * argv[]) {
    int arr[] = {3, 1, 7, 5, 8, 9, 0, 2, 4, 6};
    int sz = sizeof(arr) / sizeof(arr[0]);

    bubble_sort(arr, sz);
    int i = 0;
    for (i = 0; i < sz; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}

// 方法2
#include <stdio.h>

int count = 0;    // 计数变量,统计相邻两个数比较的次数
void input(int *arr, int sz) {
    int i = 0;
    for (i = 0; i < sz; i++) {
        scanf("%d", arr + i);
    }
}

void print_arr(int *arr, int sz) {
    int i = 0;
    for (i = 0; i < sz; i++) {
        printf("%d ", *(arr + i));
    }
    printf("\n");
}

void bubble_sort(int *arr, int sz) {
    int i = 0;
    int j = 0;
    int temp = 0;

    for (i = 0; i < sz - 1; i++) {
        int flag = 1;    // 假设这一趟数已经有序了
        for (j = 0; j < sz - i - 1; j++) {
            count++;
            if (arr[j] > arr[j + 1]) {
                flag = 0;  // 发生交换就说明此时数组无序
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
        if (flag == 1) {    // 一趟过后没有交换说明已经有序,后续无需在排列
            break;
        }
    }
}

int main(int argc, const char * argv[]) {
    int arr[10] = {0};
    int sz = sizeof(arr) / sizeof(arr[0]);

    input(arr, sz);
    bubble_sort(arr, sz);
    print_arr(arr, sz);
    printf("count = %d\n", count);
    return 0;
}

5.二级指针

  • 5.1 定义和指向原理

    • C语言中的普通变量都有地址。类似地,指针变量是一种特殊的变量,它也应当有地址

    • 指针变量的地址存放在二级指针中

    #include <stdio.h>
    
    int main(int argc, const char * argv[]) {
        int a = 10;
        int *pa = &a;
        int **ppa = &pa;
    
        printf("&a   = %p\n", &a);
        printf("&pa  = %p\n", &pa);
        printf("&ppa = %p\n", &ppa);
    
        return 0;
    }
    

    image

    image

  • 5.2 二级指针的运算

    • *ppa通过解引用ppa中的地址,找到了pa*ppa访问的其实就是pa
    int a = 20;
    *ppa = &a;    // 等价于 pa = &a;
    
    • **ppa先通过*ppa找到pa,然后对pa进行解引用操作,即*pa找到了a
    **ppa = 30;
    // 等价于 *pa = 30;
    // 等价于 a = 30;
    

6.指针数组

  • 6.1 定义和指向原理

    • 整型数组是存放整型数据的数组,字符数组是存放字符型数据的数组

    • 类似地,指针数组应该是存放指针类型数据的数组

    image

  • 6.2 指针数组模拟二维数组

    • parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型一维数组,parr[i][j]就是整型一维数组中的元素
    • 代码模拟出二维数组的效果,实际上并非完全是二维数组,因为每一行不是连续的
    #include <stdio.h>
    
    int main(int argc, const char * argv[]) {
        int arr1[] = {1, 2, 3, 4, 5};
        int arr2[] = {2, 3, 4, 5, 6};
        int arr3[] = {3, 4, 5, 6, 7};
        
        // 数组名是数组首元素的地址,类型是int *的,就可以存放在parr数组中
        int *parr[] = {arr1, arr2, arr3};
        int i = 0, j = 0;
        for (i = 0; i < 3; i++) {
            for (j = 0; j < 5; j++) {
                printf("%d ", parr[i][j]);
                // printf("%d ", *(*(parr + i) + j));
            }
            printf("\n");
        }
        
        return 0;
    }
    

    image

posted @ 2025-08-14 10:13  pycoder_666  阅读(17)  评论(0)    收藏  举报