C++ Primer第三章⑤
C++ Primer
第三章 字符串,向量和数组
数组
数组和vector非常类似,也是用于存放类型相同的对象,不同的地方在于,数组的大小确定不变,不能随意向数组中增加元素。因为数组的大小固定,因此对某些特殊的应用来说,程序的运行时性能较好,但是相应的也损失了一些灵活性。
如果你觉得这一章看不下去,可以直接跳过,就用vector好了,真的没太大关系
如果不清楚元素的确切个数,请使用vector。
定义和初始化内置数组
值得注意的是,数组中元素的个数也是数组类型的一部分,编译的时候维度应该是已知的,也就是说,维度必须是一个常量表达式
int arr[10]; //含有十个整数的数组,默认初始化都为0 unsigned cnt = 10; //搞了半天unsigned == unsigned int,作者装什么逼。。。 constexpr unsigned f = 10; // string good[f]; //正确 string bad[cnt]; //错误:cnt不是常量表达式
为了避免程序出现什么低级错误,好的习惯是显式初始化数组元素,下面来看看这五花八门的初始化方式:
const unsigned sz = 3; int a1[sz] = {0, 1, 2}; //含有三个元素的数组,元素值分别为0,1,2 int a2[] = {0, 1, 2}; //与a1等价 int a3[5] = {0, 1, 2}; //等价于a3[] = {0, 1, 2, 0, 0} string a4[3] = {"hi", "bye"}; //等价于a4[] = {"hi", "bye", ""} int a5[2] = {0, 1, 2}; //错误:初始值过多
总结一下就是,元素不足我可以帮你补,维度不知道我可以推断,但元素多了就不行了。
注意一下烦人的小妖精,字符数组
只有在用字符串字面值初始化数组时,编译器会在最后面加一个空字符'\0'
char a1[] = {'C', '+', '+'}; //列表初始化,后面没有空字符 char a2[] = "C++"; //自动添加空字符 char a[3] = "C++"; //错误:维度不够,没办法存放空字符
数组还有个奇怪的地方是:不允许拷贝和复制,换句话说,你只能初始化它,不知道为什么,有了解的同学请赐教啊
==下面要把数组和指针分别结合,搞点事情了== 要看懂下面的1定义,记住从右到左看,有括号先看括号,是不是跟四则运算差不多啊,就是这里是从右到左。
int *ptrs[10]; //ptrs是数组,数组里面的元素类型是int * int (*parray)[10]; //parray是指针,指向含有10个元素的数组 int &refs[10] = ptrs; //ref是数组,数组里面的元素是引用, //难道就这么简单?不是的——>因为引用不是对象,这个定义错了 int (&arrRef)[10] = arr; //arrRef是一个引用,引用了一个含有10个整数的数组
来个难点的,只要按照上面的基本法就可以,不要慌
int *(&arry)[10] = ptrs; //首先看括号,括号里表示arry是个引用,括号看完了 //接下来从右往左看,引用的对象是一个大小为10的数组,最后看左边知道 //数组的元素类型是指针,指向的类型是int
访问数组元素
int a[3] = {1, 2, 3}; for(int i=0; i<3; ++i) { cout << a[i] << endl; } for(auto i : a) { cout << i << endl; }
检查下标的值
与vector和string一样,数组的下标是否在合理范围之内由程序员负责检查(其实挺讨厌的),所谓合理就是下标应该大于等于0且小于数组的大小。
==既然有了个新东西数组,于是讨厌的指针又来搞事情了==
指针和数组
像其他对象一样。对数组的元素使用取地址符就能得到指向该元素的指针:
string nums[] = {"1", "2", "3"}; string *p1 = &nums[1] //p1指向2
数组还有一个特性,在很多用到数组名字的地方,编译器会自动将数组名字替换为指向数组首元素的指针:
string *p0 = nums; //等价于string p0 = &nums[0];
就因为这个,接下来的代码就需要你好好看看了:
int a[] = {0, 1, 2}; auto a2(a); //a是数组名,所以会被转化为指针,指向0,初始化a2,也就是说a2是int *,指向a[0] //等价于auto a2(&a[0]); //但是,当使用decltype关键字时,上述转换不会发生 decltype(a) a3 = {3, 4, 5}; //这里decltype的参数虽然是数组名,但是它还是会返回整型数组int[3] //注意,数组的类型包括它的维度哦,这里列表初始化的元素最多就是3个 a3[2] = 7; //修改a3数组元素的值,与a无关了哦
用指针遍历数组
int arr[] = {0, 1, 2, 3}; int *p = arr; //p指向首元素 int *e = &arr[4] //e指向尾后元素,虽然看着不舒服,但是C++也支持这么做 for(int *b = p; b != e; ++b) { cout << *b << endl; }
标准库函数begin和end
为了用起来更顺手,我们一般用这两个函数,看着也高大上点
int arr[] = {0, 1, 2, 3}; int *beg = begin(arr); int *end = end(arr) while(beg != end) { cout << *beg << endl; ++beg; }
注意:尾后指针不能执行解引用和递增操作
指针运算
超简单,看看代码就好了
int arr[5] = {1, 2, 3, 4, 5}; int *p1 = arr; int *p2 = p1 + 3; //p2指向arr[3],就是4 //给前四个数都加1 while(p1 <= p2) //指针指的是同一个对象的才能比较 { ++(*p1); ++p1; }
空指针也可以有上述运算,虽然意义不大
解引用和指针运算的交互
名字起得玄乎罢了,没什么可怕的,看懂下面代码就行:
int arr[5] = {1, 2, 3, 4, 5}; int last = *(arr + 4); //last = arr[4],arr是数组名, 也就是指向数组首元素的指针,后移四位就是arr[4]的地址,解引用就是arr[4]
下标和指针
int arr[5] = {1, 2, 3, 4, 5}; int i1 = arr[2]; //等价于下面的句子 int *p = arr; int i2 = *(p + 2);
接下来的一种是C++也支持的运算,但是我们不推荐这么做,大家能看懂就好,这个特性只在数组中有。(vector和string都没有,它们的下标都必须是非负数)
int arr[5] = {1, 2, 3, 4, 5}; int *p = &arr[2]; //p指向arr[2] int j = p[1]; //p[1]等价于*(p+1),就是arr[3] int k = p[-2]; //p[-2]等价于*(p-2),就是arr[0]