【C++ 学习 ㉜】- 超详解 C++11 之新的类功能以及在模板中使用可变参数

2023-11-05 18:12

本文主要是介绍【C++ 学习 ㉜】- 超详解 C++11 之新的类功能以及在模板中使用可变参数,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一、新的类功能

二、在模板中使用可变参数

2.1 - 可变参数函数

2.2 - 可变参数模板

2.2.1 - 可变参数函数模板

2.2.2 - 可变参数类模板

【C++ 学习 ㉚】- 超详解 C++11 的右值引用-CSDN博客 


一、新的类功能

  1. 默认的移动构造函数和移动赋值函数

    使用一个类的右值对象去初始化同类对象和为同类对象赋值时,在该类自定义了移动构造函数和移动赋值函数的情况下,会调用该类自定义的移动构造函数和移动赋值函数。

    而在没有自定义移动构造函数和移动赋值函数的情况下,如果没有自定义拷贝构造函数、赋值运算符重载以及析构函数,那么编译器会自动生成默认的移动构造函数和移动赋值函数

    默认的移动构造函数和移动赋值函数,对于内置类型的成员变量,会执行逐成员按字节拷贝;对于自定义类型的成员变量,如果该成员自定义了移动构造函数和移动赋值函数,那么调用对应的移动构造函数和移动赋值函数,否则调用拷贝构造函数和赋值运算符重载

    #include "string.h"
    ​
    class Person
    {
    public:Person(const char* name = "", int age = 0): _name(name), _age(age){ }
    private:yzz::string _name;int _age;
    };
    ​
    int main()
    {Person p1("张三", 18);Person p2 = std::move(p1);// string(string&& rr)
    ​Person p3("李四", 19);Person p4;p4 = std::move(p3);// string& operator=(string&& rr)return 0;
    }
  2. 强制生成默认函数的关键字 default

    class Person
    {
    public:Person(const char* name = "", int age = 0): _name(name), _age(age){ }
    ​Person(const Person& p): _name(p._name), _age(p._age){ }// 自定义了拷贝构造函数后,编译器就不会生成默认的移动构造函数和移动赋值函数,// 此时就需要使用 default 关键字显示指定默认的移动构造函数和移动赋值函数的生成Person(Person&& p) = default;Person& operator=(Person&& p) = default;
    private:yzz::string _name;int _age;
    };
    ​
    int main()
    {Person p1("张三", 18);Person p2 = std::move(p1);// string(string&& rr)Person p3("李四", 19);Person p4;p4 = std::move(p3);// string& operator=(string&& rr)return 0;
    }


二、在模板中使用可变参数

2.1 - 可变参数函数

C/C++ 一直都支持为函数设置可变参数,最典型的代表就是 printf() 函数,它的语法格式为:

int printf(const char* format, ...);

其中 ... 表示的是可变参数(参数包),即 printf() 函数可以接受任意个参数,且各个参数的类型可以不同,例如

printf("\n");
printf("%d\n", 10);
printf("%d %c\n", 10, 'a');
printf("%d %c %lf\n", 10, 'a', 3.14);

下面的程序中,自定义了一个简单的可变参数函数:

#include <cstdarg>
#include <iostream>
using namespace std;
​
void func(int count, ...)
{va_list args_ptr;va_start(args_ptr, count);for (int i = 0; i < count; ++i){int arg = va_arg(args_ptr, int);cout << arg << endl;}va_end(args_ptr);
}
​
int main()
{// 可变参数有 4 个,分别为 10、20、30、40func(4, 10, 20, 30, 40);return 0;
}

func() 函数中有 2 个参数,一个是 count,另一个就是 ... 可变参数,我们很容易在函数内部使用 count 参数,但想要使用参数包中的参数,需要借助 <cstdarg> 头文件中的 va_list 类型以及 va_start、va_arg、va_end 这三个带参数的宏

其中 va_list 可以简单地视为 char*,即

typedef char* va_list;

而要理解 va_start、va_arg、va_end 是如何实现的,需要先理解参数是如何传递给函数的

函数的数据是放在栈中的,给一个函数传递参数的过程就是将函数的参数从右往左逐次压栈。例如:给 func(int i, char c, double d) 函数传递参数的过程就是将 d、c、i 逐次压到函数的栈中,并且由于栈是从高地址向低地址扩展的,所以 d 的地址最高,i 的地址最低

理解了给一个函数传递参数的过程,就可以理解 va_start、va_arg、va_end 是如何实现的

// 让 ap 指向第一个可变参数
#define my_va_start(ap, x) ( ap = (my_va_list)(&x + 1) )
​
// 让 ap 指向下一个可变参数,并将 ap 指向的上一个可变参数提取出来
#define my_va_arg(ap, t) ( *(t*)((ap += sizeof(t)) - sizeof(t)) )
​
// 销毁 ap
#define my_va_end(ap) ( ap = (my_va_list)0 )

2.2 - 可变参数模板

C++11 之前,函数模板和类模板只能设定固定数量的模板参数;C++11 对模板的功能进行了扩展,允许模板中包含任意数量的模板参数,这样的模板又称为可变参数模板

2.2.1 - 可变参数函数模板

如下定义了一个可变参数的函数模板:

template<class... Types>
void ShowList(Types... args)
{// 函数体
}

在模板参数中,class 或者 typname 后面跟 ... 就表明 Types 是一个可变模板参数,它可以接受多种数据类型,又称模板参数包

在函数参数中,args 的参数类型用 Types... 表示,表明 args 可以接受任意个参数,又称函数参数包

使用可变参数模板的难点在于,如何在模板函数内部"解开"参数包(使用包内数据)

  1. 【递归方式解包】

    #include <iostream>
    using namespace std;
    ​
    // 递归结束的出口
    void ShowList()
    {cout << endl;
    }
    ​
    template<class T, class... Types>
    void ShowList(T val, Types... args)
    {cout << val << " ";ShowList(args...);
    }
    ​
    int main()
    {ShowList();ShowList(10);ShowList(10, 'a');ShowList(10, 'a', 3.14);ShowList(10, 'a', 3.14, "hello");return 0;
    }

    注意:以递归方式解包,一定要设置递归结束的出口。在上例中,无形参的 ShowList() 函数就是递归结束的出口

  2. 【借助列表初始化和逗号表达式】

    #include <iostream>
    using namespace std;
    ​
    template<class T>
    void display(T val)
    {cout << val << " ";
    }
    ​
    template<class...Types>
    void ShowList(Types...args)
    {cout << sizeof...(args) << " : ";int arr[] = { (display(args), 0)... };cout << endl;
    }
    ​
    int main()
    {ShowList(10, 'a', 3.14);return 0;
    }

    我们使用了 { } 列表初始化的方式对数组 arr 进行了初始化,(display(args), 0)... 会依次展开为 (display(10), 0)(display(‘a'), 0)(display(3.14), 0)

    每个元素都是一个逗号表达式,以 (display(10), 0) 为例,会先计算 display(10),然后再将 0 作为整个表达式的值返回给数组,因此 arr 数组中最终存储的都是 0,arr 数组纯粹是为了将参数包展开,没有发挥其他作用

STL 容器中的 emplace 相关接口

list::emplace_back - C++ Reference (cplusplus.com)。

vector::emplace_back - C++ Reference (cplusplus.com)。

template<class... Args>
void emplace_back(Args&&... args);

emplace_back 相较于 push_back,优势在哪呢

#include <list>
#include "string.h"
#include <iostream>
using namespace std;
​
int main()
{list< pair<int, char> > lt1;lt1.push_back({ 10, 'a' });lt1.push_back(make_pair(20, 'b'));// emplace 支持可变参数,拿到构造 pair 对象的参数后可以自己去创建对象,// 这里只有用法上的区别lt1.emplace_back(30, 'c');lt1.emplace_back(make_pair(40, 'b'));
​list< pair<int, yzz::string> >lt2;lt2.push_back({ 10, "hello" });lt2.push_back(make_pair(20, "world"));// string(string&& rr)// string(string && rr)cout << "-------------------------" << endl;// push_back 是先构造,再移动构造;而 push_back 是直接构造了lt2.emplace_back(30, "你好");lt2.emplace_back(make_pair(40, "世界"));return 0;
}

2.2.2 - 可变参数类模板

C++11 中,类模板中的模板参数也可以是一个可变参数。C++11 提供的 tuple 元组类就是一个典型的可变参数类模板,它的定义如下:

template<class... Types> class tuple;

如下代码展示了一个支持可变参数的类模板:

#include <iostream>
using namespace std;
​
// 声明可变参数类模板
template<class... Values> class demo;
​
// 继承式递归的出口
template<> class demo<> {};
​
// 以 "继承 + 递归" 的方式解包
template<class Head, class... Tail>
class demo<Head, Tail...> : private demo<Tail...>
{
public:demo(Head v, Tail... vtail): _head(v), demo<Tail...>(vtail...){display_head();}
​void display_head() const{cout << _head << endl;}
protected:Head _head;
};
​
int main()
{demo<int, char, double> d(10, 'a', 3.14);// 3.14// a// 10return 0;
}

程序中,继承关系可以表述为:

demo<>
↓
demo<double>
↓
demo<char, double>
↓   
demo<int, char, double>

可变参数类模板还有其他的解包方法,这里不再一一赘述。

这篇关于【C++ 学习 ㉜】- 超详解 C++11 之新的类功能以及在模板中使用可变参数的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/351623

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Python使用国内镜像加速pip安装的方法讲解

《Python使用国内镜像加速pip安装的方法讲解》在Python开发中,pip是一个非常重要的工具,用于安装和管理Python的第三方库,然而,在国内使用pip安装依赖时,往往会因为网络问题而导致速... 目录一、pip 工具简介1. 什么是 pip?2. 什么是 -i 参数?二、国内镜像源的选择三、如何

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

Linux使用nload监控网络流量的方法

《Linux使用nload监控网络流量的方法》Linux中的nload命令是一个用于实时监控网络流量的工具,它提供了传入和传出流量的可视化表示,帮助用户一目了然地了解网络活动,本文给大家介绍了Linu... 目录简介安装示例用法基础用法指定网络接口限制显示特定流量类型指定刷新率设置流量速率的显示单位监控多个

Debezium 与 Apache Kafka 的集成方式步骤详解

《Debezium与ApacheKafka的集成方式步骤详解》本文详细介绍了如何将Debezium与ApacheKafka集成,包括集成概述、步骤、注意事项等,通过KafkaConnect,D... 目录一、集成概述二、集成步骤1. 准备 Kafka 环境2. 配置 Kafka Connect3. 安装 D

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

python使用fastapi实现多语言国际化的操作指南

《python使用fastapi实现多语言国际化的操作指南》本文介绍了使用Python和FastAPI实现多语言国际化的操作指南,包括多语言架构技术栈、翻译管理、前端本地化、语言切换机制以及常见陷阱和... 目录多语言国际化实现指南项目多语言架构技术栈目录结构翻译工作流1. 翻译数据存储2. 翻译生成脚本