C++ 语言特性09 - 默认构造函数

news2024/10/3 11:23:55

1. C++11 以后都有哪些默认构造函数?

                1)  默认构造函数

                2)析构函数

                3)拷贝构造函数

                4)拷贝赋值运算符构造函数

                5)移动构造函数(C++11新增)

                6)移动赋值运算符构造函数(C++新增)

2. 构造函数举例 

#include <iostream>

class Point {
public:
    int x, y;

    // 拷贝构造函数
    Point(const Point& other) : x(other.x), y(other.y) {
        std::cout << "Copy constructor called\n";
    }
};

int main() {
    Point p1 = {1, 2};
    Point p2(p1); // 调用拷贝构造函数
    return 0;
}
#include <iostream>
#include <algorithm>  // For std::copy

class Data {
public:
    int* values;
    size_t size;

    Data() : values(nullptr), size(0) {}
    explicit Data(size_t size) : size(size) {
        values = new int[size];
        std::fill(values, values + size, 0);  // 初始化为0
    }

    ~Data() {
        delete[] values;
    }

    // 拷贝构造函数
    Data(const Data& other) : size(other.size) {
        values = new int[size];
        std::copy(other.values, other.values + size, values);
    }

    // 拷贝赋值运算符
    Data& operator=(const Data& other) {
        if (this != &other) {  // 检查自赋值
            delete[] values;  // 释放原有资源
            size = other.size;
            values = new int[size];
            std::copy(other.values, other.values + size, values);
        }
        return *this;
    }
};

int main() {
    Data d1(5);  // 创建一个大小为5的Data对象
    Data d2 = d1;  // 调用拷贝构造函数

    Data d3;
    d3 = d2;  // 调用拷贝赋值运算符

    return 0;
}
class MyClass {
public:
    int* data;

    // 移动构造函数
    MyClass(MyClass&& other) : data(other.data) {
        other.data = nullptr;  // 将源对象的指针置为空,防止其析构时释放内存
    }
};

class MyClass {
public:
    int* data;

    // 移动赋值运算符
    MyClass& operator=(MyClass&& other) {
        if (this != &other) {  // 检查自赋值
            delete[] data;  // 释放原有资源
            data = other.data;
            other.data = nullptr;  // 将源对象的指针置为空,防止其析构时释放内存
        }
        return *this;  // 返回当前对象的引用
    }
};

3. 什么情况不会生成默认构造函数?  

  • 用户定义了其他构造函数: 如果类中定义了至少一个构造函数,编译器将不会自动生成默认构造函数。

  • 类中有不具有默认构造函数的非静态成员对象: 如果类包含一个或多个没有默认构造函数的非静态成员对象,编译器不会生成默认构造函数。

  • 类中有虚拟函数: 如果类中有虚拟函数(即至少有一个函数声明为virtual),编译器不会生成默认构造函数。

  • 类中有引用成员: 引用需要在创建时被初始化,因此如果类包含引用类型的成员,必须提供一个构造函数来初始化这些成员。

  • 类中有常量成员: 常量成员也必须在创建时初始化,所以如果类包含常量类型的成员,编译器不会生成默认构造函数。

  • 类是抽象类: 如果类是抽象类(包含至少一个纯虚函数),则不会有默认构造函数,因为抽象类不能实例化。

  • 类中有自定义的默认构造函数: 如果已经显式定义了默认构造函数,则不需要生成另一个。

  • 类中有:初始化的非静态成员: 如果类成员使用了:初始化(即在成员声明时直接赋值),则需要构造函数来初始化这些成员。

  • 类中有动态分配的成员: 如果类中包含动态分配的成员(如指针成员),通常需要自定义构造函数来正确地分配内存。

  • 类继承自一个没有默认构造函数的基类: 如果派生类继承自一个没有默认构造函数的基类,派生类也不会有默认构造函数。

  • 类中有std::initializer_list成员: 如果类包含std::initializer_list类型的成员,需要自定义构造函数来初始化。

  • 类中有数组成员: 如果类包含数组类型的成员,通常需要自定义构造函数来初始化数组。

4. 什么是POD类型的对象?

        在C++中,POD是“Plain Old Data”的缩写,指的是最普通的C++数据类型和对象。POD类型包括了C++中的原始数据类型(如intdouble等)、POD结构体(不含用户定义的构造函数、虚函数、静态成员、引用成员等的类)、POD联合体、POD数组以及它们的数组。

4.1. POD类型满足以下条件:

  • 不包含用户自定义的构造函数:POD类型不能包含任何构造函数。

  • 不包含虚函数:POD类型不能包含任何虚函数。

  • 不包含静态成员变量:POD类型不能包含静态成员变量。

  • 不包含引用成员:POD类型不能包含引用类型的成员。

  • 不包含常量成员:POD类型不能包含常量成员,除非它们是字面量。

  • 不包含基类或派生自其他类:POD类型不能有继承关系。

  • 成员变量也必须是POD类型:POD类型的成员变量也只能是POD类型。

  • 标准布局类型:POD类型必须是标准布局类型(Standard-Layout Type),这意味着其成员的排列方式在不同的实现中是一致的。

//POD 类型举例
class PODStruct {
    int x;
    double y[3];
    char z;
};

int main() {
    int i;  // POD类型
    PODStruct s;  // POD类型
    char str[] = "Hello, World!";  // POD类型
}

//非POD类型举例

class NonPODStruct {
    NonPODStruct() : value(0) {}  // 用户自定义的构造函数
    int value;
};

class NonPODStruct2 {
    static int static_member;  // 静态成员变量
    int value;
};

class NonPODStruct3 {
    int& ref_member;  // 引用成员
    int value;
};

class NonPODStruct4 {
    const int const_member;  // 常量成员
    int value;
};

class NonPODClass {
public:
    virtual void foo() {}  // 虚函数
};

int main() {
    NonPODStruct non_pod1;  // 非POD类型
    NonPODStruct2 non_pod2;  // 非POD类型
    NonPODStruct3 non_pod3;  // 非POD类型
    NonPODStruct4 non_pod4;  // 非POD类型
    NonPODClass non_pod5;  // 非POD类型
}

4.2 POD类型的作用:

  • 简单的内存布局: POD类型具有简单的内存布局,这意味着它们的内存表示直接对应于它们的成员变量的内存表示的串联。这种简单性使得POD类型易于理解和使用。

  • 高性能: 由于POD类型的内存布局简单,它们可以高效地在函数间传递和存储。编译器可以优化POD类型的访问和操作,从而提高程序性能。

  • 内存对齐: POD类型通常具有自然的内存对齐,这意味着它们的内存布局符合硬件的对齐要求,这有助于提高访问速度。

  • 与C语言的兼容性: POD类型可以无缝地与C语言代码互操作,因为它们的内存布局与C语言结构体相同。

  • 字节级操作: POD类型可以进行字节级操作,如内存拷贝和比较,而不需要调用构造函数、析构函数或赋值运算符。

  • 默认初始化: C++标准保证POD类型的默认初始化将所有字节设置为零,这简化了某些类型的初始化过程。

 4.3 POD类型的使用场景:

  1. 与C代码互操作: 当需要与C语言库或API交互时,POD类型非常有用,因为它们可以直接映射到C语言的结构体。

  2. 性能敏感的应用程序: 在性能至关重要的场景中,如嵌入式系统、游戏开发或实时系统,POD类型可以提供高效的数据处理。

  3. 内存操作: 当需要手动管理内存或进行低层次的内存操作时,POD类型是理想的选择,因为它们可以确保内存布局的一致性和简单性。

  4. 数据结构的序列化: POD类型可以轻松地进行序列化和反序列化,因为它们的内存布局是已知的,这使得它们适合于网络通信和文件存储。

  5. 数组操作: 当处理固定大小的数据数组时,POD类型可以提供高效的存储和访问方式。

  6. 科学计算和数值分析: 在科学计算和数值分析中,POD类型常用于表示向量、矩阵和其他数学结构。

  7. 内存对齐要求: 在需要特定内存对齐的硬件或平台编程中,POD类型的自然对齐特性非常有用。

  8. 简单的结构体: 对于只包含数据而没有成员函数的简单结构体,POD类型是合适的选择。

  9. 缓存和数据存储: POD类型可以用于缓存和数据存储,因为它们可以被直接映射到存储介质。

  10. 位字段: 在需要精确控制内存布局的场景中,如位字段操作,POD类型可以提供这种控制。

5. C++11中 explicit 的作用和举例 

       在C++中,explicit 关键字用于类声明中的构造函数,其作用是防止编译器使用该构造函数进行类型转换,从而增加代码的明确性和安全性。

  1. 防止隐式转换: 使用 explicit 关键字可以防止编译器在需要时自动调用该构造函数进行隐式类型转换。这可以避免一些不直观的转换,减少潜在的错误。

  2. 增加代码明确性: 当构造函数被声明为 explicit 时,对象的创建必须显式地使用构造函数进行,这使得代码的意图更加明确。

#include <iostream>
#include <string>

class MyClass {
public:
    int value;
    
    // 显式构造函数
    explicit MyClass(int val) : value(val) {}
    
    // 隐式构造函数(用于演示)
    MyClass(const std::string& val) : value(std::stoi(val)) {}
};

int main() {
    MyClass obj1 = 10;       // 正确:显式调用构造函数
    MyClass obj2(MyClass(20)); // 正确:显式调用构造函数
    
    MyClass obj3 = "30";      // 错误:隐式构造函数被阻止,需要显式调用
    // MyClass obj4("40");  // 正确:显式调用隐式构造函数
    
    std::cout << "obj1: " << obj1.value << std::endl;
    std::cout << "obj2: " << obj2.value << std::endl;
    // std::cout << "obj3: " << obj3.value << std::endl;
    // std::cout << "obj4: " << obj4.value << std::endl;
    
    return 0;
}

6. C++11 中显示删除构造函数

        在C++中,显式删除函数(也称为删除成员函数)是使用delete关键字来防止编译器自动生成默认的特殊成员函数(如拷贝构造函数、赋值运算符、移动构造函数和移动赋值运算符)。从C++11开始,可以在类内部直接使用= delete;来删除特定的函数。 

  • default一起使用: 如果显式定义了默认构造函数、拷贝构造函数、移动构造函数、拷贝赋值运算符或移动赋值运算符中的任何一个,就需要显式地定义其他的,否则默认的会被删除。

class MyClass {
public:
    MyClass() = default;  // 显式默认构造函数
    MyClass(const MyClass&) = default;  // 显式默认拷贝构造函数
    MyClass& operator=(const MyClass&) = default;  // 显式默认拷贝赋值运算符
    MyClass(MyClass&&) = default;  // 显式默认移动构造函数
    MyClass& operator=(MyClass&&) = default;  // 显式默认移动赋值运算符

    // 删除拷贝赋值运算符
    MyClass& operator=(const MyClass&) const = delete;
};

int main() {
    // ...
    return 0;
}
  • 类模板: 在类模板中,如果模板参数会影响某些操作的可行性,可以使用SFINAE(Substitution Failure Is Not An Error)技巧来条件性地删除函数。

template <typename T>
class MyClass {
public:
    void swap(MyClass& other) {
        // ...
    }
    
    // 特化,当T是不可移动的类型时,删除移动操作
    void swap(MyClass& other, typename std::enable_if<!std::is_move_constructible<T>::value>::type* = 0) = delete;
};

int main() {
    // ...
    return 0;
}
  • 删除函数与继承: 如果派生类需要删除从基类继承的特定成员函数,可以在派生类中再次使用delete关键字。

class Base {
public:
    Base& operator=(const Base&) = delete;  // 基类中删除赋值运算符
};

class Derived : public Base {
public:
    // 派生类继承了基类的赋值运算符删除
    using Base::operator=;
};

int main() {
    Derived d1, d2;
    // d1 = d2;  // 编译错误:赋值运算符被删除
    return 0;
}
  • 异常安全: 删除某些操作(如拷贝或移动操作)可能会影响异常安全的保证,因为这些操作可能在资源管理中起作用。

class NonCopyable {
public:
    NonCopyable() = default;
    
    NonCopyable(const NonCopyable&) = delete;  // 删除拷贝构造函数
    NonCopyable& operator=(const NonCopyable&) = delete;  // 删除拷贝赋值运算符

    NonCopyable(NonCopyable&&) = default;  // 保留移动构造函数
    NonCopyable& operator=(NonCopyable&&) = default;  // 保留移动赋值运算符
};

int main() {
    NonCopyable a;
    // NonCopyable b = a;  // 编译错误:拷贝构造函数被删除
    // NonCopyable c;
    // c = a;  // 编译错误:拷贝赋值运算符被删除
    return 0;
}

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

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

相关文章

Linux相关概念和重要知识点(11)(进程调度、Linux内核链表)

1.Linux调度算法 上篇文章我粗略讲过queue[140]的结构&#xff0c;根据哈希表&#xff0c;我们可以将40个不同优先级的进程借助哈希桶链入queue[140]中。调度器会根据queue的下标来进行调度。但这个具体的调度过程是怎样的呢&#xff1f;以及runqueue和queue[140]的关系是什么…

谷歌给到的185个使用生成式AI的案例

很多公司从利用AI回答问题&#xff0c;进而使用AI进行预测&#xff0c;向使用生成式AI Agent转变。AI Agent的独特之处在于它们可以采取行动以实现特定目标&#xff0c;比如引导购物者找到合适的鞋子&#xff0c;帮助员工寻找合适的健康福利&#xff0c;或在护理人员交接班期间…

python之输入输出

1、输入 Python在控制台输入内容&#xff0c;需要使用input函数。input函数会在控制台等待用户输入&#xff0c;直到用户按下了回车键才算完成输入。 注意&#xff1a;input函数接收的内容为字符串。 str1 input("请输入内容\n") print(str1) print(type(str1))1…

Python酷库之旅-第三方库Pandas(132)

目录 一、用法精讲 591、pandas.DataFrame.plot方法 591-1、语法 591-2、参数 591-3、功能 591-4、返回值 591-5、说明 591-6、用法 591-6-1、数据准备 591-6-2、代码示例 591-6-3、结果输出 592、pandas.DataFrame.plot.area方法 592-1、语法 592-2、参数 592-…

9.28学习笔记

1.ping 网址 2.ssh nscc/l20 3.crtl,打开vscode的setting 4.win 10修改ssh配置文件及其密钥权限为600 - 晴云孤魂 - 博客园 整体来看&#xff1a; 使用transformer作为其主干网络&#xff0c;代替了原先的UNet 在latent space进行训练&#xff0c;通过transformer处理潜…

查缺补漏----该不该考虑不可屏蔽中断

可以看看这个视频&#xff1a; 讨论中断时&#xff0c;该不该考虑不可屏蔽中断&#xff1f;_哔哩哔哩_bilibili 首先要知道一个概念&#xff1a;可屏蔽中断和不可屏蔽中断 可屏蔽中断&#xff1a; 可屏蔽中断是可通过中断屏蔽字来启用或禁用的中断。对于多级中断而言&#…

①EtherCAT转ModbusTCP, EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关

EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关https://item.taobao.com/item.htm?ftt&id822721028899 协议转换通信网关 EtherCAT 转 ModbusTCP GW系列型号 MS-GW15 简介 MS-GW15 是 EtherCAT 和 Modbus TCP 协议转换网关&#xff0c;为用户提供一种 …

map_set的使用

map_set的使用 关联式容器树形结构的关联式容器setset的介绍set的使用 multisetmultiset的介绍multiset的使用 mapmap的介绍map的使用键值对 multimapmultimap的介绍 &#x1f30f;个人博客主页&#xff1a;个人主页 关联式容器 在初阶阶段&#xff0c;我们已经接触过STL中的部…

黑科技外绘神器:一键扩展图像边界

黑科技外绘神器&#xff1a;一键扩展图像边界 Diffusers Image Outpaint✨是一个开源工具&#xff0c;能智能扩展图像边界&#xff0c;创造完美视觉效果&#x1f3de;️。用户可自定义风格&#xff0c;生成高清图像&#x1f929;&#xff0c;应用场景广泛&#xff0c;释放你的…

大模型~合集6

我自己的原文哦~ https://blog.51cto.com/whaosoft/11566566 # 深度模型融合&#xff08;LLM/基础模型/联邦学习/微调等&#xff09; 23年9月国防科大、京东和北理工的论文“Deep Model Fusion: A Survey”。 深度模型融合/合并是一种新兴技术&#xff0c;它将多个深度学习模…

爬虫——爬取小音乐网站

爬虫有几部分功能&#xff1f;&#xff1f;&#xff1f; 1.发请求&#xff0c;获得网页源码 #1.和2是在一步的 发请求成功了之后就能直接获得网页源码 2.解析我们想要的数据 3.按照需求保存 注意&#xff1a;开始爬虫前&#xff0c;需要给其封装 headers {User-…

本地化测试对游戏漏洞修复的影响

本地化测试在游戏开发的质量保证过程中起着至关重要的作用&#xff0c;尤其是在修复bug方面。当游戏为全球市场做准备时&#xff0c;它们通常会被翻译和改编成各种语言和文化背景。这种本地化带来了新的挑战&#xff0c;例如潜在的语言错误、文化误解&#xff0c;甚至是不同地区…

C++ 双端队列(deque)的深入理解

前言&#xff1a; 双端队列deque看起来是一个相当牛的容器&#xff0c;表面看起来将list和vector进行结合起来&#xff0c;形成了一个看起来很完美的容器&#xff0c;但是事实不是这样&#xff0c;要是deque如此完美&#xff0c;数据结构也就没list和vector的事情了&#xff0c…

多系统萎缩患者必看!这些维生素助你对抗病魔

亲爱的朋友们&#xff0c;今天我们来聊聊一个相对陌生但重要的健康话题——多系统萎缩&#xff08;MSA&#xff09;。这是一种罕见的神经系统疾病&#xff0c;影响着患者的自主神经系统、运动系统和平衡功能。面对这样的挑战&#xff0c;科学合理的饮食和营养补充显得尤为重要。…

暴力数据结构——AVL树

1.认识AVL树 AVL树最先发明的⾃平衡⼆叉查找树,AVL可以是⼀颗空树,或者具备下列性质的⼆叉搜索树&#xff1a; • 它的左右⼦树都是AV树&#xff0c;且左右⼦树的⾼度差的绝对值不超过1 • AVL树是⼀颗⾼度平衡搜索⼆叉树&#xff0c; 通过控制⾼度差去控制平衡 AVL树整体结点…

路由交换实验指南

案例 01&#xff1a;部署使用 eNSP 平台实验需求&#xff1a; 安装华为 eNSP 网络模拟平台打开 eNSP 平台&#xff0c;新建拓扑并绘制网络能够成功启动交换机、计算机设备 实验步骤&#xff1a; 安装华为 eNSP 网络模拟平台启动安装程序 配置安装内容 防护墙允许 eNSP 程序的…

IDTL:茶叶病害识别数据集(猫脸码客 第205期)

Identifying Disease in Tea Leaves茶叶病害识别数据集 一、引言 在农业领域&#xff0c;茶叶作为一种重要的经济作物&#xff0c;其生产过程中的病害防治是确保茶叶质量和产量的关键环节。然而&#xff0c;传统的病害识别方法主要依赖于人工观察和经验判断&#xff0c;这不仅…

从零开始实现RPC框架---------项目介绍及环境准备

一&#xff0c;介绍 RPC&#xff08;Remote Procedure Call&#xff09;远程过程调⽤&#xff0c;是⼀种通过⽹络从远程计算机上请求服务&#xff0c;⽽不需要 了解底层⽹络通信细节。RPC可以使⽤多种⽹络协议进⾏通信&#xff0c; 如HTTP、TCP、UDP等&#xff0c; 并且在 TCP/…

匿名方法与Lambda表达式+泛型委托

匿名方法 和委托搭配使用&#xff0c;方便我们快速对委托进行传参&#xff0c;不需要我们定义一个新的函数&#xff0c;直接用delegate关键字代替方法名&#xff0c;后面跟上参数列表与方法体。 格式&#xff1a;delegate(参数列表){方法体} lambda表达式 是匿名方法的升级…

Brave编译指南2024 MacOS篇-环境配置(四)

引言 在上一篇文章中&#xff0c;我们成功获取了Brave浏览器的源代码。现在&#xff0c;我们将进入编译过程的关键阶段&#xff1a;环境配置。正确的环境配置对于成功编译Brave浏览器至关重要&#xff0c;它能确保所有必要的工具和依赖项都已就位&#xff0c;并且版本兼容。 …