%d和%i的区别?%l %h和%d %o %u %x?%a和%A格式串?C语言是如何区分开%f和%lf的?为什么%lx会将lx作为格式串, %fx只会将f作为格式串?万能格式串?

目录

%d和%i的区别

%l %h和%d %o %u %x

%h数值可能不是预期

无符号版本

%a和%A格式串

C语言是如何区分开%f和%lf的?

为什么%lx会将lx作为格式串, %fx只会将f作为格式串?

万能格式串

格式串基本规则

格式

不同格式符

格式串安全

格式串详解

示例: 输出右对齐宽度为8个字符整形数据

格式串语法

浮点数小数位

不同进制

输出地址

带前缀的格式串

格式串大小写

看起来另类的格式串

格式串补零

宽度和精度可被指定?

废弃的格式符


%d和%i的区别

  • 用于printf格式串: 没有区别。
  • 用于scanf格式串:
    %d只用于十进制整数,%i 除此之外,还可以匹配八进制、十六进制表示的整数。
    即:012用%d解析为12, %i解析是10.

%l %h和%d %o %u %x

C语言%d对应整形,%hd和%ld对应短整形和长整形,%lld代表long long int,%o代表八进制输出,%u是%d的无符号版本,格式串解析是在libc完成。

  • 不能有独立的%h或%l格式串,会和上面的组合格式产生冲突。
    • VS2019编译C代码只有%h格式符: 编译警告: 格式字符串“%h”未终止
  • %d/%o/%u/%x和%h或%l可以组合,比如%ho, %hu, %lo, %lx, %ld等。
  • 不能用大写模式,比如%L或者%LL, libc不识别

%h数值可能不是预期

在C语言中,假设short是2字节,%hd只会获取2个字节数据。如下第二句输出结果会变成-32768.

  • printf("%hd\n", 200);
  • printf("%hd\n", 32768);

无符号版本

  • 注意,%o和%x输出格式是无符号版本。

%a和%A格式串

C语言中,%a或%A用于在*printf函数中以十六进制浮点数的形式输出一个floatdouble类型的值。

输出格式一般是[-]0xh.hhhhp[+-]d,其中h.hhhh是十六进制的小数部分,p是分隔符,d是指数部分,底数是2.

        double value = 1.5;

        printf("%a\n", value);

        printf("%A\n", value);
输出结果:

        0x1.8p+0

        0X1.8P+0

C语言是如何区分开%f和%lf的?

区分开它们是实现在libc. 具体到scanf函数执行时,libc会检查格式串是否包含%符号,如果有,继续从已知的格式串中枚举,发现单个f,就把输入当成float类型大小(一般为4字节),发现是lf组合,就把输入当成double类型大小(一般8字节)。需要注意,libc是将输入的数值写入到4字节或8字节的参数地址里面。有时,需要注意传入参数的大小,避免这种行为造成越界。

但在printf输出时,libc又会将%f和%lf统一用double类型输出,前提是编译器默认先把float类型参数先转换成double, 达成统一输出的目的。

为什么%lx会将lx作为格式串, %fx只会将f作为格式串?

libc解析格式串是按预定义的格式,当看到%l的时候,会向后查看是否是d/x/i/f等字符,但看到%g的时候就知道后面不管是任何格式都不是支持的,就会当做浮点数解析,当然后面的x当做原字符输出。

万能格式串

C语言提供了大量的格式串,记错了很可能导致编译错误或者运行异常。作为喜欢"偷懒"的程序员:

  • C语言没有那么灵活,格式串需要严格遵循变量类型(一些隐式转换的除外)。
  • ObjC基本类型格式串follow C语言,为对象类型引入了%@格式串,只要类实现了description()函数返回字符串。
  • C++ iostream定义了基本类型默认输出形式,cout不需要特别指定格式(除非指定特定格式)。
  • 更高级的编程语言,如Java/C#/Python/JS就更不在话下,格式串只是可选的行为。
  • Go也提供多种格式串,但也提供%v格式可以按默认格式输出基本类型和结构体。
    %v: 代表数值value.
  • PHP 提供了内置函数var_dump函数可以方便输出变量基本信息,不需要特别使用格式串。
  • Rust 提供 {:#?} 输出任何非基本类型的类JSON值。
  • Python 类实现 __str__ 方法可作为对象默认输出对应的字符串。

格式串基本规则

编程语言毕竟是文本,只在ASCII码内无法表达更多的含义,如果不用新的函数或者关键字,就需要用“转义”的字符串格式来代表特定格式串。这就是格式串的来源。

格式

  • C/ObjC 语言的格式串比较典型,采用%符号作为格式类型前缀,避免编译器当做普通%字符。此方法有一定优势,同一个内存变量可以用不同%格式表达,可以随心控制。
  • C++除了继承它,iostream提供了新的设定模式,形如ios setw/setprecision/fixed等属性,它依托输入输出流对象属性。C++20引入了std::format()集成了格式化的优点,颇有C#风格,例如:
    • std::format("Age: {}, sex: {}", 28, 1) // 输出Age: 28, sex: 1
    • std::format("{1}, {0}, {1}", 1, 2) // 输出2, 1, 2
      • C++20也提供了自定义类型parse/format方法std::formatter类。
  • C#改进了C语言必须写%格式符的问题,用{0} {1}表达不同变量,也被称为复合格式化。C# 6.0之后更甚,0和1都不用输入,利用字符串前添加$将变量放入{}即可。
    • 注意,占位符可以按任意顺序,也可以多次使用。
  • Python除了类似C语言的%格式串,还提供字符串的format方法,Python 3.6以上还提供f-string创建格式串,它和C#的{}占位符类似,例如f"name: {name}, sex: {sex}".
    • %格式串需要在真实变量前统一加%,稍显繁琐,例如print("%d %d", % (a, b)).
    • Python 2.6开始支持字符串format方法,虽然也可以完成f-string同样的功能,但稍显繁琐:
      What’s New in Python 2.6
    • f-string格式串还支持插入函数,例如f"{len(str)}"是str的长度。
  • Swift也提供了一种简约的字符串插值方式,形如:let s3 = "\(str1) \(str2)"
  • Rust使用{}格式实现字符串插值。
  • Java对于简约格式串的支持比较落后,Java 15开始才支持formatted方法,但其方便程度只能和原始的C语言%格式串属于一个级别。
  • VB 格式串特立独行,利用Format函数,通过传递一些特别的格式字符达到特定显示。
    • 数值类型:例如 0代表显示字符或者0,#代表显示字符或不显示,%代表百分比格式。
      Format(123, "00000") 会输出:"00123" (实际显示去掉两边的引号)
    • 字符格式类型:例如 @代表显示字符或者空格,&代表显示字符或者不显示。
      Format("xyz", "@@@@") 会展示:" xyz" (实际显示去掉两边的引号)

不同格式符

  • C语言%d对应整形,%hd和%ld对应短整形和长整形。格式串解析是在libc完成。
    • 不能有%h或%l格式串,会产生冲突。
    • VS2019编译C代码只有%h格式符: 编译警告: 格式字符串“%h”未终止
  • 如果新增格式符,可以选择没有用过的符号。

格式串安全

  • C语言可以自由使用格式化,且printf第一个参数为const char *也可有程序员指定,形如printf(s)有很大风险。可以构造字符串s中的格式符,误导程序去访问特定内存。scanf同样可以构造带特定格式,产生一些有趣或危险的结果。

格式串详解

不同语言格式串五花八门,究其本质,无非是如何处理格式串和变量对应关系。

示例: 输出右对齐宽度为8个字符整形数据

  • C/ObjC/Java/Ruby/Go
    • %8d
      直接依赖库函数解析格式串,区分格式符前缀%、对齐长度、对齐方向、类型等,做填充、对齐操作。
  • C++
    • std::setw(8)和std::right
      C++ 输出流对象提供方法,设置对齐长度和对齐方向。
  • C#
    • "{0,8:D}"
      和C语言风格很像,不过用新的展示格式,冒号分隔。
  • Python
    • {num:8d}
      最接近C语言风格。
  • Rust
    • {:8}
  • VB.NET
    • String.Format("{0,8}", num)
  • Pascal
    • 整型a输出宽度为8:a:8

格式串语法

  • C语言: %[flag][min width][.precision][length]format
    • min width/precision/length在不同语境部分可选或要忽略
  • Java: %[arguments_index$][flags][field width][.precision]format

浮点数小数位

如下示例保留两位小数:

  • 仓颉:float_var.format(".2")
  • C/ObjC/C++: "%.2f"
  • Pascal:float_var:0:2

不同进制

  • C/ObjC/C++ %o和%x代表八进制和十六进制。
  • C# {:X}代表十六进制,例如$"{12:X}" 输出C.
  • Rust {:x} 代表十六进制。

输出地址

  • C/ObjC/C++ 用 %p 输出变量地址。
  • Rust {:p} 输出变量地址。

带前缀的格式串

  • C/ObjC/C++ 允许用%#o和%#x分别代表加前缀的八进制和十六进制。
    即,15的%#o格式是017, %#x格式为0xf. 当然,%#X对应为0XF.

格式串大小写

  • C语言%g/%e代表浮点数,大写%G和%E功能一样,但表达指数的E用大写,而非前面的小写。

看起来另类的格式串

  • C语言“%8%”会输出一个百分号%, 且左侧有7个空格。
  • C语言%n代表已经输出的字节个数。
    printf("hello我%n\n", &b); // 假设字符串UTF-8编码,b == 8

格式串补零

  • C语言整数格式符%d/%o/%x/%u也可指定精度代表最少位数,最终输出位数不足,会补0.
    printf("%.2d/%.2d/%.4d\n", 2, 12, 12345); // 02/12/12345
  • %02d和%.2d一样可以补0.

宽度和精度可被指定?

  • C语言可以动态指定格式串宽度和精度:
    printf("%.*d\n", 2, 1); // 输出宽度是2,输出01,宽度2也可以用变量传入

废弃的格式符

  • C语言早期,%D指代%ld, %O指代%lo, %X指代%lx, 但后来被发现其实%X也可以指代十六进制大写形式,后来这三个大写格式符被抛弃了。


若文章对您有帮助,欢迎关注 程序员小迷 。助您在编程路上越走越好!

微风不燥,阳光正好,你就像风一样经过这里,愿你停留的片刻温暖舒心。

我是 程序员小迷 (致力于C、C++、C#、Android、iOS、Java、Kotlin、Objective-C、Swift、Shell、JavaScript、TypeScript、Python等编程技术的技巧经验分享),若作品对您有帮助,请关注、分享、点赞、收藏、在看、喜欢,您的支持是我们为您提供帮助的最大动力。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值