alt text

容易忘记的点

001-028

  • stdint.h
  • 后缀问题
int32_t myInt32 = 2147483647;
uint32_t myUInt32 = 4294967295U;

int64_t myInt64 = 9223372036854775807LL;
uint64_t myUInt64 = 18446744073709551615ULL;

printf("Size of int16_t: %zu byte(s)\n", sizeof(myInt16));//单位是字节
printf("Size of uint16_t: %zu byte(s)\n", sizeof(myUInt16));
printf("Size of int32_t: %zu byte(s)\n", sizeof(myInt32));
printf("Size of uint32_t: %zu byte(s)\n", sizeof(myUInt32));
printf("Size of int64_t: %zu byte(s)\n", sizeof(myInt64));
printf("Size of uint64_t: %zu byte(s)\n", sizeof(myUInt64));
  • 显式转换方便后续
// 无符号到有符号 强制转换 显式类型转换 (int32_t)
uint16_t uSmallNum = 12345;
int16_t sSmallNum = (int16_t)uSmallNum;

029. 固定宽度整数类型的格式化宏输出 inttypes. h

#include <stdio.h>
#include <stdint.h>

int main() {
// 固定宽度整数类型
int32_t myInt32 = INT32_MAX;
uint32_t myUInt32 = UINT32_MAX;

uint64_t myUInt64 = UINT64_MAX;

printf("int32_t: %d\n", myInt32);
printf("uint32_t: %u\n", myUInt32);
printf("uint64_t: %llu\n", myUInt64);

return 0;
}

头文件<inttypes.h>的使用

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main() {
// 固定宽度整数类型
int32_t myInt32 = INT32_MAX;
uint32_t myUInt32 = UINT32_MAX;

uint64_t myUInt64 = UINT64_MAX;

printf("int32_t: %" PRId32 "\n", myInt32);
printf("uint32_t: %" PRIu32 "\n", myUInt32);
printf("uint64_t: %" PRIu64 "\n", myUInt64);

return 0;
}

030. least 和 fast 整型的企业用途与区别

//int_leastN_t number;
//至少有N位,可能更多,适用于需要保证最小存储容量的可移植代码。
//int_fastN_t 特效8bit 16bit
//至少有N位,但是选择运算最快的类型,适用于需要性能敏感的场景。
//标准整数类型 → 固定宽度整数类型
//固定的位数,不可以越界,适用于需要精确数据大小的场景。

032-033. (选修) 浮点数 IEEE754指数偏移存储原理-(选修) 浮点数计算范围的原理

  • 微软文档:

由于指数是以无符号形式存储的,因此指数的偏差为其可能值的一半。 对于 float 类型,偏差为 127;对于 double 类型,偏差为 1023。 您可以通过将指数值减去偏差值来计算实际指数值。

存储为二进制分数的尾数大于或等于 1 且小于 2。 对于 float 和 double 类型,最高有效位位置的尾数中有一个隐含的前导 1,这样,尾数实际上分别为 24 和 53 位长,即使最高有效位从未存储在内存中也是如此。

类型 有效位(十进制) 字节数 指数长度 尾数长度
float 6-7 4 8 位 23 位
double 15-16 8 11 位 52 位

如果存储比精度更重要,请考虑对浮点变量使用 float 类型。 相反,如果精度是最重要的条件,则使用 double 类型。

【代码笔记】c语言_image-1.png

034-035. float 类型的定义和输出-float 丢失精度以及%E 与%A 科学计数法输出

#include <stdio.h>
int main(void){
float temperature = 36.5f;
float humidity = 48.3f;
float speed_of_sound = 343.5e2f; // 343.5 * 10^2
float length = 12.34f, width = 23.45f, height = 34.56f;
printf("Temperature: %f\n", temperature);
printf("Humidity: %f\n", humidity);
printf("Speed of Sound: %f\n", speed_of_sound);
printf("Dimensions (LxwxH): %f x %f x %f\n", length, width, height);
// 丢失精度
return 0;
}
#include <stdio.h>
int main() {
float num = 123.456f;

// %f
printf("Using %f: %f\n", num);

// %e %E 科学计数法格式化输出
printf("Using %e: %e\n", num);
printf("Using %EE: %E\n", num);

// %a %A 十六进制浮点数 p计数法
printf("Using %a: %a\n", num);
printf("Using %A: %A\n", num);

return 0;
}

036. 浮点数 overflow 上溢与 underflow 下溢

#include <stdio.h>
#include <float.h> // 包含 FLT_MAX 和 FLT_MIN 的定义的头文件

int main() {
float max_float = FLT_MAX;
float overflow = max_float * 1000.0f;

// Overflow 上溢
float min_float = FLT_MIN;
float underflow = min_float / 1000.0f;
// infinity 无穷大

printf("Maximum Float: %e\n", max_float);
printf("Overflow: %e\n", overflow);
printf("Minimun Positive Float: %e\n", min_float);
printf("Underflow: %e\n", underflow);
// underfow 可以理解为 CPU 认为存储位数不够用借用前导1导致丢失精度

return 0;
}

037. Infinity 与 Nan

#include <stdio.h>
#include <float.h>
#include <math.h>

int main() {
// 正无穷大
float positive_infinity = INFINITY;
printf("Positive Infinity: %f\n", positive_infinity);

// 负无穷大
float negative_infinity = -INFINITY;
printf("Negative Infinity: %f\n", negative_infinity);

// 除以0产生的无穷大
float num = 1.0f;
float infinity = num / 0.0f;
printf("1.0 / 0.0 = %f\n", infinity);

//Nano 0/0
//float nan = 0.0f / 0.0f;
//printf("0.0 / 0.0 = %f\n", nan);

// float number = sqrt(4000);
// printf("sqrt4 = %f\n", number);

float negative_sqrt = sqrt(-1.0f);
printf("sqrt(1.0f) = %f\n", negative_sqrt);
//nan:not a number

return 0;
}

038-1. (选修-难-可忽略) 最近偶数舍入 (银行家舍入标准

#include <stdio.h>
#include <float.h>
#include <math.h>

int main() {
// 四舍五入
// IEEE 754
// 最近偶数舍入 round to nearest, ties to even
// 银行家舍入

float a = 3.15f;
float b = 3.25f;

printf("尾数为奇数:%.1f\n",a);
printf("尾数为偶数:%.1f\n",b);
//double %lf
//float %f

return 0;
}
//都输出3.2
// 如果尾数的整数部分是奇数,向上舍入,使得变为偶数
// 如果尾数的整数部分是偶数,保持不变,因为它已经是偶数

039. double, long double 科研与企业用途的差别

  • 小数默认是 double 类型,加上 f 后缀隐式转换 float 类型
#include <stdio.h>

int main() {
printf("sizeof(3.12) = %zu (double=%zu)\n", sizeof(3.12), sizeof(double));
printf("sizeof(3.12f) = %zu (float=%zu)\n", sizeof(3.12f), sizeof(float));
printf("sizeof(3.12l) = %zu (long double=%zu)\n", sizeof(3.12l), sizeof(long double));

// 验证
if (sizeof(3.12) == sizeof(double)) {
printf("\n结论: 3.12 默认是 double 类型\n");
}

return 0;
}
  • double 类型用 %f 输出仍然会是 double 类型
  • long double
long double a = 1.233L;
printf("long double:%Lf",a);

040. float 和 double 有效精度对比原理与计算

  • float.h 头文件:FLT_DIG,DBL_DIG
#include <stdio.h>
#include <float.h>

int main() {
float float_num = 1.0 / 3.0;
double double_num = 1.0 / 3.0;

printf("Float precision: %.20f\n", float_num);
printf("Double precision: %.20lf\n", double_num);

printf("Defined max precision for float: %d\n", FLT_DIG);
printf("Defined max precision for double: %d\n", DBL_DIG);
return 0;
}

041-2. (选修) 补录 Decimal

  • GMP 定点数

042. char 与 ASCII

char a = 'A';
printf("char a:%c\n",a);

043-044. 转义序列-转义序列练习

  • 微软文档
转义序列 表示
\a 响铃(警报)
\b Backspace
\f 换页
\n 换行
\r 回车(相当于现在键盘的 home)
(早期 window 系统是\r\n 现在是系统内部\n 自动转为\r\n)
\t 水平制表符
\v 垂直制表符
%> 单引号
:" 双引号
\ 反斜杠
%> 文本问号
** ooo 八进制表示法的 ASCII 字符
\xhh 十六进制表示法的 ASCII 字符
\xhhhh 十六进制表示法的 Unicode 字符(如果此转义序列用于宽字符常量或 Unicode 字符串文本)。

例如,WCHAR f = L'\x4e00' 或 WCHAR b[] = L"The Chinese character for one is \x4e00"
//清除屏幕
printf("\033[2J");

045-046. bool 类型与实际案例-char 范围与无符号 char

  • c99 添加 bool 类型
  • unsigned char/uint8
  • <stdbool. h>头文件

047. 常量 const 与#define 宏

#define PI 3.12

int main() {
const int a = 3;

049. 运算符的介绍

  • 基本运算符:+ - * / %
  • 关系运算符:== != > < >= <=
  • 逻辑运算符:! && ||
  • 赋值语句:=

050-053

    1. 数据对象与左值和右值
    1. 多重赋值:x=y=z=10
    1. 算数运算符的应用
    1. 一元与二元运算符:
    • -a:只对一个变量操作→一元运算符
    • a+b:二元运算符

054. 前缀后缀递增与递减

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main(){
int32_t value = 5;
uint32_t result;
result = value + 1;
//后缀递增
result = value++; // 使用value的当前值(5)-〉然后value变成6
printf("After postfix increment, result = %" PRId32 ", value = %" PRId32"\n", result, value);
value = 5;

//前缀递增
result = ++value; // value先增加到6,然后再把这个整个右值赋值给result
}
//value-- --value同理

055-056. 按位移位运算符-按位移位的另外问题

  • << >>
  • 但要注意操作变量类型
uint32_t num = 25;
uint32_t result = num << 10;
  • ALU:Arithmetic Logic unit

057. 逻辑的真与假、C关系运算符

  • 关系运算符只做判断

058. 条件表达式运算符

059-062. 按位运算符

操作 描述
& 按位与:如果两个位均为 1,则对应的结果位将设置为 1。 否则,为 0。
^ 按位异或:如果一个位是 0,另一个位是 1,则相应的结果位将设置为 1。 否则,为 0。
| 按位或:如果其中一个位是 1,则将对应的结果位设置为 1。 否则,为 0。
~ 按位取反
  • ab 交换值 (其实还是 temp 最优解):
    • temp 中间变量:
      • temp = a; a = b; b = temp;
    • 三次运算:
      • a = a+b; b = a-b; a = a-b;
    • 按位异或:
      • a = a^b; b = a^b; a = a^b;

064. C逻辑运算符

操作 描述
&& 逻辑与:串联开关控制灯 and
|| 逻辑或:并联 or
  • 优先级&&||
  • A || B && C→A || (B && C)

065. 复合赋值

  • += -= *= /= %= <<= >>=&= |= ^=

066. 逗号运算符

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
int main(){
int32_t a = 1;
int32_t b = 2;
int32_t c = 3;

int32_t d = (a += 1, b += 1, c += 1);
printf("result:a = %d,b = %d,c = %d,result = %d", a, b, c, d);

return 0;
}
//输出:2344
//逗号运算符只输出最后一个结果

067-069. 计算的优先级和顺序

  • 微软文档:
符号 操作类型 结合性
[ ] ( ) . ->
++ -- (后缀)
表达式 从左到右
sizeof & * + - ~ !
++ -- (前缀)
一元 从右到左
typecasts 一元 从右到左
* / % 乘法性的 从左到右
+ - 累加性 从左到右
<< >> 按位移位 从左到右
< > <= >= 关系 从左到右
== != 平等 从左到右
& Bitwise-AND 从左到右
^ Bitwise-exclusive-OR 从左到右
| Bitwise-inclusive-OR 从左到右
&& Logical-AND 从左到右
|| Logical-OR 从左到右
? : Conditional-expression 从右到左
= *= /= %=
+= -= <<= >>= &=
^= |=
简单和复合赋值 从右到左
, 顺序评估 从左到右

1 运算符按优先顺序降序列出。 如果多个运算符出现在同一行或组中,则它们具有相等的优先级。
2 所有简单和复合赋值运算符都具有相等的优先级。

表达式可以包含多个优先级相等的运算符。 当多个此类运算符出现在表达式中的同一级别时,计算会根据运算符的关联性(从右到左或从左到右)进行。 计算方向不会影响在同一级别包含多个乘法()、加法(*或二元位(+``&|^运算符的表达式结果。 作顺序不是由语言定义的。 如果编译器可以保证一致的结果,编译器可以按任意顺序计算此类表达式。
只有顺序求值()、logical-AND(,``&&)、logical-OR(||)、条件表达式(? :)和函数调用运算符构成序列点,因此保证其作数的特定计算顺序。 函数调用运算符是函数标识符后面的括号集。 保证顺序求值运算符 (,) 从左到右计算其作数。 (函数调用中的逗号运算符与顺序计算运算符不同,不提供任何此类保证。有关详细信息,请参阅 序列点

  • 说白了函数是调用不是运算所以不遵循优先级原则

逻辑运算符还保证从左到右计算其作数。 但是,它们计算确定表达式结果所需的最小作数。 这称为“短路”评估。 因此,可能无法计算表达式的某些作数。 例如,在表达式x && y++中,仅当为 true(非零)时x,才会计算第二个作数y++。 因此, y 如果 x 为 false(0),则不会递增。

072-077

if (true){
printf("true");
}
else if(){
printf("...");
}
else{
printf("false");
}

080. switch… case的用途

switch(role){
case 1: //role == 1
//...
case 2:
//...
break;
default:
//...
break;
}
//085. 应用:状态机,break前改变变量值

081. 再探条件运算符

grade = (score > 90) ? "A":
(score > 80) ? "B":
(score > 70) ? "C": "D";

082. 检查账户锁定案例与提前return出

#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
int main(){
uint32_t wrong_attempts = 3;
bool is_locked = wrong_attempts >= 3; // 密码如果输入错了三次就锁定
if(is_locked){
puts("账户被锁定!");
return 0; // 暂时可以理解为提前退出
}
//......
puts("继续往后执行》。。");
return 0;
}

083-084. 卫语句的使用:租车案例-简化逻辑表达式

#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
int main(){
// 租车案例
// age >= 21 ,驾驶经验一年或一年以上
uint8_t age = 23;
uint8_t driving_exp_years = 2;

if (age < 21){
puts("不符合条件,年龄不足。");
return 0;
}
if (driving_exp_years < 1){
puts("不符合条件,驾驶经验不足。");
return 0;
}
//所有的都满足
puts("满足租车条件");

return 0;
}

卫语句(Guard Clause)是编程中的一种代码结构,用于在函数或方法的开头提前检查条件,如果不满足条件就立即返回或退出,从而避免深层嵌套的 if-else 结构。

086. switch-case与if-else的根本区别

  • switch 判断变量 expression 必须是 int 整型(理论上比 if 快)

090. 初探while循环

while(current_laps < total_laps){
current_laps++;
printf("ok");
}

常量要大写!!!

093. break与利用死循环-求和案例

  • while 的判断条件不能没有初始值
  • break 来提前跳出

094. 处理字符和字符串的退出检测问题(选修)

#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <inttypes.h>

//用户的输入加起来输出和,或者输入q退出
int main(){
char input;
uint16_t sum = 0;

while (true) {
puts("please input a number(0-9) or q to exit:");
scanf_s(" %c", &input,1);//%c前面空格:跳过所有输入的空白符

if (input == 'q') {
printf("sum = %" PRId16 "\n",sum);
break;
}
if (input > '0' && input < '9') {
sum += (uint16_t)input - '0';
}
else {
puts("invalid input!!!");
}
}

return 0;
}

095-096. do-while与while的区别

do {
//...
} while ();
//先不检查,执行一次再说
  • 比如游戏主菜单,至少执行一次让用户看到菜单

098-099. continue的用法-continue和break联用条件判断的实际用途

  • 联合卫语句

100. 初探for循环

//for (初始化控制变量;循环条件;自增/减的控制){
for (uint32_t current_laps = 1; current_laps <= 5; current_lap++){
printf();
}
//这里初始化的控制变量只和for内函数有关(可以使用)
//或者定义在前面才可以全局使用

【代码笔记】c语言_image-2.png

101. 训练:求平方和

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

//用户输入n输出从1到n的平方和
int main(){
uint32_t input;
uint32_t sum = 0;

puts("please input number:");
//scanf_s("%u",&input);
scanf_s("%"SCNu32,&input);

for (uint32_t index = 1; index <= input; index++) {
sum += index * index;
}
printf("从1到%" PRIu32 "的平方和是:%" PRIu32 "\n", input, sum);

return 0;
}

103. 训练:延迟毫秒扩展Sleep

#include <windows.h>
Sleep(1000);//单位毫秒

//linux里面有库→sleep(1)

105-106. sqrt开根与素数的概念-训练:判断素数

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <math.h>

//用户输入正整数,输出是否为素数
int main(){
uint32_t input;

puts("请输入正整数:");
scanf_s("%" SCNu32, &input);

for (uint32_t index = 2; index <= sqrt(input); index++) {
//优化:index * index <= input
if (input % index == 0) {
printf("您输入的%" PRIu32 "不是素数", input);
break;
}
}
printf("您输入的%" PRIu32 "是素数",input);
}

110. 训练:金字塔数字

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

//用户输入n输出金字塔
int main(){
uint32_t input;

puts("请输入:");
scanf_s("%" SCNu32, &input);
puts("金字塔:");
for (uint32_t index = 1; index <= input; index++) {
for (uint32_t kongge = 1; kongge <= input - index; kongge++) {
printf(" ");
}
for (uint32_t num = 1; num <= index; num++) {
printf("%" PRIu32 " ",num);
}
for (uint32_t num = index -1; num > 0; num--) {
printf("%" PRIu32 " ", num);
}
puts("\n");
}
return 0;

}

111. 案例:进度条

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <windows.h>

//模拟进度条
int main(){
uint32_t finish;
const uint32_t total = 50;

printf("进度条:\n");

for (uint32_t i = 0; i <= total; i++) {
printf("\r[");
for (uint32_t j = 1; j <= i; j++) {
printf("#");
}
for (uint32_t j = i; j <= total; j++) {
printf(" ");
}
printf("]");
printf("%" PRIu32 "%%\r", (i*100)/total);
fflush(stdout); //刷新缓存
Sleep(100);
}
printf("结束!!");
return 0;
}

113. 使用VS进行debug调试以及for的注意事项

【代码笔记】c语言_image-3.png

116-1. 数组 array[index]=element

116. 数组的初步使用

uint32_t numbers[10] = {1,2,3,4,5,6,7,8,9,10};
//调用:numbers[n]
  • n必须是常量,数组必须初始化才可以使用
  • 这里的常量不包括const定义的常量,#define宏定义或者直接123
uint32_t numbers[5] = {1};
//同等
uint32_t numbers[5] = {1,0,0,0,0};

119.★数组的注意事项

  • 错误1:越界访问
  • 错误2:未初始化
  • 错误3:数组大小错误uint32_t numbers[-1];
  • 警告1:部分初始化uint32_t numbers[5]={1};

124. 数组的案例:字母次数统计

'\0' 在 C 语言中是空字符(null character),也称为字符串终止符空字节

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

//字母频率统计
int main(){
char text[] = "Example text for a frequency analysis.";
uint32_t frequency[26] = { 0 };

for (uint32_t i = 0; text[i] != '\0'; i++) {
if (text[i] >= 'a' && text[i] <= 'z') {
frequency[text[i] - 'a']++;
}
}

for (uint32_t i = 0; i < 26; i++) {
printf("%c出现的次数是%d\n", 'a' + i, frequency[i]);
}

return 0;
}

126. 二维数组. mp4

int main(){
uint32_t array[3][5] = {
{1,2,3,4,5},
{1,2,3,4,5},
{1,2,3,4,5}
};
printf("%"PRIu32, array[2][3]); //4

return 0;
}

126-2. 补录:隐式确定数组大小的初始化

//隐式确定数组大小的初始化
int arr[] = {0}; // 这个数组一定全部初始化

127

127-1. Unicode字符编码与wchar

  • gbk 汉字内码扩展规范 → unicode

头文件<locale.h> 是 C 标准库中用于本地化的头文件,主要功能是让程序适应不同国家/地区的语言、字符集、数字格式、日期格式等习惯

127-3. 五子棋棋盘绘制

  • 头文件<wchar.h>
wchar_t XXX = L'';
wprintf(L"%lc");
  • unicode:0x25CB,0x25CF,0x00B7

129. 游戏案例:农场作物成长

  • 数组初始化指定位置值:
int farm[ROWS][COLS] = { [3][3] = STONE, [6][6] = STONE };

132-133. 函数声明与定义的规则-函数的参数

  • 先声明后定义
void XXX();
//...
int main(void){
XXX(12,1); // 实际参数
}
void XXX(int XXX,bool XX){ //形式参数
//...;
}
int XXX(){
//...;
return YY; // YY和int位置类型一致并且一定要return
}

135. 函数编写要领

1.明确函数目的,明确它的功能将帮助你决定哪些输入是必须的(形式参数/模板),以及如何报告它的工作结果
2.识别所需要的输入
3.确定函数的输出
4.使用适当的数据类型
5.函数的命名清晰,并且先声明后定义

136. 初步认识全局变量、局部变量

#define XXX 123 //全局常量
int XXX; //全局变量
int main(void){
}

137. 游戏案例:石头剪刀布与软件工程的-规则映射-设计技巧

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
#include <time.h>
#include <stdlib.h>

//常量
#define ROCK 1
#define PAPER 2
#define SCISSORS 3
//全局变量
uint32_t input;
uint32_t robot_input;

//函数声明
void print_rule();
int get_input_move();
int get_robot_move();
void determine_winner(input, robot_input);

//石头剪刀布与软件工程的[规则映射]设计技巧
int main(){
//初始化随机数生成器
srand(time(NULL));
print_rule();
input = get_input_move();
while (input == 0) {
printf("\n");
input = get_input_move();
}
robot_input = get_robot_move();
printf("你选择了%" PRIu32 "\n", input);
printf("机器人选择了%" PRIu32 "\n", robot_input);
determine_winner(input, robot_input);
return 0;
}

void print_rule() {
printf("石头剪刀布游戏规则:\n");
printf("1:石头,2:布,3:剪刀\n");
}

int get_input_move() {
printf("请选择:");
scanf_s("%" SCNu32, &input);
if (input != 1 && input != 2 && input != 3) {
printf("无效的输入!!!");
return 0;
}
return input;
}
int get_robot_move() {
return (rand() % (SCISSORS - ROCK + 1)) + ROCK;
}
void determine_winner(uint32_t input, uint32_t robot_input) {
int arr[4] = { 0,PAPER,SCISSORS,ROCK};
if (input == arr[robot_input]) {
printf("你赢啦!!!");
}
else {
printf("你输了呜呜呜~~");
}
}

138. 案例:软件工程设计技巧:表驱动法之再探成绩评分系统、再探闰年返回月份天数

  • 表驱动法:使用数据结构替代复杂语句
    • 核心:许多程序行为可以通过查表来替代
const int days in month[MONTH COUNT]={
31,
is leap year(year)?29:28,
31,30,31,30,31,31,30,31,30,31
};

存档闰年/平年判断:

def is_leap_year(year):
if year % 400 == 0:
return True # 整百年能被400整除 → 闰年 → 2月29天
if year % 100 == 0:
return False # 整百年不能被400整除 → 平年 → 28
if year % 4 == 0:
return True # 非整百年能被4整除 → 闰年
return False # 其他情况 → 平年

139-141. 递归,尾递归

  • 递归:函数内使用自己
uint32_t factorial(uint32_t n){
if(n == 0){
return 1;
}
else{
return n*factorial(n-1);
}
}
  • 优化:尾递归,尾部增加变量用于叠加计算
uint32_t factorial(uint32_t n,uint32_t acc){
if(n == 0){
return acc;
}
else{
return factorial(n-1,n*acc);
// return f(4,5)
// return f(3,4*5)
// return f(2,3*4*5)
// return f(1,2*3*4*5)
// return (0,120)
// return 120
}
}

141. 为什么不建议使用递归以及递归和尾递归用途区别

  • 套娃,有溢出风险,浪费内存,可读性差,依赖编译器支持

142. 企业使用迭代方法来替代递归

#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int flib(uint32_t n);

//斐波那契数列
int main(void) {
printf("%"PRIu32, flib(4));
return 0;
}
//1,1,2,3,5,8
int flib(uint32_t n) {
if (n <= 1) {
return n;
}
uint32_t flib_0 = 0;
uint32_t flib_1 = 1;
uint32_t flib_n;

for (uint32_t i = 2; i <= n; ++i) {
//++i:c中没有太大性能区别(理论上++i性能更好)
flib_n = flib_0 + flib_1;
flib_0 = flib_1;
flib_1 = flib_n;
}
return flib_n;
}

143. 企业规范之void作为函数参数的必要性

int greet(void); //检查是否有不应该传递的变量/值

144-153

  • 作用域Scope
  • 生命周期Lifetime
  • 局部变量的作用域限定、自动存储期、初始值未定义
  • 全局变量的跨越边界性与程序范围可见性
    • 静态存储期:程序整个运行期间都存在,不是临时创建、用完销毁的存储方式
    • 默认初始化:默认会有值(0)
  • 静态局部变量static variables:在函数内部声明,并且程序的期间只初始化一次
    • 多次调用函数该变量会累计改变
    • 全局静态变量:在文件开头声明,只在文件内可见
static int XXX = 0;
  • extern全局变量:可以跨文件访问
extern int XXX = 0;
  • register寄存器变量,不能用&获取地址(尽可能放到寄存器)
register int XXX = 0;
  • 块作用域 Block Scope 与链接性 Linkage

154-155. 地址-取地址的含义

printf("%d的地址是:%p\n",target floor, (void*)&target floor);

指针与数组

156. 指针

  • 特殊的变量,不存具体的数值,只存数据的地址
    • 就像快递员地图上标记的门牌号
int* XXX = &XXY;

157. 指针与修改

  • 使用*来根据指针找变量值
int* ptr_floor_103 = &building_floors[2];
printf("找到了业主的门牌号%d\n",*ptr_floor_103);
//等价于
printf("找到了业主的门牌号%d\n",building_floors[2]);

158. 指针星号的企业风格规范以及容易引发的问题

int* XXX = &XXY; //微软写法,强调变量类型
int *XXX = &XXY; //强调变量是一个指针
int* p,q;
//等价于
int* p;
int q;
//如果一定要一行声明
int *p,*q;
int *p,* q; //微软要求这样写

160. 野指针的初步介绍

  • 野指针:指向了一个无效的内存地址或者是已经释放的内存地址的指

161-162. 空指针的初步介绍-初始化

空指针:通常指没有指向任何有效内存地址的指针。

char *XXX = NULL;

164. 数组的首地址与指针的算数运算

  • 数组内存连续性体现:
int num[] = {...};
int* ptr = num;
//等同于
int* ptr = &num[0];
  • 指针之间的减法有专门的类型:ptrdiff_t
    • printf 使用%tdint64
// Definitions of common types
#ifdef _WIN64
typedef unsigned __int64 size_t;
typedef __int64 ptrdiff_t;
typedef __int64 intptr_t;
  • 课程代码:
#include <stdio.h>
#include <inttypes.h>

int main(void) {
int num[] = { 1,2,3,4,5,6,7,8,9,10 };
int* addr_start = num;
int size = sizeof(num) / sizeof(num[0]);
printf("num的大小是:%d\n",size);
printf("num开头的地址是:%p\n", addr_start);

addr_start += 4;
printf("num的第五个元素是%d\n", *addr_start);
addr_start -= 4;
printf("num的第一个元素是%d\n", *addr_start);

int* addr_end = &num[size - 1];
printf("num的大小是:%td\n", addr_end - addr_start);

return 0;
}

165. 续上集:指针的算数运算与比较运用

//比较
int* addr_4 = &num[3];
if (addr_4 < addr_end) {
printf("第四个元素在最后一个元素前面\n");
}
printf("反向遍历num:{");
//反向遍历
for (int* i = addr_end; i >= addr_start; i--) {
printf("%d,", *i);
}
printf("}");

166. 再探size_t与数组与指针的使用

  • size_t无符号整数类型,专门用于表示内存大小长度数组索引
// Definitions of common types
#ifdef _WIN64
typedef unsigned __int64 size_t;
特性 size_t int
有符号性 无符号(只能 ≥ 0) ✅ 有符号(可正可负)
位数 32位系统:32位
64位系统:64位
通常 32 位(即使64位系统)
用途 内存大小、数组长度、对象大小 通用整数、计数、循环
最大值 非常大(64位上约 1.8×10¹⁹) 约 21 亿(2³¹-1)
printf 格式 %zu %d
size_t size = sizeof(num) / sizeof(num[0]);
//直接通过指针修改值
for (int* i = addr_start; i <= addr_end; i++) {
*i += 5;
}
printf("\n\n直接通过指针修改num:{");
for (int* i = addr_start; i <= addr_end; i++) {
printf("%d,", *i);
}
printf("}");

168-169. 指针访问多维数组-指针数组

int(*ptr)[3] = num;
//(*ptr)[3]: ptr是一个指针,它指向一个包含3个int元素的一维数组的指针
int* ptr[3] = num;
//ptr是一个指针数组
#include <stdio.h>

int main(void) {
int num[2][3] = {
{1,2,3},
{4,5,6}
}; //要加;!!!
int(*ptr)[3] = num;

printf("num:\n");
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d", ptr[i][j]); //这里不需要取地址符&
}
printf("\n");
}

return 0;
}

170. 函数的值传递与地址引用传递

  • 地址作为值传递给函数内指针,从而直接编辑任何该地址(附件)的变量的值

171.案例:员工薪资系统:指针作为函数返回值

  • 指针可以作为函数返回值

结构体

176-178. 初识结构体 structures -创建结构体变量与访问方式-匿名结构体、函数参数为结构体

struct 自己定义类型的类型名{
//声明类型包含的变量
char name[50];
int age;
float height;
//...
};
struct 类型名 变量名 = {‘luoyinhui’,123,123};
//使用时前面不想加struct
typedef struct human{ //typedef 这里的human其实可以省略:匿名结构体
//声明类型包含的变量
char name[50];
int age;
float height;
//...
}human01; //别名,不是一定要和human一样
human01 luoyinhui = {‘luoyinhui’,123,123};
  • 定义一般不在 main 里面从而让它全局可被使用
  • 访问方式:1)通过.;2)指针
printf("%d",human01.age);
//指针
human01* luoyinhui_ptr = &luoyinhui;
printf("%d",luoyinhui_ptr->age);
  • 函数参数为结构体
#include <stdio.h>
#include <inttypes.h>
#define NEW_ID 23
//匿名结构体
typedef struct {
char name[50];
uint32_t id;
float height;
}Student;
//声明函数
void print_student(Student stu);
void change_student(Student* stu);

int main(void) {
Student luoyinhui = { "luoyinhui",12,123 };
print_student(luoyinhui);

change_student(&luoyinhui);
printf("\n\nchange:\n");
print_student(luoyinhui);
return 0;
}

void print_student(Student stu) {
printf("stu name:%s\n", stu.name);
printf("stu id:%"PRIu32"\n", stu.id);
printf("stu name:%.2f\n", stu.height);
}

void change_student(Student* stu) {
stu->id = NEW_ID;
}
声明 类型 含义 能否 %s
char* p 指针 指向一个字符串 ✅ 可以
char* arr[100] 指针数组 100 个字符串指针 ❌ 不行,arr 是数组
char s[100] 字符数组 存 100 个字符 ✅ 可以

179. 值语义初始化结构体变量

  • 值语义 values semantics 通过返回值是结构体的函数来初始化结构体
    • 更加安全(反外挂,立刻销毁结构体从而无法通过地址来改变值)、简单

180. 结构体数组

#include <stdio.h>

typedef struct {
int x;
int y;
}Point;


int main(void) {
Point points[2] = {
{1,2},{3,4}
};
for (int i = 0; i < 2; i++) {
printf("%d,%d\n", points[i].x, points[i].y);
}
return 0;
}


181. 嵌套结构体

#include <stdio.h>

typedef struct {
char city[50];
char street[50];
}Address;

typedef struct {
char name[50];
Address address;
}People;

int main(void) {
People luoyinhui = {
"luoyinhui",
{"moumoudi","moustreet"}
};

printf("%s:%s %s\n",luoyinhui.name,luoyinhui.address.city,luoyinhui.address.street);
People* ptr = &luoyinhui;
printf("%s:%s %s",ptr->name,ptr->address.city,ptr->address.street);

return 0;
}

182. Enumeration 枚举

typedef enum { //枚举
XXX, //0
YYY,
ZZZ
}Weekday;

int main(void) {
Weekday day = YYY;
printf("%d", day);
//输出为:1
return 0;
}

183. Union 联合

  • 允许在相同的内存位置存储不同的数据类型
  • 联合体的所有成员共享一块内存空间,大小等于其最大成员的大小
  • 这就意味着在任一时刻,联合体只能存储一个成员的值
  • 一个变量可能存储多种类型的数据,但是在一个给定时刻里,只是用其中一种的数据类型,这样可以节省内存
typedef union {
int int_value; //注意是分号!!!
float float_value;
char char_value[50]
}Data;
  • 实际案例:
#include <stdio.h>

typedef enum {
Int,
Float,
String
}Datatype;

typedef union {
int int_value;
float float_value;
char char_value[50];
}Data;

typedef struct {
Datatype datatype;
Data data;
}Typeddata;

void print_typeddata(Typeddata data);

int main(void) {
Typeddata data_01 = {
Int,{.int_value = 123}
};
print_typeddata(data_01);
Typeddata data_02 = {
Float,{.float_value = 1.23}
};
print_typeddata(data_02);
Typeddata data_03 = {
String,{.char_value = "hello"}
};
print_typeddata(data_03);
return 0;
}

void print_typeddata(Typeddata data) {
switch (data.datatype) {
case Int:printf("%d\n", data.data.int_value);
break;
case Float:printf("%.2f\n", data.data.float_value);
break;
case String:printf("%s\n", data.data.char_value);
break; //注意要有break!!!
}
}

184.游戏设计:结构体、枚举、联合与多文件编程

//头文件中通过#include "XXX.h"引入其他自定义头文件

字符串

  • 头文件`<string.h>

186. 字符串

char c = 't';
//两种定义方式
//1不用const就可以修改 arr[0] = 'W';
const char arr[] = { "Hello" };
//最好const 不要显示定义长度容易错误
// 'H' 'e' 'l' 'l' 'o' '\0'

//2不可以修改,修改会报错,所以最好加const
const char* ptr = "Hello";

187. strcpy_s 的用法

char str[100] = "hello";
// hello \0 \0 \0 \0 \0 \0 \0 ...
  • 头文件<string.h>
strcpy(a,b); //把b复制到a
//微软默认不安全
strcpy_s(a,size_of_a,b);
strcpy_s(a,ssizeof(a),b);

192. strncpy_s

errno_t result = strncpy_s(dest,sizeof(dest),src,max_copy);
if (result == 0){
printf("Copied string: %s\n", dest);
}
else{
printf("Error copying string.\n");
}

189. strlen

char dest[50] = "Hello";
printf("%zd\n", strlen(dest));
//vs警告解决:
char dest[50] = { 0 };
strcy_s(dest,50,"Hello");
printf("%zd\n", strlen(dest));
//frank这里用的是%zd可能是因为这里字符串长度肯定是正值
//ai:%zu→size_t %zd→ssize_t有符号类型

190. strcat_s:拼接字符串

//拼接字符串
char dest[50] = { 0 };
strcpy_s(dest, sizeof(dest), "Hello");

const char* src = ", World!\n";

strcat_s(dest, sizeof(dest), src);
printf("%s", dest);

193. strncat_s

char dest[50] = { 0 };
strcpy_s(dest, sizeof(dest), "Hello");

const char* src = ", World!\n";
size_t max_append = 7;

int result = strncat_s(dest, sizeof(dest), src,max_append);
if (result == 0){
printf("%s", dest);
}
else{
printf("Error concatenating string.\n");
}

191. sprintf_s

char buffer[50] = { 0 };
int number = 3;
double pi = 3.14159;
int ret = sprintf_s(buffer, sizeof(buffer), "Integer:%d,Double:%.2f", number, pi);
if(ret > 0){
printf("Formatted string: %s\n", buffer);
}
else {
printf("Error Formatting string!\n");
}

194. gets_s

  • 只输入字符串时使用,提高安全性
//puts();
//gets(); 不安全,不限制长度
//gets_s();
char buffer[100];
gets_s(buffer,sizeof(buffer));
if (gets_s(buffer,sizeof(buffer)) == NULL){
printf("Error or end od fle encountered.\n");
}
else{
printf("your entered:%S\n",buffer);
}

195. strtok_s

//按照要求分解字符串
char str[] = "This is a sample string";
char delim[] = " ";
char* token;
char** context = { 0 }; // 保存 strtok_s函数在其内部位置之间的上下文指针
// char context = NULL;
token = strtok_s(str, delim, &context);
while (token != NULL) {
printf("Token: %s\n", token);
token = strtok_s(NULL, delim, &context); //第一个参数NULL代表从上一次结束的地方继续
}

196. strcmp

int result = strcmp(str1,str2);
//0 相同
//>0 ascii 第一个不一样的字符 str1>str2
//<0

197.strncmp

strncmp(str1,str2,num); //比较前num个字符

198. strchr 与 strrchr

const char* str = "XXXXXX";
char* ptr_char = strchr(str, what_to_find) //从前面找的第一个
//strrchr(str,what_to_find) 从后面找
printf("找到了位置是:%td\n",ptr_char - str + 1);

199. strstr

const char* text = "XXXXXXXXXXXXXXYYYXXX";
const char* sub = "YYY";

char* result = strstr(text, sub); // 返回值为指针char*
// 大小写敏感!!!
// 没找到或者子字符串为空会返回NULL
if (result != NULL){
printf("Found '%s' in '%s' at position:%%td\n", sub, text,result - text +1);
}
else{
printf("not found");
}

200. strspn 与 strcspn

//strspn() 返回从开头开始连续最大相同字符数
const char* str1 = "123456abcdefghi678910";
const char* str2 = "h2345678910";
size_t len = strspn(str1, str2);
printf("%zu\n", len);

//strcspn() 返回连续最大相同字符数
char input[] = "filename.txt";
char invalid_chars[] = "/\\:*?\"<>|.";
if (strcspn(input, invalid_chars) < strlen(input)) {
printf("Input contains invalid characters.\n");
}
else {
printf("Input is valid.\n");
}

201. 再次废话

声明 类型 含义 修改 能否 %s
const char* p 指针 指向一个字符串常量 ❌ 不行 ✅ 可以
char* arr[100] 指针数组 100 个字符串指针 ✅ 可以 ❌ 不行,arr 是数组
char s[100] 字符数组 存 100 个字符 ✅ 可以 ✅ 可以

202. 案例(略难):关于 string.h

  • 替换单词:
  • char* text传进去只是指针所以如果函数最后一步是strcpy_s(text, sizeof(text), new_str);就是指针大小而不是数组大小导致替换不完全
  • 字数统计:ff
while (text)  // ❌ 错误:这是检查指针是否非空,不是检查字符
while (*text) // ✅ 正确:检查当前字符是否为 '\0'
  • 案例:
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#define SIZE 500

char change_text[] = "test";
char new_text[] = "example";
char change_char = 'a';
int uniquecount = 0;
char uniqueword[SIZE][SIZE] = {0};

void f_change_text(char* text, char change[],char new[]);
int f_calculate_s(char* text, char simple);
int f_cal(char* text);
void f_extract_words(char* text);
int uniquecount;

int main(void) {
char text[SIZE] = "This is a test do you want to know why it is a test ye ye ye";

//替换单词
f_change_text(text,change_text,new_text);
printf("%s\n", text);
//字数统计
uint32_t count;
count = f_calculate_s(text, change_char);
printf("新句子里面有 %"PRIu32" 个'a'\n", count);
//单词计数
count = f_cal(text);
printf("新句子里面有 %"PRIu32" 个单词\n", count);
//提取单词(不重复输出
f_extract_words(text);
printf("独一无二的单词有:\n");
for (int i = 0; i < uniquecount; i++) {
printf("%s\n", uniqueword[i]);
}
return 0;
}

void f_change_text(char* text, char change[], char new[]) {
//检测到单词位置,先切分,再拼起来
char str[SIZE] = { 0 };
strcpy_s(str, sizeof(str), text);
char new_str[SIZE] = { 0 };
char delim[] = " ";
char* token;
char* context = NULL;
token = strtok_s(str, delim, &context);
while (token != NULL) {
if (strcmp(token,change)==0) {
strcat_s(new_str, sizeof(new_str), new);
strcat_s(new_str, sizeof(new_str), " ");
}
else {
strcat_s(new_str, sizeof(new_str), token);
strcat_s(new_str, sizeof(new_str), " ");
}
token = strtok_s(NULL, delim, &context);
}
strcpy_s(text, SIZE, new_str);
}

int f_calculate_s(char* text, char simple) {
int count = 0;
while (*text) {
if (*text == simple) {
count++;
}
text++;
}
return count;
}

int f_cal(char* text) {
int count = 0;
char str[SIZE] = { 0 };
strcpy_s(str, sizeof(str), text);
char* context = NULL;
char* token;
token = strtok_s(str, " ", &context);
while (token != NULL) {
count++;
token = strtok_s(NULL, " ", &context);
}
return count;
}

void f_extract_words(char* text) {
char str[SIZE] = { 0 };
strcpy_s(str, sizeof(str), text);
char* context = NULL;
char* token;
token = strtok_s(str, " ", &context);
while (token != NULL) {
int found = 0;
for (int j = 0; j < uniquecount; ++j) {
if (strcmp(token, uniqueword[j]) == 0) {
found = 1;
break;
}
}
if (!found) {
strcpy_s(uniqueword[uniquecount], SIZE, token);
uniquecount++;
}
token = strtok_s(NULL, " ", &context);
}
}

204. 输入输出初步认识

  • 输入/输出流 input/output stream
  • buffer 缓冲区暂存数据的位置 有助于批量读取数据
  • 标准输入/输出 stdin/stdout

205-206. 复习 scanfs-scanf s 返回值

#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#define SIZE 50

int main(void) {
char text[SIZE];
scanf_s("%49s", text, (unsigned int)sizeof(text));
printf("%s", text);

char ch;
scanf_s("%c", &ch, 1);
printf("%c", ch);

int age;
scanf_s("%d", &age);
//_s是由于修改字符串存在缓冲区溢出风险而产生的,所以这里不需要
printf("%d", age);
}
  • 返回值:
    • EOF end of file
    • 1:正常

文件流

207. stream 流的概述

    • 文件流:磁盘 用于读取/写入在磁盘上的文件
    • 标准 I/O 流:
      • stdin:默认连接到键盘用于程序的输入 scanf_s()
      • stdout:默认连接到控制台或者屏幕用于程序输出 printf()
      • stderr:默认链接控制台/屏幕,专门输出错误信息和警告
    • 管道流:用于进程之间的通信 (IPC),允许一个进程的输出成为另一个进程的输入 popen()
    • 内存流:允许你将流与内存缓冲区关联
      • fmemopen 是 POSIX 提供的一个函数,用来创建内存流
    • 网络流:套接字 sockets
    • 设备流
  • FILE* stream

208. fopen_s, fgetc, fgets, fclose 读取 r 模式

#include <stdio.h>
#include <inttypes.h>
//下面两个是perror、EXIT_FAILURE的依赖头文件
#include <stdlib.h>
#include <errno.h>

int main(void) {
char buffer[256];
FILE* stream = NULL; //相当于指针会随读取移动
//rewind(stream); 移动指针到开头

errno_t error = fopen_s(&stream, "C:\\Users\\luoyinhui\\Desktop\\1.txt","r");

if (error != 0 || stream == NULL) {
perror("Error happened:");
return EXIT_FAILURE;
}

while (fgets(buffer, sizeof(buffer), stream) != NULL) {
printf("%s", buffer);
} //一行一行读取

memset(buffer,0,sizeof(buffer)); //释放缓冲区

if (fclose(stream) != 0){
perror("Error happened:");
return EXIT_FAILURE;
}

return 0;
}
  • 一个一个字符读取:
int ch;
while ((ch = fgetc(stream)) != EOF) { //!= 优先级高于 =
putchar(ch);
}

209. fputs,fputc 与 w 模式

  • a:追加;w:覆盖
#include <stdio.h>
#include <inttypes.h>
#include <stdlib.h>
#include <errno.h>

int main(void) {
FILE* ptr = NULL;

errno_t error = fopen_s(&ptr, "C:\\Users\\luoyinhui\\Desktop\\1.txt", "w");
if (error != 0 || ptr == NULL) {
perror("Error happened:");
return EXIT_FAILURE;
}
fputc('a', ptr);
fputc('b', ptr);
fputc('\n', ptr);
fputs("nifdhfhskfdjsjfjs", ptr);
char name[] = "luoyinhui";
fprintf(ptr,"your name is %s", name);

if (fclose(ptr) != 0) {
perror("Error happened:");
return EXIT_FAILURE;
}
else {
printf("successfully write!");
}
return 0;
}
  • 微软文档:
mode 访问
"r" 打开以便读取。 如果文件不存在或找不到,fopen_s 调用将失败。
"w" 打开用于写入的空文件。 如果给定文件存在,则其内容会被销毁。
"a" 在文件末尾打开以进行写入(追加),在新数据写入到文件之前不移除文件末尾 (EOF) 标记。 如果文件不存在,则创建文件。
"r+" 打开以便读取和写入。 文件必须存在。
"w+" 打开用于读取和写入的空文件。 如果文件存在,则其内容会被销毁。
"a+" 打开以进行读取和追加。 追加操作包括在新数据写入文件之前移除 EOF 标记。 写入完成后,EOF 标记不会还原。 如果文件不存在,则创建文件。

210. ftell,fseek,rewind

#include <stdio.h>
#include <inttypes.h>
#include <stdlib.h>
#include <errno.h>

int main(void) {
char buffer[256];
FILE* stream = NULL;
errno_t error = fopen_s(&stream, "C:\\Users\\luoyinhui\\Desktop\\1.txt", "r");

if (error != 0 || stream == NULL) {
perror("Error happened:");
return EXIT_FAILURE;
}

if (fgets(buffer, sizeof(buffer), stream) != NULL) {
printf("%s", buffer);
}
//ftell获取当前指针位置
long ptr = ftell(stream);
printf("position is:%ld\n", ptr);

//fseek移动指针位置
fseek(stream, 0, SEEK_SET);
ptr = ftell(stream);
printf("position is:%ld\n", ptr);

if (fgets(buffer, sizeof(buffer), stream) != NULL) {
printf("%s", buffer);
}
ptr = ftell(stream);
printf("position is:%ld\n", ptr);

rewind(stream);
ptr = ftell(stream);
printf("position is:%ld\n", ptr);

memset(buffer, 0, sizeof(buffer)); //释放缓冲区

if (fclose(stream) != 0) {
perror("Error happened:");
return EXIT_FAILURE;
}

return 0;
}

211-1. fscanf_s

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
long l;
float fp;
char s[81];
char c;

FILE* stream;
errno_t err = fopen_s(&stream, "C:\\Users\\luoyinhui\\Desktop\\1.txt", "r");
if (err)
printf_s("The file fscanf.out was not opened\n");
else
{
if (fscanf_s(stream, "%80s", s, (unsigned)_countof(s)) != 1) { //_countof()计算数组元素个数
printf("fail to read words\n");
}
if (fscanf_s(stream, "%ld",&l) != 1) {
printf("fail to read ld\n");
}
if (fscanf_s(stream, "%f",&fp) != 1) {
printf("fail to read lf\n");
}
if (fscanf_s(stream, " %c", &c,1) != 1) { //忽略空格
printf("fail to read c\n");
}

// Output data read:
printf("%s\n", s);
printf("%ld\n", l);
printf("%f\n", fp);
printf("%c\n", c);

fclose(stream);
}
return 0;
}

211-2. fprintf

#include <stdio.h>
#include <stdlib.h>

int main(void) {
FILE* stream;

int age = 12;
float height = 1.75f;
char name[] = "luoyinhui";
char grade = 'A';

errno_t err = fopen_s(&stream,"C:\\Users\\luoyinhui\\Desktop\\1.txt","w+");
if (err != 0){
printf("fail to open the txt\n");
return EXIT_FAILURE;
}
fprintf(stream, "name:%s\n", name);
fprintf(stream, "height:%.2f\n", height);
fprintf(stream, "age:%d\n", age);
fprintf(stream, "grade:%c\n", grade);

if (fclose(stream) != 0) {
perror("Error happened:");
return EXIT_FAILURE;
}

return 0;
}

212. ferror,feof,clearerr

if (ferror(stream)) {
perror("error happened\n");
clearerr(stream);
}

if (feof(stream)) {
printf("reach eof\n");
}
else {
printf("can't reach eof,maybe error happened\n");
}

213. 抽离读写函数

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

void safe_read(char* file);

int main(void) {
char* filename = "C:\\Users\\luoyinhui\\Desktop\\1.txt";

safe_read(filename);

return 0;
}

void safe_read(char* file) {
FILE* stream;
errno_t error = fopen_s(&stream,file,"r");

if (error != 0 || stream == NULL) {
//C11标准的安全版本
char error_msg[256];
strerror_s(error_msg, sizeof(error_msg), errno);// fopen 失败时,系统会自动设置 errno,依赖头文件<errno.h>
fprintf(stderr, "error happened:%s\n", error_msg);
exit(EXIT_FAILURE);
}

char buffer[256];
while (fgets(buffer, sizeof(buffer), stream) != NULL) {
printf("%s", buffer);
}
fclose(stream);//只涉及到读可以简写,写入时要加if判断报错
}

214. a 模式追加

mode 访问
"a" 在文件末尾打开以进行写入(追加),在新数据写入到文件之前不移除文件末尾 (EOF) 标记。 如果文件不存在,则创建文件。
"a+" 打开以进行读取和追加。 追加操作包括在新数据写入文件之前移除 EOF 标记。 写入完成后,EOF 标记不会还原。 如果文件不存在,则创建文件。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

void safe_append(char* file,char* append);

int main(void) {
char* logname = "C:\\Users\\luoyinhui\\Desktop\\log.txt";
char* append = "select something from log";
safe_append(logname,append);

return 0;
}

void safe_append(char* file,char* append) {
FILE* stream;
errno_t error = fopen_s(&stream, file, "a");

if (error != 0 || stream == NULL) {
char error_msg[256];
strerror_s(error_msg, sizeof(error_msg), errno);
fprintf(stderr, "error happened:%s\n", error_msg);
exit(EXIT_FAILURE);
}

fprintf(stream, "%s\n",append);

fclose(stream);
}
  • 清空流,防止错误
int numclosed = _fcloseall();
printf("number of files closed by _fcloseall:%u\n", numclosed);
//平常只要_fcloseall();就可以了,纠错时如上

215. w 模式清空

mode 访问
"w" 打开用于写入的空文件。 如果给定文件存在,则其内容会被销毁
"w+" 打开用于读取和写入的空文件。 如果文件存在,则其内容会被销毁
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

void safe_read(char* file);
void safe_append(char* file,char* append);
void clear_log(char* file);

int main(void) {
char* filename = "C:\\Users\\luoyinhui\\Desktop\\1.txt";
safe_read(filename);

char* logname = "C:\\Users\\luoyinhui\\Desktop\\log.txt";
char* append = "select something from log";
safe_append(logname,append);

clear_log(logname);

int numclosed = _fcloseall();
printf("number of files closed by _fcloseall:%u\n", numclosed);

return 0;
}

void clear_log(char* file) {
FILE* stream;
errno_t error = fopen_s(&stream, file, "w");

if (error != 0 || stream == NULL) {
char error_msg[256];
strerror_s(error_msg, sizeof(error_msg), errno);
fprintf(stderr, "error happened:%s\n", error_msg);
exit(EXIT_FAILURE);
}

fclose(stream);
}

216. 企业实际案例(难):修改 log,r+模式

mode 访问
"r+" 打开以便读取和写入。 文件必须存在,指针在文件中间使输入是覆盖而不是插入。

EINVAL(error invalid),代表 “Invalid argument”(无效参数)
ERANGE:字符串长度超出范围

错误码 全称 含义
ENOENT Error No Entry/Entity 文件或目录不存在
EACCES Error Access 权限拒绝
ENOMEM Error No Memory 内存不足
EINVAL Error Invalid 无效参数
EIO Error I/O 输入输出错误
EEXIST Error Exist 文件已存在
ENOSPC Error No Space 设备无剩余空间
EPERM Error Permission 操作不允许
ESRCH Error Search 没有该进程
EINTR Error Interrupt 系统调用被中断
  • 208. fopen_s, fgetc, fgets, fclose 读取 r 模式 memset 用于缓冲区释放:memset(buffer,0,sizeof(buffer)); //释放缓冲区
  • 听完课第一次自己写的遗漏:
    • 返回无效参数错误
    • 指针没有初始化 FILE* ptr = 0;
    • fopen_s 返回值报错遗漏 ptr == NULLif (err != 0) {
    • position 是文件中的字节偏移量,所以不是指针
    • strstr 判断完忘记 break 逻辑
    • 遗漏清空行前后字符串大小判断逻辑:ERANGE
    • 最后fputs的返回值未与 EOF 判断:ENOENT
  • fputs()返回值:
返回值 含义
非负数(通常是 0 成功
EOF(通常是 -1 失败
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define SIZE 1024

errno_t change_file_s(char* file, const char search_words[SIZE], const char change_words[SIZE]);

int main(void) {
const char search_words[] = "Automatic reconnection attempt 1/3...";
const char change_words[] = "[2026-03-03 09:34:16] Automatic reconnection attempt 1/3...";
char log_file[] = "C:\\Users\\luoyinhui\\Desktop\\log.txt";

errno_t err = change_file_s(log_file, search_words, change_words);
if (err != 0) {
char error_msg[256];
strerror_s(error_msg, sizeof(error_msg), err);
fprintf(stderr, "error happened:%s\n", error_msg);
}
else {
printf("successfully change log!!!");
}
_fcloseall();
return 0;
}

errno_t change_file_s(char* file, const char search_words[SIZE], const char change_words[SIZE]) {
char buffer[SIZE];
int found = 0;
long position = 0; //position是文件中的字节偏移量,所以不是指针

FILE* file_ptr = NULL;//指针初始化

if (file == NULL || search_words == NULL || change_words == NULL) {
return EINVAL; //返回无效参数错误
}

errno_t err = fopen_s(&file_ptr,file,"r+");
if (err != 0||file_ptr == NULL) { //指针为空错误
char err_msg[SIZE];
strerror_s(err_msg, sizeof(err_msg), errno);
fprintf(stderr, "error happened:%s\n", err_msg);
exit(EXIT_FAILURE);
}
while (fgets(buffer, sizeof(buffer),file_ptr) != NULL) {
if (strstr(buffer, search_words) != NULL) {
position = ftell(file_ptr) - (long)strlen(buffer);
found = 1;
break;//要记得break
}
}
if (found == 1) {
fseek(file_ptr, position, SEEK_SET);

size_t search_len = strlen(search_words); //清空行前后字符串大小判断逻辑
size_t change_len = strlen(change_words);
if (search_len > SIZE - 1 || change_len > SIZE - 1) {
fclose(file);
return ERANGE;
}


memset(buffer, " ", strlen(buffer) - 1);
buffer[strlen(buffer) - 1] = '\n';
fputs(buffer, file_ptr);
fseek(file_ptr, position, SEEK_SET);
int end = fputs(change_words, file_ptr);
if (end == EOF) { //fputs返回值判断
fclose(file_ptr);
return errno;
}
}
else { //未找到逻辑判断
fclose(file_ptr);
return ENOENT;
}
if (fclose(file_ptr) != 0) {
perror("Error happened:");
return EXIT_FAILURE;
}
}

216-2.临时文件的方案

  • 上节方案会由于 c 特性导致覆盖问题,用临时文件解决该 bug
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define SIZE 1024

errno_t change_file_s(char* file, const char search_words[SIZE], const char change_words[SIZE]);

int main(void) {
const char search_words[] = "666";
const char change_words[] = "[2026-03-04] [INFO] [Auth] PAM service ready";
char log_file[] = "C:\\Users\\luoyinhui\\Desktop\\log.txt";

errno_t err = change_file_s(log_file, search_words, change_words);
if (err != 0) {
char error_msg[256];
strerror_s(error_msg, sizeof(error_msg), err);
fprintf(stderr, "error happened:%s\n", error_msg);
}
else {
printf("successfully change log!!!");
}
_fcloseall();
return 0;
}

errno_t change_file_s(char* file, const char search_words[SIZE], const char change_words[SIZE]) {
char buffer[SIZE];
int found = 0;
long position = 0;

//创建临时文件
char temp_filename[L_tmpnam_s];
errno_t err = NULL;
//errno_t err = tmpnam_s(temp_filename, L_tmpnam_s);
//if (err != 0) {
// return err;
//}
snprintf(temp_filename, sizeof(temp_filename), "%s.tmp", file);

FILE* file_ptr = NULL;
FILE* temp_file_ptr = NULL;


if (file == NULL || search_words == NULL || change_words == NULL) {
return EINVAL;
}

//打开临时文件用于写入
err = fopen_s(&temp_file_ptr, temp_filename, "w");//w模式确保临时文件存在且空白
if (err != 0 || temp_file_ptr == NULL) {
//fclose(temp_file_ptr);
return errno;
}

err = fopen_s(&file_ptr, file, "r+");
if (err != 0 || file_ptr == NULL) {
char err_msg[SIZE];
strerror_s(err_msg, sizeof(err_msg), errno);
fprintf(stderr, "error happened:%s\n", err_msg);
exit(EXIT_FAILURE);
}

//逐行读取处理后写入临时文件
while (fgets(buffer, sizeof(buffer), file_ptr) != NULL) {
if (found == 0 && strstr(buffer, search_words) != NULL) {
fprintf(temp_file_ptr, "%s\n", change_words);
found = 1;
}
else {
fputs(buffer, temp_file_ptr);
}
}
//关键!!!!记得关掉不然不能后面操作!!!
fclose(file_ptr);
fclose(temp_file_ptr);
file_ptr = NULL;
temp_file_ptr = NULL;
//临时文件替换原始文件如果匹配到
if (found) {
if (remove(file) != 0) {
remove(temp_filename);
return errno;
}
if (rename(temp_filename, file) != 0) {
return errno;
}
}
else {
remove(temp_filename);
return ENOENT;
}
return 0;
}

217. fflush 简单略过

  • 快速保存缓存区内容

218. 游戏设置案例:bin 二进制文件存储与读取,wb 与 rb 模式的使用

  • fread/fwrite 读写二进制
    219.复制文件
    220.第11章结束语
    221-1.章节开头8
    221-2.math.h 头文件的概述
    222.三角函数与 MPI
    223.双曲函数
    224.指数和对数
    225.常见 math 类别函数汇总
    226.pow 函数
    227.对于 math 类别的错误处理:EDOM,ERANGE,HUGE_VAL
    228.检查浮点数类别与属性
    229.浮点数的比较与差值
    230.舍入和剩余函数
    231.time.h 与时间戳的使用
    232.简单回顾错误处理函数
    233.传统数组的问题
    234.★重点:栈内存和堆内存的对比
    235.malloc 函数动态内存分配的使用与释放
    236.企业案例:realloc 函数与释放
    237.malloc 与结构体的使用以及防止内存的泄露
    238.calloc 函数
    239.多级指针
    239-2.多级指针的用途
    240.游戏案例:游戏服务器动态玩家列表管理之 realloc 与多级指针的应用
    241.续上节:卫语句与 log 编写
    242.三级指针案例:字符串无限追加的应用
    243.案例:动态数据结构的管理
    244.FucntionPointer 函数指针的概念
    245-1.再探 typedef
    245-2.练习:函数指针用途
    246-1.函数指针与 callback 回调函数的作用
    246-2.实际用途
    247.事件处理框架 Event Handling Framework
    248.游戏架构事件设计:事件类型、事件处理函数、事件注册、事件分发机制
    249.再谈头文件与编译
    250.编写头文件:函数声明与函数实现
    251.泛型编程:比较与排序
    252.企业案例:自定义函数处理比较器
    253.闲聊休息
    254.指针的作用域和生命周期
    255.悬挂指针 Dangling pointer
    256.可变参数 Variadic function final
    257.练习:自定义日志函数
    258.assert 断言
    259.断言的 debug 与练习
    260.企业案例:日志系统与指针问题处理的架构设计第1部分 logger
    261.企业案例:日志系统与指针问题处理的架构设计第2部分 内存管理
    262.续上节
    263.企业案例:日志系统与指针问题处理的架构设计第3部分 error_handling
    264.企业案例:日志系统与指针问题处理的架构设计第4部分 pointer safety 空指针野指针悬挂指针的处理
    265.企业案例:日志系统与指针问题处理的架构设计第5部分 application_logic 模块
    266.企业案例:日志系统与指针问题处理的架构设计第6部分 测试
    267.企业案例:日志系统与指针问题处理的架构设计第7部分 写入文件
    268.环境变量的读写8
    269.命令行参数
    270.小案例:命令行程序的编写
    271.案例:自定义泛型对列第1部分泛型对列节点结构模块
    272.案例:自定义泛型对列第2部分安全内存处理模块
    273.案例:(选修)自定义泛型对列第3部分测试运行模块
    274.函数传递数组
    275.数组作为函数参数的练习
    276.小总结8
    277.案例:小仓库 items 管理
    278.事件驱动注册器触发器:完善之前游戏触发机制
    279.会再探数组指针的用途
    280.★结构体指针成员:再探动态内存,堆内存和占内存
    281.★动态数组 Dynamic Array
    282★软件工程:组合、聚合、嵌套、构造、析构一结构体指针的高级应用