渐渐地我们发觉编写的每一个程序都或多或少地需要存储一些数据,而C++这方面只提供了集中最基本的方法。
你可以创建局部或全局变量来保存单个值,可以使用数组来保存多个值。
今天的概念:能容纳两个或更多个值的数据结构通常我们称为容器(container)。
这么说来,数组是C++唯一直接支持的容器,但数组并不适合用来解决所有的问题。
如果打算编写一个简单的拼写检查程序,应该怎么做?
首先想到的是用一个数组存储所有的单词,然后用遍历。但是这种效率也太低下了。
计算机领域的科学家们在过去的几十年里投入了大量的精力来为不同类别的问题寻找最合适的数据结构。
就拿刚才提到的拼写检查程序来说吧,最适合用来解决这类问题的数据结构是散列表和二叉树。
如何制作容器
其实前两节的基于模板的Stack类就是一种新容器。
在C++标准库里有许多现成的容器,可以直接拿来就用。
解决一个问题,找到最合适的容器只是编程工作的一部分。还需要一些适当的函数(算法)来处理这个容器里的数据才能实现最优效率。
向量容器
数组这种数据结构的最大的先天不足就是它受限于一个固定的长度。
在程序里用int myArray[40]这样的语句定义一个数组时,程序将遇到两个问题:
- 首先,你最多只能在那个数组变量里存储40个整型数据,万一你需要存储第41个数据,那么就不行了。
- 其次,不管程序是不是真的需要存储40个整型数据,编译器都会它分配40个整型数据的空间。
像这样的问题用C语言解决起来往往很复杂,而C++提供的解决方案就高明很多。
C++标准库提供的向量(vector)类型从根本上解决了数组先天不足的问题。
就像可以创建各种不同类型的数组一样,我们也可以创建各种不同类型的向量。
std::vector<type> vectorName;
因为属于标准库,所以用标准库的命名空间std。
我们用不着对一个向量能容纳多少个元素做出限定,因为向量可以动态地随着你往它里面添加元素而无限增大(前提是有足够可用的内存)。
然后你还可以用它的size()方法插值某给定向量的当前长度(它当前包含的元素个数)。
定义一个向量后,我们可以用push_back()方法往它里面添加东西。
我们还可以用访问数据元素的语法来访问某给定向量的各个元素。
例子:
#include <iostream>
#include <string>
#include <vector> //包含在这个头文件里面
int main()
{
std::vector<std::string> names;
names.push_back("小甲鱼");
names.push_back("小由鱼");
for (int i = 0; i < names.size(); i++)
{
std::cout << names[i] << std::endl;
}
return 0;
}
迭代器
上节课我们的例子虽然工作的很好,并且使用了一个标准的容器(向量容器),但它还是有个小问题。
就是在遍历向量里的各个元素时,我们仍把它视为一个C++数组来对待。
刚好我们的向量容器允许使用下标操作符来访问它的各个元素:names[x];
但是如果想改用另一种不提供此方法访问的容器(比如栈),我们就不得不对程序进行很多修改才能得以实现。
因为对容器里面的各个元素进行遍历是一种十分常见的任务,所以应该有一种标准的方法来做这件事。
C++标准库提供的各种迭代器(iterator)就是这么来的。
迭代器是一种功能有限却非常实用的函数,提供一些基本操作符:*、++、==、!=、=。
迭代器是个所谓的智能指针,具有遍历复杂数据结构的能力。
因为迭代器的功能是如此的基本,所以标准库里的每一个容器都支持。
通过实用迭代器,当在程序里改用另一种容器的时候就用不着谢盖那么多的代码了。
每一种容器都必须提供自己的迭代器,事实上每种容器都将其迭代器以嵌套的方式定义于内部。
因此各种迭代器的接口相同,型号却不同,这就是所谓泛型程序设计的概念:所有操作行为都是用相同接口,虽然它们的具体实现不同。
修改上面的那个代码:
#include <iostream>
#include <string>
#include <vector> //包含在这个头文件里面
int main()
{
std::vector<std::string> names;
names.push_back("小甲鱼");
names.push_back("小由鱼");
std::vector<std::string>::iterator iter = names.begin(); //进行了初始化,用了names.begin(),返回了最初的值。
while (iter != names.end()) //这里的names.end()指向的是最后一个元素的下一个位置
{
std::cout << *iter << std::endl; //前面说了,迭代器其实一个指针
++iter;
}
return 0;
}
算法
先随便来个例子,后续学习《数据结构与算法》
#include <iostream>
#include <string>
#include <vector> //包含在这个头文件里面
#include <algorithm>
int main()
{
std::vector<std::string> names;
names.push_back("Larry");
names.push_back("Rola");
names.push_back("DingDing");
names.push_back("Joyjoy");
names.push_back("Michael");
names.push_back("Lucy");
names.push_back("Lilei");
std::sort(names.begin(), names.end());
std::vector<std::string>::iterator iter = names.begin(); //进行了初始化,用了names.begin(),返回了最初的值。
while (iter != names.end()) //这里的names.end()指向的是最后一个元素的下一个位置
{
std::cout << *iter << std::endl; //前面说了,迭代器其实一个指针
++iter;
}
return 0;
}
输出结果