C++菜鸟学习笔记系列(10)
本期主题:数组、数组与指针之间的关系
我们在学习C语言时对于数组一定有很多的了解。数组在C以及C#中都很常见,但是对于C++语言我们更喜欢用vector和迭代器而不是数组和下标。
数组与我前面介绍的vector类型相比非常类似,它们都是可以存放相同对象的元素,且都可以通过其所在位置进行访问。但是与vector相比数组的大小确定不变,不能随意向数组中添加元素,缺少了一部分灵活性。(也正是数组的大小固定导致我们在C++中更喜欢用vector而不是数组)
(如果你有一定的C语言基础,可以直接跳过第一部分内容,直接读后面关于数组的使用及其与指针之间的关系就可以了。)
1.定义及初始化内置数组
数组是一种复合类型。数组的声明类似于a[d]的形式,其中a是数组的名字,d是数组的维度。维度说明了数组中元素的个数,因此必须大于0。数组中元素的个数也属于数组类型的一部分,编译的时候维度应该是已知的。即维度必须是一个常量或者是一个常量表达式。
下面我们来看一下具体怎么定义数组:
const int i = 10; void main() { int j = 10; int arr1[10]; int arr2[i]; int arr3[j]; //error }
在上述代码中我们可以通过字面值或者一个常量值来作为数组的维度值,但是一个变量不能作为数组的维度值。
显式初始化数组元素
我们可以对数组的元素进行列表初始化,此时允许忽略数组的维度。如果在声明时没有指明维度,编译器会根据初始值的数量计算并推测出来;相反的,如果我们指明了维度,那么在列表初始化的时候初始值的总数量不能超出指定的大小。如果维度比提供的初始值数量大,则用提供的初始值初始化靠前的元素,剩余的元素则默认初始化。
下面我们来看一下显式初始化数组的例子:
int arr1[sz] = {1, 2, 3}; int arr2[] = {1, 2, 3}; // equal to arr1[sz] = {1, 2, 3} int arr3[sz] = {1, 2, 3, 4};//error 初始值的总数量超出维度 int arr[4] = {1, 2, 3};
上述代码中的数组初始化方式适合大多数据类型数组的声明,但是对于字符数组仍然存在一些特殊性。
字符数组有一种额外的初始化方式,即我们可以使用字符串的字面值对此类数组进行初始化。当使用这种方式时初始化字符数组时一定要注意字符串字面值的结尾处还有一个空字符,这个空字符也会像字符串的其他形式一样被拷贝到字符数组中去。
char a1[] = {'1', '2', '3'}; // 没有添加空字符 char a2[] = {'1', '2', '3', '\0'}; // 显式添加了一个空字符 char a3[] = "123"; // 等价于a2[] = {'1', '2', '3', '\0'},会自动添加空字符 char a4[3] = "123"; //提示错误,因为没有考虑后面的空字符,所以超出了数组大小
注意:不能把数组的内容拷贝给其他数组作为其初始值,也不能使用数组为其他数组赋值。
2.访问数组元素
与标准库类型vector和string一样,数组的元素也是能够使用范围for语句或者下标运算符来访问。
数组除了大小固定这一特点外其余用法和vector基本类似,它们都可以使用下标运算符,但是仍存在一个不太明显的区别是数组中的下标运算符是由C++语言直接定义的,而vector中的下标运算符是库模板vector定义的,只能用于vector类型的运算对象。
下面我们来看一个使用数组下标的小例子:
/* Author: wxc_1998 Date: 2018/10/6 */ #include <iostream> using namespace std; const int sz = 10; void main() { int arr[sz], i = 0; int sum = 0; while (i < sz) { arr[i] = i; i++; } cout << "the array of 'arr' is:"<< endl; for (int j = 0 ; j < sz ; j++) { sum += arr[j]; cout << arr[j] << " "; } cout << endl << "the sum of 'arr' is :" << sum; cout << endl << "press any key to continue!"; cin.clear(); cin.sync(); cin.get(); }
注意:与标准库类型vector和string类似的,检查数组的下标是否在正确的区间内也是我们对代码检验正确性的一个重要内容,但是除了谨慎小心和进行彻底测试之外我们并没有更好的办法去避免这一类问题的出现。
3.指针和数组之间的联系
在C++语言中指针与数组之间有非常密切的联系,使用数组时,编译器常常把它转换为指针。
我们在C++菜鸟学习笔记系列(4)中曾经有过介绍使用取址符&可以获得某个对象的指针,取址符可以用于任何对象。数组的元素也是对象,对数组使用下标运算符得到该数组中某个位置的元素然后再通过取址符就可以获得指向该元素的指针。
例如:
string arr[] = {"123", "456", "789"}; string *p = &arr[0];
当我们省略下标直接对数组的名字使用取地址符时:
string *p2 = &arr;
编译器会默认将其替换为一个指向数组首元素的指针,等价于string *p = &arr[0];
由此我们还可以对数组的下标有这样的理解,即我们可以认为:arr[3]等价于在编译器中先找到arr[0]的位置,然后再往后移动3个元素得到arr[3],类似于
string *p2 = &arr; cout << *(p2 +3);
从上述的介绍中我们可以意识到尽管我们肯定可以通过计算得到数组最后一个元素的指针,但是这样是非常繁琐而且极易出现错误。为了让指针的使用更加简单、安全,C++ 11标准中引入了begin 和 end两个函数,类似于迭代器中的成员函数begin()、end(),不同的是在这里数组是作为begin 和 end两个函数的参数。
下面我们来看一个使用的小例子:
/* Author: wxc_1998 Date: 2018/10/6 */ #include <iostream> #include <iterator> using namespace std; const int sz = 10; void main() { int arr[sz], i = 0; int sum = 0; while (i < sz) { arr[i] = i; i++; } cout << "the array of 'arr' is:"<< endl; int *b = begin(arr); int *e = end(arr); while (b != e) { cout << *b << " "; sum += *b; b++; } cout << endl << "the sum of 'arr' is :" << sum; cout << endl << "press any key to continue!"; cin.clear(); cin.sync(); cin.get(); }
如上所示我们改写了第一个例子中的代码,实现了相同的功能,通过begin 和 end两个函数来确定数组的头和尾,然后通过指针的移动获得数组中的所有元素。
好了这次我们就介绍到这里了。
注:虽然这篇博客的内容十分简单,但是大家若有转载还请标明出处!
还有大家若对博客中的内容有任何问题可以随时联系我提问。