学习 C 语言时,我们很容易把注意力放在公式上,却忽略数据的类型。例如,同样是计算两个整数的平均值,(a + b) / 2 和 (a + b) / 2.0 可能得到不同的结果。
原因是:数据类型不仅决定数据如何存储,还会影响表达式怎样计算。
本文介绍整型、浮点型、字符型,以及自动转换、赋值转换和强制类型转换。所有示例都是可以单独编译运行的完整程序。
示例采用 C11。涉及字符编码的运行结果以 ASCII 兼容环境为前提。类型占用的字节数可能随编译器和平台变化,文中的
sizeof结果是本次验证环境的实际输出。
一、先认识变量与数据类型
变量可以理解为一个有名称的存储位置。声明变量时,需要告诉编译器变量叫什么、存放什么类型的数据。
int count = 10;
float price = 2.5f;
double average = 86.75;
char grade = 'A';
这四个变量分别用于存放整数、单精度浮点数、双精度浮点数和字符。
变量需要先声明后使用。在声明时给初值,称为初始化;声明之后更新变量的值,称为赋值。
int score = 60; // 初始化
score = 90; // 赋值
注意,同一条声明中,每个变量都需要单独初始化。例如 int a = 0, b; 只初始化了 a。函数内部未初始化的普通局部变量 b,不能直接拿来读取或计算。
二、整型:表示没有小数部分的数据
1. 有符号整数与无符号整数
整型适合表示人数、次数、编号等数据。
有符号整数能够表示正数、负数和零,例如
int、long。无符号整数不能表示负数,例如
unsigned int、unsigned long。
具体能够表示多大的数,取决于类型及其实现。不要简单认为所有机器上的 int、long 都具有相同大小。
2. 十进制、八进制和十六进制
同一个整数可以用不同进制的常量形式书写。
前导零会影响整数常量的含义。 010 表示八进制的 10,也就是十进制的 8。
示例 1:观察不同进制的整数
#include <stdio.h>
int main(void)
{
int decimal = 83;
int octal = 0123;
int hexadecimal = 0x53;
printf("decimal = %d\n", decimal);
printf("octal = %d\n", octal);
printf("hexadecimal = %d\n", hexadecimal);
printf("010 = %d\n", 010);
printf("83 in octal = %o\n", 83u);
printf("83 in hexadecimal = %X\n", 83u);
return 0;
}
运行结果:
decimal = 83
octal = 83
hexadecimal = 83
010 = 8
83 in octal = 123
83 in hexadecimal = 53
解释:
83、0123 和 0x53 的写法不同,但表示相同的数值。输出时,%d 按十进制显示,%o 按八进制显示,%X 按十六进制显示,并使用大写字母。后两种格式对应无符号整数,因此示例使用带 u 后缀的 83u。
常量的书写进制与输出进制是两件事:用八进制写入的整数,同样可以按十进制输出。
示例 2:查看类型占用的空间
#include <stdio.h>
int main(void)
{
printf("sizeof(char) = %zu\n", sizeof(char));
printf("sizeof(short) = %zu\n", sizeof(short));
printf("sizeof(int) = %zu\n", sizeof(int));
printf("sizeof(long) = %zu\n", sizeof(long));
printf("sizeof(long long) = %zu\n", sizeof(long long));
printf("sizeof(float) = %zu\n", sizeof(float));
printf("sizeof(double) = %zu\n", sizeof(double));
return 0;
}
本次验证环境的运行结果:
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4
sizeof(long) = 8
sizeof(long long) = 8
sizeof(float) = 4
sizeof(double) = 8
解释:
sizeof 得到对象或类型占用的字节数,其结果类型是 size_t,使用 %zu 输出。
C 语言保证 sizeof(char) 为 1,但不能据此推断所有平台的 long 都是 4 字节或 8 字节。实际编程中,应通过 sizeof 查询大小;需要整数类型的范围时,可使用 <limits.h> 中的 INT_MIN、INT_MAX 等宏。
三、浮点型:表示带小数的数据
C 语言常用的浮点型有 float 和 double。double 通常比 float 能保存更多有效数字。
浮点常量可以采用小数形式,也可以采用指数形式:
3.14 // 小数形式
.5 // 等于 0.5
6.0 // 浮点常量
3.2e2 // 3.2 × 10²,即 320.0
1.23E-2 // 1.23 × 10⁻²,即 0.0123
后缀决定浮点常量的类型:
注意,12L 是 long 整数常量,12.0L 才是 long double 浮点常量。不能只看后缀,还要看常量本身的写法。
示例 3:计算圆的面积
#include <stdio.h>
int main(void)
{
double radius = 3.5;
double pi = 3.14159;
double area = pi * radius * radius;
printf("radius = %.1f\n", radius);
printf("area = %.4f\n", area);
printf("3.2e2 = %.1f\n", 3.2e2);
printf("1.23E-2 = %.4f\n", 1.23E-2);
return 0;
}
运行结果:
radius = 3.5
area = 38.4845
3.2e2 = 320.0
1.23E-2 = 0.0123
解释:
radius、pi 和 area 都是 double,计算过程采用浮点运算。%.1f 表示输出小数点后 1 位,%.4f 表示输出小数点后 4 位。
格式中的精度控制的是显示效果,不会修改变量中保存的值。浮点数的精度有限,很多十进制小数无法被二进制浮点数精确表示,因此也不能把“显示了很多位”理解为“这些位都准确”。
在 printf 中,float 参数会先提升为 double,所以二者都可以用 %f 输出。long double 应使用 %Lf。
四、字符型:字符背后也有数值
char 属于整数类型。它可以用来保存字符编码,因此字符既可以显示为符号,也可以参与算术运算。
在 ASCII 字符集中:
字符 '6' 和整数 6 不同。前者表示数字字符的编码,后者表示数值 6。
示例 4:输出字符、编码和后一个字符
#include <stdio.h>
int main(void)
{
char upper = 'A';
char lower = 'g';
char digit = '6';
char space = ' ';
printf("%c has code %d\n", upper, upper);
printf("next of %c is %c\n", upper, upper + 1);
printf("next of %c is %c\n", lower, lower + 1);
printf("%c has code %d\n", digit, digit);
printf("digit value = %d\n", digit - '0');
printf("space has code %d\n", space);
return 0;
}
在 ASCII 兼容环境下,运行结果为:
A has code 65
next of A is B
next of g is h
6 has code 54
digit value = 6
space has code 32
解释:
%c 把参数显示为字符,%d 把参数显示为整数。对于这里的字符,参与运算或传给 printf 时会提升为 int。
ASCII 中的大写字母和小写字母分别连续排列,所以 'A' + 1 对应 'B','g' + 1 对应 'h'。这并不意味着所有字符都能按这种方法寻找“下一个字符”。
数字字符连续排列,因此当 digit 确实是 '0'~'9' 时,digit - '0' 可以得到对应的整数值。
单引号、双引号与空字符
这些写法很容易混淆:
字符串不是单个 char。例如 char ch = 'A'; 合法,但不能用 char ch = "A"; 来保存一个字符。
常见转义序列包括 \n(换行)、\t(水平制表)、\\(反斜杠)、\'(单引号)和 \"(双引号)。例如 printf("Hello\n"); 会在输出 Hello 后换行。
五、自动类型转换:混合运算怎样进行
不同类型的数据可以出现在同一个表达式中。编译器会按照规则转换操作数,再执行计算。
对于初学者,先掌握下面几种常见情况:
char、short参与算术运算时,通常先提升为int。int与double运算时,整数转换为double。float与double运算时,float转换为double。
这不是所有规则的完整列表。有符号和无符号整数混合时,还涉及类型等级及可表示范围,不能仅用“低精度变成高精度”概括。
示例 5:为什么平均值会少了小数
#include <stdio.h>
int main(void)
{
int a = 5;
int b = 20;
double average1 = (a + b) / 2;
double average2 = (a + b) / 2.0;
printf("average1 = %.2f\n", average1);
printf("average2 = %.2f\n", average2);
printf("3 + 3.5 = %.1f\n", 3 + 3.5);
return 0;
}
运行结果:
average1 = 12.00
average2 = 12.50
3 + 3.5 = 6.5
解释:
对于 average1,计算顺序是:
a + b得到整数25。25 / 2的两个操作数都是整数,执行整数除法,得到12。把
12转换为double,赋给average1,得到12.0。
对于 average2,除数 2.0 是 double,所以整数 25 先转换为 double,再进行浮点除法,得到 12.5。
把计算结果存入浮点变量,并不会让之前的整数除法自动变成浮点除法。 必须在除法发生之前,让相关操作数具有浮点类型。
本例中的整数很小。如果 a、b 很大,a + b 还可能先发生整数溢出。需要考虑这种情况时,可在加法前转换,例如 ((double)a + b) / 2.0,同时留意浮点数的精度限制。
六、赋值转换:以目标变量的类型为准
赋值时,如果右侧表达式的类型与左侧变量不同,会发生类型转换。
示例 6:浮点数赋给整数会怎样
#include <stdio.h>
int main(void)
{
double x = 5;
int positive = 3.9;
int negative = -3.9;
int code = 'A';
printf("x = %.1f\n", x);
printf("positive = %d\n", positive);
printf("negative = %d\n", negative);
printf("code = %d\n", code);
return 0;
}
运行结果:
x = 5.0
positive = 3
negative = -3
code = 65
解释:
double x = 5;把整数5转换为浮点值5.0。int positive = 3.9;舍去小数部分,得到3。int negative = -3.9;同样舍去小数部分,得到-3。在 ASCII 兼容环境下,
'A'的编码值为65。
浮点数转整数时采用向零截断,不是四舍五入,也不是一律向下取整。以上结论以截断后的值能够由目标整数类型表示为前提;不能依靠超范围转换得到有效结果。
赋值转换不会改变变量的声明类型。positive 声明为 int,即使右侧写着小数,它仍然是整数变量。编译器可能对这种会丢失小数部分的转换给出警告。
七、强制类型转换:转换的位置很重要
强制类型转换的常见写法是:
(类型名) 表达式
例如 (double)a 表示把 a 的值转换为 double,供当前表达式使用。它不会把变量 a 本身永久改成 double。
示例 7:先转换与后转换的区别
#include <stdio.h>
int main(void)
{
int a = 5;
int b = 2;
double result1 = (double)a / b;
double result2 = (double)(a / b);
double result3 = a / (double)b;
printf("result1 = %.1f\n", result1);
printf("result2 = %.1f\n", result2);
printf("result3 = %.1f\n", result3);
printf("a = %d, b = %d\n", a, b);
return 0;
}
运行结果:
result1 = 2.5
result2 = 2.0
result3 = 2.5
a = 5, b = 2
解释:
对于这里的两个整数,要得到实数商,只需在除法前把其中一个操作数转换为浮点型。
(float)a / b 也体现相同原理。这里选用 double,以便使用通常更高的计算精度。无论选哪一种,转换都不能恢复已经在整数除法中丢失的小数部分。
八、综合示例:计算两个整数的和、差、积、商
下面把变量声明、整数运算、强制转换和格式化输出放进一个程序。
示例 8:四则运算
#include <stdio.h>
int main(void)
{
int a = 9;
int b = 4;
int sum = a + b;
int difference = a - b;
int product = a * b;
printf("%d + %d = %d\n", a, b, sum);
printf("%d - %d = %d\n", a, b, difference);
printf("%d * %d = %d\n", a, b, product);
if (b != 0) {
double quotient = (double)a / b;
printf("%d / %d = %.2f\n", a, b, quotient);
} else {
printf("Division by zero is not allowed.\n");
}
return 0;
}
运行结果:
9 + 4 = 13
9 - 4 = 5
9 * 4 = 36
9 / 4 = 2.25
解释:
本例中,和、差、积都可以用 int 保存,商使用 double 保存。(double)a / b 在除法前转换操作数,因此能够得到 2.25。
if (b != 0) 用来检查除数是否为零。这里出现的 != 表示“不等于”。若将 b 改为 0,程序会输出提示,而不会执行除法。
这组数据较小,不会发生整数溢出。如果把程序用于更大范围的数据,还需要检查加法、减法和乘法的结果是否超出类型范围。
九、常见问题速查
还要区分 printf 和 scanf 的格式规则:printf 输出 float、double 都可使用 %f;如果用 scanf 读取,float * 对应 %f,double * 对应 %lf。不要直接照搬输出函数的格式到输入函数中。
十、课后练习
先判断下面各表达式的结果,再写程序验证:
9 / 4
9 / 4.0
(double)9 / 4
(double)(9 / 4)
(int)3.9
(int)-3.9
'8' - '0'
参考答案依次为:2、2.25、2.25、2.0、3、-3、8。
还可以修改综合示例,把 a 和 b 分别改成 5 和 2,再把 b 改成 0,观察正常计算与除零检查的区别。
遇到计算结果与预期不一致时,依次检查三个位置:操作数的类型、运算发生前的转换、结果赋值时的转换。把这三个阶段分清楚,整数除法和类型转换就容易理解了。
C 语言的数据类型与类型转换:从变量声明到正确计算
https://www.wy55.top/archives/c-data-tyoe