加载中...
从零接触C语言(初览)-XI: 指针

从零接触C语言(初览)-XI: 指针

周三 9月 03 2025
3509 字 · 14 分钟

指针

有许多人说, 指针是C语言中最难的知识点, 说它抽象、难以理解

也说指针是造成C语言不安全的原因之一, 说它危险、不受控制

但也有许多人说, 指针是C语言的魅力所在, 说它高效、灵活自由

指针几乎是所有C语言使用者心中一个又爱又恨的角色

那么, 究竟什么是指针?

内存 **

要理解C语言的指针, 就必须要先了解一个概念: 内存

可能有人会问: 是手机上 8G+128G 12G+256G 16G+512G 配置里, 128G 256G 512G的”内存”吗?

不是, 严格意义上来讲, 手机上的128G 256G等不叫内存, 前面的8G 12G 16G才叫内存

内存, 是电脑上的内存条的内存, 是手机上的运行内存, 通常也被称为RAM

而手机常见配置里的128G 256G 512G, 以及电脑上的硬盘等, 应该被称为外存

不谈手机, 电脑系统中, 你可以很简单的的看到自己的电脑内存是多大的、现在已经用了多少:

  1. Linux

    终端直接输入free并回车:

    total, 就是总可用内存

    used, 就是已用内存

    free, 就是空闲可用内存

  2. Windows

    可以直接打开任务管理器查看当前的内存使用状态:

当然, 这些不是重点

为什么理解C语言的指针, 要先理解内存呢?

因为, 在现代通用计算机中, 所有的C语言代码编译为程序之后, 在运行时, 都是运行在内存上的

而指针, 则是C语言提供的一种可以直接操作内存的机制

当一个程序需要运行的时候, 为了程序能够正常的运行, 操作系统就要给程序分配一块内存, 让程序的代码、数据能够存放在内存中, 然后程序才能运行起来

一般情况下, 内存是由操作系统分配管理的

为了有效地使用内存, 操作系统会将内存划分为一块一块的内存单元, 每个内存单元是1个字节

为了更好地管理、有效地访问内存, 操作系统还会对每个内存单元进行唯一编号, 内存单元的编号就是我们常说的 内存单元的地址, 即 内存地址

简单用图片理解, 内存可以看作是这样的:

什么是指针? **

当程序被加载运行后, 它的代码和数据都会被加载到内存中

换句话说, 操作系统会为程序分配若干内存单元, 并把程序的”代码”和”数据”存放到里面

所以, 以C语言程序的角度来看, 我们在代码中定义的各种变量, 也会被加载到内存中

且不同数据类型占用内存的大小是不同的: char类型变量只需要占用1字节(即 1个内存单元), int类型的变量需要占用连续的4字节(即 4个连续的内存单元)

上面介绍到, 操作系统会对每一个内存单元都进行编号, 这个编号就是内存单元的地址

而, C语言规定 以变量所占用的第一个内存单元的地址作为该变量的地址, 且每个变量都有属于自己的地址

指针, 就是C语言提供的一种能够存储并操作这些地址的机制

指针, 可以存储地址并访问地址, 这种能力看起来和变量很相似

事实上, 指针本质就是一种变量, 不过与普通变量不同的是, 指针存储的是地址这种特殊数据, 而普通变量存储的是特定类型的数据

变量的地址

在正式开始使用指针之前, 我们先来看一下变量是否有自己的地址

其实很简单, 在介绍操作符的文章中有一个操作符是&取地址, 我们可以用这个操作符取变量的地址, 并打印显示:

C
#include <stdio.h>

int main() {
    int val = 10;
    printf("val addr: %p\n", &val);

    return 0;
}

这段代码编译运行的结果是:

从结果看到, val确实存在地址0x7fffe135589c

那么, 变量val在内存中的示意图暂时可以简单理解为:

指针的定义

指针是一种变量, 但它不是普通的变量, 它与普通变量的定义也有一些差别

介绍操作符的文章中有一个操作符是*, 它可以用来定义指针

C
数据类型* 标识符;

没错, 定义指针变量与定义普通变量只有一个区别, 那就是*

定义变量时, 在数据类型之后加上*, 定义的就是指针变量

C
char* pS = NULL;
int* pI = NULL;
long* pL = NULL;

向上面这样, 就定义了三种类型的指针变量: char* int* long*, 且均赋值为NULL

指针变量的类型, 就分别对应char* int* long*

当然, 除了给指针赋值为NULL之外, 我们还可以使用取地址符&, 取出已有变量的地址, 存储到指针变量中

需要注意的是, 普通变量和指针变量的类型要一致, 具体原因不在本篇说明

C
#include <stdio.h>

int main() {
    int val = 20;
    int* pVal = &val;
    printf("val: %d, addr: %p\npVal: %p\n", val, &val, pVal);
    
    return 0;
}

这段代码, 定义int类型变量val, 取变量val的地址 并赋值给 定义的int类型指针pVal

并依次打印: val的值, val的地址, pVal的值

代码的编译运行结果为:

从结果看到: &val取到的地址是0x7ffc810495e4, 而pVal的值也是0x7ffc810495e4

即, pVal这个指针变量, 存储了变量val的地址

指针的使用

已经了解了指针的定义和指针的赋值, 但这有什么用呢?

我们知道, 变量存储数据, 是为了在之后的代码中, 通过这个变量使用这个数据

指针也是一样的, 指针存储地址, 是为了之后可以通过指针访问、操作这个地址的内存单元

那么指针是如何访问地址的内存单元的呢?

答案, 在上面也已经提到过了: *解引用操作符

*不仅在定义指针时需要, 在访问指针存储地址的内存单元时也需要

使用方法也很简单, 在指针变量前加*就可以访问指针存储地址的内存单元:

C
#include <stdio.h>

int main() {
    int val = 20;
    int* pVal = &val;
    printf("pVal 指向地址的数据值为: %d\n", *pVal);
    printf("val 的值为: %d\n", val);

    *pVal = 30;
    printf("pVal 指向地址的数据值为: %d\n", *pVal);
    printf("val 的值为: %d\n", val);

    return 0;
}

这段代码的执行结果为:

从结果来看

  1. 第一次, 尝试打印*pVal

    打印成功, 打印出的值为20

    即, 通过*pVal获取到的值与val相同

    事实上, *pVal访问到的就是val所占用的内存单元

    因为pVal存储的就是val的地址, *pVal访问的当然就是val的内存单元, 其实就可以看作是访问val本身

  2. 第二次, 尝试给*pVal赋值30, 并打印*pValval

    从结果看, *pVal成功赋值为30

    而且, 打印*pValval的结果都是30

    事实上, 还是相同的原因: *pVal访问到的就是val所占用的内存单元, 尝试给*pVal赋值, 就是尝试修改pVal存储地址的内存单元, 这个内存单元就是val所占用的内存单元

    所以*pVal赋值, 就相当于修改val本身

所以, 当一个指针存储有另一个变量的地址时, 就可以直接通过指针操作对应地址的内存单元, 也就能够操作这个变量的值

为了更形象的理解, 我们也可以将 指针存储变量的地址 理解为 指针指向这个变量的地址:

我们可以通过指针, 访问此指针指向的地址


了解了指针本身, 再来了解一些其他的 指针基本用法:

  1. 多个指针可以指向同一个地址

    这个意思就是说, 同一个变量的地址, 可以赋值给多个不同的指针, 且这多个指针都可以访问这个变量的地址

    C
    int val = 20;
    int* pVal1 = &val;
    int* pVal2 = &val;
    int* pVal3 = &val;
    int* pVal4 = &val;
    int* pVal5 = &val;

    此代码定义5个指针, 赋值同一个变量的地址

    可以理解为: 5个指针指向同一个地址

    这也意味着, 只要val的值被修改, 通过*访问5个指针指向地址的值, 都会一起变动

  2. 指针的指向是可以发生改变的

    这个意思是说, 一个指针, 可以先指向变量1的地址, 然后再指向变量2的地址或其他变量的地址

    但, 一个指针的指向 同时最多只有一个, 即 指针不能在指向变量1地址的同时 还指向变量2的地址

    C
    #include <stdio.h>
    
    int main() {
        int val1 = 20;
        int val2 = 30;
    
        int* pVal = &val1;
        // 此时, pVal指向val1的地址
        printf("pVal: %p, *pVal: %d\n", pVal, *pVal);
        
        pVal = &val2;    // 给pVal赋值, 而不是给*pVal赋值
        // 此时, pVal指向val2的地址
        printf("pVal: %p, *pVal: %d\n", pVal, *pVal);
            
        return 0;
    }

  3. 指针类型最好与 要指向地址的变量数据类型相匹配

    这个原因暂时不做分析

指针变量的大小

C语言中, 变量类型决定了变量的大小: char1字节, int4字节, long long8字节等

而指针的类型是数据类型*, 那么, 指针变量的大小是多少呢?

指针变量的大小, 其实与定义指针时使用的数据类型无关

指针变量的大小, 在通用计算机平台, 通常只与操作系统有关

指针是用来存储地址的, 而地址 是系统分配的内存单元的编号, 即 指针本质上存储的是内存单元的编号

那么指针的大小, 其实就是这个系统分配的内存单元的编号的大小, 所以指针的大小与操作系统有关

更具体地说, 指针的大小其实与操作系统的位数有关

32位系统中, 指针的大小为4字节

64为系统中, 指针的大小通常为8字节

同样可以用代码举例:

C
#include <stdio.h>

int main() {
    printf("char* size: %lu\n", sizeof(char*));
    printf("short* size: %lu\n", sizeof(short*));
    printf("int* size: %lu\n", sizeof(int*));
    printf("double* size: %lu\n", sizeof(double*));

    return 0;
}

现在的操作系统, 基本都用的64位版本了, 所以打印出来大概率是8


Thanks for reading!

从零接触C语言(初览)-XI: 指针

周三 9月 03 2025
3509 字 · 14 分钟