C++从入门到放弃
函数模板
1. 函数模板的定义
- 1.1 函数模板的定义形式
template<class 类型参数1, class 类型参数2, ...>
返回值类型 函数模板名(调用形参1, 调用形参2), ...{
...
}
eg:
template<class T>
T Max(T x, Ty){
return x>y ? x:y;
}
- 1.2 可以使用任何标识符作为类型形参的名称,但是使用
T
已经是一种惯例,T
表示的是调用者在使用这个函数模板时指定的任意数据类型
2. 函数模板的使用
- 2.1 使用函数模板必须对函数模板进行实例化
- 2.2 形式:
函数模板名<类型实参1,类型实参2,...>(调用实参1,...);
例如:
Max<int>(123,456);
Max<double>(12.3,45.6);
Max<string>("hello","wordl");
3. 函数模板的分析
- 3.1 编译器并不是把函数模板编译成一个可以处理任何数据类型的单一实体
- 3.2 编译器在实例化函数模板时根据类型实参从函数模板中产生一个真正的函数
- 3.3 函数模板并不是一个函数实体,只有通过实例化才能产生真正的函数
- 3.4 函数模板只是编译器生成真正函数的一个依据而已
- 3.5 这种用具体数据类型替换函数模板类型形参的过程叫做实例化,这个过程将会产生一个函数模板的实例(真正的函数)
- 3.6 只要使用函数模板,就会自动引发编译器实例化过程,因此程序员不需要额外的请求对函数模板的实例化
4. 实例化函数模板的条件
- 4.1 原则上来说可以使用任何类型来实例化函数模板,不管其为基本类型还是类类型,单前提是这个类型必须要支持函数模板所要执行的操作
例如一个不支持>
操作符的类型来实例化一个Max
函数模板,编译器将报错
5. 函数模板扩展(二次编译)
编译器对函数模板都会进行两次编译
- 第一次编译发生在实例化函数模板之前(产生真正的函数之前)只会检查函数模板本身内部的代码的基本词法是否正确
- 函数模板内部出现的所有标识符是否均有声明
- 对于已知类型的调用查看调用是否有效
- 对于未知类型的调用认为都是合理的
- 第二次编译发生在实例化函数模板之后(产生真正函数实体之后)
- 结合所使用的的类型实参,再次检查模板代码,查看所有调用是否真的有效
6. 隐式推断类型实参
- 6.1 如果函数模板的调用形参和类型形参相关
例如:template<class T>T Max(T x, T y){...}
那么在实例化函数模板时及时不显示指明函数模板的类型实参,编译器也有能力根据调用实参的类型推断出正确的类型实参的类型
例如 :
Max(123,456);
Max<int>(123,456);
获得和调用函数一致的语法表现形式 - 6.2 以下三种情况不能做隐式判断
- 调用参数和类型参不完全相关
例如:
template<class T,class D>T Max(T x,T y){...}
- 隐式推断不支持隐式类型转换
例如:
template<class T> T Max(T x,T y){```}
Max(123,45.6)
//这种调用是错误的 - 返回值类型不支持隐式推断
- 调用参数和类型参不完全相关
7. 函数模板的重载
- 7.1 普通函数和可实例化出该函数的函数模板构成重载关系,在数据类型匹配度相同的情况下,编译器优先选择普通函数,除非函数模板可以产生匹配度更好的函数,或指定使用函数模板产生的函数
- 7.2 函数模板的实例化不支持隐式类型转换,但普通函数支持
- 7.3 可以在实例化函数模板时使用
< >
强行通知编译器选择函数模板
类模板
类模板并不是一个真正的类
1.类模板声明
- 1.1 类模板的声明形式
template<class 类型形参1, ...>
class 类模板名 {
...
};
示例:
template<class A, class B>
class CMath{
public:
A m_a;
B func(){...};
};
- 1.2 在类外实现成员函数
要素:帽子不能丢,类名引成员
template <class 类型形参1,...>
返回值类型 类模板名 <类型形参1,...>::函数名(调用形参1, ...){
函数实现;
}
示例:
template<class A.class B>B CMath<A,B>::func(){
...;
}
2. 类模板的使用
-
2.1 使用类模板必须对类模板进行实例化(产生真正的类).
-
2.2 类模板本身并不代表一个确定的类型,(即不能用于定义对象),只有通过类型实参实例化成真正的类后才具备类的语义,即可以定义对象
例如CMath<int,double> math;
示例:
#include <iostream>
using namespace std;
template<class T>
class CMath{
public:
CMath(T const& t1, T const& t2):m_t1(t1),m_t2(t2){}
T sum();
// T sum(){
// return m_t1+m_t2;
// }
private:
T m_t1;
T m_t2;
};
template<class T>//帽子不能丢
T CMath<T>::sum(){//类模板并不是一个真正的类,不能用模板名引成员,只有类才可以引成员,可以使用未知类来引成员
return m_t1+m_t2;
}
int main(int argc,char* argv[]){
int i=9,j=1;
CMath<int> a(i,j);
cout<<a.sum()<<endl;
string s="hello ";
string s2="world";
CMath<string> s3(s,s2);
cout<<s3.sum()<<endl;
return 0;
}
-
2.3 类模板被实例化时类模板中的成员函数并没有实例化,只有成员变量和虚函数,成员函数只有在被调用时才会被实例化,即产生真正的成员函数,注意:成员虚函数除外
-
2.4某些类型虽然没有提供类模板所需要的全部功能,但是照样可以实例化类模板,只是不要调用那些没有提供功能的成员函数即可
3.类模板的静态成员
C:
静态局部变量:声明周期:进程级别,可见性:本函数可见
静态全局变量:声明周期:进程级别,可见性:本文件内可见
C++:
静态成员变量:生命周期:进程级别,可见性:本文件可见
- 3.1 类模板中的静态成员既不是每个对象拥有一份,也不是类模板拥有一份,而是由类模板实例化出的每一个真正的类各有一份,且为该实例化类定义的所有对象所共享
#include <iostream>
using namespace std;
template<class T>
class A {
public:
static int m_i;
static T m_t;
int a;
static void print() {
cout << "m_i: " << &m_i << " " << "m_t: " << &m_t << endl;
}
};
template<class T> int A<T>::m_i = 0;
template<class T> T A<T>::m_t;
int main() {
A<int> a, b, c;
a.print();
b.print();
c.print();
A<double> d, e, f;
d.print();
e.print();
f.print();
return 0;
}
}
/* 打印结果
m_i: 0x55b291109154 m_t: 0x55b291109158
m_i: 0x55b291109154 m_t: 0x55b291109158
m_i: 0x55b291109154 m_t: 0x55b291109158
m_i: 0x55b29110915c m_t: 0x55b291109160
m_i: 0x55b29110915c m_t: 0x55b291109160
m_i: 0x55b29110915c m_t: 0x55b291109160
*/
4. 类模板的递归实例化
- 4.1 类模板可以使用任何类型来实例化
- 4.2 有类模板实例化产生的类也可以来实例化类模板本身,这种做法称之为类模板的递归实例化
- .3 通过这种方法可以构建空间上具有递归特性的数结构(例如递归数组)
示例
#include <iostream>
using namespace std;
template<class T>
class Array{
public:
T& operator[](size_t i){
return m_arr[i];
}
private:
T m_arr[10];
};
int main (int argc,char**argv){
// Array<int> a;
// for(int i=0;i<10;i++){
// a[i]=i+1;
// }
// for (size_t i = 0,count=10; i < count; i++)
// {
// /* code */
// cout<<a[i]<<endl;
// }
Array< Array<int> > c;
for (size_t i=0,count=10; i < count; i++)
{
/* code */
for (size_t j = 0; j < count; j++)
{
/* code */
c[i][j]=i+j;
}
}
for (size_t i=0,count=10; i < count; i++)
{
/* code */
for (size_t j = 0; j < count; j++)
{
/* code */
cout<<i<<"+"<<j<<"="<<c[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
0+0=0 0+1=1 0+2=2 0+3=3 0+4=4 0+5=5 0+6=6 0+7=7 0+8=8 0+9=9
1+0=1 1+1=2 1+2=3 1+3=4 1+4=5 1+5=6 1+6=7 1+7=8 1+8=9 1+9=10
2+0=2 2+1=3 2+2=4 2+3=5 2+4=6 2+5=7 2+6=8 2+7=9 2+8=10 2+9=11
3+0=3 3+1=4 3+2=5 3+3=6 3+4=7 3+5=8 3+6=9 3+7=10 3+8=11 3+9=12
4+0=4 4+1=5 4+2=6 4+3=7 4+4=8 4+5=9 4+6=10 4+7=11 4+8=12 4+9=13
5+0=5 5+1=6 5+2=7 5+3=8 5+4=9 5+5=10 5+6=11 5+7=12 5+8=13 5+9=14
6+0=6 6+1=7 6+2=8 6+3=9 6+4=10 6+5=11 6+6=12 6+7=13 6+8=14 6+9=15
7+0=7 7+1=8 7+2=9 7+3=10 7+4=11 7+5=12 7+6=13 7+7=14 7+8=15 7+9=16
8+0=8 8+1=9 8+2=10 8+3=11 8+4=12 8+5=13 8+6=14 8+7=15 8+8=16 8+9=17
9+0=9 9+1=10 9+2=11 9+3=12 9+4=13 9+5=14 9+6=15 9+7=16 9+8=17 9+9=18
5. 类模板的扩展(特化)
类模板中所有的类型参数都进行了特化即全局特化
类模板中一部分的类型参数进行了特化即局部特化
template<class T, class D>class CMath{...};
全局特化
全类特化
template<>class CMath<int, double>{...};
成员特化
template<>int CMath<int, double>::sum{...}
局部特化
全类特化
template<class T>class CMath<T,short>{...};
成员特化
template<class T>int class CMath<T,short>::sum(){...}
5.1 全局特化
5.1.1 全类特化
特化一个类模板可以特化该类模板的所有成员函数,相当于重写写了一个针对某种特定数据类型的具体类
声明形式:
template<>
class 类模板名 <类型参数1,...>{
...;
};
例如:
template<class CMath<char* const>{...};
针对char* const
修改上面CMath模板类
#include <iostream>
#include <string.h>
using namespace std;
template<class T>
class CMath{
public:
CMath(T const& t1, T const& t2):m_t1(t1),m_t2(t2){}
T sum(){
return m_t1+m_t2;
}
private:
T m_t1;
T m_t2;
};
//全类特化
template<>class CMath<char* const>{
public:
CMath(char* const& t1, char* const& t2):m_t1(t1),m_t2(t2){}
char* const sum(){
return strcat(m_t1,m_t2);
}
private:
char* const m_t1;
char* const m_t2;
};
int main(int argc,char* argv[]){
char c1[100]="hello ";
char c2[100]="world";
CMath<char* const> cc(c1,c2);
cout<<cc.sum()<<endl;
return 0;
}
5.1.2 成员特化
类模板除了可以对整个类进行特化以外.可以只针对某部分成员函数进行特化,要点:帽子不能丢,类名引成员
声明形式
template<>
返回值类型 类模板名<类型参数1,...>::成员函数名(调用参数1,..){
...;
}
例如:
template<>
char* const CMath<char* const>::sum(...){
...;
}
示例:
#include <iostream>
#include <string.h>
using namespace std;
template<class T>
class CMath{
public:
CMath(T const& t1, T const& t2):m_t1(t1),m_t2(t2){}
T sum(){
return m_t1+m_t2;
}
private:
T m_t1;
T m_t2;
};
//成员特化
template<>
char* const CMath<char* const>::sum(){
return strcat(m_t1,m_t2);
}
int main(int argc,char* argv[]){
char c1[100]="hello ";
char c2[100]="world";
CMath<char* const> cc(c1,c2);
cout<<cc.sum()<<endl;
return 0;
}
hello world
5.2 局部特化
至少有两个及两个以上的类型参数,才会有局部特化
类模板的局部特化,除非必要否则尽量不要特化,因为特化版本过多容易引发编译器匹配歧义
局部特化也有全局特化和成员特化
示例:
#include <iostream>
#include <string.h>
using namespace std;
template<class T,class D>
class CMath{
public:
static void foo(){
cout<<"1:CMath<T,D>"<<endl;
}
};
//局部特化
template<class T>class CMath<T,short>{
public:
static void foo(){
cout<<"2:CMath<T,short>"<<endl;
}
};
int main(int argc,char* argv[]){
CMath<int,double>::foo();
CMath<int,short>::foo();
return 0;
}
1:CMath<T,D>
2:CMath<T,short>
#include <iostream>
#include <string.h>
using namespace std;
template<class T,class D>
class CMath{
public:
static void foo(){
cout<<"1:CMath<T,D>"<<endl;
}
};
//局部特化
template<class T>class CMath<T,short>{
public:
static void foo(){
cout<<"2:CMath<T,short>"<<endl;
}
};
template<class T>class CMath<T,T>{
public:
static void foo(){
cout<<"3:CMath<T,T>"<<endl;
}
};
template<class T,class D>class CMath<T*,D*>{
public:
static void foo(){
cout<<"4:CMath<T*,D*>"<<endl;
}
};
int main(int argc,char* argv[]){
CMath<int,double>::foo();//1:CMath<T,D>
CMath<int,short>::foo();//2:CMath<T,short>
CMath<int,int>::foo();//3:CMath<T,T>
//CMath<short,short>::foo;//出现歧义,2和3
CMath<int *,double *>::foo();//4:CMath<T*,D*>
//CMath<int*,int*>::foo();//出现歧义,4和3
return 0;
}
6.类型形参的缺省值
- 6.1 类目标的类型形参可以带缺省值
实例化类模板时,如果提供了类型实参则用所提供的的类型实参来实例化类模板,如果没有提供类型实参,则用相应的类型形参的缺省类型来实例化类模板 - 6.2 如果类模板的某个类型形参带有缺省值,那么他后面的类型形参都必须带缺省值(道理同C++缺省参数一样)
示例:
#include <iostream>
#include <string.h>
#include <typeinfo>
using namespace std;
template<class T,class D=short>class A{
public:
void print(){
cout<<"m_t:"<<typeid(m_t).name()
<<",m_d:"<<typeid(m_d).name()<<endl;
}
private:
T m_t;
D m_d;
};
int main(int argc,char* argv[]){
A<int,double> a;
A<int> a2;
a.print();//m_t:i,m_d:d
a2.print();//m_t:i,m_d:s
return 0;
}
7. 数值型的模板参数
- 7.1 类模板形参并不限于类型参数,普通数值也可以作为模板的参数
- 7.2 只限制为
int
类型size_t
类型
#include <iostream>
using namespace std;
template<class T,size_t S=10>
class Array{
public:
T& operator[](size_t i){
return m_arr[i];
}
size_t size(){
return S;
}
private:
T m_arr[S];
};
int main (int argc,char**argv){
Array<int,5> a;
for(int i=0;i<a.size();i++){
a[i]=i+1;
}
for(int i=0;i<a.size();i++){
cout<<a[i];
}
return 0;
}
12345
8. 模板技巧
8.1 模板型成员变量
成员变量,但其类型是一个类模板实例化的未知类,那它才可以称之为模板型成员变量
例如:
template<class T>
class Array{
...;
};//类模板
template<class D>
class Sum{ //类模板
public: Array<D> m_s;//模板型成员变量
}
示例:
#include <iostream>
using namespace std;
template<class T>
class Array{
public:
T& operator[](size_t i){
return m_arr[i];
}
private:
T m_arr[10];
};
template<class D> class Sum{
public:
Sum(Array<D>& a):m_s(a){}
D add(){
D d=0;
for(int i=0;i<10;i++){
d+=m_s[i];
}
return d;
}
private:
Array<D> m_s;//模板型成员变量
};
int main (int argc,char**argv){
Array<int> a;
for(int i=0;i<10;i++){
a[i]=i+1;
}
Sum<int> s(a);
cout<<s.add()<<endl;//55
}
8.2 模板型成员函数
模板型成员函数
类模板的成员函数模板
例如:
template<class T>
class CMath{
public:
template<class D>
void foo1(){//成员函数模板
...;
}
template<class D>
void foo2();
};
//类外实现:
template<class T>
template<class D>
void CMath<T>::foo2(){
....;
}
示例:
#include <iostream>
using namespace std;
template<class T>
class CMath{
public:
template<class D>void foo(){//模板型成员函数
cout<<"CMath<T>::foo<D>()"<<endl;
}
template<class G>void foo2();
private:
};
//类外实现foo2
template<class T> //帽子不能对
template<class G> //帽子不能丢
void CMath<T>::foo2(){ //类名引成员
cout << "CMath<T>::foo2<G>()"<<endl;
}
int main (int argc,char**argv){
CMath<int> c;
c.foo<int>();//CMath<T>::foo<D>()
c.foo2<int>();//CMath<T>::foo2<G>()
return 0;
}
8.3 模板型成员类型
类模板中嵌套类模板
例如:
template<class X>
class A{
public:
template<class Y>
class B{//模板型成员类型
public:
....;
};
};
类外实现:
template<class X>
class A{
public:
template<class Y>
class B;
}
template<class X>
template<class Y>
class A<X>::B{
...;
};
示例:
#include <iostream>
using namespace std;
template<class X>
class A{
public:
template<class Y>
class B{
public:
template<class Z>
class C;
};
private:
};
template<class X>
template<class Y>
template<class Z>
class A<X>::B<Y>::C{
public:
template<class T>void foo(){
cout << "A<X>::B<Y>::C<Z>::foo<T>()"<<endl;
}
};
int main (int argc,char**argv){
A<int>::B<int>::C<int> c;
c.foo<string>();//A<X>::B<Y>::C<Z>::foo<T>()
return 0;
}
8.4 模板型模板参数
类模板的模板形参也可以是类模板,也可以有缺省值
例如:
template<class T>
class Array{
...;
};
template < template<classD>class C=Array>class Sum{
...;
};
示例:
#include <iostream>
using namespace std;
template<class T>
class Array{
public:
T& operator[](size_t i){
return m_arr[i];
}
private:
T m_arr[10];
};
template< class D,template<class M>class C=Array >
class Sum{
public:
Sum(Array<D>& a):m_s(a){}
D add(){
D d=0;
for(int i=0;i<10;i++){
d+=m_s[i];
}
return d;
}
private:
C<D> m_s;//模板型成员变量
};
int main (int argc,char**argv){
Array<int> a;
for(int i=0;i<10;i++){
a[i]=i+1;
}
Sum<int,Array> s(a);
cout<<s.add()<<endl;//55
}
9. 模板典型错误
9.1 嵌套依赖
- 问题
- 由于模板要经过两次编译,在第一次编译模板代码时,类型形参的具体类型尚不明确,编译器将把类型形参的嵌套类型理解为某个未知类型的静态成员变量,因此,编译器看到使用这样的标识符声明变量时就会报告错误,这就叫嵌套依赖
- 解决方法
在类型形参的前面加上一个typename
标识符,意在告诉编译器其后是一个类模板的嵌套使用
示例:
#include <iostream>
using namespace std;
class A{
public:
class B{
void foo(){
cout<<"A::B::foo()"<<endl;
}
};
};
template<class T>
void Func(){
//T::B b;//报错,编译器第一次编译时,会将B认为是T中的一个静态成员的调用
typename T::B b;
//A::B b;
b.foo();
}
int main(){
return 0;
}
9.2 依赖模板参数访问成员函数模板
- 问题
利用位置类定义的对象来访问成员函数模板时,在编译器第一次编译时无法解析成员函数模板的类型参数列表的<>
而报告编译错误 - 解决方法
在成员函数模板之前添加template
关键字,意在告诉编译器其后是一个函数模板示例,编译器就可以正确理解<>
了
示例
#include <iostream>
using namespace std;
class A{
public:
template<class T>
void foo(){
cout<<"A::foo()"<<endl;
}
};
template<class D>
void Func(){
D d; //未知类定义的对象
//d.foo<int>();//未知类型调用包含了"<>"报错
d.template foo<int>();
}
int main(){
return 0;
}
9.3 类模板访问基类模板
- 问题
在子类模板中访问基类模板的成员时,编译器第一次编译时,只在子类模板和全局域中搜索使用的标识符号,不会到基类模板中搜索 - 解决方法
在子类模板中可以通过使用作用域限定符或显示使用this
指针,原理是变成了未知类型调用,编译器第一次编译就会认为合理有效
示例:
#include <iostream>
using namespace std;
// int m_i;
// void foo(){}
//基 类模板
template<class T>
class Base{
public:
int m_i;
void foo(){
cout<<"Base<T>::foo()"<<endl;
}
};
//子 类模板
template<class M,class D>//帽子写一块
class Derived:public Base<M>{
public:
void bar(){
// int m_i;
// void foo(){}
// m_i=100;//报错
// foo();//报错
/*解决1
Base<M>::m_i=100;
Base<M>::foo();
*/
///*解决2
this->m_i=100;
this->foo();
//*/
}
};
int main(){
return 0;
}
9.4 零值初始化
-
问题
- 基本类型不存在缺省构造函数,未被初始化的局部变量都具有一个不确定值(int a;//a值不确定)
- 类类型由于存在缺省构造函数,在未被初始化的情况下可以有一个确定的缺省参数(Integer a;//a值确定)
基于以上两点,就会在模板实现中产生不一致的语法语义
-
解决方法
如果希望模板中所有类型参数的变量.无论是类类型还是基本类型都以缺省方式获得初始化,就必须对其进行缺省的缺省构造T()
#include <iostream>
using namespace std;
class Integer{
public:
Integer():m_i(0){}
private:
int m_i;
friend ostream& operator<<(ostream& os,Integer const& that);
};
ostream & operator<<(ostream& os,Integer const& that){
return os<<that.m_i;
}
template<class T>
void Func(){
//T t;//当T为基本类型时,因没有进行初始化,t的值是随机的
T t();//基本类型使用零值进行初始化,类类型使用无参构造来初始化
cout<<"t="<<t<<endl;
}
int main(){
Func<double>();
Func<Integer>();
return 0;
}
9.5 类模板中的成员虚函数
问题:
类模板中的成员虚函数的实例化是在虚函数表生成之后,而虚函数表生成时就需要进行虚函数地址的绑定,但是虚函数此时并没有实际地址,因此发生了冲突
- 类模板中的普通成员函数可以是虚函数
即可以为类定义成员虚函数,和普通的成员虚函数一样,类模板的成员虚函数可以表现出多态性, - 类模板中的成员函数模板不可以是虚函数
- 根据成员虚函数的多态机制,需要一个虚函数表(表中保存成员虚函数的入口地址),而这个表时表一起在实例化模板时就产生的,类的成员函数模板的实例化(即产生真正的成员函数)需要编译器处理完调用后才会完成,这时候才会出现虚函数地址
#include <iostream>
using namespace std;
template<class T>
class Base {
public:
virtual void foo() {
cout << "base::foo" << endl;
}
private:
};
template<class M, class N>
class Derived : public Base<N> {
public:
virtual void foo() {
cout << "derived::foo" << endl;
}
};
int main() {
Derived<int, int> d;
Base<int> *b = &d;
b->foo(); //derived::foo
return 0;
}
总结
成员函数模板的延迟编译,阻碍了虚函数表的静态构建
- 理解
类模板中的成员虚函数的生成真正的函数实在实例化类模板之后,然而虚函数表则是指在实例化函数模板时就生成了,在此时类模板中的成员虚函数并没有被实例化,也就无法实现虚函数表的绑定,因此会有冲突
标准模板库(Standard Template Library)
1. STL的作用
- 首先STL并不是C++语言的一部分,他就是一个工具库,没有这个工具是,程序员写程序都要自己做
- STL模板库内部使用模板时操作更加泛化,STL内部三大部分构成(容器,泛型,算法)
2. 数组结构和链表结构
计算机存储数据的基本结构(数组结构,链表结构)
- 数组结构:数据保存在连续内存中
- 链表结构:数据按一个节点一个节点的存储,节点之间通过指针相连接
优缺点
数组结构
- 优点:随机访问方便,速度快,效率高
- 插入删除不方便,效率低(内存空间分布限制)
链表结构
- 优点:插入删除操作方便,效率高
- 缺点:随机访问不方便,效率低,往往就是通过在遍历过程中对给定的条件进行检测
总结
STL模板库 中所提供的容器类,结合了数组和链表的优缺点,是用户从诸如内存管理的细节中得以解脱(对数组和链表的操作进行了封装)
3. 十大容器
1> vector(向量)
类似数组(内部是线性存储)支持下标访问,在尾部添加和删除元素效率高,中间执行添加和删除操作也支持,但是效率低
成员函数
front()
/ back()
/ insert()
/ erase()
push_back()
/ pop_back()
/ empty()
/ clear()
size()
向量维护元素个数
resize()
设置向量元素个数
capacity()
获取向量容量
reserve()
设置向量的容量
初始化
- 向量中的元素被存储在一段连续的内存空间中
- 向量维护的内存空间会随着新元素的增加而自动增长
- 内存空间的连续性不会妨碍向量元素的增加,如果内存空间无法满足新元素的增加,向量会- - 开辟新的足够的连续的内存空间,并把原内存空间的数据复制到新的内存空间,释放原内存空间
- 向量的增加会伴随内存空间的分配和释放,元素复制和销毁等额外开销
- 如果能够在创建向量时,合理预分配一些空间,将在很大程度上缓解这些额外的开销
CODE
#include <iostream>
#include <utility>
#include <vector>
using namespace std;
class Student {
public:
explicit Student(string name = "") : m_name(std::move(name)) {}
Student(Student const &that) : m_name(that.m_name) {
cout << "用: " << that.m_name <<" "<<&that<<" "<< " 拷贝构造了: " << m_name << " " << this << endl;
}
~Student() {
cout << "析构了: " << m_name << " " << this << endl;
}
private:
string m_name;
};
int main() {
vector<Student> vs;
vs.push_back(Student("LiSi"));
// 用: LiSi 0x7ffeda273fc0 拷贝构造了: LiSi 0x557535dcbeb0
// 析构了: LiSi 0x7ffeda273fc0
vs.push_back(Student("Zs"));
// 用: Zs 0x7ffeda273fc0 拷贝构造了: Zs 0x557535dcc310
// 用: LiSi 0x557535dcbeb0 拷贝构造了: LiSi 0x557535dcc2f0
// 析构了: LiSi 0x557535dcbeb0
// 析构了: Zs 0x7ffeda273fc0
// vs.emplace_back(Student("LiSi"));
// vs.emplace_back(Student("Zs"));
getchar();
return 0;
// 析构了: LiSi 0x557535dcc2f0
// 析构了: Zs 0x557535dcc310
}
/*
* push_back()函数向容器中加入一个临时对象(右值元素)时,
* 首先会调用构造函数生成这个对象,然后条用拷贝构造函数将这个对象放入容器中, 最后释放临时对象。
* 但是emplace_back()函数向容器中中加入临时对象, 临时对象原地构造,没有赋值或移动的操作。
* */
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void print(string const &str, vector<int> &v) {
vector<int>::iterator begin = v.begin();
vector<int>::iterator end = v.end();
cout << "----------" << endl;
cout << str << endl;
for (vector<int>::iterator it = begin; it != end; ++it) {
cout << *it << ' ';
}
cout << endl;
cout << "----------" << endl << endl;
}
//比较器
class CMP {
public:
bool operator()(int i, int j) {
return i > j;
}
};
int main() {
vector<int> vi;
for (int i = 0; i < 10; ++i) {
vi.push_back(i);
}
print("push_back:", vi);
// ----------
// push_back:
// 0 1 2 3 4 5 6 7 8 9
// ----------
vi.pop_back();
print("pop_back:", vi);
// ----------
// pop_back:
// 0 1 2 3 4 5 6 7 8
// ----------
vi.insert(vi.begin(), 111);
print("insert:", vi);
// ----------
// insert:
// 111 0 1 2 3 4 5 6 7 8
// ----------
vi.erase(vi.begin());
print("erase:", vi);
// ----------
// erase:
// 0 1 2 3 4 5 6 7 8
// ----------
//print("start:", vi);
cout << vi.empty() << endl;//0
CMP cmp;
sort(vi.begin(), vi.end(), cmp);
print("sort", vi);
// ----------
// sort
// 8 7 6 5 4 3 2 1 0
// ----------
return 0;
}
2> deque(双端队列)
支持下标访问(头尾两端都可以添加/删除操作)
Code
#include <iostream>
#include <deque>
#include <algorithm>
using namespace std;
template<class T>
void print(string const &str, T &v) {
auto begin = v.begin();
auto end = v.end();
cout << "----------" << endl;
cout << str << endl;
for (auto it = begin; it != end; ++it) {
cout << *it << ' ';
}
cout << endl;
cout << "----------" << endl << endl;
}
//比较器
template<class C>
class CMP {
public:
bool operator()(C i, C j) {
return i > j;
}
};
int main() {
deque<int> de;
for (int i = 0; i < 10; ++i) {
de.push_front(100+i);
}
print("push_front",de);
// ----------
// push_front
// 109 108 107 106 105 104 103 102 101 100
// ----------
for (int i = 0; i < 10; ++i) {
de.push_back(10+i);
}
print("push_back",de);
// ----------
// push_back
// 109 108 107 106 105 104 103 102 101 100 10 11 12 13 14 15 16 17 18 19
// ----------
CMP<int> cmp;
sort(de.begin(),de.end(),cmp);
print("sort",de);
// ----------
// sort
// 109 108 107 106 105 104 103 102 101 100 19 18 17 16 15 14 13 12 11 10
// ----------
auto it = find(de.begin(),de.end(),101);
if (it!=de.end()){
de.erase(it);
}else{
cout<<"not find: "<<101<<endl;
}
print("find and erase 101",de);
// ----------
// find and erase 101
// 109 108 107 106 105 104 103 102 100 19 18 17 16 15 14 13 12 11 10
// ----------
return 0;
}
3> list(列表)
在任何位置添加和删除都很方便,不支持下标访问
成员
- 唯一化
将连续重复出现的元素唯一化
void unique(void);
- 排序(都是全局排序)
list中的sort是成员函数,在快排过程中使用修改指针的方式排序,而不是交换数据位置
void sort(void);
通过<
比较大小
template<class LESS>void sort(LESS less);
通过比较器比较大小 - 拆分
将参数列表中的部分或全部元素剪切到调用列表中
//将lst剪切到调用列表的pos前位置
template<class IT>void splice(IT pos, list& lst);
//将lst中的del指向的节点剪切到调用列表pos前位置
template<class IT>void splice(IT pos, list& lst, IT del);
//将lst[begin,end)部分的节点剪切到调用列表pos前位置
template<class IT>void splice(IT pos, list& lst, IT begin, IT end);
Code
#include <iostream>
#include <list>
using namespace std;
template<class T>
void print(string const &str, T &v) {
auto begin = v.begin();
auto end = v.end();
cout << "----------" << endl;
cout << str << endl;
for (auto it = begin; it != end; ++it) {
cout << *it << ' ';
}
cout << endl;
cout << "----------" << endl << endl;
}
//比较器
template<class C>
class CMP {
public:
bool operator()(C const& i, C const& j) {
return i > j;
}
};
int main() {
list<int> li;
for (int i = 0; i < 10; ++i) {
li.push_front(i);
li.push_front(i);
}
for (int i = 0; i < 10; ++i) {
li.push_front(i);
}
print("init", li);
// ----------
// init
// 9 8 7 6 5 4 3 2 1 0 9 9 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1 1 0 0
// ----------
//唯一化,只会删除相邻的重复节点
li.unique();
print("unique", li);
// ----------
// unique
// 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// ----------
li.sort();
print("sort",li);
// ----------
// sort
// 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9
// ----------
CMP<int> cmp;
li.sort(cmp);
print("sort with cmp",li);
// ----------
// sort with cmp
// 9 9 8 8 7 7 6 6 5 5 4 4 3 3 2 2 1 1 0 0
// ----------
li.unique();
print("unique2",li);
// ----------
// unique2
// 9 8 7 6 5 4 3 2 1 0
// ----------
list<int> lst;
for (int i = 0; i <10; ++i) {
lst.push_back(100+i);
}
print("lst is:",lst);
// lst is:
// 100 101 102 103 104 105 106 107 108 109
// ----------
li.splice(li.begin(),lst);
print("lst splice",lst);
// ----------
// lst splice
//
// ----------
print("li splice:",li);
// ----------
// li splice:
// 100 101 102 103 104 105 106 107 108 109 9 8 7 6 5 4 3 2 1 0
// ----------
return 0;
}
4> 堆栈(stack)
支持在一端存储和提取数据(压栈,弹栈)
概述
- 定义形式
stack<元素类型, [底层容器]> 堆栈对象(构造实参表)
- 底层容器(缺省为deque)
vector
deque
list
自己实现的容器
- 成员函数
栈成员函数 底层容器被调用函数
push()
->push_back()
pop()
->pop_back()
top()
->back()
size()
->size()
empty()
->empty()
clear()
->clear()
栈底层容器的实现
#include <deque>
template<class T, class D=deque<T> >
class stack{
public:
void push(T data){
m_d.push_back(data);
}
vodi pop(){
m_d.pop_back();
}
private:
D m_d;//唯一底层容器
};
5> 队列(queue)
支持从前端提取,后端压入元素
概述
- 定义形式
queue<元素类型, [底层容器]> 队列对象(构造实参表)
- 底层容器(缺省为deque)
deque
list
自己实现的容器
不可以是vector
- 成员函数
队列成员函数 底层容器被调用函数
push()
->push_back()
pop()
->pop_front()
back()
->back()
size()
->size()
front()
->front()
empty()
->empty()
clear()
->clear()
6> 优先队列(prioriyt_queue)
类似队列,但所提取的是具有最高优先级的元素(默认最大者优先)
概述
- 定义形式
prioriyt_queue<元素类型, [底层容器], [比较器类型]> 优先队列对象(构造实参表)
- 底层容器(缺省为
deque
)不可以是list
vector
deque
底层容器支持随机迭代 - 注意事项
- 优者先出,默认以大者为优先,也可以通过比较器定制(比较器必须是类)
- 如果没有
比较器
参数,默认使用<
运算符 - 并不是出队列时挑,而是进队列时就保证了有序(内部使用插入排序)
- 成员函数
优先队列成员函数
push()
pop()
top()
size()
empty()
clear()
7> 映射(map)
以key-value对的形式存储数据,以key的升序排列,key唯一(内部结构是红黑树)
概述
- 定义形式:
map<键类型,值类型> 映射对象
- 逻辑模型: 一一对应模型 键(信息索引) 值(信息内容)对,主要用于信息检索,性能可以到达对数级(O(logN)),类似二分法
- 物理模型: 平衡有序二叉树
- 键必须唯一
- 迭代过程实际上是关于键的中序遍历(LDR),键的升序
- 映射中的键是只读的
- 检索性能好,构建和修改性能较差,适用于结构稳定,但是需要频繁检索的操作
- 支持下标运算,用键作为下标,得到对应的值的引用,如果给出的键不存在,则据此键添加一个新节点,返回其值的引用,可直接赋值
存储单元是由键和值组成的pair类对象
映射的迭代器相当于指向pair对象的指针
template<class FIRST, class SECOND>
class pair{
public:
pair(FIRST const& f, SECOND const& s):first(f),seconf(s){}
FIRST first;//键
SECOND second;//值
};
成员函数
insert(pair<FIRST, SECOND>(key,value)
insert(make_pair(key,value))
迭代器 = find(键) //失败返回终止迭代器,并非全局函数,而是成员函数
8> 多重映射(multimap)
允许key重复出现的映射
概述和定义
表示一对多的逻辑关系,不支持下标运算符
multimap<键类型, 值类型> 多重映射对象;
9> 集合(set)
没有value的映射
与向量等基本容器相比最大的优势是排重
定义:
set<键类型> 集合对象
10> 多重集合(multiset)
没有value的多重映射
定义:
multiset<键类型> 多重集合对象;
4. 容器的分类
- 线型容器
向量,双端队列,列表,这类容器元素按照线性顺序排列,必须支持某种形式的next操作,以便从一个元素移动到下一个元素(迭代) - 适配器容器
堆栈,队列,优先队列,这类容器是对线性容器的一些接口加以屏蔽的产物 - 关联容器
映射,多重映射,集合,多重集合,这类容器根据一个元素相关联的key来存储或提取数据元素,存储是以key-value对的形式,按照key的升序(二叉树存储)
5. 十大容器的共同特点
- 所有容器都支持拷贝构造和拷贝赋值
- 相同类型的两个容器之间可以通过==进行相等性比较
- 容器存储的为数据的副本,这也就意味着存入容器中的对象应支持拷贝构造和拷贝赋值
- 通常情况下被存放到容器中的对象应支持无参构造
6. 迭代器
分类1
- 顺序迭代器
依次只能向后或者向前一步,只支持++或–运算 - 随机迭代器
既能一次向后或者向前迭代一步,也可以迭代多步,除了++和–也支持对整数的加减运算(iterator+5)
除了向量和双端队列以及优先队列支持随机迭代器以外,其余迭代器只支持顺序迭代器
分类2
- 正向迭代器
起始迭代器指向向量第一个元素位置,终止迭代器指向向量最后一个元素的下一个位置,增操作向容器尾部移动,减操作向容器的首部移动 - 反向迭代器
起始迭代器指向向量的最后一个元素的位置,终止迭代器指向向量的第一个元素的前一个位置,增操作向容器首部移动,减操作向容器尾部移动 - 四个迭代器类
iterator
/const_iterator
/reverse_iterator
/const_reverse_iterator
- 八个迭代器对象
begin()
/end()
begin()const
/end()const
rebegin()
/rend()
rebegin()const
/rend()const