引言:为什么选择C语言?

C语言作为一门诞生于20世纪70年代的编程语言,至今仍然在系统编程、嵌入式开发、操作系统内核等领域占据着不可替代的地位。对于初学者而言,学习C语言不仅能够帮助你理解计算机底层工作原理,还能为学习其他高级语言(如C++、Java、Python)打下坚实的基础。

C语言的核心优势:

  • 高效性:接近硬件,执行速度快
  • 灵活性:直接操作内存,提供指针等强大功能
  • 可移植性:标准C语言可以在多种平台上运行
  • 基础性:理解计算机科学核心概念的最佳语言

第一章:开发环境搭建

1.1 选择合适的编译器

对于Windows用户,推荐使用:

  • MinGW-w64:GCC的Windows版本,轻量且功能完整
  • Visual Studio:微软官方IDE,功能强大但体积较大

对于macOS用户:

  • Xcode Command Line Tools:系统自带,安装简单
  • Homebrew安装GCCbrew install gcc

对于Linux用户:

  • GCC:大多数发行版默认安装,使用gcc --version检查

1.2 编写第一个C程序

创建一个名为hello.c的文件,内容如下:

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

代码解析:

  • #include <stdio.h>:包含标准输入输出头文件
  • int main():程序入口函数,返回整型
  • printf():输出函数,\n表示换行
  • return 0:正常结束程序

1.3 编译与运行

在命令行中执行:

# 编译
gcc hello.c -o hello

# 运行
./hello

常见问题:

  • 问题1:编译时出现“未找到gcc命令”
    • 解决方案:确保编译器已正确安装并添加到系统PATH
  • 问题2:运行时出现“权限被拒绝”
    • 解决方案:在Linux/macOS上使用chmod +x hello添加执行权限

第二章:C语言基础语法

2.1 数据类型与变量

C语言提供了多种基本数据类型:

#include <stdio.h>

int main() {
    // 整型
    int age = 25;
    short smallNumber = 100;
    long largeNumber = 1000000L;
    
    // 浮点型
    float price = 19.99f;
    double pi = 3.1415926535;
    
    // 字符型
    char grade = 'A';
    
    // 布尔型(C99标准)
    #include <stdbool.h>
    bool isStudent = true;
    
    printf("年龄:%d\n", age);
    printf("价格:%.2f\n", price);
    printf("等级:%c\n", grade);
    
    return 0;
}

变量命名规则:

  • 以字母或下划线开头
  • 只能包含字母、数字和下划线
  • 区分大小写
  • 不能使用C关键字(如int、char、if等)

2.2 运算符与表达式

C语言运算符丰富,包括算术、关系、逻辑、位运算等:

#include <stdio.h>

int main() {
    // 算术运算符
    int a = 10, b = 3;
    printf("a + b = %d\n", a + b);      // 13
    printf("a - b = %d\n", a - b);      // 7
    printf("a * b = %d\n", a * b);      // 30
    printf("a / b = %d\n", a / b);      // 3(整数除法)
    printf("a %% b = %d\n", a % b);     // 1(取余)
    
    // 关系运算符
    printf("a > b: %d\n", a > b);       // 1(真)
    printf("a == b: %d\n", a == b);     // 0(假)
    
    // 逻辑运算符
    int x = 1, y = 0;
    printf("x && y: %d\n", x && y);     // 0(与)
    printf("x || y: %d\n", x || y);     // 1(或)
    printf("!x: %d\n", !x);             // 0(非)
    
    // 赋值运算符
    int c = 5;
    c += 3;  // 等价于 c = c + 3
    printf("c = %d\n", c);              // 8
    
    // 自增自减运算符
    int d = 5;
    printf("d++: %d\n", d++);           // 5(先使用后自增)
    printf("++d: %d\n", ++d);           // 7(先自增后使用)
    
    return 0;
}

运算符优先级:

  1. 括号 ()
  2. 自增自减 ++ --
  3. 乘除模 * / %
  4. 加减 + -
  5. 关系运算符 > < >= <= == !=
  6. 逻辑运算符 && ||
  7. 赋值运算符 = += -=

2.3 输入输出函数

标准输入输出:

#include <stdio.h>

int main() {
    char name[50];
    int age;
    float height;
    
    // 输入
    printf("请输入姓名:");
    scanf("%s", name);  // 注意:scanf遇到空格会停止
    
    printf("请输入年龄:");
    scanf("%d", &age);  // 注意:需要取地址符&
    
    printf("请输入身高(m):");
    scanf("%f", &height);
    
    // 输出
    printf("\n--- 个人信息 ---\n");
    printf("姓名:%s\n", name);
    printf("年龄:%d岁\n", age);
    printf("身高:%.2fm\n", height);
    
    return 0;
}

安全输入问题: scanf函数容易造成缓冲区溢出,推荐使用更安全的替代方案:

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

int main() {
    char name[50];
    
    // 安全输入方法1:限制输入长度
    printf("请输入姓名(最多49个字符):");
    scanf("%49s", name);  // 限制输入长度
    
    // 安全输入方法2:使用fgets(推荐)
    printf("请输入姓名:");
    fgets(name, sizeof(name), stdin);
    name[strcspn(name, "\n")] = 0;  // 移除换行符
    
    printf("你好,%s!\n", name);
    
    return 0;
}

第三章:控制结构

3.1 条件语句

#include <stdio.h>

int main() {
    int score;
    printf("请输入成绩(0-100):");
    scanf("%d", &score);
    
    // if-else if-else 结构
    if (score >= 90) {
        printf("优秀!\n");
    } else if (score >= 80) {
        printf("良好!\n");
    } else if (score >= 60) {
        printf("及格!\n");
    } else {
        printf("不及格!\n");
    }
    
    // switch-case 结构
    int day;
    printf("请输入星期几(1-7):");
    scanf("%d", &day);
    
    switch (day) {
        case 1:
            printf("星期一\n");
            break;
        case 2:
            printf("星期二\n");
            break;
        case 3:
            printf("星期三\n");
            break;
        case 4:
            printf("星期四\n");
            break;
        case 5:
            printf("星期五\n");
            break;
        case 6:
            printf("星期六\n");
            break;
        case 7:
            printf("星期日\n");
            break;
        default:
            printf("无效的输入!\n");
    }
    
    return 0;
}

常见问题:

  • 问题:忘记在case后加break语句
    • 后果:会发生”case穿透”,继续执行下一个case
    • 示例
    switch (day) {
        case 1:
            printf("星期一");
            // 缺少break,会继续执行
        case 2:
            printf("星期二");  // 这行也会执行
            break;
    }
    // 输出:星期一星期二
    

3.2 循环结构

#include <stdio.h>

int main() {
    // for循环:打印1到10的平方
    printf("for循环示例:\n");
    for (int i = 1; i <= 10; i++) {
        printf("%d的平方是:%d\n", i, i * i);
    }
    
    // while循环:计算1到100的和
    printf("\nwhile循环示例:\n");
    int sum = 0, j = 1;
    while (j <= 100) {
        sum += j;
        j++;
    }
    printf("1到100的和:%d\n", sum);
    
    // do-while循环:至少执行一次
    printf("\ndo-while循环示例:\n");
    int num;
    do {
        printf("请输入一个正整数(输入0结束):");
        scanf("%d", &num);
        if (num > 0) {
            printf("你输入了:%d\n", num);
        }
    } while (num != 0);
    
    // 循环控制语句
    printf("\n循环控制语句示例:\n");
    for (int i = 1; i <= 10; i++) {
        if (i == 3) {
            continue;  // 跳过本次循环
        }
        if (i == 8) {
            break;  // 退出循环
        }
        printf("%d ", i);
    }
    printf("\n");
    
    return 0;
}

循环嵌套示例:

#include <stdio.h>

int main() {
    // 打印乘法表
    printf("9x9乘法表:\n");
    for (int i = 1; i <= 9; i++) {
        for (int j = 1; j <= i; j++) {
            printf("%d×%d=%-4d", i, j, i * j);
        }
        printf("\n");
    }
    
    // 打印菱形图案
    printf("\n菱形图案:\n");
    int n = 5;
    // 上半部分
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n - i; j++) {
            printf(" ");
        }
        for (int j = 1; j <= 2 * i - 1; j++) {
            printf("*");
        }
        printf("\n");
    }
    // 下半部分
    for (int i = n - 1; i >= 1; i--) {
        for (int j = 1; j <= n - i; j++) {
            printf(" ");
        }
        for (int j = 1; j <= 2 * i - 1; j++) {
            printf("*");
        }
        printf("\n");
    }
    
    return 0;
}

第四章:函数

4.1 函数定义与调用

#include <stdio.h>

// 函数声明
int add(int a, int b);
void printMessage(char* message);
float calculateArea(float radius);

// 主函数
int main() {
    // 调用函数
    int result = add(5, 3);
    printf("5 + 3 = %d\n", result);
    
    printMessage("Hello from function!");
    
    float area = calculateArea(2.5);
    printf("半径为2.5的圆面积:%.2f\n", area);
    
    return 0;
}

// 函数定义
int add(int a, int b) {
    return a + b;
}

void printMessage(char* message) {
    printf("%s\n", message);
}

float calculateArea(float radius) {
    return 3.14159 * radius * radius;
}

4.2 函数参数传递

#include <stdio.h>

// 值传递:修改形参不影响实参
void incrementByValue(int x) {
    x = x + 1;
    printf("函数内部x = %d\n", x);
}

// 地址传递:通过指针修改实参
void incrementByPointer(int *x) {
    *x = *x + 1;
    printf("函数内部*x = %d\n", *x);
}

// 数组作为参数
void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    // 值传递示例
    int a = 10;
    printf("调用前a = %d\n", a);
    incrementByValue(a);
    printf("调用后a = %d\n", a);  // a仍然是10
    
    // 地址传递示例
    int b = 10;
    printf("\n调用前b = %d\n", b);
    incrementByPointer(&b);
    printf("调用后b = %d\n", b);  // b变为11
    
    // 数组参数示例
    int numbers[] = {1, 2, 3, 4, 5};
    printf("\n数组内容:");
    printArray(numbers, 5);
    
    return 0;
}

4.3 递归函数

#include <stdio.h>

// 阶乘函数(递归)
int factorial(int n) {
    if (n <= 1) {
        return 1;  // 基本情况
    }
    return n * factorial(n - 1);  // 递归情况
}

// 斐波那契数列(递归)
int fibonacci(int n) {
    if (n <= 1) {
        return n;
    }
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main() {
    // 计算阶乘
    int num;
    printf("请输入一个正整数:");
    scanf("%d", &num);
    printf("%d! = %d\n", num, factorial(num));
    
    // 打印斐波那契数列
    printf("\n斐波那契数列前10项:\n");
    for (int i = 0; i < 10; i++) {
        printf("%d ", fibonacci(i));
    }
    printf("\n");
    
    return 0;
}

递归注意事项:

  • 必须有基本情况(终止条件)
  • 递归深度不能太大(可能导致栈溢出)
  • 递归效率通常低于迭代

第五章:数组与字符串

5.1 一维数组

#include <stdio.h>

int main() {
    // 数组声明与初始化
    int scores[5] = {85, 92, 78, 88, 95};
    
    // 访问数组元素
    printf("第三个学生的成绩:%d\n", scores[2]);  // 索引从0开始
    
    // 遍历数组
    printf("所有学生成绩:");
    for (int i = 0; i < 5; i++) {
        printf("%d ", scores[i]);
    }
    printf("\n");
    
    // 动态计算数组长度
    int length = sizeof(scores) / sizeof(scores[0]);
    printf("数组长度:%d\n", length);
    
    // 数组作为函数参数
    void printScores(int arr[], int size) {
        for (int i = 0; i < size; i++) {
            printf("学生%d:成绩%d\n", i + 1, arr[i]);
        }
    }
    
    printScores(scores, 5);
    
    return 0;
}

5.2 二维数组

#include <stdio.h>

int main() {
    // 声明3x4的二维数组
    int matrix[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    
    // 访问元素
    printf("matrix[1][2] = %d\n", matrix[1][2]);  // 输出7
    
    // 遍历二维数组
    printf("\n遍历二维数组:\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%-4d", matrix[i][j]);
        }
        printf("\n");
    }
    
    // 二维数组作为函数参数
    void printMatrix(int arr[][4], int rows) {
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < 4; j++) {
                printf("%-4d", arr[i][j]);
            }
            printf("\n");
        }
    }
    
    printf("\n使用函数打印矩阵:\n");
    printMatrix(matrix, 3);
    
    return 0;
}

5.3 字符串处理

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

int main() {
    // 字符串声明
    char name[20] = "Alice";
    char greeting[50];
    
    // 字符串复制
    strcpy(greeting, "Hello, ");
    strcat(greeting, name);
    printf("%s\n", greeting);  // Hello, Alice
    
    // 字符串比较
    char str1[] = "apple";
    char str2[] = "banana";
    int result = strcmp(str1, str2);
    if (result < 0) {
        printf("%s < %s\n", str1, str2);
    } else if (result > 0) {
        printf("%s > %s\n", str1, str2);
    } else {
        printf("%s == %s\n", str1, str2);
    }
    
    // 字符串长度
    char text[] = "Hello, World!";
    printf("字符串长度:%d\n", strlen(text));
    
    // 字符串查找
    char sentence[] = "The quick brown fox";
    char *found = strstr(sentence, "fox");
    if (found != NULL) {
        printf("找到'fox',位置:%ld\n", found - sentence);
    }
    
    // 安全字符串操作
    char safeStr[20];
    // 使用strncpy避免缓冲区溢出
    strncpy(safeStr, "This is a long string", sizeof(safeStr) - 1);
    safeStr[sizeof(safeStr) - 1] = '\0';  // 确保以空字符结尾
    
    printf("安全字符串:%s\n", safeStr);
    
    return 0;
}

字符串常见问题:

  • 问题1:忘记在字符数组末尾添加空字符\0

    • 后果:字符串函数会读取到内存中的随机数据
    • 解决方案:确保数组大小足够,或使用memset初始化
  • 问题2:使用scanf读取带空格的字符串

    • 后果:只读取到第一个空格
    • 解决方案:使用fgetsscanf("%[^\n]", str)读取整行

第六章:指针

6.1 指针基础

#include <stdio.h>

int main() {
    int num = 42;
    int *ptr = &num;  // ptr指向num的地址
    
    printf("变量num的值:%d\n", num);
    printf("变量num的地址:%p\n", &num);
    printf("指针ptr的值(num的地址):%p\n", ptr);
    printf("通过指针访问num的值:%d\n", *ptr);
    
    // 修改指针指向的值
    *ptr = 100;
    printf("修改后num的值:%d\n", num);
    
    // 指针运算
    int arr[] = {10, 20, 30, 40, 50};
    int *p = arr;  // p指向数组第一个元素
    
    printf("\n指针运算示例:\n");
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d (地址:%p)\n", i, *(p + i), p + i);
    }
    
    return 0;
}

6.2 指针与数组

#include <stdio.h>

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int *ptr = arr;  // 数组名退化为指针
    
    // 数组访问的两种方式
    printf("数组访问方式1:arr[2] = %d\n", arr[2]);
    printf("数组访问方式2:*(arr + 2) = %d\n", *(arr + 2));
    
    // 指针遍历数组
    printf("\n指针遍历数组:\n");
    for (int i = 0; i < 5; i++) {
        printf("%d ", *(ptr + i));
    }
    printf("\n");
    
    // 指针数组
    int a = 10, b = 20, c = 30;
    int *ptrArr[] = {&a, &b, &c};
    
    printf("\n指针数组:\n");
    for (int i = 0; i < 3; i++) {
        printf("ptrArr[%d]指向的值:%d\n", i, *ptrArr[i]);
    }
    
    return 0;
}

6.3 动态内存分配

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

int main() {
    // 动态分配整数数组
    int size;
    printf("请输入数组大小:");
    scanf("%d", &size);
    
    // 使用malloc分配内存
    int *arr = (int*)malloc(size * sizeof(int));
    if (arr == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }
    
    // 初始化数组
    for (int i = 0; i < size; i++) {
        arr[i] = i * 10;
    }
    
    // 使用数组
    printf("动态分配的数组:\n");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    // 重新分配内存
    int newSize = size + 5;
    int *newArr = (int*)realloc(arr, newSize * sizeof(int));
    if (newArr == NULL) {
        printf("内存重新分配失败!\n");
        free(arr);  // 释放原内存
        return 1;
    }
    arr = newArr;
    
    // 初始化新增部分
    for (int i = size; i < newSize; i++) {
        arr[i] = i * 10;
    }
    
    printf("\n重新分配后的数组:\n");
    for (int i = 0; i < newSize; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    
    // 释放内存
    free(arr);
    printf("内存已释放\n");
    
    return 0;
}

内存管理注意事项:

  • 内存泄漏:分配内存后忘记释放
  • 野指针:释放内存后继续使用指针
  • 双重释放:重复释放同一块内存
  • 越界访问:访问未分配或已释放的内存

第七章:结构体与共用体

7.1 结构体定义与使用

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

// 定义结构体
struct Student {
    char name[50];
    int age;
    float score;
    char grade;
};

// 使用typedef简化
typedef struct {
    int year;
    int month;
    int day;
} Date;

int main() {
    // 结构体变量声明与初始化
    struct Student student1 = {"张三", 20, 85.5, 'B'};
    
    // 访问结构体成员
    printf("姓名:%s\n", student1.name);
    printf("年龄:%d\n", student1.age);
    printf("成绩:%.1f\n", student1.score);
    printf("等级:%c\n", student1.grade);
    
    // 修改结构体成员
    student1.score = 92.5;
    student1.grade = 'A';
    
    // 结构体数组
    struct Student students[3] = {
        {"李四", 21, 88.0, 'B'},
        {"王五", 19, 95.5, 'A'},
        {"赵六", 22, 76.0, 'C'}
    };
    
    printf("\n学生信息表:\n");
    for (int i = 0; i < 3; i++) {
        printf("%s, %d岁, 成绩%.1f, 等级%c\n", 
               students[i].name, students[i].age, 
               students[i].score, students[i].grade);
    }
    
    // 结构体指针
    struct Student *ptr = &student1;
    printf("\n通过指针访问:\n");
    printf("姓名:%s\n", ptr->name);
    printf("成绩:%.1f\n", ptr->score);
    
    return 0;
}

7.2 结构体与函数

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

typedef struct {
    char name[50];
    int age;
    float score;
} Student;

// 函数参数为结构体(值传递)
void printStudent(Student s) {
    printf("姓名:%s,年龄:%d,成绩:%.1f\n", s.name, s.age, s.score);
}

// 函数参数为结构体指针(地址传递)
void updateScore(Student *s, float newScore) {
    s->score = newScore;
}

// 函数返回结构体
Student createStudent(char *name, int age, float score) {
    Student s;
    strcpy(s.name, name);
    s.age = age;
    s.score = score;
    return s;
}

int main() {
    Student student1 = {"张三", 20, 85.5};
    
    // 调用函数
    printStudent(student1);
    
    // 修改成绩
    updateScore(&student1, 92.5);
    printStudent(student1);
    
    // 创建新学生
    Student student2 = createStudent("李四", 21, 88.0);
    printStudent(student2);
    
    return 0;
}

7.3 共用体(联合体)

#include <stdio.h>

// 定义共用体
union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    union Data data;
    
    // 共用体同一时间只能存储一个成员的值
    data.i = 10;
    printf("data.i = %d\n", data.i);
    
    data.f = 220.5;
    printf("data.f = %.1f\n", data.f);
    printf("data.i = %d (被覆盖了)\n", data.i);  // 值已被覆盖
    
    strcpy(data.str, "Hello");
    printf("data.str = %s\n", data.str);
    printf("data.i = %d (被覆盖了)\n", data.i);
    
    // 共用体大小
    printf("\n共用体大小:%zu字节\n", sizeof(data));
    printf("int大小:%zu字节\n", sizeof(data.i));
    printf("float大小:%zu字节\n", sizeof(data.f));
    printf("char[20]大小:%zu字节\n", sizeof(data.str));
    
    return 0;
}

第八章:文件操作

8.1 文件读写基础

#include <stdio.h>

int main() {
    // 写入文件
    FILE *file = fopen("data.txt", "w");
    if (file == NULL) {
        printf("无法打开文件!\n");
        return 1;
    }
    
    fprintf(file, "姓名:%s\n", "张三");
    fprintf(file, "年龄:%d\n", 20);
    fprintf(file, "成绩:%.1f\n", 85.5);
    
    fclose(file);
    printf("数据已写入data.txt\n");
    
    // 读取文件
    file = fopen("data.txt", "r");
    if (file == NULL) {
        printf("无法打开文件!\n");
        return 1;
    }
    
    char line[100];
    printf("\n读取文件内容:\n");
    while (fgets(line, sizeof(line), file) != NULL) {
        printf("%s", line);
    }
    
    fclose(file);
    
    return 0;
}

8.2 二进制文件操作

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

typedef struct {
    char name[50];
    int age;
    float score;
} Student;

int main() {
    // 写入二进制文件
    FILE *file = fopen("students.dat", "wb");
    if (file == NULL) {
        printf("无法创建文件!\n");
        return 1;
    }
    
    Student students[3] = {
        {"张三", 20, 85.5},
        {"李四", 21, 88.0},
        {"王五", 19, 95.5}
    };
    
    // 写入整个数组
    fwrite(students, sizeof(Student), 3, file);
    fclose(file);
    printf("数据已写入students.dat\n");
    
    // 从二进制文件读取
    file = fopen("students.dat", "rb");
    if (file == NULL) {
        printf("无法打开文件!\n");
        return 1;
    }
    
    Student readStudents[3];
    fread(readStudents, sizeof(Student), 3, file);
    fclose(file);
    
    printf("\n从文件读取的学生信息:\n");
    for (int i = 0; i < 3; i++) {
        printf("%s, %d岁, 成绩%.1f\n", 
               readStudents[i].name, readStudents[i].age, 
               readStudents[i].score);
    }
    
    return 0;
}

8.3 文件定位与随机访问

#include <stdio.h>

int main() {
    // 创建测试文件
    FILE *file = fopen("test.txt", "w");
    for (int i = 1; i <= 10; i++) {
        fprintf(file, "Line %d\n", i);
    }
    fclose(file);
    
    // 随机访问读取
    file = fopen("test.txt", "r");
    if (file == NULL) {
        printf("无法打开文件!\n");
        return 1;
    }
    
    // 移动文件指针到第5行
    fseek(file, 0, SEEK_SET);  // 从文件开头开始
    for (int i = 0; i < 4; i++) {
        fgets(NULL, 0, file);  // 跳过前4行
    }
    
    char line[100];
    fgets(line, sizeof(line), file);
    printf("第5行内容:%s", line);
    
    // 使用ftell获取当前位置
    long pos = ftell(file);
    printf("当前文件位置:%ld\n", pos);
    
    // 移动到文件末尾
    fseek(file, 0, SEEK_END);
    printf("文件大小:%ld字节\n", ftell(file));
    
    fclose(file);
    
    return 0;
}

第九章:预处理器与宏

9.1 宏定义

#include <stdio.h>

// 简单宏
#define PI 3.14159
#define MAX(a, b) ((a) > (b) ? (a) : (b))

// 带参数的宏
#define SQUARE(x) ((x) * (x))
#define PRINT_INT(x) printf(#x " = %d\n", x)

// 多行宏
#define PRINT_INFO(name, age, score) \
    printf("姓名:%s\n", name); \
    printf("年龄:%d\n", age); \
    printf("成绩:%.1f\n", score)

int main() {
    // 使用宏
    printf("圆周率:%f\n", PI);
    
    int x = 10, y = 20;
    printf("最大值:%d\n", MAX(x, y));
    
    printf("平方:%d\n", SQUARE(5));
    
    int num = 42;
    PRINT_INT(num);  // 输出:num = 42
    
    PRINT_INFO("张三", 20, 85.5);
    
    return 0;
}

9.2 条件编译

#include <stdio.h>

// 定义调试模式
#define DEBUG 1

int main() {
    int value = 100;
    
    #if DEBUG
        printf("[调试] value = %d\n", value);
    #else
        printf("value = %d\n", value);
    #endif
    
    // 检查宏是否定义
    #ifdef PLATFORM_WINDOWS
        printf("运行在Windows平台\n");
    #elif defined(PLATFORM_LINUX)
        printf("运行在Linux平台\n");
    #elif defined(PLATFORM_MAC)
        printf("运行在macOS平台\n");
    #else
        printf("未知平台\n");
    #endif
    
    // 使用__FILE__和__LINE__宏
    printf("当前文件:%s,行号:%d\n", __FILE__, __LINE__);
    
    return 0;
}

第十章:常见问题解析

10.1 内存相关问题

问题1:野指针

// 错误示例
int *ptr;
*ptr = 10;  // 未初始化的指针,指向随机内存地址

// 正确做法
int *ptr = (int*)malloc(sizeof(int));
*ptr = 10;
free(ptr);
ptr = NULL;  // 释放后置为NULL

问题2:内存泄漏

// 错误示例
void leakyFunction() {
    int *arr = (int*)malloc(100 * sizeof(int));
    // 忘记释放内存
}

// 正确做法
void safeFunction() {
    int *arr = (int*)malloc(100 * sizeof(int));
    if (arr != NULL) {
        // 使用数组...
        free(arr);
        arr = NULL;
    }
}

问题3:缓冲区溢出

// 错误示例
char buffer[10];
scanf("%s", buffer);  // 输入超过10个字符会导致溢出

// 正确做法
char buffer[10];
scanf("%9s", buffer);  // 限制输入长度

// 或者使用fgets
fgets(buffer, sizeof(buffer), stdin);

10.2 指针相关问题

问题4:指针与数组混淆

// 错误示例
char *str = "Hello";  // 字符串常量,只读
str[0] = 'h';         // 错误!尝试修改只读内存

// 正确做法
char str[] = "Hello";  // 字符数组,可修改
str[0] = 'h';          // 正确

问题5:指针算术错误

// 错误示例
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;
ptr += 10;  // 越界访问,未定义行为

// 正确做法
int *ptr = arr;
for (int i = 0; i < 5; i++) {
    printf("%d ", *(ptr + i));  // 安全访问
}

10.3 函数相关问题

问题6:函数返回局部变量地址

// 错误示例
int* getArray() {
    int arr[5] = {1, 2, 3, 4, 5};
    return arr;  // 返回局部数组地址,函数结束后内存被释放
}

// 正确做法
int* getArray() {
    static int arr[5] = {1, 2, 3, 4, 5};  // 静态变量
    return arr;
}
// 或者动态分配
int* getArray() {
    int *arr = (int*)malloc(5 * sizeof(int));
    // 初始化...
    return arr;  // 调用者需要释放
}

问题7:函数参数传递错误

// 错误示例:试图修改字符串常量
void modifyString(char *str) {
    str[0] = 'h';  // 如果str指向字符串常量,会出错
}

// 正确做法:确保传入的是可修改的字符数组
void modifyString(char str[]) {
    str[0] = 'h';  // 安全
}

10.4 输入输出问题

问题8:scanf的常见错误

// 错误1:忘记取地址符&
int num;
scanf("%d", num);  // 错误!应该用&num

// 错误2:缓冲区溢出
char name[10];
scanf("%s", name);  // 输入超过10个字符会溢出

// 错误3:处理输入失败
int result;
printf("请输入一个整数:");
if (scanf("%d", &result) != 1) {
    printf("输入无效!\n");
    // 清理输入缓冲区
    while (getchar() != '\n');
}

问题9:文件操作错误

// 错误示例
FILE *file = fopen("data.txt", "r");
fprintf(file, "写入数据");  // 错误!以只读模式打开的文件不能写入

// 正确做法
FILE *file = fopen("data.txt", "r+");
if (file != NULL) {
    fprintf(file, "写入数据");
    fclose(file);
}

第十一章:项目实践:学生成绩管理系统

11.1 系统设计

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

#define MAX_STUDENTS 100
#define MAX_NAME_LENGTH 50

// 学生结构体
typedef struct {
    int id;
    char name[MAX_NAME_LENGTH];
    float score;
} Student;

// 全局变量
Student students[MAX_STUDENTS];
int studentCount = 0;

// 函数声明
void addStudent();
void displayStudents();
void searchStudent();
void saveToFile();
void loadFromFile();
void sortStudents();
void deleteStudent();
void showMenu();

int main() {
    loadFromFile();  // 启动时加载数据
    
    int choice;
    do {
        showMenu();
        printf("请输入选择(1-8):");
        scanf("%d", &choice);
        
        switch (choice) {
            case 1: addStudent(); break;
            case 2: displayStudents(); break;
            case 3: searchStudent(); break;
            case 4: sortStudents(); break;
            case 5: deleteStudent(); break;
            case 6: saveToFile(); break;
            case 7: loadFromFile(); break;
            case 8: printf("再见!\n"); break;
            default: printf("无效选择!\n");
        }
    } while (choice != 8);
    
    return 0;
}

void showMenu() {
    printf("\n=== 学生成绩管理系统 ===\n");
    printf("1. 添加学生\n");
    printf("2. 显示所有学生\n");
    printf("3. 搜索学生\n");
    printf("4. 按成绩排序\n");
    printf("5. 删除学生\n");
    printf("6. 保存到文件\n");
    printf("7. 从文件加载\n");
    printf("8. 退出系统\n");
    printf("========================\n");
}

void addStudent() {
    if (studentCount >= MAX_STUDENTS) {
        printf("学生数量已达上限!\n");
        return;
    }
    
    Student s;
    printf("请输入学号:");
    scanf("%d", &s.id);
    printf("请输入姓名:");
    scanf("%s", s.name);
    printf("请输入成绩:");
    scanf("%f", &s.score);
    
    students[studentCount++] = s;
    printf("学生添加成功!\n");
}

void displayStudents() {
    if (studentCount == 0) {
        printf("没有学生记录!\n");
        return;
    }
    
    printf("\n学号\t姓名\t成绩\n");
    printf("----------------------------\n");
    for (int i = 0; i < studentCount; i++) {
        printf("%d\t%s\t%.1f\n", students[i].id, students[i].name, students[i].score);
    }
}

void searchStudent() {
    int id;
    printf("请输入要搜索的学号:");
    scanf("%d", &id);
    
    for (int i = 0; i < studentCount; i++) {
        if (students[i].id == id) {
            printf("\n找到学生:\n");
            printf("学号:%d\n", students[i].id);
            printf("姓名:%s\n", students[i].name);
            printf("成绩:%.1f\n", students[i].score);
            return;
        }
    }
    
    printf("未找到学号为%d的学生!\n", id);
}

void sortStudents() {
    if (studentCount == 0) {
        printf("没有学生记录!\n");
        return;
    }
    
    // 使用冒泡排序按成绩降序
    for (int i = 0; i < studentCount - 1; i++) {
        for (int j = 0; j < studentCount - 1 - i; j++) {
            if (students[j].score < students[j + 1].score) {
                // 交换
                Student temp = students[j];
                students[j] = students[j + 1];
                students[j + 1] = temp;
            }
        }
    }
    
    printf("按成绩排序完成!\n");
    displayStudents();
}

void deleteStudent() {
    int id;
    printf("请输入要删除的学号:");
    scanf("%d", &id);
    
    int found = 0;
    for (int i = 0; i < studentCount; i++) {
        if (students[i].id == id) {
            // 移动后面的元素
            for (int j = i; j < studentCount - 1; j++) {
                students[j] = students[j + 1];
            }
            studentCount--;
            printf("学生删除成功!\n");
            found = 1;
            break;
        }
    }
    
    if (!found) {
        printf("未找到学号为%d的学生!\n", id);
    }
}

void saveToFile() {
    FILE *file = fopen("students.dat", "wb");
    if (file == NULL) {
        printf("无法保存到文件!\n");
        return;
    }
    
    fwrite(students, sizeof(Student), studentCount, file);
    fclose(file);
    printf("数据已保存到students.dat\n");
}

void loadFromFile() {
    FILE *file = fopen("students.dat", "rb");
    if (file == NULL) {
        printf("没有找到保存的数据文件!\n");
        return;
    }
    
    studentCount = fread(students, sizeof(Student), MAX_STUDENTS, file);
    fclose(file);
    printf("已从文件加载%d条学生记录!\n", studentCount);
}

11.2 编译与运行

# 编译
gcc student_system.c -o student_system

# 运行
./student_system

11.3 功能扩展建议

  1. 添加更多功能

    • 按学号排序
    • 统计平均分、最高分、最低分
    • 导出为CSV格式
    • 添加密码保护
  2. 改进数据结构

    • 使用链表代替数组(动态增长)
    • 添加文件加密
    • 实现数据备份
  3. 用户界面优化

    • 添加图形界面(使用GTK或Qt)
    • 支持命令行参数
    • 添加帮助文档

第十二章:学习建议与进阶路径

12.1 学习建议

  1. 动手实践:每个概念都要编写代码验证
  2. 调试技巧:学会使用gdb调试器
  3. 阅读源码:阅读优秀开源项目代码
  4. 参与项目:在GitHub上参与C语言项目
  5. 定期复习:整理笔记,定期回顾

12.2 进阶学习路径

  1. 数据结构与算法

    • 链表、栈、队列
    • 树、图
    • 排序、搜索算法
  2. 系统编程

    • Linux系统调用
    • 进程与线程
    • 网络编程
  3. 嵌入式开发

    • 单片机编程
    • 实时操作系统
    • 硬件接口
  4. 高级主题

    • C++面向对象编程
    • 内存管理优化
    • 性能调优

12.3 推荐资源

  1. 书籍

    • 《C Primer Plus》
    • 《C程序设计语言》(K&R)
    • 《C陷阱与缺陷》
  2. 在线课程

    • Coursera上的C语言课程
    • MIT OpenCourseWare
    • 中国大学MOOC
  3. 开发工具

    • VS Code + C/C++扩展
    • CLion(JetBrains)
    • Code::Blocks
  4. 练习平台

    • LeetCode(C语言题解)
    • HackerRank
    • 牛客网

结语

C语言作为一门经典编程语言,虽然学习曲线较陡峭,但掌握后将为你打开计算机科学的大门。记住,编程是一门实践的艺术,只有通过不断的编码、调试和项目实践,才能真正掌握C语言的精髓。

最后的建议

  • 保持耐心,不要急于求成
  • 遇到问题时,先自己思考,再查阅资料
  • 多与他人交流,参与编程社区
  • 定期回顾和总结所学知识

祝你在C语言的学习道路上取得成功!