【C语言程序设计】第28篇:指针的概念与指针变量

1 引言

考虑这样一个问题:我们有一个变量 int a = 10;,它存储在内存中的某个位置。我们可以通过变量名 a 来访问它,但计算机实际上是通过地址来找到它的。指针就是用来存储这些地址的变量。

c

复制代码
#include <stdio.h>

int main(void)
{
    int a = 10;       /* 普通变量 */
    int *p = &a;      /* 指针变量 p 存储 a 的地址 */
    
    printf("a 的值:%d\n", a);      /* 直接访问 */
    printf("a 的地址:%p\n", &a);    /* 取地址 */
    printf("p 的值:%p\n", p);       /* p 存储的就是地址 */
    printf("通过 p 访问 a:%d\n", *p); /* 间接访问 */
    
    return 0;
}

这段代码展示了指针的核心操作:取地址(&)和间接访问(*)。本章我们将深入理解这些概念。


2 内存地址

2.1 内存的基本模型

计算机的内存可以看作一个巨大的字节数组 ,每个字节都有一个唯一的编号,这个编号就是内存地址

text

复制代码
内存示意图:
地址: 0x1000    0x1001    0x1002    0x1003
      +---------+---------+---------+---------+
      |  字节0  |  字节1  |  字节2  |  字节3  | ...
      +---------+---------+---------+---------+
  • 内存以字节为单位编址

  • 每个字节对应一个地址

  • 地址通常用十六进制表示(如 0x7ffd5a3e4a00

2.2 变量与地址

当我们定义一个变量时,系统会为它分配一块内存空间,这块空间有一个起始地址。

c

复制代码
int a = 10;      /* 假设分配到地址 0x1000 */
float b = 3.14;  /* 假设分配到地址 0x1004 */

不同的数据类型占用不同大小的内存:

  • char:1字节

  • int:通常4字节

  • double:通常8字节

2.3 取地址运算符 &

使用 & 可以获取变量的地址:

c

复制代码
int a = 10;
printf("a 的地址:%p\n", &a);  /* 输出类似 0x7ffd5a3e4a00 */

%p 是专门用于打印地址的格式说明符。

注意

  • & 只能用于变量和数组元素,不能用于常量或表达式

  • &10 是错误的,常量没有地址

  • &(a+b) 也是错误的,表达式结果没有地址


3 指针变量

3.1 什么是指针变量

指针变量(Pointer Variable)是专门用来存储地址的变量。它和普通变量一样,也占用内存,但存储的内容是地址。

text

复制代码
内存示意图:
变量 a (地址 0x1000): [   10   ]  ← int 类型,存的是数值
变量 p (地址 0x2000): [ 0x1000 ]  ← 指针类型,存的是地址

3.2 指针变量的定义

c

复制代码
类型 *变量名;
  • 类型:指针指向的数据的类型

  • *:表明这是一个指针变量

  • 变量名:指针的名字

c

复制代码
int *p;      /* p 是一个指向 int 类型的指针 */
float *fp;   /* fp 是一个指向 float 类型的指针 */
char *cp;    /* cp 是一个指向 char 类型的指针 */
void *vp;    /* vp 是一个无类型指针(可以指向任何类型) */

理解int *p 读作"p 是一个指针,它指向一个 int"。

3.3 指针变量的初始化

c

复制代码
int a = 10;
int *p = &a;      /* 定义时初始化,p 指向 a */
int *q;           /* 只定义,未初始化(危险!) */
q = &a;           /* 之后赋值 */

int *r = NULL;    /* 初始化为空指针(安全) */

重要 :未初始化的指针是野指针,必须避免。

3.4 指针的大小

指针变量本身也有大小,它取决于系统架构:

c

复制代码
#include <stdio.h>

int main(void)
{
    char *cp;
    int *ip;
    double *dp;
    
    printf("char* 大小:%zu\n", sizeof(cp));   /* 32位系统:4;64位系统:8 */
    printf("int* 大小:%zu\n", sizeof(ip));    /* 都是4或8,和指向类型无关 */
    printf("double* 大小:%zu\n", sizeof(dp)); /* 都是4或8 */
    
    return 0;
}

关键:所有类型的指针大小在同一个系统中是相同的,因为它们存储的都是地址。


4 间接访问运算符 *

4.1 基本用法

*间接访问运算符(dereference operator),用于通过指针访问它指向的变量。

c

复制代码
int a = 10;
int *p = &a;

printf("%d\n", *p);   /* 输出 10,*p 等价于 a */
*p = 20;              /* 通过指针修改 a 的值 */
printf("%d\n", a);    /* 输出 20 */

理解*p 就是"p 指向的那个变量"。

4.2 & 和 * 的关系

&* 互为逆运算:

  • & 取变量的地址

  • * 通过地址访问变量

c

复制代码
int a = 10;
int *p = &a;

/* & 和 * 的关系 */
*p == a;        /* 真 */
&(*p) == p;     /* 真(前提 p 有效) */
*(&a) == a;     /* 真 */

4.3 多级指针

指针也可以指向另一个指针,形成多级指针:

c

复制代码
int a = 10;
int *p = &a;    /* 一级指针:指向 int */
int **pp = &p;  /* 二级指针:指向 int* */
int ***ppp = &pp; /* 三级指针:指向 int** */

printf("%d\n", ***ppp);  /* 输出 10 */

理解

  • *ppp 得到 pp(类型 int**

  • **ppp 得到 p(类型 int*

  • ***ppp 得到 a(类型 int


5 空指针与野指针

5.1 空指针 NULL

空指针(NULL pointer)是指不指向任何有效对象的指针。

c

复制代码
#include <stdio.h>
#include <stddef.h>  /* 包含 NULL 的定义 */

int main(void)
{
    int *p = NULL;  /* p 不指向任何有效内存 */
    
    if (p == NULL) {
        printf("p 是空指针\n");
    }
    
    /* *p = 10; */  /* 危险!解引用空指针会导致程序崩溃 */
    
    return 0;
}
  • NULL 在 C 语言中通常定义为 ((void*)0) 或简单的 0

  • 解引用空指针是未定义行为,通常导致程序崩溃(段错误)

  • 总是检查指针是否为 NULL 再使用

5.2 野指针

野指针(Wild pointer)是指指向不确定位置的指针,是 C 语言中最危险的错误来源之一。

c

复制代码
int *p;          /* 未初始化,p 的值是随机的(野指针) */
*p = 10;         /* 危险!写入未知位置,可能崩溃或破坏数据 */

int *q;
q = NULL;        /* 好习惯:初始化指针 */

产生野指针的常见情况:

  1. 指针未初始化

    c

    复制代码
    int *p;        /* 野指针 */
  2. 指向已释放的内存

    c

    复制代码
    int *p = malloc(sizeof(int));
    free(p);
    /* p 现在是野指针(悬空指针) */
  3. 指向局部变量的地址

    c

    复制代码
    int *get_int(void)
    {
        int x = 100;
        return &x;  /* x 是局部变量,函数返回后销毁 */
    }  /* 返回的指针成为野指针 */

5.3 如何避免野指针

  1. 初始化所有指针

    c

    复制代码
    int *p = NULL;  /* 好习惯 */
  2. 释放后置为 NULL

    c

    复制代码
    free(p);
    p = NULL;  /* 防止悬空指针 */
  3. 使用前检查

    c

    复制代码
    if (p != NULL) {
        *p = 10;  /* 安全 */
    }
  4. 不要返回局部变量的地址


6 const 与指针

6.1 指向常量的指针

c

复制代码
const int *p;  /* p 指向一个 const int */
int const *p;  /* 同上,两种写法等价 */

特点:

  • 不能通过 *p 修改指向的值

  • 指针本身可以修改(可以指向别处)

c

复制代码
int a = 10, b = 20;
const int *p = &a;
*p = 30;        /* 错误!不能修改 */
p = &b;         /* 可以,p 可以指向别处 */

6.2 指针常量

c

复制代码
int * const p = &a;  /* p 是一个常量指针 */

特点:

  • 可以通过 *p 修改指向的值

  • 指针本身不能修改(不能指向别处)

c

复制代码
int a = 10, b = 20;
int * const p = &a;
*p = 30;        /* 可以,a 变成 30 */
p = &b;         /* 错误!p 是常量,不能修改 */

6.3 指向常量的指针常量

c

复制代码
const int * const p = &a;  /* 都不能修改 */

特点:

  • 不能通过 *p 修改值

  • 指针本身也不能修改


7 指针的运算

7.1 指针的算术运算

指针支持有限的算术运算:加法、减法、自增、自减。

c

复制代码
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;      /* 指向 arr[0] */

p++;               /* 现在指向 arr[1] */
printf("%d\n", *p); /* 输出 20 */

p += 2;            /* 现在指向 arr[3] */
printf("%d\n", *p); /* 输出 40 */

int *q = &arr[4];
int diff = q - p;   /* 两个指针的差(元素个数) */
printf("相差 %d 个元素\n", diff);  /* 输出 1(从 3 到 4)*/

重要 :指针的加减运算是以指向的类型大小为单位,而不是字节。

c

复制代码
int *p;
p + 1;  /* 实际地址增加 sizeof(int) 字节 */

7.2 指针的比较

可以用关系运算符比较两个指针:

c

复制代码
if (p < q) {
    printf("p 在低地址\n");
}

if (p == NULL) {
    printf("p 是空指针\n");
}

注意:只有指向同一数组的指针比较才有意义。


8 指针与数组的关系(预览)

指针和数组有着密切的关系,将在下一章详细讨论,这里先提几点:

c

复制代码
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;      /* p 指向 arr[0] */

/* 下面三组等价 */
arr[2] = 10;
*(arr + 2) = 10;
p[2] = 10;         /* 指针也可以使用下标 */
*(p + 2) = 10;
  • 数组名在大多数表达式中被当作指向首元素的指针

  • 指针可以通过下标访问(p[i] 等价于 *(p + i)


9 常见错误与注意事项

9.1 混淆指针定义

c

复制代码
int* p, q;        /* p 是 int*,q 是 int,不是 int*! */
int *p, *q;       /* 正确:p 和 q 都是 int* */

9.2 解引用未初始化的指针

c

复制代码
int *p;
*p = 10;          /* 危险!p 指向哪里? */

9.3 类型不匹配

c

复制代码
int a = 10;
float *p = &a;     /* 警告:类型不匹配 */
/* 虽然能编译,但解引用时会把 int 当 float 解释,结果错误 */

9.4 忘记取地址

c

复制代码
int *p;
p = a;            /* 错误:a 是 int,不能赋给 int* */
p = &a;           /* 正确 */

9.5 对 NULL 解引用

c

复制代码
int *p = NULL;
*p = 10;          /* 运行时错误:段错误 */

9.6 指针运算越界

c

复制代码
int arr[5];
int *p = arr + 5;  /* 指向数组末尾的下一个位置(允许) */
*p = 10;           /* 错误!不能访问这个位置 */

10 本章小结

本章系统介绍了指针的基本概念:

1. 内存地址

  • 内存以字节编址,每个字节有一个唯一地址

  • & 运算符获取变量的地址

2. 指针变量

  • 存储地址的变量,定义:类型 *变量名

  • 必须初始化,避免野指针

  • 所有指针大小相同(取决于系统位数)

3. 间接访问

  • * 运算符通过指针访问指向的变量

  • &* 互为逆运算

4. 空指针与野指针

  • 空指针(NULL):不指向任何有效对象,解引用会崩溃

  • 野指针:指向不确定位置,是主要错误来源

  • 初始化指针、释放后置 NULL、使用前检查

5. const 与指针

  • const int *p:不能通过 p 修改数据

  • int * const p:不能修改 p 本身

  • const int * const p:都不能修改

6. 指针运算

  • 加减运算以类型大小为单位

  • 可以比较指针大小(同一数组内)

7. 与数组的初步关系

  • 数组名是首元素地址

  • 指针可以用下标访问(p[i]

相关推荐
lxh01132 小时前
串联所有单词的子串
算法
像污秽一样2 小时前
算法设计与分析-习题5.4
数据结构·算法·排序算法
七夜zippoe2 小时前
Redis高级数据结构实战:从Stream到HyperLogLog的深度解析
数据结构·数据库·redis·python·缓冲
IronMurphy2 小时前
【算法二十四】101. 对称二叉树 543. 二叉树的直径
数据结构·算法·leetcode
qingy_20462 小时前
Java基础:数据类型
java·开发语言·算法
小璐资源网2 小时前
排序算法概览:十大排序算法一览
数据结构·算法·排序算法
sycmancia2 小时前
C++——智能指针类模板
开发语言·c++
王夏奇2 小时前
Python-对excel文件操作的总览
开发语言·python·excel
knighthood20012 小时前
ROS1中source xxx.bash失效
开发语言·bash