文章目录
- QT C++ 培训
- Day1 环境安装和入门(2025.03.05)
- Qt 自带的编译器
- Qt 的编译脚本:qmake / CMake
- **示例:Test.pro 文件**
- Qt 的版本控制系统
- C++ 中的头文件
- C++ 中的命名空间
- C++ 中的编译、链接、运行
- Day2 C++语法和工程实践(2025.03.06)
- C++中的内置类型
- 1. 基本数据类型
- 2. 布尔类型
- 3. 空类型
- 4. 类型限定符
- C++中的自定义类型
- 1. 类(Class)
- 2. 结构体(Struct)
- 3. 枚举(Enum)
- 4. 类型别名(Type Aliases)
- 5. 模板(Template)
- 左值/右值
- 1. 左值(Lvalue)
- 2. 右值(Rvalue)
- 3. 左值引用与右值引用
- 4. 重要概念
- 指针/引用
- 📌 1. 指针(Pointer)
- 1.1 什么是指针?
- 1.2 指针的语法
- 1.3 常见操作
- 1.4 指针的种类
- 1.5 动态内存分配
- 📌 2. 引用(Reference)
- 2.1 什么是引用?
- 2.2 引用的特点
- 2.3 常量引用
- 📌 3. 指针与引用的区别
- 📌 4. 指针和引用在函数中的应用
- 4.1 通过指针修改参数
- 4.2 通过引用修改参数
- 4.3 指针和引用的混合使用
- 📌 5. 什么时候用指针?什么时候用引用?
- 🎯 6. 小结
- 优秀实践
- Day3 移动语义和构造/析构/拷贝构造(2025.03.07)
- 进程的内存映像
- 栈和堆
- new和malloc
- 类和对象
- 1. 类(Class)
- 2. 结构体(Struct)
- Day4-1 智能指针(2025.03.18)
- 智能指针的用法
- MyClass类头文件
- MyClass类实现文件
- 主程序main.cpp
- Day4-2移动构造函数和移动赋值运算符 (C++)(2025.03.19)
- 为 C++ 创建移动构造函数
- 为 C++ 类创建移动赋值运算符
- 示例:完成移动构造函数和赋值运算符
- 示例 使用移动语义来提高性能
- 可靠编程
- Day4-3 C++四大函数(构造/拷贝构造/析构/赋值运算符函数)(2025.03.19)
- Point类
- Computer类
QT C++ 培训
Day1 环境安装和入门(2025.03.05)
Qt 自带的编译器
Qt 使用 MinGW 作为其默认的编译器。MinGW 是基于 GCC 的编译器,而在 Java 和 Python 中,分别可以使用 Maven 和 pip install
等工具安装依赖或第三方库。
C++ 由标准委员会指定标准,不同厂商会实现自己的编译器和标准库。Qt 致力于跨平台开发,封装了一套跨平台的库,但并不是“一次编译,到处运行”。
C++ 参考手册指出,从 C++11 开始,C++ 进入了一个较快的发展阶段,每三年就会有一个新版本发布。
Qt 的编译脚本:qmake / CMake
qmake
是 Qt 官方维护的成熟构建工具,但从 Qt 5.15 版本开始,官方推荐使用 CMake
作为默认的构建工具。
示例:Test.pro 文件
TEMPLATE = app
CONFIG += console c++11
CONFIG -= app_bundle
CONFIG -= qtSOURCES += \main.cpp
Qt 的版本控制系统
推荐使用 Git 进行版本控制。在实际开发中,每个开发人员通常有各自的分工,合理的 Git 流程可以减少代码冲突,提高协作效率。
C++ 中的头文件
- 头文件主要用于声明(declaration)。
- 源文件(.cpp)主要用于定义和实现(definition & implementation)。
C++ 中的命名空间
using namespace std;
这行代码会将整个 std
标准库暴露在源代码中。一般推荐使用具体引用,例如:
using std::cout;
using std::cin;
这样可以避免命名冲突,提高代码的可读性。
C++ 中的编译、链接、运行
在 C++ 开发过程中,程序的构建主要包括以下几个阶段:
-
构建(Build)
- 预处理:处理
#include
头文件,展开宏定义。 - 编译:将每个
.cpp
或.c
文件编译成.o
(目标文件),这些目标文件是二进制格式的编译单元。 - 链接:将所有
.o
目标文件链接在一起,生成.exe
可执行文件。
- 预处理:处理
-
运行(Run)
- 运行编译好的可执行文件,执行程序逻辑。
Day2 C++语法和工程实践(2025.03.06)
C++中的内置类型
C++ 提供了一系列内置数据类型,用于存储不同类型的值。常见的内置类型包括:
1. 基本数据类型
- 整数类型:
int
:用于存储整数,大小通常为 4 字节(具体大小依赖于系统)。short
:较小的整数类型,通常为 2 字节。long
:较大的整数类型,通常为 4 或 8 字节。long long
:更大的整数类型,通常为 8 字节。
- 字符类型:
char
:用于存储字符,通常为 1 字节。wchar_t
:用于存储宽字符(Unicode 字符),通常为 2 或 4 字节。
- 浮点类型:
float
:用于存储单精度浮点数,通常为 4 字节。double
:用于存储双精度浮点数,通常为 8 字节。long double
:用于存储更高精度的浮点数,大小依赖于系统,通常为 10 或 12 字节。
2. 布尔类型
bool
:用于存储布尔值,取值为true
或false
,通常为 1 字节。
3. 空类型
void
:表示没有类型,常用于函数返回类型或指针声明。
4. 类型限定符
const
:常量类型,表示变量的值不可修改。volatile
:表示变量可能会被外部因素修改,告诉编译器避免优化。mutable
:允许即使在const
成员函数中也可以修改类的某些成员变量。
//C++内置类型
//int char float double
void builtin_type()
{bool a = true;//1个字节 布尔型char b; sizeof(b);//1个字节 字符型unsigned char c; sizeof(c);//1个字节short d = 0; sizeof(c);//2个字节 短整型unsigned short d1; sizeof(d);//2个字节 int e; sizeof(e);//4个字节 整型unsigned int f; sizeof(f);//4个字节long g; sizeof(g); //4个字节 长整型unsigned long h; sizeof(h);//4个字节 long long i; sizeof(i);//8个字节 长长整型unsigned long long j; sizeof(j);//8个字节const char* str = "hello";//str指向的内容不能改变sizeof(str);//4个字节 指针的大小 32位系统4个字节 64位系统8个字节 C语言字符串以'\0'结尾 1个字节std::cout << strlen(str) << std::endl;//5个字节std::cout << sizeof(str) << std::endl;const char* str1 = "xxx";std::cout << sizeof(str1) << std::endl;std::cout << sizeof("xxx") << std::endl;//"xxx"是一个字符串常量,占用4个字节 因为C语言字符串以'\0'结尾 1个字节//数组//定义:类型 数组名[元素个数] = {元素1, 元素2, ...};int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//40个字节std::cout << sizeof(arr) << std::endl;//40个字节//不用new[]分配的数组是栈上的数组,编译时就确定了数组的大小,也就是要求个数必须是常量(已知的)const int aa = 10;int a_stack_arr[aa] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };//40个字节//访问数组元素(第一个值)std::cout << arr[0] << std::endl; //1std::cout << *arr << std::endl; //1//访问第二个值std::cout << a_stack_arr[1] << std::endl; //2std::cout << *(a_stack_arr + 1) << std::endl; //2
}int main() {builtin_type();return 0;
}
C++中的自定义类型
C++ 允许开发者定义自定义类型,以便构建更复杂的程序结构。常见的自定义类型包括:
1. 类(Class)
类是面向对象编程(OOP)中最常见的自定义类型,允许开发者定义对象的属性和方法。
class Person {
public:std::string name;int age;// 构造函数Person(std::string n, int a) : name(n), age(a) {}// 方法void introduce() {std::cout << "Hello, my name is " << name << " and I am " << age << " years old." << std::endl;}
};
2. 结构体(Struct)
结构体与类相似,但结构体的成员默认是公共的,且通常用于存储数据。
struct Point {int x, y;Point(int a, int b) : x(a), y(b) {}
};
3. 枚举(Enum)
枚举用于表示一组常量值,通常用于有限的离散值。
enum Color { RED, GREEN, BLUE };
Color color = GREEN;
4. 类型别名(Type Aliases)
C++ 允许使用 typedef
或 using
为现有类型创建别名,使代码更易读。
typedef unsigned long ulong;
using uint = unsigned int;
5. 模板(Template)
模板允许定义类型参数化的函数或类,使代码更加通用。
template <typename T>
T add(T a, T b) {return a + b;
}
左值/右值
在 C++ 中,左值(Lvalue) 和 右值(Rvalue) 是两个重要的概念,它们与表达式的求值位置和类型紧密相关。
1. 左值(Lvalue)
左值表示可以出现在赋值语句左侧的对象,它表示一个可以被修改的内存位置。
- 左值的特点:
- 有持久的内存地址,可以取地址(
&
)。 - 可以作为赋值的目标。
- 示例:变量、数组元素、解引用指针等。
- 有持久的内存地址,可以取地址(
int a = 10;
a = 20; // a 是一个左值
2. 右值(Rvalue)
右值表示没有持久内存地址的对象,通常是表达式的临时值,不能作为赋值语句的左侧。
- 右值的特点:
- 没有明确的内存地址,通常是临时值。
- 右值可以被用来初始化左值。
- 示例:常量、临时对象、返回值等。
int a = 10;
a = 20 + 30; // 20 + 30 是右值
3. 左值引用与右值引用
-
左值引用(Lvalue Reference):绑定到左值,使用
&
。int a = 10; int& ref = a; // 左值引用
-
右值引用(Rvalue Reference):绑定到右值,使用
&&
。右值引用允许移动语义,避免不必要的复制。int&& rref = 20; // 右值引用
-
普通引用/非常量引用/左值引用: 变量引用,只能引用左值
int c = 0; int& ref = c; // 左值引用
-
常量引用 :可以引用左值和右值
int c = 0 ; const int& crc = c; // 左值引用 const int& crc2 = 1; // 右值引用
4. 重要概念
- 完美转发(Perfect Forwarding):通过右值引用和
std::forward
可以将传递给函数的参数“完美”转发给另一个函数,保持其值类别(左值或右值)。 - 移动语义(Move Semantics):通过右值引用,C++ 可以“移动”资源,而不是复制,减少不必要的内存分配和提高性能。
指针/引用
在 C++ 中,指针(Pointer) 和 引用(Reference) 是两个用于间接访问变量的强大工具。它们在内存管理、函数参数传递、动态分配和面向对象编程中起着重要作用。下面是它们的详尽比较和使用方法:
📌 1. 指针(Pointer)
1.1 什么是指针?
- 指针是一个变量,存储的是另一个变量的内存地址。
- 通过指针可以间接访问和修改该地址上的数据。
1.2 指针的语法
int a = 10;
int* p = &a; // p 是一个指向 a 的指针,& 取 a 的地址std::cout << "a 的值: " << a << std::endl; // 10
std::cout << "a 的地址: " << &a << std::endl; // 0x7ffeed5c (示例地址)
std::cout << "p 指针存储的地址: " << p << std::endl; // 0x7ffeed5c
std::cout << "通过 p 访问 a 的值: " << *p << std::endl; // 10
1.3 常见操作
- 取地址符
&
:获取变量的地址。 - 解引用符
*
:通过指针访问(读取/修改)变量的值。
*p = 20; // 修改 a 的值为 20
std::cout << "a 的新值: " << a << std::endl; // 20
1.4 指针的种类
- 空指针(nullptr):
int* p = nullptr; // 指针不指向任何有效地址
- 野指针(Dangling Pointer):
int* p; // 未初始化的指针,指向未知内存,可能会引发未定义行为
- 常量指针(指针常量):
const int a = 10;
const int* p1 = &a; // p1 指向的值不能修改
int* const p2 = &a; // p2 本身的指向不能修改
const int* const p3 = &a; // p3 指向和值都不能修改
- 指针数组 和 数组指针:
int arr[3] = {1, 2, 3};
int* pArr = arr; // 指针指向数组首元素
int (*pArray)[3] = &arr; // 数组指针,指向整个数组
1.5 动态内存分配
int* p = new int(10); // 动态分配内存并初始化为 10
delete p; // 释放内存,避免内存泄漏int* arr = new int[5]; // 动态数组
delete[] arr; // 释放数组内存
📌 2. 引用(Reference)
2.1 什么是引用?
- 引用是一个变量的别名(Alias),创建后始终指向初始化的对象,无法更改。
- 使用
&
符号定义,但与取地址操作不同。
int a = 10;
int& ref = a; // ref 是 a 的引用ref = 20; // 相当于 a = 20
std::cout << "a 的值: " << a << std::endl; // 20
2.2 引用的特点
- 必须初始化:引用在创建时必须绑定到一个有效变量。
- 不可更改绑定:引用绑定后无法更改指向其他变量。
- 没有
null
引用:不存在空引用,必须总是绑定有效对象。 - 不占用额外内存:引用本质上是变量的一个别名,不分配新内存。
2.3 常量引用
- 保护数据不被修改,常用于函数参数传递中以避免复制大对象。
void showValue(const int& ref) {std::cout << "值: " << ref << std::endl;
}int a = 50;
showValue(a); // 安全,不会修改 a 的值
📌 3. 指针与引用的区别
特性 | 指针(Pointer) | 引用(Reference) |
---|---|---|
初始化 | 可以不初始化(野指针) | 必须初始化 |
重新绑定 | 可以指向不同地址 | 绑定后不可改变引用对象 |
是否为空 | 可以为 nullptr | 不能为 null |
内存占用 | 需要内存存储地址 | 不占用内存,作为别名存在 |
指针运算 | 支持算术运算 (如 ++ , -- ) | 不支持指针运算 |
用途 | 动态分配、数组、函数指针、复杂数据结构 | 主要用于安全、简单的别名和参数传递 |
安全性 | 容易产生野指针、空指针问题 | 更安全,避免指针操作的风险 |
📌 4. 指针和引用在函数中的应用
4.1 通过指针修改参数
void modify(int* p) {*p = 20;
}int a = 10;
modify(&a);
std::cout << "a = " << a << std::endl; // 20
4.2 通过引用修改参数
void modify(int& ref) {ref = 30;
}int a = 10;
modify(a);
std::cout << "a = " << a << std::endl; // 30
4.3 指针和引用的混合使用
void swap(int* a, int* b) {int temp = *a;*a = *b;*b = temp;
}void swap(int& a, int& b) {int temp = a;a = b;b = temp;
}
📌 5. 什么时候用指针?什么时候用引用?
-
使用指针的场景:
- 需要动态内存分配(例如
new/delete
)。 - 需要修改指针的指向(例如链表、树结构)。
- 用于实现复杂数据结构(例如数组、函数指针、回调函数)。
- 指针数组或数组指针的操作。
- 需要动态内存分配(例如
-
使用引用的场景:
- 用作函数参数,避免对象拷贝,提高效率(尤其是大对象)。
- 常量引用用于只读访问对象(例如
const T&
)。 - 简化语法(例如范围
for
循环、运算符重载)。
🎯 6. 小结
- 指针 提供了灵活的内存操作能力,但容易出错,需要小心避免野指针、空指针、内存泄漏。
- 引用 更安全、语法简洁,但缺乏指针的灵活性。
- 在实际项目中,优先使用引用,只有在需要指针特性时才使用指针,尤其是涉及动态分配和复杂数据结构时。
优秀实践
class MyClass;MyClass test(const MyClass& a,MyClass &b)
{}
好的做法是加const关键字的参数,通常是作为传入参数(只读),其他参数作为传出参数。
在 C++ 中,指针(Pointer) 和 引用(Reference) 是两个用于间接访问变量的强大工具。它们在内存管理、函数参数传递、动态分配和面向对象编程中起着重要作用。下面是它们的详尽比较和使用方法:
Day3 移动语义和构造/析构/拷贝构造(2025.03.07)
进程的内存映像
栈和堆
- 栈:传递给函数的参数,是函数里面定义的,不通过malloc/new 分配的变量,都在栈上,对这些变量做拷贝是不计代价的。栈的内容和大小是已知的,编译时才确定。
void readFile(const std::string& filename)
{//读文件的大小int size = getSizeofFile();//分配堆内存char* buf = new char[size];/** buf本身存放在栈上,buf指向的是堆内存*///把文件内容读到buf中readFileToBuf(buf, size);//处理buf中的内容process(buf, size);//释放堆内存delete[] buf;
}
- 堆:编译时不知道,只有运行时才确定。堆是计量代价的,如果要使用堆内存,必须手动分配和手动回收,否则会造成内存泄漏。
- 内存泄漏: 一直分配内存,但从不手动释放,就会造成操作系统无可用内存,new 分配的内存直到通过delete还给操作系统才会失效,而且使用delete后的内存是非法的。
- 内存泄露典型案例:在函数分配的堆内存赋给函数的局部变量,而忘记将该局部变量传出来,内存泄露发生在程序运行时,程序结束后,操作系统会回收所有程序的内存。所以内存泄漏往往在长期运行的服务器上发生。
char* flle = new int[100];
//把堆内存还给操作系统
delete file;
file = nullptr;
new和malloc
- new 一般是用malloc实现的
//C++风格
int* pInt = new int[100];//100×4个字节
//C风格
int* pInt2 = (int*)malloc(100 * sizeof(int));//100×4个字节
类和对象
1. 类(Class)
类是面向对象编程(OOP)中最常见的自定义类型,允许开发者定义对象的属性和方法。
class Person {
public:std::string name;int age;// 构造函数Person(std::string n, int a) : name(n), age(a) {}// 方法void introduce() {std::cout << "Hello, my name is " << name << " and I am " << age << " years old." << std::endl;}
};
面试常见
//MSVC
//指针: 8字节
//总长度 4字节/8字节
//使用长度 4字节/8字节//GCC
//指针: 8字节
//指向末尾的指针 8字节
//指向最后一个元素的指针 8字节
2. 结构体(Struct)
结构体与类相似,但结构体的成员默认是公共的,且通常用于存储数据。
Day4-1 智能指针(2025.03.18)
智能指针的用法
MyClass类头文件
//MyClass.h
#ifndef MYCLASS_H
#define MYCLASS_Hclass MyClass
{
public:MyClass(int val);~MyClass();void show();private:int value;
};#endif // MYCLASS_H
MyClass类实现文件
//MyClass.cpp
#include "MyClass.h"
#include <iostream>MyClass::MyClass(int val):value(val)
{std::cout << "Constructor: " << value << std::endl;
}MyClass::~MyClass()
{std::cout << "Destructor: " << value << std::endl;
}void MyClass::show()
{std::cout << "Value: " << value << std::endl;
}
主程序main.cpp
#include "myclass.h"
#include <memory>
#include <iostream>
#include <vector>//#include <QApplication>// 1️.尝试返回一个 std::unique_ptr<MyClass> 的函数
std::unique_ptr<MyClass> createInstance(int val) {return std::make_unique<MyClass>(val); // ✅ 这样是安全的,返回临时 unique_ptr
}// ❌ 错误示范(不能返回局部变量的 unique_ptr,否则会导致悬空指针)
// std::unique_ptr<MyClass> createInvalidInstance() {
// std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>(20);
// return ptr; // ❌ 这样会尝试拷贝 unique_ptr,而 unique_ptr 不允许拷贝
// }void testuniqueptr()
{std::unique_ptr<MyClass> ptr1 = std::make_unique<MyClass>(10); // 创建 unique_ptrptr1->show(); // 访问成员函数// std::unique_ptr<MyClass> ptr2 = ptr1; // ❌ 错误!unique_ptr 不支持拷贝//std::unique_ptr<MyClass> ptr2 = std::move(ptr1); // ✅ 使用 std::move 转移所有权if (!ptr1)std::cout << "ptr1 is now nullptr" << std::endl; //ptr1失去所有权// 3️⃣ 使用 createInstance() 返回 unique_ptrstd::unique_ptr<MyClass> ptr3 = createInstance(30);ptr3->show();// 4️⃣ 使用 std::vector<std::unique_ptr<MyClass>> 存储多个 unique_ptrstd::vector<std::unique_ptr<MyClass>> myVector;myVector.push_back(std::make_unique<MyClass>(100));myVector.push_back(std::make_unique<MyClass>(200));myVector.push_back(std::make_unique<MyClass>(300));std::cout << "Vector Contentd" << std::endl;for (const auto& item : myVector){item->show();}
}void testsharedptr()
{std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(10); // 创建 shared_ptr{std::shared_ptr<MyClass> ptr2 = ptr1; // 共享 ptr1 的对象,引用计数+1std::cout << "ptr2 use count: " << ptr2.use_count() << std::endl;ptr2->show();} // 退出作用域,ptr2 释放,引用计数-1std::shared_ptr<MyClass> ptr3 = ptr1;std::cout << "ptr3 use count: " << ptr3.use_count() << std::endl;ptr3->show();std::shared_ptr<MyClass> ptr4 = ptr1;std::cout << "ptr4 use count: " << ptr4.use_count() << std::endl;std::vector<std::shared_ptr<MyClass>> myVector2;myVector2.push_back(std::make_shared<MyClass>(500));myVector2.push_back(std::make_shared<MyClass>(600));myVector2.push_back(std::make_shared<MyClass>(700));std::cout << "Vector Contentd" << std::endl;for (const auto& item : myVector2){item->show();}ptr3.reset(); // 释放 ptr3,引用计数 -1ptr4.reset(); // 释放 ptr4,引用计数 -1ptr1.reset(); // 释放 ptr1,引用计数 -1,此时应该调用析构函数std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl;// 程序结束,ptr1 释放,引用计数归 0,析构对象
}class A;class B {
public:std::weak_ptr<A> a_ptr; // ❗改成 weak_ptr~B() { std::cout << "B destroyed" << std::endl; }
};class A {
public:std::shared_ptr<B> b_ptr;~A() { std::cout << "A destroyed" << std::endl; }
};void testweakptr()
{std::shared_ptr<A> a = std::make_shared<A>();std::shared_ptr<B> b = std::make_shared<B>();a->b_ptr = b; // A 持有 Bb->a_ptr = a; // B **弱引用** A(不会增加计数)//return 0之后 A 和 B 都会被正确释放
}int main(int argc, char* argv[])
{//testuniqueptr();//testsharedptr();testweakptr();return 0; //return a.exec();
}
Day4-2移动构造函数和移动赋值运算符 (C++)(2025.03.19)
本主题介绍如何为 C++ 类编写移动构造函数和移动赋值运算符。 移动构造函数使右值对象拥有的资源无需复制即可移动到左值中。 有关移动语义的详细信息,请参阅 Rvalue 引用声明符:&&。
此主题基于用于管理内存缓冲区的 C++ 类 MemoryBlock
。
// MemoryBlock.h
#pragma once
#include <iostream>
#include <algorithm>class MemoryBlock
{
public:// Simple constructor that initializes the resource.explicit MemoryBlock(size_t length): _length(length), _data(new int[length]){std::cout << "In MemoryBlock(size_t). length = "<< _length << "." << std::endl;}// Destructor.~MemoryBlock(){std::cout << "In ~MemoryBlock(). length = "<< _length << ".";if (_data != nullptr){std::cout << " Deleting resource.";// Delete the resource.delete[] _data;}std::cout << std::endl;}// Copy constructor.MemoryBlock(const MemoryBlock& other): _length(other._length), _data(new int[other._length]){std::cout << "In MemoryBlock(const MemoryBlock&). length = "<< other._length << ". Copying resource." << std::endl;std::copy(other._data, other._data + _length, _data);}// Copy assignment operator.MemoryBlock& operator=(const MemoryBlock& other){std::cout << "In operator=(const MemoryBlock&). length = "<< other._length << ". Copying resource." << std::endl;if (this != &other){// Free the existing resource.delete[] _data;_length = other._length;_data = new int[_length];std::copy(other._data, other._data + _length, _data);}return *this;}// Retrieves the length of the data resource.size_t Length() const{return _length;}private:size_t _length; // The length of the resource.int* _data; // The resource.
};
以下过程介绍如何为示例 C++ 类编写移动构造函数和移动赋值运算符。
为 C++ 创建移动构造函数
-
定义一个空的构造函数方法,该方法采用一个对类类型的右值引用作为参数,如以下示例所示:
MemoryBlock(MemoryBlock&& other): _data(nullptr), _length(0) { }
-
在移动构造函数中,将源对象中的类数据成员添加到要构造的对象:
_data = other._data; _length = other._length;
-
将源对象的数据成员分配给默认值。 这可以防止析构函数多次释放资源(如内存):
other._data = nullptr; other._length = 0;
为 C++ 类创建移动赋值运算符
-
定义一个空的赋值运算符,该运算符采用一个对类类型的右值引用作为参数并返回一个对类类型的引用,如以下示例所示:
C++复制
MemoryBlock& operator=(MemoryBlock&& other) { }
-
在移动赋值运算符中,如果尝试将对象赋给自身,则添加不执行运算的条件语句。
C++复制
if (this != &other) { }
-
在条件语句中,从要将其赋值的对象中释放所有资源(如内存)。
以下示例从要将其赋值的对象中释放
_data
成员:// Free the existing resource. delete[] _data;
执行第一个过程中的步骤 2 和步骤 3 以将数据成员从源对象转移到要构造的对象:
// Copy the data pointer and its length from the // source object. _data = other._data; _length = other._length;// Release the data pointer from the source object so that // the destructor does not free the memory multiple times. other._data = nullptr; other._length = 0;
-
返回对当前对象的引用,如以下示例所示:
return *this;
示例:完成移动构造函数和赋值运算符
以下示例显示了 MemoryBlock
类的完整移动构造函数和移动赋值运算符:
// Move constructor.
MemoryBlock(MemoryBlock&& other) noexcept: _data(nullptr), _length(0)
{std::cout << "In MemoryBlock(MemoryBlock&&). length = "<< other._length << ". Moving resource." << std::endl;// Copy the data pointer and its length from the// source object._data = other._data;_length = other._length;// Release the data pointer from the source object so that// the destructor does not free the memory multiple times.other._data = nullptr;other._length = 0;
}// Move assignment operator.
MemoryBlock& operator=(MemoryBlock&& other) noexcept
{std::cout << "In operator=(MemoryBlock&&). length = "<< other._length << "." << std::endl;if (this != &other){// Free the existing resource.delete[] _data;// Copy the data pointer and its length from the// source object._data = other._data;_length = other._length;// Release the data pointer from the source object so that// the destructor does not free the memory multiple times.other._data = nullptr;other._length = 0;}return *this;
}
示例 使用移动语义来提高性能
以下示例演示移动语义如何能提高应用程序的性能。 此示例将两个元素添加到一个矢量对象,然后在两个现有元素之间插入一个新元素。 vector
类使用移动语义,通过移动矢量元素(而非复制它们)来高效地执行插入操作。
// rvalue-references-move-semantics.cpp
// compile with: /EHsc
#include "MemoryBlock.h"
#include <vector>using namespace std;int main()
{// Create a vector object and add a few elements to it.vector<MemoryBlock> v;v.push_back(MemoryBlock(25));v.push_back(MemoryBlock(75));// Insert a new element into the second position of the vector.v.insert(v.begin() + 1, MemoryBlock(50));
}
该示例产生下面的输出:
In MemoryBlock(size_t). length = 25.
In MemoryBlock(MemoryBlock&&). length = 25. Moving resource.
In ~MemoryBlock(). length = 0.
In MemoryBlock(size_t). length = 75.
In MemoryBlock(MemoryBlock&&). length = 75. Moving resource.
In MemoryBlock(MemoryBlock&&). length = 25. Moving resource.
In ~MemoryBlock(). length = 0.
In ~MemoryBlock(). length = 0.
In MemoryBlock(size_t). length = 50.
In MemoryBlock(MemoryBlock&&). length = 50. Moving resource.
In MemoryBlock(MemoryBlock&&). length = 25. Moving resource.
In MemoryBlock(MemoryBlock&&). length = 75. Moving resource.
In ~MemoryBlock(). length = 0.
In ~MemoryBlock(). length = 0.
In ~MemoryBlock(). length = 0.
In ~MemoryBlock(). length = 25. Deleting resource.
In ~MemoryBlock(). length = 50. Deleting resource.
In ~MemoryBlock(). length = 75. Deleting resource.
在 Visual Studio 2010 之前,此示例生成了以下输出:
In MemoryBlock(size_t). length = 25.
In MemoryBlock(const MemoryBlock&). length = 25. Copying resource.
In ~MemoryBlock(). length = 25. Deleting resource.
In MemoryBlock(size_t). length = 75.
In MemoryBlock(const MemoryBlock&). length = 25. Copying resource.
In ~MemoryBlock(). length = 25. Deleting resource.
In MemoryBlock(const MemoryBlock&). length = 75. Copying resource.
In ~MemoryBlock(). length = 75. Deleting resource.
In MemoryBlock(size_t). length = 50.
In MemoryBlock(const MemoryBlock&). length = 50. Copying resource.
In MemoryBlock(const MemoryBlock&). length = 50. Copying resource.
In operator=(const MemoryBlock&). length = 75. Copying resource.
In operator=(const MemoryBlock&). length = 50. Copying resource.
In ~MemoryBlock(). length = 50. Deleting resource.
In ~MemoryBlock(). length = 50. Deleting resource.
In ~MemoryBlock(). length = 25. Deleting resource.
In ~MemoryBlock(). length = 50. Deleting resource.
In ~MemoryBlock(). length = 75. Deleting resource.
使用移动语义的此示例版本比不使用移动语义的版本更高效,因为前者执行的复制、内存分配和内存释放操作更少。
可靠编程
若要防止资源泄漏,请始终释放移动赋值运算符中的资源(如内存、文件句柄和套接字)。
若要防止不可恢复的资源损坏,请正确处理移动赋值运算符中的自我赋值。
如果为你的类同时提供了移动构造函数和移动赋值运算符,则可以编写移动构造函数来调用移动赋值运算符,从而消除冗余代码。 以下示例显示了调用移动赋值运算符的移动构造函数的修改后的版本:
// Move constructor.
MemoryBlock(MemoryBlock&& other) noexcept: _data(nullptr), _length(0)
{*this = std::move(other);
}
std::move 函数将左值 other
转换为右值。
Day4-3 C++四大函数(构造/拷贝构造/析构/赋值运算符函数)(2025.03.19)
Point类
#include <iostream>using std::cout;
using std::endl;class Point
{
public:Point(int ix = 0, int iy = 0): _ix(ix), _iy(iy){cout << "Point(int,int)" << endl;}~Point() {cout << "~Point()" << endl;}//拷贝构造函数Point(const Point& rhs): _ix(rhs._ix), _iy(rhs._iy){cout << "Point(const Point&)" << endl;}//问题1:拷贝构造函数中的引用符号能不能去掉?//问题2:拷贝构造函数中的const关键字能不能去掉?//解答1:去掉引用符号,会导致无穷递归调用,直到栈溢出(满足拷贝构造函数的调用时机1:形参与实参结合)//解答2:去掉const关键字,会导致无法传递右值,即无法传递临时对象//左值和右值int number = 10;int& ref = number;//左值引用//int& ref1 = 10;//错误,不能将右值赋值给左值引用int&& ref2 = 10;//右值引用const int& ref3 = 10;//常量左值引用//赋值运算符函数Point& operator=(const Point& rhs){cout << "Point& operator=(const Point&)" << endl;if (this != &rhs){_ix = rhs._ix;_iy = rhs._iy;}}void print() const{cout << "(" << _ix<< "," << _iy<< ")" << endl;}private:int _ix;int _iy;};class Line
{
public:Line(int x1, int y1, int x2, int y2):_pt1(x1, y1)//如果不显示调用构造函数,会调用默认构造函数,所以最好可以显示初始化子对象, _pt2(x2, y2){//cout << "Line(int,int,int,int)" << endl;}//问题:为什么没有定义析构函数,也没有调用析构函数,就能释放资源?// 定义析构函数~Line(){cout << "~Line()" << endl;}public:void print() const{_pt1.print();cout << "---->";_pt2.print();}private:Point _pt1;Point _pt2;};void testLine()
{Line line(1, 2, 3, 4);line.print();
}void test() {Point pt;//栈对象cout << "pt = ";pt.print();//Point* p = new Point();//堆对象Point pt2(3, 4);//栈对象cout << "pt2 = ";pt2.print();Point pt3 = pt2;//拷贝构造函数cout << "pt3 = ";pt3.print();
}Point func()
{Point pt3(1, 2);cout << "pt3 = ";pt3.print();return pt3;
}void test1()
{Point pt4 = func();cout << "pt4 = ";pt4.print();
}int main(int argc, char* argv[])
{cout << "begin test..." << endl;//test();//test1();cout << "end test..." << endl;cout << "sizeof(Point)= " << sizeof(Point) << endl;testLine();return 0;
}
Computer类
//Computer.h
#pragma once
#include <iostream>
using std::cout;
using std::endl;
class Computer
{
public:Computer(const char* brand,float price);Computer(const Computer& rhs);Computer operator=(const Computer& rhs);~Computer();void setBrand(const char* brand);void setPrice(int price);void print();void print() const;static void printTotalPrice();//静态成员函数void printTotalPrice();
private:char* _brand;float _price;static float _totalPrice;//静态数据成员
};
实现文件
#include "Computer.h"
#define _CRT_SECURE_NO_WARNINGSfloat Computer::_totalPrice = 0.0f;/*
1. 构造函数:
• Computer(const char* brand, float price):分配内存并复制品牌字符串。
2. 拷贝构造函数:
• Computer(const Computer& rhs):分配新内存并复制 rhs 的品牌字符串,而不是简单地复制指针。
3. 析构函数:
• ~Computer():释放分配的内存。
*/
Computer::Computer(const char* brand, float price): _brand(new char[strlen(brand) + 1]()), _price(price)
{std::cout << "Computer(const char*,float)" << std::endl;strcpy_s(_brand, strlen(brand) + 1, brand);_totalPrice += _price;
}//Computer::Computer(const Computer& rhs)
// : _brand(rhs._brand) // 浅拷贝
// , _price(rhs._price)Computer::Computer(const Computer& rhs): _brand(new char[strlen(rhs._brand) + 1]()) // 深拷贝, _price(rhs._price)
{std::cout << "Computer(const Computer&)" << std::endl;strcpy_s(_brand, strlen(rhs._brand) + 1, rhs._brand); // 复制内容
}/*赋值运算符函数中需要关注的几个问题*/
//赋值运算符函数参数中的引用符号能不能去掉?
//赋值运算符函数参数中的const关键字能不能去掉?
//赋值运算符函数返回类型中的引用能不能去掉?
//赋值运算符函数的返回类型可以不是对象吗?//解答1:在形参与实参结合的时候,会多执行一次拷贝构造函数
//解答2:去掉const关键字,会导致无法传递右值,即无法传递临时对象
//解答3:函数的返回类型是类类型的时候,会满足拷贝构造函数的调用条件
//解答4:返回类型可以不是对象,但是会导致无法连续赋值 pc3 = pc2 = pc1
Computer Computer::operator=(const Computer& rhs)
{// TODO: 在此处插入 return 语句std::cout << "Computer& operator=(const Computer&)" << std::endl;// 防止自复制 pc = pcif (this != &rhs) {delete[] _brand;_brand = new char[strlen(rhs._brand) + 1]();strcpy_s(_brand, strlen(rhs._brand) + 1, rhs._brand);_price = rhs._price;}//_brand = rhs._brand;// 浅拷贝//_price = rhs._price;// 浅拷贝/** 问题1:两个指针指向同一块内存,当其中一个对象释放内存后,另一个对象的指针就变成了野指针(double free)* 例如:pc2 = pc;pc2析构释放内存后,pc的析构函数再次释放内存,导致double free*///double free 的解决办法:使用深拷贝/** 问题2:两个指针指向同一块内存,当其中一个对象释放内存后,另一个对象的指针就变成了悬空指针(dangling pointer)* pc2改变指针指向之后,原来pc2指向的那块堆内存就找不到了*///内存泄漏的解决办法:delete[] _brand; _brand = new char[strlen(rhs._brand) + 1](); strcpy(_brand, rhs._brand);return *this;
}Computer::~Computer()
{std::cout << "~Computer()" << std::endl;_totalPrice -= _price;if (_brand) {delete[] _brand;_brand = nullptr;}
}void Computer::setBrand(const char* brand)
{// 这里可以实现设置品牌的逻辑// 释放旧的品牌内存delete[] _brand;// 分配新内存并复制品牌_brand = new char[strlen(brand) + 1]();strcpy_s(_brand, strlen(brand) + 1, brand);
}void Computer::setPrice(int price)
{// 这里可以实现设置价格的逻辑_price = price;
}void Computer::print()
{cout << "print()" << endl;std::cout << "brand:" << _brand << std::endl;std::cout << "price:" << _price << std::endl;
}void Computer::print() const
{cout << "print() const" << endl;std::cout << "brand:" << _brand << std::endl;std::cout << "price:" << _price << std::endl;
}void Computer::printTotalPrice()
{std::cout << "total price:" << _totalPrice << std::endl;
}void testStaticMember() {cout << "购买第一台电脑前的总价:" << endl;Computer::printTotalPrice();Computer pc("lenovo", 5555);pc.print();cout << "购买第一台电脑后的总价:" << endl;pc.printTotalPrice();Computer pc2("Mac", 8888);pc2.print();cout << "购买第二台电脑后的总价:" << endl;pc2.printTotalPrice();cout << endl;pc2.setBrand("Mac");pc.print();pc2.print();
}void testCopyConstructor() {Computer pc("lenovo", 5555);/*pc.setBrand("ThinkPad");pc.setPrice(8888);*/pc.print();Computer pc2 = pc;pc2.print();cout << endl;pc2.setBrand("Mac");pc.print();pc2.print();
}void testAssignmentOperator() {Computer pc("lenovo", 5555);cout << "pc: " << endl;pc.print();Computer pc2("Mac", 8888);cout << "pc2: " << endl;pc2.print();pc2 = pc;cout << "pc2: " << endl;pc2.print();pc2 = Computer("lenovo", 6666);pc2.print();
}void testConst()
{//const对象只能调用const成员函数,非const对象可以调用任意成员函数const Computer pc("lenovo", 5555);pc.print();cout << endl << endl;Computer pc2("Mac", 8888);pc2.print();
}int main(int argc, char* argv[])
{//testCopyConstructor();//testAssignmentOperator();//testStaticMember();testConst();return 0;
}