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不会。

iostream的效率也是一直饱受诟病的,因为设计和兼容性原因,C++ 的标准输入输出(cincout)的效率会明显低于 C 的标准输入输出(scanfprintf):

  • cincout 是绑定在一起的。这意味着在调用 cin 进行输入时,cout 会被自动刷新,以确保任何未输出的数据在读取输入之前会被显示。这种自动刷新虽然保证了输出的时效性,但会降低效率,特别是在需要频繁进行输入输出时。
  • C++ 的标准输入输出(cincout)与 C 的标准输入输出(scanfprintf)是保持同步的。这意味着每次使用 cincout 时,都会检查 C 的流是否有输出或输入,以确保数据的一致性。

可以使用下面的语句来优化 C++ 的输入输出效率

1
2
3
4
5
// 关闭输入输出缓存同步
ios::sync_with_stdio(false);

// 解除cin和cout的默认绑定
cin.tie(NULL); cout.tie(NULL);

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;
}