学而时习之:C语音中的指针

C 语言中的指针

指针是一种变量,它存储的是另一个变量的内存地址。与直接存储值不同,指针存储的是 在内存(运行内存)中的位置(地址)。它是 C 语言中进行底层内存操作的核心工具。

指针的声明方式是指定其数据类型,并在变量名前加上星号(*)。

ini 复制代码
指针声明语法格式为:  数据类型   *指针名;

数据类型表示该指针可以指向的变量类型。例如,int *ptr; 声明了一个指向整型变量的指针。

直接访问指针变量只会得到它所存储的地址。如果想获取该地址中存储的值,我们需要使用 * 运算符,这称为"解引用"操作符。

需要注意的是,* 在指针中有两种不同的用途:

  1. 用于声明指针变量;
  2. 作为运算符,用于获取指针所指向地址中的值。

c 复制代码
#include <stdio.h>

int main()
{
    // 普通变量
    int var = 10;

    // 指针变量 ptr,用于存储变量 var 的地址
    int *ptr = &var;

    // 直接访问 ptr 会输出一个地址
    printf("%d", ptr);

    return 0;
}

输出: 1751215308 这个十六进制整数(以 0x 开头)是内存地址。

让我们来了解一下上述程序的不同步骤。

1. 初始化指针

指针的初始化是通过使用取地址运算符(&)将其指向一个变量的地址来完成的。

语法:pointer_name = &variable;

初始化指针可以确保在使用前它指向一个有效的内存位置。

如果指针暂时不指向任何变量,也可以将其初始化为 NULL:int *ptr = NULL;

2. 解引用指针

我们必须先对指针进行解引用,才能访问该内存地址中存储的值。这一步通过解引用运算符(*)完成(该运算符与声明指针时使用的符号相同)。

c 复制代码
#include <stdio.h>

int main() {
    int var = 10;

    // 指针变量的初始化, 存储变量 var 的地址
    int* ptr = &var;

    printf("%d", *ptr); // 解引用 ptr 以访问其中的值
    printf("%p", ptr);  // 打印指针
    return 0;
}
复制代码
输出  
10 
0x7ffc83d4f7cc

注意:之前我们曾用 %d 打印指针,但 C 专门提供了格式说明符 %p 用于打印指针。

3. 指针的大小

C 语言中指针的大小取决于机器操作系统的架构(位数),而不是它所指向的数据类型。

  • 在 32 位系统上,所有指针通常占用 4 字节。
  • 在 64 位系统上,所有指针通常占用 8 字节。

无论数据类型是什么(int*、char*、float* 等),指针的大小都是固定的。我们可以用 sizeof 运算符来验证这一点。

c 复制代码
#include <stdio.h>

int main() {
    int *ptr1;
    char *ptr2;

    // 使用 sizeof() 获取大小
    printf("%zu\n", sizeof(ptr1));
    printf("%zu", sizeof(ptr2));

    return 0;
}
复制代码
输出  
8  
8

所有指针大小相同,是因为它们存储的是内存地址,而不是数据本身。既然不同内存地址的存储需求相同,那么各类指针所需的内存空间也就一致。

注意:指针的实际大小可能因编译器和系统架构而异,但在同一系统上,所有数据类型的指针大小始终是统一的。

特殊类型的指针

有 4 种在不同场景下使用或提到的特殊指针类型:

第一种类型:NULL 指针

NULL 指针是指那些不指向任何内存地址的指针。

可以通过将指针赋值为 NULL 来创建。任何类型的指针都可以被赋值为 NULL。

这样我们就能通过判断指针是否等于 NULL,来检查它是否指向一个有效的内存位置。

c 复制代码
#include <stdio.h>

int main() {
    // 空指针
    int *ptr = NULL;

    return 0;
}

第二种类型:void 指针(空类型指针)

C 语言中的 void 指针是指类型为 void 的指针。 这意味着它们没有关联的具体数据类型。

因此也被称为"通用指针",可以指向任意类型的数据,并且可以通过类型转换变成任意其他类型的指针。

c 复制代码
#include <stdio.h>

int main() {
    // void 指针
    void *ptr;

    return 0;
}

第三种类型:野指针

野指针是指那些尚未被初始化的指针。

这类指针在程序中会引发各种问题,甚至可能导致程序崩溃。 如果通过野指针去修改值,可能会造成数据异常或数据损坏。

c 复制代码
#include <stdio.h>

int main() {
    // 野指针
    int *ptr;

    return 0;
}

第四种类型:悬空指针

当指针指向的内存已被释放(delete 或 free)后,该指针就称为悬空指针。

这种情况会导致程序出现意想不到的行为,也是 C 程序中常见的 bug 来源之一。

c 复制代码
#include <stdio.h>
#include <stdlib.h>

int main() {
    int* ptr = (int*)malloc(sizeof(int));

    // 执行 free 后,ptr 变成悬空指针
    free(ptr);
    printf("Memory freed\n");

    // 解除悬空状态
    ptr = NULL;

    return 0;
}
复制代码
输出  
Memory freed

第五种类型:常量指针(Constant Pointers)

在常量指针中,指针内部存储的内存地址是固定的,一旦定义后便不能再被修改。也就是说,它将始终指向同一个内存地址。

c 复制代码
#include <stdio.h>

int main() {
    int a = 90;
    int b = 50;

    // 创建一个常量指针
    int* const ptr = &a;

    // 尝试将其重新指向 b
    ptr = &b;  // 错误:不能给只读变量 'ptr' 赋值

    return 0;
}

输出错误信息:

ini 复制代码
solution.c: In function 'main':
solution.c:11:9: error: assignment of read-only variable 'ptr'
   11 |     ptr = &b;
      |         ^

第六种类型:函数指针

函数指针是一种存储函数地址的指针,它允许将函数作为参数传递,并可以动态调用函数。这在回调函数、事件驱动程序等技术中非常有用。

c 复制代码
#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int main() {

    // 声明一个与 add() 函数签名匹配的函数指针
    int (*fptr)(int, int);

    // 将 add() 的地址赋给指针
    fptr = &add;

    // 通过指针调用函数
    printf("%d", fptr(10, 5));

    return 0;
}
复制代码
输出  
15

第七种类型:多级指针

在 C 语言中,我们可以创建任意层级的多级指针,例如:***ptr3****ptr4******ptr5 等等。

其中最常用的是二级指针(指针的指针)。它存储的是另一个指针的内存地址。也就是说,它指向的不是数据本身,而是另一个指针。

c 复制代码
#include <stdio.h>

int main() {
    int var = 10;

    // 指向 int 的指针
    int *ptr1 = &var;

    // 指向指针的指针(二级指针)
    int **ptr2 = &ptr1;

    // 使用这三种方式访问值
    printf("var: %d\n", var);
    printf("*ptr1: %d\n", *ptr1);
    printf("**ptr2: %d", **ptr2);

    return 0;
}

输出

makefile 复制代码
var: 10
*ptr1: 10
**ptr2: 10

指针的优势

以下是 C 语言中指针的主要优势:

  • 用于动态内存的分配与释放
  • 借助指针可高效访问数组或结构体
  • 便于直接访问内存地址
  • 可构建链表、图、树等复杂数据结构
  • 缩短程序代码并减少运行时间

指针带来的问题

指针容易产生错误,主要缺点如下:

  • 若赋予指针错误的值,可能导致内存损坏
  • 概念相对复杂,不易掌握
  • 是 C 语言中内存泄漏的主要原因
  • 通过指针访问的速度比直接访问变量慢
  • 未初始化的指针可能引发段错误
相关推荐
冷凝雨1 天前
FreeRTOS源码学习(一)内存管理heap_1、heap_3
嵌入式·c·freertos·内存管理·源码分析
小志biubiu3 天前
linux_缓冲区及简单libc库【Ubuntu】
linux·运维·服务器·c语言·学习·ubuntu·c
Dragon_D.4 天前
排序算法大全——插入排序
算法·排序算法·c·学习方法
iriczhao7 天前
【u-boot】u-boot的分区支持
c·u-boot·bootloader·引导加载
煤球王子8 天前
学而时习之:C语言中的"悬空指针"、"空类型指针"、"野指针"
c
煤球王子8 天前
学而时习之:C语言中的内存管理
c
。。。90413 天前
mit6s081 lab8 locks
操作系统·c
CAU界编程小白13 天前
数据结构系列之堆
数据结构·c
煤球王子15 天前
学而时习之:C语言中文件操作Error处理
c