数组

数组是由类型名、标识符和维数组成的复合数据类型,类型名规定了存放在数组中的元素的类型,而维数则指定数组中包含的元素个数。

数组定义中的类型名可以是内置数据类型或类类型;除引用之外,数组元素的类型还可以是任意的复合类型。没有所有元素都是引用的数组。

数组的定义和初始化

数组的维数必须用值大于等于 1 的常量表达式定义。此常量表达式只能包含整型字面值常量、枚举常量或者用常量表达式初始化的整型 const 对象。非 const 变量以及要到运行阶段才知道其值的 const变量都不能用于定义数组的维数。

数组的维数必须在一对方括号 [] 内指定:

// both buf_size and max_files are const
const unsigned buf_size = 512, max_files = 20;
int staff_size = 27; // nonconst
const unsigned sz = get_size(); // const value not known until run time
char input_buffer[buf_size]; // ok: const variable
string fileTable[max_files + 1]; // ok: constant expression
double salaries[staff_size]; // error: non const variable
int test_scores[get_size()]; // error: non const expression
int vals[sz]; // error: size not known until run time

虽然 staff_size 是用字面值常量进行初始化,但 staff_size 本身是一个非 const 对象,只有在运行时才能获得它的值,因此,使用该变量来定义数组维数是非法的。而对于 sz,尽管它是一个 const 对象,但它的值要到运行时调用 get_size 函数后才知道,因此,它也不能用于定义数组维数。

max_files + 1

另一方面,由于 max_files 是 const 变量,因此表达式是常量表达式,编译时即可计算出该表达式的值为 21。

显式初始化数组元素

在定义数组时,可为其元素提供一组用逗号分隔的初值,这些初值用花括号{}括起来,称为初始化列表:

const unsigned array_size = 3;
int ia[array_size] = {0, 1, 2};

如果没有显式提供元素初值, 则数组元素会像普通变量一样初始化:

在函数体外定义的内置数组,其元素均初始化为 0。

在函数体内定义的内置数组,其元素无初始化。

不管数组在哪里定义,如果其元素为类类型,则自动调用该类的默认构造

函数进行初始化;如果该类没有默认构造函数,则必须为该数组的元素提供显式初始化。

除非显式地提供元素初值,否则内置类型的局部数组的元素没有初始化。此时,除了给元素赋值外,其他使用这些元素的操作没有定义。

显式初始化的数组不需要指定数组的维数值,编译器会根据列出的元素个数来确定数组的长度:

int ia[] = {0, 1, 2}; // an array of dimension 3

如果指定了数组维数,那么初始化列表提供的元素个数不能超过维数值。如果维数大于列出的元素初值个数,则只初始化前面的数组元素; 剩下的其他元素,若是内置类型则初始化为 0,若是类类型则调用该类的默认构造函数进行初始化:

特殊的字符数组

字符数组既可以用一组由花括号括起来、逗号隔开的字符字面值进行初始化,也可以用一个字符串字面值进行初始化。然而,要注意这两种初始化形式并不完全相同,字符串字面值包含一个额外的空字符(null)用于结束字符串。当使用字符串字面值来初始化创建的新数组时,将在新数组中加入空字符:

char ca1[] = {'C', '+', '+'}; // no null
char ca2[] = {'C', '+', '+', '\0'}; // explicit null
char ca3[] = "C++"; // null terminator added automatically

ca1 的维数是 3,而 ca2 和 ca3 的维数则是 4。使用一组字符字面值初始化字符数组时,一定要记得添加结束字符串的空字符。例如,下面的初始化将导致编译时的错误:

const char ch3[6] = "Daniel"; // error: Daniel is 7 elements

上述字符串字面值包含了 6 个显式字符,存放该字符串的数组则必须有 7个元素——6 个用于存储字符字面值,而 1 个用于存放空字符 null。

不允许数组直接复制和赋值

与 vector 不同,一个数组不能用另外一个数组初始化,也不能将一个数组

赋值给另一个数组,这些操作都是非法的:

int ia[] = {0, 1, 2}; // ok: array of ints
int ia2[](ia); // error: cannot initialize one array with another
int ia3[array_size]; // ok: but elements are uninitialized!
ia3 = ia; // error: cannot assign one array to another

理解复杂的数组声明

int *ptrs[10];                     
int &refs[10]=/*?*/;   //错误,不存在引用的数组
int (*Parray)[10]=&arr;     // Parray指向一个含有10个整数的数组
int (&arrRef)[10]=arr;       // arrRef引用一个含有10个整数的数组

访问数组元素

与 vector和string 一样,数组元素也能使用范围for语句或下标操作符来访问,数组元素也是从 0 开始计数。对于一个包含 10 个元素的数组,正确的下标值是从 0 到 9,而不是从 1 到 10。

在使用数组下标的时候,通常将其定义为size_t类型。size_t是一种机器相关的无符号类型,它被设计得足够大以便能表示内存中任意对象的大小。

使用范围for语句遍历数组中的元素:

unsigned scores[11] = {};
for(auto i:scores)
       cout<<i<<endl;

检查数组下标值

正如 string 和 vector 类型,程序员在使用数组时,也必须保证其下标值在正确范围之内,即数组在该下标位置应对应一个元素。

导致安全问题的最常见原因是所谓“缓冲区溢出(buffer overflow)”错误。当我们在编程时没有检查下标,并且引用了越出数组或其他类似数据结构边界的元素时,就会导致这类错误。

指针和数组

通常,使用取地址符来获取指向某个对象的指针,取地址符可以用于任何对象,对数组的元素使用取地址符能够得到指向该元素的指针。

数组还有一个特性:在很多使用数组名字的地方,编译器会自动将其转换为一个指向数组首元素的指针。

当使用数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组:

int ia[]={0,1,2,3,4,5,6};
auto ia2(ia);
ia2=42;         //错误,ia2是一个指针,不能用int值给指针赋值

而当使用decltype关键字时上述转换不会发生,decltype(ia)返回的类型是由7个整数构成的数组:

decltype(ia) ia3={0,1,2,3,4,5,6};

指针也是迭代器

指向数组元素的指针拥有跟多的功能,vector和string的迭代器支持的运算,数组的指针都支持。例如,允许使用递增运算符将指向数组的指针向前移动到下一个位置:

int arr[]={0,1,2,3,4,5,6,7,8,9};
int *p =arr;
++p;

使用指针也可以遍历数组中的每一个元素,数组名字或者数组中首元素的地址都能得到指向首元素的指针,尾后指针可以设法获取数组尾元素后的那个不存在的元素的地址:

int *e = &arr[10];

尾后指针不指向具体的元素,不能对尾后指针执行解引用或者递增操作:

for(int*b=arr;b!=e;++b)
       cout<<*b<<endl;

标准库函数begin和end

尽管能计算得到尾后指针,但是这种方法容易出错,为了让指针的使用更简单、安全,C++11新标准引入两个名为begin和end的函数,这两个函数使用数组作为它们的参数,这两个函数定义在iterator头文件中:

int ia[]={0,1,2,3,4,5,6,7,8,9};
int *beg=begin(ia);     //指向ia首元素的指针
int *last=end(ia);        //指向arr尾元素的下一个位置的指针

指针运算

指向数组元素的指针可以执行:解引用、递增、比较、与整数相加、两个指针相减等。

在指针上加上(或减去)一个整型数值 n 等效于获得一个新指针,该新指针指向指针原来指向的元素之后(或之前)的第 n 个元素。指针的算术操作只有在原指针和计算出来的新指针都指向同一个数组的元素,或指向该数组存储空间的下一单元时才是合法的。如果指针指向一对象,我们还可以在指针上加 1 从而获取指向相邻的下一个对象的指针

只要两个指针指向同一数组或有一个指向该数组末端的下一单元,C++ 还支持对这两个指针做减法操作:

ptrdiff_t n = ip2 - ip; // ok: distance between the pointers

两个指针减法操作的结果是标准库类型(library type) ptrdiff_t 的数据。与 size_t 类型一样,ptrdiff_t 也是一种与机器相关的类型,在 cstddef 头文件中定义。size_t 是unsigned 类型,而 ptrdiff_t 则是 signed 整型。

这两种类型的差别体现了它们各自的用途:size_t 类型用于指明数组长度,它必须是一个正数; ptrdiff_t 类型则应保证足以存放同一数组中两个指针之间的差距,它有可能是负数。

解引用和指针算术操作之间的相互作用

在指针上加一个整型数值,其结果仍然是指针。允许在这个结果上直接进行解引用操作,而不必先把它赋给一个新指针:

int last = *(ia + 4); // ok: initializes last to 8, the value of ia[4]

这个表达式计算出 ia 所指向元素后面的第 4 个元素的地址,然后对该地址进

行解引用操作,等价于 ia[4]。

由于加法操作和解引用操作的优先级不同,上述表达式中的圆括号是必要的。解引用操作符的优先级比加法操作符高。

下标和指针

在表达式中使用数组名时,实际上使用的是指向数组第一个元素的指针。

只要指针指向数组元素,就可以对它进行下标操作:

int ia[] = {0,2,4,6,8};
int *p = &ia[2]; // ok: p points to the element indexed by 2
int j = p[1]; // ok: p[1] equivalent to *(p + 1),// p[1] is the same element as ia[3]
int k = p[-2]; // ok: p[-2] is the same element as ia[0]
posted @ 2018-04-28 17:59  刘-皇叔  阅读(243)  评论(0)    收藏  举报