目录
3.1 命名空间的using声明
3.2 标准库类型string
3.2.1 定义和初始化string对象
- 如果 使用等号 初始化一个变量,实际上执行的是 拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去。如果 不使用等号,则执行的是 直接初始化。
string s5 = "hiya"; // 拷贝初始化
string s6("hiya") ; // 直接初始化
string s7(10, 'c'); // 直接初始化,s7的内容是cccccccccc
3.2.2 string对象上的操作
- 在执行读取操作时,string 对象会自动忽略开头的空白(空格符、换行符、制表符等)并从第一个真正的字符开始读取,直到遇见下一处空白为止。
- 当希望能在输出的字符串中保留输入的空白字符时,可以使用 getline 函数,getline 函数可以读取一整行字符。该函数只要遇到换行符就结束读取并返回结果(换行符也被读进来了),然后把所读内容存入到 string 对象中(不保存换行符)。如果输入的开始就是一个换行符,则得到空 string。
string line;
getline( cin, line );
- size 函数返回 string 对象的长度(即 string 对象中字符的个数),返回值其实是 string::size_type 类型,这是一种无符号类型。要使用 size_type,必须先指定它是由哪种类型定义的。
- 当把 string 对象和字符字面值及字符串字面值混合在一条语句中使用时,必须确保每个加法运算符两侧的运算对象中至少有一个是 string。
string s4 = s1 + ", "; // ok: 把一个string对象和一个字面值相加
string s5 = "hello" + ", "; // error: 两个运算对象都不是string
string s6 = s1 + ", " + "world"; // ok: 每个加法运算符都有一个运算对象是string
string s7 = "hello" + "," + s2; // error:不能把字面值直接相加
- 如果n是一个具有负值的int,则表达式s.size() < n的判断结果几乎肯定是true。因为负数n会自动转换成一个比较大的无符号值。
3.2.3 处理string对象中的字符
- 如果想在范围 for 语句中改变 string 对象中字符的值,必须把循环变量定义成引用类型。
- 下标运算符接收的输入参数是 string::size_type 类型的值,表示要访问字符的位置,返回值是该位置上字符的引用。
3.3 标准库类型vector
3.3.1 定义和初始化vector对象
3.3.2 向vector对象中添加元素
- 范围 for 语句体内不应该改变其所遍历序列的大小
3.3.3 其他vector操作
3.4 迭代器的介绍
3.4.1 使用迭代器
- 如果容器为空,则 begin 和 end 返回的是同一个迭代器,都是尾后迭代器。
- 定义了迭代器的类型都拥有 begin 和 end 两个成员函数。begin 函数返回指向第一个元素的迭代器,end 函数返回指向容器 尾元素的下一位置(one past the end) 的迭代器。
auto b = ivec.begin(), e = ivec.end(); // b和e的类型相同
- 迭代器运算符
- 在 for 或者其他循环语句的判断条件中,最好使用 != 而不是 <。所有标准库容器的迭代器都定义了 == 和 !=,但是只有其中少数同时定义了 < 运算符。(泛型编程)
- 如果 vector 或 string 对象是常量,则只能使用 const_iterator 迭代器,该迭代器只能读元素,不能修改元素。
- C++11新增了 cbegin 和 cend 函数,不论 vector 或 string 对象是否为常量,都返回 const_iterator 迭代器。
auto it3 = v.cbegin(); // it3的类型是vector<int>::const_iterator
- 解引用和成员访问操作
(*it).empty(); //注意必须加括号
it->empty(); //两者等价
- 使用了迭代器的循环体,不能向迭代器所属的容器添加元素
3.4.2 迭代器的运算
- 使用迭代器完成二分查找
// text必须是有序的
// beg和end表示我们搜索的范围
// beg指向搜索范围内的第一个元素、end指向居元素的下一位置、mid指向中间的那个元素
auto beg = text.begin(), end = text.end();
auto mid = text.begin() + (end - beg)/2; // 初始状态下的中间点
// 当还有元素尚未检查并且还没有找到sought时执行循环
whi1e (mid != end && *mid != sought)
{
if (sought < *mid) // 我们要找的元素在前半部分吗?
end = mid; // 如果是,调整搜索范围使得忽略掉后半部分
e1se // 我们要找的元素在后半部分
beg = mid + 1; // 在mid之后寻找
mid = beg + (end - beg)/2; // 新的中间点
}
3.5 数组
数组类似 vector,也是存放类型相同的对象的容器,但数组的大小确定不变,不能随意向数组中添加元素。
3.5.1 定义和初始化内置数组
- 数组中元素的个数也属于数组类型的一部分, 编译的时候维度应该是己知的,即维度必须是一个常量表达式。
unsigned cnt = 42; // 不是常量表达式
constexpr unsigned sz = 42; // 常量表达式
int arr[10]; // 含有10个整数的数组
int *parr[sz]; // 含有42个整型指针的数组
string bad[cnt]; // error:cnt不是常量表达式
string strs[get_size()]; // 当get_size是constexpr时正确,否则错误
- 定义数组的时候必须指定数组的类型,不允许用 auto 关键字由初始值列表推断类型。
- 可以用字符串字面值初始化字符数组,但字符串字面值结尾处的空字符也会一起被拷贝到字符数组中。
char a1[] = {'C', '+', '+'}; // 列表初始化,没有空字符
char a2[] = {'C', '+', '+', '\0'}; // 列表初始化,含有显式的空字符
char a3[] = "C++"; // 自动添加表示字符串结束的空字符
const char a4[6] = "Daniel"; // error:没有空间可存放空字符!
- 数组不允许拷贝和赋值
int a[] = {O , 1 , 2}; // 含有3个整数的数组
int a2[] = a; // error:不允许使用一个数组初始化另一个数组
a2 = a; // error:不能把一个数组直接赋值给另一个数组
- 从数组的名字开始由内向外阅读有助于理解复杂数组声明的含义。
int *ptrs[10]; // ptrs是含有10个整型指针的数组
int &refs[10] = /* ? */; // error:不存在引用的数组
int (*Parray)[10] = &arr; // Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr; // arrRef引用一个含有10个整数的数组
int *(&arry)[10] = ptrs; //arry是数组引用,该数组含有10个int型指针
3.5.3 指针和数组
- 当使用数组作为一个 auto 变量的初始值时,推断得到的类型是指针而非数组。但 decltype 关键字不会发生这种转换,直接返回数组类型。
int ia[] = {0,1,2,3,4,5,6,7,8,9}; // ia是一个含有10个整数的数纽
auto ia2(ia); // ia2是一个整型指针,指向ia的第一个元素
ia2 = 42; // error:ia2是一个指针,不能用int值给指针赋值
auto ia2(&ia[0]); // 显然ia2的类型是int*
// ia3是一个含有10个整数的数组
decltype(ia) ia3 = {0,1,2,3,4,5,6,7,8,9};
ia3 = p; // error:不能用整型指针给数组赋值
ia3[4] = i; // ok:把i的值赋给ia3的一个元素
- C++11在头文件 iterator 中定义了两个名为 begin 和 end 的函数,功能与容器中的两个同名成员函数类似,参数是一个数组。
- 表达式 * (ia+4) 计算 ia 前进4个元素后的新地址,解引用该结果指针的效果等价于表达式 ia[4]。
int ia[] = {0, 2, 4, 6, 8}; // 含有5个整数的数组
int last = *(ia + 4); // ok:把last初始化成8,也就是ia[4]的值
- 标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求。(这是vector和string与内置数组的区别)
3.5.5 与旧代码的接口
- 任何出现字符串字面值的地方都可以用以空字符结束的字符数组来代替
- 不能用 string 对象直接初始化指向字符的指针。为了实现该功能,string 提供了一个名为 c_str 的成员函数,返回 const char* 类型的指针,指向一个以空字符结束的字符数组,数组的数据和 string 对象一样。
string s("Hello World"); // s的内容是Hello World
char *str = s; // error: 不能用string对象初始化char*
const char *str = s.c_str();// ok
- 可以使用数组来初始化 vector 对象,但是需要指明要拷贝区域的首元素地址和尾后地址。
int int_arr[] = {0, 1, 2, 3, 4, 5};
// ivec有6个元素,分别是int_arr中对应元素的副本
vector<int> ivec(begin(int_arr), end(int_arr));
3.6 多维数组
3.6.1 多维数组的初始化
int ia[3][4] =
{ // 三个元素,每个元素都是大小为3的数组
{0, 1, 2, 3}, // 第1行的初始值
{4, 5, 6, 7}, // 第2行的初始值
{8, 9, 10, 11} // 第3行的初始值
};
// 没有标识每行的花括号,与之前的初始化语句是等价的
int ib[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
// 显式地初始化每行的首元素
int ic[3][4] = {{ 0 }, { 4 }, { 8 }};
// 显式地初始化第1行,其他元素执行值初始化
int id[3][4] = {0, 3, 6, 9};
- 使用范围 for 语句处理多维数组时,为了避免数组被自动转换成指针,语句中的外层循环控制变量必须声明成引用类型。
for (const auto &row : ia) // 对于外层数组的每一个元素
for (auto col : row) // 对于内层数组的每一个元素
cout << col << endl;
- 如果 row 不是引用类型,编译器初始化 row 时会自动将数组形式的元素转换成指向该数组内首元素的指针,这样得到的 row 就是 int* 类型,而之后的内层循环则试图在一个 int* 内遍历,程序将无法通过编译。
for (auto row : ia)
for (auto col : row)
- 使用范围 for 语句处理多维数组时,除了最内层的循环,其他所有外层循环的控制变量都应该定义成引用类型。
- 声明指向数组类型的指针时,必须带有圆括号。
int *ip[4]; // 整型指针的数组
int (*ip)[4]; // 指向含有4个整数的数组
- C++11新标准,使用 auto 和 decltype 能省略复杂的指针定义。
// 输出ia中每个元素的值,每个内层数组各占一行
// p指向含有4个整数的数组
for (auto p = ia; p != ia + 3; ++p)
{
// q指向4个整数数组的首元素,也就是说,q指向一个整数
for (auto q = *p; q != *p + 4; ++q)
cout << *q << ' ';
cout << endl;
}
- 使用标准库函数 begin 和 end 也能实现同样的功能,而且看起来更简洁一些。
// p指向ia的第一个数组
for (auto p = begin(ia); p != end(ia); ++p)
{
// q指向内层数组的首元素
for (auto q = begin(*p); q != end(*p); ++q)
cout << *q << ' '; // 输出q所指的整数值
cout << endl;
}