标准库类型
本章重要的两个:
string
和vector
,以及配套的迭代器。
3.1 命名空间的using声明
using声明具有如下的形式:
using namespace::name;
一旦声明了上述语句,就可以直接访问命名空间中的名字:
#include<iostream>
//using声明,当我们使用名字cin时,从命名空间std中获取它
using std::cin;
int main()
{
int i;
cin >> i;//正确:cin和std::cin含义相同
cout << i;//错误:没有对应的using声明,必须使用完整的名字
std::cout << i;//正确:显式地从std中使用cout
return 0;
}
用下面的using可以使用std里面的所有名字:
using namespace std;
- 头文件不应包含using声明
3.2 标准库类型string
- 初始化string对象的方式
string s1; //默认初始化,si是一个空串
string s2(s1); //s2是s1的副本
string s2=s1; //等价于s2(sl>,s2是s1的副本
string s3("value"); //S3是字面值"value"的副本,除了字面值最后的那个空字符外
string s3="value"; //等价于s3("value"),S3是字面值"value"的副本
string s4(n,'c'); //把s4初始化为由连续n个字符c组成的串
直接初始化和拷贝初始化
- 如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去。
- 如果不使用等号,则执行的是直接初始化。
使用getline读取一整行
cin
遇到空格和换行就会输入结束。要保留空格,需要使用
getline
。getline
的返回值是什么?s.size()
的返回值返回的是一个
string::size_type
类型的值,与机器无关,尽管我们不太清楚string::size_type
类型的细节,但有一点是肯定的:它是一个无符号类型的值。当把string对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符(+)的两侧的运算对象至少有一个是strinq:
string s4 = s1+",";//正确:把一个string对象和一个字面值相加
string s5 = "hello" + ",";//错误:两个运算对象都不是string
//正确:每个加法运算符都有一个运算对象是string
string s6 = s1 + "," + "world"; //从左到右加,所以是正确的
string s7 = "hello"+","+s2;//错误:不能把字面值直接相加
字符串字面值与string是不同的类型
建议:使用
C++
版本的C标准库头文件
C++
程序应该使用名为的cname头文件而不使用name.h的形式,标准库中的名字总能在命名空间std中找到。
- 范围for, 语法形式:
for (declaration : expression)
statement
string str("somestring");
//每行输出str中的一个字符
for(auto c : str)//对于str中的每个字符
cout << c << endl;//输出当前字符,后面紧跟一个换行符
- 如果想要改变string对象中字符的值,必须把循环变量定义成引用类型
strings("Hello World!!!");
//转换成大写形式.
for(auto &c : s)//对于s中的每个字符(注意:c是引用)
c = toupper(c);//c是一个引用,因此賦值语句将改变s中字符的值
cout << s << endl;
- 访问string对象中的单个字符有两种方式:一种是使用下标,另外一种是使用迭代器
3.3 标准库类型vector
vector是模板而非类型,由vector生成的类型必须包含vector中元素的类型,例如
vector<int>
初始化vector对象的方法
vector<T> v1
vector<T> v2(v1)
vector<T> v2 = v1
vector<T> v3(n, val)
vector<T> v4(n)
vector<T> v5{a, b, c ...}
vector<T> v5={a,b,c...}; // c++ 11
- 值初始化
vector<int> ivec(10);//10个元素,每个都初始化为0
vector<string> svec(10);//10个元素,每个都是空string对象
使用如下语句将会输出10个0:
vector<int> iv(10);
for (int i : iv)
cout << i << " ";
- 列表初始值、元素数量
vector<int>v1(10);//v1有10个元素,每个的值都是0
vector<int>v2{10};//v2有1个元素,该元素的值是10
vector<int>v3(10,1);//v3有10个元素,每个的值都是1
vector<int>v4{10,1};//v4有2个元素,值分别是10和1
如果用的是圆括号,可以说提供的值是用来构造(construct)vector对象的。
如果用的是花括号,可以表述成我们想列表初始化(listinitialize)该vector对象。
如果初始化时使用了花括号的形式但是提供的值又不能用来列表初始化,就要考虑用这样的值来构造vector对象了:
vector<string> v5("hi"};//列表初始化:v5有一个元素
vector<string> v6("hi"); //错误:不能使用字符串字面值构建vector对象
vector<string> v7{10}; //v7有10个默认初始化的元素
vector<string> v8{10,"hi"};//v8有10个值为"hi"的元素
- vector效率问题
vector对象能高效增长,不需要在定义的时候指定大小,这样做的性能可能更差,只有所有元素都是一样的时候才会有性能上的提升。
- 确保下标合法的一种有效手段就是尽可能使用范围for语句
3.4迭代器介绍
//b表示v的第一个元素,e表示v尾元素的下一位置
auto b = v.begin(), e = v.end();//b和e的类型相同
end成员则负责返回指向容器(或string对象)“尾元素的下一位置,也就是说,该迭代器指示的是容器的一个本不存在的”尾后”.
如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器。
- 使用迭代器和!=的习惯
只有string和vector等一些标准库类型有下标运算符,而并非全都如此。与之类似,所有标准库容器的迭代器都定义了==和!=,但是它们中的大多数都没有定义<运算符。因此,只要我们养成使用迭代器和!=的习惯,就不用太在意用的到底是哪种容器类型。
- 迭代器类型
那些拥有迭代器的标准库类型使用iterator
和const_iterator
来表示迭代器的类型:
vector<int>::iterator it; //it能读写vector<int>的元素
string::iterator it2; //it2能读写string对象中的字符
vector<int>::const_iterator it3;//it3只能读元素,不能写元素
string::const_iterator it4; //it4只能读字符,不能写字符
- 箭头运算符(->)
箭头运算符把解引用和成员访问两个操作结合在一起,也就是说,it->mem
和(*it).mem
表达的意思相同。
//依次输出text的每一行直至遇到第一个空白行为止
for(auto it = text.cbegin();it != text.cend() && !it->empty(); ++it)
cout << *it << endl;
但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素.
迭代器的算术运算
vector<int> iv;
for (int i = 0; i != 10; ++i) {
iv.push_back(i);
}
//计算得到最接近iv中间元素的一个迭代器
auto mid = iv.begin() + iv.size() / 2;
cout << endl << *mid << endl;
3.5数组
如果不清楚元素的确切个数,请使用vector.
初始化问题
int a3[5] = { 0, 1, 2 };
for (int i = 0; i != 5; ++i) {
cout << a3[i] << " ";
}
输出:0 1 2 0 0
没有给初始值的,被默认初始化为0了,与变量的默认初始化不同,变量是函数内只定义不赋值,是处于未初始化状态的,直接输出 会报错。
这样会将所以元素都初始化为0:
int aa[10] = {0};
但是这样并不会将所有元素都会初始化为1,只能将第一个元素初始化为1,其余初始化为0:
int aa[10] = {1};
- 字符数组的特殊性
char a1[] = {'C','+','+'};//列表初始化,没有空字符
char a2[]={'C','+','+',' '};//列表初始化,含有显式的空字符
char a3[]="C++";//自动添加表示字符串结束的空字符
const char a4[6] = "Daniel"; // 错误:没有空间存放空字符
用字符串字面值对此类数组初始化时,一定要注意字符串字面值的结尾处还有一个空字符,这个空字符也会像字符串的其他字符一样被拷贝到字符数组中去.
- 就数组而言,由内向外阅读要比从右向左好多了
int *ptrs[10]; // ptrs是含有10个整型指针的数组
int &refs[10] = /* ? */; // 错误:不存在引用的数组
int (*Parray)[10] = &arr; // Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr; // arrRef引用一个含有10个整数的数组
由内向外的顺序可帮助我们更好地理解Parray的含义:首先是圆括号括起来的部分,*Parray意味着Parray是个指针,接下来观察右边,可知道Parray是个指向大小为10的数组的指针,最后观察左边,知道数组中的元素是int。这样最终的含义就明白无误了,Parray是一个指针,它指向一个int数组,数组中包含10个元素。
与vector和string—样,当需要遍历数组的所有元素时,最好的办法也是使用范围for语句。
对数组的元素使用取地址符就能得到指向该元素的指针
string nums[] = {"one", "two", "three"};//数组的元素是string对象
string *p = &nums[0];//p指向nmns的第一个元素
- 标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求
int *p = &ia[2];//p指向索引为2的元素
int j = p[1]; //p[1]等价于*(p+1),就是ia[3]表示的那个元素
int k = p[-2]; //p[-2]是ia[0]表示的那个元素
尽管
C++
支持C风格字符串,但在C++
程序中最好还是不要使用它们.C风格字符串的函数
strlen(p); // 返回P的长度,空字符不计算在内
strcmp(p1, P2); // 比较pl和p2的相等性。如果pl==p2,返回0;如果p1>p2,
// 返回一个正值;如果p1<p2,返回一个负值
strcat(p1, P2); // 将p2附加到p1之后,返回p1
strcpy(p1, P2); // 将p2拷贝给p1,返回p1
它们定义在cstring头文件中,cstring是C语言头文件string.h的C++版本。
传入此类函数的指针必须指向以空字符作为结束的数组,切记:
char ca[]={'C','+', '+'}; //不以空字符结束
cout << strlen(ca) << endl; //严重错误:ca没有以空字符结束
ca虽然也是一个字符数组但它不是以空字符作为结束的,因此上述程序将产生未定义的结果。strlen函数将有可能沿着ca在内存中的位置不断向前寻找,直到遇到空字符才停下来。
此时我的vs输出的是15!!这样并不会报错,还会给一个不正确的值,是非常危险的!
- 比较字符串
注意C和标准库的区别:
string s1 = "Astringexample";
string s2 = "Adifferentstring";
if(s1 < s2) //false:s2小于s1
const char ca1[] = "Astringexample";
const char ca2[] = "Adifferentstring";
if(ca1 < ca2) //未定义的:试图比较两个无关地址
当使用数组的时候其实真正用的是指向数组首元素的指针
对大多数应用来说,使用标准库string要比使用C风格字符串更安全、更高效。
允许使用以空字符结束的字符数组来初始化string对象或为string对象赋
char ca[] = { 'c', '+', '+', ' ' };
string ss(ca);
如果不是以空字符结尾,那么ss会被初始化为ca在内存中从头开始直到遇到空字符的位置。
- 用string对象直接初始化指向字符的指针
const char *str = s.c_str (); // 正确
注意必须是const,函数c_str()
返回值就是const的,函数的返回结果是一个指针,该指针指向一个以空字符结束的字符数组。
- 现代的C++程序应当尽量使用vector和迭代器,避免使用内置数组和指针;应该尽量使用string,避免使用C风格的基于数组的字符串
3.6多维数组
- 要使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。
for(const auto &row : ia) //对于外层数组的每一个元素
for(auto col : row) //对于内层数组的每一个元素
cout << col << endl;
不然,编译器初始化row时会自动将这些数组形式的元素(和其他类型的数组一样)转换成指向该数组内首元素的指针。
- 区别
int *ip[4]; //整型指针的数组
int (*ip)[4]; //指向含有4个整数的数组