本章介绍了两种重要的标准类型,string 和vector, string是可变长的字符序列,vector存放的是给定类型对象的可变长序列.本章还将介绍内置数组类,和其他类型一样,数组实现和硬件密切相关,在灵活性上稍显不足
3.1, 3.2命令空间的using声明
每个名字都需要using声明的方式:
例如using std::cin.
头文件不应包含using声明
不经意间包含的一些名字可能会造成始料未及的冲突
3.3 string 容器
string是std定义在命名空间中.c++标准对库的操作做了详细的说明,另外对库的实现也有性能上的要求,因此标准库在一般应用场合由足够的效率.
3.3.1 定义和初始化strng对象
几个例子:
string s1; //默认初始化为一个空串
string s2 = s1; //s2是s1的副本
string s3 = “hiya”;//s3是该字符字面值的副本
string s4 =(10 , ‘c’); // s4的内容是cccccccccc
strinf s5(“hiya”); //构造函数初始化
直接初始化和拷贝初始化
用等号初始化是拷贝初始化,拷贝等式右边的对象到左边,不适用等号执行的直接初始化.
3.3.2 string 对象上的操作
直接的函数名操作和和定义的特殊的操作符操作.
string s;
读写
string: is >>s,is>>s1>>s2,os << s, os <<s1<<s2.
读取未知数量的string
while(is>>word)cout<<word;
getline读取一行:
while(getline(cin, line))cout <<line<<endl;
遇到换行符读取一行,读取换行符但不存储,如果一开始是换行符line字符串为空
string的empty和size操作,如名所示
string::size_type类型:string size 函数的返回值,这是配套的,体现了标准库与机器无关的特性,他是一个无符号类型,
而且足够放的下任何对象的大小
不要担心,编译器允许通过auto或者decltype来推断变量的类型,但是切记,表达式中混合了无符号类型和有符号类型整数会有意想不到的和后果,比如n是一个负数的int
s.size() < n 会永远是true,因为n会被转换成一个比较大的无符号整数.因此表达式中有了size就不要用int了
比较string对象
两个string对象长度不同,短的每个字符都与长字符对应位置上相同,短的小于长的
两个string对象有些对应位置不一样,比较的结果是string对象第一对相异字符比较的结果
为string对象赋值 允许!
两个对象相加: 前提是两个都是string对象
字面值与string对象相加:
字符字面值与字符串字面值可以转化成string对象,所以需要string对象的地方可以用这两字面值代替.例如:
string s3 = s1 + “,” + ‘\n’;
注意这么做的前提是按照算数的计算顺序,每一步相加必须至少有一个是string对象,这样是不正确的:
string s4 = “,” + ‘\n’ + s1; //错误
历史原因字面值不是string对象.
3.3.3 处理string 对象中的字符
cctype 头文件中的函数用来检查字符的特性
isalnum(c) 是否字母或数字 isalpha(c)是否字母 isdigit(c)是否数字 iscntrl(c)是否控制字符 ispunct(c)是否标点符号 etc.
处理每个字符,使用基于范围的for语句. for(auto i : str) cout <
使用范围for语句改变字符串中的字符: 必须把变量定义为引用型
for(auto &c : s)c = toupper(c);
只处理一部分字符
下标运算符[]接收的string::size_type 类型,只要字符串不是常量就能为下标运算符返回字符赋值.
再比如将第一个单词改写成大写:
for(deltype(s.size()) index = 0 ; index != s.size()&& isspace(sindex[index]; index++); index++)
s[index] = toupper(s[index]);
使用下标执行随机访问
3.4标准库类型
vector 表示对象的集合,其中所有的对象类型都相同,每一个对象都有一个对应的索引.
vector是一个类模板,c++也有函数模板,所谓模板是编译生成类或函数的一份说明.编译器根据末班创建类或函数的过程为实例化.
不存在存放引用的vector
3.4.1定义和初始化vector对象
vector<T> v1 ; //默认初始化
vector<T> v2(v1);// 拷贝初始化
vector<T> v2 = v1; // 拷贝初始化
vector<T> v3(n, val); // 创建指定数量的元素
vector<T> v4(n); // 包含n个重复执行值初始化的对象,如果是内置类 型元素会被设成0,如果是某种类类型执行默认初始化.
vector<T> v5{a,b,c,...}; //列表初始化,v5包含初始值个数的元素, 每个元素被赋予相应的值
vector<T> v5= {a,b,c,...};// 等价于以上
对于v4使用有两个限制,vector中的类类型必须提供初始值否则不行.
对于列表初始化,如果花括号元素不能用来初始化就要考虑用这些值构造对象了.
3.4.2向vector对象中添加元素
push_back();
vector对象添加元素蕴含的编程假定,向vector对象添加元素蕴含的编程假定
如果循环体内有向vector添加元素的语句,不能使用范围for循环
3.4.3其他vector操作
v.empty()
v.size()
v.push_back(e);
v[n]
v1 = v2
v1 = {a,b,c,...}
v1!=v2
<,<=,>,>=
需要使用size_type时候要 vector::size_type;
计算vector内对象的索引
不能用对象的形式添加元素,只能对确知已存在的元素执行下表操作
3.5 迭代器
和指针不一样,含有迭代器类型同时拥有返回迭代器的成员,end成员返回尾元素的下一位置,通常也叫做尾后迭代器
3.5.1迭代器运算符
iter 返回iter所指元素的引用
iter->mem := (*iter).mem
++iter
--iter
iter1 == iter2 判断两个迭代器是否相等,如果两个迭代器指示的是同一个元素或者他们是同一元素的尾喉迭代器.则相等,否则相反.
迭代器类型.
一般那些迭代器标准库类型使用iterator和const_iterator来表示迭代器的类型
迭代器有三种含义: 迭代器概念本身,容器定义的迭代器类型,某个迭代器对象
begin 和 end 运算符
begin 和 end的返回是否常量与对象的类型有关系,显示获得 const_iterator,c++引入了两个函数分别是cbegin 和 cend
某些对vector对象的操作会使迭代器失效
vector的动态增长,一方面可能改变range,另一方面容器容量的改变会使得迭代器失效
3.5.2迭代器的运算
iter + n 迭代器元素与原来相比移动了若干个元素
iter - n 迭代器向后移动了若干个元素
iter +=n 迭代器加法复合语句
iter -=n 迭代器减法复合语句
iter1 - iter2 两个迭代器之间的距离,两个迭代器必须是指向同一容器中的 元素或者尾元素的下一位置
> >= < <= 位置前后之间的关系
迭代器的算数运算
两个迭代器的差值是 different_type ,是个带符号类型.
使用迭代器运算
3.6数组
3.6.1 定义和初始化数组
数组中元素个数是数组类型的一部分,而且维度必须是一个常量表达式.
unsigned cnt = 42 ;
constexpr unsigned sz = 42;
int * p[sz];
string bad[cnt] //错误cnt不是常量表达式
string strs[get_size()];//当编译时刻确定get_size()值的时候是正确的.
默认情况下数组的元素被默认初始化.
显示的初始化数组
看例子就明白
constexpr unsigned sz = 3;
int ial[sz] = {0,1,2}; //含有三个元素的数组,元素值分别是 0 1 2
int a2[] = {0 ,1 , 2}; //维度是3的数组
int a3[5] = {0,1,2,0,0}; // 等价于{0,1,2,0,0}
string a4[3] = {“hi” , “bye”}; //{“hi”,”bye” ,”“}
int a5[2] = {0,1,2} //错误, 初始值太多
字符数组的特殊性
字符数组可以用字符串字面值初始化,但是特殊的是结束符也会被拷贝进去
看下面的例子,尤其是最后一个
char a1[] = {‘C’,’+’ ,’+’}; //列表初始化没有空字符
char a2[] = {‘C’,’+’ , ‘+’,’\0’};//列表初始化,含有显示的空字符
char a3[] = “C++”; //自动添加表示字符串结束的空字符
const char a4[6] =”Daniel”; //错了,字符数组不够大,盛不下了
不允许拷贝和赋值
不能将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值
int a[] = {0,1,2};含有3个整数的数组
int a2[]= a; //错误,不允许用一个数组初始化另一个数组
int a2 = a; //错误,不能把一个数组直接赋值给另一个数组
虽然一些编译器支持但是这是编译器的扩展
理解复杂的数组声明
数组本身是对象, 可以定义数组的指针和引用,也可以定义一个存放指针的数组.前者稍微复杂一些看下面的例子:
int *ptrs[10] //ptrs 是含有10个整形指针的数组
int &refs[10] = /* ? */ //错误,不存在引用的数组
int (*Parray)[10] = &arr; //parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr ; //arrRef 引用一个含有10个整数的数组
类型修饰符从右向左一次绑定. 有括号的由内向外依次分析,要想理解数组声明的含义,最好的办法是从数组的名字开始按照由内向外的顺序阅读
3.6.2 访问数组元素
与标准类型vector和string一样,数组的元素可以使用范围for语句,在使用数组下标的时候size_t , 这是一种机器相关的无符号类型,他设计的足够大能够容纳内存中任意对象的大小.可以对数组使用范围for语句.
检查下标志
小标志不越界对于数组来说是程序员的的责任
3.6.3指针和数组
指针和数组有着非常紧密的联系,使用数组的时候编译器一般会转换成指针.
取地址可以作用于任何的对象,包括数组元素,对数组的某个元素取地址也可以得到指向某个数组元素的指针.比如 string *p = &nums[0]
数组还有一个特性没就是用到数组名字的地方编译器都会自动的将其替换为一个指向数组的指针.
这隐含着一层意思,当使用数组作为一个auto变量的初始值时候推断得到的类型是指针而非数组.
例如
int ia[] = {1,2,3};
auto ia2(ia); // 实际执行的是auto ia2(&ia);ia2是指针,指向数组的第一个元素
但是但是,当使用decltype时候上述转换不会发生
decltype(ia) ia3 = { 1,2,3};
int * p = &ia[0];
ia3 = p ;//错误,不能将数组指针赋值给数组对象
指针也是迭代器:
vector 和 string 的迭代器支持的运算数组指针也都支持. 不过取尾后迭代器就要用到数组特殊的性质,要知道尾元素最后的哪个元素的下一个位置使可以取到的, 不要不相信,这是真的:
int * e = &arr[0];//指向为尾元素的下一个地址,只有这种情况下使用
对于这个e是不能接引用或者递增操作的.
标准库函数的begin和end
标准库提供了begin和end函数用于返回数组的初始位置和最后元素下一位置:
int *b = begin(ia);
int *e = end(ia);
指针运算
包括接引用,递增,比较,与整数相加,两个指针相减,用在指着和迭代器上的意义完全一致.
两个指针的运算结果类型是 ptrdiff_t 与size_t一样,是带符号的类型.
两个空指针允许相减,结果是0
解引用和指针运算的交互
允许接引用指针的运算的结果,前提是指针运算结果指向合法的对象.
3.6.4 C 风格的字符串
尽管c++支持c风格的字符串,但是最好不要使用他们,因为会造成程序的诸多漏洞.
C标准库的string函数
定义在cstring中
函数 | 描述 |
---|---|
strlen(p) | 返回p的长度,不包括空字符 |
strcmp(p1,p2) | 比较p1和p2相等性,相等返回0,大于返回正值,小于返回负值 |
strcat(p1,p2) | 将p2附加到p1之后,返回p1. |
strcp(p1,p2) | 将p2拷贝给p1,返回p1 |
比较字符串
直接实际比较的是指向数组元素首元素的指针
而要用strcmp函数
目标字符串的大小由调用者指定
最好不要用c风格的写法,比如你要在某个字符串后边接上某个字符串,你得这样写:
strcpy(largeStr , cal);
strcpy(largeStr , ” ” );
strcat(largeStr , ca2);
这样的代码充满风险.
3.6.5 与旧代码的接口
混用string 对象和c风格的字符串
string s(“Hello world”);
加法中c风格像string风格的转换
string中由.c_str() 可以将string转化为字符数组
使用数组初始化vector对象
虽然不允许使用一个vector对象初始化数组,也不允许使用一个数组初始化另一个数组,但是允许使用一个数组初始化一个vector
例如:
int arr[] = {0,1,2,3,4,5};
vector ivec(begin(arr), end(arr));
建议尽量使用标准库类型而不是数组
3.7 多维数组
多维数组的初始化
多维数组的每一行分别用中括号扩了起来
int ia[3][4] = {
{0,1,2,3},
{4,5,6,7},
{8,9,10,11}
}
但并非所有的元素都要包含进来可以:
int ia[3][4] = {{0}, {4}, {8}};//显示的初始化每一行的首元素
int ix[3][4] = {0,3,6,9};//显示的初始化第一行
多维数组的下标引用
如果表达式含有的下标运算符的数量和数组的维度一样多,该表达式的结果将是给定类型的元素.反之如果给定下标运算符的数量比数组的唯独要小,索引的是一个内层数组.
int (&row)[4]= ia[1];//把row绑定到ia第二个4元素的数组上.
使用for范围语句处理多维数组
例如遍历ia
int cnt =0;
for(auto &row:ia) //这里用引用是因为当遇到数组名的时候auto会将其推断成指针,所以必须加引用
for(auto &col:row)//这里加引用是要改变当前的值,不改可以直接不加引用.
col = cnt++;
也就是说,要使用范围for语句处理多维数组,除了最内层循环以外,其他所有控制变量都应该是引用.
指针和多维数组
当程序使用多维数组的名字时,也会自动将其转换成指向首元素的指针
注意写法,由内向外取解释含义
int * ip[4]; //整形指针的数组
int (*ip)[4];// 指向含有四个整数的数组
我们使用auto来避免总是在名字前面加一个指针,auto类型可以推断指向非内层数组首元素的指针.例如
for(auto p = begin(ia) ; p!= end(ia); ++p)
for(auto q =begin(*p) ; q!= end(*p) ; ++q)
cout <<*q<<’ ‘;
cout <
小结:
string 和 vector是两种重要的标准库类型,string 是一个可变长的字符序列,vector对象是一组同类型对象的容器
迭代器允许对容器进行间接访问.可以通过迭代器访问对象元素和在对象间进行移动.
数组和指向数组元素的指针是一种比较低层次的实现vector和string类似的功能.一般来说应该优先考虑标准库提供的功能.