IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++到底有多少种魔法来实现println? -> 正文阅读

[C++知识库]C++到底有多少种魔法来实现println?


PrintLn函数实现

想要实现PrintLn,关键在于支持无限个参数的打印函数,所以我大致总结下C++能够如何去实现它!

方式一:用初始化列表实现PrintLn() 【C++11】

版本一:朴素初始化列表版本版本

函数版本:

#include<iostream>
using namespace std;

void PrintLn(std::initializer_list<int> args){
    for(auto arg:args){
        cout<<arg<<", ";
    }
    cout<<std::endl;
}
int main() {
    PrintLn({3,23,2,12});
    return 0;
}

类的构造器版本(可去掉小括号):

#include<iostream>
using namespace std;

class PrintLn {
public:
    PrintLn(std::initializer_list<int> args) {
        for (auto arg:args) {
            cout << arg << ", ";
        }
        cout << std::endl;
    }
};

int main() {
    auto t = PrintLn{3, 23, 2, 12};
    return 0;
}

版本二:加模板参数版本

实际上和上面的基本没太大区别,除了上面确定为了int类型,而加了模板后,可以为任意类型,但是实际上传入的还是只能是同一个类型。所以初始化列表的方式实现Println只能说形似而神不似。

#incldue<iostream>
using namespace std;

template<typename T>
class PrintLn {
public:
    PrintLn(std::initializer_list<T> args) {
        for (auto arg:args) {
            cout << arg << ", ";
        }
        cout << std::endl;
    }
};

int main() {
    auto t = PrintLn<int>{3, 23, 2, 12};
    return 0;
}

方式二:用可变参模板实现 【C++11/17】

如果有了解过C的可变参函数和可变参的宏,那么这个可变参模板与它有些类型,只不过C里面的va_start,va_list,va_arg,va_end这一系列实现可变参数的宏用起来非常麻烦,而且无法确定每个参数的类型,而可变参的模板则带有模板的泛型性质,所以是能确定类型的,甚至由于模板可以传值,后面还可直接传值使用。

以下简单描述可变参模板的使用方式:

  1. typenam... 算C++的一个新的关键字,它可以用来定义一个可变参的模板类型,而这个类型在其他地方定义使用的时候也要在后面带上 ... 表示拆包,否则会报错。
    例如:

    template<typename... T>
    void f(T... t){//TODO 这种类型或变量在任何地方作为参数定义或者传递的时候都需要加上...表示拆包
        f(t...)
    }
    
  2. 在C++17出现fold expression之前,这个拆包过程只能借助另一个模板参数来得到模板参数包里面的内容。

注意以上两点,那么可以开始编写泛型模板,实现可变参数的完全打印过程了。

C++11版本实现

错误实现版本:如果你直接像下面这样进行拆包,那么编译是会报错的,因为拆包过程相当于一个递归的过程,而你这个递归的过程没有一个跳出的条件,比如args如果为0个参数时,继续在往下就无法展开了,所以需要实现一个没有参数的版本让拆包过程停止。

template<typename T,typename... Args>
void PrintLn(T firstArg,Args... args){
    cout<<firstArg<<", ";
    PrintLn(args...);
}
//拆包过程:PrintLn(3,1,3,4)->
// PrintLn(firstArg:3,args(1,3,4));
// PrintLn(firstArg:1,args(3,4));
// PrintLn(firstArg:3,args(4));
// PrintLn(firstArg:4,args(null))
// 由于到了上面的第四行还要继续往下拆包
// 而此时只有0个参数,没有对应的PrintLn版本可以调用,故报错!

以下为正确修改版本:

#include<iostream>
using namespace std;

void PrintLn(){

}
template<typename T,typename... Args>
void PrintLn(T firstArg,Args... args){
    cout<<firstArg<<", ";
    PrintLn(args...);
}

int main() {
    PrintLn(3, 23, 2, 12);
    return 0;

当然也可以控制只剩一个参数时就停止拆包。

#include<iostream>
using namespace std;

template<typename T>
void PrintLn(T arg){
    cout<<arg<<endl;
}
template<typename T,typename... Args>
void PrintLn(T firstArg,Args... args){
    cout<<firstArg<<", ";
    PrintLn(args...);
}

int main() {
    PrintLn(3, 23, 2, 12);
    return 0;
}

C++17版本实现

上面的实现流程实际上在C++17中可以用 if constexpr()+sizeof… 在编译期间来进行流程控制。

首先来讲一讲为什么普通的 if + sizeof… 来实现可变参数的长度控制流程会报错呢?

因为整个模板推断和拆包解包过程是在编译期完成的,而if的控制流程在编译期是完全不清楚的,所以会报错,但是有了if constexpr之后,就能控制编译期的模板拆包过程了!

如上面实现PrintLn,可以直接简化成下面这样:

#include<iostream>
using namespace std;

template<typename T,typename... Args>
void PrintLn(T firstArg,Args... args){
    if constexpr(sizeof...(args)==0){//当参数个数为0个的时候就不继续拆包了
        cout<<firstArg<<endl;
    }else{
        cout<<firstArg<<", ";
        PrintLn(args...);//往下继续拆包
    }
}

int main() {
    PrintLn(3, 23, 2, 12);
    return 0;
}

方式三:可变参模板的fold expression展开 【C++17】

在C++17中,加入了一个fold expression的语法,让可变参数模板可以不通过递归的方式来解包,直接把每个包解开放入一个表达式,然后剩余的包都以该表达式解开,基本的语法如下:

((expression)op...);

expression : 表示希望每个解开的参数所执行的表达式。
op : 你指定的操作符。
... : 一直不断的解包,由于此处放的位置是右边,所以往右边解包,如果放左边则往左边解包。

示例代码:

#include <bits/stdc++.h>
using namespace std;

template<typename... Args>
double sum(Args... args){
    return (args+...);//等价于3+23+1+3.32
}
template<auto... val>//可变的传值的模板参数
constexpr int sum(){
    return (val+...);
}

int main() {
    cout<<sum<3,23,1,32>()<<endl;//传值模板参数不支持浮点类型,所以全用的int类型
    cout<<sum(3,23,1,3.32);
    return 0;
}

简单的利用fold expr实现

  • 基于以上对fold expr的使用,我们来正式实现PrintLn,值得一提的是,这个fold expr的性能肯定是比之前递归解包的性能要好的,因为只是迭代的拓宽而已。

我们可以将拆开的包用 ',' 展开

#include<iostream>
using namespace std;

template<typename... Args>
void PrintLn(Args... args){
    ((cout<<args<<", "),...)<<endl;
}

int main() {
    PrintLn(1,2.3,"LB","hhh");
    return 0;
}

加上流程控制实现

通过更复杂的流程控制把最后一个打印出来的逗号去掉。

通过延申三元运算符,使得运行时能够正确的打印最后一次。

反正我这里编译期只负责文本替换,所以被fold expr展开的表达式并不会有什么要是编译期常量的要求。

这一切都看作简单宏替换即可。

#include<iostream>
using namespace std;

template<typename... Args>
void PrintLn(Args... args){
    int lastIndex = sizeof...(args)-1;//得到传入的参数长度
    int i = 0;
    ((i++==lastIndex?cout<<args<<endl:cout<<args<<", "),...);
}

int main() {
    PrintLn(1,2.3,"LB","hhh");
    return 0;
}

更多fold expr运用…

利用与或表达式展开,然后利用它们的短路性质,实现得到拆包元素的精准打击(获得包里的第几个元素)。

#include<iostream>
using namespace std;

template<typename... Args>
auto GetNth(int n, Args... args) {
    int i = 0;
    using CommonType = common_type_t<Args...>;
    CommonType ret;
    ((i++ == n && (ret = args, true))||...);
    return ret;
}

int main() {
    cout << GetNth(3, 2, 1, 2.3, 32.2);
    return 0;
}
  • 上面为了存储不确定的类型用了common_type_t,这个可以帮助你得到一个公共可用的类型,而这个类型必须是公共可用,比如int了float型可以进行相互转化所以有公共类型,而 char* 和int类型则没有,所以这个GetNth中的元素不能传递 char*类型的同时传递int类型。
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-01-25 10:24:21  更:2022-01-25 10:24:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:09:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码