个人主页静态网站,手机网站开发开发,衡水大型网站建设,做网站开发 甲方提供资料C六大默认成员函数 默认构造函数默认析构函数RAII技术RAII的核心思想优点示例应用场景 默认拷贝构造深拷贝和浅拷贝 默认拷贝赋值运算符移动构造函数#xff08;C11起#xff09;默认移动赋值运算符#xff08;C11起#xff09;取地址及const取地址操作符重载取地址操作符重… C六大默认成员函数 默认构造函数默认析构函数RAII技术RAII的核心思想优点示例应用场景 默认拷贝构造深拷贝和浅拷贝 默认拷贝赋值运算符移动构造函数C11起默认移动赋值运算符C11起取地址及const取地址操作符重载取地址操作符重载常量取地址操作符重载示例代码输出结果注意事项 扩展前置和后置重载重载规则 C中的六大默认成员函数是编译器在特定条件下自动生成的成员函数用于管理对象的生命周期和资源操作。它们分别是
默认构造函数 作用初始化对象当类没有显式定义任何构造函数时生成。 生成条件用户未定义任何构造函数。 注意若类有其他构造函数如带参数的构造函数需显式使用 default 声明默认构造函数。
class Person
{
public://Person()//{//} 不写的话默认自动生成void GetAge(){std::cout _age std::endl;}
private:int _age;
};int main()
{Person p;p.GetAge();
}其特征如下 函数名与类名相同。无返回值。对象实例化时编译器自动调用对应的构造函数。构造函数可以重载。如果类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数一旦用户显式定义编译器将不再生成。编译器生成默认的构造函数会对自定类型成员调用的它的默认成员 函数。C11 中针对内置类型成员不初始化的缺陷又打了补丁即内置类型成员变量在类中声明时可以给默认值。 默认析构函数 作用释放对象资源默认析构函数调用成员变量的析构函数。 生成条件用户未定义析构函数。 注意若类管理动态资源如堆内存需自定义析构函数以避免内存泄漏
class Person
{
public://Person()//{//} 不写的话默认自动生成void GetAge(){std::cout _age std::endl;}~Person(){}
private:int _age;
};int main()
{Person p;p.GetAge();
}RAII技术
RAIIResource Acquisition Is Initialization资源获取即初始化是C中一种管理资源的编程技术。它通过将资源的生命周期与对象的生命周期绑定在一起利用C的构造函数和析构函数来自动管理资源从而避免了手动分配和释放资源可能带来的问题如内存泄漏、资源未正确释放等。
RAII的核心思想
资源在对象构造时获取当一个对象被创建时它的构造函数负责获取所需的资源例如动态内存分配、文件打开、网络连接等。资源在对象销毁时释放当对象离开作用域或被显式删除时其析构函数会自动释放之前获取的资源。
优点
异常安全性由于资源管理由构造和析构函数自动处理即使程序中抛出了异常也能确保资源得到正确释放。简化代码开发者不需要手动跟踪每个资源的状态并且可以在不使用显式的try-finally块的情况下保证资源的释放。防止资源泄露只要对象被正确地创建并最终销毁资源就会被正确释放。
示例
以下是一个简单的例子展示了如何使用RAII来管理动态分配的内存
#include iostreamclass ResourceHandler {
private:int* data;
public:// 构造函数资源获取ResourceHandler() {data new int(10); // 分配资源std::cout Resource acquired. std::endl;}// 析构函数资源释放~ResourceHandler() {delete data; // 释放资源std::cout Resource released. std::endl;}void showData() const {std::cout Data: *data std::endl;}
};void useResource() {ResourceHandler handler;handler.showData();// 不需要手动释放资源handler离开作用域时会自动调用析构函数
}int main() {useResource();return 0;
}在这个例子中ResourceHandler类负责管理一个整数类型的动态分配内存。构造函数在对象创建时分配资源而析构函数在对象销毁时释放这些资源。这样就确保了无论函数useResource如何退出正常结束或因异常退出资源都会被正确释放。
应用场景
RAII不仅限于内存管理还可以应用于其他资源类型如文件句柄、网络套接字、数据库连接等。标准库中的智能指针如std::unique_ptr、std::shared_ptr、锁机制如std::lock_guard、std::unique_lock都是RAII原则的实际应用案例。通过使用这些工具可以有效地减少资源管理错误提高代码的安全性和可靠性。
默认拷贝构造 声明形式ClassName(const ClassName) 作用通过已有对象初始化新对象默认执行浅拷贝。 生成条件用户未定义拷贝构造函数。 注意若类包含指针或动态资源需自定义深拷贝防止重复释放。
class Person
{
public:Person(){}Person(const Person person){this-_age person._age;}~Person(){}void GetAge(){std::cout _age std::endl;}
private:int _age;
};int main()
{Person p;p.GetAge();
}深拷贝和浅拷贝
class Stack
{
public://初始化Stack(){_array new int[20];}//默认生成拷贝构造//析构~Stack(){delete[] _array;}
private:int* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2(s1);
}这里我没有写实际的拷贝构造函数这里s2调用的默认的拷贝构造所以s2_array的地址就是s1中_array的地址这就叫浅拷贝 这样代码就会有问题因为一个地址会被析构两次 正确的方法应该是给s2的array开辟一块新的空间
class Stack
{
public://初始化Stack(){_array new int[20];}Stack(const Stack st){_array new int[10];_size st._size;_capacity st._capacity;}//析构~Stack(){delete[] _array;}
private:int* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2(s1);
}这样的拷贝我们称为深拷贝再次运行程序
默认拷贝赋值运算符 声明形式ClassName operator(const ClassName) 作用将已有对象的值赋给另一个对象默认浅拷贝。 生成条件用户未定义拷贝赋值运算符。 注意需处理自赋值问题并在资源管理时实现深拷贝。
class Stack
{
public://初始化Stack(){_array new int[20];}Stack(const Stack st){_array new int[10];_size st._size;_capacity st._capacity;}Stack operator(const Stack st){if (this ! st){_array new int[10];_size st._size;_capacity st._capacity;}return *this;}//析构~Stack(){delete[] _array;}
private:int* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2;s2 s1;
}移动构造函数C11起 声明形式ClassName(ClassName) 作用通过右值引用“窃取”资源避免深拷贝开销。 生成条件用户未定义拷贝操作、移动操作或析构函数。 注意移动后源对象应处于有效但未定义状态如空指针。
class Stack
{
public://初始化Stack(){_array new int[20];}Stack(const Stack st){_array new int[10];_size st._size;_capacity st._capacity;}Stack operator(const Stack st){if (this ! st){_array new int[10];_size st._size;_capacity st._capacity;}return *this;}void swap(Stack st){std::swap(_array, st._array);std::swap(_size, st._size);std::swap(_capacity, st._capacity);}//移动构造函数Stack(Stack st):_array(nullptr), _size(0), _capacity(0){swap(st);}//析构~Stack(){delete[] _array;}
private:int* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2(std::move(s1));
}默认移动赋值运算符C11起 声明形式ClassName operator(ClassName) 作用通过右值引用转移资源所有权。 生成条件同移动构造函数。 注意需正确处理自移动赋值。
class Stack
{
public://初始化Stack(){_array new int[20];}Stack(const Stack st){_array new int[10];_size st._size;_capacity st._capacity;}Stack operator(const Stack st){if (this ! st){_array new int[10];_size st._size;_capacity st._capacity;}return *this;}void swap(Stack st){std::swap(_array, st._array);std::swap(_size, st._size);std::swap(_capacity, st._capacity);}//移动构造函数Stack(Stack st):_array(nullptr), _size(0), _capacity(0){swap(st);}//移动复制构造Stack operator(Stack st){swap(st);st._array nullptr;st._size 0;st._capacity 0;return *this;}//析构~Stack(){delete[] _array;}
private:int* _array;size_t _size;size_t _capacity;
};int main()
{Stack s1;Stack s2;s2 std::move(s1);
}在C中前置和后置运算符可以通过成员函数或非成员函数的形式进行重载。两者的主要区别在于参数列表和返回值
前置增加对象的值并返回增加后的对象引用。后置首先保存当前对象的状态然后增加对象的值最后返回之前保存的对象的副本。
取地址及const取地址操作符重载
在C中取地址操作符和常量取地址操作符const 通常不需要显式地重载因为编译器提供了默认的实现它们分别返回对象或常量对象的内存地址。然而在某些特定情况下你可能想要自定义这些操作符的行为。
取地址操作符重载
当你重载取地址操作符时你通常是为了改变其默认行为例如返回一个代理对象的地址而不是原始对象的地址。不过这种情况非常少见大多数时候并不需要这样做。
常量取地址操作符重载
类似地重载常量版本的取地址操作符也是为了提供特定的行为但同样这并不是常见的需求。
示例代码
尽管不常见这里还是给出如何重载这两种操作符的基本示例
#include iostreamclass MyClass {
private:int value;
public:MyClass(int val) : value(val) {}// 重载取地址操作符int* operator() {std::cout 非const取地址操作符被调用 std::endl;return value;}// 重载const取地址操作符const int* operator() const {std::cout const取地址操作符被调用 std::endl;return value;}
};int main() {MyClass obj(10);const MyClass constObj(20);int* addr obj; // 调用非const版本const int* constAddr constObj; // 调用const版本std::cout *addr: *addr std::endl;std::cout *constAddr: *constAddr std::endl;return 0;
}输出结果
非const取地址操作符被调用
const取地址操作符被调用
*addr: 10
*constAddr: 20在这个例子中我们为MyClass类重载了取地址操作符和常量取地址操作符。当通过非常量对象调用operator()时会调用非常量版本的操作符并打印一条消息。而当通过常量对象调用operator()时则调用常量版本的操作符并打印另一条不同的消息。
注意事项
谨慎使用一般情况下不需要也不建议重载这两个操作符除非有特别的需求。这是因为它们改变了标准语义可能会导致混淆或者不可预期的行为。保持一致性如果你决定重载这些操作符请确保它们的行为符合逻辑且一致避免引入错误。理解限制需要注意的是即使你重载了取地址操作符也无法阻止使用内置的取地址操作来获取对象的实际地址。例如obj总是可以获得obj的实际地址除非你完全隐藏了对象的访问方式。
总的来说重载取地址操作符和常量取地址操作符是一种高级技巧适用于特定场景下的特殊需求。在大多数日常编程任务中这种重载是不必要的。
扩展前置和后置重载
重载规则
前置只需要一个参数即调用该运算符的对象本身并且通常返回一个指向修改后的对象的引用。后置需要两个参数第一个是调用该运算符的对象本身第二个是一个int类型的占位参数用于区分前置和后置形式。后置返回的是操作前对象的一个副本通常是通过值返回。
#define _CRT_SECURE_NO_WARNINGS 1
#includeiostream
using namespace std;class Count
{
public://重载后置Count operator(){_count;return *this;}//后置Count operator(int){Count temp *this;_count;return temp;}int getCount() const {return _count;}
private:int _count 0;
};int main()
{Count myCounter;std::cout Initial count: myCounter.getCount() std::endl;myCounter; // 调用前置std::cout After prefix increment: myCounter.getCount() std::endl;Count d;d myCounter; // 调用后置std::cout After postfix increment: d.getCount() std::endl;return 0;
}