【C++漂流记】一文搞懂指针的定义和使用、野指针、空指针、const修饰指针、指针与数组、指针与函数

指针是C和C++语言中的一个重要概念,它是一个变量,存储另一个变量的内存地址。通过指针,可以间接地访问该变量的值。指针在内存管理、数据结构、系统编程等领域有广泛应用。使用指针时需要注意指针的正确性和安全性,避免空指针、野指针等错误的使用方式,以避免程序出现不可预期的行为。
在这里插入图片描述


一、指针的定义和使用

指针是C语言中的一个重要概念,它是一种变量,存储的是另一个变量的地址。通过指针,我们可以间接地访问和修改变量的值。
在这里插入图片描述
定义和使用指针可以通过以下方式:

  1. 声明一个指针变量:int* ptr; 声明一个指向 int 类型的指针 ptr
  2. 初始化指针:int* ptr = # 将指针 ptr 初始化为变量 num 的地址。
  3. 解引用指针:int value = *ptr; 解引用指针 ptr,获取指针所指向的值。

下面是一个指针的简单示例:

#include <stdio.h>

int main() {
    
    
    int a = 10;   // 定义一个整数变量a,并初始化为10
    int *p;        // 定义一个指针变量p

    p = &a;        // 将a的地址赋给p

    printf("a的值:%d\n", a);   // 输出a的值
    printf("a的地址:%p\n", &a);   // 输出a的地址
    printf("p指向的值:%d\n", *p);   // 输出p指向的值,也就是a的值
    printf("p的地址:%p\n", p);   // 输出p的地址,也就是a的地址

    *p = 20;       // 通过指针修改a的值

    printf("a的新值:%d\n", a);   // 输出a的新值,也就是20

    return 0;
}

解释:

  1. int a = 10; 定义了一个整数变量 a,并将其初始化为 10
  2. int *p; 定义了一个指针变量 p,这个变量存储的是一个整数变量的地址。
  3. p = &a; 这行代码将 a 的地址赋值给 p。现在,p 存储的是 a 的地址。
  4. printf("a的值:%d\n", a); 这行代码输出 a 的值,结果是 10
  5. printf("a的地址:%p\n", &a); 这行代码输出 a 的地址,结果是类似 0x7fff5fbffb68 这样的内存地址。
  6. printf("p指向的值:%d\n", *p); 这行代码输出 p 指向的值,也就是 a 的值。因为 p 存储的是 a 的地址,所以 *p 就表示访问这个地址处的值,结果是 10
  7. printf("p的地址:%p\n", p); 这行代码输出 p 的地址,也就是 a 的地址。这是因为 p 存储的是 a 的地址,所以输出的地址也是 a 的地址。
  8. *p = 20; 这行代码通过指针修改了 a 的值。因为 p 存储的是 a 的地址,所以 *p 就表示访问这个地址处的值,现在将这个值设置为 20,实际上就是修改了 a 的值。
  9. printf("a的新值:%d\n", a); 这行代码输出 a 的新值,也就是 20。可以看到,通过指针修改了 a 的值。

二、指针占用的内存

不同的系统和编译器可能会为指针分配不同的字节大小。通常来说,在32位系统中,指针占用4个字节(32位 = 4字节),而在64位系统中,指针占用8个字节。这是因为在32位系统中,地址空间是232,所以每个地址必须用32位(4字节)来表示。同样,在64位系统中,地址空间是2^64,所以每个地址必须用64位(8字节)来表示。

你可以使用C语言的sizeof运算符来获取指针的字节大小。

以下是一个简单的示例:

#include <stdio.h>

int main() {
    
    
    int *p;
    printf("指针p的字节大小:%zu\n", sizeof(p));
    return 0;
}

这段代码将输出指针变量p的字节大小。在32位系统中,结果为4;在64位系统中,结果为8。


三、空指针

空指针(null pointer)是一种特殊类型的指针,不指向任何对象或函数。空指针通常用于表示没有有效指针的情况。

在C和C++语言中,空指针的表示形式为NULL(在C++中也可以使用nullptr)。当一个指针被初始化为NULL时,它就是一个空指针。

使用空指针可能会导致未定义的行为,因为无法确定指针指向的内存位置。为了避免这种情况,最好在使用指针之前先检查其是否为空。例如:

int *ptr = nullptr;
if (ptr != nullptr) {
    // 指针不为空,可以使用它
    int value = *ptr;
} else {
    // 指针为空,不能使用它
    // 可以进行适当的错误处理或初始化操作
}

在上面的代码中,我们首先检查指针是否为空,然后才使用它。这样可以避免未定义的行为。


四、野指针

野指针(wild pointer)是指在C和C++中,未初始化的指针或未释放的指针。野指针是很危险的,它们可能会导致程序崩溃或者产生不可预期的行为。

为了避免野指针的问题,程序员应该在定义指针时进行初始化,或者在使用完指针后及时释放内存。例如:

int *ptr = malloc(sizeof(int)); // 正确的方式,先分配内存,再定义指针
if (ptr == NULL) {
    
    
    // 处理错误
}

// ... 使用 ptr ...

free(ptr); // 释放内存
ptr = NULL; // 将指针设置为 NULL,避免野指针

在上面的代码中,我们首先使用malloc函数分配了内存,然后定义了一个指向该内存的指针。在使用完指针后,我们使用free函数释放了内存,并将指针设置为NULL。这样可以避免野指针的问题。


五、const修饰指针

在C语言中,const关键字可以用来修饰指针,以限制指针的使用权限。

const修饰指针时,根据const的位置,可以有两种不同的语法:

  1. const在指针符号的左边,表示指针指向的是一个常量,即不可通过指针来修改该地址上的值。例如:
const int *p;

这里,const修饰的是int类型的变量,而指针p指向这个变量。这意味着你不能通过指针p来修改该地址上的值,即:

*p = 10; // 编译错误,不能通过指针修改该地址上的值
  1. const在指针符号的右边,表示指针本身是一个常量指针,即该指针一旦指向某个地址后,就不能再指向其他地址。例如:
int *const p;

这里,const修饰的是指针p本身,而不是p所指向的变量。这意味着指针p一旦指向某个地址后,就不能再指向其他地址,即:

p = &a; // 可以编译通过,p指向变量a的地址
p = &b; // 编译错误,p不能指向其他地址
  1. const还可以同时修饰指针本身和指针所指向的数据,表示指针指向的数据不可通过指针修改,并且指针本身也不能被重新赋值。例如:
const int *const p;

这里,const修饰指针p所指向的int类型数据,表示不能通过指针p修改该数据;同时,const也修饰指针p本身,表示指针p不能被重新赋值。因此,指针p一旦指向某个地址后,就不能再指向其他地址,并且不能通过指针p修改该地址上的值。


六、指针和数组

指针和数组之间有着紧密的联系,因为数组的名称可以当作一个指针使用。下面将分别介绍指针和数组的相关知识,以及它们之间的联系。

  1. 指针

指针是一个变量,其值为另一个变量的地址。通过指针,可以间接地访问另一个变量。指针有指向数据类型的指针和指向函数的指针之分。指向数据类型的指针用于存储数据类型的地址,指向函数的指针用于调用函数。

  1. 数组

数组是一种有序的数据结构,可以存储相同类型的多个元素。数组的名称可以看作是一个指向数组第一个元素的指针。因此,可以通过数组名称来访问数组元素。

  1. 指针和数组的联系

指针和数组之间有着密切的联系。首先,数组的名称可以看作是一个指向数组第一个元素的指针。其次,可以通过指针来访问数组元素。例如,可以使用指针的加法运算来遍历数组。

下面是一个示例代码,演示了指针和数组的联系:

#include <stdio.h>

int main() {
    
    
    int arr[] = {
    
    1, 2, 3, 4, 5};
    int *p = arr; // 数组名称可以看作是一个指向数组第一个元素的指针

    // 使用指针遍历数组
    for (int i = 0; i < 5; i++) {
    
    
        printf("%d ", *(p + i));
    }

    return 0;
}

在上述代码中,定义了一个名为arr的整数类型数组,并将其初始化为{1, 2, 3, 4, 5}。然后,定义了一个指向整数类型的指针p,并将其初始化为arr。这里,arr可以看作是一个指向数组第一个元素的指针。接下来,使用指针p来遍历数组,输出每个元素的值。

需要注意的是,当使用指针访问数组元素时,需要使用指针的加法运算来计算元素的地址。例如,*(p + i)表示指针p所指向的地址加上i个元素的大小,即访问数组的第i个元素。


七、指针和函数

指针和函数之间也有一定的联系。通过指针,可以将函数的地址传递给另一个函数,从而调用该函数。此外,指针也可以用于作为函数的参数,以实现更灵活和高效的操作。

下面是一个示例代码,演示了指针和函数之间的联系:

#include <stdio.h>

void func(int *p) {
    
    
    printf("Value: %d\n", *p);
}

int main() {
    
    
    int a = 10;
    int *p = &a; // 指针p指向变量a的地址

    // 调用函数,传递指针p作为参数
    func(p);

    return 0;
}

在上述代码中,定义了一个名为func的函数,该函数接受一个指向整数类型的指针作为参数。在main函数中,定义了一个整数类型变量a,并初始化为10。然后,定义了一个指向整数类型的指针p,并将其初始化为a的地址。接下来,调用func函数,并将指针p作为参数传递给该函数。在func函数中,通过指针p访问变量a的值,并输出结果。

猜你喜欢

转载自blog.csdn.net/Goforyouqp/article/details/132699238