C: 常量与类型转换

常量就像数学里的圆周率——值一旦确定就不能改变。而类型转换就像货币兑换,不同币种之间有固定汇率,但换算时要注意精度损耗。

1. 字面量

字面量(Literal)是代码中直接写出来的固定值,不需要计算,一看就知道是多少。

(1) 整数字面量

C
int a = 42;
int b = 0xFF;
int c = 052;
int d = 0b101010;
进制 前缀 示例 十进制值
十进制 42 42
十六进制 0x0X 0xFF 255
八进制 0 052 42
二进制(C99GNU扩展/C23标准) 0b0B 0b101010 42

💡 八进制陷阱:不小心在数字前写0会被当成八进制。int x = 010; 的值是8而不是10,这是一个经典的初学者bug。

整数字面量默认类型是 int,如果超出int范围会自动变成 longlong long。你也可以手动指定:

后缀 含义 示例
uU unsigned 42U
lL long 42L
llLL long long 42LL
UL unsigned long 42UL

⚠️ 建议:用大写 L 而非小写 l,因为 l 容易和数字 1 混淆。42l 看起来像 421

(2) 浮点字面量

C
double a = 3.14;
double b = 2.0e8;
double c = 1.5E-3;
float  d = 3.14f;

浮点字面量默认类型是 double。加 fF 后缀表示 float,加 lL 表示 long double

🔥 常见错误

C
float f = 3.14;

3.14double 字面量,赋值给 float 变量时会发生隐式截断,编译器可能发出警告。正确写法:

C
float f = 3.14f;

(3) 字符字面量

用单引号包裹的单个字符:

C
char ch = 'A';
char newline = '\n';
char zero = '\0';

字符字面量的类型是 int(不是 char),这在C中是一个容易忽略的细节:

C
printf("%zu\n", sizeof('A'));
TEXT 📖 仅展示
4

在C中 sizeof('A') 是4(int的大小),而在C++中是1(char的大小)。这是C和C++的一个微妙差异。

(4) 字符串字面量

用双引号包裹的字符序列:

C
printf("Hello, World!\n");

字符串字面量的类型是 char[],编译器会自动在末尾添加 \0 作为结束标志。所以 "abc" 实际占4个字节(a、b、c、\0)。

⚠️ 重要:字符串字面量存储在只读内存区域,尝试修改会导致未定义行为(通常是程序崩溃):

C
char *s = "hello";
s[0] = 'H';

2. const修饰符

const 是C语言的关键字,用来修饰变量为"只读"——初始化后不能再修改:

C
const int MAX_SIZE = 100;
const float PI = 3.14159f;
const char NEWLINE = '\n';

试图修改const变量会导致编译错误:

C
const int MAX_SIZE = 100;
MAX_SIZE = 200;
TEXT 📖 仅展示
error: assignment of read-only variable 'MAX_SIZE'

(1) const与数组

const变量可以用来指定数组大小吗?

C
const int SIZE = 10;
int arr[SIZE];

⚠️ 注意:在C89中,这不合法——数组大小必须是编译期常量,而 const int 在C中不是编译期常量(它只是运行期只读变量)。C99引入了变长数组(VLA),所以上面的代码在C99下可以编译,但VLA是可选特性,并非所有编译器都支持。

如果你需要一个真正的编译期常量来定义数组大小,用 #defineenum

(2) const与指针

const与指针的组合是C语言的经典难点,有三种情况:

C
int value = 42;

const int *p1 = &value;
int * const p2 = &value;
const int * const p3 = &value;
声明 含义
const int *p 指针指向的数据不可修改,指针本身可以指向别处
int * const p 指针本身不可修改(固定指向一处),指向的数据可以修改
const int * const p 指针和指向的数据都不可修改

💡 记忆技巧:看 const* 的左边还是右边。左边修饰数据,右边修饰指针。


3. #define宏常量

#define 是预处理器指令,用来定义宏常量:

C
#define PI 3.14159
#define MAX_SIZE 100
#define NEWLINE '\n'

预处理器会在编译前将代码中所有 PI 替换为 3.14159。它不是变量,不占内存,没有类型检查。

(1) #define vs const

特性 #define const
处理时机 预处理阶段(文本替换) 编译阶段
类型检查
作用域 从定义到文件末尾 遵循变量作用域规则
调试 调试器看不到宏名 调试器可以看到const变量
可取地址 不可以 可以

💡 建议:优先用 const,因为它有类型检查和作用域限制,更安全。#define 适合定义条件编译标志或需要编译期常量的场景。

(2) 宏定义的常见陷阱

C
#define DOUBLE(x) x + x
int result = DOUBLE(3) * 2;

你期望 3 + 3 * 2 = 12,实际展开为 3 + 3 * 2 = 9。宏是纯文本替换,不考虑运算优先级。

修正:给宏加括号:

C
#define DOUBLE(x) ((x) + (x))

4. 枚举常量

enum 是定义整数常量的另一种方式:

C
enum Color {
    RED,
    GREEN,
    BLUE
};

默认从0开始递增:RED = 0GREEN = 1BLUE = 2。你可以手动指定值:

C
enum Weekday {
    MON = 1,
    TUE,
    WED,
    THU,
    FRI,
    SAT = 100,
    SUN
};

TUE = 2WED = 3,...,SUN = 101(在前一个值基础上加1)。

枚举值是真正的编译期常量,可以用于数组大小等场景。相比 #define,枚举有类型信息,调试时能看到名字,推荐优先使用。


5. 隐式类型转换

当不同类型的数据参与运算时,C语言会自动进行类型转换。理解隐式转换规则,是避免bug的关键。

(1) 整数提升

在表达式中,charshort 会自动提升为 int 再参与运算:

C
char a = 10;
char b = 20;
printf("%zu\n", sizeof(a + b));
TEXT 📖 仅展示
4

a + b 的结果是 int 类型,占4字节,而不是1字节的char。

(2) 算术转换

当不同类型的值进行运算时,较低类型会自动转换为较高类型。转换等级从低到高:

C
int → unsigned int → long → unsigned long → long long → unsigned long long → float → double → long double

规则:运算前,较低等级的操作数自动转换为较高等级。

C
int a = 5;
double b = 2.0;
printf("%.1f\n", a / b);
TEXT 📖 仅展示
2.5

a 被自动转换为 double,然后执行浮点除法,结果为2.5。

(3) 赋值转换

赋值时,右值自动转换为左值的类型:

C
int x = 3.14;
double y = 42;

3.14 赋值给 int 变量,截断为 342 赋值给 double,变成 42.0

⚠️ 浮点→整数截断:浮点数赋值给整数类型时,小数部分直接丢弃(不是四舍五入):

C
int n = 2.9;

n 的值是2,不是3。

(4) 整数除法陷阱

这是初学者最容易踩的坑:

C
int a = 5;
int b = 2;
double result = a / b;
printf("%.1f\n", result);
TEXT 📖 仅展示
2.0

你期望2.5,但得到2.0。原因:a / b 是整数除法,结果为2(整数),然后2才被转换为double的2.0。

修正方法:让至少一个操作数为浮点数:

C
double result = (double)a / b;

或者:

C
double result = a * 1.0 / b;

或者:

C
double result = 5.0 / 2;

6. 显式强制转换

当隐式转换不符合你的需要时,可以用强制类型转换(cast)明确告诉编译器你要转换类型:

(1) 语法

C
(类型名)表达式

▶ 示例

C
#include <stdio.h>

int main(void) {
    int a = 7;
    int b = 2;

    printf("整数除法: %d\n", a / b);
    printf("强制转换: %.2f\n", (double)a / b);

    return 0;
}
▶ 试一试
TEXT 📖 仅展示
整数除法: 3
强制转换: 3.50

(double)a 先把a转为7.0,然后与b运算时b也被隐式提升为double,执行浮点除法。

▶ 示例

C
#include <stdio.h>
#include <math.h>

int main(void) {
    double x = 3.7;

    printf("直接截断: %d\n", (int)x);
    printf("四舍五入: %d\n", (int)(x + 0.5));
    printf("round函数: %.0f\n", round(x));

    return 0;
}
▶ 试一试
TEXT 📖 仅展示
直接截断: 3
四舍五入: 4
round函数: 4

💡 负数的截断:C99规定浮点→整数的截断是"向零取整"。所以 (int)(-3.7) 的结果是 -3,不是 -4。这与"向下取整"不同,需要注意。


7. 类型转换的常见陷阱

(1) 有符号与无符号混合

C
int a = -5;
unsigned int b = 10;

if (a + b > 0) {
    printf("大于0\n");
} else {
    printf("小于等于0\n");
}
TEXT 📖 仅展示
大于0

你可能以为 -5 + 10 = 5 > 0 输出"大于0"是对的,但这段代码的行为其实取决于隐式转换规则。当 intunsigned int 混合运算时,int 会被转换为 unsigned int-5 转为无符号后变成一个很大的正数,所以结果确实大于0——但这可能不是你期望的逻辑。

⚠️ 核心规则永远不要将有符号和无符号整数混合运算。如果必须,显式转换。

(2) 精度丢失

C
int big = 123456789;
float f = big;
printf("%d\n", big);
printf("%.0f\n", f);
TEXT 📖 仅展示
123456789
123456792

float 只有6-7位有效数字,无法精确表示9位整数,末尾3位被丢失。把大整数赋给float时要特别小心。

(3) 溢出转换

C
char ch = 200;
printf("%d\n", ch);

char 为有符号的平台上,200超出了char的范围(-128~127),结果是未定义行为,可能输出-56。如果需要存储0~255的值,用 unsigned char

(4) 指针转换

不同类型的指针之间直接强制转换是危险的:

C
int value = 0x41424344;
char *p = (char *)&value;
printf("%c\n", *p);

输出取决于系统的字节序(大端序还是小端序),结果不可移植。这种转换只在底层编程中使用,初学阶段应该避免。

❓ 常见问题

Q #define和const到底用哪个?
A 优先用const。const有类型检查、遵循作用域、调试友好。#define是纯文本替换,没有类型安全,但它是编译期常量,可以用在数组大小、条件编译等场景。如果一个值需要在编译期使用(如数组大小),用#define或enum;否则用const。
Q 为什么5/2的结果是2而不是2.5?
A 因为5和2都是int,C语言规定整数除以整数结果还是整数,小数部分直接丢弃。要得到2.5,至少让一个操作数变成浮点数:5.0/2或(double)5/2。这是C语言最常见的新手陷阱之一。
Q 强制类型转换会改变原变量的值吗?
A 不会。强制转换只是产生一个临时的转换结果,原变量的值和类型都不变。例如(int)3.7产生临时值3,但3.7本身还是3.7。
Q unsigned和signed混合运算为什么容易出bug?
A 因为C标准规定混合运算时有符号数会被隐式转为无符号数。负数转无符号后变成很大的正数,导致比较和运算结果完全不符合直觉。建议永远避免混用,或显式转换后再运算。

📖 小节

📝 作业

  1. 编写程序,分别用十进制、十六进制、八进制输出数字255,观察输出格式
  2. 编写程序,计算圆的面积(半径用int类型10,圆周率用const double定义),注意避免整数除法陷阱
  3. 编写程序,测试以下情况并记录结果:(int)3.9的值、(int)(-3.9)的值、char ch = 128后%d输出的值,解释每个结果的原因
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏