以下为个人学习笔记和习题整理
课程:计算机程序设计(C++)- 西安交通大学 @ 中国大学 MOOC
https://www.icourse163.org/course/XJTU-46006

# 课堂笔记

# 多态性

# 含义

多态指相同语法结构,代表多种功能或操作
多态实现了 “一种接口,多种方法”
将运算符重载,将函数重载,实现不同功能

# 两种形式

  1. 编译时多态性
    编译器对源程序进行编译时,就可以确定所调用的是哪一个函数
    编译时多态性通过重载来实现
    - 函数重载
    - 运算符重载

  2. 运行时多态性
    在程序运行过程中,根据具体情况来确定调用的是哪一个函数
    运行时多态通过虚函数 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(); // 究竟运行哪个函数,只有运行时才知道
}

# 派生类对象替换基类对象

# 一个替换原则

凡是基类对象出现的场合都可以用公有派生类对象取代

# 三个替换形式

  1. 派生类对象给基类对象赋值 派生类对象=基类对象
  2. 派生类对象可以初始化基类对象的引用 &基类对象=派生类对象
  3. 可以令基类对象的指针指向派生类对象,即,将派生类对象的地址传递给基类指针 基类名 *基类对象的指针=&派生类对象

# 虚函数

# 定义

在函数定义的头部加上 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;
}

# 使用限制

  1. 应通过指针引用调用虚函数,而不要以对象名调用虚函数

    Pet obj;
    Dog dog1;
    obj = dog1;
    obj.Speak(); // 执行的是基类 Speak () 函数
    Pet *p1 = &dog1;
    p1->Speak();
  2. 在派生类中重定义的基类虚函数仍为虚函数,同时可以省略 virtual 关键字

  3. 不能定义虚构造函数,可以定义虚析构函数

# 虚析构函数

#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;
}

# 课堂讨论

  1. 请总结虚函数实现的功能?

实现运行时的多态性,即通过指向派生类的基类指针,访问派生类中同名覆盖成员函数。

  1. 什么是运算符的重载? 请查找资料,研究提取运算符 >> 和插入运算符 << 的重载。

C++ 的流插入运算符 << 和流提取运算符 >>C++ 编译系统在类库中提供的,所有 C++ 编译系统都在其类库中提供输入流类 istream 和输出流类 ostreamcincout 分别是 istreamostream 类的对象。
<<>> 重载的函数形式如下:

istream& operator>>(istream&,自定义类&);
ostream& operator<<(ostream&, 自定义类&);

重载 >> 的函数的第一个参数和函数的类型都必须是 istream& 类型,也就是 istream 类对象的引用,第二个参数是要进行输入操作的类。
重载 << 的函数的第一个参数和函数的类型都必须是 ostream& 类型,也就是 ostream 类对象的引用,第二个参数是要进行输出操作的类。

# 随堂练习

  1. 编译时多态主要指运算符重载与函数重载,而运行时多态主要指虚函数。

  2. 有基类 SHAPE ,派生类 CIRCLE ,声明如下变量:

    SHAPE shape1,*p1;
    CIRCLE circle1,*q1;

    下列哪些项是 “派生类对象替换基类对象”。

    • p1=&circle1;
    • q1=&shape1;
    • shape1=circle1;
    • circle1=shape1;
    • ✔️ 令基类对象的指针指向派生类对象
    • ❌ 派生类指针指向基类的引用
    • ✔️ 派生类对象给基类对象赋值
    • ❌ 基类对象给派生类对象赋值
  3. 下列叙述正确的是

    • 虚函数只能定义成无参函数
    • 虚函数不能有返回值
    • 能定义虚构造函数
    • A、B、C 都不对
  4. 关于虚函数的描述中,正确的是

    • 虚函数是一个静态成员函数
    • 虚函数是一个非成员函数
    • 说明虚函数的 virtual ,即可以出现在类内函数说明时,也可以出现在类外函数定义时。
    • 派生类的虚函数与基类中对应的虚函数具有相同的参数个数和类型
  5. 以下 成员函数表示纯虚函数。

    • virtual int vf(int);
    • void vf(int)=0;
    • virtual void vf()=0;
    • virtual void vf(int) {};
  6. 下列描述中, 是抽象类的特征。

    • 说明有虚函数
    • 说明有纯虚函数
    • 有其他类的对象作数据成员
    • 有指针作数据成员
  7. 设有复数类 COMPLEX ,在复数类中重载乘法运算符。下列哪项是运算符重载的正确的声明格式?

    • COMPLEX *(COMPLEX c1, COMPLEX c2);
    • COMPLEX * (COMPLEX c2);
    • COMPLEX operator * (COMPLEX c1, COMPLEX c2);
    • COMPLEX operator * (COMPLEX c2);
阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Ruri Shimotsuki 微信支付

微信支付

Ruri Shimotsuki 支付宝

支付宝

Ruri Shimotsuki 贝宝

贝宝