在C语言链表中,为什么经常把结构体指针用const 修饰?用const修饰之后不就变成常量了吗?还怎么修改,比如给链表增减节点?
C语言链表中const修饰结构体指针的作用
在C语言中,const关键字用于修饰指针时有不同的含义,这取决于const放置的位置。理解这些差异对于正确使用链表至关重要。
const修饰指针的三种情况
指向常量的指针 - 不能通过指针修改数据
常量指针 - 指针本身不可修改(必须初始化)
1
| struct Node* const ptr = &some_node;
|
指向常量的常量指针 - 两者都不可修改
1
| const struct Node* const ptr = &some_node;
|
为什么在链表中使用const
1. 保护数据不被意外修改
在只需要读取链表数据而不修改链表结构的函数中使用const,可以防止意外修改节点内容:
1 2 3 4 5 6 7 8 9
| void print_list(const struct Node* head) { const struct Node* current = head; while (current != NULL) { printf("%d ", current->data); current = current->next; } }
|
2. 增加代码可读性和安全性
使用const明确表达了函数的意图,让调用者知道函数是否会修改链表内容。
3. 允许函数接受常量链表
当你的链表被声明为常量时,只有使用const修饰的函数参数才能接受它:
1 2
| const struct Node* const_list = create_list(); print_list(const_list);
|
如何修改链表
对于需要修改链表的函数,我们不会使用const修饰指向需要修改部分的指针:
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
| void add_node(struct Node** head_ref, int data) { struct Node* new_node = (struct Node*)malloc(sizeof(struct Node)); new_node->data = data; new_node->next = *head_ref; *head_ref = new_node; }
void delete_node(struct Node** head_ref, int key) { struct Node* temp = *head_ref, *prev = NULL; while (temp != NULL && temp->data != key) { prev = temp; temp = temp->next; } if (temp == NULL) return; if (prev == NULL) { *head_ref = temp->next; } else { prev->next = temp->next; } free(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
| #include <stdio.h> #include <stdlib.h>
struct Node { int data; struct Node* next; };
void print_list(const struct Node* head) { const struct Node* current = head; while (current != NULL) { printf("%d -> ", current->data); current = current->next; } printf("NULL\n"); }
void append_node(struct Node** head_ref, int data) { struct Node* new_node = (struct Node*)malloc(sizeof(struct Node)); struct Node* last = *head_ref; new_node->data = data; new_node->next = NULL; if (*head_ref == NULL) { *head_ref = new_node; return; } while (last->next != NULL) { last = last->next; } last->next = new_node; }
int main() { struct Node* head = NULL; append_node(&head, 1); append_node(&head, 2); append_node(&head, 3); printf("Linked list: "); print_list(head); return 0; }
|
总结
在链表中使用const修饰结构体指针的主要目的是:
- 保护数据不被意外修改
- 提高代码的可读性和安全性
- 允许函数处理常量链表
const并不妨碍我们修改链表,它只是限制了某些函数对数据的修改能力。需要修改链表时,我们使用非常量指针,而只需要读取数据时,使用const指针可以提高代码的健壮性。
*我见到的大部分是常量指针的用法,类似List_Item_t const pxItem。常量指针的意思是指针无法改变,一直指向一个地址,但是改地址的内容可以改变吗?
您观察得非常准确!List_Item_t *const pxItem 这种写法确实是 C 语言链表中非常常见且重要的模式。您对它的理解也基本正确,让我们来详细解析一下。
核心概念:常量指针 (Constant Pointer)
List_Item_t *const pxItem 声明的是一个 常量指针。
- 指针本身是常量:
pxItem 这个指针变量存储的内存地址不可改变
- 指向的数据非常量:可以通过
pxItem 来修改它指向的 List_Item_t 结构体的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| typedef struct List_Item { int data; struct List_Item* next; } List_Item_t;
void example_function() { List_Item_t item1 = {10, NULL}; List_Item_t item2 = {20, NULL}; List_Item_t *const pxItem = &item1; pxItem->data = 100; pxItem->next = &item2; }
|
为什么在链表操作中使用常量指针?
1. 遍历链表时的安全性
在遍历链表时,我们通常需要一个当前指针来遍历节点,但我们不希望这个指针本身被意外修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void process_list(List_Item_t *const head) { List_Item_t *const current = head; while (current != NULL) { printf("Processing item with data: %d\n", current->data); current->data *= 2; List_Item_t *next_ptr = current->next; } }
|
2. 函数参数中的使用
在函数参数中使用常量指针可以明确表达函数的契约:
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
| void print_list(const List_Item_t *const head) { const List_Item_t *current = head; while (current != NULL) { printf("%d ", current->data); current = current->next; } }
void insert_at_head(List_Item_t **const head_ref, int data) { List_Item_t *new_node = malloc(sizeof(List_Item_t)); new_node->data = data; new_node->next = *head_ref; *head_ref = new_node; }
|
3. 在链表算法中的实际应用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| void sorted_insert(List_Item_t **const head_ref, List_Item_t *const new_node) { List_Item_t **current_ref = head_ref; while (*current_ref != NULL && (*current_ref)->data < new_node->data) { current_ref = &((*current_ref)->next); } new_node->next = *current_ref; *current_ref = new_node; }
|
总结
| 指针类型 |
语法 |
能否改变指针 |
能否改变指向的数据 |
| 非常量指针 |
List_Item_t* ptr |
是 |
是 |
| 指向常量的指针 |
const List_Item_t* ptr |
是 |
否 |
| 常量指针 |
List_Item_t* const ptr |
否 |
是 |
| 指向常量的常量指针 |
const List_Item_t* const ptr |
否 |
否 |
在链表操作中使用 List_Item_t *const pxItem 这种常量指针的主要目的是:
- 确保指针稳定性:防止指针本身被意外修改
- 明确设计意图:代码明确表达”这个指针不应该改变指向”
- 编译时检查:编译器会捕获意外的指针赋值错误
- 保持修改能力:仍然可以修改指针所指向的数据内容
这种用法在需要固定引用某个节点但同时需要修改该节点内容的场景中特别有用。