2.2 类型转换与 printf 函数深度解析

目录

1 类型转换

1.1 隐式类型转换

1.2 显式类型转换

1.3 应用:整除除法

2 printf 函数

2.1 语法格式

2.2 格式说明符

2.2.1 浮点数的规范性输出

2.2.2 格式说明符不匹配错误

2.3 修饰符

2.3.1 对齐方式

2.3.2 宽度

2.3.3 精度

2.3.4 填充字符

2.3.5 其他修饰符(# +)

3 练习


1 类型转换

1.1 隐式类型转换

隐式类型转换(又称自动类型转换)发生在不同数据类型的值进行运算时,编译器会自动将较低类型的数据转换为较高类型的数据,以保证运算的顺利进行。转换的规则主要基于数据类型的“大小”和“符号性”。一般来说,转换的优先级如下(从低到高):

  1. char 和 short 会被提升为 int(如果 int 能够表示它们的所有值)。
  2. 如果 int 不能表示 char 或 short 的所有值,则它们可能会被提升为 unsigned int
  3. 如果操作数中有 float 类型,则 int 和 unsigned int 会被转换为 float
  4. 如果操作数中有 double 类型,则 float 和 int 类型的操作数会被转换为 double
  5. 如果操作数中有 long double 类型,则其他类型的操作数会被转换为 long double

注意,char 类型是否被视为有符号还是无符号取决于编译器和具体的上下文(如是否使用了 signed 或 unsigned 关键字)。

下面是一个隐式类型转换的例子,这个例子中,intValue 是一个 int 类型的变量,而 floatValue 是一个 float 类型的变量。当它们被加在一起时,由于 float 类型比 int 类型具有更高的精度和范围,因此 intValue 会被隐式地转换为 float 类型,以便与 floatValue 进行加法运算。这种转换是自动的,不需要程序员显式地进行强制类型转换。

#include <stdio.h>

int main() {
    int intValue = 5;
    float floatValue = 3.14;

    // 在这里,intValue 会被隐式地转换为 float 类型,以与 floatValue 进行加法运算
    float result = intValue + floatValue;

    printf("Result of addition: %f\n", result);// 8.140000

    return 0;
}

1.2 显式类型转换

显式类型转换(又称强制类型转换)允许显式地将一个数据类型的值转换为另一个类型。这种转换是通过类型转换运算符来完成的,该运算符是一对圆括号,其中包含了目标类型的名称。例如,如果有一个 float 类型的变量,但想将它作为一个 int 类型来处理,可以这样做:

#include <stdio.h>

int main() {
    float f = 3.14;

    int i = (int)f; // 强制类型转换

    printf("%f\n",f);//3.140000
    printf("%d\n",i);//3

    return 0;
}

注意,强制类型转换可能会导致数据丢失。

1.3 应用:整除除法

在整数除法的场景中,如果两个操作数都是整数,那么结果也会是整数这意味着,小数部分会被自动截断。如果想看到小数部分,只需要将操作数之一转换为浮点数即可,如下所示:

#include <stdio.h>

int main() {

    int i = 5;
    float j = i / 2;  //由于 i 和 2 都是整数,所以这里的除法运算是整数除法。

    // 方法1:将整数变量 i 强制转换为浮点数 (float) i
    float k = (float) i / 2; //强制转换,将整数 i 转换为浮点数

    // 方法2:将除数 2 明确指定为浮点数 2.0 改变除法运算的性质
    // i 被隐式地转换为浮点数(通常是 double,因为 2.0 是 double 类型的)
    // 除法运算的结果是 double 类型的,因此在将结果赋值给 m 时,会发生从 double 到 float 的隐式类型转换
    float m = i / 2.0;  //2.0 是一个浮点数字面量

    printf("%f\n", j);  // 2.000000
    printf("%f\n", k);  // 2.500000
    printf("%f\n", m);  // 2.500000
    
    return 0;
}

2 printf 函数

2.1 语法格式

printf 函数可以输出各种类型的数据,包括整型、浮点型、字符型、字符串型等,实际原理是 printf 函数将这些类型的数据格式化为字符串后,放入标准输出缓冲区,然后将结果显示到屏幕上。

语法如下:

#include <stdio.h>
int printf(const char *format, ...);
  • printf 函数根据 format 给出的格式打印输出到 stdout (标准输出)和其他参数中。
  • format 是一个字符串,包含了要输出的文本以及一系列格式化说明符(如 %d、%f、%s 等),这些说明符指定了后续参数如何被格式化并插入到输出字符串中。
  • ... 表示printf函数可以接受可变数量的额外参数,这些参数与format字符串中的格式化说明符一一对应。

2.2 格式说明符

格式化说明符描述示例输出(假设变量值)
%d 或 %i十进制整数int a = 10; -> 10
%u无符号整数unsigned int b = 20; -> 20
%f浮点数(默认为双精度)

float c = 3.14; -> 3.140000 

double d = 3.14; -> 3.140000

%lf双精度浮点数double e = 3.14159; -> 3.141590
%s字符串char* f = "Hello"; -> Hello
%c字符char g = 'A'; -> A
%x 或 %X十六进制整数(小写/大写)int h = 255; -> ff 或 FF
%o八进制整数int i = 10; -> 12
%%百分号本身无直接变量对应,直接输出 %
%n到目前为止写入的字符数(不直接输出,而是存储在变量中)通常与指针一起使用,示例略
%p指针地址int* j = &a; -> 类似 0x7ffeefbff6f8 的地址

示例代码:

#include <stdio.h>

int main() {
    int a = 10;
    unsigned int b = 20;
    float c = 3.14;
    double d = 3.14159;
    char *f = "Hello";
    char g = 'A';
    int h = 255;
    int i = 10;

    printf("Decimal integer: %d\n", a);//Decimal integer: 10
    printf("Decimal integer: %i\n", a);//Decimal integer: 10
    printf("Unsigned integer: %u\n", b);//Unsigned integer: 20
    printf("Float: %f\n", c);//Float: 3.140000
    // 推荐在printf中对double类型使用 %f
    printf("Double: %f\n", d);//Double: 3.141590
    // 没必要在printf中对double类型使用 %lf
    printf("Double: %lf\n", d);//Double: 3.141590

    printf("String: %s\n", f);//String: Hello
    printf("Hello %s ,My name is %s\n", "everybody","Thanks");//Hello everybody ,My name is Thanks

    printf("Character: %c\n", g);//Character: A

    // 以其他进制数输出 int 定义的十进制数
    printf("Hexadecimal (lower): %x\n", h);//Hexadecimal (lower): ff
    printf("Hexadecimal (upper): %X\n", h);//Hexadecimal (upper): FF
    printf("Octal: %o\n", i);//Octal: 12

    printf("Percent sign: %%\n");//Percent sign: %

    // 指针地址的示例
    int *j = &a;
    printf("Pointer address: %p\n", j);//Pointer address: 000000000061FDEC
    //建议将指针转换为 (void*) 类型再用 %p 打印
    printf("Pointer address: %p\n", (void *) j); // Pointer address: 000000000061FDEC

    return 0;
}

2.2.1 浮点数的规范性输出

 见下面这段代码:

#include <stdio.h>

int main() {
    float f = 3.14f;
    double d = 3.141592653589793;

    printf("Float: %f\n", f);  // 推荐
    printf("Double: %f\n", d); // 推荐

    printf("Float: %lf\n", f); // 不推荐,应该避免这种做法
    printf("Double: %lf\n", d);// 没必要,%f就行


    return 0;
}

结果如下:

在C语言中,当涉及到可变参数列表(如 printf 和 scanf 函数的参数)时,存在类型提升(type promotion)的规则。具体来说,对于 float 类型的参数,在传递给这些函数时,它们会被自动提升为 double 类型。这就是为什么在 printf 中使用 %f 可以正确地输出 float 类型的变量,因为 %f 格式说明符预期的是一个 double 类型的参数(尽管在实际使用中,由于历史原因和兼容性考虑,%f 也被用于 float 类型的输出)。

总结一下:

  1. printf 中,对于 float 使用 %f,对于 double 也通常使用 %f(尽管技术上 %lf 可能在某些编译器上被接受,但这不是标准行为)。
  2. scanf 中,对于 float 使用 %f,对于 double 必须使用 %lf
#include <stdio.h>  
  
int main() {  
    float f = 0.0f;  
    double d = 0.0;  
  
    // 正确的 printf 使用  
    printf("Float with %f: %f\n", f, f);  
    printf("Double with %f (correct): %f\n", d, d);  
  
    // 注意:虽然下面这行在大多数编译器上可能工作,但它不是标准行为  
    // printf("Double with %lf (non-standard in printf): %lf\n", d, d); // 非标准,不建议使用  
  
    // 错误的 printf 使用(在技术上不是错误,但可能导致混淆)  
    // 这里没有直接的“错误”,但使用 %lf 在 printf 中可能会引起误解  
  
    // 正确的 scanf 使用  
    printf("Enter a float: ");  
    scanf("%f", &f);  
    printf("You entered: %f\n", f);  
  
    printf("Enter a double: ");  
    scanf("%lf", &d); // 必须使用 %lf 来读取 double  
    printf("You entered: %lf\n", d);  
  
    // 错误的 scanf 使用  
    // 下面这行代码是错误的,因为它试图用 %f 来读取 double 类型的变量  
    // printf("Incorrect scanf for double: ");  
    // scanf("%f", &d); // 错误:应该使用 %lf  
  
    return 0;  
}

关于 scanf 函数后续再进行讲解。

2.2.2 格式说明符不匹配错误

在C语言中,如果 printf 函数的格式说明符与提供的参数类型不匹配,那么可能会导致未定义行为(Undefined Behavior, UB)。这通常意味着程序可能会以不可预测的方式运行,包括但不限于输出垃圾值、程序崩溃或更隐蔽的错误。常见的类型不匹配情况如下:

1. 整数与浮点数混用:

  • 使用 %d 来打印 float 或 double 类型的变量。
  • 使用 %f 来打印 int 或 long 类型的变量。
#include <stdio.h>  
  
int main() {  
    float f = 3.14f;  
    double d = 3.14159;  
    int i = 42;  
    long l = 123456789L;  
  
    // 整数与浮点数混用  
    printf("Float as integer: %d\n", f);  // 未定义行为,可能输出垃圾值  
    printf("Double as integer: %d\n", d); // 同样,未定义行为  
    printf("Integer as float: %f\n", i);  // 正确输出整数对应的浮点数  
    printf("Long as float: %f\n", l);     // 正确输出长整数对应的浮点数,但注意精度可能丢失  
  
    // 正确的做法  
    printf("Float: %f\n", f);  
    printf("Double: %lf\n", d);  
    printf("Integer: %d\n", i);  
    printf("Long: %ld\n", l);  
  
    return 0;  
}

输出结果:

2. 指针与整数混用:

  • 使用 %d 来打印指针(应该使用 %p,并且指针值需要转换为 void* 类型)。
#include <stdio.h>

int main() {
    int var = 10;
    int *ptr = &var;

    // 指针与整数混用
    printf("错误:Pointer as integer: %d\n", ptr);  // 未定义行为,但通常打印指针的数值表示
    // 正确的做法
    printf("正确:Pointer: %p\n", (void*)ptr);  // 使用%p并转换为void*

    return 0;
}

输出结果:

3.字符与整数混用:

  • 使用 %s 来打印 char 类型的变量(应该使用 %c)。
  • 使用 %d 来打印 char 类型的变量时,虽然通常可以工作(因为 char 通常被提升为 int ),但这不是最佳实践。
#include <stdio.h>

int main() {
    char c = 'A';

    // 字符与整数混用
    printf("错误:Char as string: %s\n", &c);  // 未定义行为,因为%s期望一个字符串(以null结尾的字符数组)
    printf("ASCII码:Char as integer: %d\n", c);  // 通常可以工作,因为char被提升为int
    printf("字符:Char as char: %c\n", c);     // 正确的做法

    return 0;
}

输出结果:

 4.宽度/精度指示符的误用:

  • 格式说明符中的宽度/精度指示符与变量类型不匹配时,可能不会导致编译错误,但可能会产生不符合预期的输出。
#include <stdio.h>

int main() {
    int i = 123;
    float f = 123.456f;

    // 宽度/精度指示符的误用
    // 不会产生编译错误,但.2被忽略
    printf("Integer with precision: %.2d\n", i);
    // 正确,但宽度可能超出需要
    printf("Float with too much width: %20.2f\n", f);

    // 注意:%lf在printf中不是标准用法,但某些编译器可能接受
    printf("Float with invalid precision: %.2lf\n", f);

    // 正确的做法(对于float,使用%f)
    printf("Float with valid precision: %.2f\n", f);

    return 0;
}

输出结果:

2.3 修饰符

printf 函数的修饰符是格式化字符串中用于进一步控制输出格式的部分。它们可以指定输出的对齐方式、宽度、精度以及是否包含特定的前缀等。

2.3.1 对齐方式

  • 左对齐:在宽度指定前加上-(减号),表示输出将左对齐右边用空格填充。例如,%-5d
  • 默认(右对齐):如果不指定对齐方式,则默认为右对齐

2.3.2 宽度

  • 固定宽度:通过在 % 和格式字符之间指定一个整数来设置最小字段宽度。如果要输出的字符数少于这个宽度,则默认情况下会在左边(对于右对齐)或右边(对于左对齐)填充空格以达到指定的宽度。例如,%5d 表示输出一个整数,至少占用 5 个字符的宽度,不足部分用空格填充。如果要输出的字符数大于这个宽度不会截断它或在其左侧填充空格,直接原样输出,因为字符本身的长度已经超过了指定的宽度。
  • 动态宽度:使用 作为宽度的值,表示宽度将由格式化字符串中的下一个参数指定。例如,%*d,其中*对应的宽度值将在d之前的参数中给出
#include <stdio.h>

int main() {
    int num1 = 123;
    int num2 = 4567;

    // 固定宽度(右对齐)
    // 注意,这是最小字段宽度
    printf("%1d\n", num1);//原样输出:123
    printf("%2d\n", num1);//原样输出:123

    printf("%5d\n", num1);//默认为右对齐,左边填充两个空格。
    printf("%5d\n", num2);//默认为右对齐,左边填充一个空格。

    // 固定宽度(左对齐)
    printf("%-5d\n", num1);//为左对齐,右边填充两个空格。
    printf("%-5d\n", num2);//为左对齐,右边填充一个空格。

    // 动态宽度
    int width = 8;
    printf("%*d\n", width, num1);//为右对齐,左边填充5个空格。
    printf("%*d\n", width, num2);//为右对齐,左边填充4个空格。

    return 0;
}

输出结果:

2.3.3 精度

  • 对于浮点数:通过在.后指定一个整数来设置小数点后的位数。例如,%.2f 表示输出浮点数时保留两位小数。如果省略了精度,则默认为六位小数
  • 对于字符串:精度表示要输出的最大字符数。如果字符串的长度大于指定的精度,则会被截断。即使你指定了一个比字符串实际长度还要大的精度值,printf 也不会在字符串末尾添加任何字符或空格来填充到指定的长度。它只会打印出字符串中直到遇到结尾的 '\0' 或达到指定的最大字符数为止的部分
  • 动态精度:与动态宽度类似,使用 作为精度的值,表示精度将由格式化字符串中的下一个参数指定。
#include <stdio.h>

int main() {
    double num = 3.1415926;
    char str[] = "Hello World!";

    // 浮点数精度
    printf("%f\n", num);  // 输出 3.141593,默认6位,四舍五入
    //最小长度为4,小数点后两位
    printf("%4.2f\n", num);  // 输出 3.14
    printf("%.5f\n", num);  // 输出 3.14159

    // 字符串精度
    printf("%.5s\n", str);  // 输出 Hello
    //只会打印出字符串中直到遇到结尾的 '\0' 或达到指定的最大字符数为止的部分。
    printf("%.20s\n", str);  // 输出 Hello World!

    // 动态精度
    int precision1 = 3;
    int precision2 = 7;
    printf("%.*f\n", precision1, num);  // 输出 3.142
    printf("%.*f\n", precision2, num);  // 输出 3.141592

    printf("%.*s\n", precision1, str);  // 输出 Hel
    printf("%.*s\n", precision2, str);  // 输出 Hello W

    return 0;
}

2.3.4 填充字符

  • 默认情况下,如果输出值的字符数少于指定的宽度,则使用空格进行填充。但是,可以通过在宽度指定前加上 0 来指定使用零作为填充字符。例如,%05d 表示输出一个整数,至少占用5个字符的宽度,不足部分用零填充。
#include <stdio.h>

int main() {
    int num1 = 12;
    int num2 = 1234;

    printf("%05d\n", num1);  // 输出 00012
    printf("%05d\n", num2);  // 输出 01234

    return 0;
}

2.3.5 其他修饰符(# +)

  • #:对于八进制和十六进制整数,# 修饰符会在输出中包含前缀(0对于八进制,0x或0X对于十六进制)。例如,%#o、%#x、%#X
  • +:对于整数,+ 修饰符会强制在输出中包含正号或负号,即使数值是正数
#include <stdio.h>

int main() {
    int octal = 0123; // 八进制数
    int hex = 0xabc;  // 十六进制数
    int positive = 123;
    int negative = -123;

    // 使用 # 修饰符
    printf("Octal with #: %#o\n", octal); // 输出:Octal with #: 0123
    printf("Hex with #: %#x\n", hex);     // 输出:Hex with #: 0xabc
    printf("Hex with upper case #: %#X\n", hex); // 输出:Hex with upper case #: 0XABC

    // 使用 + 修饰符
    printf("Positive with +: %+d\n", positive); // 输出:Positive with +: +123
    printf("Negative with +: %+d\n", negative); // 输出:Negative with +: -123

    // 示例:使用字段宽度和对齐控制  
    printf("|%-10d|\n", positive); // 左对齐,宽度为10,不足部分用空格填充
    printf("|%+10d|\n", positive); // 右对齐(默认),宽度为10,正数前加空格以与负号对齐(但这里+修饰符不影响对齐)
    printf("|%-10d|\n", negative); // 左对齐,负数直接显示

    return 0;
}

输出结果:

下面通过一个例子来展示对齐的魅力:

#include <stdio.h>

int main() {
    // 假设我们有一些学生的姓名和分数
    char *names[] = {"Alice", "Bob", "Charlie", "David"};
    int scores[] = {90, 85, 95, 78};

    // 设置打印的列宽
    int nameWidth = 10; // 假设所有名字都不会超过10个字符宽(包括空格和制表符)
    int scoreWidth = 5; // 分数列宽度设置为5,足够显示两位数字和可能的空格

    // 打印表头
    printf("%-*s | %*s\n", nameWidth, "Name", scoreWidth, "Score");
    printf("%-*s | %*s\n", nameWidth, "------", scoreWidth, "-----");

    // 遍历数组并打印每个学生的姓名和分数
    for (int i = 0; i < 4; i++) {
        // 使用%-*s进行左对齐的字符串,%*d进行右对齐的整数
        printf("%-*s | %*d\n", nameWidth, names[i], scoreWidth, scores[i]);
    }

    return 0;
}

输出结果:


3 练习

1、int i = 5; float f = i / 2; 那么f 的值为2.5
A 正确            B 错误
答案: B
解释: 因为 i 是整型,所以除2是整除,得到的值是2,如果要得到2.5,应是(float)i/2。


2、printf 的 format 参数中含有%c代表要输出字符,%d 代表整型,%f代表浮点, %s 代表字符串
A 正确            B 错误
答案: A
解释: printf 的输出格式需要记住,这样在在线评测(OJ)中才能熟练应对,包括对于机试是很重要的。


3、printf的输出默认是左对齐
A 正确            B 错误
答案: B
解释: printf 的输出默认是右对齐,不是左对齐。如果需要左对齐,那么加入负号。

  • 13
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thanks_ks

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值