首页 > 其他分享 >printf() 格式字符串的使用方法

printf() 格式字符串的使用方法

时间:2024-06-05 11:33:14浏览次数:23  
标签:main return int printf 字符串 格式 include

printf() 是 C 语言中一个非常重要的函数,它的核心功能是打印格式化的字符串。而其中的关键则是第一个参数——格式字符串(format string)。虽然大多数人都会使用格式字符串,但一些细节可能未必了解。本文将详细说明格式字符串的使用方法。

格式字符串(format string)

格式字符串是包含转换规范(conversion specification)的字符串。每一个转换规范都对应于 printf() 调用时从第二个开始的参数,说明要如何呈现该参数的内容,比如浮点数数据要打印几位小数等。每一个转换规范的指定方法如下:

%[旗标][宽度][.精确度][长度修饰符]格式代码

其中只有开头的百分比符号和最后的格式代码(format code)是必要的,其余项目都可视需要再加上。

格式代码(format code)

格式代码有许多种,都以单一英文字母表示。我们先以最简单、用于转换字符串数据的 s 为例,并借此说明转换规范中其他项目的用法。

转换字符串的格式代码——s
#include <stdio.h>

int main(){
  char s[] = "hello";
  printf("%s:\n", s);

  return 0;
}

转换规范 %s 会将对应的字符串类型参数带入,所以实际打印的内容为:

hello:

我们故意在转换规范后加一个 ':',以便能看出单一参数转换后的结束位置。

宽度(width)

如果你希望打印出像是表格的样式,让个别参数占据特定栏宽,可以在转换规范加上宽度(width),例如:

#include <stdio.h>

int main(){
  char s[] = "hello";
  printf("12345678901234567890\n");
  printf("%10s:\n", s);

  return 0;
}

在百分比符号和 s 间加上宽度 10 后,结果变成:

12345678901234567890
     hello:

我们特意先打印出一列序号让大家方便看出打印位置,你可以看到 "hello" 的前面多了 5 个空格,这是因为宽度是 10,但 "hello" 只有 5 个字符,不足的部分默认会在左边补上空白字符。

你也可以用星号 * 指定宽度,这样就可以通过额外的整数参数来指定宽度,例如:

#include <stdio.h>

int main(){
  char s[] = "hello";
  printf("12345678901234567890\n");
  printf("%*s:\n", 10, s);

  return 0;
}

注意到用来提供宽度的参数必须在要打印的数据前,结果如下:

12345678901234567890
     hello:

旗标(flags)

如果你希望字符串长度不足时把空白补在右边,可以加上 - 旗标:

#include <stdio.h>

int main(){
  char s[] = "hello";
  printf("12345678901234567890\n");
  printf("%-10s:\n", s);

  return 0;
}

结果如下:

12345678901234567890
hello     :

- 旗标表示要将数据靠左对齐栏位开头。

精确度(precision)

如果数据的长度超过指定的宽度,转换后并不会截掉超过的部分,仍然会打印出来,例如:

#include <stdio.h>

int main(){
  char s[] = "hello";
  printf("12345678901234567890\n");
  printf("%4s:\n", s);

  return 0;
}

虽然指定的宽度是 4,但仍然会打印出完整 5 个字符的内容。如果你希望打印时不要超过栏宽,可以在转换规范加上精确度(precision),例如:

#include <stdio.h>

int main(){
  char s[] = "hello";
  printf("12345678901234567890\n");
  printf("%4.4s:\n", s);

  return 0;
}

有小数点开头的就是精确度,表示最多只要打印原字符串内的前几个字符,因此虽然字符串的长度是 5,但因为精确度是 4,所以只会打印前 4 个字符:

12345678901234567890
hell:

精确度也一样可以用星号 * 表示要由参数来决定实际的位数,例如:

#include <stdio.h>

int main(){
  char s[] = "hello";
  printf("12345678901234567890\n");
  printf("%4.*s:\n", 4, s);

  return 0;
}

会得到一样的结果。

以 10 进位呈现有号整整数的格式代码——d/i

如果要打印的是有号整数(signed integer),可以用格式代码 di 转换成 10 进位数字:

printf("%10d:\n", 300);
printf("%-10d:\n", -300);

结果如下:

       300:
-300      :
旗标

如果希望正数的前方显示 + 号,可以加上 + 旗标:

printf("%+10d:\n", 300);

结果如下:

      +300:

你也可以加上 0 旗标,改用 '0' 填补不足宽度的部分:

printf("%010d:\n", -300);
printf("%-010d:\n", 300);

结果如下:

-000000300:
300       :

请特别留意正负号现在会出现在最左端,另外如果有使用 - 旗标,右侧空位并不会补 0,否则会造成误解。

如果你不想显示正号,但又希望若是正数,可以在符号的位置空一格,那么可以使用空白字符旗标:

printf("% 010d:\n", 300);

结果如下:

 000000300:
精确度

你也可以使用精确度来指定最少要显示的位数,默认为 1,若实际的位数不足,会在前面补 0

printf("%10.6d:\n", 300);
printf("%010.6d:\n", 300);

结果如下:

    000300:
    000300:

要注意的是,与 0 旗标并用的话,不足宽度的部分会补空白,不是补 0

另外,如果转换后的值是 0,而且精确度也是 0,那就不会打印任何内容。

长度修饰符(length modifier)

如果格式代码 d/i 对应的参数是 long 类型,就必须要在格式代码前加上 l 长度修饰符,否则当数值超过 int 范围时就会出错:

#include <stdio.h>

int main(){
  long l1 = 300l;
  long l2 = 0x1FFFFFFFFl; //8589934591
  printf("int is %d bytes.\n", sizeof(int));
  printf("long is %d bytes.\n", sizeof(long));
  printf("%d:\n", l1);
  printf("%d:\n", l2);
  printf("%ld:\n", l2);

  return 0;
}

结果如下:

int is 4 bytes.
long is 8 bytes.
300:
-1:
8589934591:

由于 l2 超过 int 的范围,所以若是用格式代码 d 转换,只会取最低的 4 个字节 0xFFFFFFFF,转换结果就变成 -1 了,只要加上 l 长度修饰符,就会取得完整的 long 数据转换成正确的值了。

在某些平台或编译器上,intlong 占的字节数一样,加或不加 l 长度修饰符结果虽然一样,但为了程序的相容性,请都还是养成加上长度修饰符的好习惯。

如果数据是 short,就要使用 h 长度修饰符;若数据是 long long,就要改用 ll 长度修饰符。

以 10 进位呈现无号整数的格式代码——u

格式代码 u 的用法和 d 一样,只是它会把对应的参数视为无号整数,例如:

#include <stdio.h>

int main(){
  int i1 = 300;
  int i2 = -1;
  unsigned int i3 = 0xFFFFFFFF;
  unsigned long l = 0xFFFFFFFFFFFFFFFFl; //8589934591
  printf("%u:\n", i1);
  printf("%u:\n", i2);
  printf("%d:\n", i3);
  printf("%u:\n", i3);
  printf("%u:\n", l);
  printf("%lu:\n", l);

  return 0;
}

结果如下:

300:
4294967295:
-1:
4294967295:
4294967295:
18446744073709551615:

你可以看到 i2 虽然是 -1,但用格式代码 u 打印却是正整数;反之,i3 虽然是无号整数,但是若是使用格式代码 d 打印,它会当成是有号整数处理,反而打印出 -1 了。

对于无号长整数,一样要加上 l 长度修饰符,否则只会截取到部分数据,打印出错误的值。

以 16 进位呈现无号整数的格式代码——x/X

x/X 的用法就如同 u,但是会用 16 进位的格式,例如:

#include <stdio.h>

int main(){
  int i1 = 300;
  int i2 = -1;
  unsigned long l = 0xFFFFFFFFFFFFFFFFl; //8589934591
  printf("%08x:\n", i1);
  printf("%X:\n", i2);
  printf("%X:\n", l);
  printf("%lx:\n", l);

  return 0;
}

结果如下,Xx 的差别就是使用大写还是小写 a~z 英文字母来表示 10 进位的 10~15:

0000012c:
FFFFFFFF:
FFFFFFFF:
ffffffff:
替代格式(alternative implementation)旗标——#

如果加上 # 旗标,就会额外加上 '0x' 或是 '0X' 字首,让打印结果更清楚是 16 进位。

printf("%#x:\n", i2);

结果如下:

0xffffffff:

以 10 进位呈现双精度浮点数的格式代码——f/F

fF 功能相同,可用于打印浮点数,这时精确度指的是要打印的小数位数,会以四舍五入的方式截掉多余的位数,没有指定精确度时,默认是 6 位。要特别留意的是小数点也会占掉一个字符,指定宽度时要算进去。范例如下:

#include <stdio.h>
#include <math.h>

int main(){
  printf("%10f:\n", 300.0);
  printf("%010.0f:\n", 300.3f);
  printf("%#10.0f:\n", 300.3);
  printf("%010.4f:\n", log2(3));

  return 0;
}

结果如下:

300.000000:
0000000300:
      300.:
00001.5850:

若精确度设为 0,因为没有小数,默认就不会打印小数点,但只要加上替代格式旗标 #,就还是会打印小数点,明确表示这是浮点数。

由于默认引数类型提升规则的关系,传给 printf()float 数据会先被转成 double 才传入 printf(),因此虽然 f 格式代码处理的对象是 double,但传入 float 类型的数据也能正确处理。

如果数据是 long double,就要加上 L 长度修饰符。

以科学记号显示双精度浮点数的格式代码——e/E

e/E 功能同 f/F,但改成以科学记号形式,其中 e/E 的差异就在于用 'e' 还是 'E' 表示指数,指数部分至少会有 2 位数字:

#include <stdio.h>
#include <math.h>

int main(){
  printf("%10e:\n", 30.0);
  printf("%010.0e:\n", 300.3f);
  printf("%#10.0E:\n", 300.3);
  printf("%010.4E:\n", log2(3));

  return 0;
}

结果如下,注意到精确度设定的是实数的小数位数,指数的位数无法变更:

3.000000e+01:
000003e+02:
    3.E+02:
1.5850E+00:

在 Windows 平台上,不知道是什么原因,指数至少会有 3 位数。如果希望能和其他平台一致,可以使用以下函数设定成 2 位:

_set_output_format(_TWO_DIGIT_EXPONENT);

本文都假设采用 C 语言标准规格,指数至少 2 位。

以精简格式显示双精度浮点数格式代码——g/G

这种格式会根据转换的数值从 f/Fe/E 挑选格式使用,假设精确度是 P,若未指定精确度时默认是 6,若精确度设为 0,会自动调升为 1;而使用 e/E 格式转换后的指数部分为 X,规则如下:

  • P > X ≧ -4,就以精确度 P - 1 - X 采用 f/F 格式。
  • 否则就以精确度 P - 1 依照格式代码大小写套用 e/E 格式。

请看以下例子:

#include <stdio.h>

int main(){
  printf("e:%10.4e\n", 10.12345);
  printf("f:%10.4f\n", 10.12345);
  printf("g:%10.4g\n", 10.12345);
  return 0;
}

结果如下:

e:1.0123e+01
f:   10.1235
g:     10.12

在这个例子中,P 是 4,X 是 1,符合 P > X ≧ -4 的条件,所以会套用 f/F 格式,并设定精确度为 4 - 1 - 1,也就是 2,因此小数有 2 位。若是将例子改成以下:

#include <stdio.h>

int main(){
  printf("e:%10.4e\n", 0.000012345);
  printf("f:%10.4f\n", 0.000012345);
  printf("g:%10.4g\n", 0.000012345);
  return 0;
}

结果就会变成:

e:1.2345e-05
f:    0.0000
g: 1.235e-05

这是因为虽然 P 还是 4,但 X 是 -5,不再符合 P > X ≧ -4 的条件,所以会套用 e 格式,并设定精确度为 4 - 1,也就是 3,因此以小数 3 位的科学记号表示法呈现。

有些书籍或是教学文章说 g/G 格式是挑选 e/Ef/F 两者转换后较短的结果,这并不正确,从上面的例子就可以看到不但不一定是采用较短的结果,连精确度都不一样。

g/G 格式还有一个很重要的特色就是会帮你把小数尾端的 0 自动去除,例如:

#include <stdio.h>

int main(){
  printf("e:%10.6e\n", 0.55);
  printf("f:%10.6f\n", 0.55);
  printf("g:%10.6g\n", 0.55);
  return 0;
}

结果如下:

e:5.500000e-01
f:  0.550000
g:      0.55

P 是 6,X 是 1,所以 g 格式的精确度应该是 6 - 1 - 1 为 4,但是实际看到的小数只有 2 位,因为尾端的 00 被删除了。如果要保留小数尾端的 0,可以加上替代格式旗标 #

#include <stdio.h>

int main(){
  printf("e:%10.6e\n", 0.55);
  printf("f:%10.6f\n", 0.55);
  printf("g:%#10.6g\n", 0.55);
  return 0;
}

结尾的 0 就会出现了:

e:5.500000e-01
f:  0.550000
g:  0.550000

以字符呈现整数的格式代码——c

这个格式会将对应的参数先转型为 unsigned char,再显示对应的字符:

#include <stdio.h>

int main(){
  printf("%c\n", 65);
  printf("%c\n", 'A');

  return 0;
}

不论传入整数或是字符,都可以正常显示:

A
A

显示指针的格式代码——p

如果需要打印变量的地址,那 p 格式就非常好用:

#include <stdio.h>

int main(){
  int a = 20;
  printf("%p\n", &a);

  return 0;
}

会以 16 进位格式打印地址:

0X000000000061FE1C

显示目前转换字符数的格式代码——n

如果想知道已经处理了多少字符,可以使用 n 格式。与之前说明过的格式代码不同,对应的参数必须是指针,它会将字符数放入所指向的地址:

#include <stdio.h>

int main(){
  int numOfChars;

  printf("1234567890\n");
  printf("%5d :%n\n", 10, &numOfChars);
  printf("%5d\n", numOfChars);

  return 0;
}

结果如下,n 不会打印任何字符:

1234567890
   10 :
    7

由于到 ':' 共 7 个字符,因此会将 7 写入 numOfChars 变量内。

在 Arduino 中使用 printf()

如果你想在 Arduino 中使用 printf(),会发现在串口监视器窗口中看不到任何输出,这是因为 printf() 是输出到 stdout,而不是串口。

使用 sprintf()

要将 stdout 设置成串口比较费工,我们可以改用 sprintf() 先将格式化输出的结果放置在自定义的缓冲区中,再使用 Serial.println() 送至串口即可:

void setup() {
  // put your setup code here, to run once:
  char buf[40];

  Serial.begin(9600);
  sprintf(buf, "%10d, %06.3f", 20, 3.14159);
  Serial.println(buf);
}

void loop() {
  // put your main code here, to run repeatedly:

}

串口监视器窗口看到的结果如下:

        20,      ?

咦,浮点数的结果怎么变问号了?这是因为 Arduino UNO 的 AVR 芯片工具链默认链接的是精简版库,为了减少程序代码的大小,所以并不支持格式代码 f/F

让 sprintf() 支持完整的格式

只要加上必要的编译器选项,就可以链接支持浮点数格式的库:

compiler.c.elf.extra_flags=-Wl,-u,vfprintf -lprintf_flt -lm

你可以加在 Arduino 安装文件夹下 \hardware\arduino\avr 路径的 platform.txt 文件中,也可以在同路径下新增 platform.local.txt 文件,并在此文件中加入上述编译器选项,后者的好处是可以将自定义的选项独立出来,不会跟默认的选项混在一起。重新编译后就可以看到正确的结果了:

        20, 03.142

不过这样的功能是要付出代价的,不支持浮点数格式时代码大小如下:

草稿代码使用了 3028 bytes (9%) 的程序存储空间。上限为 32256 bytes。
全局变量使用了 200 bytes (9%) 的动态内存,剩余 1848 bytes 给局部变量。上限为 2048 bytes 。

支持浮点数格式后的代码大小为:

草稿代码使用了 4500 bytes (13%) 的程序存储空间。上限为 32256 bytes。
全局变量使用了 200 bytes (9%) 的动态内存,剩余 1848 bytes 给局部变量。上限为 2048 bytes 。

程序代码足足多了近 1.5KB。

使用 dtostrf()/dtostre()

如果刚刚那 1.5K 一定要省,可以改用 dtostrf()/dtostre() 来实现 f/e 格式的功能,例如:

void setup() {
  // put your setup code here, to run once:
  char buf[40];

  Serial.begin(9600);
  dtostrf(3.14159, 6, 3, buf);
  Serial.println(buf);
  dtostre(3.14159, buf, 3, DTOSTR_PLUS_SIGN);
  Serial.println(buf);
}

void loop() {
  // put your main code here, to run repeatedly:

}

结果如下:

 3.142
+3.142e+00

这两个函数的说明可以在 AVR 的参考网页找到,你也可以在 Arduino 安装文件夹下 hardware\tools\avr\avr\includestdlib.h 文件中找到,不过要注意的是 Arduino 给 dtostre() 的旗标定义名称是 DTOSTR_XXXX,不是 AVR 网页中的 DTOSTRE_XXXX,不要弄错。使用这两个函数的代码大小为:

草稿代码使用了 3412 bytes (10%) 的程序存储空间。上限为 32256 bytes。
全局变量使用了 188 bytes (9%) 的动态内存,剩余 1860 bytes 给局部变量。上限为 2048 bytes 。

可以看到少了 1KB 了。

标签:main,return,int,printf,字符串,格式,include
From: https://blog.csdn.net/mzgxinhua/article/details/139393560

相关文章

  • ASP.NET Web应用程序升级最新的MSBuild格式后,Visual Studio 2022中如何调试?
    摘要把ASP.NET的Web应用程序,Project文件从<ProjectToolsVersion="12.0"DefaultTargets="Build"xmlns="http://schemas.microsoft.com/developer/msbuild/2003">改为<ProjectSdk="Microsoft.NET.Sdk.Web">之后,升级成了最新的格式之后,如......
  • ASP.NET Web应用程序升级最新的MSBuild格式后,Visual Studio 2022中如何调试?
    摘要把ASP.NET的Web应用程序,Project文件从<ProjectToolsVersion="12.0"DefaultTargets="Build"xmlns="http://schemas.microsoft.com/developer/msbuild/2003">改为<ProjectSdk="Microsoft.NET.Sdk.Web">之后,升级成了最新的格式之后,如......
  • JSON 数据格式化方法
    文章目录数据介绍IDE或脚本格式化在线工具网址总结数据介绍JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式,它基于JavaScript编程语言的一个子集。尽管它起源于JavaScript,但JSON已经成为了一个完全独立于语言的文本格式,被广泛采用和应用于多种编程......
  • ### Python 字符串操作详解
    1.创建字符串使用引号创建字符串#单引号str1='Hello,World!'#双引号str2="Hello,World!"#三引号(可用于创建多行字符串)str3='''Hello,World!'''str4="""Hello,World!"""2.基本操作字符串连接str1=&quo......
  • C语言杂谈:从Hello world说起 #include| main| printf| return
    #include<stdio.h>intmain(){ printf("Hellowworld"); return0;}        打印出“Helloworld”的这个程序相信每个人都是见过的,这段代码非常的简单,没有调用函数,没有使用指针,没有各种杂七杂八的东西,但我相信,第一次看见这个代码的朋友一定会有很多疑问。 ......
  • 【每周例题】C++ 力扣 旋转字符串
    旋转字符串 题目旋转字符串 题目分析方法1:模拟字符串1.采用双for循环去模拟字符串旋转,第一个for循环,模拟字符串循环位移;第二个for循环,进行逐个字符串检测2.使用if进行判断是否符合要求方法2:假设我们将goal字符串拆分为2个字符串,将其命名为R、L,我们将会得到以下式子go......
  • 将某xlsx文件转为UTF-8编码格式
    问题:        在使用jupyter对数据进行处理时,导入的文件可能不是UTF-8编码格式,这时就要将文件转为UTF-8。处理:    (1)将文件使用excel打开。    (2)点击左上角文件    (3)选择其他格式,将文件类型选为csvUTF-8格式。    (4)再次将文......
  • 字符串的应用---合并
    准备:publicclassEmployee{publicintId{get;set;}publicstringName{get;set;}publicdoubleSalary{get;set;}}publicclassSeat{publicintId{get;set;}publ......
  • IDEA maven 项目 如何获取项目离线运行所需的全部依赖( .m2格式)
    背景:maven项目要将整个项目的依赖移植到某无法联网服务器进行测试,需要项目离线运行所需的全部依赖步骤:1. 首先需要有项目源码,解压后,使用IDEA Open Project 2. 在Settings中,配置settings.xml文件的完整路径,以及依赖文件夹的完整路径 setting.xml如果没有,可以复用下面的......
  • 字符函数和字符串函数
    目录1.字符分类函数2.字符转换函数3.strlen的使用和模拟实现4.strcpy的使⽤和模拟实现5.strcat的使用和模拟实现 6.strcmp的使用和模拟实现7. strncpy函数的使用8.strncat函数的使用9.strncmp函数的使用10.strstr的使用和模拟实现11.strtok函数的......