【C++入门到精通】新的类功能 | 可变参数模板 C++11 [ C++入门 ]

在这里插入图片描述

阅读导航

  • 引言
  • 一、新的类功能
    • 1. 默认成员函数
    • 2. 类成员变量初始化
    • 3. 强制生成默认函数的关键字default
    • 4. 禁止生成默认函数的关键字delete
    • 5. override 和 final
      • (1)override
      • (2)final
  • 二、可变参数模板
    • 递归函数方式展开参数包
    • 逗号表达式展开参数包
  • 温馨提示

引言

随着C++11标准的发布,引入了许多令人振奋的新特性,其中包括强大的类功能和可变参数模板。这些新增的功能为C++编程带来了更加灵活和高效的可能性,极大地丰富了语言的表达能力和应用范围。本文将重点探讨C++11中这些新特性的优势和用法,帮助读者更好地理解和运用现代C++编程的最新技术。😍

一、新的类功能

1. 默认成员函数

在C++11标准中,引入了两个重要的默认成员函数:移动构造函数和移动赋值运算符重载。这两个功能的引入极大地提升了C++语言的性能和效率。

⭕移动构造函数

移动构造函数允许对象通过移动资源而不是复制资源来进行构造。在传统的复制构造函数中,对象的构造是通过逐个复制成员变量来完成的,这可能导致资源的不必要拷贝和分配,从而降低程序的性能。而移动构造函数则允许对象直接获取资源的所有权,而无需进行复制,从而提高了程序的效率。移动构造函数通过使用右值引用来实现,可以显著减少资源的拷贝和内存分配,特别适用于管理大量数据的类对象。

⭕移动赋值运算符重载

移动赋值运算符重载与移动构造函数类似,它也通过移动资源而不是复制资源来实现对象之间的赋值操作。传统的赋值运算符重载会对已有的资源进行释放和重新分配,这样的操作可能会消耗大量的时间和系统资源。而移动赋值运算符重载则通过将资源的所有权转移给目标对象,避免了资源的拷贝和分配,提高了程序的性能和效率。

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

  • 如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值

以下代码在vs2013中不能体现,在vs2019下才能演示体现上面的特性。

class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}/*Person(const Person& p):_name(p._name),_age(p._age){}*//*Person& operator=(const Person& p){if(this != &p){_name = p._name;_age = p._age;}return *this;}*//*~Person(){}*/
private:std::string _name;int _age;
};int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);Person s4;s4 = std::move(s2);return 0;
}

2. 类成员变量初始化

在C++11标准中,新增了一种方便的类成员变量初始化方式,即在类定义中直接对成员变量进行初始化。这种初始化方式使得在定义类的同时就可以为成员变量赋予初始值,而不需要依赖于构造函数来完成初始化操作

具体来说,我们可以在类定义的同时为成员变量提供默认值,例如:

class MyClass {
public:int x = 0;  // 直接对成员变量进行初始化double y = 3.14;std::string name = "C++";
};

在上面的示例中,成员变量x、y和name都在类定义中直接进行了初始化赋值,这样在创建对象时,如果没有显式地指定初始值,那么这些成员变量将会自动以指定的默认值进行初始化。

这种类成员变量的直接初始化方式简化了代码,使得类的定义更加清晰和简洁。同时,它也提供了对类成员变量进行默认值设置的便利途径,使得开发者可以更加方便地管理和维护类的成员变量初始化状态。

3. 强制生成默认函数的关键字default

在C++11标准中,引入了关键字"default",它可以用来显式地指示编译器生成默认的特殊成员函数,例如默认构造函数、析构函数、拷贝构造函数、移动构造函数和赋值操作符等。使用"default"关键字可以方便地告诉编译器去生成这些函数,而不需要手动编写它们的定义。

例如,假设我们有一个类需要生成默认的构造函数和析构函数,我们可以这样使用"default"关键字:

class MyDefaultClass {
public:// 显式指示编译器生成默认构造函数和析构函数MyDefaultClass() = default;~MyDefaultClass() = default;
};

在上面的示例中,我们使用"default"关键字来告诉编译器生成默认的构造函数和析构函数。这样做的好处在于,我们无需手动编写这些默认函数的定义,而是交由编译器自动生成,从而简化了代码并提高了代码的可读性。

"default"关键字的另一个重要应用是在移动构造函数和移动赋值操作符中。我们可以使用"default"关键字来告诉编译器生成默认的移动构造函数和移动赋值操作符,例如下面的代码使用"default"关键字来告诉编译器生成默认的移动构造函数

#include <iostream>
#include <utility>class Person {
public:Person(const char* name = "", int age = 0):_name(name), _age(age) {}Person(const Person& p):_name(p._name), _age(p._age) {}// 使用默认的移动构造函数Person(Person&& p) = default;private:std::string _name;int _age;
};int main() {Person s1; // 调用默认构造函数Person s2 = s1; // 调用拷贝构造函数Person s3 = std::move(s1); // 调用移动构造函数return 0;
}

在这段代码中,我们定义了一个名为Person的类,包括默认构造函数、拷贝构造函数和移动构造函数。在main函数中,我们创建了三个Person对象s1、s2和s3,并展示了它们在不同情况下调用构造函数的过程。

4. 禁止生成默认函数的关键字delete

在C++11标准中,可以使用关键字"delete"来显式地删除默认生成的特殊成员函数,例如默认构造函数、拷贝构造函数、移动构造函数和赋值操作符等。通过使用"delete"关键字,可以阻止特定的函数被默认生成或者调用。

下面是一个简单的示例,展示了如何使用"delete"关键字阻止默认构造函数的生成:

class NoDefault {
public:// 删除默认构造函数NoDefault() = delete;
};int main() {NoDefault nd; // 这里会导致编译错误,因为默认构造函数已被删除return 0;
}

在上述示例中,类"NoDefault"的默认构造函数被使用"= delete"语法删除了,因此在main函数中尝试创建"NoDefault"类的实例将会导致编译错误

类似地,你也可以使用"delete"关键字来删除其他默认生成的特殊成员函数,以满足特定的设计需求。这种方式通常用于禁用某些不希望被调用的函数,或者确保特定的行为不发生。

5. override 和 final

overridefinal 都是 C++11 中引入的关键字,用于标识和修饰虚函数的行为。

(1)override

override 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错

示例:

class Base {
public:virtual void myFunction() {// 基类虚函数的默认实现}
};class Derived : public Base {
public:void myFunction() override {// 派生类重写基类虚函数的实现}
};

在上述示例中,派生类 Derived 使用 override 关键字表明它重写了基类 Base 的虚函数 myFunction()。如果在派生类中意外地使用了错误的函数签名(参数列表或返回类型不匹配),编译器会发出错误提示。

(2)final

final 用于标识类、成员函数或虚函数,表示它们被声明为最终版本,禁止在派生类中进一步继承或重写。

示例:

class Base final {
public:virtual void myFunction() {// 基类虚函数的默认实现}
};class Derived : public Base {
public:void myFunction() /* override 不可使用 */ {// 派生类重写基类虚函数的实现}
};

在上述示例中,基类 Base 使用 final 关键字标识它是最终类,不允许被继续派生。同时,派生类 Derived 中的 myFunction() 不能使用 override 关键字进行标识,因为基类已经被声明为最终类

二、可变参数模板

C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。现阶段呢,我们掌握一些基础的可变参数模板特性就够我们用了,所以这里我们点到为止,以后大家如果有需要,再可以深入学习。

下面就是一个基本可变参数的函数模板

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。

递归函数方式展开参数包

// 递归终止函数
template <class T>
void ShowList(const T& t)
{cout << t << endl;
}// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{cout << value <<" ";ShowList(args...);
}
int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}

逗号表达式展开参数包

这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式。

expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…}将会展开成((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int arr[sizeof…(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。

template <class T>
void PrintArg(T t)
{cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{int arr[] = { (PrintArg(args), 0)... };cout << endl;
}
int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}

温馨提示

感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

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

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

相关文章

读像火箭科学家一样思考笔记03_第一性原理(上)

1. 思维的两种障碍 1.1. 为什么知识会成为一种缺陷而非一种美德 1.1.1. 知识是一种美德 1.1.2. 知识同样的特质也会把它变成一种缺点 1.1.3. 知识确实是个好东西&#xff0c;但知识的作用应该是给人们提供信息&#xff0c;而不是起约束作用 1.1.4. 知识应该启发智慧&#…

Git精讲

Git基本操作 创建Git本地仓库 git initgit clone 配置Git git config [--global] user.name "Your Name" git config [--global] user.email "emailexample.com"–global是一个可选项。如果使用了该选项&#xff0c;表示这台机器上所有的Git仓库都会使…

6 Redis的慢查询配置

1、redis的命令执行流程 redis的慢查询只针对步骤3 默认情况下&#xff0c;慢查询的阈值是10ms 在配置文件中进行配置 //这个参数的单位为微秒 //如果将这个值设置为负数&#xff0c;则会禁用慢日志功能 //如果将其设置为0&#xff0c;则会强制记录每个命令 slowlog-log-slow…

【C++历练之路】list的重要接口||底层逻辑的三个封装以及模拟实现

W...Y的主页 &#x1f60a; 代码仓库分享&#x1f495; &#x1f354;前言&#xff1a; 在C的世界中&#xff0c;有一种数据结构&#xff0c;它不仅像一个神奇的瑰宝匣&#xff0c;还像一位能够在数据的海洋中航行的智慧舵手。这就是C中的list&#xff0c;一个引人入胜的工具…

立仪科技光谱共焦在半导体领域的应用

半导体技术在近年来以极快的速度发展&#xff0c;对质量和精密度的要求也不断提升。在这样的背景下&#xff0c;用于材料与设备研究的先进检测技术如光谱共焦成像将自然地找到一席之地。下面我们将详细探讨一下光谱共焦在半导体领域中的应用。 光谱共焦技术&#xff0c;通过在细…

【DevOps】Git 图文详解(四):Git 使用入门

Git 图文详解&#xff08;四&#xff09;&#xff1a;Git 使用入门 1.创建仓库2.暂存区 add3.提交 commit 记录4.Git 的 “指针” 引用5.提交的唯一标识 id&#xff0c;HEAD~n 是什么意思&#xff1f;6.比较 diff 1.创建仓库 创建本地仓库的方法有两种&#xff1a; 一种是创建…

MongoDB之索引和聚合

文章目录 一、索引1、说明2、原理3、相关操作3.1、创建索引3.2、查看集合索引3.3、查看集合索引大小3.4、删除集合所有索引&#xff08;不包含_id索引&#xff09;3.5、删除集合指定索引 4、复合索引 二、聚合1、说明2、使用 总结 一、索引 1、说明 索引通常能够极大的提高查…

CSS的选择器(一篇文章齐全)

目录 Day26&#xff1a;CSS的选择器 1、CSS的引入方式 2、CSS的选择器 2.1 基本选择器​编辑 2.2 组合选择器 2.3 属性选择器 2.4 伪类选择器 2.5 样式继承 2.6 选择器优先级 3、CSS的属性操作 3.1 文本属性 3.2 背景属性 3.3 边框属性 3.4 列表属性 3.5 dispal…

Hive调优

1.参数配置优化 设定Hive参数有三种方式&#xff1a; &#xff08;1&#xff09;配置Hive文件 当修改配置Hive文件的设定后&#xff0c;对本机启动的所有Hive进程都有效&#xff0c;因此配置是全局性的。 一般地&#xff0c;Hive的配置文件包括两部分&#xff1a; a&#xff…

Node.js之TCP(net)

Hi I’m Shendi Node.js之TCP&#xff08;net&#xff09; 最近使用Nodejs编写程序&#xff0c;需要用到自己编写的分布式工具&#xff0c;于是需要将Java版的用NodeJs重新写一遍&#xff0c;需要使用到TCP通信&#xff0c;于是在这里记录下Node.js TCP 的使用方法 依赖 需要使…

【面试经典150 | 算术平方根】

文章目录 写在前面Tag题目来源解题思路方法一&#xff1a;数学表达式方法二&#xff1a;二分法 其他语言python3 写在最后 写在前面 本专栏专注于分析与讲解【面试经典150】算法&#xff0c;两到三天更新一篇文章&#xff0c;欢迎催更…… 专栏内容以分析题目为主&#xff0c;并…

Asp.net MVC Api项目搭建

整个解决方案按照分层思想来划分不同功能模块&#xff0c;以提供User服务的Api为需求&#xff0c;各个层次的具体实现如下所示&#xff1a; 1、新建数据库User表 数据库使用SQLExpress版本&#xff0c;表的定义如下所示&#xff1a; CREATE TABLE [dbo].[User] ([Id] …

YOLOv8改进 | 2023 | InnerIoU、InnerSIoU、InnerWIoU、FoucsIoU等损失函数

论文地址&#xff1a;官方Inner-IoU论文地址点击即可跳转 官方代码地址&#xff1a;官方代码地址-官方只放出了两种结合方式CIoU、SIoU 本位改进地址&#xff1a; 文末提供完整代码块-包括InnerEIoU、InnerCIoU、InnerDIoU等七种结合方式和其Focus变种 一、本文介绍 本文给…

手写消息队列(基于RabbitMQ)

一、什么是消息队列&#xff1f; 提到消息队列是否唤醒了你脑海深处的记忆&#xff1f;回看前面的这篇文章&#xff1a;《Java 多线程系列Ⅳ&#xff08;单例模式阻塞式队列定时器线程池&#xff09;》&#xff0c;其中我们在介绍阻塞队列时说过&#xff0c;阻塞队列最大的用途…

PWM实验

PWM相关概念 PWM:脉冲宽度调制定时器 脉冲&#xff1a;方波信号&#xff0c;高低电平变化产生方波 周期&#xff1a;高低电平变化所需要时间 频率&#xff1a;1s钟可以产生方波个数 占空比&#xff1a;在一个方波内&#xff0c;高电平占用的百分比 宽度调制&#xff1a;占…

开发知识点-uniapp微信小程序-开发指南

uniapp Vue的原型链生命周期函数onLoaduni.chooseLocationgetCurrentPages美团外卖微信小程序开发uniapp-美团外卖微信小程序开发P1 成果展示P2外卖小程序后端&#xff0c;学习给小程序写http接口P3 主界面配置P4 首页组件拆分P13 外卖列表布局筛选组件商家 布局测试数据创建样…

莹莹API管理系统源码附带两套模板

这是一个API后台管理系统的源码&#xff0c;可以自定义添加接口&#xff0c;并自带两个模板。 环境要求 PHP版本要求高于5.6且低于8.0&#xff0c;已测试通过的版本为7.4。 需要安装PHPSG11加密扩展。 已测试&#xff1a;宝塔/主机亲测成功搭建&#xff01; 安装说明 &am…

Flutter 中数据存储的四种方式

在 Flutter 中&#xff0c;存储是指用于本地和远程存储和管理数据的机制。以下是 Flutter 中不同存储选项的概述和示例。 Shared Preferences&#xff08;本地键值存储&#xff09; Shared Preferences 是一种在本地存储少量数据&#xff08;例如用户首选项或设置&#xff09…

C/C++统计数 2021年12月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C统计数 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、程序说明 五、运行结果 六、考点分析 C/C统计数 2021年12月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 给定一个数的序列S&#xff0c;以及一个区间[L, R], 求序列…

基于Vue+SpringBoot的大病保险管理系统 开源项目

项目编号&#xff1a; S 031 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S031&#xff0c;文末获取源码。} 项目编号&#xff1a;S031&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统配置维护2.2 系统参保管理2.3 大…