Skip to content

C语言实战与系统编程

在掌握了C语言的基础语法和核心特性后,我们将进入实战阶段,学习如何使用C语言进行系统编程、数据结构实现以及嵌入式开发等高级应用。

数据结构基础(C语言实现)

线性表

单链表

单链表是链式存储结构的基本形式,每个节点包含数据域和指向下一个节点的指针。

c
#include <stdio.h>
#include <stdlib.h>

// 定义链表节点结构
typedef struct Node {
    int data;
    struct Node* next;
} Node;

// 创建新节点
Node* createNode(int data) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->data = data;
    newNode->next = NULL;
    return newNode;
}

// 在链表头部插入节点
Node* insertAtHead(Node* head, int data) {
    Node* newNode = createNode(data);
    newNode->next = head;
    return newNode;
}

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

// 删除节点
Node* deleteNode(Node* head, int data) {
    if (head == NULL) return NULL;
    
    // 如果要删除的是头节点
    if (head->data == data) {
        Node* temp = head;
        head = head->next;
        free(temp);
        return head;
    }
    
    // 查找要删除的节点
    Node* current = head;
    while (current->next != NULL && current->next->data != data) {
        current = current->next;
    }
    
    // 删除节点
    if (current->next != NULL) {
        Node* temp = current->next;
        current->next = current->next->next;
        free(temp);
    }
    
    return head;
}

// 释放链表内存
void freeList(Node* head) {
    Node* current = head;
    while (current != NULL) {
        Node* temp = current;
        current = current->next;
        free(temp);
    }
}

栈与队列

栈是后进先出(LIFO)的数据结构,队列是先进先出(FIFO)的数据结构。

c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

#define MAX_SIZE 100

// 栈的实现(顺序存储)
typedef struct {
    int data[MAX_SIZE];
    int top;
} Stack;

// 初始化栈
void initStack(Stack* stack) {
    stack->top = -1;
}

// 判断栈是否为空
bool isStackEmpty(Stack* stack) {
    return stack->top == -1;
}

// 判断栈是否已满
bool isStackFull(Stack* stack) {
    return stack->top == MAX_SIZE - 1;
}

// 入栈
bool push(Stack* stack, int value) {
    if (isStackFull(stack)) {
        return false;
    }
    stack->data[++stack->top] = value;
    return true;
}

// 出栈
bool pop(Stack* stack, int* value) {
    if (isStackEmpty(stack)) {
        return false;
    }
    *value = stack->data[stack->top--];
    return true;
}

// 队列的实现(顺序存储)
typedef struct {
    int data[MAX_SIZE];
    int front;
    int rear;
} Queue;

// 初始化队列
void initQueue(Queue* queue) {
    queue->front = 0;
    queue->rear = 0;
}

// 判断队列是否为空
bool isQueueEmpty(Queue* queue) {
    return queue->front == queue->rear;
}

// 判断队列是否已满
bool isQueueFull(Queue* queue) {
    return (queue->rear + 1) % MAX_SIZE == queue->front;
}

// 入队
bool enqueue(Queue* queue, int value) {
    if (isQueueFull(queue)) {
        return false;
    }
    queue->data[queue->rear] = value;
    queue->rear = (queue->rear + 1) % MAX_SIZE;
    return true;
}

// 出队
bool dequeue(Queue* queue, int* value) {
    if (isQueueEmpty(queue)) {
        return false;
    }
    *value = queue->data[queue->front];
    queue->front = (queue->front + 1) % MAX_SIZE;
    return true;
}

树与图

二叉树

二叉树是每个节点最多有两个子树的树结构。

c
#include <stdio.h>
#include <stdlib.h>

// 二叉树节点定义
typedef struct TreeNode {
    int data;
    struct TreeNode* left;
    struct TreeNode* right;
} TreeNode;

// 创建新节点
TreeNode* createTreeNode(int data) {
    TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
    newNode->data = data;
    newNode->left = NULL;
    newNode->right = NULL;
    return newNode;
}

// 前序遍历(根-左-右)
void preorderTraversal(TreeNode* root) {
    if (root != NULL) {
        printf("%d ", root->data);
        preorderTraversal(root->left);
        preorderTraversal(root->right);
    }
}

// 中序遍历(左-根-右)
void inorderTraversal(TreeNode* root) {
    if (root != NULL) {
        inorderTraversal(root->left);
        printf("%d ", root->data);
        inorderTraversal(root->right);
    }
}

// 后序遍历(左-右-根)
void postorderTraversal(TreeNode* root) {
    if (root != NULL) {
        postorderTraversal(root->left);
        postorderTraversal(root->right);
        printf("%d ", root->data);
    }
}

图的表示

图可以用邻接矩阵或邻接表来表示。

c
#include <stdio.h>
#include <stdlib.h>
#define MAX_VERTICES 100

// 邻接矩阵表示图
typedef struct {
    int vertices;
    int adjMatrix[MAX_VERTICES][MAX_VERTICES];
} GraphMatrix;

// 初始化图
void initGraphMatrix(GraphMatrix* graph, int vertices) {
    graph->vertices = vertices;
    for (int i = 0; i < vertices; i++) {
        for (int j = 0; j < vertices; j++) {
            graph->adjMatrix[i][j] = 0;
        }
    }
}

// 添加边
void addEdgeMatrix(GraphMatrix* graph, int src, int dest) {
    if (src >= 0 && src < graph->vertices && dest >= 0 && dest < graph->vertices) {
        graph->adjMatrix[src][dest] = 1;
        graph->adjMatrix[dest][src] = 1; // 无向图
    }
}

// 打印邻接矩阵
void printGraphMatrix(GraphMatrix* graph) {
    for (int i = 0; i < graph->vertices; i++) {
        for (int j = 0; j < graph->vertices; j++) {
            printf("%d ", graph->adjMatrix[i][j]);
        }
        printf("\n");
    }
}

// 邻接表节点
typedef struct AdjListNode {
    int vertex;
    struct AdjListNode* next;
} AdjListNode;

// 邻接表表示图
typedef struct {
    int vertices;
    AdjListNode* adjList[MAX_VERTICES];
} GraphList;

// 创建邻接表节点
AdjListNode* createAdjListNode(int vertex) {
    AdjListNode* newNode = (AdjListNode*)malloc(sizeof(AdjListNode));
    newNode->vertex = vertex;
    newNode->next = NULL;
    return newNode;
}

// 初始化图
void initGraphList(GraphList* graph, int vertices) {
    graph->vertices = vertices;
    for (int i = 0; i < vertices; i++) {
        graph->adjList[i] = NULL;
    }
}

// 添加边
void addEdgeList(GraphList* graph, int src, int dest) {
    // 添加边 src -> dest
    AdjListNode* newNode = createAdjListNode(dest);
    newNode->next = graph->adjList[src];
    graph->adjList[src] = newNode;
    
    // 添加边 dest -> src (无向图)
    newNode = createAdjListNode(src);
    newNode->next = graph->adjList[dest];
    graph->adjList[dest] = newNode;
}

系统编程入门(Linux环境)

进程基础

在Linux系统中,进程是程序执行的实例。

c
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

// 进程创建示例
int processExample() {
    pid_t pid = fork();
    
    if (pid < 0) {
        // 创建失败
        perror("fork failed");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("子进程: PID = %d, 父进程 PID = %d\n", getpid(), getppid());
        // 子进程执行的任务
        execlp("/bin/ls", "ls", "-l", NULL);
        // 如果execlp执行成功,下面的代码不会执行
        printf("execlp执行失败\n");
        exit(1);
    } else {
        // 父进程
        printf("父进程: PID = %d, 子进程 PID = %d\n", getpid(), pid);
        // 等待子进程结束
        int status;
        wait(&status);
        printf("子进程已结束,退出状态: %d\n", WEXITSTATUS(status));
    }
    
    return 0;
}

信号处理

信号是进程间通信的一种方式,用于通知进程发生了某种事件。

c
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

// 信号处理函数
void signalHandler(int signum) {
    printf("捕获到信号 %d\n", signum);
    if (signum == SIGINT) {
        printf("程序将退出\n");
        exit(0);
    }
}

// 信号处理示例
int signalExample() {
    // 注册信号处理函数
    signal(SIGINT, signalHandler);
    
    printf("程序运行中,按 Ctrl+C 发送 SIGINT 信号\n");
    
    // 无限循环,等待信号
    while(1) {
        printf("程序正在运行...\n");
        sleep(1);
    }
    
    return 0;
}

文件系统操作

Linux系统中的文件操作是系统编程的重要部分。

c
#include <stdio.h>
#include <dirent.h>
#include <sys/stat.h>
#include <time.h>

// 目录操作示例
void directoryExample() {
    DIR *dir;
    struct dirent *entry;
    
    // 打开当前目录
    dir = opendir(".");
    if (dir == NULL) {
        perror("无法打开目录");
        return;
    }
    
    printf("当前目录中的文件:\n");
    // 读取目录内容
    while ((entry = readdir(dir)) != NULL) {
        printf("%s\n", entry->d_name);
    }
    
    // 关闭目录
    closedir(dir);
}

// 文件属性获取示例
void fileStatExample(const char* filename) {
    struct stat fileStat;
    
    if (stat(filename, &fileStat) < 0) {
        perror("获取文件状态失败");
        return;
    }
    
    printf("文件: %s\n", filename);
    printf("大小: %ld 字节\n", fileStat.st_size);
    printf("创建时间: %s", ctime(&fileStat.st_ctime));
    printf("修改时间: %s", ctime(&fileStat.st_mtime));
    
    // 判断文件类型
    if (S_ISREG(fileStat.st_mode)) {
        printf("类型: 普通文件\n");
    } else if (S_ISDIR(fileStat.st_mode)) {
        printf("类型: 目录\n");
    } else if (S_ISLNK(fileStat.st_mode)) {
        printf("类型: 符号链接\n");
    }
}

嵌入式C语言基础

寄存器操作

在嵌入式开发中,直接操作硬件寄存器是常见需求。

c
#include <stdint.h>

// 假设的GPIO寄存器地址
#define GPIO_BASE_ADDR 0x40020000
#define GPIO_MODER_OFFSET 0x00
#define GPIO_ODR_OFFSET 0x14

// 定义寄存器指针
#define GPIOA_MODER (*(volatile uint32_t*)(GPIO_BASE_ADDR + GPIO_MODER_OFFSET))
#define GPIOA_ODR (*(volatile uint32_t*)(GPIO_BASE_ADDR + GPIO_ODR_OFFSET))

// 配置GPIO引脚为输出模式
void gpioInit() {
    // 配置PA5为输出模式 (MODER寄存器中,每两位控制一个引脚)
    GPIOA_MODER &= ~(0x3 << (5 * 2)); // 清除PA5的模式位
    GPIOA_MODER |= (0x1 << (5 * 2));  // 设置PA5为输出模式
}

// 设置GPIO引脚电平
void gpioWrite(int pin, int value) {
    if (value) {
        GPIOA_ODR |= (1 << pin);  // 设置引脚为高电平
    } else {
        GPIOA_ODR &= ~(1 << pin); // 设置引脚为低电平
    }
}

位运算

位运算是嵌入式开发中的重要工具。

c
#include <stdio.h>
#include <stdint.h>

// 位操作宏定义
#define SET_BIT(reg, bit) ((reg) |= (1 << (bit)))
#define CLEAR_BIT(reg, bit) ((reg) &= ~(1 << (bit)))
#define TOGGLE_BIT(reg, bit) ((reg) ^= (1 << (bit)))
#define CHECK_BIT(reg, bit) (((reg) >> (bit)) & 1)

// 位操作示例
void bitOperationExample() {
    uint8_t reg = 0x00;
    
    printf("初始值: 0x%02X\n", reg);
    
    // 设置第3位
    SET_BIT(reg, 3);
    printf("设置第3位后: 0x%02X\n", reg);
    
    // 清除第3位
    CLEAR_BIT(reg, 3);
    printf("清除第3位后: 0x%02X\n", reg);
    
    // 翻转第5位
    TOGGLE_BIT(reg, 5);
    printf("翻转第5位后: 0x%02X\n", reg);
    
    // 检查第5位
    if (CHECK_BIT(reg, 5)) {
        printf("第5位为1\n");
    } else {
        printf("第5位为0\n");
    }
}

项目实战工具

调试工具:GDB使用

GDB是Linux下强大的调试工具,可以帮助我们调试C程序。

bash
# 编译时加入调试信息
gcc -g -o program program.c

# 启动GDB
gdb ./program

# GDB常用命令
# (gdb) break main        # 在main函数设置断点
# (gdb) run              # 运行程序
# (gdb) step             # 单步执行(进入函数)
# (gdb) next             # 单步执行(不进入函数)
# (gdb) print variable   # 打印变量值
# (gdb) continue         # 继续执行
# (gdb) quit             # 退出GDB

构建工具:Makefile基础

Makefile用于自动化编译和构建项目。

makefile
# Makefile示例

# 编译器
CC = gcc

# 编译选项
CFLAGS = -Wall -g

# 目标文件
TARGET = myprogram

# 源文件
SRCS = main.c utils.c

# 对象文件
OBJS = $(SRCS:.c=.o)

# 默认目标
all: $(TARGET)

# 链接目标文件
$(TARGET): $(OBJS)
	$(CC) $(CFLAGS) -o $(TARGET) $(OBJS)

# 编译源文件
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# 清理目标文件
clean:
	rm -f $(OBJS) $(TARGET)

# 安装目标
install: $(TARGET)
	cp $(TARGET) /usr/local/bin/

# 伪目标
.PHONY: all clean install

版本控制:Git基础

Git是分布式版本控制系统,用于管理代码变更。

bash
# 初始化Git仓库
git init

# 添加文件到暂存区
git add .

# 提交更改
git commit -m "初始提交"

# 查看状态
git status

# 查看提交历史
git log

# 创建分支
git branch feature-branch

# 切换分支
git checkout feature-branch

# 合并分支
git checkout main
git merge feature-branch

# 推送到远程仓库
git push origin main

综合项目

小型数据库实现

让我们实现一个简单的基于文件的数据库系统:

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_RECORDS 1000
#define MAX_NAME_LEN 50
#define DATABASE_FILE "database.dat"

// 数据记录结构
typedef struct {
    int id;
    char name[MAX_NAME_LEN];
    int age;
    float score;
} Record;

// 数据库结构
typedef struct {
    Record records[MAX_RECORDS];
    int count;
} Database;

// 初始化数据库
void initDatabase(Database* db) {
    db->count = 0;
}

// 添加记录
int addRecord(Database* db, int id, const char* name, int age, float score) {
    if (db->count >= MAX_RECORDS) {
        return -1; // 数据库已满
    }
    
    db->records[db->count].id = id;
    strncpy(db->records[db->count].name, name, MAX_NAME_LEN - 1);
    db->records[db->count].name[MAX_NAME_LEN - 1] = '\0';
    db->records[db->count].age = age;
    db->records[db->count].score = score;
    db->count++;
    
    return 0; // 成功
}

// 查找记录
Record* findRecord(Database* db, int id) {
    for (int i = 0; i < db->count; i++) {
        if (db->records[i].id == id) {
            return &db->records[i];
        }
    }
    return NULL; // 未找到
}

// 删除记录
int deleteRecord(Database* db, int id) {
    for (int i = 0; i < db->count; i++) {
        if (db->records[i].id == id) {
            // 将后面的记录前移
            for (int j = i; j < db->count - 1; j++) {
                db->records[j] = db->records[j + 1];
            }
            db->count--;
            return 0; // 成功删除
        }
    }
    return -1; // 未找到
}

// 保存数据库到文件
int saveDatabase(Database* db) {
    FILE* file = fopen(DATABASE_FILE, "wb");
    if (file == NULL) {
        return -1;
    }
    
    fwrite(&db->count, sizeof(int), 1, file);
    fwrite(db->records, sizeof(Record), db->count, file);
    fclose(file);
    
    return 0;
}

// 从文件加载数据库
int loadDatabase(Database* db) {
    FILE* file = fopen(DATABASE_FILE, "rb");
    if (file == NULL) {
        return -1;
    }
    
    fread(&db->count, sizeof(int), 1, file);
    fread(db->records, sizeof(Record), db->count, file);
    fclose(file);
    
    return 0;
}

// 打印所有记录
void printAllRecords(Database* db) {
    printf("ID\t姓名\t\t年龄\t成绩\n");
    printf("----------------------------------------\n");
    for (int i = 0; i < db->count; i++) {
        printf("%d\t%-15s\t%d\t%.2f\n", 
               db->records[i].id, 
               db->records[i].name, 
               db->records[i].age, 
               db->records[i].score);
    }
}

简单HTTP服务器

实现一个处理GET请求的简单HTTP服务器:

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024

// HTTP响应模板
const char* HTTP_RESPONSE = 
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Content-Length: %d\r\n"
"\r\n"
"%s";

// 简单的HTML页面
const char* HTML_CONTENT = 
"<!DOCTYPE html>"
"<html>"
"<head><title>C语言HTTP服务器</title></head>"
"<body>"
"<h1>欢迎来到C语言HTTP服务器</h1>"
"<p>这是一个用C语言实现的简单HTTP服务器</p>"
"<p>当前时间: %s</p>"
"</body>"
"</html>";

// 处理客户端请求
void handleClient(int clientSocket) {
    char buffer[BUFFER_SIZE];
    ssize_t bytesRead;
    
    // 读取客户端请求
    bytesRead = read(clientSocket, buffer, BUFFER_SIZE - 1);
    if (bytesRead > 0) {
        buffer[bytesRead] = '\0';
        printf("收到请求:\n%s\n", buffer);
        
        // 构造响应内容
        char currentTime[100];
        time_t now = time(NULL);
        strftime(currentTime, sizeof(currentTime), "%Y-%m-%d %H:%M:%S", localtime(&now));
        
        char htmlContent[500];
        snprintf(htmlContent, sizeof(htmlContent), HTML_CONTENT, currentTime);
        
        char response[BUFFER_SIZE];
        snprintf(response, sizeof(response), HTTP_RESPONSE, (int)strlen(htmlContent), htmlContent);
        
        // 发送响应
        write(clientSocket, response, strlen(response));
    }
    
    // 关闭客户端连接
    close(clientSocket);
}

// 启动HTTP服务器
int startHttpServer() {
    int serverSocket, clientSocket;
    struct sockaddr_in serverAddr, clientAddr;
    socklen_t clientAddrLen = sizeof(clientAddr);
    
    // 创建套接字
    serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket < 0) {
        perror("创建套接字失败");
        return 1;
    }
    
    // 设置服务器地址
    memset(&serverAddr, 0, sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(PORT);
    
    // 绑定套接字
    if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) {
        perror("绑定失败");
        close(serverSocket);
        return 1;
    }
    
    // 监听连接
    if (listen(serverSocket, 5) < 0) {
        perror("监听失败");
        close(serverSocket);
        return 1;
    }
    
    printf("HTTP服务器启动,监听端口 %d\n", PORT);
    
    // 接受客户端连接
    while (1) {
        clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
        if (clientSocket < 0) {
            perror("接受连接失败");
            continue;
        }
        
        printf("客户端连接: %s:%d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
        
        // 处理客户端请求
        handleClient(clientSocket);
    }
    
    // 关闭服务器套接字
    close(serverSocket);
    return 0;
}

实践练习

  1. 实现一个完整的学生成绩管理系统,包含增删改查功能
  2. 开发一个简单的文本编辑器,支持文件读写和基本编辑功能
  3. 用函数指针实现一个通用的排序算法库
  4. 实现一个基于链表的任务调度器
  5. 开发一个简单的聊天程序(使用socket编程)

通过这些实战项目,你将能够更好地理解C语言在系统编程中的应用,并掌握实际开发中常用的工具和技术。记住,实践是掌握C语言的关键,多写代码、多调试、多思考,你将逐步成长为一名优秀的C语言程序员!