string和vector是两种最重要的标准库类型。内置数组是一种更基础的数据类型,string和vector是对数组的某种抽象。
命名空间的using声明
使用using声明可以更方便的使用命名空间中的成员。using namespace::name;,添加该语句后可以直接饮用name,而不用再添加namespace。注意命名空间中的每个成员都要使用using语句声明才能直接引用。
头文件中不应包括using声明,因为头文件会被引用到其他文件中去,这可能造成始料未及的名称冲突。
注意使用using声明语句后将在当前名称空间中增加引用的成员名称。
标准库类型string
string表示可变长的字符序列,使用string类型必须包含string头文件,并且string定义在std命名空间中,因此使用string应包含以下两条语句:#include <string> using std::string;
如何初始化类是由类本身决定的,初始化string的一些常用方式s1="str1";
如果使用赋值语句“=”初始化,实际上执行的是拷贝初始化,建议使用直接初始化的方式如:string s("a string");。
字符串字面值和字符字面值不是string类型,但是他们可以转换成string类型,因此当执行加法操作时如果有一个操作数为string类型则另一个字面值将转换成string类型,但是两个字面值不能执行加法操作。string类型可以执行比较操作,字符串相同,个数相等时是否相等比较返回true,如果字符不相等则根据字典顺序比较。
string的size函数返回的类型为string::size_type,为无符号类型的值,在参与算数运算时要格外注意,因为有符号数会自动转换为无符号数。
使用范围for语句遍历字符串中的每个字符,
for (auto c : str) { cout << c << endl; }
使用范围for语句改变字符串中的字符,需要把变量声明为引用类型,如for(auto &c:str){c=toupper(c);}
下标运算符([])接受的参数类型为string::size_type类型,下标运算返回的是该位置上字符的引用。只要字符串不是常量,就能为下标运算符返回的字符赋新值。
使用下标访问字符时一定要注意下标的合法性,下标一定大于等于0而小于字符串的size()值,也就是说用下标范文一个空字符串时一定会产生不可预知的结果。
标准库类型vector
vector表示对象的集合,所有对象的类型都相同,要想使用vector,必须include相关的头文件,并可以使用using语句声明:using std::vector; vector是一个类模板,编译器根据模板创建类或函数的过程称为实例化,在模板名字后面跟尖括号,尖括号中提供具体信息。vector的类型不能是引用类型。列表初始化方法:vector<T> v{a,b,c....},包含了初始值个数的元素。
通过push_back函数想vector对象添加元素,元素会添加到末尾。vector对象能高效增长,因此即使在知道元素个数时也创建一个空vector然后在像其中添加元素。如果指定元素个数后再像其中添加元素可能效率会更低,除非需要添加的元素完全一样。特别注意,范围for语句中不能包含改变序列大小的代码。
vector的size函数返回元素的个数,返回的类型为vector<T>::size_type,注意vector::size_type的写法是错误的。
vector可以通过下标运算符获取指定的元素,下标的类型为size_type类型。只要vector对象不是一个常亮,就能想下标运算符返回的元素赋值。不能用下标操作添加元素,只能对已经存在的元素执行下标操作。
迭代器介绍
所有标准库容器都可以使用迭代器,string类型可以使用迭代器,迭代器提供了对元素的间接访问,迭代器能从一个元素移动到另一个元素。迭代器分为有效和无效两种状态。有效的迭代器或者指向一个元素,或者指向尾元素的下一个位置。有迭代器的类型同时拥有返回迭代器的成员,begin返回指向第一个元素的迭代器,end返回指向最后一个元素下一个位置的迭代器,称为尾后迭代器,只是一个标识,没有实际意义。*iter返回迭代器所指元素的引用,iter->member返回迭代器所指元素的成员,++iter,指向下一个元素,--iter,指向上一个元素。
通过判断begin返回的迭代器和end返回的迭代器是否相等确定容器是否为空,通过判断迭代器是否为end返回的迭代器确定是否已遍历完容器。
如果是常量容器,begin和end返回常量迭代器const_iterator,否则返回iterator类型。常量迭代器只能执行只读操作。可以通过调用cbegin和cend返回常量迭代器而不管容器是否为常量。
注意,通过解引用来访问元素的成员的方式(*it).member,括号是必须的。为了简化上述操作,C++定义了箭头运算符->,箭头运算符把解引用和成员访问结合到一起,it->member与(*it).member等价。
注意,不能在范围for循环中想vector添加元素,同样任何改变容器容量的操作都会使迭代器失效。
数组
数组与vector类似,是用来存放相同类型对象的容器,但数组的大小固定。数组是一种复合类型,声明形式如a[d],a是数组的名字,d是维度,编译时数组的维度必须是已知的,所以维度必须是常量表达式。默认情况下,数组的元素被默认初始化。但是如果在函数内部定义内置类型的数组,那么默认初始化会另数组含有未定义值。声明数组不允许使用auto关键字,并且数组的元素必须为对象,不能是引用类型。可以通过赋值等号并使用列表初始化数组,初始化时可以不指定维度而由编译器根据初始化列表的元素个数推断。如:int arr[]={1,2,3,4,5};
字符数组可以使用字符串字面值进行初始化,但是会自动在最后一个元素添加空字符' ',如char a[4]="c++";,a数组的纬数为4。
数组不允许拷贝和赋值,即不允许使用一个数组赋值另一个数组,也不能使用一个数组初始化另一个数组。
要想很好的理解数组声明的含义,最好的方法是从数组的名字开始由内向外的阅读。
数组中的元素可以通过范围for语句或下标来访问,数组的索引从0开始,数组下标通常定义为size_t类型,数组下标的值需要由程序员来检查控制。
数组名称是指针,编译器一般会把数组名称转换成指针,int *p=&arr[0]与int *p=arr等价。当使用数组作为auto声明的变量的初始值时,变量会被推断为数组累心,但注意使用decltype运算符时还是会得到数组类型。
数组的指针也是迭代器,通过递增递减运算符移动指向元素的位置,通过获取元素的第一个地址可以得到首元素的指针,通过&arr[length]获取尾元素下一位置的地址,就像迭代器的尾后迭代器。
C++标准库中定义了begin和end函数,参数为数组,函数分别返回数组首元素指针和尾元素下一位置的指针,与迭代器类似。特别注意end返回的指针不能解引用并且不能执行递增操作。
如果指针指向了数组,则该指针可以执行迭代器所能执行的大多数操作,给指针加上一个整数,得到的指针仍指向数组中的一个元素,或者指向尾元素的下一个位置,但注意不能超过元素的个数,即最多只能从首元素开始加元素个数的整数值,得到尾元素的下一位置。
数组的下标运算可以支持负数,这和标准库类型string,vector不同。
字符串字面值是一种c风格字符串,默认会在字符串的末尾添加空字符,当使用字符串字面值初始化标准库string类型时不包含最后的空字符。当操作c风格字符串时,需要使用c标准库函数的c++版本cstring.h,提供了strlen,strcmp,strcat,strcpy等函数用来操作c风格字符串。在C++中尽量避免使用c风格字符串。
混用string对象和c风格字符串,允许使用c风格字符串初始化string或给string赋值,string的加法运算中允许其中一个为c风格字符串,即字符数组,字符数组会自动转换成string对象,相反string对象不能自动转换为c风格字符串,不过string提供方法c_str(),该函数返回c风格字符串,即返回字符数组,但无法保证返回的数组一直有效,如果改变string对象本身,则返回的数组也会改变。
可以使用数组初始化vector对象,初始化时需要指明元素的开始指针和结束指针,如vector vec(begin(arr),end(arr)),这会用数组arr中的所有元素初始化vector,也可以使用数组的部分元素初始化vector。
现代C++程序应尽量使用标准库类型如vector,迭代器,string,而尽量避免使用内置数组和指针。
多维数组
C++中的多维数组实际上是数组的数组。多维数组的定义arr[3][4],其含义是定义一个有3个元素的数组,每个元素是包含有4个元素的数组,通常把二维数组的第一维称为行,第二维称为列。
可以使用花括号来初始化多维数组,每行可以用嵌套的花括号括起来,也可以不使用嵌套花括号,如
int arr[3][4] = { { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 } }; int arr2[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
以上两种初始化方式等价。
可以通过范围for语句遍历多维数组,声明循环变量时使用auto修饰符声明引用类型,这样可以改变数组元素的值,但是只有最内层的数组可以不使用引用类型,这是因为如果不声明引用类型编译器会自动把数组转换成指针,如代码:
for (auto &row : arr) { for (auto &col : row) { cout << col << endl; } } for (auto &row : arr) { for (auto col : row) { cout << col << endl; } }
以上两种方法都是合法的,只是如果循环变量不是引用类型则不能改变数组元素的值。
通过使用auto和decltype关键字可以省去晦涩难懂的复合类型声明。也可以通过类型别名简化多维数组的指针声明。