引言:为什么需要这本习题与实验指导手册?

C语言作为计算机科学的基石,其学习曲线往往伴随着理论与实践的鸿沟。许多学生在阅读教材(如谭浩强教授的经典教材或K&R的《The C Programming Language》)时,觉得概念理解了,但一到写代码就无从下手。这本《C语言程序设计习题与实验指导》正是为了解决这一痛点而生。它不仅仅是习题集,更是连接理论与实战的桥梁,帮助你从“看懂”到“会写”,再到“写好”。

核心价值

  • 系统性:覆盖C语言核心知识点,从基础语法到高级应用。
  • 实战性:提供大量实验案例,模拟真实开发场景。
  • 指导性:详细解析习题,避免“卡壳”时的挫败感。
  • 出版社背书:人民邮电出版社的教材以严谨著称,这本指导手册是其生态的重要补充。

作为你的C语言导师,我将基于这本手册的经典结构,为你生成一份详尽的学习指南。我们将按章节拆解,结合代码示例和实验指导,确保你能一步步掌握C语言。假设你使用的是配套谭浩强《C程序设计》的版本(这是最常见的),我会聚焦于通用核心内容。如果你有具体章节需求,请补充。

第一章:C语言基础与程序结构

主题句:掌握C语言的基本结构是入门的第一步,它定义了程序的骨架。

C程序由预处理指令、函数、语句和注释组成。理解这些元素能让你快速编写第一个“Hello, World!”程序,并避免常见语法错误。

支持细节:

  • 基本结构:C程序总是从main()函数开始执行。main()是程序的入口点。
  • 预处理指令:以#开头,如#include <stdio.h>,用于包含标准输入输出库。
  • 语句:以分号;结束,如赋值、输出语句。
  • 注释:单行用//,多行用/* ... */,用于解释代码,便于调试。

完整代码示例:第一个C程序

让我们编写一个简单的程序,输出问候语并计算两个数的和。这个例子展示了基本结构。

// 文件名: hello.c
// 这是一个简单的C程序示例
#include <stdio.h>  // 预处理指令:包含标准输入输出库

int main() {  // main函数,程序入口,int表示返回整型值
    // 变量声明
    int a = 5, b = 3;  // 整型变量a和b,赋初值
    int sum;  // 声明sum变量用于存储和

    // 计算和
    sum = a + b;  // 赋值语句

    // 输出结果
    printf("Hello, World!\n");  // 输出字符串,\n表示换行
    printf("The sum of %d and %d is %d\n", a, b, sum);  // 格式化输出

    return 0;  // 返回0,表示程序正常结束
}

代码解析

  • 编译与运行:在命令行使用gcc hello.c -o hello编译,然后./hello运行(Windows用hello.exe)。输出应为:
    
    Hello, World!
    The sum of 5 and 3 is 8
    
  • 常见错误:忘记分号会导致编译错误,如error: expected ';' before 'return'。调试技巧:用gcc -Wall启用警告。
  • 实验指导:修改程序,让用户输入a和b(用scanf)。实验目标:理解输入输出。预期时间:15分钟。

第二章:数据类型、运算符与输入输出

主题句:数据类型是C语言的核心,它决定了变量如何存储和操作数据。

C语言提供基本数据类型(如int、float、char)和运算符(如算术、关系运算符)。掌握这些能让你处理数值和字符输入。

支持细节:

  • 基本数据类型
    • int:整型,通常4字节,范围-2^31到2^31-1。
    • float/double:浮点型,double精度更高。
    • char:字符型,1字节,用于ASCII字符。
  • 运算符:算术(+、-、*、/、%)、关系(==、>、<)、逻辑(&&、||、!)。
  • 输入输出printf用于输出,scanf用于输入。格式符如%d(整型)、%f(浮点)、%c(字符)。

完整代码示例:温度转换器

这个程序读取摄氏温度,转换为华氏温度,展示数据类型和输入输出。

// 文件名: temp_converter.c
#include <stdio.h>

int main() {
    float celsius, fahrenheit;  // 浮点变量,用于存储温度

    // 输入提示
    printf("Enter temperature in Celsius: ");

    // 读取输入,%f表示浮点数,&celsius是地址
    scanf("%f", &celsius);

    // 计算转换:F = (C * 9/5) + 32
    fahrenheit = (celsius * 9.0 / 5.0) + 32;

    // 输出结果
    printf("%.2f Celsius is %.2f Fahrenheit\n", celsius, fahrenheit);  // %.2f保留两位小数

    return 0;
}

代码解析

  • 输入处理scanf需要变量地址(&),否则段错误。输入示例:37,输出:37.00 Celsius is 98.60 Fahrenheit
  • 运算符优先级:乘除先于加减,用括号确保顺序。实验中,尝试输入负数,观察浮点精度。
  • 实验指导:扩展程序,计算BMI(体重kg / 身高m^2)。用int存储整数输入,float计算。调试提示:如果输入非数字,scanf返回0,需检查返回值。预期时间:20分钟,目标:处理多类型数据。

第三章:控制结构(顺序、选择、循环)

主题句:控制结构赋予程序“智能”,让它能根据条件决策和重复执行。

C语言通过if-else、switch实现选择,for、while、do-while实现循环。这些是算法的基础。

支持细节:

  • 顺序结构:代码从上到下执行。
  • 选择结构if (条件) { ... } else { ... }switch (表达式) { case 值: ... }
  • 循环结构for (初始化; 条件; 更新)while (条件)do { ... } while (条件)
  • break/continue:跳出循环或跳过迭代。

完整代码示例:素数判断器

这个程序判断输入数是否为素数,使用for循环和if条件。

// 文件名: prime_checker.c
#include <stdio.h>
#include <math.h>  // 用于sqrt函数

int main() {
    int num, i;
    int is_prime = 1;  // 标志变量,1表示是素数

    printf("Enter a positive integer: ");
    scanf("%d", &num);

    // 特殊情况处理
    if (num <= 1) {
        is_prime = 0;
    } else {
        // for循环:从2到sqrt(num)检查因子
        for (i = 2; i <= sqrt(num); i++) {
            if (num % i == 0) {  // 取模运算检查整除
                is_prime = 0;
                break;  // 找到因子,跳出循环
            }
        }
    }

    // 输出结果
    if (is_prime) {
        printf("%d is a prime number.\n", num);
    } else {
        printf("%d is not a prime number.\n", num);
    }

    return 0;
}

代码解析

  • 循环优化:只需检查到sqrt(num),因为因子成对出现。输入17输出素数,15输出非素数。
  • 标志变量is_prime跟踪状态,避免嵌套if。
  • 实验指导:修改为输出1到n的所有素数,用嵌套循环。添加switch处理菜单:1.判断素数 2.输出范围素数。调试:注意sqrt需要math.h,编译时加-lm。预期时间:30分钟,目标:掌握循环与条件组合。

第四章:数组与字符串

主题句:数组和字符串是C语言处理批量数据的利器,让你能存储和操作序列。

数组是固定大小的同类型元素集合,字符串是字符数组(以’\0’结尾)。

支持细节:

  • 一维数组int arr[5] = {1,2,3,4,5};,下标从0开始。
  • 二维数组int matrix[3][3];,用于矩阵。
  • 字符串char str[20] = "Hello";,长度包括’\0’。
  • 函数strlenstrcpystrcat(需string.h)。

完整代码示例:学生成绩统计

这个程序读取5个学生的成绩,计算平均分和最高分,使用数组。

// 文件名: grades.c
#include <stdio.h>
#define SIZE 5  // 宏定义数组大小

int main() {
    int grades[SIZE];  // 一维数组存储成绩
    int i, sum = 0, max;
    float average;

    // 输入成绩
    printf("Enter %d grades:\n", SIZE);
    for (i = 0; i < SIZE; i++) {
        scanf("%d", &grades[i]);
    }

    // 计算总和和最高分
    max = grades[0];  // 初始化max
    for (i = 0; i < SIZE; i++) {
        sum += grades[i];  // 累加
        if (grades[i] > max) {
            max = grades[i];  // 更新最大值
        }
    }

    average = (float)sum / SIZE;  // 类型转换计算平均

    // 输出统计
    printf("Grades: ");
    for (i = 0; i < SIZE; i++) {
        printf("%d ", grades[i]);
    }
    printf("\nAverage: %.2f\n", average);
    printf("Highest: %d\n", max);

    return 0;
}

代码解析

  • 数组初始化:未初始化元素为0,但最好显式赋值。输入示例:85 90 78 92 88,输出平均86.60,最高92。
  • 边界检查:循环确保不越界,避免运行时错误。
  • 实验指导:扩展为二维数组存储多科目成绩,计算每科平均。添加字符串处理:输入学生姓名,用char names[SIZE][20]存储并输出。调试:用gets(不推荐,用fgets代替)读取字符串。预期时间:40分钟,目标:处理序列数据。

第五章:函数与递归

主题句:函数是C语言模块化编程的核心,让代码可复用和易维护。

函数封装特定功能,支持参数传递和返回值。递归是函数自调用,用于解决分治问题。

支持细节:

  • 函数定义返回类型 函数名(参数列表) { ... }
  • 调用:值传递(复制参数)或引用传递(指针)。
  • 递归:需有基准情况(终止条件),否则栈溢出。
  • 库函数:如powrand(math.h、stdlib.h)。

完整代码示例:阶乘计算器(递归版)

这个程序计算n!,展示递归函数。

// 文件名: factorial.c
#include <stdio.h>

// 递归函数定义
long long factorial(int n) {
    // 基准情况:0! = 1! = 1
    if (n <= 1) {
        return 1;
    }
    // 递归情况:n! = n * (n-1)!
    return n * factorial(n - 1);
}

int main() {
    int n;

    printf("Enter a positive integer (n <= 20): ");
    scanf("%d", &n);

    if (n < 0) {
        printf("Factorial is not defined for negative numbers.\n");
    } else {
        long long result = factorial(n);
        printf("%d! = %lld\n", n, result);
    }

    return 0;
}

代码解析

  • 递归过程:输入5,计算5*factorial(4),直到factorial(1)=1,返回120。注意long long防止溢出(n>20会溢出)。
  • 参数传递:n是值传递,不影响原值。
  • 实验指导:编写非递归版本用for循环。添加函数计算斐波那契数列(fib(n) = fib(n-1) + fib(n-2))。调试:递归深度大时用ulimit -s unlimited增加栈大小。预期时间:25分钟,目标:理解函数复用和递归思维。

第六章:指针

主题句:指针是C语言的灵魂,提供对内存的直接访问,实现高效数据操作。

指针存储地址,支持动态内存和数组高效处理。

支持细节:

  • 声明int *p;,p指向int。
  • 操作&取地址,*解引用。
  • 指针与数组:数组名即首地址,arr[i]等价于*(arr + i)
  • 函数指针:传递地址,实现引用传递。

完整代码示例:交换两个数的值

这个程序用指针函数交换变量,展示指针用途。

// 文件名: swap.c
#include <stdio.h>

// 指针函数:交换两个整数
void swap(int *a, int *b) {
    int temp = *a;  // 解引用获取值
    *a = *b;
    *b = temp;
}

int main() {
    int x = 10, y = 20;

    printf("Before swap: x = %d, y = %d\n", x, y);

    // 传递地址
    swap(&x, &y);

    printf("After swap: x = %d, y = %d\n", x, y);

    return 0;
}

代码解析

  • 地址传递&x传地址,函数内修改影响原变量。输出:Before:10,20 After:20,10。
  • 常见错误:未初始化指针(野指针)导致崩溃。始终检查NULL
  • 实验指导:扩展为排序数组,用指针遍历。添加动态内存:用malloc分配数组。调试:用valgrind检查内存泄漏。预期时间:35分钟,目标:掌握指针操作内存。

第七章:结构体与文件操作

主题句:结构体让你定义自定义数据类型,文件操作则实现数据持久化。

结构体组合不同类型,文件I/O处理文本/二进制读写。

支持细节:

  • 结构体struct Student { char name[20]; int age; };
  • 文件FILE *fp = fopen("file.txt", "r");fscanf/fprintf读写。
  • 模式:”r”读、”w”写、”a”追加。

完整代码示例:学生管理系统(文件版)

这个程序定义结构体,读写文件存储学生信息。

// 文件名: student_manager.c
#include <stdio.h>
#include <string.h>

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

int main() {
    struct Student s;
    FILE *fp;

    // 写入文件
    fp = fopen("students.txt", "w");
    if (fp == NULL) {
        printf("Error opening file for writing.\n");
        return 1;
    }

    printf("Enter student name, age, score:\n");
    scanf("%s %d %f", s.name, &s.age, &s.score);

    fprintf(fp, "%s %d %.2f\n", s.name, s.age, s.score);
    fclose(fp);

    // 读取文件
    fp = fopen("students.txt", "r");
    if (fp == NULL) {
        printf("Error opening file for reading.\n");
        return 1;
    }

    printf("\nStored data:\n");
    while (fscanf(fp, "%s %d %f", s.name, &s.age, &s.score) != EOF) {
        printf("Name: %s, Age: %d, Score: %.2f\n", s.name, s.age, s.score);
    }
    fclose(fp);

    return 0;
}

代码解析

  • 结构体使用:打包相关数据,便于管理。输入示例:Alice 20 95.5,文件写入并读取。
  • 文件错误处理:检查fopen返回值,避免崩溃。
  • 实验指导:扩展为多学生记录,用循环写入。添加二进制模式"wb"fwrite。调试:文件路径用绝对路径。预期时间:45分钟,目标:持久化数据管理。

实验总体指导与学习建议

实验流程

  1. 准备:安装IDE(如Dev-C++、Code::Blocks)或用VS Code + GCC。
  2. 编写:先伪代码,再实现。
  3. 测试:边界输入、异常情况。
  4. 调试:用printf打印中间值,或GDB单步执行(gdb ./programbreak mainrunnext)。

常见问题与解决方案

  • 编译错误:检查头文件、分号、括号匹配。
  • 运行时错误:数组越界用-fsanitize=address编译检测。
  • 性能:大数组用指针,避免拷贝。

进阶建议

  • 完成手册所有习题,目标:独立实现小型项目如计算器、通讯录。
  • 参考在线资源:GeeksforGeeks C教程、LeetCode简单题。
  • 每天练习1小时,记录错误日志。

通过这本手册的系统训练,你将从C语言新手成长为能独立开发的程序员。坚持实践,C语言将是你编程生涯的强大工具!如果需要特定章节扩展或更多代码,请随时告知。