c++primer学习笔记-----3.5数组

来源:互联网 发布:手机淘宝怎么评价卖家 编辑:程序博客网 时间:2024/05/16 18:22

数组是一种类似于标准库类型vector 的数据结构。与vector 不同的地方是,数组的大小确定不变,不能随意向数组中增加元素。因为数组的大小固定,因此对某些特殊的应用来说程序的运行时性能较好,但是相应地也损失了一些灵活性。

{本章内容早有接触,笔记较为随意}


3.5.1 定义和初始化内置数组


【数组是一种复合类型。数组的声明形如a[d],其中a 是数组的名字,b 是数组的维度。数组中元素的个数也属于数组类型的一部分,编译的时候维度应该是已知的。维度必须是一个常量表达式(2.4.4)】


【和内置类型的变量一样,如果在函数内部定义了某种内置类型的数组,那么默认初始化会令数组含有未定义的值。

如果在函数体外部,这些内置类型元素会被初始化成默认值

不允许将数组的内容拷贝给其他数组作为其初始值,也不允许用数组为其他数组赋值。】


【定义数组的时候必须指定数组的类型,不允许使用auto 关键字由初始值的列表推断类型。(decltype 关键字推断类型是允许的)

另外和vector 一样,数组的元素应为对象,因此不存在引用的数组。】


可以对数组的元素进行列表初始化,此时允许忽略数组的维度。

const unsigned sz = 3;int ia1[sz] = {0, 1, 2}; // 合法,sz 是常量表达式int a2[] = {0, 1, 2}; // 合法,编译器根据初始值的数量构造了维度为3 的整形数组int a3[5] = {0, 1, 2}; // 合法,编译器会对没有匹配的元素初始化成默认值


【字符数组有一种额外的初始化形式,我们可以用字符串字面值对此类数组初始化。当时用这种方式时,一定要注意字符串字面值的结尾处还有一个空字符,这个字符也会像字符串的其他字符一样被拷贝到字符数组中去

char a3[] = "C++" // 最终a3 是4 个元素的数组char a4[3] = "C++" // 报错,数组界限溢出


在复杂的定义变量的语句中,与名字近的修饰符先考虑,同样近时先考虑右边(待修正)

int arr[10];int *ptr[10]; // ptr 是一个含有10 个指向整形变量的指针的数组int (*ptr1)[10]; // ptr1 是一个指向含有10 个整形变量的数组的指针int (&ref)[10] = arr; // ref 是一个含有10 个整形变量的数组的引用int *(&ref1)[10] = ptr; // ref1 是一个含有10 个指向整形变量指针的数组的引用


3.5.2 访问数组元素


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


【大多数常见的安全问题都源于缓冲区溢出错误。当数组或其他类似数据结构的下标越界并试图访问非法内存区域时,就会产生此类错误。

与vector 和string 一样,当需要遍历数组的所有元素时,最好的办法也是使用范围for 语句。】


3.5.3 指针和数组


【在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针。

在一些情况下,对数组的操作实际上是对指针的操作,如:使用数组作为auto 变量的初始值时,推断得到的类型是指针而非数组(使用decltype 仍为数组)。】


【指针也是迭代器,vector 和string 的迭代器支持的运算,数组的指针全都支持

使用超过数组规模的下标,可以得到数组的尾后指针。】


c++11 新标准引入了两个名为begin 和end 的函数,这两个函数不是数组的成员函数(数组毕竟不是类类型),正确的使用方式是将数组作为它们的参数:

int ia[] = {1, 2, 3, 4, 5, 6, 7, 8};int *beg = begin(ia); // 指向ia 首元素的指针(同ia)int *last = end(ia); // 指向ia 尾元素的下一元素的指针
两个函数均定义在iterator 头文件中。】


【在数组使用下标运算符时,实际上是将数组作为指针进行运算并解引用,最终得到下标位置元素的引用(对于指向数组中元素的指针使用下标运算符,同样如此):

int *p = &ia[2];int j = p[1]; // 元素ia[2] 作为了j 的初始值int k = p[-2]; // 元素ia[0] 作为了k 的初始值
内置的下标运算并不要求索引严格为无符号类型,它可以处理负值。这点与vector 和string 并不相同。】


3.5.4 C 风格字符串


【C 风格字符串指使用字符数组来存储字符串。但其不仅使用起来不太方便,而且由于所需数组大小不易估算,极易引发程序漏洞。
比较两个C 风格字符串不能使用关系运算符,因为实际上比较的将会是两个指针,同样加法运算符也是试图将两个指针相加。
const char ca1[] = "HELLO";const char ca2[] = "bye-bye";if (ca1 < ca2) // 未定义的:试图比较两个无关地址
要想比较两个C 风格字符串需要调用strcmp 函数:
if (strcmp(ca1, ca2) < 0) // 如果两个字符串相等,函数返回0;前者较大,返回正值;后者较大,返回负值
要想连接两个C 风格字符串,需要调用strcpy 和strcat 函数(不赘述)。】

对大多数应用来说,使用标准库string 要比使用C 风格字符串更安全,更高效。】


3.5.5 与旧代码的接口

{在标准库出现之前出现的C++ 程序中,充斥着各类数组和C 风格的字符串,现代的C++ 程序不得不与其衔接,C++ 专门提供了一组功能完成这类工作。}

【string 对象允许使用字符串字面值做为初始值来进行初始化,更一般的情况,任何出现字符串字面值的地方都可以使用以空字符结束的字符数组来替代
允许使用以空字符结束的字符数组来初始化string 对象或为string 对象赋值;
在string 对象的加法运算中允许使用以空字符结束的字符数组作为其中一个运算对象。】

【上述性质反过来使用就不成立了,string 对象提供了一个名为c_str 的成员函数完成向C 风格字符串转化的功能
const char *str = s.str();
c_str 函数的返回值是一个C 风格字符串。就是说,函数的返回结果是一个指针,该指针指向一个以空字符结束的字符数组,而这个数组所存的数据恰好与string 对象的一样。结果指针的类型是const char,从而确保我们不会改变字符数组的内容。
通常我们希望得到的C 字符串不再发生改变,但我们无法保证c_str 函数返回的数组一直都有效,如果后续的操作改变了s 的值就可能让之前返回的数组也发生改变。
如果执行完c_str() 函数后程序想一直都能使用其返回的数组,最好将该数组重新拷贝一份。
说的啰嗦了,实际上就如语句的字面意义,就是构造了一个指向stiring 对象首个元素的指针,该指针可以当做数组来使用罢了

【不允许使用数组为另一个数组赋初值,也不允许使用vector 对象初始化数组;相反,允许使用数组来初始化vector 对象
int arr[] = {0, 1, 2, 3, 4, 5, 6};vector<int> ivec(begin(arr), end(arr));
只需指明要拷贝区域的首元素地址和尾后地址,就可以实现用数组来初始化vector 对象的操作。当然创建vector 对象的初值也可以仅是数组的一部分,只要地址对应改变即可。】

【使用指针和数组很容易出错。一部分原因是概念上的问题:指针常用于底层操作,因此容易引发一些与繁琐细节有关的错误。其他原因则源于语法错误,特别是声明指针时的语法错误。
现代的C++ 程序应当尽量使用vector 和迭代器,避免使用内置数组和指针;应该尽量使用string,避免使用C 风格的基于数组的字符串。】
0 0
原创粉丝点击