嵌入式面经笔记

知识点:C++ 运算符

Q1:C++ 运算符优先级怎么记?

回答模板:

C++ 运算符优先级从高到低可以这样记:

最高()[]->.(括号和成员访问)

!~++--(单目运算符)

*/%(乘除)→ +-(加减)

中低<><=>=(关系比较)→ ==!=(等于比较)

&&(逻辑与)→ ||(逻辑或)

最低?:(条件运算符)→ =(赋值运算符)

记忆口诀:"单目 > 算术 > 关系 > 逻辑 > 赋值 "

Q2:!~ 的区别是什么?

回答模板:

! 是逻辑非,~ 是按位取反。

运算符 类型 作用 示例
! 逻辑非 非0→0,0→1 !5 = 0!0 = 1
~ 按位取反 每一位0→1,1→0 ~5 = -6

详细解释:

cpp 复制代码
// ! 逻辑非(针对整个值)
int a = 5;
!a == 0;   // 5 非0,所以为 0

int b = 0;
!b == 1;   // 0 取反为 1

bool flag = true;
!flag == false;

// ~ 按位取反(针对每一位)
// 5 = 0000 0101
// ~5 = 1111 1010 = -6
int c = 5;
int d = ~c;  // d = -6

Q3:什么是"单目运算符"?为什么优先级高?

回答模板:

单目运算符是只操作一个操作数 的运算符,如 !~++---(负号)。

它们优先级最高,是因为它们通常直接作用于一个变量,需要先计算才能参与后续运算。

示例:

cpp 复制代码
int a = 5;
int b = 10;
int c = -a + b;  // 先计算 -a(单目),再计算加法

// 相当于:int c = (-a) + b;

Q4:++ii++ 的区别?

回答模板:

写法 含义 返回值 常见用法
++i 先自增,后使用 自增后的值 推荐(效率略高)
i++ 先使用,后自增 自增前的值 需要原值时使用
cpp 复制代码
int i = 5;
int a = ++i;  // i=6, a=6(先加后用)
int b = i++;  // b=6, i=7(先用后加)

Q5:什么是"右结合"运算符?

回答模板:

大多数运算符是左结合 的(从左到右计算),但单目运算符、条件运算符、赋值运算符是右结合的(从右到左计算)。

cpp 复制代码
// 右结合示例
int a = b = c = 5;
// 从右到左执行:c=5, b=c, a=b
// 等价于:int a = (b = (c = 5));

// 条件运算符也是右结合
int result = a > b ? a : b > c ? b : c;
// 等价于:int result = a > b ? a : (b > c ? b : c);

Q6:写代码时如何避免优先级错误?

回答模板:

我遵循一个原则:不确定优先级时,用括号明确表达意图

cpp 复制代码
// ❌ 不清晰(需要查优先级)
if (a > b && c > d || e > f)

// ✅ 清晰(括号明确优先级)
if ((a > b && c > d) || e > f)

6."三目运算符是 C++ 唯一的三目运算符,格式是 条件 ? 表达式1 : 表达式2,条件为真返回表达式1,为假返回表达式2。它适合简单的条件赋值,比如 int max = (a > b) ? a : b。复杂逻辑建议用 if-else,可读性更好。"

7.源码、补码、反码

"计算机存储整型数值时,最左边的一位是符号位,0 代表正数,1 代表负数。

原码 就是二进制本身,比如 10 的原码是 0000 1010,-10 的原码是 1000 1010

反码 :正数的反码和原码一样。负数的反码是符号位不变,其他位 0 变 1、1 变 0。比如 -10 原码 1000 1010,反码就是 1111 0101

补码 :正数的补码和原码一样。负数的补码是反码加 1。比如 -10 反码 1111 0101,补码就是 1111 0110

计算机实际存储用的是补码,因为补码可以把减法统一成加法来运算,简化了硬件设计。"

"原码就是二进制本身,反码正数不变、负数按位取反,补码是反码加 1。计算机存储和运算都用补码。"

为什么要用补码?

"如果用原码,正数和负数的加法需要单独处理,硬件更复杂。补码的好处是可以把减法变成加法,比如 5 - 3 可以变成 5 + (-3),统一用加法器处理,简化了硬件电路设计。"

举例

"比如 10 + (-10)

  • 原码:0000 1010 + 1000 1010 = 1001 0100(-20)❌ 错误!

  • 补码:0000 1010 + 1111 0110 = 1 0000 0000,溢出舍去高位,结果是 0 ✅ 正确!

这就是为什么计算机用补码的原因。"

8.短路求值

一句话定义

逻辑运算中,如果左侧表达式已经能确定结果,右侧表达式就不会执行。

两个核心规则

运算符 规则 说明
&&(逻辑与) 左假则右不执行 因为 false && anything = false
` `(逻辑或)

有符号转无符号,运算是补码运算,输出看格式。

cpp 复制代码
int a = -7;           // 有符号
unsigned int b = 3;   // 无符号

// a + b 时,a 被隐式转换为无符号数
// 按补码进行运算
// 打印结果取决于格式

原码、反码、补码总结表

数值 原码 反码 补码
7 0000 0111 0000 0111 0000 0111
-7 1000 0111 1111 1000 1111 1001
3 0000 0011 0000 0011 0000 0011
-4 1000 0100 1111 1011 1111 1100

8."% 是取模运算符,用来计算整数除法的余数。两边必须是整数类型 ,如果一边是浮点数,编译会报错。如果是浮点数取余,可以用 fmod() 函数。"

9."三目运算符格式是 条件 ? 表达式1 : 表达式2,条件为真返回表达式1,为假返回表达式2。注意两个表达式类型要一致,并且 % 运算符两边必须是整数。"

1.continue 关键字定义

continue 跳过本次循环剩余的代码,立即进入下一次循环判断。

注意:continue 不会终止整个循环,只结束当前这一轮!

核心理解

cpp 复制代码
┌─────────────────────────────────────────────────────────────────────┐
│  for (i = 0; i < 5; i++) {                                        │
│      if (i == 2) {                                               │
│          continue;  ← 跳过本次循环剩下的代码                       │
│      }                                                           │
│      printf("%d", i);  ← i=2 时这行不执行                         │
│  }                                                               │
│  输出:0 1 3 4                                                    │
└─────────────────────────────────────────────────────────────────────┘

continue vs break

对比项 continue break
作用 跳过本次循环 终止整个循环
循环是否继续 继续下一次 彻底结束
使用场景 跳过某些条件 提前结束

break 关键字

定义

break 用于立即退出当前所在的循环或 switch 语句,跳到后面的代码执行。

break 的两种使用场景

场景 作用
循环中 跳出本层循环(for、while、do-while)
switch-case 中 跳出当前 case,防止继续执行下一个 case

3.return

定义

return 结束当前函数执行,返回调用处,并可带回一个返回值。

核心作用

作用 说明
结束函数 函数执行到 return 立即停止
返回调用处 跳回调用该函数的地方
返回值 可以返回一个值给调用者(void 函数无返回值)

"return 有两个作用:结束当前函数返回一个值 。函数执行到 return 立即停止,后面的代码不再执行。void 函数也可以使用 return; 提前结束。我经常用 return 来处理错误情况,比如参数校验失败时提前返回。"

4.goto

定义

goto 无条件跳转到程序中的指定标签位置。

为什么不推荐使用 goto?

原因 说明
破坏结构化编程 使代码逻辑混乱
可读性差 跳来跳去难以理解
维护困难 修改代码时容易出错
有更好的替代 用循环、函数、break 替代

5.volatile

定义

volatile 告诉编译器:这个变量随时可能被外部改变,不要优化,每次使用都必须从内存重新读取。

为什么需要 volatile?

cpp 复制代码
// ============================================================
// 没有 volatile:编译器优化
// ============================================================
int a = 20;
int b, c;
b = a;   // 从内存读取 a 到寄存器,再存到 b
c = a;   // 编译器优化:直接从寄存器取 a 的值(可能已过时)

// 如果 a 在两次读取之间被外部(中断、其他线程)修改了
// c 拿到的就是旧值!

// ============================================================
// 有 volatile:强制从内存读取
// ============================================================
volatile int a = 20;
int b, c;
b = a;   // 从内存读取 a
c = a;   // 还是从内存读取 a(保证拿到最新值)

常见使用场景

场景 说明
多线程共享变量 多个线程同时读写,需要最新值
中断服务程序 中断可能修改变量值
硬件寄存器 AD 转换、GPIO 寄存器随时变化
信号处理函数 信号可能修改全局变量

"volatile 告诉编译器不要对该变量进行优化,每次使用都必须从内存中读取最新值,而不是从寄存器取缓存值。

因为编译器默认会优化,如果变量在两次读取之间没有赋值操作,编译器可能会直接从寄存器取值。但如果这个变量会被中断、其他线程或硬件改变,就会读到旧值。

常见使用场景:多线程共享变量、中断程序、硬件寄存器。"

一、struct 结构体

1. C 语言 vs C++ 结构体区别

对比项 C 语言 C++
函数成员 ❌ 不允许 ✅ 允许
继承 ❌ 不支持 ✅ 支持
访问权限 默认 public,不可修改 可设置 private/public/protected
数据成员初始化 ❌ 不可以 ✅ 可以在声明时初始化
空结构体大小 0 1
使用方式 必须加 struct 关键字或 typedef 可直接使用类名

2. 代码示例

cpp 复制代码
// ============================================================
// C 语言使用方式
// ============================================================
// 方式一:必须加 struct 关键字
struct Student {
    int age;
    int num;
    int sex;
};
struct Student s1;  // 必须写 struct

// 方式二:使用 typedef
typedef struct Student {
    int age;
    int num;
    int sex;
} Student;
Student s2;  // 可以直接使用

// ============================================================
// C++ 使用方式(直接使用类名)
// ============================================================
struct Student {
    int age;
    int num;
    int sex;
};
Student s;  // 直接使用,不需要 struct 关键字
  1. 结构体大小计算(字节对齐)
cpp 复制代码
// ============================================================
// 字节对齐规则
// ============================================================
struct A {
    char a;      // 1 字节
    int b;       // 4 字节
    char c;      // 1 字节
};
// 实际大小:12 字节(不是 6)
// 原因:对齐到最大成员(4)的倍数

struct B {
    int a;       // 4 字节
    char b;      // 1 字节
    char c;      // 1 字节
};
// 实际大小:8 字节

struct C {
    char a;      // 1 字节
    char b;      // 1 字节
    int c;       // 4 字节
};
// 实际大小:8 字节

// ============================================================
// 面试高频题:求结构体大小
// ============================================================
struct Test {
    char c;      // 1
    double d;    // 8
    int i;       // 4
};
// 大小:24(对齐到 8 的倍数)

4. struct vs class(C++)

对比项 struct class
默认访问权限 public private
默认继承权限 public private
定义模板 ❌ 不能 ✅ 可以
使用场景 纯数据结构 对象封装
cpp 复制代码
// ============================================================
// struct 默认 public
// ============================================================
struct Person {
    int age;     // 默认 public
};

// ============================================================
// class 默认 private
// ============================================================
class Person {
    int age;     // 默认 private,外部不能访问
};

二、union 联合体

1. union 与 struct 区别

对比项 union struct
内存分配 所有成员共享一块内存 所有成员内存叠加
大小 最大成员的大小 所有成员大小之和(+对齐)
修改影响 修改一个成员影响其他成员 成员独立,互不影响
同时使用 同一时间只能用一个成员 可以同时使用所有成员
cpp 复制代码
// ============================================================
// struct 内存布局
// ============================================================
struct StructTest {
    int a;       // 4 字节
    char b;      // 1 字节
    short c;     // 2 字节
};
// 大小:8 字节(各自独立存储)

// ============================================================
// union 内存布局
// ============================================================
union UnionTest {
    int a;       // 4 字节
    char b;      // 1 字节(与 a 共享内存)
    short c;     // 2 字节(与 a 共享内存)
};
// 大小:4 字节(取最大成员)

union u {
    int a;
    char b[4];
};  // 大小:4 字节,a 和 b 共享同一块内存

2. union 判断大小端

cpp 复制代码
// ============================================================
// 大小端判断
// ============================================================
#include <stdio.h>

union MyUnion {
    short t;      // 2 字节
    char b[2];    // 2 字节,与 t 共享内存
};

int main()
{
    union MyUnion test;
    test.t = 0x0102;  // 0x0102 = 258

    // 大端:高字节(01)存低地址,低字节(02)存高地址
    // 小端:低字节(02)存低地址,高字节(01)存高地址

    if (test.b[0] == 0x01 && test.b[1] == 0x02) {
        printf("大端字节序\n");
        printf("b[0]=%x, b[1]=%x\n", test.b[0], test.b[1]);
    } else if (test.b[0] == 0x02 && test.b[1] == 0x01) {
        printf("小端字节序\n");
        printf("b[0]=%x, b[1]=%x\n", test.b[0], test.b[1]);
    }

    return 0;
}
// 大部分 PC 是小端,输出:小端字节序 \n b[0]=2, b[1]=1

3. 大小端图示

cpp 复制代码
大端字节序(网络字节序):
┌────────┬────────┐
│  0x01  │  0x02  │
│ 高字节 │ 低字节 │
└────────┴────────┘
低地址 →         高地址

小端字节序(PC 常见):
┌────────┬────────┐
│  0x02  │  0x01  │
│ 低字节 │ 高字节 │
└────────┴────────┘
低地址 →         高地址

Q1:union 和 struct 的区别?

对比项 union struct
内存分配 所有成员共享一块内存 成员内存独立分配
大小 最大成员大小 所有成员大小之和
修改影响 一个成员会覆盖其他成员 互不影响
同时使用 只能用一个成员 可同时用所有成员

Q2:为什么 struct 需要字节对齐?

硬件访问对齐的数据效率更高,编译器会自动对齐以提高访问速度。

Q3:什么时候用 union?

当需要节省内存或需要同一块内存存放不同类型数据时,比如判断大小端、协议解析、类型转换等场景。

5.计算占用空间大小问题:

对于不同位的操作系统,个别数据类型数据大小不一样,

Long 和 unsigned long 在 32 位中是 4 个字节

在 64 位中是 8 个字节

计算的时候需要考虑字节对齐问题:

  1. 所占空间必须是成员变量中字节最大的整数倍

  2. 每个变量类型的偏移量必须是该变量类型的整数倍

  3. 对于联合体,由于所有变量都是共用一块内存,还需注意数组占用最大内

例如:

cpp 复制代码
Typedef union {double I;int k[5];char c;} DATE;

在联合体中成员变量最大为 double 为 8 个字节,所以最终大小必须是 8 的整数倍;又因为联合体是共占内存空间,即 int*5=20 字节,所以最终为 24 个字节。

cpp 复制代码
Typedef struct data {int cat;DATE cow; double dog;}too;

求 sizeof(too);

解:在结构体里面联合体为 24,联合体的最大类型为 8 字节,所以联合体 的起始位置必须满足偏移量为 8 的倍数,计算如下:

cpp 复制代码
Cat:1-4,

DATE cow 8+24

Double dog 32+8=40

结构体大小计算与字节对齐详解

一、基础概念

1. 不同操作系统的数据类型大小

数据类型 32位系统 64位系统
char 1 字节 1 字节
short 2 字节 2 字节
int 4 字节 4 字节
long 4 字节 8 字节 ⚠️
unsigned long 4 字节 8 字节 ⚠️
float 4 字节 4 字节
double 8 字节 8 字节
指针 4 字节 8 字节

注意:longunsigned long 在 64 位系统下是 8 字节!

2. 字节对齐规则

规则 说明
规则1 结构体总大小必须是最大成员大小的整数倍
规则2 每个成员的偏移量必须是该成员类型大小的整数倍
规则3 联合体大小 = 最大成员的大小(向上对齐到最大对齐值)

二、结构体大小计算示例

示例1:基础对齐

cpp 复制代码
struct Example1 {
    char a;    // 1 字节,偏移量 0
    int b;     // 4 字节,偏移量必须为 4 的倍数 → 从 4 开始
    char c;    // 1 字节,偏移量 8
};
// 计算:
// a: 0-1(1字节)
// b: 4-8(4字节)← 中间空出 1-3(3字节填充)
// c: 8-9(1字节)
// 总大小:9,但必须对齐到最大成员(4)的倍数 → 12

// 实际占用:12 字节

示例2:重排顺序优化

cpp 复制代码
// ❌ 浪费空间:12 字节
struct Bad {
    char a;    // 1 字节
    int b;     // 4 字节(从 4 开始)
    char c;    // 1 字节
};
// 大小:12

// ✅ 优化空间:8 字节
struct Good {
    int b;     // 4 字节,偏移量 0
    char a;    // 1 字节,偏移量 4
    char c;    // 1 字节,偏移量 5
};
// 大小:8(对齐到最大成员 4 的倍数)

三、联合体大小计算

cpp 复制代码
// ============================================================
// 联合体大小计算
// ============================================================
typedef union {
    double I;      // 8 字节
    int k[5];      // 4*5 = 20 字节
    char c;        // 1 字节
} DATE;

// 计算过程:
// 1. 最大成员:int k[5] = 20 字节
// 2. 最大对齐值:double = 8 字节
// 3. 总大小必须是 8 的整数倍
// 4. 20 向上对齐到 8 的倍数 → 24

// 所以:sizeof(DATE) = 24

图示

cpp 复制代码
union DATE 内存布局(24 字节):
┌───────────────────────────────────────────────────────────────┐
│   double I 区域(8 字节)                                    │
│   int k[5] 区域(20 字节)                                   │
│   char c   区域(1 字节)                                    │
└───────────────────────────────────────────────────────────────┘
所有成员共享同一块内存,大小取最大成员(20),对齐到 8 → 24

四、嵌套结构体计算

cpp 复制代码
// ============================================================
// 联合体 + 结构体嵌套
// ============================================================
typedef union {
    double I;      // 8 字节
    int k[5];      // 20 字节
    char c;        // 1 字节
} DATE;  // 联合体大小:24(最大对齐 8)

typedef struct data {
    int cat;       // 4 字节
    DATE cow;      // 24 字节(联合体)
    double dog;    // 8 字节
} too;

联合体的计算公式:

cpp 复制代码
#include <stdio.h>

// ============================================================
// 联合体定义
// ============================================================
union date {
    char a;        // 1 字节
    double b[3];   // 8 * 3 = 24 字节
    char c;        // 1 字节
};
typedef union date DATE;

// ============================================================
// 计算过程
// ============================================================
// 1. 最大成员:double b[3] = 24 字节
// 2. 最大对齐值:double = 8 字节
// 3. 24 已经是 8 的倍数 → sizeof(DATE) = 24
// ============================================================

int main() {
    printf("sizeof(DATE) = %ld\n", sizeof(DATE));  // 24
    return 0;
}

联合体(Union)大小计算

计算公式

联合体大小 = 最大成员大小(向上对齐到最大对齐值的整数倍)

规则说明

规则 说明
规则1 联合体大小至少能容纳最大的成员
规则2 联合体大小必须是其内部最大对齐值的整数倍
规则3 所有成员共享同一块内存,同一时间只能使用一个成员

结构体(Struct)大小计算

计算公式

结构体大小 = 所有成员大小之和 + 填充字节(对齐到最大对齐值的整数倍)

规则说明

规则 说明
规则1 结构体总大小必须是最大成员对齐值的整数倍
规则2 每个成员的起始偏移量 必须是其自身类型大小的整数倍
规则3 如果成员是联合体,联合体的起始偏移量必须满足联合体内部最大对齐值
规则4 结构体的最大对齐值是所有成员中最大的对齐值

示例代码

cpp 复制代码
#include <stdio.h>

// ============================================================
// 结构体定义(包含联合体成员)
// ============================================================
struct test {
    char a;      // 1 字节,偏移量 0
    DATE d;      // 24 字节,偏移量 8(需要 8 的倍数)
    char b;      // 1 字节,偏移量 32
    char c;      // 1 字节,偏移量 33
};
typedef struct test TEST;

// ============================================================
// 计算过程
// ============================================================
// 1. char a     → 偏移量 0,占用 1 字节
// 2. DATE d     → 起始偏移量必须是 8 的倍数
//                 当前偏移量 1 → 填充 7 字节 → 偏移量 8
//                 占用 24 字节 → 偏移量 8-31
// 3. char b     → 偏移量 32,占用 1 字节
// 4. char c     → 偏移量 33,占用 1 字节
// 5. 当前总大小:34 字节
// 6. 最大对齐值:8(double)
// 7. 对齐到 8 的倍数 → 34 → 40
//    sizeof(TEST) = 40
// ============================================================

int main() {
    printf("sizeof(TEST) = %ld\n", sizeof(TEST));  // 40
    return 0;
}

结构体内存布局

cpp 复制代码
┌─────────────────────────────────────────────────────────────────────┐
│  偏移量  │  0  │  1-7  │  8-31  │  32  │  33  │  34-39          │
├──────────┼─────┼───────┼────────┼──────┼──────┼─────────────────┤
│  成员    │  a  │  填充 │  DATE  │  b   │  c   │  填充           │
│  大小    │  1  │  7    │  24    │  1   │  1   │  6              │
└──────────┴─────┴───────┴────────┴──────┴──────┴─────────────────┘

三、完整代码

cpp 复制代码
#include <stdio.h>

// ============================================================
// 联合体定义
// ============================================================
union date {
    char a;        // 1 字节
    double b[3];   // 24 字节(8*3)
    char c;        // 1 字节
};
typedef union date DATE;  // sizeof(DATE) = 24

// ============================================================
// 结构体定义(包含联合体成员)
// ============================================================
struct test {
    char a;        // 1 字节,偏移量 0
    DATE d;        // 24 字节,偏移量 8(需要 8 的倍数)
    char b;        // 1 字节,偏移量 32
    char c;        // 1 字节,偏移量 33
};
typedef struct test TEST;  // sizeof(TEST) = 40

// ============================================================
// 主函数验证
// ============================================================
int main()
{
    printf("sizeof(DATE) = %ld\n", sizeof(DATE));  // 24
    printf("sizeof(TEST) = %ld\n", sizeof(TEST));  // 40

    // 验证各成员偏移量
    TEST t;
    printf("\n成员偏移量验证:\n");
    printf("&t.a = %p\n", (void*)&t.a);
    printf("&t.d = %p\n", (void*)&t.d);
    printf("&t.b = %p\n", (void*)&t.b);
    printf("&t.c = %p\n", (void*)&t.c);

    // 计算偏移量
    printf("\n偏移量(字节):\n");
    printf("a 的偏移量:%ld\n", (char*)&t.a - (char*)&t);     // 0
    printf("d 的偏移量:%ld\n", (char*)&t.d - (char*)&t);     // 8
    printf("b 的偏移量:%ld\n", (char*)&t.b - (char*)&t);     // 32
    printf("c 的偏移量:%ld\n", (char*)&t.c - (char*)&t);     // 33

    return 0;
}
相关推荐
CodeStats5 分钟前
【Java进程通信】Java进程通信系统完全指南:从ProcessBuilder底层原理到多语言实战
java·前端·python·进程·ai编程·processbuilder
绿智校园7 分钟前
物联网平台选型指南:为什么底层协议自研比云端SDK接入更可靠?
java·物联网·struts
T01156187 分钟前
艺培场馆课时预约小程序用户端 & 后台联动踩坑上篇(预约、课时、缓存、消息、学员端自身问题)
java·缓存·小程序·bug·全栈项目实战手记·艺培课时系统‘’
长谷深风1119 分钟前
AI Agent 踩坑:盲目重试会把小故障炸成系统灾难
java·大数据·ai·agent·ai agent·工具调用·agent设计
金立基包装胶水11 分钟前
纸袋封口热封胶不粘是什么原因?
大数据·笔记·其他
chnyi6_ya21 分钟前
论文阅读笔记 | From Priors to Perception: Grounding Video-LLMs in Physical Reality
论文阅读·笔记
自己的九又四分之三站台32 分钟前
C# 接入 RasterLite2:从原生 DLL 加载到 Coverage、Section、Tile 验证
开发语言·c#·地理信息
飞翔的火箭弹33 分钟前
企业网络资产管理用什么系统工具
开发语言·网络·php
evans在进步34 分钟前
Spring Boot 核心机制详解:自动装配、事件监听、异步任务与请求映射
java·spring boot·后端
码云骑士38 分钟前
116-Python调用GPT-4V分析图片-Base64编码-多图-JSON结构化输出
开发语言·python·json