type
status
date
slug
summary
tags
category
icon
password
Property
数组类似
vector
,也是存放类型相同的对象的容器,但数组的大小确定不变,不能随意向数组中添加元素。如果不清楚元素的确切个数,应该使用vector
。通常使用数组是由于其优于vector
的运行性能。建议除非有充分的理由使用数组,尽可能是在任何场景使用vector
。定义和初始化内置数组
数组是复合类型,而且数组的维度是其类型的一部分,因而
int[3]
和int[10]
不是同一种类型。C++
要求数组的维度在编译时就得确定,且维度必须是一个常量表达式。注:某些编译器是允许定义可变长度数组(VLA),这是编译器自己的扩展行为并不属于语言标准。
在不给定初始值时,数组是默认初始化的。内置类型数组如果定义在函数中,其默认初始化是未定义值。
定义数组的时候必须指定数组的类型,不允许使用
auto
关键字对其元素类型进行推断。另外和vector
一样, 数组的元素应为对象, 因此不存在引用的数组。显式初始化数组元素
数组可以用列初始化,这跟所有的容器类的列初始化是一样的。如果进行列初始化时不提供维度,则编译器从初始值列表中推断。如果指定了维度,则初始列表的个数一定不能超过维度,否则将是编译错误。如果初始值列表长度不足维度数,则剩余的元素将执行值初始化,对于内置类型来说就是都初始化为
0
。字符数组
C++
继承了C
的字符串字面量,它们是以空字符 \0
结尾的字符数组。因而,当用字符串字面量初始化数组时,实际的维度比看到的字符数多1,这个字符串包括结尾空字符都复制到字符数组中去。不允许拷贝和赋值
不能将数组初始化为另外一个数组,也不能将数组赋值给另外一个数组:
一些编译器支持数组的赋值,这就是所谓的编译器扩展。但最好避免使用非标准特性,含有非标准特性的程序很可能在其他编译器上无法正常工作。
复杂数组声明
和
vector
一样,数组能存放大多数类型的对象。例如,可以定义一个存放指针的数组。又因为数组本身就是对象,所以允许定义数组的指针及数组的引用。访问数组元素
通过范围
for
和下标操作可以访问数组的元素,索引是从 0 开始的。下标值通常被定义为 size_t
类型,size_t
是一种机器相关的无符号类型,它被设计得足够大以便能表示内存中任意对象的大小。数组的索引还可以用int
类型并被指定为负数,表示从数组的某个位置向前n
个位置。新标准建议现在的程序最好使用范围for
来遍历整个数组。注:数组的类型包含了其维度。这在指针、范围 for 和引用中都会用到此特性。
访问数组的数组越界行为同样是未定义行为,所谓未定义行为就是即便出错了,编译器也不协助检查,一切全靠程序员自己检查。而且,在运行时未定义行为可能会正确,可能会在很久之后引起系统崩溃,但绝不会抛出异常。
指针和数组
指针和数组有非常紧密的联系,使用数组的时候编译器一般会把它转换成指针。
取地址符可以用于任何对象,数组的元素也是对象,对数组使用下标运算符得到该数组指定位置的元素。因此像其他对象一样,对数组的元素使用取地址符就能得到指向该元素的指针:
指针和数组在很多时候可以相互替换使用,原因在于数组名其实是数组首元素的指针。在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针:
在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。
当使用数组作为一个
auto
变量的初始值时,推断得到的类型是指针而非数组:当使用ia作为初始值时,编译器实际执行的初始化过程类似于下面的形式:
然而,当使用
decltype
关键字时上述转换不会发生,decltype(ia)
返回的是由10个整数构成的数组:对数组进行
sizeof
操作得到是整个数组的所占的内存的大小,而对指针的sizeof
操作得到的是指针所占内存的大小。C++
不允许对数组进行直接赋值,但是指针可以,对指针赋值使得指针指向别的位置。对指针进行取地址得到是指针的指针,对数组进行取地址得到是包含数组维度的数组指针。
用数组初始化的字符串常量可以改变其元素,用指针初始化的字符串常量改变其元素将是未定义行为,原因在于前者拷贝了字符串常量,而后者指向的是只读存储字符串常量的只读内存位置(称为字符串常量表)。
指针是语言定义的迭代器
事实上迭代器是对指针的模拟和抽象。指针支持自增、自减和算术运算。指向数组元素的指针中有一个特殊指针即指向数组尾元素下一个位置的指针,这个指针叫尾后(off-the-end)指针。通过将索引指定为数组长度得到的就是尾后指针:
上面
ia[10]
是一个不存在的元素,对其唯一允许的操作就是取地址,除此之外的任何操作都是未定义的。尾后指针不能解引用,向后移动亦是非法的。标准库 begin 和 end 函数
新标准中在
<iterator>
头文件中定义了begin
和end
函数用于返回数组的头指针和尾后指针,行为与容器的同名函数一样。这两个方法以数组为参数。这样就将迭代器和指针统一了,范围 for 以及泛型方法就是利用了这个特点得以以统一的方式对它们进行操作。指针算术运算
毫无疑问数组是支持随机访问的,程序员可以在任何时候访问数组中的任意位置上的元素。指针支持与整数的加减法,确保结果指针依然指向数组中的元素的工作交给了程序员完成。指向相同数组的指针间的减法将得到两者之间的距离,结果类型是 ptrdiff_t ,此类型是机器相关的有符号整数,并且保证容纳任何地址差。同样指针支持关系运算符,然而将其运用于两个不相关的对象指针上结果是未定义的。
指针和下标操作
对数组进行下标操作和对指针进行下标操作的效果是等同的,意味着可以在对指针进行下标操作。如以下方式都是等同的:
甚至可以像如下代码这样做,将索引指定为负数,只要取出的元素确实存在于数组中:
vector
和string
的下标要求一定是无符号整数,而数组的下标可以是负数。这是它们之间的重大区别。多维数组
多维数组的用途比较受限,因而较少使用。事实上,多维数组的所有元素都是顺序排列在一起的,所以用相同长度的一维数组构建是一样的。唯一的区别在于类型上的不一样:多维数组的元素是指定维度的数组。多维数组因而又称为数组的数组。
定义多维数组是一件很简单的事,多加一个中括号维度即是。如:
int ia[3][4]
int arr[10][20][30]
。这里第一个数组的元素是 int[4]
类型,第二个数组是 int[20][30]
类型。多维数组的初始化很有意思,根据前面的描述:所有元素都是依次排列成一线。所以内部的
{}
可以消除:如果不提供全所有值的话,每个内嵌的初始列表将初始化那一列,未给出的值均为 0 。
而不给出内嵌大括号,将只初始化整个数组的前几个元素:
多维数组的下标引用
多维数组进行下标引用将得到多种不同类型的元素,具体看给出了多少个下标值:
这同样会影响到指针的类型,指向数组的指针会带上数组的维度:
如果将多维数组运用于范围
for
,外部循环中的控制变量必须使用引用形式,否则得到的将是指针而不是数组,而指针是不能遍历的:C++11
新标准,使用auto
和decltype
能省略复杂的指针定义。使用标准库函数
begin
和end
也能实现同样的功能,而且看起来更简洁一些C 风格字符串
C 风格字符串并不是一种新的类型而是保存在字符数组中并且以空字符结束。通常是使用指针来操作这种形式的字符串。C 风格字符串函数定义在
<cstring>
头文件中。以上函数中的参数必须是以空字符结尾的字符数组的首元素指针。否则行为是未定义的。使用
string
类型时可以直接用关系操作符进行比较,而 C 风格字符串却是必须调用函数进行比较,如果用关系操作符比较的则是指针的值。如:strcat
和 strcpy
都需要程序员保证内存不会溢出,如果使用 string
则没有这样的担心,这正是应该使用string
的原因所在:更加安全而且更加高效。缓冲溢出已经成为了 C 语言中最严重的安全问题。提供给旧代码的接口
在
C++
标准订立前C++
就已经横空出世了,那时很多程序根本没有 string
类可用,并且很多时候 C++
程序必须给 C
程序提供接口,因而需要将string
字符串转为C
风格字符串。C++
中C
风格字符串可以自动转为string
类型,但是并没有相反的转换,string
提供 c_str
成员函数用来返回其内容的 C
风格字符串。返回结果是 const char*
类型。并且,不保证这个 C 风格字符串一直是有效的,当原始 string
改变时,此字符串就很可能会失效。所以要求使用者每次都调用 c_str
函数。