C++继承、派生、访问权限、成员对象、插入运算符重载
#include <iostream>
using namespace std;
//继承inherit 派生derive。子类继承父类。基类派生出派生类。两个词描述的是一件事情
class ClassBase1 //声明基类
{
public:
int pub; //公有
protected:
int pro; //保护,类及子类外不可调用
private:
int pri; //私有,类外不可调用
};
class ClassDerive1 :public ClassBase1 //派生类。继承基类。继承的格式为: class 类名:[public/protected/private] 基类名 ,访问修饰符不写默认private
{
void accessbasemember()
{
pub = 3; //在public继承基类时,基类的public成员在子类中仍然是public可直接访问
pro = 2; //在public继承基类时,基类的protected成员在子类中仍然是protected可直接访问,但类及子类外不可访问。比如在main中创建对象,通过对象不能调用protected成员
//报错:pri = 1; //在public继承基类时,基类的private成员在子类中不可访问,要想访问需要通过父类的接口。所以private成员和protected成员的区别是子类中是否能访问,在类外通过创建对象的话两者都不能访问。同样子类的子类不可以访问父类们(无论哪一级)的private成员。即ClassDerive1的子类也不可访问ClassBase1的private成员
}
};
class ProtectedDerive1 :protected ClassBase1 //protected继承,会将父类的所有public成员作为protected访问权限
{
void accessbasemember()
{
pub = 20; //这里pub变成了protected。即外部不能通过创建这个类的对象来访问了(不影响父类的对象访问public成员)。且这个类的public/protected子类也将pub作为protected访问来继承
pro = 10; //依然是protected保持不变
//pri = 0; 依然不能访问,且子子类也不能,所以如果需要在子类中直接使用这个成员,并且不希望外部访问这个成员,就应该声明为protected
}
};
class PrivateDerive1 :private ClassBase1 //private继承,可省略,不写修饰词默认是private
{
void accessbasemember()
{
pub = 30; //pub作为private继承。这意味着外部不能调用。且子子类不能访问
pro = 20; //pro作为private继承。这使得这个成员在子子类中不能访问了。任何这个类的子类(无论三种访问修饰)都不能访问这个类的任何成员了
//pri = 0; 依然不能访问
}
//虽然父类们的private成员不能访问,但可以通过父类们的接口访问,这说明父类们所有的数据成员都会被继承,只是有些不能访问
//三种继承方式不影响子类中的新成员访问权限
public:
int d1; //依然公有
protected:
int d2;
private:
int d3;
public:
using ClassBase1::pub; //在私有继承中可以通过using 基类::成员 将基类的数据成员或成员函数重新定义访问权限,原本因为私有继承而私有的pub在public下using使得对外部可见
};
//通常使用public继承
class ClassBase2 //父类
{
public:
int base;
ClassBase2() { cout << "base no parameter" << endl; } //无参构造器
ClassBase2(int base)
:base(base) //通过列表初始化时不能使用this,也就是说这里的初始化发生在产生对象之前
{ cout << "base 1 parameter" << endl; } //有参构造器
~ClassBase2() { cout << "base destructor" << endl; } //析构器
ClassBase2(const ClassBase2& another):base(another.base){ cout << "base copy" << endl; } //拷贝构造器
ClassBase2& operator=(const ClassBase2& another) //赋值运算符重载
//: base(another.base) 报错。说明列表初始化只能在构造阶段使用。对已有的对象重新赋值不能调用
{
if(&another!=this)
base = another.base;
cout << "base operator=" << endl;
return *this;
}
};
class ClassDerive2:public ClassBase2{}; //子类。子类会继承父类 除构造器、析构器、拷贝构造器、赋值重载 之外的所有内容。 这里子类没有手动定义构造器等,程序默认生成public的四类函数
int maininherit1()
{
ClassBase2 b1; //调用"base no parameter" "base destructor"
ClassDerive2 d1; //调用"base no parameter" "base destructor",即子类自动生成的构造器会自动调用父类的 无参 构造器,且先构造父类再构造子类。子类自动生成的析构器会自动调用父类的析构器,且先析构子类再析构父类(后进先出),无论子类是否显式声明了析构器,子类的析构器在用户定义的代码之后会自动调用父类的析构器
ClassBase2 b2(2); //带参构造器 调用"base 1 parameter" "base destructor"
//报错:ClassDerive2 b2(2); 子类不继承父类的任何构造器
ClassBase2 b3(b2); //调用"base copy" 析构器不再赘述
ClassDerive2 d2(d1); //调用"base copy" ,子类默认生成的拷贝构造器会自动调用父类的拷贝构造器
d2 = d1; //调用"base operator=" ,子类默认生成的赋值运算符重载函数会自动调用父类的
//所以子类虽然不继承父类的这四类函数,却会在默认情况下使用,这里不继承是指不会将父类的四类函数作为当前函数的四类函数以直接使用
//对象的创建必须进行初始化。所以ClassDerive2对象创建时调用了ClassDerive2的构造器
//但ClassDerive2对象在属于ClassDerive2类之前先属于ClassBase2类。在分配好内存之后,程序先对ClassDerive2内存块中的ClassBase2部分初始化,再对ClassDerive2部分进行初始化。所以虽然看起来是创建了两个对象,实际是子类对象中保留的父类对象区域,而既然这个区域是父类对象,就要进行对象的初始化,所以会执行父类的构造器
return 0;
}
class ClassDerive3 :public ClassBase2 //另一个子类
{
public:
ClassDerive3() {} //手动声明默认构造器,程序不再自动生成。这里没有显式调用父类构造器,默认会调用父类的无参构造器来初始化父类的区域。如果父类没有无参构造器就会报错
ClassDerive3(int v) //手动声明有参构造器,只要是构造器(无论默认还是拷贝),只要没有显式调用父类构造器,就会默认调用父类无参构造器
:ClassBase2(v) //显式调用父类构造器,调用的位置只能写在这里,不能写在{}中,如果不写的话默认在这里写作:ClassBase2()无参构造器。
//:ClassBase2() //无参可省略,也就意味着调用子类的这个构造器,在执行到{}之前,都会调用冒号这里指定的父类构造器来初始化
{
//错误:ClassBase2(v);如果在这里写,就是额外生成一个当前块作用域内的对象,而非对当前对象的父类区域进行父类初始化
}
ClassDerive3(const ClassDerive3& another)
//这里不写父类构造器的话,默认使用父类无参构造器而不是拷贝构造器。因为拷贝构造器也只是构造器的一种重载。不手动定义拷贝构造器会把调用父类拷贝构造器的模板直接粘过来。手动定义了程序就将这个构造器一视同仁,都添加无参构造器
//:ClassBase2(another) 手动指定,这时父类构造器接收到子类的参数,先严格匹配,没有找到ClassBase2(ClassDerive3)重载构造器时,再将参数隐式转换为ClassBase2类型
//:ClassBase2(another.base) 也可以调用父类的有参构造器,想用哪个都可以
{}
ClassDerive3& operator=(const ClassDerive3& another)
//注意这里不能用 :xxxxx 因为这个函数不是构造器,而是函数成员
{
if (&another == this)
return *this;
//默认生成的operator= 会在这里调用父类的赋值函数,手动定义则需要手动写,不写不调用。因为这不同于构造器,创建对象时必须初始化,所以必须调用构造器,但赋值不是创建对象,不必须调用父类的赋值函数
//错误:operator=(another); 这样写会调用当前函数,变成无限递归
ClassBase2::operator=(another); //正确写法。在子类中通过父类的名称空间访问父类的函数成员
//ClassDerive3::operator=(another); //有效 当前类可以通过当前类的名称空间访问当前类的函数成员
//this->ClassDerive3::operator=(another); //有效
//base; //有效
//ClassBase2::base; //有效
//this->base; //有效
//this->ClassDerive3::base; //有效
//ClassDerive3::base; //有效
//结论:在类外不能通过类的名称空间访问类的函数成员,因为函数成员属于对象,没有对象不能调用。但在类内可以通过名称空间访问函数成员。这个过程会默认使用当前对象 *this
//当前类的operator=会遮挡父类的operator=函数,无论参数列表是否相同,只要重名就是挡住,不能直接访问。这称为shadow。解决方法为使用父类名称空间注明函数归属
return *this;
}
};
class ClassDerive4 :public ClassBase2
{
public:
const int conval; //const类型。c++中要求必须执行初始化,这和对象相似。
ClassBase1 b1; //在类中声明一个对象数据成员。即在ClassDerive4对象中保存一个完整的ClassBase1对象区域。因为声明对象必须初始化,所以这也和const成员类似
ClassDerive4()
: conval(0),b1(ClassBase1()), ClassBase2(2) //需要初始化的const成员、父类、对象数据成员都需要写在 :这里进行初始化。即便将父类构造器写在最后,也会最先调用,再对其他值进行初始化。而成员初始化的顺序是按照声明的顺序,所以即便先写b1()再写conval()也是先初始化conval
//成员的初始化相当于 const int conval = 0; ClassBase1 b1 = ClassBase1(); 所以b1是直接初始化而不是先构造再拷贝构造。
{
//必须初始化的内容不能写在这里。可以不初始化的成员可以写在这里,比如ClassBase1* ptr ,类的指针和类的对象不同,因为类的指针并没有创建对象,所以不必须初始化
}
//创建ClassDerive4对象的过程:分配内存->调用ClassBase2构造器初始化父类区域->调用ClassBase1构造器初始化对象成员->执行ClassDerive4构造器的函数体
//释放对象的过程永远和创建的过程相反(后进先出):执行~ClassDerive4析构器->调用~ClassBase1析构器->调用~ClassBase2析构器->释放内存 ,析构ClassDerive4对象并不会清空ClassBase1对象成员的内容,因为析构器的目的是释放堆内存。所以三个区域各自析构。最后安全释放内存,不需要在ClassDerive4的析构器中手动调用ClassBase1的析构器,即不用在类析构器中调用成员对象的析构器。
};
ostream& operator<<(ostream& out, const ClassBase2& c) //重载全局函数,插入运算符,实现cout<<ClassBase2对象
{
out << c.base;
return out; //接收和返回ostream&均为非const,因为流对象在使用中可能更改流状态
}
ostream& operator<<(ostream& out, const ClassDerive4& c)
{
//错误:out << c; 相当于operator<<(out,c)。根据严格匹配,会调用函数自身,无限递归
out << static_cast<const ClassBase2&>(c); //正确用法。需要将 const ClassDerive4& 转换成 const ClassBase2& 来调用打印父类的函数。类型转换应该写完整。有的情况下将const ClassDerive4& c转换成ClassBase2不会产生问题(虽然程序内部的执行依然有区别),但只是侥幸运行正常。类型转换应写完整类型
out << endl << c.conval;
return out;
}
class BaseUDM
{
char* data;
public:
BaseUDM()
{
data = new char[20];
cout << "base constructor" << endl;
}
BaseUDM(const BaseUDM& another)
{
data = new char[20];
cout << "base copy constructor" << endl;
}
~BaseUDM()
{
delete[]data;
cout << "base destructor" << endl;
}
BaseUDM& operator=(const BaseUDM& another)
{
if (this == &another)return *this;
cout << "base operator" << endl;
//省略赋值过程
}
};
class MemberUDM
{
char* data;
public:
MemberUDM()
{
data = new char[20];
cout << "member constructor" << endl;
}
MemberUDM(const MemberUDM& another)
{
data = new char[20];
cout << "member copy constructor" << endl;
}
~MemberUDM()
{
delete[]data;
cout << "member destructor" << endl;
}
MemberUDM& operator=(const MemberUDM& another)
{
if (this == &another)return *this;
cout << "member operator" << endl;
//省略赋值过程
}
};
class DeriveUDM:public BaseUDM
{
MemberUDM mem;
//在派生类不使用动态内存的情况下,对于使用动态内存的基类和使用动态内存的成员对象,默认的构造器、析构器、拷贝构造器、赋值运算符均可以正确工作,即在派生类函数中自动调用成员、基类的对应的函数
};
class DeriveUDM2 :public BaseUDM
{
MemberUDM mem;
char* deriveData_;
//当派生类需要使用动态内存时,不得不手动定义这几类函数来完成深拷贝
public:
DeriveUDM2() //如果要使用基类默认构造器、成员默认构造器以外的构造器需要显式指定
{
deriveData_ = new char[20];
}
DeriveUDM2(const DeriveUDM2& another)
:BaseUDM(another), mem(another.mem) //默认的派生类拷贝构造器会使用基类的拷贝构造器及成员的拷贝构造器,手动定义时则需要指定否则会调用默认构造器
{
deriveData_ = new char[20];
}
~DeriveUDM2()
{
delete[]deriveData_; //析构器依然会自动调用基类、成员的析构器,所以只需要处理派生类的动态内存
}
DeriveUDM2& operator=(const DeriveUDM2& another)
{
if (this == &another)return *this;
BaseUDM::operator=(another);//对于派生类中的基类部分(子对象)需要显式调用BaseUDM::operator=避免递归
mem = another.mem; //等价于mem.operator=(another.mem)
//省略赋值过程
}
//除析构器自动完成以外,其他函数都要考虑使用基类、成员的哪个函数
};