抽象数据类型库
标准库类型:string:可变长字符串
vector:可变长的集合
迭代器:访问string中的字符或vector中的元素
内置数组
1. 命名空间的using声明
基本形式:using namespace::name;有了using声明就无须专门的前缀也能使用所需的名字了。
头文件不应包含using声明。
2.标准库类型string
标准库类型string表示可变长的字符序列,使用string类型必须首先包含string头文件。
#include <string>
(1)定义和初始化string对象
- 直接初始化和拷贝初始化
对s7这种形式的初始化,最好用直接初始化方法。
(2)string对象上的操作
- 读写string对象
在执行读取操作时,string对象会自动忽略开头的空白(空格符、换行符、制表符等)并从第一个真正的字符开始读起,知道遇到下一处空白为止。
string s;
cout << "请输入一个字符串:" << endl;
cin >> s;
cout <<"您输入的字符串为:"<< s << endl;
输出结果:
请输入一个字符串:
Hello world!
您输入的字符串为:Hello
多个输入:
string s1, s2;
cout << "请输入一个字符串:" << endl;
cin >> s1 >> s2;
cout << "您输入的字符串为:" << s1 << " " << s2 << endl;
输出结果:
请输入一个字符串:
Hello world!
您输入的字符串为:Hello world!
- 读取未知量的string对象(while 循环)
string word;
cout << "请输入一个字符串:" << endl;
while (cin >> word) //反复读取,直至达到文件末尾
cout << word << " ";
输出结果:
请输入一个字符串:
Hello world!
Hello world!
- 使用getline读取一整行
getline函数的参数是一个输入流和一个string对象,标准形式为getline(cin,string名)。函数从给定的输入流中读入内容,直到遇到换行符为止(换行符也被读进来了),然后把所读的内容存入到string对象中去(不存换行符)。
string line;
cout << "请输入一行字符串:" << endl;
getline(cin, line);
cout << "您输入的字符串为:" << line << endl;
- string的empty和size操作
empty函数:根据string对象是否为空返回对应的布尔值。
string line;
cout << "请输入一行字符串:" << endl;
getline(cin, line);
if (!line.empty()) //判断空行
cout << "您输入的字符串为:" << line << endl;
else
cout << "空字符串" << endl;
size函数:返回string对象的长度
cout << line.size() << endl;
注:size函数返回的值的类型是string::size_type类型,而非int或unsigned,但它是一个无符号类型的值。
- 比较string对象
== 、!= 、 < 、 <= 、 > 、 >=
- 为string对象赋值 =
- 两个string对象相加
两个string对象相加得到一个新的string对象,其内容是把左侧的运算对象与右侧的运算对象串接而成。string对象支持复合赋值运算符 += 。
- 字面值和string对象相加
当字符(串)字面值和string对象混在一条语句中使用时,必须确保每个加法运算符(+)的两侧的运算对象至少有一个是string。
切记:字符串字面值与string是不同的类型!
(3)处理string对象中的字符
cctype头文件中定义了一组标准库函数处理的函数,下表列出了其主要函数名及含义。
- 使用基于范围的for语句处理每个字符
基本形式: for (变量名 : 表示序列的对象 )
string str("!some,string.sososo?");
decltype(str.size()) punct_cnt = 0; //统计标点符号的数量
for (auto c : str) //对于str中的每个字符,c的类型是char
if (ispunct(c))
++punct_cnt;
cout << punct_cnt << "个标点符号。" << endl; //4个
用decltype关键字声明计数变量punct_cnt,使punct_cnt的类型和s.size的返回类型一样。
- 使用范围for语句改变字符串中的字符
把循环变量定义成引用类型。
string s("Hello World!");
for (auto &c : s)
c = toupper(c); //c是一个引用,因此赋值语句将改变s中字符的值
cout << s << endl;
输出结果:
HELLO WORLD!
- 只处理一部分字符
下标运算符([ ])
迭代器(见4.迭代器介绍) - 使用下标执行迭代
例:把s的第一个词改写成大写形式:
string s("some string");
for (decltype(s.size()) index = 0; index != s.size() && !isspace(s[index]); ++index)
s[index] = toupper(s[index]);
cout << s << endl;
输出结果:
SOME string
- 使用下标执行随机访问
用 if 语句限制
3. 标准库类型vector
标准库类型vector表示对象的集合,其中所有对象的类型都相同。vector也被称作容器。vector头文件:
#include <vector>
C++语言既有类模板,也有函数模板,其中vector是一个类模板。编译器根据模板创建类或函数的过程称为实例化。当使用模板时,需要指出编译器应把类或函数实例化成何种类型,即在模板名字后面跟一对尖括号< >,在括号内放上所存放对象类型的信息。
vector是模板而非类型。
vector不能包含引用,因为引用不是对象。
(1)定义和初始化
花括号或圆括号:
(2)向vector对象中添加元素
vector的成员函数 push_back
基本形式:vector.push_back(元素)
//从标准输入中读取单词,将其作为vector对象的元素储存
string word;
vector<string> text;
while (cin >> word){
text.push_back(word);
}
cout << text << endl; //报错:error C2679:二进制“<<”: 没有找到接受“std::vector<std::string,std::allocator<_Ty>>”
//类型的右操作数的运算符(或没有可接受的转换)
cout << text[2] << endl; //正确
注:如果循环体内部包含有向vector对象添加元素的语句,则不能使用范围for循环。即范围for语句体内不应改变其所遍历序列的大小。
(3)其他vector操作
计算vector内对象的索引
vector对象(及string对象)的下标运算符可用于访问已存在的元素,而不能用于添加元素。
string word;
vector<string> text;
cout << "请输入一组词,按ctrl+z结束:" << endl;
while (cin >> word) //将词存入vector对象
text.push_back(word);
for (auto &s : text) //改写为大写形式
{
for (decltype(s.size())i = 0; i != s.size(); i++)
s[i] = toupper(s[i]);
}
for (auto m : text) //输出
cout << m << endl;
输出结果:
请输入一组词,按ctrl+z结束:
HDK hnvfFH ABoknsAHBH jnXXoo
^Z
HDK
HNVFFH
ABOKNSAHBH
JNXXOO
int num;
vector<int> text;
cout << "请输入一组整数,按ctrl+z结束:" << endl;
while (cin >> num) //将数存入vector对象
text.push_back(num);
vector<int> sum1(text.size() - 1, 0); //求相邻整数的和
for (decltype(sum1.size())i = 0; i != sum1.size(); i++)
{
sum1[i] = text[i] + text[i + 1];
}
for (auto m : sum1) //输出
cout << m << " ";
cout << endl;
vector<int> sum2(text.size()/2, 0); //求第一个和最后一个整数的和
for (decltype(sum2.size())i = 0; i != sum2.size(); i++)
{
sum2[i] = text[i] + text[text.size() - i-1];
}
for (auto n : sum2) //输出
cout << n << " ";
cout << endl;
输出结果:
请输入一组整数,按ctrl+z结束:
12 65 33 17 50 0 3 1 88
^Z
77 98 50 67 50 3 4 89
100 66 36 17
4. 迭代器介绍
除了可以用 下标运算符([ ]) 来访问string对象的字符或vector对象的元素,迭代器也可实现上述功能。所有标准库容器都可使用迭代器。
(1)使用迭代器
begin 、end
end成员返回指向容器(或string对象)“尾元素的下一位置”的迭代器,表示我们已经处理完了容器中的所有元素。end成员返回的迭代器成为尾后迭代器或尾迭代器。如果容器为空,则begin和end返回的是同一个迭代器。
- 迭代器运算符
和指针类似,也能通过解引用迭代器来获取它所指示的元素。
例:利用迭代器把string对象的第一个字母改为大写形式
string s("some string");
if (s.begin() != s.end()) { //确保s非空
auto it = s.begin();
*it = toupper(*it); //通过解引用将第一个字母改为大写形式
}
cout << s << endl;
- 将迭代器从一个元素移动到另一个元素
使用递增(++)/递减(--)运算符。例:利用迭代器把string对象的第一个单词改为大写形式
string s("some string");
//依次处理s的字符直至我们处理完全部字符或者遇到空白
for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)
*it = toupper(*it);
cout << s << endl;
- 迭代器类型
一般来说我们无须知道迭代器的精确类型。拥有迭代器的标准库类型使用iterator 和const_iterator 来表示迭代器的类型。
- begin和end运算符
begin 和end返回的具体类型由对象是否是常量决定。如果对象是常量,返回const_iterator类型;如果对象不是常量,返回iterator。
如果对象只需读操作而无须写操作的话最好使用常量类型。为此引入两个新函数:cbegin 和cend。
- 结合解引用和成员访问操作
基本形式:(*iter).mem 或 iter->mem
- 某些对vector对象的操作会使迭代器失效
不能在范围for循环中向vector对象添加元素。
但凡使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。
vector<string> text{ "Hello!", "My name is Jenny.", "", "How are you?" }; //空字符串表示段落分隔
for (auto it = text.begin(); it != text.end()&& !it->empty(); it++) //找到第一段
{
for (auto i = (*it).begin(); i != it->end(); i++) //所有字母大写
*i = toupper(*i);
cout << *it << endl;
}
(2)迭代器运算
- 迭代器的算术运算
- 使用迭代器运算
二分搜索:
5. 数组
数组与标准库类型vector类似。数组也是存放类型相同的对象的容器,这些对象本身没有名字,需要通过其所在位置访问。但是,数组的大小是固定的,不能随意向数组中增加元素。
数组特征:
数组中的元素都是相同的数据类型,
数组的内存空间连续。
(1)定义和初始化内置数组
基本形式: 类型说明符 数组名[维度] ,其中维度必须是一个常量表达式。
定义数组的时候必须指定数组的类型,不允许用auto关键字推断类型;和vector一样,数组的元素应为对象,因此不存在引用的数组。
- 显式初始化数组元素
可以对数组的元素进行列表初始化:
- 字符数组的特殊性
对于字符数组,既可以用列表初始化,也可以用字符串字面值进行初始化,但要注意字符串字面值的结尾处还有一个空字符:
- 不允许拷贝和赋值
不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值:
- 复杂的数组声明
(2)访问数组元素
与标准库类型vector相似。使用下标运算符[ ],也可使用范围for语句遍历。
类型:size_t类型。
求数组长度:arr.size()是错误的。应该用sizeof(arr)/sizeof(arr[0])。也可用end-begin。
一维数组名称的用途:
①可以统计整个数组在内存中的长度
②可以获取数组在内存中的首地址
(3)指针和数组
数组的元素都是对象,所以都可以使用取地址符得到对应的指针。
使用数组的时候编译器一般会把它转换成指向数组首元素的指针。所以用auto推断变量的初始值时,得到的类型是指针而非数组。但是用decltype关键字推断时,返回的类型仍然是数组。
- 指针也是迭代器
vector和string的迭代器支持的运算,数组的指针全都支持。例如,允许使用递增运算符将指向数组元素的指针向前移动到下一个位置上:
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尾元素的下一位置的指针
arr[10]唯一的用处就是提供其地址用于初始化e。就像尾后迭代器一样,尾后指针也不指向具体的元素。因此,不能对尾后指针执行解引用或递增操作。
输出arr的全部元素:
for (int *b = arr; b != e; ++b)
cout << *b << endl; //输出arr的元素
输出结果:
0
1
2
3
4
5
6
7
8
9
- 标准库函数begin和end
用上述方法取尾后指针极易出错,所以可以使用begin和end函数。这两个函数与前面容器中的两个同名成员功能类似,但数组不是类类型,因此这两个函数不是成员函数。这两个函数定义在iterator头文件中。
int ia[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int *beg = begin(ia); //指向ia首元素的指针
int *last = end(ia); //指向arr尾元素的下一位置的指针
//输出ia的元素
for (auto b = beg; b != last;++b)
cout << *b << endl;
- 指针运算
指向数组元素的指针可以执行前面所有的迭代器运算。
const size_t sz = 5;
int arr[sz] = { 1, 2, 3, 4, 5 };
int *ip = arr; //等价于int *ip=&arr[0]
// +或-一个整数
int *ip2 = ip + 4;
int *p = arr + sz; //正确:arr转换成指针,p指向arr尾元素的下一位置。注意不能解引用p!
int *p2 = arr + 10; //有疑问
//两指针相-
auto n = end(arr) - begin(arr); //n的值为5,类型为ptrdiff_t
- 解引用和指针运算的交互
int last = *(ip + 4);
- 下标和指针
对数组执行下标运算其实是对指向数组元素的指针执行下标运算:
注:标准库类型限定使用的下标必须是无符号型,但内置的下标运算无此要求,可以处理负值,前提是结果地址必须指向原来的指针所指同一数组中的元素。
(4)C风格字符串
基本形式: char 字符串名[ ] = “字符串值”;即数组形式。
使用标准库string要比使用C风格字符串更安全、更高效。
字符串字面值是一种通用结构的实例,这种结构即是C++由C继承来的C风格字符串。按此习惯书写的字符串存放在字符数组中并以空字符(’\0’)结束.一般利用指针来操作这些字符串。
- C标准库String函数
下表列举了C语言标准库提供的一组函数,这些函数可用于操作C风格字符串,它们定义在cstring头文件中。
传入此类函数的指针必须指向以空字符作为结束的数组:
- 比较字符串
- 目标字符串的大小由调用者指定
使用strcat和strcpy来连接和拷贝字符串。
(5)与旧代码的接口
- 混用string对象和C风格字符串
允许使用字符串字面值来初始化string对象,任何出现字符串字面值的地方都可以用以空字符结束的字符数组来替代:
string s("Hello world");
但是,如果程序的某处需要一个C风格字符串,无法直接用string对象来代替它。可以用string中的s.c_str()成员函数完成该功能。
- 使用数组初始化vector对象
不允许使用数组和vector对象来初始化数组,但是可以使用数组来初始化vector对象。语句基本形式为vector<数据类型> 名称(首元素地址,尾元素地址):
int arr[] = { 0, 1, 2, 3, 4, 5 };
//拷贝全部元素
vector<int> ivec(begin(arr), end(arr));
//拷贝三个元素:1,2,3
vector<int> subvec(arr + 1, arr + 4);
6.多维数组
多维数组就是数组的数组。
基本形式:数据类型 名称[维度]…[维度];对于二维数组来说,常把第一个维度称为行,第二个维度称为列。
(1)多维数组的初始化
可以不标明行数,但一定要标明列数。
//全部元素初始化1
int ia[3][4] = {
{0,1,2,3},
{4,5,6,7},
{8,9,10,11}
};
//全部元素初始化2
int ia[3][4] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
//初始化第1列
int ia[3][4] = {
{ 0 },
{ 4 },
{ 8 }
};
//初始化第1行
int ia[3][4] = { 0, 1, 2, 3 };
(2)多维数组的下标引用(访问数组元素)
const 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;
}
(3)使用范围for语句处理多维数组
//范围for
const size_t rowCnt = 3, colCnt = 4;
int ia[rowCnt][colCnt];
size_t cnt = 0;
for (auto &row:ia) //row的类型是含有4个整数的数组的引用
for (auto &col : row){ //col的类型是整数的引用
col = cnt;
++cnt;
}
//输出
for (auto &row : ia){ //int (&row)[4]
for (auto col : row) //int col
cout << col << endl;
}
注:没有写操作时可以不用引用,但是row要声明成引用类型,不然会被当成指向数组首元素的指针。使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。
(4)指针和多维数组
当程序使用多维数组的名字时,也会自动将其转化成指向第一个内层数组的指针。
输出ia中每个元素的值,每个内层数组各占一行:
//p指向含有4个整数的数组 int (*p)[4]
for (auto p = ia; p != ia + 3; ++p){
//q指向4个整数数组的首元素,即一个整数 int *q
for (auto q = *p; q != *p + 4; ++q)
cout << *q << ' ';
cout << endl;
}
使用标准库函数begin和end实现:
for (auto p = begin(ia); p != end(ia); ++p){ //int (*p)[4]
for (auto q = begin(*p); q != end(*p); ++q) //int *q
cout << *q << ' ';
cout << endl;
}
(5)类型别名简化多维数组的指针
将“4个整数组成的数组”命名为int_array:
using int_array = int[4]; //等价的两种声明
typedef int int_array[4];
for (int_array *p = ia; p != ia + 3; ++p){
for (int *q = *p; q != *p + 4; ++q)
cout << *q << ' ';
cout << endl;
}