图形学推荐
  • 【十一】MySQL事务和字符集
    【十一】MySQL事务和字符集
    当多个用户访问同一数据时,一个用户在更改数据的过程中可能有其它用户同时发起更改请求,为保证数据的一致性状态,MySQL引入了事务。
  • 【十】MySQL存储过程和触发器
    【十】MySQL存储过程和触发器
    存储过程是在数据库中定义一些SQL语句的集合,可以直接调用这些存储过程来执行已经定义好的SQL语句。避免了开发人员重复编写相同SQL语句的问题。
  • 【九】MySQL视图、索引
    【九】MySQL视图、索引
    视图在数据库中的作用类似于窗户,用户可以通过这个窗口看到只对自己有用的数据。既保障了数据的安全性,又大大提高了查询效率。
  • 【八】MySQL操作表中数据
    【八】MySQL操作表中数据
    MySQL提供了功能丰富的数据库管理语句,包括向数据库中插入数据的INSERT语句,更新数据的UPDATE语句,以及当数据不再使用时,删除数据的DELETE语句。
  • 【七】MySQL约束、函数和运算符
    【七】MySQL约束、函数和运算符
    约束是一种限制,它通过限制表中的数据,来确保数据的完整性和唯一性。使用约束来限定表中的数据是很有必要的。
  • 【六】MySQL数据表的基本操作
    【六】MySQL数据表的基本操作
    数据表是数据库的重要组成部分,每一个数据库都是由若干个数据表组成的。换句话说,没有数据表就无法在数据库中存放数据。
  • 【五】MySQL数据类型和存储引擎
    【五】MySQL数据类型和存储引擎
    数据表由多个字段组成,每个字段在进行数据定义的时候都要确定不同的数据类型。向每个字段插入的数据内容决定了该字段的数据类型。MySQL提供了丰富的数据类型,根据实际需求,用户可以选择不同的数据类型。
  • 【四】数据库设计
    【四】数据库设计
    数据库设计是根据用户的需求,在某一具体的数据库管理系统上,设计数据库结构和建立数据库的过程。

现代C++实现多种print

6425

学习C++的朋友会遇到这样的问题,有char,int,double等对象,我们想把它们打印出来看看,初学者会通过cout或者传统C语言的printf函数来打印这些对象。

例如:

int i = 1;
char c = 'f';
double d = 3.14;

//cout
cout << i << endl;
cout << c << endl;
cout << d << endl;

//printf
printf("%d\n%c\n%f", i, c, d);


传统C中的printf 函数,虽然也能达成不定个数的形参的调用,但其并非类别安全,写输出格式也不方便,而且支持的是基础类型。使用cout缺点在于代码量会比较多,不好看。所以,能不能很简单地打印出每一个元素呢?


Print Version1

幸运的是,有更好的解决方案,那就是使用C++11引入的variadic template,先来看看第一个版本的print,并介绍variadic template,代码如下:

#include <iostream>
#include <bitset>

using namespace std;

// version1
void print1() {};

template <typename T, typename... Types>
void print1(const T& firstArg, const Types&... args)
{
    cout << firstArg << endl;
    print1(args...);
}

int main()
{
    print1(7.5, "hello", bitset<16>(377), 42);
    return 0;
}


运行结果:

7.5
hello
0000000101111001
42


Variadic Template: 是指数量不定,类型不定的模板,如上所示的print函数,可以看到接受了不同类型的参数,调用的函数就是拥有Variadic Template的函数,print(7.5, "hello", bitset<16>(377), 42)运行的时候,首先会7.5作为firstArg,剩余部分就是一包,然后在函数内部,继续递归调用print函数,然后把"hello"作为firstArg, 其余的作为一包,一直递归直到一包中没有数据,调用边界条件的print(空函数)结束。

函数的...表示一个包,可以看到,用在三个地方,

第一个地方是模板参数typename... ,这代表模板参数包。

第二个就是函数参数类型包(Type&...), 指代函数参数类型包。

第三个就是函数参数包args...,指的是函数参数包。

 另外,还可以使用sizeof...(args)得到包的长度。

总的来说,上面是一种递归解决方案,依次展开参数包,然后进行操作。


Print Version2

你可能觉得上述边界条件的print函数有些多余,比version1更简洁的就是下面的version2:

template < typename T , typename ... Types>
void print2 (const T& firstArg , const Types&... args)
{
    cout << firstArg << endl;
    if constexpr ( sizeof ...(args) > 0) print2 (args...) ;
}


上述函数能够实现version1一样的功能,通过判断args的长度来选择是否结束递归,constexpr可以确保在编译期间去创建空的print边界条件以及print函数。


Print Version3

除了递归,还有一种通过逗号表达式和初始化列表的方式来解开参数包。

template < typename T , typename ... Types>
void print3 (const T& firstArg , const Types&... args)
{
    cout << firstArg << endl ;
    initializer_list <T> { ( [&args] {cout << args << endl;}(), firstArg )...};
}


其中的[&args] {cout << args << endl;}()是构建了一个lambda表达式,并直接运行,没有]{之间省略了(),所谓逗号表达式展开是指initializer_list会将( [&args] {cout << args << endl;}(), firstArg )...展开为([&args1] {cout << args1 << endl;}(), firstArg),.... ,([&argsN] {cout << argsN << endl;}(), firstArg),内部的lambda表达式只会生成临时对象,所以最后的initializer_list变为了N个firstArg,也就是类型为T,所以initializer_list后面才会接上<T>。当然也可以将initializer_list打印出来看看:

template < typename T , typename ... Types>
void prin3_test (const T& firstArg , const Types&... args)
{
    cout << firstArg << endl ;
    auto i_l = initializer_list <T> { ( [&args] {cout << args << endl;}(), firstArg )...};
    for (auto i : i_l)
        cout << i << endl;
}


另外, 逗号表达式可以接上多个,不限于一个:

initializer_list <T> {
            ( [&args] {cout << args << endl;}(), [&args] {cout << args << endl;}(), firstArg)...};


Print Version4

知道上面的initializer_list的解包方式, 还可以只使用一行实现print:

template <typename ... Types>
void print4 (const Types&... args)
{
    initializer_list <int> { ([&args] {cout << args << endl;}(), 0)...};
}


关键在于直接传递参数包 , 第一个参数不需要分开 , 如此就可以达到一行实现print的功能.


容器的Print

上述的print只能针对那些基础类型以及重构了<<操作符的自定义对象, 对于STL中的容器, 则需要自己重载操作符, 下面给出vector的重载操作符函数(当然容器内部的对象也需要重载<<):

template <typename T>
ostream& operator << (ostream& os, const vector<T>& vec){
    os << "{ ";
    for (auto& v : vec)
        os << v << ' ';
    os << "}";
    return os;
}


重载后, 也可以使用上述的print函数了, 除了tuple容器以外, 其他容器的重载操作符与上述类似, 有些许差异.


tuple容器的print

tuple是C++11提出来的, 内部实现使用的是variadic template, 所以需要特别处理. 下面给出tuple一种基于递归继承的简洁实现:

template <typename ... Values> class mytuple1; //前向申明
template <> class mytuple1<> {}; //递归终止类

template <typename Head, typename ... Tail>
class mytuple<Head, Tail...> : private mytuple1<Tail ...> //递归继承
{
    using inherited = mytuple1<Tail...>;
public:
    mytuple1() {}
    mytuple1(Head v, Tail... vtail) : m_head(v), inherited(vtail...) {}
    Head head() {return m_head;}
    inherited& tail() {return *this;}
protected:
    Head m_head;
};
mytuple1<int, float, string> t(41, 6.3, "nico");
print1(t1.head(), t1.tail().head(), t1.tail().tail().head());

上述继承关系可以表示为如下结构:

tuple还有一种递归组合的实现方式, 也列出来, 有兴趣的朋友也可以看看:

template <typename ... Values> class mytuple2;
template <> class mytuple2<> {};

template <typename Head, typename ... Tail>
class mytuple2<Head, Tail...>
{
    using composited = mytuple2<Tail...>;
public:
    mytuple2() {}
    mytuple2(Head v, Tail... vtail) : m_head(v), m_tail(vtail...) {}
    Head head() {return m_head;}
    composited& tail() {return m_tail;}
protected:
    Head m_head;
    composited m_tail;
};


现在来重载tuple容器的操作符, 代码如下:

template <int IDX, int MAX, typename... Args>
struct PRINT_TUPLE {
    static void print (ostream& os, const tuple<Args...>& t){
        os << get<IDX>(t) << (IDX + 1 == MAX ? "": ",");
        PRINT_TUPLE<IDX+1, MAX, Args...>::print(os, t);
    }
};

template <int MAX, typename... Args>
struct PRINT_TUPLE<MAX, MAX, Args...> {
    static void print (ostream& os, const tuple<Args...>& t){

    }
};

template <typename ... Args>
ostream& operator << (ostream& os, const tuple<Args...>& t) {
    os << "[";
    PRINT_TUPLE<0, sizeof...(Args), Args...>::print(os, t);
    return os << "]";
}


一个技巧是通过sizeof...求出参数包的长度, 然后从建立一个索引, 依次调用get函数打印元素, 直到索引等于包的长度调用递归结束函数, 其中PRINT_TUPLE类中的是否打印逗号也是一样的道理.


结语

最后附上所有代码, 以供试玩, 建议在C++17环境运行, if constexpr是C++17引入的新功能.

#include <iostream>
#include <bitset>
#include <string>
#include <vector>
#include <tuple>

using namespace std;

// version1
void print1() {};

template <typename T, typename... Types>
void print1(const T& firstArg, const Types&... args)
{
    cout << firstArg << endl;
    print1(args...);
}

// version2
template < typename T , typename ... Types>
void print2 (const T& firstArg , const Types&... args)
{
    cout << firstArg << endl;
    if constexpr ( sizeof ...(args) > 0) print2 (args...) ;
}

// version3
template < typename T , typename ... Types>
void print3 (const T& firstArg , const Types&... args)
{
    cout << firstArg << endl ;
    initializer_list <T> {
        ( [&args] {cout << args << endl;}(), firstArg)...};
}

// version4
template <typename ... Types>
void print4 (const Types&... args)
{
    initializer_list <int> { ([&args] {cout << args << endl;}(), 0)...};
}

template < typename T , typename ... Types>
void print3_test1 (const T& firstArg , const Types&... args)
{
    cout << firstArg << endl ;
    auto i_l = initializer_list <T> {
            ( [&args] {cout << args << endl;}(), firstArg)...};
    for (auto i : i_l)
        cout << i << endl;
}

template < typename T , typename ... Types>
void print3_test2 (const T& firstArg , const Types&... args)
{
    cout << firstArg << endl ;
    auto i_l = initializer_list <T> {
            ( [&args] {cout << args << endl;}(), [&args] {cout << args << endl;}(), firstArg)...};
    for (auto i : i_l)
        cout << i << endl;
}

template <typename T>
ostream& operator << (ostream& os, const vector<T>& vec){
    os << "{ ";
    for (auto& v : vec)
        os << v << ' ';
    os << "}";
    return os;
}

template <typename ... Values> class mytuple1;
template <> class mytuple1<> {};

template <typename Head, typename ... Tail>
class mytuple1<Head, Tail...> : private mytuple1<Tail ...>
{
    using inherited = mytuple1<Tail...>;
public:
    mytuple1() {}
    mytuple1(Head v, Tail... vtail) : m_head(v), inherited(vtail...) {}
    Head head() {return m_head;}
    inherited& tail() {return *this;}
protected:
    Head m_head;
};

template <typename ... Values> class mytuple2;
template <> class mytuple2<> {};

template <typename Head, typename ... Tail>
class mytuple2<Head, Tail...>
{
    using composited = mytuple2<Tail...>;
public:
    mytuple2() {}
    mytuple2(Head v, Tail... vtail) : m_head(v), m_tail(vtail...) {}
    Head head() {return m_head;}
    composited& tail() {return m_tail;}
protected:
    Head m_head;
    composited m_tail;
};

template <int IDX, int MAX, typename... Args>
struct PRINT_TUPLE {
    static void print (ostream& os, const tuple<Args...>& t){
        os << get<IDX>(t) << (IDX + 1 == MAX ? "": ",");
        PRINT_TUPLE<IDX+1, MAX, Args...>::print(os, t);
    }
};

template <int MAX, typename... Args>
struct PRINT_TUPLE<MAX, MAX, Args...> {
    static void print (ostream& os, const tuple<Args...>& t){

    }
};

template <typename ... Args>
ostream& operator << (ostream& os, const tuple<Args...>& t) {
    os << "[";
    PRINT_TUPLE<0, sizeof...(Args), Args...>::print(os, t);
    return os << "]";
}

int main()
{
    print1(7.5, "hello", bitset<16>(377), 42);
    print2(7.5, "hello", bitset<16>(377), 42);
    print3(7.5, "hello", bitset<16>(377), 42);
    print4(7.5, "hello", bitset<16>(377), 42);
    print1(vector<int> {1, 2, 3, 4});
    print2(vector<int> {1, 2, 3, 4});
    print3(vector<int> {1, 2, 3, 4});
    print4(vector<int> {1, 2, 3, 4});
    mytuple1<int, float, string> t1(41, 6.3, "nico");
    print1(t1.head(), t1.tail().head(), t1.tail().tail().head());
    mytuple2<int, float, string> t2(41, 6.3, "nico");
    print1(t2.head(), t2.tail().head(), t2.tail().tail().head());
    cout << make_tuple(41, 6.3, "nico");
    return 0;
}



特别声明:本文仅供交流学习 , 版权归属原作者,并不代表游民部落赞同其观点和对其真实性负责。若文章无意侵犯到您的知识产权,损害了您的利益,烦请与我们联系vmaya_gz@126.com,我们将在24小时内进行修改或删除。

相关推荐:

软件设计推荐
  • 【二十八】游戏UI之UI界面管理
    【二十八】游戏UI之UI界面管理
    游戏UI界面之UI管理游戏UI界面之UI管理游戏UI界面之UI管理游戏UI界面之UI管理游戏UI界面之UI管理游戏UI界面之UI管理游戏UI界面之UI管理游戏UI界面之UI管理
  • 【二十七】游戏UI之透视相机模式规划
    【二十七】游戏UI之透视相机模式规划
    游戏UI界面之透视相机模式规划游戏UI界面之透视相机模式规划游戏UI界面之透视相机模式规划游戏UI界面之透视相机模式规划游戏UI界面之透视相机模式规划游戏UI界面之透视相机模式规划
  • 【二十六】游戏UI之正交相机模式规划
    【二十六】游戏UI之正交相机模式规划
    游戏UI界面之正交相机模式规划游戏UI界面之正交相机模式规划游戏UI界面之正交相机模式规划游戏UI界面之正交相机模式规划游戏UI界面之正交相机模式规划游戏UI界面之正交相机模式规划游戏UI界面之正交相机模式规划游戏UI界面之正交相机模式规划游戏UI界面
  • 【二十五】游戏UI之NGUI和UGUI简介
    【二十五】游戏UI之NGUI和UGUI简介
    游戏UI界面NGUI和UGUI简介游戏UI界面NGUI和UGUI简介游戏UI界面NGUI和UGUI简介游戏UI界面NGUI和UGUI简介游戏UI界面NGUI和UGUI简介游戏UI界面NGUI和UGUI简介
  • 【二十四】Lua与C、C++间模块交互
    【二十四】Lua与C、C++间模块交互
    Lua与C、C++间模块交互Lua与C、C++间模块交互Lua与C、C++间模块交互Lua与C、C++间模块交互Lua与C、C++间模块交互Lua与C、C++间模块交互Lua与C、C++间模块交互Lua与C、C++间模块交互Lua与C、C++间模块交互
  • 【二十二】Lua游戏配置内存优化策略
    【二十二】Lua游戏配置内存优化策略
    Lua游戏配置内存优化策略Lua游戏配置内存优化策略Lua游戏配置内存优化策略Lua游戏配置内存优化策略Lua游戏配置内存优化策略Lua游戏配置内存优化策略Lua游戏配置内存优化策略Lua游戏配置内存优化策略
  • 【二十三】Lua模块和Unity引擎C#模块间交互
    【二十三】Lua模块和Unity引擎C#模块间交互
    Lua模块和Unity引擎C#模块间交互Lua模块和Unity引擎C#模块间交互Lua模块和Unity引擎C#模块间交互Lua模块和Unity引擎C#模块间交互Lua模块和Unity引擎C#模块间交互Lua模块和Unity引擎C#模块间交互
  • 【二十一】Lua内存开销规划和优化策略
    【二十一】Lua内存开销规划和优化策略
    Lua内存开销规划和优化策略Lua内存开销规划和优化策略Lua内存开销规划和优化策略Lua内存开销规划和优化策略Lua内存开销规划和优化策略Lua内存开销规划和优化策略Lua内存开销规划和优化策略Lua内存开销规划和优化策略Lua内存开销规划和优化策略