C++ 标准String类以及其他常用STL容器、函数、算法文档

STL

STL即标准模板库(Standard Template Library),于1979年加入C++,提供了模板化的通用类和通用函数。
STL的核心包括容器、迭代器和算法。

string的用法以及常用的算法请往下翻~

函数对象

C++为一些常用的运算符(算术运算、关系运算、逻辑运算)定义了对应的运算符模板类,称为函数对象
函数对象定义在functional头文件中。

算术对象 关系对象 逻辑运算对象
plus equl_to logical_and
minus not_equal_to logical_or
multipies greater logical_not
divides greate_equal
modulus less
megate less_equal
int a[5] = {
    
     8,3,2,9,4 };

plus<int> myAdd;
// 等同于 1 + 2
cout << myAdd(1, 2) << endl;

greater<int> gt;
// 使用STL的sort函数进行降序排序,省去了定义排序函数的过程
sort(a, a + 5, gt);
// 或者直接申请一个临时对象的空间
sort(a, a + 5, *(new greater<int>));
// 或使用greater的静态函数调用运算符函数
sort(a, a + 5, greater<int>());
for (auto i : a)
{
    
    
	cout << i << '\t';
}
cout << endl;

容器

容器(container)是用来存储其他对象的对象。STL的容器分为顺序容器关联容器容器适配器三类。
所有容器都有以下的成员函数:

  • empty():判断容器是否为空
  • max_size():返回容器最大容量
  • size():返回当前容器中对象个数
  • swap():交换容器中的两个元素
    此外,容器还定义了默认构造函数、拷贝构造函数和析构函数,以及常用的运算符函数。

对于顺序容器和关联容器,还有以下成员函数:

  • begin():指向第一个元素
  • end():指向最后一个元素的后一个位置
  • erase():删除一个或多个元素
  • clear():清空容器
  • rbegin():指向反向的第一个元素
  • rend():指向反向的最后一个元素的前一个位置

迭代器

迭代器(iterator)是访问顺序容器和关联容器的关键。迭代器是 容器类型::iterator类型的变量,可以指向容器中的某个元素,是一个指针。
由于是指针,迭代器不能被直接赋值下标来使用,但是可以进行+、-、+=、-=、++和–以及*间接寻址运算符,并可进行==和!=的关系运算,还可用[](iterator[n])运算符返回当前地址后第n个元素的地址。

小细节:由于迭代器涉及大量数据访问,最好使用前缀形式的++和–,前缀形式的运算符返回的是一个引用,而后缀返回的是变量,会生成额外的临时变量,效率较低。

容器适配器(stack、queue等)不支持迭代器(具体请参考栈和队列的数据结构定义)。

// 以vector为示例,创建vector的迭代器
vector<int> v;

// 正向迭代器
vector<int>::iterator iter;

// 常量迭代器
vector<int>::const_iterator citer;

// 反向迭代器
vector<int>::reverse_iterator riter;

// 反向常量迭代器
vector<int>::const_reverse_iterator criter;

v.assign(100, 0);

// 利用迭代器遍历v
for (iter = v.begin(); iter != v.end(); iter++)
{
    
    
	cout << *iter << '\t';
}

// 迭代器指向容器起点
iter.begin();

// 迭代器指向容器终点(指针位于最后一个元素的后一地址)
iter.end();

顺序容器

顺序容器采用线性数据结构,常见顺序类型有向量(vector)、链表(list)、双端队列(deque)。

string

string(字符串)是C++如今应用最多的STL容器之一,代替了传统的char字符数组

  • string和char*是不同的,char*不能直接赋值给string,但是char[]可以
  • string不能赋值给char[]和char*
  • string不需要\0来结尾
  • 要将string赋值给char[],使用string.copy()成员函数
string str = "string for char";
char chs[20];
str.copy(chs,str.length());
char *s = "chars";			// 在MSVC中会出现数据类型错误,const char*类型不能用于初始化char*

使用string可以让开发过程变得更有效率,当然,如果需要提高程序运行效率,仍可以通过设计安全的char数组实现字符串。

// API文档
string s;									// 创建空string容器
string s = "Standard C++ String";			  // 创建string并初始化
string s1 = "another string";
const char* cs;								// 底层const

cs = s1.data();								// 将s1的内容转换成const char类型,并将其指针赋给底层const cs
s + s1;									    // operater+,返回连接两个string后的string
s.length();									// 返回字符串长度
s.substr(i, count);							// 取下标i后的count个字符组成的string
s.swap(s1);								    // 交换两个string的内容
// s.swap("123");							// 这是错误的用法,"123"的类型为const char*,无法隐式转换为string类型
s.capacity();								// 返回string当前的最大字符容量,string会自动变长,因此最大容量会根据目前string内字符数量变化而变化
s.max_size();								// 返回string理论上最大的字符容量,可能是2147483647,不同平台下有不同结果
s.find("C++");								// 查找string中第一个子串的位置,如果找到返回其第一个字符下标
											// 如没有找到返回string::npos(即有符号整型的-1)
s.rfind("C++");								// 反向查找子串
s.find_first_of("C++");						// 查找第一个目标子串出现的位置,返回下标
s.find_last_of("C++");						// 查找目标子串最后一次出现的位置
s.replace(start,n,s1);						// 用s1替换s[start]后的n个字符,如果s1.length()大于n,则把多余的字符连接在替换的字符后,不改变其他字符
s.replace(start1,n,s1,start2,m);			// 使用s1[start2]后的m个字符替换s[start1]后的n个字符
s.insert(n,s1);								// 将s1插入s[n]前
s.insert(n1,s1,n2,m);						// 将s1[n2]后的m个字符插入s[n1]前
s.insert(i,n,'1');							// 在s[i]前插入n个'1'(char类型,不能是字符串或string)
array

array是内置数组类型的升级版,每个元素的类型相同,支持使用[]运算符随机访问。array有两个模板参数,第一个类型模板参数为数组元素类型,第二个非类型模板参数为数组长度。

array支持普通数组无法进行的关系运算(<, >, ==, !=),以及其他内置数组无法进行的操作。

array<int, 5> arr={
    
     1,2,3,4,5 };  	// 定义一个长度为5,每个元素为int类型的array容器	
arr.at(index);						// 返回下标为index的元素
arr.fill(num);						// 将num赋给arr的每个元素
arr1 == arr2;						// 判断arr1中对应位置的元素是否与arr2相等

注意:在一些编译环境下,如果使用了GCC编译器的非标准头文件bits/stdc++.h,要使用array容器还需额外包含array头文件

vector

vector是向量容器,自动存储和管理空间,每个元素的类型相同,插入和删除操作时自动扩展和压缩,通过[]运算符像数组一样访问元素。

// API文档
vector<T> v;					// 默认构造函数,创建空vector
vector<T> v1(v);				// 拷贝构造函数
vector<T> v2(n,elemnt);			// 产生长度为n,每个元素为element的v2

v1 = v2;						// 赋值运算符
v2.assign(n,element)			// 复制n个element添加至v2末尾
v1.swap(v2);					// 将v1和v2内容互换
v1[i];							// 访问第i个元素,不进行下标检查
v1.at(i);						// 访问第i个元素,越界则抛出类型为out_of_range的异常
v1.front();						// 返回第一个元素
v1.back();						// 返回最后一个元素

v.insert(pos,elem);					// 在下标为pos的位置插入elem,返回插入的元素的下标
v.insert(pos,n,elem);				// 在下标为pos的位置插入n个elem的副本,不返回
v.push_back(elem);					// 在尾部添加elem
v.pop_back();						// 删除尾部元素
v.erase(pos);						// 删除下标为i的元素
v.erase(beg,end);					// 删除beg至end间的元素(包括beg和end)
v.clear()// 清空容器
v.size();							// 返回元素个数
v.resize(n);						// 重新设置v的大小,多出部分的内存位全部置0,长度不够时裁剪

注意,上述代码中pos和beg、end的类型是vector的迭代器(iterator),而不是整型,使用方法如下

// 定义vector的迭代器
vector<int>::iterator iter;
int n = [需要的下标];

// iter=&v[n];		// 错误用法,iter并不是int类型的指针,&v[n]是元素即一个int类型的指针,而iter是指向vector内容的迭代器类型

// 注意:如果在n前有一个等于v[n]的元素,该算法会删除错误的元素,此算法只是一个迭代器使用的示范
for (iter = v.begin(); iter != v.end(); iter++)
{
    
    
	if (*iter == v[n])
	{
    
    
		break;
	}
}

v.erase(iter);
list

list是一个双向列表,可以从头到尾或从尾到头访问节点,节点可以是相同的任意类型。

// API文档
list<T> l;					// 创建空链表
list<T> l1(l);				// 拷贝构造函数
list<T> l(n,e);				// 创建n个元素全为e的链表

l=l1;						// 赋值运算符函数
l.assign(n,e);				// 将n个e赋值给l
l.swap(l1);					// 交换l和l1全部元素
l.front();					// 返回第一个元素
l.back();					// 返回最后一个元素
l.insert(pos,e);			// 在pos位置插入e,pos是迭代器
l.insert(pos,n,e);			// 在pos插入n个e
l.push_back();_				// 在尾部添加
l.pop_back();_				// 删除尾部元素
l.push_front();_			// 在头部添加
l.pop_front();_				// 删除头部元素
l.remove(value);			// 删除值为value的元素
l.remove_if(callback)_		// 删除使回调函数返回true的元素,回调函数需要接收一个和元素同类型的参数e(代表该元素),回调也可以是lambda表达式
l.erase(pos);				// 删除pos位置的元素,pos是迭代器
l.erase(begin,end);			// 删除begin到end间的元素(包括),两者是迭代器
l.size();					// 返回容器大小
l.resize(s);				// 重新设置容器大小为s,多出的内存位全部置0,不够裁剪
l.clear();					// 容器置空

// 链表特有操作
l.unique();					// 删除相等且相邻的元素,保留一个
l.unique(callback)			// 删除相等且相邻并使回调函数返回true的元素,保留一个,接收两个和模板类型同类型的参数,回调可以是lambda
l.splice(pos,l1);			// 将l1的所有元素拼接到l的pos位置之前,不会删除l的元素,pos是迭代器
l.splice(pos,l1,l1pos);		// 将l1在l1pos位置上的元素插入到l的pos位置之前
l.splice(pos,l1,beg,end);	// 将l1的beg至end间的元素插入到l的pos位置之前
l.sort();					// 以模板参数给定的数据类型的<操作符(operator<)的含义为准(对于int而言是小于,即默认升序)进行排序
l.sort(callback);			// 以使得回调函数返回true的规则进行排序,回调函数接收两个和模板类型同类型的参数
l.merge(l1);				// 将l1合并进l,如果两个链表都有序,合并后的l仍然有序
l.reverse();				// 倒转所有元素

// 注:对于排序的回调函数,可以使用greater<T>(降序)和less<T>(升序)这两个结构体声明的变量
greater<T> gl;
l.sort(gl);
stack

stack(堆栈)是一种非常简单的常用容器。在一些编译器里如果有存放于栈中的变量超过一定大小(如MSVC的16384个字节),可以考虑使用stack容器将其移入堆中.

// C++的stack容器使用的即是标准的stack数据结构
stack<int> s;

s.push(10);			// 将int(10)压入栈中
s.top();			// 返回栈顶元素,但不删除
s.pop();			// 返回栈顶元素且删除
pair
  • pair是键值对形式的容器,键与值的类型可以通过模板参数指定,位于utility实用头文件中。
  • pair通常和其他容器一起使用,作为其他容器的元素。
  • 通过make_pair()函数创建一个pair对象,成员类型会被自动推断
  • pair和map不同,pair只有一个键和一个值,没有迭代器
pair<string,int> p("id",10000);			// 创建一个pair对象

// pair内容的访问需要通过first和second两个成员
p.first = "uid";					// first即是键
p.second = 100001;					// first即是值

// 同类型的pair对象可以互相赋值
// 如果没有另一个pair对象,可以使用make_pair函数创建一个pair对象
p = make_pair("score",100);
tuple
  • C++11中增加的tuple(元组)是功能非常强大的容器,和上述只能容纳单一类型的容器不同,tuple是可以容纳任意类型的一个容器。但是容纳的成员的类型和数量必须在模板参数表中被确认。
  • tuple的构造函数被关键字explicit进行显式声明,不能用于隐式类型转换
  • tuple元素都是公有的,可以直接访问,但是需要通过标准模板函数get()
  • 通过make_tuple()函数创建一个tuple对象_
  • 由于可能出现拥有非常复杂的模板参数列表的tuple,用auto进行自动类型推断会提高效率
  • 元组还可以为返回多个不同类型的参数
// 初始化tuple
tuple<string,int,bool> t("Steven",20,true);

// 也可使用列表初始化,列表内容会自动匹配参数类型一致的构造函数
// tuple<string,int,bool> t{"Steven",20,true};

// 访问tuple
get<0>(t);				// "Steven"
get<1>(t);				// 20
get<2>(t);				// true

auto t1 = make_tuple("Ouchdex",24,true);

关联容器

关联容器采用非线性结构,根据(key,也称查询键)来操作数据,可以存储值的集合或键值对。关联容器主要有集合(set)、多重集合(multiset)、映射(map)、多重映射(multimap)。
键是关联容器中存储在有序对中的特定类型的值(如用string和int作为键)。映射(map)存储和操作的是键与其相对的值,而集合(set)存储和操作的只能是键。

集合
  • multiset和set的操作基本相同,区别在于set的关键字不允许重复,而multiset可以。如果向set中插入重复的关键字,set会忽略
  • set和multiset是用来操作数字(包括字符和字符串)类型的,数字被称为关键字,不需要有一个值与之相对应
  • set和multiset中的关键字会按照指定规则自动排序
  • set和multiset类型不能使用[]运算符访问元素,必须使用迭代器
// 建立空集合对象
set<T> s;

// 按照使callback返回true的条件排列集合并创建集合对象,回调可以用greater和less类型的对象
set<T> s(callback);

// 也可以在模板参数中指定排序方式,注意两个T需要是同类型,默认的第二个模板参数是less<T>,即降序排列
set<T, greater<T>> s;

// 用beg和end的区间创建集合,两者是和创建集合对象同类型的集合的迭代器
set<T> s(beg,end);

// 拷贝构造函数
set<T> s1(s);

// 赋值运算符函数
s1 = s;

// 计算容器大小
s.size();

// 判断是否为空
s.empty();

// 容器最大存储元素个数
s.max_size();

// 容器内e的个数
s.count(e);

// 查找第一个e的下标
s.find(e);

// 插入元素,会自动排序
s.insert(e);

// 删除元素,参数可以是值,也可是迭代器
s.erase(e);

// 清空容器
s.clear();

// 起始地址
s.begin();

// 结束地址后一位
s.end();
映射
  • map和multimap与set和multiset不同,前者通过键操作值,后者访问键
  • 同样,map不允许有重复的元素,而multimap运行,插入重复元素时map会忽略
  • 和集合不同,map可以通过[]下标访问运算符访问元素,但是multimap不行(有重复元素)
  • set的各种api同样适用于map,但是有一些不同:
// 创建map
map<string,int> m;

// 映射的键值对是不可分割的整体,在执行插入操作时,必须插入完整的键值对,使用make_pair函数
m.insert(make_pair("Steven",20));

// 通过[]运算符访问,操作键来访问值的内容
m["Steven"];

// 集合访问到的直接是键的值,而映射和pair一样,通过first(即是键)和second(即是值)成员访问键值对
// 使用迭代器的时候会用到这两个成员,map和multimap本身没有first和second,两者是属于pair的
map<string,int>::iterator iter;
iter = m.begin();
iter->first;
iter->second;

容器适配器

容器适配器主要指堆栈(stack)和队列(queue),实际是受到访问限制的顺序容器。上面已经介绍了stack。

算法

  • C++ STL中的算法(algorithm)是使用模板技术编写的。虽然STL的算法具有鲁棒性,但是程序员依旧要为算法提供正确有效的输入,否则依旧会引发未知错误。
  • STL总共提供了100多个算法,每个算法都是一个或一组模板函数。这些算法常常使用迭代器操作容器,并返回迭代器(指针)作为结果。
  • STL算法覆盖了容器常用的操作,如遍历、排序、选择、检索、插入和删除
  • 除了对STL容器进行操作,这些算法还适用于普通数组和自定义容器,下面介绍几个常用算法,剩下100多个算法可以查看cplusplus的文档或微软的VC++文档

查找

find

find函数用来查找指定区间是否存在某个值,区间是某个容器的迭代器(或数组指针),这是一种间接访问方法

  • 如果找到结果,返回第一个符合结果元素的下标
  • 如果找不到,迭代器会位于容器末尾,指向最后一个元素,因此要注意,返回了并不一定代表找到,还要验证值
  • 如果查找对象是一个普通数组,找不到目标元素时find返回的不是最后一个数组元素,而是最后一个元素的后一位地址
// 查找某个容器的beg至end区间有无对应的值value
// beg和end都是该容器的迭代器
find(beg,end,value);

// 例子
vector<int> v = ...;
vector<int>::iterator it1 = v.begin();
vector<int>::iterator it2 = v.end();

// 迭代器操作
...

// 在v的it1至it2区间查找整型1
find(it1,it2,1);

// 或者对一般的数组使用
int arr[] = {
    
    ...};

find(arr,arr+_countof(arr),1);		// _countof是stdlib中宏定义的一个函数,返回数组大小,该宏定义有严格的类型检查,不用担心类型问题
search
  • search查找一个容器中有无与另一个容器相同的子空间,参数均是迭代器(或数组指针)
  • 如果找到,返回前一组迭代器中对应第一个子空间的第一个元素的迭代器位置
  • 如果没有找到,返回前一组迭代器区间的最后一个元素位置
  • 对一般数组的查找在没有找到时仍有和find同样的问题,返回的不是最后一个元素地址,而是后一位地址
// 如果找到,返回beg1至end1间的某个位置,没有找到返回end1
search(beg1,end1,beg2,end2);
count
  • count统计容器中出现指定值的个数
  • 参数依旧是迭代器(或数组指针)
count(beg,end,value);

组合

迭代配接器
  • 迭代配接器(Iterator Adapters)在STL中担任不同类型容器配接接口的作用。由于不同的容器有不同的插入方法,在对不同的容器执行合并操作时可能会因为没有对应的插入方法而出错。
  • 使用迭代配接器来避免这个问题
  • 迭代配接器有四种基本操作:取值(*)、赋值(=)、迭代(++和–)以及比较(!=和==)
安插型配接器
  • back_inserter:使用push_back成员函数来进行容器插入,用于vector、list、deque
back_inserter(container);
  • front_inserter:使用push_front成员函数进行容器插入,用于list、deque
front_inserter(container);
  • inserter:使用inserter成员函数进行容器插入,用于拥有insert成员函数的容器
inserter(container,pos);		// pos是迭代器类型的参数
流迭代器
  • ostream_iterator:用于通过迭代器向输出流输送数据
// 参数1为需要输出数据的输出流,参数2为每个数据间的分隔字符串
ostream_iterator<T>(os,"text");
  • istream_iterator:用于通过迭代器向输入流输送数据
// 参数1为需要输入数据的输入流,参数2为每个数据间的分隔字符串
istream_iterator<T>(is,"text");
merge
  • merge对两个容器进行合并,并存储到第三个容器中
  • 如果参与合并的两个容器是有序的,则合并结果也是有序的
  • 注意,merge函数内容的插入是按照一定规则进行的,如果目标容器不支持这样的插入方式就会出错
  • 如:用merge合并两个vector至第三个vector,如果merge第三个参数直接是一个vector会出现非法间接寻址错误,vector只有push_back方法,不支持头插入_
  • 使用back_inserter函数作为迭代配接器进行第三个vector的插入可以解决这个问题
merge(beg1,end1,beg2,end2,destination);vector<int> v1(2, 3);vector<int> v2(3, 2);vector<int> v3;// 直接输出合并结果merge(v1.begin(), v1.end(), v2.begin(), v2.end(),ostream_iterator<int>(cout," "));// 使用back_insertermerge(v1.begin(), v1.end(), v2.begin(), v2.end(), back_inserter(v3));

排序

sort
  • sort排序算法是最常用的STL算法之一,可以对容器的一定区间进行排序,默认为升序
  • sort的第三个参数是一个回调,可以自定义回调函数的内容实现自定义和复杂类型排序,回调接收两个和容器元素同类型的参数
// 排序算法将满足将在callback返回true时将元素排在前面的位置
sort(beg,end,callback);
// 如:降序
greater<T> gt;
sort(beg,end,gt);
// 或使用lambda
sort(beg,end,[](T a,T b){
    
    return a > b;});

C++虽然实际开发效率不如一些语言,但是对于学习编程和计算机科学非常重要,并且好的C++程序对于计算机而言有着很高的执行效率,是锻炼编程思维最好的工具之一。学好C++的意义除了用于实践生产外,更多是思想体系的塑造。

猜你喜欢

转载自blog.csdn.net/Ouchdex/article/details/117050503
今日推荐