C语言

printf

这是C语言提供的输出语句,示例如下

1
2
3
4
5
6
7
#include <stdio.h>

int main() {
int number = 42;
printf("The answer is %d.\n", number);
return 0;
}

printf支持不定参数,占位符采用%d等。

C++

std::cout

这是C++20之前提供的基于面向对象和流输出的方式,示例如下

1
2
3
4
5
6
7
#include <iostream>

int main() {
int number = 42;
std::cout << "The answer is " << number << ".\n";
return 0;
}

流式输出在简单的情况下比较好用,但是一旦我们需要复杂的格式化输出,这就变得非常繁琐了。

std::cout的输出是带缓冲的,可以手动调用cout.flush()清空缓冲区,如果检测到\n或者手动调用std::endl也会自动清空缓冲。

<iostream>中实际上提供了如下一组用于输出的全局对象:

  • std::cout:绑定到标准输出流stdout,带缓冲
  • std::cerr:绑定到标准错误流stderr,不带缓冲
  • std::clog:绑定到标准错误流stderr,带缓冲

最后的两个的区别仅仅是是否具有缓冲区,因此std::cerr适合立即输出警告和错误信息,std::clog适合输出日志信息。

事实上,<iostream>这个库都是充满争议的,流式输入输出除了基本使用比较简单,有非常多的缺点,比如:

  • std::cout在复杂格式的情况下使用非常繁琐;
  • std::cout不是线程安全的,多线程情况下的流式输出可能会乱序,而printf不会;
  • std::cin在大多数情况下比scanf慢很多,除非经过一些配置才能达到相当的速度。

std::format

在C++20中参考开源库fmt提供了新的格式化方案:<format>std::format提供的字符串格式化可以弥补std::cout的不足,使用和Python类似的{}占位

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <format>

int main() {
int number = 42;
std::string result = std::format("The answer is {}.", number);
std::cout << result << std::endl;
return 0;
}

但这只是格式化的过程,最后还是需要使用std::cout进行输出。

在很多情况下,我们都需要使用不支持<format>的低版本编译器,可以参考下面的方式进行分类处理

1
2
3
4
5
6
#if __has_include(<format>)
#include <format>
#define USE_FORMAT
#else
#include <iomanip>
#endif

std::print

基于std::format设计新的输出函数是呼之欲出的,例如直接封装得到的std::print和自动回车的std::println。 目前各大编译器对std::print的支持各不相同了,正常使用应该是下面的效果

1
2
3
4
5
6
7
8
#include <iostream>
#include <print>

int main() {
int number = 42;
std::print("The answer is {}.\n", number);
return 0;
}

但是很多编译器的最新版的实现都不完整,可能需要开启C++23的语法标准,某些情况下还需要开启额外的编译选项。

可以用下面的封装作为std::print的简易实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <format>
#include <iostream>

namespace my {

template <class... Args>
void print(std::iostream &stream, std::format_string<Args...> fmt,
Args &&...args) {
stream << std::format(fmt, std::forward<Args>(args)...);
}

template <class... Args>
void print(std::format_string<Args...> fmt, Args &&...args) {
std::cout << std::format(fmt, std::forward<Args>(args)...);
}

template <class... Args>
void print(std::FILE *stream, std::format_string<Args...> fmt, Args &&...args) {
std::fprintf(stream, std::format(fmt, std::forward<Args>(args)...));
}

} // namespace my

int main() {
int number = 42;
my::print("The answer is {}.\n", number);
return 0;
}