C语言是一门经典而强大的编程语言,因其高效性和底层控制能力,成为初学者和专业开发者的首选。它不仅是操作系统(如Linux)和嵌入式系统的基石,也是算法竞赛的热门语言。本文面向零基础学习者,带你从C语言的基本语法到初阶数据结构,再到算法竞赛案例和实战项目,逐步构建编程思维。
为什么选择C语言?
学习路线:本文从C语言基础(变量、流程控制、函数)开始,逐步引入数组、指针、结构体等进阶内容,再到数据结构(链表、栈、队列),最后通过算法竞赛案例和实战项目巩固知识。每节包含详细文字讲解、规范代码和注释,确保初学者易懂。
适合人群:
C语言诞生于1972年,由贝尔实验室的Dennis Ritchie开发,用于编写Unix操作系统。它的设计理念“简单而强大”影响了C++、Java等现代语言。C语言的代码执行效率高,广泛应用于操作系统、嵌入式设备和游戏开发。学习C语言就像掌握了一把“万能钥匙”,为你打开编程世界的大门。
开始编程前,需要搭建开发环境。推荐以下工具:
环境搭建步骤(以Windows为例):
gcc
命令可用。让我们编写第一个C程序,输出“Hello, World!”:
#include
int main() {
// 输出欢迎信息
printf("Hello, World!\n");
return 0;
}
代码解析:
#include
:引入标准输入输出库,提供printf
函数。int main()
:程序的入口函数,所有C程序从这里开始执行。printf("Hello, World!\n")
:打印字符串,\n
表示换行。return 0
:表示程序正常结束,返回0给操作系统。练习建议:修改程序,输出你的名字或一句个性化的问候语。尝试用printf
输出多行文本,观察换行符的效果。
变量是程序中存储数据的容器,C语言支持以下基本数据类型:
int
:整数,如42
或-10
。float
:单精度浮点数,如3.14
。double
:双精度浮点数,精度更高,适合大范围计算。char
:单个字符,如'A'
或'1'
。const
关键字定义不可修改的值,如const int MAX = 100;
。studentAge
或total_score
。int
、if
)。1score
是非法的。C语言支持多种运算符:
+
(加)、-
(减)、*
(乘)、/
(除)、%
(取模)。>
、<
、==
、!=
、>=
、<=
。&&
(与)、||
(或)、!
(非)。让我们编写一个程序,计算用户的BMI(体重/身高²)并判断健康状况:
#include
int main() {
float weight, height, bmi;
// 获取用户输入
printf("请输入体重(kg):");
scanf("%f", &weight);
printf("请输入身高(m):");
scanf("%f", &height);
// 计算BMI
bmi = weight / (height * height);
// 输出结果并判断健康状况
printf("你的BMI是:%.2f\n", bmi);
if (bmi < 18.5) {
printf("偏瘦,建议均衡饮食。\n");
} else if (bmi < 24) {
printf("正常,保持健康生活!\n");
} else {
printf("偏胖,建议适量运动。\n");
}
return 0;
}
代码解析:
float
:用于存储小数,确保计算精度。scanf("%f", &weight)
:读取用户输入,&
表示变量的内存地址。%.2f
:控制输出格式,保留两位小数。练习建议:添加输入验证,确保体重和身高为正数。尝试输出更详细的健康建议。
printf
是C语言的核心输出函数,支持格式化输出:
%d
:整数。%f
:浮点数(默认6位小数)。%c
:字符。%s
:字符串。%.2f
表示保留2位小数。scanf
用于读取用户输入,常见注意事项:
&
获取变量地址(字符串除外)。" %c"
。编写一个程序,让用户输入姓名、年龄和身高,格式化输出一段自我介绍:
#include
int main() {
char name[50];
int age;
float height;
// 获取用户输入
printf("请输入你的名字:");
scanf("%s", name);
printf("请输入你的年龄:");
scanf("%d", &age);
printf("请输入你的身高(m):");
scanf("%f", &height);
// 格式化输出自我介绍
printf("\n--- 自我介绍 ---\n");
printf("大家好,我的名字是%s,今年%d岁,身高%.2f米。\n", name, age, height);
return 0;
}
代码解析:
char name[50]
:定义字符数组存储名字,最多49个字符(留1位给\0
)。scanf("%s", name)
:字符串输入无需&
,因为数组名是地址。printf
拼接成完整句子,增强可读性。练习建议:添加兴趣爱好输入,输出更丰富的介绍。尝试限制名字长度,避免溢出。
条件语句根据条件执行不同代码块,类比生活中的“如果…那么…”:
#include
int main() {
int score;
printf("请输入成绩(0-100):");
scanf("%d", &score);
// 判断成绩等级
if (score >= 90) {
printf("优秀\n");
} else if (score >= 60) {
printf("及格\n");
} else {
printf("不及格\n");
}
return 0;
}
代码解析:
if-else
:根据条件选择执行路径。score
是否在0-100范围内。switch
适合多分支选择,简化代码:
#include
int main() {
int choice;
printf("请选择(1-3):");
scanf("%d", &choice);
// 根据选项执行
switch (choice) {
case 1:
printf("你选择了选项1:开始游戏\n");
break;
case 2:
printf("你选择了选项2:查看设置\n");
break;
case 3:
printf("你选择了选项3:退出\n");
break;
default:
printf("无效选项,请输入1-3\n");
}
return 0;
}
代码解析:
break
:防止“穿透”到下一个case
。default
:处理无效输入。C语言支持三种循环:
for
:适合已知循环次数。while
:适合条件驱动循环。do-while
:至少执行一次循环体。打印九九乘法表,练习嵌套循环:
#include
int main() {
// 外层循环控制行
for (int i = 1; i <= 9; i++) {
// 内层循环控制列
for (int j = 1; j <= i; j++) {
printf("%d * %d = %-2d ", j, i, i * j);
}
printf("\n");
}
return 0;
}
代码解析:
%-2d
:左对齐,占2位宽度,确保输出整齐。1*1=1 2*2=4 ...
。练习建议:修改程序,打印完整的9x9表格(包括重复项)。尝试用while
循环重写。
竞赛中常考简单数学问题,如计算1到N的和。以下展示暴力法和公式法:
#include
int main() {
int n;
printf("请输入正整数N:");
scanf("%d", &n);
// 暴力法:循环累加
long long sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
printf("暴力法:1到%d的和是%lld\n", n, sum);
// 公式法:(n * (n + 1)) / 2
sum = (long long)n * (n + 1) / 2;
printf("公式法:1到%d的和是%lld\n", n, sum);
return 0;
}
代码解析:
(n * (n + 1)) / 2
,时间复杂度O(1),效率更高。long long
:防止大数溢出,适合竞赛中处理大范围输入。练习建议:添加输入验证,确保N为正整数。尝试计算1到N的平方和。
函数将代码分解成可重用的模块,优点包括:
函数由声明(原型)和定义组成。以下是求两数最大值的函数:
#include
// 函数声明
int max(int a, int b);
int main() {
int x, y;
printf("请输入两个整数:");
scanf("%d %d", &x, &y);
printf("较大值是:%d\n", max(x, y));
return 0;
}
// 函数定义
int max(int a, int b) {
return a > b ? a : b; // 三目运算符,简洁返回较大值
}
代码解析:
?:
:等价于if-else
,但更简洁。实现一个支持加、减、乘、除的计算器,封装运算为函数:
#include
// 函数声明
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);
double divide(double a, double b);
int main() {
double num1, num2;
char op;
// 获取用户输入
printf("请输入第一个数字:");
scanf("%lf", &num1);
printf("请输入运算符(+,-,*,/):");
scanf(" %c", &op); // 注意空格,避免换行符干扰
printf("请输入第二个数字:");
scanf("%lf", &num2);
// 根据运算符调用对应函数
switch (op) {
case '+':
printf("结果:%.2f\n", add(num1, num2));
break;
case '-':
printf("结果:%.2f\n", subtract(num1, num2));
break;
case '*':
printf("结果:%.2f\n", multiply(num1, num2));
break;
case '/':
if (num2 != 0) {
printf("结果:%.2f\n", divide(num1, num2));
} else {
printf("错误:除数不能为0\n");
}
break;
default:
printf("无效运算符\n");
}
return 0;
}
// 函数定义
double add(double a, double b) { return a + b; }
double subtract(double a, double b) { return a - b; }
double multiply(double a, double b) { return a * b; }
double divide(double a, double b) { return a / b; }
代码解析:
double
:支持小数运算,提高精度。scanf(" %c", &op)
:空格吸收换行符,确保输入正确。练习建议:添加幂运算函数(如pow(a, b)
)。实现连续计算功能(循环输入)。
数组是存储相同类型元素的连续内存空间。以下是输入并打印数组的示例:
#include
#define MAX_SIZE 100 // 定义最大数组大小
int main() {
int arr[MAX_SIZE], n;
// 输入数组元素
printf("请输入数组元素个数:");
scanf("%d", &n);
printf("请输入%d个整数:\n", n);
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
// 打印数组
printf("数组内容:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
代码解析:
#define MAX_SIZE 100
:定义常量,避免硬编码。n
,增强灵活性。for
循环。二维数组可看作矩阵,适合存储表格数据:
#include
int main() {
int matrix[3][3];
// 输入3x3矩阵
printf("请输入3x3矩阵:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
scanf("%d", &matrix[i][j]);
}
}
// 打印矩阵
printf("矩阵内容:\n");
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
return 0;
}
代码解析:
matrix[3][3]
:定义3x3二维数组。字符串是字符数组,以空字符\0
结尾。以下是基本字符串操作:
#include
#include
int main() {
char str[100];
printf("请输入一个字符串:");
scanf("%s", str); // 注意:scanf只读取不含空格的字符串
// 使用字符串函数
printf("字符串长度:%lu\n", strlen(str));
printf("字符串:%s\n", str);
return 0;
}
代码解析:
char str[100]
:分配足够空间存储字符串。strlen
:计算字符串长度(不含\0
)。scanf
不适合读取含空格的字符串,后续会介绍fgets
。编写程序,统计用户输入文本中的单词数:
#include
#include
int main() {
char text[1000];
int wordCount = 0;
int inWord = 0;
// 获取整行输入
printf("请输入一段文字(以回车结束):\n");
fgets(text, sizeof(text), stdin);
// 统计单词
for (int i = 0; text[i] != '\0'; i++) {
if (text[i] == ' ' || text[i] == '\n') {
inWord = 0; // 遇到空格或换行,单词结束
} else if (inWord == 0) {
inWord = 1; // 遇到非空格,单词开始
wordCount++;
}
}
printf("单词数:%d\n", wordCount);
return 0;
}
代码解析:
fgets
:读取整行输入,包括空格,适合处理文本。inWord
:标记是否在单词内部,避免重复计数。练习建议:统计每个单词的长度,输出最长单词。尝试忽略标点符号。
指针存储变量的内存地址,类比“藏宝图”,指向数据的存储位置。指针是C语言的核心特性,理解它对后续数据结构学习至关重要。
#include
int main() {
int a = 10;
int *p = &a; // 取地址,p存储a的地址
// 打印变量、地址和通过指针访问的值
printf("a的值:%d\n", a);
printf("a的地址:%p\n", (void*)&a);
printf("通过指针访问a:%d\n", *p); // 解引用
// 修改a的值
*p = 20;
printf("修改后a的值:%d\n", a);
return 0;
}
代码解析:
&a
:取变量a
的地址。*p
:解引用,访问指针指向的内存值。*p = 20
直接改变a
的值。数组名本质是指向数组首元素的指针:
#include
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *p = arr; // 数组名是首地址
// 使用指针遍历数组
for (int i = 0; i < 5; i++) {
printf("%d ", *(p + i));
}
printf("\n");
return 0;
}
代码解析:
arr
等价于&arr[0]
,指向数组第一个元素。*(p + i)
:访问第i个元素,等价于arr[i]
。p + i
自动跳跃i
个元素大小。指针在竞赛中常用于高效操作,如交换变量:
#include
// 使用指针交换两个变量
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 5, y = 10;
printf("交换前:x=%d, y=%d\n", x, y);
swap(&x, &y);
printf("交换后:x=%d, y=%d\n", x, y);
return 0;
}
代码解析:
int *a
接收变量地址。*a
和*b
直接修改原变量。temp
保存交换过程中的值。练习建议:用指针实现数组逆序(如[1,2,3]
变为[3,2,1]
)。尝试不用临时变量交换两个数(使用异或运算)。
结构体(struct
)将多个相关数据组合在一起,如学生的姓名和成绩:
#include
struct Student {
char name[50];
int score;
};
int main() {
struct Student s1;
// 输入学生信息
printf("请输入学生姓名和成绩:");
scanf("%s %d", s1.name, &s1.score);
// 输出信息
printf("学生:%s, 成绩:%d\n", s1.name, s1.score);
return 0;
}
代码解析:
struct Student
:定义结构体,包含姓名和成绩字段。.
操作符,如s1.name
。存储多个学生信息:
#include
struct Student {
char name[50];
int score;
};
int main() {
struct Student students[3];
// 输入学生信息
for (int i = 0; i < 3; i++) {
printf("请输入第%d个学生姓名和成绩:", i + 1);
scanf("%s %d", students[i].name, &students[i].score);
}
// 输出学生列表
printf("\n学生列表:\n");
for (int i = 0; i < 3; i++) {
printf("%s: %d\n", students[i].name, students[i].score);
}
return 0;
}
代码解析:
students[3]
:定义结构体数组,存储3个学生。C语言支持文件操作,使用fopen
、fscanf
和fprintf
:
#include
struct Student {
char name[50];
int score;
};
int main() {
struct Student s;
FILE *fp;
// 写入文件
fp = fopen("students.txt", "w");
printf("请输入学生姓名和成绩:");
scanf("%s %d", s.name, &s.score);
fprintf(fp, "%s %d\n", s.name, s.score);
fclose(fp);
// 读取文件
fp = fopen("students.txt", "r");
fscanf(fp, "%s %d", s.name, &s.score);
printf("从文件读取:%s %d\n", s.name, s.score);
fclose(fp);
return 0;
}
代码解析:
fopen("students.txt", "w")
:以写模式打开文件。fprintf
:格式化写入,类似printf
。fscanf
:从文件读取数据。fclose
:关闭文件,释放资源。实现一个支持添加、查询和文件存储的成绩管理系统:
#include
#include
#define MAX_STUDENTS 100
struct Student {
char name[50];
int score;
};
int main() {
struct Student students[MAX_STUDENTS];
int count = 0;
FILE *fp;
// 菜单循环
while (1) {
printf("\n1. 添加学生\n2. 查询成绩\n3. 保存到文件\n4. 从文件读取\n5. 退出\n");
printf("请选择:");
int choice;
scanf("%d", &choice);
if (choice == 5) break;
if (choice == 1) {
if (count < MAX_STUDENTS) {
printf("请输入学生姓名和成绩:");
scanf("%s %d", students[count].name, &students[count].score);
count++;
printf("添加成功!\n");
} else {
printf("存储已满!\n");
}
} else if (choice == 2) {
char name[50];
printf("请输入学生姓名:");
scanf("%s", name);
int found = 0;
for (int i = 0; i < count; i++) {
if (strcmp(students[i].name, name) == 0) {
printf("%s的成绩:%d\n", name, students[i].score);
found = 1;
break;
}
}
if (!found) printf("未找到学生!\n");
} else if (choice == 3) {
fp = fopen("students.txt", "w");
for (int i = 0; i < count; i++) {
fprintf(fp, "%s %d\n", students[i].name, students[i].score);
}
fclose(fp);
printf("保存成功!\n");
} else if (choice == 4) {
fp = fopen("students.txt", "r");
count = 0;
while (fscanf(fp, "%s %d", students[count].name, &students[count].score) != EOF) {
count++;
}
fclose(fp);
printf("读取成功!\n");
} else {
printf("无效选项!\n");
}
}
return 0;
}
项目解析:
练习建议:增加删除学生功能,按成绩排序输出。尝试限制姓名长度,防止溢出。
数据结构是组织和管理数据的方式,直接影响程序效率。数组适合静态数据,数据结构如链表、栈、队列适合动态场景。学习数据结构能帮助你解决复杂问题,如算法竞赛中的高效查询和排序。
链表由节点组成,每个节点包含数据和指向下一节点的指针。以下是单向链表的实现:
#include
#include
struct Node {
int data; // 存储数据
struct Node* next; // 指向下一节点
};
// 在头部插入节点
struct Node* insertAtHead(struct Node* head, int value) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = value;
newNode->next = head;
return newNode;
}
// 打印链表
void printList(struct Node* head) {
struct Node* current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
int main() {
struct Node* head = NULL;
// 插入节点
head = insertAtHead(head, 3);
head = insertAtHead(head, 2);
head = insertAtHead(head, 1);
// 打印链表
printList(head);
// 释放内存
struct Node* current = head;
while (current != NULL) {
struct Node* temp = current;
current = current->next;
free(temp);
}
return 0;
}
代码解析:
struct Node
包含数据和指针。malloc
分配内存,free
释放,防止内存泄漏。练习建议:实现尾部插入和删除节点功能。尝试反转链表。
栈是“后进先出”(LIFO)的数据结构,类似盘子堆叠。以下是数组实现的栈:
#include
#define MAX_SIZE 100
struct Stack {
int items[MAX_SIZE];
int top; // 栈顶索引
};
// 初始化栈
void init(struct Stack* s) {
s->top = -1;
}
// 压栈
void push(struct Stack* s, int value) {
if (s->top < MAX_SIZE - 1) {
s->items[++s->top] = value;
} else {
printf("栈溢出!\n");
}
}
// 出栈
int pop(struct Stack* s) {
if (s->top >= 0) {
return s->items[s->top--];
}
printf("栈空!\n");
return -1;
}
int main() {
struct Stack s;
init(&s);
// 测试栈操作
push(&s, 1);
push(&s, 2);
printf("出栈:%d\n", pop(&s));
printf("出栈:%d\n", pop(&s));
return 0;
}
代码解析:
top
记录栈顶位置,push
和pop
操作简单。栈常用于检查括号序列是否有效(如()
、{}
、[]
):
#include
#include
#define MAX_SIZE 100
struct Stack {
char items[MAX_SIZE];
int top;
};
// 初始化栈
void init(struct Stack* s) {
s->top = -1;
}
// 压栈
void push(struct Stack* s, char c) {
if (s->top < MAX_SIZE - 1) {
s->items[++s->top] = c;
}
}
// 出栈
char pop(struct Stack* s) {
if (s->top >= 0) {
return s->items[s->top--];
}
return '\0';
}
// 检查括号是否匹配
int isValid(char* s) {
struct Stack stack;
init(&stack);
for (int i = 0; s[i] != '\0'; i++) {
if (s[i] == '(' || s[i] == '{' || s[i] == '[') {
push(&stack, s[i]); // 左括号入栈
} else {
char top = pop(&stack); // 右括号与栈顶比较
if ((s[i] == ')' && top != '(') ||
(s[i] == '}' && top != '{') ||
(s[i] == ']' && top != '[')) {
return 0; // 不匹配
}
}
}
return stack.top == -1; // 栈空表示匹配
}
int main() {
char str[100];
printf("请输入括号序列:");
scanf("%s", str);
printf(isValid(str) ? "有效\n" : "无效\n");
return 0;
}
代码解析:
练习建议:扩展支持多类型括号(如<>
)。尝试统计无效括号的位置。
队列是“先进先出”(FIFO)的数据结构,类似排队。以下是数组实现的队列:
#include
#define MAX_SIZE 100
struct Queue {
int items[MAX_SIZE];
int front, rear; // 队首和队尾
};
// 初始化队列
void init(struct Queue* q) {
q->front = 0;
q->rear = -1;
}
// 入队
void enqueue(struct Queue* q, int value) {
if (q->rear < MAX_SIZE - 1) {
q->items[++q->rear] = value;
} else {
printf("队列已满!\n");
}
}
// 出队
int dequeue(struct Queue* q) {
if (q->front <= q->rear) {
return q->items[q->front++];
}
printf("队列为空!\n");
return -1;
}
int main() {
struct Queue q;
init(&q);
// 测试队列操作
enqueue(&q, 1);
enqueue(&q, 2);
printf("出队:%d\n", dequeue(&q));
printf("出队:%d\n", dequeue(&q));
return 0;
}
代码解析:
front
和rear
跟踪队首和队尾。模拟银行排队叫号系统:
#include
#define MAX_SIZE 100
struct Queue {
int items[MAX_SIZE];
int front, rear;
};
// 初始化队列
void init(struct Queue* q) {
q->front = 0;
q->rear = -1;
}
// 入队
void enqueue(struct Queue* q, int value) {
if (q->rear < MAX_SIZE - 1) {
q->items[++q->rear] = value;
} else {
printf("队列已满!\n");
}
}
// 出队
int dequeue(struct Queue* q) {
if (q->front <= q->rear) {
return q->items[q->front++];
}
printf("队列为空!\n");
return -1;
}
int main() {
struct Queue q;
init(&q);
int number = 1; // 号码从1开始
// 菜单循环
while (1) {
printf("\n1. 取号\n2. 叫号\n3. 退出\n");
printf("请选择:");
int choice;
scanf("%d", &choice);
if (choice == 3) break;
if (choice == 1) {
enqueue(&q, number);
printf("你的号码是:%d\n", number++);
} else if (choice == 2) {
int num = dequeue(&q);
if (num != -1) {
printf("请%d号客户办理业务\n", num);
}
} else {
printf("无效选项!\n");
}
}
return 0;
}
项目解析:
练习建议:实现循环队列,解决数组空间浪费问题。添加显示等待人数功能。
排序是竞赛常见问题,以下是冒泡排序:
#include
void bubbleSort(int arr[], int n) {
// 外层控制轮数
for (int i = 0; i < n - 1; i++) {
// 内层比较相邻元素
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// 交换
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main() {
int arr[] = {5, 3, 8, 1, 2};
int n = 5;
bubbleSort(arr, n);
printf("排序后:");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
代码解析:
二分查找适用于有序数组:
#include
int binarySearch(int arr[], int n, int target) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2; // 防止溢出
if (arr[mid] == target) return mid;
if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1; // 未找到
}
int main() {
int arr[] = {1, 2, 3, 4, 5};
int n = 5, target;
printf("请输入要查找的数字:");
scanf("%d", &target);
int result = binarySearch(arr, n, target);
if (result != -1) {
printf("找到在位置%d\n", result);
} else {
printf("未找到\n");
}
return 0;
}
代码解析:
mid = left + (right - left) / 2
防止整数溢出。实现一个支持排序和二分查找的通讯录:
#include
#include
#define MAX_CONTACTS 100
struct Contact {
char name[50];
char phone[20];
};
// 按姓名排序
void bubbleSort(struct Contact arr[], int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (strcmp(arr[j].name, arr[j + 1].name) > 0) {
struct Contact temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
// 二分查找姓名
int binarySearch(struct Contact arr[], int n, char* target) {
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
int cmp = strcmp(arr[mid].name, target);
if (cmp == 0) return mid;
if (cmp < 0) left = mid + 1;
else right = mid - 1;
}
return -1;
}
int main() {
struct Contact contacts[MAX_CONTACTS];
int count = 0;
// 输入联系人
printf("请输入联系人数量:");
scanf("%d", &count);
for (int i = 0; i < count; i++) {
printf("请输入第%d个联系人姓名和电话:", i + 1);
scanf("%s %s", contacts[i].name, contacts[i].phone);
}
// 排序
bubbleSort(contacts, count);
// 打印排序后的通讯录
printf("\n通讯录:\n");
for (int i = 0; i < count; i++) {
printf("%s: %s\n", contacts[i].name, contacts[i].phone);
}
// 查找
char target[50];
printf("\n请输入要查找的姓名:");
scanf("%s", target);
int result = binarySearch(contacts, count, target);
if (result != -1) {
printf("找到:%s, 电话:%s\n", contacts[result].name, contacts[result].phone);
} else {
printf("未找到\n");
}
return 0;
}
项目解析:
练习建议:增加添加和删除联系人功能。尝试用顺序查找比较效率。
问题:有n级台阶,每次可走1或2步,问有多少种走法。
#include
int climbStairs(int n) {
if (n <= 2) return n;
int dp[100];
dp[1] = 1; // 1级台阶:1种走法
dp[2] = 2; // 2级台阶:2种走法
for (int i = 3; i <= n; i++) {
dp[i] = dp[i-1] + dp[i-2]; // 当前台阶 = 前一级 + 前两级
}
return dp[n];
}
int main() {
int n;
printf("请输入台阶数:");
scanf("%d", &n);
printf("走法数量:%d\n", climbStairs(n));
return 0;
}
代码解析:
dp
记录子问题解,避免重复计算。dp[i] = dp[i-1] + dp[i-2]
,类似斐波那契数列。练习建议:优化空间复杂度到O(1),仅用两个变量存储。尝试扩展到每次走1、2或3步。
用户猜测系统生成的随机数,练习循环和条件:
#include
#include
#include
int main() {
srand(time(NULL)); // 初始化随机种子
int target = rand() % 100 + 1; // 1-100的随机数
int guess, attempts = 0;
printf("猜数字游戏(1-100)!\n");
do {
printf("请输入你的猜测:");
scanf("%d", &guess);
attempts++;
if (guess > target) {
printf("太大了!\n");
} else if (guess < target) {
printf("太小了!\n");
} else {
printf("恭喜你,猜对了!用了%d次。\n", attempts);
}
} while (guess != target);
return 0;
}
项目解析:
rand()
生成,srand(time(NULL))
确保每次不同。do-while
适合至少执行一次的场景。练习建议:限制猜测次数,添加重玩功能。
在第五章计算器基础上,添加菜单和文件存储:
#include
#include
double add(double a, double b) { return a + b; }
double subtract(double a, double b) { return a - b; }
double multiply(double a, double b) { return a * b; }
double divide(double a, double b) { return a / b; }
int main() {
FILE *fp;
double num1, num2, result;
char op;
while (1) {
printf("\n1. 计算\n2. 查看历史\n3. 退出\n");
printf("请选择:");
int choice;
scanf("%d", &choice);
if (choice == 3) break;
if (choice == 1) {
printf("请输入第一个数字:");
scanf("%lf", &num1);
printf("请输入运算符(+,-,*,/):");
scanf(" %c", &op);
printf("请输入第二个数字:");
scanf("%lf", &num2);
fp = fopen("calc_history.txt", "a");
switch (op) {
case '+':
result = add(num1, num2);
break;
case '-':
result = subtract(num1, num2);
break;
case '*':
result = multiply(num1, num2);
break;
case '/':
if (num2 != 0) {
result = divide(num1, num2);
} else {
printf("错误:除数不能为0\n");
fclose(fp);
continue;
}
break;
default:
printf("无效运算符\n");
fclose(fp);
continue;
}
printf("结果:%.2f\n", result);
fprintf(fp, "%.2f %c %.2f = %.2f\n", num1, op, num2, result);
fclose(fp);
} else if (choice == 2) {
fp = fopen("calc_history.txt", "r");
if (fp == NULL) {
printf("暂无历史记录\n");
continue;
}
char line[100];
printf("\n计算历史:\n");
while (fgets(line, sizeof(line), fp)) {
printf("%s", line);
}
fclose(fp);
} else {
printf("无效选项\n");
}
}
return 0;
}
项目解析:
练习建议:添加清除历史功能。支持三角函数运算(如sin、cos)。
管理图书信息,支持添加、查询和文件存储:
#include
#include
#define MAX_BOOKS 100
struct Book {
char title[100];
char author[50];
int id;
};
int main() {
struct Book books[MAX_BOOKS];
int count = 0;
FILE *fp;
while (1) {
printf("\n1. 添加图书\n2. 查询图书\n3. 保存到文件\n4. 从文件读取\n5. 退出\n");
printf("请选择:");
int choice;
scanf("%d", &choice);
if (choice == 5) break;
if (choice == 1) {
if (count < MAX_BOOKS) {
printf("请输入图书ID、标题和作者:");
scanf("%d %s %s", &books[count].id, books[count].title, books[count].author);
count++;
printf("添加成功!\n");
} else {
printf("库已满!\n");
}
} else if (choice == 2) {
int id;
printf("请输入图书ID:");
scanf("%d", &id);
int found = 0;
for (int i = 0; i < count; i++) {
if (books[i].id == id) {
printf("图书:%s, 作者:%s, ID:%d\n", books[i].title, books[i].author, books[i].id);
found = 1;
break;
}
}
if (!found) printf("未找到图书!\n");
} else if (choice == 3) {
fp = fopen("library.txt", "w");
for (int i = 0; i < count; i++) {
fprintf(fp, "%d %s %s\n", books[i].id, books[i].title, books[i].author);
}
fclose(fp);
printf("保存成功!\n");
} else if (choice == 4) {
fp = fopen("library.txt", "r");
count = 0;
while (fscanf(fp, "%d %s %s", &books[count].id, books[count].title, books[count].author) != EOF) {
count++;
}
fclose(fp);
printf("读取成功!\n");
} else {
printf("无效选项!\n");
}
}
return 0;
}
项目解析:
练习建议:增加删除图书功能。按标题或作者排序。
本文从C语言基础(变量、流程控制、函数)到进阶(数组、指针、结构体),再到初阶数据结构(链表、栈、队列),通过详细讲解和代码示例,带你逐步掌握编程核心知识。算法竞赛案例和实战项目帮助你将理论应用于实践,培养解决实际问题的能力。
malloc
、free
)。string.h
、math.h
等库函数。编程是一门需要耐心和实践的技能。每天写几行代码,尝试新项目,调试错误,你会逐渐从新手成长为高手。坚持下去,编程的世界将为你敞开无限可能!