现代C++实现多种print
学习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;
}
Print("到此结束!")
现代C++实现多种print的更多相关文章
- python 列表 总结
在python里创建列表和字典非常简单,这里总结一下它们的常用方法 1.创建列表 myArry = ["one", "two", "three&quo ...
- Python 简单模块学习
1. openpyxl / xlrd / xlwt => 操作Excel 文件(xlsx格式) => xlrd + xlwt : 只能操作xls文件,分别负责读写, 暂时不讨论 => ...
- python多种格式数据加载、处理与存储
多种格式数据加载.处理与存储 实际的场景中,我们会在不同的地方遇到各种不同的数据格式(比如大家熟悉的csv与txt,比如网页HTML格式,比如XML格式),我们来一起看看python如何和这些格式的数 ...
- print、sp_helptext的限制与扩展
在SQL中,使用动态SQL是很常见的.有些复杂的计算,或是存储过程,代码很长,中间可能有多次执行SQL语句.而调试拼串的SQL语句却是件痛苦的事,很难看出来运行的语句是什么.所以我会经常使用print ...
- grep(Global Regular Expression Print)
.grep -iwr --color 'hellp' /home/weblogic/demo 或者 grep -iw --color 'hellp' /home/weblogic/demo/* (-i ...
- sql语句分页多种方式ROW_NUMBER()OVER
sql语句分页多种方式ROW_NUMBER()OVER 摘自: http://www.cnblogs.com/CodingArt/articles/1692468.html 方式一 select to ...
- PHP获取时间日期的多种方法
分享下PHP获取时间日期的多种方法. <?php echo "今天:".date("Y-m-d")."<br>"; ...
- PrintWriter的print和write方法(转)
public void print(String s) {if (s == null) {s = "null";}write(s); } print只是先对s==null转换为 ...
- print流
PrintWriter和PrintStream都属于输出流,分别针对字符和字节. PrintWriter和PrintStream提供了重载的print,println方法用于多种类型的输出 Print ...
随机推荐
- 如何将pyqt5的qt-designer设计出来的 .ui 和 .qrc 文件转化成 .py 文件
一.pyrcc5的使用 1.1 作用 将 .qrc 资源文件转换成py文件,并在主程序中通过 import 引入 1.2 资源文件编写说明 创建一个icon.qrc,代码如下: <RCC> ...
- SQL Server 索引分析开关
set statistics io onset statistics profile on
- Git终端命令行的常用操作
一.git源代码管理的优点 方便多人协同开发.工作 降低代码的管理成本 良好的分支管理机制 二.结构分析 服务端和客户端都有版本控制能力,都能进行代码的提交.合并 结构一: 结构二: 三.工作区的创建 ...
- JAVA集合框架(一)-综述
目录 什么是java集合框架 使用类型安全的容器 集合框架简图 集合类库主要接口简述 Collection接口方法概览 什么是java集合框架 其实就是java类库提供的一套相当完整的各种数据结构的实 ...
- SpringCloud中Feign的适配器的实现方案
前言 最近在做微服务的项目,各个系统之间需要进行调用,然后用一个适配器来实现服务之间的feign调用,使用适配器进行统一管理. 实现方案 首先我们需要将服务的名称进行单独的配置,可以方便的进行切换和扩 ...
- Html5 Canvas动画基础碰撞检测的实现
在Canvas中进行碰撞检测,大家往往直接采用游戏引擎(Cocos2d-JS.Egret)或物理引擎(Box2D)内置的碰撞检测功能,好奇的你有思考过它们的内部运行机制吗?下面将针对基本的碰撞检测技术 ...
- Data Management Technology(3) -- SQL
SQL is a very-high-level language, in which the programmer is able to avoid specifying a lot of data ...
- centos python3 的 卸载 删除
卸载/删除python 3.4看到网上说慎用 apt-get remove和 yum remove ,因此不敢用此类命令用卸载了(以后阿里云服务器快过期不用了的时候可以试一下,看看系统是否会崩,哈哈) ...
- mssql sqlserver 添加表注释和添加列注释的方法分享
转自: http://www.maomao365.com/?p=8919 摘要: 下文讲述使用sql脚本对数据表或数据列添加注释(备注说明)的方法分享,如下所示: 实验环境:sql server 2 ...
- CodeForces - 763A(并查集/思维)
题意 https://vjudge.net/problem/CodeForces-763A 一棵无根树中各个节点被染上了一种颜色c[i] 现在让你选择一个点作为根节点,使得这个根节点的所有儿子满足以该 ...