关于控制符的认识

我认为,在类内部,protected是一个“可被继承的私有成员”
而private是“不可被继承的私有成员”

而在继承类的时候,使用控制符public,protected,private就像在继承时多加了一层控制符,类似于经过一层滤纸,才把对方的当成自己的。

可以理解为控制符public是大口径滤纸,成员访问权限的public是大口径粒子。

叠甲:此处只是笔者的一个比喻,不具备权威性

所以public就像用“public”区过滤,不会改变基类public和protected性质(因为public开放程度更高,所以取交集的时候筛剩protected),并且无法继承对方“不想给的”private

使用protected和private就会让基类给出的public和protected被筛剩对应的控制符

内存空间储存顺序

对于一个派生类的实例,先存基类成员入栈,再存对象成员入栈,最后存自己的成员入栈。

同类的存入顺序,与其声明顺序相关,也在构造函数的初始化列表内体现。

默认会调用基类的无参构造,如果要调用有参构造,需要在成员初始化列表内显式调用。

注意:存入栈的顺序就相当于声明这个变量/函数的顺序

而折构释放的顺序也相当于从栈中pop的顺序

构造函数的问题

假如基类没有无参构造,那么就必须在成员初始化列表显示调用有参构造。

不然就相当于先声明类a的一个实例,再定义,默认调用无参构造,报错。

插一句题外话,在类内部直接给成员变量赋值相当于在构造函数的初始化列表给成员赋固定值。

改变访问权限

可以通过using A::x,把A中的x显式声明成公有成员。

这种本质上仍是继承,所以无法提升私有成员。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
using namespace std;
class A {
protected:
int x;
public:
A() : x(10) {}
};

class B : public A {
public:
using A::x; // 通过 using 声明提升 x 为公有成员
void print() {
std::cout << x << std::endl; // 现在可以访问 x
}
};
int main(){
B obj1;
cout<<obj1.x;
return 0;
}

继承顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;
class X {
public:
X() { cout << "X"; }
};
class Y {
public:
Y() { cout << "Y"; }
};
class Z : public Y, public X {
public:
Z() { cout << "Z"; }
};
int main() {
Z z;
}

输出:YXZ

继承顺序有现实意义,对应存入内存的顺序/声明顺序

默认拷贝构造函数会逐个调用基类的拷贝构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A {
public:
A() { cout << "A"; }
A(const A&) { cout << "A_copy"; }
};
class B : public A {
public:
B() { cout << "B"; }
};
int main() {
B b1;
B b2(b1);
}

输出:ABA_copy

派生类的构造函数

派生类的构造函数往往需要访问基类的私有成员。
此时可以把派生类声明为友元类或者显式调用拷贝函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Base {
public:
Base& operator=(const Base& other) {
std::cout << "Base assignment" << std::endl;
return *this;
}
};

class Derived : public Base {
public:
Derived& operator=(const Derived& other) {
if (this != &other) {
Base::operator=(other); // 显式调用基类赋值函数
std::cout << "Derived assignment" << std::endl;
}
return *this;
}
};

纯虚折构函数问题

构造函数无法成为纯虚函数,但是折构函数可以
不过纯虚折构函数仍然被要求给出定义,没有函数体的折构函数在被调用的时候是非法的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

class Base {
public:
Base() {
std::cout << "Base constructor" << std::endl;
}

virtual ~Base()=0;
};

Base::~Base(){
std::cout << "Base destructor" << std::endl;
}

纯虚函数可以被继承

如果不重写一个纯虚函数,那么这个函数将会被继承下去。
抽象类定义或继承了至少一个纯虚函数,且因为有纯虚函数,所以不能被实例化。

final类

类被标记成final之后无法被继承,否则会产生编译错误。

1
2
class A final {};
class B : public A {}; // ❌ 错误:A 是 final,禁止继承