数组
数组是一种类似于标准库类型vector的数据结构,与vector相似的是,数组也是存放类型相同的对象的容器,这些对象需要通过其所在位置访问;与vector不同的是,数组的大小确定不变,不能随意向数组中增加元素。
定义和初始化内置数组
数组的声明形如 a[d] ,其中a是数组的名字,d是数组的维度。维度必须是一个常量表达式。
1 | constexpr unsigned sz = 42; //常量表达式 |
默认情况下,数组的元素被默认初始化。定义数组的时候必须指定数组的类型,不能用auto关键字由初始值的列表推断类型。数组的元素应为对象,因此不存在引用的数组。
显式初始化数组元素
可以对数组的元素进行列表初始化,如果没有指明维度,编译器会根据初始值的数量计算并推测出来;若指明了维度,那么初始值的总数量不应该超出指定的大小;如果维度比提供的初始值数量大,则剩下的元素被初始化成默认值。
1 | const unsigned sz = 3; |
字符数组的特殊性
当使用字符串字面值对字符数组初始化(只可以在数组并初始化的时候)时,一定要注意字符串字面值的结尾处还有一个空字符。不能用赋值语句将一个字符串常量或字符数组直接赋给另一个数组。
1 | char a1[] = {'C', '+', '+'}; //列表初始化,没有空字符 |
不允许拷贝和赋值
不能将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值。
复杂的数组声明
数组能存放大多数类型的对象,可以定义一个存放指针的数组;又因为数组本身是对象,所以允许定义数组的指针及数组的引用。默认情况下,类型修饰符从右向左依次绑定。就数组而言,从数组的名字开始由内向外阅读更容易理解。
1 | int *ptrs[10]; //ptrs是含有10个整型指针的数组 |
练习
3.27 设txt_size是一个无参数的函数,它的返回值是int。下列哪些定义是非法的?为什么?
1 | unsigned buf_size = 1024; |
访问数组元素
与标准库类型vector 和string 一样,数组的元素也能使用范围for 语句或下标运算符来访问。数组的索引从0开始。
数组下标通常定义为size_t类型,size_t是一种机器相关的无符号类型,在cstddef头文件中定义。
1 | //以10分为一个分段统计成绩的数量:0~9.10~19,...,90~99,100 |
与vector 和string 一样,当需要遍历数组的所有元素时,最好的办法是使用范围for语句。
1 | //对于scores中的每个计数值输出当前的计数值 |
必须要检查数组下标的值在合理范围内,下标越界会产生缓冲区溢出。
练习
3.31编写一段程序,定义一个含有10个int的数组,令每个元素的值就是其下标值。
1 | include<iostream> |
3.32 将上一题创建的数组拷贝给另外一个数组,利用vector重写程序,实现类似的功能。
//如果要把数组的内容拷贝给另外一个数组,不能直接对数值使用赋值运算符,而应该逐一拷贝数组的元素。
1 | include<iostream> |
//用vector重写
1 |
|
例子:
输出100以内的所有素数。
一种思路:让2,3,4,5,…,c中的每个数自我相加多次,来获得100之内的所有合数,筛掉合数之后就得到素数。若n为合数,则n的最小正因数c满足:
循环结构的N-S图:

1 |
|
多维数组
严格来说,C++中并没有多维数组,通常所说的多维数组其实是数组的数组。按照由内而外的顺序阅读。
多维数组初始化的几种方式:
1 | int ia[3][4] = { {0,1,2,3}, {4,5,6,7}, {8,9,10,11} }; //每一行分别用花括号括起来 |
多维数组的下标引用:如果表达式含有的下标运算符和数组的维度一样多,该表达式的结果是给定类型的元素;如果表达式含有的下标运算符数量比数组的维度小,则表达式的结果将是给定索引处的一个内层数组。
例子:
某学校有1000位老师,分布在20个不同的学院中,每个学院最多有12个系,请你编写一个程序,输入每位老师的所在院、系的编号(院编号1-20,系编号1-12),打印出各个系老师的数量。
1 |
|
指针和数组
数组的地址
在C++语言中,指针和数组有非常紧密的联系。数组名代表数组首元素的地址:数组名是指向数组第一个元素的指针。对于数组a[10],数组名a代表数组a[10]中第一个元素a[0]的地址,即a与&a[0]等价。需要注意的是,a是地址常量,不是变量,不能给a赋值
1 | int a[4] = {10,11,12,13}; |
若a是指向数组第一个元素的指针,即a相当于&a[0]。
&a是”指向数组“的指针,&a+1将跨越16个字节,&a相当于管辖范围”上升“了一级;
a是数组的第一个元素a[0],即 a等价于a[0], *a相当于管辖范围“下降”了一级。
在一些情况下数组的操作实际上是指针的操作。当使用数组作为一个auto变量的初始值时,得到的类型是指针而非数组。当使用decltype关键字时上述转换不会发生,decltyoe(ia)返回的类型是由整数构成的数组。
1 | int ia[] = {0,1,2,3,4}; |
C++11新标准引入了begin和end函数,定义在iterator头文件中,这两个函数与容器中的两个同名成员功能类似,但由于数组不是类类型,因此这两个函数不是成员函数,使用时需要将数组作为它们的参数。
1 | int ia[] = {0,1,2,3,4}; |
利用指针变量引用数组元素
1 | int a[10], *pointer; |
需要注意的是:a++是没有意义的,但p++会引起p的变化。p可以指向数组最后一个元素以后的元素,称为尾后指针,就像尾后迭代器,尾后指针不能执行解引用和递增操作。指针做加减运算时一定要注意有效的范围。
1 | int a[5]; |
根据运算符的优先级有:
*++p相当于a[++i],先将p自加,再做*运算。*--p相当于a[--i],先将p自减,再做*运算。*p++相当于a[i++],先做*运算,再将p自加。*p--相当于a[i--],先做*运算,再将p自减。
例子:
1 | //使用指针代替数组下标 |
1 | //倒置数组元素 |
二维数组的地址
数组名相当于指向数组第一个元素的指针。
*a等价于a[0],相当于a下降了一级;
&a表示“指向二维数组”的指针,相当于上升了一级。
1 | int main(){ |
例子1:遍历数组元素
1 |
|
例子2:输入i,j;输出a[i] [j]
1 |
|
问题分析:
从
p=a开始,a相当于指向a[3][4]的“第一个元素”的指针;所谓“第一个元素”是指一个 “包含4个int型元素的一维数组”;所以,a相当于一个 “包含4个int型元素的一维数组”的地址;因此p的基类型应该是 “包含4个int型元素的一维数组”。如何定义一个指向 “包含4个int型元素的一维数组” 的指针变量? ———-
int (*p)[4]*(*(p+i)+j)是什么?p指向一个“包含4个int型元素的一维数组”;p+i是第i+1个“包含4个Int 型元素的一维数组”的地址;p+i等价于&a[i];
*(p+i)等价于a[i];*(p+i)+j等价于a[i]+j;
因为:a[i]+j等价于&a[i][j],所以:*(*(p+i)+j)等价于a[i][j]。
p[i][j]是什么?p[i]等价于
*(p+i);p[i][j]等价于*(*(p+i)+j),等价于a[i][j]。
使用范围for语句处理多维数组
C++11新标准中新增了范围for语句,可以使用范围for语句处理多维数组,为了避免数组被自动转成指针,处理最内层的循环外,其他所有循环的控制变量应该都是引用类型。
1 | constexpr size_t rowCnt=3. colCnt=4, cnt=0; |
也可以使用标准库函数begin和end。
1 | for (auto p=begin(ia); p!=end(ia); ++p) |
字符串指针
1 |
|