实现多态的必要条件
1>
函数重写
2>
虚函数
3>
父类指针指向子类对象
函数重写
在子类中定义与父类原型相同的函数
原型相同:函数名、参数(参数个数、参数类型)必须一致,但是,函数体不同
注意:函数重写发生在父子类直接
辨析:重载、重写
重载:函数名相同、形参列表必须不同,发生在同一个类中
重写:函数名相同、形参列表必须相同,发生在不同类中
虚函数
当父类指针或者引用指向子类对象,并且想要通过父类指针或引用调用
子类中的函数时,那么需要将该函数在父类中进行定义,并且设置为虚函数。
其他:
不使用或者没有父类的指针或引用指向子类对象时,则用不到虚函数,
父类一旦设置了虚函数,就会形成一个虚函数表,子类的重写函数都会记到表里;设置父类的引用目标为子类时,通过引用目标访问到的就是子类的函数。
#include <iostream>
using namespace std;
class Animal
{
protected:
string name;
string food; //食物
public:
Animal() {}
Animal(string n, string f):name(n), food(f) {}
virtual void voice()
{
//虚函数表,virtual的功能是建立一个虚函数表,并创建一个指针指向这个表
//子类继承时,也会继承这个虚函数表,当子类有自己的函数,子类定义的对象会通过
//指针去表中找自己对应的函数,如果此时还想用父类的函数,可以在函数名前加作用域限定符
}
//纯虚函数存在,该类为抽象类(不能实例化类对象。但是可以设置引用,指针);
};
class Sheep : public Animal
{
private:
int leg; //腿的个数
public:
Sheep() {}
Sheep(string n, string f, int l):Animal(n, f), leg(l) {}
void voice()override
{
//override关键字用来确保虚函数存在
cout<<name<<" "<<food<<" "<<leg;
cout<<" mie mie mie..."<<endl;
}
};
class Wolf : public Animal
{
private:
int tail; //尾巴的个数
public:
Wolf() {}
Wolf(string n, string f, int t):Animal(n, f), tail(t) {}
void voice() override
{
cout<<name<<" "<<food<<" "<<tail;
cout<<" wu wuu wuuu..."<<endl;
}
};
void print(Animal & a)
{
//引用的作用,实现多态
a.voice();
}
int main()
{
Sheep s("xiyy", "gress", 4);
s.voice();
s.Animal::voice();
//此处通过父类的作用域限定符可以调用父类的函数
cout<<"7777"<<endl;
Wolf w("xiaohh", "meat", 1);
w.voice();
cout<<"*******************************"<<endl;
print(s); //羊的叫声
print(w); //狼的叫声
return 0;
}
虚析构函数
在以后开发过程中,如果定义的类作为基类使用,那么要将其析构函数设置成虚析构函数
功能:争取指引
delete
关键字释放子类空间的
virtual ~Person
() {
cout
<<
"Person::xigou"
<<
endl
;}
抽象类:不能实例化对象,但是可以定义引用和指针
virtual
void
show
()
=
0
;
//纯虚函数,
在类中,只有声明,没有定义
类中包含至少一个纯虚函数,那么该类便是抽象类
#include <iostream>
using namespace std;
class Animal //如果类中有纯虚函数,那么该类便是抽象类
//抽象类不允许实例化对象,但是可以用抽象类的指针或引用
{
protected:
string name;
string food; //食物
public:
Animal() {cout<<"Animal::construct"<<endl;}
Animal(string n, string f):name(n), food(f) {}
virtual void voice() = 0; //纯虚函数
//所在类,无需通过实例调用该函数,只是为了让子类进行重写的函数
};
class Sheep : public Animal
{
private:
int leg; //腿的个数
public:
Sheep() {}
Sheep(string n, string f, int l):Animal(n, f), leg(l) {}
void voice() override
{
cout<<name<<" "<<food<<" "<<leg;
cout<<" mie mie mie..."<<endl;
}
};
class Wolf : public Animal
{
private:
int tail; //尾巴的个数
public:
Wolf() {}
Wolf(string n, string f, int t):Animal(n, f), tail(t) {}
void voice() override
{
cout<<name<<" "<<food<<" "<<tail;
cout<<" wu wuu wuuu..."<<endl;
}
};
void print(Animal & a)
{
a.voice();
}
int main()
{
Sheep s("xiyy", "gress", 4);
s.voice();
Wolf w("xiaohh", "meat", 1);
w.voice();
cout<<"*******************************"<<endl;
print(s); //羊的叫声
print(w); //狼的叫声
cout<<"*******************************"<<endl;
//Animal a; //报错,抽象类是不允许实例化对象的
//a.voice();
Animal &a=s; //抽象类通过引用实例化对象
a.voice();
return 0;
}
函数模板
1>
有时程序员定义函数时,由于函数参数类型不同,或者参数的返回值类型不
同,会导致定义多个功能性质相同的函数,造成代码的冗余,此时我们可以考虑定义
函数模板来实现
2>
所谓函数模板,就是在定义函数时,函数参数和返回值的类型都不给定,而
是由函数调用时实参进行传递过来使用
3>
定义函数模板时跟普通函数有所区别,但是调用函数时,隐式调用跟普通函
数调用没有区别,系统会根据传递参数的类型,自动推导函数的类型。
4>
也可以进行显式调用,在调用函数名后加个尖括号,写上要传递的类型:尖
找尖,圆找圆
5>
一个模板只能对应下面一个函数,如果要再定义目标函数,需要重新定义模
板参数
/*
* template <typename T>
* template
:定义模板的关键字,说明要开始定义模板了
* <>:
里面时类型形参名,里面可以有多个类型的参数,中间用逗号隔开
* template <typename T1
,
typename T2
,。。。,
Tn >
* typename:
声明类型的形参,也可以用关键字
class
* */
#include <iostream>
using namespace std;
//定义函数模板,参数和返回值的类型不给定
//而是有函数调用时,实参传递过来
template <typename T>
T my_sum(T m,T n)
{
return m+n;
}
int main()
{
cout << my_sum(1,2)<< endl; //隐式调用,自动识别类型
cout << my_sum<double>(1.5,2)<< endl;//显示调用,跟上类型名
//c++返回值只输出有效数字,如 double (2 +3)=5
cout<<"00000000"<<endl;
cout << my_sum(1.2,2.1)<< endl;
// "hi" 加string转成c++风格字符串 ;c风格是字符串类型
cout << my_sum(string("hi "),string("hello"))<< endl;
return 0;
}
T
my_sum
(
T m
,
int
n
)
//
函数模板的特化
模板类
template
<
typename T
>
class Node
{
private
:
T data
;
//
数据域
Node
*
next
;
//
指针域
public
:
Node
() {}
Node
(
T d
):
data
(
d
),
next
(
NULL
) {}
void
show
();
}
//
后面只要用到
Node
,就要加
<T>,
并且在前面要声明模板
template
<
typename T
>
void
Node
<
T
>
::
show
()
{
cout
<<
data
<<
endl
;
}
#include <iostream>
using namespace std;
template<typename T>
class Node
{
private:
T data; //数据域
Node *next; //指针域
public:
Node() {}
Node(T d):data(d), next(NULL) {}
void show();
};
//后面只要用到Node,就要加<T>,并且在前面要声明模板
template<typename T>
void Node<T>::show()
{
cout<<data<<endl;
}
int main()
{
//必须显性调用
Node<int> p1(10); //10
p1.show();
Node<char> p2('a'); //a
p2.show();
Node<string> p3("hello"); //hello
p3.show();
return 0;
}
核心机制:延时编译、二次编译
当编译器第一次遇到函数模板或者类模板时,由于类型不明确,所以只是对除了类型之外的其他的相关语法的检查,如果检查无误,则会生产模板的内部实现机制。
当编译器第二次遇到函数模板或者类模板时,会根据传过来的实参的类型,确定模板类型,再一次进行语法检查,如果无误,则会生产函数或者类的原型。
STL标准模板库
C++ STL (Standard Template Library
标准模板库
)
是通用类模板和算法的集合,它提供给程序员一些标准的数据结构的实现如
queues
(
队列
),
lists
(
链表
),
和
stacks
(
栈
) 等.
vector 函数运用
#include <iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v1; //无参构造一个vector对象
//判断容器内是否为空
if(v1.empty())
{
cout<<"empty"<<endl;
}else
{
cout<<"not empty"<<endl;
}
//求容器大小
cout<<"size of v1:"<<v1.size()<<endl;
//求容器当前最大容量
cout<<"capacity of v1:"<<v1.capacity()<<endl;
//进行尾插
for(int i=0; i<5; i++)
{
v1.push_back(i+10);
cout<<"capacity of v1:"<<v1.capacity()<<endl;
}
cout<<"size of v1:"<<v1.size()<<endl;
//尾删
v1.pop_back();
cout<<"size of v1:"<<v1.size()<<endl;
for(int i=0; i<v1.size(); i++)
{
//cout<<v1.at(i)<<" ";
cout<<v1[i]<<" ";
}
cout<<endl;
//找到第一个和最后一个
cout<<"the first one:"<<v1.front()<<endl;
cout<<"the last one:"<<v1.back()<<endl;
//使用迭代器遍历容器
for( auto it=v1.begin(); it!=v1.end(); it++ )
//for( vector<int>::iterator it=v1.begin(); it!=v1.end(); it++ )
{
cout<<*it<<" ";
}
cout<<endl;
//使用枚举for循环输出容器信息
for(int va:v1)
{
cout<<va<<" ";
}
cout<<endl;
//清空所有元素
v1.clear();
cout<<"size of v1:"<<v1.size()<<endl;
cout<<"***************************************"<<endl;
int arr[10] = {2,3,6,8,4,5,1,9,37,7};
vector<int> v3(arr, arr+6);
//从数组开始到第6个位置,放到容器中
for(auto val:v3)
{
cout<<val<<" ";
}
cout<<endl;
return 0;
}