【维生素C语言】第十章 - 指针的进阶(上)

 🔥 CSDN 累计订阅量破千的火爆 C/C++ 教程的 2023 重制版,C 语言入门到实践的精品级趣味教程。
了解更多: 👉 "不太正经" 的专栏介绍 试读第一章
订阅链接: 🔗《C语言趣味教程》 ← 猛戳订阅!

前言:

指针的主题,我们在初级阶段的 【维生素C语言】第六章 - 指针 章节已经接触过了,我们知道了指针的概念:

1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。

2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。

3. 指针是有类型的,指针的类型决定了指针的 + - 整数步长,指针解引用操作时的权限。

4. 指针的运算。

这个章节,我们将继续探讨指针的高级主题。

🚪 【维生素C语言】第十章 - 指针的进阶(下)


一、字符指针

0x00 字符指针的定义

📚 定义:字符指针,常量字符串,存储时仅存储一份(为了节约内存)

0x01 字符指针的用法

💬 用法:

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'w';
    
    return 0;
}

💬 关于指向字符串:

❓ 这里是把一个字符串放在 pstr 里了吗?

int main()
{
    char* pstr = "hello world";
    printf("%s\n", pstr);
    
    return 0;
}

🚩  hello world

🔑 解析:上面代码 char* pstr = " hello world "  特别容易让人以为是把 hello world 放在字符指针 pstr 里了,但是本质上是把字符串 hello world 首字符的地址放到了 pstr 中;

0x02 字符指针练习

💬 下列代码输出什么结果?

int main()
{
    char str1[] = "abcdef";
    char str2[] = "abcdef";
    const char* str3 = "abcdef";
    const char* str4 = "abcdef";

    if (str1 == str2)
        printf("str1 == str2\n");
    else
        printf("str1 != str2\n");

    if (str3 == str4)
        printf("str3 == str4\n");
    else
        printf("str3 != str4\n");

    return 0;
}

🚩  运行结果如下:

🔑 解析:

① 在内存中有两个空间,一个存 arr1,一个存 arr2,当两个起始地址在不同的空间上的时候,这两个值自然不一样,所以 arr1 和 ar2 不同。

② 因为 abcdef 是常量字符串,本身就不可以被修改,所以内存存储的时候为了节省空间只存一份,叫 abcdef。这时,不管是 p1 还是 p2,都指向同一块空间的起始位置,即第一个字符的地址,p1 和 p2值又一模一样,所以 arr3 和 arr4 相同。

0x03 Segmentfault 问题

💬 把一个常量字符串的首字符 a 的地址存放到指针变量 pstr 中:

二、指针数组

0x00 指针数组的定义

📚 指针数组是数组,数组:数组中存放的是指针(地址)

[] 优先级高,先与 p 结合成为一个数组,再由 int* 说明这是一个整型指针数组,它有 n 个指针类型的数组元素。这里执行 p+1 时,则 p 指向下一个数组元素。

0x01 指针数组的用法

💬 几乎没有场景用得到这种写法,这个仅供理解:

int main()
{
    int a = 10;
    int b = 20;
    int c = 30;
    int* parr[4] = {&a, &b, &c};

    int i = 0;
    for(i=0; i<4; i++) {
        printf("%d\n", *(parr[i]) );
    }
 
    return 0;
}

🚩  10  20  30

💬 指针数组的用法:

#include <stdio.h>

int main()
{
    int arr1[] = {1, 2, 3, 4, 5};
    int arr2[] = {2, 3, 4, 5, 6};
    int arr3[] = {3, 4, 5, 6, 7};

    int* p[] = { arr1, arr2, arr3 }; // 首元素地址

    int i = 0;
    for(i=0; i<3; i++) {
        int j = 0;
        for(j=0; j<5; j++) {
            printf("%d ", *(p[i] + j)); // j-> 首元素+0,首元素+1,+2...
            // == p[i][j] 
        }
        printf("\n");
    }
    
    return 0;
}

🚩 运行结果如下:

🔑 解析:

三、数组指针

0x00 数组指针的定义

📚 数组指针是指针,是指向数组的指针,数组指针又称 行指针,用来存放数组的地址

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

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

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

int main()
{
    int a = 10;
    int* pa = &a;
    char ch = 'w';
    char* pc = &ch;

    int arr[10] = {1,2,3,4,5};
    int (*parr)[10] = &arr; // 取出的是数组的地址
    // parr 就是一个数组指针

    return 0;
}

💬 试着写出 double* d [5] 的数组指针:

double* d[5];
double* (*pd)[5] = &d;

0x01 数组名和&数组名的区别

💬 观察下列代码:

int main()
{
    int arr[10] = {0};

    printf("%p\n", arr);
    printf("%p\n", &arr);

    return 0;
}

🚩 运行后我们发现,它们地址是一模一样的

🔑 解析:

💬 验证:

int main()
{
    int arr[10] = { 0 };

    int* p1 = arr;
    int(*p2)[10] = &arr;

    printf("%p\n", p1);
    printf("%p\n", p1 + 1);

    printf("%p\n", p2);
    printf("%p\n", p2 + 1);

    return 0;
}

🚩 运行结果如下:

🔺 数组名是数组首元素的地址,但是有 2 个 例外:

①  sizeof ( 数组名 )  - 数组名表示整个数组,计算的是整个数组的大小,单位是字节。

②  &数组名 - 数组名表示整个数组,取出的是整个数组的地址。

0x02 数组指针的用法

💬 数组指针一般不在一维数组里使用:

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int (*pa)[10] = &arr; // 指针指向一个数组,数组是10个元素,每个元素是int型
    
    int i = 0;
    for(i=0; i<10; i++) {
        printf("%d ", *((*pa) + i));
    }

    return 0;
}

❓ 上面的代码是不是有点别扭?数组指针用在这里非常尴尬,并不是一种好的写法。

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

    return 0;
}

💬 二维数组以上时使用数组指针:

void print1 (
    int arr[3][5], 
    int row, 
    int col
    )
{
    int i = 0;
    int j = 0;
    for(i=0; i<row; i++) {
        for(j=0; j<col; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

void print2 (
    int(*p)[5], // 👈 数组指针,指向二维数组的某一行
    int row, 
    int col
    )
{
    int i = 0;
    int j = 0;
    for(i=0; i<row; i++) {
        for(j=0; j<col; j++) {
            printf("%d ", *(*(p + i) + j));
            // printf("%d ", (*(p + i))[j]);
            // printf("%d ", *(p[i] + j));
            // printf("%d ", p[i][j]);
        }
        printf("\n");
    }
}

int main()
{
    int arr[3][5] = {{1,2,3,4,5}, {2,3,4,5,6}, {3,4,5,6,7}};
    // print1(arr, 3, 5);
    print2(arr, 3, 5); // arr数组名,表示元素首元素的地址

    return 0;
}

🚩 运行结果如下:

0x03 关于数组访问元素的写法

💡 以下写法全部等价:

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int i = 0;
    int* p = arr;
    for(i=0; i<10; i++)
    {
        //以下写法全部等价
        printf("%d ", p[i]);
        printf("%d ", *(p+i));
        printf("%d ", *(arr+i));
        printf("%d ", arr[i]); //arr[i] == *(arr+i) == *(p+i) == p[i]
    }
}

0x04 练习

💬 分析这些代码的意思:

int arr[5];                                                   
int* parr1[10];                                                             
int (*parr2)[10];                                                        
int (*parr3[10])[5];

💡 解析:

四、数组参数和指针参数

写代码时要把数组或者指针传递给函数的情况在所难免,那函数参数该如何设计呢?

0x00 一维数组传参

💬 判断下列形参的设计是否合理:

void test(int arr[]) //合理吗?
{}
void test(int arr[10]) // 合理吗?
{}
void test(int *arr) // 合理吗?
{}
void test(int *arr[]) // 合理吗?
{}
void test2(int *arr[20]) // 合理吗?
{}
void test2(int **arr) // 合理吗?
{}

int main()
{
    int arr[10] = {0};
    int* arr2[20] = {0};
    test(arr);
    test2(arr2);
}

🚩 答案:以上都合理

🔑 解析:

0x01 二维数组传参

💬 判断下列二维数组传参是否合理:

void test(int arr[3][5]) // 合理吗?
{}
void test(int arr[][5]) // 合理吗?
{}
void test(int arr[3][]) // 合理吗?
{}
void test(int arr[][]) // 合理吗?
{}

int main()
{
    int arr[3][5] = {0};
    test(arr); // 二维数组传参
    
    return 0;
} 

🚩 答案:前两个合理,后两个不合理

🔑 解析:

🔺 总结:二维数组传参,函数形参的设计只能省略第一个 [ ] 的数字(行可省略但列不可以省略

因为对一个二维数组来说,可以不知道有多少行,但是必须确定一行有多少多少元素!

💬 判断下列二维数组传参是否合理:

void test(int* arr) // 合理吗?
{}
void test(int* arr[5]) // 合理吗?
{}
void test(int(*arr)[5]) // 合理吗?
{}
void test(int** arr) // 合理吗?
{}

int main()
{
    int arr[3][5] = { 0 };
    test(arr);

    return 0;
}

🚩 答案:只有第三个合理,其他都不合理

🔑 解析:

0x02 一级指针传参

💬 一级指针传参例子:

void print(int* ptr, int sz) // 一级指针传参,用一级指针接收
{
    int i = 0;
    for(i=0; i<sz; i++) {
        printf("%d ", *(ptr + i));
    }
}

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int *p = arr;
    int sz = sizeof(arr) / sizeof(arr[0]);
    // p是一级指针,传给函数
    print(p, sz);

    return 0;
}

🚩  1 2 3 4 5 6 7 8 9 10

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

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

void test1(int* p)
{}
void test2(char* p)
{}

int main()
{
    int a = 10;
    int* pa = &a;
    test1(&a);  // ✅
    test1(pa);  // ✅
    
    char ch = 'w';
    char* pc = &ch;
    test2(&ch); // ✅
    test2(pc);  // ✅

    return 0;
}

📌 需要掌握:

      ① 我们自己在设计函数时参数如何设计

      ② 别人设计的函数,参数已经设计好了,我该怎么用别人的函数

0x03 二级指针传参

💬 二级指针传参例子:

void test(int** ptr)
{
    printf("num = %d\n", **ptr);
}

int main()
{
    int n = 10;
    int* p = &n;
    int** pp = &p;
    
    // 两种写法,都是二级指针
    test(pp);
    test(&p); // 取p指针的地址,依然是个二级指针

    return 0;
}

🚩  num = 10   num = 10

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

💬 当函数参数为二级指针时:

void test(int **p) // 如果参数时二级指针
{
    ;
}

int main()
{
    int *ptr;
    int** pp = &ptr;
    test(&ptr); // 传一级指针变量的地址 ✅
    test(pp); // 传二级指针变量 ✅
    
    //指针数组也可以
    int* arr[10]; 
    test(arr); // 传存放一级指针的数组,因为arr是首元素地址,int* 的地址 ✅

    return 0;
}


参考资料:

Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .

比特科技. C语言进阶[EB/OL]. 2021[2021.8.31]. .

📌 本文作者: Foxny

📃 更新记录: 2021.6.15

勘误记录:

📜 本文声明: 由于作者水平有限,本文有错误和不准确之处在所难免,本人也很想知道这些错误,恳望读者批评指正!

未完待续……

 🚪 【维生素C语言】第十章 - 指针的进阶(下)

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王平渊

喜欢的话可以支持下我的付费专栏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值