数组
数组是一种类似于标准库类型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 |
|