C++primer 第三章 字符串、向量和数组
第三章 字符串、向量和数组
3.1 命名空间的 using
声明
using
声明的具体形式:using namespace :: name;
有了using
声明就没必要在写单独的命名空间前缀了,可以直接写所需的名字,但是每个名字都要有单独的 using
声明。
#include <iostream>
using std::cin;
using std::cout;
using std::endl;
int main() {
int v1, v2;
cin >> v1 >> v2;
cout << v1 + v2 << endl;
return 0;
}
但是在头文件不应该包含 ```using`` 声明,因为头文件中的内容会拷贝到所有引用他的文件中,可能会造成名字冲突。
3.2标准库类型
3.2.1定义和初始化 string
对象
string
对象初始化的几种方法:
string s1; //默认是空字符串
string s2 = s1;
string s3("value"); //直接初始化
string s3 = "value; //拷贝初始化,等价于string s3("value")
string s4(10,'c'); //s4的内容是cccccccccc
3.2.2string对象上的操作
使用 getline
读取一整行
int main() {
string line;
while(getline(cin, line))
cout << line << endl;
return 0;
}
string::size_type
类型
string::size_type
是一个无符号类型的值 auto len = line.size(); //len的类型是string::size_type
size函数返回的是无符号类型的值,所以使用size的时候避免使用int,避免int和Unsigned混用带来的问题
字面值和string对象相加
string s1 = "hello", s2 = "world";
string s3 = s1 + s2; //+的使用必须保证+两边至少有一个是string对象
s1 += s2; //相当于s1 = s1 + s2;
string s4 = s1 + ','; //正确,s1是string对象
string s5 = "hello" + ","; //错误,两个都不是string对象
string s6 = s1 + "," + "world"; //正确,相当于(s1 + ",") + "world",每个+两边都有一个string对象
string s7 = "," + "world" + s1; //错误,相当于("," + "world") + s1, 连个字面值不能直接相加
注意: C++为了和C兼容,C++中的字符串字面值和string是不同的类型。
3.2.3处理string对象中的字符
遍历string对象中的每个字符可以使用C++中的范围for语句
for (declaration : expression)
statements
要想实现对每个字符的修改需要加上&引用
#include <iostream>
#include <string>
#include <cctype>
using std::cin;
using std::cout;
using std::string;
using std::endl;
int main() {
string str("Hello world!!!!!!!");
decltype(str.size()) punct_cnt = 0;
for (auto c : str) {
if (ispunct(c))
punct_cnt++;
}
cout << punct_cnt << " punctuation characters in " << str << endl;
//利用引用可以实现对str中的字符的修改
for (auto &c : str) {
c = toupper(c);
}
cout << str << endl;
return 0;
}
// 7 punctuation characters in Hello world !!!!!!!
// HELLO WORLD !!!!!!!
3.3标准库类型vector
vector是具有相同数据类型的对象的集合,注意是对象的集合不能是引用。
3.3.1定义和初始化vector对象
常用初始化vector对象的方法:
vector<T> v1;
vector<T> v2(v1); //v2中含有v1的所有元素
vector<T> v2 = v1; //等价于v2(v1)
vector<T> v3(n,val); //v3中含有n个重复的元素,每个都是val
vector<T> v4(n); //v4中包含了n个默认初始化的对象
vector<T> v5{a, b, c, ....}; //列表初始化
vector<T> v5 = {a, b, c, ....};
vector<int> ivec;
vector<int> ivec2(ivec); //将ivec中的元素拷贝给ivec2
vector<string> svec(ivec2); //错误,对象不同
vector<string> v1{"a", "b","c"}; //列表初始化
vector<string> v2("a", "b", "c"); //错误的列表初始化
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元素,每个都是空
vector<int> v1(10); //10个0元素
vector<int> v2{10}; //1个元素,元素值是10
vector<int> v3(10, 1); //10个元素,每个都是1
vector<int> v4{10,1}; //两个元素,分别是10,1
()表示用来构造vector对象的,{} 表示用来列表初始化的,把{}里面的值当成是元素初始值的列表处理
如果初始化时采用了{}但是提供的值又不能用来列表初始化,就考虑用这样的值来构造vector对象
vector<string> v5{"hi"}; //列表初始化
vector<string> v6("string"); //错误,不能使用字面值类构造string对象
vector<string> v7{10}; //构造10个空string对象
vector<string> v8{10, "hi"}; //有10个值为"hi"的元素
3.4迭代器介绍
3.4.1使用迭代器
begin成员返回指向第一个元素的迭代器,end成员返回指向容器“尾元素的下一位置”,该迭代器指示的是容器的一个本不存在的尾后元素,没有实际意义,主要是用来标记最后一个位置
迭代器的运算符
语法 | 功能 |
---|---|
*iter | 返回迭代器所指向的元素的引用 |
iter->item | 解引用iter并获取名为item的成员,等价于(*iter).mem |
++iter | 指向下一个元素 |
--iter | 指向上一个元素 |
iter1 == iter2 | 判断迭代器是否相等 |
iter1 != iter2 | 判断迭代器不等 |
注意解引用的迭代器必须指向一个确切的元素,不能解引用非法迭代器和尾迭代器
迭代器类型
vector<int>::iterator it; //it能读写vector<int>的元素
string::iterator it2;
vector<int>::const_iterator it3; //it3只能读不能写
string::const_iterator it4;
const_iterator
类似于常量指针,对象只能读不能写, iterator
对象可读可写,如果vector\string对象是一个常量只能用const_iteraotr
反之都能用
beign和end运算符
如果对象是常量begin返回const_iterator
,反之返回iterator
.C++11提供cbegin、cend可直接返回const_iterator
vector<int> v;
const vector<int> cv;
auto it1 = v.begin(); //ite是vector<int>::iterator
auto it2 = cv.begin(); //it2是vector<int>::const_iterator
auto it3 = v.cbegin(); //it3是vector<int>::const_iterator
结合解引用和成员访问操作
*it通过解引用可以访问迭代器所指对象,如果对象是一个类,可以通过(*it).empty()
来访问,C++提供->运算符将解引用和成员访问结合起来,(*it).empty()
等价于it->empty()
3.4.2迭代器运算
语法 | 描述 |
---|---|
iter+n | 加上一个整数的结果还是迭代器 |
iter-n | 减去一个整数的结果还是迭代器 |
iter+=n | iter向后移动N个位置 |
iter-=n | iter向前移动N个位置 |
iter1 - iter2 | 两个迭代器相减,结果是两个迭代器之间的距离 |
>,>=,<,<= | 比较的是两个迭代器之间的距离 |
3.5数组
3.5.1定义和初始化内置数组
理解复杂的数组声明
int arr[10];
int *ptrs[10]; //ptrs是含有10个整型指针的数组
int &refs[10]; //错误,不存在引用数组
int (*Parray)[10] = &arr; //Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr; //arrRef引用含有10个整数的数组
默认情况下类型修饰符从右向左依此绑定,比如int *ptrs[10]
从右向左理解先是定义一个大小为10的数组,名字是Ptrs,然后是数组中存放的是指向整型的指针。
对于这种带有括号的,采用从内向外理解比如int (*Parray)[10] = &arr
,括号内显示一个名为Parray的指针,然后在从右向左理解,右边是一个大小为10的数组,所以Parray是一个指向大小为10的数组的指针,再然后是左边int,说明Parray是一个指针,指向一个包含10个元素的整型数组。同样的方法理解int (&arrRef)[10] = arr
,括号内是一个名为arrRef的引用,右边是一个大小为10的数组,所以arrRef是一个大小为10的数组的引用,左边是int,结合起来就是arrRef是一个引用,引用的对象是一个大小为10的整型数组。
3.5.3指针和数组
指针也是迭代器
vector和string的迭代器的运算,数组的指针都能用
int arr[] = {0,1,2,3,4,5,6,7,8,9};
int *p = arr; //p指向数组元素的首地址
int *e = arr[10]; //e指向数组尾元素的下一个位置的指针
for (int *p = arr; p != e; ++ p)
cout << *p << endl;
标准库函数中提供begin和end函数
C++11新标准中引用begin函数和end函数来获取数组的首元素指针和尾元素的下一个位置的指针
int arr[] = {0,1,2,3,4,5,6,7,8,9};
int *beg = begin(arr); //指向arr首元素的指针
int *last = end(arr); //指向arr尾元素的下一个位置的指针
while(beg != last)
cout <<*last << endl;
3.5.5与旧代码的接口
混用string对象和C风格字符串
如果程序的某处需要一个C风格的字符串,无法直接用string对象来代替,比如:不能直接用string对象直接初始化指向字符的指针。可以利用c_str()
函数完成该功能
char *str = s; //错误,不能用string初始化char*
const char *str = s.c_str(); //正确
c_str()
函数的返回结果是一个指针,该指针指向一个以空字符结束的字符数组,数组中的数据就是s中存储的内容。
使用数组初始化vector对象
int int_arr = {0,1,2,3,4,5,6,7,8,9};
//ivec被初始化为int_arr中的全部元素
vector<int> ivec(begin(int_arr), end(int_arr));
//ivec初始化为int_arr中int_arr[1], int_arr[2], int_arr[3]
vector<int> ivec(int_arr + 1, int_arr + 4);