【C++】3、类与类的关系
一、类和类之间的特殊关系
- 包含关系:组合类(类中包含其他类对象)
- 朋友关系:友元类(在类中声明友元数据)
- 继承关系:父类和子类
二、类的组合
1、概念
类中的数据成员是另一个类的对象
2、组合类的构造函数
若成员类对象带有参数的构造函数,那么组合类的构造函数需要负责自身数据初始化,还要完成对象数据的构造函数初始化
格式:
构造函数名(对象成员参数1, 对象成员参数2, ……):对象成员()
#include <iostream>
#include <cstring>
using namespace std;
class A
{
public:
A(int a, int b): a(a), b(b)
{
cout << "A()\n";
cout << a << "---" << b << endl;
}
~A()
{
cout << "~A()\n";
}
private:
int a, b;
};
class B
{
public:
// 若组合类的成员函数是带参构造函数,那么需要帮助初始化
B(int a = 10, int b = 12): a(a, b)
{
cout << "B()\n";
}
~B()
{
cout << "~B()\n";
}
private:
A a; // 将其他类的对象作为当前类的成员
};
int main()
{
B b;
return 0;
}

3、C++中的内存管理
每一层对象只用管理本层的资源申请和释放。
4、关于构造函数和析构函数的调用顺序
先调用内嵌对象的构造函数,然后执行本类的构造函数的内容,当有多个内嵌对象的时候,按照内嵌对象在当前类中的声明顺序进行构造。
先调用本类的析构函数释放资源,然后再调用内嵌对象的析构函数。
#include <iostream>
#include <cstring>
using namespace std;
class A
{
public:
A()
{
cout << "A()\n";
}
~A()
{
cout << "~A()\n";
}
private:
};
class B
{
public:
B()
{
cout << "B()\n";
}
~B()
{
cout << "~B()\n";
}
private:
A a; // 将其他类的对象作为当前类的成员
};
int main()
{
B b;
return 0;
}

三、友元关系
1、什么是友元关系
友元是C++提供的一种高效的访问机制,可以提高代码的效率,因为该方法是一种破坏数据封装和数据隐藏的方式。
将一个模块A声明为另一个模块B的友元,这个模块A就能直接访问B中所有的隐藏信息
- 友元的关键字:friend
- 友元函数:
friend void fun(); - 友元类:
friend class A; - 友元的声明可以写在类中的任何一个位置(私有共有都可)
2、友元函数
(1)、实现方式
将非类成员声明为类的友元函数,只需在函数之前加上friend修饰,那么在函数体中通过该类的对象直接访问该类的所有数据。
(2)、格式
friend 数据类型 函数名(参数列表){函数体}
(3)、操作及注意
- 在类中声明友元函数,类外实现友元函数
- 也可在类外声明,在类中直接实现定义友元函数
- 友元函数的访问模式:让类对象直接访问所有成员
- 类的友元函数是非成员函数,不能使用this指针
这里注意看,我们其实反复强调了一个字——类,但我们首先不去看这个字眼,先想一个很深刻的问题,就是两个函数之间构成友元关系吗,就比如我现在有两个类外函数A和B,我将A的声明用friend修饰放在B中,此时B是不是A的友元函数啊,A可以使用B函数中的变量吗?
答案当然是否定的,我们把目光看向本博客的开头,回首看一下我们会发现,友元关系我们是类与类之间使用的一种关系,所以当然不能抛开类去谈友元。
如果你有两个类外函数 A 和 B,并且将函数 A 的声明用 friend 关键字放在函数 B 中,此时 B 并不成为 A 的友元函数。在这种情况下,函数 A 和函数 B 之间没有建立友元关系。
友元关系通常是在类中使用 friend 关键字来建立的,它允许一个类授予其他类或函数对其私有成员的访问权限。在你的情况下,如果 A 和 B 是类外的函数而不是类的成员函数,并且将函数 A 的声明放在函数 B 中使用 friend 关键字修饰,这种语法是无效的,不会建立友元关系。
友元关系只能在类内部建立,通过在一个类中使用 friend 关键字来指定其他类或函数作为友元。在类外部,函数之间的访问权限是通过公开的接口和参数传递来实现的,而不是通过友元关系。因此,函数 A 和函数 B 之间不构成友元关系,它们之间的访问权限受到普通的访问规则限制。
(4)、几个例程
#include <iostream>
#include <cstring>
using namespace std;
class Data
{
public:
int publicdata;
private:
int privatedata;
protected:
int protecteddata;
// 将display声明为类中的友元函数
friend void display()
{
// 实例化对象
Data data;
// 赋值
data.publicdata = 120;
data.privatedata = 121;
data.protecteddata = 122;
// 打印
cout << data.publicdata << "---" << data.privatedata << "---" << data.protecteddata << endl;
}
// 声明show函数作为友元函数
friend void show();
};
// 声明一个类外非成员接口
void display();
// 外部实现友元函数
void show()
{
// 实例化对象
Data data;
// 赋值
data.publicdata = 120;
data.privatedata = 121;
data.protecteddata = 122;
// 打印
cout << data.publicdata << "---" << data.privatedata << "---" << data.protecteddata << endl;
}
int main()
{
display();
show();
return 0;
}

也不仅仅是对于类外的函数,我们也能将另外一个类的成员函数,声明为某个类的友元函数,就比如我可以将B类的公开成员函数接口声明A类的友元函数。
#include <iostream>
#include <cstring>
using namespace std;
// 声明类
class A;
// 实现B类的公开接口
class B
{
public:
void funB(A& a); // 借助A类对象访问数据
private:
};
// 定义A类
class A
{
public:
A(int a): a(a) {}
// 将B类中的公开函数接口作为A类的友元函数接口声明
friend void B::funB(A& a);
private:
int a;
};
// 外部实现数据获取
void B::funB(A& a)
{
cout << a.a << endl;
}
int main()
{
A a(156);
B b;
b.funB(a);
return 0;
}

这个程序涉及到了类之间的友元关系,可以分为以下几个部分来理解:
-
声明类 A 和类 B:
- 类 A 是一个简单的类,它具有一个私有成员变量
int a和一个构造函数,用于初始化a。 - 类 B 是另一个类,它具有一个公有成员函数
funB()。
- 类 A 是一个简单的类,它具有一个私有成员变量
-
在类 B 中声明
funB()函数:- 这个函数的参数是一个引用类型的 A 类对象。它用来通过 A 类对象访问 A 类的数据。
-
在类 A 中使用友元关系:
- 在类 A 的定义中,通过
friend关键字将类 B 中的funB()函数声明为 A 类的友元函数。 - 这意味着
funB()函数可以直接访问 A 类的私有成员变量a,即使它是私有的。
- 在类 A 的定义中,通过
-
在类外部实现
funB()函数:- 在类外部定义了
funB()函数,它接受一个 A 类对象的引用作为参数,并输出 A 类对象的私有成员变量a。
- 在类外部定义了
-
在
main()函数中使用类 A 和类 B:- 在
main()函数中,创建了一个 A 类对象a,并将值 156 传递给构造函数进行初始化。 - 创建了一个 B 类对象
b。 - 调用了
b对象的funB()函数,并将a对象作为参数传递给它。 funB()函数内部可以访问到a对象的私有成员变量a,并输出它的值。
- 在
总体来说,这个程序展示了如何使用友元函数实现类之间的数据访问。通过将类 B 的成员函数 funB() 声明为类 A 的友元函数,可以在 funB() 中直接访问 A 类对象的私有成员变量。这样可以实现某个类对另一个类的私有成员变量的访问权限,而不需要将这些成员变量公开为公有成员。
下面看看讲师的一个例程:
设计一个学生类,这个类有一个私有成员成绩socre,要求初始化5个学生对象,使用类的非成员函数完成所有学生总成绩计算
#include <iostream>
using namespace std;
class Student
{
public:
Student() {}
Student(int score): score(score) {}
~Student() {}
private:
int score;
// 声明非成员函数,此处采用友元函数
friend void Sum(Student* stu, int len);
};
void Sum(Student* stu, int len)
{
int sum = 0;
for(int i = 0; i < len; i++)
{
sum += stu[i].score;
}
cout << "sum:\t" << sum << endl;
}
int main()
{
Student stu[5] = {Student(0), Student(1), Student(2), Student(3), Student(4)};
Sum(stu, 5);
return 0;
}

3、友元类
在A类中通过关键字friend修饰另一个类B,那么在B类中就能通过A类对象访问A中的所有成员。
#include <iostream>
using namespace std;
class A
{
public:
int publicdata;
private:
int privatedata;
protected:
int protecteddata;
void show()
{
cout << "A::protect::show()" << endl;
}
// 将B类声明为当前A类的友元类
friend class B;
};
class B
{
public:
void show()
{
// 实例化A类对象
A a;
// 调用
a.privatedata = 100;
a.protecteddata = 150;
a.publicdata = 200;
cout << a.privatedata << "---" << a.protecteddata << "---" << a.publicdata << endl;
a.show(); // 私有成员函数
}
};
int main()
{
B b;
b.show();
return 0;
}

友元类的特点:
- 被声明友元类之后,可以通过对象访问类中所有成员
- 需要借助类的对象(指针或者引用)来访问对象数据
- 友元数据是单向的,即数据不是互通的,被声明后的类数据可被另一方使用,但该类不能使用另一方的数据
- 友元关系不能传递
- 友元关系不存在继承
四、继承关系
1、继承的概念
- 继承:保持已有类的特性,从而构造新类的过程
- 派生:在已有类的基础上新增自己的属性,产生新类的过程
- 父类:被继承的类,也被称为基类
- 子类:派生出来的新类,也被称为派生类
一个新类从多个基类继承,这种情况被称为多继承。
2、继承和派生的作用
- 继承:实现代码复用
- 派生:当有新的特征出现的时候,原有的类无法表达,需要对原有类继承扩展新功能
3、继承方式在类中的权限问题
| 父类成员/继承权限 | public | protected | private |
|---|---|---|---|
| (父)public | 可用/public | 可用/protected | 可用/private |
| protect | 可用/protected | 可用/protected | 可用/private |
| private | 不可用/private | 不可用/private | 不可用/private |
总结:
- 父类的共有成员,无论以什么方法继承,那么在派生类中就是什么权限
- 父类的保护成员,公有继承和保护继承都为保护成员,私有继承为私有成员
- 父类的私有成员,无论以什么方式继承,在派生类中都不可见
个人的一些总结:
在类中只要是可见的,都可以通过基类的公开函数接口访问,也能直接访问对应的数据,对于不可见的,只能在类中通过公开接口访问,而在类外,只有公开接口可以通过对象直接访问。
保护成员即实现了对外的数据隐藏,又方便继承,能让继承的派生类在内部直接访问,实现代码复用。
根据上面的情况,我们其实已经可以判断一个成员是什么权限的了:
- 在类外可以通过对象直接访问的一定是公开成员
- 排除掉共有成员后,以共有方式派生该类,在他的派生类中能直接访问该成员,说明是保护成员,否则就是私有成员
4、继承的语法
格式:
class 新类的类名:继承方式 基类名
{
};
#include <iostream>
using namespace std;
class Data
{
public:
int publicdata = 1;
void display()
{
cout << publicdata << endl;
cout << protecteddata << endl;
cout << privatedata << endl << endl << endl;
}
protected:
int protecteddata = 2;
private:
int privatedata = 3;
};
/* 派生类-公有继承 */
class A:public Data
{
public:
void show()
{
display();
/* 基类中的公有成员和保护成员,在类中可以直接调用 */
publicdata = 100;
protecteddata = 150;
display();
}
};
int main(int argc, char const *argv[])
{
A a;
a.show();
return 0;
}

5、继承的操作
(1)、构造函数和析构函数的运行情况
基类构造函数 -> 派生类构造函数 -> 派生类析构函数 -> 基类析构函数
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A() \n";
}
~A()
{
cout << "~A() \n";
}
};
class B: public A
{
public:
B()
{
cout << "B() \n";
}
~B()
{
cout << "~B() \n";
}
};
int main()
{
B b;
return 0;
}

看到这个熟悉的运行顺序,我们是不是想起来了上文提到过的“类的嵌套”,更大胆一点,我们可不可以认为基类的派生就是一种类的嵌套?
这个想法乍一看是有一定的道理的,但深究一下就不能成立了,但单纯的如此理解是可以的,可以如此类比,但是在语义上它们是不同的概念。
在继承关系中,派生类是基类的扩展,它继承了基类的成员和特性,并可以添加自己的成员和行为。派生类可以访问基类的公开和保护成员,但基类不能直接访问派生类的成员。派生类可以重写基类的虚函数,并且可以扩展或修改基类的功能。
嵌套类是将一个类定义在另一个类的内部,嵌套类是外部类的一个成员,与外部类的其他成员具有相同的访问权限。嵌套类可以直接访问外部类的私有成员,而外部类不能直接访问嵌套类的成员。
虽然基类和派生类之间的关系在某种程度上可以类比为类的嵌套,但从语法和语义上来说,它们是不同的概念。基类和派生类是通过继承关系连接的,而嵌套类是一个类定义在另一个类的内部。
(2)、继承时的带参构造
若基类中存在带参构造函数,则派生类需要帮助完成构造函数初始化。
格式:
派生类构造函数(基类函数1, 基类函数2, …… 本类函数):基类构造函数(基类参数1, 基类参数2), 本类成员(本类参数){}
#include <iostream>
using namespace std;
class A
{
public:
A(int a): a(a)
{
cout << "A() \n";
}
~A()
{
cout << "~A() \n";
}
private:
int a;
};
class B: public A
{
int b;
public:
B(int a, int b): A(a), b(b)
{
cout << "B() \n";
cout << a << "---" << b << endl;
}
~B()
{
cout << "~B() \n";
}
};
int main()
{
B b(1, 2);
return 0;
}

(3)、基类和派生类出现同名接口
如果基类和派生类的内部发生了函数重名,基类的同名函数会被派生类所忽略。
#include <iostream>
using namespace std;
class A
{
public:
A(int a): a(a)
{
cout << "A() \n";
}
~A()
{
cout << "~A() \n";
}
void show()
{
cout << "基类show()\n";
}
private:
int a;
};
class B: public A
{
int b;
public:
B(int a, int b): A(a), b(b)
{
cout << "B() \n";
}
~B()
{
cout << "~B() \n";
}
void show()
{
cout << "派生类show()\n";
}
// 类中的同名函数使用
void info()
{
show();
}
};
int main()
{
B b(1, 2);
b.info();
return 0;
}

如需要使用到基类的同名函数接口,调用方法如下:
- 类中:
基类::函数名(参数列表); - 类外:
派生类对象.基类::函数名(参数列表);


(4)、基类和派生类之间不存在重载函数
重载函数的前提是在同一个作用域内,而基类和派生类明显就是在;两个空间内。
你的理解是正确的。重载函数的前提是在同一个作用域内,而基类和派生类是在不同的作用域中定义的。基类和派生类的成员函数可以具有相同的名称,但它们在不同的作用域中。
基类和派生类之间的继承关系使得派生类可以重写(覆盖)基类的成员函数,称为函数的重写。在派生类中重新定义和实现与基类相同名称和参数的成员函数,可以实现对基类函数的重写。
重载函数是指在同一个作用域中,可以有多个函数具有相同的名称,但参数类型或数量不同。通过重载,可以根据不同的参数类型或数量来选择调用适当的函数。
所以基类和派生类之间的成员函数关系是函数的重写,而不是函数的重载。基类和派生类在不同的作用域内,派生类的成员函数重写了基类的成员函数,形成了覆盖关系。
下面看一个上课时候的一个例程:
现有教师类和学生类,
教师类属性:姓名、年龄、性别、职称、部门
学生类属性:姓名、年龄、性别、学号、生日选择公共属性建立基类,然后分别派生不同的类,实现数据初始化和数据显示
#include <iostream>
using namespace std;
class People
{
public:
People(string name, int age, string sex)
: name(name), age(age), sex(sex)
{
cout << "People()\n";
}
~People()
{
cout << "~People()\n";
}
void show()
{
cout << "name:\t" << name << endl;
cout << "age:\t" << age << endl;
cout << "sex\t" << sex << endl;
}
private:
string name;
int age;
string sex;
};
class Teacher: public People
{
public:
Teacher(string name, int age, string sex, string pro, string project)
: People(name, age, sex), pro(pro), project(project)
{
cout << "Teacher()\n";
}
~Teacher() {}
void show()
{
People::show();
cout << "pro:\t" << pro << endl;
cout << "project\t" << project << endl;
}
public:
string pro;
string project;
};
class Classmate: public People
{
public:
Classmate(string name, int age, string sex, int id, int date)
: People(name, age, sex), id(id), date(date)
{
cout << "Classmate()\n";
}
~Classmate() {}
void show()
{
People::show();
cout << "id:\t" << id << endl;
cout << "date\t" << date << endl;
}
public:
int id;
int date;
};
int main()
{
Teacher a("name1", 30, "man", "1", "big");
Classmate b("name2", 10, "woman", 8, 512);
cout << endl;
a.show();
cout << endl;
b.show();
cout << endl;
return 0;
}

6、多继承
- 单继承:派生类继承于一个基类
- 多继承:派生类继承于多个基类
- 多层继承:派生类的基类还有基类(多层派生)
- 多重派生:一个基类派生出多个子类
多继承的格式:
class 派生类名:继承方式 基类1, 继承方式 基类2...
{
成员操作
};
首先来看一下简单的多继承:
#include <iostream>
using namespace std;
class A
{
int a;
public:
A(int a): a(a)
{
cout << "A()\n";
}
~A()
{
cout << "~A()\n";
}
void show()
{
cout << "a:\t" << a << endl;
}
};
class B
{
public:
B()
{
cout << "B()\n";
}
~B()
{
cout << "~B()\n";
}
};
// 多继承
class C: public A, public B
{
public:
C(int a): A(a)
{
cout << "C()\n";
}
~C()
{
cout << "~C()\n";
}
void show()
{
A::show();
}
};
int main()
{
C c(1);
c.show();
return 0;
}

然后是多层继承:


7、虚基类
(1)、命名冲突
首先见识一下命名冲突:
#include <iostream>
using namespace std;
class A
{
protected:
int m_a;
};
class B1: public A
{
protected:
int m_b1;
};
class B2: public A
{
protected:
int m_b2;
};
class C: public B1, public B2
{
public:
void seta(int a){ m_a = a; } //命名冲突
void setb(int b1){ m_b1 = b1; } //正确
void setc(int b2){ m_b2 = b2; } //正确
void setd(int c){ m_c = c; } //正确
private:
int m_c;
};
int main()
{
C c;
return 0;
}

例程摘自博客:http://c.biancheng.net/view/2280.html,这一篇可以看看
我们知道,友元是不具备传递性的,但是继承这玩意是有的,要不然也不会有多层继承一说。
以这个视角看向本例程,我们首先看到,A派生除了B1和B2,此时A中的资源对B1和B2是可用的,又B1和B2一起派生出了C,B1和B2的资源也是对C是可见的,然后进行一番传递,C是可以看见A中的资源的,但是看见归看见,到底是通过B1看见的还是B2看见的并不清楚,不知编辑器不清楚,我们自己也是不清楚的,于是就产生了二义性,此时就会报错了。
(2)、虚基类
上面的情况叫做菱形继承,会出现资源冲突的情况,为此我们引入了虚基数的概念。

说具体点,就是为了解决不同路径继承下来的同名数据,在内存中存在不同份的拷贝导致数据不一致,如果只需要保留一份数据,那么可以将继承的共同的基类设置为虚基类,此时从不同路径拷贝下载的同名数据只保留一份内存。
我们的解决方案是让处于中间位置的间接基类签一份保证书,让他们承诺愿意共享相同的基类数据,为此,我们假如一个关键字 virtual
格式:
class 新类的类名: virtual 继承方式 基类名
{
}
#include <iostream>
using namespace std;
class A
{
protected:
int m_a;
};
class B1: virtual public A
{
};
class B2: virtual public A
{
};
class C: public B1, public B2
{
public:
int seta(int a)
{
m_a = a; // 此时已经没有命名冲突
return m_a;
}
};
int main()
{
C c;
cout << "m_a = " << c.seta(10);
return 0;
}

(3)、虚继承的注意事项

这里提到了之前没有见到过的一个词:子对象。
在面对对象编程中,一个对象可以由多个子对象组成。子对象是指在一个对象中作为成员存在的其他对象。当一个对象包含其他对象作为其成员时,这些成员对象被称为子对象。
这样的解释还是很费解,具体来看,在派生类中,基类的对象是派生类对象的一个子对象,当派生类继承一个基类时,基类的成员变量和成员函数会成为派生类的一部分,这些基类成员在派生类对象中以子对象的形式存在。
对于上面的abc三条,真的是很晦涩难懂,叫ChatGPT帮忙解释了一下,很可悲的是,还是很难懂,,,,,,,
a. 在派生类的对象中,同名的虚基类只产生一个虚基类子对象,其他非虚基类产生各自的子对象。这意味着如果派生类继承了多个基类,其中有一个或多个是虚基类,并且这些虚基类有相同的基类,那么在派生类的对象中,只会有一个共享的虚基类子对象,而其他非虚基类则会各自产生自己的子对象。
b. 派生类的构造函数的初始化列表中必须对虚基类的构造函数进行调用;如果未列出参数初始化,则表示调用该虚基类的缺省构造函数。这意味着在派生类的构造函数中,必须在初始化列表中调用虚基类的构造函数,以确保虚基类对象的正确初始化。如果在初始化列表中未列出参数初始化,即使用了默认构造函数的形式,那么将调用虚基类的缺省构造函数进行初始化。
c. 在一个成员初始化列表中同时出现虚基类和非虚基类的构造函数调用时,虚基类的构造函数先于非虚基类的构造函数执行。这意味着在派生类的成员初始化列表中,如果同时出现了虚基类和非虚基类的构造函数调用,那么虚基类的构造函数将先于非虚基类的构造函数执行,确保虚基类对象的正确初始化顺序。
对于a:
当一个派生类继承了多个基类,并且其中有一个或多个是虚基类,并且这些虚基类有相同的基类,那么在派生类的对象中,只会有一个共享的虚基类子对象。
具体来说,假设有一个派生类 D 继承了两个基类 B1 和 B2,其中 B1 和 B2 都是虚基类,并且它们有一个共同的基类 Base。在这种情况下,D 类的对象中只会有一个 Base 类的子对象,而不会有两个。
这是因为虚基类的目的是为了避免多次复制同一个基类的数据,从而节省内存并保持数据的一致性。因此,在派生类的对象中,同名的虚基类只会产生一个共享的子对象。
举个例子,假设虚基类 Base 有一个成员变量 x,那么无论 D 继承了多少个虚基类 Base,D 类的对象中只会有一个 x 变量,而不会有多个。
这样设计可以避免派生类中对同一虚基类的数据产生冗余和不一致的情况,保证了派生类对象中虚基类数据的唯一性和一致性。
对于b:
当派生类的构造函数需要初始化虚基类时,可以使用构造函数的初始化列表来调用虚基类的构造函数。
假设有以下的类继承关系:
class Base {
public:
Base(int x) {
// Base类的构造函数
}
};
class Derived : public virtual Base {
public:
Derived(int x) : Base(x) {
// Derived类的构造函数
}
};
在上面的例子中,Derived类继承了Base类,并且Base类是虚基类(通过使用virtual关键字来声明)。当Derived类的对象被创建时,它需要调用Base类的构造函数来初始化Base类的成员。
在Derived类的构造函数初始化列表中,我们使用Base(x)来调用Base类的构造函数,并传递参数x。这样就确保了Derived类对象的Base子对象得以正确初始化。
所以,语句b中的描述是说,在派生类的构造函数初始化列表中,我们必须对虚基类的构造函数进行调用,以确保虚基类的成员得以正确初始化。
对于c:
在一个派生类的构造函数初始化列表中,如果同时出现了虚基类和非虚基类的构造函数调用,那么虚基类的构造函数会先于非虚基类的构造函数执行。
考虑以下类的继承关系:
class Base {
public:
Base(int x) {
// Base类的构造函数
}
};
class VirtualBase : public virtual Base {
public:
VirtualBase(int x) : Base(x) {
// VirtualBase类的构造函数
}
};
class NonVirtualBase : public Base {
public:
NonVirtualBase(int x) : Base(x) {
// NonVirtualBase类的构造函数
}
};
class Derived : public VirtualBase, public NonVirtualBase {
public:
Derived(int x) : VirtualBase(x), NonVirtualBase(x) {
// Derived类的构造函数
}
};
在这个例子中,Derived类同时继承了VirtualBase类和NonVirtualBase类。在Derived类的构造函数初始化列表中,我们分别调用了VirtualBase类和NonVirtualBase类的构造函数。
根据c中的描述,首先会执行VirtualBase类的构造函数,然后才会执行NonVirtualBase类的构造函数。这样可以确保虚基类的成员得以正确初始化,并且在构造过程中不会出现重复初始化。

浙公网安备 33010602011771号