嵌入式之C/C++(一)关键字

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++ 规则,主要用于:

  1. C++ 代码调用 C 语言编写的函数 / 库;
  2. 解决 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 核心作用

  1. 定义常量:变量值不可修改,定义时必须初始化;

    cs 复制代码
    const int MAX_NUM = 100; // 正确
    // MAX_NUM = 200; // 错误:常量不可修改
    // const int MIN_NUM; // 错误:常量必须初始化
  2. 修饰函数参数:函数体内不能修改参数值;

    cs 复制代码
    void print_str(const char *str) {
        // str[0] = 'a'; // 错误:不能修改const参数
        printf("%s\n", str);
    }
  3. 修饰函数返回值:限制返回值的修改(仅指针 / 对象返回值有意义);

    cs 复制代码
    const char* get_str() {
        static char str[] = "hello";
        return str;
    }
    // char *p = get_str(); // 错误:返回值是const,不能赋值给非const指针
    const char *p = get_str(); // 正确
  4. 节省内存: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;
}
相关推荐
diediedei2 小时前
C++构建缓存加速
开发语言·c++·算法
晚霞的不甘2 小时前
Flutter for OpenHarmony 电商 App 搜索功能深度解析:从点击到反馈的完整实现
开发语言·前端·javascript·flutter·前端框架
{Hello World}2 小时前
Java内部类:深入解析四大类型与应用
java·开发语言
春日见2 小时前
C++单例模式 (Singleton Pattern)
java·运维·开发语言·驱动开发·算法·docker·单例模式
Remember_9932 小时前
网络原理初识:从基础概念到协议分层
开发语言·网络·php
LOYURU2 小时前
Centos7.6安装Go
开发语言·后端·golang
小二·2 小时前
Go 语言系统编程与云原生开发实战(第1篇):从零搭建你的第一个 Go 服务 —— 理解 GOPATH、Modules 与现代 Go 工作流
开发语言·云原生·golang
星期五不见面2 小时前
嵌入式学习!(一)C++学习-STL(21)-26/1/27
开发语言·c++·学习
大白要努力!2 小时前
Android Spinner自定义背景
java·开发语言