zhyDaDa的个人站点

目录


格式化输入输出

输出的格式字符串

格式字符串有4个可选的参数
完整的格式字符串是这样的:
%[flags][width][.prec][h|L]type

flags

flags是一个标志, 控制数字输出的样式
有这样一些可选项
| flags | 含义 |
| :—: | :—————-: |
| - | 左对齐 |
| + | 显示正负号 |
| 空格 | 正数留空 |
| 0 | 用零填充多余的位置 |

注意, 一个格式字符串可以有很多flags
这些flags的先后顺序无关紧要
例如printf("%+-9d",123);就会输出左对齐的+123
但是, 左对齐, 正数留空用零填充 这三个需求是冲突的, 只能出现一个

width和.prec

width是一个正整数, 表示输出的数字总共所占据的字符的个数
.prec是加在width前面的小数点, 表示输出的数字保留小数点后的个数

注意:
+ %9.2d表示总共是9个字符位, 小数点后保留两位

不要把%9.2d误当做是小数点前有9位的意思!
+ width还可以是一个星号*, 让一个参数传入来作为width的值

举例:

int len1 = 10;
int len2 = 2;
printf("下面这个数字共%d位, 保留了%d位小数点\n->|%+0*.*f|-<", len1, len2, len1, len2, 123.4);

其输出为:

下面这个数字共10位, 保留了2位小数点
->|+000123.40|-<

h|L

h|L是对类型的修饰
| 类型修饰 | 含义 |
| :———: | :———–: |
| hh | 单个字节 |
| h | short |
| l(小写L) | long |
| ll(小写L) | long long |
| L | long double |

事实上并未生效, 能生效的只有hl两个
hh要实现的效果似乎可以直接用c替代, 就是输出最短的数据类型
这里有个有趣的现象: printf("%c", 12345);输出的结果是9

这是因为12345在内存中储存为16进制的形式0x3039
只取最后一位, 以字符形式输出就是9

type

汇总一下所有的类型
| type | 用于 |
| :——–: | :—————————————————-: |
| i或者d | int |
| u | unsigned int |
| o | 八进制 |
| x | 字母小写的十六进制 |
| X | 字母大写的十六进制 |
| f或者F | 浮点数(到小数点后6位) |
| e或者E | 用科学计数法/指数形式表示, 大小写对应输出的”e”的大小写 |
| g或者G | 保留6位有效数字的浮点 |
| a或者A | 十六进制浮点 |
| c | char |
| s | 字符串 |
| p | 指针 |
| n | 读入/写出的个数 |

int num=0;
printf("->|%d|<-%n\n", 12345, &num);
printf("%d", num);

输出结果是:

->|12345|<-
11

笔者在VSC没有成功生效(虽然但是也没warning)

输入的格式字符串

相较于输出, 输入可选的参数比较简单
其格式字符串是这样的: %[flag]type

flag

| flag | 含义 |
| :———: | :—————-: |
| * | 跳过一项 |
| 数字 | 最大读入的字符数 |
| hh | 作为字符读入char |
| h | short |
| l(小写L) | long或者double |
| ll(小写L) | long long |
| L | long double |

举例:

int num=0;
scanf("%*d%d", &num);
printf("%d", num);

输入输出:

输入: 12345 67890  
输出: 67890

type

| type | 用于 |
| :——-: | :———————————-: |
| d | int |
| i | 整数, 可以是十/八/十六进制的(更灵活) |
| u | unsigned int |
| o | 八进制 |
| x | 十六进制 |
| a/e/f/g | float |
| c | char |
| s | 字符串 |
| [...] | 所允许的字符(类似于正则表达式) |
| p | 指针 |

举例1:

int n1 = 0;
int n2 = 0;
scanf("%i%i", &n1, &n2);
printf("%i %i", n1, n2);

输入输出:

输入: 0xab 012
输出: 171 10

举例2:

char n1[20] ;
char n2[20];
scanf("%*[^:]:%[^,],%[^.]", n1, n2);
printf("%s %s", n1, n2);

输入输出:

输入: 输入了两个数字:123,456.
输出: 123 456

这里的[^:]是指: 在第一个:前(不包括:)所有的内容(视为字符串)
其他两个[^,] [^.]同理
分析一下这里的输入格式字符串:
+ 首先, 跳过封号:前所有内容
+ 读入封号:
+ 读入逗号,前所有内容, 作为字符串交给地址n1
+ 读入逗号,
+ 读入句号.前所有内容, 作为字符串交给地址n2

[...]的作用不止这一个, 但网上一下子搜不到相关知识点orz
有一篇blog讲的较为详细, 链接-> c语言学习笔记第四章——字符串和格式化输入、输出

printf和scanf的返回值

printf会返回成功输出的 字符数
scanf会返回成功读入的 项目数

在较为严格的项目中, 应当判断每次输入输出的返回值
以确保程序运行中不会产生问题

举例:

int num = 0;
int s = 0, p = 0;
s=scanf("%i", &num);
p=printf("%i\n",num);
printf("scanf返回值:%d\n",s);
printf("printf返回值:%d\n",p);

输入: 0xabc
输出:

0xabc
2748
scanf返回值:1
printf返回值:5

这里返回值为5, 因为回车符也算在其中

文件的输入输出

FILE

FILE是标准库中定义的结构
常用的是文件指针: FILE *

该结构的其他成员用来反映文件的相关信息

操作文件的函数

  • fopen函数打开指定文件, 并返回一个FILE指针
    FILE * fopen (const char * restrict path, const char * restrict mode);
  • fclose函数关闭文件
    int fclose (FILE * stream);
  • fscanf函数负责从文件中读入
    int fscanf(FILE *__stream, const char *__format, ...)
  • fscanf函数负责向文件中写入
    int fprintf (FILE *__stream, const char *__format, ...)

打开文件的标准代码

FILE *fp = fopen("file", "r");
if (fp)
{
    fscanf(fp, ...);
    /* handler */
    fclose(fp);
}
else
{
    /* fp == null */
    printf("无法打开文件\n");
}

如果找不到文件, fopen函数会返回null

函数

fopen

fopen的第一个参数是文件的路径
第二个变量是打开文件的方式, 下面这个表格是所有可选的方式
| 方式代号 | 效果 |
| :————————: | :————————————–: |
| r | 只读 |
| r+ | 读写, 从文件头开始 |
| w | 只写, 不存在就新建, 存在就清空 |
| w+ | 读写, 不存在就新建, 存在就清空 |
| a | 追加, 不存在就新建, 存在就从文件尾部开始 |
| ..x(在其他代号的后面加x) | 只新建, 如果文件已经存在了就不能打开 |

二进制文件

所有文件最终都是二进制
文本文件可以通过简单的方式实现读写
但是二进制文件需要专门的程序来实现读写

选择文本文件还是二进制文件的历史

Unix 喜欢使用文本文件来做数据存储和程序配置

由于交互式终端的出现(在Unix之前), 人们更倾向于使用文本与计算机”talk”(交互)

Unix的shell提供了利于读写文本的小程序, 因此使用文本很方便

Windows喜欢使用二进制文件

DOS操作系统的发明来源于草根文化, 是设计者革新的发明, 完全出自对于计算机硬件的理解
因此, DOS并不继承Unix的特点

用二进制的方式做输入输出更接近底层

文本文件和二进制文件的比较

文本文件:
+ 优点
– 方便人类读写
– 跨平台(到哪儿都能用)
+ 缺点
– 输入输出需要格式化
– 运算和处理的开销较大

二进制文件:
+ 缺点
– 输入输出是直接的
– 程序读写无需计算, 速度很快
+ 缺点
– 人类读写困难
– 不支持跨平台(例如int这样的类型在不同操作系统中的长度不一样, 还有可能涉及到大小端的问题)

程序运行为什么需要文件

  1. 配置信息
  2. Unix采用文本
  3. Windows用注册表记录

    注册表是一个很大的二进制文件, 所有文件的配置信息全部记录在其中

  4. 数据存储
  5. 量大的数据可以使用数据库很轻松的存储
  6. 媒体文件
  7. 媒体文件只能是二进制文件

事实上, 程序基本都通过第三方的库来实现对文件的读写
很少直接读写二进制文件

操作二进制文件

读写

实在要直接处理二进制文件, 则采用以下这对函数:

size_t fread (void * pointer, size_t size, size_t nitems, FILE * stream);
size_t fwrite (const void * pointer, size_t size, size_t nitems, FILE * stream);

参数表:
1. 指针, 指向要读写的内存
2. 这块内存的大小
3. 有几个这样的内存
4. 文件指针

返回值 的是成功的读写的字节数

第三个参数nitems( number of items )的作用是什么?
一般对二进制文件的读写都是通过对一个结构变量的操作进行的
nitems就是用来说明这次读写操作几个结构变量

定位

  • 得到现在文件中的位置(从文件开头到现在位置的字节数)
    long ftell (FILE * stream);
  • 定位到指定的位置
    int fseek (FILE * stream, long offset, int whence);

fseek这个函数有三个参数:
1. stream是FILE指针
2. offset是偏移量, 也就是数到第几个字节
3. whence是寻找的方式, 有三个选项(都是预定义的宏, 所以表现出来的是int)
+ SEEK_SET: 从头开始找
+ SEEK_CUR: 从当前位置开始找
+ SEEK_END: 从尾巴开始从后向前找

一般从二进制文件中得到结构个数的方式如下:
fseek(fp, 0L, SEEK_END);可以移动到末尾(这里的0L是将数字0作为长整型Long的表达方式)
然后再用long size = ftell(fp);就可以得到从头到尾的字节数
用这个size除以每一个结构的大小就可以得到这个文件中的结构个数

可移植性问题

二进制文件不具有可以执行性

int在32位机的字节数与64位机的不一致
读写就是错误的

解决方案之一是放弃使用int
转而使用typedef人为定义出具有明确字节数的自定义类型
当然, 更好的方案还是使用文本

Avatar photo
我是 zhyDaDa

前端/UI/交互/独立游戏/JPOP/电吉他/游戏配乐/网球/纸牌魔术

发表回复