C/Cpp 输出语句的变迁
C语言
printf
这是C语言提供的输出语句,示例如下 1
2
3
4
5
6
7
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
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++
的标准输入输出(cin
和 cout
)的效率会明显低于
C 的标准输入输出(scanf
和 printf
):
cin
和cout
是绑定在一起的。这意味着在调用cin
进行输入时,cout
会被自动刷新,以确保任何未输出的数据在读取输入之前会被显示。这种自动刷新虽然保证了输出的时效性,但会降低效率,特别是在需要频繁进行输入输出时。- C++ 的标准输入输出(
cin
和cout
)与 C 的标准输入输出(scanf
和printf
)是保持同步的。这意味着每次使用cin
或cout
时,都会检查 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
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
std::print
基于std::format
设计新的输出函数是呼之欲出的,例如直接封装得到的std::print
和自动回车的std::println
。
目前各大编译器对std::print
的支持各不相同了,正常使用应该是下面的效果
1
2
3
4
5
6
7
8
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
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;
}