3.1 命名空间using声明
1.有了using声明就无须专门的前缀(形如命名空间::)也能使用所需的名字了。using声明具有如下的形式:
using namespace::name;
一旦声明了上述语句,就可以直接访问命名空间中的名字。
3.2 标准库类型string
1.标准库类型string表示可变长的字符序列,使用string类型必须首先包含string头文件。作为标准库的一部分,string定义在命名空间std中。
2.
<span style="color: rgb(64, 70, 76); font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, STHeiti, 'Microsoft Yahei', sans-serif; font-size: 14px; line-height: 22.399999618530273px; background-color: rgb(253, 254, 249);">读取未知数量的string对象</span><pre name="code" class="cpp"><pre name="code" class="cpp">#include <iostream>
#include <string>
using std::cin;
using std::cout;
using std::endl;;
using std::string;<pre name="code" class="cpp"> int main()
{
string str;
// 逐个读取单词,直到文件末尾
while (cin >> str) { // 逐行读取,保留输入的空格:while (getline(str))
cout << str << endl;
}
return 0;
}
std::string s;
(1)cin
读入string
,从非空白字符读到第一个空白符之前
(2)getline(cin,s)
读入换行符,但不保存换行符,读入失败返回0
(3)s.size()
返回的值为string::size_type
类型,这是一个无符号整数,所以注意不要混用size_type
与有符号整数
(4)字符串字面值不能相加,string与自己、字面值可以相加(相连)
当把string对象和字符字面值,字符串字面值混合在一个语句中时候,必须保证每个加法运算符(+)的两侧必须有一个是
string对象
string a(10,'c');
string b=a+"bb"+"aaa";//正确 a+字面值“bb”是string对象
string c="bb"+"aaa"+a;//错误 两个字面值bb,aaa不能直接相加
3.如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化(copy initialization),编译器把等号右侧的初始值拷贝到新创建的对象中去。与之相反,如果不使用等号,则执行的是直接初始化(direct initialization)。
4.
5.有时我们希望能在最终得到的字符串中保留输入时的空白符,这时应该用getline函数代替原来的>>运算符。getline函数的参数是一个输入流和一个string对象,函数从给定的输入流中读入内容,直到遇到换行符为止(注意换行符也被读进来了),然后把所读的内容存入到那个string对象中去(注意不存换行符)。getline只要一遇到换行符就结束读取操作并返回结果,哪怕输入的一开始就是换行符也是如此。如果输入真的一开始就是换行符,那么所得的结果是个空string。
6.相等性运算符(==和!=)分别检验两个string对象相等或不相等,string对象相等意味着它们的长度相同而且所包含的字符也全都相同。关系运算符<、<=、>、>=分别检验一个string对象是否小于、小于等于、大于、大于等于另外一个string对象。上述这些运算符都依照(大小写敏感的)字典顺序:
1.如果两个string对象的长度不同,而且较短string对象的每个字符都与较长string对象对应位置上的字符相同,就说较短string对象小于较长string对象。
2.如果两个string对象在某些对应的位置上不一致,则string对象比较的结果其实是string对象中第一对相异字符比较的结果。
7.
8.如果想对string对象中的每个字符做点儿什么操作,目前最好的办法是使用C++11新标准提供的一种语句:范围for(range for)语句。这种语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作,其语法形式是:
for (declaration : expression)
statement
其中,expression部分是一个对象,用于表示一个序列。declaration部分负责定义一个变量,该变量将被用于访问序列中的基础元素。每次迭代,declaration部分的变量会被初始化为expression部分的下一个元素值。
for
循环遍历std::string
std::string s;
for(auto c : s)cout<<c;//只访问s中的每个字符
for(auto &c : s)c=tolower(c);//访问与修改s中的每个字符
//auto可以用char代替
3.3 标准库类型vector
1.标准库类型vector表示对象的集合,其中所有对象的类型都相同。集合中的每个对象都有一个与之对应的索引,索引用于访问对象。因为vector"容纳着"其他对象,所以它也常被称作容器(container)。
std::vector v;
(1)不存在包含引用的vector
(2)初始元素值列表
vector<string> v1{"a","b","c"};//正确
vector<string> v2("a","b","c");//错误
- 1
- 2
(3)初始元素数量
vector<int> v1(10);//初始10个元素,默认为0
vector<int> v2(10,1);//初始10个元素,默认为1
//{}会尽可能尝试元素值列表,否则构造
vector<int> v3{10,1};//10,1
vector<string> v4{10,"a"};//a,a,a,a,a,a,a,a,a,a
2.
3.push_back负责把一个值当成vector对象的尾元素"压到(push)"vector对象的"尾端(back)"。例如:
vector<int> v2; // 空vector对象
for (int i = 0; i != 100; ++i)
v2.push_back(i); // 依次把整数值放到v2尾端
// 循环结束后v2有100个元素,值从0到99
4.
3.4 迭代器介绍
1.和指针不一样的是,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员。比如,这些类型都拥有名为begin和end的成员,其中begin成员负责返回指向第一个元素(或第一个字符)的迭代器。
// 由编译器决定b和e的类型;参见2.5.2节(第68页)
// b表示v的第一个元素,e表示v尾元素的下一位置
auto b = v.begin(), e = v.end(); //b 和e的类型相同
end成员则负责返回指向容器(或string对象)"尾元素的下一位置(one past the end)"的迭代器,也就是说,该迭代器指示的是容器的一个本不存在的"尾后(off the end)"元素。这样的迭代器没什么实际含义,仅是个标记而已,表示我们已经处理完了容器中的所有元素。end成员返回的迭代器常被称作尾后迭代器(off-the-end iterator)或者简称为尾迭代器(end iterator)。特殊情况下如果容器为空,则begin和end返回的是同一个迭代器。
如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器。
2.
3.const_iterator和常量指针差不多,能读取但不能修改它所指的元素值。相反,iterator的对象可读可写。如果vector对象或string对象是一个常量,只能使用const_iterator;如果vector对象或string对象不是常量,那么既能使用iterator也能使用const_iterator。
(1)const_iterator
std::vector<int>::const_iterator it
只能读,不能修改
(2)const
与容器
vector<int> v;
const vector<int>cv;
auto it=cv.begin()//const_iterator
auto it2=v.cbegin();//cbegin()与cend():const_iterator
4.begin和end返回的具体类型由对象是否是常量决定,如果对象是常量,begin和end返回const_iterator;如果对象不是常量,返回iterator。
cbegin和cend也分别返回指示容器第一个元素或最后元素下一位置的迭代器。有所不同的是,不论vector对象(或string对象)本身是否是常量,返回值都是const_iterator。
5.
3.5 数组
1.数组是一种复合类型(参见2.3节,第50页)。数组的声明形如a[d],其中a是数组的名字,d是数组的维度。维度说明了数组中元素的个数,因此必须大于0。数组中元素的个数也属于数组类型的一部分,编译的时候维度应该是已知的。也就是说,维度必须是一个常量表达式。unsigned n=1024;int a[n];//错误,n不是常量表达式
2.定义数组的时候必须指定数组的类型,不允许用auto关键字由初始值的列表推断类型。另外和vector一样,数组的元素应为对象,因此不存在引用的数组。
3.可以对数组的元素进行列表初始化,此时允许忽略数组的维度。如果在声明时没有指明维度,编译器会根据初始值的数量计算并推测出来;相反,如果指明了维度,那么初始值的总数量不应该超出指定的大小。如果维度比提供的初始值数量大,则用提供的初始值初始化靠前的元素,剩下的元素被初始化成默认值。
4.
注意:if(s1<s2)
比较的是指针s1与s2;s1+s2
是将两个地址相加
5.c_str函数的返回值是一个C风格的字符串。也就是说,函数的返回结果是一个指针,该指针指向一个以空字符结束的字符数组,而这个数组所存的数据恰好与那个string对象的一样。结果指针的类型是const char*,从而确保我们不会改变字符数组的内容。
s.c_str()
:返回C风格字符串的字符指针const char *
,不保证一直有效
vector<int> v(begin(a),end(a))
:C数组初始化vector
int arr[]={0,1,2,3,4,5};
vector<int> vec(begin(arr),end(arr));
6.使用指针和数组很容易出错。一部分原因是概念上的问题:指针常用于底层操作,因此容易引发一些与烦琐细节有关的错误。其他问题则源于语法错误,特别是声明指针时的语法错误。
现代的C++程序应当尽量使用vector和迭代器,避免使用内置数组和指针;应该尽量使用string,避免使用C风格的基于数组的字符串。
(1)字符数组最后\0
(2)复杂数组声明
int *p[10];//含有10个整数指针的数组
int (*p2)[10]=&arr;//p2指向一个含有10个整数的数组
int (&Arr)[10]=arr;//Arr引用一个含有10个整数的数组
int *(&ARR)[10]=p;//ARR引用一个含有10个整数指针的数组
- 1
- 2
- 3
- 4
(3)范围for
语句:for(auto a : arr)
(4)指针与数组
int arr1={0,1,2,3};
auto arr2(arr1);//arr2:整数指针
int *p;
decltype(arr1) arr3={2,3,4,5};//arr3是一个数组
arr3=p;//错误!不能用整数指针给数组赋值!
- 1
- 2
- 3
- 4
- 5
- 6
数组的头尾指针:
#include <iterator>
int a[]={1,2,3,4};
int *s=begin(a),*t=end(a);
- 1
- 2
- 3
注意:尾指针永远不能做解引用与迭代
(5)数组的大小是size_t
类型,为无符号类型,数组的指针作差是ptrdiff_t
类型,为有符号类型,两者均需使用头文件cstddef
for(size_t i=0;i!=n;i++)a[i]++;
- 1
(6)数组的内置下标运算符是有符号类型,所以可以实现如下操作
int a[]={1,2,3,4,5,6,7,8}
int *b=&a[4];
int k=b[-2];//k==a[2]
3.6 多维数组
1.严格来说,C++语言中没有多维数组,通常所说的多维数组其实是数组的数组。
2.要使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。
int a[10][10];
for(auto &row : a)
for(auto &col : row)
//可修改
for(auto &row : a)
for(auto col : row)
//只读
for(auto row : a)
for(auto col : row)
//错误!此时row的类型是int*!
using int_array int[10];
for(int_array *i=a; i!=a+10; i++)
for(int_array (*j)[10]=*i; j!=*i + 10; j++)
cout<<*j<<endl;//*j遍历了a中的每一个元素