在学习类和对象时,我们知道对象是基本,我们从对象上抽象出类。但是,世界可并不是一层对象一层类那么简单,对象抽象出类,在类的基础上可以再进行抽象,抽象出更高层次的类。所以经过抽象的对象论世界,形成了一个树状结构。 (动物分类图 金融类 家用电器分类图 家族图谱 — 泛化)
对象论的世界观认为,世界的基本元素是对象,我们将抽象思维作用于对象,形成了类的概念,而抽象的层次性形成了抽象层次树的概念。而抽象层次树为我们衍生出继承的概念,提供了依据。
我们需要继承这个概念,本质上是因为对象论中世界的运作往往是在某一抽象层次上进行的,而不是在最低的基本对象层次上。举个例子,某人发烧了,对其他人说:“我生病了,要去医院看医生。” 这句简短的话中有一个代词 “我”和三个名词“病”、“医院”、“医生”。这四个具有名词性的词语中,除了“我”是运作在世界的最底层——基本对象层外,其他三个都运作在抽象层次,在这个语境中,“病”、“医院”、“医生”都是抽象的。但是,本质上他确实是生了一个具体的病,要去一个具体的医院看一个具体的医生,那么在哲学上要如何映射这种抽象和具体呢?就是靠继承, 拿医生来说吧,所有继承自“医生”类的类所指的所有具体对象都可以替换掉这里具体的医生,这都不影响这句话语义的正确性。
回到编程语言层面,在C语言中重用代码的方式就是拷贝代码、修改代码。C++中代码重用的方式之一就是采用继承。继承是面向对象程序设计中重要的特征,可以说,不掌握继承就等于没有掌握面向对象的精华。继承在C++中的理解:从既有类(既有类:动物类。父类,基类)产生新类(新类:鸟类。子类,派生类)的过程。通过继承,我们可以用原有类型来定义一个新类型,定义的新类型既包含了原有类型的成员,也能自己添加新的成员,而不用将原有类的内容重新书写一遍。原有类型称为“基类”或“父类”,在它的基础上建立的类称为“派生类”或“子类。
1. 继承的定义
当一个派生类继承一个基类时,需要在派生类的类派生列表中明确的指出它是从哪个基类继承而来的。类派生列表的形式是在类名之后,大括号之前用冒号分隔,后面紧跟以逗号分隔的基类列表,其中每个基类前面可以有访问修饰限定符,其形式如下:
class 基类类名
{
public:
protetced:
private:
};
class 派生类类名
: public/protected/private 基类类名 //派生类列表
{
public:
protetced:
private:
};
派生类的生成过程包含3个步骤:
- 吸收基类的成员;
- 改造基类的成员;
- 新增自己的成员。
总结:吸收、改造、新增。(重要)
2. 继承的局限
所有的数据成员是可以被派生类吸收、改造的,但部分成员函数不行:
- 构造函数
- 析构函数
- 用户重载的operator new/delete运算符
- 用户重载的operator=运算符
- 友元关系
3. 派生方式对基类成员的访问权限
派生类继承了基类的全部成员变量和成员方法(除了构造和析构之外的成员方法),但是这些成员的访问属性,在派生过程中是可以调整的。
派生(派生类列表,继承)方式有3种,分别是
- public(公有)继承
- protected(保护型)继承
- private(私有)继承
举例子说明:
//Point3D1.cc
#include <iostream>
using std::cout;
using std::endl;
class Point
{
public:
Point(int ix = 0, int iy = 0)
: _ix(ix)
, _iy(iy)
{
cout << "Point(int, int)" << endl;
}
int getY() const
{
return _iy;
}
~Point()
{
cout << "~Point()" << endl;
}
protected:
int _ix;
private://封装性
int _iy;
};
class Point3D
: public Point
{
public:
Point3D(int ix = 0, int iy = 0, int iz = 0)
: Point(ix, iy)//从基类吸收过来的数据成员的初始化,可以借助基类构造函数
, _iz(iz)
{
cout << "Point3D(int, int, int)" << endl;
}
void print() const
{
cout << "(" << _ix //protected
/* << ", " << _iy //error,能被继承但不能访问*/
<< ", " << getY() //ok,public
<< ", " << _iz //派生类private
<< ")" << endl;
}
~Point3D()
{
cout << "~Point3D()" << endl;
}
private:
int _iz;
};
void test()
{
cout << "sizeof(Point) = " << sizeof(Point) << endl;
cout << "sizeof(Point3D) = " << sizeof(Point3D) << endl;
Point3D pt3d(1, 2, 3);
/* pt3d._ix;//error,基类中是protected修饰,以public继承 */
/* pt3d._iy;//error,基类中是private修饰,以public继承*/
pt3d.getY();//ok,在基类中是public修饰,并且是public继承
}
int main(int argc, char* argv[])
{
test();
return 0;
}
//Point3D2.cc
#include <iostream>
using std::cout;
using std::endl;
class Point
{
public:
Point(int ix = 0, int iy = 0)
: _ix(ix)
, _iy(iy)
{
cout << "Point(int, int)" << endl;
}
int getY() const
{
return _iy;
}
~Point()
{
cout << "~Point()" << endl;
}
protected:
int _ix;
private://封装性
int _iy;
};
class Point3D
: protected Point
{
public:
Point3D(int ix = 0, int iy = 0, int iz = 0)
: Point(ix, iy)//从基类吸收过来的数据成员的初始化,可以借助基类构造函数
, _iz(iz)
{
cout << "Point3D(int, int, int)" << endl;
}
void print() const
{
cout << "(" << _ix //protected
/* << ", " << _iy //error,能被继承但不能访问*/
<< ", " << getY() //ok,protected
<< ", " << _iz //派生类private
<< ")" << endl;
}
~Point3D()
{
cout << "~Point3D()" << endl;
}
private:
int _iz;
};
void test()
{
cout << "sizeof(Point) = " << sizeof(Point) << endl;
cout << "sizeof(Point3D) = " << sizeof(Point3D) << endl;
Point3D pt3d(1, 2, 3);
/* pt3d._ix;//error,基类中是protected修饰,以protected继承 */
/* pt3d._iy;//error,基类中是private修饰,以protected继承*/
/* pt3d.getY();//error,在基类中是public修饰,并且是protected继承 */
}
class Point4D
: protected Point3D
{
public:
void show() const
{
cout << "(" << _ix //ok,protected
/* << ", " << _iy //error,能被继承但不能访问,基类private */
<< ", " << getY() //ok,protected
/* << ", " << _iz //error,派生类private */
<< ", " << _im
<< ")" << endl;
}
private:
int _im;
};
int main(int argc, char* argv[])
{
test();
return 0;
}
//Point3D3.cc
#include <iostream>
using std::cout;
using std::endl;
class Point
{
public:
Point(int ix = 0, int iy = 0)
: _ix(ix)
, _iy(iy)
{
cout << "Point(int, int)" << endl;
}
int getY() const
{
return _iy;
}
~Point()
{
cout << "~Point()" << endl;
}
protected:
int _ix;
private://封装性
int _iy;
};
class Point3D
: private Point
{
public:
Point3D(int ix = 0, int iy = 0, int iz = 0)
: Point(ix, iy)//从基类吸收过来的数据成员的初始化,可以借助基类构造函数
, _iz(iz)
{
cout << "Point3D(int, int, int)" << endl;
}
void print() const
{
cout << "(" << _ix //private
/* << ", " << _iy //error,能被继承但不能访问*/
<< ", " << getY() //ok,private
<< ", " << _iz //派生类private
<< ")" << endl;
}
~Point3D()
{
cout << "~Point3D()" << endl;
}
private:
int _iz;
};
void test()
{
cout << "sizeof(Point) = " << sizeof(Point) << endl;
cout << "sizeof(Point3D) = " << sizeof(Point3D) << endl;
Point3D pt3d(1, 2, 3);
/* pt3d._ix;//error,基类中是protected修饰,以private继承 */
/* pt3d._iy;//error,基类中是private修饰,以private继承*/
/* pt3d.getY();//error,在基类中是public修饰,并且是private继承 */
}
class Point4D
: private Point3D
{
public:
void show() const
{
/* cout << "(" << _ix //error,以private继承 */
/* << ", " << _iy //error,基类private */
/* << ", " << getY() //error,以private继承 */
/* << ", " << _iz //error,基类private */
/* << ", " << _im */
/* << ")" << endl; */
}
private:
int _im;
};
int main(int argc, char* argv[])
{
test();
return 0;
}
总结:派生类的访问权限规则如下:
- 不管以什么继承方式,派生类内部都不能访问基类的私有成员;
- 不管以什么继承方式,派生类内部除了基类的私有成员不可以访问外,其他的都可以访问;
- 不管以什么继承方式,派生类对象除了公有继承基类中的公有成员可以访问外,其他的一律不能访问。
此外,可以发现proctected继承可以无限的继承下去,但是private继承一次就终止。(千秋万代,断子绝孙)注意:如果不写继承方式,那么默认的继承方式都是私有的。
4. 派生类对象的构造
我们知道,构造函数和析构函数是不能继承的,为了对数据成员进行初始化,派生类必须重新定义构造函数和析构函数。由于派生类对象通过继承而包含了基类数据成员,因此,创建派生类对象时,系统首
先通过派生类的构造函数来调用基类的构造函数,完成基类成员的初始化,而后对派生类中新增的成员进行初始化。
派生类构造函数的一般格式为:
派生类的构造函数名(总参数列表)
: 基类的构造函数(基类构造函数的参数列表)
{
//派生类的构造函数的函数体
}
对于派生类对象的构造,我们分下面4种情况进行讨论:
- 如果派生类有显式定义构造函数,而基类没有显示定义构造函数,则创建派生类的对象时,派生类相应的构造函数会被自动调用,此时都自动调用了基类缺省的无参构造函数。 如何理解呢?
错误说法:创建派生类对象,会先执行基类的构造函数,然后执行派生类的构造函数。
正确理解:创建派生类对象的时候,会调用派生类的构造函数,但是派生类是继承基类的,所以会吸收基类的数据成员,为了完成从基类这边吸收过来的数据成员的初始化,会借鉴基类的构造函数,完成从基类吸收过来的数据成员的初始化,然后再执行派生类的构造函数的函数体。
#include <iostream>
using std::cout;
using std::endl;
class Base
{
public:
Base()
{
cout << "Base()" << endl;
}
};
class Derived
: public Base
{
public:
Derived(long derived = 0)
: Base()
, _derived(derived)
{
cout << "Derived(long)" << endl;
}
void print() const
{
cout << "derived: " << _derived << endl;
}
private:
long _derived;
};
int main(int argc, char* argv[])
{
Derived d(10);
d.print();
return 0;
}
2. 如果派生类没有显式定义构造函数而基类有显示定义构造函数,则基类必须拥有默认构造函数。原因是:派生类的构造函数会调用基类的默认(无参)构造函数。目的:还是完成吸收的数据成员的初始化。
#include <iostream>
using std::cout;
using std::endl;
class Base
{
public:
Base()
/* : _base(0) */
{
cout << "Base()" << endl;
}
private:
long _base;
};
class Derived
: public Base
{
public:
Derived()
{
cout << "Derived()" << endl;
}
};
int main(int argc, char* argv[])
{
Derived d;
return 0;
}
3. 如果派生类有构造函数,基类有默认构造函数,则创建派生类的对象时,基类的默认构造函数会自动调用,如果你想调用基类的有参构造函数,必须要在派生类构造函数的初始化列表中显示调用基类的有参构造函数。
4. 如果派生类和基类都有构造函数,但基类没有默认的无参构造函数,即基类的构造函数均带有参数,则派生类的每一个构造函数必须在其初始化列表中显示的去调用基类的某个带参的构造函数。如果派生类的初始化列表中没有显示调用则会出错,因为基类中没有默认的构造函数。
#include <iostream>
using std::cout;
using std::endl;
class Base
{
public:
Base()//当派生类自动调用时,如果没有提供这种默认的,则会报错
/* : _base(0) */
{
cout << "Base()" << endl;
}
Base(long base)
: _base(base)
{
cout << "Base(long)" << endl;
}
private:
long _base;
};
class Derived
: public Base
{
public:
Derived(long derived)
/* : Base() *///默认自动调用无参的
: Base(10)//可以使用带参的
, _derived(derived)
{
cout << "Derived()" << endl;
}
void print() const
{
cout << "derived: " << _derived << endl;
}
private:
long _derived;
};
int main(int argc, char* argv[])
{
Derived d(10);
d.print();
return 0;
}
从上面的几种情况可以知道:在创建派生类对象的时候,为了完成从基类吸收过来的数据成员的初始化,需要使用基类的构造函数,那么最好可以手动在派生类的初始化列表中将基类的构造函数显示的调用出来,这样可以保证代码不出错。
调用顺序如下:
step1. 完成对象所占内存的开辟;
step2. 调用基类的构造函数,完成从基类吸收的数据成员的初始化;
step3. 完成派生类中子对象成员、const数据成员、引用数据成员的初始化;
step4. 执行派生类构造函数的函数体。
5. 派生类对象的销毁
派生类对象在销毁的时候(离开作用域的时候),会执行派生类自己的析构函数(完成派生类自己数据成员的清理操作)。然后,当派生类的析构函数执行完成之后,基类的析构函数会被自动调用。所以,执行顺序是先执行派生类的析构函数,再执行基类的析构函数,这和执行构造函数时的顺序正好相反。
如果当考虑对象成员时,继承机制下析构函数的调用顺序:
step1.先调用派生类的析构函数;
step2.再调用派生类中成员对象的析构函数;
step3.最后调用普通基类的析构函数。
6. 多基继承(多基派生)
6.1 多基继承的派生类对象的构造和销毁
C++除了支持单根继承外,还支持多重继承。那为什么要引入多重继承呢?其实是因为在客观现实世界中,我们经常碰到一个人身兼数职的情况,如在学校里,一个同学可能既是一个班的班长,又是学生会中某个部门的部长;在创业公司中,某人既是软件研发部的CTO,又是财务部的CFO;一个人既是程序员,又是段子手。诸如此类的情况出现时,单一继承解决不了问题,就可以采用多基继承了。
多重继承的定义形式如下:
class 派生类
: public/protected/private 基类1
, ...
, public/protected/private 基类N
{
//...
};
举例如下:
#include <iostream>
using std::cout;
using std::endl;
class A
{
public:
A()
{
cout << "A()" << endl;
}
void print() const
{
cout << "void A::print() const" << endl;
}
~A()
{
cout << "~A()" << endl;
}
};
class B
{
public:
B()
{
cout << "B()" << endl;
}
void show() const
{
cout << "void B::show() const" << endl;
}
~B()
{
cout << "~B()" << endl;
}
};
class C
{
public:
C()
{
cout << "C()" << endl;
}
void display() const
{
cout << "void C::display() const" << endl;
}
~C()
{
cout << "~C()" << endl;
}
};
//1、派生类继承基类时,如果是多继承,那么多个基类的构造函数的执行顺序只与多个
//基类被继承的先后顺序有关,与基类的构造函数在派生类初始化列表中没有关系;
class D
/* : public A, B, C */
//2、对于多继承而言,每个基类的面前都要写继承方式,否则就会按照默认的私有进行继承。
: public A
, public B
, public C
{
public:
D()
: C()
, A()
, B()
{
cout << "D()" << endl;
}
~D()
{
cout << "~D()" << endl;
}
};
void test()
{
D d;//调用D的构造函数
d.print();
d.show();
d.display();
}
int main(int argc, char* argv[])
{
test();
return 0;
}
运行结果如下:
多基继承和单基继承的派生类构造函数完成的任务和执行顺序并没有本质不同,但在使用多基继承过程中,会产生两种二义性:成员函数访问冲突和数据成员的二义性。
6.2 成员函数访问冲突
一般来说,在派生类中对基类成员函数的访问应当具有唯一性,但在多基继承时,如果多个基类中存在同名成员函数的情况,造成编译器无从判断具体要访问的哪个基类中的成员,则称为对基类成员访问的二义性问题。
如下面的例子,我们先定义3个不同的类A、B、C,这3个类中都有一个同名成员函数print,然后让类D继承自A、B、C,则当创建D的对象d,用d调用成员函数print时,发生编译错误。
void test()
{
D d;
d.print();//error,多基继承(多基派生)成员函数访问冲突
解决方案:使用类名+作用域限定符的形式!
d.A::print();//ok
d.B::print();//ok
d.C::print();//ok
}
6.3 数据成员的二义性
即存在菱形继承的二义性问题。多基派生中,如果在多条继承路径上有一个共同的基类,如下图所示,不难看出,在D类对象中,会有来自两条不同路径的共同基类(类A)的双重拷贝。
出现这种问题时,我们的解决方案是采用虚拟继承。中间的类B、C虚拟继承基类A,就可以解决了。至于背后到底发生了什么,待我们学了多态的知识后一起做讲解。
举例解释如下:
#include <iostream>
using std::cout;
using std::endl;
class A
{
public:
A()
: _lx(0)
{
cout << "A()" << endl;
}
void setX(long lx)
{
_lx = lx;
}
void print() const
{
cout << "A::lx = " << _lx << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
long _lx;
};
class B
: virtual public A
{
//继承1份long _lx;
};
class C
: virtual public A
{
//继承1份long _lx;
};
class D
: public B
, public C
{
public:
D()
{
cout << "D()" << endl;
}
~D()
{
cout << "~D()" << endl;
}
//2份 long _lx;
};
void test()
{
cout << "sizeof(A) = " << sizeof(A) << endl;
cout << "sizeof(B) = " << sizeof(B) << endl;
cout << "sizeof(C) = " << sizeof(C) << endl;
cout << "sizeof(D) = " << sizeof(D) << endl;
cout << "=======" << endl;
D d;
d.B::setX(1);
d.B::print();
d.C::setX(100);
d.C::print();
//多继承问题二:数据成员的存储二义性
//解决方案:让B与C虚拟继承A(virtual)
d.setX(300);
d.print();
}
int main(int argc, char* argv[])
{
test();
return 0;
}
运行结果:
7. 基类与派生类间的相互转换
“类型适应”是指两种类型之间的关系,说A类适应于B类是指A类的对象能直接用于B类对象所能应用的场合,从这种意义上讲,派生类适应于基类,派生类的对象适应于基类对象,派生类对象的指针和引用也适应于基类对象的指针和引用。
- 可以把派生类的对象赋值给基类的对象
- 可以把基类的引用绑定到派生类的对象
- 可以声明基类的指针指向派生类的对象(向上转型)
总结一下,也就是说如果函数的形参是基类对象或者基类对象的引用或者基类对象的指针类型,在进行函数调用时,相应的实参可以是派生类对象。
举例如下:
#include <iostream>
using std::cout;
using std::endl;
class Base
{
public:
Base(long base = 0)
: _base(base)
{
cout << "Base(long)" << endl;
}
void print() const
{
cout << "Base::_base = " << _base << endl;
}
~Base()
{
cout << "~Base()" << endl;
}
private:
long _base;
};
class Derived
: public Base
{
public:
Derived(long base = 0, long derived = 0)
: Base(base)
, _derived(derived)
{
cout << "Derived(long, long)" << endl;
}
void show() const
{
cout << "Derived::_derived = " << _derived << endl;
}
~Derived()
{
cout << "~Derived()" << endl;
}
private:
long _derived;
};
void test()
{
Base base(11);
base.print();
cout << "=======" << endl;
Derived derived(22, 33);
derived.show();
cout << "将派生类对象转换为基类对象" << endl;
base = derived;//1、可以将派生类对象赋值给基类对象
//Base &operator=(const Base &rhs)
//const Base &rhs = derived
/* base.operator=(derived); */
base.print();
cout << "--" << endl;
Base &ref = derived;//2、可以将基类的引用绑定到派生类的对象
ref.print();
cout << "--" << endl;
Base *pbase = &derived;//3、可以将基类的指针指向派生类对象
pbase->print();
cout << "能否将基类对象转换为派生类对象?答案是:不可以" << endl;
//Derived &operator(const Derived &rhs);
//const Derived &rhs = base1;
/* derived = base1; error,1、不能将基类对象赋值给派生类对象*/
/* Derived &ref = base1;error,2、不能将基类的引用绑定到派生类 */
/* Derived *pderived = &base1;error,3、不能将派生类的指针指向基类的对象 */
}
int main(int argc, char* argv[])
{
test();
return 0;
}
运行结果为:
向上转型:从派生类向基类进行转换,都是可以的。
向下转型:从基类向派生类进行转换,本来语法是不支持的,但是C++支持强转,所以又支持了。但是向下转型存在不安全性。(有安全的,也有不安全的向下转型)
//向下转型(不安全的)
Base base2;
Derived *pderived = static_cast<Derived *>(&base2);
//向下转型(安全的)
Derived derived1;
Base *pbase2 = &derived1;
Derived *pderived3 = static_cast<Derived *>(pbase2);
8. 派生类对象间的赋值控制
从前面的知识,我们知道,基类的拷贝构造函数和operator=运算符函数不能被派生类继承,那么在执行派生类对象间的复制操作时,就需要注意以下几种情形:
- 如果用户定义了基类的拷贝构造函数,而没有定义派生类的拷贝构造函数,那么在用一个派生类对象初始化新的派生类对象时,两对象间的派生类部分执行缺省的行为,而两对象间的基类部分执行用户定义的基类拷贝构造函数;
- 如果用户重载了基类的赋值运算符函数,而没有重载派生类的赋值运算符函数,那么在用一个派生类对象给另一个已经存在的派生类对象赋值时,两对象间的派生类部分执行缺省的赋值行为,而两对象间的基类部分执行用户定义的重载运算符函数;
- 如果用户定义了派生类的拷贝构造函数或者重载了派生类的对象赋值运算符=,则在用已有的派生类对象初始化新的派生类对象时,或者在派生类对象间赋值时,将会执行用户定义的派生类的拷贝构造函数或者重载赋值函数,而不会再自动调用基类的构造函数和基类的重载对象赋值运算符,这时,通常需要用户在派生类的拷贝构造函数或者派生类的赋值函数中显示调用基类的拷贝构造或者赋值运算符函数。
下面举例首先说明第1、2种情形:
#include <string.h>
#include <iostream>
#include <ostream>
using std::cout;
using std::endl;
class Base
{
public:
Base()
/* : _pbase(nullptr) */
: _pbase(new char[1])
{
cout << "Base()" << endl;
}
Base(const char* pbase)
: _pbase(new char[strlen(pbase) + 1]())
{
cout << "Base(const char*)" << endl;
strcpy(_pbase, pbase);
}
Base(const Base& rhs)
: _pbase(new char[strlen(rhs._pbase) + 1]())
{
cout << "Base(const Base&)" << endl;
strcpy(_pbase, rhs._pbase);
}
Base& operator=(const Base& rhs)
{
cout << "Base& operator=(const Base&)" << endl;
if(this != &rhs)//1、自复制
{
//2、释放左操作数
delete [] _pbase;
_pbase = nullptr;
//3、深拷贝
_pbase = new char[strlen(rhs._pbase) + 1]();
strcpy(_pbase, rhs._pbase);
}
return *this;//4、返回*this
}
~Base()
{
cout << "~Base()" << endl;
if(_pbase)
{
delete [] _pbase;
_pbase = nullptr;
}
}
friend std::ostream& operator<<(std::ostream& os, const Base& rhs);
private:
char* _pbase;
};
std::ostream& operator<<(std::ostream& os, const Base& rhs)
{
if(rhs._pbase)
{
os << rhs._pbase;
}
return os;
}
class Derived
: public Base
{
public:
Derived(const char* pbase)
: Base(pbase)
{
cout << "Derived(const char*)" << endl;
}
~Derived()
{
cout << "~Derived()" << endl;
}
friend std::ostream& operator<<(std::ostream& os, const Derived& rhs);
};
std::ostream& operator<<(std::ostream& os, const Derived& rhs)
{
const Base &ref = rhs;
os << ref;
return os;
}
void test()
{
Derived d1("hello");
cout << "d1 = " << d1 << endl;
cout << "========" << endl;
Derived d2 = d1;//第1种情形:执行基类的拷贝构造函数
cout << "d1 = " << d1 << endl;
cout << "d2 = " << d2 << endl;
cout << "========" << endl;
Derived d3("world");
cout << "d3 = " << d3 << endl;
d3 = d1;//第2种情形:执行基类的赋值运算符函数
cout << "d1 = " << d1 << endl;
cout << "d3 = " << d3 << endl;
}
int main(int argc, char* argv[])
{
test();
return 0;
}
运行情况:
我们再来讨论第3种情形:
#include <string.h>
#include <iostream>
#include <ostream>
using std::cout;
using std::endl;
class Base
{
public:
Base()
/* : _pbase(nullptr) */
: _pbase(new char[1])
{
cout << "Base()" << endl;
}
Base(const char* pbase)
: _pbase(new char[strlen(pbase) + 1]())
{
cout << "Base(const char*)" << endl;
strcpy(_pbase, pbase);
}
Base(const Base& rhs)
: _pbase(new char[strlen(rhs._pbase) + 1]())
{
cout << "Base(const Base&)" << endl;
strcpy(_pbase, rhs._pbase);
}
Base& operator=(const Base& rhs)
{
cout << "Base& operator=(const Base&)" << endl;
if(this != &rhs)//1、自复制
{
//2、释放左操作数
delete [] _pbase;
_pbase = nullptr;
//3、深拷贝
_pbase = new char[strlen(rhs._pbase) + 1]();
strcpy(_pbase, rhs._pbase);
}
return *this;//4、返回*this
}
~Base()
{
cout << "~Base()" << endl;
if(_pbase)
{
delete [] _pbase;
_pbase = nullptr;
}
}
friend std::ostream& operator<<(std::ostream& os, const Base& rhs);
private:
char* _pbase;
};
std::ostream& operator<<(std::ostream& os, const Base& rhs)
{
if(rhs._pbase)
{
os << rhs._pbase;
}
return os;
}
class Derived
: public Base
{
public:
Derived(const char* pbase, const char* pderived)
: Base(pbase)
, _pderived(new char[strlen(pderived) + 1]())
{
cout << "Derived(const char*)" << endl;
strcpy(_pderived, pderived);
}
//派生类的拷贝构造函数
Derived(const Derived& rhs)
: Base(rhs)//显式执行基类Base的拷贝构造函数
, _pderived(new char[strlen(rhs._pderived) + 1]())
{
cout << "Derived(const Derived&)" << endl;
strcpy(_pderived, rhs._pderived);
}
//派生类的赋值运算符函数
Derived& operator=(const Derived& rhs)
{
cout << "Derived& operator=(const Derived&)" << endl;
if(this != &rhs)//1、自复制
{
Base::operator=(rhs);//特殊写法,有继承,有基类,此时显式执行Base的赋值运算符函数
//2、释放左操作数
delete [] _pderived;
_pderived = nullptr;
//3、深拷贝
_pderived = new char[strlen(rhs._pderived) + 1]();
strcpy(_pderived, rhs._pderived);
}
return *this;//4、返回*this
}
~Derived()
{
cout << "~Derived()" << endl;
if(_pderived)
{
delete [] _pderived;
_pderived = nullptr;
}
}
friend std::ostream& operator<<(std::ostream& os, const Derived& rhs);
private:
char* _pderived;
};
std::ostream& operator<<(std::ostream& os, const Derived& rhs)
{
const Base &ref = rhs;
if(rhs._pderived)
{
os << ref << ", " << rhs._pderived;
}
return os;
}
void test()
{
Derived d1("hello","world");
cout << "d1 = " << d1 << endl;
cout << "========" << endl;
Derived d2 = d1;//第3种情形:执行派生类的拷贝构造函数
cout << "d1 = " << d1 << endl;
cout << "d2 = " << d2 << endl;
cout << "========" << endl;
Derived d3("hubei", "wuhan");
cout << "d3 = " << d3 << endl;
d3 = d1;//第3种情形:执行派生类的赋值运算符函数
cout << "d1 = " << d1 << endl;
cout << "d3 = " << d3 << endl;
}
int main(int argc, char* argv[])
{
test();
return 0;
}
运行如下:
9. 禁止复制
很明显,根据以前的知识点,我们有在本类中进行操作,限制拷贝构造函数与赋值运算符函数正常使用,主要有两种方法:
#include <iostream>
using std::cout;
using std::endl;
class Example
{
public:
Example();
//方法1、将拷贝构造函数与赋值运算符函数设置为私有
private:
Example(const Example& rhs);
Example& operator=(const Example& rhs);
/* //方法2、将拷贝构造函数与赋值运算符函数=delete(删掉) */
/* Example(const Example& rhs) = delete; */
/* Example& operator=(const Example& rhs) = delete; */
};
void test()
{
Example e1;
Example e2 = e1;//拷贝构造函数,使其error
Example e3;
e3 = e1;//赋值运算函数,使其error
}
int main(int argc, char* argv[])
{
test();
return 0;
}
接下来我们使用派生类对象间的赋值控制的相关知识来实现,可以在基类中=delete或者设置为private:
#include <iostream>
using std::cout;
using std::endl;
class NonCpyable
{
public:
NonCpyable()
{
}
~NonCpyable()
{
}
private:
NonCpyable(const NonCpyable& rhs);
NonCpyable& operator=(const NonCpyable& rhs);
};
class Example
: NonCpyable
{
public:
Example();
};
void test()
{
Example e1;
Example e2 = e1;//拷贝构造函数,使其error
Example e3;
e3 = e1;//赋值运算函数,使其error
}
int main(int argc, char* argv[])
{
test();
return 0;
}