以下为个人学习笔记和习题整理
课程:计算机程序设计(C++)- 西安交通大学 @ 中国大学 MOOC
https://www.icourse163.org/course/XJTU-46006
# 课堂笔记
# 多态性
# 含义
多态指相同语法结构,代表多种功能或操作
多态实现了 “一种接口,多种方法”
将运算符重载,将函数重载,实现不同功能
# 两种形式
编译时多态性
编译器对源程序进行编译时,就可以确定所调用的是哪一个函数
编译时多态性通过重载来实现
- 函数重载
- 运算符重载运行时多态性
在程序运行过程中,根据具体情况来确定调用的是哪一个函数
运行时多态通过虚函数virtual
来实现
#include<iostream> | |
using namespace std; | |
//max_ 是重载函数 | |
char max_( char x, char y ) { | |
return x>y?x:y; | |
} | |
int max_( int x, int y ) { | |
return x>y?x:y; | |
} | |
float max_( float x, float y ) { | |
return x>y?x:y; | |
} | |
void main() | |
{ | |
float a=3.14,b=2.718; | |
cout<<"d与s:"<<max_('d','s')<<"大"<<endl; | |
cout<<"28与168:"<<max_(28,168)<<"大"<<endl; | |
cout<<"3.14与2.718:"<<max_(a,b)<<"大"<<endl; | |
} |
#include<iostream> | |
using namespace std; | |
class pet | |
{ | |
public: | |
virtual void speak() { | |
cout<<"zzz"<<endl; | |
}; | |
}; | |
class cat: public pet | |
{ | |
public: | |
void speak() { | |
cout<< "miao!miao!"<<endl; | |
} | |
}; | |
class dog: public pet | |
{ | |
public: | |
void speak() { | |
cout<< "wang!wang!"<<endl; | |
} | |
}; | |
void main() | |
{ | |
pet pet1,*p=&pet1; //p 为宠物类指针 | |
cat cat1; // 定义猫类对象 | |
dog dog1; // 定义狗类对象 | |
int x; // 根据用户输入将猫或狗对象地址赋给 p 指针, | |
cin>>x; // 石头扔的远近,只有扔了才知道效果 | |
if(x==1) p = &cat1; // 例如用户输入 1,则执行猫对象地址赋给 p | |
if(x==2) p = &dog1; // 例如用户输入 2,则执行猫对象地址赋给 p | |
p->speak(); // 究竟运行哪个函数,只有运行时才知道 | |
} |
# 派生类对象替换基类对象
# 一个替换原则
凡是基类对象出现的场合都可以用公有派生类对象取代
# 三个替换形式
- 派生类对象给基类对象赋值
派生类对象=基类对象
- 派生类对象可以初始化基类对象的引用
&基类对象=派生类对象
- 可以令基类对象的指针指向派生类对象,即,将派生类对象的地址传递给基类指针
基类名 *基类对象的指针=&派生类对象
# 虚函数
# 定义
在函数定义的头部加上 virtual
,该函数就是虚函数。
事实上,在某基类中声明为 virtual
,并在一个或多个派生类中被重新定义的同名成员函数,称为虚函数。
virtual <函数返回类型> <函数名>(<参数表>) | |
{ | |
<函数体> | |
} |
# 用途
实现运行时的多态性,即通过指向派生类的基类指针,访问派生类中同名覆盖成员函数。
#include <iostream> | |
using namespace std; | |
class mobile | |
{ | |
public: | |
char mynumber[11]; // 机主的电话号码 | |
virtual void showinfo() // 显示制式 | |
{ | |
cout<<"The phone is mobile"<<endl; | |
} | |
}; | |
class mobilegsm:public mobile | |
{ | |
public: | |
virtual void showinfo() // 显示制式 | |
{ | |
cout<<"The phone is mobilegsm"<<endl; | |
} | |
}; | |
class mobilecdma:public mobile | |
{ | |
public: | |
virtual void showinfo() // 显示制式 | |
{ | |
cout<<"The phone is mobilecdma"<<endl; | |
} | |
}; | |
int main() | |
{ | |
mobile m,*p1; // 基类对象指针 p1,基类对象 m | |
mobilegsm gsm; | |
mobilecdma cdma; | |
m=gsm; // 用 gsm 类对象给 mobile 类对象赋值 | |
m.showinfo(); | |
// The phone is mobile | |
m=cdma; // 用 cdma 类对象给 mobile 类对象赋值 | |
m.showinfo(); | |
// The phone is mobile | |
p1=&gsm; // 用 gsm 类对象地址给 mobile 类对象赋值 | |
p1->showinfo(); | |
// The phone is mobilegsm | |
p1=&cdma; // 用 cdma 类对象地址给 mobile 类对象赋值 | |
p1->showinfo(); | |
// The phone is mobilecdma | |
mobile &p4=gsm; // 以 gsm 类对象初始化 mobile 类引用 | |
p4.showinfo(); | |
// The phone is mobilegsm | |
mobile &p5=cdma; // 以 cdma 类对象初始化 mobile 类引用 | |
p5.showinfo(); | |
//The phone is mobilecdma | |
return 0; | |
} |
#include <iostream> | |
using namespace std; | |
class Pet // 基类 | |
{ | |
public: | |
virtual void Speak() { | |
cout<<"How does a pet speak ?"<<endl; | |
} | |
}; | |
class Cat: public Pet // 派生类 | |
{ | |
public: | |
virtual void Speak() { | |
cout<<"miao!miao!"<<endl; | |
} | |
}; | |
class Dog: public Pet // 派生类 | |
{ | |
public: | |
virtual void Speak() { | |
cout<<"wang!wang!"<<endl; | |
} | |
}; | |
int main() | |
{ | |
Pet *p1, *p2, *p3, obj; // 基类对象指针 p1, 基类对象 obj | |
Dog dog1; | |
Cat cat1; | |
obj = dog1; // 用 Dog 类对象给 Pet 类对象赋值 | |
obj.Speak(); // 执行基类的 Speak () 函数 | |
// How does a pet speak ? | |
p1 = &cat1; // 用 Cat 类对象地址给基类指针赋值 | |
p1->Speak(); | |
// miao!miao! | |
p1 = &dog1; // 用 Dog 类对象地址给基类指针赋值 | |
p1->Speak(); | |
// wang!wang! | |
p2=new Cat; // 动态生成 Cat 类对象 | |
p2->Speak(); | |
// miao!miao! | |
p3=new Dog; // 动态生成 Dog 类对象 | |
p3->Speak(); | |
// wang!wang! | |
Pet &p4 = cat1; // 以 Cat 类对象初始化 Pet 类引用 | |
p4.Speak(); | |
// miao!miao! | |
return 0; | |
} |
# 使用限制
应通过指针或引用调用虚函数,而不要以对象名调用虚函数
Pet obj;
Dog dog1;
obj = dog1;
obj.Speak(); // 执行的是基类 Speak () 函数
Pet *p1 = &dog1;
p1->Speak();
在派生类中重定义的基类虚函数仍为虚函数,同时可以省略
virtual
关键字不能定义虚构造函数,可以定义虚析构函数
# 虚析构函数
#include<iostream> | |
using namespace std; | |
class Base // 基类 | |
{ | |
public: | |
int x; | |
virtual void f() { | |
cout<<"base class\n"; | |
}; | |
virtual void show() { | |
cout<<"x="<<x<<endl; | |
}; | |
~Base() { | |
cout<<"destructor base class\n"; | |
} | |
}; | |
class Derived : public Base // 派生类 | |
{ | |
public: | |
int y; | |
virtual void f() { | |
cin>>y; | |
cout<<"Derived class\n"; | |
} | |
virtual void show() { | |
Base::show(); | |
cout<<"y="<<y<<endl; | |
} | |
~Derived() { | |
cout<<"destructor derived class\n"; | |
} | |
}; | |
int main() | |
{ | |
Base *p; | |
p=new Derived; | |
cin>>p->x; | |
p->f(); // 调用的是派生类的 f | |
p->show(); // 调用的是派生类的 show | |
delete p; // 释放动态申请空间 | |
return 0; | |
} |
通过基类指针释放派生类对象空间,执行的是基类析构函数!!!
#include<iostream> | |
using namespace std; | |
class Base // 基类 | |
{ | |
public: | |
int x; | |
virtual void f() { | |
cout<<"base class\n"; | |
}; | |
virtual void show() { | |
cout<<"x="<<x<<endl; | |
}; | |
virtual ~Base() { // 虚析构函数 | |
cout<<"destructor base class\n"; | |
} | |
}; |
定义为虚析构函数,通过基类指针可以释放派生类对象的空间。
# 声明位置
必须在类内声明,不能在类外函数定义时声明。
class Base | |
{ | |
public: | |
int x; | |
virtual void f(){cout<<"base class\n";}; | |
void show(); | |
virtual ~Base(){cout<<"destructor base class\n"} | |
}; | |
virtual void Base::show(){cout<<"x="<<x<<endl;} |
class Base | |
{ | |
public: | |
int x; | |
virtual void f(){cout<<"base class\n";}; | |
virtual void show(); | |
virtual ~Base(){cout<<"destructor base class\n";} | |
}; | |
void Base::show(){cout<<"x="<<x<<endl;} |
# 抽象类
# 概念
类是对象的集合,类是从相似对象中抽取共性,而得到的抽象数据类型。
将不用来声明对象(实例化)的类称为抽象类,只供继承。
virtual <返回类型> <函数名>(<参数表>) = 0 |
抽象类又可以定义成:至少包含一个纯虚函数的类。
# 使用要求
- 抽象类不能实例化,即不声明对象
- 抽象类只作为基类被继承
- 可以定义抽象类的指针或引用
#include<iostream> | |
#include<cmath> | |
using namespace std; | |
#define PI 3.1415926 | |
// 平面上的几何图形可以抽象定义为类,如矩形类、圆类、三角形类等 | |
// 将所有几何图形再加以抽象,定义为形状类 | |
// 基类定义为抽象类 | |
class Shape | |
{ | |
public: | |
// 由于几何图形类中都包含求面积函数和求周长函数 | |
virtual double area()=0; // 声明为纯虚函数 | |
virtual double circumference()=0; // 声明为纯虚函数 | |
}; | |
// 派生出矩形类 | |
class Rectangle:public Shape | |
{ | |
int x,y; | |
int width,hight; | |
public: | |
// 构造函数 | |
Rectangle(int x,int y,int w,int h) { | |
this->x=x; | |
this->y=y; | |
width=w; | |
hight=h; | |
} | |
// 具体定义相应的求面积与周长的函数 | |
virtual double area() { | |
return width*hight; | |
} | |
virtual double circumference() { | |
return 2.0*(width+hight); | |
} | |
}; | |
// 派生出圆类 | |
class Circle:public Shape | |
{ | |
int x,y; | |
int r; | |
public: | |
// 构造函数 | |
Circle(int x,int y,int r) { | |
this->x=x; | |
this->y=y; | |
this->r=r; | |
} | |
// 具体定义相应的求面积与周长的函数 | |
virtual double area() { | |
return PI*r*r; | |
} | |
virtual double circumference() { | |
return 2.0*PI*r; | |
} | |
} | |
// 测试主函数 | |
void main() | |
{ | |
Rectangle r1(10,10,10,5); | |
Circle c1(1,2,1); | |
// 通过抽象类的对象指针或引用,访问派生类对象,实现动态绑定 | |
Shape *p1=&r1, &p2=c1; | |
cout<<"长方形面积:"<<p1->area()<<endl; | |
cout<<"长方形周长:"<<p1->circumference()<<endl; | |
cout<<"圆面积:"<<p2.area()<<endl; | |
cout<<"圆周长:"<<p2.circumference()<<endl; | |
} |
改造:将派生类的点坐标移入基类定义
意义:所有派生类图形的中心坐标点
由于点坐标是私有成员,需要增加下列函数
构造函数Shape()
输出点坐标函数print()
得到点坐标值函数getx()
,gety()
class Shape | |
{ | |
private: | |
int x,y; | |
public: | |
Shape(int xx,int yy):x(xx),y(yy){} | |
int getx() { | |
return x; | |
} | |
int gety() { | |
return y; | |
} | |
void print() { | |
cout<<'['<<x<<','<<y<<']'<<endl; | |
} | |
virtual double area()=0; | |
virtual double circumference()=0; | |
}; |
# 运算符重载
指赋予运算符新的操作功能,主要用于对类的对象的操作。
<类型> <类名>::operator <操作符>(<参数表>) | |
{ | |
<函数体> | |
} |
#include <iostream> | |
using namespace std; | |
class Complex | |
{ | |
private: | |
double real, imag; | |
public: | |
Complex(double r = 0, double i = 0): real(r), imag(i) { } | |
double Real() { | |
return real; | |
} | |
double Imag() { | |
return imag; | |
} | |
Complex operator +(Complex&); | |
Complex operator +(double); | |
bool operator ==(Complex); | |
~Complex(){ }; | |
}; | |
// 重载运算符 +,两边是虚数对象 | |
Complex Complex::operator + (Complex &c) | |
{ | |
Complex temp; | |
temp.real = real + c.real; | |
temp.imag = imag + c.imag; | |
return temp; | |
} | |
// 重载运算符 +,左边是虚数对象,右边是双精度数 | |
Complex Complex::operator + (double d) | |
{ | |
Complex temp; | |
temp.real = real + d; | |
temp.imag = imag; | |
return temp; | |
} | |
// 重载运算符 == | |
bool Complex::operator == (Complex c) | |
{ | |
if (real == c.real && imag == c.imag) | |
{ | |
return true; | |
} | |
else | |
{ | |
return false; | |
} | |
} | |
// 测试复数相加和判相等运算符重载 | |
int main() | |
{ | |
Complex c1(3,4), c2(5,6), c3; | |
cout << "C1 = " << c1.Real() << "+j" << c1.Imag() << endl; | |
cout << "C2 = " << c2.Real() << "+j" << c2.Imag() << endl; | |
c3 = c1 + c2; | |
cout << "C3 = " << c3.Real() << "+j" << c3.Imag() << endl; | |
c3 = c3 + 6.5; | |
cout << "C3 + 6.5 = " << c3.Real() << "+j" << c3.Imag() << endl; | |
if ( c1==c2 ) | |
{ | |
cout<<"两个复数相等"; | |
} | |
else | |
{ | |
cout<<"两个复数不相等"; | |
} | |
return 0; | |
} |
# 单目运算符重载
运算符 ++
分前置运算符 ++Y
和后置运算符 Y++
Complex Complex::operator ++ () | |
{ | |
real+=1; | |
return *this; | |
} |
Complex Complex::operator ++ (int) | |
{ | |
real+=1; | |
return *this; | |
} |
# 课堂讨论
- 请总结虚函数实现的功能?
实现运行时的多态性,即通过指向派生类的基类指针,访问派生类中同名覆盖成员函数。
- 什么是运算符的重载? 请查找资料,研究提取运算符
>>
和插入运算符<<
的重载。
C++
的流插入运算符<<
和流提取运算符>>
是C++
编译系统在类库中提供的,所有C++
编译系统都在其类库中提供输入流类istream
和输出流类ostream
,cin
和cout
分别是istream
和ostream
类的对象。
对<<
和>>
重载的函数形式如下:
istream& operator>>(istream&,自定义类&); | |
ostream& operator<<(ostream&, 自定义类&); |
重载
>>
的函数的第一个参数和函数的类型都必须是istream&
类型,也就是istream
类对象的引用,第二个参数是要进行输入操作的类。
重载<<
的函数的第一个参数和函数的类型都必须是ostream&
类型,也就是ostream
类对象的引用,第二个参数是要进行输出操作的类。
# 随堂练习
编译时多态主要指运算符重载与函数重载,而运行时多态主要指虚函数。
有基类
SHAPE
,派生类CIRCLE
,声明如下变量:SHAPE shape1,*p1;
CIRCLE circle1,*q1;
下列哪些项是 “派生类对象替换基类对象”。
下列叙述正确的是 。
关于虚函数的描述中,正确的是 。
以下 成员函数表示纯虚函数。
下列描述中, 是抽象类的特征。
设有复数类
COMPLEX
,在复数类中重载乘法运算符。下列哪项是运算符重载的正确的声明格式?