1. const 关键字

// 这两种写法完全等效,都表示a是一个不可修改的整型常量。通常推荐使用第一种写法(const int)。
const int a;       // a是一个整形常量
int const a;       // a是一个整形常量

// 指针a可以修改(指向其他地址)
// 但不能通过a修改它指向的值(值是不可变的)
const int *a;      // a是一个指向整型常量的常量指针

// 指针a本身不可修改(必须初始化且不能改变指向)
// 但可以通过a修改它指向的值
int * const a;     // a是一个指向整型变量的指针常量

// 指针a本身不可修改
// 也不能通过a修改它指向的值
// 必须初始化(因为指针本身是const)
int const * const a = &b;  // a是一个指向整型常量的指针常量

// 技巧:
// const在*左侧:指向的内容是常量(常量指针)
// const在*右侧:指针本身是常量(指针常量)
// 两边都有const:指针和指向的内容都是常量

2. 一个指针可以是volatile吗?

// ptr 本身是 volatile,意味着指针的地址可能被外部修改(例如硬件或中断)。
// 每次访问 ptr 时,编译器必须重新读取它的值,不能优化掉访问。

​// ​适用场景​​:
// 在多线程环境中,指针可能被其他线程修改。
// 在嵌入式系统中,指针可能指向内存映射的硬件寄存器,其地址可能被硬件改变。

3. 计算机底层运算

// 几乎所有的现代CPU和编程语言都基于补码进行整数运算
// 统一加减法​​:补码的加减法可以直接使用​​相同的硬件电路​​(无需区分正负数),简化CPU设计。
    例如:A - B 可以转换为 A + (-B),其中 -B 直接用补码表示。
​​// 零的唯一性​​:补码中只有一个零(000...0),避免了原码中“+0”和“-0”的问题。
​// ​溢出处理自然​​:计算结果超出位数时,直接截断高位即可得到正确结果(符合模运算规则)。

// 正数​​:与原码相同(最高位为0,其余为二进制值),最高位为 0 是符号位
// 负数​​:
    1. 取绝对值的二进制表示(原码);
​​    2. 按位取反​​(反码),其中最高位是符号位为 1
​​    3. 加1​​(得到补码)。

​加法​​:直接按位相加,忽略最高位进位。
例如:5 + (-3)(8位补码):
5 → 00000101
-3 → 11111101
相加:00000101 + 11111101 = 00000010(结果为2,正确)。
​​减法​​:转换为加法(A - B = A + (-B))。
例如:7 - 5 → 7 + (-5):
-5 的补码:11111011
计算:00000111 + 11111011 = 00000010(结果为2)。


// 注意!!!!!!!!!
// 当运算表达式中存在有符号数和无符号数时,有符号数隐式转换成了无符号数(即底层的补码不变,但是此数从有符号数变成了无符号数)
// ​​将有符号数转换为无符号数时,并不是简单地“将最高位置0”​​,而是直接​​复用补码的二进制位模式​​,将其重新解释为无符号数。
// 有符号解释​​:0xFFFFFFFF → -1(最高位是符号位,表示负数)。
​// ​无符号解释​​:0xFFFFFFFF → 4294967295(最高位是数值的一部分)。

4. 内存对齐

​// 对齐访问​​:数据存储在内存地址上,其地址是数据大小的整数倍。
    例如,int32_t(4字节)对齐访问时,地址必须是 0, 4, 8, 12, ...(即 addr % 4 == 0)。
​​// 未对齐访问​​:数据跨越两个对齐的内存块。
    例如,int32_t 存储在地址 3,则它占用了 3-6,跨越了 0-3 和 4-7 两个 4 字节对齐块。

5. 结构体对齐

// 1. 结构体变量的起始地址必须是其最宽基本类型成员大小的整数倍
// 2. 每个成员相对结构体起始地址的偏移量必须是该成员大小的整数倍
// 3. 结构体的总大小必须是其最宽基本类型成员大小的整数倍

// ​(1) 成员对齐规则​​
​​每个成员的起始地址​​ 必须是其自身大小(或编译器指定的对齐值)的整数倍。
例如:int(4字节)的地址必须是 0, 4, 8, ...(即 addr % 4 == 0)。
double(8字节)的地址必须是 0, 8, 16, ...(addr % 8 == 0)。
​​// (2) 结构体总大小​​
​​必须是所有成员中最严格对齐值的整数倍​​(即最大对齐值的倍数)。
例如:若结构体包含 char(1字节)和 double(8字节),则总大小需对齐到 8 的倍数。
​​// (3) 填充字节(Padding)​​
编译器在成员之间或结构体末尾插入空白字节,以满足对齐要求。

struct Example1 {
    char a;      // 1字节 (地址 0)
    int b;       // 4字节 (需对齐到4的倍数,地址 4)
    char c;      // 1字节 (地址 8)
};               // 总大小:9字节 → 填充到12(对齐到4)

// 内存布局​​(假设起始地址为 0):
Offset 0: [a]    (1字节)
Offset 1-3: [padding] (填充3字节)
Offset 4-7: [b]   (4字节)
Offset 8: [c]    (1字节)
Offset 9-11: [padding] (填充3字节,使总大小为12)
​​// 总大小​​:12 字节(对齐到 4)。

// 嵌套结构体​
struct Inner {
    double d;    // 8字节 (需对齐到8)
    char e;      // 1字节 (地址 8)
};               // 总大小:16字节(对齐到8)

struct Outer {
    int f;       // 4字节 (地址 0)
    Inner inner; // 需对齐到8 → 起始地址8
    char g;      // 1字节 (地址 24)
};               // 总大小:32字节(对齐到8)

// 内存布局​​:
Offset 0-3: [f]       (4字节)
Offset 4-7: [padding] (填充4字节)
Offset 8-15: [inner.d] (8字节)
Offset 16: [inner.e]  (1字节)
Offset 17-23: [padding] (填充7字节,使Inner对齐到8)
Offset 24: [g]       (1字节)
Offset 25-31: [padding] (填充7字节,使Outer对齐到8)
// ​​总大小​​:32 字节(Inner 本身对齐到 8,Outer 也需对齐到 8)。

// 控制对齐的方式​​
​​// (1) 编译器指令
// #pragma pack(n)​​:强制按 n 字节对齐(可能牺牲性能)。
#pragma pack(1)  // 取消填充,紧密排列
struct Tight {
    char a;
    int b;
};               // 大小:5字节(无填充)
#pragma pack()   // 恢复默认对齐

// (2) C++11 alignas
struct Aligned {
    alignas(16) int a; // 强制16字节对齐
    char b;
};                   // 总大小:32字节(对齐到16)​​

6. 运算顺序

// c = a + b > 0 ? 1 : 2;
// 运算符优先级(从高到低)​​
​​+(加法)​​
​​>(大于比较)​​
​​?:(三元条件运算符)​​
​​=(赋值)

在C/C++中,表达式 c = a + b > 0 ? 1 : 2; 的运算顺序由​​运算符优先级​​和​​结合性​​决定。以下是逐步解析:

​​1. 运算符优先级(从高到低)​​
​​+(加法)​​
​​>(大于比较)​​
​​?:(三元条件运算符)​​
​​=(赋值)​​
​​
// 运算顺序分解​​
​​步骤1:计算 a + b​​
​​步骤2:比较 (a + b) > 0​​
​​步骤3:执行三元运算符 ?:​​
​​步骤4:赋值给 c​​

// c = ((a + b) > 0) ? 1 : 2;

7. 返回值

// 1. 可以返回局部变量
return value; 会将 value 的值复制到调用者的接收变量中,value 的生命周期仅限于函数执行期间,但它的 ​​值​​ 在 return 时已经被复制出去,因此调用者可以安全使用。

// 2. 不能返回局部变量的地址
如果函数返回的是 ​​局部变量的地址​​(指针),则会导致 ​​未定义行为(Undefined Behavior)​​,因为函数结束后局部变量的内存会被回收。
// 调用者获取的指针指向已释放的栈内存,访问会导致崩溃或数据错误。
unsigned char* invalid_reverse(unsigned char input) {
    unsigned char result = 0;
    // ... 计算逻辑 ...
    return &result; // 错误!返回局部变量的地址
}

8. 位域

// ​1. 位域的定义与作用
位域​​:允许将结构体成员定义为特定位数(如 a:1 表示 a 占 1 比特),用于节省内存。
​​语法​​:type member_name : bit_width;
    例如:unsigned int a:1; 表示 a 是无符号整型,仅占 1 比特。​

// ​2. 结构体 A 的位域布局​
struct A {
    unsigned int a:1;  // 1 bit
    unsigned int b:3;  // 3 bits
    unsigned int c:2;  // 2 bits
};

​总位数​​:1 + 3 + 2 = 6 bits。
​​内存对齐规则​​:
位域按​​声明顺序​​紧密排列,但​​不能跨越其底层类型(unsigned int)的边界​​。
unsigned int 的大小通常是 4 字节(32 位),因此所有位域必须容纳在一个 unsigned int 内。

// 3. 内存布局示意图​
假设从低地址到高地址排列:
| a (1 bit) | b (3 bits) | c (2 bits) | 未使用的填充位 (26 bits) |
​​总占用​​:6 比特有效数据 + 26 比特填充 = 32 比特(4 字节)。

// 4.1 位域总位数超过底层类型​
// sizeof(B)​​:8(两个 unsigned int,共 8 字节)。
struct B {
    unsigned int a:20;
    unsigned int b:20;  // 超出 32 位,b 必须另起一个 unsigned int
};

// 4.2 混合类型位域​
struct A {
    char t:4;           // 4位位域
    char k:4;           // 4位位域
    unsigned short i:8;  // 8位位域
    unsigned long m;     // 普通变量
};
// 位域必须存储在 ​​同一类型​​ 的存储单元中(如 char 位域不能和 int 位域混用)。
// 必须是 ​​所有成员对齐值的最大值的整数倍​​。
// ​​位域(Bit-field)的存储起始地址不需要保证是该位域类型的整数倍​​
1字节(t;k) | 2字节(i) | 1字节(空闲) 4字节(m)
struct C {
    unsigned int a:1;
    unsigned char b:3;  // 底层类型变为 unsigned char
    unsigned int c:2;
};
// 严格编译器(如 GCC/Clang)​​:不同底层类型的位域 ​​不能共享同一存储单元​​,即使总位数看似可压缩。
// a:1(unsigned int)和 b:3(unsigned char)属于不同类型,必须分开存储。
// c:2(unsigned int)需另起一个 unsigned int 单元。

9. 数组指针 & 函数声明分析

// 请指出如下语句的含义
void *(*(p1)(int))[10];
float (*(*p2)(int,int,float))(int);
typedef double (*(*(*p3)())[10])();
int (*(*p4())[10])();

// 重点: ​​“从内到外”​​ 和 ​​“右左法则”​​(The Right-Left Rule)

// 参考:
int (*p1)[10];      // p1 是指向含10个int的数组的指针
int *p2[10];        // p2 是含10个int指针的数组
int (*fp1)(int);    // fp1 是指向函数的指针,函数参数为int
int *(*fp2)(int*);  // fp2 是指向函数的指针,该函数参数为int*,返回int*

// 1. void *(*(p1)(int))[10];
内:​从 p2 开始​​:p2 是一个标识符。
​​向右看 (int, int, float)​​:p2 是一个函数指针,接受 (int, int, float) 参数。
​​向左看 *​​:该函数返回指针。
​​向右看 (int)​​:返回的指针指向一个函数(接受 int 参数)。
​​向左看 float​​:该函数返回 float。
结论:p1 是一个函数​​:参数:int,返回值:​​指针​​,指向 void *[10](即 void* 数组,长度为 10)。

// 2. float (*(*p2)(int,int,float))(int);
内:从 p2 开始​​:p2 是一个标识符。
​​向右看 (int, int, float)​​:p2 是一个函数指针,接受 (int, int, float) 参数。
​​向左看 *​​:该函数返回指针。
​​向右看 (int)​​:返回的指针指向一个函数(接受 int 参数)。
​​向左看 float​​:该函数返回 float。
结论:p2 是一个函数指针​​:参数:(int, int, float),返回值:​​另一个函数指针​​(该函数指针接受 int 参数,返回 float)。

// 3. typedef double (*(*(*p3)())[10])();
内:从 p3 开始​​:p3 是一个标识符。         *p3 
​​向右看 ()​​:p3 是一个函数指针,无参数。     (*p3)()
​​向左看 *​​:该函数返回指针。                *(*p3)()
​​向右看 [10]​​:返回的指针指向 [10] 数组。   (*(*p3)())[10]
​​向左看 *​​:数组的每个元素是指针。          *(*(*p3)())[10]
​​向右看 ()​​:每个元素是函数指针,无参数。   (*(*(*p3)())[10])()
​​向左看 double​​:函数返回 double。  double (*(*(*p3)())[10])()
结论:p3 是一个函数指针​​:参数:void(无参数)返回值:​​指针​​,指向 [10] 数组,数组的每个元素是 ​​函数指针​​(返回 double)。

// 4. int (*(*p4())[10])();
内:从 p4 开始​​:p4 是一个标识符。
​​向右看 ()​​:p4 是一个函数,无参数。
​​向左看 *​​:该函数返回指针。
​​向右看 [10]​​:返回的指针指向 [10] 数组。
​​向左看 *​​:数组的每个元素是指针。
​​向右看 ()​​:每个元素是函数指针,无参数。
​​向左看 int​​:函数返回 int。
结论:p4 是一个函数​​:参数:void(无参数),返回值:​​指针​​,指向 [10] 数组,数组的每个元素是函数指针​​(返回 int)。

10. 单精度浮点数在内存中的十六进制表示

// 步骤 1:转换为二进制科学计数法​​
​​确定符号位(Sign)​​:
    负数 → 符号位 S = 1。
​​转换绝对值为二进制​​:
    12.75 的二进制:
    整数部分:12 → 1100
    小数部分:0.75 → 0.11(因为 0.5 + 0.25 = 0.75)
    合并:1100.11
​​规范化科学计数法​​:
    移动小数点:1100.11 → 1.10011 × 2³
    有效数(Mantissa):1.10011(隐含前导1)
    指数(Exponent):3(需加上偏移量 127 → 130)
    尾数:10011

/*
重点!小数部分的二进制转换方法:​​乘2取整法
1. ​将小数部分 F 乘以2,得到乘积 P。
2. 记录 P 的整数部分(0或1),作为二进制的一位。
3. 用 P 的小数部分作为新的 F。
4. 重复上述过程,直到 F=0 或达到指定精度。

示例:将 0.625 转换为二进制​​
​​0.625×2=1.25 → 整数部分 1,小数部分 0.25,二进制:0.1
0.25×2=0.5 → 整数部分 0,小数部分 0.5,二进制:0.10
0.5×2=1.0 → 整数部分 1,小数部分 0.0(终止),二进制:0.101
​​结果​​:0.625=0.101 
*/

// ​步骤 2:计算IEEE 754字段​
指数计算​​:实际指数要在指数的基础上加127
实际指数:3 → 130(3 + 127) → 二进制:10000010
​​尾数计算​​:尾数就是小数部分
规范化后的有效数:1.10011(忽略前导1,保留小数部分) → 10011
补零到23位:10011000000000000000000

// 步骤 3:合并为32位二进制​​
1 10000010 10011000000000000000000

​​// 步骤 4:转换为十六进制​​
每4位一组:1100 0001 0100 1100 0000 0000 0000 0000
十六进制:0xC14C0000

11. 数组

// 1. 普通指针 +1(跳过单个元素)​
void main(void) {
    char a[] = "SF-TECH";
    a++;// 错误:数组名是常量指针,不能修改,编译器会报错:error: lvalue required as increment operand          
    printf("%s", a);
}

// a 是数组名,代表数组的首地址,​​类型为 char *const(常量指针)​​,不能进行 a++ 这样的修改。
// 不仅是字符串数组不允许 a++,整型数组同样不允许!!!
// 改用指针(如 char *p = a; p++;printf("%s", p);)或直接偏移(printf("%s", a + 1);)
// 输出 F-TECH(跳过首字符 'S'),p + 1 移动 sizeof(char) 字节

// 2. 数组指针 +1(跳过整个数组)​
int arr[3][2] = {{1, 2}, {3, 4}, {5, 6}};
int (*p)[2] = arr;  // p 是「指向含 2 个 int 的数组」的指针,数组指针
p++;                // p 跳过整个子数组 {1, 2},指向 {3, 4}
// p + 1 移动 sizeof(int[2]) 字节(即 8 字节,假设 int 为 4 字节)。

// 3. 二维数组的指针跳跃​
int arr[3][2] = {{1, 2}, {3, 4}, {5, 6}};
int (*p)[2] = arr;  // p 指向 arr[0](即 {1, 2})

// 在 C/C++ 中,​​[](数组下标)的优先级高于 *(解引用)​​。因此:
// *p[0] 等价于 *(p[0]),即先取 p[0],再解引用。
printf("初始指向: %d\n", p[0]);     // 输出 -522695216(地址),相差8个字节两个int类型
printf("初始指向: %d\n", p[1]);     // 输出 -522695208(地址)
printf("初始指向: %d\n", p[2]);     // 输出 -522695200(地址)
printf("初始指向: %d\n", *p);       // 输出 -522695216(地址) 
printf("初始指向: %d\n", *p[0]);     // 输出 1
printf("初始指向: %d\n", *p[1]);     // 输出 3
printf("初始指向: %d\n", *p[2]);     // 输出 5
printf("初始指向: %d\n", **p);       // 输出 1(arr[0][0])
p++;                                // 跳过 {1, 2}
printf("跳过整个子数组后: %d\n", **p); // 输出 3(arr[1][0])

// 4. 一维数组的指针跳跃
int arr[5] = {10, 20, 30, 40, 50};
int (*p)[5] = &arr;  // p 是「指向整个数组的指针」

printf("初始指向: %d\n", arr);  // 输出 -1359123840(地址)
printf("初始指向: %d\n", p);  // 输出 -1359123840(地址)
printf("初始指向: %d\n", p[0]);  // 输出 -1359123840(地址)
printf("初始指向: %d\n", (*p)[0]);  // 输出 10
printf("初始指向: %d\n", *p[0]);  // 输出 10
printf("初始指向: %d\n", *p[0]);  // 输出 10
printf("初始指向: %d\n", *p[1]);  // 跳过整个 arr[5],越界访问,输出 32765
p++;                               // 跳过整个 arr[5](危险操作!)
printf("跳过整个数组后: %d\n", (*p)[0]); // 未定义行为(越界访问),输出 32765

12. 字符串数组指针

        []优先级要高于*:数组优先级高于解引用

char *a[] = {"BEIJING", "SHENZHEN", "SHANGHAI", "GUANGZHOU"};
char **pa[] = {a+3, a+2, a+1, a};// GUANGZHOU, SHANGHAI, SHENZHEN, BEIJING 
char ***ppa = pa;
int main(void) {
    printf("%s, %s, %s, %s \n", **(ppa), **(ppa+1), **(ppa+2), **(ppa+3));// GUANGZHOU, SHANGHAI, SHENZHEN, BEIJING 
    printf("%s \n", a[0]);// BEIJING 
    printf("%x, %x, %x \n", ppa, pa, &pa[0]);// b1c44040, b1c44040, b1c44040 
    printf("\n");
    printf("%s, ", **++ppa); // SHANGHAI
    printf("%s, ", *--*++ppa+3);// JING
    printf("%s, ", *ppa[-2]+3);// NGZHOU
    printf("%s", ppa[-1][-1]+1);// HENZHEN
    printf("\n");

内存示意图

 解析

// 最重要的是参考上图内存示意图!!!!!!!!!!
// 可知,指针存放的是数组的地址;数组名即为数组地址,第 0 个元素取址也为数组地址!!!
printf("%x, %x, %x \n", ppa, pa, &pa[0]);// b1c44040, b1c44040, b1c44040 

// 可知,字符串数组中的各元素为字符串
printf("%s \n", a[0]);// BEIJING 

// 由ppa = pa = &pa[0] -> ++ppa == &pa[1] -> *++ppa = *&pa[1] == pa[1] -> 
// **++ppa = *pa[1] = *&a[2] == a[2] == SHANGHAI 
printf("%s, ", **++ppa); // SHANGHAI

// 由上,此时ppa == &pa[1] -> ++ppa == &pa[2] -> *++ppa = pa[2] == &a[1] ->
// --*++ppa == &a[0] -> *--*++ppa == a[0] -> *--*++ppa+3 == a[0]+3 == JING
// 注意a[0]等存放的是字符指针地址
printf("%s, ", *--*++ppa+3);// JING

// 数组访问 arr[i] 本质上是 ​​指针算术​​ 的语法糖,等价于 *(arr + i)。因此:
arr[-1] → *(arr - 1)
arr[-n] → *(arr - n)
// 由上,此时ppa == &pa[2] -> ppa[-2] == *(ppa-2) == *(&pa[2]-2) == *&pa[0] == pa[0]
// -> *ppa[-2] == *pa[0] == a[3] -> a[3]+3 == NGZHOU
printf("%s, ", *ppa[-2]+3);// NGZHOU

// 由上,此时ppa == &pa[2] -> ppa[-1] == *(ppa-1) == *(&pa[2]-1) == *&pa[1] == pa[1]
// ->ppa[-1][-1] == pa[1][-1] == *(pa[1]-1) == *(&a[2]-1) == *&a[1] == a[1] 
// ppa[-1][-1]+1 == a[1]+1 == HENZHEN
// 注意:在 pa[1][-1] == *(pa[1]-1)中,不要想当然pa[1][-1] == *(&pa[1]-1),就按照上述规则来!!!!
printf("%s", ppa[-1][-1]+1);// HENZHEN

注意区分:

// ​1. 内存布局​​:
// 每个字符串常量(如 "Hello")存储在只读内存的独立位置。
// strs 数组的每个元素(strs[0]、strs[1] 等)是指向这些字符串的指针。
// 2. 访问方式​​:
// strs[i] 返回第 i 个字符串的地址(char* 类型)。
// strs[i][j] 访问第 i 个字符串的第 j 个字符。
char *strs[] = {"Hello", "World", "C"};

// 1.内存布局​​:
// 所有字符串按行紧密排列在连续内存中,每行固定 6 字节(包括 '\0')。
// 不足部分用 '\0' 填充(如 "C" 存储为 'C', '\0', '\0', ...)。
// 2. 访问方式​​:
// strs[i] 返回第 i 行的首地址(类型为 char[6],可退化为 char*)。
// strs[i][j] 访问第 i 行第 j 列的字符。
char strs[3][6] = {"Hello", "World", "C"};

13. 函数指针

// 函数位于0x20000000处,输入参数为int类型,输出为void类型,调用该函数代码正确的是?
// * 解引用操作符在此场景是多余的,但语法上允许((*ptr)(arg) 和 ptr(arg) 等效)。
typedef void (*func)(int); func a = (func)0x20000000; a(); (*a)();//解引用
((void (*)(int))0x20000000)()
(*(void (*)(int))0x20000000)(123);  // 解引用后调用,语法正确但冗余

14. sizeof

#include <stdio.h>
#include <stdlib.h>

void Foo(char str[100]) {
    printf("%ld\n", sizeof(str)); // 输出指针大小
}

int main() {
    char str[] = "http://localhost"; // 长度 16
    char *p1 = str;
    void *p2 = malloc(100);
    int n = 10;

    struct A {
        int n;
        char str[10];
    } struct_a;

    printf("%ld\n", sizeof(str));      // 17(字符串常量初始化,包含 \0)
    printf("%ld\n", sizeof(p1));       // 4(指针大小,32 位系统
    printf("%ld\n", sizeof(p2));       // 4(void 指针大小)
    printf("%ld\n", sizeof(n));        // 4(int 类型)
    printf("%ld\n", sizeof(struct_a)); // 16(结构体包含对齐)
    
    Foo(str); // 4(函数参数退化为指针)
    free(p2);
    return 0;
}
// sizeof 是编译时运算符,返回对象或类型的大小(字节数)。
// ​​编译时运算符(Compile-Time Operator)​​,这意味着它的值在 ​​代码编译期间​​ 就已经被计算确定,而不是在程序运行时动态计算。
// 在 C/C++ 中,​​当数组作为函数参数传递时,它会自动退化为指向其首元素的指针​​。
// 即使你声明为 char str[100],函数内部实际接收到的是 char* 类型的指针,而非真正的数组。
// 内存效率​​:避免复制大数组(如 int arr[10000])带来的性能开销。
// 以下三种声明完全等价(编译器视为同一函数):
void Foo(char str[100]);  // 看似数组,实为指针
void Foo(char str[]);     // 不指定大小,仍是指针
void Foo(char *str);      // 直接声明为指针

15. 存储位置

        注意!在main函数中的变量也是局部变量,只有在函数之外(包括主函数)定义的变量才是全局变量!

char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc"; 
const char str4[] = "abc"; 
const char* str5 = "abc";
const char* str6 = "abc";

// false abc abc
std::cout << boolalpha << ( str1 == str2 ) <<' ' << str1 << ' ' << str2 << std::endl; 
// false abc abc
std::cout << boolalpha << ( str3 == str4 ) <<' ' << str3 << ' ' << str4 << std::endl;
// true abc abc
std::cout << boolalpha << ( str5 == str6 ) <<' ' << str5 << ' ' << str6 << std::endl; 

// 1. const char[](栈内存)​:
// 定义 const char str3[] = "abc"; 时,编译器会在栈上创建一个长度为 4 的数组(含 \0),并复制字符串 "abc" 到该数组。
// 2. const char*(常量区)​
// 定义 const char* str5 = "abc"; 时,字符串字面量 "abc" 存储在只读数据段(.rodata)。所有相同的字符串字面量可能被编译器优化为同一地址(字符串池化)。

// 3. 关于str为何输出内容而不是地址?
// C 语言中,char* 通常表示字符串,printf("%s", str) 会输出内容而非地址。
// C++ 继承了这一行为,使 std::cout << str 也输出字符串内容。


Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐