
1 C语言宏中"#"和"##"的用法?
答: 宏定义中的#和##是预处理阶段的特殊操作符。
① "#"是字符串化操作符 。作用 :将宏定义中的传入参数名转换成用一对双引号括起来的参数字符串。使用规则:只能用于有传入参数的宏定义中,且必须置于宏定义体中的参数名前。
cs
#include <stdio.h>
// 基础示例
#define example(instr) printf("输入的字符串是:\t%s\n", #instr)
// 直接返回字符串
#define str_wrap(instr) #instr
int main() {
example(hello_world); // 展开为:printf("输入的字符串是:\t%s\n","hello_world")
const char* str = str_wrap(12345); // 展开为:const char* str = "12345";
printf("str = %s\n", str);
return 0;
}
输出结果:
cs
输入的字符串是: hello_world
str = 12345
②"##"符号连接操作符。作用 :将宏定义的多个形参(或形参与固定字符)拼接成一个实际的标识符。使用规则:
##前后的空格可有可无;- 拼接后的标识符必须是实际存在的(变量/宏);
##会阻止其后面参数的宏展开。
cs
#include <stdio.h>
#include <string.h>
#define STRCPY(a, b) strcpy(a ## _p, #b)
int main() {
char var1_p[20];
char var2_p[30];
strcpy(var1_p, "aaaa");
strcpy(var2_p, "bbbb");
STRCPY(var1, var2); // 展开为:strcpy(var1_p, "var2")
STRCPY(var2, var1); // 展开为:strcpy(var2_p, "var1")
printf("var1_p = %s\n", var1_p);
printf("var2_p = %s\n", var2_p);
// 注意:以下代码会报错(##阻止宏展开)
// STRCPY(STRCPY(var1,var2),var2);
// 展开结果:strcpy(STRCPY(var1,var2)_p,"var2")
return 0;
}
输出结果:
bash
var1_p = var2
var2_p = var1
2 volatile 关键字的含义与应用场景
volatile的核心含义:告诉编译器该变量的值可能被程序外部因素(硬件/中断/其他线程)修改,禁止编译器对该变量进行优化(如缓存到寄存器),每次访问必须直接读写内存。
典型应用场景(3 个核心例子)
①并行设备的硬件寄存器
存储器映射的硬件寄存器(如串口、定时器寄存器)随时可能被外设修改,必须用volatile修饰:
cs
// 示例:串口数据寄存器
volatile unsigned char *UART_DATA = (unsigned char *)0x1000;
// 每次读取都直接访问0x1000地址,而非寄存器缓存
unsigned char data = *UART_DATA;
②中断服务程序中修改的变量
中断服务程序(ISR)修改的变量,主程序检测时需避免编译器优化:
cs
// 全局标志位,由中断修改
volatile int interrupt_flag = 0;
// 中断服务函数
void timer_isr() {
interrupt_flag = 1; // 中断触发时置位
}
// 主程序
int main() {
while(1) {
// 每次都读取内存中的interrupt_flag,而非缓存值
if(interrupt_flag) {
// 处理中断逻辑
interrupt_flag = 0;
}
}
return 0;
}
③多线程共享变量
多线程共享的变量,防止编译器优化导致线程间数据不一致:
cs
// 线程1和线程2共享的标志位
volatile int thread_running = 1;
// 线程1
void thread1() {
while(thread_running) {
// 业务逻辑
}
}
// 线程2
void thread2() {
// 停止线程1
thread_running = 0;
}
3 static 关键字的作用
static的核心作用是限制作用域 和延长生命周期,具体分3种场景:
①函数体内的 static 变量
- 仅初始化一次,生命周期与程序一致;
- 函数调用结束后值不销毁,下次调用仍保留上次结果。
cs
#include <stdio.h>
int count() {
static int num = 0; // 仅初始化一次
num++;
return num;
}
int main() {
printf("%d\n", count()); // 输出1
printf("%d\n", count()); // 输出2
printf("%d\n", count()); // 输出3
return 0;
}
②函数体外(模块内)的 static 变量
- 作用域限制在当前源文件(.c),其他文件无法通过
extern访问; - 本质是 "本地全局变量",避免全局变量的命名冲突。
③static 修饰函数
- 函数的作用域限制在当前源文件,其他文件无法调用;
- 常用于封装模块内部的辅助函数,避免外部调用。
3.1 为什么 static 变量只初始化一次?
static 变量存储在静态存储区,生命周期与程序一致(程序启动时分配内存,退出时释放),因此初始化仅在程序启动时执行一次;而 auto 变量(局部非 static)存储在栈区,函数调用时分配、结束时销毁,每次调用都会重新初始化。
4 extern "C" 的作用
extern "C"的核心作用:指示编译器按 C 语言的规则编译代码,而非 C++ 规则,主要用于:
- C++ 代码调用 C 语言编写的函数 / 库;
- 解决 C++ 的函数重载导致的命名修饰(name mangling)问题。
cs
// C语言文件(test.c)
#include <stdio.h>
void print_hello() {
printf("Hello C\n");
}
// C++文件(main.cpp)
extern "C" {
// 声明C语言函数,按C规则编译
void print_hello();
}
int main() {
print_hello(); // 正确调用C语言函数
return 0;
}
5 const 关键字的作用与使用场景
const的核心作用:定义只读的常量/限制变量修改,提升代码安全性和可读性。
5.1 核心作用
-
定义常量:变量值不可修改,定义时必须初始化;
csconst int MAX_NUM = 100; // 正确 // MAX_NUM = 200; // 错误:常量不可修改 // const int MIN_NUM; // 错误:常量必须初始化 -
修饰函数参数:函数体内不能修改参数值;
csvoid print_str(const char *str) { // str[0] = 'a'; // 错误:不能修改const参数 printf("%s\n", str); } -
修饰函数返回值:限制返回值的修改(仅指针 / 对象返回值有意义);
csconst char* get_str() { static char str[] = "hello"; return str; } // char *p = get_str(); // 错误:返回值是const,不能赋值给非const指针 const char *p = get_str(); // 正确 -
节省内存:const 常量仅分配一次内存,宏常量每次使用都会重新分配。
5.2 常见使用场景
| 场景 | 示例代码 |
|---|---|
| 修饰常数组 | const int arr[5] = {1,2,3,4,5}; |
| 常量指针(指向常量的指针) | const int *p; // p可改,*p不可改 |
| 指针常量(指针本身是常量) | int *const p; // p不可改,*p可改 |
| 常引用 | void func(const int &x); |
6 new/delete 与 malloc/free 的区别
| 特性 | new/delete | malloc/free |
|---|---|---|
| 本质 | C++ 操作符 | C 标准库函数 |
| 构造 / 析构 | 自动调用构造 / 析构函数 | 仅分配 / 释放内存,无构造 / 析构 |
| 返回值 | 直接返回对应类型指针 | 返回 void*,需强制类型转换 |
| 内存计算 | 自动计算所需内存大小 | 需手动计算(如malloc(sizeof(int))) |
| 异常处理 | 分配失败抛出 bad_alloc 异常 | 分配失败返回 NULL |
7 sizeof 与 strlen 的核心区别
7.1 基础示例
cs
#include <stdio.h>
#include <string.h>
int main() {
// 核心区别演示
char str[] = "\0";
printf("strlen(\"\\0\") = %zu\n", strlen(str)); // 输出0
printf("sizeof(\"\\0\") = %zu\n", sizeof(str)); // 输出2(\0 + 字符串结束符)
char arr[20] = "0123456789";
printf("strlen(arr) = %zu\n", strlen(arr)); // 输出10(实际字符数)
printf("sizeof(arr) = %zu\n", sizeof(arr)); // 输出20(数组总大小)
return 0;
}
7.2 核心区别总结
| 特性 | sizeof | strlen |
|---|---|---|
| 本质 | 关键字 / 运算符 | 库函数 |
| 计算时机 | 编译期计算 | 运行期计算 |
| 计算范围 | 整个变量 / 类型的内存大小 | 从起始地址到第一个 '\0' 的字符数 |
| 参数类型 | 支持类型、变量、函数 | 仅支持以 '\0' 结尾的 char* |
| 返回值 | size_t(无符号整数) | size_t(无符号整数) |
7.3 不使用 sizeof 求变量字节数
利用指针偏移计算内存大小:
cs
#include <stdio.h>
#define MySizeof(value) (char*)(&value + 1) - (char*)&value
int main() {
int i;
double f;
double *q;
printf("int 字节数:%d\n", MySizeof(i)); // 输出4
printf("double 字节数:%d\n", MySizeof(f));// 输出8
printf("double* 字节数:%d\n", MySizeof(q));// 输出4(32位系统)
return 0;
}
8 struct 与 union 的区别
| 特性 | struct(结构体) | union(联合体) |
|---|---|---|
| 内存分配 | 所有成员内存累加(需字节对齐) | 所有成员共享同一块内存,大小为最长成员的大小(需对齐) |
| 成员访问 | 各成员独立存在,可同时访问 | 仅能访问当前赋值的成员,赋值新成员会覆盖旧值 |
| 内存利用率 | 低(成员独立占内存) | 高(共享内存) |
cs
#include <stdio.h>
// 联合体:最大成员是int[5](20字节),8字节对齐后为24字节
typedef union {double i; int k[5]; char c;} DATE;
// 结构体:int(4) + DATE(24) + double(8) = 36,8字节对齐后为40字节
typedef struct { int cat; DATE cow; double dog; } TOO;
int main() {
DATE max;
// 40 + 24 = 64
printf("sizeof(TOO) + sizeof(DATE) = %zu\n", sizeof(TOO) + sizeof(max));
return 0;
}
输出结果 :64
9 左值与右值
- 左值(lvalue):可出现在等号左边,具有可寻址性(可修改),如变量、数组元素、指针解引用;
- 右值(rvalue):仅能出现在等号右边,无持久地址(只读),如字面量、表达式结果、函数返回值。
示例:
cs
int a = 10; // a是左值,10是右值
int b = a + 5; // a+5是右值,不能赋值:a+5 = 20;(错误)
10 短路求值
短路求值是逻辑运算符(||/&&)的特性:
||:只要左侧表达式为真,右侧表达式不执行;&&:只要左侧表达式为假,右侧表达式不执行。
示例代码:
cs
#include <stdio.h>
int main() {
int i = 6, j = 1;
// i>0为真,j++不执行
if(i>0 || (j++)>0);
printf("j = %d\n", j); // 输出1
int m = 0, n = 2;
// m>0为假,n++不执行
if(m>0 && (n++)>0);
printf("n = %d\n", n); // 输出2
return 0;
}
11 ++a 与 a++ 的区别
| 特性 | ++a(前置自增) | a++(后置自增) |
|---|---|---|
| 执行顺序 | 先自增,后返回值 | 先返回原值,后自增 |
| 实现逻辑 | a=a+1; return a; |
int temp=a; a=a+1; return temp; |
| 效率 | 更高(无临时变量) | 更低(需复制临时变量) |
示例代码:
cs
#include <stdio.h>
int main() {
int a = 5;
printf("++a = %d\n", ++a); // 输出6,a=6
printf("a++ = %d\n", a++); // 输出6,a=7
printf("a = %d\n", a); // 输出7
return 0;
}