关于一些 C语言 零碎小知识

📅 2026/7/4 4:37:13
关于一些 C语言 零碎小知识
目录头文件格式说明符%i字符串输入输出\0continue与breakfloat与double函数实参与形参算式符号typedef__func__静态本地变量static返回指针的函数宏 #define项目头文件#include不对外公开的函数跨文件 声明全局变量标准头文件结构头文件使用pow(x,y); //计算x的y次方 需要带上#includemath.h 使用system(pause); //可以在使vscode在终端运行时按任意键后继续仅适用Windows系统 需要带上#includestdlib.h 定义一个数 #define TOL 1E-3 //定义TOL为1乘10的-3次方 函数 作用 例子 strlen(str) 求字符串长度不含末尾的 \0 strlen(abc) 结果是 3 strcpy(a, b) 把 b 复制给 a覆盖 strcpy(arr1, arr2) strcat(a, b) 把 b 拼接在 a 的末尾 strcat(arr1, world) strcmp(a, b) 比较 a 和 b 是否相等 相等返回 0不等返回非0 需要带上#includestring.h bool 是 C 语言里专门用来表示“真”和“假”逻辑值的类型。 需要带上#include stdbool.h 如 #include stdbool.h bool a 3; // 3 是非 0存进去变成 1true bool b -5; // -5 是非 0存进去变成 1true bool c 0; // 存进去是 0false printf(%d %d %d, a, b, c); // 输出1 1 0 又如 #include stdio.h #include stdbool.h // 重点这行代码做了两件事 // 1. 定义了 bool 这个类型其实就是 _Bool // 2. 定义了 true 1 false 0 宏替换 int main() { // ----- 场景1定义变量并赋值 ----- bool isPass true; // 编译时编译器把 true 换成 1相当于 int isPass 1; bool isFail false; // 编译时编译器把 false 换成 0相当于 int isFail 0; // ----- 场景2看看它们底层的真实数值 ----- // 虽然你写的是 true/false但打印出来绝对是整数 printf(true 实际上存的是%d\n, true); // 输出1 printf(false 实际上存的是%d\n, false); // 输出0 // ----- 场景3用于 if 判断这才是主要用途 ----- if (isPass) { // 因为 isPass 里面是 1非0即真所以这行必执行 printf(恭喜考试通过了\n); } if (!isFail) { // !false 就是 !0结果是 1真所以这行也执行 printf(没有挂科继续加油\n); } // ----- 场景4警惕bool 变量被赋值整数会发生截断 ----- bool weird 123; // 123 是非0编译器会强制转成 1 存进去 printf(weird 的值变成了%d\n, weird); // 输出1 绝对不是 123 return 0; }格式说明符%02d //整形向前补零2位如5会变成05但29依然是29179也依然是179 %.2f //浮点数向后保留两位小数如3.14159变成3.14或者3浮点数变成3.00 %-4d //占4位左对齐也就是占4位空位后优先往左边占位如3()()()代表空格 %4d //占4位右对齐也就是占4位空位后优先往右边占位如()()()3 //如果超过4位则不受限制 %7s //最多读7位字符串如果输入不足7位不影响超过7位只能读前7位多的给后面scanf的读入 %p //输入输出时表示地址 输出浮点数时%f可以通吃 但是输入有严格要求float型对应%fdouble型对应%lf%i############# %i是 C 语言里整数的格式占位符 ##############1. 在printf中%i完全等价于%d无论是打印int、short还是char整数你用%i还是%d输出的结果一模一样没有任何差别。int num 100; printf(%d\n, num); // 输出 100 printf(%i\n, num); // 输出 100完全一样2. 在scanf中%i和%d天差地别重点这是它俩唯一、也是致命的区别。%d只能老老实实读10进制但%i是个“变色龙”它会根据用户输入的前缀自动识别进制用户输入%d读到的值%i读到的值原因100100100没前缀默认十进制01010忽略前导08%i看到前缀0当成八进制0x100读到x识别失败赋016%i看到前缀0x当成十六进制正负号也能识别%i可以识别开头的或-号。比如输入-010它会先识别负号再看到0前缀当做八进制最终存入-8。不能识别二进制%i不认0b或0B前缀这是很多其他语言里的二进制写法。如果你输入0b101%i读到0后遇到b会直接停止只把0存进去。int a, b; scanf(%d %i, a, b); // 假设用户键盘输入010 010 // 变量 a用%d读取结果是10 // 变量 b用%i读取结果是8 printf(a%d, b%d, a, b); // 输出a10, b8字符串输入输出ngetchar(); //是标准C库函数专用于读取一个字符。它无格式解析开销执行效率更高。返回值是 int 类型 //返回读取到的字符的 ASCII 码值int如果遇到文件末尾或读取错误返回 EOF通常是 -1。因此它可以安全地判断是否读到了数据结尾。 scanf(%c,n); //是格式化输入函数虽然这里只读一个字符但它内部需要解析格式字符串 %c性能开销略大于 getchar //返回成功赋值的参数个数。正常读到字符返回 1如果遇到文件末尾或错误返回 EOF-1或 0。它无法直接通过 n 的值来判断是否读到 EOF因为 char 类型可能无法区分 EOF。 //二者都每一次调用且只调用一次时永远只从输入缓冲区读取 1 个字符。 读取字符串 getchar();方法 #include stdio.h #define SIZE 100 // 定义数组最大长度 int main() { char str[SIZE]; // 存储字符串的数组 int i 0; // 数组下标 char ch; // 临时存储每次读到的字符 printf(请输入一行字符串可含空格按回车结束); // 核心循环只要没按回车且数组没装满就一直读 while ((ch getchar()) ! \n i SIZE - 1) { str[i] ch; // 将字符存入数组 i; // 下标后移 } // 循环结束后在末尾手动补上字符串结束符 \0 str[i] \0; // 输出结果 printf(你输入的字符串是%s\n, str); return 0; } //请输入一行字符串可含空格按回车结束Hello C World 123 //你输入的字符串是Hello C World 123 //空格和数字都被完整保留并输出了这是 scanf(%s) 做不到的。 如果你的代码前面刚好有一句 scanf(%d, num); 读取了数字紧接着用上面的 getchar() 读字符串程序会直接跳过字符串输入不给你打字的机会。 原因scanf(%d) 读数字后把用户敲的回车键 \n 留在了缓冲区下面的 getchar() 第一个看到的就是 \n立刻满足退出条件循环一次都没执行。 解决方案在 getchar() 循环之前加一句代码把残留的回车“吃掉” int num; char str[100]; scanf(%d, num); // 读数字 getchar(); // 关键吃掉缓冲区里残留的换行符 // 现在再调用上面的 while 循环就能正常等待你输入字符串了 while ((ch getchar()) ! \n i SIZE - 1) { ... } scanf();方法 char str[100]; scanf(%s, str); // 注意不需要 因为数组名就是地址 fgets(str, size, stdin) ;方法 //一次读取一整行含空格 #include stdio.h int main() { char str[100]; // 预留100个字符的空间 printf(请输入字符串可含空格); fgets(str, sizeof(str), stdin); // sizeof(str) 保证最多读99个字符 \0 //str:数据存放的目标地址数组名代表起始地址 //sizeof(str):告诉系统缓冲区的总大小。这里sizeof(str) 计算得到 100。 //stdin:标准输入流即从键盘读取。 printf(你输入的是%s, str); return 0; } getchar() 可控性极强可以自己决定遇到什么字符停止比如遇到 # 停止绝对安全。 代码量稍多需要手动补 \0。 fgets() 代码最简短一行搞定同样安全且能读空格。 会自动把末尾的回车 \n 也存进去有时需要额外处理。 如果你的需求就是“读取一整行遇到回车就停”那么直接用 fgets(str, sizeof(str), stdin); 更省事但如果你想练习底层逻辑或者需要遇到特定字符如 * 或 #就提前停止那么上面的 getchar() 循环写法是最好的模板。输入字符 cgetchar(); 输出字符 putchar(c); 都只对单个字符操作 返回和接收的都是 int整型而不是 char如puchar(65); 和 putchar(A); 都是输出A\0\0 它是一个值为 0 的字符ASCII 码为 0在 C 语言中写作 \0。 核心作用标记字符串的结束。C 语言没有单独的“字符串类型”它靠字符数组干活。系统读取字符串时会从数组开头一直往下读直到遇到 \0 才停下来。 如 char str[5] ABC; 内存里实际存的是A B C \0 第5个格子空闲 sizeof(); 它是 C 语言的运算符不是函数用来计算变量或数据类型在内存中占用的字节数,会计入\0。continue与breakfloat与double对比维度float单精度double双精度内存占用4 个字节32位8 个字节64位有效小数位数约 6~7 位十进制有效数字约 15~16 位十进制有效数字数值范围约 1.2E-38 ~ 3.4E38约 2.3E-308 ~ 1.7E308格式说明符printf%f%f注意输出都用%f格式说明符scanf%f%lf极易写错字面量默认类型需加后缀f如3.14f默认就是double如3.14读入float变量scanf(%f, x);正确读入double变量必须用scanf(%lf, x);如果%lf写成%f程序不会报错但读进去的数据会变成乱码一个字节即8位2进制函数函数声明和函数定义的参数可以不一致但是个数和类型要一致 如 // 声明参数名叫 a int add(int a, int b); // 定义参数名叫 x 和 y完全合法没有任何问题 int add(int x, int y) { return x y; } 调用函数直接写函数名和参数不写类型如 d dist(h, p); printf(%d\n, gcd(x, y)); 如果参数是数组 声明 int OddSum( int List[], int N ); 定义 int OddSum( int List[], int N ) { int s0,m; for(m0;mN;m) { if(even(List[m])0) sList[m]; } return s; } 调用 printf(%d\n, OddSum(List, N)); //List是数组名原本是List[]调用去掉括号实参与形参******实参和形参 // 定义函数这里的 a 和 b 就是【形参】占位用的空盒子 int add(int a, int b) { return a b; } int main() { int x 10; int y 20; // 调用函数这里的 x 和 y或者直接写 10,20就是【实参】真实数据 int result add(x, y); return 0; } ******实参传给形参是“值传递”在函数里面修改形参的值绝对影响不到外面的实参 void change(int num) { // num 是形参 num 100; // 改的是复印件 } int main() { int a 5; // a 是实参原件 change(a); printf(%d, a); // 输出依然是 5原件没受影响 } ******通过指针改实参 // ❌ 错误写法传值复印件 void swap_wrong(int a, int b) { int temp a; a b; // 改的是复印件 b temp; // 外面的实原纹丝不动 } // ✅ 正确写法传指针门牌号 void swap_right(int *a, int *b) { // 形参是指针用来收地址a和b存的是地址如0x100 //这里的*相当于表明是地址的标志 int temp *a; // *a 意思是去门牌号 a 屋里拿数据这里的*a表示对应的数值不是地址 *a *b; // 去门牌号 b 屋里拿数据放到 a 屋里交换对应地址的值不是交换地址 *b temp; // 直接改屋里的原件 } int main() { int x 10, y 20; swap_right(x, y); // x 意思是把 x 的门牌号传给函数 printf(%d %d, x, y); // 输出20 10 原件真的被改了 } 定义函数时int *a 星号*表示这个变量存的是“地址” 调用函数时x 取地址符拿到变量的门牌号 函数内部*a 100; 星号*表示“去这个地址里放东西” 数组名本身就是地址所以传数组时不需要加直接写数组名就行比如func(arr) 数组名如 int arr[5] 里的 arr本身就是地址相当于 arr[0]。 所以传数组给函数时不需要加 直接写 func(arr)。 在函数定义时void func(int arr[]) 本质上会被编译器看成 void func(int *arr)。 因此在函数里修改 arr[0]外面实参数组的 arr[0] 也会被改掉因为它操作的就是原件地址算式符号-9%2-1 9%-21 -9%-2-2 -9%30 9%-30 -9%-30 %算式符号取决于被除数 %取模/取余运算符的两侧必须是整数类型 -9/2-4 9/-2-4 -9/-24 9/2.0 和 9.0/2 都一样为double类型 /算式取决于算式整体正负typedef__func__printf(%s,__func__); 可以输出当前函数名静态本地变量static关键字static如static int a1;静态本地变量只初始化一次意思就是静态本地变量和全局变量一样不会在函数结束后消失但是只能在局部函数内访问他返回指针的函数本地变量结束后他的地址可能会被其他新的本地变量占用故而这种办法很危险宏 #define像这种#define的形式就叫宏注意结尾不能有分号例子代码__LINE__ //这个源代码文件的行号 __FILE__ //源代码的文件名 __DATE__ //编译时的日期 __TIME__ //编译时的时间宏替换内容甚至可以是一个式子替换后会参与进替换的地方因此如果要保持整体性就要加括号包括参数使用的时候给x赋值就行了可以是数值也可以是变量项目例如会报错将两个.c文件放入同一个项目中程序才能正常运行两个.c文件被链接可以相互调用函数因为在max.c文件中定义了max函数故而这个main.c文件中不需要对这个函数进行声明都可以头文件从而能够判断是否报错max.h头文件把函数的原型放在头文件中main.c文件需要调用max函数所以要#incldue一下注意写自己的头文件的时候是双引号max.c文件也需要#include一下让编译器明白函数本体此时假设max.c文件中int改为double就会报错了如果没有这个头文件的加入改为double后会输出奇怪的东西不报错#include所以自定义的头文件最好用双引号不对外公开的函数跨文件 声明全局变量假设max.c中定义的全局变量gAll想在main.c中使用需要对这个全局变量进行声明max.c中的全局变量gAllmax.h头文件中加入声明extern int gAll; 意思是告诉编译器在整个项目的某个地方有一个全局变量gAllextern是声明变量的关键字如此便可在main.c中使用gAll标准头文件结构关键字#ifndef #define #endif例如可以防止重复编译避免报错