1. 数组定义和初始化
数组的维度必须是常量表达式。
unsigned cnt =42;//不是常量表达式,只是一个无符号数
constexpr unsigned sz=30;//c++11 特性,声明为constexpr的变量一定是一个常量,必须由常量表达式初始化。
int arr[10];
string bad[cnt];//错误,cnt不是常量表达式
string strs[get_size()];//当get_size()是constexpr函数时正确。
数组的初始化分为函数体内部和外部初始化。
string sa[12];
int ia[20];
int main()
{
string sa2[12];
int ia2[21];
}
string 无论在函数体内还是体外都被默认初始化为空串。int型数组函数体外被默认初始化为0,函数体内将不被初始化,数组会含有未定义的值。
字符数组的特殊性
char a1[]={
'c','b','+'};//列表初始化,没有空字符
char a2[]={
'b','g','+',' '};//列表初始化,含有显式的空字符
char a3[]="c++primer";//字面值初始化,自动添加标识字符串结束的空字符
const char a4[6]="Daniel";//错误,没有空间存放空字符
使用字符串字面值对字符数组进行初始化时,一定要注意字符串字面值的结尾处还有一个空字符,这个空字符也会像其他字符一样被拷贝到字符数组中去。
2.理解复杂的数组声明
数组本身就是对象,允许定义数组的指针及数组的引用。
int *ptr[10];//从右到左理解,首先定义了一个大小为10的数组,名字为ptr,数据类型为int *,即存放的是指向int的指针。
int &ref[10];//错误,不存在数组的引用
int (*Parray)[10]=&arr;//从内向外理解,首先是括号,*Parray意味着Parray是一个指针,在观察右边,Parray是一个指向大小为10的数组的指针,数据类型为int,即Parray指向一个含有10个整数的数组。
int (&arrRef)[10]=arr;//(&arrRef)表示arrRef是一个引用,引用的对象是一个大小为10的数组,数组元素的数据类型为int型。即arrRef引用一个含有10个整数的数组。
int *(&array)[10]=ptrs;//array是一个引用,引用的对象是一个含有10个元素的数组,数组的数据类型是int*,即array是数组的引用,该数组含有10个指针。
3.数组元素的访问
使用数组下标时,通常将其定义为size_t 类型,其是一种机器相关的无符号类型,被设计的足够大以便能表示内存中任意对象的大小。
4.指针和数组
使用数组作为auto变量的初始值时,推断得到的类型是指针而非数组。
int ia[]={
1,3,4,5};
auto ia2(ia);
//ia2是一个整型指针,指向ia的第一个元素,相当于auto ia2(&ia[0]);
decltype(ia) ia3={
1,4,6};//ia3是一个含有10个整数的数组。
//当使用decltype关键字时上述转换不会发生,decltype(ia)返回的类型是由10个整数构成的数组。
指向数组元素的指针也是迭代器,可以使用指针遍历数组中的元素,必须先获取指向第一个元素的指针和指向数组尾元素的下一个位置的指针。
//设法获取数组尾元素之后的那个并不存在的地址
int *d=&arr[10];//arr是一个含有10个元素的数组。指针d指向尾元素的下一个位置,该指针不能执行解引用和递增操作。
for(int *b=arr;b!=d;++b)
cout<<*b<//但是这样方法极易出错,c++11引入了两个名为begin()和end()的函数,和容器的两个同名成员函数类似,但数组不是类类型,因此这两个函数也不是成员函数,使用数组作为他们的参数。
int ia[]={
2,3,4,6};
int *beg=begin(ia);//指向ia首元素的指针
int *last=end(ia);//指向is尾元素的下一个位置的指针
5. 指针运算
给指针加上或者减去一个整数,其结果仍是指针。
constexpr size_t sz=5;
int arr[sz]={
1,4,5,7,8};
int *ip=arr;//等价于int *ip=&arr[0]
int *ip2=ip+4;//ip2指向arr的尾元素arr[4]
int *p=arr+sz;//p指向arr的尾元素的下一个位置,不要对p使用解引用操作。
两个指针相减的结果是它们之间的距离。
auto n=end(arr)-begin(arr);//n的值为5,也就是arr中的元素的个数。
两个指针相减的结果的类型是以一种名为ptrdiff_t 的标准类型和size_t 一样,定义在cstddef头文件的机器相关的类型。差值可能是负值,所以ptrdiff_t 是一种带符号的类型。
6. 下标和指针
只要指针指向的是数组中的元素,都可以执行下标运算,内置的下标运算符所用的索引值不是无符号数。
int *p=&ia[2];//p指向ia中的第三个元素
int j=p[1];//p[1]等价于*(p+1),就是ia[3]表示的那个元素。
int k=p[-2];//p[-2]是ia[0]表示的那个元素
7. C 风格字符串
尽管c++支持c风格字符串,但是c++程序中最好不要使用它们。字符串存放在字符数组中并以空字符结束。
char ca[]={
'c','a','j'};//不以空字符结束
cout<<strlen(ca)<//错误,ca没有以空字符结束,strlen函数可能沿着ca内存中的位置不断寻找,直到遇到空字符。strlen是c标准函数。
把两个指针相加不但是非法的,而且没有任何意义。
8. 混用string对象和c风格字符串
可以使用字符串字面值来初始化string对象。
string s("hello,world");
任何出现字符串字面值的地方都可以用空字符结束的字符数组来代替。
允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值。
在string对象的加法运算中允许使用以空字符结束的数组作为其中一个运算对象(不能两个运算对象都是)。
如果程序某处需要一个c风格的字符串,无法直接用string对象来代替它,例如不能用string对象直接初始化指向字符的指针,为了完成这一功能,string专门提供了一个名为c_str的成员函数。c_str()返回一个c风格的字符串,即返回一个指针,该指针指向一个以空字符结束的字符数组。
char *str=s;//错误,不能用string对象初始化char*
const char *str=s.c_str();//c_str()是string类对象的成员函数。
9. 使用数组初始化vector对象
不能用数组为另一个内置类型的数组赋初值,也不能用vector对象初始化数组。但可以用数组来初始化vector对象。
int int_arr[]={
0,3,4,6};
vector<int> ivec(begin(int_arr),end(int_arr));
c++程序应该尽量使用vector和迭代器,避免使用内置数组和指针。应该尽量使用string,避免使用c风格字符串。