C++ Primer 第3章 字符串、向量和数组
- 3.1 命名空间的using声明
- 一、每个名字都需要独立的using声明
- 二、头文件不应包含using声明
- 三、一点注意事项
- 3.2 标准库类型string
- 3.2.1 定义和初始化string对象
- 一、直接初始化和拷贝初始化
- 3.2.2 string对象上的操作
- 一、读写string对象
- 二、读取未知数量的string对象
- 三、使用getline读取一整行
- 四、string的empty和size操作
- 五、string::size_type类型
- 六、比较string对象
- 七、为string对象赋值
- 八、两个string对象相加
- 九、字面值和string对象相加
- 十、练习
- 3.2.3 处理string对象中的字符
- 一、处理每个字符使用基于范围的for语句
- 二、使用范围for语句改变字符串中的字符
- 三、只处理一部分字符
- 四、使用下标执行迭代
- 五、使用下标执行随机访问
- 3.3 标准库vector
- 3.3.1 定义和初始化vector对象
- 一、列表初始化vector对象
- 二、创建指定数量的元素
- 三、值初始化
- 四、列表初始值还是元素数量?
- 3.3.2 向vector对象中添加元素
- 一、向vector对象添加元素蕴含的编程假定
- 3.3.3 其他vector操作
- 一、计算vector内对象的索引
- 二、不能用下标形式添加元素
- 3.4 迭代器介绍
- 3.4.1 使用迭代器
- 一、迭代器运算符
- 二、将迭代器从一个元素移动到另外一个元素
- 三、迭代器类型
- 四、begin和end运算符
- 五、结合解引用和成员访问操作
- 六、某些对vector对象的操作会使迭代器失效
- 七、练习
- 3.4.2 迭代器运算
- 一、迭代器的算术运算
- 二、使用迭代器运算
- 3.5 数组
- 3.5.1 定义和初始化内置数组
- 一、显式初始化数组元素
- 二、字符数组的特殊性
- 三、不允许拷贝和赋值
- 四、理解复杂的数组声明
- 3.5.2 访问数组元素
- 一、检查下标的值
- 3.5.3 指针和数组
- 一、指针也是迭代器
- 二、标准库函数begin和end
- 三、指针运算
- 四、解引用和指针运算的交互
- 五、下标和指针
- 3.5.4 C风格字符串
- 一、C标准库String函数
- 二、比较字符串
- 三、目标字符串的大小由调用者指定
- 3.5.5 与旧代码的接口
- 一、混用string对象和C风格字符串
- 二、使用数组初始化vector对象
- 3.6 多维数组
- 一、多维数组的初始化
- 二、多维数组的下标引用
- 三、使用范围for语句处理多维数组
- 四、指针和多维数组
- 五、类型别名简化多维数组的指针
- 六、练习
- 小结
3.1 命名空间的using声明
using naspace::name;
using std::cin;
using std::cout;
using std::endl;
一、每个名字都需要独立的using声明
每个using声明引入命名空间中的一个成员。
二、头文件不应包含using声明
位于头文件的代码一般来说不应该使用using声明。这是因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件里有某个using声明,那么每个使用了该头文件的文件都会有这个声明。对于某些程序来说,由于不经意间包含了一些名字,反而可能产生始料未及的名字冲突。
三、一点注意事项
3.2 标准库类型string
标准库类型string表示可变长的字符序列,使用string类型必须首先包含string头文件,string定义在命名空间std中。
# include <string>
using std::string;
3.2.1 定义和初始化string对象
一、直接初始化和拷贝初始化
如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去。如果不使用等号,则执行的是直接初始化。
拷贝初始化:创建临时对象,复制构造函数。直接初始化效率更高
string s5 = "hiya"; // 拷贝初始化
string s6("hiya"); // 直接初始化
string s7(10, 'c'); // 直接初始化
string s8 = string(10, 'c'); // 拷贝初始化,需要显式地创建一个临时对象用于拷贝
3.2.2 string对象上的操作
一、读写string对象
#include <iostream>
#include <string>using std::cin;
using std::cout;
using std::endl;
using std::string;int main()
{string s1, s2;cin >> s1 >>s2;; // 在执行读取操作时,string对象会自动忽略开头的空白(空格符、换行符、制表符)并从第一个真正的字符开始读起,直到遇见下一处空白为止cout << s1 << s2 << endl;return 0;
}
二、读取未知数量的string对象
#include <iostream>
#include <string>using std::cin;
using std::cout;
using std::endl;
using std::string;int main()
{string word;while (cin >> word) {cout << word << endl;}return 0;
}
三、使用getline读取一整行
getline函数的参数是一个输入流和一个string对象,函数从给定的输入流中读入内容,直到遇到换行符为止(换行符也被读进来了),然后把所读的内容存入到那个string对象中去(注意不存换行符)。getline只要一遇到换行符就结束读取操作并返回结果,哪怕输入的一开始就是换行符也是如此。如果输入真的一开始就是换行符,那么所得到的结果是个空string。getling也会返回它的流参数。
#include <iostream>
#include <string>using std::cin;
using std::cout;
using std::endl;
using std::string;int main()
{string line;// getline不忽略开头和结尾的空白while (getline(cin, line)) {cout << line << endl;}return 0;
}
四、string的empty和size操作
empty函数根据string对象是否为空返回一个对应的布尔值。
string line;
while (getline(cin, line)) {if (!line.empty()) {cout << line << endl;}
}
size函数返回string对象的长度(即string对象中字符的个数)。
string line;
while (getline(cin, line)) {if (line.size() > 80) {cout << line << endl;}
}
五、string::size_type类型
size函数返回的是一个string::size_type类型的值。
string类及其他大多数标准库类型都定义了几种配套的类型。这些配套类型体现了标准库类型与机器无关的特性,类型size_type即使其中的一种。它是一个无符号类型的值,而且能足够存放下任何string对象的大小。
注意:由于size函数返回的是一个无符号整数类型,因此切记,不要在表达式中混用带符号数和无符号数。
六、比较string对象
相等性运算符(==和!=)分别检验两个string对象相等或不相等,string对象相等意味着他们的长度相同而且所包含的字符也全都相同。关系运算符<、<=、>、>=分别检验一个string对象是否小于、小于等于、大于、大于等于另外一个string对象。
七、为string对象赋值
string st1(10, 'c'), st2;
st1 = st2; // 赋值:用st2的副本替换st1的内容
八、两个string对象相加
string s1 = "hello, ", s2 = "world\n";
string s3 = s1 + s2; // s3的内容是hello, world\n
s1 += s2; // 等价于s1 = s1 + s2
九、字面值和string对象相加
标准库允许把字符字面值和字符串字面值转换成string对象。
string s1 = "hello", s2 = "world";
// 当把string对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符的两侧的运算对象至少有一个是string
string s3 = s1 + ", " + s2 + '\n'; // 正确
string s4 = s1 + ", "; // 正确
string s5 = "hello" + ", "; // 错误,两个运算对象都不是string,双引号的字符串实际是地址,两个地址相加无法转换为string对象
string s6 = s1 + ", " + "world"; // 正确
string s7 = "hello" + ", " + s2; // 错误
cout << *"Hello world" << endl; // H
十、练习
// 当cin函数输入错误的时候,cin里面有个函数可以自动检测到输入错误,若想继续输入便要清除掉这个错误
cin.clear();
// 将输入的错误字符清理掉
cin.sync();
3.2.3 处理string对象中的字符
C++标准库中除了定义C++语言特有的功能外,也兼容了C语言的标准库。C语言的头文件形如name.h,C++则将这些文件命名为cname。特别的,在cname的头文件中定义的名字从属于命名空间std。
一、处理每个字符使用基于范围的for语句
范围for语句:遍历给定序列中的每个元素并对序列中的每个值执行某种操作。
expression部分是一个对象,用于表示一个序列
declaration部分负责定义一个变量,该变量将被用于访问序列中的基础元素
每次迭代,declaration部分的变量会被初始化为expression部分的下一个元素值
for (declaration : expression)statement
string str("some string");
for (auto c : str) {cout << c << endl;
}string s("Hello World!!!");
decltype(s.size()) punct_cnt = 0;
for (auto c : s) {if (ispunct(c)) {++punct_cnt;}
}
cout << punct_cnt << " punctuation characters in " << s << endl;
二、使用范围for语句改变字符串中的字符
string s("Hello World!!!");
for (auto &c : s) {c = toupper(c);
}
cout << s << endl; // HELLO WORLD!!!
三、只处理一部分字符
下标运算符([ ])接收的输入参数是string::size_type类型的值,这个参数表示要访问的字符的位置;返回值是该位置上字符的引用。
if (!s.empty())cout << s[0] << endl;string s("some string");
if (!s.empty())s[0] = toupper(s[0]);
四、使用下标执行迭代
逻辑与运算符(&&):如果参与运算的两个运算对象都为真,则逻辑与结果为真;否则结果为假。C++语言规定只有当左侧运算对象为真时才会检查右侧运算对象的情况。
五、使用下标执行随机访问
#include <iostream>
#include <string>using std::cin;
using std::cout;
using std::endl;
using std::string;int main()
{const string hexdigits = "0123456789ABCDEF";cout << "Enter a series of numbers between 0 and 15"<< " separated by spaces. Hit ENTER when finished: "<< endl;string result;string::size_type n;while (cin >> n) {if (n < hexdigits.size()) {result += hexdigits[n];}}cout << "Your hex number is: " << result << endl;return 0;
}
3.3 标准库vector
标准库类型vector表示对象的集合,其中所有对象的类型都相同。
#include <vector>
using std::vector
C++语言既有类模板,也有函数模板,其中vector是一个类模板。模板本身不是类或函数,可以将模板看作为编译器生成类或函数编写的一份说明。编译器根据模板创建类或函数的过程称为实例化,当使用模板时,需要指出编译器应把类或函数实例化成何种类型。
vector<int> ivec;
vector<Sales_item> Sales_vec;
vector<vector<string>> file;
3.3.1 定义和初始化vector对象
vector<string> svec; // 默认初始化,svec不含任何元素vector<int> ivec; // 初始化状态为空
vector<int> ivec2(ivec); // 把ivec的元素拷贝给ivec2
vector<int> ivec3 = ivec; // 把ivec的元素拷贝给ivec3
vector<string> svec(ivec2); // 错误
一、列表初始化vector对象
vector<string> articles = {"a", "an", "the"}; // 列表初始化
vector<string> v1{"a", "an", "the"}; // 列表初始化
vector<string> v2("a", "an", "the"); // 错误
二、创建指定数量的元素
vector<int> ivec(10, -1); // 10个int类型的元素,每个都被初始化为-1
vector<string> svec(10, "hi!"); // 10个string类型的元素,每个都被初始化为"hi!"
三、值初始化
vector<int> ivec(10); // 10个int类型的元素,每个都被初始化为0
vector<string> svec(10); // 10个string类型的元素,每个都是空string对象
四、列表初始值还是元素数量?
vector<int> v1(10); // v1有10个元素,每个值都是0
vector<int> v2{10}; // v2有1个元素,该元素的值10vector<int> v3(10, 1); // v3有10个元素,每个值都是1
vector<int> v4{10, 1}; // v4有2个元素,值分别是10和1
// 如果用的是圆括号,可以说提供的值是用来构造vector对象的
// 如果用的是花括号,可以表述成我们想列表初始化该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”的元素
3.3.2 向vector对象中添加元素
push_back负责把一个值当成vector对象的尾元素“压到”vector对象的“尾端”。
vector<int> v2;
for(int i = 0; i != 100; ++i)v2.push_back(i); // 循环结束后v2有100个元素,值从0到99string word;
vector<string> text;
while(cin >> word) text.push_back(word);
一、向vector对象添加元素蕴含的编程假定
如果循环体内部包含有向vector对象添加元素的语句,则不能使用范围for循环。(范围for语句体内不应该改变其所遍历序列的大小)
3.3.3 其他vector操作
vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};
for (auto &i : v)i *= i;
for (auto i: v)cout << i << " ";
cout << endl;
empty检查vector对象是否包含元素然后返回一个布尔值。
size返回vector对象中元素的个数,返回值的类型是vector定义的size_type类型。
vector<int>::size_type // 正确
vector::size_type // 错误
一、计算vector内对象的索引
#include <iostream>
#include <string>
#include <vector>using std::cin;
using std::cout;
using std::endl;
using std::string;
using std::vector;int main()
{vector<unsigned> scores(11, 0);unsigned grade;while (cin >> grade) {if (grade <= 100)++scores[grade / 10];}for (auto s : scores)cout << s << " ";return 0;
}
二、不能用下标形式添加元素
vector对象(以及string对象)的下标运算符可用于访问已存在的元素,而不能用于添加元素。只能对确知已存在的元素执行下标操作。
vector<int> ivec;
for (decltype(ivec.size()) ix = 0; ix != 10; ++ix)ivec[ix] = ix; // 错误,ivec是一个空vector,根本不包含任何元素,当然不能通过下标去访问任何元素vector<int> ivec;
cout << ivec[0]; // 错误
3.4 迭代器介绍
迭代器也提供了对对象的间接访问。使用迭代器可以访问某个元素,迭代器也能从一个元素移动到另外一个元素。迭代器有有效和无效之分。有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一位置;其他所有情况都属于无效。
3.4.1 使用迭代器
auto b = v.begin(), e = v.end();
// b和e类型相同,b表示v的第一个元素,e表示v尾元素的下一位置(尾后迭代器)
// 如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器
一、迭代器运算符
执行解引用的迭代器必须合法并确实指示着某个元素。
string s("sone string");
if (s.begin() != s.end()) {auto it = s.begin();*it = toupper(*it);
}
二、将迭代器从一个元素移动到另外一个元素
迭代器的递增是将迭代器“向前移动一个位置”。
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)*it = toupper(*it);
三、迭代器类型
迭代器类型:iterator(可读可写)和const_iterator(可读不可写)
vector<int>::iterator it;
string::iterator it2;vector<int>::const_iterator it3;
string::const_iterator it4;
四、begin和end运算符
begin和end返回的具体类型由对象是否是常量决定,如果对象时常量,begin和end返回const_iterator;如果对象不是常量,返回iterator。
vector<int> v;
const vector<int> cv;
auto it1 = v.begin(); // it1的类型是vector<int>::iterator
auto it2 = cv.begin(); // it2的类型是vector<int>::const_iterator
auto it3 = v.cbegin(); // it3的类型是vector<int>::const_iterator
五、结合解引用和成员访问操作
// 对于一个由字符串组成的vector对象,令it是该vector对象的迭代器
(*it).empty(); // 解引用it,然后调用结果对象的empty成员
*it.empty(); // 错误:试图访问it的名为empty的成员,但it是个迭代器,没有empty成员
it->empty(); // 箭头运算符把解引用和成员访问两个操作结合在一起for (auto it = text.cbegin(); it != text.cend() && !it->empty(); ++it)cout << *it << endl;
六、某些对vector对象的操作会使迭代器失效
限制:
(1)不能在for循环中向vector对象添加元素
(2)任何一种可能改变vector对象容量的操作,比如push_back,都会使vector对象的迭代器失效
// 检查非空
s.begin() != s.end()
// 范围for语句等价为
for (auto beg = v.begin(), end = v.end(); beg != end; ++beg) {auto &r = *beg;
}
七、练习
// 随机数生成
srand((unsigned)time(NULL)); // 生成随机数种子
int n = rand() % 1000; // 1000以内的随机数
3.4.2 迭代器运算
一、迭代器的算术运算
可以令迭代器和一个整数值相加(或相减),其返回值是向前(或向后)移动了若干个位置的迭代器。
对于string或vector的迭代器来说,除了判断是否相等,还能使用关系运算符(<、<=、>、>=)对其进行比较。参与比较的两个迭代器必须合法而且指向的是同一个容器的元素(或者尾元素的下一位置)。
只要两个迭代器指向的是同一个容器中的元素或者尾元素的下一位置,就能将其相减,所得的结果是两个迭代器的距离,其类型是名为difference_type的带符号整型数。
不支持两迭代器相加。
二、使用迭代器运算
// text必须是有序的
auto beg = text.begin(), end = text.end();
auto mid = text.begin() + (end - beg) / 2;
while (mid != end && *mid != sought) {if (sought < *mid)end = mid;else beg = mid + 1;mid = beg + (end - beg) / 2;
}
3.5 数组
数组与vector:
相同点:数组也是存放类型相同的对象的容器,这些对象本身没有名字,需要通过其所在位置访问
不同点:数组的大小确定不变,不能随意向数组中增加元素。数组的维度在定义时已经确定,如果我们想更改数组的长度,只能创建一个更大的新数组,然后把原数组的所有元素赋值到新数组中区。无法像vector那样使用size函数直接获取数组的维度。如果是字符数组,可以调用strlen函数得到字符串的长度;如果是其他数组,只能使用sizeof(array)/sizeof(array[0]) 的方式计算数组的维度。
3.5.1 定义和初始化内置数组
数组是一种复合类型。数组的声明形如a[d],其中a是数组的名字,d是数组的维度。维度说明了数组中元素的个数,因此必须大于0。数组中元素的个数也属于数组类型的一部分,编译的时候维度应该是已知的(维度必须是一个常量表达式)。默认情况下,数组的元素被默认初始化。
定义数组的时候必须制定指定数组的类型,不允许用auto关键字由初始化值的列表推断类型。数组的元素应为对象,不存在引用的数组。
一、显式初始化数组元素
const unsigned sz = 3;
int ia1[sz] = {0, 1, 2}; // 含有3个元素的数组,元素值分别是0, 1, 2
int a2[] = {0, 1, 2}; // 维度是3的数组
int a3[5] = {0, 1, 2}; // 等价于a3[] = {0, 1, 2, 0, 0}
int a5[2] = {0, 1, 2}; // 错误:初始值过多
unsigned buf_size = 1024;
int ia[buf_size]; // 错误,buf_size不是常量
int ia[4 * 7 -14]; // 正确
二、字符数组的特殊性
char a1[] = {'C', '+', '+'}; // 没有空字符,维度是3
char a2[] = {'C', '+', '+', '\0'}; // 含有显式的空字符,维度是4
char a3[] = "C++"; // 自动添加表示字符串结束的空字符
const char a4[6] = "Daniel"; // 错误:没有空间存放空字符
三、不允许拷贝和赋值
int a[] = {0, 1, 2};
int a2[] = a; // 错误,不允许使用一个数组初始化另一个数组
a2 = a; // 错误:不能把一个数组直接赋值给另一个数组
四、理解复杂的数组声明
默认情况下,类型修饰符从右向左依次绑定。
int *ptrs[10]; // ptrs是含有10个整型指针的数组
int &refs[10] = ...; // 错误:不存在引用的数组
int (*Parray)[10] = &arr; // Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr; // arrRef引用一个含有10个整数的数组
int *(&arry)[10] = ptrs; // array是数组引用,该数组含有10个指针
3.5.2 访问数组元素
在使用数组下标时,通常将其定义为size_t类型。size_t是一种机器相关的无符号类型,它被设计得足够大以便能表示内存中任意对象的大小(在cstddef头文件中)
unsigned scores[11] = {};
unsigned grade;
while (cin >> grade) {if (grade <= 100)++scores[grade/10];
}for (auto i : scores)cout << i << " ";
一、检查下标的值
3.5.3 指针和数组
string nums = {"one", "two", "three"};
string *p = &nums[0]; // p指向nums的第一个元素
// 在用到数组名字的地方,编译器会自动将其替换为一个指向数组首元素的指针
string *p2 = nums; // 等价于p2 = &nums[0]// 使用数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组;而decltype不会发生转换
int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto ia2(ia); // ia2是一个整型指针,指向ia的第一个元素
decltype(ia) ia3; // ia3是一个数组
一、指针也是迭代器
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *p = arr; // p指向arr的第一个元素
++p; // p指向arr[1]
int *e = &arr[10]; // 指向arr尾元素的下一位置的指针,不能执行解引用和递增
for (int *b = arr; b != e; ++b)cout << *b << endl;
二、标准库函数begin和end
int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int *beg = begin(ia); // begin函数返回指向ia首元素的指针
int *last = end(ia); // end函数返回指向ia尾元素下一位置的指针
// 这两个函数定义在iterator头文件中,命名空间std中
三、指针运算
constexpr size_t sz = 5;
int arr[sz] = {1, 2, 3, 4, 5};
int *ip = arr; // 等价于int *ip = &arr[0]
int *ip2 = ip + 4; // ip2指向arr[4]
// 给指针加上一个整数,得到的新指针仍需指向同一数组的其他元素,或者指向同一数组尾元素的下一位置
int *p = arr + sz; // p指向arr尾元素的下一位置,不要解引用!
int *p2 = arr + 10; // 错误:arr只有5个元素,p2的值未定义
两个指针相减的结果是它们之间的距离,参与运算的两个指针必须指向同一数组当中的元素,两个指针相减的结果的类型是一种名为ptrdiff_t的标准库类型,定义在cstddef头文件中。
只要两个指针指向同一个数组的元素,或者指向该数组的尾元素的下一位置,就能利用关系运算符对其进行比较。如果两个指针分别指向不相关的对象,则不能比较它们。
如果p是空指针,允许给p加上或减去一个值为0的整型常量表达式。两个空指针也允许彼此相减,结果为0。
四、解引用和指针运算的交互
int ia[] = {0, 2, 4, 6, 8};
int last = *(ia + 4); // 把last初始化为ia[4]
last = *ia + 4; // ia[0] + 4
五、下标和指针
对数组执行下标运算其实是对指向数组元素的指针执行下标运算
int ia[] = {0, 2, 4, 6, 8};
int i = ia[2]; // ia转换成指向数组首元素的指针,ia[2]得到(ia+2)所指的元素
int *p = ia;
i = *(p + 2); // 等价于ia[2]int *p = &ia[2];
int j = p[1]; // p[1]等价于*(p+1),即ia[3]
int k = p[-2]; // ia[0]
虽然标准库类型string和vector也能执行下标运算,但是数组与它们相比还是有所不同。标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求。
3.5.4 C风格字符串
C风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法。按此习惯书写的字符串放在字符数组中并以空字符结束。
一、C标准库String函数
头文件cstring
传入此类函数的指针必须指向以空字符作为结束的数组
char ca[] = {'C', '+', '+'};
cout << strlen(ca) << endl; // 严重错误:ca没有以空字符结束
二、比较字符串
const char ca1[] = "A string example";
const char ca2[] = "A different string";
if (ca1 < ca2) // 未定义的:试图比较两个无关地址
if (strcmp(ca1, ca2) < 0) // 和两个string对象的比较s1<s2效果一样
三、目标字符串的大小由调用者指定
strcpy(largeStr, ca1); // 把cal拷贝给largeStr
strcat(largeStr, " "); // 在largeStr的末尾加上一个空格
strcat(largeStr, ca2); // 把ca2连接到largeStr后面
3.5.5 与旧代码的接口
一、混用string对象和C风格字符串
1、允许使用字符串字面值来初始化string对象
2、允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值
3、在string对象的加法运算中允许使用以空字符结束的字符数组作为其中一个运算对象(不能两个运算对象都是);在string对象的复合赋值运算中允许使用以空字符结束的字符数组作为右侧的运算对象
string s("Hello World");
char *str = s; // 错误:不能用string对象初始化char*
const char *str = s.c_str();
// c_str()函数的返回值是一个C风格的字符串,函数的返回结果是一个指针,该指针指向一个空字符结束的字符数组,而这个数组所存的数据恰好与那个string对象的一样
// str指向s的内存单元,并未将s的内容进行拷贝
二、使用数组初始化vector对象
int int_arr[] = {0, 1, 2, 3, 4, 5};
vector<int> ivec(begin(int_arr), end(int_arr)); // ivec有6个元素,分别是int_arr中对应元素的副本
vector<int> subVec(int_arr+1, int_arr+4); // 拷贝3个元素int_arr[1]、int_arr[2]、int_arr[3]
3.6 多维数组
严格来说,C++语言中没有多维数组,通常所说的多维数组其实是数组的数组。
int ia[3][4]; // 大小为3的数组,每个元素是含有4个整数的数组
int arr[10][20][30] = {0}; // 大小为10的数组,它的每个元素都是大小为20的数组,这些数组的元素是含有30个整数的数组
一、多维数组的初始化
int ia[3][4] = {{0, 1, 2, 3},{4, 5, 6, 7},{8, 9, 10, 11}
};
int ia[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
int ia[3][4] = {{0}, {4}, {8}}; // 显式地初始化每行的首元素
二、多维数组的下标引用
如果表达式含有的下标运算符数量和数组的维度一样多,该表达式的结果将是给定类型的元素;反之,如果表达式含有的下标运算符数量比数组的维度小,则表达式的结果将是给定索引处的一个内层数组。
ia[2][3] = arr[0][0][0];
int (&row)[4] = ia[1]; // 把row绑定到ia的第二个4元素数组上
constexpr size_t rowCnt = 3, colCnt = 4;
int ia[rowCnt][colCnt];
for (size_t i = 0; i != rowCnt; ++i) {for (size_t j = 0; j != colCnt; ++j) {ia[i][j] = i * colCnt + j;}
}
三、使用范围for语句处理多维数组
size_t cnt = 0;
for (auto &row : ia) {for (auto &col : row) {col = cnt;++cnt;}
}// 多维数组使用范围for循环时,只能使用引用
for (auto row : ia)for (auto col : row)
// 错误:第一个循环遍历ia的所有元素,这些元素实际上是大小为4的数组,因为row不是引用类型,所以编译器初始化row时会自动将这些数组形式的元素转换成指向数组内首元素的指针,这样得到的row的类型就是int*,显然不合法
四、指针和多维数组
// 因为多维数组实际上是数组的数组,所以由多维数组名转换得来的指针实际上是指向第一个内层数组的指针
int ia[3][4];
int (*p)[4] = ia; // p指向含有4个整数的数组
p = &ia[2]; // p指向ia的尾元素
for (auto p = ia; p != ia + 3; ++p) {for (auto q = *p; q != *p + 4; ++q) { // *p是一个含有4个整数的数组cout << *q << ' ';}
}for (auto p = begin(ia); p != end(ia); p++) {for (auto q = begin(*p); q != end(*p); q++) {cout << *q << ' ';}
}
五、类型别名简化多维数组的指针
using int_array = int[4];
tydefef int int_array[4];
for (int_array *p = ia; p != ia + 3; ++p) {for (int *q = *p; q != *p + 4; ++q) {cout << *q << ' ';}
}
六、练习
#include <iostream>
#include <iterator>using std::cin;
using std::cout;
using std::endl;
using std::begin;
using std::end;int main()
{int ia[3][4] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};for (int (&p)[4] : ia) {for (int &q : p) {cout << q << " ";}cout << endl;}for (int i = 0; i < 3; i++) {for (int j = 0; j < 4; j++) {cout << ia[i][j] << " ";}cout << endl;}for (int (*p)[4] = ia; p != ia + 3; p++) {for (int *q = *p; q != *p + 4; q++) {cout << *q << " ";}cout << endl;}return 0;
}
小结
string对象和vector是两种最重要的标准库类型。string对象是一个可变长的字符序列,vector对象是一组同类型对象的容器。
迭代器允许对容器中的对象进行间接访问,对于string对象和vector对象来说,可以通过迭代器访问元素或者在元素间移动。
数组和指向数组元素的指针在一个较低的层次上实现了与标准库类型string和vector类似的功能。一般来说,应该优先选用标准库提供的类型,之后再考虑C++语言内置的低层的替代品数组或指针