51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

C/C++ 指针语法详解

C 语言是一种强大的编程语言,它提供了指针的概念和相关的语法。指针是一种变量,它存储了内存地址,可以用于直接访问和操作内存中的数据。

  1. C 指针类型的作用

  2. 多级指针

  3. 指针与内存 {#title-0} ===================

指针与 int、float 等类型一样就是普通的数据类型,唯一不同的是像 int、double 这样的类型变量存储的是值,而指针存储的是另外一个变量的地址而已,如下图所示:

上面的定义的变量 value 是普通的 int 类型变量,它所属的内存空间中存储的是普通的数据。而指针变量 p 所属的内存空间中存储的则是另外一个变量的地址。这里有个问题:从内存的角度来看,无论是指针变量还是普通变量存储的都是二进制数据,我们如何区分某个变量存储的是普通的数据还是地址数据呢?这就要从语法上来区分了,如下所示:

int value = 100;
int* p = 0x3456;

只要看到变量定义时旁边有个星,那就表示该变量存储的是地址。另外,指针在定义时,还应该知道存储的地址是什么类型变量的地址,比如:int* 表示变量存储的地址 0x3456 所代表的内存空间中存储的是 int 类型数据。所以,我们也可以说 p 变量指向一个 int 类型的变量。

指针也是一个变量,所以指针为了存储地址值也需要占用内存空间,并且也有自己的地址。

如果你理解了这个规律,那么如果指针变量指向的变量仍然是指针,如下图所示:

p2 变量的类型可以表示为 int*,p1 类型是什么呢?如何表示?答案就是二级指针,表示如下:

// * 星号表示 p1 变量存储的是地址,是个指针变量
// int* 表示 p1 指向的变量是 int* 类型
// 合起来写成 int**
int**

以此类推,如果再来一个指针变量 p0 指向 p1,则 p0 就是三级指针,写作 int*** p0。

如何获得一个变量的地址呢?对变量使用取地址符号 &, 就可以拿到变量地址,变量地址输出时,使用占位符 %p,下面是示例代码:

#include <stdio.h>

int main() {


    int iv = 100;
    int* ip = &iv;   // 获得变量 iv 的内存地址,赋值到 ip 变量中
    int** ipp = &ip;  // 获得变量 ip 的内存地址,赋值到 ipp 变量中

    printf("iv 变量的地址是: %p %p\n", &iv, ip);
    printf("ip 变量的地址是: %p %p\n", &ip, ipp);


    // 其他类型指针
    double dv = 3.14;
    double* dp = &dv;
    double** dpp = &dp;

    return 0;
}

程序输出结果:

iv 变量的地址是: 0x7ffee79be6f8 0x7ffee79be6f8
ip 变量的地址是: 0x7ffee79be6f0 0x7ffee79be6f0
  1. 指针的运算规则 {#title-1} =====================

基本类型的变量能够进行一些算术运算,例如:对变量进行加减法计算。指针变量也可以进行一些运算。我们这里主要讲解指针的加减法、解引用两种操作。

  1. 指针的加减法指的是移动指针在内存中的指向,这个操作很危险,一定要注意;
  2. 指针的解引用就是拿到指针指向空间的值。

2.1 指针加减法 {#title-2}

#include <stdio.h>

int main() {

    // NULL 表示指针指向地址为0的空间
    int* ip = NULL;
    printf("ip 存储的地址: %ld\n", ip);
    ip += 1;
    printf("ip 存储的地址: %ld\n", ip);

    printf("--------------\n");

    double* dp = NULL;
    printf("dp 存储的地址: %ld\n", dp);
    dp += 1;
    printf("dp 存储的地址: %ld\n", dp);
    
    return 0;
}

程序运行结果:

ip 存储的地址: 0
ip 存储的地址: 4
--------------
dp 存储的地址: 0
dp 存储的地址: 8

从输出结果可以看到,ip 和 dp 都是指针变量,但是 + 1 操作之后的值是不同的。这就要提到指针步长的概念。指针+1操作我们可以理解为指针在内存中向前移动了一步,至于这一步是多少个字节,取决于指针指向数据的类型大小,例如:

  1. ip 指向的数据的类型是 int,而 int 是 4 字节大小,故而 ip 移动一步就会在内存中向前移动 4 个字节;
  2. dp 指向的数据的类型是 double,而 double 是 8 字节大小,故而 dp 移动一步就会在内存中向前移动 8 个字节。

那如果 ip + 2 会移动多少个字节呢?

2.2 指针解引用 {#title-3}

当我们拿到一个指针变量,如果通过该指针变量拿到其指向内存中存储的值呢?这就是解引用操作,如下代码所示: 多字节变量的地址指的是首字节的地址。指针 p 存储的是多字节类型的首字节地址,解引用时会根据类型大小读取相应数量字节的数据。

#include <stdio.h>

int main() {

    int value = 100;
    int* p = &value;

    // *p 表示从 p 变量中取出存储的内存地址,并寻址到该空间。此时,我们可以
    // 从该空间中取值,如下:
    printf("p 指向变量的地址: %d\n", *p);
    // 修改该空间的值,如下:
    *p = 200;
    printf("p 指向变量的地址: %d\n", *p);

    // 通过指针间接修改了 value 变量的值
    printf("value 变量值是: %d\n", value);


    return 0;
}

程序运行结果:

p 指向变量的地址: 100
p 指向变量的地址: 200
value 变量值是: 200

2.3 越界内存访问 {#title-4}

我们了解了指针的算术运算和解引用操作,那么就要注意一个在 C/C++ 很容易出现的致命错误,叫做越界内存读写。所谓的越界内存读写,是我们通过指针操作没有操作权限的内存。有权限的内存指的是我们自己申请的内存。

#include <stdio.h>

int main() {

    int value = 100;
    int* p = &value;

    // p 指针进行移动,此时指向我们没有申请的内存
    p += 1;

    // 如果只是访问越界内存的值,只是数据可能是错误的
    printf("越界内存中的值:%d\n", *p);

    // 注意: 如果尝试去修改越界内存,这是极其危险的操作
    // 有些系统会直接终止我们程序的运行
    // 有些则看起来允许你进行修改,但是这是未定义的行为
    *p = 200;
    printf("越界内存中的值:%d\n", *p);

    return 0;
}

赞(2)
未经允许不得转载:工具盒子 » C/C++ 指针语法详解