58.类成员初始化方式?构造函数的执行顺序 ?为什么用成员初始化列表会快一些?
58.类成员初始化方式?构造函数的执行顺序 ?为什么用成员初始化列表会快一些?
1.类成员初始化方式
1.1初始化方式一:默认时初始化
如果类成员没有被显式初始化,将会使用默认初始化。默认初始化指没有提供初始化式的情况下,将使用默认值进行初始化。对于基本数据类型(如整数、浮点数等),默认值为0或空值(对于指针类型)。对于类类型,将会使用默认构造函数进行初始化。
class MyClass
{
public:
int a; // 默认初始化为0
MyClass() {} // 默认构造函数
};
1.2初始化方式二:声明时初始化(也称就地初始化,C++11后支持)
普通成员可以声明时初始化
class Aclass
{
public:
int mA = 1;// 声明时初始化
Aclass()
{
}
};
1.3初始化方式三:构造函数初始化
1.默认构造函数:如果没有显式地定义构造函数,则编译器会自动生成一个默认构造函数。
2.带参数的构造函数:可以显式地定义一个或多个带参数的构造函数来初始化类成员。
3.拷贝构造函数:可以显式地定义一个拷贝构造函数来初始化类成员。
4.移动构造函数:可以显式地定义一个移动构造函数来初始化类成员。
5.赋值运算符重载:可以显式地定义赋值运算符重载来初始化类成员。
对于在函数体中初始化,是在所有的数据成员被分配内存空间后才进行的。
class MyClass
{
public:
int mA;
MyClass(int a)
{
mA = a;
}
};
1.4初始化方式四:值初始化
可以使用值初始化来指定类成员的初始值。值初始化使用括号将初始值括起来。
class MyClass
{
public:
int mA;
MyClass():mA(10)// 在初始化列表初始化mA的值
{
}
};
1.5初始化方式五:初始化列表
列表初始化是给数据成员分配内存空间时就进行初始化,就是说分配一个数据成员只要冒号后有此数据成员的赋值表达式(此表达式必须是括号赋值表达式),那么分配了内存空间后在进入函数体之前给数据成员赋值,就是说初始化这个数据成员此时函数体还未执行。
class MyClass
{
public:
int a;
MyClass(int b) : a(b) {} // 使用成员初始化列表指定初始值为参数b
};
1.6初始化方式六:动态初始化
可以使用动态初始化在运行时为类成员分配内存并指定初始值。这种方式通常使用new
运算符进行动态内存分配,然后使用初始化列表来指定初始值。
class MyClass
{
public:
int a;
MyClass() { new(&a) int; } // 使用动态初始化并为a分配内存,不指定初始值
};
需要注意的是,对于类成员的初始化方式,应根据具体情况选择合适的方式。在一些情况下,使用默认初始化可能更加简洁和高效;而在其他情况下,显式初始化可能更明确和安全。
特例:
1.静态成员变量类内声明,类外初始化。
class MyClass
{
public:
static int mA;// 类内声明
MyClass()
{
}
};
int MyClass::mA = 1;// 类外初始化
2.非静态的常量如何初始化
只能在构造函数的初始化列表中初始化
int a = 1;
class MyClass
{
public:
const int mA;
MyClass():mA(a)// 在初始化列表初始化mA的值
{
}
};
3.静态常量整型数据成员
静态常量整型数据成员可以在类的声明中初始化,而且只有这种变量可以在类的声明中定义。
class MyClass
{
public:
MyClass() {};
private:
int a;
int b;
static const int sc_int = 10; //static const
};
2.一个派生类构造函数的执行顺序如下
① 虚拟基类的构造函数(多个虚拟基类则按照继承的顺序执行构造函数)。
② 基类的构造函数(多个普通基类也按照继承的顺序执行构造函数)。
③ 类类型的成员对象的构造函数(按照成员对象在类中的定义顺序)
④ 派生类自己的构造函数。
构造函数的执行顺序可以分为以下几个步骤:
1.基类构造函数:首先执行基类的构造函数,确保基类对象被正确地初始化。如果基类还有基类,则依次递归执行基类构造函数。
2.成员变量初始化:对于每个成员变量,根据其在类中的声明顺序,使用初始化列表或赋值语句进行初始化。
3.成员函数:如果类中有成员函数,则在构造函数执行期间调用这些成员函数。这些成员函数可以访问类的成员变量,但不能修改它们。
4.派生类构造函数:最后执行派生类的构造函数,确保派生类对象被正确地初始化。如果派生类还有成员变量或成员函数,则按照声明顺序进行初始化。
需要注意的是,在构造函数执行期间,对象的成员变量可以被初始化,但不能被修改。此外,如果基类和派生类都有构造函数,那么在执行派生类构造函数之前,必须先执行基类的构造函数。这是因为派生类依赖于基类的基础设施,必须确保基类对象已经被正确地初始化。
◆继承中的构造和析构
#include<iostream>
#include <stdio.h>
#include <stdlib.h>
#pragma warning(disable:4996)
using namespace std;
class A {
public:
A() {
cout << "A类构造函数!" << endl;
}
~A() {
cout << "A类析构函数!" << endl;
}
};
class B : public A {
public:
B() {
cout << "B类构造函数!" << endl;
}
~B() {
cout << "B类析构函数!" << endl;
}
};
class C : public B {
public:
C() {
cout << "C类构造函数!" << endl;
}
~C() {
cout << "C类析构函数!" << endl;
}
};
void test() {
C c;
}
int main()
{
test();
return 0;
}
输出:
A类构造函数!
B类构造函数!
C类构造函数!
C类析构函数!
B类析构函数!
A类析构函数!
请按任意键继续. . .
继承与组合混搭的构造和析构
#include<iostream>
#include <stdio.h>
#include <stdlib.h>
#pragma warning(disable:4996)
using namespace std;
class D {
public:
D() {
cout << "D类构造函数!" << endl;
}
~D() {
cout << "D类析构函数!" << endl;
}
};
class A {
public:
A() {
cout << "A类构造函数!" << endl;
}
~A() {
cout << "A类析构函数!" << endl;
}
};
class B : public A {
public:
B() {
cout << "B类构造函数!" << endl;
}
~B() {
cout << "B类析构函数!" << endl;
}
};
class C : public B {
public:
C() {
cout << "C类构造函数!" << endl;
}
~C() {
cout << "C类析构函数!" << endl;
}
public:
D c;
};
void test() {
C c;
}
int main()
{
test();
return 0;
}
输出:
A类构造函数!
B类构造函数!
D类构造函数!
C类构造函数!
C类析构函数!
D类析构函数!
B类析构函数!
A类析构函数!
3.为什么用成员初始化列表会快一些?
使用成员初始化列表而不是在构造函数内部逐个赋值,可以提高构造函数的执行效率。以下是使用成员初始化列表的几个好处:
1.避免多次复制:在构造函数内部逐个赋值时,如果对象有多个成员变量,可能会导致多次复制操作。而使用成员初始化列表,可以一次性将所有成员变量初始化为原始值,避免了多次复制的开销。
2.提高效率:使用成员初始化列表可以避免在构造函数内部进行逐个赋值时需要进行额外的计算和转换操作。成员初始化列表允许在编译时进行类型检查,并且可以将成员变量直接初始化为原始值,不需要进行额外的计算。
3.确保正确性:使用成员初始化列表可以确保成员变量的正确初始化顺序。成员初始化列表中的初始化顺序与类中成员变量的声明顺序一致,这可以避免在构造函数内部忘记初始化某个成员变量或初始化顺序不正确的问题。
总的来说,使用成员初始化列表可以提高构造函数的执行效率,并确保成员变量的正确初始化。但是,对于某些类型的成员变量(例如引用类型或无法进行拷贝构造的类型),使用成员初始化列表可能会导致问题,因此需要根据具体情况进行权衡。