类对象作为类成员
输出结果将会是这样的:
代码:
#include <iostream>
class B {
public:
B() {
std::cout << "B constructor called." << std::endl;
}
void show() {
std::cout << "Class B function called." << std::endl;
}
};
class A {
private:
B b; // A 类的成员是 B 类对象
public:
// A 类的构造函数
A() : b() { // 通过初始化列表显式调用 B 的构造函数
std::cout << "A constructor called." << std::endl;
}
void callB() {
b.show();
}
};
int main() {
A a; // 创建 A 类对象时,B 类的对象也会被构造
a.callB(); // 调用 A 类的方法,通过 A 类的成员对象调用 B 类的方法
return 0;
}
输出:
B constructor called.
A constructor called.
Class B function called.
输出解释:
-
B constructor called.
:当创建A
类的对象时,首先会调用B
类的构造函数。因为A
类有一个B
类类型的成员变量b
,所以B
的构造函数会被自动调用。 -
A constructor called.
:接着,A
类的构造函数被调用。这是在A
对象的构造过程中输出的信息。 -
Class B function called.
:然后,调用a.callB()
,这会通过A
类的成员对象b
调用B
类的show()
方法,输出这行信息。
总结起来,程序按照这个顺序执行并输出相应的构造函数信息。
好的,我们可以通过一个更复杂的示例来详细分析初始化列表的使用,尤其是在 B
类有多个成员属性的情况下。
示例:B
类具有多个属性
假设 B
类有多个成员变量(例如整数和字符串),我们希望在 A
类的构造函数中初始化这些属性。
代码示例:
#include <iostream>
#include <string>
class B {
private:
int x;
std::string y;
public:
// B 类的构造函数,带有参数
B(int _x, const std::string& _y) : x(_x), y(_y) {
std::cout << "B constructor called: x = " << x << ", y = " << y << std::endl;
}
void show() {
std::cout << "B::show() called: x = " << x << ", y = " << y << std::endl;
}
};
class A {
private:
B b; // A 类成员变量是 B 类对象
public:
// A 类的构造函数,使用初始化列表初始化 B 类成员
A(int x, const std::string& y) : b(x, y) {
std::cout << "A constructor called." << std::endl;
}
void callB() {
b.show(); // 调用 B 类的成员函数
}
};
int main() {
A a(10, "Hello"); // 创建 A 类对象时,B 类对象将被初始化
a.callB(); // 调用 A 类的方法,通过 A 类的成员对象调用 B 类的方法
return 0;
}
输出结果:
B constructor called: x = 10, y = Hello
A constructor called.
B::show() called: x = 10, y = Hello
解析:
1. B
类构造函数:
B
类有两个成员:x
和y
。我们通过构造函数B(int _x, const std::string& _y)
来初始化这两个成员。- 在
A
类的构造函数中,使用初始化列表: b(x, y)
来调用B
类的构造函数并传递相应的参数。
2. A
类构造函数:
- 在
A
类的构造函数中,B
类成员b
通过初始化列表进行初始化,b(x, y)
调用B
的构造函数,将x
和y
的值传递给B
类的构造函数。 - 如果我们没有显式地使用初始化列表,
B
类的默认构造函数(如果有的话)将会先被调用,然后再对x
和y
进行赋值。这样做的效率较低,并且不太符合我们需要的初始化顺序。
3. callB
函数:
callB()
函数调用了B
类的show()
方法,它输出B
类成员的值:x
和y
。
4. 输出解释:
- 当我们创建
A
类的对象a
时,首先会调用B
类的构造函数来初始化B
类的成员x
和y
,输出B constructor called: x = 10, y = Hello
。 - 然后会输出
A constructor called.
,表明A
类的构造函数被调用。 - 最后,通过
a.callB()
,会输出B::show() called: x = 10, y = Hello
,显示B
类成员的值。
为什么使用初始化列表?
-
初始化效率:
- 在
A
类构造函数的初始化列表中,我们直接初始化了B
类的成员x
和y
,这比在构造函数体内先默认构造B
对象然后再赋值要高效得多。
- 在
-
避免不必要的默认构造:
- 如果不使用初始化列表,C++ 会先使用
B
类的默认构造函数(如果有的话)构造对象,然后再进行赋值。对于像int
或std::string
这样的类型,默认构造和赋值都是有效的,但这会多进行一次不必要的赋值操作,浪费了效率。
- 如果不使用初始化列表,C++ 会先使用
-
显式控制初始化顺序:
B
类的成员变量x
和y
在初始化列表中明确初始化,这样可以控制它们的初始化顺序,确保它们在对象构造时按照正确的顺序被初始化。
-
必要性:
- 如果
B
类的成员包含const
或reference
类型(例如const int
或int&
),它们就无法在构造函数体内通过赋值进行初始化,而必须通过初始化列表来初始化。
- 如果
总结:
- 对于包含多个成员变量(特别是对象类型成员)的类,使用初始化列表是最佳实践。
- 初始化列表能够提高效率,避免多余的构造和赋值操作。
- 通过初始化列表可以确保成员变量按照正确的顺序初始化,并且对于
const
或引用类型成员,初始化列表是唯一可行的方式。