1. C语言基础概述
C语言作为一门经典的编程语言,自1972年由Dennis Ritchie在贝尔实验室开发以来,一直是系统编程和底层开发的首选语言。它的设计哲学强调简洁性、效率和直接访问硬件的能力,这使得C语言在操作系统、嵌入式系统和高性能计算等领域占据着不可替代的地位。
提示:学习C语言时,理解其"接近硬件"的特性非常重要。这意味着你需要对计算机的内存模型和底层工作原理有基本认知。
C语言的核心优势在于它的可移植性和高效性。一个遵循标准的C程序可以在不同平台上编译运行,而编译器生成的机器码通常非常高效。这也是为什么Linux内核、Windows操作系统核心组件以及各种嵌入式系统(如你提到的STM32开发)都大量使用C语言的原因。
2. 输入输出操作详解
2.1 标准输入输出函数
C语言本身不包含输入输出语句,而是通过标准库函数实现。最基础的I/O函数是printf()和scanf(),它们都定义在stdio.h头文件中。
c复制#include <stdio.h>
int main() {
int num;
printf("请输入一个整数: ");
scanf("%d", &num); // &符号获取变量地址
printf("你输入的数字是: %d\n", num);
return 0;
}
2.1.1 printf格式化输出
printf的格式化字符串可以包含多种占位符:
| 格式说明符 | 含义 | 示例 |
|---|---|---|
| %d | 十进制整数 | printf("%d", 123) |
| %f | 浮点数 | printf("%.2f", 3.1415) |
| %c | 单个字符 | printf("%c", 'A') |
| %s | 字符串 | printf("%s", "hello") |
| %p | 指针地址 | printf("%p", &var) |
注意:使用
scanf读取字符串时存在缓冲区溢出风险,实际开发中应使用fgets()替代。
2.2 文件输入输出
C语言通过FILE结构体和相关函数实现文件操作:
c复制FILE *f = fopen("data.txt", "r"); // 打开文件
if (f == NULL) {
perror("打开文件失败");
return 1;
}
char buffer[100];
while (fgets(buffer, sizeof(buffer), f)) { // 逐行读取
printf("%s", buffer);
}
fclose(f); // 关闭文件
常见文件模式:
- "r": 只读
- "w": 只写(清空原有内容)
- "a": 追加
- "r+": 读写(文件必须存在)
- "w+": 读写(创建或清空文件)
3. 运算符全面解析
3.1 运算符分类与优先级
C语言的运算符可分为以下几类(按优先级从高到低):
- 基本运算符:
()[].->++--(后缀) - 单目运算符:
!~++--+-*&sizeof(前缀) - 算术运算符:
*/%+- - 移位运算符:
<<>> - 关系运算符:
<<=>>===!= - 位运算符:
&^| - 逻辑运算符:
&&|| - 条件运算符:
?: - 赋值运算符:
=+=-=等 - 逗号运算符:
,
经验:当表达式复杂时,使用括号明确优先级,不要依赖记忆的优先级顺序。
3.2 特殊运算符深入
3.2.1 位运算符实战
位运算符在嵌入式开发和性能优化中特别重要:
c复制unsigned char flags = 0; // 8位标志位
// 设置第3位(从0开始)
flags |= (1 << 3);
// 检查第3位
if (flags & (1 << 3)) {
printf("第3位已设置\n");
}
// 清除第3位
flags &= ~(1 << 3);
3.2.2 条件运算符的妙用
条件运算符(?:)可以简洁地实现条件赋值:
c复制int max = (a > b) ? a : b;
这等价于:
c复制int max;
if (a > b) {
max = a;
} else {
max = b;
}
4. 流程控制结构
4.1 分支结构
4.1.1 if-else语句
c复制if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else {
grade = 'C';
}
4.1.2 switch-case语句
switch适用于多路分支,比多个if-else更清晰:
c复制switch (operator) {
case '+':
result = a + b;
break;
case '-':
result = a - b;
break;
case '*':
result = a * b;
break;
case '/':
if (b != 0) {
result = a / b;
} else {
printf("除数不能为0\n");
}
break;
default:
printf("无效运算符\n");
}
关键点:每个
case后应有break,否则会继续执行下一个case(称为"fall through")。
4.2 循环结构
4.2.1 for循环
c复制for (int i = 0; i < 10; i++) {
printf("%d ", i);
}
4.2.2 while和do-while循环
c复制// while循环
int count = 0;
while (count < 5) {
printf("%d ", count++);
}
// do-while循环(至少执行一次)
int num;
do {
printf("输入正数: ");
scanf("%d", &num);
} while (num <= 0);
4.2.3 循环控制语句
break: 立即退出循环continue: 跳过当前迭代goto: 跳转到标签处(慎用)
c复制for (int i = 0; i < 100; i++) {
if (i == 50) {
break; // 当i为50时退出循环
}
if (i % 2 == 0) {
continue; // 跳过偶数
}
printf("%d ", i);
}
5. 实用技巧与常见问题
5.1 输入验证
不安全的scanf使用:
c复制int age;
scanf("%d", &age); // 如果用户输入非数字会导致问题
更安全的替代方案:
c复制char input[100];
if (fgets(input, sizeof(input), stdin)) {
if (sscanf(input, "%d", &age) != 1) {
printf("输入无效\n");
}
}
5.2 避免常见错误
- 未初始化变量:
c复制int sum; // 未初始化
printf("%d", sum); // 未定义行为
- 数组越界:
c复制int arr[5];
arr[5] = 10; // 越界访问
- 内存泄漏:
c复制int *p = malloc(100 * sizeof(int));
// 使用后忘记free(p)
5.3 调试技巧
- 使用
assert进行断言检查:
c复制#include <assert.h>
assert(ptr != NULL); // 如果ptr为NULL,程序会中止
- 打印调试信息:
c复制#define DEBUG 1
#if DEBUG
printf("调试信息: x=%d\n", x);
#endif
- 使用调试器(如GDB):
bash复制gcc -g program.c -o program
gdb ./program
6. 综合应用实例
6.1 计算器程序
c复制#include <stdio.h>
#include <stdlib.h>
int main() {
char operator;
double num1, num2, result;
printf("输入运算符 (+, -, *, /): ");
scanf(" %c", &operator);
printf("输入两个操作数: ");
if (scanf("%lf %lf", &num1, &num2) != 2) {
printf("输入无效\n");
return 1;
}
switch (operator) {
case '+':
result = num1 + num2;
break;
case '-':
result = num1 - num2;
break;
case '*':
result = num1 * num2;
break;
case '/':
if (num2 == 0) {
printf("错误: 除数不能为0\n");
return 1;
}
result = num1 / num2;
break;
default:
printf("错误: 无效运算符\n");
return 1;
}
printf("结果: %.2lf %c %.2lf = %.2lf\n", num1, operator, num2, result);
return 0;
}
6.2 素数判断程序
c复制#include <stdio.h>
#include <stdbool.h>
bool is_prime(int n) {
if (n <= 1) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
for (int i = 3; i * i <= n; i += 2) {
if (n % i == 0) {
return false;
}
}
return true;
}
int main() {
int num;
printf("输入一个整数: ");
scanf("%d", &num);
if (is_prime(num)) {
printf("%d是素数\n", num);
} else {
printf("%d不是素数\n", num);
}
return 0;
}
在实际开发中,我发现初学者最容易犯的错误是忽略边界条件。比如在素数判断中,忘记处理1和2的特殊情况,或者在循环条件中使用i <= sqrt(n)而忘记包含math.h头文件。另一个常见陷阱是在使用scanf读取字符时,前导空格会导致意外行为,因此格式字符串中应使用" %c"而非"%c"。
