C++生成格式化的标准字符串实例代码

两种格式化字符串方法

众所周知,C++的std::string功能残缺,各种功能都没有,比如格式化字符串功能。

在python3中,支持两种格式化字符串的方法,一种是C风格,格式化的部分用%开头,%后面的对应具体类型(比如%s对应字符串%d对应整型),另一种则是类型无关的风格,{0}对应第1个参数,{1}对应第2个参数。

>>> "{0}'s age is {1}".format("赤红", 11)
"赤红's age is 11"
>>> "%s's age is %d" % ("赤红", 11)
"赤红's age is 11"

而在C++中则只能借用C函数,用snprintf来格式化一片缓冲区

#define BUFFSIZE 512
 char buf[BUFFSIZE];
 snprintf(buf, BUFFSIZE, "%s's age is %d\n", "赤红", 11);

亦或者用类型无关的流运算符

 std::ostringstream os;
 os << "赤红" << "'s age is " << 11 << "\n";
 std::string s = os.str();

暂且不谈效率问题,这种用<<拼接多个不同类型对象的做法代码量较大,而且在控制具体输出格式时更为麻烦,比如控制数字所占位数,或者小数点后位数。至少繁杂得让我总是记不起来,宁可使用C风格snprintf来控制。比如

 double d = 3.1415926;
 snprintf(buf, BUFFSIZE, "圆周率: %-8.3lf是祖冲之发现的\n", d);
$ ./a.out
圆周率: 3.142 是祖冲之发现的

通过%-8.3lf将lf(long float即double)类型的浮点数设置占位数为8,设置小数点后位数为3,负号表示左对齐,这种表示方法非常简单紧凑。

至于用C++的iomanip头文件实现,我还花了点时间查文档。

 double d = 3.1415926;
 os << "圆周率: " << std::setw(8) << std::fixed
  << std::setprecision(3) << std::left
  << d << "是祖冲之发现的\n";

除了代码如此之长以及有可能漏掉std::fixed外,还有问题在于setprecision已经改变了默认设置,也就是说,如果再os <<传入一个浮点数,保留的小数点位数仍然是3位。

也许有人说,这种好处在于setprecision和setw接收的可以是一个变量而非常量。实际上snprintf一样可以做到。

 double d = 3.1415926;
 int n1 = 8, n2 = 3;
 snprintf(buf, BUFFSIZE, "圆周率: %-*.*lf是祖冲之发现的\n", n1, n2, d);

C++包装snprintf生成格式化的std::string对象

APUE UNP TLPI这几本讲Linux下C编程的书中,都自己写了错误处理库来包装snprintf产生格式化的输出,以免每次重复定义缓冲区/调用snprintf等等。

这样的做法有个缺陷就是缓冲区(字符数组)长度有限制,当然一般而言buffer size定义得足够大的话是足够的,毕竟打印太长的格式化字符串不如多调用几次函数。

另一方面,由于这些函数仅仅是打印信息,尤其是经常打印信息后直接退出程序。所以不会返回错误字符串。如果在C++中想要把错误信息作为异常传给上一层处理,这些函数是不够的。因此需要简单修改下。

inline std::string format_string(const char* format, va_list args) {
 constexpr size_t oldlen = BUFSIZ;
 char buffer[oldlen]; // 默认栈上的缓冲区
 va_list argscopy;
 va_copy(argscopy, args);
 size_t newlen = vsnprintf(&buffer[0], oldlen, format, args) + 1;
 newlen++; // 算上终止符'\0'
 if (newlen > oldlen) { // 默认缓冲区不够大,从堆上分配
  std::vector<char> newbuffer(newlen);
  vsnprintf(newbuffer.data(), newlen, format, argscopy);
  return newbuffer.data();
 }
 return buffer;
}

inline std::string format_string(const char* format, ...) {
 va_list args;
 va_start(args, format);
 auto s = format_string(format, args);
 va_end(args);

 return s;
}

这是模仿UNP的实现,定义形参为va_list和...的两个版本,其中接受va_list的版本还可为其它函数所用。因为C风格的可变参数列表...不能作为参数传递。另一点,va_list类型也不一定有拷贝构造函数,因此得用va_copy来拷贝一份va_list,以供第二次使用。

C++11新增了可变模板参数特性,使得上述代码可以得到简化

template <typename ...Args>
inline std::string format_string(const char* format, Args... args) {
  constexpr size_t oldlen = BUFSIZ;
  char buffer[oldlen]; // 默认栈上的缓冲区

  size_t newlen = snprintf(&buffer[0], oldlen, format, args...);
  newlen++; // 算上终止符'\0'

  if (newlen > oldlen) { // 默认缓冲区不够大,从堆上分配
    std::vector<char> newbuffer(newlen);
    snprintf(newbuffer.data(), newlen, format, args...);
    return std::string(newbuffer.data());
  }

  return buffer;
}

而传递可变模板参数也变得十分容易(使用forward完美转发),示例代码如下

xyz@ubuntu:~/unp_practice/lib$ cat test.cc
#include <string.h>
#include <unistd.h>
#include "format_string.h"

template <typename ...Args>
void errExit(const char* format, Args... args) {
  auto errmsg = format_string(format, std::forward<Args>(args)...);
  errmsg = errmsg + ": " + strerror(errno) + "\n";
  fputs(errmsg.c_str(), stderr);
  exit(1);
}

int main() {
  const char* s = "hello world!";
  int fd = -1;
  if (write(fd, s, strlen(s)) == -1)
    errExit("write \"%s\" to file descriptor(%d) failed", s, fd);
  return 0;
}
xyz@ubuntu:~/unp_practice/lib$ g++ test.cc -std=c++11
xyz@ubuntu:~/unp_practice/lib$ ./a.out
write "hello world!" to file descriptor(-1) failed: Bad file descriptor

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • C/C++格式化日志库实现代码

    头文件如下: /*****************************************************/ /* 跨平台日志函数,Linux下与windows下亲测有效 */ /*****************************************************/ #ifndef _LOG_FORMAT_H_ #define _LOG_FORMAT_H_ // 日志等级 enum LogLevel { _LOG_TRACE, _LOG_INFO, _LOG

  • C++编程中的格式化输出详解

    在输出数据时,为简便起见,往往不指定输出的格式,由系统根据数据的类型采取默认的格式,但有时希望数据按指定的格式输出,如要求以十六进制或八进制形式输出一个 整数,对输出的小数只保留两位小数等.有两种方法可以达到此目的.一种是使用控制符的方法:第2种是使用流对象的有关成员函数.分别叙述如下. 使用控制符控制输出格式 控制格式的使用方法这里不再赘述,仅举例说明 [例] 用控制符控制输出格式. #include <iostream> #include <iomanip>//不要忘记包含此头

  • C++ 格式化日志输出实现代码

    核心代码 void LogOut(LPCTSTR pFormat, ...) { char LogFile[128]={0}; char str[1024]={0}; FILE** fp=&(ThreadDatas[0].pf); va_list pArg; EnterCriticalSection(&cs_log);//独占访问 __try { SYSTEMTIME st; if(ThreadDatas[0].num>100000)//10W条一个文件 { if(fp) { fcl

  • 解析C++ 浮点数的格式化输出

    C++格式化输出浮点数 复制代码 代码如下: #include <iostream>using std::cout;using std::endl;using std::fixed;using std::scientific;int main(){   double x = 0.001234567;   double y = 1.946e9;   cout << "Displayed in default format:" << endl <&

  • C++ Log日志类轻量级支持格式化输出变量实现代码

    CLog 头 代码很简单 如果需要的直接Ctrl+C ----Ctrl+V 即可 #ifndef __CLOG__ #define __CLOG__ #include <windows.h> #include <string> #include <fstream> #include <tchar.h> #include <ctime> class CLog { public: CLog(); CLog(const std::string LogF

  • 解析C++ 浮点数的格式化显示

    代码如下所示: 复制代码 代码如下: #include <stdlib.h>      #include <string>      #include <windows.h>      #include <stdio.h>      #include <iostream>      #include <limits>      #include <sstream>      using namespace std; str

  • C++生成格式化的标准字符串实例代码

    两种格式化字符串方法 众所周知,C++的std::string功能残缺,各种功能都没有,比如格式化字符串功能. 在python3中,支持两种格式化字符串的方法,一种是C风格,格式化的部分用%开头,%后面的对应具体类型(比如%s对应字符串%d对应整型),另一种则是类型无关的风格,{0}对应第1个参数,{1}对应第2个参数. >>> "{0}'s age is {1}".format("赤红", 11) "赤红's age is 11&quo

  • C# 根据字符串生成二维码的实例代码

    1.先下载NuGet包(ZXing.Net) 2.新建控制器及编写后台代码 using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Linq; using System.Web; using System.Web.Mvc; using ZXing; using ZXing.QrCode; nam

  • 使用Vue动态生成form表单的实例代码

    具有数据收集.校验和提交功能的表单生成器,包含复选框.单选框.输入框.下拉选择框等元素以及,省市区三级联动,时间选择,日期选择,颜色选择,文件/图片上传功能,支持事件扩展. 欢迎大家star学习交流:github地址 示例 https://raw.githubusercontent.com/xaboy/form-create/dev/images/sample110.jpg 安装 npm install form-create OR git clone https://github.com/xa

  • js生成随机数(指定范围)的实例代码

    1.随机生成4位数的随机数 <script language="javascript"> /** * 随机生成4位的随机数 * http://www.yulu.jb51.net */ document.write(parseInt(10*Math.random())); //输出0-10之间的随机整数 document.write(Math.floor(Math.random()*10+1)); //输出1-10之间的随机整数 function RndNum(n){ var

  • Struts2实现生成动态验证码并验证实例代码

     一.基本流程: 产生一个验证码页面(很小)→嵌入到表单中→点击可以刷新页面→表单提交时验证. 二.方法: 1.定义TestAction,实现画图方法 package com.zhuguang.action; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.util.Map; import javax.se

  • Oracle生成单据编号存储过程的实例代码

    Oracle生成单据编号存储过程,在做订单类似的系统都可能会存在订单编号不重复,或是流水号按日,按年,按月进行重新编号. 可以参考以下存储过程 CREATE OR REPLACE procedure Pro_GetBillNO(TypeTable in varchar2,cur_mycursor out sys_refcursor) as DReceiptCode varchar2(40); DReceiptName varchar2(50); DPrefix1 varchar2(50); DI

  • Mybatis结果生成键值对的实例代码

    下面给大家介绍下mybatis结果生成键值对的实例代码,具体内容如下所示: 在实际应用中我们经常会遇到这样的情况,需要给下拉框赋值,这个时候就需要键值对了,具体使用方法如下 1,在maper.xml文件中定义结果类型(resultType)定义为hashmap,如下所示 <select id="selectSuperUnitInfo" resultType="hashmap"> SELECT unit_id ,unit_name from unit_in

  • python方法生成txt标签文件的实例代码

    1.如果想要利用代码(不论是python.c++.亦或是matlab)实现生成标签文件,首先,也是灰常重要的一件事就是你的图片集一定要是有规律的命名.数字字母顺序排开.这一点非常重要,相关重命名方法请自行百度或请教大牛. 2.如图为博主的图片集.(注意命名规律) 博主只分为猫和鸟两类. 3.看代码. 在caffe根目录下创建一个我们的工程目录my-caffe-project 创建并编辑create_db.py文件,使用如下指令: vim create_db.py 然后,代码内容就是我们的重点了.

  • Mybatis逆向生成使用扩展类的实例代码详解

    1.背景介绍 用的mybatis自动生成的插件,然而每次更改数据库的时候重新生成需要替换原有的mapper.xml文件,都要把之前业务相关的sql重新写一遍,感觉十分麻烦,就想着把自动生成的作为一个基础文件,然后业务相关的写在扩展文件里面,这样更改数据库后只需要把所有基础文件替换掉就可以了 2.代码 2.1 BaseMapper.java 把自动生成的方法都抽到一个base类,然后可以写一些公共的方法 /** * @author 吕梁山 * @date 2019/4/23 */ public i

  • nodejs 生成和导出 word的实例代码

    前段时间由于项目需求,得做excel和word的导出功能,excel的导出百度一下一大把,小伙伴们都写的好详细,基本打来改改就可以用,可导出word的功能,百度了貌似都找不到可用资料,哎,费解呀.后来找同事,同事们也没整过,看来还得自己上呀... 第一次发现原来百度Google这强大,同事推荐的 http://www.baigoogledu.com/ 百度Google一起摆 nodejs word  找到https://github.com/Ziv-Barber/officegen   看到这里

随机推荐