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

本文涉及的产品
云解析DNS-重点域名监控,免费拨测 20万次(价值200元)
简介: 指针的主题,我们在初级阶段的 【维生素C语言】第六章 - 指针 章节已经接触过了,我们知道了指针的概念:

b7f8287d04e55a75e5f0e4a7cc7355c5_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

前言


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


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


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


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


4. 指针的运算。


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


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


一、字符指针


0x00 字符指针的定义

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

dfa2785be7800ed1260a66ba988cd5ee_20210615171351741.png

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


🚩  运行结果如下:

10f955b691b278e4176a15ce670dcdaa_20210616045428785.png


🔑 解析:


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


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

941e30b01320473e351b7813f8aac3a3_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png



0x03 Segmentfault 问题

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


二、指针数组


0x00 指针数组的定义

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

8c9161183e16d83eb9bdada2791fdd5c_20210616055859329.png

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

27b58c2f1a5005a82c37fb056ca00826_20210616060014419.png


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


🚩 运行结果如下:

213e67daf8b8d01cf920e044c66b4c62_20210616061032273.png

🔑 解析:

c85c62de608eb58a3ad590b6e0baaa5d_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


三、数组指针


0x00 数组指针的定义

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


整形指针 - 是指向整型的指针
字符指针 - 是指向字符的指针
数组指针 - 是指向数组的指针

0897e1f77cc5ec30cc41f30879c7be2b_20210616062813415.png

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

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

00d90528950e89b7eb4c504d086c0425_20210616064720450.png

🔑 解析:

168836950d92fbaf6a96902e867d4bf1_20210616065039563.png

💬 验证:


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

🚩 运行结果如下:

f0d9558205521fe183ead595798696c2_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

🔺 数组名是数组首元素的地址,但是有 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;
}

🚩 运行结果如下:

210d39a9d62a337c97df15ca7694ac66_20210616095046827.png


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

💡 解析:

3504546abba1867c340fb01b8de86612_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


四、数组参数和指针参数


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


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


🚩 答案:以上都合理


🔑 解析:

c3765e7274ebec8a59e58018d44e065f_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


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

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


🔑 解析:

9a3190be63640132ebb2335e6ec50f90_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


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


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


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


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


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


🔑 解析:

eb23dfdd60da97cf1ddd0aa37989a697_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


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


相关文章
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
240 1
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
390 7
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
10月前
|
存储 人工智能 Java
一文轻松拿捏C语言的指针的基础使用
本文介绍了C语言中的指针概念,包括直接访问和间接访问内存的方式、指针变量的定义与使用、取址运算符`&`和取值运算符`*`的应用,帮助读者深入理解指针这一C语言的核心概念。君志所向,一往无前!
181 0
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
1322 9
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
1140 13
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
370 7
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
392 11
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
733 3