数组1. 定义与本质内存的“强连通”物理特性数组是物理内存中一段地址连续、大小固定且类型相同的空间。思考为什么一定要“类型相同”为了确保每个元素的偏移步长一致从而实现 O(1) 随机访问。2. 核心机理寻址公式推导过程假设数组首地址为base_address元素类型大小为size。则第i个元素的地址为addr base_address i * size。深度解析为什么索引从 0 开始从编译器角度看0代表偏移量Offset。如果从1开始公式变为base_address (i-1) * size每次寻址都会多出一次减法运算。在底层高性能计算中这种多余的指令积累起来影响巨大。3. 数组名它到底是不是指针真相数组名是一个常量左值它在大多数表达式中会“退化”为指向首元素的指针。实验验证sizeof 差异int a[10]; printf(%zu\n, sizeof(a)); // 结果是 40 (整个数组的大小) int *p a; printf(%zu\n, sizeof(p)); // 结果是 8 或 4 (指针的大小)取地址的奥秘a与a[0]的值虽然相同但它们的类型完全不同一个是数组指针int (*)[10]一个是元素指针int *。4. 性能之魂缓存局部性Cache Locality空间局部性原理当 CPU 访问数组的一个元素时硬件会自动将该元素附近的整块数据Cache Line通常是 64 字节加载到高速缓存中。为什么数组比链表快链表节点在内存中是离散的容易导致 Cache Miss数组的连续性保证了极高的缓存命中率。int arr[5] {10, 20, 30, 40, 50}; printf(数组首地址: %p\n, (void*)arr); for (int i 0; i 5; i) { // 观察地址增长每个地址正好相差 sizeof(int) printf(arr[%d] 的地址: %p, 值: %d\n, i, (void*)arr[i], arr[i]); }5. 安全边际越界与缓冲区溢出Buffer OverflowC 语言的权衡为了追求极致性能C 编译器不进行边界检查。底层后果数组越界可能覆盖掉栈帧上的返回地址Return Address这是黑客进行栈溢出攻击Stack Smashing的基本原理。必知在处理输入时永远优先使用fgets或strncpy等带长度限制的函数拒绝gets和strcpy目的是为了防止溢出。注意fgets会把换行符\n也读进去。如果不需要换行符通常需要手动处理buf[strcspn(buf, \n)] 0;。6. 函数传递中的“身份丢失”传址调用数组作为参数时退化为指针。后果函数内部无法通过sizeof获取原数组长度。解决方案额外传递长度参数。字符数组与字符串一句话总结字符数组只是一个装字符的容器没有结束标记字符串以\0结尾的字符数组自带结束标志字符串 字符数组 结尾的 \01. 字符数组不是字符串// 只是 3 个字符a b c char arr1[3] {a, b, c};✅ 是数组❌ 不是字符串⚠️ 没有 ‘\0’不能用 strlen 等字符串函数2. 字符串特殊的字符数组// 自动在末尾加 \0实际是 a b c \0 char arr2[] abc;✅ 是字符数组✅ 也是字符串✅ 可以用 strlen 等所有字符串操作注意当使用%s进行输出时一定要确保是以\0结尾的不然会溢出产生乱码
一维数组、字符数组、字符串
数组1. 定义与本质内存的“强连通”物理特性数组是物理内存中一段地址连续、大小固定且类型相同的空间。思考为什么一定要“类型相同”为了确保每个元素的偏移步长一致从而实现 O(1) 随机访问。2. 核心机理寻址公式推导过程假设数组首地址为base_address元素类型大小为size。则第i个元素的地址为addr base_address i * size。深度解析为什么索引从 0 开始从编译器角度看0代表偏移量Offset。如果从1开始公式变为base_address (i-1) * size每次寻址都会多出一次减法运算。在底层高性能计算中这种多余的指令积累起来影响巨大。3. 数组名它到底是不是指针真相数组名是一个常量左值它在大多数表达式中会“退化”为指向首元素的指针。实验验证sizeof 差异int a[10]; printf(%zu\n, sizeof(a)); // 结果是 40 (整个数组的大小) int *p a; printf(%zu\n, sizeof(p)); // 结果是 8 或 4 (指针的大小)取地址的奥秘a与a[0]的值虽然相同但它们的类型完全不同一个是数组指针int (*)[10]一个是元素指针int *。4. 性能之魂缓存局部性Cache Locality空间局部性原理当 CPU 访问数组的一个元素时硬件会自动将该元素附近的整块数据Cache Line通常是 64 字节加载到高速缓存中。为什么数组比链表快链表节点在内存中是离散的容易导致 Cache Miss数组的连续性保证了极高的缓存命中率。int arr[5] {10, 20, 30, 40, 50}; printf(数组首地址: %p\n, (void*)arr); for (int i 0; i 5; i) { // 观察地址增长每个地址正好相差 sizeof(int) printf(arr[%d] 的地址: %p, 值: %d\n, i, (void*)arr[i], arr[i]); }5. 安全边际越界与缓冲区溢出Buffer OverflowC 语言的权衡为了追求极致性能C 编译器不进行边界检查。底层后果数组越界可能覆盖掉栈帧上的返回地址Return Address这是黑客进行栈溢出攻击Stack Smashing的基本原理。必知在处理输入时永远优先使用fgets或strncpy等带长度限制的函数拒绝gets和strcpy目的是为了防止溢出。注意fgets会把换行符\n也读进去。如果不需要换行符通常需要手动处理buf[strcspn(buf, \n)] 0;。6. 函数传递中的“身份丢失”传址调用数组作为参数时退化为指针。后果函数内部无法通过sizeof获取原数组长度。解决方案额外传递长度参数。字符数组与字符串一句话总结字符数组只是一个装字符的容器没有结束标记字符串以\0结尾的字符数组自带结束标志字符串 字符数组 结尾的 \01. 字符数组不是字符串// 只是 3 个字符a b c char arr1[3] {a, b, c};✅ 是数组❌ 不是字符串⚠️ 没有 ‘\0’不能用 strlen 等字符串函数2. 字符串特殊的字符数组// 自动在末尾加 \0实际是 a b c \0 char arr2[] abc;✅ 是字符数组✅ 也是字符串✅ 可以用 strlen 等所有字符串操作注意当使用%s进行输出时一定要确保是以\0结尾的不然会溢出产生乱码