俗语说浅水淹死人,作为Hello World的第一门必修语法,格式化输出就是这样的存在。明明熟背在心,隔几个礼拜不print就只记得print和printf了,每次要用都要上网再查一次。干脆在这篇博文,简单归纳一下C语言、python两大块常用的格式化输出语法。

参考文章: 《printf()用法详解》 《简述strcpy、sprintf、memcpy》 《C/C++中volatile关键字详解》

C语言 printf sprintf fprintf

printf

最简单用法:

1
printf("Hello World")
和python的print和C++的cout不同,printf不能直接输出字符数据,如printf(a)这种写法是错误的,必须经过格式化参数输出,参数以下:
1
2
3
4
5
6
7
8
9
参数                  数据类型                  说明
%d(%i) int 十进制有符号32bits整数
%u unsigned int 无符号十进制整数
%o unsigned int 无符号8进制(octal)整数
%x(%X) unsigned int 无符号16进制整数(%x表示字母部分为小写,X为大写)
%f float 单精度浮点数(默认6位,自定义"%.nf")
%lf double 双精度浮点数(默认6位)
%c char 根据数字寻找对应ASCII码字符
%s char* 字符串
以上,用法均是printf("参数",对应数据类型数据)

sprintf strcpy()

字符串打印函数,printf是将数据打印到终端或者输出,而sprintf是将字符串打印到字符串变量;例如:

1
2
3
char test[12]; 
sprintf(test,"Hello World"); //Hello World打印到test中,但是不会输出到终端
printf("%s",test); //打印到终端
可能看到这里你会想到strcpy()函数,是的,两个都是字符串复制函数,理论上两者的效果是等效的:
1
2
3
4
5
char test1[12]; 
sprintf(test1,"Hello World1"); //复制函数
printf("%s",test1); //打印到终端

输出:Hello World1
同样,两个函数都可以实现两个字符串之间的值传递:
1
2
3
4
5
6
7
8
9
char a[16];
char b[16]="Hello Eden";
char c[16];
sprintf(a,"%s",b);
printf("%s",a); //打印到终端
strcpy(c,b);
printf("%s",c); //打印到终端

输出:Hello Eden;Hello Eden;
尽管两者的功能是相近的,但strcpy由于只是完成复制工作,其读写内存和操作复杂性都要比字符串打印要简单一些,因此strcpy的执行效率明显是高于sprintf的。此外还有基于内存空间传递的memcpy函数,是更高效的复制操作,暂不展开。 此外还有与strcpy函数相近的strncpy函数,使用时需要额外指定复制大小:
1
2
3
4
5
6
char a[15];
char b[15]="hello";
strncpy(a,b,3);
printf("%s",a);

输出:hel

fprintf

fprintf主要是用于文件操作

1
2
3
4
5
6
7
8
char name[10] = "Eden";
FILE *file;
file = fopen( "output.txt", "w" );
if( file != NULL )
fprintf(file, "Hello %s\n", name);
fclose(file);

响应:本地生成output.txt,打开看到字符串Hello Eden

Python print

对于Python的print就无需长篇大论叙述了,上述内容已经完成了很好的叙述基础,包括参数等都是高级程序语言一脉相承的。 最简单用法:

1
2
3
4
print("Hello World") #字符
#甚至无需格式化打印数据
a=10
print(a)
格式化输出一般用于既有文字叙述,又有变量数据的输出需要,例如:
1
2
3
Name="Eden"
Score=59
print("我的名字是:%s,我的成绩是:%d"%(Name,Score))
还有一种比较简单的格式化方法是采用format映射的方式,可以说是十分方便:
1
2
3
Name="Eden"
Score=59
print("我的名字是:{},我的成绩是:{}".format(Name,Score))
这种方法的好处是使用者无需关心参数数据类型是什么就能将变量映射到文本中,避免了因为错误参数导致输出错误结果的尴尬。

数据类型

记录了在系统中常用的数据类型重定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
typedef unsigned char       uint8;              // 无符号  8 bits
typedef unsigned short int uint16; // 无符号 16 bits
typedef unsigned int uint32; // 无符号 32 bits
typedef unsigned long long uint64; // 无符号 64 bits

typedef signed char int8; // 有符号 8 bits
typedef signed short int int16; // 有符号 16 bits
typedef signed int int32; // 有符号 32 bits
typedef signed long long int64; // 有符号 64 bits

typedef volatile uint8 vuint8; // 易变性修饰 无符号 8 bits
typedef volatile uint16 vuint16; // 易变性修饰 无符号 16 bits
typedef volatile uint32 vuint32; // 易变性修饰 无符号 32 bits
typedef volatile uint64 vuint64; // 易变性修饰 无符号 64 bits

typedef volatile int8 vint8; // 易变性修饰 有符号 8 bits
typedef volatile int16 vint16; // 易变性修饰 有符号 16 bits
typedef volatile int32 vint32; // 易变性修饰 有符号 32 bits
typedef volatile int64 vint64; // 易变性修饰 有符号 64 bits

主要说明两点: 1 有符号和无符号: 无符号表明数据全部用于表示值,例如uinit8可以表示8位数据,即0-2^8-1,所以赋值256是非法的;而如果是有符号,高位表示符号位,数据位就剩下7位,范围就分布在正负区间,int8范围应该是-128-127;
2 volatile是用于修饰变量的关键字,指明该变量能够被编译器意料之外的因素修改,如操作系统、多线程等,能避免编译器对其进行优化导致变量修改失效。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
void main()
{
int i = 10;
int a = i;

printf("i = %d", a);


__asm {
mov dword ptr [ebp-4], 20h // 通过汇编语句改变内存中 i 的值
}

int b = i;
printf("i = %d", b);
}
上述示例在Debug版本中(代码无优化,影响执行性能):分别为i=10、i=32,而在Release版本中:则为i=10、i=10,说明代码的优化造成了错误。改善的方法是采用volatile int i,那么无论哪个版本都能输出正确的i=10、i=32;既能使程序能够在多线程下工作,也不会丧失代码优化服务。一般说来,volatile用在如下的几个地方: 1、中断服务程序中修改的供其它程序检测的变量需要加volatile; 2、多任务环境下各任务间共享的标志应该加volatile; 3、存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能由不同意义;