在C++中,当我们想要在派生类中重载赋值运算符时,我们需要遵循一些特定的规则和约定。这是因为赋值运算符在派生类中涉及到基类的成员,并且涉及到对象自身的状态。
接第 第十二章丨C++编程宝典:快速上手、深入进阶、挑战高级技巧,助你成为编程达人 – 菜鸟资源 (xiciw.com) 继续讲解
以下是一个简单的示例,演示如何在派生类中重载赋值运算符:
#include <iostream>
class Base {
public:
int x;
Base(int value) : x(value) {}
};
class Derived : public Base {
public:
Derived(int value) : Base(value) {}
// 派生类的赋值运算符重载
Derived& operator=(const Derived& other) {
if (this != &other) { // 避免自赋值
Base::operator=(other); // 调用基类的赋值运算符
// 这里可以添加派生类的其他成员的赋值逻辑
}
return *this; // 返回对调用对象的引用,以便链式赋值
}
};
int main() {
Derived d1(10);
Derived d2(20);
d1 = d2; // 使用派生类的赋值运算符重载
std::cout << d1.x << std::endl; // 输出:20
return 0;
}
在上述代码中,我们首先定义了一个基类Base和一个派生类Derived。在Derived类中,我们重载了赋值运算符。这个重载的赋值运算符首先检查是否是自赋值(即this指针是否等于other的地址),然后调用基类的赋值运算符,最后添加派生类的其他成员的赋值逻辑。
注意,为了保持一致性和正确性,我们通常在派生类的赋值运算符重载中显式地调用基类的赋值运算符。
7.6. 派生类的赋值运算符重载
赋值运算符函数不是构造器,所以可以继承,语法上就没有构造器的严格一些。
7.6.1. 格式
子类& 子类::operator=(const 子类& another)
{
if(this == &another)
return *this; //防止自赋值
父类::operator =(another); // 调用父类的赋值运算符重载
this->salary = another.salary;//子类成员初始化
return * this;
}
7.6.2. 代码
基类
student.h
Student & operator=(const Student & another);
student.cpp
Student & Student::operator=(const Student & another)
{
this->name = another.name;
this->num = another.num;
this->sex = another.sex;
return * this;
}
派生类
graduate.h
Graduate & operator=(const Graduate & another);
graduate.cpp
Graduate & Graduate::operator=(const Graduate & another)
{
if(this == &another)
return *this;
Student::operator =(another);
this->salary = another.salary;
return * this;
}
测试代码
int main()
{
Graduate g("liuneng",2001,'x',2000);
g.dump();
Graduate gg = g;
gg.dump();
cout<<"-----------"<<endl;
Graduate ggg("gege",2001,'x',4000);
ggg.dump();
ggg = g;
ggg.dump();
return 0;
}
7.6.3. 结论:
派生类的默认赋值运算符重载函数,会调用父类的默认或自实现函数。派生类若自实现,
则不会发生调用行为,也不报错。需要自实现。
7.7. 派生类析构函数的语法
派生类的析构函数的功能是在该对象消亡之前进行一些必要的清理工作,析构函数没有类
型,也没有参数。析构函数的执行顺序与构造函数相反。
析构顺序
子类->成员->基类
无需指明析构关系。why? 析构函数只有一种,无重载,无默参。
7.8. 派生类成员的标识和访问
7.8.1. 作用域分辨符
格式:
基类名::成员名;基类名::成员名(参数表);
如果某派生类的多个基类拥有同名的成员,同时,派生类又新增这样的同名成员,在
这种情况下,派生类成员将 shadow(隐藏)所有基类的同名成员。这就需要这样的调用方式
才能调用基类的同名成员。
代码
#include <iostream>
using namespace std;
class Base
{
public:
void func(int)
{
cout<<"haha"<<endl;
}
};
class Drive:public Base
{
public:
void func()
{
// func(); //func 死循环
// Base::func(); //被 shadow 的成员,可以这样访问
cout<<"hehe"<<endl;
}
};
//
int main()
{
Drive d;
d.func(); // 访问派生类成员
// d.Base::func(3); //访问基类成员
return 0;
}
小结
重载:同一作用域 ,函数同名不同参(个数,类型,顺序);
隐藏:父子类中,标识符(函数,变量)相同,无关乎返值和参数(函数),或声明类
型(变量)。
7.8.2. 继承方式
7.8.2.1. 图示
7.8.2.2. 详解
public 公有继承
当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可访问。即基类的公有成员和保护成员被继承到派生类中仍作为派生类的公有成员和保护成员。派生类的其他成员可以直接访问它们。无论派生类的成员还是派生类的对象都无法访问基类的私有成员。
private 私有继承
当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可访问。基类的公有成员和保护成员被继承后作为派生类的私有成员,派生类的其他成员可以直接访问它们,但是在类外部通过派生类的对象无法访问。无论是派生类的成员还是通过派生类的对象,都无法访问从基类继承的私有成员。通过多次私有继承后,对于基类的成员都会成为不可访问。因此私有继承比较少用。
protected 保护继承
保护继承中,基类的公有成员和私有成员都以保护成员的身份出现在派生类中,而基类的私有成员不可访问。派生类的其他成员可以直接访问从基类继承来的公有和保护成员,但是类外部通过派生类的对象无法访问它们,无论派生类的成员还是派生类的对象,都无法访问基类的私有成员。
图示之:
7.8.3. 派生类成员属性划分为四种:
公有成员;保护成员;私有成员;不可访问的成员;
#include <iostream>
using namespace std;
class Base
{
public:
int pub;
protected:
int pro;
private:
int pri;
};
class Drive:public Base
{
public:
void func()
{
pub = 10;
pro = 100;
// pri = 1000;
}
};
//
int main()
{
Base b;
b.pub = 10;
// b.pro = 100;
// b.pri = 1000;
return 0;
}
7.9. why public
7.9.1. 继承方式与成员访问属性
7.9.2. 公有继承的意义:
#include <iostream>
using namespace std;
class Base
{
public:
int pub;
protected:
int pro;
private:
int pri;
};
class Drive:public Base
{
public:
};
private 在子类中不可见,但仍可通过父类接口访问。
7.9.2. 公有继承的意义:
#include <iostream>
using namespace std;
class Base
{
public:
int pub;
protected:
int pro;
private:
int pri;
};
class Drive:public Base
{
public:
};
private 在子类中不可见,但仍可通过父类接口访问。
public 作用:传承接口 间接的传承了数据(protected)。
protected 作用:传承数据,间接封杀了对外接口。
private 统杀了数据和接口
1.只要是私有成员到派生类中,均不可访问. 正是体现的数据隐蔽性.其私有成员仅可被本类的成员函数访问
2.如果多级派生当中,均采用 public,直到最后一级,派生类中均可访问基类的public,protected 成员.
兼顾了数据的隐蔽性和接口传承和数据传递
3.如果多级派生当中,均采用 private,直到最后一级,派生类中基类的所有成员均变为不可见.
只兼顾了数据的隐蔽性
4.如果多级派生当中,均采用 protected,直到最后一级,派生类的基类的所有成员即使可见,也均不可被类外调用
只兼顾了数据的隐蔽性和数据传递
综上所述:记住 public 足矣。
7.9.3. 私有继承和保护继承的存在意义
此两种方式有效的防止了基类公有接口的扩散。是一种实现继承,而不是一种单纯的isA 的关系了。
class Windows
{
public:
一般常见基础接口
protected:
常用特效接口
高级特效接口
private:
duang 特效接口
};
7.10.多继承
从继承类别上分,继承可分为单继承和多继承,前面讲的都是单继承。
7.10.1. 多继承的意义
俗话讲的,鱼与熊掌不可兼得,而在计算机就可以实现,生成一种新的对象,叫熊掌
鱼,多继承自鱼和熊掌即可。还比如生活中,“兼”。
7.10.2. 继承语法
派生类名::派生类名(参数总表)
:基类名 1(参数表 1),基类名(参数名 2)....基类名 n(参数名 n),
内嵌子对象 1(参数表 1),内嵌子对象 2(参数表 2)....内嵌子对象 n(参数表 n)
{
派生类新增成员的初始化语句;
}
7.10.3. 沙发床实现
7.10.3.1. 继承结构:
7.10.3.2. 代码:
床类
bed.h
#ifndef BED_H
#define BED_H
class Bed
{
public:
Bed();
~Bed();
void sleep();
};
#endif // BED_H
bed.cpp
#include "bed.h"
#include "iostream"
using namespace std;
Bed::Bed()
{
}
Bed::~Bed()
{
}
void Bed::sleep()
{
cout<<"take a good sleep"<<endl;
}
沙发类
sofa.h
#ifndef SOFA_H
#define SOFA_H
class Sofa
{
public:
Sofa();
~Sofa();
void sit();
};
#endif // SOFA_H
sofa.cpp
#include "sofa.h"
#include "iostream"
using namespace std;
Sofa::Sofa()
{
}
Sofa::~Sofa()
{
}
void Sofa::sit()
{
cout<<"take a rest"<<endl;
}
沙发床类
sofabed.h
#ifndef SOFABED_H
#define SOFABED_H
#include "sofa.h"
#include "bed.h"
class SofaBed:public Sofa,public Bed
{
public:
SofaBed();
~SofaBed();
};
#endif // SOFABED_H
sofabed.cpp
#include "sofabed.h"
SofaBed::SofaBed()
{
}
SofaBed::~SofaBed()
{
}
测试代码:
main.cpp
#include <iostream>
#include "sofa.h"
#include "bed.h"
#include "sofabed.h"
using namespace std;
int main()
{
Sofa s;
s.sit();
Bed b;
b.sleep();
SofaBed sb;
sb.sit();
sb.sleep();
return 0;
}
7.10.4. 三角问题(二义性问题)
#include <iostream>
using namespace std;
class X
{
public:
X(int d)
:_data(d){}
void setData(int i)
{
_data = i;
}
int _data;
};
class Y
{
public:
Y(int d)
:_data(d){}
int getData()
{
return _data;
}
int _data;
};
class Z:public X,public Y
{
public:
Z():X(2),Y(3)
{}
void dis()
{
cout<<X::_data<<endl;
cout<<Y::_data<<endl;
}
};
int main()
{
Z z;
z.dis();
z.setData(2000);
cout<<z.getData()<<endl;
return 0;
}
7.10.5. 钻石问题
7.10.5.1. 三角转四角
采用提取公因式的方法
#include <iostream>
using namespace std;
class M
{
public:
M(int i)
:_data(i){}
int _data;
};
class X:public M
{
public:
X(int d)
:M(d){}
void setData(int i)
{
_data = i;
}
};
class Y:public M
{
public:
Y(int d)
:M(d){}
int getData()
{
return _data;
}
};
class Z:public X,public Y
{
public:
Z():X(2),Y(3)
{}
void dis()
{
cout<<X::_data<<endl;
cout<<Y::_data<<endl;
}
};
int main()
{
Z z;
z.dis();
z.setData(2000);
cout<<z.getData()<<endl;
return 0;
}
7.10.5.2. 虚继承
#include <iostream>
using namespace std;
class M
{
public:
M(int d)
:_data(d)
{}
int _data;
};
class X :virtual public M
{
public:
X(int d)
:M(d){}
void setD(float d)
{
_data = d;
}
};
class Y:virtual public M
{
public:
Y(int d)
:M(d){}
int getD()
{
return _data;
}
};
class Z:public X,public Y
{
public:
Z(int _x,int _y):X(_x),Y(_y),M(100)
{}
void dis()
{
// cout<<X::_data<<endl;
// cout<<Y::_data<<endl;
cout<<_data<<endl;
}
};
int main()
{
Z z;
z.dis();
z.setData(2000);
cout<<z.getData()<<endl;
return 0;
}
7.10.5.3. 小结
虚继承的意义
在多继承中,保存共同基类的多份同名成员,虽然有时是必要的,可以在不同的
数据成员中分别存放不同的数据,但在大多数情况下,是我们不希望出现的。因为保
留多份数据成员的拷贝,不仅占有较多的存储空间,还增加了访问的困难。
为此,c++提供了,虚基类和虚继承机制,实现了在多继承中只保留一份共同成员。 虚基类,需要设计和抽象。
虚继承,是一种继承的扩展。
语法总结
a. M 类称为虚基类(virtual base class ),是抽象和设计的结果。
b. 虚继承语法
class 派生类名:virtual 继承方式 基类名
c.虚基类及间接类的实始化
class A{
A(int i)
{}
};
{
B(int n):A(n){}
};
class C:virtual public A
{
C(int n):A(n){}
};
class D:public B,public C
{
D(int n)
:A(n),B(n),C(n)
{}
};
7.10.5.4. 改造沙发床
这一章就到这里!下一章我们讲解多态(PolyMorphism)