在日常开发中,我们经常需要处理各种各样的数据,而如何高效地组织和管理这些数据,就显得至关重要。数据结构学习(1),我们先从 C 语言的基础也是核心部分入手:指针、结构体、链表。它们是构建更复杂数据结构和算法的基础,也是理解操作系统、数据库等底层原理的敲门砖。
指针:C 语言的灵魂
指针是 C 语言的精髓所在,也是很多初学者觉得难以理解的地方。简单来说,指针就是一个变量,它存储的是另一个变量的内存地址。通过指针,我们可以直接访问和修改内存中的数据,这使得 C 语言具有了强大的灵活性和控制力。
指针的声明和使用
int a = 10; // 定义一个整型变量 a
int *p; // 声明一个指向整型变量的指针 p
p = &a; // 将变量 a 的地址赋值给指针 p
printf("a 的值:%d\n", a); // 输出 a 的值
printf("a 的地址:%p\n", &a); // 输出 a 的地址
printf("p 的值:%p\n", p); // 输出 p 的值(a 的地址)
printf("*p 的值:%d\n", *p); // 输出 p 指向的内存地址的值(a 的值)
* 运算符用于解引用,即获取指针指向的内存地址中存储的值。& 运算符用于获取变量的地址。
指针与数组
在 C 语言中,数组名本质上就是一个指向数组首元素的指针。利用指针可以方便地访问数组中的元素。
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr; // ptr 指向数组 arr 的首元素
printf("arr[0] 的值:%d\n", arr[0]); // 输出数组第一个元素的值
printf("*ptr 的值:%d\n", *ptr); // 输出指针 ptr 指向的值(也是数组第一个元素的值)
printf("arr[2] 的值:%d\n", arr[2]); // 输出数组第三个元素的值
printf("*(ptr + 2) 的值:%d\n", *(ptr + 2)); // 指针 ptr 向后移动两个位置,然后解引用
指针的避坑经验
- 野指针:指针没有被初始化,或者指向的内存已经被释放,此时的指针就是野指针。访问野指针会导致程序崩溃或产生不可预测的结果。初始化指针或者在释放内存后将指针置为
NULL可以避免野指针。 - 内存泄漏:使用
malloc等函数动态分配内存后,如果忘记使用free函数释放内存,就会导致内存泄漏。长期运行的程序发生内存泄漏可能会耗尽系统资源。可以使用 valgrind 等工具检测内存泄漏。
结构体:自定义数据类型
结构体是一种复合数据类型,可以将多个不同类型的变量组合在一起,形成一个新的数据类型。这使得我们可以更方便地表示复杂的数据结构,比如用户信息、商品信息等。
结构体的定义和使用
// 定义一个名为 Person 的结构体
struct Person {
char name[50]; // 姓名
int age; // 年龄
float height; // 身高
};
int main() {
// 声明一个 Person 类型的变量 person1
struct Person person1;
// 给 person1 的成员赋值
strcpy(person1.name, "张三");
person1.age = 30;
person1.height = 1.75;
// 打印 person1 的信息
printf("姓名:%s\n", person1.name);
printf("年龄:%d\n", person1.age);
printf("身高:%.2f\n", person1.height);
return 0;
}
使用 . 运算符访问结构体的成员。
结构体指针
可以使用指针来操作结构体,这在函数参数传递和动态内存分配时非常有用。
struct Person *pPerson = &person1; // 声明一个指向 Person 结构体的指针
// 使用 -> 运算符访问结构体指针指向的成员
printf("姓名:%s\n", pPerson->name);
printf("年龄:%d\n", pPerson->age);
printf("身高:%.2f\n", pPerson->height);
使用 -> 运算符通过结构体指针访问其成员。
链表:动态数据结构
链表是一种动态数据结构,它由一系列节点组成,每个节点包含数据和指向下一个节点的指针。链表可以灵活地插入和删除节点,而不需要像数组那样移动大量的元素。
链表的定义
// 定义链表节点
struct Node {
int data; // 数据
struct Node *next; // 指向下一个节点的指针
};
链表的基本操作
- 创建链表:动态分配内存,创建新的节点,并设置节点的数据和指针。
- 插入节点:在链表的指定位置插入新的节点,需要修改指针的指向。
- 删除节点:从链表中删除指定的节点,需要修改指针的指向。
- 遍历链表:从链表的头节点开始,依次访问每个节点的数据。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义链表节点
struct Node {
int data; // 数据
struct Node *next; // 指向下一个节点的指针
};
// 创建链表
struct Node* createList() {
return NULL; // 返回空指针,表示空链表
}
// 插入节点 (头插法)
struct Node* insertNode(struct Node* head, int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); // 分配内存
if (newNode == NULL) {
perror("Failed to allocate memory");
exit(EXIT_FAILURE);
}
newNode->data = data; // 赋值数据
newNode->next = head; // 新节点的next指向旧的头节点
head = newNode; // 新节点成为新的头节点
return head; // 返回新的头节点
}
// 打印链表
void printList(struct Node* head) {
struct Node* current = head;
printf("List: ");
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
// 释放链表内存
void freeList(struct Node* head) {
struct Node* current = head;
while (current != NULL) {
struct Node* temp = current;
current = current->next;
free(temp);
}
}
int main() {
struct Node* head = createList(); // 创建空链表
head = insertNode(head, 10); // 插入节点
head = insertNode(head, 20); // 插入节点
head = insertNode(head, 30); // 插入节点
printList(head); // 打印链表
freeList(head); // 释放链表内存
head = NULL; // 将头指针置空
return 0;
}
链表的应用场景
链表在许多场景中都有应用,例如:
- 实现栈和队列:可以使用链表来实现栈和队列这两种常用的数据结构。
- 动态内存管理:操作系统可以使用链表来管理空闲内存块。
- 图形图像处理:可以用链表来存储图像的像素数据。
数据结构学习(1) 中的指针、结构体和链表是 C 语言编程的基础,掌握它们对于理解更高级的数据结构和算法至关重要。 熟练运用这些基本功,可以为日后学习 Nginx 反向代理、高并发服务器设计打下坚实基础,甚至在面对宝塔面板的性能瓶颈时,也能从容地进行优化。
冠军资讯
加班到秃头