Tip: 不清楚元素的确切个数,请使用vector
3.5.1 定义和初始化内置数组
数组是一种复合类型
unsigned cnt = 42;
constexpr unsigned sz = 42;
int arr[10] ;//含有十个整数的数组
int *parr[sz]; //含有42个整形指针的数组
string bad [cnt];//错cnt不是常量表达式
string strs[get_size()]//当get_size是constexpr时正确;否则错误
waning:和内置类型的变量一样,如果函数内部定义了某种内置类型的数组,那么默认初始化会令数组含有未定义的值
不允许用 auto定义数组
和vector一样,元素都是对象不能有引用
显示初始化数组元素
如果维度比初始值大,初始化靠前的元素,剩下初始化为默认值
const unsigned sz = 3;
int ial[sz] = {1,2,3}
int a3[5] = {0,1,2} // 等价于 0 1 2 0 0
字符数组的特殊性
字符串字面值的结尾有一个空字符,这个空字符也会想字符串的其他字符一样被拷贝到字符数组中去
char a1[] = {'C','+','+'} //列表初始化没有空字符 维度是3
char a2[] = {'C','+','+','\0'} //列表初始化,含有显示的空字符 维度是4
char a3[] = {"C++"}; // 自动添加空字符 维度是4
char a4[6] = {"Danial"} // 错误:无处存放空字符
不允许拷贝和赋值
不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值
int a2[] = a;
a2 = a;
Warning 一些编译器支持数组的赋值,也就是编译器扩展(compiler extension)
一般来说避免使用非标准特性,可能在其他编译器上无法工作
理解复杂的数组声明
int *ptrs[10]; //ptrs是含有10个整型指针的数组
int &refs[10] = /*?*/; //错误:不存在引用的数组
int (*Parray)[10] = &arr; //Parray 指向一个含有10个整数的数组
int (&arrRef)[10] = arr; // Parray 引用一个含有10个整数的数组
int *(&arry)[10] = ptrs;// arry是数组的引用,该数组含有10个指针
Tip:想要理解数组声明的含义,最好的办法是从数组的名字开始按照由内向外的顺序阅读
3.5.2 访问数组元素
数组也能用范围for语句或下标运算符来访问
在使用数组下标的时候,通常将其定义为 size_t 类型
size_t 是一种机器相关的无符号类型,被设计的足够大表示内存中任意对象的大小
在cstddef 头文件中定义了 size_t
数组下标是由c++语言直接定义的
容器下标是库模板vector定义的,只能用于vector类型的运算对象
检查下标的值
warning: 大多数常见的安全问题都源于缓冲区溢出错误。当数组或其他类似数据结构的下标越界并试图访问非法内存区域时,就会产生此类错误
3.5.3 指针和数组
使用数组的时候编译器一般会把它转换成指针
很多用到数组名字的地方,编译器会自动地将其替换成为一个指向数组首元素的指针
string *p2 = nums; // 等价于 p2 = &nums[0]
Note:在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针
int ia[] = {0,1,2,3,4,5,6,7,8,9}//ia是一个含有十个整数的数组
auto ia2(ia); //ia2是一个整形指针,指向ia的第一个元素
ia2 = 42; //错误,ia2是一个指针
实际执行时是
auto ia2(ia[ 0 ]); // 显然ia2的类型是int*
当使用decltype关键字的时候不会发生转换
decltype( ia ) ia3 = {0,1,2,3,4,5,6,7,8,9};
ia3 =p; // 错:不能给整形指针赋值
指针也是迭代器
vector和string的迭代器支持的运算,数组的指针全部支持
arr[10] = {};
int *e = &arr[10]//指向arr尾元素的下一位置的指针
for(int *b = arr; b!=e; ++b)
cout << *b << endl; //输出arr的元素
标准库函数begin和end
这两个函数和vector的两个同名函数功能类似,因为数组不是类类型,使用形式不同
int ia[ ] = {0,1,2,3,4,5,6,7,8,9};//ia是一个含有10个整数的数组
int *beg = begin(ia);
int *last = end(ia);
寻找arr中第一个负数
int *pbeg = begin(arr), *pend = end(arr);
while (pbeg!= pend && *pbeg >=0)
++pbeg;
Note : 特别注意,尾后指针不能执行解引用和递增操作
指针运算
指向数组元素的指针和迭代器的运算操作完全相同(见前表)
auto n = end(arr) - begin(arr);//n的值就是arr中元素的数量
两个指针相减的结果的类型是一种名为ptrdiff_t的标准库类型
和 size_t一样,ptrdiff_t 也是定义在cstddef头文件的机器相关的类型,带符号
解引用和指针运算的交互
int last = *(ia + 4);//正确:ia[4]的值
要加圆括号
下标和指针
int *p = &ia[2]; //p指向索引为2的元素
int j = p[1]; //p[1]等价于*(p+1),就是ia[3]
int k = p [-2]; // ia[0]
Warning: 标准库限定使用的下表类型是无符号型
内置下标运算符所用的索引值不是无符号类型,和string和vector不一样
3.5.4 C风格字符串
warning:尽管C++支持C风格字符串,但在C++程序中最好不要用
C风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法
习惯字符串存放在字符数组中并以空字符结束
C标准库String函数
定义在cstring中
C风格字符串的函数
strlen(p) | 返回p的长度,空字符不计在内 |
strcmp(p1,p2) | 比较p1和p2的相等性。如果p1==p2,返回0;如果p1>p2返回一个正值,反之负值 |
strcat(p1,p2) | p2附加到p1后返回p1 |
strcpy (p1,p2) | p2拷贝给p1,返回p1 |
warning:这些函数不负责验证其字符串参数
传入此类函数的指针必须指向以空字符作为结束的数组
char ca[] = {'C','+','+'}; //不以空字符结束,这类不行!
比较字符串
如果把c++运算用在c风格上
const char ca1[] = "A string example";
const char ca2[] = "A different string";
if(ca1 <ca2) // 未定义的:试图比较两个无关地址
实际上是两个 const char* 比较,两个指针指向并非同一对象,得到结果未定义
目标字符串的大小由调用者指定
c++风格
string largeStr = s1 + " "+s2;
如果用c就是两个数组相加,没有意义且非法
正确方法用 strcat和 strcpy
Tip:对大多数应用来说,使用string更安全
3.5.5 与旧代码的接口
混用string对象和C风格字符串
若在某处需要一个c风格的字符串
char *str = s;//错误 : 不能用string对象初始化 char*
const char *str = s.c_str();//正确
string中的一个名为c_str的成员函数返回值是一个C风格的字符串
函数返回结果是一个指针,指针指向一个以空字符串结束的字符数组
warning: 如果执行完 c_str() 函数后程序一直都能使用其返回的数组,最好将该数组重新拷贝一份
使用数组初始化vector 对象
不允许使用一个数组为另一个内置类型的数组赋初始值,也不允许vector对象初始化数组。
相反的允许使用数组来初始化vector对象,
只需指名要拷贝区域的首元素地址和尾后地址就可以
vector<int> ivec(begin(int_arr),end(int_arr));
//拷贝三个元素: int_arr[1],int_arr[2],int_arr[3];
vector<int> subVec(int_arr +1, int_arr + 4);
3个元素的值来自 【1】,【2】,【3】
建议:尽量使用标准库类型而非数组
尽量用vector和迭代器少用数组指针
尽量用string少用c风格的字符串