C/C++ Linux 开发教程(GCC + CMake) / 06 — 指针基础
指针基础
1. 指针概念
指针是 C 语言的核心特性,也是最让初学者困惑的部分。简单来说,指针就是一个存储内存地址的变量。
变量 a 指针 p
┌──────┐ ┌──────┐
│ 42 │ <──── │ 0x7f │ p 存储的是 a 的地址
└──────┘ └──────┘
地址: 0x7f00 地址: 0x7f08
1.1 取地址 & 与解引用 *
#include <stdio.h>
int main(void)
{
int a = 42;
int *p = &a; // & 取地址:p 指向 a
printf("a 的值: %d\n", a); // 42
printf("a 的地址: %p\n", (void *)&a);
printf("p 的值: %p\n", (void *)p); // 与 &a 相同
printf("*p 解引用: %d\n", *p); // 42(通过地址访问 a 的值)
// 通过指针修改值
*p = 100;
printf("修改后 a: %d\n", a); // 100
return 0;
}
| 操作符 | 名称 | 说明 |
|---|---|---|
& | 取地址 | 获取变量的内存地址 |
* | 解引用 | 通过地址访问(读/写)该内存处的值 |
2. 指针变量声明
int *p; // 指向 int 的指针
char *s; // 指向 char 的指针(常用于字符串)
double *dp; // 指向 double 的指针
int **pp; // 指向 int 指针的指针(二级指针)
void *vp; // 通用指针(可指向任何类型)
💡 提示:
int *p和int* p都合法。推荐int *p,因为int *a, b中只有a是指针,b是int。
int *a, b; // a 是 int*,b 是 int
int *c, *d; // c 和 d 都是 int*
3. 指针运算
3.1 指针加减
#include <stdio.h>
int main(void)
{
int arr[] = {10, 20, 30, 40, 50};
int *p = arr; // 指向 arr[0]
printf("p = %p, *p = %d\n", (void *)p, *p); // arr[0] = 10
p++; // 移动到下一个 int(地址 + sizeof(int))
printf("p = %p, *p = %d\n", (void *)p, *p); // arr[1] = 20
p += 2;
printf("p = %p, *p = %d\n", (void *)p, *p); // arr[3] = 40
p--;
printf("p = %p, *p = %d\n", (void *)p, *p); // arr[2] = 30
return 0;
}
3.2 指针比较
#include <stdio.h>
int main(void)
{
int arr[5] = {10, 20, 30, 40, 50};
int *start = &arr[0];
int *end = &arr[4];
if (start < end) {
printf("start 在 end 之前\n");
}
// 计算两个指针之间的距离(元素个数)
ptrdiff_t diff = end - start;
printf("距离: %td 个元素\n", diff); // 4
// 用指针遍历数组
for (int *p = arr; p <= &arr[4]; p++) {
printf("%d ", *p);
}
printf("\n");
return 0;
}
4. 指针与数组
数组名在大多数上下文中会退化为指向首元素的指针:
#include <stdio.h>
void print_array(const int *arr, int n)
{
for (int i = 0; i < n; i++) {
// arr[i] 等价于 *(arr + i)
printf("%d ", arr[i]);
}
printf("\n");
}
int main(void)
{
int arr[] = {1, 2, 3, 4, 5};
int n = sizeof(arr) / sizeof(arr[0]);
// 传数组给函数时,退化为指针
print_array(arr, n);
// 下标访问 vs 指针访问
printf("arr[2] = %d\n", arr[2]);
printf("*(arr+2) = %d\n", *(arr + 2));
// 指针常量 vs 常量指针
// arr 本身是"指针常量",不能重新赋值
// arr = NULL; // 编译错误!
return 0;
}
| 表达式 | 等价写法 | 含义 |
|---|---|---|
arr[i] | *(arr + i) | 第 i 个元素的值 |
&arr[i] | arr + i | 第 i 个元素的地址 |
arr | &arr[0] | 首元素的地址 |
5. const 指针
#include <stdio.h>
int main(void)
{
int a = 10, b = 20;
// 指向 const int 的指针:不能通过指针修改值,但可以改变指向
const int *p1 = &a;
// *p1 = 100; // 错误!不能通过 p1 修改值
p1 = &b; // OK,可以改变指向
// const 指针,指向 int:不能改变指向,但可以修改值
int *const p2 = &a;
*p2 = 100; // OK,可以修改值
// p2 = &b; // 错误!不能改变指向
// const 指针,指向 const int:都不能改
const int *const p3 = &a;
// *p3 = 100; // 错误
// p3 = &b; // 错误
printf("*p1 = %d, *p2 = %d, *p3 = %d\n", *p1, *p2, *p3);
return 0;
}
const 指针速记法
从右向左读:
| 声明 | 读法 | 含义 |
|---|---|---|
const int *p | p is a pointer to const int | 不能通过 p 修改值 |
int *const p | p is a const pointer to int | 不能改变 p 的指向 |
const int *const p | p is a const pointer to const int | 两者都不能改 |
💡 提示: 使用
const指针可以在函数中承诺不修改传入的数据,既是文档也是保护。
6. void 指针
void * 是通用指针,可以指向任何类型的数据:
#include <stdio.h>
void print_bytes(const void *data, size_t size)
{
const unsigned char *bytes = (const unsigned char *)data;
for (size_t i = 0; i < size; i++) {
printf("%02X ", bytes[i]);
}
printf("\n");
}
int main(void)
{
int x = 0x12345678;
double d = 3.14;
printf("int x 的字节: ");
print_bytes(&x, sizeof(x));
printf("double d 的字节: ");
print_bytes(&d, sizeof(d));
// void* 可以存储任意类型指针
void *vp = &x;
printf("通过 void* 读取: %d\n", *(int *)vp); // 需要先转换
return 0;
}
⚠️ 注意: void * 不能直接解引用,必须先转换为具体类型指针。
7. NULL 指针
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p = NULL; // 空指针,不指向任何有效内存
// 解引用 NULL 是未定义行为(通常导致段错误)
// printf("%d\n", *p); // 危险!
// 使用前必须检查
p = malloc(sizeof(int));
if (p == NULL) {
fprintf(stderr, "内存分配失败\n");
return 1;
}
*p = 42;
printf("*p = %d\n", *p);
free(p);
p = NULL; // 释放后置空,防止悬空指针
return 0;
}
💡 提示: 在 C 中,
NULL通常是(void *)0或0。C23 引入了nullptr,语义更明确。
8. 指针常见错误
8.1 野指针(Uninitialized Pointer)
int *p; // 未初始化,指向随机地址
*p = 42; // 未定义行为!可能写入任意内存
// 正确做法:声明时初始化
int *p = NULL;
8.2 悬空指针(Dangling Pointer)
int *p = malloc(sizeof(int));
*p = 42;
free(p); // 内存已释放
// *p = 100; // 未定义行为!p 成为悬空指针
// 正确做法:free 后置空
free(p);
p = NULL;
8.3 内存泄漏
void leaky_function(void)
{
int *p = malloc(100 * sizeof(int));
// 忘记 free(p)!
return; // 内存泄漏!
}
错误总结表
| 错误类型 | 原因 | 后果 | 预防 |
|---|---|---|---|
| 野指针 | 未初始化 | 写入随机内存 | 声明时置 NULL |
| 悬空指针 | 使用已释放的内存 | 读写无效内存 | free 后置 NULL |
| 内存泄漏 | 分配后未释放 | 内存耗尽 | 确保每个 malloc 都有对应 free |
| 越界访问 | 指针偏移超出范围 | 数据损坏 | 注意边界检查 |
9. 指针作为函数参数
#include <stdio.h>
// 值传递 —— 无法修改原值
void swap_wrong(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
// 地址传递 —— 可以修改原值
void swap_right(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
// 输出参数
int divide(int a, int b, int *remainder)
{
if (b == 0) return -1;
*remainder = a % b;
return a / b;
}
int main(void)
{
int x = 10, y = 20;
swap_wrong(x, y);
printf("swap_wrong 后: x=%d, y=%d\n", x, y); // 未变
swap_right(&x, &y);
printf("swap_right 后: x=%d, y=%d\n", x, y); // 已交换
int rem;
int quot = divide(17, 5, &rem);
printf("17 / 5 = %d 余 %d\n", quot, rem);
return 0;
}
| 传递方式 | 函数签名 | 能否修改原值 |
|---|---|---|
| 值传递 | void f(int x) | ❌ |
| 指针传递 | void f(int *p) | ✅ |
| const 指针 | void f(const int *p) | ❌ 只读 |
10. 实际场景:动态数组
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int *data;
size_t size;
size_t capacity;
} IntArray;
IntArray *int_array_create(size_t capacity)
{
IntArray *arr = malloc(sizeof(IntArray));
if (!arr) return NULL;
arr->data = malloc(capacity * sizeof(int));
if (!arr->data) { free(arr); return NULL; }
arr->size = 0;
arr->capacity = capacity;
return arr;
}
int int_array_push(IntArray *arr, int value)
{
if (arr->size >= arr->capacity) {
size_t new_cap = arr->capacity * 2;
int *new_data = realloc(arr->data, new_cap * sizeof(int));
if (!new_data) return -1;
arr->data = new_data;
arr->capacity = new_cap;
}
arr->data[arr->size++] = value;
return 0;
}
void int_array_destroy(IntArray *arr)
{
if (arr) {
free(arr->data);
free(arr);
}
}
int main(void)
{
IntArray *arr = int_array_create(4);
if (!arr) {
fprintf(stderr, "创建失败\n");
return 1;
}
for (int i = 0; i < 20; i++) {
int_array_push(arr, i * i);
}
for (size_t i = 0; i < arr->size; i++) {
printf("arr[%zu] = %d\n", i, arr->data[i]);
}
printf("容量: %zu, 大小: %zu\n", arr->capacity, arr->size);
int_array_destroy(arr);
return 0;
}
11. 实际场景:函数返回多个值
#include <stdio.h>
typedef struct {
double min;
double max;
double avg;
} Stats;
Stats compute_stats(const double *data, size_t n)
{
Stats s = {0};
if (n == 0) return s;
s.min = data[0];
s.max = data[0];
double sum = 0;
for (size_t i = 0; i < n; i++) {
if (data[i] < s.min) s.min = data[i];
if (data[i] > s.max) s.max = data[i];
sum += data[i];
}
s.avg = sum / (double)n;
return s;
}
int main(void)
{
double data[] = {3.5, 1.2, 7.8, 2.1, 5.6};
size_t n = sizeof(data) / sizeof(data[0]);
Stats s = compute_stats(data, n);
printf("min=%.2f max=%.2f avg=%.2f\n", s.min, s.max, s.avg);
return 0;
}