C++学习(多态性)
多态性概述
• 多态的概念
▫ 在C++语言中,多态是指具有不同功能的函数可以使用同一个函数名,这样就可以用一个函数名调用不同内容的函数。
• 多态的种类
▫ C++语言支持的多态性可以按其实现的时机分为编译时多态和运行时多态两类。
• 绑定是指把一个标识符名和一个存储地址联系在一起的过程
• 编译时的多态
▫ 绑定工作在编译连接阶段完成的情况称为静态绑定。
▫ 重载(函数、操作符),模板(函数、类)
• 运行时的多态
▫ 绑定工作在程序运行阶段完成的情况称为动态绑定。
▫ 虚函数
运算符重载
• C++ 几乎可以重载全部的运算符,但只能够重载C++中已经支持的。
▫ 不能重载的运算符举例:“.”、“.*”、“::”、“?:”(带点的都不能重载)
• 重载之后运算符的优先级和结合性都不会改变。
• 运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。
• 两种重载方式:重载为类的非静态成员函数和重载为非成员函数。
运算符重载为成员函数
• 声明形式
函数类型operator 运算符(形参)
{
…
}
• 重载为类成员函数时(含有this指针指向本身)
参数个数=原操作数个数**-1** (后置++、–除外)
• 重载为非成员函数时参数个数=原操作数个数,且至少应该有一个自定义类型的形参。
• 双目运算符B
▫ 如果要重载B 为类成员函数,使之能够实现表达式oprd1 B oprd2,其中oprd1 为A 类对象,则B 应被重载为A 类的成员函数,形参类型应该是oprd2 所属的类型。
▫ 经重载后,表达式oprd1 B oprd2 相当于
oprd1.operator B(oprd2)
#include <iostream>
using namespace std;
class Complex { //复数类定义
public: //外部接口
Complex(double r = 0.0, double i = 0.0) :
real(r), imag(i) { } //构造函数
Complex operator + (const Complex &c2) const;
//运算符+重载成员函数
Complex operator - (const Complex &c2) const;
//运算符‐重载成员函数
void display() const; //输出复数
private: //私有数据成员
double real; //复数实部
double imag; //复数虚部
};
Complex Complex::operator + (const Complex &c2)
const { //重载运算符函数实现
return Complex(real + c2.real, imag +
c2.imag); //创建一个临时无名对象作为返回值
}
Complex Complex::operator - (const Complex &c2)
const { //重载运算符函数实现
return Complex(real - c2.real, imag -
c2.imag); //创建一个临时无名对象作为返回值
}
void Complex::display() const {
cout << "(" << real << ", " << imag << ")" <<
endl;
}
int main() { //主函数
Complex c1(5, 4), c2(2, 10), c3; //定义复数类的对象
cout << "c1 = "; c1.display();
cout << "c2 = "; c2.display();
c3 = c1 - c2; //使用重载运算符完成复数减法
cout << "c3 = c1 - c2 = "; c3.display();
c3 = c1 + c2; //使用重载运算符完成复数加法
cout << "c3 = c1 + c2 = "; c3.display();
system("pause");
return 0;
}
运算符成员函数的设计
• 前置单目运算符U
▫ 如果要重载U 为类成员函数,使之能够实现表达式U oprd,其中oprd 为A类对象,则U 应被重载为A类的成员函数,无形参。
▫ 经重载后,表达式U oprd 相当于oprd.operator U()
• 后置单目运算符++和–
▫ 如果要重载++或–为类成员函数,使之能够实现表达式oprd++ 或oprd-- ,其中oprd 为A类对象,则++或-- 应被重载为A 类的成员函数,且具有一个int 类型形参。
▫ 经重载后,表达式oprd++ 相当于oprd.operator++(0)
#include <iostream>
using namespace std;
class Clock { //时钟类定义
public: //外部接口
Clock(int hour = 0, int minute = 0, int second = 0);
void showTime() const;
Clock& operator ++ (); //前置单目运算符重载
Clock operator ++ (int); //后置单目运算符重载
private: //私有数据成员
int hour, minute, second;
};
Clock::Clock(int hour, int minute, int second) {
if (0 <= hour && hour < 24 && 0 <= minute && minute < 60
&& 0 <= second && second < 60) {
this->hour = hour;
this->minute = minute;
this->second = second;
} else
cout << "Time error!" << endl;
}
void Clock::showTime() const { //显示时间函数
cout << hour << ":" << minute << ":" << second << endl;
}
Clock & Clock::operator ++ () { //前置单目运算符重载函数
second++;
if (second >= 60) {
second -= 60;
minute++;
if (minute >= 60) {
minute -= 60;
hour = (hour + 1) % 24;
}
}
return *this;
}
Clock Clock::operator ++ (int) { //后置单目运算符重载
//注意形参表中的整型参数
Clock old = *this;
++(*this); //调用前置“++”运算符
return old;
}
int main() {
Clock myClock(23, 59, 59);
cout << "First time output: ";
myClock.showTime();
cout << "Show myClock++: ";
(myClock++).showTime();
cout << "Show ++myClock: ";
(++myClock).showTime();
system("pause");
return 0;
}
运算符重载为非成员函数
• 函数的形参代表依自左至右次序排列的各操作数。
• 后置单目运算符++和–的重载函数,形参列表中要增加一个int,但不必写形参名。
• 如果在运算符的重载函数中需要操作某类对象的私有成员,可以将此函数声明为该类的友元。
#include <iostream>
using namespace std;
class Complex { //复数类定义
public: //外部接口
Complex(double r = 0.0, double i = 0.0) : real(r),
imag(i) { } //构造函数
friend Complex operator + (const Complex &c1, const
Complex &c2); //运算符+重载
friend Complex operator - (const Complex &c1, const
Complex &c2); //运算符‐重载
friend ostream & operator << (ostream &out, const
Complex &c); //运算符<<重载
private: //私有数据成员
double real; //复数实部
double imag; //复数虚部
};
Complex operator + (const Complex &c1, const Complex &c2)
{ //重载运算符函数实现
return Complex(c1.real + c2.real, c1.imag + c2.imag);
}
Complex operator - (const Complex &c1, const Complex &c2)
{ //重载运算符函数实现
return Complex(c1.real - c2.real, c1.imag - c2.imag);
}
ostream & operator << (ostream &out, const Complex &c) {
//重载运算符函数实现
out << "(" << c.real << ", " << c.imag << ")";
return out;
}
int main() {//主函数
Complex c1(5, 4), c2(2, 10), c3; //定义复数类的对象
cout << "c1 = " << c1 << endl;
cout << "c2 = " << c2 << endl;
c3 = c1 - c2; //使用重载运算符完成复数减法
cout << "c3 = c1 ‐ c2 = " << c3 << endl;
c3 = c1 + c2; //使用重载运算符完成复数加法
cout << "c3 = c1 + c2 = " << c3 << endl;
system("pause");
return 0;
}
虚函数
一般虚函数成员
• C++中引入了虚函数的机制在派生类中可以对基类中的成员函数进行覆盖(重定义)。
• 虚函数的声明
virtual 函数类型函数名(形参表)
{
函数体
}
#include <iostream>
using namespace std;
class Base1 { //基类Base1定义
public:
virtual void display() const; //虚函数
};
void Base1::display() const {
cout << "Base1::display()" << endl;
}
class Base2:public Base1 { //公有派生类Base2定义
public:
void display() const; //覆盖基类的虚函数
};
void Base2::display() const {
cout << "Base2::display()" << endl;
}
class Derived: public Base2 { //公有派生类
public:
void display() const; //覆盖基类的虚函数
};
void Derived::display() const {
cout << "Derived::display()" << endl;
}
void fun(Base1 *ptr) { //参数为指向基类对象的指针
ptr->display(); //"对象指针‐>成员名"
}
int main() {//主函数
Base1 base1; //定义Base1类对象
Base2 base2; //定义Base2类对象
Derived derived; //定义Derived类对象
fun(&base1);//用Base1对象的指针调用fun函数
fun(&base2);//用Base2对象的指针调用fun函数
fun(&derived);//用Derived对象的指针调用fun函数
system("pause");
return 0;
}
运行结果与上一篇文章中“绝对不要重新定义继承而来的非虚函数”进行对比
虚析构函数
为什么需要虚析构函数?
• 可能通过基类指针删除派生类对象;
• 如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),就需要让基类的析构函数成为虚函数,否则执行delete的结果是不确定的。
抽象类
纯虚函数
• 纯虚函数是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本,纯虚函数的声明格式为:
▫ virtual 函数类型函数名(参数表) = 0;
• 带有纯虚函数的类称为抽象类:
class 类名
{
virtual 类型函数名(参数表)=0; //纯虚函数
…
}
抽象类
• 作用
▫ 抽象类为抽象和设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。
▫ 对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。
• 注意
▫ 抽象类只能作为基类来使用。
▫ 不能声明抽象类的对象。
▫ 构造函数不能是虚函数,析构函数可以是虚函数。
#include <iostream>
using namespace std;
class Base1 { //基类Base1定义
public:
virtual void display() const = 0; //纯虚函数
};
class Base2: public Base1 { //公有派生类Base2定义
public:
void display() const; //覆盖基类的虚函数
};
void Base2::display() const {
cout << "Base2::display()" << endl;
}
class Derived: public Base2 { //公有派生类Derived定义
public:
void display() const; //覆盖基类的虚函数
};
void Derived::display() const {
cout << "Derived::display()" << endl;
}
void fun(Base1 *ptr) { //参数为指向基类对象的指针
ptr->display(); //"对象指针‐>成员名"
}
int main() { //主函数
Base2 base2; //定义Base2类对象
Derived derived; //定义Derived类对象
fun(&base2); //用Base2对象的指针调用fun函数
fun(&derived); //用Derived对象的指针调用fun函数
system("pause");
return 0;
}
模板
函数模板
• 函数模板可以用来创建一个通用功能的函数,以支持多种不同形参,进一步简化重载函数的函数体设计。
• 定义方法:
template <模板参数表>
函数定义
• 模板参数表的内容
▫ 类型参数:class(或typename) 标识符
▫ 常量参数:类型说明符标识符
▫ 模板参数:template <参数表> class 标识符
#include <iostream>
using namespace std;
template<typename T>
T abs(T x) {
return x < 0? -x : x;
}
int main() {
int n = -5;
double d = -5.5;
cout << abs(n) << endl;
cout << abs(d) << endl;
system("pause");
return 0;
}
求绝对值函数的模板分析
• 编译器从调用abs()时实参的类型,推导出函数模板的类型参数。
• 例如,对于调用表达式abs(n),由于实参n为int型,所以推导出模板中类型参数T为int。
• 当类型参数的含义确定后,编译器将以函数模板为样板,生成一个函数:
int abs(int x) {
return x < 0 ? –x : x;
}
#include <iostream>
using namespace std;
template <class T> //定义函数模板
void outputArray(const T *array, int count) {
for (int i = 0; i < count; i++)
cout << array[i] << " ";
cout << endl;
}
int main() { //主函数
const int A_COUNT = 8, B_COUNT = 8, C_COUNT = 20;
int a [A_COUNT] = { 1, 2, 3, 4, 5, 6, 7, 8 }; //定义int数组
double b[B_COUNT] = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6,
7.7, 8.8 };//定义double数组
char c[C_COUNT] = "Welcome to see you!";//定义char数组
cout << " a array contains:" << endl;
outputArray(a, A_COUNT); //调用函数模板
cout << " b array contains:" << endl;
outputArray(b, B_COUNT); //调用函数模板
cout << " c array contains:" << endl;
outputArray(c, C_COUNT); //调用函数模板
system("pause");
return 0;
}
类模板
• 类模板的作用
使用类模板使用户可以为类声明一种模式,使得类中的某些数据成员、某些成员函数的参数、某些成员函数的返回值,能取任意类型(包括基本类型的和用户自定义类型)。
类模板的声明
• 类模板:
template <模板参数表>
class 类名
{类成员声明}
• 如果需要在类模板以外定义其成员函数,则要采用以下的形式:
template <模板参数表>
类型名 类名<模板参数标识符列表>::函数名(参数表)
#include <iostream>
#include <cstdlib>
using namespace std;
// 结构体Student
struct Student {
int id; //学号
float gpa; //平均分
};
template <class T>
class Store {//类模板:实现对任意类型数据进行存取
private:
T item; // item用于存放任意类型的数据
bool haveValue; // haveValue标记item是否已被存入内容
public:
Store(); // 缺省形式(无形参)的构造函数
T &getElem(); //提取数据函数
void putElem(const T &x); //存入数据函数
};
//以下实现各成员函数。
template <class T> //缺省构造函数的实现
Store<T>::Store(): haveValue(false) { }
template <class T> //提取数据函数的实现
T &Store<T>::getElem() {
//如试图提取未初始化的数据,则终止程序
if (!haveValue) {
cout << "No item present!" << endl;
exit(1); //使程序完全退出,返回到操作系统。
}
return item; // 返回item中存放的数据
}
template <class T> //存入数据函数的实现
void Store<T>::putElem(const T &x) {
// 将haveValue 置为true,表示item中已存入数值
haveValue = true;
item = x; // 将x值存入item
}
int main() {
Store<int> s1, s2;
s1.putElem(3);
s2.putElem(-7);
cout << s1.getElem() << " " << s2.getElem() << endl;
Student g = { 1000, 23 };
Store<Student> s3;
s3.putElem(g);
cout << "The student id is " << s3.getElem().id <<
endl;
system("pause");
Store<double> d;
cout << "Retrieving object D... ";
cout << d.getElem() << endl;
//由于d未经初始化,在执行函数D.getElement()过程中导致程序终止
return 0;
}
多态类型与非多态类型
• 多态类型与非多态类型
▫ 有虚函数的类类型称为多态类型
▫ 其它类型皆为非多态类型
• 二者的差异
▫ 语言层面的差异
多态类型支持运行时类型识别
多态类型对象占用额外的空间
▫ 设计原则上的差异
设计原则
• 多态类型
▫ 多态类型的析构函数一般应为虚函数
• 非多态类型
▫ 非多态类型不宜作为公共基类
由于没有利用动态多态性,一般可以用组合,而无需用公有继承;
如果继承,则由于析构函数不是虚函数,删除对象时所执行操作与指针类型有关,易引起混乱。
▫ 把不需被继承的类型设定为非多态类型
由于成员函数都是静态绑定,调用速度较快;
对象占用空间较小。
多态类型的示例
#include <cstdlib>
#include <iostream>
using namespace std;
const double PI = 3.14;
//基类:Shape类
class Shape
{
public:
Shape(){}
virtual ~Shape(){}
virtual double Area() = 0;
};
//派生类:矩形类
class Rectangle: public Shape {
public:
Rectangle(double x, double y)
{ width=x; height=y; }
~Rectangle() {}
double Area() {
cout << width*height << endl;
return (double)width*height; }
private:
double width;
double height;
};
//派生类:圆类
class Circle: public Shape {
public:
Circle(double x) { radius = x; }
~Circle() {}
double Area() {
cout << PI*pow(radius,2) << endl;
return (double)PI*pow(radius,2);
}
private:
double radius;
};
//主程序
void main() {
cout << "虚函数多态实例:" << endl;
//定义基类对象
Shape *shape;
//使用基类指针调用派生类的实现函数
shape = new Rectangle(2,3);
shape->Area();
delete shape;
shape = new Circle(3);
shape->Area();
system("pause");
delete shape;
}
上一篇: jquery中get和post的简单实例
下一篇: flink集群的搭建与部署