深入理解 C 语言指针

发布时间 2023-11-21 18:48:08作者: Ba11ooner

指针

程序

代码
#include<stdio.h>

void charPtTest() {
    // 声明一级指针
    char *p1;
    char *p2;
    char *p3;

    // 利用字符串初始化一级指针:将一级指针指向字符串首地址
    // 获取地址 By & (取地址运算符)↑
    // 强制类型转换(可选)
    p1 = (char *) &"Hello";

    // 利用一级指针初始化一级指针:将一级指针的值赋给一级指针,本质上就是两个一级指针指向同一个地址
    // 获取地址 By 直接赋值 ←
    p2 = p1;

    // 利用字符串初始化一级指针:将一级指针指向字符串首地址
    // 获取地址 By & ↑(缩写形式)
    p3 = "Hello";

    printf("字符串常量的值:%s\n", "Hello");
    printf("字符串常量的地址:%p\n", "Hello");

    printf("\n");
    printf("一级指针\n");
    // 按照字符串形式打印一级指针的值,就是按照字符串形式打印一级指针中存储的地址,即字符串常量的地址
    // 按照指针形式打印一级指针的值,就是按照指针形式打印一级指针中存储的地址,即字符串常量的地址
    // 打印的对象都是一级指针的值,即字符串常量的地址,因为是字符串常量,所以支持两种不同的输出格式
    printf("p1 的值: %s %p\n", p1, p1);
    printf("p1 的地址: %p\n", &p1);
    printf("p2 的值: %s %p\n", p2, p2);
    printf("p2 的地址: %p\n", &p2);
    printf("p3 的值: %s %p\n", p3, p3);
    printf("p3 的地址: %p\n", &p3);

    // 声明二级指针,同时给二级指针赋值,二级指针的值就是一级指针的地址
    // 获取地址 By & ↑
    char **pp1 = &p1;
    char **pp2 = &p2;
    char **pp3 = &p3;

    printf("\n");
    // 二级指针的值就是一级指针的地址
    // 因为二级指针存储的是地址,所以只能按照指针形式打印二级指针的值,而不能按照字符串形式打印
    printf("二级指针\n");
    printf("pp1 的值: %p\n", pp1);
    printf("pp1 的地址: %p\n", &pp1);

    printf("pp2 的值: %p\n", pp2);
    printf("pp2 的地址: %p\n", &pp2);

    printf("pp3 的值: %p\n", pp3);
    printf("pp3 的地址: %p\n", &pp3);


    printf("\n");
    printf("一级指针\n");
  
    // 声明一级指针
    char *str1;
    // 利用二级指针初始化一级指针
    // 获取二级指针的值(一级指针的地址) By * (取值运算符)↓
    str1 = *pp1;
    printf("str1 的值:%s %p\n", str1, str1);
    printf("str1 的地址:%p\n", &str1);

    // 利用强制类型转换初始化一级指针
    char *p_str1 = (char *) pp1;
    // 因为二级指针存储的是地址,所以只能按照指针形式打印二级指针的值,而不能按照字符串形式打印
    // 此处强行通过类型转换将二级指针变成一级指针,能打印,但是打印结果是乱码
    printf("p_str1 的值:%s %p\n", p_str1, p_str1);
    printf("p_str1 的地址:%p\n", &p_str1);

    printf("\n");
    printf("常量\n");
    // 获取指针的值 By * ↓
    // 获取一级指针的值(字符串首地址):第一个字符
    char ch = *p1;
    printf("ch 的值: %c\n", ch);
    printf("ch 的地址: %p\n", &ch);
}

int main() {
    charPtTest();
    return 0;
}
结果
字符串常量的值:Hello
字符串常量的地址:0x104e1fdc4

一级指针
p1 的值: Hello 0x104e1fdc4
p1 的地址: 0x16afe3800
p2 的值: Hello 0x104e1fdc4
p2 的地址: 0x16afe37f8
p3 的值: Hello 0x104e1fdc4
p3 的地址: 0x16afe37f0

二级指针
pp1 的值: 0x16afe3800
pp1 的地址: 0x16afe37e8
pp2 的值: 0x16afe37f8
pp2 的地址: 0x16afe37e0
pp3 的值: 0x16afe37f0
pp3 的地址: 0x16afe37d8

一级指针
str1 的值:Hello 0x104e1fdc4
str1 的地址:0x16afe37d0
p_str1 的值:��� 0x16afe3800
p_str1 的地址:0x16afe37c8

常量
ch 的值: H
ch 的地址: 0x16afe37c7
int 的值: 1
int 的地址: 0x16afe37b4

关系图

引用关系

基于结果整理引用关系

graph LR p1[p1] p2[p2] p3[p3] he[Hello] pp1[pp1] pp2[pp2] pp3[pp3] p4[p4] p_p1[一级指针 p_p1] p1-->he p2-->he p3-->he pp1-->p1 pp2-->p2 pp3-->p3 p4-->he p_p1-->p1
赋值关系

基于程序整理复制关系

graph LR p1[p1] p2[p2] p3[p3] he[Hello] pp1[pp1] pp2[pp2] pp3[pp3] p4[p4] p_p1[一级指针 p_p1] pp1 -.(char*)=.-> p_p1 pp1 -.*.->p4 p3 -.&.-> pp3 p2 -.&.-> pp2 p1 -.&.-> pp1 he -.&.->p3 p1 -.=.-> p2 he -.(char*)&.-> p1
合并

合并引用关系图和赋值关系图

graph LR p1[p1] p2[p2] p3[p3] he[Hello] pp1[pp1] pp2[pp2] pp3[pp3] p4[p4] p_p1[一级指针 p_p1] p1 -.=.-> p2 p1-->he he -.(char*)&.-> p1 p3-->he he -.&.->p3 pp1-->p1 p1 -.&.-> pp1 pp1 -.(char*)=.-> p_p1 pp1 -.*.->p4 pp3-->p3 p3 -.&.-> pp3 p4-->he p_p1-->p1 p2-->he pp2-->p2 p2 -.&.-> pp2

总结

  • 指针赋值手段

    • 正统做法

      二级指针
       * ↓ 取值
      一级指针 ← 赋值 一级指针 
       & ↑ 取地址
        变量
      
      graph LR 01[二级指针 pp1] 02[一级指针 p1] 03[一级指针 p2] 04[字符串常量 str] 01-->02-->04 03-->04 01-.*.-> 03 02-.=.->03 04-.&.->03
      • 二级指针取值:取值后的到二级指针指向的一级指针的指向,将该指向赋给一级指针
      • 一级指针赋值:两个一级指针指向同一片内存空间,但是两个一级指针的地址不同
      • 变量取地址:获取变量首地址,将一级指针指向该地址
    • 强制类型转换:所有指针存的都是地址,可用通过强制类型转换

      二级指针 赋值 By 强类型转换 → 一级指针 
      
      graph LR 01[二级指针 pp1] 02[一级指针 p1] 03[一级指针 p2] 04[字符串常量 str] 01-->02-->04 03-->02 01-.=(char*).->03