义乌微信网站建设费用,菏泽住房与城乡建设官网,网站集约化建设意见,视频网站咋么做文章目录 拷贝构造函数的基本概念定义语法何时使用拷贝构造函数示例代码运行结果注意事项 拷贝赋值运算符的基本概念定义语法何时使用拷贝赋值运算符示例代码运行结果注意事项 析构函数的基本概念定义语法何时调用析构函数示例代码运行结果注意事项 三/五法则三法则 (Rule of T… 文章目录 拷贝构造函数的基本概念定义语法何时使用拷贝构造函数示例代码运行结果注意事项 拷贝赋值运算符的基本概念定义语法何时使用拷贝赋值运算符示例代码运行结果注意事项 析构函数的基本概念定义语法何时调用析构函数示例代码运行结果注意事项 三/五法则三法则 (Rule of Three)五法则 (Rule of Five)示例实现三/五法则注意事项使用场景示例代码注意事项 阻止拷贝方法 1删除拷贝构造函数和拷贝赋值运算符方法 2继承自 std::noncopyable为什么要阻止拷贝 “行为像值”的类实现“行为像值”的类的关键点示例代码注意事项 定义行为像指针的类关键特征实现策略示例代码注意事项 交换操作交换操作的基本概念实现交换操作完整的示例代码代码说明 拷贝构造函数的基本概念
定义
拷贝构造函数是一种特殊的构造函数用于创建一个新对象作为现有对象的副本。在 C 中当对象以值传递的方式传入函数或从函数返回时或用一个对象初始化另一个对象时拷贝构造函数会被调用。
语法
ClassName (const ClassName old_obj);何时使用拷贝构造函数
对象作为参数传递给函数按值传递对象从函数返回按值返回对象需要通过另一个对象进行初始化
示例代码
假设我们有一个简单的类 Point它有一个拷贝构造函数。
#include iostream
using namespace std;class Point {private:int x, y;public:Point(int x1, int y1) { // 普通构造函数x x1;y y1;}// 拷贝构造函数Point(const Point p2) {x p2.x;y p2.y;}int getX() { return x; }int getY() { return y; }
};int main() {Point p1(10, 15); // 普通构造函数被调用Point p2 p1; // 拷贝构造函数被调用cout p1.x p1.getX() , p1.y p1.getY();cout \np2.x p2.getX() , p2.y p2.getY();return 0;
}运行结果
p1.x 10, p1.y 15
p2.x 10, p2.y 15注意事项
如果你没有为类定义拷贝构造函数C 编译器会自动生成一个默认的拷贝构造函数。拷贝构造函数通常应该使用引用传递以避免无限递归的拷贝操作。当类中包含指针成员时可能需要深度拷贝。这需要自定义拷贝构造函数来确保每个成员正确地被复制。
拷贝赋值运算符在 C 中同样扮演着重要的角色特别是在对象间赋值时。让我们详细探讨一下这个概念。
拷贝赋值运算符的基本概念
定义
拷贝赋值运算符用于将一个对象的值复制到另一个已经存在的对象中。每个类都有一个拷贝赋值运算符可以是显式定义的也可以是编译器自动生成的。
语法
对于类 ClassName拷贝赋值运算符通常定义为
ClassName operator(const ClassName other);何时使用拷贝赋值运算符
当使用赋值操作符将一个对象的值赋给另一个已经存在的对象时就会调用拷贝赋值运算符。例如
ClassName obj1, obj2;
obj1 obj2; // 这里调用了拷贝赋值运算符示例代码
让我们以 Point 类为例为其添加一个拷贝赋值运算符
#include iostream
using namespace std;class Point {private:int x, y;public:Point(int x1, int y1) { // 构造函数x x1;y y1;}// 拷贝赋值运算符Point operator(const Point p) {x p.x;y p.y;return *this;}int getX() { return x; }int getY() { return y; }
};int main() {Point p1(10, 15); // 构造函数被调用Point p2; // 默认构造函数被调用p2 p1; // 拷贝赋值运算符被调用cout p2.x p2.getX() , p2.y p2.getY();return 0;
}运行结果
p2.x 10, p2.y 15注意事项
拷贝赋值运算符应该检查自赋值的情况。与拷贝构造函数类似当类中有指针成员时需要考虑深拷贝。拷贝赋值运算符通常返回一个指向当前对象的引用以允许链式赋值。
析构函数在 C 中是一个基本概念用于管理对象销毁时的资源释放和清理工作。下面是关于析构函数的详细讲解。
析构函数的基本概念
定义
析构函数是一个特殊的成员函数当对象生命周期结束时被自动调用。它的主要作用是释放对象占用的资源例如释放分配给对象的内存、关闭文件等。
语法
对于类 ClassName其析构函数的定义如下
~ClassName();它没有返回值也不接受任何参数。
何时调用析构函数
析构函数会在以下情况被调用
局部对象当局部对象的作用域结束时例如函数执行完毕时。动态分配的对象当使用 delete 操作符删除动态分配的对象时。通过 delete[] 删除的对象数组为数组中的每个对象调用。程序结束当程序结束时为全局对象或静态对象调用。
示例代码
下面是一个简单的示例演示如何定义和使用析构函数。
#include iostream
using namespace std;class Point {private:int x, y;public:Point(int x1, int y1) { // 构造函数x x1;y y1;cout 构造函数被调用 endl;}~Point() { // 析构函数cout 析构函数被调用 endl;}int getX() { return x; }int getY() { return y; }
};void createPoint() {Point p(10, 15); // 构造函数被调用cout Point created: p.getX() , p.getY() endl;// 当createPoint函数结束时p的析构函数被调用
}int main() {createPoint();return 0;
}运行结果
构造函数被调用
Point created: 10, 15
析构函数被调用注意事项
析构函数不能被显式调用它由 C 运行时自动调用。析构函数应该足够简单避免在其中抛出异常。当类包含动态分配的资源时如指针通常需要在析构函数中释放这些资源以防止内存泄露。
三/五法则Rule of Three/Five是 C 编程中的一个重要原则它涉及类的拷贝控制成员拷贝构造函数、拷贝赋值运算符和析构函数。这个法则帮助程序员处理资源管理特别是在涉及动态内存分配时。
三/五法则
三法则 (Rule of Three)
如果你的类需要显式定义或删除以下任何一个成员则它可能需要显式定义或删除所有三个
拷贝构造函数拷贝赋值运算符析构函数
这是因为这三个函数通常涉及资源的分配和释放。例如如果你的类动态分配内存则需要确保在拷贝对象时正确地复制这些资源并在对象销毁时释放资源。
五法则 (Rule of Five)
随着 C11 的引入新增了两个成员函数扩展了三法则成为五法则
拷贝构造函数拷贝赋值运算符析构函数移动构造函数移动赋值运算符
这两个新成员函数用于支持移动语义这在处理大型资源时是非常有用的因为它允许资源的所有权从一个对象转移到另一个而不是进行昂贵的拷贝。
示例实现三/五法则
假设我们有一个类 ResourceHolder它管理一个动态分配的数组。
#include algorithm // std::swap
#include iostream
using namespace std;class ResourceHolder {private:int* data;size_t size;public:// 构造函数ResourceHolder(size_t size): size(size), data(new int[size]) {}// 析构函数~ResourceHolder() { delete[] data; }// 拷贝构造函数ResourceHolder(const ResourceHolder other): size(other.size), data(new int[other.size]) {std::copy(other.data, other.data size, data);}// 拷贝赋值运算符ResourceHolder operator(ResourceHolder other) {swap(*this, other);return *this;}// 移动构造函数ResourceHolder(ResourceHolder other) noexcept : data(nullptr), size(0) {swap(*this, other);}// 移动赋值运算符ResourceHolder operator(ResourceHolder other) noexcept {if (this ! other) {delete[] data;data nullptr;swap(*this, other);}return *this;}// 交换函数friend void swap(ResourceHolder first, ResourceHolder second) noexcept {using std::swap;swap(first.size, second.size);swap(first.data, second.data);}// 其他成员函数...
};int main() {ResourceHolder r1(10);ResourceHolder r2 r1; // 拷贝构造函数ResourceHolder r3(15);r3 std::move(r1); // 移动赋值运算符// ...
}注意事项
使用这些规则可以帮助避免资源泄漏、双重释放等问题。在实现移动构造函数和移动赋值运算符时要注意处理自赋值情况并确保符合异常安全的要求。
在 C11 及更高版本中default 关键字的引入提供了一种简洁的方式来让编译器自动生成类的默认构造函数、析构函数、拷贝构造函数、拷贝赋值运算符、移动构造函数和移动赋值运算符。使用 default 可以显式地告诉编译器我们希望使用它的默认实现而不是完全禁用这些特殊的成员函数。
使用场景
默认构造函数当你希望类有一个默认构造函数但不需要特别的实现时。拷贝构造函数和拷贝赋值运算符当你希望类能被正常拷贝且默认的逐成员拷贝行为是适当的。移动构造函数和移动赋值运算符当你希望类支持移动语义但不需要特别的移动逻辑。析构函数当你希望类有一个默认的析构函数通常在没有动态分配的资源需要清理时使用。
示例代码
下面的示例演示了如何使用 default
#include iostream
#include vector
using namespace std;class MyClass {
public:MyClass() default; // 默认构造函数~MyClass() default; // 默认析构函数MyClass(const MyClass other) default; // 默认拷贝构造函数MyClass(MyClass other) noexcept default; // 默认移动构造函数MyClass operator(const MyClass other) default; // 默认拷贝赋值运算符MyClass operator(MyClass other) noexcept default; // 默认移动赋值运算符// 其他成员函数...
};int main() {MyClass obj1; // 调用默认构造函数MyClass obj2 obj1; // 调用默认拷贝构造函数MyClass obj3 std::move(obj1); // 调用默认移动构造函数// ...
}注意事项
使用 default 时编译器生成的成员函数是公共的、非虚的、非显式的且具有相同的异常规范。如果类中有成员不可拷贝或不可移动对应的拷贝或移动操作将被编译器删除。使用 default 声明的函数可以在类定义中此时为内联的或类定义外声明。
在 C 中阻止一个类被拷贝是一种常见的实践尤其是对于那些管理独占资源的类。阻止拷贝可以确保对象的唯一性和资源管理的安全性。有两种主要方法来阻止类被拷贝
阻止拷贝
方法 1删除拷贝构造函数和拷贝赋值运算符
在 C11 及以后的版本中最简单的方式是使用 delete 关键字明确地删除拷贝构造函数和拷贝赋值运算符。
class NonCopyable {
public:NonCopyable() default; // 默认构造函数// 删除拷贝构造函数和拷贝赋值运算符NonCopyable(const NonCopyable) delete;NonCopyable operator(const NonCopyable) delete;// 允许移动构造函数和移动赋值运算符NonCopyable(NonCopyable) default;NonCopyable operator(NonCopyable) default;
};方法 2继承自 std::noncopyable
在 C11 之前的版本中或者在更喜欢这种方法的情况下可以通过继承 boost::noncopyable 或自定义的非拷贝基类来实现。
#include boost/noncopyable.hppclass NonCopyable : private boost::noncopyable {// 类定义...
};或者自定义一个非拷贝基类
class NonCopyable {
protected:NonCopyable() default;~NonCopyable() default;NonCopyable(const NonCopyable) delete;NonCopyable operator(const NonCopyable) delete;
};class MyClass : private NonCopyable {// 类定义...
};为什么要阻止拷贝
某些对象比如文件句柄、数据库连接或者网络套接字管理着不能简单复制的资源。在这些情况下拷贝这样的对象可能会导致资源管理混乱如多次释放同一资源或者违反对象的唯一性约束。
通过阻止拷贝你可以确保这类对象的实例保持唯一并且避免了复制可能导致的问题。
在 C 中创建一个“行为像值”的类意味着该类的实例在被拷贝时表现得就像基本数据类型如 int、double那样。这通常涉及到实现深拷贝确保每个对象都有自己的数据副本从而使得对象之间相互独立。
“行为像值”的类
实现“行为像值”的类的关键点
深拷贝在拷贝构造函数和拷贝赋值运算符中实现深拷贝以确保复制对象的数据而非仅复制指针或引用。资源管理确保管理动态分配的内存防止内存泄漏。拷贝赋值运算符遵循赋值时的“拷贝并交换”惯用法以确保异常安全和代码简洁。析构函数释放对象拥有的资源。
示例代码
假设我们有一个简单的 String 类它包含一个动态分配的字符数组
#include cstring
#include algorithmclass String {
private:char* data;size_t length;void freeData() {delete[] data;}public:// 构造函数String(const char* str ) : length(strlen(str)) {data new char[length 1];std::copy(str, str length, data);data[length] \0;}// 拷贝构造函数String(const String other) : length(other.length) {data new char[length 1];std::copy(other.data, other.data length, data);data[length] \0;}// 拷贝赋值运算符String operator(String other) {swap(*this, other);return *this;}// 移动构造函数String(String other) noexcept : data(nullptr), length(0) {swap(*this, other);}// 析构函数~String() {freeData();}// 交换函数friend void swap(String first, String second) noexcept {using std::swap;swap(first.length, second.length);swap(first.data, second.data);}// 其他成员函数...
};在这个例子中String 类实现了深拷贝确保每个 String 对象都独立拥有自己的字符数组。析构函数释放这些资源而拷贝赋值运算符则使用“拷贝并交换”惯用法来确保异常安全。
注意事项
确保深拷贝是必要的并且能正确处理自赋值情况。考虑异常安全性特别是在处理资源分配和释放时。为了提高效率可以考虑实现移动构造函数和移动赋值运算符。
在 C 中定义一个“行为像指针”的类通常意味着这个类的实例在被拷贝时表现得就像指针一样。这种行为通常涉及到共享数据而不是像值类型那样进行深拷贝。这种模式经常通过实现引用计数或使用智能指针来实现。
定义行为像指针的类
关键特征
共享数据实例之间共享相同的数据。引用计数通常通过引用计数来管理共享数据的生命周期。自管理资源类负责管理其指向的资源如分配和释放内存。
实现策略
使用智能指针如 std::shared_ptr它自动处理引用计数和资源管理。手动引用计数在类内部手动实现引用计数机制。
示例代码
假设我们有一个简单的 SharedString 类该类使用 std::shared_ptr 来共享字符串数据
#include iostream
#include string
#include memoryclass SharedString {
private:std::shared_ptrstd::string data;public:SharedString(const char* str ) : data(std::make_sharedstd::string(str)) {}// 使用默认的拷贝构造函数、赋值运算符和析构函数SharedString(const SharedString) default;SharedString operator(const SharedString) default;~SharedString() default;// 获取字符串const std::string get() const {return *data;}// 其他成员函数...
};int main() {SharedString s1(Hello);SharedString s2 s1; // s1 和 s2 共享相同的数据std::cout s1: s1.get() , s2: s2.get() std::endl;return 0;
}在这个例子中SharedString 类的实例 s1 和 s2 共享相同的字符串。通过使用 std::shared_ptr当最后一个 SharedString 实例被销毁时字符串数据会被自动释放。
注意事项
当使用共享数据时需注意线程安全和同步问题。确保所有的拷贝操作都正确地管理引用计数。考虑对象的所有权和生命周期以防止悬挂指针或内存泄漏。
在 C 中交换操作是一个重要的概念尤其是在实现拷贝赋值运算符和移动构造函数时。正确的交换操作可以提高代码的效率和安全性。
交换操作
交换操作的基本概念
交换操作的目的是交换两个对象的状态。在许多情况下这比通过传统的拷贝更高效特别是对于大型对象或资源密集型对象。
实现交换操作
交换操作通常通过定义一个非成员函数 swap 来实现该函数接受两个同类型对象的引用并交换它们的内部状态。
当然我可以提供一个更完整的代码示例展示如何在 C 类中实现交换操作。我们将创建一个简单的类 MyClass该类将包含一些基本的成员变量和一个交换操作的实现。
完整的示例代码
#include iostream
#include utility // For std::swap (C11 and later)class MyClass {
private:int* data;size_t size;public:// 构造函数MyClass(size_t size) : size(size), data(new int[size]) {for (size_t i 0; i size; i) {data[i] i; // 示例数据初始化}}// 拷贝构造函数MyClass(const MyClass other) : size(other.size), data(new int[other.size]) {std::copy(other.data, other.data size, data);}// 拷贝赋值运算符MyClass operator(MyClass other) {swap(*this, other);return *this;}// 移动构造函数MyClass(MyClass other) noexcept : MyClass() {swap(*this, other);}// 移动赋值运算符MyClass operator(MyClass other) noexcept {swap(*this, other);return *this;}// 析构函数~MyClass() {delete[] data;}// 交换成员函数friend void swap(MyClass first, MyClass second) noexcept {using std::swap;swap(first.size, second.size);swap(first.data, second.data);}// 用于演示的函数void print() const {for (size_t i 0; i size; i) {std::cout data[i] ;}std::cout std::endl;}
};int main() {MyClass obj1(5);MyClass obj2(10);std::cout Original obj1: ;obj1.print();std::cout Original obj2: ;obj2.print();// 使用交换操作swap(obj1, obj2);std::cout Swapped obj1: ;obj1.print();std::cout Swapped obj2: ;obj2.print();return 0;
}Original obj1: 0 1 2 3 4
Original obj2: 0 1 2 3 4 5 6 7 8 9
Swapped obj1: 0 1 2 3 4 5 6 7 8 9
Swapped obj2: 0 1 2 3 4代码说明 这个 MyClass 类包含一个动态分配的整型数组和一个表示数组大小的成员变量。 实现了构造函数、拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符和析构函数。 实现了一个 swap 函数用于交换两个 MyClass 实例的内部状态。 在 main 函数中创建了两个 MyClass 对象并使用 swap 函数展示了交换操作的效果。 obj1 最初包含 5 个元素从 0 到 4。 obj2 最初包含 10 个元素从 0 到 9。 执行交换后obj1 和 obj2 的内容互换。