• 附录A printf、varargs与stdarg A.1 printf函数族


    C语言中经常被误解的3个常用工具:printf库函数族、varargs和stdarg工具。后两者主要用于编写那些随调用场合的不用,其参数的数目和类型也不同的函数。 
    printf函数族 
    #include
    main() {
        printf("Hello world\n");
    }
    printf函数的第一个参数是关于输出格式的说明,它是一个描述了输出格式的字符串。这个字符串遵循通常的C语言惯例,以空字符(即\0)结尾。我们把这个字符串写成字符串常量的形式(即用双引号括起来),就能够自动保证它以空字符结尾。
    printf函数是把数据写到标准输入,fprintf函数则可以把数据写到任何文件中。需要写入的特定文件,将作为fprintf函数的第一个参数,它必须是一个文件指针。因此 
    printf(stuff);
    从意义上来说就等效于 
    fprintf(stdout, stuff);
    sprintf函数的第1个参数是一个指向字符数组的指针,sprintf函数将把其输出数据写到这个字符数组中。编程人员应该确保这个数组足够大以容纳sprintf函数所生成的输出数据。sprintf函数其余的参数与printf函数的参数相同。sprintf函数生成的输出数据总是以空字符收尾,如果希望在输出数据中出现一个空字符,我们可以显示地使用%c格式项把它打印出来。
    这3个函数的返回值都是已传送的字符数。对于sprintf的情形,作为输出数据结束标志的空字符并不计入总的字符数。如果printf或fprintf在试图写入时出现一个I/O错误,将返回一个负值。因为格式字符串决定了其余参数的类型,而且可以到运行时才建立格式字符串,所以要检查printf函数的参数类型是否正确是异常困难的。 
    printf("%d\n", 0.1);
    printf("%g\n", 2);
    最后得到的结果可能毫无意义,而且在程序实际运行之前,这些错误既有可能不会被编译器检测到,而成为“漏网之鱼”。 
    fprintf("error\n");
    本意是使用fprintf函数输出一行出错信息到stderr,但是一时大意忘记写stderr,而fprintf函数会把格式字符串当做一个文件结构来处理,这种情况下就很可能出现内核转储的后果! 
    A.1.1 简单格式类型
    每个格式项都是以格式码结束。
    %d的含义是以十进制形式打印一个整数,例如 
    printf("2 + 2 = %d\n", 2 + 2);
    %u要求打印无符号十进制整数。 
    printf("%u\n", -37);
    char类型和short类型会被自动扩展为int类型,在把char类型视为有符号类型整数的机器上,这一点经常会引起令人吃惊的后果。 例如下面的例子:
    char c;
    c = -37;
    printf("%u\n", c);

    %o打印八进制整数 
    %x用于打印十六进制整数 
    八进制和十六进制总是作为无符号数处理。
    %s格式项用于打印字符串:与之对应的参数应是一个字符指针,待输出的字符起始于所指向的地址,直到出现一个空字符('\0')才终止。 
    int n = 108;
    printf("%d decimal = %o octal = %x hex\n", n, n, n);
    printf("There %s %d item%s in the list.\n", n != 1 ? "are" : "is",
    n, n != 1 ? "s" : "");

    printf(s);
    与 
    printf("%s", s);
    两者的含义并不相同。第一个例子将把字符串s中的任何%字符视为一个格式项的标志,因而其后的字符会被视为格式码。如果除%%之外的任何格式码在字符串s中出现,而后面又没有对应的参数,将会带来麻烦。而第二个例子将会打印出任何以空字符结尾的字符串。
    一个NULL指针并不指向任何实际的内存为止,它肯定不可能指向一个字符串。 
    printf("%s\n", NULL);

    printf("%c", c);
    等效于 
    putchar(c);
    %g、%f、%e这3个格式项用于打印浮点值。%g格式项在打印那些不需要按列对齐的浮点数时特别有用。
    printf("The decimal equivalent of '%c' is %d\n", '*', '*');
    但是前者的适应性和灵活性更好,能够把字符c的值嵌入某个更大的上下文中。
    它在打印出对应的数值时,会去掉该数值尾缀的零,保留6位有效数字。 
    printf("Pi = %g\n", 4 * atan(1.0));
    printf("%g %g %g %g\n", 1/1.0, 1/2.0, 1/3.0, 1/4.0, 0.0);
    注意,因为一个数中出现在前面的零对于精度没有贡献,输出的数值被四舍五入,而不是直接截断: 
    printf("%g\n", 2.0/3.0);
    printf("%g\n", 123456789.0);
    如果一个数的绝对值大于999999,%g会采用科学计数法来打印这样的数值。 
    当指数是-4时,这两种形式的长度就恰好相等。例如,0.000314159与3.14159e-04所占用的空间大小相同。对于比较小的数值,除非该数的指数小于或等于-5,%g格式项才会采用科学计数法来表示。因此, 
    printf("%g %g %g\n", 3.14159e-3, 3.14159e-4, 3.14159e-5);
    %e要求一律显式地使用指数形式:%e格式项将打印出小数点后6位有效数字,而并非如%g格式项那样打印出的数是总共6位有效数字。
    %f格式项则恰好相反,它强制禁止使用指数形式来表示浮点数。 
    printf("%f\n", 1e38);
    printf("%%d prints a decimal value\n");

    A.1.2 修饰符
    整数有3种类型:short、long(l)和正常长度 
    %ld、%lo、%lx和%lu 
    long size;
    ...
    printf("%d\n", size);
    在某些机器上能工作,某些机器上不能工作。
    宽度修饰符
    如果一个数值太大而不能被它所在的栏所容纳,那么它就会挤占同一行右侧紧邻数值的位置。 
    int i;
    for (i = 0; i <= 10; i++) {
        printf("%2d %2d *\n", i, i * i);
    }
    宽度修饰符对所有的格式码都有效。
     
    printf("%8%\n");

    精度修饰符的作用是控制一个数值的表示中将要出现的数字位数,或者用于限制将要打印的字符串中应该出现的字符数。精度修饰符包括一个小数点和小数点后面的一串数字。精度修饰符出现在%符号和宽度修饰符之后,格式码与长度修饰符之前。精度修饰符的确切含义与格式码有关。
    对于整数类型%d、%o、%x和%u,精度修饰符指定了打印数字的最少位数。如果待打印的数值不需要这么多位数来表示,就会在它的前面补上0. 
    printf("%.2d/%.2d/%.4d\n", 7, 14, 1789);

    对于%e、%E和%f格式项,精度修饰符制定了小数点后应该出现的数字位数。除非标志(Flag,我们马上将讨论到)另有说明,否则仅当精度大于0时打印的数值中才会实际出现小数点。 
    double pi;
    pi = 4 * atan(1.0);
    printf("%.0f %.1f %.2f %.3f %.6f %.10f\n", pi, pi, pi, pi, pi, pi);
    printf("%.0e %.1e %.2e %.10e\n", pi, pi, pi, pi, pi, pi);
    对于%g和%G格式项,精度修饰符指定了打印数值中的有效数字位数。除非标准另有说明,否则非有效数字的0将被去掉。如果小数点后不跟点后不跟数字,则小数点也将被删除。 
    printf("%.1g %.2g %.4g %.8g\n", 10/3.0, 10/3.0, 10/3.0, 10/3.0);

    char name[14];
    ...
    printf("... %.14s ...", ..., name, ...);

    对于%c和%%格式符,精度修饰符将被忽略。

    A.1.3 标志: 
    可以在%符号和域宽修饰符之间插入标志字符,以微调格式项的效果。 
    在显示宽度大于被显示位数时,数据尾部都以显示区的右端对齐,左端则被填充空白字符。
    *标志字符-的作用是,要求显示方式改为左端对齐,在右端填充空白字符。因此,仅当域宽
    存在时,标志字符-才有意义(否则,填充空白字符就无从谈起)。 
    char name[14];
    ...
    printf("... %-14s ...", ..., name, ...);
    *标志字符+的作用是,规定每个待打印的数值在输出时都应该以它的符号(正好或符号)作为
    第一个字符。因此,非负数打印出来后,应该在最前面有一个正号。标志字符+与标志字符-
    之间不存在任何联系。 
    printf("%+d %+d %+d\n", -5, 0, 5);

    *空白字符作为标志字符时,它的含义是:如果某数是一个非负数,就在它的前面插入一个空白
    字符。如果我们希望让固定栏内的数值向左对齐,而又不想用标志字符+,这一点就特别有用。
    如果标志字符+与空白字符同时出现在一个格式项中,最终的效果以标志字符+为准。例如: 
    int i;
    for (i = -3; i <= 3; i++) {
        print("% d\n", i);
    }
    如果我们希望在固定栏内按科学计数法打印数值,格式项% e和%+e要比正常的格式项%e有用的多。因为这时出现在非负数前面的正号(或者空白)保证了所有输出数值的小数点都会对齐。例如: 
    double x;
    for (x = -3; x <= 3; x++) {
        printf("% e %+e %e\n", x, x, x);
    }
    标志字符#的作用是对数值输出的格式进行微调,具体的方式与特定格式项有关。
    %#o的效果是打印数字以0开头,%#x的效果是打印数字以0x或者0X开头。
    0%o把数值0打印成00,%#o把数值0打印成0。
    标志字符对浮点数格式的影响有两方面:其一,它要求小数点必须被打印出来,即使小数点后没有数字也是如此;其二,如果用于%g或%G格式项,打印出的数值尾缀的0将不会被去掉。例如: 
    printf("%.0f %#.0f %g %#g\n", 3.0, 3.0, 3.0, 3.0);
    除了+和空白字符,其余的标志字符都是各自独立的。

    A.1.4 可变域宽与精度 
    #define NAMESIZE 14
    char name[NAMESIZE];
    ...
    printf("... %.14s ....", ...., name, ...);
    printf("... %.NAMESIZE ...", ..., name, ...); //no use,因为预处理器的作用范围不能到达字符串的内部。
    printf函数因此允许间接指定域宽和精度。要做到这一点,我们只需用*替换域宽修饰符或精度修饰符其中之一,或者两者都替换。在这种情况下,printf函数首先从参数列表中取得将要使用的域宽或精度的实际数值,然后使用该数值来完成打印任务。 
    printf("... %.*s ...", ..., NAMESIZE, name, ...);
    printf("%*.*s\n", 12, 5, str);
    与下式完全等效 
    printf("%12.5s\n", str);
    printf("%*%\n", n);
    如果*用于替换域宽修饰符,而与其对应的参数的值为负数,那么效果相当把负号作为-标志字符来处理。因此,上例中如果n为负数,输出结果首先是一个%符号,后面再跟n-1个空格。

    A.1.5 新增的格式码 
    %p用于以某种形式打印一个指针,具体的形式与特定的C语言实现有关(译注:一般是打印出该指针所指向的地址)
    %n用于之处已经打印的字符数,这个数被存储在对应参数(一个整型指针) 
    int n;
    printf("hello\n%n", &n);

    A.1.6 废止的格式码
    过去,要打印一个数值并在它前面填充0,唯一的办法就是使用标志字符0。标志字符0的作用是指定待打印的数值前应该填充0而不是空白字符。因此, 
    printf("%06d %06d\n", -37, 37);
    即使用精度修饰符也能达到这种效果: 
    printf("%.6d %.6d\n", -37, 37);
    在大多数场合,我们都可以用%.来替换%0,效果也非常接近。

  • 相关阅读:
    JVM探究
    萨特——治愈了迷茫的你吗
    LeetCode | 168.Excel表列名称
    蓝桥杯2024年第十五届省赛
    吐血总结:Python学习方向、发展副业求职全攻略(自学Python做副业,教你如何月入10000+)
    巅峰对决:英伟达 V100、A100/800、H100/800 GPU 对比
    概率论与数据统计学习:随机变量(一)——知识总结与C语言案例实现
    合并分支导致的问题
    树状数组&线段树 的奇妙用法
    怎么语音转文字?快来看看这些方法
  • 原文地址:https://blog.csdn.net/weixin_40186813/article/details/126056003