【C++】面向对象

news2024/12/27 11:59:32

文章目录

    • 3.1 类与对象
      • 3.1.1 类成员的访问控制
      • 3.1.2 类的成员函数
        • 对象的访问方式
        • 成员函数的实现
        • 内联成员函数
      • 3.1.3 构造函数
        • 复制构造函数
          • 调用复制构造函数的三种情况
          • 深复制与浅复制?
        • 析构函数
        • 类的组合
      • 3.1.4 前向引用声明
      • 3.1.5 结构体与类对比
      • 3.1.6 UML类图
        • 属性表示
        • 方法表示
        • 类关系表示
          • 关联关系
          • 包含关系
          • 泛化
          • 注释
      • 3.1.7 临时对象
    • 3.2 继承与派生
      • 3.2.1 派生类定义及生成
        • 派生类定义
        • 派生类生成过程
        • 继承方式
          • 公有继承
          • 私有继承
          • 保护继承
      • 3.2.2 派生类的构造函数和析构函数
        • 派生类构造函数
        • 复制构造函数
        • 析构函数
      • 3.2.3 类型(向上)兼容
      • 3.2.4 派生类成员的表示与控制
        • using关键字
      • 3.2.5 虚基类
        • 虚基类及派生类的构造函数
      • 3.2.6 类对象的构造顺序
    • 3.3 多态性
      • 3.3.1 多态及其实现
        • 多态分类
        • 运行时多态
      • 3.3.2 运算符多态(编译时多态)
        • 类的运算符重载成员函数
          • 单目运算符重载
          • 双目运算符重载
      • 3.3.3 虚函数(运行时多态)
        • 虚析构函数
      • 3.3.4 抽象类(运行时多态)
        • 纯虚函数
        • 抽象类
      • 3.3.5 多态性总结
        • 动态类型与非动态类型声明原则
        • 多态的三个条件
        • 多态的实现——重写
        • 重写与重载

3.1 类与对象

对象:用于描述客观事物的实体,用于构成系统的一个基本单位

  • 由一组属性和行为构成

:具有相同属性和行为的一组对象集合

  • 类中的数据只能通过本类的方法进行处理
  • 类通过一个简单的 外部接口 与外界发生关系
  • 对象和类之间通过消息进行通信

抽象:对一些具体问题进行概括,抽出一类可以描述这些问题的公共性质的过程

面向对象方法

  • 将数据和对数据的操作封装在一起,作为一个相互依存、不可分离的整体——对象
  • 对同类型的对象抽象出其共性形成类
  • 特性:
    • 封装:将抽象得到的性质结合称为一个整体
      • 将数据和操作数据的行为进行结合,形成类
    • 继承与派生
    • 多态性
class Name{
    ElemType data;
    ReturnType op(){
        return xx;
    }
}

面向对象的语言:程序模块是由类构成的

面向对象的软件开发

  • 面向对象分析:OOA
  • 面向对象设计:OOD
  • 面向对象编程:OOP
  • 面向对象测试:OOT
  • 面向对象软件维护:OOSM

3.1.1 类成员的访问控制

public :类外可访问

private:只允许本类成员函数访问

protected :在本类中具有 private 属性,在派生类中可以被访问,不可被外部使用者访问

3.1.2 类的成员函数

对象的访问方式

对象名.数据成员名
对象名.函数成员(参数表)

成员函数的实现

/*类中只保留函数声明,在类外实现函数的定义*/
返回值类型 类名::函数成员名(参数表){
	/*函数体*/
}

内联成员函数

隐式声明:将函数体放在类定义中;也可以使用 inline

3.1.3 构造函数

实现对象的初始化,定义对象时必须初始化

  • 存在自定义的构造函数,会覆盖默认的无参构造函数
# include<iostream>
using namespace std;

/*构造函数与类名相同*/
class Clock{
public:
	Clock(int newH,int newM,int newS);
	
	/*若没有无参构造函数,在主函数中不能定义未初始化的对象*/
	Clock(){
		hour = 0;
		minute = 0;
		second = 0;
	}
	
	void setTime(int newH,int newM,int newS);
	void showTime();

private:
	int hour,minute,second;
};//记住加分号

Clock::Clock(int newH,int newM,int newS)
{
	hour = newH;
	minute = newM;
	second = newS;
}

inline void Clock::showTime()
{
	cout << hour << ":" << minute << ":" << second << endl;
}

int main()
{
	Clock c2;//相当于Clock c2(0,0,0)
    Clock(1,2,3);//输出为0:0:0
    //1. 无类变量
    //2. 封装性,只能通过成员函数操作成员变量
    c2.showTime();
	
	return 0;
}

复制构造函数

用已知的对象初始化另一同类对象

调用复制构造函数的三种情况
  • 用类的一个对象 初始化另一对象

  • 函数的实参与形参结合时

  • 函数返回值是对象,赋值给调用者时

    先赋值给临时对象,待执行完语句后,临时对象消失

class Point{
public:
	Point(int xx,int yy):x(xx),y(yy){}
	Point(Point &p);
	int getX(){return x;}
	int getY(){return y;}
private:
	int x,y;
};

Point::Point(Point &p){//复制构造函数
	x = p.x;
	y = p.y;
}

Point middlePoint(Point &a,Point &b){
    Point x((a.getX()+b.getX())/2,(a.getY()+b.getY())/2);//复制
    return x;
}

int main(){
    Point a(4,4);
    Point b(2,2);

    cout << middlePoint(a,b).getX() << endl;//复制构造函数,执行完本语句,临时变量销毁
}
深复制与浅复制?

析构函数

清理功能

析构函数在对象的生存期即将结束时被自动调用

  • 析构函数执行顺序与构造函数的执行顺序相反
~ 类名(){}

在这里插入图片描述

类的组合

一个类可以嵌套其他类对象,包含与被包含关系

如果这个类具有内嵌对象成员,内嵌对象成员先被创建

  • 存在多个内嵌对象成员时,构造函数调用顺序与其被声明次序有关,与初始化列表的顺序无关。
#include<iostream>
#include<cmath>
using namespace std;

class Point{
	public:
		Point(int xx = 0,int yy = 0):x(xx),y(yy){
            cout << "Point Constructor:" << xx << endl;
        }
		Point(Point &p);
		int getX(){return x;}
		int getY(){return y;}	
	private:
		int x,y;
};

Point::Point(Point &p){
	x = p.x;
	y = p.y;
    cout << "Point Copy Constructor x=" << x << endl;
}

class Line{
	public:
		Line(Point xp1,Point xp2);
		Line(Line &l);
		double getLen()
		{
			return len;
		}	
	private:
		Point p1,p2;
		double len;
};

Line::Line(Point xp1,Point xp2):p1(xp1),p2(xp2){
	double x = static_cast<double>(p1.getX() - p2.getX());
	double y = static_cast<double>(p1.getY() - p2.getY());
    
	len = sqrt(x*x + y*y);
    cout << "Line Constructor" << endl;
}

Line::Line(Line &l):p1(l.p1),p2(l.p2){
	len = l.len;
    cout << "Line Copy Constructor" << endl;
}

int main()
{
	Point myp1(0,0),myp2(1,1);
	Line line1(myp1,myp2);
	Line line2(line1);
	
	cout << line1.getLen() << endl;
	cout << line2.getLen() << endl;
		
	return 0;
}

在这里插入图片描述

3.1.4 前向引用声明

在被引用类未定义前,将被引用类的名称放在调用者前

class B;//只告诉编译器B表示一个类,占用多少字节及内部成员等全不知道,故仅可作为形参,引用,指针,不可作为对象
class A{
	public:
    	void f(B b);//在A类中,可使用B类形参,使用B类的对象错误
		void method();		
	private:
		B &b;//使用对象的引用或指针正确
};

void A::method(){
	b.b_method();//错误,此时b_method 未定义
}

class B{
	public:
		void b_method();
};

void  B::b_method(){}
/*可将A中 method 方法放在 b_method 定义之后*/

3.1.5 结构体与类对比

结构体和类:

  • 未指定访问控制的成员,类中为私有,结构体为公有

联合体:

  • 全部数据成员共用同一内存单元

  • 不能自定义构造函数,析构函数,重载赋值运算符

  • 不能被继承,不支持多态

3.1.6 UML类图

在UML类图中,使用包含类名,属性 field 和方法 method 且有分割线的矩形表示类

属性表示

[访问控制] 属性名 [重数] : 类型 [默认值] [约束特征]
  • +public
  • -private
  • #protected

在这里插入图片描述

方法表示

[访问控制] 函数名 (参数列表):返回类型 [约束特征]

在这里插入图片描述

类关系表示

关联关系

单向关联

在这里插入图片描述

顾客(Customer)拥有地址(Address),则Customer类与Address类具有单向关联关系

多重性关联

在这里插入图片描述

一个界面(Form)可以拥有零个或多个按钮(Button),但是一个按钮只能属于一个界面

表示方法多重性说明
1…1本类对象与另一类对象之间是一对一关系
0…*本类一个对象与另一类对象的零个或多个有关系
1…*本类一个对象与另一类对象的一个或多个有关系
0…1本类一个对象与另一类对象的零个或一个有关系
m…n本类一个对象与另一类最少m个最多n个对象有关系
包含关系

聚合

聚合(Aggregation)关系表示整体与部分关系

在聚合关系中,成员对象是整体对象的一部分,但是成员对象可以脱离整体对象独立存在

在这里插入图片描述

汽车发动机(Engine)是汽车(Car)的组成部分,但是汽车发动机可以独立存在,因此,汽车和发动机是聚合关系

构成

构成(Composition)关系也表示类之间整体和部分的关系

但是在组合关系中整体对象可以控制成员对象的生命周期,一旦整体对象不存在,成员对象也将不存在

在这里插入图片描述

泛化

泛化(Generalization)关系也就是继承关系,用于描述父类与子类之间的关系

在这里插入图片描述

Student类和Teacher类都是Person类的子类,Student类和Teacher类继承了Person类的属性和方法,Person类的属性包含姓名(name)和年龄(age),每一个Student和Teacher也都具有这两个属性

另外Student类增加了属性学号(studentNo),Teacher类增加了属性教师编号(teacherNo),Person类的方法包括行走move()和说话say(),Student类和Teacher类继承了这两个方法,而且Student类还新增方法study(),Teacher类还新增方法teach()

注释

在这里插入图片描述

3.1.7 临时对象

cout << Line(Point(1),Point(4)).getLen <<endl;
cout << Line((Point)1,(Point)4).getLen << endl;
cout << Line(static_cast<Point>1,static_cast<Point>4) << endl;

cout << Line(1,4).getLen << endl;

explict Point(int xx = 0,yy = 0):x(xx),y(yy){}
//以此构造函数定义的类型转换只能通过显式完成,在声明处添加关键字,第四种类型转换失效

3.2 继承与派生

继承:派生类通过继承得到基类的特性

用于解决复用问题

  • 不继承默认构造函数和析构函数

在这里插入图片描述

3.2.1 派生类定义及生成

派生类定义

class 派生类名:继承方式 基类1,继承方式 基类2,...{
    /*成员声明*/
};

class Derived:public Base1,private Base2{
	public:
		Derived();
		~Derived();
};
  • 单继承:只继承于一个基类
  • 多继承:继承于多个基类

派生类生成过程

  1. 吸收基类成员函数

  2. 改造基类

  3. 添加新成员

    同名隐藏:若派生类声明了与基类同名的成员函数(不管参数列表是否相同)或成员属性,则隐藏基类同名成员

    • 被隐藏的同名函数,仍存在。对象名.基类名:: func();
#include<iostream>
using namespace std;

class Point{
	public:
		Point(int xx = 0,int yy = 0):x(xx),y(yy){
            cout << "Point Constructor:" << xx << endl;
        }
		Point(Point &p);
		int getX(){return x;}
		int getY(){return y;}	
	private:
		int x,y;
};

Point::Point(Point &p){
	x = p.x;
	y = p.y;
    cout << "Point Copy Constructor x=" << x << endl;
}

class PPoint:public Point{
	public:
		PPoint(int xx = 0,int yy = 0):x(xx),y(yy){
            cout << "PPoint Constructor:" << xx << endl;
        }
		int getX(){return x+1;}
		int getY(){return y+1;}	
	private:
		int x,y;
};

int main(){
	PPoint pp(1,1);

	cout << pp.getX() << endl;

	return 0;
}

在这里插入图片描述

继承方式

用于控制复用部分的访问权限

默认的继承方式是私有继承

  • public
  • protected
  • private

类族中或类族外都无法访问基类的私有成员

公有继承

基类的公有成员和保护成员在派生类中访问属性不变

  • 在类族之外只能通过派生类的对象访问从基类继承的公有成员,不可访问保护成员
# include<iostream>
using namespace std;

class Base{
    public:
        Base(int a,int b,int c):basePublic(a),baseProtected(b),basePrivate(c){}
        int basePublic;
    protected:
        int baseProtected;
    private:
        int basePrivate;
};

class PublicDerived:public Base{
    public:
        PublicDerived(int a,int b,int c):Base(a,b,c){
            cout << basePublic << endl;
            cout << baseProtected << endl;
            cout << basePrivate << endl;
        }
};

int main(){
    PublicDerived son1(1,2,3);
    cout << son1.basePublic << endl;
    cout << son1.baseProtected << endl;
    cout << son1.basePrivate << endl;
}

在这里插入图片描述

私有继承

公有成员和保护成员都以私有成员身份出现在派生类中,不可再向下派生

  • 类族外不能访问基类的成员
# include<iostream>
using namespace std;

class Base{
    public:
        Base(int a,int b,int c):basePublic(a),baseProtected(b),basePrivate(c){}
        int basePublic;
    protected:
        int baseProtected;
    private:
        int basePrivate;
};

class ProtectedDerived:protected Base{
    public:
        ProtectedDerived(int a,int b,int c):Base(a,b,c){
            cout << basePublic << endl;
            cout << baseProtected << endl;
            cout << basePrivate << endl;
        }
};

int main(){
    ProtectedDerived son1(1,2,3);
    cout << son1.basePublic << endl;
    cout << son1.baseProtected << endl;
    cout << son1.basePrivate << endl;
}

在这里插入图片描述

保护继承

基类的公有成员和保护成员以保护成员身份出现在派生类中

  • 类族外无法通过派生类对象访问
# include<iostream>
using namespace std;

class Base{
    public:
        Base(int a,int b,int c):basePublic(a),baseProtected(b),basePrivate(c){}
        int basePublic;
    protected:
        int baseProtected;
    private:
        int basePrivate;
};

class ProtectedDerived:protected Base{
    public:
        ProtectedDerived(int a,int b,int c):Base(a,b,c){
            cout << basePublic << endl;
            cout << baseProtected << endl;
            cout << basePrivate << endl;
        }
};

class DoubleProtectedDerived:protected ProtectedDerived{
    public:
        DoubleProtectedDerived(int a,int b,int c):ProtectedDerived(a,b,c){
            cout << basePublic << endl;
            cout << baseProtected << endl;
            cout << basePrivate << endl;
        }
};


int main(){
    ProtectedDerived son1(1,2,3);
    cout << son1.basePublic << endl;
    cout << son1.baseProtected << endl;
    cout << son1.basePrivate << endl;
}

在这里插入图片描述

在孙子代,私有继承无法访问间接继承基类,保护继承可以间接继承基类

# include<iostream>
using namespace std;

class Base{
    public:
        Base(int a,int b,int c):basePublic(a),baseProtected(b),basePrivate(c){}
        int basePublic;
    protected:
        int baseProtected;
    private:
        int basePrivate;
};

class ProtectedDerived:protected Base{
    public:
        ProtectedDerived(int a,int b,int c):Base(a,b,c){
            cout << basePublic << endl;
            cout << baseProtected << endl;
            cout << basePrivate << endl;
        }
};

class PrivateDerived:private Base{
    public:
        PrivateDerived(int a,int b,int c):Base(a,b,c){
            cout << basePublic << endl;
            cout << baseProtected << endl;
            cout << basePrivate << endl;
        }
};

class PublicProtectedDerived:public ProtectedDerived{
    public:
        PublicProtectedDerived(int a,int b,int c):ProtectedDerived(a,b,c){
            cout << basePublic << endl;
            cout << baseProtected << endl;
            cout << basePrivate << endl;
        }
};

class PublicPrivateDerived:public PrivateDerived{
    public:
        PublicPrivateDerived(int a,int b,int c):PrivateDerived(a,b,c){
            cout << basePublic << endl;
            cout << baseProtected << endl;
            cout << basePrivate << endl;
        }
};

int main(){
    ProtectedDerived son1(1,2,3);
    cout << son1.basePublic << endl;
    cout << son1.baseProtected << endl;
    cout << son1.basePrivate << endl;
}

在这里插入图片描述

3.2.2 派生类的构造函数和析构函数

派生类的构造函数只负责对新增成员初始化,基类成员的初始化由基类构造函数完成

派生类构造函数

派生类名::派生类名(参数表)::基类名1(基类1初始化参数列表),...基类名n(基类n初始化参数列表),派生类新增成员对象(派生类新增成员对象参数列表){
    /*派生类构造函数函数体*/
};

执行顺序

  1. 调用基类的构造函数,调用顺序按他们在被继承时声明的顺序
  2. 对派生类新增成员对象初始化,调用顺序按其在类中的声明顺序
  3. 执行派生类的构造函数体中内容
    • 对于构造函数中未显示列出的基类,系统会调用默认构造函数

复制构造函数

/*由于类型兼容规则,可以用派生类的对象去初始化基类的引用*/
Derived::Derived(Derived &v):Base(v){}

析构函数

派生类的析构函数只需要负责清理新增的成员,系统会自动调用基类的析构函数

析构函数调用顺序

  1. 执行析构函数的函数体
  2. 对派生类新增的成员进行清理
  3. 对所有从基类继承来的成员清理
# include<iostream>

using namespace std;

class Base1{
	public:
		Base1(int i){
            cout << "Constructing Base1 " << i << endl;
        }
		~Base1(){
            cout << "Destructing Base1 " << endl;
        }
};

class Base2{
	public:
		Base2(int j){
            cout << "Constructing Base2 " << j << endl;
        }
		~Base2(){
            cout << "Destructing Base2" << endl;
        }
};

class Base3{
	public:
		Base3(){
            cout << "Constructing Base3 * " << endl;
        }//声明为默认构造函数
		~Base3(){
            cout << "Destructing Base3 " << endl;
        }
};

class Derived:public Base2,public Base1,public Base3{
	public:
		Derived(int a,int b,int c,int d):Base1(a),member2(d),member1(c),Base2(b){}
	private:
		Base1 member1;
		Base2 member2;
		Base3 member3;
};

int main(){
	Derived obj(1,2,3,4);
	
	return 0;
}



/*运行结果*/
Constructing Base2 2//先调用父类的三个构造函数
Constructing Base1 1
Constructing Base3 *
Constructing Base1 3//派生类新增三个成员,按照声明顺序调用相应的构造函数
Constructing Base2 4
Constructing Base3 *
Destructing Base3 //析构函数调用顺序与构造函数调用顺序相反,3-2-1,3-1-2
Destructing Base2
Destructing Base1
Destructing Base3
Destructing Base1
Destructing Base2

3.2.3 类型(向上)兼容

任何需要基类对象的地方都可以使用派生类的对象来代替——相当于隐式类型转换

  • 派生类对象(指针) 都可以隐式转换为基类对象(指针)

    转换之后,派生类只能发挥基类功能——只能调用基类成员

  • 派生类对象可以初始化基类对象

# include<iostream>

using namespace std;

class Base1{
	public:
		void display(){
			cout << "Base1::display" <<endl;
		}
};

class Base2:public Base1{
	public:
		void display(){
			cout << "Base2::display" <<endl;
		}
};

class Derived:public Base2{
	public:
		void display(){
			cout << "Derived::display" <<endl;
		}
};

void fun(Base1 *ptr){
	ptr->display();
}

int main(){
	Base1 base1;
	Base2 base2;
	Derived derived;

	fun(&base1);
	fun(&base2);
	fun(&derived);

	return 0;
}

/*由于类型兼容规则,派生类的dispaly函数并未隐藏基类的同名函数*/
/*运行结果*/
Base1::display
Base1::display
Base1::display

3.2.4 派生类成员的表示与控制

作用域分辨符::

  • 类名::成员名

  • 类名:成员函数(参数列表)

同名隐藏:如果派生类中声明了与基类同名的新函数,即使参数列表不同,从基类继承的同名函数的所有重载形式都会被隐藏

  • 派生类对象名.成员名 派生类对象指针->成员名 唯一表示派生类成员

  • 同名隐藏不同于重载,重载发生与同一级的类,同名隐藏发生于父类与子类之间

using关键字

解决同名隐藏问题

using 的作用是将一个作用域名引入到另一个作用域中

using 用于基类中的函数名,派生类中定义同名但参数列表不同的函数,派生类也不会发生同名隐藏

/*既可以使用无参数fun,也可以使用带参数的fun*/
class Derived:public Base{
	public:
		using Base::fun;//引用Base类中的fun();
		void fun(int i){}
};

在这里插入图片描述

  • 即多个路径上的派生类对同一基类处理,使用 using :: 会形成多个基类副本——不一定是一件好事,通过虚基类解决

3.2.5 虚基类

将共同基类设为虚基类

  • 通过继承同一虚基类的所有派生类,访问基类的数据与函数成员时,会访问同一份映射

虚基类关键字作用范围和继承方式只对紧随其后的基类起作用

class 派生类名:virtual 继承方式 基类名{};

在这里插入图片描述

# include<iostream>
using namespace std;

class Base0{
    public:
        int var0;
        void fun0(){
            cout << "Member of Base0" << endl;
        }
};

class Base1:virtual public Base0{
    public:
        int var1;
        void fun0(){
            cout << "Member of Base1" << endl;
        }
};

class Base2:virtual public Base0{
    public:
        int var2;
        void fun0(){
            cout << "Member of Base2" << endl;
        }
};

class Derived:public Base1,public Base2{
    public:
        int var;
        using Base0::fun0;
        void fun0(){
            cout << "Member of Derived" << endl;
        }
};

int main(){   
    Derived d;

    d.var0 = 2;
    cout << d.var0 << endl;
    d.fun0();

    return 0;
}
/*运行结果*/
2//因为在父类,派生类中都没有同名成员,所以从虚基类中继承了唯一var0
Member of Derived//出现同名隐藏
//若将派生类的fun0删除,而不引入Base0中的fun0(),会导致fun0()调用的二义性
//若删除派生类新增成员函数fun0(),引入Base0的fun0(),则会调用Base0的fun0()

虚基类及派生类的构造函数

只保证了虚基类只有一份成员数据,并不消除类型兼容

  • 在整个继承关系中,直接或间接继承的所有派生类都必须在构造函数的初始化列表中对虚基类的成员初始化

  • 只有最远派生类的构造函数会真正调用虚基类的构造函数,该派生类的其他基类对虚基类构造函数调用都会被忽略

#include<iostream>
using namespace std;

class Furniture{
	public:
		Furniture(){};
		Furniture(double weight);
		void show(void);		
	protected:
		double weight;
};

Furniture::Furniture(double weight){
	this->weight = weight;
}
void Furniture::show(){
	cout << "家具重量为:" << weight << endl;
}

class Sofa:virtual public Furniture{
	public:
		Sofa(){};
		Sofa(double weight);
		void show(void);
};
Sofa::Sofa(double weight):Furniture(weight){  //派生类的构造函数应该去初始化基类的数据成员
	this->weight = weight;
}
void Sofa::show(){
	cout << "沙发重量为:" << weight << endl;
}

class Bed :virtual public Furniture{
	public:
		Bed(){};
		Bed(double weight);
		void show(void);
};

Bed::Bed(double weight):Furniture(weight)  //派生类的构造函数应该去初始化基类的数据成员{
	this->weight = weight;
}
void Bed::show(){
	cout << "床重量为:" << weight << endl;
}

class SofaBed:public Sofa,public Bed{
	public:
		SofaBed(){};
		SofaBed(double weight);
		void show(void);
};
SofaBed::SofaBed(double weight) :Furniture(weight),Sofa(weight), Bed(weight){
	this->weight = weight;
}
void SofaBed::show(){
	cout << "沙发床重量为:" << weight << endl;
}

//=========================================
int main(){
	Furniture furniture(1);
	Sofa sofa(2);
	Bed bed(3);
	SofaBed sofabed(3.5);
    //此处发生同名隐藏
	sofabed.show();//沙发床重量为:3.5
	
    /*类型兼容*/
	Furniture *p;
	p = &furniture;
	p->show();//家具重量为:1
	p = &sofa;
	p->show();//家具重量为:2
	p = &bed;
	p->show();//家具重量为:3
	system("pause");
	
	return 0;
}

3.2.6 类对象的构造顺序

  1. 该类有直接或间接的虚基类,则先执行虚基类的构造函数
  2. 按照基类在派生类中的声明顺序执行构造函数
  3. 按照声明顺序对新增成员初始化
  4. 执行构造函数的函数体

3.3 多态性

指允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。(发送消息就是函数调用)

3.3.1 多态及其实现

多态分类

专用多态

  • 重载多态:普通函数和成员函数,运算符重载
  • 强制多态:变量的类型变化

通用多态

  • 包含多态:同一类族中,不同类的同名函数的多态行为
  • 参数多态

  • 编译时多态:函数重载,运算符重载
  • 运行时多态:虚函数机制

绑定:计算机程序自身相互关联的过程:一条消息和一个对象方法相结合的过程

  • 静态绑定:发生在编译期间
  • 动态绑定:发生在程序运行期间,根据在执行期间判断所引用对象的实际类型调用相应的方法

运行时多态

运行期多态的实现依赖虚函数机制。当某个类声明了虚函数时,编译器将为该类对象声明一个虚函数表指针,并为该类设置一张唯一的虚函数表,虚函数表中存放的是该类虚函数的地址。运行期间通过虚函数表指针与虚函数表确定该类虚函数的真正实现

在这里插入图片描述

class Animal{
    public :
        virtual void shout() = 0;
};
class Dog :public Animal{
    public:
        virtual void shout(){ cout << "汪汪!"<<endl; }
};
class Cat :public Animal{
    public:
        virtual void shout(){ cout << "喵喵~"<<endl; }
};
class Bird : public Animal{
    public:
        virtual void shout(){ cout << "叽喳!"<<endl; }
};

int main(){
    Animal *anim1 = new Dog;
    Animal *anim2 = new Cat;
    Animal *anim3 = new Bird;
     
   //借由指针(或引用)调用的接口,在运行期确定指针(或引用)所指对象的真正类型,调用该类型对应的接口
    anim1->shout();
    anim2->shout();
    anim3->shout();
 
    //delete 对象
    ...
   return 0;
}

3.3.2 运算符多态(编译时多态)

与函数重载一样,都是通过在编译时静态绑定

限制:

  • 只能重载C++已有的运算符
  • 不能改变运算符操作数数量,至少有一个自定义类型
  • 重载之后,优先级和结合性不变
  • 不能用于重载的运算符:
    • 类属性访问符 .
    • 指针访问符 -> , *
    • 作用域 ::
    • 三目运算符 ?:
返回类型 operator 运算符(形参表){
    /*函数体*/
}
# 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;
	
	return 0;
}

类的运算符重载成员函数

运算符重载形参表会少一个,第一个操作数为调用该函数的对象,函数体可以通过 this 指针访问调用对象

  • 双目运算符左操作数为调用函数对象本身的数据,由 this 指出
  • 若为单目运算符,则为调用对象本身,不需要传递参数
单目运算符重载

前置单目运算符

class A{
    A& operator运算符(){
        /*函数体*/
    }
};

A oprand;//声明一个A类的操作数
++operand;
//<==>调用
//operand.operator++()

后置单目运算符

class A{
    A operator运算符(int){
        //与前置单目运算符从形参列表上做形式上区分
        /*函数体*/
    }
};

A operand;//定义A类操作数
operand++;
operand--;
//<==>调用operand.operator++(int)、operand.operator--(int)
//由于需要返回执行前操作数,所以需要以参数形式保存

/*对单目运算符++的重载*/
# include<iostream>
using namespace std;

class Clock{
	public:
		Clock(int hour = 0,int minute = 0,int second = 0);
		void showTime();
		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(){
	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:";
	myClock.showTime();
	cout << "Show myTime++:";
	(myClock++).showTime();
	cout << "++Show myTime:";
	(++myClock).showTime();
	
	return 0;
}

/*运行结果*/
First time:23:59:59
Show myTime++:23:59:59
++Show myTime:0:0:1
双目运算符重载
class A{
    A operator 运算符(DataType B){
        /*函数体*/
    }
};
class C{};

A operand1;
C operand2;

operand1 运算符 operand2;//在A类中定义运算符重载
//<==>相当于调用函数operand1.operator运算符(operand2)

/*对双目运算符+的重载*/
# 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,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();
	
	return 0;
}

/*运行结果*/
c1 = (5,4)
c2 = (2,10)
c3 = c1 - c2 = (3,-6)
c3 = c1 + c2 = (7,14)

3.3.3 虚函数(运行时多态)

virtual 函数名(参数列表);//使用virtual关键字声明为虚函数

虚函数不能是构造函数,不能是静态的成员函数

带默认值的形参函数,不能在派生类中重新定义,属于静态绑定

除声明为内联函数的虚函数,其余虚函数都是动态绑定的

如何确定派生类的成员函数为虚函数 :相同函数名、相同形参列表、返回值一致的成员函数确认为虚函数

基类含有的虚函数,其派生类必须覆盖基类虚函数及所有同名重载函数

/*在派生类中,隐式声明为虚函数,也可以在派生类中同名函数前加 virtual*/
#include<iostream>
using namespace std;

class Furniture{
	public:
		Furniture(){};
		Furniture(double weight);
		virtual void show(void);		
	protected:
		double weight;
};

Furniture::Furniture(double weight){
	this->weight = weight;
}
void Furniture::show(){
	cout << "家具重量为:" << weight << endl;
}

class Sofa:virtual public Furniture{
	public:
		Sofa(){};
		Sofa(double weight);
		void show(void);
};

Sofa::Sofa(double weight):Furniture(weight){  //派生类的构造函数应该去初始化基类的数据成员
	this->weight = weight;
}
void Sofa::show(){
	cout << "沙发重量为:" << weight << endl;
}

class Bed:virtual public Furniture{
	public:
		Bed(){};
		Bed(double weight);
		void show(void);
};

Bed::Bed(double weight):Furniture(weight){  //派生类的构造函数应该去初始化基类的数据成员
	this->weight = weight;
}
void Bed::show(){
	cout << "床重量为:" << weight << endl;
}

class SofaBed:public Sofa,public Bed{
	public:
		SofaBed(){};
		SofaBed(double weight);
		void show(void);
};

SofaBed::SofaBed(double weight) :Furniture(weight),Sofa(weight), Bed(weight){
	this->weight = weight;
}
void SofaBed::show(){
	cout << "沙发床重量为:" << weight << endl;
}

//=========================================
int main(){
	Furniture furniture(1);
	Sofa sofa(2);
	Bed bed(3);
	SofaBed sofabed(3.5);
	sofabed.show();
	
	Furniture *p;
	p = &furniture;
	p->show();
	p = &sofa;
	p->show();
	p = &bed;
	p->show();
	system("pause");
	
	return 0;
}
/*运行结果*/
沙发床重量为:3.5
家具重量为:1
沙发重量为:2
床重量为:3
请按任意键继续. . .

可以用 基类名::函数名() 调用基类被隐藏的函数

基类构造函数 Base::Base() 中调用 virtual 修饰的虚函数,被调用的是基类 Base:: 中的虚函数

  • 基类被构造时,派生类还为构造

析构函数类似

虚析构函数

不能声明构造函数,但可以声明虚析构函数

适用情况: 如果有可能通过基类指针调用对象的析构函数,就需要让基类的析构函数为虚函数

virtual ~类名();

3.3.4 抽象类(运行时多态)

纯虚函数

virtual 函数类型 函数名(参数表) = 0;

声明为纯虚函数后,不能给出函数定义部分,带纯虚函数的类不能进行实例化

  • 如果将析构函数声明为纯虚构函数,必须给出定义

对基类纯虚函数的调用,必须通过 基类名::函数名(参数列表)

抽象类

带纯虚函数的类为抽象类

抽象类不能被实例化,但可以使用指针、引用

若其派生类给出纯虚函数的定义,不再是抽象类,可以进行实例化

  • 若未给出定义,仍为抽象类
# include<iostream>
using namespace std;

class Base1{
	public:
		virtual void display() const = 0;
};

class Base2:public Base1{
	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(){
	Base2 base2;
	Derived derived;

	fun(&base2);
	fun(&derived);

	return 0;
}

/*运行结果*/
Base2::display
Derived::display

3.3.5 多态性总结

动态类型与非动态类型声明原则

如果一个函数的执行方式十分明确,则将他声明为非虚函数;

如果一个类族中所有类都有这个函数特点,则将这个函数声明为虚函数;

如果一个类需要被其他类对其进行继承,就需要设计为多态,至少设计为虚析构函数

多态的三个条件

  • 继承:多态性体现在基类与派生类的继承关系中
  • 重写:父类中定义方法,子类中有对方法的重写
  • 类型兼容:父类的引用指向子类的对象

多态的实现——重写

重写与重载

前提:方法名一致

重载

发生在同一类中,

方法名相同

参数列表不同:参数个数,类型,顺序

  • 参数类型不同时,顺序不同也可以理解为参数列表不同
  • 参数类型相同时,顺序不同不构成重载

重载与返回类型无关

重写

一定发生在父子类之间,方法名相同

参数列表相同(参数个数,类型,顺序)

子类返回值类型和抛出的异常要是父类的子类

子类的访问控制权限比父类更高或相等

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/461638.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IMX6ULL裸机篇之按键消抖实验

一. 按键消抖 在之前的 按键中断实验时&#xff0c;我们讲了如何使用中断的方式驱动按键或GPIO。如果通过中断的方式处理按键的话&#xff0c;按键是需要消抖处理的。 而在之前 按键中断实验中&#xff0c;在中断处理函数中对按键进行消抖&#xff0c;调用了 delay 延时函数。…

剑指 Offer 32 - II. 从上到下打印二叉树 II

目录 题目思路BFS 题目来源 剑指 Offer 32 - II. 从上到下打印二叉树 II 题目思路 I. 按层打印&#xff1a; 题目要求的二叉树的 从上至下 打印&#xff08;即按层打印&#xff09;&#xff0c;又称为二叉树的 广度优先搜索&#xff08;BFS&#xff09;。BFS 通常借助 队列 的…

Midjourney v4 | 如何结合参考图像来生成AI艺术图

网址&#xff1a;midjourney.com 首页展示 首页如下图&#xff1a; 第一步&#xff1a;进入社群 点击首页右下角“Join the Beta”&#xff0c;进入如下页面&#xff1a; 点击“接受邀请”&#xff0c;验证之后进入 可以点击认证账号&#xff0c;进行注册&#xff1a; 应该不…

Redis三种集群模式

一、引言 Redis有三种集群模式&#xff0c;第一个就是主从模式&#xff0c;第二种“哨兵”模式&#xff0c;第三种是Cluster集群模式&#xff0c;第三种的集群模式是在Redis 3.x以后的版本才增加进来的&#xff0c;我们今天就来说一下Redis第一种集群模式&#xff1a;主从集群模…

【英语】100个句子记完7000个托福单词

其实主要的7000词其实是在主题归纳里面&#xff0c;不过过一遍100个句子也挺好的&#xff0c;反正也不多。 文章目录 Sentence 01Sentence 02Sentence 03Sentence 04Sentence 05Sentence 06Sentence 07Sentence 08Sentence 09Sentence 10Sentence 11Sentence 12Sentence 13Sent…

Redis的底层数据结构

Redis的底层数据结构 Redis的底层数据类型&#xff08;对比&#xff09;Redis的底层数据结构Redis数据类型和底层数据结构的对应关系Redis的使用 Redis的底层数据类型&#xff08;对比&#xff09; String&#xff08;字符串&#xff09;List&#xff08;列表&#xff09;Hash…

CRE66365 应用资料

CRE66365是一款高度集成的电流模式PWM控制IC&#xff0c;为高性能、低待机功耗和低成本的隔离型反激转换器。在正常负载条件下&#xff0c;AC输入高电压下工作在QR模式。为了最大限度地减少开关损耗&#xff0c;QR 模式下的最大开关频率被内部限制为 77kHz。当负载较低时&#…

Dcoekr 部署前后端分离项目SpringBoot +Vue

1.docker 部署vue docker 安装 nginx的镜像 niginx 配置文件 nginx.conf #user nobody; worker_processes 1;#error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info;#pid logs/nginx.pid;events {worker_connections…

给大家分享一个比Top更好用的Linux进程管理工具htop

一、前言 相信用过Linux操作系统的同学对Top应该都不陌生&#xff0c;我们通过Top命令可以查看CPU的占用率以及每个进程的详细信息&#xff0c;但是今天我要给大家分享一个比Top更好用的进程管理工具htop&#xff08;High Top&#xff09;。 二、htop功能介绍 htop 是一个高…

Shell编程规范及变量

这里写目录标题 一、Shell脚本编程概述1.1 shell脚本的概念1.2Shell脚本应用场景1.3 shell的作用1.4 linux中有哪些shell 二、 shell脚本的使用2.1shell脚本的构成2.2 运行脚本2.3 重定向和管道操作2.31交互式硬件设备2.32 重定向操作2.33 管道符号 三、shell脚本变量3.1 shell…

【FPGA-DSP】第九期:音频信号处理

从本文开始将记录一些简单的音频信号处理算法在System Generator中的实现方法。本文将介绍如何搭建音频信号的采集与输出模型。 音频信号属于一维信号&#xff0c;一些基本概念如下&#xff1a; 采样频率&#xff1a;根据奈奎斯特采样定理&#xff0c;采样频率Fs应该不低于声…

Vite vue 使用cdn引入element-plus

vite-plugin-cdn-import&#xff1a;cdn的引入插件 npm i vite-plugin-cdn-import or pnpm i vite-plugin-cdn-import vite.config.js import AutoImport from unplugin-auto-import/viteexport default defineConfig({ plugins: [vue({reactivityTransform: true}),importT…

0401概述-最短路径-加权有向图-数据结构和算法(Java)

文章目录 1 最短路径2 最短路径的性质3 加权有向图的数据结构3.1 加权有向边3.2 加权有向图 4 最短路径4.1 最短路径API4.2 最短路径的数据结构4.3 边的松弛4.4 顶点的松弛 结语 1 最短路径 如图1-1所示&#xff0c;一幅加权有向图和其中的一条最短路径&#xff1a; 定义&…

事务—MySQL

文章目录 1.事务的四大特性1.1原子性1.2一致性1.3隔离性1.4持久性 2.并发访问中存在的一些问题2.1丢失更新2.2脏读2.3不可重复读2.4幻读 3.隔离级别解决一致性的问题3.1未提交读3.2提交读3.3可重复读3.4可串行化 4.不同隔离级别可以解决的问题 1.事务的四大特性 1.1原子性 事…

BBR原版/魔改/plus/锐速/七合一脚本linux加速脚本/硬盘挂载/cc防御/宝塔

BBR原版/魔改/plus/锐速七合一脚本linux加速脚本/硬盘挂载/CC防御/宝塔 新云分享的七合一脚本&#xff0c;包含原版BBR、魔改BBR、bbrplus以及锐速可选。 在vultr上Centos 7, Debian 8/9, Ubuntu 16/18测试通过&#xff0c;不支持ovz。 安装指令&#xff1a;复制下面命令在s…

Openswan安装和简单配置

Openswan安装和简单配置 安装环境&#xff1a; 操作系统&#xff1a;Ubuntu20.0.4TLS 用户权限&#xff1a;root下载Openswan: wget https://github.com/xelerance/Openswan/archive/refs/tags/v3.0.0.zip安装Openswan: 解压Openswan&#xff1a;&#xff08;PS&#xff1a…

[golang gin框架] 26.Gin 商城项目-前台自定义商品列表模板, 商品详情数据渲染,Markdown语法使用

一.前台自定义商品列表模板 当在首页分类点击进入分类商品列表页面时&#xff0c;可以根据后台分类中的分类模板跳转到对应的模板商品列表页面 1.管理后台商品分类模板设置如下图所示 2.代码展示 (1).商品控制器方法Category()完善 修改controllers/frontend/productController…

大模型竞逐,再造AI新格局

作者 | 辰纹 来源 | 洞见新研社 “面对AI时代&#xff0c;所有产品都值得用大模型重做一次。” 这是阿里巴巴集团董事会主席兼CEO、阿里云智能集团CEO张勇在2023阿里云峰会上对AIGC&#xff08;生成式AI&#xff09;进化的判断&#xff0c;在这背后则是由ChatGPT为起始点&…

Shiro学习笔记,一篇就够用了

目录 一、Shiro基础 1.1Shiro定义: 1.2Shiro架构: 1.3快速上手: 二、Spring整合Shiro 2.1导入spring整合shiro的依赖 2.2两个配置类 寻找maven版本号:Maven Repository: org.apache.shiro shiro-core (mvnrepository.com) Shiro官网:Apache Shiro | Simple. Java. Securi…

C. Trailing Loves (or L‘oeufs?)(求某个质因子在n的阶乘中的个数 + 思维)

Problem - C - Codeforces Aki喜欢数字&#xff0c;尤其是那些带有尾随零的数字。例如&#xff0c;数字9200有两个尾随零。Aki认为数字拥有的尾随零越多&#xff0c;它就越漂亮。 然而&#xff0c;Aki认为&#xff0c;一个数字拥有的尾随零的数量并不是固定的&#xff0c;而是…