引言:为什么选择C语言作为编程入门的首选?
C语言作为一门诞生于20世纪70年代的编程语言,至今仍然是计算机科学教育和系统开发的基石。对于零基础学习者来说,选择C语言作为入门语言具有深远的意义。谭浩强教授的《C语言程序设计》教程之所以成为经典,正是因为它完美地平衡了理论深度与实践应用,让初学者能够循序渐进地掌握编程思维。
C语言的核心价值
系统级编程能力:C语言提供了对内存和硬件的直接访问能力,这是高级语言如Python或Java所不具备的。通过学习C语言,你将理解程序如何在计算机底层运行,这种理解对于成为优秀程序员至关重要。
高效性与可移植性:C语言编写的程序执行效率极高,同时可以在不同平台间轻松移植。从嵌入式设备到操作系统内核,C语言无处不在。
现代编程语言的基石:C++、C#、Java、Objective-C等语言都直接继承了C语言的语法和思想。掌握C语言后,学习其他语言将事半功倍。
第一章:C语言基础环境搭建与第一个程序
开发环境的选择与配置
对于初学者,推荐使用以下环境组合:
Windows平台:
- 编译器:MinGW(GCC for Windows)或Visual Studio Community
- 编辑器:Visual Studio Code 或 Code::Blocks
Linux平台:
- 编译器:GCC(通常已预装)
- 编辑器:Vim、Emacs 或 VS Code
macOS平台:
- 编译器:Xcode Command Line Tools(包含GCC)
- 编辑器:VS Code 或 Xcode
配置开发环境的详细步骤(以Windows + VS Code为例)
下载并安装MinGW:
- 访问MinGW官网下载安装管理器
- 选择安装gcc-core、g++、make等组件
- 将MinGW的bin目录添加到系统PATH环境变量
安装VS Code:
- 从官网下载并安装
- 安装C/C++扩展(Microsoft官方提供)
验证安装: 打开命令提示符,输入:
gcc --version如果显示版本信息,说明安装成功。
编写你的第一个C程序:Hello World
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
代码逐行解析:
#include <stdio.h>:预处理指令,包含标准输入输出头文件。stdio.h定义了printf、scanf等函数。int main():主函数,所有C程序的入口点。int表示函数返回整型值。{ ... }:函数体,包含要执行的代码。printf("Hello, World!\n");:输出函数,\n是换行符。return 0;:返回值,0表示程序正常结束。
编译与运行
命令行方式:
# 编译
gcc hello.c -o hello.exe
# 运行
./hello.exe
IDE方式:
在VS Code中,按F5选择”C++ (GDB/LLDB)“,然后选择”clang++ - 生成和调试活动文件”即可自动编译调试。
第二章:数据类型、变量与常量
基本数据类型详解
C语言提供了多种基本数据类型,每种类型都有其特定的用途和内存占用:
| 数据类型 | 内存占用 | 取值范围 | 用途说明 |
|---|---|---|---|
| char | 1字节 | -128~127 或 0~255 | 存储字符或小整数 |
| short | 2字节 | -32768~32767 | 较小整数 |
| int | 4字节 | -2^31~2^31-1 | 标准整数 |
| long | 4或8字节 | 系统相关 | 较大整数 |
| float | 4字节 | ±3.4e±38 | 单精度浮点数 |
| double | 8字节 | ±1.7e±308 | 双精度浮点数 |
变量的声明与初始化
#include <stdio.h>
int main() {
// 变量声明
int age;
float salary;
char grade;
// 变量初始化
age = 25;
salary = 5000.50;
grade = 'A';
// 声明同时初始化
int score = 95;
double pi = 3.14159;
printf("年龄:%d\n", age);
printf("工资:%.2f\n", salary);
printf("等级:%c\n", grade);
printf("分数:%d\n", score);
printf("圆周率:%f\n", pi);
return 0;
}
关键概念:
- 变量命名规则:只能包含字母、数字、下划线,不能以数字开头,区分大小写。
- 类型修饰符:
signed、unsigned、short、long可以修饰整型。 - 类型转换:C语言支持隐式和显式类型转换。
常量的定义方式
#include <stdio.h>
#define PI 3.14159 // 宏定义常量
int main() {
const double GRAVITY = 9.8; // const常量
printf("圆周率:%f\n", PI);
printf("重力加速度:%f\n", GRAVITY);
// GRAVITY = 10.0; // 错误!const常量不能修改
return 0;
}
第三章:运算符与表达式
算术运算符
#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 = %.2f\n", (float)a / b); // 3.33
return 0;
}
关系运算符与逻辑运算符
#include <stdio.h>
int main() {
int x = 5, y = 8;
// 关系运算符
printf("x > y: %d\n", x > y); // 0(假)
printf("x == y: %d\n", x == y); // 1(真)
// 逻辑运算符
printf("(x > 0) && (y > 0): %d\n", (x > 0) && (y > 0)); // 1
printf("(x > 10) || (y > 5): %d\n", (x > 10) || (y > 5)); // 1
printf("!(x > y): %d\n", !(x > y)); // 1
return 0;
}
自增自减运算符
#include <stdio.h>
int main() {
int a = 5;
int b = a++; // 先使用a,再a+1
printf("b=%d, a=%d\n", b, a); // b=5, a=6
int c = 5;
int d = ++c; // 先c+1,再使用c
printf("d=%d, c=%d\n", d, c); // d=6, c=6
return 0;
}
第四章:输入输出函数详解
printf函数详解
#include <stdio.h>
int main() {
int num = 123;
float f = 3.14159;
char ch = 'A';
char str[] = "Hello";
// 基本格式化
printf("整数:%d\n", num);
printf("八进制:%o\n", num);
printf十六进制:%x\n", num);
printf("浮点数:%f\n", f);
printf("字符:%c\n", ch);
printf("字符串:%s\n", str);
// 宽度与精度控制
printf("宽度控制:%5d\n", num); // 右对齐,宽度5
printf("左对齐:%-5d\n", num); // 左对齐
printf("精度控制:%.2f\n", f); // 保留2位小数
printf("总宽度:%8.2f\n", f); // 总宽度8,保留2位小数
return 0;
}
scanf函数详解
#include <stdio.h>
int main() {
int age;
float height;
char name[50];
// 基本输入
printf("请输入年龄:");
scanf("%d", &age); // 注意:&取地址运算符
printf("请输入身高(米):");
scanf("%f", &height);
printf("请输入姓名:");
scanf("%s", name); // 数组名本身就是地址
// 清除输入缓冲区(重要!)
while (getchar() != '\n');
// 重新输入字符串(包含空格)
printf("请输入完整姓名:");
fgets(name, 50, stdin); // 可以读取空格
printf("\n--- 输入信息 ---\n");
printf("姓名:%s", name);
printf("年龄:%d\n", age);
printf("身高:%.2f米\n", height);
return 0;
}
scanf使用注意事项:
- 变量必须使用地址(&运算符)
- 读取字符串时,遇到空格会停止
- 输入多个数据时,用空格或回车分隔
- 清除缓冲区避免数据残留
第五章:流程控制结构
if-else条件语句
#include <stdio.h>
int main() {
int score;
printf("请输入分数:");
scanf("%d", &score);
if (score >= 90) {
printf("优秀\n");
} else if (score >= 80) {
printf("良好\n");
} else if (score >= 60) {
printf("及格\n");
} else {
printf("不及格\n");
}
// 三元运算符
printf("等级:%s\n", score >= 60 ? "及格" : "不及格");
return 0;
}
switch-case语句
#include <stdio.h>
int main() {
int choice;
printf("请选择操作:\n");
printf("1. 新建\n");
printf("2. 打开\n");
printf("3. 保存\n");
printf("4. 退出\n");
printf("输入选项:");
scanf("%d", &choice);
switch (choice) {
case 1:
printf("新建文件...\n");
break;
case 2:
printf("打开文件...\n");
break;
case 3:
printf("保存文件...\n");
break;
case 4:
printf("退出程序\n");
break;
default:
printf("无效选项\n");
}
return 0;
}
循环结构
#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("\n--- while循环 ---\n");
int sum = 0, j = 1;
while (j <= 100) {
sum += j;
j++;
}
printf("1到100的和:%d\n", 数学表达式:sum = %d\n", sum);
// do-while循环:至少执行一次
printf("\n--- do-while循环 ---\n");
int input;
do {
printf("输入正数(输入0结束):");
scanf("%d", &input);
printf("你输入了:%d\n", input);
} while (input != 0);
// 嵌套循环:打印乘法表
printf("\n--- 九九乘法表 ---\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");
}
return 0;
}
break与continue
#include <stdio.h>
int main() {
// break:立即退出循环
printf("--- break示例 ---\n");
for (int i = 1; i <= 10; i++) {
if (i == 5) {
break; // 当i=5时,退出整个循环
}
printf("%d ", i);
}
printf("\n");
// continue:跳过本次循环剩余代码
printf("\n--- continue示例 ---\n");
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
continue; // 跳过偶数
}
printf("%d ", i);
}
printf("\n");
// 带标签的break(C语言不支持,但可以用goto)
printf("\n--- goto示例(慎用) ---\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (i == 1 && j == 1) {
goto end; // 跳转到标签
}
printf("(%d,%d) ", i, j);
}
}
end:
printf("\n跳转到end标签\n");
return 0;
}
第六章:数组与字符串
一维数组
#include <stdio.h>
int main() {
// 数组声明与初始化
int scores[5] = {85, 92, 78, 96, 88};
// 访问数组元素
printf("第三个元素:%d\n", scores[2]); // 78
// 遍历数组
printf("所有成绩:");
for (int i = 0; C语言学习路径:从零基础到精通的完整指南
// 计算平均分
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += scores[i];
}
printf("平均分:%.1f\n", sum / 5.0);
// 动态数组(变长数组,C99标准)
int n;
printf("请输入数组长度:");
scanf("%d", &n);
int dynamicArray[n];
for (int i = 0; i < n; i++) {
dynamicArray[i] = i * 10;
}
printf("动态数组内容:");
for (int i = 0; i < n; i++) {
printf("%d ", dynamicArray[i]);
}
printf("\n");
return 0;
}
二维数组与矩阵操作
#include <stdio.h>
int main() {
// 二维数组声明
int matrix[3][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
// 遍历二维数组
printf("--- 矩阵内容 ---\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
// 矩阵转置
printf("\n--- 矩阵转置 ---\n");
int temp;
for (int i = 0; i < 3; i++) {
for (int j = i+1; i < 3; j++) {
temp = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = temp;
}
}
// 打印转置后的矩阵
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
return 0;
}
字符串与字符数组
#include <stdio.h>
#include <string.h>
int main() {
// 字符串的两种定义方式
char str1[] = "Hello"; // 自动添加'\0'
char str2[10] = {'H', 'e', 'l', 'l', 'o', '\0'};
// 字符串输入输出
char name[50];
printf("请输入姓名:");
scanf("%s", name); // 遇到空格停止
printf("你好,%s\n", name);
// 清除缓冲区后重新输入
while (getchar() != '\n');
printf("请输入完整姓名(可含空格):");
fgets(name, 50, stdin); // 安全的输入函数
// 字符串处理函数
printf("\n--- 字符串处理 ---\n");
printf("长度:%zu\n", strlen(name));
char greeting[50] = "Hello, ";
strcat(greeting, name); // 连接字符串
printf("连接后:%s", greeting);
char copy[50];
strcpy(copy, greeting); // 复制字符串
printf("复制后:%s", copy);
// 比较字符串
char str3[] = "Apple";
char str4[] = "Banana";
int result = strcmp(str3, str4);
if (result < 0) {
printf("%s 小于 %s\n", str3, str4);
}
return 0;
}
字符串与数字转换
#include <stdio.h>
#include <stdlib.h>
int main() {
// 字符串转数字
char numStr[] = "12345";
int num = atoi(numStr); // 字符串转整数
printf("字符串'%s'转为整数:%d\n", numStr, num);
char floatStr[] = "3.14159";
float f = atof(floatStr); // 字符串转浮点数
printf("字符串'%s'转为浮点数:%.2f\n", floatStr, f);
// 数字转字符串
int score = 95;
char buffer[20];
sprintf(buffer, "分数:%d", score); // 格式化到字符串
printf("%s\n", buffer);
return 0;
}
第七章:函数
函数的定义与调用
#include <stdio.h>
// 函数声明(原型)
int add(int a, int b);
void printMessage(char* msg);
float calculateAverage(int arr[], int size);
int main() {
// 函数调用
int sum = add(10, 20);
printf("10 + 20 = %d\n", sum);
printMessage("Hello from main!");
int scores[] = {85, 92, 78, 96, 88};
float avg = calculateAverage(scores, 5);
printf("平均分:%.2f\n", avg);
return 0;
}
// 函数定义
int add(int a, int b) {
return a + b;
}
void printMessage(char* msg) {
printf("%s\n", msg);
}
float calculateAverage(int arr[], int size) {
int sum = 0;
for (int i = 0; i < size; i++) {
sum += arr[i];
}
return (float)sum / size;
}
参数传递机制
#include <stdio.h>
// 值传递:函数内修改不影响原变量
void increment(int x) {
x++;
printf("函数内x=%d\n", x);
}
// 地址传递:函数内修改会影响原变量
void incrementByPointer(int* x) {
(*x)++;
printf("函数内*x=%d\n", *x);
}
// 数组传递:本质是地址传递
void doubleArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
arr[i] *= 2;
}
}
int main() {
int a = 5;
increment(a);
printf("main中a=%d\n", a); // 仍然是5
int b = 5;
incrementByPointer(&b);
printf("main中b=%d\n", b); // 变为6
int arr[] = {1, 2, 3, 4, 5};
doubleArray(arr, 5);
printf("数组元素:");
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
递归函数
#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);
}
// 汉诺塔问题(递归)
void hanoi(int n, char from, char to, char aux) {
if (n == 1) {
printf("移动盘子1从 %c 到 %c\n", from, to);
return;
}
hanoi(n-1, from, aux, to);
printf("移动盘子%d从 %c 到 %c\n", n, from, to);
hanoi(n-1, aux, to, from);
}
int main() {
printf("5! = %d\n", fibonacci(5));
printf("斐波那契数列第10项:%d\n", fibonacci(10));
printf("\n汉诺塔(3层):\n");
hanoi(3, 'A', 'C', 'B');
return 0;
}
变量作用域与存储类别
#include <stdio.h>
int globalVar = 100; // 全局变量
void testFunction() {
static int staticVar = 0; // 静态局部变量
int localVar = 0; // 自动局部变量
staticVar++;
localVar++;
printf("静态变量:%d,局部变量:%d\n", staticVar, localVar);
}
int main() {
printf("全局变量:%d\n", globalVar);
printf("\n第一次调用:\n");
testFunction();
printf("\n第二次调用:\n");
testFunction();
// 局部变量与全局变量同名
int globalVar = 50; // 遮蔽全局变量
printf("\n局部变量遮蔽全局变量:%d\n", globalVar);
return 0;
}
第八章:指针
指针基础
#include <stdio.h>
int main() {
int num = 100;
int* ptr = # // 指针指向num的地址
printf("num的值:%d\n", num);
printf("num的地址:%p\n", &num);
printf("指针ptr的值(num的地址):%p\n", ptr);
printf("指针ptr指向的值:%d\n", *ptr);
// 通过指针修改变量
*ptr = 200;
printf("通过指针修改后,num的值:%d\n", num);
// 指针的指针
int** ptr2 = &ptr;
printf("二级指针访问num:%d\n", **ptr2);
return 0;
}
指针与数组
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr; // 数组名是首元素地址
printf("--- 指针遍历数组 ---\n");
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d, *(ptr+%d) = %d\n",
i, arr[i], i, *(ptr + i));
}
// 指针运算
printf("\n--- 指针运算 ---\n");
printf("ptr当前指向:%d\n", *ptr); // 10
ptr++; // 移动到下一个元素
printf("ptr++后指向:%d\n", *ptr); // 20
ptr += 2; // 移动两个元素
printf("ptr+=2后指向:%d\n", *ptr); // 40
ptr--; // 移动到前一个元素
printf("ptr--后指向:%d\n", *ptr); // 30
// 指针比较
int* start = arr;
int* end = arr + 5;
printf("\n数组范围:%p 到 %p\n", start, end);
return 0;
}
指针与函数
#include <stdio.h>
// 指针作为函数参数
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 指针作为函数返回值
int* findMax(int* arr, int size) {
int* maxPtr = arr;
for (int i = 1; i < size; i++) {
if (arr[i] > *maxPtr) {
maxPtr = &arr[i];
}
}
return maxPtr;
}
// 函数指针
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int main() {
// 指针作为函数参数
int x = 10, y = 20;
printf("交换前:x=%d, y=%d\n", x, y);
swap(&x, &y);
printf("交换后:x=%d, y=%d\n", x, y);
// 指针作为函数返回值
int arr[] = {12, 45, 67, 23, 89};
int* maxPtr = findMax(arr, 5);
printf("最大值:%d,地址:%p\n", *maxPtr, maxPtr);
// 函数指针
int (*operation)(int, int);
operation = add;
printf("加法:%d\n", operation(5, 3));
operation = subtract;
printf("减法:%d\n", operation(5, 3));
return 0;
}
动态内存分配
#include <stdio.h>
#include <stdlib.h>
int main() {
// 动态分配单个变量
int* dynamicInt = (int*)malloc(sizeof(int));
if (dynamicInt == NULL) {
printf("内存分配失败\n");
return 1;
}
*dynamicInt = 100;
printf("动态分配的整数:%d\n", *dynamicInt);
free(dynamicInt); // 释放内存
// 动态分配数组
int size;
printf("请输入数组大小:");
scanf("%d", &size);
int* dynamicArray = (int*)malloc(size * sizeof(int));
if (dynamicArray == NULL) {
printf("内存分配失败\n");
return 1;
}
// 初始化数组
for (int i = 0; i < size; i++) {
dynamicArray[i] = i * 10;
}
// 使用数组
printf("动态数组:");
for (int i = 0; i < size; i++) {
printf("%d ", dynamicArray[i]);
}
printf("\n");
free(dynamicArray); // 释放内存
// 使用calloc(初始化为0)
int* zeroArray = (int*)calloc(5, sizeof(int));
printf("calloc初始化数组:");
for (int i = 0; i < 5; i++) {
printf("%d ", zeroArray[i]);
}
printf("\n");
free(zeroArray);
// 使用realloc调整大小
int* arr = (int*)malloc(3 * sizeof(int));
for (int i = 0; malloc(3 * sizeof(int));
for (int i = 0; i < 3; i++) arr[i] = i+1;
arr = (int*)realloc(arr, 5 * sizeof(int));
for (int i = 3; i < 5; i++) arr[i] = i+1;
printf("realloc后数组:");
for (int i = 0; i < 5; i++) printf("%d ", arr[i]);
printf("\n");
free(arr);
return 0;
}
第九章:结构体、共用体与枚举
结构体(struct)
#include <stdio.h>
#include <string.h>
// 定义结构体类型
struct Student {
char name[50];
int age;
float score;
char grade;
};
// 结构体数组
struct Student class[3] = {
{"张三", 20, 85.5, 'A'},
{"李四", 19, 92.0, 'A'},
{"王五", 21, 78.5, 'B'}
};
// 结构体指针
void printStudent(struct Student* s) {
printf("姓名:%s,年龄:%d,分数:%.1f,等级:%c\n",
s->name, s->age, s->score, s->grade);
}
int main() {
// 结构体变量初始化
struct Student s1 = {"赵六", 20, 88.0, 'B'};
// 访问结构体成员
printf("学生信息:\n");
printf("姓名:%s\n", s1.name);
printf("年龄:%d\n", s1.age);
printf("分数:%.1f\n", s1.score);
// 修改结构体成员
s1.score = 90.0;
s1.grade = 'A';
// 结构体数组遍历
printf("\n班级学生信息:\n");
for (int i = 0; i < 3; i++) {
printf("%d. ", i+1);
printStudent(&class[i]);
}
// 结构体指针
struct Student* ptr = &s1;
printf("\n通过指针访问:\n");
printStudent(ptr);
return 0;
}
共用体(union)
#include <stdio.h>
// 定义共用体类型
union Data {
int i;
float f;
char str[20];
};
int main() {
union Data data;
printf("共用体大小:%zu字节\n", sizeof(data)); // 20字节(最大成员)
// 同一时间只能使用一个成员
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.f = %.1f(被覆盖)\n", data.f);
return 0;
}
枚举(enum)
#include <stdio.h>
// 定义枚举类型
enum Weekday {
Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday
};
enum Boolean {
FALSE = 0, TRUE = 1
};
int main() {
enum Weekday today = Wednesday;
printf("今天是星期");
switch (today) {
case Monday: printf("一\n"); break;
case Tuesday: printf("二\n"); break;
case Wednesday: printf("三\n"); C语言学习路径:从零基础到精通的完整指南
// 枚举值本质是整数常量
printf("Wednesday的值:%d\n", Wednesday); // 2
// 枚举数组
enum Weekday schedule[5] = {Monday, Wednesday, Friday, Saturday, Sunday};
printf("\n本周安排:\n");
for (int i = 0; i < 5; i++) {
switch (schedule[i]) {
case Monday: printf("周一\n"); break;
case Wednesday: printf("周三\n"); break;
case Friday: printf("周五\n"); break;
case Saturday: printf("周六\n"); break;
case Sunday: printf("周日\n"); break;
}
}
return 0;
}
结构体与指针、共用体的综合应用
#include <stdio.h>
#include <string.h>
// 结构体中包含共用体
struct Employee {
char name[50];
int id;
union {
int basicSalary;
float hourlyRate;
} payInfo;
int payType; // 0=月薪,1=时薪
};
int main() {
struct Employee emp1, emp2;
// 员工1:月薪制
strcpy(emp1.name, "张三");
emp1.id = 1001;
emp1.payInfo.basicSalary = 8000;
emp1.payType = 0;
// 员工2:时薪制
strcpy(emp2.name, "李四");
emp2.id = 1002;
emp2.payInfo.hourlyRate = 50.0;
emp2.payType = 1;
// 计算并显示工资
printf("--- 员工工资表 ---\n");
printf("%s (ID:%d) - ", emp1.name, emp1.id);
if (emp1.payType == 0) {
printf("月薪:%d元\n", emp1.payInfo.basicSalary);
} else {
printf("时薪:%.2f元\n", emp1.payInfo.hourlyRate);
}
printf("%s (ID:%d) - ", emp2.name, emp2.id);
if (emp2.payType == 0) {
printf("月薪:%d元\n", emp2.payInfo.basicSalary);
} else {
printf("时薪:%.2f元\n", emp2.payInfo.hourlyRate);
}
return 0;
}
第十章:文件操作
文件的打开与关闭
#include <stdio.h>
int main() {
FILE* fp;
// 打开文件用于写入
fp = fopen("test.txt", "w");
if (fp == NULL) {
printf("无法打开文件\n");
return 1;
}
fprintf(fp, "Hello, File!\n");
fprintf(fp, "这是第二行\n");
fclose(fp);
printf("文件写入完成\n");
// 打开文件用于读取
fp = fopen("test.txt", "r");
if (fp == NULL) {
printf("无法打开文件\n");
return 1;
}
char buffer[100];
while (fgets(buffer, 100, fp) != NULL) {
printf("%s", buffer);
}
fclose(fp);
return 0;
}
文件读写函数详解
#include <stdio.h>
#include <string.h>
struct Student {
char name[50];
int age;
float score;
};
int main() {
// 格式化读写
FILE* fp = fopen("data.txt", "w");
fprintf(fp, "Name: %s, Age: %d, Score: %.1f\n", "Alice", 20, 92.5);
fprintf(fp, "Name: %s, Age: 20, Score: 92.5\n", "Bob", 19, 88.0);
fclose(fp);
// 二进制读写结构体
FILE* binFile = fopen("students.bin", "wb");
struct Student s1 = {"Charlie", 21, 95.0};
struct Student s2 = {"David", 20, 87.5};
fwrite(&s1, sizeof(struct Student), 1, binFile);
fwrite(&s2, sizeof(struct Student), 1, binFile);
fclose(binFile);
// 读取二进制文件
binFile = fopen("students.bin", "rb");
struct Student temp;
printf("--- 从二进制文件读取 ---\n");
while (fread(&temp, sizeof(struct Student), 1, binFile) == 1) {
printf("姓名:%s,年龄:%d,分数:%.1f\n",
temp.name, temp.age, temp.score);
}
fclose(binFile);
// 文件定位
fp = fopen("test.txt", "r");
fseek(fp, 0, SEEK_END); // 移动到文件末尾
long size = ftell(fp); // 获取当前位置(文件大小)
printf("文件大小:%ld字节\n", size);
fclose(fp);
return 0;
}
文件操作综合示例:学生成绩管理系统
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_STUDENTS 100
struct Student {
char name[50];
int age;
float score;
};
void saveToFile(struct Student* students, int count, const char* filename) {
FILE* fp = fopen(filename, "wb");
if (fp == NULL) {
printf("无法创建文件\n");
return;
}
fwrite(students, sizeof(struct Student), count, fp);
fclose(fp);
printf("数据已保存到 %s\n", filename);
}
int loadFromFile(struct Student* students, const char* filename) {
FILE* fp = fopen(filename, "rb");
if (fp == NULL) {
return 0; // 文件不存在
}
int count = fread(students, sizeof(struct Student), MAX_STUDENTS, fp);
fclose(fp);
return count;
}
void displayStudents(struct Student* students, int count) {
printf("\n--- 学生列表 ---\n");
printf("序号\t姓名\t年龄\t分数\n");
for (int i = 0; i < count; i++) {
printf("%d\t%s\t%d\t%.1f\n",
i+1, students[i].name, students[i].age, students[i].score);
}
}
int main() {
struct Student students[MAX_STUDENTS];
int count = 0;
char filename[] = "students.dat";
// 从文件加载数据
count = loadFromFile(students, filename);
if (count > 0) {
printf("已加载 %d 条记录\n", count);
}
int choice;
do {
printf("\n--- 学生管理系统 ---\n");
printf("1. 添加学生\n");
printf("2. 显示所有学生\n");
printf("3. 保存到文件\n");
printf("4. 退出\n");
printf("选择:");
scanf("%d", &choice);
switch (choice) {
case 1:
if (count >= MAX_STUDENTS) {
printf("已达到最大数量\n");
break;
}
printf("请输入姓名:");
scanf("%s", students[count].name);
printf("请输入年龄:");
scanf("%d", &students[count].age);
printf("请输入分数:");
scanf("%f", &students[count].score);
count++;
break;
case 2:
displayStudents(students, count);
break;
case 3:
saveToFile(students, count, filename);
break;
case 4:
printf("再见!\n");
break;
default:
printf("无效选项\n");
}
} while (choice != 4);
return 0;
}
第十一章:高级主题与编程技巧
预处理器指令
#include <stdio.h>
// 宏定义
#define PI 3.14159
#define SQUARE(x) ((x) * (x)) // 带参数的宏
#define MAX(a,b) ((a) > (b) ? (a) : (b))
// 条件编译
#define DEBUG 1
int main() {
// 宏替换示例
int radius = 5;
printf("圆面积:%.2f\n", PI * SQUARE(radius));
printf("较大值:%d\n", MAX(10, 20));
// 条件编译
#if DEBUG
printf("调试模式已开启\n");
printf("半径:%d\n", radius);
#else
printf("生产模式\n");
#endif
// 文件包含
#include <math.h>
printf("sqrt(16) = %.2f\n", sqrt(16.0));
return 0;
位运算
#include <stdio.h>
int main() {
unsigned char a = 60; // 00111100
unsigned char b = 13; // 00001101
printf("a = %d (二进制: 00111100)\n", a);
printf("b = %d (二进制: 00001101)\n", b);
// 按位与
printf("a & b = %d\n", a & b); // 12 (00001100)
// 按位或
printf("a | b = %d\n", a | b); // 61 (00111101)
// 按位异或
printf("a ^ b = %d\n", a ^ b); // 49 (00110001)
// 按位取反
printf("~a = %d\n", ~a); // -61 (11000011)
// 左移
printf("a << 2 = %d\n", a << 2); // 240 (11110000)
// 右移
printf("a >> 2 = %d\n", a >> 2); // 15 (00001111)
// 实际应用:设置、清除、检查特定位
unsigned char flags = 0;
// 设置第3位(从0开始)
flags |= (1 << 3);
printf("设置第3位后:%d\n", flags);
// 检查第3位
if (flags & (1 << 3)) {
printf("第3位已设置\n");
}
// 清除第3位
flags &= ~(1 << 3);
printf("清除第3位后:%d\n", flags);
return 0;
}
命令行参数
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[]) {
printf("程序名称:%s\n", argv[0]);
printf("参数个数:%d\n", argc);
for (int i = 1; i < argc; i++) {
printf("参数 %d: %s\n", i, argv[i]);
}
// 实际应用:简单计算器
if (argc == 4) {
int a = atoi(argv[1]);
char op = argv[2][0];
int b = atoi(argv[3]);
switch (op) {
case '+': printf("%d + %d = %d\n", a, b, a+b); break;
case '-': printf("%d - %d = %d\n", a, b, a-b); break;
case '*': printf("%d * %d = %d\n", a, b, a*b); break;
case '/':
if (b != 0) printf("%d / %d = %d\n", a, b, a/b);
else printf("除数不能为0\n");
break;
default: printf("无效运算符\n");
}
} else {
printf("用法:%s <数字> <运算符> <数字>\n", argv[0]);
printf("示例:%s 5 + 3\n", argv[0]);
}
return 0;
}
常见错误与调试技巧
#include <stdio.h>
#include <assert.h>
// 常见错误示例1:数组越界
void arrayBoundsError() {
int arr[5] = {1, 2, 3, 4, 5};
// arr[5] = 6; // 错误!越界访问,可能导致程序崩溃
// printf("%d\n", arr[5]); // 未定义行为
// 正确做法
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
// 常见错误示例2:未初始化变量
void uninitializedError() {
int x; // 未初始化
// printf("%d\n", x); // 未定义行为,可能输出随机值
// 正确做法
int y = 0;
printf("%d\n", y);
}
// 常见错误示例3:内存泄漏
void memoryLeakExample() {
int* ptr = (int*)malloc(sizeof(int));
*ptr = 100;
printf("%d\n", *ptr);
// 错误:忘记free(ptr),导致内存泄漏
// free(ptr); // 应该释放内存
}
// 常见错误示例4:使用已释放的内存
void useAfterFree() {
int* ptr = (int*)malloc(sizeof(int));
*ptr = 100;
free(ptr);
// printf("%d\n", *ptr); // 错误!使用已释放的内存
}
// 使用assert进行调试
void debugWithAssert(int value) {
assert(value > 0); // 如果value<=0,程序会终止并显示错误信息
printf("value = %d\n", value);
}
int main() {
printf("--- 常见错误演示 ---\n");
printf("1. 数组越界:\n");
arrayBoundsError();
printf("\n2. 未初始化变量:\n");
uninitializedError();
printf("\n3. 调试技巧:\n");
debugWithAssert(10);
// debugWithAssert(-5); // 这行会触发assert错误
return 0;
}
第十二章:综合项目实战
项目1:通讯录管理系统
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_CONTACTS 100
#define NAME_LEN 50
#define PHONE_LEN 20
typedef struct {
char name[NAME_LEN];
char phone[PHONE_LEN];
char email[NAME_LEN];
} Contact;
typedef struct {
Contact contacts[MAX_CONTACTS];
int count;
} AddressBook;
void addContact(AddressBook* book) {
if (book->count >= MAX_CONTACTS) {
printf("通讯录已满!\n");
return;
}
printf("请输入姓名:");
scanf("%s", book->contacts[book->count].name);
printf("请输入电话:");
scanf("%s", book->contacts[book->count].phone);
printf("请输入邮箱:");
scanf("%s", book->contacts[book->count].email);
book->count++;
printf("联系人添加成功!\n");
}
void displayContacts(AddressBook* book) {
if (book->count == 0) {
printf("通讯录为空!\n");
return;
}
printf("\n--- 通讯录列表 ---\n");
printf("序号\t姓名\t电话\t邮箱\n");
for (int i = 0; i < book->count; i++) {
printf("%d\t%s\t%s\t%s\n", i+1,
book->contacts[i].name,
book->contacts[i].phone,
book->contacts[i].email);
}
}
void searchContact(AddressBook* book) {
char name[NAME_LEN];
printf("请输入要查找的姓名:");
scanf("%s", name);
for (int i = 0; i < book->count; i++) {
if (strcmp(book->contacts[i].name, name) == 0) {
printf("\n找到联系人:\n");
printf("姓名:%s\n", book->contacts[i].name);
printf("电话:%s\n", book->contacts[i].phone);
printf("邮箱:%s\n", book->contacts[i].email);
return;
}
}
printf("未找到联系人\n");
}
void deleteContact(AddressBook* book) {
char name[NAME_LEN];
printf("请输入要删除的姓名:");
scanf("%s", name);
for (int i = 0; i < book->count; i++) {
if (strcmp(book->contacts[i].name, name) == 0) {
// 移动后续元素
for (int j = i; j < book->count - 1; j++) {
book->contacts[j] = book->contacts[j+1];
}
book->count--;
printf("联系人已删除\n");
return;
}
}
printf("未找到联系人\n");
}
void saveContacts(AddressBook* book) {
FILE* fp = fopen("addressbook.dat", "wb");
if (fp == NULL) {
printf("无法保存文件\n");
return;
}
fwrite(book, sizeof(AddressBook), 1, fp);
fclose(fp);
printf("数据已保存\n");
}
void loadContacts(AddressBook* book) {
FILE* fp = fopen("addressbook.dat", "rb");
if (fp == NULL) {
return; // 文件不存在
}
fread(book, sizeof(AddressBook), 1, fp);
fclose(fp);
printf("已加载 %d 个联系人\n", book->count);
}
int main() {
AddressBook book = {0};
loadContacts(&book);
int choice;
do {
printf("\n=== 通讯录管理系统 ===\n");
printf("1. 添加联系人\n");
printf("2. 显示所有联系人\n");
printf("3. 查找联系人\n");
printf("4. 删除联系人\n");
printf("5. 保存并退出\n");
printf("选择:");
scanf("%d", &choice);
switch (choice) {
case 1: addContact(&book); break;
case 2: displayContacts(&book); break;
case 3: searchContact(&book); break;
case 4: deleteContact(&book); break;
case 5: saveContacts(&book); break;
default: printf("无效选项\n");
}
} while (choice != 5);
return 0;
}
项目2:简单计算器(支持表达式解析)
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
// 简单的表达式解析器
double parseExpression(char* expr, int* index);
double parseNumber(char* expr, int* index) {
double num = 0;
int decimal = 0;
int divisor = 1;
while (isdigit(expr[*index]) || expr[*index] == '.') {
if (expr[*index] == '.') {
decimal = 1;
} else if (decimal) {
divisor *= 10;
num = num * 10 + (expr[*index] - '0');
} else {
num = num * 10 + (expr[*index] - '0');
}
(*index)++;
}
return num / divisor;
}
double parseFactor(char* expr, int* index) {
// 跳过空格
while (isspace(expr[*index])) (*index)++;
// 处理括号
if (expr[*index] == '(') {
(*index)++; // 跳过 '('
double result = parseExpression(expr, index);
(*index)++; // 跳过 ')'
return result;
}
// 处理负号
if (expr[*index] == '-') {
(*index)++;
return -parseFactor(expr, index);
}
// 数字
return parseNumber(expr, index);
}
double parseTerm(char* expr, int* index) {
double result = parseFactor(expr, index);
while (expr[*index] == '*' || expr[*index] == '/') {
char op = expr[*index];
(*index)++;
double factor = parseFactor(expr, index);
if (op == '*') {
result *= factor;
} else {
if (factor == 0) {
printf("错误:除数不能为零\n");
return 0;
}
result /= factor;
}
}
return result;
}
double parseExpression(char* expr, int* index) {
double result = parseTerm(expr, index);
while (expr[*index] == '+' || expr[*index] == '-') {
char op = expr[*index];
(*index)++;
double term = parseTerm(expr, index);
if (op == '+') {
result += term;
} else {
result -= term;
}
}
return result;
}
int main() {
char expression[100];
printf("简单计算器(支持 + - * / 和括号)\n");
printf("输入表达式(例如:(2+3)*4-5/2):");
scanf("%[^\n]", expression); // 读取整行
int index = 0;
double result = parseExpression(expression, &index);
printf("结果:%.2f\n", result);
return 0;
}
项目3:链表实现与应用
#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));
if (newNode == NULL) {
printf("内存分配失败\n");
exit(1);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 在链表头部插入
void insertAtHead(Node** head, int data) {
Node* newNode = createNode(data);
newNode->next = *head;
*head = newNode;
}
// 在链表尾部插入
void insertAtTail(Node** head, int data) {
Node* newNode = createNode(data);
if (*head == NULL) {
*head = newNode;
return;
}
Node* temp = *head;
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = newNode;
}
// 删除节点
void deleteNode(Node** head, int key) {
Node* temp = *head;
Node* prev = NULL;
// 如果头节点就是要删除的节点
if (temp != NULL && temp->data == key) {
*head = temp->next;
free(temp);
return;
}
// 查找要删除的节点
while (temp != NULL && temp->data != key) {
prev = temp;
temp = temp->next;
}
// 如果没找到
if (temp == NULL) {
printf("节点 %d 不存在\n", key);
return;
}
// 从链表中移除
prev->next = temp->next;
free(temp);
}
// 打印链表
void printList(Node* head) {
Node* temp = head;
printf("链表:");
while (temp != NULL) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}
// 计算链表长度
int getLength(Node* head) {
int count = 0;
Node* temp = head;
while (temp != NULL) {
count++;
temp = temp->next;
}
return count;
}
// 反转链表
void reverseList(Node** head) {
Node* prev = NULL;
Node* current = *head;
Node* next = NULL;
while (current != NULL) {
next = current->next; // 保存下一个节点
current->next = prev; // 反转当前节点的指针
prev = current; // 移动prev
current = next; // 移动current
}
*head = prev;
}
// 释放链表内存
void freeList(Node** head) {
Node* temp = *head;
while (temp != NULL) {
Node* next = temp->next;
free(temp);
temp = next;
}
*head = NULL;
}
int main() {
Node* head = NULL;
printf("--- 链表操作演示 ---\n");
// 插入操作
insertAtTail(&head, 10);
insertAtTail(&head, 20);
insertAtTail(&head, 30);
insertAtHead(&head, 5);
printList(head);
// 删除操作
printf("\n删除节点20:\n");
deleteNode(&head, 20);
printList(head);
// 长度
printf("\n链表长度:%d\n", getLength(head));
// 反转
printf("\n反转链表:\n");
reverseList(&head);
printList(head);
// 释放内存
freeList(&head);
printf("\n链表已释放\n");
return 0;
}
学习建议与进阶路径
学习C语言的黄金法则
- 动手实践:每个概念都要编写代码验证,不要只看不练
- 理解内存:C语言的核心是内存操作,要理解栈、堆、静态区的区别
- 调试习惯:学会使用gdb或IDE调试器,理解程序执行流程
- 阅读源码:阅读优秀开源项目代码,学习编程规范
- 代码规范:养成良好的命名、注释、缩进习惯
谭浩强教程学习路线
第一阶段(基础):第1-5章,掌握基本语法和流程控制 第二阶段(进阶):第6-8章,深入理解数组、字符串、指针 第三阶段(高级):第9-11章,掌握结构体、文件操作、动态内存 第四阶段(实战):完成综合项目,尝试自己设计小型系统
常见问题解答
Q: 为什么我的程序运行后立即退出?
A: 在main函数末尾添加 getchar(); 或 system("pause");
Q: scanf读取字符串时遇到空格怎么办?
A: 使用 fgets 替代,或循环使用 getchar()
Q: 如何处理内存泄漏?
A: 每次 malloc 都要有对应的 free,使用工具如Valgrind检测
Q: 指针总是出错怎么办? A: 从简单例子开始,画内存图理解,使用调试器单步执行
推荐练习项目
- 初级:学生成绩统计、文本加密器、简易日历
- 中级:通讯录、计算器、文件压缩器
- 高级:迷宫求解、排序算法可视化、简单数据库
通过系统学习谭浩强C语言教程并结合以上实践,你将从零基础逐步掌握C语言的核心技巧,为后续学习C++、Java、操作系统等高级内容打下坚实基础。记住,编程是实践的艺术,多写代码、多思考、多调试,你一定能成为优秀的程序员!
