13.1-1格式化输入输出格式
%flags.prectype
Flag | 含义 |
- | 左对齐 |
+ | 在前面放+或- |
(space) | 正数留空 |
0 | 0填充 |
#include
intmain(intargc,charconst*argv[])
{
printf("%9d\n",123);//需要占9个字符的空间,123前还有6个空格
printf("%-9d\n",123);//一样需要占9个字符,123后面还有6个空格
printf("%+9d\n",123);//在123前面多上一个+,是可以0时跟左对齐同时进行的
printf("%09d\n",123);//空格地方变成0,000000123
return0;
}
width或prec | 含义 |
number | 最小字符数 |
* | 下一个参数是字符数 |
.number | 小数点后的位数 |
.* | 下一个参数是小数点后的位数 |
#include
intmain(intargc,charconst*argv[])
{
printf("%9.2f\n",123.0);//占据9个字符且其中有两位小数
printf("%*d\n",6,123);//让格式更灵活,6这个数是会替换到*号上的,具体效果就是占据6个字符
return0;
}
类型修饰 | 含义 |
hh | 单个字节 |
h | short |
l | long |
ll | long long |
L | long double |
#include
intmain(intargc,charconst*argv[])
{
printf("%hhd\n",12345);//只能输入单个字符,输出多个报错
printf("%hhd\n",(char)12345);//但可以强制类型转换,可以得到57的结果
printf("%9.2f\n",123.0);
return0;
}
type | 用于 | type | 用于 |
i或者d | int | g | float |
u | unsigned int | G | float |
o | 八进制 | a或者A | 十六进制浮点 |
x | 十六进制 | c | char |
X | 字母大写的十六进制 | s | 字符串 |
f或者F | float,6 | p | 指针 |
e或者E | 指数 | n | 输入/写出的个数 |
#include
intmain(intargc,charconst*argv[])
{
intnum;
printf("hhd%n\n",(char)12345,&num);
printf(num);//以上的意思是,截至到%n为止前面有几位字符,会将多少位字符传递给指针&num的地址
//然后在下面num就会输出2,因为上面(char)12345的值输出是57,是2位数
return0;
}
scanf:%[flag]type
flag | 含义 | flag | 含义 |
* | 跳过 | l | long,double |
数字 | 最大字符数 | ll | long long |
hh | char | L | long double |
h | short |
type | 用于 | type | 用于 |
d | int | s | 字符串(单词) |
i | 整数,可能为十六进制或八进制可以将读入的八进制和十六进制格式的数值转化为十进制 | [...] | 所允许的字符 |
u | unsigned int | p | 指针 |
o | 八进制 | ||
x | 十六进制 | ||
a,e,f,g | float | ||
c | char |
printf和scanf的返回值
- 读入的项目数
- 输出的字符数
- 在要求严格的程序中,应该判断每次调用scanf或printf的返回值,从而了解程序运行中是否存在问题
13.1-3文件输入输出
文件输入输出
- 用>和<做重定向
FILE
- FILEfopen(const char**restrict path,const char*restrict mode);
- int fcolose(FILE*stream);
- fscanf(FILE,...)
- fprintf(FILE*,...)
- fprintf(FILE,...)
打开文件的标准代码
FILE*fp=fopen(“file(文件名)”,“r”);
if(fp){
fscanf(fp,...);
fcolose(fp);
}else{
...
}
-----------------------------------------------------------------
#include
intmain(intargc,charconst*argv[])
{
FILE*fp=fopen("12.in","r");
if( fp ){
intnum;
fscanf(fp,"%d",&num);
printf("%d\n",num);
fclose(fp);
}else{
printf("无法打开文件\n");
}
return0;
}
fopen
r | 打开只读 |
r+ | 打开读写,从文件头开始 |
w | 打开只写。如果不存在则新建,如果存在则清空 |
w+ | 打开读写。如果不存在则新建,如果存在则清空 |
a | 打开追加。如果不存在则新建,如果存在则从文件尾开始 |
..x | 只新建,如果文件已存在则不能打开(防止对文件造成破坏) |
13.1-3二进制文件
- 其实所有的文件最终都是二进制的
- 文件无非是用最简单的方式可以读写的文件
- more、tail
- cat
- vi
- 而二进制文件是需要专门的程序来读写的文件
- 文本文件的输入输出是格式化,可能经过转码
文本文件 VS 二进制文件
- Unix喜欢用文本文件来做数据存储和程序配置
- 交互式终端的出现使得人们夏欢用文本和计算机“talk”
- Unix的shell提供了一些读写文本的小程序
- windows喜欢二进制文件
- DOS是草根文化,并不继承和熟悉Unix文化
- PC刚开始的时候能力有限,DOS的能力更有限,二进制进行输入输出更接近底层
优劣:
- 文本的优势是方便人类读写,而且跨平台
- 文本的缺点是程序输入输出要经过格式化,开销大
- 二进制的缺点是人类读写困难,而且不跨平台
- int的大小不一致,大小端的问题
- 二进制的优点是程序读写快
程序为什么要文件
- 配置
- Unix用文本,Windows用注册表
- 数据
- 稍微有点量的数据都放数据库了
- 媒体
- 这个只能是二进制的
- 现实是,程序通过第三方库来读写文件,很少直接读写二进制文件了
二进制读写(可跳过,底层)
- size_t fread(voidrestrict ptr,size_t size,size_t nitems,FILErestrict stream);
- size_t fwrite(const voidrestruct ptr,size_t size,size_t nitems,FILErestrict stream);
- 注意FILE指针是最后一个参数
- 返回的是成功读写的字节数
为什么nitems
- 因为二进制文件的读写一般都是通过对一个结构变量的操作来进行的
- 于是nitem就是用于说明这次读写几个结构变量!
在文件中定位
- long ftell(FILE*stream);
- int fseek(FILE*stream,long offset,int whence);
- SEEK_SET:从头开始
- SEEK_CUR:从当前位置开始
- SEEK_END:从尾开始(倒过来)
#include
#include"student.h"
voidread(FILE*fp,intindex);
intmain(intgrac,charconstgrav[])
{
FILE*fp=fopen("student.data","r");
if( fp ) {
fseek(fp,0L,SEEK_END);
longsize=ftell(fp);
intnumber=size/sizeof(Student);
intindex=0;
printf("有%d个数据,你要看第几个:",number);
scanf("%d",&index);
read(fp,index-1);
fcolose(fp);
}
return0;
}
voidread(FILE*fp,intindex)
{
fseek(fp,index*sizeof(Student),SEEK_SET);
Studentstu;
if( fread(&stu,sizeof(Student),1,fp) ==1){
printf("第%d个学生:",index+1);
printf("\t姓名:%s\n",stu.name);
printf("\t性别:");
switch ( stu.gender ){
case0:printf("男\n");break;
case1:printf("女\n");break;
case1:printf("其他\n");break;
}
printf("\t年龄:%d\n",stu.age);
}
}
可移值性
- 这样的二进制文件不具有可移植性
- 在int为32位的机器上写成的数据文件无法直接在int为64的机器上正确读出
- 解决方法之一就是放弃使用int,而是使用typedef具有明确大小的类型
- 更好的方案是用文本
13.2*位运算
13.2-1按位运算
- C有这些按位运算的运算符:其实就是把整数的东西当做二进制来进行运算
·& | 按位的与 |
·| | 按位的或 |
·~ | 按位取反 |
·^ | 按位的异或 |
·<< | 左移 |
·>> | 右移 |
按位与&
- 其实就是两组二进制的数,对应的数字必须都为1,新形成的数对应的数才会是1,否则就是0
- F:1111 E:1110,FE的作用就是使得跟他对应形成新的数最低位为0
按位或|
- 也是两组二进制的数,对应的数字必须都为0,新形成的数对应的数才会是0,否则就是1。跟上面那个相反
按位取反~
#include
intmain(intargc,charconst*argv[])
{
unsignedcharc=0xAA;
printf("% c=%hhx\n",c);//aa
printf("%~c=%hhx\n",(char)~c);//按位取反 55
printf("%-c=%hhx\n",(char)-c);//补码 56
}
逻辑运算vs按位运算
- 对于逻辑运算,它只看到两个值:0和1
- 可以认为逻辑运算相当于把所有非0值都变成1,然后做按位运算
- 5&4——>4而5&&4——>1&1——>1
- 5|4——>5而5||4——>1|1——>1
- ~4——>3而!4——>!1——>0
按位异或^
- 两组二进制,上下位数值对应相等为0,上下不相等为1
- 做两次相同的异或运算数值就翻回去了
移位运算
左移<<
- i << j
- i中所有的位向左移动j个位置,而右边填入0
- 所有小于int的类型,移位以int的方式来做,结果是int
- x <<= 1 等价于x*=2
- x <<= n 等价于x*=2的n次方
#include
intmain(intargc,charconst*argv[])
{
unsignedcharc=0xA5;
printf(" c=%d\n",c);//165
printf("c<<=%d\n",c<<2);//660
return0;
}
右移>>
- i >> j
- 所有小于int的类型,移位以int的方式来做,结果是int
- 对于unsigned的类型,左边填入0
- 对于signed的类型,左边填入原来的最高位(保持符号不变)
- x >>= 1 等价于x/=2
- x >>= n 等价于x/=2的n次方
#include
intmain(intargc,charconst*argv[])
{
inta=0x80000000;
unsignedintb=0x80000000;
printf("a=%d\n",a);//-2147483648
printf("b=%u\n",b);//2147483648
printf("a>>1=%d\n",a>>1);//-1073741824
printf("b>>1=%u\n",b>>1);//1073741824
return0;
}
no zuo no die
- 移位的位数不要用负数,这是没有定义的行为
- x<<-2 //!!NO!!
13.2-3位运算例子
输出一个数的二进制
#include
intmain(intargc,charconst*argv[])
{
intnumber;
scanf("%d",&number);
number=0x55555555;//输出01010101...,其实就是16个01(32个值)
unsignedmask=1u<<31;//int被省略但是其实是有生效的
for(; mask ; mask>>=1 ){
printf("%d",number&mask?1:0);
}
printf("\n");
return0;
}
13.2-4位段
- 把一个int的若干位组合成一个结构
struct{
unsignedintleading : 3;//冒号后面的数字表示占几个比特
unsignedintFLAG1:1;
unsignedintFLAG2:1;
inttrailing:11;
};
- 可以直接用位段的成员名称来访问
- 比移位、与、或还方便
- 编译器会安排其中的位的排列,不具有可移植性
- 当所需的位超过一个int时会采用多个int