当前位置: 首页 > news >正文

数组

> 数组:内存布局、越界陷阱、字符数组与字符串区别、多维数组万能拆解法,看完就能搞定连续内存!


一、数组本质:连续内存块

变量散定义 数组定义 内存 guarantee
int a,b,c; int a[3]; 连续
地址随机 元素依次排布 可指针偏移

语法释义:

  • arr 是数组名,即这片连续内存的名称
  • [5] 代表这片连续内存总共分成5个相等的格子,每个格子称为数组的元素
  • int 代表每个元素的类型,可以是任意基本类型,也可以是组合类型,甚至可以是数组
int arr[5];                 // 地址:arr ≡ &arr[0]
printf("%p %p\n", arr, &arr[0]); // 值完全相同
存放图解

二、定义 · 初始化 · 越界

写法 是否合法 初始值 备注
int a[5]; 随机 局部变量
int a[5] = {1,2,3}; 1,2,3,0,0 不完全初始化补0
int a[] = {1,2,3,4,5}; 1~5 编译器自动计数
int a[3] = {1,2,3,4,5}; 警告+丢弃 越界初始化
int a[0]; ❌/扩展 GNU扩展 零长数组,仅GNU可用

口诀:定义必须给长度,初始化可省略,越界编译阶段就报警

三、访问与越界演示

  • 越界访问
int a[5] = {10,20,30,40,50};
a[5] = 99;          // 越界!C不会报错,但行为未定义
  • 实验: 越界可能篡改相邻变量
int a[3] = {1,2,3};
int x = 10;
a[3] = 100;         // 越界写入,x 可能被改成100
printf("x=%d\n", x);
越界示例

运行结果不确定,取决于编译器布局——千万别这么干!

四、字符数组 vs 字符串

定义 内存布局 可读写 长度/结尾
char s[] = "abc"; 栈数组{'a','b','c','\0'} 自动补\0
char *s = "abc"; 指向只读常量区 自动补\0
char s[3] = {'a','b','c'}; \0 纯字符数组,不是字符串
char s1[] = "abc";      // sizeof(s1)=4
char *s2  = "abc";      // sizeof(s2)=8(64位指针)

结论: 要可修改字符串,请用字符数组;要只读、省内存,用字符指针指向常量。

  • 实际应用示例
#include <stdio.h>int main(void)
{// 字符数组 - 可修改char str1[] = "Hello";str1[0] = 'h';      // ✅ 正确:修改栈上的数组printf("%s\n", str1); // hello// 字符指针 - 只读char *str2 = "World";// str2[0] = 'w';    // ❌ 错误:段错误,试图修改常量区// 纯字符数组 - 无结束符char str3[3] = {'a','b','c'};printf("%zu\n", sizeof(str3)); // 3// printf("%s\n", str3);       // ❌ 危险:可能越界读取return 0;
}

五、多维数组:二维就是「数组的数组」

1. 定义与初始化

int a[2][3] = {            // 2行3列{1, 2, 3},             // a[0][0] a[0][1] a[0][2]{4, 5, 6}              // a[1][0] a[1][1] a[1][2]
};

2. 内存排布:行主序(Row-Major)

地址低 → 高
|1|2|3|4|5|6|连续 12 字节

3. 可以省略「最高维」

int a[][3] = {            // 编译器数行{1,2,3},{4,5,6},{7,8,9}               // 自动推断为 int[3][3]
};

错误示例: nint a[2][] = {...}; ❌ 编译器无法推断列数

4. 遍历二维数组

// 遍历二维数组
int matrix[2][3] = {{1,2,3}, {4,5,6}};
for (int i = 0; i < 2; i++) {for (int j = 0; j < 3; j++) {printf("matrix[%d][%d] = %d\n", i, j, matrix[i][j]);}
}

5. 连续内存验证实验

#include <stdio.h>int main(void)
{int arr[2][3] = {{1,2,3}, {4,5,6}};// 验证连续内存printf("内存地址验证:\n");for (int i = 0; i < 2; i++) {for (int j = 0; j < 3; j++) {printf("&arr[%d][%d] = %p, 值 = %d\n", i, j, &arr[i][j], arr[i][j]);}}// 用一维方式访问int *p = &arr[0][0];printf("\n一维方式访问:\n");for (int i = 0; i < 6; i++) {printf("p[%d] = %d\n", i, p[i]);}return 0;
}
  • 运行示例
运行示例

六、万能拆解法:任何数组 = 「元素类型」+ 「数组名[个数]」

原始声明 拆解步骤 元素类型 数组部分
int a[4]; int a[4] 整型 一维整型数组
int b[3][4]; int [4] b[3] 4元素整型数组 3 个「int[4]」
int *c[6]; int * c[6] 整型指针 6 个「整型指针」
int (*d[7])(int,float); int(*)(int,float) d[7] 函数指针 7 个「函数指针」

口诀:去掉「数组名[个数]」剩下的就是元素类型;多维从最外层开始拆。

拆解步骤详解

示例1: int a[4];

  • 去掉 a[4] → 剩下 int
  • 元素类型:int
  • 数组:4个整型元素的数组

示例2: int b[3][4];

  • 去掉 b[3] → 剩下 int [4]
  • 元素类型:int [4](4元素整型数组)
  • 数组:3个「int[4]」的数组

示例3: int *c[6];

  • 去掉 c[6] → 剩下 int *
  • 元素类型:int *(整型指针)
  • 数组:6个整型指针的数组

示例4: int (*d[7])(int,float);

  • 去掉 d[7] → 剩下 int (*)(int,float)
  • 元素类型:int (*)(int,float)(函数指针)
  • 数组:7个函数指针的数组

实际应用

#include <stdio.h>// 函数声明
int add(int a, float b) { return a + (int)b; }
int sub(int a, float b) { return a - (int)b; }int main(void)
{// 应用万能拆解法int a[4] = {1,2,3,4};                    // 4个int的数组int b[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}; // 3个int[4]的数组int x = 10, y = 20, z = 30;int *c[6] = {&x, &y, &z, NULL, NULL, NULL}; // 6个int*的数组int (*d[7])(int,float) = {add, sub, NULL}; // 7个函数指针的数组printf("a[2] = %d\n", a[2]);printf("b[1][2] = %d\n", b[1][2]);printf("*c[1] = %d\n", *c[1]);printf("d[0](5, 2.5) = %d\n", d[0](5, 2.5));return 0;
}

记忆技巧:

  • 从标识符开始,向右看直到遇到右括号,再向左看

  • [] 优先级高于 *,所以 int *p[5] 是指针数组,int (*p)[5] 是数组指针

  • 复杂声明可以从内向外层层剥离

七、综合实战:字符统计 + 大小写翻转

功能:

  • 读取一行字符
  • 统计大写、小写、数字、其他
  • 把大写转小写、小写转大写后输出
#include <stdio.h>
#include <ctype.h>#define MAX 128int main(void)
{char buf[MAX];int upper = 0, lower = 0, digit = 0, other = 0;/* 1. 读取一行 */fgets(buf, MAX, stdin);/* 2. 遍历统计 + 翻转 */for (int i = 0; buf[i] != '\0' && buf[i] != '\n'; ++i) {char ch = buf[i];if (isupper(ch)) { ++upper; putchar(tolower(ch));   // 大→小,使用tolower更安全}else if (islower(ch)) { ++lower; putchar(toupper(ch));   // 小→大,使用toupper更安全}else if (isdigit(ch)) { ++digit; putchar(ch); }else { ++other; putchar(ch); }}putchar('\n');/* 3. 结果 */printf("大写=%d 小写=%d 数字=%d 其他=%d\n", upper, lower, digit, other);return 0;
}

运行示例:

运行示例
http://www.hskmm.com/?act=detail&tid=30311

相关文章:

  • CF2153 Codeforces Round 1057 (Div. 2) 游记
  • 从《花果山》到《悬鉴》:一首诗的蜕变与AI元人文理论的建构历程
  • java循环
  • 10.13做题笔记
  • java语法(switch)
  • 详细介绍:微服务与面向服务编程(SOA)入门指南:从架构演进到 Spring Cloud 实践(初学者友好版)
  • python中修改局部json的思路
  • LSNet
  • Webpack 构建速度优化
  • [模拟赛] 过关(pass)
  • 2025.10.13
  • 第十三节:基于 Redis+MQ+DB实现高并发秒杀下的扣减
  • c++初体验
  • 元宇宙的搜索引擎:如何在虚拟世界中查找信息 - 详解
  • 四则运算错题本和错题重做的建立
  • 行列式的性质
  • 04_SQL语句一
  • 死锁的原因、表现以排查
  • 详细介绍:【C++】二叉搜索树
  • 朱世乐的 Johnson 算法笔记
  • day010
  • 20232323 2025-2026-1《网络与系统攻防技术》实验一实验报告
  • 树莓派4B安装WiringPi使用gpio命令
  • 单调队列优化 dp
  • 1分钟Get宠物神兽壁纸我家猫被问疯了!
  • Zabbix 6.0+ 运用官方模板监控 Redis 数据库的完整安装指南
  • 【图论】Floyd算法简析
  • MyEclipse 2017 激活教程
  • 插入 dp
  • 05_Mysql与图片的存储