C Primer Plus 第4章——第一篇

你该逆袭了

一、章节简介

函数:strlen( )
关键字:const
字符串
如何创建、存储字符串
如何使用 strlen( ) 函数获取字符串的长度
用C预处理器指令 #define 和 ANSIC 的 const 修饰符创建符号常量

二、字符串

(1)

//P73 程序清单4.2
#include <stdio.h>

#define PRAISE "you are an extraodinary being."
int main()
{
	char name[40] = { 0 };

	printf("What's your name? \n");
	scanf("%s", name);
	printf("Hello, %s. %s\n", name, PRAISE);   

	return 0;
}

在这里插入图片描述

(2) sizeof 以及 strlen() 计算字符串长度

//书P74 程序清单4.3
#include <stdio.h>
#include <string.h>  //提供 strlen() 函数的原型
#define PRAISE "you are an extraodinary being"

int main()
{
	char name[40] = { 0 };

	printf("What's your name? ");
	scanf("%s", name);
	printf("Hello, %s. %s\n", name, PRAISE);
	printf("Your name of %zd letters occupies  %zd memory cells.\n",
		strlen(name), sizeof name);
	
	//printf()语句分为两行(可以在参数之间断为两行,但是不要在双引号中的字符串中间断开)
	
	//name 数组的第12个单元存储空字符,strlen() 并未将其计入

	printf("The phrase of praise has %zd letters",
		strlen(PRAISE));
	printf("and occupies %zd memory cells.\n", sizeof PRAISE);

	//C99 和 C11 标准专门为 sizeof 运算符的返回类型添加了 %zd 转换说明,这对于 strlen() 同样适用。
	//对于早期的 C ,还要知道 sizeof 和 strlen()返回的实际类型(通常是 unsigned 或 unsigned long)。
	
	//对于 PRAISE,用 strlen() 得出的是字符串中的字符数(包括空格和标点符号)。
	//然而,sizeof 运算符给出的数更大,因为它把字符串末尾不可见的空字符也计算在内。

	return 0;
}

在这里插入图片描述

三、C预处理器 和 明示常量

(1):C预处理器

#define TAXRETE 0.015

编译程序时,程序中所有的 TAXRATE 都会被替换成 0.015。
这一过程被称为 编译时替换
在运行程序时,程序中所有的替换均已完成。
通常,这样定义的常量也称为 明示常量
(本书作者认为:”明示常量“相当于”符号常量“,书中经常混用这两个术语)

#define 指令可以还可以定义 字符 和 字符串常量 。
前者使用 单引号,后者使用 双引号。如下所示:

#define BEEP '\a'
#define THE 'T'
#define ESC '\033'
#define OPPS "Now you have done it!"

(2):明示常量 (本书作者认为:”明示常量“相当于”符号常量“,书中经常混用这两个术语)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

//书P80 程序清单4.5
#include <stdio.h>
#include <limits.h>  //整形限制
#include <float.h>   //浮点数限制

int main()
{
	printf("some number limits for this system:\n");
	printf("biggest int: %d\n", INT_MAX);
	printf("smallest long long: %lld\n", LLONG_MIN);
	printf("one byte = %d bits on this system.\n", CHAR_BIT);
	printf("largest double: %e\n", DBL_MAX);
	printf("smallest normal float: %e\n", FLT_MIN);
	printf("float precision = %d digits\n", FLT_DIG);
	printf("float epsilon = %e\n", FLT_EPSILON);

	return 0;
}

四、printf( )

1、printf( ) 的转换说明修饰符

(0):注意:float 参数的转换

C语言中,没有专门为 float 类型提供的转换说明符,但是 printf 函数的 %f 说明符足以处理 float 类型的数据。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(1):标记

在这里插入图片描述

1、-

‘-’ 待打印项左对齐。 即从字段的左侧开始打印该项 示例:“%-20s”

//标记
// -     待打印项左对齐。 即从字段的左侧开始打印该项  示例:"%-20s"

#include <stdio.h>

int main()
{
    printf("%-5s\n", "abcdef");
    printf("%-5s\n", "ab");
    printf("%5s\n", "abcdef");
    printf("%5s\n", "ab");
    return 0;
}

在这里插入图片描述

2、+

‘+’ 有符号值若为正,则在值前面显示加号;若为负,则在前面显示减号 示例:“%+6.2f”

//标记
// +     有符号值若为正,则在值前面显示加号;若为负,则在前面显示减号 示例:"%+6.2f"
#include <stdio.h>

int main()
{
    printf("%+6.2f\n", 123.234);   //+123.23
    printf("%+6.2f\n", 123.235);   //+123.23  
    printf("%+6.2f\n", 123.236);   //+123.24  
    printf("%+6.2f\n", -123.236);  //-123.24 

    return 0;
}

在这里插入图片描述

3、空格

空格 有符号值若为正,则在值前面显示前导空格(不显示任何符号);若为负,则在值前面显示减号;+ 标记覆盖一个空格

//标记
// 空格  有符号值若为正,则在值前面显示前导空格(不显示任何符号);
         //若为负,则在值前面显示减号
         //+ 标记覆盖一个空格

#include <stdio.h>

int main()
{ 
    printf("%6.2f\n", 123.236);
    printf("%6.2f\n", -123.236);

    printf("% 6.2f\n", 123.236);  
    printf("% 6.2f\n", -123.236);

    printf("%f\n", 123.236);
    printf("%f\n", -123.236);

    printf("% f\n", 123.236);
    printf("% f\n", -123.236);

    return 0;
}

在这里插入图片描述

4、#

‘#’ 把结果转换为另一种形式。
如果是 %o 格式,则以 0 开始;
如果是 %x 或 %X 格式,则以 0x 或 0X 开始;

对于所有的浮点格式, # 保证了即使后面没有任何数字,也打印一个小数点字符。
这句话可以这么理解:
通常情况下,如果一个浮点数没有小数部分,直接输出时可能不会显示小数点。但当使用了 # 标志后,无论该浮点数实际上有没有小数部分,都会强制显示出小数点。
在 VS2019 的环境下,即使没有小数部分,也会在末尾加上小数点的。比如 float a = 12;以 %f 打印结果就是 12.000000。

对于 %g 或 %G 格式,# 防止结果后面的 0 被删除
%g 格式用于以较短的输出宽度打印浮点数,它会根据数值的大小自动选择 %f 格式 或 %e 格式。
具体来说,当指数小于 -4 或者 大于等于精度时,会使用 %e 格式,否则,使用 %f 格式
使用 # 标志后,对于 %g 或 %G 格式,它会保留结果中可能被删除的尾部 0。
例如,如果一个浮点数的值实际上是 1.200000 ,在不使用 # 标志时可能会输出 1.2 ,而使用 # 标志时则会输出 1.200000 。
可以看到,在使用 # 标志后,原本可能被省略的尾部 0 被保留了下来。这样可以确保输出结果更精确地反映原始数值。需要注意的是, %g 格式仍然会去掉多余的零,至多保留六位有效数字。
如果数值的有效数字超过六位,它会采用科学计数法( %e 格式)并进行四舍五入到六位数字的处理。同时, %G 与 %g 类似,只是指数部分用大写 E 表示。

示例:“%#o” “%#8.0f” “%+#10.3e”

//标记
// # 把结果转换为另一种形式。
// 如果是 %o 格式,则以 0 开始;
// 如果是 %x 或 %X 格式,则以 0x 或 0X 开始;

// 对于所有的浮点格式, # 保证了即使后面没有任何数字,也打印一个小数点字符。  
// 这句话可以这么理解:
// 通常情况下,如果一个浮点数没有小数部分,直接输出时可能不会显示小数点。但当使用了 # 标志后,无论该浮点数实际上有没有小数部分,都会强制显示出小数点。
// 在 VS2019 的环境下,即使没有小数部分,也会在末尾加上小数点的。比如 float a = 12;以 %f 打印结果就是 12.000000。

// 对于 %g 或 %G 格式,# 防止结果后面的 0 被删除
// %g 格式用于以较短的输出宽度打印浮点数,它会根据数值的大小自动选择 %f 格式 或 %e 格式。
// 具体来说,当指数小于 -4 或者 大于等于精度时,会使用 %e 格式,否则,使用 %f 格式
//使用  #  标志后,对于  %g  或  %G  格式,它会保留结果中可能被删除的尾部 0。
//例如,如果一个浮点数的值实际上是  1.200000 ,在不使用  #  标志时可能会输出  1.2 ,而使用  #  标志时则会输出  1.200000 。
//可以看到,在使用  #  标志后,原本可能被省略的尾部 0 被保留了下来。这样可以确保输出结果更精确地反映原始数值。需要注意的是, %g  格式仍然会去掉多余的零,至多保留六位有效数字。
//如果数值的有效数字超过六位,它会采用科学计数法( %e  格式)并进行四舍五入到六位数字的处理。同时, %G  与  %g  类似,只是指数部分用大写  E  表示。

// 示例:"%#o" "%#8.0f" "%+#10.3e"

#include <stdio.h>

int main()
{
    float a = 12;

    printf("%o\n", 12);
    printf("%#o\n", 12);

    printf("%x\n", 12);
    printf("%#x\n", 12);

    printf("%X\n", 12);
    printf("%#X\n", 12);

    printf("%f\n", a);    
    printf("%#f\n", a);   

    printf("%#.2f\n", 12.455);
    printf("%#.2f\n", 12.456);

    printf("%g\n", 12.455);
    printf("%#g\n", 12.456);

    return 0;
}

在这里插入图片描述

#include <stdio.h>

int main() 
{
    double num1 = 1.2;
    double num2 = 1.200000;

    printf("Without #: %g, %g\n", num1, num2);
    printf("With #: %#g, %#g\n", num1, num2);

    return 0;
}

在这里插入图片描述

#include <stdio.h>

int main()
{
    float a = 1.20000;
    printf("%f\n", a);
    printf("%g\n", a);

    return 0;
}

在这里插入图片描述

#include <stdio.h>

int main()
{
    printf("%f\n", 12);    //结果:0.000000
    printf("%#f\n", 12);   //结果:0.000000

    //原因:在 C 语言中, printf("%f\n", 12);  这样的用法是不正确的。 %f  格式说明符用于打印浮点数,而您提供的  12  是一个整数。
 
    //当以错误的方式使用时,可能会得到不可预测或不符合预期的结果,这里输出  0.000000  就是这种错误使用导致的异常结果。

    //而  printf("%#f\n", 12);  同样也是错误的用法,也会得到不可预测的结果。

    //如果您想正确打印整数  12  ,应该使用 % d  格式说明符,即  printf("%d\n", 12);

    return 0;
}

在这里插入图片描述

5、0

对于数值格式,用前导 0 代替空格填充字段宽度。
对于整数格式,如果出现 - 标记 或者 指定精度,则忽略 0 标记。

什么是指定精度?
控制输出数字的 小数位数 或者 数字的总位数。
对于浮点数,指定精度可以控制小数点后的数字位数。例如,%.2f 表示将浮点数输出并保留两位小数。
对于整数,指定精度可以控制输出的数字的总位数。例如,%05d 表述将整数以至少5位数字输出,如果数字不足5位,则在前面用 0 补充。


#include <stdio.h>

int main()
{
    printf("%8d\n", 12345);      //结果:   12345
    printf("%08d\n", 12345);     //结果:00012345
    printf("%-08d\n", 12345);    //结果:12345       //出现 -  则忽略 0 标记
    
    printf("%.3f\n", 11.25);    //结果:11.250
    //这表示输出浮点数11.25,并确保小数点后有 3 位数字。
    //由于是11.25,实际输出会是:11.250
    return 0;
}
#include <stdio.h>

int main()
{
    printf("*%010.2f*\n", 1.2);  //*0000001.20*
    // .数字
    // 表示小数点右边数字的位数 
    //标志 0 
    //对于数值格式,用前导 0 代替 空格填充字段宽度
    
    printf("*%5.3d*\n", 6);     //*  006*
    // .数字 
    //对于整形转换,表示待打印数字的最小位数;如有必要,使用 前导 0 来达到这个位数
    //%5.3d 生成足够的前导 0 以满足最小位数的要求

    printf("*%08.3d*\n", 6);     //*     006*
    // 标记 0
    //对于整数格式,如果出现 - 标记 或者 指定精度,则忽略该标记
    // 8 是指定宽度为 8 ,又有前导 0 ,不足宽度为 8 的部分 用 0 代替 空格 填充字段宽度
    // 但是由于是 %d 整数格式,又有 .3 精度,所以,前导 0 被忽略了。

    return 0;
}

在这里插入图片描述

(2):数字

最小字段宽度
如果该字段不能容纳待打印的 数字 或 字符串 ,系统会使用更宽的字段
示例:“%4d”

//最小字段宽度
//如果该字段不能容纳待打印的 数字 或 字符串 ,系统会使用更宽的字段
//示例:"%4d"

#include <stdio.h>

int main()
{
    printf("%4d\n", 1);
    printf("%4d\n", 123456);

    printf("%4s\n", "a");
    printf("%4s\n", "abcdefgh");

    return 0;
}

在这里插入图片描述

(3):. 数字

精度
对于 %e、%E 和 %f 转换,表示小数点右边数字的位数。
对于 %g 和 %G 转换,表示有效数字最大位数。
对于 %s 转换,表示待打印字符的最大数量。
对于整形转换,表示待打印数字的最小位数。如有必要,使用前导 0 达到这个位数。
只使用 . 表示其后跟随一个 0 ,所以 %.f 和 %.0f 相同。
示例:"%5.2f"打印一个浮点数,字段宽度为 5 字符,其中小数点后有两位数字。

什么是 有效数字?
小数点本身并不是有效数字。
有效数字是指在数值中能够提供有意义信息的数字,通常包括所有确定的数字和一个不确定但估计的数字。
在确定有效数字时,我们只计算数字,小数点、正负号、指数符号等都不计入有效数字的数目。
例如,在数值12.345中,有效数字是1、2、3、4和5,小数点不是有效数字的一部分。
小数点的作用是区分整数部分和小数部分,它不携带数值上的信息,因此在计算有效数字时不会被计算在内。
当使用printf函数的g格式化选项时,小数点也不会影响有效数字的计数。例如,在 % .5g格式中,5指的是最多显示的数字数量,不包括小数点。
因此,对于数值11.256,所有数字1、1、2、5和6都被认为是有效数字,而小数点不被计算在内

// .数字
// 精度
// 对于 %e、%E 和 %f 转换,表示小数点右边数字的位数。
// 对于 %g 和 %G 转换,表示有效数字最大位数。
// 对于 %s 转换,表示待打印字符的最大数量。
// 对于整形转换,表示待打印数字的最小位数。如有必要,使用前导 0 达到这个位数。
// 只使用 . 表示其后跟随一个 0 ,所以 %.f 和 %.0f 相同。
// 示例:"%5.2f"打印一个浮点数,字段宽度为 5 字符,其中小数点后有两位数字。

//什么是 有效数字?
//小数点本身并不是有效数字。
// 有效数字是指在数值中能够提供有意义信息的数字,通常包括所有确定的数字和一个不确定但估计的数字。
// 在确定有效数字时,我们只计算数字,小数点、正负号、指数符号等都不计入有效数字的数目。
//例如,在数值12.345中,有效数字是1、2、3、4和5,小数点不是有效数字的一部分。
//小数点的作用是区分整数部分和小数部分,它不携带数值上的信息,因此在计算有效数字时不会被计算在内。
//当使用printf函数的g格式化选项时,小数点也不会影响有效数字的计数。例如,在 % .5g格式中,5指的是最多显示的数字数量,不包括小数点。
//因此,对于数值11.256,所有数字1、1、2、5和6都被认为是有效数字,而小数点不被计算在内

#include <stdio.h>

int main()
{
    printf("%.2f\n", 11.255);    //11.26   //正儿八经的使用 四舍五入
    printf("%.2f\n", 11.256);    //11.26   //正儿八经的使用 四舍五入

    printf("%.1e\n", 11.25689);  //1.1e+01
    printf("%.1e\n", 0.112);     //1.1e-01

 //%g 格式说明符用于以更紧凑的形式输出浮点数 
 //%g 格式的有效数字是指从第一个非零数字开始,知道满足所需精度的数字。
 //它会根据数值的大小和精度自动选择 %f 或者 %e 的输出方式,以更简洁清晰地展示数字。
 //具体来说,如果数值较大或者较小,会以科学计数法形式输出,并保证有效数字的位数能准确反映数值的精度;
 //如果数值的大小适中,则以 %f 形式输出,并去除末尾多余的零。
    printf("%.2g\n", 11.25);        //11      //由于最多允许 2 个有效数字,11.25 被四舍五入为 11   

    printf("%.3g\n", 11.26);        //11.3
    //这里,11.26 有 4 个有效数字(1、1、2、6)。 
    //%.3g 格式化选项要求输出最多 3 个有效数字,由于 6 是超过限制的第四个数字,并且它大于 5 ,
    //根据四舍五入规则,2 会被加 1 ,变成 3 。
    //因此,输出结果是 11.3,这是 11.26 四舍五入到最接近的十分位数的结果。

    printf("%.3g\n", 11.25);        //11.2    //允许最多 3 个有效数字
    //这个情况稍微复杂一些,因为 11.25 有 4 个有效数字(1、1、2、5)。
    //然而,%.3g 格式化选项要求最多 3 个有效数字。
    //由于 5 是超过限制的第 4 个数字,但是它正好等于 5 ,根据四舍五入规则, 2 后面没有数字影响它,因此 2 保持不变。
    //但是,%.3g 格式化选项的目的是减少有效数字的数量,而不是进行传统的四舍五入。
    //因此,它简单地省略了额外数字 5 ,而不是增加 2 的值。结果,输出是 11.2,而不是四舍五入后的 11.3。

    //简而言之,%.3g 格式 化选项的目的是输出最多 3 个有效数字,而它处理数字的方式可能与传统的四舍五入规则有所不同。
    //在 11.26 的例子中,由于超过限制的数字是 6 ,它触发了 四舍五入,
    //而 11.25 的例子中,尽管超过限制的数字是 5 ,但由于 %.3g 格式化选项的规则,它只是被省略了,没有处罚四舍五入。

    printf("%.4g\n", 11.25);        //11.25     //允许最多 4 个有效数字,因此 11.25 被完整输出为 11.25  
    printf("%.5g\n", 11.25);        //11.25     //允许最多 5 个有效数字,所以 11.25 和 11.2500 都输出为 11.25,11.256 则输出为 11.256
    printf("%.5g\n", 11.2500);      //11.25     //去除末尾多余的零
    printf("%.5g\n", 11.256);       //11.256    
    
    printf("%5s\n", "1");           //    1

    printf("%.5s\n", "1");          //1
    printf("%.5s\n", "123");        //123
    printf("%.5s\n", "12345");      //12345
    printf("%.5s\n", "123456789");  //12345

    printf("%.5d\n", 1);            //00001
    printf("%.5d\n", 123);          //00123
    printf("%.5d\n", 12345);        //12345
    printf("%.5d\n", 123456789);    //123456789

    printf("%.f\n", 11.25);         //11     对于 %e、%E 和 %f 转换,表示小数点右边数字的位数。
    //小数点右边的位数是 0 
    printf("%.0f\n", 11.25);        //11     对于 %e、%E 和 %f 转换,表示小数点右边数字的位数。
    //小数点右边的位数是 0

    return 0;
}

(4):h

和整形转换说明一起使用,表示 short int 或 unsigned short int 类型的值
示例:“%hu” “%hx” “%6.4hd”

// h 
// 和整形转换说明一起使用,表示 short int 或 unsigned short int 类型的值
// 示例:"%hu" "%hx" "%6.4hd"

#include <stdio.h>

int main()
{
    printf("%d\n", sizeof(short int));  // 2 字节

    printf("%d\n", sizeof(unsigned short int));  // 2 字节

    printf("%d\n", sizeof(int));   // 4 字节

    int a = 337588464;   //14 1F 30 F0 
                         //0001 0100 0001 1111 0011 0000 1111 0000

    printf("%hx\n", a);   //结果:30f0

    return 0;
}

(5):hh

和整形转换说明一起使用,表示 signed char 或 unsigned char 类型的值
示例:“%hhu” “%hhx” “%6.4hhd”

#include <stdio.h>

int main()
{
    printf("%hhd\n", 'A');   //65
    //以十进制形式输出有符号的字符(signed char)
    
    printf("%hhu\n", 'A');   //65 
    //以无符号十进制形式输出无符号的字符(unsigned char)
    //%hhu 这个格式化说明符用于输出无符号的字符(unsigned char)。
    //hh 前缀表示数据类型是 char。而 u 表示数据是无符号的。
    
    printf("%hhx\n", 'A');   //41
    //以十六进制形式输出无符号的字符(unsigned char)。
    //hh 前缀同样表示数据类型是 char ,而 x 表示十六进制格式。

    char c = -1;
    
    printf("%hhd\n", c);

    char a = 65;

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

    return 0;
}
#include <stdio.h>

int main()
{
    char a = 'A';         //十进制是 65 

    printf("%hhx\n", a);  //41

    return 0;
}

%hhx 是 C 语言中的一个格式化输出控制符,主要用于在 printf 函数中将 char 类型的数据以 十六进制 的形式输出。
这个控制符的主要作用是确保当使用 printf 函数处理单个字节的数据时,能够正确地以 十六进制 的形式输出,
而不被错误地解释为 大端 或 小端 字节顺序的字节序列。

在C语言中,当我们需要将单个字节的 char 类型数据以十六进制形式输出时,通常会使用 %hhx 格式控制符。
这是因为,如果不特别指明,printf 函数在默认情况下会将 char 类型提升为 int 类型处理,这可能导致数据以不同于预期的方式输出,
使用 %hhx 可以避免这种类型提升,确保每个字节都被单独处理并以 十六进制 形式输出。

#include <stdio.h>

int main() 
    {
        unsigned char hexVal;
        printf("请输入一个十六进制数: ");   
        scanf("%hhx", &hexVal);                     //41
        printf("你输入的数值是:%hhu\n", hexVal);   //65
        return 0;
    }
#include <stdio.h>

int main() 
    {
        unsigned char hexVal;
        printf("请输入一个十六进制数: ");   
        scanf("%hhx", &hexVal);                     //41
        printf("你输入的数值是:%hhu\n", hexVal);   //65
        return 0;
    }
#include <stdio.h>

int main() {
    printf("%d\n", sizeof(int));  // 4 字节
    
    int i = 65535; // 一个32位整数,值远大于无符号8位整数的最大值255
    
    //65535 转换成二进制 1111 1111 1111 1111  
    //255   转换成二进制 1111 1111
    
    printf("%hhx\n", i);     //结果:ff    // 错误使用,可能导致未定义行为
    
    //虽然在理论上,将一个int类型的值直接用%hhx格式化输出
    //(该格式化符期望一个unsigned char类型参数)可能产生未定义行为,
    //但在实际中,许多编译器和运行环境会对这种类型不匹配做出某种形式的处理,
    //这通常涉及到类型转换。
    //当i的值被传递给printf函数,并使用%hhx输出时,尽管i是一个int类型,
    //但编译器会尝试将其转换为unsigned char类型,以便与格式化符匹配。
    //这种转换是通过截断i的高位来实现的,仅保留最低的8位(即最低的一个字节)。
   
    //例如,对于值65535(十六进制0xFFFF),当它被截断到unsigned char时,仅保留了最低的8位,即0xFF。
    //因此,即使这种使用方式是错误的,你仍然可能看到ff作为输出结果。
    //然而,这种行为并不是标准C语言规范中定义的,因此在不同的编译器或平台上,你可能会得到不同的结果。
    //在某些情况下,这种类型不匹配可能确实会导致未定义行为,包括程序崩溃或输出错误的结果。
    //因此,正确的做法是始终确保传递给格式化输出函数的参数类型与格式化符相匹配,以避免任何潜在的问题。
    //在本例中,正确的做法是将值显式转换为unsigned char类型再进行输出,或者使用与类型匹配的格式化符,如 %x 用于int类型。

    return 0;
}
//#include <stdio.h>
//
//int main()
//{
//    int i = 24624;    //转化成 二进制 0110 0000 0011 0000
//
//    printf("%hhx\n", i);   // 30    //这正符合上述的解释
//    //截断高位,留下低位的一个字节:0011 0000
//
//    return 0;
//}

//在本例中,正确的做法是将值显式转换为unsigned char类型再进行输出,或者使用与类型匹配的格式化符,如 %x 用于int类型。
//显式类型转换
//当你的意图是输出一个int类型的值,但是由于某种原因需要它以unsigned char的形式来展示,你可以使用显式类型转换。
//这通常在你需要确保值被截断到8位(即一个字节)的上下文中使用。例如:
//#include <stdio.h>
//
//int main() {
//    int i = 65535; // 一个32位整数
//    unsigned char uc = (unsigned char)i;  //结果:ff    // 将int类型值转换为unsigned char类型
//    printf("%hhx\n", uc);   //结果:ff      // 正确输出unsigned char类型的十六进制值
//    return 0;
//}

//在这个例子中,通过unsigned char uc = (unsigned char)i; ,i的值被显式转换为unsigned char类型。
//这意味着i的值会被截断到最低8位,然后以十六进制形式通过% hhx输出。
//使用正确的格式化符
//如果目标是输出int类型的十六进制值,而不需要它被截断到unsigned char,则应当使用与int类型相匹配的格式化符% x。例如:
//#include <stdio.h>
//
//int main() {
//    int i = 65535;      // 一个32位整数
//    printf("%x\n", i);     //ffff         // 正确输出int类型的十六进制值
//    return 0;
//}

//在这个例子中,printf("%x\n", i); 直接将int类型的值i以十六进制形式输出,没有进行任何类型转换,这适合于不需要截断值的情况。
//使用正确的格式化符可以避免类型不匹配的问题,确保输出结果的正确性。
//总之,根据你的具体需求选择正确的处理方式:如果需要将int类型的值截断到unsigned char类型并输出,使用显式类型转换;
//如果需要直接输出int类型的十六进制值,使用% x格式化符。这样可以避免未定义行为,确保程序的稳定性和输出结果的准确性。

(6):j

和整形转换说明一起使用,表示 intmax_t 或 uintmax_t 类型的值。
这些类型定义在 stdint.h 中。
示例: “%jd” “%8jx”

intmax_t 和 uintmax_t 是 C 语言中定义在 stdint.h 头文件中的两种整数类型,他们用于表示整数的最大宽度。
intmax_t 是有符号整数类型,而 uintmax_t 是无符号整数类型。
这两种类型在不同的系统上可能也有不同的具体表现,
但他们保证了至少具有 long long (在 64 位系统上通常是 64 位)的宽度

intmax_t 和 uintmax_t 在 C 语言中提供了跨平台的、确定整数宽度的类型,
这对于处理大数据或者在不同硬件架构之间移植代码非常有用。
在使用这些类型时,应当配合 inttypes.h 中的格式化宏来确保输出的一致性。
同时, uintptr_t 提供了一种安全的方式来处理指针转换的需求。

例子:
假设我们需要在一个程序中存储一个很大的整数,并且我们需要确保这个整数在所有平台上都能准确表示。
在这种情况下,我们可以使用 intmax_t 类型来存储这个整数。

 //和整形转换说明一起使用,表示 intmax_t 或 uintmax_t 类型的值。
 //这些类型定义在 stdint.h 中。
 //示例: "%jd" "%8jx" 
 //
 // intmax_t 和 uintmax_t 是 C 语言中定义在 stdint.h 头文件中的两种整数类型,他们用于表示整数的最大宽度。
 // intmax_t 是有符号整数类型,而 uintmax_t 是无符号整数类型。
 // 这两种类型在不同的系统上可能也有不同的具体表现,
 // 但他们保证了至少具有 long long (在 64 位系统上通常是 64 位)的宽度
 //
 // intmax_t 和 uintmax_t 在 C 语言中提供了跨平台的、确定整数宽度的类型,
 // 这对于处理大数据或者在不同硬件架构之间移植代码非常有用。
 // 在使用这些类型时,应当配合 inttypes.h 中的格式化宏来确保输出的一致性。
 // 同时, uintptr_t 提供了一种安全的方式来处理指针转换的需求。
 // 
 //例子:
 //假设我们需要在一个程序中存储一个很大的整数,并且我们需要确保这个整数在所有平台上都能准确表示。
 //在这种情况下,我们可以使用 intmax_t 类型来存储这个整数。

#include <stdio.h>
#include <stdint.h>

int main()
{
    intmax_t bigNumber = 9223372036854775807LL;  //使用 intmax_t 存储一个很大的整数

    printf("%jd\n", bigNumber);   // 9223372036854775807
    printf("%jx\n", bigNumber);   // 7fffffffffffffff

    return 0;
}

(7):l

和整形转换说明一起使用,表示 long int 或 unsigned long int 类型的值
示例:“%ld” “%8lu”

(8):ll

和整形转换说明一起使用,表示 long long int 或 unsigned long long int 类型的值 (C99)
示例:“%lld” “%8llu”

(9):L

和浮点转换说明一起使用,表示 long double 类型的值
示例:“%Lf” “%10.4Le”

 //和浮点转换说明一起使用,表示 long double 类型的值
 //示例:"%Lf" "%10.4Le"

#include <stdio.h>

int main()
{
    printf("%d\n", sizeof(long double));  // 8

    long double num = 123.45678901234567890;
    printf("The value of num is: %Lf\n", num);

    return 0;
}

(10):t

和整形转换说明一起使用,表示 ptrdiff_t 类型的值。
ptrdiff_t 是两个指针差值的类型(C99)
示例:“%td” “%12ti”

(11):z

和整形转换说明一起使用,表示 size_t 类型的值。
size_t 是 sizeof 返回的类型(C99)
示例:“%zd” “%12zd”

// 和整形转换说明一起使用,表示 size_t 类型的值。
// size_t 是 sizeof 返回的类型(C99)
// 示例:"%zd" "%12zd"

#include <stdio.h>

int main()
{
    printf("%zd\n", sizeof(int));   //4

    return 0;
}

(12):使用修饰符和标记的示例

1、字段宽度
// 书P84 程序清单4.7 
// 字段宽度

#include <stdio.h>
#define PAGES 456

int main()
{
    printf("*%d*\n", PAGES);

    printf("*%2d*\n", PAGES);  // %2d,其对应的输出结果应该是 2 字段宽度
    //因为待打印的整数是 3 位数字,所以字段宽度自动扩大以符合整数的长度。
    
    printf("*%10d*\n", PAGES);
    printf("*%-10d*\n", PAGES);

    return 0;
}

在这里插入图片描述

2、一些浮点型修饰符的组合
// P85 程序清单4.8
// 一些浮点型修饰符的组合

#include <stdio.h>

int main()
{
    const double RENT = 3852.99;

    printf("*%f*\n", RENT);  
    //错误
    //我的答案:*3852.99*
    //正确答案:*3852.990000*  
    // %f 在这种情况下,字段宽度 和 小数点后面的位数 均为系统默认设置,
    //即字段宽度是容纳带打印数字所需的位数 和 小数点后打印 6 位数字
    
    printf("*%e*\n", RENT);  
    //错误
    //我的答案:*3.85299e+3*
    //我的答案:*3.852990e+03*  // 注意:指数:e+03
    // %e 默认情况下,编辑器在小数点的左侧打印 1 个数字,在小数点的右侧打印 6 个数字。
    
    printf("*%4.2f*\n", RENT);       //我的答案:*3852.99*
    printf("*%3.1f*\n", RENT);       //我的答案:*3853.0*      // 四舍五入
    printf("*%10.3f*\n", RENT);      //我的答案:*  3852.990*
   
    printf("*%10.3E*\n", RENT);      
    //我的答案:*  3.853E+3*
    //正确答案:* 3.853E+03* 
    //注意:指数:e+03
   
    printf("*%+4.2f*\n", RENT);      //我的答案:*+3852.99*
    printf("*%010.2f*\n", RENT);     //我的答案:*0003852.99*
      
    return 0;
}

在这里插入图片描述

//书P86 程序清单4.9 
//演示一些格式标记

#include <stdio.h>

int main()
{
    printf("%x %X %#x\n", 31, 31, 31);                   
    //正确
    //我的答案:1f 1F 0x1f 
    //根据 %1F, 打印出:1F
    
    printf("**%d**% d**% d**\n", 42, 42, -42);
    //正确
    //我的答案:**42** 42**-42
    
    //书中的解释:
    //在转换说明中,用 空格 在输出的正值前面 生成前导空格,负值前面 不产生前导空格
   
    //我结合书中的解释的理解:
    //有符号值若为正,则在值前面显示前导空格(不显示任何符号),+ 标记被一个空格所覆盖
    //若为负,则在值前面显示减号
     
    printf("**%5d**%5.3d**%05d**%05.3d**\n", 6, 6, 6, 6);
    //错误
    //我的答案:**    6**006**00006**00006**
    //正确答案:**    6**  006**00006**  006**
    
    //书中的解释:
    // .数字 
    // 对于整形转换,表示待打印数字的最小位数,如有必要,使用前导 0 来达到这个位数。
    // 
    //演示了如何在整形格式中使用精度(%5.3d)生成足够的前导 0 以满足最小位数的要求。
    //然而,使用 0 标记会使得编辑器用 前导 0 填充满整个字段宽度。  //书P84
    //最后,如果 0 标记和精度一起出现,0 标记会被忽略。            //书P84

    return 0;
}
#include <stdio.h>

int main()
{
    printf("*%010.2f*\n", 1.2);  //*0000001.20*
    // .数字
    // 表示小数点右边数字的位数 
    //标志 0 
    //对于数值格式,用前导 0 代替 空格填充字段宽度

    printf("*%5.3d*\n", 6);     //*  006*
    // .数字 
    //对于整形转换,表示待打印数字的最小位数;如有必要,使用 前导 0 来达到这个位数
    //%5.3d 生成足够的前导 0 以满足最小位数的要求

    printf("*%08.3d*\n", 6);     //*     006*
    // 标记 0
    //对于整数格式,如果出现 - 标记 或者 指定精度,则忽略该标记
    // 8 是指定宽度为 8 ,又有前导 0 ,不足宽度为 8 的部分 用 0 代替 空格 填充字段宽度
    // 但是由于是 %d 整数格式,又有 .3 精度,所以,前导 0 被忽略了。

    return 0;
}
//书P86 程序清单 4.10
//字符串的格式

#include <stdio.h>
#define BLURB "Authentic imitation!"

int main()
{
    printf("[%2s]\n", BLURB);      //[Authentic imitation!]
    printf("[%24s]\n", BLURB);     //[    Authentic imitation!]
    
    printf("[%24.5s]\n", BLURB);   //[                   Authe]
    //.数字 
    //对于 %s 转换,表示待打印字符的最大数量;
    
    printf("[%-24.5s]\n", BLURB);  //[Authe                   ]

    return 0;
}

在这里插入图片描述

2、转换说明的意义

(1)转换不匹配


//P87 程序清单4.11
//一些不匹配的整形转换

#include <stdio.h>
#define PAGES 336           //二进制:0001 0101 0000
#define WORDS 65618         //二进制:0001 0000 0000 0101 0010

int main()
{
    short num = PAGES;
    short mnum = -PAGES;

    printf("%zd\n", sizeof(short));            // 2 字节
    printf("%zd\n", sizeof(unsigned short));   // 2 字节

    printf("num as short and unsigned short:%hd %hu\n", num, num); 
    //我的答案:
    //short           0000 0001 0101 0000       336
    //unsigned short  0000 0001 0101 0000       336

    printf("-num as short and unsigned short:%hd %hu\n", mnum, mnum);
    //我的答案:
    //short           1000 0001 0101 0000             -336
    //unsigned short  1111 1110 1011 0000             65200

    //系统使用二进制 补码 来表示 有符号整数。
    //这种方法,数字 0~32767 代表他们本身,而数字 32768~65535 则表示负数。
    //其中,65535 表示 -1,65534 表示 -2,以此类推。
    //short int 是 2 字节
    //0111 1111 1111 1111 正数:32767  正数的原码、反码、补码一致
    //1111 1111 1111 1111 负整数 原码 -32767
    //1000 0000 0000 0000 负整数 反码
    //1000 0000 0000 0001 负整数 补码 32769

    //1111 1111 1111 1111 负整数 补码 65535
    //1111 1111 1111 1110 负整数 反码
    //1000 0000 0000 0001 负整数 原码 -1
    
    //unsigned short  原码:1000 0001 0101 0000       -336
    //                反码:1111 1110 1010 1111
    //                补码:1111 1110 1011 0000       65200

    printf("num as int and char:%d %c\n", num, num);
    //我的答案:
    //int    0000 0001 0101 0000       336
    //char   0101 0000                 80      P

    printf("WORDS as int,short,and char:%d %hd %c\n", WORDS, WORDS, WORDS);
    //我的答案:
    //int    0001 0000 0000 0101 0010  变为 4 字节
    //       0000 0000 0000 0001 0000 0000 0101 0010    65618
    //short  0001 0000 0000 0101 0010  截断为 2 字节
    //       0000 0000 0101 0010                        82
    //char   0001 0000 0000 0101 0010  截断为 1 字节
    //       0101 0010                                  82   R

    return 0;
}

在这里插入图片描述

//书P88 程序清单 4.11
//不匹配的浮点型转换 

#include <stdio.h>

int main()
{
    float n1 = 3.0;
    double n2 = 3.0;
    long n3 = 2000000000;
    long n4 = 1234567890;

    printf("%zd\n", sizeof(long int));   // 4 字节    表示的最大整数:4294967295
    printf("%zd\n", sizeof(float));      // 4 字节
    printf("%zd\n", sizeof(double));     // 8 字节

    printf("%.1e %.1e %.1e %.1e\n", n1, n2, n3, n4);
    //错误
    //我的答案:
    //n1  3.0e+00   //float 类型的值作为 printf() 参数时会被转换成 double 类型。 在本系统中,float 是 4 字节,但是为了 printf() 能正确地显示该值,n1 被扩成 8 字节。
    //n2  3.0e+00
    //n3  2.0e+09
    //n4  1.2e+09

    //正确答案
    //n1  3.0e+00
    //n2  3.0e+00
    //n3  3.1e+46   这是无意义的值,每个系统都不一样
    //n4  3.2e-306  这是无意义的值,每个系统都不一样

    //答案显示:%e 转换说明没有把整数转换成浮点数。
    //考虑一下,如果使用 %e 转换说明打印 n3(long类型)会发生什么情况。
    //首先,%e 转换说明让 printf() 函数认为待打印的值是 double 类型(本系统中double为 8 字节)。
    //当printf() 查看 n3 (本系统中是 4 字节的值)时,除了查看 n3 的 4 字节外,
    //还会查看 n3 相邻的 4 字节,共 8 字节单元。
    //接着,它将 8 字节单元中的位组合解释成浮点数。(如:把一部分的 位组合 解释成 指数)
    
    //因此,即使 n3 的位数正确,根据 %e 转换说明和 %ld 转换说明解释出来的值也不同。最终得到的结果是无意义的。
    //书P56 系统存储一个浮点数要占用 32 位。其中 8 位用于表示指数的值和符号,剩下的 24 位用于表示 非指数部分(也叫作 尾数或有效值)及其符号。

    printf("%ld %ld\n", n3, n4);
    //我的答案:
    // 由于并没有超过 long 能表示的最大整数:4294967295
   //n3  2000000000
   //n4  1234567890

    printf("%ld %ld %ld %ld\n", n1, n2, n3, n4);
    //错误
    //我的答案:
    //n1  3
    //n2  3
    //n3  2000000000
    //n4  1234567890

    //正确答案:
    //n1  0
    //n2  1074266112
    //n3  0
    //n4  1074266112

    //输出显示:如果printf()语句有其他不匹配地地方,即使用对了转换说明也会生成虚假地结果。
    //用 %ld 转换说明打印浮点数会失败,但是在这里,用 %ld 打印成 long 类型地数竟然也失败了!
    //问题出在 C 如何把信息传递给函数。具体情况因编译器而异。
    //"参数传递"框中针对一个有代表性地系统进行了讨论,见下图。

    return 0;
}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

(2) printf( ) 返回值

在这里插入图片描述

(3) 打印较长的字符串

在这里插入图片描述
在这里插入图片描述

微软雅黑字体
黑体
3号字
4号字
红色
绿色
蓝色

  • 14
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值