1、参考引用
- C++高级编程(第4版,C++17标准)马克·葛瑞格尔
2、建议先看《21天学通C++》 这本书入门,笔记链接如下
- 21天学通C++读书笔记(文章链接汇总)
类定义是一条 C++ 语句,必须用分号结束
// SpreadsheetCell.h
class SpreadsheetCell {
public:
// 类支持的方法
void setValue(double inValue);
double getValue() const; // 最好将不改变对象的任何数据成员的成员函数声明为 const
private:
double mValue; // 类的数据成员
};
类的成员
访问控制
声明顺序
类内成员初始化器
class SpreadsheetCell {
public:
...
private:
double mValue = 0;
};
类定义必须在方法定义之前,通常类定义在头文件中,方法定义在包含头文件的源文件中
#include "SpreadsheetCell.h"
void SpreadsheetCell::setValue(double inValue) {
mValue = inValue;
}
double SpreadsheetCell::getValue() const {
return mValue;
}
访问数据成员
mValue = inValue;
this 指针
void SpreadsheetCell::setValue(double value) {
// value = value;
this->value = value; // 避免名称歧义
}
void printCell(const SpreadsheetCell &cell) {
cout << cell.getString() << endl;
}
void SpreadsheetCell::setValue(double value) {
this->value = value;
printCell(*this); // 该指针指向 setValue() 操作的 SpreadsheetCell 对象
// cout << *this << endl; // 重载了 << 后可使用该行输出,不用编写 printCell 函数
}
可采用两种方法来创建和使用对象:在堆栈中或者在堆中
堆栈中的对象
SpreadsheetCell myCell;
myCell.setValue(6);
cout << "cell: " << myCell.getValue() << endl;
堆中的对象
SpreadsheetCell *myCellp = new SpreadsheetCell();
myCellp->setValue(3.7);
cout << "cell: " << myCellp->getValue() << endl;
// cout << "cell: " << (*myCellp).getValue() << endl; // 与上一行等价
// 用 new 为某个对象分配内存,使用完对象后,要用 delete 销毁对象(或使用智能指针自动管理内存)
delete myCellp;
myCellp = nullptr;
#include
class myClass {
private:
std::string mName;
};
int main() {
myClass obj;
return 0;
}
class SpreadsheetCell {
public:
SpreadsheetCell(double initialValue); // 构造函数
};
// 必须提供构造函数的实现
SpreadsheetCell::SpreadsheetCell(double initialValue) {
setValue(initialValue);
}
在堆栈中使用构造函数
SpreadsheetCell myCell(5);
cout << "cell: " << myCell.getValue() << endl;
// 不要显式调用构造函数
SpreadsheetCell myCell.SpreadsheetCell(5); // 编译错误
// 后来也不能调用构造函数
SpreadsheetCell myCell;
myCell.SpreadsheetCell(5); // 编译错误
在堆中使用构造函数
auto smartCellp = make_unique<SpreadsheetCell>(4);
// ...do something with the cell, 不需要删除智能指针
// 不推荐下面使用原始指针的方式
SpreadsheetCell *myCellp = new SpreadsheetCell(5);
delete myCellp;
myCellp = nullptr;
class SpreadsheetCell {
public:
SpreadsheetCell(double initialValue);
SpreadsheetCell(std::string_view initialValue);
};
// 该行没有定义 SpreadsheetCell 类的默认构造函数,无法通过编译
SpreadsheetCell cells[3];
// 基于堆栈的数组可使用下列初始化器的方式绕过上面的限制
SpreadsheetCell cells[3] = {SpreadsheetCell(0), SpreadsheetCell(23), SpreadsheetCell(41)};
class SpreadsheetCell {
public:
SpreadsheetCell(); // 默认构造函数
};
SpreadsheetCell::SpreadsheetCell() { // 默认构造函数的实现
mValue = 0; // 如果使用类内成员初始化方式则可省略该行代码
}
SpreadsheetCell myCell; // 在堆栈中创建对象时,调用默认构造函数不需要使用圆括号
myCell.setValue(6);
cout << "cell: " << myCell.getValue() << endl;
// 对于堆中的对象,可以这样使用默认构造函数
auto smartCellp = make_unique<SpreadsheetCell>();
默认构造函数 = 无参构造函数
- 默认构造函数并不仅仅是说如果没有声明任何构造函数就会自动生成一个构造函数
- 而且还指:如果没有参数,构造函数就采用默认值
class SpreadsheetCell {
public:
SpreadsheetCell() = default; // 显式默认构造函数
};
class MyClass {
public:
MyClass() = delete; // 显式默认构造函数
};
SpreadsheetCell::SpreadsheetCell(double initialValue) : mValue(initialValue) {}
class Foo {
public:
Foo(double value);
private:
double mValue;
};
Foo::Foo(double value) : mValue(value) {
cout << "Foo::mValue = " << mValue << endl;
}
// 将 Foo 对象作为自己的一个数据成员
class MyClass {
public:
MyClass(double value);
private:
// 必须保持下列顺序,否则输出结果未知
double mValue;
Foo mFoo;
};
// 其构造器函数首先在 mValue 中存储给定的值,然后将 mValue 作为实参来调用 Foo 构造函数
MyClass::MyClass(double value) : mValue(value), mFoo(mValue) {
cout << "MyClass::mValue = " << mValue << endl;
}
MyClass instance(1.2);
// 输出
Foo::mValue = 1.2
MyClass::mValue = 1.2
C++ 中有一种特殊的构造函数,叫作复制构造函数,允许所创建的对象是另一个对象的精确副本
class SpreadsheetCell {
public:
// 复制构造函数采用源对象的 const 引用作为参数
SpreadsheetCell(const SpreadsheetCell &src);
};
什么时候调用复制构造函数
显式调用复制构造函数
SpreadsheetCell myCell1(4);
SpreadsheetCell myCell2(myCell1);
按引用传递对象
#include
#include
#include
#include
using namespace std;
class EvenSequence {
public:
// 初始化列表构造函数
EvenSequence(initializer_list<double> args) {
if (args.size() % 2 != 0) {
throw invalid_argument("initializer_list should "
"contain even number of elements.");
}
mSequence.reserve(args.size());
// 将顺序遍历 initializer_list 并将其元素添加到 mSequence 中
for (const auto &value : args) {
mSequence.push_back(value);
}
}
// 用于打印 mSequence 中的所有元素
void dump() const {
for (const auto& value : mSequence) {
cout << value << ", ";
}
cout << endl;
}
private:
vector<double> mSequence;
};
int main() {
EvenSequence p1 = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; // 等号可省略
p1.dump();
try {
EvenSequence p2 = {1.0, 2.0, 3.0};
} catch (const invalid_argument& e) {
cout << e.what() << endl;
}
return 0;
}
SpreadsheetCell::SpreadsheetCell(string_view initialValue)
: SpreadsheetCell(stringToDouble(initialValue)) {}
此处的赋值运算符也称复制赋值运算符,因为左边和右边的对象都会继续存在。之所以要这样区分,是因为还有移动赋值运算符,当赋值结束后右边的对象会被销毁
SpreadsheetCell myCell(5), anotherCell;
anotherCell = myCell;
class SpreadsheetCell {
public:
SpreadsheetCell& operator= (const SpreadsheetCell &rhs);
};
SpreadsheetCell cell(4);
cell = cell;
SpreadsheetCell& SpreadsheetCell::operator= (const SpreadsheetCell &rhs) {
// this 是一个指向左边对象的指针,&rhs 是一个指向右边对象的指针
if (this == &rhs) {
return *this; // this 指针指向执行方法的对象,因此 *this 就是对象本身
}
}
声明时会使用复制构造函数,赋值语句会使用赋值运算符
SpreadsheetCell myCell(5);
SpreadsheetCell anotherCell(myCell); // 复制构造函数
SpreadsheetCell aThirdCell = myCell; // 也是复制构造函数
anotherCell = myCell; // 此处,anotherCell 已经构建,因此会调用 operator =
按值返回对象
string SpreadsheetCell::getString() const {
return doubleToString(mValue);
}
// 复制构造函数 & 赋值运算符
SpreadsheetCell myCell2(5);
string s1;
// 当 getString() 返回 mString 时,编译器实际上调用 string 复制构造函数
// 创建一个未命名的临时字符串对象,将结果赋给 s1 时,会调用 s1 的赋值运算符
s1 = myCell2.getString();
// 复制构造函数
SpreadsheetCell myCell3(5);
// 此时 s2 调用的是复制构造函数
string s2 = myCell3.getString();
通过移动语义,编译器可使用移动构造函数而不是复制构造函数,这样效率更高
复制构造函数和对象成员
// 现在 Bar 类的所有成员可以访问 Foo 类的 private 和 protected 数据成员和方法
class Foo {
friend class Bar;
// ...
}
// 也可将 Bar 类的一个特定方法作为友元
class Foo {
friend void Bar::processFoo(const Foo &foo);
// ...
}
// 独立函数也可成为类的友元
class Foo {
// 类中的 friend 声明可以用作函数的原型
friend void dumpFoo(const Foo &foo);
// ...
}
void dumpFoo(const Foo &foo) {
// 将 Foo 对象的所有数据转存到控制台,希望可以访问 Foo 对象的内部数据成员
}
#include
#include "SpreadsheetCell.h"
class Spreadsheet {
public:
Spreadsheet(size_t width, size_t height);
void setCellAt(size_t x, size_t y, const SpreadsheetCell &cell);
SpreadsheetCell& getCellAt(size_t x, size_t y);
private:
bool inRange(size_t value, size_t upper) const;
size_t mWidth = 0;
size_t mHeight = 0;
SpreadsheetCell* *mCells = nullptr;
};
Spreadsheet::Spreadsheet(size_t width, size_t height) : mWidth(width), mHeight(height) {
mCells = new SpreadsheetCell*[mWidth];
for (size_t i = 0; i < mWidth; ++i) {
mCells[i] = new SpreadsheetCell[mHeight];
}
}
void Spreadsheet::setCellAt(size_t x, size_t y, const SpreadsheetCell &cell) {
// 使用 inRange 类检测电子表格中的 x 和 y 坐标是否有效
if (!inRange(x, mWidth) || !inRange(y, mHeight)) {
throw std::out_of_range("");
}
mCells[x][y] = cell;
}
SpreadsheetCell& Spreadsheet::getCellAt(size_t x, size_t y) {
if (!inRange(x, mWidth) || !inRange(y, mHeight)) {
throw std::out_of_range("");
}
return mCells[x][y];
}
void verifyCoordinate(size_t x, size_t y) const;
void Spreadsheet::verifyCoordinate(size_t x, size_t y) const {
if (x >= mWidth || y >= mHeight) {
throw std::out_of_range("");
}
}
void Spreadsheet::setCellAt(size_t x, size_t y, const SpreadsheetCell &cell) {
// 使用 inRange 类检测电子表格中的 x 和 y 坐标是否有效
verifyCoordinate(x, y);
mCells[x][y] = cell;
}
SpreadsheetCell& Spreadsheet::getCellAt(size_t x, size_t y) {
verifyCoordinate(x, y);
return mCells[x][y];
}
class Spreadsheet {
public:
Spreadsheet(size_t width, size_t height);
~Spreadsheet(); // 析构函数的定义
};
// 析构函数的实现
Spreadsheet::~Spreadsheet() {
for (size_t i = 0; i < mWidth; ++i) {
delete[] mCells[i];
}
delete[] mCells;
mCells = nullptr;
}
如果没有自行编写复制构造函数或赋值运算符,C++ 将自动生成:编译器生成的方法递归调用对象数据成员的复制构造函数或赋值运算符
#include "SpreadsheetCell.h"
void printSpreadsheet(Spreadsheet s) {
// ...
}
int main() {
Spreadsheet s1(4, 3);
printSpreadsheet(s1);
return 0;
}
Spreadsheet 包含一个指针变量:mCells。Spreadsheet 的表层复制向目标对象提供了一个 mCells 指针的副本,但没有复制底层数据。最终结果是 s 和 s1 都有一个指向同一数据的指针,如下图所示
如果 s 修改了 mCells 所指的内容,这一改动也会在 s1 中表现出来。更糟糕的是,当函数 printSpreadsheet() 退出时,会调用 s 的析构函数,释放 mCells 所指内存,现在 s1 拥有的指针所指内存不再有效,称为悬挂指针
令人难以置信的是,当使用赋值时,情况会变得更糟。假定编写了下面的代码,在第一行之后,当构建两个对象时,内存的布局如下图
Spreadsheet s1(2, 2), s2(4, 3);
s1 = s2;
现在,不仅 s1 和 s2 中的 mCells 指向同一内存,而且 s1 前面所指的内存被遗弃,这称为内存泄漏,这就是在赋值运算符中进行深层复制的原因
可以看出,依赖 C++ 默认的复制构造函数或赋值运算符并不总是正确的
在类中动态分配内存后,应该编写自己的复制构造函数和赋值运算符,以提供深层的内存复制
Spreadsheet 类的复制构造函数
class Spreadsheet {
public:
// 复制构造函数的声明
Spreadsheet(const Spreadsheet& src) : Spreadsheet(src.mWidth, src.mHeight) {
// ...
}
};
// 复制构造函数的定义
Spreadsheet::Spreadsheet(const Spreadsheet &src) : Spreadsheet(src.mWidth, src.mHeight) {
for (size_t i = 0; i < mWidth; ++i) {
for (size_t j = 0; j < mHeight; ++j) {
mCells[i][j] = src.mCells[i][j];
}
}
}
Spreadsheet 类的赋值运算符
class Spreadsheet {
public:
// 复制构造函数的声明
Spreadsheet& operator= (const Spreadsheet &rhs);
friend void swap(Spreadsheet &first, Spreadsheet &second) noexcept;
};
void swap(Spreadsheet &first, Spreadsheet &second) noexcept {
// 使用 std::swap() 工具函数交换每个数据成员
using std::swap;
swap(first.mWidth, second.mWidth);
swap(first.mHeight, second.mHeight);
swap(first.mCells, second.mCells);
}
Spreadsheet& Spreadsheet::operator= (const Spreadsheet &rhs) {
// 检查自赋值
if (this == &rhs) {
return *this;
}
// 对右边进行复制,称为 temp,然后用这个副本替代 *this
Spreadsheet temp(rhs);
swap(*this, temp);
return *this;
}
禁止赋值和按值传递
// 4 * 2 的结果为右值,是一个临时值,将在语句执行完毕时销毁
int a = 4 * 2;
void handleMessage(std::string& message) {
cout << "handleMessage with lvalue reference: " << message << endl;
}
void handleMessage(std::string&& message) {
cout << "handleMessage with rvalue reference: " << message << endl;
}
std::string a = "Hello ";
std::String b = "World";
handleMessage(a); // 调用左值引用
handleMessage(a + b); // a + b 的结果是临时的,故调用右值引用
handleMessage("Hello World"); // 字面量不是左值,故调用右值引用
handleMessage(std::move(b));
void helper(std::string&& message) {}
void handleMessage(std::string&& message) {
// helper(message); // message 具有名称为左值,导致编译错误
helper(std::move(message));
}
class Spreadsheet {
public:
Spreadsheet(Spreadsheet&& src) noexcept; // 移动构造函数声明
Spreadsheet& operator= (Spreadsheet&& rhs) noexcept; // 移动赋值声明
private:
void cleanup() noexcept;
void moveFrom(Spreadsheet& src) noexcept;
};
// 在析构函数和移动赋值运算符中调用
void Spreadsheet::cleanup() noexcept {
for (size_t i = 0; i < mWidth; ++i) {
delete[] mCells[i];
}
delete[] mCells;
mCells = nullptr;
mWidth = mHeight = 0;
}
// 把成员变量从源对象移动到目标对象,接着重置源对象
void Spreadsheet::moveFrom(Spreadsheet& src) noexcept {
// 假设 Spreadsheet 类有一个名为 mName 的 std::string 数据成员
// 移动对象数据成员
mName = std::move(src.mName);
// 浅复制对象
mWidth = src.mWidth;
mHeight = src.mHeight;
mCells = src.mCells;
// Reset the source object, because ownership has been moved!
src.mWidth = 0;
src.mHeight = 0;
src.mCells = nullptr;
}
// 移动构造函数定义
Spreadsheet::Spreadsheet(Spreadsheet&& src) noexcept {
cout << "Move constructor" << endl;
moveFrom(src);
}
// 移动赋值运算符定义
Spreadsheet& Spreadsheet::operator=(Spreadsheet&& rhs) noexcept {
cout << "Move assignment operator" << endl;
if (this == &rhs) {
return *this;
}
cleanup();
moveFrom(rhs);
return *this;
}
如果类中动态分配了内存,则通常应当实现:析构函数、复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符,这称为 “5 规则”
void swapCopy(T& a, T& b) {
T temp(a);
a = b;
b = temp;
}
void swapMove(T& a, T& b) {
T temp(std::move(a));
a = std::move(b);
b = std::move(temp);
}
class SpreadsheetCell {
private:
static std::string doubleToString(double inValue);
static double stringToDouble(std::string_view inString);
};
class SpreadsheetCell {
public:
double getValue() const; // 最好将不改变对象的任何数据成员的成员函数声明为 const
};
// const 规范是方法原型的一部分,必须放在方法的定义中
double SpreadsheetCell::getValue() const {
return mValue;
}
class SpreadsheetCell {
public:
double getValue() const; // 最好将不改变对象的任何数据成员的成员函数声明为 const
private:
double mValue = 0;
mutable size_t mNumAccesses = 0;
};
// 定义
double SpreadsheetCell::getValue() const {
mNumAccesses++;
return mValue;
}
class MyClass {
public:
void foo(int i);
};
MyClass c;
c.foo(123);
c.foo(1.23);
class MyClass {
public:
void foo(int i);
void foo(double d) = delete;
};
// 提示编译器,用实际方法体替换对 getValue() 调用,而不是生成代码进行函数调用
inline double SpreadsheetCell::getValue() const {
mNumAccesses++;
return mValue;
}
class SpreadsheetCell {
public:
double getValue() const {
mNumAccesses++;
return mValue;
}
};
class Spreadsheet {
public:
Spreadsheet(size_t width = 100; size_t height = 100);
};
Spreadsheet s1;
Spreadsheet s2(5);
Spreadsheet s3(5, 6);
class Spreadsheet {
private:
static size_t sCounter;
};
// 为 sCounter 分配空间并初始化为 0
size_t Spreadsheet::sCounter; // 若采用下述内联变量则可删除该行代码
class Spreadsheet {
private:
static inline size_t sCounter = 0;
};
class Spreadsheet {
public:
size_t getId() const;
private:
static size_t sCounter;
size_t mId = 0;
};
Spreadsheet::Spreadsheet(size_t width, size_t height) : mId(sCounter++), mWidth(width), mHeight(height) {
mCells = new SpreadsheetCell*[mWidth];
for (size_t i = 0; i < mWidth; ++i) {
mCells[i] = new SpreadsheetCell[mHeight];
}
}
class Spreadsheet {
public:
static const size_t kMaxHeight = 100;
static const size_t kMaxWidth = 100;
};
class SpreadsheetApplication;
class Spreadsheet {
public:
Spreadsheet(size_t width, size_t height, SpreadsheetApplication& theApp);
private:
SpreadsheetApplication& mTheApp;
};
class Spreadsheet {
public:
Spreadsheet(size_t width, size_t height, const SpreadsheetApplication& theApp);
private:
const SpreadsheetApplication& mTheApp;
};
class Spreadsheet {
public:
class Cell {
public:
Cell() = default;
Cell(double initialValue);
};
Spreadsheet(size_t width, size_t height, const SpreadsheetApplication& theApp)
};
Spreadsheet::Cell::Cell(double initialValue) : mValue(initialValue) {}
class Spreadsheet {
public:
class Cell;
Spreadsheet(size_t width, size_t height, const SpreadsheetApplication& theApp)
};
class Spreadsheet::Cell {
public:
Cell() = default;
Cell(double initialValue);
};
嵌套的类有权访问外围类中的所有 private 或 protected 成员,而外围类却只能访问嵌套类中的 public 成员