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 隐式类型转换
隐式类型转换(又称自动类型转换)发生在不同数据类型的值进行运算时,编译器会自动将较低类型的数据转换为较高类型的数据,以保证运算的顺利进行。转换的规则主要基于数据类型的“大小”和“符号性”。一般来说,转换的优先级如下(从低到高):
- char 和 short 会被提升为 int(如果 int 能够表示它们的所有值)。
- 如果 int 不能表示 char 或 short 的所有值,则它们可能会被提升为 unsigned int。
- 如果操作数中有 float 类型,则 int 和 unsigned int 会被转换为 float。
- 如果操作数中有 double 类型,则 float 和 int 类型的操作数会被转换为 double。
- 如果操作数中有 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 | 浮点数(默认为双精度) |
|
%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 类型的输出)。
总结一下:
- 在 printf 中,对于 float 使用 %f,对于 double 也通常使用 %f(尽管技术上 %lf 可能在某些编译器上被接受,但这不是标准行为)。
- 在 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 的输出默认是右对齐,不是左对齐。如果需要左对齐,那么加入负号。