第7章 指针 1. 指针的概念 1.1 什么是指针 指针是一种变量,用于存储内存地址。它指向内存中的某个位置,可以通过指针访问或修改该位置的数据。
1.2 指针的作用 直接访问内存 :通过指针可以直接访问和修改内存中的数据传递大型数据 :通过传递指针而不是整个数据,可以提高函数调用的效率动态内存分配 :用于管理动态分配的内存实现数据结构 :如链表、树、图等函数指针 :用于实现回调函数和多态2. 指针的声明与初始化 2.1 指针声明 指针声明的基本语法:
其中:
类型 是指针指向的数据类型* 表示这是一个指针变量指针变量名 是指针变量的名称示例 :
1 2 3 4 int *p; char *str; float *fp; void *vp;
2.2 指针初始化 指针可以通过以下方式初始化:
使用地址运算符 & :1 2 int num = 10 ;int *p = #
使用NULL :使用其他指针 :1 2 int *p1 = #int *p2 = p1;
使用动态内存分配 :1 int *p = (int *)malloc (sizeof (int ));
2.3 指针的大小 指针的大小取决于系统的地址总线宽度:
示例 :
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); *p = 20 ; printf ("%d\n" , num);
底层原理 :解引用操作会将指针中存储的内存地址转换为对应类型的左值,允许读取或修改该地址处的数据。在汇编层面,这通常对应于一条内存访问指令,如 mov eax, [ebx](x86)或 ldr r0, [r1](ARM)。
3.2 地址运算符 & 地址运算符用于获取变量的内存地址,是指针操作的基础:
1 2 3 int num = 10 ;int *p = # printf ("%p\n" , p);
注意事项 :
不能对寄存器变量使用 & 运算符,因为寄存器变量不存储在内存中 不能对临时值或表达式结果使用 & 运算符,因为它们没有持久的内存地址 对于数组名,& 运算符返回整个数组的地址,类型为「指向数组的指针」 3.3 指针算术运算 指针算术运算是C语言中最强大也最容易出错的特性之一,其行为与指针指向的类型密切相关:
指针加法 :p + n 表示指向p指向的位置之后n个元素的位置,实际地址偏移为 n * sizeof(*p)指针减法 :p - n 表示指向p指向的位置之前n个元素的位置,实际地址偏移为 -n * sizeof(*p)指针自增 :p++ 或 ++p 表示指向下一个元素,等同于 p = p + 1指针自减 :p-- 或 --p 表示指向上一个元素,等同于 p = p - 1指针比较 :可以使用比较运算符(如 ==, <, >)比较两个指针,仅当它们指向同一数组或内存块时才有意义示例 :
1 2 3 4 5 6 7 8 9 10 11 12 int arr[] = {10 , 20 , 30 , 40 , 50 };int *p = arr; printf ("%d\n" , *p); p++; printf ("%d\n" , *p); p = arr + 2 ; printf ("%d\n" , *p); int *q = arr + 4 ; printf ("%zu\n" , q - p);
底层实现 :指针算术运算在编译时会根据指针类型进行缩放计算。例如,对于 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; printf ("%d\n" , *p); printf ("%d\n" , *(p+1 )); printf ("%d\n" , *(p+2 ));
数组衰减的例外情况 :
当数组名作为 sizeof 运算符的操作数时,返回整个数组的大小 当数组名作为 & 运算符的操作数时,返回指向整个数组的指针,类型为 type (*)[size] 当数组名作为字符串字面量初始化字符数组时 示例 :
1 2 3 4 5 6 7 int arr[5 ];printf ("sizeof(arr): %zu\n" , sizeof (arr)); printf ("sizeof(&arr): %zu\n" , sizeof (&arr)); 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)); }
性能优化技巧 :
使用指针递增而非下标访问,减少循环内的地址计算 对于大型数组,考虑使用寄存器变量存储指针,进一步提高性能 利用编译器的自动向量化优化,确保代码风格有利于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; printf ("%d\n" , *(*p)); printf ("%d\n" , *(*p + 1 )); printf ("%d\n" , *(*(p + 1 )));
内存布局 :多维数组在内存中按行主序(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 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; } int (*create_2d_array_contiguous(int rows, int cols))[cols] { int (*arr)[cols] = (int (*)[cols])malloc (rows * cols * sizeof (int )); return arr; } int *create_2d_array_flat (int rows, int cols) { return (int *)malloc (rows * cols * sizeof (int )); }
性能比较 :
方法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); printf ("x = %d, y = %d\n" , x, y); 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 ); 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 void *safe_malloc (size_t size) { void *ptr = malloc (size); if (!ptr) { fprintf (stderr , "Memory allocation failed\n" ); exit (EXIT_FAILURE); } return ptr; } 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; } 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; int result = func_ptr(5 , 3 ); printf ("Result: %d\n" , result); return 0 ; }
函数指针的语法 :
1 2 3 4 5 typedef int (*Operation) (int , int ) ;Operation op = add;
高级应用 :
回调函数 :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 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 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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); printf ("%c\n" , *str); printf ("%c\n" , *(str+1 ));
字符串字面量的特性 :
存储位置 :通常存储在只读内存区域,修改会导致未定义行为生命周期 :程序整个运行期间都存在唯一性 :相同的字符串字面量可能会被编译器优化为同一个内存地址类型 :const char[],但在C中允许隐式转换为char*(C++中不允许)示例 :
1 2 3 4 5 6 7 8 const char *str1 = "Hello" ;const char *str2 = "Hello" ; printf ("str1: %p\n" , (void *)str1);printf ("str2: %p\n" , (void *)str2);char *str3 = "World" ;
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" );
字符串操作的底层实现 :
字符串长度计算 :1 2 3 4 5 6 7 8 size_t my_strlen (const char *s) { const char *p = s; while (*p != '\0' ) { p++; } return (size_t )(p - s); }
字符串复制 :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 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++; } while (i < n) { *p++ = '\0' ; i++; } return dest; }
字符串连接 :1 2 3 4 5 6 7 8 9 10 11 char *my_strcat (char *dest, const char *src) { char *p = dest; while (*p != '\0' ) { p++; } while ((*p++ = *src++) != '\0' ) { ; } return dest; }
字符串比较 :1 2 3 4 5 6 7 8 int my_strcmp (const char *s1, const char *s2) { while (*s1 && *s2 && *s1 == *s2) { s1++; s2++; } return (unsigned char )*s1 - (unsigned char )*s2; }
高级字符串操作技巧 :
字符串反转 :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 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 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; }
字符串处理的安全最佳实践 :
始终使用带边界检查的函数 :如strncpy、strncat、snprintf等避免使用已弃用的函数 :如gets、sprintf等检查所有字符串操作的返回值 :特别是内存分配函数使用size_t类型表示字符串长度 :避免整数溢出对所有输入字符串进行验证 :特别是来自用户的输入使用unsigned char进行字符串比较 :避免符号扩展问题字符串与指针的关系 :
字符串本质上是字符数组,数组名衰减为指向第一个字符的指针 字符串操作函数通常接受char*类型的参数 指针算术是字符串操作的基础,如遍历、查找、复制等 理解指针与字符串的关系是掌握C语言字符串处理的关键 7. 复杂指针类型 7.1 指针的指针 指针的指针(Double Pointer)是指向指针的指针,常用于需要在函数中修改指针变量本身的场景:
1 2 3 4 5 int num = 10 ;int *p = #int **pp = &p; printf ("%d\n" , **pp);
指针的指针的内存布局 :
num:存储整数值 10p:存储 num 的地址pp:存储 p 的地址高级应用 :
函数中修改指针 :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); for (size_t i = 0 ; i < size; i++) { arr[i] = i + 1 ; } 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 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 2 3 4 5 6 7 int main (int argc, char *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 )); operation = subtract; printf ("5 - 3 = %d\n" , operation(5 , 3 )); 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 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 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); printf ("Name: %s\n" , p->name); 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; int i; double d; } MyStruct; int main () { printf ("Size of MyStruct: %zu\n" , sizeof (MyStruct)); MyStruct s = {'A' , 42 , 3.14 }; MyStruct *p = &s; printf ("Offset of c: %zu\n" , offsetof(MyStruct, c)); printf ("Offset of i: %zu\n" , offsetof(MyStruct, i)); printf ("Offset of d: %zu\n" , offsetof(MyStruct, d)); return 0 ; }
内存对齐的原因 :
性能优化 :许多CPU架构访问对齐的内存地址比非对齐地址更快硬件要求 :某些CPU架构不支持非对齐的内存访问,会导致硬件异常高级应用 :
结构体指针与链表 :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 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 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 指针使用规范 始终初始化指针 :避免使用未初始化的指针使用NULL表示空指针 :便于检查指针是否有效避免指针算术运算错误 :确保指针运算在有效范围内正确释放动态内存 :避免内存泄漏使用const修饰符 :对于不需要修改的指针指向的数据,使用const修饰9.2 指针与安全性 避免缓冲区溢出 :确保指针操作不会超出缓冲区边界使用安全的内存分配函数 :如calloc、realloc等定期检查指针有效性 :在使用指针前检查其是否为NULL避免使用全局指针 :减少指针的作用域使用智能指针 :在C++中使用智能指针管理内存10. 常见指针错误 10.1 野指针 未初始化的指针:
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 )); }
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 = # 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 ; }