类成员初始化
方式:
1、通过构造函数的参数列表初始化。
2、在构造函数中赋值完成初始化。
//1、通过构造和函数的参数列表初始化
Seles_data::Sales_data(const Sales_data &sa)
{
this->bookNo = sa.bookNo;
this->revenue = sa.revenue;
this->units_sold = sa.units_sold;
}
但不是所有成员都可以通过构造函数内赋值完成初始化,比如const,引用类型的成员变量。这种类型需要用构造函数初始化列表进行初始化。
//2、构造函数中赋值完成初始化
class ConstRef
{
public:
ConstRef(int ii) : ci(ii), ri(ii)
{
i = ii;
}
private:
int i;
const int ci;
int &ri;
}
补充:构造函数初始值列表
构造函数初始值列表,是一种用于初始化类成员变量的机制。
它可以在构造函数体执行之前,以一种高效的方式对成员变量进行初始化。
这种初始化方式尤其适合初始化非静态成员变量和基类对象。
基本语法:
构造函数初始值列表紧跟在构造函数声明的参数列表之后,用冒号引入,并用逗号分割各个初始化操作。
每个初始化操作指定了成员变量或基类对象以及它们的初始化表达式。
ClassName::ClassName(参数) : member1(初始化表达式), member2(初始化表达式) {}
委托构造函数
是C++11引入的新特性。
它允许一个构造函数调用同一个类的另一个构造函数来完成初始化的过程。
这种机制可以避免代码的重复,特别是在处理具有多个构造函数且这些构造函数之间有许多共通初始化任务时。
基本语法:
class MyClass {
public:
// 构造函数A
MyClass(int a) : memberA(a) {
// 初始化代码A
}
// 构造函数B,委托给构造函数A
MyClass() : MyClass(0) {
// 初始化代码B
}
private:
int memberA;
};
在这个例子中,Myclass()构造函数(构造函数B)委托给了MyClass(int a)构造函数(构造函数A),通过传递一个默认值0。
这意味着,当使用构造函数B创建对象时,它首先会调用构造函数A来完成一部分初始化任务,然后再执行自己的初始化代码。
委托流程
- 委托调用:委托构造函数在初始化列表中直接调用另一个构造函数。
- 顺序执行:被委托的构造函数先执行其初始化列表和函数体中的初始化代码。
- 继续执行:然后控制权返回给委托的构造函数,继续执行其函数体中的初始化代码。
使用场景
委托构造函数适用于以下场景:
- 避免代码重复:当多个构造函数需要执行相同的初始化代码时。
- 简化代码:简化复杂的构造函数,提高代码的可读性和可维护性。
- 层次化初始化:按照不同的初始化层次和步骤,逐步完成对象的构建。
注意事项 - 避免循环委托:构造函数不能无限循环地相互委托,否则会导致编译错误。
- 委托给其他构造函数:委托构造函数可以委托给类内定义的任何其他构造函数,包括私有构造函数。
转换构造函数
是一种特殊的构造函数,它允许将一个其他类型的对象隐式或显式地转换为当前类的对象。
这种构造函数在实现类型转换时非常有用,可以增强代码的灵活性和可读性。
基本形式:
class MyClass {
public:
// 转换构造函数
MyClass(const OtherClass& other) {
// 转换逻辑
}
};
这里,MyClass类的构造函数接收一个OtherClass类型的常量引用作为参数。
这意味着,当你尝试用一个OtherClass对象初始化一个MyClass对象时,或者将一个OtherClass对象赋值给一个MyClass对象时,这个转换构造函数会被自动调用。
使用场景:
- 隐式类型转换:当需要从一个类型隐式转换为另一个类型时。比如:从一个自定义的时间类转换为内置的int类型表示时间戳。
- 显式类型转换:通过关键字explicit来防止隐式转换,增加代码的安全性。
示例:
//厘米类
class Centimeter
{
public:
Centimeter(double v) : value(v){} //构造函数初始值列表
double getValue() const
{
return value;
}
private:
double value;
};
//米类
class Meter
{
public:
//转换构造函数
Meter(cosnt Cenetimeter& cm) : value(cm.getValue() / 100.0){} //构造函数初始值列表
private:
double value;
};
int main()
{
Centimeter cm(500); //cm为500厘米
Meter m = cm; //隐式类型转换,将cm的500厘米转为5米
return 0;
}
explicit 关键字
用于修饰一个类的构造函数,目的是防止构造函数被用于隐式类型转换,
也就是说:使用explicit 关键字可以强制构造函数只能用于显式类型转换。
基本语法:
class MyClass
{
public:
//显式构造函数
explicit MyClass(int value) : data(value) {} //构造函数初始值列表
private:
int data;
}
聚合类
使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。
当一个类满足以下条件时,我们说它是聚合的:
1、所有成员都是public的;
2、没有定义任何构造函数;
3、没有类内初始值;
4、没有基类,也没有virtual函数。
以下是聚合类:
struct Data
{
int ival;
string s;
}
可以用初始化{}来初始化聚合类:
Data val2 = {0, "xiaohe"};
初始值的顺序必须与声明的顺序一致。
初始值列表的元素个数绝对不能超过类的成员数量。
constexpr关键字
C++11引入constexpr关键字。
允许在编译时计算表达式,这不仅适用于变量和函数,也适用于构造函数。constexpr 构造函数可以用于创建 constexpr 对象,这些对象在编译时就能被初始化,从而在需要常量表达式的地方使用。
constexpr构造函数的条件
要使构造函数成为 constexpr,它必须满足以下条件:
- 返回类型:构造函数不能有返回类型(因为构造函数返回的是正在构造的对象)。
- constexpr函数体:构造函数的函数体必须是 constexpr,这意味着它只能包含初始化列表和一条返回语句(或者没有返回语句,因为构造函数隐式地返回其正在构造的对象)。
- constexpr参数:所有参数必须是可以 constexpr 计算的。
- 成员初始化:所有成员变量必须通过构造函数的初始化列表进行初始化。
示例:
class Vector2D {
public:
constexpr Vector2D(double xVal = 0, double yVal = 0) : x(xVal), y(yVal) {}
constexpr double magnitude() const { return sqrt(x * x + y * y); }
private:
double x, y;
};
constexpr Vector2D globalVec(3.0, 4.0); // 编译时初始化
int main() {
constexpr Vector2D localVec(5.0, 12.0); // 编译时初始化
constexpr double length = localVec.magnitude(); // 编译时计算
return 0;
}
在这个例子中,Vector2D 的构造函数被声明为 constexpr,允许我们使用该构造函数在编译时创建 constexpr 对象(如 globalVec 和 localVec)。此外,magnitude 函数也是 constexpr,允许在编译时计算向量的长度。
使用场景
- 编译时常量:当需要在编译时创建和使用常量对象时,constexpr 构造函数非常有用。
- 模板元编程:在模板元编程中,constexpr 构造函数允许在编译时执行更复杂的操作,如计算数组的大小或执行数学计算。
- 接口一致性:在库或框架开发中,提供 constexpr 构造函数可以提高接口的一致性,使得用户可以在需要常量表达式的地方使用你的类。