1934 lines
42 KiB
Markdown
Raw Normal View History

2022-10-13 01:19:47 +08:00
C 备忘清单
===
提供基本语法和方法的 C 快速参考备忘单。
入门
----
### hello.c
```c
#include <stdio.h>
int main() {
printf("Hello World!");
return 0;
}
```
使用 `gcc` 编译 `hello.c` 源文件
2022-10-13 01:19:47 +08:00
```bash
$ gcc -o hello hello.c
```
运行编译后的二进制文件可执行文件(`hello`)
2022-10-13 01:19:47 +08:00
```bash
$ ./hello
# 输出 => Hello World
```
### 变量
<!--rehype:wrap-class=row-span-2-->
```c
2025-01-01 21:33:24 +08:00
int myNum = 15; // 定义并初始化变量 myNum
int myNum2; // 声明变量 myNum2
myNum2 = 15; // 初始化变量 myNum2
2022-10-13 01:19:47 +08:00
2025-01-01 21:33:24 +08:00
int myNum3 = 15; // 定义并初始化变量 myNum3
myNum3 = 10; // 重新赋值 myNum3
```
2022-10-13 01:19:47 +08:00
2025-01-01 21:33:24 +08:00
定义不同类型的变量
2022-10-13 01:19:47 +08:00
2025-01-01 21:33:24 +08:00
```c
// 定义并初始化浮点数变量
float myFloatNum = 5.99;
// 定义并初始化字符变量
char myLetter = 'D';
```
2022-10-13 01:19:47 +08:00
2025-01-01 21:33:24 +08:00
变量相加
```c
int x = 5, y = 6;
int sum = x + y; // 变量相加
```
声明并初始化多个变量
```c
2022-10-13 01:19:47 +08:00
int x = 5, y = 6, z = 50;
2024-12-30 03:01:44 +08:00
int a, b, c = 10;
2025-01-01 21:33:24 +08:00
```
2024-12-30 03:01:44 +08:00
2025-01-01 21:33:24 +08:00
仅声明变量不初始化
2024-12-30 03:01:44 +08:00
2025-01-01 21:33:24 +08:00
```c
int result;
// 未初始化的变量 result 会导致不可预测的结果
2024-12-30 03:01:44 +08:00
2025-01-01 21:33:24 +08:00
result = result + 10; // 错误:未初始化的变量
// 部分编译器会警告未初始化的变量可能导致未定义行为
2022-10-13 01:19:47 +08:00
```
### 常量 Constants
2025-01-01 21:33:24 +08:00
<!--rehype:wrap-class=row-span-2-->
2023-09-10 15:34:31 +08:00
2025-01-01 21:33:24 +08:00
常量是不能被改变的值,使用常量可以使代码更清晰和安全。
2022-10-13 01:19:47 +08:00
```c
const int minutesPerHour = 60;
const float PI = 3.14;
```
最佳实践
```c
const int BIRTHYEAR = 1980;
```
2025-01-01 21:33:24 +08:00
#### **命名规范**
<!--rehype:style=text-align: left;-->
常量通常使用全大写字母,单词间用下划线分隔(如 BIRTHYEAR、MAX_LENGTH
2024-12-30 03:01:44 +08:00
2025-01-01 21:33:24 +08:00
#### **数组大小**
<!--rehype:style=text-align: left;-->
2024-12-30 03:01:44 +08:00
2025-01-01 21:33:24 +08:00
使用 `const` 定义数组大小,编译器将其作为编译时常量处理。
2024-12-30 03:01:44 +08:00
2025-01-01 21:33:24 +08:00
#### **`#define` 与 `const`**
<!--rehype:style=text-align: left;-->
2024-12-30 03:01:44 +08:00
2025-01-01 21:33:24 +08:00
- `#define`:宏常量在预处理阶段替换,不进行类型检查
- `const`:类型安全的常量,编译器可检查类型,推荐使用
#### **注意事项**
- `const` 常量在定义时必须初始化,否则会导致编译错误。
- `const` 常量的值不能被修改,任何尝试修改 `const` 常量的操作都会导致编译错误。
- 使用 `const` 常量可以提高代码的可读性和可维护性,避免魔法数字的使用。
2024-12-30 03:01:44 +08:00
2022-10-13 01:19:47 +08:00
### 注释
```c
2025-01-01 21:33:24 +08:00
// 这是一个单行注释
printf("Hello World!"); // 行内注释
2024-12-30 03:01:44 +08:00
/*
2025-01-01 21:33:24 +08:00
多行注释:
用于注释跨多行的内容
2024-12-30 03:01:44 +08:00
注意:多行注释不能嵌套,否则会导致编译错误
*/
```
2025-01-01 21:33:24 +08:00
2024-12-30 03:01:44 +08:00
**注意**:
2025-01-01 21:33:24 +08:00
- 单行注释 `//` 可以嵌套,如 `/////`
- 行内注释应避免过长,以免影响代码可读性。
- 多行注释不能嵌套,否则会导致编译错误。
2024-12-30 03:01:44 +08:00
```c
/* 这是一个多行注释的开始
2025-01-01 21:33:24 +08:00
/* 嵌套的多行注释C语言不支持 */
*/
2022-10-13 01:19:47 +08:00
```
2025-01-01 21:33:24 +08:00
<!--rehype:style=background:#ff000030;-->
2022-10-13 01:19:47 +08:00
### 打印文本
```c
printf("I am learning C.");
int testInteger = 5;
printf("Number = %d", testInteger);
float f = 5.99; // 浮点数
printf("Value = %f", f);
short a = 0b1010110; // 2 进制数字
int b = 02713; // 8 进制数字
long c = 0X1DAB83; // 16 进制数字
2025-01-01 21:33:24 +08:00
```
2022-10-13 01:19:47 +08:00
2025-01-01 21:33:24 +08:00
变量a和c分别为 `short``long` 型,所以输出必须加上对应的修饰符 `h``l`
#### 以 8 进制形式输出
```c
2022-10-13 01:19:47 +08:00
printf("a=%ho, b=%o, c=%lo\n", a, b, c);
// 输出 => a=126, b=2713, c=7325603
2025-01-01 21:33:24 +08:00
```
#### 以 10 进制形式输出
2022-10-13 01:19:47 +08:00
2025-01-01 21:33:24 +08:00
```c
2022-10-13 01:19:47 +08:00
printf("a=%hd, b=%d, c=%ld\n", a, b, c);
// 输出 => a=86, b=1483, c=1944451
2025-01-01 21:33:24 +08:00
```
2022-10-13 01:19:47 +08:00
2025-01-01 21:33:24 +08:00
#### 以 16 进制形式输出(字母小写)
```c
2022-10-13 01:19:47 +08:00
printf("a=%hx, b=%x, c=%lx\n", a, b, c);
// 输出 => a=56, b=5cb, c=1dab83
2025-01-01 21:33:24 +08:00
```
2022-10-13 01:19:47 +08:00
2025-01-01 21:33:24 +08:00
#### 以 16 进制形式输出(字母大写)
```c
2022-10-13 01:19:47 +08:00
printf("a=%hX, b=%X, c=%lX\n", a, b, c);
// 输出 => a=56, b=5CB, c=1DAB83
```
### 控制空格数
```c
int a1=20, a2=345, a3=700;
int b1=56720, b2=9999, b3=20098;
int c1=233, c2=205, c3=1;
int d1=34, d2=0, d3=23;
2024-12-30 03:01:44 +08:00
// %-9d: 十进制输出最少宽度为9左对齐
2022-10-13 01:19:47 +08:00
printf("%-9d %-9d %-9d\n", a1, a2, a3);
printf("%-9d %-9d %-9d\n", b1, b2, b3);
printf("%-9d %-9d %-9d\n", c1, c2, c3);
printf("%-9d %-9d %-9d\n", d1, d2, d3);
```
输出结果
```bash
20 345 700
56720 9999 20098
233 205 1
34 0 23
```
2025-01-01 21:33:24 +08:00
解释:`%-9d``d` 表示十进制输出,`9` 表示最少占 9 个字符宽度,`-` 表示左对齐,不使用 `-` 则默认右对齐。
对于整型数据:
2024-12-30 03:01:44 +08:00
```c
int a = 12345;
2025-01-01 21:33:24 +08:00
printf("%md", a);
2024-12-30 03:01:44 +08:00
```
2025-01-01 21:33:24 +08:00
-`m <=` 实际数据宽度,则按实际情况输出。
-`m >` 实际数据宽度,则在左边用空格补齐。
- `printf("%0md", a);` 则在左边用 `0` 补齐。
对于浮点型数据:
2024-12-30 03:01:44 +08:00
```c
float a = 1.2345;
2025-01-01 21:33:24 +08:00
printf("%m.nf", a);
// m -- 整个数据宽度n -- 小数位数
2024-12-30 03:01:44 +08:00
```
2025-01-01 21:33:24 +08:00
- 实际小数位数 `> n`,截去多余小数,注意四舍五入。
- 实际小数位数 `< n`,在小数最后补 `0`
-`m` 省略则写作 `%.n`,整数部分按实际输出,小数部分按以上规则。
-`m < n+1`,自动突破宽度限制,按实际数据输出。
-`m > n+1`,左边补空格。
### 字符串 (Strings)
2022-10-13 01:19:47 +08:00
2025-01-01 21:33:24 +08:00
在 C 语言中,字符串是以 `\0` 结尾的字符数组,而不是一种单独的数据类型。可以通过字符数组来表示字符串。
2022-10-13 01:19:47 +08:00
2025-01-01 21:33:24 +08:00
#### 定义并打印字符串
2022-10-13 01:19:47 +08:00
```c
char greetings[] = "Hello World!";
printf("%s", greetings);
```
2025-01-01 21:33:24 +08:00
#### 访问字符串中的字符
2022-10-13 01:19:47 +08:00
```c
char greetings[] = "Hello World!";
printf("%c", greetings[0]);
```
2025-01-01 21:33:24 +08:00
访问字符串 `greetings` 的第一个字符 `H`
#### 修改字符串中的字符
2022-10-13 01:19:47 +08:00
```c
char greetings[] = "Hello World!";
2025-01-01 21:33:24 +08:00
greetings[0] = 'J'; // 修改第一个字符为 'J'
2022-10-13 01:19:47 +08:00
printf("%s", greetings);
// 输出 "Jello World!"
```
2025-01-01 21:33:24 +08:00
#### 另一种创建字符串的方法
2022-10-13 01:19:47 +08:00
```c
char greetings[] = {'H','e','l','l','\0'};
printf("%s", greetings);
2025-01-01 21:33:24 +08:00
// 输出 "Hell"
2022-10-13 01:19:47 +08:00
```
2025-01-01 21:33:24 +08:00
**注意**C 语言中没有 `String` 类型,字符串是由字符数组 `char[]` 表示的,且必须以 `\0` 结尾以标识字符串的结束。
2022-10-13 01:19:47 +08:00
### 条件判断
```c
int time = 20;
if (time < 18) {
printf("再会!");
} else {
printf("晚上好!");
}
// 输出 -> "晚上好!"
2025-01-01 21:33:24 +08:00
2022-10-13 01:19:47 +08:00
int time = 22;
if (time < 10) {
printf("早上好!");
} else if (time < 20) {
printf("再会!");
} else {
printf("晚上好!");
}
// 输出 -> "晚上好!"
2024-12-30 03:01:44 +08:00
int time = 10;
if (time > 8) {
//再嵌套一个if
if (time < 12) {
printf("中午好!")
}
}
// 输出 -> "中午好!"
2022-10-13 01:19:47 +08:00
```
2025-01-01 21:33:24 +08:00
#### 说明
- `if` 语句用于根据条件执行代码块。
- `else` 语句在 `if` 条件不满足时执行。
- `else if` 语句用于检查多个条件。
- 可以嵌套 `if` 语句以检查多个条件。
2022-10-13 01:19:47 +08:00
### 三元运算符
2025-01-01 21:33:24 +08:00
三元运算符(`? :`)是一种简洁的条件判断方式,用于根据条件选择表达式的值。由三个部分组成:
- 条件表达式
- 条件为真时的结果
- 条件为假时的结果
基本语法:`(条件) ? 表达式1 : 表达式2;`
如果 `条件` 为真,则返回 `表达式1`,否则返回 `表达式2`
#### 示例
2022-10-13 01:19:47 +08:00
```c
int time = 20;
2025-01-01 21:33:24 +08:00
(time < 18) ? printf("再会")
: printf("晚上好!");
2024-12-30 03:01:44 +08:00
// 输出 -> "晚上好!"
```
2025-01-01 21:33:24 +08:00
嵌套使用示例(不建议过多嵌套):
2024-12-30 03:01:44 +08:00
```c
int time = 22;
2025-01-01 21:33:24 +08:00
printf((time < 10) ? "早上好"
: (time < 20) ? "再会"
: "晚上好!");
2024-12-30 03:01:44 +08:00
// 输出 -> "晚上好!"
2022-10-13 01:19:47 +08:00
```
### Switch
```c
int day = 4;
switch (day) {
case 3: printf("周三"); break;
case 4: printf("周四"); break;
2025-01-01 21:33:24 +08:00
default: printf("期待周末");
2022-10-13 01:19:47 +08:00
}
2025-01-01 21:33:24 +08:00
// 输出 -> "周四"
```
2025-01-01 21:33:24 +08:00
#### 说明
- `switch` 语句根据表达式的值跳转到匹配的 `case` 标签。
- 匹配到 `case` 后执行相应代码,并通过 `break` 跳出 `switch`
- 如果没有匹配到任何 `case`,则执行 `default` 语句(如果存在)。
#### 注意事项
- `switch` 表达式可以是整型、字符型和枚举型。
- `case` 后的常量表达式值不能相同。
- `case` 后可以有多个语句,不需要 `{ }` 括起来。
- `case``default` 语句的顺序不影响程序执行结果。
- `break` 语句用于结束 `switch`,如果没有 `break`,程序会继续执行下一个 `case`
#### 示例
```c
int day = 3;
switch (day) {
case 3: printf("周三");
case 4: printf("周四"); break;
2025-01-01 21:33:24 +08:00
default: printf("期待周末");
}
2025-01-01 21:33:24 +08:00
// 输出 -> "周三周四"
2022-10-13 01:19:47 +08:00
```
### While 循环
```c
int i = 0;
while (i < 5) {
printf("%d\n", i);
i++;
}
2025-01-01 21:33:24 +08:00
// 输出 -> 0 1 2 3 4
```
#### 解释
- `while` 循环首先检查条件 `i < 5` 是否为真。
- 如果为真,程序进入循环体,执行打印操作并增加 `i` 的值。
- 循环会继续进行,直到 `i` 达到 5此时条件 `i < 5` 不再为真,循环结束。
#### 打印 1 到 10 的数字
```c
int i = 1;
while (i <= 10) {
printf("%d\n", i);
i++;
}
// 输出 -> 1 2 3 4 5 6 7 8 9 10
```
#### 打印偶数
```c
int i = 0;
while (i <= 10) {
if (i % 2 == 0) {
printf("%d\n", i);
}
i++;
}
// 输出 -> 0 2 4 6 8 10
2022-10-13 01:19:47 +08:00
```
2025-01-01 21:33:24 +08:00
#### 无限循环(需手动终止)
```c
int i = 0;
while (1) {
printf("无限循环\n");
i++;
if (i == 5) break; // 添加条件以退出循环
}
// 输出 -> 无限循环 (打印 5 次)
```
2022-10-13 01:19:47 +08:00
### Do/While 循环
```c
int i = 0;
do {
printf("%d\n", i);
i++;
} while (i < 5);
2025-01-01 21:33:24 +08:00
// 输出 -> 0 1 2 3 4
```
#### 解释
- `do/while` 循环至少执行一次循环体,因为条件检查在循环体执行之后进行。
- 首先执行 `do` 中的代码,然后检查条件 `i < 5` 是否为真。
- 如果为真,继续执行循环;如果为假,则退出循环。
#### 打印从 5 开始的数字,直到条件不满足
```c
int i = 5;
do {
printf("%d\n", i); // 输出 5
i++;
} while (i < 5);
2025-01-01 21:33:24 +08:00
// 输出 -> 5
```
#### 计算 1 到 10 的和
```c
int i = 1;
int sum = 0;
do {
sum += i;
i++;
} while (i <= 10);
printf("Sum: %d\n", sum);
// 输出 -> Sum: 55
2022-10-13 01:19:47 +08:00
```
### For 循环
2025-01-01 21:33:24 +08:00
```c
for (表达式1; 表达式2; 表达式3) {
循环体语句;
}
```
2025-01-01 21:33:24 +08:00
- 表达式1设置初始条件只执行一次。
- 表达式2循环条件表达式每次循环前检查。
- 表达式3循环体执行后的调整操作。
示例:
2022-10-13 01:19:47 +08:00
```c
int i;
for (i = 0; i < 5; i++) {
printf("%d\n", i);
}
2025-01-01 21:33:24 +08:00
// 输出 -> 0 1 2 3 4
2022-10-13 01:19:47 +08:00
```
2025-01-01 21:33:24 +08:00
#### 注意事项
2025-01-01 21:33:24 +08:00
- `for` 语句的三个表达式不是必须的。
- 当条件表达式表达式2为假时`for` 循环结束。
- 可以在循环体内使用 `break``continue``goto` 语句。
- 如果表达式2为空则表示无限循环`for(;;)` 相当于 `while(1)`
2025-01-01 21:33:24 +08:00
#### 变体
2025-01-01 21:33:24 +08:00
- `for (i = m; i < n; i++)``i = m` 开始到 `i = n-1`,循环 `n - m` 次。
- `for (i = m; i <= n; i++)``i = m``i = n`,循环 `n - m + 1` 次。
2025-01-01 21:33:24 +08:00
### 跳出循环 (Break/Continue/Goto)
<!--rehype:wrap-class=row-span-3-->
2025-01-01 21:33:24 +08:00
#### `break` 语句
2022-10-13 01:19:47 +08:00
2025-01-01 21:33:24 +08:00
跳出当前循环或 `switch` 语句,执行后续代码。
2022-10-13 01:19:47 +08:00
```c
int i;
for (i = 0; i < 10; i++) {
if (i == 4) {
break;
}
printf("%d\n", i);
}
2025-01-01 21:33:24 +08:00
// 输出 -> 0 1 2 3
2022-10-13 01:19:47 +08:00
```
2025-01-01 21:33:24 +08:00
#### `continue` 语句
2025-01-01 21:33:24 +08:00
跳过当前循环的剩余语句,直接进入下一次循环。
2022-10-13 01:19:47 +08:00
```c
int i;
for (i = 0; i < 10; i++) {
if (i == 4) {
continue;
}
printf("%d\n", i);
}
2025-01-01 21:33:24 +08:00
// 输出 -> 0 1 2 3 5 6 7 8 9
2022-10-13 01:19:47 +08:00
```
2025-01-01 21:33:24 +08:00
#### `goto` 语句
2022-10-13 01:19:47 +08:00
2025-01-01 21:33:24 +08:00
无条件跳转到指定标签位置。
2025-01-01 21:33:24 +08:00
```c
2022-10-13 01:19:47 +08:00
int i = 0;
while (i < 10) {
if (i == 4) {
goto skip; // 跳转到 skip 标签
2022-10-13 01:19:47 +08:00
}
printf("%d\n", i);
i++;
}
skip:
printf("Exited the loop at i = %d\n", i);
2025-01-01 21:33:24 +08:00
// 输出 -> 0 1 2 3 Exited the loop at i = 4
2022-10-13 01:19:47 +08:00
```
2025-01-01 21:33:24 +08:00
#### 注意事项
2022-10-13 01:19:47 +08:00
2025-01-01 21:33:24 +08:00
- 标签必须在当前函数内定义,命名规则与变量相同。
- `goto` 语句应慎用,避免代码逻辑混乱,通常使用循环或条件语句代替。
2022-10-13 01:19:47 +08:00
2025-01-01 21:33:24 +08:00
#### 设置数组大小
2022-10-13 01:19:47 +08:00
```c
2025-01-01 21:33:24 +08:00
// 声明一个由四个整数组成的数组
2022-10-13 01:19:47 +08:00
int myNumbers[4];
2025-01-01 21:33:24 +08:00
2022-10-13 01:19:47 +08:00
// 添加元素
myNumbers[0] = 25;
myNumbers[1] = 50;
myNumbers[2] = 75;
myNumbers[3] = 100;
```
### 枚举 Enum
<!--rehype:wrap-class=col-span-2-->
```c
enum week { Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
```
定义枚举变量
```c
enum week a, b, c;
enum week { Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } a, b, c;
```
有了枚举变量,就可以把列表中的值赋给它
```c
enum week { Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
enum week a = Mon, b = Wed, c = Sat;
// 或者
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } a = Mon, b = Wed, c = Sat;
```
### 枚举示例应用
```c
enum week {Mon = 1, Tues, Wed, Thurs} day;
scanf("%d", &day);
switch(day){
case Mon: puts("Monday"); break;
case Tues: puts("Tuesday"); break;
case Wed: puts("Wednesday"); break;
case Thurs: puts("Thursday"); break;
default: puts("Error!");
}
```
2025-01-01 21:33:24 +08:00
### 数组 Arrays
#### 定义和访问数组元素
```c
int myNumbers[] = {25, 50, 75, 100};
printf("%d", myNumbers[0]); // 输出 25
```
#### 更改数组元素
```c
int myNumbers[] = {25, 50, 75, 100};
myNumbers[0] = 33;
printf("%d", myNumbers[0]); // 输出 33
```
#### 循环遍历数组
```c
int myNumbers[] = {25, 50, 75, 100};
int i;
for (i = 0; i < 4; i++) {
printf("%d\n", myNumbers[i]);
}
// 输出 -> 25 50 75 100
```
2022-10-13 01:19:47 +08:00
### 用户输入
```c
// 创建一个整数变量来存储我们从用户那里得到的数字
int myNum;
// 要求用户输入一个数字
printf("请输入一个数字: \n");
// 获取并保存用户输入的号码
scanf("%d", &myNum);
// 输出用户输入的数字
printf("您输入的数字: %d", myNum);
```
### 用户输入字符串
```c
// 创建一个字符串
char firstName[30];
// 要求用户输入一些文本
printf("输入您的名字: \n");
// 获取并保存文本
scanf("%s", firstName);
// 输出文本
printf("Hello %s.", firstName);
```
### 内存地址
2025-01-01 21:33:24 +08:00
创建变量时,会为该变量分配一个内存地址。
2022-10-13 01:19:47 +08:00
```c
int myAge = 43;
printf("%p", &myAge);
2025-01-01 21:33:24 +08:00
// 输出 myAge 的内存地址例如0x7ffe5367e044
2022-10-13 01:19:47 +08:00
```
2025-01-01 21:33:24 +08:00
要访问变量的内存地址,请使用引用运算符 (`&`)。
2022-10-13 01:19:47 +08:00
### 创建指针
<!--rehype:wrap-class=col-span-2-->
```c
2025-01-01 21:33:24 +08:00
int myAge = 43; // 一个 int 变量
int *ptr = &myAge; // 创建指向 myAge 的指针
2022-10-13 01:19:47 +08:00
2025-01-01 21:33:24 +08:00
printf("%d\n", myAge); // 输出 myAge 的值 -> 43
printf("%p\n", &myAge); // 输出 myAge 的内存地址例如0x7ffe5367e044
printf("%p\n", ptr); // 输出指针 ptr 的值(即 myAge 的内存地址)
printf("%d\n", *ptr); // 通过指针访问 myAge 的值 -> 43
2022-10-13 01:19:47 +08:00
```
### 取消引用
```c
int myAge = 43; // 变量声明
int* ptr = &myAge; // 指针声明
// 参考:用指针输出 myAge 的
// 内存地址0x7ffe5367e044
printf("%p\n", ptr);
// 取消引用:用指针输出 myAge 的值 (43)
printf("%d\n", *ptr);
```
2025-01-01 21:33:24 +08:00
### 指针变量
<!--rehype:wrap-class=col-span-2-->
```c
int myAge = 43; // 一个 int 变量
int* ptr = &myAge; // 名为 ptr 的指针变量,用于存储 myAge 的地址
printf("%d\n", myAge); // 输出 myAge (43) 的值
printf("%p\n", &myAge); // 输出 myAge 的内存地址0x7ffe5367e044
printf("%p\n", ptr); // 用指针0x7ffe5367e044输出myAge的内存地址
```
2022-10-13 01:19:47 +08:00
运算符
---
### 算术运算符
```c
int myNum = 100 + 50;
int sum1 = 100 + 50; // 150 (100 + 50)
int sum2 = sum1 + 250; // 400 (150 + 250)
int sum3 = sum2 + sum2; // 800 (400 + 400)
```
----
| Operator | Name | Description | Example |
| -------- | -------- | -------- | -------- |
| `+` | 加 | 将两个值相加 | `x + y` |
| `-` | 减 | 从另一个值中减去一个值 | `x - y` |
| `*` | 乘 | 将两个值相乘 | `x * y` |
| `/` | 除 | 将一个值除以另一个 | `x / y` |
| `%` | 取模 | 返回除法余数 | `x % y` |
| `++` | 增量 | 将变量的值增加 1 | `++` |
| `--` | 乘量 | 将变量的值减 1 | `--x` |
### 赋值运算符
| 符号 | 示例 | 如同 |
| -------- | ------- | ---------- |
| `=` | x `=` 5 | x `=` 5 |
| `+=` | x `+=` 3 | x `=` x `+` 3 |
| `-=` | x `-=` 3 | x `=` x `-` 3 |
| `*=` | x `*=` 3 | x `=` x `*` 3 |
| `/=` | x `/=` 3 | x `=` x `/` 3 |
| `%=` | x `%=` 3 | x `=` x `%` 3 |
| `&=` | x `&=` 3 | x `=` x `&` 3 |
| `\|=` | x `\|=` 3 | x `=` x `\|` 3 |
| `^=` | x `^=` 3 | x `=` x `^` 3 |
| `>>=` | x `>>=` 3 | x `=` x `>>` 3 |
| `<<=` | x `<<=` 3 | x `=` x `<<` 3 |
### 比较运算符
```c
int x = 5;
int y = 3;
printf("%d", x > y);
// 返回 1因为 5 大于 3
```
----
| 符号 | 名称 | 示例 |
| -------- | ------- | ------- |
| `==` | 等于 | x `==` y |
| `!=` | 不等于 | x `!=` y |
| `>` | 大于 | x `>` y |
| `<` | 小于 | x `<` y |
| `>=` | 大于或等于 | x `>=` y |
| `<=` | 小于或等于 | x `<=` y |
比较运算符用于比较两个值
### 逻辑运算符
<!--rehype:wrap-class=col-span-2-->
| 符号 | 名称 | 说明 | 示例 |
| -------- | -------- | -------- | -------- |
| `&&` | `与`逻辑 | 如果两个语句都为真,则返回真 | `x < 5 && x < 10` |
| `\|\|` | `或`逻辑 | 如果其中一个语句为真,则返回真 | `x < 5 \|\| x < 4` |
| `!` | `非`逻辑 | 反转结果,如果结果为真则返回假 | `!(x < 5 && x < 10)` |
### 运算符示例
<!--rehype:wrap-class=row-span-2-->
```c
unsigned int a = 60; /* 60 = 0011 1100 */
unsigned int b = 13; /* 13 = 0000 1101 */
int c = 0;
c = a & b; /* 12 = 0000 1100 */
printf("Line 1 - c 的值是 %d\n", c );
c = a | b; /* 61 = 0011 1101 */
printf("Line 2 - c 的值是 %d\n", c );
c = a ^ b; /* 49 = 0011 0001 */
printf("Line 3 - c 的值是 %d\n", c );
c = ~a; /*-61 = 1100 0011 */
printf("Line 4 - c 的值是 %d\n", c );
c = a << 2; /* 240 = 1111 0000 */
printf("Line 5 - c 的值是 %d\n", c );
c = a >> 2; /* 15 = 0000 1111 */
printf("Line 6 - c 的值是 %d\n", c );
```
### 位运算符
<!--rehype:wrap-class=col-span-2-->
运算符 | 描述 | 实例
:- |:- |:-
2022-10-13 01:19:47 +08:00
`&` | 按位与操作,按二进制位进行"与"运算 | `(A & B)` 将得到 `12` 即为 0000 1100
`\|` | 按位或运算符,按二进制位进行"或"运算 | `(A \| B)` 将得到 `61` 即为 0011 1101
`^` | 异或运算符,按二进制位进行"异或"运算 | `(A ^ B)` 将得到 `49` 即为 0011 0001
`~` | 取反运算符,按二进制位进行"取反"运算 | `(~A)` 将得到 `-61` 即为 1100 0011
`<<` | 二进制左移运算符 | `A << 2` 将得到 `240` 即为 1111 0000
`>>` | 二进制右移运算符 | `A >> 2` 将得到 `15` 即为 0000 1111
2022-10-13 01:19:47 +08:00
数据类型 Data Types
---
### 基本数据类型
<!--rehype:wrap-class=col-span-2-->
| 数据类型 | 大小 Size | 范围 Range | 描述 Description |
| ----- | ----- | ----- | ----- |
| `char` | 1 字节 | `128` ~ `127` | 单个字符/字母/数字/ASCII |
| `signed char` | 1 字节 | `128` ~ `127` | - |
| `unsigned char` | 1 字节 | `0` ~ `255` | - |
| `int` | `2``4` 字节 | `32,768` ~ `32,767` | 存储整数 |
| `signed int` | 2 字节 | `32,768` ~ `32,767` | |
| `unsigned int` | 2 字节 | `0` ~ `65,535` | |
| `short int` | 2 字节 | `32,768` ~ `32,767` | |
| `signed short int` | 2 字节 | `32,768` ~ `32,767` | |
| `unsigned short int` | 2 字节 | `0` ~ `65,535` | |
| `long int` | 4 字节 | `-2,147,483,648` ~ `2,147,483,647` | |
| `signed long int` | 4 字节 | `-2,147,483,648` ~ `2,147,483,647` | |
| `unsigned long int` | 4 字节 | `0` ~ `4,294,967,295` | |
| `float` | 4 字节 | | |
| `double` | 8 字节 | | |
| `long double` | 10 字节 | | |
### 数据类型
```c
// 创建变量
int myNum = 5; // 整数
float myFloatNum = 5.99; // 浮点数
char myLetter = 'D'; // 字符串
// 高精度浮点数据或数字
double myDouble = 3.2325467;
// 打印输出变量
printf("%d\n", myNum);
printf("%f\n", myFloatNum);
printf("%c\n", myLetter);
printf("%lf\n", myDouble);
```
----
2022-10-13 01:19:47 +08:00
数据类型 | 说 明
:- | :-
`char` | 字符型
`short` | 短整型
`int` | 整型
`long` | 长整型
`float` | 单精度浮点型
`double` | 双精度浮点型
`void` | 无类型
### 基本格式说明符
| 格式说明符 | 数据类型 |
| ----- | ----- |
| `%d``%i` | `int` 整数 |
| `%f` | `float` 单精度的十进制类型 |
| `%lf` | `double` 高精度浮点数据或数字 |
| `%c` | `char` 字符 |
| `%s` | 用于 `strings` 字符串 |
### 基本格式说明符
| | short | int | long |
| ---- | ---- | ---- | ---- |
| 8 进制 | `%ho` | `%o` | `%lo` |
| 10 进制 | `%hd` | `%d` | `%ld` |
| 16 进制 | `%hx` / `%hX` | `%x` / `%X` | `%lx` / `%lX` |
### 数据格式示例
```c
int myNum = 5;
float myFloatNum = 5.99; // 浮点数
char myLetter = 'D'; // 字符串
// 打印输出变量
printf("%d\n", myNum);
printf("%f\n", myFloatNum);
printf("%c\n", myLetter);
```
预处理器
---
### 预处理器指令
<!--rehype:wrap-class=row-span-2-->
指令 | 描述
---- | ----
`#define` | 定义宏
`#include` | 包含一个源代码文件
`#undef` | 取消已定义的宏
`#ifdef` | 如果宏已经定义,则返回真
`#ifndef` | 如果宏没有定义,则返回真
`#if` | 如果给定条件为真,则编译下面代码
`#else` | `#if` 的替代方案
2022-10-13 01:19:47 +08:00
`#elif` | 如果 `#if` 条件为假,当前条件为`真`
`#endif` | 结束一个 `#if……#else` 条件编译块
`#error` | 当遇到标准错误时,输出错误消息
`#pragma` | 使用标准化方法,向编译器发布特殊的命令到编译器中
2022-10-13 01:19:47 +08:00
```c
// 所有的 MAX_ARRAY_LENGTH 替换为 20
#define MAX_ARRAY_LENGTH 20
// 系统库中获取 stdio.h
#include <stdio.h>
// 本地目录中获取 myheader.h
#include "myheader.h"
#undef FILE_SIZE
#define FILE_SIZE 42 // 取消已定义并定义为 42
```
### 预定义宏
<!--rehype:wrap-class=row-span-2-->
宏 | 描述
---- | ----
`__DATE__` | 当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量
`__TIME__` | 当前时间,一个以 "HH:MM:SS" 格式表示的字符常量
`__FILE__` | 这会包含当前文件名,一个字符串常量
`__LINE__` | 这会包含当前行号,一个十进制常量
`__STDC__` | 当编译器以 `ANSI` 标准编译时,则定义为 `1`
<!--rehype:className=style-list-->
`ANSI C` 定义了许多宏,您可以使用这些宏,但是不能直接修改这些预定义的宏
#### 预定义宏示例
```c
#include <stdio.h>
int main() {
printf("File :%s\n", __FILE__);
printf("Date :%s\n", __DATE__);
printf("Time :%s\n", __TIME__);
printf("Line :%d\n", __LINE__);
printf("ANSI :%d\n", __STDC__);
}
```
### 宏延续运算符(\
一个宏通常写在一个单行上。
```c
#define message_for(a, b) \
printf(#a " 和 " #b ": 我们爱你!\n")
```
如果宏太长,一个单行容纳不下,则使用宏延续运算符 `\`
### 字符串常量化运算符(#
```c
#include <stdio.h>
#define message_for(a, b) \
printf(#a " 和 " #b ": 我们爱你!\n")
int main(void) {
message_for(Carole, Debra);
return 0;
}
```
当上面的代码被编译和执行时,它会产生下列结果:
```
Carole 和 Debra: 我们爱你!
```
需要把一个宏的参数转换为字符串常量时,使用字符串常量化运算符 `#`
### 标记粘贴运算符(##
```c
#include <stdio.h>
2024-04-09 17:15:50 +08:00
#define tokenpaster(n) \
printf ("token" #n " = %d", token##n)
2022-10-13 01:19:47 +08:00
int main(void){
int token34 = 40;
tokenpaster(34);
return 0;
}
```
### defined() 运算符
```c
#include <stdio.h>
#if !defined (MESSAGE)
#define MESSAGE "You wish!"
#endif
int main(void) {
2024-04-09 17:15:50 +08:00
printf("信息如下: %s\n", \
MESSAGE);
return 0;
2022-10-13 01:19:47 +08:00
}
```
### 参数化的宏
```c
int square(int x) {
return x * x;
}
```
宏重写上面的代码,如下:
```c
#define square(x) ((x) * (x))
```
宏名称和左圆括号之间不允许有空格
```c
#include <stdio.h>
#define MAX(x,y) ((x) > (y) ? (x) : (y))
int main(void) {
2024-04-09 17:15:50 +08:00
printf("20 到 10 之间的最大值是 %d\n", \
MAX(10, 20));
return 0;
2022-10-13 01:19:47 +08:00
}
```
2024-12-30 03:01:44 +08:00
### Warning 和 Error
<!--rehype:wrap-class=row-span-2-->
2025-01-01 21:33:24 +08:00
在 C 语言中警告Warning和错误Error是编译器用于标识代码潜在问题或阻止代码编译的机制。
#### 警告
2024-12-30 03:01:44 +08:00
警告提示代码中可能存在的问题,但不会阻止代码编译。处理警告可以提升代码质量和可移植性。
2025-01-01 21:33:24 +08:00
#### **常见警告示例**
2024-12-30 03:01:44 +08:00
2025-01-01 21:33:24 +08:00
未使用的变量
```c
int x; printf("%d", x);
```
类型隐式转换(可能导致数据丢失)
```c
int x = 3.14; // 浮点数被隐式转换
int a = 2147483647 + 1; // 可能溢出
```
函数声明与定义不匹配
#### 错误
错误会阻止代码编译,必须修复才能继续编译。
2024-12-30 03:01:44 +08:00
**常见错误示例**
2025-01-01 21:33:24 +08:00
语法错误(如缺少分号)
```c
int x = 1
```
函数定义冲突
```c
2024-12-30 03:01:44 +08:00
void func(int);
void func(double);
```
2025-01-01 21:33:24 +08:00
函数或变量未定义
```c
y = 10; printf("%d", y);
```
头文件缺失或冲突
```c
#include <unknown.h>
```
### 使用编译器指令控制警告和错误
<!--rehype:wrap-class=col-span-2-->
#### 抑制警告
2024-12-30 03:01:44 +08:00
可以使用编译器选项来关闭特定的警告,例如在 GCC 中:
2025-01-01 21:33:24 +08:00
```sh
2024-12-30 03:01:44 +08:00
gcc -w file.c # 禁用所有警告
gcc -Wall file.c # 启用所有常见警告
gcc -Werror file.c # 将警告视为错误
```
2025-01-01 21:33:24 +08:00
#### 使用 `#pragma` 控制警告
在某些编译器中,可以使用 `#pragma` 指令启用或禁用警告:
```c
2024-12-30 03:01:44 +08:00
#include <stdio.h>
#pragma warning(disable : 4996) // 禁用警告(适用于 MSVC 编译器)
int main() {
printf("Hello, world!");
return 0;
}
```
2025-01-01 21:33:24 +08:00
#### 总结
2024-12-30 03:01:44 +08:00
| 区别点 | Warning警告 | Error错误 |
|------------------|----------------------------------------|----------------------------------------|
| 严重程度 | 程序可继续编译,但可能存在隐患 | 编译无法完成,必须修复 |
| 编译结果 | 生成可执行文件 | 无法生成可执行文件 |
| 触发原因 | 潜在问题,例如隐式转换或未使用的变量 | 语法或语义错误,例如语法错误或未定义变量 |
| 修复必要性 | 可选择修复,但建议修复以避免潜在问题 | 必须修复才能继续编译 |
| 编译器选项调整 | 可以忽略或转换为错误(如 `-Werror` | 无法调整,必须修复 |
2022-10-13 16:15:18 +08:00
函数
----
### 函数声明和定义
<!--rehype:wrap-class=row-span-2-->
```c
int main() {
printf("Hello World!");
return 0;
}
```
函数由两部分组成
```c
void myFunction() { // 声明 declaration
// 函数体(要执行的代码)(definition)
}
```
----
2022-10-13 16:15:18 +08:00
- `Declaration` 声明函数名称、返回类型和参数 _(如果有)_
- `Definition` 函数体 _(要执行的代码)_
----
2022-10-13 16:15:18 +08:00
```c
// 函数声明
void myFunction();
// 主要方法
int main() {
myFunction(); // --> 调用函数
return 0;
}
void myFunction() {// 函数定义
printf("晚上好!");
}
```
### 调用函数
```c
// 创建函数
void myFunction() {
printf("晚上好!");
}
int main() {
myFunction(); // 调用函数
myFunction(); // 可以被多次调用
return 0;
}
// 输出 -> "晚上好!"
// 输出 -> "晚上好!"
```
### 函数参数
```c
void myFunction(char name[]) {
printf("Hello %s\n", name);
}
int main() {
myFunction("Liam");
myFunction("Jenny");
return 0;
}
// Hello Liam
// Hello Jenny
```
### 多个参数
```c
void myFunction(char name[], int age) {
printf("你好 %s 你 %d 岁了。\n",name,age);
}
int main() {
myFunction("Liam", 3);
myFunction("Jenny", 14);
return 0;
}
// 你好 Liam 你 3 岁了。
// 你好 Jenny 你 14 岁了。
```
### 返回值
<!--rehype:wrap-class=row-span-2-->
```c
int myFunction(int x) {
return 5 + x;
}
int main() {
printf("结果: %d", myFunction(3));
return 0;
}
// 输出 8 (5 + 3)
```
两个参数
```c
int myFunction(int x, int y) {
return x + y;
}
int main() {
printf("结果: %d", myFunction(5, 3));
// 将结果存储在变量中
int result = myFunction(5, 3);
printf("结果 = %d", result);
return 0;
}
// 结果: 8 (5 + 3)
// 结果 = 8 (5 + 3)
```
### 递归示例
```c
int sum(int k);
int main() {
int result = sum(10);
printf("%d", result);
return 0;
}
int sum(int k) {
if (k > 0) {
return k + sum(k - 1);
} else {
return 0;
}
}
```
### 数学函数
```c
#include <math.h>
printf("%f", sqrt(16)); // 平方根
printf("%f", ceil(1.4)); // 四舍五入 (入)
printf("%f", floor(1.4)); // 四舍五入 (舍)
printf("%f", pow(4, 3)); // x(4)的y(3)次方
```
----
2022-10-13 16:15:18 +08:00
- `abs(x)` 绝对值
- `acos(x)` 反余弦值
- `asin(x)` 反正弦值
- `atan(x)` 反正切
- `cbrt(x)` 立方根
- `cos(x)` 余弦
- `exp(x)` Ex 的值
- `sin(x)` x 的正弦值
- `tan(x)` 角度的正切
<!--rehype:className=cols-2-->
Structures 结构
---
### 创建结构
```c
struct MyStructure { // 结构声明
int myNum; // 成员int 变量)
char myLetter; // 成员char 变量)
}; // 用分号结束结构
```
创建一个名为 `s1` 的结构变量
```c {7}
struct myStructure {
int myNum;
char myLetter;
};
int main() {
struct myStructure s1;
return 0;
}
```
### 结构中的字符串
```c {9}
struct myStructure {
int myNum;
char myLetter;
char myString[30]; // String
};
int main() {
struct myStructure s1;
strcpy(s1.myString, "Some text");
// 打印值
printf("我字符串: %s", s1.myString);
return 0;
}
```
使用 `strcpy` 函数为字符串赋值
### 访问结构成员
<!--rehype:wrap-class=row-span-2-->
```c {11,12,16}
// 创建一个名为 myStructure 的结构
struct myStructure {
int myNum;
char myLetter;
};
int main() {
// 创建一个名为 s1 的 myStructure 结构变量
struct myStructure s1;
// 为 s1 的成员赋值
s1.myNum = 13;
s1.myLetter = 'B';
// 创建一个名为 s2 的 myStructure 结构变量
// 并为其赋值
struct myStructure s2 = {13, 'B'};
// 打印值
printf("My number: %d\n", s1.myNum);
printf("My letter: %c\n", s1.myLetter);
return 0;
}
```
创建不同的结构变量
```c
struct myStructure s1;
struct myStructure s2;
// 为不同的结构变量赋值
s1.myNum = 13;
s1.myLetter = 'B';
s2.myNum = 20;
s2.myLetter = 'C';
```
### 复制结构
```c {6}
struct myStructure s1 = {
13, 'B', "Some text"
};
struct myStructure s2;
s2 = s1;
```
示例中,将 `s1` 的值复制到 `s2`
### 修改值
2022-10-13 16:31:20 +08:00
```c {6,7}
2022-10-13 16:15:18 +08:00
// 创建一个结构变量并为其赋值
struct myStructure s1 = {
13, 'B'
};
// 修改值
s1.myNum = 30;
s1.myLetter = 'C';
// 打印值
printf("%d %c %s",
s1.myNum,
s1.myLetter);
```
文件处理
---
### 文件处理函数
函数 | 描述 Description
---- | ----
`fopen()` | `打开`新文件或现有文件
`fprintf()` | 将数据`写入`文件
`fscanf()` | 从文件中`读取`数据
`fputc()` | 将一个字符`写入`文件
`fgetc()` | 从文件中`读取`一个字符
`fclose()` | `关闭`文件
`fseek()` | 将文件指针设置到`给定位置`
`fputw()` | 将整数`写入`文件
`fgetw()` | 从文件中`读取`一个整数
`ftell()` | 返回当前`位置`
`rewind()` | 将文件指针设置为文件的开头
C 库中有许多函数可以`打开`/`读取`/`写入`/`搜索``关闭`文件
### 打开模式参数
模式 Mode | 描述 Description
---- | ----
`r` | 以`读取`模式打开一个文本文件,允许读取文件
`w` | 以`写`模式打开一个文本文件,允许写入文件
`a` | 以`追加`模式打开一个文本文件<br />如果文件不存在,则会创建一个新文件
`r+` | 以`读写`模式打开一个文本文件,允许读写文件
`w+` | 以`读写`模式打开一个文本文件,允许读写文件
`a+` | 以`读写`模式打开一个文本文件,允许读写文件
`rb` | 以`读取`模式打开二进制文件
`wb` | 以`写入`模式打开二进制文件
`ab` | 以`追加`模式打开二进制文件
`rb+` | 以`读写`模式打开二进制文件
`wb+` | 以`读写`模式打开二进制文件
`ab+` | 以`读写`模式打开二进制文件
### 打开文件fopen()
2022-10-13 16:31:20 +08:00
```c {6}
2022-10-13 16:15:18 +08:00
#include<stdio.h>
2022-10-13 16:31:20 +08:00
2022-10-13 16:15:18 +08:00
void main( ) {
FILE *fp;
char ch;
fp = fopen("file_handle.c", "r");
2022-10-13 16:31:20 +08:00
2022-10-13 16:15:18 +08:00
while (1) {
ch = fgetc(fp);
if (ch == EOF)
break;
printf("%c", ch);
}
fclose(fp);
}
```
对文件执行所有操作后,必须关闭 `fclose()` 该文件
### 写入文件fprintf()
2022-10-13 16:31:20 +08:00
```c {7}
#include <stdio.h>
2022-10-13 16:15:18 +08:00
main() {
FILE *fp;
fp = fopen("file.txt", "w"); // 打开文件
2022-10-13 16:31:20 +08:00
// 将数据写入文件
fprintf(fp, "fprintf 的 Hello 文件..\n");
2022-10-13 16:15:18 +08:00
fclose(fp); // 关闭文件
}
```
### 读取文件fscanf()
2022-10-13 16:31:20 +08:00
```c {6}
2022-10-13 16:15:18 +08:00
#include <stdio.h>
main(){
FILE *fp;
2022-10-13 16:31:20 +08:00
char buff[255]; // 创建char数组存储文件数据
2022-10-13 16:15:18 +08:00
fp = fopen("file.txt", "r");
while(fscanf(fp, "%s", buff)!=EOF) {
printf("%s ", buff);
}
fclose(fp);
}
```
### 写入文件fputc()
2022-10-13 16:31:20 +08:00
```c {6}
2022-10-13 16:15:18 +08:00
#include <stdio.h>
2022-10-13 16:31:20 +08:00
2022-10-13 16:15:18 +08:00
main(){
FILE *fp;
fp = fopen("file1.txt", "w"); // 打开文件
fputc('a',fp); // 将单个字符写入文件
fclose(fp); // 关闭文件
}
```
### 读取文件fgetc()
2022-10-13 16:31:20 +08:00
```c {8}
2022-10-13 16:15:18 +08:00
#include<stdio.h>
#include<conio.h>
void main() {
FILE *fp;
char c;
clrscr();
fp=fopen("myfile.txt", "r");
while((c=fgetc(fp))!=EOF){
printf("%c", c);
}
fclose(fp);
getch();
}
```
### 写入文件fputs()
2022-10-13 16:31:20 +08:00
```c {8}
2022-10-13 16:15:18 +08:00
#include<stdio.h>
#include<conio.h>
2022-10-13 16:31:20 +08:00
2022-10-13 16:15:18 +08:00
void main(){
FILE *fp;
clrscr();
fp = fopen("myfile2.txt","w");
fputs("hello c programming",fp);
fclose(fp);
getch();
}
```
### 读取文件fgets()
2022-10-13 16:31:20 +08:00
```c {10}
2022-10-13 16:15:18 +08:00
#include<stdio.h>
#include<conio.h>
2022-10-13 16:31:20 +08:00
2022-10-13 16:15:18 +08:00
void main() {
FILE *fp;
char text[300];
clrscr();
fp=fopen("myfile2.txt", "r");
printf("%s", fgets(text, 200, fp));
fclose(fp);
getch();
}
```
### fseek()
2022-10-13 16:31:20 +08:00
```c {8}
2022-10-13 16:15:18 +08:00
#include <stdio.h>
void main(){
FILE *fp;
fp = fopen("myfile.txt","w+");
fputs("This is Book", fp);
2022-10-13 16:31:20 +08:00
// 将文件指针设置到给定位置
2022-10-13 16:15:18 +08:00
fseek(fp, 7, SEEK_SET);
fputs("Kenny Wong", fp);
fclose(fp);
}
```
将文件指针设置到给定位置
### rewind()
2022-10-13 16:31:20 +08:00
```c {11}
2022-10-13 16:15:18 +08:00
#include<stdio.h>
#include<conio.h>
void main(){
FILE *fp;
char c;
clrscr();
fp=fopen("file.txt", "r");
while((c=fgetc(fp)) != EOF){
printf("%c", c);
}
rewind(fp); // 将文件指针移动到文件的开头
while((c=fgetc(fp)) != EOF){
printf("%c", c);
}
fclose(fp);
getch();
}
// 输出
// Hello World!Hello World!
```
### ftell()
2022-10-13 16:31:20 +08:00
```c {11}
2022-10-13 16:15:18 +08:00
#include <stdio.h>
#include <conio.h>
2022-10-13 16:31:20 +08:00
2022-10-13 16:15:18 +08:00
void main (){
FILE *fp;
int length;
clrscr();
fp = fopen("file.txt", "r");
fseek(fp, 0, SEEK_END);
2022-10-13 16:31:20 +08:00
length = ftell(fp); // 返回当前位置
2022-10-13 16:15:18 +08:00
fclose(fp);
printf("文件大小: %d bytes", length);
getch();
}
// 输出
// 文件大小: 18 bytes
```
2024-09-12 22:53:31 +08:00
## C 网络编程
### 网络编程介绍
C使用sockets进行网络通信。包含头文件
- `#include <sys/socket.h>`: 套接字操作,如创建、绑定和监听套接字
- `#include <arpa/inet.h>`: IP 地址转换
- `#include <unistd.h>`: 关闭套接字等
- `#include <netinet/in.h>`: 网络地址结构定义和相关敞亮
### 创建套接字
网络通信的第一步是创建套接字。套接字是网络通信的基础,通过它可以与远程主机进行数据交换。
#### 服务端
```cpp
int server_fd, new_socket; // 定义服务器文件描述符和新连接的套接字
int port = 8080; // 服务器使用的端口号
// 创建套接字文件描述符
// AF_INET 表示使用 IPv4 协议SOCK_STREAM 表示使用 TCP 协议,协议参数通常为 0默认 TCP
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
```
#### 客户端
```cpp
int sock = 0; // 客户端的套接字描述符
struct sockaddr_in serv_addr; // 定义服务器地址结构体
// 创建套接字
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
```
### 绑定套接字
服务端创建套接字后,需要将其绑定到特定的 IP 地址和端口,以便客户端能够连接。
#### 服务端
```cpp
struct sockaddr_in address; // 定义存储地址信息的结构体
address.sin_family = AF_INET; // 设置地址族为 IPv4
address.sin_addr.s_addr = INADDR_ANY; // 将服务器绑定到所有可用的网络接口(即本机的所有 IP 地址)
address.sin_port = htons(port); // 将端口号转换为网络字节序,大端模式
// 将套接字绑定到指定的地址和端口上
// bind() 将服务器的文件描述符与 IP 地址和端口号进行绑定,以便客户端能够通过该地址和端口访问服务器
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
```
### 监听和接收连接
服务端在绑定套接字之后,需要进入监听状态,以等待客户端的连接请求。
#### 服务端
```cpp
// 开始监听客户端连接
// 监听连接请求
// listen() 函数将套接字设置为被动模式,准备接收来自客户端的连接请求
if (listen(server_fd, 3) < 0) { // 第二个参数 3 表示连接请求的队列大小
perror("listen failed");
exit(EXIT_FAILURE);
}
int addrlen = sizeof(address); // 获取地址结构体的大小
// accept() 函数会阻塞等待客户端的连接请求,一旦连接请求到来,创建一个新的套接字 new_socket 用于数据传输
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept failed");
exit(EXIT_FAILURE);
}
```
### 连接到服务端
客户端使用 `connect()` 函数连接到服务器的 IP 地址和端口。
#### 客户端
```cpp
// 设置服务器地址
serv_addr.sin_family = AF_INET; // 设置地址族为 IPv4
serv_addr.sin_port = htons(port); // 将端口号转换为网络字节序
// 将 IP 地址转换为二进制并存储在 serv_addr 结构体中
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
perror("Invalid address/ Address not supported");
exit(EXIT_FAILURE);
}
// 连接服务器
// connect() 函数将客户端的套接字与服务器的地址绑定,从而建立连接
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
perror("Connection Failed");
exit(EXIT_FAILURE);
}
```
### 发送和接收数据
一旦连接建立,服务端和客户端可以通过套接字发送和接收数据。
#### 服务端
```cpp
// 服务端从客户端接收数据
char buffer[1024] = {0}; // 缓冲区,用于存储接收的数据
int valread = read(new_socket, buffer, 1024); // 从客户端读取数据
printf("Client: %s\n", buffer); // 打印接收到的客户端数据
// 服务端发送响应数据给客户端
const char *response = "Hello from server"; // 响应消息
send(new_socket, response, strlen(response), 0); // 发送数据到客户端
printf("Server message sent\n");
```
#### 客户端
```cpp
// 客户端发送数据给服务端
const char *message = "Hello from client"; // 要发送的消息
send(sock, message, strlen(message), 0); // 发送数据到服务端
printf("Client message sent\n");
// 客户端从服务端接收响应数据
char buffer[1024] = {0}; // 缓冲区,用于存储接收到的数据
int valread = read(sock, buffer, 1024); // 读取服务端的响应数据
printf("Server: %s\n", buffer); // 打印接收到的服务端数据
```
### 关闭套接字
完成通信后,双方都应关闭各自的套接字以释放资源。
#### 服务端
```cpp
// 关闭服务端套接字
close(new_socket); // 关闭用于数据传输的客户端套接字
close(server_fd); // 关闭服务器的监听套接字
```
#### 客户端
```cpp
// 关闭客户端套接字
close(sock); // 关闭客户端的套接字
```
## I/O多路复用
### 多路复用介绍
在网络编程中,服务端可以使用 I/O 多路复用 技术,如 `select``poll``epoll`。这些技术允许服务端同时监听多个文件描述符(如套接字),并在其中一个发生事件时进行处理,提升系统效率。包含头文件:
- `#include <sys/select.h>`: 提供 `select`
- `#include <poll.h>`: 提供 `poll`
- `#include <sys/epoll.h>`: 提供`epoll`
### 使用select
```c
fd_set read_fds; // 定义文件描述符集合
FD_ZERO(&read_fds); // 清空集合
FD_SET(server_socket, &read_fds); // 将服务端套接字加入集合
int max_fd = server_socket;
int activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL); // 等待事件发生
if (activity < 0 && errno != EINTR) {
perror("select error");
}
```
### 使用poll
```c
struct pollfd fds[2]; // 定义文件描述符数组
fds[0].fd = server_socket;
fds[0].events = POLLIN; // 监听读事件
int poll_count = poll(fds, 2, -1); // 等待事件
if (poll_count < 0) {
perror("poll error");
}
```
### 使用epoll
```c
int epoll_fd = epoll_create1(0); // 创建 epoll 文件描述符
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = server_socket;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event) == -1) {
perror("epoll_ctl failed");
}
struct epoll_event events[10]; // 事件数组
int event_count = epoll_wait(epoll_fd, events, 10, -1); // 等待事件发生
for (int i = 0; i < event_count; i++) {
if (events[i].data.fd == server_socket) {
// 处理服务端套接字上的事件
}
}
```
2022-10-13 16:40:26 +08:00
杂项
---
### Docker 运行环境
<!--rehype:wrap-class=col-span-2-->
- 安装 [`Docker`](./docker.md)
- 创建 [`Dockerfile`](./dockerfile.md) 文件
2022-10-13 16:40:26 +08:00
```dockerfile
FROM alpine:3.14
RUN apk add --no-cache gcc musl-dev
RUN apk add --no-cache g++
```
2022-10-13 16:40:26 +08:00
- 生成本地 myalpine 镜像
2022-10-13 16:40:26 +08:00
```bash
docker build -t myalpine .
```
2022-10-13 16:40:26 +08:00
- 运行映像,把当前路径 `($PWD)` 映射至容器的 `/test` 目录,用 `gcc` 编译程序,`exit`返回
2022-10-13 16:40:26 +08:00
```bash
docker run -it -v $PWD:/test myalpine
root@b1a38bd7107a:/# cd test
root@b1a38bd7107a:/test# gcc -o hello hello.c
Hello World
root@b1a38bd7107a:/test# exit
exit
```
<!--rehype:className=style-timeline-->
2022-10-13 01:19:47 +08:00
另见
---
- [C 教程](https://jaywcjlove.github.io/c-tutorial) _(jaywcjlove.github.io)_