c语言十题(持续更新)

volatile 关键字的作用

volatile 关键字的核心作用,简单来说就是 **“告诉编译器:这个变量的 value 可能会被你不知道的方式偷偷修改,千万别自作主张优化它 —— 每次使用时,必须去内存里面读取最新的值,别为了快速使用寄存器里面的旧数据”**。

具体场景如下:

  1. 多线程共享的变量一个线程修改了变量,另一个线程如果没有加 volatile,编译器可能觉得 “本线程没有改动它”,就一直使用寄存器里的旧值,导致读不到新数据。加了 volatile 后,能保证每次读的都是内存里的最新值。

  2. 中断处理里的标志位比如硬件触发中断后,会修改一个变量(例如flag = 1;)。如果没有 volatile,编译器可能觉得这段代码没有修改 flag,就优化掉对 flag 的判断,导致程序不响应中断。加了 volatile 后,每次都会去内存读 flag,确保能看到硬件的修改。

  3. 硬件寄存器像传感器的状态值、外设的控制寄存器,硬件会直接修改它们的内存值。这时候必须用 volatile,否则编译器可能缓存旧值,导致程序读不到真实状态。

不过需要注意:volatile 只解决 “编译器优化导致读不到最新值” 的问题,不保证操作的原子性。比如多线程中同时读写一个 volatile 变量,还是可能出现 “读一半被改了” 的情况,这时候需要靠锁等同步机制,volatile 无法处理。

不能做 switch 的参数类型

C 语言中,switch 的参数类型有严格限制,以下几类不能作为 switch 的参数:

  1. 浮点型浮点数在内存中表示有精度问题(例如 0.1 在二进制中是无限循环的近似值)。如果用浮点型作为 switch 参数,case 后的常量和参数的实际值可能因精度差异不匹配,导致逻辑混乱。因此 C 语言直接禁止浮点型作为 switch 参数。

  2. 字符串(char * 类型)字符串本质是指针,不属于整数类型。且 switch 的 case 后必须跟整数常量表达式,字符串无法作为这种常量。例如想根据不同字符串分支,不能直接写switch (str),需用strcmp配合if-else

  3. 结构体、联合体等复合类型这些类型不是单一整数,内存结构复杂,无法作为 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 语言中,关系运算符和逻辑运算符的优先级从高到低为:单目逻辑运算符(!)> 关系运算符 > 双目逻辑运算符(&&、||),具体分层如下:

  1. 逻辑非(!)单目运算符,优先级最高,高于所有关系运算符。例如!a == b,会先算!a(取 a 的逻辑非),再用结果与 b 比较(==是关系运算符)。

  2. 关系运算符整体优先级高于双目逻辑运算符(&&、||),但内部有高低:

    • 高优先级:<><=>=(如 “大于”“小于等于”);
    • 低优先级:==(等于)、!=(不等于)。

    例如a > b == c,先算a > b(结果为 0 或 1),再用结果与 c 比较==

  3. 双目逻辑运算符&&(逻辑与)优先级高于||(逻辑或),但均低于所有关系运算符。

    例如a < b && c > d || e == f,执行顺序为:先算a < bc > d(关系运算),再算两者的&&,最后用结果与e == f(关系运算)的结果做||

常见例子:判断 “x 大于 1 且 x 小于 10”,需写成1 < x && x < 10(因<优先级高于&&,先算两边关系运算,再做逻辑与)。若误写成1 < x < 10,会先算1 < x(得 0 或 1),再与 10 比较,逻辑错误。

总结优先级(从高到低):! > <><=>= > ==!= > && > ||。不确定时,加括号()可明确顺序,避免依赖优先级出错。

函数的作用

函数是 C 语言的核心工具,主要作用如下:

  1. 代码复用一段逻辑(如计算最大公约数、打印日志)需在多处使用时,写成函数后可直接调用,无需重复编写。修改时仅需改函数本身,所有调用处同步生效,减少代码量和维护成本。

  2. 模块化拆分大程序(如管理系统)功能复杂,若全堆在main函数中会混乱不堪。用函数拆分功能(如 “用户登录”“数据查询” 各一个函数),每个函数只负责一件事,程序结构清晰,便于定位问题。

  3. 抽象和封装隐藏实现细节,调用者只需知道 “传什么参数,得什么结果”,无需关心内部逻辑。例如 C 标准库的printf,使用者无需了解硬件操作,仅传字符串即可输出,降低使用难度,提升协作效率。

  4. 方便调试和维护函数化代码出错时,可逐个测试函数是否正常,快速定位问题;维护时,新增功能可加新函数,修改旧功能仅改对应函数,减少对其他部分的影响。

  5. 支持递归可解决循环难以处理的问题(如二叉树遍历、汉诺塔),用递归写逻辑更直观清晰。

总之,函数将代码 “化整为零”,让程序更好写、读、改,是结构化编程的核心。

逻辑运算符有哪些

C 语言中的逻辑运算符共 3 个,用于处理 “真(非 0 值)” 和 “假(0 值)” 的逻辑判断,返回结果为 0(假)或 1(真)。

  1. 逻辑非(!)

    • 类型:单目运算符(仅需 1 个操作数);
    • 规则:真变假,假变真(如!5结果为 0,!0结果为 1)。
  2. 逻辑与(&&)

    • 类型:双目运算符(需 2 个操作数,格式:左操作数 && 右操作数);
    • 规则:两操作数都为真(非 0)时结果为 1,否则为 0;
    • 特性:短路求值 —— 左操作数为假时,直接返回 0,不执行右操作数(结果已确定)。
  3. 逻辑或(||)

    • 类型:双目运算符(格式:左操作数 || 右操作数);
    • 规则:两操作数至少一个为真(非 0)时结果为 1,都为假时为 0;
    • 特性:短路求值 —— 左操作数为真时,直接返回 1,不执行右操作数(结果已确定)。

注意:区分逻辑运算符与按位运算符

  • 逻辑运算符(&&||)处理 “真 / 假”,返回 0 或 1;
  • 按位运算符(&|)处理二进制位,按位运算,返回运算后的数值。

例如:

3 && 5;  // 结果为1(都为真)
3 & 5;   // 结果为1(二进制 011 & 101 = 001)

前 ++ 与后 ++ 哪个效率高?为什么?

在 C 语言中,前 ++(++i)通常比后 ++(i++)效率更高,核心原因是两者操作逻辑和实现步骤不同,关键在于 “是否需要保存原始值”。

  1. 操作逻辑的差异

    • 前 ++(++i):先自增,再返回自增后的值。步骤:直接修改变量(+1),然后使用新值参与运算。例:int a = ++i; → 先i = i + 1,再将i的新值赋给a

    • 后 ++(i++):先返回原始值,再自增。步骤:需先用临时变量保存原始值,再自增(+1),最后返回临时变量的值。例:int a = i++; → 先存i的原始值到临时变量,再i = i + 1,最后将临时变量的值赋给a

  2. 效率差异的根源后 ++ 比前 ++ 多了 “保存原始值到临时变量” 的操作。对于基本类型(int、char、指针等),临时变量开销虽小,但 “额外的存储和读取” 比前 ++ 的 “直接修改” 多一步,因此理论上效率更低。

  3. 特殊场景:编译器优化可能消除差异在 “自增结果不被使用” 的场景(如 for 循环的i++),现代编译器会优化:例:for (int i = 0; i < 10; i++)中,i++的返回值未被使用,编译器会将其优化为与++i等价的操作(省去保存临时变量),此时两者效率相同。

总结:

  • 本质区别:后 ++ 需额外保存原始值,前 ++ 无需临时变量,步骤更少;
  • 效率结论:需使用自增后的值时(如a = i++ vs a = ++i),前 ++ 效率更高;自增结果无意义时(如循环),编译器优化后两者效率一致。

实际开发中,不必过度纠结效率(差异微乎其微),更应关注逻辑正确性:需 “先用后增” 用后 ++,需 “先增后用” 用前 ++。

C 语言清空缓冲区的几种情况

C 语言中,缓冲区清空的常见情况如下:

  1. 程序正常结束后作为main函数返回工作的一部分,系统会自动清空所有输出缓冲区。

  2. 手动调用fflush函数通过fflush(stdout)(刷新标准输出缓冲区)或fflush(FILE *stream)(刷新指定流的缓冲区)手动触发清空。

  3. 遇到换行符\n对于行缓冲(如标准输出 stdout 在终端模式下),当输出内容包含\n时,会自动刷新缓冲区。

  4. 缓冲区已满当缓冲区存储的数据达到其容量上限时,会自动清空并输出内容。

全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?

可以。

如果不想让全局变量或者函数跨文件访问,只在文件的内部被访问,在头文件或不同的c文件中用static来修饰同名全局变量。

相反,如果要在多个文件中共享全局变量和函数,可以在不同的c文件中声明同名的全局变量,前提是其中只能有一个c文件中对此变量赋值,此时链接不会出错;同时,在定义全局变量的源文件中,使用 extern 关键字进行外部变量声明。

注意:extern和static不能同时修饰同一变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值