volatile 关键字的作用
volatile 关键字的核心作用,简单来说就是 **“告诉编译器:这个变量的 value 可能会被你不知道的方式偷偷修改,千万别自作主张优化它 —— 每次使用时,必须去内存里面读取最新的值,别为了快速使用寄存器里面的旧数据”**。
具体场景如下:
-
多线程共享的变量一个线程修改了变量,另一个线程如果没有加 volatile,编译器可能觉得 “本线程没有改动它”,就一直使用寄存器里的旧值,导致读不到新数据。加了 volatile 后,能保证每次读的都是内存里的最新值。
-
中断处理里的标志位比如硬件触发中断后,会修改一个变量(例如
flag = 1;)。如果没有 volatile,编译器可能觉得这段代码没有修改 flag,就优化掉对 flag 的判断,导致程序不响应中断。加了 volatile 后,每次都会去内存读 flag,确保能看到硬件的修改。 -
硬件寄存器像传感器的状态值、外设的控制寄存器,硬件会直接修改它们的内存值。这时候必须用 volatile,否则编译器可能缓存旧值,导致程序读不到真实状态。
不过需要注意:volatile 只解决 “编译器优化导致读不到最新值” 的问题,不保证操作的原子性。比如多线程中同时读写一个 volatile 变量,还是可能出现 “读一半被改了” 的情况,这时候需要靠锁等同步机制,volatile 无法处理。
不能做 switch 的参数类型
C 语言中,switch 的参数类型有严格限制,以下几类不能作为 switch 的参数:
-
浮点型浮点数在内存中表示有精度问题(例如 0.1 在二进制中是无限循环的近似值)。如果用浮点型作为 switch 参数,case 后的常量和参数的实际值可能因精度差异不匹配,导致逻辑混乱。因此 C 语言直接禁止浮点型作为 switch 参数。
-
字符串(char * 类型)字符串本质是指针,不属于整数类型。且 switch 的 case 后必须跟整数常量表达式,字符串无法作为这种常量。例如想根据不同字符串分支,不能直接写
switch (str),需用strcmp配合if-else。 -
结构体、联合体等复合类型这些类型不是单一整数,内存结构复杂,无法作为 switch 参数,编译器会直接报错。
总结:switch 的参数必须是 “整数类型”(如 int、char、short、long、枚举类型 enum),因为这些类型能准确比较,且 case 后的常量可对应。除整数类型外,基本都不能作为 switch 参数。
断言宏的作用
断言宏(如 C 语言中的assert)主要是调试阶段的 “哨兵”,作用是帮助开发者在开发时快速揪出程序中的逻辑错误。
具体用法:assert(条件),例如assert(p != NULL),意思是 “在当前逻辑中,p 绝对不能为 NULL,若违反则报错”。
- 如果程序运行到该行时,条件为真(如 p 确实不为 NULL),assert 不做任何操作,程序继续运行;
- 如果条件为假(如 p 意外为 NULL),assert 会立即报错,打印文件名、行号、条件表达式,然后终止程序。
核心价值是 **“提前暴露问题”**。例如代码中默认某个参数有效、某个下标不越界,这些假设若隐藏错误,平时可能不明显,但上线后可能出大问题。用 assert 将假设 “显式量化”,调试时一旦违反,能快速定位到具体位置,减少排查成本。
注意:
- assert 是调试工具,发布版本(编译时定义
NDEBUG宏)会被关闭,不执行也不占运行时开销; - 与普通错误处理(如
if判断)的区别:assert 用于 “绝对不该发生的情况(代码 bug)”,而if用于处理 “可能发生的预期错误(如用户输入错误)”,发布版本需保留if判断。
因此,对于 “理论上必须成立” 的逻辑(如函数参数不为 NULL、循环变量不越界),建议用 assert,调试时能减少很多坑。
浮点数的内存模型
(注:原内容未补充,此处暂空。通常浮点数内存模型遵循 IEEE 754 标准,分为符号位、指数位、尾数位,例如 float 占 32 位(1 符号 + 8 指数 + 23 尾数),double 占 64 位(1 符号 + 11 指数 + 52 尾数),通过科学计数法表示数值。)
关系运算符与逻辑运算符的优先级
C 语言中,关系运算符和逻辑运算符的优先级从高到低为:单目逻辑运算符(!)> 关系运算符 > 双目逻辑运算符(&&、||),具体分层如下:
-
逻辑非(!)单目运算符,优先级最高,高于所有关系运算符。例如
!a == b,会先算!a(取 a 的逻辑非),再用结果与 b 比较(==是关系运算符)。 -
关系运算符整体优先级高于双目逻辑运算符(&&、||),但内部有高低:
- 高优先级:
<、>、<=、>=(如 “大于”“小于等于”); - 低优先级:
==(等于)、!=(不等于)。
例如
a > b == c,先算a > b(结果为 0 或 1),再用结果与 c 比较==。 - 高优先级:
-
双目逻辑运算符
&&(逻辑与)优先级高于||(逻辑或),但均低于所有关系运算符。例如
a < b && c > d || e == f,执行顺序为:先算a < b和c > d(关系运算),再算两者的&&,最后用结果与e == f(关系运算)的结果做||。
常见例子:判断 “x 大于 1 且 x 小于 10”,需写成1 < x && x < 10(因<优先级高于&&,先算两边关系运算,再做逻辑与)。若误写成1 < x < 10,会先算1 < x(得 0 或 1),再与 10 比较,逻辑错误。
总结优先级(从高到低):! > <、>、<=、>= > ==、!= > && > ||。不确定时,加括号()可明确顺序,避免依赖优先级出错。
函数的作用
函数是 C 语言的核心工具,主要作用如下:
-
代码复用一段逻辑(如计算最大公约数、打印日志)需在多处使用时,写成函数后可直接调用,无需重复编写。修改时仅需改函数本身,所有调用处同步生效,减少代码量和维护成本。
-
模块化拆分大程序(如管理系统)功能复杂,若全堆在
main函数中会混乱不堪。用函数拆分功能(如 “用户登录”“数据查询” 各一个函数),每个函数只负责一件事,程序结构清晰,便于定位问题。 -
抽象和封装隐藏实现细节,调用者只需知道 “传什么参数,得什么结果”,无需关心内部逻辑。例如 C 标准库的
printf,使用者无需了解硬件操作,仅传字符串即可输出,降低使用难度,提升协作效率。 -
方便调试和维护函数化代码出错时,可逐个测试函数是否正常,快速定位问题;维护时,新增功能可加新函数,修改旧功能仅改对应函数,减少对其他部分的影响。
-
支持递归可解决循环难以处理的问题(如二叉树遍历、汉诺塔),用递归写逻辑更直观清晰。
总之,函数将代码 “化整为零”,让程序更好写、读、改,是结构化编程的核心。
逻辑运算符有哪些
C 语言中的逻辑运算符共 3 个,用于处理 “真(非 0 值)” 和 “假(0 值)” 的逻辑判断,返回结果为 0(假)或 1(真)。
-
逻辑非(!)
- 类型:单目运算符(仅需 1 个操作数);
- 规则:真变假,假变真(如
!5结果为 0,!0结果为 1)。
-
逻辑与(&&)
- 类型:双目运算符(需 2 个操作数,格式:
左操作数 && 右操作数); - 规则:两操作数都为真(非 0)时结果为 1,否则为 0;
- 特性:短路求值 —— 左操作数为假时,直接返回 0,不执行右操作数(结果已确定)。
- 类型:双目运算符(需 2 个操作数,格式:
-
逻辑或(||)
- 类型:双目运算符(格式:
左操作数 || 右操作数); - 规则:两操作数至少一个为真(非 0)时结果为 1,都为假时为 0;
- 特性:短路求值 —— 左操作数为真时,直接返回 1,不执行右操作数(结果已确定)。
- 类型:双目运算符(格式:
注意:区分逻辑运算符与按位运算符
- 逻辑运算符(
&&、||)处理 “真 / 假”,返回 0 或 1; - 按位运算符(
&、|)处理二进制位,按位运算,返回运算后的数值。
例如:
3 && 5; // 结果为1(都为真)
3 & 5; // 结果为1(二进制 011 & 101 = 001)
前 ++ 与后 ++ 哪个效率高?为什么?
在 C 语言中,前 ++(++i)通常比后 ++(i++)效率更高,核心原因是两者操作逻辑和实现步骤不同,关键在于 “是否需要保存原始值”。
-
操作逻辑的差异
-
前 ++(++i):先自增,再返回自增后的值。步骤:直接修改变量(+1),然后使用新值参与运算。例:
int a = ++i;→ 先i = i + 1,再将i的新值赋给a。 -
后 ++(i++):先返回原始值,再自增。步骤:需先用临时变量保存原始值,再自增(+1),最后返回临时变量的值。例:
int a = i++;→ 先存i的原始值到临时变量,再i = i + 1,最后将临时变量的值赋给a。
-
-
效率差异的根源后 ++ 比前 ++ 多了 “保存原始值到临时变量” 的操作。对于基本类型(int、char、指针等),临时变量开销虽小,但 “额外的存储和读取” 比前 ++ 的 “直接修改” 多一步,因此理论上效率更低。
-
特殊场景:编译器优化可能消除差异在 “自增结果不被使用” 的场景(如 for 循环的
i++),现代编译器会优化:例:for (int i = 0; i < 10; i++)中,i++的返回值未被使用,编译器会将其优化为与++i等价的操作(省去保存临时变量),此时两者效率相同。
总结:
- 本质区别:后 ++ 需额外保存原始值,前 ++ 无需临时变量,步骤更少;
- 效率结论:需使用自增后的值时(如
a = i++vsa = ++i),前 ++ 效率更高;自增结果无意义时(如循环),编译器优化后两者效率一致。
实际开发中,不必过度纠结效率(差异微乎其微),更应关注逻辑正确性:需 “先用后增” 用后 ++,需 “先增后用” 用前 ++。
C 语言清空缓冲区的几种情况
C 语言中,缓冲区清空的常见情况如下:
-
程序正常结束后作为
main函数返回工作的一部分,系统会自动清空所有输出缓冲区。 -
手动调用
fflush函数通过fflush(stdout)(刷新标准输出缓冲区)或fflush(FILE *stream)(刷新指定流的缓冲区)手动触发清空。 -
遇到换行符
\n对于行缓冲(如标准输出 stdout 在终端模式下),当输出内容包含\n时,会自动刷新缓冲区。 -
缓冲区已满当缓冲区存储的数据达到其容量上限时,会自动清空并输出内容。
全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?
可以。
如果不想让全局变量或者函数跨文件访问,只在文件的内部被访问,在头文件或不同的c文件中用static来修饰同名全局变量。
相反,如果要在多个文件中共享全局变量和函数,可以在不同的c文件中声明同名的全局变量,前提是其中只能有一个c文件中对此变量赋值,此时链接不会出错;同时,在定义全局变量的源文件中,使用 extern 关键字进行外部变量声明。
注意:extern和static不能同时修饰同一变量。
&spm=1001.2101.3001.5002&articleId=153740093&d=1&t=3&u=7765d81334cb419a8843d25b13d3817b)
1240

被折叠的 条评论
为什么被折叠?



