七、构造函数与析构函数

七、构造函数与析构函数

  • 构造函数
  • 析构函数
  • 调用机制
    • 构造函数的调用机制
    • 析构函数的调用机制
    • 总结
  • 构造函数分类
  • 深拷贝与浅拷贝
    • 浅拷贝(Shallow Copy)
    • 深拷贝(Deep Copy)
    • 示例
  • 初始化参数列表
  • 委托构造
  • `default`、`delete`和 `explicit`
    • `default`
    • `delete`
    • `explicit`

构造函数

在C++中,构造函数是一种特殊的成员函数,它用于初始化类的对象。当创建类的对象时,构造函数会被自动调用。构造函数的名字与类的名字相同,并且没有返回类型(即使是void也没有)。

下面是一个简单的例子来说明构造函数的使用:

class MyClass {
private:int value;public:// 构造函数MyClass(int v) : value(v) { // 使用初始化列表来初始化成员变量// 构造函数的主体(在这里是空的)}// 另一个构造函数(无参构造函数)MyClass() : value(0) {// 初始化为0}// 成员函数来访问valueint getValue() const {return value;}
};int main() {// 使用带有一个参数的构造函数创建对象MyClass obj1(10);std::cout << "obj1的值: " << obj1.getValue() << std::endl; // 输出: obj1的值: 10// 使用无参构造函数创建对象MyClass obj2;std::cout << "obj2的值: " << obj2.getValue() << std::endl; // 输出: obj2的值: 0return 0;
}

在上面的例子中,MyClass类有两个构造函数:一个接受一个整数参数,另一个不接受任何参数(称为默认构造函数)。构造函数使用初始化列表(: value(v))来初始化成员变量value

注意:

  1. 如果类中没有定义任何构造函数,编译器会提供一个默认的构造函数,它什么也不做(不会初始化成员变量)。但是,如果类中定义了其他构造函数,编译器就不会再提供默认构造函数了。
  2. 构造函数可以被重载,这意味着可以有多个构造函数,它们接受不同类型的参数或不同数量的参数。
  3. 构造函数可以抛出异常,但通常建议避免在构造函数中抛出异常,因为这可能导致资源泄漏或其他问题。
  4. 构造函数可以是虚函数(在基类中),但通常不建议这样做,因为虚函数主要用于在派生类中重写基类中的函数。构造函数在创建对象时被调用,而不是在通过指针或引用调用对象时被调用。

析构函数

在C++中,析构函数是另一个特殊的成员函数,它在对象的生命周期结束时被自动调用。析构函数的名字是在类的名字前面加上波浪符(~)。析构函数不接受任何参数(也不能有返回类型,即使是void),也没有参数列表。

析构函数主要用于释放对象可能占用的资源,如动态分配的内存、文件句柄、数据库连接等。

下面是一个简单的例子来说明析构函数的使用:

#include <iostream>class MyClass {
private:int* ptr;public:// 构造函数MyClass(int v) {ptr = new int(v); // 动态分配内存}// 析构函数~MyClass() {delete ptr; // 释放动态分配的内存std::cout << "MyClass对象被销毁" << std::endl;}// 成员函数来访问值int getValue() const {return *ptr;}
};int main() {MyClass obj(10);std::cout << "obj的值: " << obj.getValue() << std::endl; // 输出: obj的值: 10// 当obj离开作用域时,析构函数会被自动调用// 输出: MyClass对象被销毁return 0;
}

在上面的例子中,MyClass类有一个指向整数的指针ptr。在构造函数中,我们使用new运算符动态地分配了一个整数,并将其地址赋给ptr。在析构函数中,我们使用delete运算符来释放这块动态分配的内存。

obj离开其作用域(在main函数的末尾)时,它的析构函数会被自动调用,输出"MyClass对象被销毁",并释放了动态分配的内存。

注意:

  1. 析构函数不能被显式调用(即不能直接调用obj.~MyClass()),它们是由编译器在对象生命周期结束时自动调用的。
  2. 如果类中有动态分配的资源,那么应该在析构函数中释放这些资源,以避免内存泄漏。
  3. 析构函数可以是虚函数,这在处理基类指针指向派生类对象(多态)时非常重要。通过将基类的析构函数声明为虚函数,可以确保在删除基类指针时调用正确的析构函数(即派生类的析构函数)。
  4. 析构函数不应该抛出异常(除非有特殊的异常处理策略),因为如果在析构函数中抛出异常且没有被捕获,程序会被终止。

调用机制

构造与析构函数的调用机制在C++中遵循一定的规则,这些规则确保了对象在创建和销毁时的正确初始化与清理。

构造函数的调用机制

  1. 自动调用

    • 当在函数体、全局或命名空间作用域中定义类的对象时,构造函数会被自动调用。
    • 如果类中没有定义任何构造函数,编译器会提供一个默认的无参构造函数(但如果有其他构造函数被定义,编译器则不会提供默认无参构造函数)。
  2. 重载

    • 构造函数可以重载,即可以有多个构造函数,它们接受不同类型的参数或不同数量的参数。
    • 重载的构造函数允许以不同的方式初始化对象。
  3. 初始化列表

    • 构造函数可以使用初始化列表来初始化成员变量,这是一种更高效的初始化方式。
    • 初始化列表在构造函数的函数体之前执行。
  4. 拷贝构造

    • 当使用另一个同类型的对象来初始化一个新对象时,拷贝构造函数会被调用。
    • 拷贝构造函数有两种主要形式:浅拷贝和深拷贝。浅拷贝只是复制对象的指针,而深拷贝会复制指针指向的实际数据。
  5. 调用顺序

    • 在创建派生类对象时,首先调用基类的构造函数,然后调用派生类的构造函数。
    • 如果在类定义中显式地指定了初始化列表中的基类或成员变量初始化顺序,则按照指定的顺序进行初始化。

析构函数的调用机制

  1. 自动调用

    • 析构函数在对象的生命周期结束时被自动调用。
    • 当局部对象离开其作用域时,析构函数会被调用。
    • 全局或静态对象的析构函数在main函数结束后调用。
    • 如果使用new运算符在堆上动态分配的对象,则当delete运算符被用于该对象时,析构函数会被调用。
  2. 调用顺序

    • 在销毁派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数。
    • 析构函数的调用顺序与构造函数的调用顺序相反。
  3. 资源释放

    • 析构函数通常用于释放对象在生命周期中分配的资源,如动态内存、文件句柄等。
    • 如果析构函数抛出异常且没有被捕获,程序会被终止。因此,析构函数中应尽量避免抛出异常。

总结

  • 构造函数和析构函数是C++中用于管理对象生命周期的特殊成员函数。
  • 构造函数在对象创建时自动调用,用于初始化对象;析构函数在对象销毁时自动调用,用于清理对象并释放资源。
  • 构造函数可以重载,以支持不同的初始化方式;析构函数不能重载。
  • 构造函数的初始化列表提供了一种高效的初始化方式;析构函数则用于释放资源并确保对象的正确销毁。

构造函数分类

构造函数在C++中扮演着初始化对象的重要角色。它们与类名相同,没有返回值,并在对象实例化时由编译器自动调用。构造函数的分类主要包括以下几种:

  1. 无参数构造函数(默认构造函数)

    • 定义:最简单的构造函数,函数没有参数。
    • 特点:
      • 如果在类中未显式定义任何构造函数,编译器会自动生成一个无参的默认构造函数。
      • 一旦用户显式定义了构造函数(无论是否有参数),编译器将不再自动生成默认构造函数。
    • 用途:
      • 为对象的成员变量提供默认值。
      • 如果类中有自定义类型的成员变量,编译器生成的默认构造函数会调用这些成员的默认构造函数进行初始化。
  2. 有参数构造函数(重载构造函数)

    • 定义:带参数的构造函数,可以根据需要为成员变量提供初始值。
    • 特点:
      • 可以有多个有参构造函数,只要它们的参数列表(个数、类型或顺序)不同,以实现函数重载。
    • 用途:
      • 在创建对象时,通过构造函数参数为成员变量设置具体的初始值。
  3. 拷贝构造函数(复制构造函数)

    • 定义:拷贝现有对象,并以此拷贝的副本为数据创建一个新的对象。
    • 形式:ClassName(const ClassName& other);
    • 特点:
      • 拷贝构造函数的参数是同类型对象的常量引用。
      • 如果没有显式定义拷贝构造函数,编译器会生成一个默认的拷贝构造函数。
    • 用途:
      • 当一个对象需要以另一个对象作为初值进行初始化时,拷贝构造函数会被调用。
      • 在对象赋值、函数参数传递、函数返回值等情况下,如果涉及同类型对象的复制,可能会隐式调用拷贝构造函数。
  4. 移动构造函数(C++11及以后版本)

    • 定义:用于将一个临时对象(如右值)的资源“移动”到另一个对象,以实现资源的高效利用。
    • 形式:ClassName(ClassName&& other);
    • 特点:
      • 移动构造函数的参数是同类型对象的右值引用。
      • 通过移动构造函数,可以避免不必要的资源复制,提高程序性能。
    • 用途:
      • 在处理临时对象或即将被销毁的对象时,使用移动构造函数可以避免资源的浪费。

总结:构造函数的分类主要基于其参数和用途。无参数构造函数和有参数构造函数用于对象的初始化,而拷贝构造函数和移动构造函数则用于对象的复制和移动。在编写类时,应根据实际需要选择和设计合适的构造函数。

深拷贝与浅拷贝

在C++中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是两种对象复制的方式,它们之间的主要区别在于如何处理对象的成员变量,特别是当成员变量是指针或引用类型时。

浅拷贝(Shallow Copy)

浅拷贝只是简单地将对象的成员变量值复制到另一个对象中。如果对象的成员变量是指针或引用,那么浅拷贝只是复制指针或引用的值,而不是指向的实际数据。因此,两个对象将指向同一块内存区域。

浅拷贝的问题在于,如果两个对象试图修改它们指向的相同内存区域的数据,可能会导致数据不一致或其他不可预测的行为。此外,如果其中一个对象删除了它指向的数据,那么另一个对象将成为一个悬挂指针(dangling pointer),指向不再有效的内存区域。

在C++中,编译器提供的默认拷贝构造函数和拷贝赋值运算符通常执行浅拷贝。

深拷贝(Deep Copy)

深拷贝会创建一个新的内存区域来存储对象的成员变量值,特别是当成员变量是指针或引用类型时。深拷贝会递归地复制对象的所有成员变量,包括指针或引用指向的实际数据。因此,两个对象将拥有各自独立的内存区域和数据副本。

深拷贝可以确保对象之间的独立性,每个对象都可以安全地修改自己的数据而不会影响其他对象。但是,深拷贝也可能导致更多的内存使用和更长的复制时间,因为需要创建新的内存区域并复制数据。

在C++中,如果需要执行深拷贝,通常需要显式地定义拷贝构造函数和拷贝赋值运算符。例如,如果类包含一个动态分配的数组作为成员变量,那么拷贝构造函数和拷贝赋值运算符应该使用new运算符来分配新的内存区域,并逐个复制数组元素。

示例

下面是一个简单的示例,展示了浅拷贝和深拷贝的区别:

#include <iostream>
#include <cstring>class String {
private:char* data;size_t len;public:// 构造函数String(const char* str) {len = strlen(str);data = new char[len + 1];strcpy(data, str);}// 浅拷贝构造函数(不安全的)String(const String& other) {len = other.len;data = other.data; // 浅拷贝,只复制指针值}// 深拷贝构造函数(安全的)// String(const String& other) {//     len = other.len;//     data = new char[len + 1]; // 分配新内存//     strcpy(data, other.data); // 复制数据// }// 析构函数~String() {delete[] data;}// ... 其他成员函数 ...
};int main() {String str1("Hello");String str2(str1); // 使用浅拷贝构造函数// 如果使用浅拷贝,这里将出现悬挂指针问题,因为str1在销毁时会删除其数据// 如果使用深拷贝,则每个对象都有自己的数据副本,可以安全地销毁return 0;
}

在上面的示例中,如果使用了浅拷贝构造函数,那么在str1被销毁时,其指向的数据也会被删除。但是,由于str2data成员变量指向了相同的内存区域,因此它现在成为了一个悬挂指针。为了避免这种情况,应该使用深拷贝构造函数来确保每个对象都有自己的数据副本。

初始化参数列表

初始化参数列表是在构造函数定义的开始部分使用冒号:后跟初始化列表的形式。这种方式可以直接初始化成员变量,甚至对于const成员变量和引用成员变量,这是唯一的初始化方式。

class MyClass {
public:int x;double y;MyClass(int a, double b) : x(a), y(b) {} // 使用初始化参数列表
};

使用初始化参数列表的好处包括:

  • 更高的效率:对于某些类型(如const成员、引用成员、类类型的成员),只能使用初始化参数列表进行初始化。
  • 可以避免一些不必要的赋值操作,从而减少代码量,提高效率。

委托构造

委托构造是C++11引入的新特性,允许一个构造函数调用另一个同类的构造函数,以避免代码重复。

class MyClass {
public:int x;double y;MyClass() : MyClass(0, 0.0) {} // 委托给另一个构造函数MyClass(int a, double b) : x(a), y(b) {}
};

在这个例子中,无参数的构造函数通过委托构造调用了带有两个参数的构造函数,从而实现了成员变量的初始化。

委托构造的使用场景包括:

  • 当类有多个构造函数,并且它们之间有共同的初始化逻辑时,可以使用委托构造来避免代码重复。
  • 当你想要在一个构造函数中扩展另一个构造函数的行为时。

总结,初始化参数列表和委托构造都是C++中用于初始化类成员变量的有用特性,它们各有适用场景,可以帮助你编写更高效、更易于维护的代码。

defaultdeleteexplicit

default

default关键字用于显式地要求编译器生成默认的特殊成员函数,比如默认构造函数、默认析构函数、默认拷贝构造函数、默认拷贝赋值运算符等。这对于想要编译器生成默认行为,同时又因为某些原因(比如定义了其他构造函数)导致编译器不会自动生成默认行为的情况非常有用。

例如:

class MyClass {
public:MyClass() = default; // 显式要求编译器生成默认构造函数MyClass(const MyClass&) = default; // 显式要求编译器生成默认拷贝构造函数// ...
};

delete

delete关键字用于删除某些特殊的成员函数或者重载的函数,这意味着这些函数不能被调用,无论是显式调用还是隐式调用。这对于禁止某些操作非常有用,比如禁止拷贝。

例如:

public:MyClass(const MyClass&) = delete; // 禁止拷贝构造函数MyClass& operator=(const MyClass&) = delete; // 禁止拷贝赋值运算符// ...
};

explicit

explicit关键字用于修饰类的一个参数的构造函数,表示该构造函数是显式的,这意味着它不能用于隐式类型转换。这主要用于防止构造函数在某些情况下被意外地用作类型转换函数。

例如:

class MyClass {
public:explicit MyClass(int x) { /* ... */ } // 显式构造函数// ...
};void func() {MyClass obj = 10; // 错误:构造函数是显式的,不能用于隐式类型转换MyClass obj2(10); // 正确:显式调用构造函数
}

综上所述,defaultdeleteexplicit是C++中用于控制类的特殊成员函数行为的三个关键字,它们分别用于显式要求编译器生成默认行为、删除某些函数以及防止隐式类型转换。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/868454.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Go】常见的变量与常量

变量 常见的变量声明方式 一、声明单个变量的多种方式 1.声明一个变量初始化一个值 //声明变量 默认值是0&#xff0c;var a int//初始化一个值a 1fmt.Println(a) 2. 在初始化的时候省去数据类型&#xff0c;通过值自动匹配当前的变量的数据类型 var b 2fmt.Println(&quo…

html+css+js随机验证码

随机画入字符、线条 源代码在图片后面 点赞❤️关注&#x1f60d;收藏⭐️ 互粉必回 图示 源代码 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"…

【java web 01】3小时快速学习前端知识(收藏备用)

3小时快速学习前端知识【全栈专用】 一、教程简介1.1 Java 开发为何学Web技术1.2 课程设计1.3 课前准备 二、HTML2.1 Html简介2.1.1 HTML、CSS、JS分别有什么作用2.1.2 什么是HTML2.1.3 什么是标记语言 2.2 Hello&#xff0c;Html2.2.1 HTML基础结构2.2.2 专业词汇2.2.3 语法细…

C++入门(C语言过渡)

文章目录 前言一、C关键字二、命名空间三、C输入&输出四、缺省参数五、函数重载六、引用七、inline八、nullptr总结 前言 C是一种通用的、高级的、静态类型的编程语言&#xff0c;它在20世纪80年代由丹尼斯里奇创建的C语言基础上发展而来。以下是C发展的一些重要里程碑。 1…

Updates were rejected because the tip of your current branch is behind

Git在push推送时&#xff0c;报错提示信息如下&#xff1a; hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. If you want to integrate the remote changes, hint: use git pull before pushing again. hint: …

[个人感悟] 消息队列应该考察哪些问题?

前言 消息队列. 不论是Java内部提供的LinkedBlockingQueue, 还是当下主流的中间件RabbitMQ, Kafka, RockMQ. 其本质上都是一个削峰填谷的工具. 我们都知道, 请求和流量都有可能瞬间很高, 或者很低. 所以, 很多时候, 我们需要请求存储起来, 或者使用异步的方式, 来匀速的处理过…

Go compress包

compress 包是 Go 标准库中的一个重要包&#xff0c;提供了对常见压缩格式&#xff08;如 gzip、zlib、bzip2 和 lzw&#xff09;的支持。这个包主要用于处理压缩和解压缩数据流。 compress 包的结构 compress 包包含以下子包&#xff1a; compress/gzip: 提供了对 gzip 格式的…

动态规划算法-以中学排课管理系统为例

1.动态规划算法介绍 1.算法思路 动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中&#xff0c;可能会有许多可行解。每一个解都对应于一个值&#xff0c;我们希望找到具有最优值的解。动态规划算法与分治法类似&#xff0c;其基本思想也是将待求解问题分解成若…

爬虫:Sentry-Span参数逆向

在抓某眼查数据太过频繁时会出现极验的验证码。极验的教程有很多&#xff0c;主要是发现在这里获取验证码的时候需要携带参数Sentry-Span。在这里记录一下逆向的主要过程&#xff0c;直接上补环境的代码。 window global; location {}; my_log console.log;(function () {l…

windows 11 + kali wsl二合一配置步骤与踩坑

windows 11 kali wsl二合一配置步骤与踩坑 在前几天的某市攻防演练中&#xff0c;在攻防前期&#xff0c;我的虚拟机经常无缘无故出现断网、卡顿等现象&#xff0c;但找不出原因。 为了不影响后续的这些天的攻防演练&#xff0c;我选择在一个晚上通宵 在我的windows 11系统上…

白骑士的C语言教学实战项目篇 4.4 简单HTTP服务器

在本项目中&#xff0c;我们将设计并实现一个简单的HTTP服务器&#xff0c;涵盖网络编程基础、HTTP协议解析、多线程处理请求。通过这个项目&#xff0c;我们可以更加深入地了解网络编程、HTTP协议以及多线程编程的基本概念和实现方法。 网络编程基础 网络编程是计算机科学中的…

2024年电脑监控软件排行榜(真实测评推荐七款电脑监控软件)

在信息化快速发展的今天&#xff0c;企业对员工电脑活动的监控变得尤为重要。有效的电脑监控软件不仅可以提升员工的工作效率&#xff0c;还能防止信息泄露&#xff0c;保障企业的数据安全。本文将介绍几款知名的电脑监控软件&#xff0c;并对其特点进行详细分析&#xff0c;帮…

java中函数式接口一般什么时候使用,并写一下详细的代码实例

在Java中&#xff0c;函数式接口&#xff08;Functional Interface&#xff09;主要用于支持Lambda表达式和方法引用&#xff0c;从而简化代码、提高可读性和可维护性。函数式接口是指仅包含一个抽象方法的接口&#xff0c;这样的接口可以通过Lambda表达式来实例化&#xff0c;…

DMException: 变量空间溢出,解决达梦数据库报错问题

达梦报错 Caused by: dm.jdbc.driver.DMException: 变量空间溢出 at dm.jdbc.driver.DBError.throwException(SourceFile:715) ~[DmJdbcDriver18.jar:- 8.1.3.100 - Production] at dm.jdbc.a.a.y.l(SourceFile:619) ~[DmJdbcDriver18.jar:- 8.1.3.100 - Production] …

笔记本电脑投屏怎么操作?一看就会!

日常工作或办公都会用到笔记本电脑&#xff0c;但很多新手用户不知道笔记本电脑的投屏要怎么操作&#xff1f;接下来系统之家给大家介绍三种简单的操作方法&#xff0c;帮助大家轻松完成笔记本电脑投屏投屏操作&#xff0c;从而满足自己的办公或学习使用需求。 方法一 1. 直接W…

Django QuerySet对象,exclude()方法

模型参考上一章内容&#xff1a; Django QuerySet对象&#xff0c;filter()方法-CSDN博客 exclude()方法&#xff0c;用于排除符合条件的数据。 1&#xff0c;添加视图函数 Test/app11/views.py from django.shortcuts import render from .models import Postdef index(re…

Eclipse运行main函数报 launch error

右键run as java application&#xff0c;运行main函数的时候报launch error 解决方式&#xff1a;文件右键run configurations 旧的是Project JRE&#xff0c;改成下图这个样子

python(opencv2、PIL)将图片透明背景转换成白色背景的两种方法

方法一&#xff1a;使用OpenCV的函数封装 pip install opencv-pythonimport cv2 import numpy as npdef convert_transparent_to_white_opencv(input_image_path, output_image_path):"""将透明背景的图片转换为白色背景&#xff0c;使用OpenCV实现。参数&…

服务器的分类有哪些

1、根据体系结构不同&#xff0c;服务器可以分成两大重要的类别&#xff1a;IA架构服务器和RISC架构服务器。   这种分类标准得主要依据是两种服务器采用得处理器体系结构不同。RISC架构服务器采用得CPU是所谓的精简指令集的处理器&#xff0c;精简指令集CPU的主要特点是采用…

Windows7彻底卸载mysql

1.控制面板卸载mysql 2.删除C:\Program Files\MySQL 3.删除C:\用户\Administrator\App Data\Roaming\MySQL”(App Data默认隐藏&#xff0c;需要在文件夹和搜索选项中勾选显示文件夹),为了删除的更彻底&#xff0c;可以直接在计算机全盘搜索MySQL关键字&#xff0c;将所有找到…