第2章介绍的内置类型是由C++语言直接定义的。这些类型,比如数字和字符,体现了大多数计算机硬件本身具备的能力。标准库定义了另外一组具有更高级性质的类型,它们尚未直接实现到计算机硬件中。
3.1 命名空间的using声明
- 使用using声明就无须专门的前缀(::)也能使用所需的名字了。
- 头文件不应包含using声明:这是因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件里有某个using声明,那么每个使用了该头文件的文件就都会有这个声明。对于某些程序来说,反而可能产生始料未及的名字冲突。
3.2 标准库类型string
标准库类型string表示可变长的字符序列,使用string类型必须包含string头文件。
- 定义和初始化string对象:如果提供了一个字符串字面值,则该字面值中除了最后那个空字符外其他所有的字符都被拷贝到新创建的string对象中去。
1 2 3 4 5 6 |
string s1; //默认初始化,s1是一个空串 string s2(s1); //s2是s1的副本 string s2 = s1; //等价于s2(s1),s2是s1的副本 string s3("value"); //s3是字面值“value"的副本,除了字面值最后的那个空字符外! string s3 = "value"; //等价于s3("value"),s3是字面值"value"的副本 string s4(n, 'c'); //把s4初始化为由连续n个字符c组成的串 |
使用等号(=)初始化一个变量,实际上执行的是拷贝初始化(copy initialization),编译器把等号右侧的初始值拷贝到新创建的对象中去。与之相反,不使用等号,则执行的是直接初始化(direct initialization)。
- string对象上的操作
- 使用getline读取一整行:getline函数的参数是一个输入流和一个string对象,函数从给定的输入流中读入内容,直到遇到换行符为止(注意换行符也被读进来了),然后把所读内容存入那个string对象中去(注意不存换行符)。getline只要一遇到换行符就结束读取操作并返回结果。
123456789//getline读取一整行int main(){string line;//每次读入一整行,直至到达文件末尾while (getline(cin, string))cout << line << endl;return 0;}
NOTE:触发getline函数返回的那个换行符实际上被丢弃了,得到的string对象中并不包含该换行符。 - string::size_type类型:string类及其他大多数标准库类型都定义了集中配套的类型。这些配套类型体现了标准库类型与机器无关的特性,类型size::type即其中一种。在C++11新标准中,允许编译器通过auto或者decltype来推断变量的类型:
1auto len = line.size(); //len的类型是string::size_type
由于size函数返回的是一个无符号整型数,因此切记,如果在表达式中混用了带带符号数和无符号数将可能产生意想不到的结果。例如,假设n是一个具有负值的int,则表达式s.size() < n 的判断结果几乎肯定是true。这是因为负值n会自动地转换成一个比较大的无符号值。
Tip:如果一条表达式中已经有了size()函数就不要再使用int了,这样可以避免混用int和unsigned可能带来的问题。
- 字面值和string对象相加:当把string对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符(+)的两侧对象至少有一个是string:
12345string s4 = s1 + ","; 正确:把一个string对象个一个字面值相加string s5 = "Hello" + ","; //错误:两个运算对象都不是string//正确:每个加法运算符都有运算对象是stringstring s6 = s1 + "," + "world";string s7 = "Hello" + "," + s2; //错误:不能把字面值直接相加
s6的初始化形式,它的工作机理和连续输入连续输出是一样的,可以用如下的形式分组:
1string s6 = (s1 + ",") + "world";
WARNING:因为某些历史原因,也为了与C兼容,所以C++语言中的字符串字面值并不是标准库string的对象。切记,字符串字面值与string是不同的类型。 - 处理string对象中的字符:在cctype头文件中定义了一组标准库函数处理这部分工作:
- 处理每个字符,使用基于范围for(range for)语句:
1234string str("some string");//每行输出str中的一个字符for (auto c : str) //对于str中的每个字符cout << c << endl; //输出当前字符,后面紧跟一个换行符
此例中,使用auto关键字让编译器来决定变量c的类型,这里c的类型是char。 - 只处理一部分字符:要想访问string对象中的单个字符有两种方式:下标和迭代器。下标运算([ ])接收的输入参数是string::size_type类型的值。
3.3 标准库类型vector
C++语言既有类模板,也有函数模板,其中vector是一个类模板。模板本身不是类或函数,相反可以将模板看做为编译器生成类或函数编写的一份说明。编译器根据模板创建类或函数的过程称为实例化(instantiation)。
早期版本的C++标准中如果vector的元素还是vector(或其他模板类型),则其定义形式必须在外层的vector对象的右尖括号和其元素类型之间添加一个空格,如vector<vector<int> >,新标准已经无此限制。
- 定义和初始化vector对象:
C++11新标准还提供了另外一种为vector对象的元素赋初值的方法,即列表初始化:
1 2 |
vector<string> v1 = { "a", "an", "the"}; //列表初始化 vector v2 = ( "a", "an", "the"); //错误 |
对vector对象来说,直接初始化的方式适用于三种情况——初始值已知且数量较少、初始值是另一个vector对象的副本、所有元素的初始值都一样。
- 向vector对象中添加元素:运用vector的成员函数push_back向其中添加元素:
1234vector <int> v2; //空vector对象for (int i =0; i != 100; ++i)v2.push_back(i); //依次把整数值放到v2尾端//循环结束后v2有100个元素,值从0到99
如果循环体内部包含有向vector对象添加元素的语句,则不能使用范围for循环,具体原因将在5.4.3详细解释。范围for语句体内不应改变其所遍历序列的大小。 - 其他vector操作
1 2 |
vector<int>::size_type //正确 vector::size_type //错误 |
WARNING:vector对象(以及string对象)的下标运算符可用于访问已存在的元素,而不能用于添加元素。
3.4 迭代器介绍
所有标准容器都可以使用迭代器,但是其中只有少数几种才同时支持下标运算。严格来说,string对象不属于容器类型,但是string支持很多与容器类型类似的操作。
和指针不一样的是,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员。比如,这些类型都有名为begin和end的成员,其中begin成员负责返回第一个元素的迭代器,end成员负责返回指向容器“尾元素的下一个位置(one past the end)”的迭代器。
Note:如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器。
- 迭代器运算符
- 迭代器类型:iterator和const_iterator来表示迭代器的类型,const_iterator和常亮指针差不多,能读取但不能修改它所指的元素。相反,iterator的对象可读可写。如果vector对象或string对象是一个常亮,只能使用const_iterator;如果vector对象或string对象不是常量,那么既能使用iterator也能使用const_iterator.
- begin和end运算符:如果对象是常量,begin和end返回const_iterator;如果对象不是常量,返回iterator:
1234vector<int> v;const vector<int> cv;auto it1 = v.begin(); //it1的类型是vector<int>::iteratorauto it2 = cv.begin(); //it2的类型是vector<int>::const_iterator
为了便于专门得到const_iterator类型的返回值,C++11新标准引入了两个新函数,分别是cbegin和cend:
1auto it3 = v.cbegin(); //it3的类型是vector<int>::const_iterator - 结合解引用和成员访问操作:C++语言定义了箭头运算符(->),箭头运算符把解引用和成员访问两个操作结合在一起,也就是说,it->mem和(*it).mem表达的意思相同。
- 某些对vector对象的操作会使迭代器失效:虽然vector对象可以动态地增长,但是也会有一些副作用。已知的一个限制是不能再范围for循环体中向vector对象添加元素。另一个限制是任何一种可能改变vector对象容量的操作,比如push_back,都会使该vector对象的迭代器失效。
WARING:谨记,但凡使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。
- 迭代器运算:
1 2 3 4 |
//计算得到最接近vi中间元素的一个迭代器 auto mid = vi.begin() + vi.size()/2; if (it < mid) //处理vi前半部分的元素 |
所谓迭代器相减的距离,指的是右侧的迭代器向前移动多少位置就能追上左侧迭代器,其类型名为difference_type的带符号整型数。string和vector都定义了difference_type,因为这个距离可正可负,所以defference_type是带符号类型的。
3.5 数组
数组是一种类似标准库类型vector的数据结构。与取相似的地方是,数组也是存放类型相同的对象的容器,不同点是数组的大小确定不变,不能随意向数组中增加元素。在使用数组下标的时候,通常将其定义为size_t类型。
Tip:如果不清楚元素的确切个数,请使用vector。
- 定义和初始化内置数组:字符数组有一种额外的初始化形式,我们可以用字符串字面值对此数组初始化。当使用这种方式时,一定要注意字符串字面值的结尾处还有一个空字符:
1234char a1[] = {'C', '+', '+' }; //列表初始化,没有空字符char a2[] = {'C', '+', '+', '\0'}; //列表初始化,含有显示的空字符char a3[] = "C++"; //自动添加表示字符串结束的空字符const char a4[6] = "Daniel"; //错误:没有空间可存放空字符 - 不允许拷贝和赋值:不能讲数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值。
- 理解复杂的数组声明:
1234int *ptrs[10]; //ptrs是含有10个整型指针的数组int &refs[10] = /* ? */; //错误,不存在引用的数组int (*Parray)[10] = &arr; //Parray指向一个含有10个整数的数组int (&arrRef)[10] = arr; //arrRef引用一个含有10个整数的数组 - 标准库函数begin和end:C++11新标准引入了两个名为begin和end的函数。这两个函数与容器中的两个同名成员功能类似,不过数组毕竟不是类类型,因此这两个函数是成员函数:
123int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9,}; //ia是一个含有10个整数的数组int *beg = begin(ia); //指向ia首元素的指针int *last = end(ia); //指向ia尾元素的下一个位置的指针 - 和迭代器一样,两个指针相减的结果是它们之间的距离。参与运算的两个指针必须指向同一个数组中的元素:
1auto n = end(arr) - begin(arr); //n的值是5,也就是arr中元素的数量
两个指针相减的结果的类型是一种名为ptrdiff_t的标准库类型,和size_t一样,ptrdiff_t也是一种定义在cstddef头文件中的机器相关的类型。
WARNING:内置的下标运算符所用的索引值不是无符号类型,这一点与vector和string不一样。
- C风格字符串:C标准库string函数,C语言标准库提供的一组函数,这些函数可用于操作C风格字符串,它们定义在cstring头文件中:
- c_str:如果程序某处需要一个C风格字符串,无法直接用string对象来替代它,string专门提供了一个名为c_str的成员函数:
12char *str = s; //错误:不能用string对象初始化char*const char *str = s.c_str(); //正确 - 使用数组初始化vector对象:
123int int_arr[] = {0, 1, 2, 3, 4, 5};//ivec有6个元素,分别是int_arr中对应元素的副本vector<int> ivec(begin(int_arr), end(int_arr));3.6多维数组
- 使用范围for语句处理多维数组:
123for (const auto &row : ia ) //对于外层数组的每一个元素for (auto col : row ) //对于内层数组的每一个元素cout << col << endl;
这个循环中没有任何写操作,可是我们还是将外层循环的控制变量声明成了引用类型,这是为了避免数组被自动转换成指针。
NOTE:要使用范围for语句处理多维数组,除了最内层的循环体,其他所有循环的控制变量都应该是引用类型。
最后一点,范围for处理多维数组,内层必须引用类型吗?矛盾了
除了最内层的循环体,其他都是引用类型,我的疏忽把最内层写成了最外层,抱歉,已更正。