一)函数模板是一种特殊的函数,可以使用不同的类型进行调用,对于功能相同的函数,不需要重复编写代码,并且函数模板与普通函数看起来很类似,区别就是类型可以被参数化
函数模板通过template与typename(换成class也可以)两个关键字来定义,如下
上边就定义了一个变量交换的函数模板,在使用函数模板时有两种方式
- 自动类型推到调用 Swap(a, b)
- 具体类型显示调用 Swap
需要注意的是 函数模板是不允许隐式类型转换的,调用时类型必须严格匹配
#include
using namespace std;
template
T1 add(T2 a, T3 b)
{
T1 ret;
ret = static_cast
return ret;
}
void main()
{
int c = 12;
float d = 23.4;
//cout << add(c, d) << endl; //error,无法自动推导函数返回值
cout << add
cout << add
system("pause");
}输入的结果为35.4和35
编译器会优先去调用普通函数,但是当函数模板有更好的匹配时或使用限定符<>时,编译器就会去匹配函数模板。
二)类模板
template <类型形式及参数>
class 类模板名
{ //类模板体 }
例子:
下面定义一个简单的输出类模板:
template
class C{
public:
type x;
C(type a){
x=a;
}
void show(){
cout<
类模板的调用:
Cp(a);
p.show();
template
class C{
public:
type1 x;
type2 y;
C(type1 a,type2 b){
x=a,y=b;
}
void show(){
cout<
};
C
p.show();
三)虚函数。虚函数的作用:实现多态性
在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数,用法格式为:virtual 函数返回类型 函数名(参数表) {函数体};实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。
class vehicle
{
protected:
int weight;
public:
vehicle(int wt = 0) { weight = wt;}
~vehicle() {}
void virtual Run() { cout << "Vehicle run." << endl; }
void virtual Stop() { cout << "Vehicle stop." << endl; }
};
class bicycle :public virtual vehicle
{
public:
bicycle(int wt = 0) { weight = wt;}
~bicycle() { }
void virtual Run() { cout << "Bicycle run." << endl; }
void virtual Stop() { cout << "Bicycle stop." << endl; }
};
class motocar :public virtual vehicle
{
public:
motocar(int wt = 0) { weight = wt;}
~motocar() {}
void virtual Run() { cout << "Motocar run." << endl; }
void virtual Stop() { cout << "Motocar stop." << endl; }
};
class motobicycle :public bicycle, public motocar
{
public:
motobicycle(int wt = 0) { weight = wt; }
~motobicycle() {}
void virtual Run() { cout << "Motobicycle run." << endl; }
void virtual Stop() { cout << "Motobicycle stop." << endl; }
};
使用虚函数并且直接调用,能正常调用各基类、派生类(包括多基类派生)中的函数。
如果在基类中没有将Run()声明为虚的,则p->Run()将根据指针类型(vehicle *)调用vehicle::Run()。指针类型在编译时已知,因此编译器在编译时,可以将Run()关联到vehicle::Run()。总之,编译器对非虚方法使用静态联编。
然而,如果将基类中将Run()声明为虚的,则p->Run()根据对象类型(vehicle)调用vehicle::Run()。在这个例子中,对象类型为vehicle,但通常只有在运行程序时才能确定对象的类型。所以编译器生成的代码将在程序执行时,根据对象类型将Run()关联到vehicle::Run()、bicycle::Run()、motocar::Run()或motobicycle::Run()。总之,编译器对虚方法使用动态联编。
四)纯虚函数。
之前学过虚函数,语法:virtual 返回值类型 函数名(参数列表),然后这个类也就变成的虚基类,然后子类重写父类的虚函数。
纯虚函数,语法:virtual 返回值类型 函数名(参数列表)=0,当类中有了纯虚函数,这个类也称为抽象类。抽象类特点:无法实例化对象,子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
纯虚函数是指在基类中声明的虚函数,它在基类中没有定义,但要求在任何派生类中都要定义自己的实现方法,在基类中实现纯虚函数的方法是函数原型后加“=0”,virtual void funtion1()=0;
引入纯虚函数的原因:
在有些情况下,基类本身生成的对象不太合理。比如说车作为一个基类可以派生出马车,汽车,自行车,但车本身生成得对象很不合理,所以引入纯虚函数的概念,则编译器要求在派生类中必须重写来实现多态性。包含纯虚函数的类叫抽象类,
例子:
class Base
{
public:
virtual void Examp() = 0;//纯虚函数
~Base()
{
cout << "父类的析构函数" << endl;
}
};
class Son:public Base
{
public:
void Examp()
{
cout << "重写了父类的纯虚函数" << endl;
}
~Son()
{
cout << "子类的析构函数" << endl;
}
};
五)类的三大特性:封装,继承和多态
(1)封装可以隐藏实现细节,使得代码模块化;封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。在面向对象编程上可理解为:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
封装可以隐藏实现细节,使得代码模块化;封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。
(2)继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。其继承的过程,就是从一般到特殊的过程。
通过继承创建的新类称为“子类”或“派生类”。被继承的类称为“基类”、“父类”或“超类”。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现
继承概念的实现方式有三类:实现继承、接口继承和可视继承。
1. 实现继承是指使用基类的属性和方法而无需额外编码的能力;
2. 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
3. 可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。
基类private成员在派生类中无论以什么方式继承都是不可见的。不可见是指基类的私有成员还是被继承到了派生类对象中,但是语法上派生类对像无法访问;在实际运用中一般使用都是public继承,不提倡使protetced/private继承,因为protetced/private继承下来的成员都只能在派生类的类里面使用,实际中扩展维护性不强;友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员;
六)类成员必须在类外初始化:static所修饰的变量
const修饰成员函数,该成员函数只能访问成员变量,不能修改,需要修改成员变量时,需要加mutable修饰成员 const修饰一个对象,称之为常对象,只能调用const修饰的成员函数;
菱形继承:菱形继承是多继承的一种特殊情况.解决方法如下(虚拟继承):
class Person
{
public:
string _name; // 姓名
};
class Student : virtual public Person
{
protected:
int _num; //学号
};
class Teacher : virtual public Person
{
protected:
int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
int main()
{
Assistant a;
a._name = "张三";
return 0;
}
(3)多态
class Person //成人
{
public:
virtual void fun()
{
cout << "全价票" << endl; //成人票全价
}
};
class Student : public Person //学生
{
public:
virtual void fun() //子类完成对父类虚函数的重写
{
cout << "半价票" << endl;//学生票半价
}
};
void BuyTicket(Person* p)
{
p->fun();
}
int main()
{
Student st;
Person p;
BuyTicket(&st);//子类对象切片过去
BuyTicket(&p);//父类对象传地址
}
C++11新增了两个关键字。用final修饰的虚函数无法重写。用final修饰的类无法被继承。final像这个单词的意思一样,这就是最终的版本,不用再更新了。
用final修饰的虚函数无法重写。用final修饰的类无法被继承。final像这个单词的意思一样,这就是最终的版本,不用再更新了。
class A final //A类无法被继承
{
public:
virtual void fun() final //fun函数无法被重写
{}
};
class B : public A //error
{
public:
virtual void fun() //立即报错
{
cout << endl;
}
};
被override修饰的虚函数,编译器会检查这个虚函数是否重写。如果没有重写,编译器会报错。
class A
{
public:
virtual void fun()
{}
};
class B : public A
{
public:
//这里我想重写fun,但写成了fun1,因为有override,编译器会报错。
virtual void fun1() override
{
cout << endl;
}
};
八)抽象类
1.概念:在虚函数的后面加上=0就是纯虚函数,有纯虚函数的类就是抽象类,也叫做接口类。抽象类无法实例化出对象。抽象类的子类也无法实例化出对象,除非重写父类的虚函数。
那么虚函数有什么用呢?
1,强制子类重写虚函数,完成多态。
2,表示某些抽象类。
虚函数是带有virtual的函数,虚函数表是存放虚函数地址的指针数组,虚函数表指针指向这个数组。对象中存的是虚函数指针,不是虚函数表。虚函数表也存放在了代码段
静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载
动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。
我们说的多态一般是指动态多态。
九)assert函数:专门为调试而准备的工具函数,在c++中要用到头文件#include
assert()需要有一个参数,括号内的语句为真,则没事,否则会报错。利用这个特性,我们可以利用它在某个程序中的关键假设不成立时立刻停止该程序的进行,从而避免更加严重的问题出现。
捕获异常
try{}
catch{}
十)内联函数inline。解决函数调用的效率问题,在函数声明前加上inline即可,