文章目录
多态
虚函数和多态
-
虚函数
在类的定义中,前面有virtual关键字的成员函数
class base{ virtual int get(); };
❗❗virtual关键字只用在类定义里的函数声明中,写函数体时候不用
int base::get()//不需要在get前面加virtual!!!
❗❗构造函数和静态成员函数不能是虚函数
❗❗虚函数和普通函数的本质差别:虚函数可以参与多态,静态函数不能
-
多态的表现形式一:指针
-
派生类的对象可以赋给基类指针
-
通过基类指针调用基类和派生类中的同名虚函数时候:
-
若指针指向一个基类的对象,则被调用的是基类的虚函数
-
若指针指向一个派生类的对象,则被调用的是派生类的虚函数
-
class Cbase(){ public: virtual void SomeVirtualFunction(){} }; class Cderived:public Cbase(){ public: virtual void SomeVirtualFunction(){} }; int main(){ Cderived Oderived; Cbase *p= &Oderived; p->SomeVirtualFunction(); //p指向派生类的对象,调用的是派生类的虚函数 return 0; }
-
-
多态的表现形式二:对象
- 派生类的对象可以赋给基类引用
- 通过基类引用调用基类和派生类中的同名虚函数时:
- 若该引用引得是一个基类的对象,那么被调用的是基类的虚函数
- 若该引用引得是一个派生类的对象,那么被调用的是派生类的虚函数
class Cbase(){
public:
virtual void SomeVirtualFunction(){}
};
class Cderived(){
public:
virtual void SomeVirtualFunction(){}
};
int main(){
Cderived Oderived;
Cbase &r=Oderived;
r.SomeVirtualFunction();//!!!引用调用函数时用"."!!!
//r引用的是派生类的对象,调用派生类的虚函数
}
-
多态的作用
增强程序的可扩充性**(程序需要修改或增加功能的时候,需要改动和增加的代码较少)**
使用多态的游戏程序示例
几何形体处理程序
#include<iostream>
#include<stdlib.h>
#include<math.h>
using namespace std;
//定义基类
class CShape{
public:
virtual double Area()=0;
//后面写了一个"=0",说明这是纯虚函数,连函数体都没有
virtual void PrintInfo()=0;
//因为我们要处理的图形只有圆形三角矩形几种,不存在CShape这样一种抽象的形状,所以不需要为CShape编写这两程序,这两程序在派生类中会分别定义
};
//定义派生类
class CRectangle:public CShape{
public:
int w,h;
virtual double Area();
virtual void PrintInfo();
};
class CCircle:public CShape{
public:
int r;
virtual double Area();
virtual void PrintInfo();
};
class CTriangle:public CShape{
public:
int a,b,c;
virtual double Area();
virtual void PrintInfo();
};
//实现派生类的成员函数
double CRectangle::Area(){
return w*h;
}
void CRectangle::PrintInfo(){
cout<<"Rectangle:"<<Area()<<endl;//输出的末尾要加endl!!
}
double CCircle::Area(){
return 3.14*r*r;
}
void CCircle::PrintInfo(){
cout<<"Circle:"<<Area()<<endl;
}
double CTriangle::Area(){
double p=(a+b+c)/2.0;
return sqrt(p*(p-a)*(p-b)*(p-c));
}
void CCircle::PrintInfo(){
cout<<"Triangle:"<<Area()<<endl;
}
//存放不同类型的几何形体
CShape *pShapes[100];
int MyCompare(const void *s1,const void *s2);
//用三个数组来存浪费空间,难以增改,难以实现排序等进阶功能
//所以我们使用多态来存储
//pShapes数组中的元素都是基类指针,由于基类指针能够指向派生类对象,我们可以在后面把指针指向new出来的派生类对象
//主函数
int main(){
int i;int n;
CRectangle *pr;CCircle *pc;CTriangle *pt;
cin>>n;
for(i=0;i<n;i++){
char c;
cin>>c;
switch(c){
case'R':
pr=new CRectangle();
cin>>pr->w>>pr->h;
pShapes[i]=pr;
break;
case 'C':
pc=new CCircle(); cin>>pc->r;
pShapes[i]=pc;
break;
case'T':
pt=new CTriangle();
cin>>pt->a>>pt->b>>pt->c;
pShapes[i]=pt;
break;
}
}
qsort(pShapes,n,sizeof(CShape*),MyCompare);
for(i=0;i<n;i++)
pShapes[i]->PrintInfo();
return 0;
}
//为什么变量前面要加关键字void*?然后可以将指向任何类型数据的指针赋给这个void*类型指针
int MyCompare(const void *s1,const void *s2){
double a1,a2;
CShape**p1;
CShape**p2;
//一定要写两个*,不能用"*s1"来取得s1指向的内容
p1=(CShape**)s1;
p2=(CShape**)s2;
//s1,s2指向数组中的元素,数组中元素的类型是CShape*
//因此p1,p2是指向指针的指针,类型为CShape**
a1=(*p1)->Area();//*p1的类型是CShape*是基类指针
a2=(*p2)->Area();
if(a1<a2)
return -1;
else if(a2<a1)
return 1;
else
return 0;
}
*下面是 qsort()
函数的声明。
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
- base – 指向要排序的数组的第一个元素的指针。
- nitems – 由 base 指向的数组中元素的个数。
- size – 数组中每个元素的大小,以字节为单位。
- compar – 用来比较两个元素的函数。
虚析构函数,纯虚函数,抽象类
虚析构函数:
class son{
public:
~son(){cout<<"bye from son"<<endl;}
};
class grandson:public son{
public:
~grandson(){cout<<"bye from grandson"<<endl;}
};
int main()
{
son *pson;
pson=new grandson();
//new一个对象的时候,如果对象的构造函数不需要参数就要写一对括号,跟new一个数据不一样
delete pson;
return 0;
}
output:
bye from son
new出来的grandson没有被删除!!!,反而执行了son的析构函数,然而我们并没有定义son类的对象
解决办法:
把基类son的析构函数变成虚函数
纯虚函数和抽象类:
-
包含纯虚函数的类就是抽象类
-
抽象类不能创建自己的对象
-
抽象类的指针/引用可以指向其派生类的对象
-
在抽象类的成员函数内可以调用纯虚函数,但是在构造函数或析构函数内部 不能调用纯虚函数。
-
如果一个类从抽象类派生而来,那么当且仅当它实现了基类中的所有纯虚函数,它才能成为非抽象类。
输入输出和模板
函数模板
- 例子-求数组最大元素的函数模板
template<class T>
T MaxElement(T a[],int size){
T tmpMax=a[0];
for(int i=1;i<size;i++)
if(tmpMax<a[i])
tmpMax=a[i]
return tmpMax;
}
- 例子-不通过参数实例化函数模板
#include<iostream>
using namespace std;
template<class T>
T Inc(T n){
return 1+T;
//由于Inc的返回值是T类型的,这里要对+进行重载
}
int main(){
cout<<Inc<double>(4)/2;
//用<double>直接规定T是double类型
}
-
函数模板是可以重载的,只要他们的形参表和类型参数表不一样即可
-
函数模板和函数的次序:
参数匹配+普通函数----->参数匹配模板函数----->参数经自动类型转换后能匹配的普通函数
类模板
编写模板是为了实现泛型程序设计,即写出一个类/函数后,可以作用与多种数据类型
类模板的定义
template<typename 类型参数,typename 类型参数,...>
class 模板名字
{
成员变量;
成员函数;
}
类模板内成员函数定义
template<typename 类型参数,typename 类型参数,...>
(返回值)类型参数 模板名字 <类型参数名字,类型参数名字,...>::成员函数名字(参数表)
{
...
}
类模板定义对象的写法
模板名字<实际类型参数表> 对象名(构造函数实参表);
示例:Pair类模板
#include<iostream>
#include<string.h>
using namespace std;
//类型参数就是这个类需要的参数的类型
//其个数就该类需要的参数个数
template<typename T1,typename T2>
class Pair
{
public:
T1 key;//关键字
T2 value;//值
Pair(T1 k,T2 v):key(k),value(v){};
//构造函数及其初始化列表
bool operator < (const Pair<T1,T2>&p)const;
//对"<"进行重载,其形参是pair模板定义的类的常引用
//我们希望这个"<"是不变的,最后加个const
};
template<typename T1,typename T2>
bool Pair<T1,T2>::operator < (const Pair<T1,T2>&p)const
{
return key<p.key;
}
int main()
{
Pair<string,int> student("Tom",19);
//对象的名字是:student
//注意:由类模板"pair"生成类"pair<string,int>",再由这个类生成对象"student"
cout<<student.key<<" "<<student.value;
return 0;
}
output:
Tom 19
-
类模板的实例化:编译器由类模板生成类的过程
-
同一个模板实例化出的类是不兼容的,他们根本就是两个类
Pair<string,int> *p; Pair<string,double> a; p=a;//报错
函数模板作为类模板成员
#include<iostream>
using namespace std;
template<typename T>
class A
{
public:
//注意这里不能写T,因为这里成员函数的类型参数相对类的类型参数来说是形参
template<typename T2>
void Func(T2 t){cout<<t;}
};
int main()
{
A<int> a;
//类模板名: A
//类名: A<int>
//对象名: a,其需要的参数类型为int(虽然下面的定义里我没有用这个参数)
a.Func('K');//成员函数模板Func被实例化,其参数类型为char
a.Func("hello");//成员函数模板Func再次被实例化,其参数类型为const char*
return 0;
}
output:
Khello
-
A<int>
这个类通过上面对函数模板Func
的两次实例化有了两个名为Func
的成员函数:Func(char c);//赋给该函数的值是:K Func(const char* string);//赋给该函数的值是:指向字符串"hello"的指针
类模板的类型参数表中可以出现非类型参数:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nYhPavV8-1636871681968)(assets/image-20210308125420974.png)]
标准模板库STL
概述
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cOnJtLQA-1636871681969)(assets/image-20210318145622473.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k2f3IK7j-1636871681969)(assets/image-20210318145709507.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MrcebDjt-1636871681970)(assets/image-20210318145751989.png)]
迭代器
-
用于指向顺序容器/关联容器中的元素
-
用法和指针类似
-
有const和非const两种
-
通过迭代器可以读取其指向的元素
-
通过非const迭代器可以修改其指向的元素
代码:
定义一个容器类的迭代器的方法:
容器类名::iterator 变量名;
容器类名::const_iterator 变量名;
访问一个迭代器指向的元素:
*迭代器变量名
迭代器示例:
#include<vector>
#include<iostream>
using namespace std;
int main()
{
vector<int> v;
//v是一个存放int类型元素的动态数组,一开始里面没有元素
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
//正向迭代器
vector<int>::const_iterator i;
//常量迭代器,只能读取元素,不能修改元素
for(i=v.begin();i!=v.end();i++)
//begin()获取容器第一个元素的位置
//end()获取容器最后一个元素后面的位置
cout<<*i<<",";
cout<<endl;
//反向迭代器
vector<int>::reverse_iterator r;
for(r=v.rbegin();r!=v.rend;r++)
//rbegin()获取容器最后一个元素的位置
//rend()获取容器第一个元素前面的位置
cout<<*r<<",";
cout<<endl;
//非常量迭代器
vector<int>::iterator j;
for(j=v.begin();j!=v.end();j++)
*j=100;//用非常量迭代器修改指向的元素
for(i=v.begin();i!=v.end();i++)
cout<<*i<<",";//用常量迭代器读取指向的元素
cout<<endl;
}
两种不同的的迭代器:
-
双向迭代器p&p1
-
++p,p++
-
–p,p–
-
p*(实际上返回值是p指向的对象的引用)**
-
p=p1
-
p==p1;p!=p1
-
-
随即访问迭代器p&p1
- 双向迭代器的所有操作
- p+=i(将p向后移动i个元素)
- p-=i
- p+i (p+i的值=指向p后面第i个元素的迭代器)
- p-i
- p[i](p[i]的值=p后面第i个元素的引用)
- p<p1;p<=p1;p>p1;p>=p1
容器
顺序容器
vector(动态数组)
vector上的操作
#include<iostream>
#include<vector>
using namespace std;
template<class T>
void PrintVector(T s,T e)
{
for(;s!=e;++s)
cout<<*s<<" ";
cout<<endl;
}
int main()
{
int a[5]={1,2,3,4,5};
vector<int> v(a,a+5);
//将数组a中下标从0到4的元素都拷贝到v里面
cout<<v.end()-v.begin();
//vector的迭代器是随即迭代器可以相减
//上面输出的v中元素的个数:5
v.insert(v.begin()+2,13);
PrintVector(v.begin(),v.end());
//在v下标为2的位置插入一个元素13,后面的元素全部后推
v.erase(v.begin()+2);
PrintVector(v.begin(),v.end());
//删除位于v中下标为2的元素
vector<int> v2(4,100);
//v2有4个元素,都是100
v2.insert(v2.begin(),v.begin()+1,v.begin()+3);
//将v的一段插入v2开头
//v下标为1的元素到v下标为3的元素(不包括v下标为3的元素!!!)
PrintVector(v2.begin(),v2.end());
v.erase(v.begin()+1,v.begin()+3);
PrintVector(v.begin(),v.end());
//删除v上的一个区间,即2,3
return 0;
}
用vector实现二维数组
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<vector<int> >v(3);
//v中有3个元素,每个元素都是vector<int>空容器
//注意vector<int>后面一定要加一个空格,不然编译器会把两个尖括号当成右移运算符
//下面往v的元素里添加元素
for(int i=0;i<v.size();++i)
for(int j=0;j<4;++j)
v[i].push_back(j);
//下面显示v的元素里的元素
for(int i=0;i<v.size();++i){
for(int j=0;j<v[i].size();++j)
cout<<v[i][j]<<" ";
cout<<endl;
}
return 0;
}
deque(双向队列)
-
所有vector的操作都适用于deque
-
deque多了:
push_front
:将元素插入到前面pop_front
:删除最前面的元素
list(双向列表)
成员函数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xglhscdu-1636871681970)(assets/image-20210318143445110.png)]
list上的操作
#include<list>
#include<iostream>
#include<algorithm>
using namepsace std;
class A
{
private:
int n;
public:
A(int n_){n=n_;}
friend bool operator<(const A & a1,const A & a2);
friend bool operator==(const A & a1,const A & a2);
friend ostream & operator << (ostream & o,const A & a);
}
bool operator<(const A & a1,const A & a2)
{return a1.n<a2.n}
bool operator==(const A & a1,const A & a2)
{return a1.n==a2.n}
ostream & operator << (ostream & o,const A & a)
{o<<a.n;return o;}
//下面定义一个函数模板,其参数是"类型可变列表的引用"
template <class T>
void PrintList(const list<T> & lst)//参数是"类型可变列表的引用"
{
typename list<T>::const_iterator i;
//typename用来说明"list<T>::const_iterator"描述的是个类型
i=lst.begin();
for(i=lst.begin();i!=lst.end();i++)
cout<<*i<<",";
}
int main()
{
list<A> lst1,lst2;
//定义了两个元素为A类对象的列表
...
PrintLsit(lst1);
}
容器适配器
容器适配器上没有迭代器
stack
-
是后进先出的数据结构,
-
可以
push
插入pop
删除top
返回栈顶元素的引用
-
可用vector,list,deque来实现,缺省情况下,用deque实现。(vector、deque实现的性能比list好)
template<class T,class Container=deque<T>> //第一个类型参数是栈里的元素类型 //第二个类型参数是栈用什么容器来实现,并表示缺省情况下是deque class stack{ ... };
queue
-
是先进先出的数据结构,
-
可以
push
插入(发生在队尾)pop
删除front
返回栈顶元素的引用back
返回队尾元素的引用
-
可用list,deque来实现,缺省情况下,用deque实现。(vector、deque实现的性能比list好)
template<class T,class Container=deque<T>> //第一个类型参数是容器适配器里的元素类型 //第二个类型参数是容器适配器用什么容器来实现,并表示缺省情况下是deque class stack{ ... };
priority_queue
-
可用vector和deque实现,缺省情况下用vector实现
template<class T,class Container=vector<T>,class Compare=less<T>> //第一个类型参数是容器适配器里的元素类型 //第二个类型参数是容器适配器用什么容器来实现,并表示缺省情况下是vector //第三个类型参数是优先队列的元素比较器用什么来实现,并表示缺省情况下是小于号 class priority_queue;
-
priority_queue通常用堆排序技术实现,保证最大的元素总是在最前面,即执行pop操作时,删除的是最大的元素;执行top操作时,返回的是最大元素的引用,默认的元素比较器是less
-
push
,pop
时间复杂度:O(logn) -
top
时间复杂度:O(1)
所有容器适配器中都有成员函数:
- empty():用于判断适配器是否为空
- size():用于返回适配器中元素个数
算法
类型参数Pred
大多重载的算法都是有两个版本的:
- 一个是
- 用“==”判断元素是否相等
- 用“<”来比较大小
- 另一个多出来一 个类型参数
Pred
,以及函数形参Pred op
- 表达式“op(x,y)”的返回值是ture,则x等于y
- 表达式“op(x,y)”的返回值是false,则x小于y
不变序列算法
- 不会修改算法作用的容器或对象
- 适用于顺序容器和关联容器
- 时间复杂度:O(n)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7NFIgeCl-1636871681970)(assets/image-20210315213723396.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I4QEWi1p-1636871681971)(assets/image-20210315213758362.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4rujTamB-1636871681971)(assets/image-20210315213935514.png)]
find
很重要
排序算法
- 需要随机访问迭代器的支持
- 不适用于关联容器和list
- 时间复杂度:O(log(n))
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QsCl9u2T-1636871681972)(assets/image-20210315215204555.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZOqEcQ2f-1636871681972)(assets/image-20210315215228540.png)]
sort:快速排序
-
模板
template<class Ranlt> void sort(Ranlt first,Ranlt last);
- 按升序排列
- 判断x是否应该比y靠前,就看x<y是否为true
template<class Ranlt,class Pred> void sort(Ranlt first,Ranlt last,Pred pr);
- 按升序排列
- 判断x是否应该比y靠前,就看pr(x<y)是否为true
应用:
#include<iostream>
#include<algorithm>
using namespace std;
//定义Pred pr
class MyLess
{
public:
bool operator()(int n1,int n2)
{
return(n1%10)<(n2%10);
}
};
int main()
{
int a[]={14,2,9,111,78};
int i;
sort(a,a+5,MyLess());//按个位数大小排序
for(i=0;i<5;i++)
cout<<a[i]<<" ";
cout<<endl;
sort(a,a+5,greater<int>());//按降序排序
for(i=0;i<5;i++)
cout<<a[i]<<" ";
}
注意:
-
sort实际上是快速排序,时间复杂度为:O(n*log(n))
- 平均性能最优
- 最坏的情况性能非常差
-
stable_sort实际上是归并排序
- 能保证相等元素之间的先后次序
- 存储空间足够时,时间复杂度:n*log(n)
- 存储空间不够时,时间复杂度:n*log(n)*log(n)
-
list只能使用内置的排序算法:
list::sort