【六、继承】多继承、继承中的构造函数和析构函数、类成员访问控制
一、类成员的访问控制
C++类成员的访问控制主要有三种权限,分别是:
public:可在类内部使用、可在派生类内部使用、可在类外部使用;
protected:可在类内部使用、可在派生类内部使用、不可在类外部使用;
private:可在类内部使用、不可在派生类内部使用、不可在类外部使用;
在类中不写权限默认为private私有属性,而struct默认为public公有属性。这里有一个小坑需要注意,有时候我们在写构造函数的时候如果忘记加public,那么该构造函数默认为private私有,这将导致我们在定义对象的时候出错
#include <iostream>
using namespace std;
class A
{
A(int a, int b)
{
this->a = a;
this->b = b;
}
private:
int a;
int b;
};
int main()
{
A a1(1, 2);
system("pause");
return 0;
}
编译程序会报错
因为构造函数默认为private,在外部不可访问,所以报错。只要在构造函数加上public权限即可。
二、继承
继承是C++的三大特性之一,通过继承可以实现代码复用。被继承的类称为基类,继承的类称为派生类,派生类是一种特殊的基类,它继承了基类除构造函数和析构函数之外的全部属性和方法,并且可以拥有自己的属性和方法,在继承过程中,派生类可以通过继承的属性来调整从基类继承的父类成员的对外访问属性。
public继承:父类public属性的成员在子类依然是public、父类protecte属性成员在子类依然是protected、父类private属性在子类依然是private ;(不改变父类中的属性)
protected继承:父类public属性的成员在子类变为protected、父类protected属性成员在子类依然是protected、父类private属性在子类依然是private;
private继承:父类public属性的成员在子类变为private、父类protected属性成员在子类变为private、父类private属性在子类依然是private;(全部变为private属性)
使用派生定义对象时,基类的私有成员其实也存在于派生类定义的对象中的,只不过无法访问。
继承案例
#include <iostream>
using namespace std;
class A
{
public:
int a;
void print_data_a()
{
cout << a << " " << b << " " << c << endl;
cout << "A 类成员函数" << endl;
}
protected:
int b;
private:
int c; //private 只能在自己内部使用 --- 私人使用
public:
A(int a, int b, int c)
{
this->a = a;
this->b = b;
this->c = c;
cout << "A 类构造函数" << endl;
}
~A()
{
a = b = c;
cout << "A 类析构函数" << endl;
}
};
//public 用于需要对外访问的情况(在类的外部访问)--- 公用
class B : public A
{
public:
void print_data_b()
{
cout << a << endl;
cout << b << endl;
//cout << c << endl; //A 的私有属性,不可访问
cout << "B 类成员函数" << endl;
}
protected:
private:
public:
B(int a, int b, int c) : A(a, b, c) //转去调用 A 类构造函数
{
cout << "B 类构造函数" << endl;
}
~B()
{
cout << "B 类析构函数" << endl;
}
};
//protected 用于在类及它的派生类中访问的情况(只能在派生类内部使用)--- 家族使用
class C : protected A
{
public:
void print_data_c()
{
cout << a << endl;
cout << b << endl;
//cout << c << endl; //A 的私有属性,不可访问
cout << "C 类成员函数" << endl;
}
protected:
private:
public:
C(int a, int b, int c) : A(a, b, c)
{
cout << "C 类构造函数" << endl;
}
~C()
{
cout << "C 类析构函数" << endl;
}
};
void func1()
{
B b1(1, 2, 3); //先调用 A 构造函数, 再调用 B 构造函数
b1.a = 10;
b1.print_data_a();
b1.print_data_b();
} //先调用 B 析构函数,再调用 A 析构函数
void func2()
{
C c1(1, 2, 3);
//c1.a = 10; //a 经过 protected 继承后变为 protected 属性
//c1.print_data_a(); //print_data_a 经过 protected 继承后变为 protected 属性
c1.print_data_c();
}
int main()
{
func1();
func2();
system("pause");
return 0;
}
三、继承中的构造与析构函数调用顺序
创建子类对象的时候,在执行子类构造函数的初始化列表时,会先调用父类构造函数,然后调用子类构造函数;析构子类对象时,会先调用子类析构函数释放子类资源,再调用父类析构函数释放从父类继承的资源。
#include <iostream>
using namespace std;
class MyClassA
{
public:
MyClassA()
{
cout << "A 构造函数" << endl;
}
~MyClassA()
{
cout << "A 析构函数" << endl;
}
protected:
private:
};
class MyClassB
{
public:
MyClassB()
{
cout << "B 构造函数" << endl;
}
~MyClassB()
{
cout << "B 析构函数" << endl;
}
protected:
private:
};
class MyClassC : public MyClassB
{
public:
MyClassC() : MyClassB(), a1()
{
cout << "C 构造函数" << endl;
}
~MyClassC()
{
cout << "C 析构函数" << endl;
}
public:
MyClassA a1;
protected:
private:
};
void FuncTest()
{
MyClassC c1;
}
int main()
{
FuncTest();
system("pause");
return 0;
}
编译运行,可以看到结果
先调用父类构造函数,在调用成员对象类的构造函数,最后调用自己的构造函数;析构函数调用顺序相反。
四、多继承
继承中如果子类和父类有同名变量,那么子类对象中,两个变量同时存在,用域作用符区分,默认情况下使用子类的成员,加父类的域作用符则使用父类的成员。继承中的静态成员要加域作用符。
#include <iostream>
using namespace std;
class MyClassB
{
public:
int a;
static int b;
void B_printA()
{
cout << a << endl;
}
protected:
private:
};
int MyClassB::b = 1;
class MyClassC : public MyClassB
{
public:
int a;
void C_printA()
{
cout << a << endl;
}
public:
protected:
private:
};
int main()
{
MyClassC c1;
c1.a = 1; //不加域作用符,默认使用子类
c1.C_printA();
c1.MyClassB::a = 2; //通过域作用符修改父类
c1.C_printA();
c1.B_printA();
c1.b = 3; //如果要使用静态成员b,必须要有int MyClassB::b = 1; 这句话
//如果不使用b,可以没有,编译运行都不报错
system("pause");
return 0;
}
五、多继承的二义性与虚继承
当C继承了B1和B2,而B1和B2都继承了A时,C定义的对象使用A成员时,就会出现二义性,因为编译器无法判断这个成员是B1继承的还是B2继承的。解决这种二义性的方法,一个是加与作用符,二是使用虚继承。
#include <iostream>
using namespace std;
class MyClassA
{
public:
MyClassA()
{
cout << "A 构造函数" << endl;
}
int a;
protected:
private:
};
//解决二义性:2.使用虚继承,虚继承只调用一次构造函数
//class MyClassB1 : virtual public MyClassA
class MyClassB1 : public MyClassA
{
public:
int b1;
protected:
private:
};
//class MyClassB2 : virtual public MyClassA
class MyClassB2 : public MyClassA
{
public:
int b2;
protected:
private:
};
class MyClassC : public MyClassB1, public MyClassB2
{
public:
int c;
public:
protected:
private:
};
int main()
{
MyClassC c1;
//c1.a = 1; //错误(活动) E0266 "MyClassC::a" 不明确
c1.MyClassB2::a = 1; //解决二义性:1.加域作用符
system("pause");
return 0;
}
编译程序运行可以看到:
普通继承调用两次A类构造函数;
虚继承只调用一次A类构造函数。