3.1 命名空间的using声明
每个名字都需要独立的using声明
头文件不应该包含using声明:因为头文件中的内容会拷贝到所有引用它的文件中去,如果头文件里面有某个using声明,那么每个使用了该头文件的文件就都会有这个声明,可能引发名字冲突。
3.2 标准库类型string
3.2.1 定义和初始化string对象
//初始化string对象的方式
string s1;
string s2(s1);
string s2=s1;
string s3("value");
string s3="value";
string s4(n,‘c’);
直接初始化和拷贝初始化
string s5 ="hiya" ; //拷贝初始化,把等号右侧的初始值拷贝到新创建的对象中
string s6("hiya"); //直接初始化
string s7(10,'c'); //这种情况只能直接初始化。初始化的值有多个就要使用直接初始化。
string s8 = string(10,'c'); //拷贝初始化,需要显示地创建一个临时对象用于拷贝
3.2.2 string对象上的操作
读写string对象
int main(){
string s;
cin>>s; //输入" Hellow World!"
cout<< s <<endl; //输出 "Hellow"
string s1,s2;
cin>>s1>>s2; //输入" Hellow World!"
cout<<s1<<s2<<endl; //输出 "HellowWorld!"
return 0;
}
读取未知数量的string对象
int main(){
string word;
while (cin>>word){ //反复读取,直至到达文件末尾
cout<< word << endl; //逐个输出单词,每个单词后面紧跟一个换行
}
return 0;
}
使用getline读取一整行
int main(){
string line;
while(getline(cin,line)) //触发getline函数返回的是换行符,得到string对象中不包含该换
cout<<line<<endl; //换行符
return 0;
}
string的empty和size操作
while(getline(cin,line))
if(!line.empty())
cout<< line <<endl;
while(getline(cin,line))
if(line.size()>80)
cout<< line <<endl;
比较string对象
每个字符串字母都一样的情况下,短的<长的
字母不一样的情况下,比较两字符串第一个不同字母的大小
字面值和string对象相加
string s1 = "hello", s2="world";
string s3 = s1+","+s2 +'\n';
//当把string对象和字符字面值混在一条语句中使用时,必须确保每个假发运算符的两侧的运算对象至少有一个string
string s4 = s1+","; //正确:把一个string对象和一个字面值相加
string s5 = "hellow" + ","; //错误:两个运算对象都不是string
string s6 = s1+ "," + "world"; //正确:s1+","会重新变成一个string对象
string s7 ="hellow" + "," +s2; //错误,前两个不能直接相加
3.2.3 处理string对象中的字符
使用基于范围的for语句,处理每个字符
string s("Hello World!!!");
//punct_cnt的类型和s.size的返回类型一样
decltype(s.size()) punct_cnt =0;
for(auto c:s)
if(ispunct(c))
++punct_cnt;
cout<< punct_cnt << s << endl;
使用范围for语句改变字符串中的字符
string s("Hello World!!!");
//转换成大写模式
for (auto &c:s) //必须使用引用类型才能改变原字符串中的值
c=toupper(c);
cout<< s <<endl;
3.3 标准库类型vector
模板本身不是类和函数,相反可以将模板看作为编译器生成类或函数编写的一份说明。编译器根据模板创建类或函数的过程称为实例化。
vector<int> ivec;
vector<Sales_item> Sales_vec;
vector<vector<string>> file; //该向量的元素是vector对象
3.3.1 定义和初始化vector对象
列表初始化vector对象
列表初始化方式的几个特殊情况:一.使用拷贝初始化时(=),只能提供一个初始值
二.如果提供的是一个类内初始值,则只能使用拷贝初始化,或者花括号初始化
三.如果提供的是初始元素值的列表,则只能把初始值都放在花括号里进行列表初始化。
vector<string> v1{"a","an","the"}; //列表初始化
vector<string> v2("a","an","the"); //错误
3.3.2 向vector对象中添加元素
string word;
vector<string> text;
while(cin >> word) {
text.push_back(word);
}
3.3.3 其他vector操作
//关于下标必须明确一点:只能对确知存在的元素执行下标操作
vector<int> ivec; //空对象
cout<< ivec[0]; //错误
vector<int> ivec2(10);
cout<< ivec2[10]; //错误,越界
试图用下标的形式去访问一个不存在的元素将引发错误,不过这种错误不会被编译器发现,而是在运行时产生一个不可预知的值。
不幸的是,这种通过下标访问不存在的元素的行为非常常见,而且会产生很严重的后果。所谓的缓冲区溢出(buffer overflow)指的就是这类错误,这也是导致PC及其他设备商应用程序出现安全问题的一个重要原因。
确保下标合法的一种有效手段就是尽可能使用范围for语句
3.4 迭代器介绍
3.4.1 使用迭代器
迭代器运算符
迭代器类型
vector<int>::iterator it;
string::iterator it2;
vector<int>::const_iterator it3; //it3只能读元素,不能写元素
string::const_iterator it4; //it4只能读元素,不能写元素
const_iterator和常量指针类似,能读不能改,且若对象是一个常量只能用const_iterator
结合解引用和成员访问操作
(*it).empty() //圆括号必不可少,先解引用,在调用结果对象的empty成员
*it.empty() //错误,试图访问it的名为empty的成员,但it是个迭代器,没有empty成员
为简化上述表达式,it->mem和(*it).mem表达的意思相同
3.4.2迭代器运算
使用迭代器运算
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 数组
3.5.1 定义和初始化内置数组
unsigned cnt = 42; //不是常量表达式
constexpr unsigned sz = 42; //常量表达式
int arr[10];
int *parr[sz]; //含有42个整型指针的数组
string bad[cnt]; //错误:cnt不是常量表达式
string strs[get_size()]; //当get_size()是constexpr时正确;否则错误
字符数组的特殊性
char a1[]={'c','+','+'}; //3个元素,没有空字符
char a2[]={'c','+','+','\0'}; //4个元素,有显式的空字符
char a3[]="C++"; //4个元素 自动添加表示字符串结束的空字符
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; //一个指向含有10个整数的数组
int (&arrRef)[10] = arr; //arrRef 引用一个含有10个整数的数组
int *(&arry)[10] =ptrs; //arry 是数组的引用,该数组含有10个指针
3.5.2 访问数组元素
检查下标的值
要想防止数组下标越界,除了小心谨慎注意细节没有其他好办法。
3.5.3 指针和数组
string nums[] = {"one","two","three"};
string *p = &nums[0];
string *p2 = nums;
int ia[]={0,1,2,3,4,5,6,7,8,9};
auto ia2(ia); //这里 ia2是一个整型指针,指向ia的第一个元素
//类似于 auto ia2(&ia[0]); 显然ia2的类型是int*
ia2 =42; //错误:ia2是一个指针
decltype(ia) ia3 = {0,1,2,3,4,5,6,7,8,9}; //decltype(ia)返回类型是10个整数构成的数组
ia3 = p; //错误
ia3[4] = i; //正确
标准库函数begin和end
C++11新标准引入了两个名为 begin和end的函数,这两个函数与容器中的两个同名成员功能类似。不过数组毕竟不是类类型,因此这两个函数不是成员函数,正确的使用形式是将数组作为它们的参数。
int ia[] ={0,1,2,3,4,5,6,7,8,9};
int *beg = begin(ia); //指向ia首元素的指针
int *last = end(ia); //指向arr尾元素的下一位置的指针
3.5.4 C风格字符串
比较字符串
//string库比较方法
string s1= “A string example”;
string s2= "A different string";
if(s1<s2)
//如果把上述操作运用到c风格字符串上,实际情况为
const char ca1[] ="A string example";
const char ca2[] ="A different string";
if(ca1<ca2) //未定义的:试图比较两个无关地址,实际比较的是两个const char*的值
//即为两个地址,两个指针并非指向同一对象,所以错误。
//正确c方法
if(strcmp(ca1,ca2)<0)
目标字符串的大小由调用者指定
//string库方法连接拷贝字符串
string largeStr = s1+" "+s2;
//c正确方法
//缺陷:必须正确计算largeStr的大小,否则会产生严重错误
strcpy(largeStr,ca1);
strcat(largeStr," ");
strcat(largeStr,ca2);
一般来说使用string标准库要比使用C风格字符串更安全,更高效