欢迎光临
我们一直在努力

第五章 数组

第五章 数组

一、项目引入-学生成绩管理系统

1.1 先看成品

成品长这样。五个学生的成绩,录进去,算平均,找最高最低,数不及格,最后从高到低排个名。

#include <iostream>
using namespace std;

int main() {
const int N = 5;
int scores[N];

for (int i = 0; i < N; i++) {
cout << "第 " << i + 1 << " 个学生的成绩: ";
cin >> scores[i];
}

cout << "\\n成绩单" << endl;
for (int i = 0; i < N; i++) {
cout << scores[i] << endl;
}

int sum = 0;
for (int i = 0; i < N; i++) {
sum += scores[i];
}
cout << "平均分 " << (double)sum / N << endl;

int maxIdx = 0, minIdx = 0;
for (int i = 1; i < N; i++) {
if (scores[i] > scores[maxIdx]) {
maxIdx = i;
}
if (scores[i] < scores[minIdx]) {
minIdx = i;
}
}
cout << "最高 " << scores[maxIdx] << " 最低 " << scores[minIdx] << endl;

int fail = 0;
for (int i = 0; i < N; i++) {
if (scores[i] < 60) {
fail++;
}
}
cout << "不及格 " << fail << " 人" << endl;

for (int i = 0; i < N – 1; i++) {
for (int j = 0; j < N – 1 – i; j++) {
if (scores[j] < scores[j + 1]) {
int t = scores[j];
scores[j] = scores[j + 1];
scores[j + 1] = t;
}
}
}

cout << "\\n排名" << endl;
for (int i = 0; i < N; i++) {
cout << i + 1 << " " << scores[i] << endl;
}
return 0;
}

输入 78 92 55 100 88,跑完是这个结果。

第 1 个学生的成绩: 78
第 2 个学生的成绩: 92
第 3 个学生的成绩: 55
第 4 个学生的成绩: 100
第 5 个学生的成绩: 88

成绩单
78
92
55
100
88
平均分 82.6
最高 100 最低 55
不及格 1 人

排名
1 100
2 92
3 88
4 78
5 55

数字都对得上。总分四百一十三,除以五得八十二点六;五十五分低于六十,不及格一人;降序排下来一百、九十二、八十八、七十八、五十五。这些都是手算能验的,跑完对一遍,比盲目相信屏幕强。

┌──────────────────────────────────────────────────────────┐
│ 成绩管理系统走这几步 │
├──────────────────────────────────────────────────────────┤
│ 五个功能一条主线,数据全住在一个数组里 │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 录入 循环里 cin,往格子里一个个放成绩 │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 打印 循环走一遍,一个成绩一行 │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 统计 求和、平均、最高最低、不及格人数 │ │
│ └────────────────────────────────────────────────────┘ │
│ │ │
│ v │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 排序 冒泡上场,把成绩从高到低排 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ 每个功能都是一个循环加几个判断 │
│ 数组换成二维的,就能管多科成绩 │
└──────────────────────────────────────────────────────────┘

1.2 没有数组之前

同样的系统,没有数组要靠什么摆数据。四十个学生,四十个变量。

int score1 = 78, score2 = 92, score3 = 55;
int score4 = 100, score5 = 88;
// 后面还有 score6 到 score40

声明阶段只是难看。真正致命的是循环用不上。第四章学的 for 循环,循环变量 i 从 0 跑到 39,可 i 转不成变量名,scorei 这个东西不存在。于是遍历只能手写四十行 cout,求和只能手写四十行加号,排序只能手写交换。循环是省事的工具,前提是数据排得整齐,变量名一人一个样,循环使不上劲。

┌────────────────────────────────────────────────────────┐
│ 四十个变量对一个数组 │
├────────────────────────────────────────────────────────┤
│ 同样的四十份成绩,两种摆法 │
│ │
│ +—————-+——————+————–+ │
│ |摆法 |四十个学生的成绩 |循环救得了吗 | │
│ +—————-+——————+————–+ │
│ |四十个变量 |score1 到 score40 |救不了 | │
│ |一个数组 |score[40] |救得了 | │
│ +—————-+——————+————–+ │
│ │
│ 循环要抓的手,得是排得整齐的数据 │
│ 数组一个名字通吃,容量写在方括号里 │
└────────────────────────────────────────────────────────┘

数组把这一切翻过来。一个名字 scores,后面方括号里装下标,scores[0] 到 scores[39],下标恰好是循环变量 i。手写四十行的活,全部缩进一个循环。这就是数组存在的理由,给循环一双能抓得住的手。

1.3 数组是个什么东西

数组可以想成一家旅馆。一个名字是店名,方括号里那个数是房间号,从零号房开始排。住进去的是同一类型的数据,成绩就全是 int,姓名就全是 string,混着住不行。

房间号和房间是一对一的。scores[2] 就是第三间房,推门进去拿到的是那一份数据。房间紧挨着排,中间不留空,这也是它能被循环一趟扫完的原因。

┌───────────────────────────────────────────────────────┐
│ 数组在内存里排成一排 │
├───────────────────────────────────────────────────────┤
│ 五个 int 挨着坐,从零开始编号 │
│ │
│ +———+———+———+———+———+ │
│ |下标 0 |下标 1 |下标 2 |下标 3 |下标 4 | │
│ +———+———+———+———+———+ │
│ |78 |92 |55 |100 |88 | │
│ +———+———+———+———+———+ │
│ │
│ 名字只有一个 scores,后面跟方括号装下标 │
│ 想要第几间房,方括号里就写第几号 │
└───────────────────────────────────────────────────────┘

长度在声明的时候定死。int scores[5] 就是五间房,营业期间不加房间也不退房。嫌它硬的,后面有 vector,能伸能缩,那是后话,数组是它的底子,必须先熟。

二、一维数组

2.1 声明与初始化

声明的写法,类型、名字、方括号里装长度。

int scores[5];
double prices[10];
string names[8];

长度只能写常量或者常量表达式。C++ 老标准里变量长度不行,用变量当长度有的编译器直接拒收,统一用 const int 定一个,稳当。

不初始化的数组,格子里装的是内存残留数据。读到什么数全看运气,跟第二章说的未初始化变量一个道理。声明的时候给值,有三种写法。

int a[5] = {78, 92, 55, 100, 88}; // 完整给
int b[5] = {78, 92}; // 给一半
int c[5] = {0}; // 全清零

┌──────────────────────────────────────────────────────┐
│ 初始化有三种写法 │
├──────────────────────────────────────────────────────┤
│ 三种初始化,写法不同,开头状态不同 │
│ │
│ +—————-+—————-+————–+ │
│ |写法 |长相 |格子里是什么 | │
│ +—————-+—————-+————–+ │
│ |完整列出来 |五个数全给 |一一对应 | │
│ |给一半 |只给前两个 |剩下自动补零 | │
│ |全清零 |第一个写 0 |后面跟着补零 | │
│ +—————-+—————-+————–+ │
│ │
│ 没给值的地方补零,不是补随机数 │
│ 长度能省是情分,初始化不能省 │
└──────────────────────────────────────────────────────┘

给一半的,剩下的格子自动补零。全清零的,第一个显式写了 0,后面跟着补。补零这个行为是标准定的,靠得住。

还有一种偷懒写法,初始化的时候长度可以省,编译器自己数。

int d[] = {1, 2, 3, 4}; // 编译器数出来是 4

前提是必须初始化,不初始化编译器不知道该开几间房。这条方便,也有代价,长度藏在花括号里,改数据的时候容易看漏。

写法例子长度注意
声明加完整初始化 int a[5] = {1,2,3,4,5} 明写 5 一一对应
声明加部分初始化 int b[5] = {1,2} 明写 5 后三个补零
全清零 int c[5] = {0} 明写 5 常用套路
省略长度 int d[] = {1,2,3} 编译器数,是 3 必须初始化
只声明 int e[5] 明写 5 里面是垃圾数据

2.2 下标从零开始

访问元素,方括号里写下标。下标从零开始,五个元素的合法下标是 0 到 4,没有 5。

cout << scores[0] << endl; // 第一个,78
cout << scores[4] << endl; // 最后一个,88
scores[2] = 60; // 改第三个

第一间房是 0 号房,这条没道理可讲,C 语言传下来的规矩,全家沿用。新手在这里翻车,翻的都是同一个姿势,打印第五个元素写了 scores[5]。

int a[5] = {1, 2, 3, 4, 5};
cout << a[5] << endl; // 越界,后果自负

越界是未定义行为。这四个字的分量,第二章讲过一回,这里再强调一遍。不报错,不崩溃,也不保证读到什么。可能是垃圾数字,可能是别人家的变量,可能是程序直接躺平。同一个程序这次跑好好的,下次换个编译器就崩,说的就是它。

┌───────────────────────────────────────────────────────┐
│ 越界是怎么发生的 │
├───────────────────────────────────────────────────────┤
│ 合法下标只到四,多一号就是越界 │
│ │
│ +———+———+———+———+———+ │
│ |下标 0 |下标 1 |下标 2 |下标 3 |下标 4 | │
│ +———+———+———+———+———+ │
│ |78 |92 |55 |100 |88 | │
│ +———+———+———+———+———+ │
│ │
│ 合法区域到此为止,scores[5] 已经出界 │
│ 下标错一位,坏的可能不是这一个数 │
└───────────────────────────────────────────────────────┘

防越界的土办法有个,循环条件写 i < 5 而不是 i <= 5,数元素个数的时候把 0 号房数进去。进阶办法是用 vector,它的 at 访问会老老实实抛异常,宁可慢一点也要个明白。数组不拦你,是它的设计哲学,拦你是你的事。

2.3 遍历

遍历就是挨个房间走一遍,下标从 0 到长度减一。

for (int i = 0; i < 5; i++) {
cout << scores[i] << endl;
}

C++11 起有个更懒的写法,范围 for,连下标都省了。

for (int x : scores) {
cout << x << endl;
}

冒号左边是每个元素的副本,右边是数组。它翻译成循环的时候还是老写法,只是不用手写下标。两个场合别用它。要改元素的值,得写成 int &x,加一个引用符号,不加改的是副本,白忙。要下标做别的事,比如奇偶行分开处理,还得回到老写法。

2.4 统计四件套

求和、平均、最大最小、计数,四个套路,成绩系统的地基。

求和用一个变量累加,循环里走一遍。

int sum = 0;
for (int i = 0; i < N; i++) {
sum += scores[i];
}

平均分是总和除以人数。除法那章的教训还在,两个 int 相除还是 int,小数部分直接扔。先转 double 再除。成品里图省事写的是 (double)sum 这种老式写法,第二章讲过的 static_cast 正式登场,写成 static_cast(sum) 更好认。

double avg = static_cast<double>(sum) / N;

最大最小有两种记法。记值简单,遇到更大的就换。记下标更值钱,拿到下标既能拿值,还能知道是谁。

int maxIdx = 0;
for (int i = 1; i < N; i++) {
if (scores[i] > scores[maxIdx]) {
maxIdx = i;
}
}

循环从 1 开始,0 号房先当擂主,后面挨个挑战。这个写法比记值多一行,用起来宽得多,排行榜、点名、发奖都指着下标。

计数是带条件的求和,条件进 if,计数器加一。

int fail = 0;
for (int i = 0; i < N; i++) {
if (scores[i] < 60) {
fail++;
}
}

四件套换到二维数组上,循环多套一层,套路一字不改。这是本章后半段的事。

2.5 冒泡排序

排序是成绩系统的刚需。冒泡是最直白的排序,直白到每个新手都该手写一遍。

思想一句话,相邻两个比,大的往前换。一趟走完,最小的沉到末位。再走一趟,次小的归位,大的像气泡一样一路往上冒,冒泡这个名字就是这么来的。五个数走四趟,就齐了。

┌───────────────────────────────────────────────┐
│ 冒泡排序走一趟 │
├───────────────────────────────────────────────┤
│ 冒泡一趟,相邻两个比,大的往前换 │
│ │
│ 排序前 78 92 55 100 88 │
│ 比 0 和 1 后 92 78 55 100 88 │
│ 比 1 和 2 后 92 78 55 100 88 没换 │
│ 比 2 和 3 后 92 78 100 55 88 │
│ 比 3 和 4 后 92 78 100 88 55 │
│ │
│ 一趟下来,最小的 55 沉到最后一位 │
│ 五个数要四趟,每趟比上一趟少比一次 │
└───────────────────────────────────────────────┘

代码是双层循环。外层管趟数,内层管每趟的比较。

for (int i = 0; i < N – 1; i++) {
for (int j = 0; j < N – 1 – i; j++) {
if (scores[j] < scores[j + 1]) {
int t = scores[j];
scores[j] = scores[j + 1];
scores[j + 1] = t;
}
}
}

三个细节。外层跑 N 减 1 趟,五个数四趟,最后一趟只剩一个元素,不用比。内层上限是 N 减 1 减 i,第 i 趟末尾已经有 i 个元素归位,再比是浪费。条件是小于号,走的是降序,换成大于号就是升序,一个符号的事。

交换要借一个临时变量,两个杯子换水,不倒第三杯倒不成。C++ 里能用 swap 函数省掉这三行, 头文件里现成的,知道有它就行,手写三行是基本功。

比较次数是 n 乘 n 减 1 除 2。五个数十次,四十个学生七百八十次。这个增长势头不妙,数据量上到一万,比较次数五千万起步。冒泡的场合适中小数据、教学演示和近乎有序的数据,正经排序有插入、快排、归并,那些是数据结构章节的角,这里不展开。

三、二维数组

3.1 表格观

一维数组是一条队,二维数组是一张表。三个学生,四门课,正好一张三行四列的成绩表。

int grid[3][4];

声明写两个方括号,前一个行数,后一个列数。这个顺序别反,反了的是另一种错法,编译器不拦,数据全乱。

初始化跟一维一个思路,按行给,一行一个花括号。

int grid[3][4] = {
{78, 92, 85, 90},
{88, 76, 90, 82},
{95, 89, 93, 87}
};

偷懒的规矩也通用,第一维的长度可以省,编译器数行数。第二维不能省,它得知道每行多宽。

int grid[][4] = {
{78, 92, 85, 90},
{88, 76, 90, 82},
{95, 89, 93, 87}
};

┌───────────────────────────────────────────────────────┐
│ 二维数组长成一张表 │
├───────────────────────────────────────────────────────┤
│ 二维数组就是一张表,两个下标各管一个方向 │
│ │
│ +———+———+———+———+———+ │
│ |姓名 |语文 |数学 |英语 |物理 | │
│ +———+———+———+———+———+ │
│ |张三 |78 |92 |85 |90 | │
│ |李四 |88 |76 |90 |82 | │
│ |王五 |95 |89 |93 |87 | │
│ +———+———+———+———+———+ │
│ │
│ 行下标横着数,列下标竖着数 │
│ 姓名另摆一个 string 数组,格子只装分数 │
└───────────────────────────────────────────────────────┘

可以把它看成三根一维数组并排站。grid[0] 是第一行那四个数,grid[1] 是第二行。这个看法在遍历的时候能省脑子。

3.2 双下标访问

访问一个格子,两个下标都要给,先写行再写列。

cout << grid[0][0] << endl; // 第一行第一列,78
cout << grid[2][3] << endl; // 第三行第四列,87
grid[1][2] = 95; // 改第二行第三列

下标同样从零开始,同样会越界,同样不报错。三行四列的表,行下标到 2,列下标到 3,各差一号,数的时候容易顺手写成 3 和 4。

内存不认表格,只认一长条。二维数组在内存里是按行铺的,第一行铺完铺第二行。

┌───────────────────────────────────────────────────────────┐
│ 二维数组怎么落进内存 │
├───────────────────────────────────────────────────────────┤
│ 内存不认表格,只认一长条,一行接一行铺 │
│ │
│ 第 0 行 grid[0][0] grid[0][1] grid[0][2] grid[0][3] │
│ │ │
│ v │
│ 第 1 行 grid[1][0] grid[1][1] grid[1][2] grid[1][3] │
│ │ │
│ v │
│ 第 2 行 grid[2][0] grid[2][1] grid[2][2] grid[2][3] │
│ │
│ 内存按行铺,遍历顺边走不吃亏 │
│ 缓存搬连续内存,跳着扫才吃亏 │
└───────────────────────────────────────────────────────────┘

行优先存储,C++ 一向如此。它带来一个实用后果,遍历按行走比按列走快。缓存一次搬一串连续的内存,按行扫,下一格就在旁边;按列扫,每次都跳到下一行的开头。数据量小感觉不出来,量大就差出来了。遍历的姿势养成本能,外层走行,内层走列。

3.3 双层循环遍历

遍历就是双层循环,外层行内层列,四件套搬过来照样好使。

for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
cout << grid[i][j] << "\\t";
}
cout << endl; // 一行打完,换行
}

范围 for 在二维数组上不好使。它展开一层,拿到的是每一行那个一维数组,还得再套一层循环才拿到数。想用懒办法,先把它想成三根一维数组,就顺了。

for (auto &row : grid) {
for (int x : row) {
cout << x << "\\t";
}
cout << endl;
}

外层引用符号要加,不加拿到的是副本,改不动原表。

3.4 成绩表实例

把 3.1 那张表算一遍。每人总分,每科平均。

int grid[3][4] = {
{78, 92, 85, 90},
{88, 76, 90, 82},
{95, 89, 93, 87}
};

for (int i = 0; i < 3; i++) {
int total = 0;
for (int j = 0; j < 4; j++) {
total += grid[i][j];
}
cout << "第 " << i + 1 << " 个学生总分 " << total << endl;
}

for (int j = 0; j < 4; j++) {
int sum = 0;
for (int i = 0; i < 3; i++) {
sum += grid[i][j];
}
cout << "第 " << j + 1 << " 科平均 " << static_cast<double>(sum) / 3 << endl;
}

跑出来是这些数。张三总分三百四十五,李四三百三十六,王五三百六十四。语文平均八十七,数学八十五点七,英语八十九点三,物理八十六点三。手算能对上的,自己拿计算器验一遍,循环错一位都对不上。

细看两段循环的姿势。求每人总分,外层走行,内层把一行加干。求每科平均,外层走列,内层把一列加干。行和列谁在外谁在内,取决于你要锁住哪个方向,这是双层循环的唯一诀窍。

四、字符串

4.1 char 数组的老底

字符串的本相是一排字符。C 语言那会儿没有专门的字符串类型,用 char 数组凑,结尾再挂一个空字符当句号。

char name[] = "Tom";

方括号里没写长度,编译器自己数。数出来是 4,不是 3,多出来那间房给结束符住。sizeof 看到的是 4,strlen 数的是 3,结束符不算内容,只当边界。

老写法有三条硬规矩。赋值不行,name = "Bob" 编译直接拦,数组名是常量,不是能塞值的变量,要换内容得逐个字符拷,或者请出 strcpy。比较不行,== 比的是地址不是内容,要比内容用 strcmp。长度问 strlen,sizeof 问的是占多少字节,俩不是一回事。

#include <cstring>

char a[] = "Tom";
char b[] = "Tom";
cout << (a == b) << endl; // 0,比地址,俩地址不一样
cout << strcmp(a, b) << endl; // 0,内容一样
cout << strlen(a) << endl; // 3
cout << sizeof(a) << endl; // 4,带着结束符

这套老底翻出来,不是让你天天用。是读老代码、看接口文档、调试内存的时候,满眼都是它。知道 char 数组是怎么回事,那些为什么这么写就有答案了。

中文在 char 数组里还有个坑。第一章说过中文输出乱码,在字符串里一样成立,一个汉字占两三个字节,按 char 数会数出一半的长度。处理中文文本的场景,交给 string 和宽字符,初学阶段先躲开。

4.2 string 上路

C++ 给了 string,字符串的现代化摆法。它是个类,不是内置类型,用之前先请头文件 出场。

string name = "张三丰";
string greeting = "hello";

┌─────────────────────────────────────────┐
│ string 里装着什么 │
├─────────────────────────────────────────┤
│ string 里也是一排字符,长度它自己记着 │
│ │
│ 字符 张 三 丰 │
│ 下标 0 1 2 │
│ │
│ 字符一个挨一个存,位置用下标数 │
│ 长度问 length(),几个字符报几个 │
└─────────────────────────────────────────┘

赋值、拼接、比较,全是正常人的操作。

string a = "hello";
string b = a; // 赋值,拷贝一份
string c = a + " world"; // 拼接
cout << (a == b) << endl; // 比内容,1
cout << a.length() << endl; // 长度,5

== 比的是内容,不是地址。这一条就平了 char 数组最烦人的那个坑。长度 length 和 size 是同一个东西,两个名字都常用,随便挑一个顺手的。

空串判断用 empty,比 length() == 0 短,意思也直白。

string s;
if (s.empty()) {
cout << "空的" << endl;
}

string 和 char 数组能互相转,转进去直接赋值,转出来用 c_str。

string s = "Tom";
const char *p = s.c_str(); // 转 C 风格,接老接口用

4.3 常用操作

截一段用 substr,头一个参数是起点,第二个是长度。

string s = "hello world";
cout << s.substr(0, 5) << endl; // hello
cout << s.substr(6) << endl; // world,省长度就是到尾

找一个子串用 find,返回位置,找不到返回一个叫 string::npos 的常量。

string s = "hello world";
cout << s.find("world") << endl; // 6
if (s.find("abc") == string::npos) {
cout << "没找到" << endl;
}

npos 的判断姿势记一下,跟 == 配着用,跟 != 的意思正好反。找到返回下标,找不到返回 npos,没有中间态。

读一整行用 getline,第三章的老朋友。cin >> 遇空格就停,getline 读到换行为止,带空格的名字全靠它。

string line;
getline(cin, line);

4.4 姓名进系统

现在把名字加回成绩系统。两个数组并排摆,一个装名字,一个装分数,下标对齐,第 i 个名字对第 i 个分数。

const int N = 3;
string names[N] = {"张三", "李四", "王五"};
int scores[N] = {78, 92, 55};

for (int i = 0; i < N; i++) {
cout << names[i] << " " << scores[i] << endl;
}

排序的时候两个数组一起换。分数换了位,名字必须跟着走,不然名次对不上人。

for (int i = 0; i < N – 1; i++) {
for (int j = 0; j < N – 1 – i; j++) {
if (scores[j] < scores[j + 1]) {
int t = scores[j];
scores[j] = scores[j + 1];
scores[j + 1] = t;
string tn = names[j];
names[j] = names[j + 1];
names[j + 1] = tn;
}
}
}

查成绩就是挨个对名字。

string target;
cout << "查谁: ";
cin >> target;

int found = -1;
for (int i = 0; i < N; i++) {
if (names[i] == target) {
found = i;
break;
}
}

if (found >= 0) {
cout << target << " 的成绩是 " << scores[found] << endl;
} else {
cout << "没这个人" << endl;
}

found 初始值是 -1,不是 0。零号房是合法下标,拿 0 当没找到,第一个学生就要蒙冤。约定一个不可能的下标,是这类查找的标准姿势。

两个数组并排摆,有个隐藏成本。名字和分数的关系,靠你脑子里那根线牵着,代码里没写着。加个班级字段,就得多牵一根,加个家长电话,再牵一根。牵得多了,总有一根牵错的时候。数据和数据的关系写进代码里,那是结构体和类的章节,这里先埋一句,数组的好处先享受着。

五、小结

┌──────────────────────────────────────────────────────────┐
│ 本章收口 │
├──────────────────────────────────────────────────────────┤
│ 一章学完,手上该有这几样 │
│ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 一维 一条队,一个下标,循环遍历 │ │
│ └────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 二维 一张表,两个下标,双层循环 │ │
│ └────────────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 字符串 一排字符,string 比 char 数组省心 │ │
│ └────────────────────────────────────────────────────┘ │
│ │
│ 成绩系统到此能跑,统计和排序全靠循环加下标 │
│ 代码还堆在 main 里,下一章收进函数 │
│ 函数的第一个数组参数,下一章开头就见 │
└──────────────────────────────────────────────────────────┘

知识一句话最容易栽在哪
一维数组 一条队,方括号装下标 下标从 1 开始数
初始化 三种写法,补零规则 省略长度忘了初始化
越界 不报错,后果自负 循环写成小于等于
遍历 for 走下标,范围 for 走值 改元素忘了加引用
统计四件套 求和、平均、最大最小、计数 int 相除丢小数
冒泡 相邻比较,大的往前 内层上限忘了减 i
二维数组 一张表,两个下标 行列数反,行优先后患
双层循环 外层锁方向,内层扫一遍 行和列谁在外弄反
char 数组 老底,结束符结尾 sizeof 和 strlen 混
string 现代化字符串,== 比内容 npos 判断写反
并行数组 名字分数两行对齐 排序只换一边

五句话带走。数组给循环一双能抓得住的手,变量编号的地狱到此为止。下标从零开始,越界不报错,这两条刻脑子里。一维是一条队,二维是一张表,外层锁方向内层扫。char 数组是老祖宗,string 是日用款,sizeof 和 strlen 别混。并行数组能用不完美,关系和字段迟早要收进结构体。

课后练习四道。录入十个成绩,打印高于平均分的那些。冒泡改成升序,再改成按分数段统计人数。用二维数组存三名学生四门课,输出每人总分和每科平均分。加姓名数组,实现按姓名查成绩,找不到要给提示,找到要报分数和排名。

附录|数组速查卡

类别写法一句话提醒
一维声明 int a[5] 长度常量,别用变量
完整初始化 int a[5] = {1,2,3,4,5} 一一对应
部分初始化 int a[5] = {1,2} 剩下补零
全清零 int a[5] = {0} 套路,靠得住
省略长度 int a[] = {1,2,3} 必须初始化
二维声明 int g[3][4] 前行后列别写反
二维初始化 int g[][4] = {{…}} 只省第一维
访问 a[i]、g[i][j] 下标从零开始
范围 for for (int x : a) 改值要加引用
string 拼接 a + b 内容操作,不碰地址
string 长度 length 或 size 一样的意思
string 截取 s.substr(0, 5) 第二个参数是长度
string 查找 s.find("x") 和 npos 配着判
char 数组 char n[] = "Tom" sizeof 比 strlen 多一
常见现象病根药方
读到乱数 数组没初始化 声明就给值
跑着跑着崩了 下标越界 查循环上下限
平均分丢小数 int 相除 先转 double
排序名次对不上人 只换了分数 两个数组一起换
查第一个人总是找不到 found 初值设成 0 初值设 -1
二维遍历结果怪 行列数反或扫反了 外层锁方向再看一遍
sizeof 和 strlen 差一 结束符算字节不算内容 各自场合用对
中文长度数不准 多字节按 char 数 换 string 或宽字符
赞(0)
未经允许不得转载:171主机测评 » 第五章 数组
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址