第7章 指针

1. 指针的概念

1.1 什么是指针

指针是一种变量,用于存储内存地址。它指向内存中的某个位置,可以通过指针访问或修改该位置的数据。

1.2 指针的作用

  • 直接访问内存:通过指针可以直接访问和修改内存中的数据
  • 传递大型数据:通过传递指针而不是整个数据,可以提高函数调用的效率
  • 动态内存分配:用于管理动态分配的内存
  • 实现数据结构:如链表、树、图等
  • 函数指针:用于实现回调函数和多态

2. 指针的声明与初始化

2.1 指针声明

指针声明的基本语法:

1
类型 *指针变量名;

其中:

  • 类型 是指针指向的数据类型
  • * 表示这是一个指针变量
  • 指针变量名 是指针变量的名称

示例

1
2
3
4
int *p;         // 指向int类型的指针
char *str; // 指向char类型的指针
float *fp; // 指向float类型的指针
void *vp; // 通用指针(可指向任何类型)

2.2 指针初始化

指针可以通过以下方式初始化:

  1. 使用地址运算符 &
1
2
int num = 10;
int *p = # // p指向num的地址
  1. 使用NULL
1
int *p = NULL;    // 空指针
  1. 使用其他指针
1
2
int *p1 = #
int *p2 = p1; // p2指向与p1相同的地址
  1. 使用动态内存分配
1
int *p = (int *)malloc(sizeof(int));

2.3 指针的大小

指针的大小取决于系统的地址总线宽度:

  • 32位系统:4字节
  • 64位系统:8字节

示例

1
2
3
printf("Size of int*: %zu\n", sizeof(int *));
printf("Size of char*: %zu\n", sizeof(char *));
printf("Size of void*: %zu\n", sizeof(void *));

3. 指针的基本操作

3.1 解引用运算符 *

解引用运算符用于访问指针指向的内存中的值,其底层实现涉及内存地址的直接访问:

1
2
3
4
5
int num = 10;
int *p = #
printf("%d\n", *p); // 输出10
*p = 20; // 修改num的值为20
printf("%d\n", num); // 输出20

底层原理:解引用操作会将指针中存储的内存地址转换为对应类型的左值,允许读取或修改该地址处的数据。在汇编层面,这通常对应于一条内存访问指令,如 mov eax, [ebx](x86)或 ldr r0, [r1](ARM)。

3.2 地址运算符 &

地址运算符用于获取变量的内存地址,是指针操作的基础:

1
2
3
int num = 10;
int *p = # // p存储num的地址
printf("%p\n", p); // 输出num的地址

注意事项

  • 不能对寄存器变量使用 & 运算符,因为寄存器变量不存储在内存中
  • 不能对临时值或表达式结果使用 & 运算符,因为它们没有持久的内存地址
  • 对于数组名,& 运算符返回整个数组的地址,类型为「指向数组的指针」

3.3 指针算术运算

指针算术运算是C语言中最强大也最容易出错的特性之一,其行为与指针指向的类型密切相关:

  1. 指针加法p + n 表示指向p指向的位置之后n个元素的位置,实际地址偏移为 n * sizeof(*p)
  2. 指针减法p - n 表示指向p指向的位置之前n个元素的位置,实际地址偏移为 -n * sizeof(*p)
  3. 指针自增p++++p 表示指向下一个元素,等同于 p = p + 1
  4. 指针自减p----p 表示指向上一个元素,等同于 p = p - 1
  5. 指针比较:可以使用比较运算符(如 ==, <, >)比较两个指针,仅当它们指向同一数组或内存块时才有意义

示例

1
2
3
4
5
6
7
8
9
10
11
12
int arr[] = {10, 20, 30, 40, 50};
int *p = arr; // p指向arr[0]

printf("%d\n", *p); // 输出10
p++; // p指向arr[1],地址增加4字节(在32位系统上)
printf("%d\n", *p); // 输出20

p = arr + 2; // p指向arr[2],地址增加8字节
printf("%d\n", *p); // 输出30

int *q = arr + 4; // q指向arr[4]
printf("%zu\n", q - p); // 输出2(两个指针之间的元素个数)

底层实现:指针算术运算在编译时会根据指针类型进行缩放计算。例如,对于 int* 类型的指针,p + 1 会被编译为 (char*)p + sizeof(int),确保指针总是指向下一个完整的元素。

性能优化应用

  • 在处理大型数组时,使用指针算术通常比数组下标访问更快,因为编译器可以生成更紧凑的汇编代码
  • 指针自增/自减操作在循环中特别高效,因为它们对应于简单的地址寄存器更新指令
  • 避免在循环中重复计算指针偏移,应使用指针递增模式

常见陷阱

  • 指针算术运算仅对指向数组或已分配内存块的指针有效
  • 对空指针或野指针进行算术运算会导致未定义行为
  • 指针运算可能会导致内存越界,应始终确保在有效范围内操作

4. 指针与数组

4.1 数组名作为指针

在大多数情况下,数组名会发生「数组衰减」(Array Decay),被视为指向数组第一个元素的指针:

1
2
3
4
5
6
int arr[] = {10, 20, 30};
int *p = arr; // 等价于 int *p = &arr[0];

printf("%d\n", *p); // 输出10
printf("%d\n", *(p+1)); // 输出20
printf("%d\n", *(p+2)); // 输出30

数组衰减的例外情况

  • 当数组名作为 sizeof 运算符的操作数时,返回整个数组的大小
  • 当数组名作为 & 运算符的操作数时,返回指向整个数组的指针,类型为 type (*)[size]
  • 当数组名作为字符串字面量初始化字符数组时

示例

1
2
3
4
5
6
7
int arr[5];
printf("sizeof(arr): %zu\n", sizeof(arr)); // 输出20(5*4)
printf("sizeof(&arr): %zu\n", sizeof(&arr)); // 输出8(指针大小,64位系统)

int (*p)[5] = &arr; // 指向整个数组的指针
printf("%p\n", (void*)arr); // 输出数组首元素地址
printf("%p\n", (void*)&arr); // 输出相同的地址,但类型不同

4.2 指针访问数组元素

使用指针访问数组元素是C语言中最常见的优化技巧之一:

1
2
3
4
5
6
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;

for (int i = 0; i < 5; i++) {
printf("%d ", *(p + i)); // 输出10 20 30 40 50
}

性能优化技巧

  • 使用指针递增而非下标访问,减少循环内的地址计算
  • 对于大型数组,考虑使用寄存器变量存储指针,进一步提高性能
  • 利用编译器的自动向量化优化,确保代码风格有利于SIMD指令生成

优化示例

1
2
3
4
5
6
7
8
9
// 优化前:使用下标访问
for (int i = 0; i < size; i++) {
sum += arr[i];
}

// 优化后:使用指针递增
for (int *p = arr, *end = arr + size; p < end; p++) {
sum += *p;
}

4.3 指针与多维数组

多维数组在内存中是线性存储的,指针操作需要理解其内存布局:

1
2
3
4
5
6
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
int (*p)[3] = arr; // p是指向包含3个int元素的数组的指针

printf("%d\n", *(*p)); // 输出1
printf("%d\n", *(*p + 1)); // 输出2
printf("%d\n", *(*(p + 1))); // 输出4

内存布局:多维数组在内存中按行主序(Row-Major Order)存储,即先存储第一行的所有元素,再存储第二行的所有元素,以此类推。对于 int arr[2][3],内存布局为:1 2 3 4 5 6

高级应用

  • 动态分配多维数组:使用指向指针的指针或指向数组的指针
  • 数组切片:通过指针操作实现数组的子数组访问
  • 矩阵运算优化:利用指针算术提高矩阵运算性能

动态分配二维数组示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 方法1:使用指向指针的指针
int **create_2d_array(int rows, int cols) {
int **arr = (int **)malloc(rows * sizeof(int *));
if (arr) {
for (int i = 0; i < rows; i++) {
arr[i] = (int *)malloc(cols * sizeof(int));
}
}
return arr;
}

// 方法2:使用指向数组的指针(连续内存)
int (*create_2d_array_contiguous(int rows, int cols))[cols] {
int (*arr)[cols] = (int (*)[cols])malloc(rows * cols * sizeof(int));
return arr;
}

// 方法3:使用一维数组模拟二维数组
int *create_2d_array_flat(int rows, int cols) {
return (int *)malloc(rows * cols * sizeof(int));
}
// 访问方式:arr[i * cols + j]

性能比较

  • 方法1(指针数组):内存不连续,缓存局部性差,但可以轻松处理不规则矩阵
  • 方法2(指向数组的指针):内存连续,缓存局部性好,语法直观
  • 方法3(一维数组模拟):内存连续,缓存局部性最好,但访问语法较复杂

多维数组与指针的类型转换

  • arr:类型为 int[2][3],衰减为 int(*)[3]
  • arr[0]:类型为 int[3],衰减为 int*
  • &arr:类型为 int(*)[2][3]
  • &arr[0]:类型为 int(*)[3]
  • &arr[0][0]:类型为 int*

5. 指针与函数

5.1 指针作为函数参数

指针作为函数参数是C语言中实现「传引用」语义的唯一方式,允许函数修改调用者作用域中的变量:

1
2
3
4
5
6
7
8
9
10
11
12
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}

int main() {
int x = 10, y = 20;
swap(&x, &y); // 传递x和y的地址
printf("x = %d, y = %d\n", x, y); // 输出x = 20, y = 10
return 0;
}

指针参数的优势

  • 允许函数修改实参的值
  • 对于大型数据结构,传递指针比传递值更高效,减少了数据复制开销
  • 可以通过指针返回多个值

高级应用

  • 输出参数:函数通过指针参数返回额外的结果
  • 指针到指针:用于修改指针变量本身,如在函数中分配内存并更新调用者的指针
  • 变长参数:通过指针和偏移量实现类似printf的变长参数函数

5.2 指针作为函数返回值

函数可以返回指针,但需要注意内存管理问题:

1
2
3
4
5
6
7
8
9
10
11
int *create_array(int size) {
int *arr = (int *)malloc(size * sizeof(int));
return arr;
}

int main() {
int *arr = create_array(5);
// 使用arr
free(arr);
return 0;
}

返回指针的注意事项

  • 避免返回局部变量的地址:局部变量在函数返回后会被销毁,返回其地址会导致悬垂指针
  • 正确管理动态内存:返回动态分配的内存时,调用者必须负责释放
  • 返回静态变量的地址:可以安全返回静态变量的地址,但要注意线程安全性
  • 返回全局变量的地址:可以安全返回全局变量的地址

安全的指针返回模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 模式1:返回动态分配的内存(调用者负责释放)
void *safe_malloc(size_t size) {
void *ptr = malloc(size);
if (!ptr) {
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
return ptr;
}

// 模式2:返回静态缓冲区(注意线程安全和重入问题)
char *get_current_time() {
static char buffer[20];
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", tm_info);
return buffer;
}

// 模式3:返回全局或静态变量的地址
static int global_counter = 0;
int *get_counter() {
return &global_counter;
}

5.3 函数指针

函数指针是C语言中最强大的特性之一,允许将函数作为参数传递、存储在数据结构中,甚至动态选择要调用的函数:

1
2
3
4
5
6
7
8
9
10
int add(int a, int b) {
return a + b;
}

int main() {
int (*func_ptr)(int, int) = add; // func_ptr指向add函数
int result = func_ptr(5, 3); // 调用add函数
printf("Result: %d\n", result); // 输出8
return 0;
}

函数指针的语法

1
2
3
4
5
// 声明函数指针类型
typedef int (*Operation)(int, int);

// 使用typedef简化函数指针声明
Operation op = add;

高级应用

  1. 回调函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 排序回调函数
typedef int (*CompareFunc)(const void *, const void *);

void bubble_sort(void *arr, size_t count, size_t elem_size, CompareFunc cmp) {
char *base = (char *)arr;
for (size_t i = 0; i < count - 1; i++) {
for (size_t j = 0; j < count - i - 1; j++) {
char *elem1 = base + j * elem_size;
char *elem2 = base + (j + 1) * elem_size;
if (cmp(elem1, elem2) > 0) {
// 交换元素
for (size_t k = 0; k < elem_size; k++) {
char temp = elem1[k];
elem1[k] = elem2[k];
elem2[k] = temp;
}
}
}
}
}

// 整数比较函数
int compare_int(const void *a, const void *b) {
return *(const int *)a - *(const int *)b;
}

// 使用示例
int main() {
int arr[] = {5, 2, 8, 1, 9};
size_t size = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, size, sizeof(int), compare_int);
// 输出排序后的数组
return 0;
}
  1. 函数指针数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 算术运算函数
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return b != 0 ? a / b : 0; }

// 函数指针数组
int (*operations[])(int, int) = {add, subtract, multiply, divide};

int main() {
int a = 10, b = 5;
char op_symbols[] = {'+', '-', '*', '/'};

for (int i = 0; i < 4; i++) {
int result = operations[i](a, b);
printf("%d %c %d = %d\n", a, op_symbols[i], b, result);
}

return 0;
}
  1. 使用函数指针实现状态机
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 状态类型
typedef enum { STATE_IDLE, STATE_ACTIVE, STATE_ERROR } State;

// 状态处理函数类型
typedef void (*StateHandler)(void);

// 状态处理函数
void handle_idle() {
printf("Handling IDLE state\n");
// 状态处理逻辑
}

void handle_active() {
printf("Handling ACTIVE state\n");
// 状态处理逻辑
}

void handle_error() {
printf("Handling ERROR state\n");
// 状态处理逻辑
}

// 状态到处理函数的映射
StateHandler state_handlers[] = {
handle_idle,
handle_active,
handle_error
};

// 状态机驱动函数
void run_state_machine(State current_state) {
if (current_state >= 0 && current_state < sizeof(state_handlers)/sizeof(state_handlers[0])) {
state_handlers[current_state]();
}
}
  1. 与标准库配合使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 使用函数指针与qsort
int compare_strings(const void *a, const void *b) {
return strcmp(*(const char **)a, *(const char **)b);
}

int main() {
const char *strings[] = {"banana", "apple", "orange", "grape"};
size_t count = sizeof(strings) / sizeof(strings[0]);

qsort(strings, count, sizeof(const char *), compare_strings);

for (size_t i = 0; i < count; i++) {
printf("%s\n", strings[i]);
}

return 0;
}

6. 指针与字符串

6.1 字符串字面量与指针

字符串字面量在内存中是连续存储的,通常位于只读数据段(.rodata),字符串名被视为指向第一个字符的常量指针:

1
2
3
4
const char *str = "Hello, world!";
printf("%s\n", str); // 输出Hello, world!
printf("%c\n", *str); // 输出H
printf("%c\n", *(str+1)); // 输出e

字符串字面量的特性

  • 存储位置:通常存储在只读内存区域,修改会导致未定义行为
  • 生命周期:程序整个运行期间都存在
  • 唯一性:相同的字符串字面量可能会被编译器优化为同一个内存地址
  • 类型const char[],但在C中允许隐式转换为char*(C++中不允许)

示例

1
2
3
4
5
6
7
8
const char *str1 = "Hello";
const char *str2 = "Hello"; // 可能与str1指向同一地址
printf("str1: %p\n", (void*)str1);
printf("str2: %p\n", (void*)str2);

// 危险:修改字符串字面量
char *str3 = "World"; // C中允许,但不推荐
// *str3 = 'w'; // 未定义行为,可能导致段错误

6.2 指针操作字符串

使用指针操作字符串是C语言中最常见的字符串处理方式,也是许多字符串函数的实现基础:

1
2
3
4
5
6
7
8
char str[] = "Hello";
char *p = str;

while (*p != '\0') {
printf("%c", *p);
p++;
}
printf("\n"); // 输出Hello

字符串操作的底层实现

  1. 字符串长度计算
1
2
3
4
5
6
7
8
// 模拟strlen函数
size_t my_strlen(const char *s) {
const char *p = s;
while (*p != '\0') {
p++;
}
return (size_t)(p - s);
}
  1. 字符串复制
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 模拟strcpy函数(不安全,无边界检查)
char *my_strcpy(char *dest, const char *src) {
char *p = dest;
while ((*p++ = *src++) != '\0') {
;
}
return dest;
}

// 安全的字符串复制(带边界检查)
char *my_strncpy(char *dest, const char *src, size_t n) {
char *p = dest;
size_t i = 0;

while (i < n && (*p++ = *src++) != '\0') {
i++;
}

// 填充剩余空间为\0
while (i < n) {
*p++ = '\0';
i++;
}

return dest;
}
  1. 字符串连接
1
2
3
4
5
6
7
8
9
10
11
// 模拟strcat函数(不安全,无边界检查)
char *my_strcat(char *dest, const char *src) {
char *p = dest;
while (*p != '\0') {
p++;
}
while ((*p++ = *src++) != '\0') {
;
}
return dest;
}
  1. 字符串比较
1
2
3
4
5
6
7
8
// 模拟strcmp函数
int my_strcmp(const char *s1, const char *s2) {
while (*s1 && *s2 && *s1 == *s2) {
s1++;
s2++;
}
return (unsigned char)*s1 - (unsigned char)*s2;
}

高级字符串操作技巧

  1. 字符串反转
1
2
3
4
5
6
7
8
9
10
11
12
13
void reverse_string(char *str) {
if (!str) return;

char *start = str;
char *end = str + strlen(str) - 1;
char temp;

while (start < end) {
temp = *start;
*start++ = *end;
*end-- = temp;
}
}
  1. 字符串分割
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
char **split_string(const char *str, const char *delimiter, int *count) {
if (!str || !delimiter) return NULL;

// 计算分割后的字符串数量
int num_parts = 0;
const char *p = str;
size_t delimiter_len = strlen(delimiter);

while ((p = strstr(p, delimiter)) != NULL) {
num_parts++;
p += delimiter_len;
}
num_parts++; // 最后一部分

if (count) {
*count = num_parts;
}

// 分配结果数组
char **result = (char **)malloc(sizeof(char *) * num_parts);
if (!result) return NULL;

// 执行分割
p = str;
int i = 0;
const char *next;

while (i < num_parts - 1) {
next = strstr(p, delimiter);
size_t len = next - p;
result[i] = (char *)malloc(len + 1);
if (!result[i]) {
// 清理已分配的内存
for (int j = 0; j < i; j++) {
free(result[j]);
}
free(result);
return NULL;
}
strncpy(result[i], p, len);
result[i][len] = '\0';
p = next + delimiter_len;
i++;
}

// 处理最后一部分
size_t len = strlen(p);
result[i] = (char *)malloc(len + 1);
if (!result[i]) {
// 清理已分配的内存
for (int j = 0; j < i; j++) {
free(result[j]);
}
free(result);
return NULL;
}
strcpy(result[i], p);

return result;
}

// 使用示例
void free_split_result(char **result, int count) {
if (!result) return;
for (int i = 0; i < count; i++) {
free(result[i]);
}
free(result);
}
  1. 字符串格式化
1
2
3
4
5
6
7
8
// 安全的字符串格式化(避免缓冲区溢出)
int safe_sprintf(char *buffer, size_t size, const char *format, ...) {
va_list args;
va_start(args, format);
int result = vsnprintf(buffer, size, format, args);
va_end(args);
return result;
}

字符串处理的安全最佳实践

  • 始终使用带边界检查的函数:如strncpystrncatsnprintf
  • 避免使用已弃用的函数:如getssprintf
  • 检查所有字符串操作的返回值:特别是内存分配函数
  • 使用size_t类型表示字符串长度:避免整数溢出
  • 对所有输入字符串进行验证:特别是来自用户的输入
  • 使用unsigned char进行字符串比较:避免符号扩展问题

字符串与指针的关系

  • 字符串本质上是字符数组,数组名衰减为指向第一个字符的指针
  • 字符串操作函数通常接受char*类型的参数
  • 指针算术是字符串操作的基础,如遍历、查找、复制等
  • 理解指针与字符串的关系是掌握C语言字符串处理的关键

7. 复杂指针类型

7.1 指针的指针

指针的指针(Double Pointer)是指向指针的指针,常用于需要在函数中修改指针变量本身的场景:

1
2
3
4
5
int num = 10;
int *p = &num;
int **pp = &p; // pp是指向p的指针

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

指针的指针的内存布局

  • num:存储整数值 10
  • p:存储 num 的地址
  • pp:存储 p 的地址

高级应用

  1. 函数中修改指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 在函数中分配内存并更新调用者的指针
void allocate_memory(int **ptr, size_t size) {
*ptr = (int *)malloc(size * sizeof(int));
if (!*ptr) {
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
}

int main() {
int *arr = NULL;
size_t size = 5;

allocate_memory(&arr, size);

// 使用arr
for (size_t i = 0; i < size; i++) {
arr[i] = i + 1;
}

free(arr);
return 0;
}
  1. 二维字符串数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 二维字符串数组(指针的指针)
const char **create_string_array(size_t size) {
const char **arr = (const char **)malloc(size * sizeof(const char *));
if (!arr) return NULL;

// 初始化字符串
for (size_t i = 0; i < size; i++) {
char buffer[20];
snprintf(buffer, sizeof(buffer), "String %zu", i + 1);
arr[i] = strdup(buffer); // 复制字符串
}

return arr;
}

void free_string_array(const char **arr, size_t size) {
if (!arr) return;

for (size_t i = 0; i < size; i++) {
free((void *)arr[i]);
}
free(arr);
}
  1. 命令行参数处理
1
2
3
4
5
6
7
int main(int argc, char *argv[]) {
// argv是指向指针的指针,指向命令行参数
for (int i = 0; i < argc; i++) {
printf("argv[%d]: %s\n", i, argv[i]);
}
return 0;
}

7.2 指向函数的指针

指向函数的指针是C语言中实现回调机制和多态行为的关键:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int (*operation)(int, int);

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

int subtract(int a, int b) {
return a - b;
}

int main() {
operation = add;
printf("5 + 3 = %d\n", operation(5, 3)); // 输出8

operation = subtract;
printf("5 - 3 = %d\n", operation(5, 3)); // 输出2

return 0;
}

指向函数的指针的高级应用

  1. 函数指针数组
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 数学运算函数
double add(double a, double b) { return a + b; }
double subtract(double a, double b) { return a - b; }
double multiply(double a, double b) { return a * b; }
double divide(double a, double b) { return b != 0 ? a / b : 0; }

// 函数指针数组
double (*math_operations[])(double, double) = {
add,
subtract,
multiply,
divide
};

// 操作符名称
const char *operation_names[] = {
"addition",
"subtraction",
"multiplication",
"division"
};

int main() {
double a = 10.0, b = 5.0;

for (size_t i = 0; i < sizeof(math_operations) / sizeof(math_operations[0]); i++) {
double result = math_operations[i](a, b);
printf("%s: %.2f\n", operation_names[i], result);
}

return 0;
}
  1. 函数指针作为结构体成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
// 排序算法结构体
typedef struct {
const char *name;
void (*sort)(int *, size_t);
} SortAlgorithm;

// 冒泡排序
void bubble_sort(int *arr, size_t size) {
for (size_t i = 0; i < size - 1; i++) {
for (size_t j = 0; j < size - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}

// 选择排序
void selection_sort(int *arr, size_t size) {
for (size_t i = 0; i < size - 1; i++) {
size_t min_idx = i;
for (size_t j = i + 1; j < size; j++) {
if (arr[j] < arr[min_idx]) {
min_idx = j;
}
}
if (min_idx != i) {
int temp = arr[i];
arr[i] = arr[min_idx];
arr[min_idx] = temp;
}
}
}

// 排序算法数组
SortAlgorithm sort_algorithms[] = {
{"Bubble Sort", bubble_sort},
{"Selection Sort", selection_sort}
};

// 测试排序算法
void test_sort_algorithm(SortAlgorithm algo, int *arr, size_t size) {
int *test_arr = (int *)malloc(size * sizeof(int));
if (!test_arr) return;

memcpy(test_arr, arr, size * sizeof(int));

printf("Testing %s:\n", algo.name);
printf("Before: ");
for (size_t i = 0; i < size; i++) {
printf("%d ", test_arr[i]);
}
printf("\n");

algo.sort(test_arr, size);

printf("After: ");
for (size_t i = 0; i < size; i++) {
printf("%d ", test_arr[i]);
}
printf("\n\n");

free(test_arr);
}

7.3 指向结构体的指针

指向结构体的指针用于存储结构体的地址,是C语言中处理复杂数据结构的基础:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef struct {
int id;
char *name;
} Person;

int main() {
Person person = {1, "John"};
Person *p = &person;

printf("ID: %d\n", p->id); // 输出1
printf("Name: %s\n", p->name); // 输出John

return 0;
}

结构体指针的内存布局与对齐

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 结构体定义
typedef struct {
char c; // 1字节
int i; // 4字节
double d; // 8字节
} MyStruct;

int main() {
printf("Size of MyStruct: %zu\n", sizeof(MyStruct)); // 输出16(由于内存对齐)

MyStruct s = {'A', 42, 3.14};
MyStruct *p = &s;

printf("Offset of c: %zu\n", offsetof(MyStruct, c)); // 输出0
printf("Offset of i: %zu\n", offsetof(MyStruct, i)); // 输出4(对齐到4字节边界)
printf("Offset of d: %zu\n", offsetof(MyStruct, d)); // 输出8(对齐到8字节边界)

return 0;
}

内存对齐的原因

  • 性能优化:许多CPU架构访问对齐的内存地址比非对齐地址更快
  • 硬件要求:某些CPU架构不支持非对齐的内存访问,会导致硬件异常

高级应用

  1. 结构体指针与链表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 单向链表节点
typedef struct Node {
int data;
struct Node *next;
} Node;

// 创建新节点
Node *create_node(int data) {
Node *node = (Node *)malloc(sizeof(Node));
if (!node) return NULL;

node->data = data;
node->next = NULL;
return node;
}

// 插入节点到链表头部
Node *insert_front(Node *head, int data) {
Node *new_node = create_node(data);
if (!new_node) return head;

new_node->next = head;
return new_node;
}

// 遍历链表
void traverse_list(Node *head) {
Node *current = head;
while (current) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}

// 释放链表
void free_list(Node *head) {
Node *current = head;
while (current) {
Node *next = current->next;
free(current);
current = next;
}
}
  1. 结构体指针与二叉树
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 二叉树节点
typedef struct TreeNode {
int data;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;

// 创建新节点
TreeNode *create_tree_node(int data) {
TreeNode *node = (TreeNode *)malloc(sizeof(TreeNode));
if (!node) return NULL;

node->data = data;
node->left = NULL;
node->right = NULL;
return node;
}

// 插入节点到二叉搜索树
TreeNode *insert_bst(TreeNode *root, int data) {
if (!root) {
return create_tree_node(data);
}

if (data < root->data) {
root->left = insert_bst(root->left, data);
} else if (data > root->data) {
root->right = insert_bst(root->right, data);
}

return root;
}

// 中序遍历二叉搜索树
void inorder_traversal(TreeNode *root) {
if (root) {
inorder_traversal(root->left);
printf("%d ", root->data);
inorder_traversal(root->right);
}
}

// 释放二叉树
void free_tree(TreeNode *root) {
if (root) {
free_tree(root->left);
free_tree(root->right);
free(root);
}
}
  1. 结构体指针与内存池
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 内存池节点
typedef struct PoolNode {
struct PoolNode *next;
char data[0]; // 柔性数组成员
} PoolNode;

// 内存池
typedef struct {
PoolNode *free_list;
size_t block_size;
size_t alignment;
} MemoryPool;

// 初始化内存池
void pool_init(MemoryPool *pool, size_t block_size, size_t alignment) {
pool->free_list = NULL;
pool->block_size = block_size;
pool->alignment = alignment;
}

// 从内存池分配内存
void *pool_alloc(MemoryPool *pool) {
if (pool->free_list) {
// 从自由列表中获取内存块
PoolNode *node = pool->free_list;
pool->free_list = node->next;
return node->data;
}

// 分配新的内存块
size_t total_size = sizeof(PoolNode) + pool->block_size;
// 对齐内存分配
PoolNode *node = (PoolNode *)aligned_alloc(pool->alignment, total_size);
if (!node) return NULL;

return node->data;
}

// 释放内存到内存池
void pool_free(MemoryPool *pool, void *ptr) {
if (!ptr) return;

// 将内存块添加到自由列表
PoolNode *node = (PoolNode *)((char *)ptr - sizeof(PoolNode));
node->next = pool->free_list;
pool->free_list = node;
}

// 销毁内存池
void pool_destroy(MemoryPool *pool) {
PoolNode *current = pool->free_list;
while (current) {
PoolNode *next = current->next;
free(current);
current = next;
}
pool->free_list = NULL;
}

8. 指针的高级应用

8.1 动态内存分配

指针与动态内存分配密切相关:

1
2
3
4
5
6
7
8
9
10
11
12
13
int *arr = (int *)malloc(5 * sizeof(int));
if (arr) {
for (int i = 0; i < 5; i++) {
arr[i] = i + 1;
}

for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");

free(arr);
}

8.2 内存模型

C语言中的内存分为以下几个区域:

  • 代码区:存储程序的可执行指令
  • 全局/静态区:存储全局变量和静态变量
  • 栈区:存储函数的局部变量和函数参数
  • 堆区:存储动态分配的内存

8.3 指针与内存管理

使用指针时需要注意内存管理:

  • 避免野指针:指针未初始化或已释放后仍被使用
  • 避免内存泄漏:动态分配的内存未释放
  • 避免悬垂指针:指针指向的内存已被释放
  • 避免指针越界:指针访问了超出分配范围的内存

9. 指针的最佳实践

9.1 指针使用规范

  1. 始终初始化指针:避免使用未初始化的指针
  2. 使用NULL表示空指针:便于检查指针是否有效
  3. 避免指针算术运算错误:确保指针运算在有效范围内
  4. 正确释放动态内存:避免内存泄漏
  5. 使用const修饰符:对于不需要修改的指针指向的数据,使用const修饰

9.2 指针与安全性

  1. 避免缓冲区溢出:确保指针操作不会超出缓冲区边界
  2. 使用安全的内存分配函数:如callocrealloc
  3. 定期检查指针有效性:在使用指针前检查其是否为NULL
  4. 避免使用全局指针:减少指针的作用域
  5. 使用智能指针:在C++中使用智能指针管理内存

10. 常见指针错误

10.1 野指针

未初始化的指针:

1
2
int *p;    // 野指针
*p = 10; // 未定义行为

10.2 悬垂指针

指针指向的内存已被释放:

1
2
3
int *p = (int *)malloc(sizeof(int));
free(p);
*p = 10; // 未定义行为

10.3 内存泄漏

动态分配的内存未释放:

1
2
3
4
void func() {
int *p = (int *)malloc(sizeof(int));
// 未释放p
}

10.4 指针越界

指针访问了超出分配范围的内存:

1
2
3
int *p = (int *)malloc(5 * sizeof(int));
p[10] = 10; // 未定义行为
free(p);

11. 指针与现代C语言

11.1 C99和C11中的指针特性

  • ** restrict关键字**:用于告诉编译器指针是唯一访问其指向内存的方式
  • ** _Alignas和_Alignof**:用于控制内存对齐
  • 泛型选择表达式:用于根据类型选择不同的代码路径

11.2 智能指针模拟

虽然C语言没有内置的智能指针,但可以通过宏和函数模拟:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
typedef struct {
void *ptr;
void (*free_func)(void *);
} smart_ptr;

void smart_ptr_init(smart_ptr *sp, void *ptr, void (*free_func)(void *)) {
sp->ptr = ptr;
sp->free_func = free_func;
}

void smart_ptr_free(smart_ptr *sp) {
if (sp->ptr && sp->free_func) {
sp->free_func(sp->ptr);
sp->ptr = NULL;
}
}

12. 示例代码

12.1 指针基础示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

int main() {
int num = 10;
int *p = &num;

printf("Value of num: %d\n", num);
printf("Address of num: %p\n", &num);
printf("Value of p: %p\n", p);
printf("Value pointed by p: %d\n", *p);

*p = 20;
printf("Value of num after modification: %d\n", num);

return 0;
}

12.2 指针与数组示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main() {
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
int size = sizeof(arr) / sizeof(arr[0]);

printf("Array elements using pointer: ");
for (int i = 0; i < size; i++) {
printf("%d ", *(p + i));
}
printf("\n");

return 0;
}

12.3 指针与函数示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

void modify_value(int *ptr, int new_value) {
*ptr = new_value;
}

int main() {
int num = 10;
printf("Original value: %d\n", num);

modify_value(&num, 20);
printf("Modified value: %d\n", num);

return 0;
}

12.4 动态内存分配示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <stdlib.h>

int main() {
int *arr = NULL;
int size;

printf("Enter size of array: ");
scanf("%d", &size);

arr = (int *)malloc(size * sizeof(int));
if (arr == NULL) {
printf("Memory allocation failed\n");
return 1;
}

printf("Enter %d elements: ", size);
for (int i = 0; i < size; i++) {
scanf("%d", &arr[i]);
}

printf("Array elements: ");
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");

free(arr);

return 0;
}