C++20高级编程 第八章 熟悉类与对象
第八章 熟悉类与对象
声明:由于本人专门有关于OOP语义学的系列博客,因而在此处仅做简要介绍
对象的生命周期: 创建,销毁,赋值
默认构造函数
默认构造函数:没有参数的构造函数
如果没有指定任何构造函数,编译器将自动生成午餐构造函数.然而,如果声明了任何构造函数,编译器就不会再自动生成默认构造函数.
显式默认的构造函数
为了避免手动编写空的默认构造函数,C++现在支持 显式默认的默认构造函数(explicitly deleted default constructor)
export class SpreadsheetCell{
public:
SpreadsheetCell()=default;//显式默认
SpreadsheetCell(double initialValue);
};
显式删除的构造函数
C++还支持 显式删除的默认构造函数(explicitly deleted default constructor)
export class MyClass{
public:
MyClass()=delete;//显式删除
};
构造函数初始化器
除了在构造函数体内初始化数据成员外,C++还提供了一种在构造函数中初始化数据成员的方法,称为 构造函数初始化器 或 成员初始化列表,也可称为ctor-initializer.
这里举一个例子:
SpreadsheetCell::SpreadsheetCell(double initialValue)
{
setValue(initialValue);
}
上面是在构造函数体内初始化数据成员,与下面通过构造函数初始化器初始化是等价的
SpreadsheetCell::SpreadsheetCell(double initialValue)
:m_value(initialValue){}
某些数据成员由于其自身特性,必须在初始化器内进行初始化:
数据类型 | 说明 |
---|---|
const数据成员 | const变量创建后无法对其正确赋值,必须在创建时提供初始值 |
引用数据成员 | 如果不指向什么,引用将无法存在,且一旦创建引用不得改变指向目标 |
没有默认构造函数的 对象数据成员 |
C++尝试用默认构造函数初始化成员对象.如果不存在默认构造函数 ,就无法初始化该对象,必须显式调用其某个构造函数. |
没有默认构造函数的 基类 |
详见第十章 |
在初始化器中初始化顺序按照类内初始化顺序进行.
拷贝构造函数
C++中有一类特殊的构造函数,称为 拷贝构造函数(copy constructor).允许所创建的对象是另一个对象的副本.
export class SpreadsheetCell
{
public:
SpreadsheetCell(const SpreadsheetCell& src);//拷贝构造
};
拷贝构造函数采用源对象的const应用作为参数,在其内部,应该复制源对象的所有数据成员.
如果没有编写拷贝构造函数,C++会自动生成一个,用源对象中相应数据成员的值初始化新对象中的每个数据成员.
C++中传递函数参数的默认方式是值传递,这意味着函数或方法接收某个值或对象的副本.因此,无论何时给函数或方法传递一个对象,编译器都会调用新对象的拷贝构造函数进行初始化.
注意:为了提高性能,最好按const引用而非按值传递对象.
显式默认或显式删除的拷贝构造函数
SpreadsheetCell(const SpreadsheetCell& src)=default;
SpreadsheetCell(const SpreadsheetCell& src)=delete;
初始化列表构造函数
初始化列表构造函数(initializer-list constructor) 将std::initializer_list
下面通过一个例子说明这种用法:
#include <initializer_list>
class A{
public:
A(std::initializer_list<double>args)
{
for(const auto& value:args)
this->weights.push_back(value);
}
private:
std::vector<double>weights;
};
委托构造函数
委托构造函数(delegating constructor) 允许狗仔函数调用同一个类的其他构造函数.然而,调用只能存储在构造函数初始化器中,且必须是列表中唯一的成员初始化器.
SpreadsheetCell::SpreadsheetCell(double value){};
SpreadsheetCell::SpreadsheetCell(std::string_view initialValue)
:Spreadsheet(stringToDouble(initialValue)){}
在使用委托构造函数时,需要小心构造函数的递归.
转换构造函数
转换构造函数 代指某些特定单参数构造函数.编译器可以使用这些构造函数执行隐式转换.
class A{
public:
A(int){}
};
然而,这可能并不总是想要的行为.通过将构造函数标记为 explicit,可以防止编译器进行此类隐式转换.
注意:建议将任何可以使用单参数调用的构造函数标记为explicit,以避免意外的隐式转换,除非该转换为有用的.
一般而言,转换构造函数只支持一个参数.然而,自C++11标准引入了列表初始化,转换构造函数可以有多个参数.下面是一个例子:
class A{
public:
A(int){}
A(int,int){}
};
A(1);//A(int(1))
A({1});//A(int(1))
A({1,2});//A(int(1),int(2))
为了避免执行此类隐式转换,两个转换构造函数都可以声明为explicit.
从C++20开始,可以将布尔参数传递给explicit,这将用于类型萃取中.
explicit(a>=15)A(int value){}
情况 | 编译器生成 |
---|---|
没有定义构造函数 | 一个无参构造函数以及一个拷贝构造函数 |
只定义了默认构造函数 | 一个拷贝构造函数 |
自定义了拷贝构造函数 | 不会生成构造函数 |
只定义了一个非拷贝构造函数 | 一个拷贝构造函数 |
一个默认构造函数与 一个非拷贝构造函数 |
一个拷贝构造函数 |
销毁对象
当销毁对象时,会发生两件事:调用对象的析构函数,释放对象占用的内存.
通常而言,销毁对象会销毁在栈区的对象,但是在自由存储区中分配的对象不会自动销毁.必须对对象指针使用delete,从而调用析构函数并释放内存.
返回值优化(Return Value Optimization,RVO): 利用省略(copy elision)在返回值时优化掉成本高昂的拷贝构造函数.