我正在学习 C++。cout
是std::ostream
类的一个实例。如何用它打印格式化的字符串?
我仍然可以使用printf
,但我想学习一种可以利用所有 C++ 优点的正确 C++ 方法。我认为这应该是可能的std::ostream
,但我找不到正确的方法。
我正在学习 C++。cout
是std::ostream
类的一个实例。如何用它打印格式化的字符串?
我仍然可以使用printf
,但我想学习一种可以利用所有 C++ 优点的正确 C++ 方法。我认为这应该是可能的std::ostream
,但我找不到正确的方法。
您唯一可以std::ostream
直接使用的是众所周知的<<
- 语法:
int i = 0;
std::cout << "this is a number: " << i;
并且有各种IO 操纵器可用于影响整数、浮点数等的格式、位数等。
但是,这与printf
. C++11 不包括任何允许您以与使用它的方式相同的方式使用字符串格式的工具printf
(除了printf
它本身,如果您愿意,您当然可以在 C++ 中使用它)。
就提供printf
-style 功能的库而言,有boost::format
,它启用了这样的代码(从概要复制):
std::cout << boost::format("writing %1%, x=%2% : %3%-th try") % "toto" % 40.23 % 50;
另请注意,在标准的未来版本中提出了包含 -style 格式的建议。printf
如果这被接受,可能会出现如下语法:
std::cout << std::putf("this is a number: %d\n",i);
在 C++20 中,您将能够使用类似std::format
安全printf
的格式:
std::cout << std::format("The answer is {}.\n", 42);
除此之外,基于{fmt} 库,std::format
还提供了print
结合格式化和输出的功能:
fmt::print("The answer is {}.\n", 42);
免责声明:我是 {fmt} 和 C++20 的作者std::format
。
这是我习以为常的成语。希望它有所帮助:
// Hacky but idiomatic printf style syntax with c++ <<
#include <cstdlib> // for sprintf
char buf[1024]; sprintf(buf, "%d score and %d years ago", 4, 7);
cout << string(buf) <<endl;
&
我建议使用 ostringstream 而不是 ostream 请参见以下示例:
#include <vector>
#include <string>
#include <iostream>
#include "CppUnitTest.h"
#define _CRT_NO_VA_START_VALIDATION
std::string format(const std::string& format, ...)
{
va_list args;
va_start(args, format);
size_t len = std::vsnprintf(NULL, 0, format.c_str(), args);
va_end(args);
std::vector<char> vec(len + 1);
va_start(args, format);
std::vsnprintf(&vec[0], len + 1, format.c_str(), args);
va_end(args);
return &vec[0];
}
示例用法:
std::ostringstream ss;
ss << format("%s => %d", "Version", Version) << std::endl;
Logger::WriteMessage(ss.str().c_str()); // write to unit test output
std::cout << ss.str() << std::endl; // write to standard output
要实现 printf,可以使用 c++11 模板参数:
#include <iostream>
#include <string>
inline std::ostream & mprintf(std::ostream & ostr, const char * fstr) throw()
{
return ostr << fstr;
}
template<typename T, typename... Args>
std::ostream & mprintf(std::ostream & ostr,
const char * fstr, const T & x) throw()
{
size_t i=0;
char c = fstr[0];
while (c != '%')
{
if(c == 0) return ostr; // string is finished
ostr << c;
c = fstr[++i];
};
c = fstr[++i];
ostr << x;
if(c==0) return ostr; //
// print the rest of the stirng
ostr << &fstr[++i];
return ostr;
}
template<typename T, typename... Args>
std::ostream & mprintf(std::ostream & ostr,
const char * fstr, const T & x, Args... args) throw()
{
size_t i=0;
char c = fstr[0];
while (c != '%')
{
if(c == 0) return ostr; // string is finished
ostr << c;
c = fstr[++i];
};
c = fstr[++i];
ostr << x;
if(c==0) return ostr; // string is finished
return mprintf(ostr, &fstr[++i], args...);
}
int main()
{
int c = 50*6;
double a = 34./67.;
std::string q = "Hello!";
// put only two arguments
// the symbol after % does not matter at all
mprintf(std::cout, "%f + %f = %a \n", c, a);
// print string object: for real printf one should write q.c_str()
mprintf(std::cout, "message: \"%s\". \n", q);
// the last argument will be ignored
mprintf(std::cout, "%z + %f\n", (long)a, 12, 544 );
}
输出
300 + 2 = %a
message: "Hello!".
2 + 12
这是一个非常简单的代码,可以改进。
1)优点是它使用<<将对象打印到流中,因此您可以放置可以通过<<输出的任意参数。
2)它忽略格式化字符串中参数的类型:在 % 之后可以代表任意符号甚至是空格。输出流决定如何打印相应的对象。它还与 printf 兼容。
3) 缺点是不能打印百分号'%',需要稍微改进一下代码。
4) 无法打印格式化数字,如 %4.5f
5) 如果参数的数量少于格式化字符串所预测的数量,则该函数只打印字符串的其余部分。
6) 如果参数的数量大于格式化字符串预测的数量,则忽略剩余的参数
可以改进代码以使 2)-6) 完全模仿 printf 行为。但是,如果您遵循 printf 的规则,则基本上只需要修复 3) 和 4)。
场宽
设置字段宽度非常简单。对于每个变量,只需在其前面加上“setw(n)”。像这样:
#include <iostream> #include <iomanip> using namespace std; int main() { const int max = 12; const int width = 6; for(int row = 1; row <= max; row++) { for(int col = 1; col <= max; col++) { cout << setw(width) << row * col; } cout << endl; } return 0; }
请注意“setw(n)”如何控制字段宽度,因此每个数字都打印在一个保持相同宽度的字段内,而不管数字本身的宽度如何。
我独立编写但想出了类似于 user3283405 的答案
我的解决方案使用 vasprintf() 来实现格式化,并使用 std::ostream 的 << 运算符重载在正确的位置释放内存。
用法:
std::cout << putf(const char *format, ...); //Same format as C printf(3)
代码:
#define _GNU_SOURCE
#include <cstdarg>
#include <iostream>
#include <cstdio>
struct putf_r{
char *s;
};
putf_r putf(const char *fmt, ...){
va_list ap;
va_start(ap, fmt);
putf_r a;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
vasprintf(&a.s, fmt, ap);
#pragma GCC diagnostic pop
va_end(ap);
return a;
}
std::ostream& operator<<(std::ostream& os, putf_r a){
os<<a.s;
free(a.s);
return os;
}
int main(){
std::cout << putf("%3d\n", 23) << putf("%a\n", 256.);
}
请注意,编译器不会检查 putf() 中的格式,因此编译器标志 -Wformat-nonliteral 不会警告 putf() 中的可疑代码,您需要自己处理不受控制的格式字符串问题。
详细信息可以在GitHub上找到
样本输出:
2017-12-20T16:24:47,604144+01:00 Hello, World!
代码(在 put_timestamp 中演示了 put_printf 的用法):
#include <assert.h>
#include <chrono>
#include <iomanip>
#include <iostream>
class put_printf {
static constexpr size_t failed = std::numeric_limits<size_t>::max(); // for any explicit error handling
size_t stream_size; // excluding '\0'; on error set to 0 or to "failed"
char buf_stack[2048+1]; // MAY be any size that fits on the stack (even 0), SHOULD be (just) large enough for most uses (including '\0')
std::unique_ptr<char[]> buf_heap; // only used if the output doesn't fit in buf_stack
public:
explicit put_printf(const char *format, ...)
#if __GNUC__
__attribute__ ((format (printf, 2, 3))) // most compelling reason for not using a variadic template; parameter 1 is implied "this"
#endif
{
va_list args;
va_start(args, format);
const int res = vsnprintf(buf_stack, sizeof(buf_stack), format, args);
va_end(args);
if (res < 0) { // easily provoked, e.g., with "%02147483646i\n", i.e., more than INT_MAX-1 significant characters (only observed, no guarantee seen)
stream_size = failed;
} else if (res < sizeof(buf_stack)) { // preferred path
stream_size = res;
} else { // not artificially constrained
try {
const size_t buf_size = static_cast<size_t>(res) + 1; // avoids relying on "res < INT_MAX" (only observed, no guarantee seen)
buf_heap.reset(new char[buf_size]); // observed to work even beyond INT_MAX=2^32-1 bytes
va_start(args, format);
if (vsnprintf(buf_heap.get(), buf_size, format, args) == res) stream_size = res;
else stream_size = failed; // can't happen
va_end(args);
} catch (const std::bad_alloc&) { // insufficient free heap space (or an environment-specific constraint?)
stream_size = failed;
}
}
}
friend std::ostream& operator<<(std::ostream& os, const put_printf& self) {
if (self.stream_size == failed) {
// (placeholder for any explicit error handling)
return os;
} else {
// using write() rather than operator<<() to avoid a separate scan for '\0' or unintentional truncation at any internal '\0' character
return os.write((self.buf_heap ? self.buf_heap.get() : self.buf_stack), self.stream_size);
}
}
};
class put_timestamp {
const bool basic = false;
const bool local = true;
public:
friend std::ostream& operator<<(std::ostream& os, const put_timestamp& self) {
const auto now = std::chrono::system_clock::now();
const std::time_t now_time_t = std::chrono::system_clock::to_time_t(now);
struct tm tm; if ((self.local ? localtime_r(&now_time_t, &tm) : gmtime_r(&now_time_t, &tm)) == nullptr) return os; // TODO: explicit error handling?
static_assert(4 <= sizeof(int), "");
const int microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch() % std::chrono::seconds(1)).count();
assert(0 <= microseconds && microseconds < 1000000); // TODO: (how) do we know?
// TODO: doesn't "point" in "decimal_point()" imply "dot"/"full stop"/"period", unlike an obviously neutral term like "mark"/"separator"/"sign"?
const char decimal_sign = std::use_facet<std::numpunct<char>>(os.getloc()).decimal_point() == '.' ? '.' : ','; // full stop accepted, comma preferred
// TODO: all well and good for a locale-specific decimal sign, but couldn't the locale also upset microseconds formatting by grouping digits?
os << std::put_time(&tm, self.basic ? "%Y%m%dT%H%M%S" : "%FT%T") << put_printf("%c%06i", decimal_sign, microseconds);
if (! self.local) return os << "Z";
const int tz_minutes = std::abs(static_cast<int>(tm.tm_gmtoff)) / 60;
return os << put_printf(self.basic ? "%c%02i%02i" : "%c%02i:%02i", 0 <= tm.tm_gmtoff ? '+' : '-', tz_minutes / 60, tz_minutes % 60);
}
};
int main() {
// testing decimal sign
///std::cout.imbue(std::locale("en_GB"));
///std::cout.imbue(std::locale("fr_FR"));
std::cout << put_timestamp() << " Hello, World!\n";
#if 0
typedef put_printf pf; // just to demo local abbreviation
std::cout << "1: " << pf("%02147483646i\n" , 1 ) << std::endl; // res < 0
std::cout << "2: " << pf("%02147483643i%i\n", 1, 100) << std::endl; // res < 0
std::cout << "3: " << pf("%02147483643i%i\n", 1, 10) << std::endl; // works
std::cout << "4: " << pf("%02147483646i" , 1 ) << std::endl; // works
#endif
return 0;
}
关于 put_printf 的评论:
// Reasons for the name "put_printf" (and not "putf" after all):
// - put_printf is self-documenting, while using the naming pattern also seen in std::put_time;
// - it is not clear whether the proposed std::putf would support exactly the same format syntax;
// - it has a niche purpose, so a longer name is not an objection, and for frequent local uses
// it is easy enough to declare an even shorter "typedef put_printf pf;" or so.
// Evaluation of delegating to vsnprintf() with intermediate buffer:
// (+) identical result without implementation and/or maintenance issues,
// (?) succeeds or fails as a whole, no output of successful prefix before point of failure
// (-) (total output size limited to INT_MAX-1)
// (-) overhead (TODO: optimal buf_stack size considering cache and VM page locality?)
// Error handling (an STL design problem?):
// - std::cout.setstate(std::ios_base::failbit) discards further std::cout output (stdout still works),
// so, to be aware of an error in business logic yet keep on trucking in diagnostics,
// should there be separate classes, or a possibility to plug in an error handler, or what?
// - should the basic or default error handling print a diagnostic message? throw an exception?
// TODO: could a function "int ostream_printf(std::ostream& os, const char *format, ...)"
// first try to write directly into os.rdbuf() before using buf_stack and buf_heap,
// and would that significantly improve performance or not?
当我需要 cout 的类型安全性和 printf() 简单变量的快速简便格式化时,我会像这样混合两者。这是一个丑陋的修复,但是当我需要输出诸如“02/07/2014 10:05am”之类的内容以及一些更复杂的实体时,它可以为我完成任务:
#include <stdio>
#include <stdarg>
#include <stdlib>
#include <iostream>
#pragma hdrstop
using namespace std;
char* print(char* fmt, ...)
{
static char buffer[80] = "";
va_list argptr;
va_start(argptr,fmt);
vsprintf(buffer, fmt, argptr);
va_end(argptr);
return buffer;
}
#pragma argsused
int main(int argc, char* argv[])
{
cout << print("\n%06d\n%6d\n%6d\n%010.3f",1,12,123,123.456);
system("PAUSE>NUL");
return 0;
}