Input/output library
cppreference Input/output library
有三种 io
库:
OOP
样式,基于流的io
库- 基于
print
的系列函数 - C 样式的
IO
函数
基于流的 'io' 库
基于流的 'io' 库是围绕抽象的 io
设备来进行组织的,这些抽象的设备允许使用相同的代码处理输入/输出文件,内存流,或定制的适配器设备,其动态执行任意操作(例如压缩)。
ios_base
basic_ios
basic_ostream
basic_ostringstream
basic_ofstream
basic_istream
basic_istringstream
basic_ifstream
basic_iostream
basic_stringstream
basic_fstream
ios_base
ios_base
state info
control info
它可以实现为两个任意长度的数组,或者一个由两个元素组成的数组结构体或另一个容器。
callbacks
flag
precison
width
formatting
exception mask
buffer error state
imbued locale
xalloc()
dec, oct, hex, basefield, left, right, internal, adjustfield, scientific, fixed, floatfield, boolalpha, showbase, showpoint, showpos, skipws, unitbuf, uppercase
在控制台乱码的情况
在源文件中设置 locale
能不能解决控制台的乱码情况?
#include <iostream>
#include <locale>
int main(int argc, char *argv[]) {
std::setlocale(LC_ALL, "");
std::cout << "你是你世界的主角,戏份由你安排!" << '\n';
return 0;
}
编译: g++ .\setlocale.cpp -o jkl
$ chcp 437
Active code page: 437
$ .\jkl.exe
����~_����,-�O�s,�,��'��O�^?����"���r%�Z'��?
编译: g++ .\setlocale.cpp -finput-charset=utf-8 -o jkl
$ chcp 437
Active code page: 437
$ .\jkl.exe
����~_����,-�O�s,�,��'��O�^?����"���r%�Z'��?
$ chcp 65001
Active code page: 65001
$ .\jkl.exe
ä
setlocale(LC_ALL, "en_US.UTF-8");
#include <iostream>
#include <locale>
int main(int argc, char *argv[]) {
//std::setlocale(LC_ALL, "");
std::setlocale(LC_ALL, "en_US.UTF_8");
std::cout << "你是你世界的主角,戏份由你安排!" << '\n';
return 0;
}
编译:g++ .\setlocale.cpp -o kkp
$ chcp 437
Active code page: 437
$ .\kkp.exe
你是你世界的主角,戏份由你安排!
用编译器的 utf8
选项来编译,能不能在 chcp 437
的控制台上正确的显示中文?
#include <iostream>
int main() {
std::cout << "你是最棒的!" << std::endl;
}
编译: g++ .\compile-use-utf8.cpp -finput-charset=utf-8 -o jkl
$ chcp 437
Active code page: 437
$ .\jkl.exe
你是最棒的!
fmt
库
fmt
是一个开源的,格式化库,它是快速、安全的,可以替代 C stdio
和 C++ iostreams
。
特征:
- 简单的 api
C++20 std::format
和C++23 std::print
的实现- 类似于
python
格式化语法 - IEEE 754 的快述圆整,使用Dragonbox算法保证短途和往返
- 可移植的
unicode
支持 - 安全的
printf 实现
,包含POSIX
对位置参数的扩展 - 可扩展:支持用户自定义类型
- 高性能:比
(s)printf
,iostream
,to_string
和to_chars
快 - 在源代码方面代码量小,最小配置仅由三个文件组成:
core.h
,format.h
和format-inl.h
- 可靠性:该库有一套广泛的测试,并且不断被模糊化
- 安全性:该库完全类型安全,格式字符串中的错误可以在编译时报告,自动内存管理可防止缓冲区溢出错误
- 易用性:小型自包含代码库,无外部依赖,宽松的MIT许可证
- 可移植性,跨平台输出一致,支持旧编译器
- 即使在高警告级别(如-Wall-Wextra-pedantic)上,也要清理无警告的代码库
- 默认情况下独立于区域设置
- 使用FMT_header_only宏启用可选的仅标头配置
使用第一个参数打印,使用 s
的指定类型为 string
(string
的打印需要手动指定类型,而 int
等不需要) ,中间对齐 ^
,两侧填充 -
,宽度为 30
:
(具体的格式化标记见:https://fmt.dev/11.0/)
#define FMT_HEADER_ONLY 1
#include <fmt/format.h>
#include <string>
int main() {
fmt::print("{0:-^30s}\n", std::string("center"));
}
fmt
的组件
fmt/base.h
:基本的 API 提供主要的格式化函数fmt/format.h
:fmt::format
和其他的格式化函数,以及 locale 支持fmt/ranges.h
: 对ranges
和tuple
进行格式化fmt/chrono.h
: 日期和时间的格式化fmt/std.h
:用于标准库类型的格式器fmt/compile.h
:格式字符串编译fmt/color.h
:终端的色彩和文本样式fmt/os.h
:系统 APIsfmt/ostream.h
:std::ostream
支持fmt/args.h
:动态参数列表fmt/printf.h
:printf
格式化fmt/xchar.h
:可选的wchar_t
支持
fmt
库中提供的类型和函数都在命名空间 fmt
中,然后库的所有宏都有前缀 FMT_
。
在 cmake
中使用
FetchContent: 从 cmake 3.11 开始,使用
FetchContent自动下载
fmt` 作为配置时的依赖项:
include(FetchContent)
FetchContent_Declare(
fmt
GIT_REPOSITORY https://github.com/fmtlib/fmt
GIT_TAG e69e5f977d458f2650bb346dadf2ad30c5320281) # 10.2.1
FetchContent_MakeAvailable(fmt)
target_link_libraries(<your-target> fmt::fmt)
已安装:找到已安装 fmt ,然后在你的 CMakeLists.txt
中使用:
find_package(fmt)
target_link_libraries(<your-target> fmt::fmt)
直接嵌入: 直接使用源码树
在你的项目中,在你的 CMakeLists.txt
中使用:
add_subdirectory(fmt)
target_link_libraries(<your-target> fmt::fmt)
Header-only
的方式使用
https://github.com/fmtlib/fmt/issues/524
You don't need CMake for that, just define FMT_HEADER_ONLY macro to 1 when including fmt/format.h.
#define FMT_HEADER_ONLY 1
#include <fmt/format.h>
int main() { fmt::print("ok!\n"); }
replacement-field
替换字段的使用和它的格式化
replacement_field ::= "{" [arg_id] [":" (format_spec | chrono_format_spec)] "}"
arg_id ::= integer | identifier
integer ::= digit+
digit ::= "0"..."9"
identifier ::= id_start id_continue*
id_start ::= "a"..."z" | "A"..."Z" | "_"
id_continue ::= id_start | digit
arg_id
std::string first {"first"};
std::string second {"second"};
fmt::print("{1}, {0}\n", first, second);
fmt::print("{0}, {0}\n", first);
// 输出:
// second first
// first first
:
指定你自己定义的替换的值的格式,而不是使用默认的。
fmt::print("{:s}\n"); // 打印字符串
format_spec
可以是,字段宽,对齐,填充,数字精度等。format_spec
可以包含嵌套的替换字段在其中确切的位置。
replacement_field ::= "{" [arg_id] [":" (format_spec | chrono_format_spec)] "}"
format_spec ::= [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
fill ::= <a character other than '{' or '}'>
align ::= "<" | ">" | "^"
sign ::= "+" | "-" | " "
width ::= integer | "{" [arg_id] "}"
precision ::= integer | "{" [arg_id] "}"
type ::= "a" | "A" | "b" | "B" | "c" | "d" | "e" | "E" | "f" | "F" |
"g" | "G" | "o" | "p" | "s" | "x" | "X" | "?"
align, 中心对齐 ^
, 左对齐 <
, 右对齐 >
replacement_field ::= "{" [arg_id] [":" (format_spec | chrono_format_spec)] "}"
format_spec ::= [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
fmt::print("{:*^{}}\n", "centered", 50);
解释:使用默认位置参数,填充字符为 *
,^
表示中间对齐,填充宽度使用嵌套字段指定(是50)
输出:
*********************centered*********************
sign 符号性
replacement_field ::= "{" [arg_id] [":" (format_spec | chrono_format_spec)] "}"
format_spec ::= [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
// '+', '-', ' '
fmt::print("{:f}; {:f}\n", 3.14, -3.14);
fmt::print("{:+f}; {:+f}\n", 3.14, -3.14);
fmt::print("{: f}; {: f}\n", 3.14, -3.14);
fmt::print("{:-f}; {:-f}\n", 3.14, -3.14)
输出:
3.140000; -3.140000
+3.140000; -3.140000
3.140000; -3.140000
3.140000; -3.140000
#
显示进制标示符
replacement_field ::= "{" [arg_id] [":" (format_spec | chrono_format_spec)] "}"
format_spec ::= [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
// based
fmt::print("int: {0:d}; hex: {0:x}; oct: {0:o}; bin: {0:b}\n", 42);
fmt::print("int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}\n", 42);
fmt::print("{:#06x}\n", 0);
输出:
int: 42; hex: 2a; oct: 52; bin: 101010
int: 42; hex: 0x2a; oct: 052; bin: 0b101010
0x0000
0
强制将0放在符号后,数字前。
replacement_field ::= "{" [arg_id] [":" (format_spec | chrono_format_spec)] "}"
format_spec ::= [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
只对数字类型有效, 只在没有填充字符的情况下有效
fmt::print("{:*>010}\n", 32);
fmt::print("{:>010}\n", -32);
fmt::print("{:*>010}\n", -32);
输出:
********32
-000000032
*******-32
width 字段
replacement_field ::= "{" [arg_id] [":" (format_spec | chrono_format_spec)] "}"
format_spec ::= [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
fmt::print("{:>10}\n", "right");
precision 精度
replacement_field ::= "{" [arg_id] [":" (format_spec | chrono_format_spec)] "}"
format_spec ::= [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
对于十进制数,在使用 f
或 F
字段时,说明多少位数字显示在小数点后面
,或者是在字段 g
或 G
时,指定小数点前后的位数。对于非数字类型字段,说明最大的字段大小,就是说这个字段能使用多少宽度。精度不允许用于 integer, character, Boolean 和 pointer 值。对于 C 风格的字符串必须使用空终结符——既使在精度指定的情况下,也必须使用空终结符。
double rank = -133.27;
fmt::print("Rank: {0: 10.8F}\n", rank);
输出:
Rank: -133.27000000
L
选项
replacement_field ::= "{" [arg_id] [":" (format_spec | chrono_format_spec)] "}"
format_spec ::= [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
使用当前的本地化设置(locale),以插入合适的数字分隔符字符。只对数字类型有效。
类型
replacement_field ::= "{" [arg_id] [":" (format_spec | chrono_format_spec)] "}"
format_spec ::= [[fill]align][sign]["#"]["0"][width]["." precision]["L"][type]
表示字符串的类型
s
字符串格
?
调试格式,字符串会有引号,并且特殊的字符会被转义
none
与 s
相同
表示字符的类型
c
字符格式
?
调试格式,字符串会有引号,并且特殊的字符会被转义
none
与 c
相同
整数类型
b
二进制格式,小写
B
二进制格式,大写
c
字符格式,将数字输出为字符
d
十进制格式,小写
o
八进制格式,大写
x
十六进制格式,小写
X
十六进制格式,大写
none
与 d
相同
整数表示类型也可以与字符和布尔值一起使用,唯一的例外是“c”不能与bool一起使用。如果未指定表示类型,则使用文本表示形式(true或false)对布尔值进行格式化。
浮点值的表示
a
十六进制浮点格式。打印以16为基数的数字,前缀为“0x”,9以上的数字用小写字母表示。使用“p”表示指数。
A
与“a”相同,除了它使用大写字母作为前缀、9以上的数字和表示指数。
e
指数表示法。以科学记数法打印数字,使用字母“e”表示指数。
E
指数表示法。与'e'相同,只是它使用大写'e'作为分隔符。
f
定点数。
F
定点数。作用与 f
相同,但是能转换 nan
为 NAN
,将 inf
转换为 INF
。
g
通用格式。对于给定的精度 p >= 1
,将数字圆整为 p 指定的位数,并且格式的结果要么是定点数格式,或者是科学记法,取决于它的大小。 精度为 0
被当做精度 1
外理。
G
作用与 g
相同,但是 e
是大写,inf
和 nan
也是大写。
none
与 g
类似,除了默认精度与表示特定值所需的精度一样高。
指针类型的表示
p
指针格式。这是指针的默认类型,可以省略。
none 与 p
相同。
chrono_format_spec
chrono_format_spec ::= [[fill]align][width]["." precision][chrono_specs]
chrono_specs ::= conversion_spec |
chrono_specs (conversion_spec | literal_char)
conversion_spec ::= "%" [padding_modifier] [locale_modifier] chrono_type
literal_char ::= <a character other than '{', '}' or '%'>
padding_modifier ::= "-" | "_" | "0"
locale_modifier ::= "E" | "O"
chrono_type ::= "a" | "A" | "b" | "B" | "c" | "C" | "d" | "D" | "e" |
"F" | "g" | "G" | "h" | "H" | "I" | "j" | "m" | "M" |
"n" | "p" | "q" | "Q" | "r" | "R" | "S" | "t" | "T" |
"u" | "U" | "V" | "w" | "W" | "x" | "X" | "y" | "Y" |
"z" | "Z" | "%"
Range Format Specifications
范围类型的格式规范具有以下语法:
range_format_spec ::= ["n"][range_type][range_underlying_spec]
fmt::print("{}", std::vector{10, 20, 30});
// Output: [10, 20, 30]
fmt::print("{::#x}", std::vector{10, 20, 30});
// Output: [0xa, 0x14, 0x1e]
fmt::print("{}", std::vector{'h', 'e', 'l', 'l', 'o'});
// Output: ['h', 'e', 'l', 'l', 'o']
fmt::print("{:n}", std::vector{'h', 'e', 'l', 'l', 'o'});
// Output: 'h', 'e', 'l', 'l', 'o'
fmt::print("{:s}", std::vector{'h', 'e', 'l', 'l', 'o'});
// Output: "hello"
fmt::print("{:?s}", std::vector{'h', 'e', 'l', 'l', 'o', '\n'});
// Output: "hello\n"
fmt::print("{::}", std::vector{'h', 'e', 'l', 'l', 'o'});
// Output: [h, e, l, l, o]
fmt::print("{::d}", std::vector{'h', 'e', 'l', 'l', 'o'});
// Output: [104, 101, 108, 108, 111]
template <typename... T>
void print(FILE* f, format_string<T...> fmt, T&&... args);
FILE
stream state
除了系统指定的,访问设备所必需的信息外(也就是 POSIX 文件描述符),每 FILE
对象都直接或间接的持有以下的信息:
- 字符宽度:unset, narror, or wide.
- 用于在多字节和宽字符间转换时(
mbstate_t
对象的类型),的解析状态。 - 缓冲状态:unbuffered, line-buffered, fully buffered。
- 一个缓冲,可能被外部的、用户提供的缓冲所替代。
I/O
模式: input, output 和 update(input 和 output)。- Binary/text 模式说明符。
- End-of-file 状态说明符。
- 文件位置说明符,一个可访问的对象,有着
fpos_t
类型,对于wide streams
,包含解析状。 - 可重入的锁,用于在多线程读、写、定位或查询流的位置时,防止数据竞争。
Narrow 和 wide 的面向
新打开的流没有方向。对fwide或任何I/O函数的第一次调用建立了定向:<详情见原文>
窄向,宽向
二进制模式和文本模式
文本流是一个有序的字符序列,能组合进一行中;'\n'
文本流中表示一行。最后一行是否需要由 '\n'
终结是实现定义的。此外,可能需要在输入和输出时添加、修改或删除字符,以符合操作系统中表示文本的约定(特别是,Windows操作系统上的C流在输出时将'\n'转换为'\r\n',并在输入时将'\r\n'转换为'\n')。
读取的数据可以等同于写入的数据,只要以下的条件为真:
- 数据的构成由唯一的可打印的字和/或唯一的控制字符
'\t'
和'\n'
(实践中,在 windows 系统,`'\0x1A' 会终结输入)。 - 没有空格字符紧接在'\n'字符前面(这些空格字符可能在以后将输出作为输入读取时消失)。
- 最后的字符是
'\n'
。
二进制流是一个有序的字符序列,可以透明地记录内部数据。从二进制流读入的数据总是等于先前写入该流的数据,除了允许在流的末尾附加不确定数量的空字符。宽二进制流不需要在初始移位状态中结束。
stdin, stdout, stderr
stdin expression of type FILE*
associated with the input stream
stdout expression of type FILE*
associated with the output stream
stderr expression of type FILE*
associated with the error output stream
unsigned int fread(void *buffer, unsigned int size, unsigned int count, FILE *fp);
unsigned int fwrite(const void* buffer, unsigned int size, unsigned int count, FILE *fp);
将 buffer
指向的内存中的数据块写入 fp
所指的文件,buffer
k待输出数据块的起始地址,size
是每个数据块的大小(字节数),count
是最多允许写入的数据块的个数(每个数据块是 size
个字节),函数返回的是实际写入的数据块的个数。
fread()函数和 fwrite()函数是按数据块的长度来处理输入/输出的,在用文本编辑器打开文本文件时可能因发生字符转换而出现莫名其妙的结果,所以这两个函数通常用于二进制文件的输入/输出