第2章 变量与数据类型
2.1 数据类型概述
在C语言中,数据类型用于定义变量可以存储的数据类型、变量占用的内存空间大小以及变量的取值范围。数据类型是C语言的基础,正确理解和使用数据类型对于编写高效、安全的C程序至关重要。
2.1.1 数据类型的分类
C语言的数据类型可以分为以下几类:
-
基本数据类型:
- 整型:用于表示整数
- 浮点型:用于表示带有小数部分的数值
- 字符型:用于表示单个字符
-
构造数据类型:由基本数据类型组合而成
- 数组:相同类型数据的集合
- 结构体:不同类型数据的集合
- 联合体:可以存储不同类型数据的特殊结构
- 枚举:一组命名的整数常量
-
指针类型:用于存储内存地址,可以指向任何数据类型
-
空类型:表示没有值的数据类型,用
void表示,主要用于函数返回值和指针类型
2.1.2 数据类型的重要性
- 内存管理:不同的数据类型占用不同大小的内存空间,合理选择数据类型可以节省内存
- 性能优化:合适的数据类型可以提高程序的执行效率
- 类型安全:编译器可以根据数据类型进行类型检查,减少编程错误
- 代码可读性:明确的数据类型可以提高代码的可读性和可维护性
章节趣事:布尔类型的引入
C语言中的布尔类型(bool)是在C99标准中才引入的,在此之前,C语言没有专门的布尔类型,通常使用int类型来表示布尔值,0表示false,非0表示true。
2.2 基本数据类型
2.2.1 整型
整型用于表示整数,包括正整数、负整数和零。C语言提供了多种整型,它们的区别在于占用的内存空间大小和表示的数值范围不同。
1. 整型的分类
C语言中的整型可以分为不同的类型,主要区别在于内存大小和取值范围:
| 数据类型 | 关键字 | 内存大小(典型值) | 最小值 | 最大值 | 格式化字符 |
|---|---|---|---|---|---|
| 短整型 | short 或 short int | 2字节 | -32768 | 32767 | %hd |
| 整型 | int | 4字节 | -2147483648 | 2147483647 | %d |
| 长整型 | long 或 long int | 4字节(32位系统)/ 8字节(64位系统) | -2147483648 | 2147483647(32位)/ 9223372036854775807(64位) | %ld |
| 长长整型 | long long 或 long long int | 8字节 | -9223372036854775808 | 9223372036854775807 | %lld |
2. 有符号与无符号整型
C语言中的整型默认是有符号的,可以使用signed关键字显式声明,也可以使用unsigned关键字声明为无符号整型。
- 有符号整型:可以表示正整数、负整数和零,最高位为符号位(0表示正数,1表示负数)
- 无符号整型:只能表示非负数,所有位都用于存储数值,没有符号位
| 数据类型 | 关键字 | 内存大小 | 数值范围 | 格式化字符 |
|---|---|---|---|---|
| 无符号短整型 | unsigned short 或 unsigned short int | 2字节 | 0 到 65535 | %hu |
| 无符号整型 | unsigned int | 4字节 | 0 到 4294967295 | %u |
| 无符号长整型 | unsigned long 或 unsigned long int | 4字节(32位)/ 8字节(64位) | 0 到 4294967295(32位)/ 18446744073709551615(64位) | %lu |
| 无符号长长整型 | unsigned long long 或 unsigned long long int | 8字节 | 0 到 18446744073709551615 | %llu |
3. 整型的存储方式
整型在计算机中以二进制形式存储,有符号整型使用补码表示法:
-
正数:原码、反码、补码相同
-
负数:
- 原码:最高位为1,其余位为数值的二进制表示
- 反码:原码除符号位外,其余位取反
- 补码:反码加1
-
零:只有一种表示方式,全0
示例:整型存储
以8位有符号整型为例:
- 十进制5的补码:00000101
- 十进制-5的补码:11111011(原码10000101 → 反码11111010 → 补码11111011)
4. 不同编译器下的整型大小差异
C标准只规定了整型的最小大小和大小关系,具体大小可能因编译器和操作系统而异:
| 系统/编译器 | short | int | long | long long |
|---|---|---|---|---|
| 32位Windows (MSVC) | 2字节 | 4字节 | 4字节 | 8字节 |
| 32位Linux (GCC) | 2字节 | 4字节 | 4字节 | 8字节 |
| 64位Windows (MSVC) | 2字节 | 4字节 | 4字节 | 8字节 |
| 64位Linux (GCC) | 2字节 | 4字节 | 8字节 | 8字节 |
| 64位macOS (Clang) | 2字节 | 4字节 | 8字节 | 8字节 |
5. 整型常量
整型常量可以用不同的进制表示:
- 十进制:默认表示,如
100、-20 - 八进制:以0开头,如
0123(十进制83) - 十六进制:以0x或0X开头,如
0x1A(十进制26)、0XFF(十进制255)
可以使用后缀指定整型常量的类型:
l或L:长整型,如100Lll或LL:长长整型,如100LLu或U:无符号整型,如100U- 组合后缀:如
100UL(无符号长整型)
6. 整型溢出问题
当整型变量的值超过其类型的最大值或低于最小值时,会发生溢出。溢出是未定义行为,结果不可预测。
示例:整型溢出
#include <stdio.h>
int main() {
unsigned char uc = 255;
signed char sc = 127;
printf("unsigned char 255 + 1 = %u\n", uc + 1); // 溢出,结果为0
printf("signed char 127 + 1 = %d\n", sc + 1); // 溢出,结果为-128
return 0;
}
注意事项
- 始终选择合适大小的整型,避免溢出
- 对于无符号整型,使用
unsigned关键字明确表示 - 当需要存储大整数时,使用
long long类型 - 避免依赖特定编译器下的整型大小,使用
sizeof运算符检查
疑难解析:整型溢出问题
当整型变量的值超过其类型的最大值或低于最小值时,会发生溢出。溢出是未定义行为,结果不可预测。例如,对于signed char类型,最大值是127,当执行char c = 127 + 1;时,结果可能是-128,这是因为补码表示的溢出行为。
易错部分:整型使用误区
- 混用有符号和无符号整型:当有符号整型和无符号整型进行运算时,有符号整型会被转换为无符号整型,可能导致意外结果。
- 忽略整型常量的类型:例如,
1000000000可能超出int类型的范围,应使用1000000000LL明确指定为长长整型。 - 使用错误的格式化字符:例如,使用
%d格式化无符号整型,可能导致输出错误。
2.2.2 浮点型
浮点型用于表示带有小数部分的数值,也可以表示整数。C语言提供了多种浮点型,它们的区别在于内存大小、精度和数值范围。
1. 浮点型的分类
| 数据类型 | 关键字 | 内存大小 | 精度 | 数值范围 | 格式化字符 |
|---|---|---|---|---|---|
| 单精度浮点型 | float | 4字节 | 约6-7位有效数字 | 1.2E-38 到 3.4E38 | %f, %e, %g |
| 双精度浮点型 | double | 8字节 | 约15-17位有效数字 | 2.2E-308 到 1.8E308 | %lf, %le, %lg |
| 长双精度浮点型 | long double | 8字节/12字节/16字节 | 约18-31位有效数字 | 3.3E-4932 到 1.1E4932 | %Lf, %Le, %Lg |
2. 浮点型的存储方式
浮点型在计算机中按照IEEE 754标准存储,分为三部分:
-
符号位(1位) :0表示正数,1表示负数
-
指数位:用于表示科学计数法中的指数
float:8位,偏移量127double:11位,偏移量1023
-
尾数位:用于表示科学计数法中的尾数
float:23位,隐含1位整数部分double:52位,隐含1位整数部分
示例:浮点型存储
以float类型的1.5为例:
- 二进制表示:1.1 × 2^0
- 符号位:0(正数)
- 指数位:0 + 127 = 127(二进制:01111111)
- 尾数位:1(二进制:10000000000000000000000)
- 完整二进制:0 01111111 10000000000000000000000
3. 浮点型的精度问题
浮点型是近似表示,存在精度限制。例如,某些十进制小数无法用二进制精确表示,会产生精度误差。
示例:浮点精度问题
#include <stdio.h>
int main() {
float f = 0.1;
double d = 0.1;
printf("float 0.1 = %.20f\n", f); // 输出:0.10000000149011611938
printf("double 0.1 = %.20f\n", d); // 输出:0.10000000000000000555
// 比较两个浮点数是否相等的正确方式
float a = 0.1 + 0.2;
float b = 0.3;
if (fabs(a - b) < 1e-6) {
printf("a 和 b 相等\n");
} else {
printf("a 和 b 不相等\n");
printf("a = %.20f\n", a); // 0.30000001192092895508
printf("b = %.20f\n", b); // 0.29999998211860656739
}
return 0;
}
4. 浮点型常量
浮点型常量可以用两种形式表示:
- 十进制形式:必须包含小数点,如
3.14、0.5、.5、5. - 科学计数法形式:使用指数表示,如
3.14e0、1.23e-4、5.67E+8
浮点型常量默认是double类型,可以使用后缀指定类型:
f或F:单精度浮点型,如3.14fl或L:长双精度浮点型,如3.14L
5. 浮点型的特殊值
根据IEEE 754标准,浮点型有几个特殊值:
- 正无穷大:
+infinity,如1.0 / 0.0 - 负无穷大:
-infinity,如-1.0 / 0.0 - NaN(Not a Number) :表示无效数值,如
0.0 / 0.0、sqrt(-1.0)
示例:浮点型特殊值
#include <stdio.h>
#include <math.h>
int main() {
float inf = 1.0f / 0.0f;
float neg_inf = -1.0f / 0.0f;
float nan = 0.0f / 0.0f;
printf("inf = %f\n", inf); // inf
printf("neg_inf = %f\n", neg_inf); // -inf
printf("nan = %f\n", nan); // nan
return 0;
}
注意事项
- 避免直接比较两个浮点数是否相等,应比较它们的差值是否小于一个很小的数(如1e-6)
- 优先使用
double类型,它具有更高的精度 - 了解浮点型的精度限制,避免在需要精确计算的场景中使用(如金融计算)
- 使用
float类型时,注意添加f后缀,否则会被视为double类型
疑难解析:为什么浮点数不能精确表示某些小数?
这是因为浮点数在计算机中以二进制形式存储,而某些十进制小数无法用有限的二进制小数精确表示。例如,十进制的0.1转换为二进制是一个无限循环小数:0.0001100110011...,因此无法用有限的二进制位精确表示,只能近似表示。
易错部分:浮点型使用误区
- 直接比较两个浮点数是否相等:由于精度问题,两个理论上相等的浮点数可能有微小差异,直接使用
==比较可能返回false。 - 使用浮点数作为循环计数器:由于精度累积误差,可能导致循环次数不符合预期。
- 忽略浮点常量的默认类型:浮点常量默认是
double类型,将其赋值给float变量时可能会有警告。
2.2.3 字符型
字符型用于表示单个字符,用char关键字表示,通常占用1字节内存。字符型实际上存储的是字符的ASCII码值,因此可以当作整数使用。
1. 字符型的分类
-
char:默认类型,具体是有符号还是无符号取决于编译器- 大多数编译器中,
char默认为signed char - 但在某些编译器(如ARM)中,
char默认为unsigned char
- 大多数编译器中,
-
signed char:有符号字符型,范围为-128到127 -
unsigned char:无符号字符型,范围为0到255
2. ASCII码表
ASCII(American Standard Code for Information Interchange)是美国信息交换标准代码,定义了128个字符的编码。
| ASCII码 | 字符 | 描述 |
|---|---|---|
| 0-31 | 不可打印字符 | 控制字符,如换行符、制表符等 |
| 32 | 空格 | 空格字符 |
| 48-57 | 0-9 | 数字字符 |
| 65-90 | A-Z | 大写字母 |
| 97-122 | a-z | 小写字母 |
| 127 | DEL | 删除字符 |
示例:字符与ASCII码的转换
#include <stdio.h>
int main() {
char ch1 = 'A';
char ch2 = 66;
printf("字符 'A' 的ASCII码:%d\n", ch1); // 输出:65
printf("ASCII码 66 对应的字符:%c\n", ch2); // 输出:B
// 字符型可以进行算术运算
char ch3 = 'A' + 32; // 大写字母转小写字母
printf("'A' + 32 = %c\n", ch3); // 输出:a
return 0;
}
3. 转义字符
转义字符用于表示一些特殊字符,以反斜杠``开头:
| 转义字符 | 描述 |
|---|---|
\n | 换行符 |
\t | 水平制表符 |
\r | 回车符 |
\b | 退格符 |
\f | 换页符 |
\a | 响铃符 |
\ | 反斜杠字符 |
' | 单引号字符 |
" | 双引号字符 |
\0 | 空字符(ASCII码为0) |
\xhh | 十六进制表示的字符,hh为两位十六进制数 |
\ddd | 八进制表示的字符,ddd为1-3位八进制数 |
示例:转义字符的使用
#include <stdio.h>
int main() {
printf("Hello\nWorld!\n"); // 换行
printf("Name\tAge\tGender\n"); // 制表符
printf(""C Language"\n"); // 输出双引号
printf("\path\to\file\n"); // 输出反斜杠
printf("\x41\x42\x43\n"); // 十六进制转义,输出ABC
printf("\101\102\103\n"); // 八进制转义,输出ABC
return 0;
}
4. 宽字符与多字节字符
除了基本的char类型,C语言还支持宽字符和多字节字符,用于表示非ASCII字符(如中文、日文等):
-
wchar_t:宽字符类型,用于表示Unicode字符,需要包含头文件<wchar.h>- 通常占用2字节或4字节内存
- 使用
L前缀表示宽字符常量,如L'中' - 使用
%lc格式化输出宽字符
-
C11标准新增了
char16_t和char32_t类型:char16_t:16位Unicode字符(UTF-16编码)char32_t:32位Unicode字符(UTF-32编码)
章节趣事:字符型的本质
在C语言中,字符类型(char)实际上是一种整数类型,它占用1字节内存,可以表示ASCII码表中的字符。例如,字符'A'的ASCII码值是65,因此char ch = 'A';等价于char ch = 65;。
2.2.4 布尔类型
C99标准引入了布尔类型,用于表示真和假。布尔类型的引入使代码更加清晰易读。
1. 布尔类型的使用
- 需要包含头文件
<stdbool.h> - 布尔类型的关键字是
bool - 布尔常量:
true(表示真,值为1)和false(表示假,值为0) - 布尔变量的大小通常为1字节
示例:布尔类型的使用
#include <stdio.h>
#include <stdbool.h>
int main() {
bool is_student = true;
bool is_adult = false;
printf("is_student = %d\n", is_student); // 输出:1
printf("is_adult = %d\n", is_adult); // 输出:0
// 布尔类型可以用于条件判断
if (is_student) {
printf("你是学生\n");
} else {
printf("你不是学生\n");
}
// 布尔类型可以进行逻辑运算
bool result = is_student && !is_adult;
printf("result = %d\n", result); // 输出:1
return 0;
}
2. 布尔类型的实现
在头文件<stdbool.h>中,布尔类型是通过宏定义实现的:
#define bool _Bool
#define true 1
#define false 0
#define __bool_true_false_are_defined 1
其中_Bool是C99标准引入的关键字,是真正的布尔类型。
章节练习:基本数据类型
- 编写程序,输出各种基本数据类型的大小、最小值和最大值。
- 编写程序,演示有符号整型和无符号整型的区别。
- 编写程序,验证浮点数的精度问题。
- 编写程序,演示字符型和整型的转换。
- 编写程序,使用布尔类型进行条件判断。
2.3 变量
变量是程序中用于存储数据的容器,它具有名称、类型和值。在C语言中,使用变量之前必须先声明,声明时需要指定变量的数据类型。
2.3.1 变量的声明与定义
在C语言中,变量的声明和定义是两个不同的概念:
-
变量声明:告诉编译器变量的名称和类型,但不分配内存空间
- 使用
extern关键字声明外部变量 - 例如:
extern int count;
- 使用
-
变量定义:告诉编译器变量的名称、类型和初始值(可选),并分配内存空间
- 例如:
int count = 10;
- 例如:
变量声明的语法格式:
数据类型 变量名;
例如:
int age; // 声明一个整型变量age
float height; // 声明一个浮点型变量height
char grade; // 声明一个字符型变量grade
bool is_student; // 声明一个布尔型变量is_student
可以在同一行声明多个同类型的变量:
int a, b, c; // 声明三个整型变量a、b、c
float x, y; // 声明两个浮点型变量x、y
2.3.2 变量的初始化
变量初始化是在声明变量的同时给它赋初始值。初始化可以确保变量在使用前有一个确定的值,避免使用未初始化的变量导致的未定义行为。
1. 初始化的方式
-
直接初始化:在声明时直接赋值
int age = 18; // 初始化整型变量age为18 float height = 1.75; // 初始化浮点型变量height为1.75 char grade = 'A'; // 初始化字符型变量grade为'A' bool is_student = true; // 初始化布尔型变量is_student为true -
赋值初始化:先声明后赋值
int age; // 声明变量age age = 18; // 赋值初始化 -
列表初始化(C99+) :使用大括号进行初始化
int a = {10}; // C99+列表初始化 int b = {}; // 初始化为0 int c[] = {1, 2, 3}; // 数组列表初始化
2. 未初始化变量的问题
未初始化的局部变量会包含随机值(垃圾值),使用未初始化的变量是未定义行为,结果不可预测。
示例:未初始化变量的问题
#include <stdio.h>
int main() {
int x; // 未初始化的局部变量
printf("x = %d\n", x); // 输出随机值,可能每次运行结果不同
return 0;
}
2.3.3 变量的命名规则
变量名必须遵循以下规则:
- 只能包含字母(a-zA-Z)、数字(0-9)和下划线(_)
- 不能以数字开头
- 区分大小写(age和Age是不同的变量)
- 不能使用C语言的关键字(如int、float、char等)
- 不能使用C语言的保留标识符(如__func__、__LINE__等)
1. 命名规范
良好的命名规范可以提高代码的可读性和可维护性:
- 驼峰命名法:第一个单词小写,后续单词首字母大写,如
studentCount、averageScore - 下划线命名法:单词之间用下划线连接,如
student_count、average_score(C语言常用) - 匈牙利命名法:变量名前加类型前缀,如
iCount、fScore(不推荐,现代C语言编程中很少使用)
2. 命名示例
| 类型 | 好的命名示例 | 不好的命名示例 |
|---|---|---|
| 整型 | student_count、age | a、123abc |
| 浮点型 | average_score、temperature | temp、f |
| 字符型 | first_name_initial、grade | c、char |
| 布尔型 | is_student、has_permission | flag、bool |
2.3.4 变量的作用域与生命周期
变量的作用域是指变量可以被访问的代码范围,生命周期是指变量在内存中存在的时间。
1. 作用域的分类
-
局部作用域:在函数内部或复合语句内部声明的变量,只能在该函数或复合语句内部访问
void func() { int x = 10; // 局部变量,作用域在func函数内部 if (x > 5) { int y = 20; // 局部变量,作用域在if语句块内部 printf("x = %d, y = %d\n", x, y); // 可以访问x和y } printf("x = %d\n", x); // 可以访问x // printf("y = %d\n", y); // 错误,无法访问y } -
全局作用域:在所有函数外部声明的变量,可以在整个程序中访问
int global_var = 100; // 全局变量,作用域在整个程序 void func1() { printf("global_var = %d\n", global_var); // 可以访问全局变量 } void func2() { printf("global_var = %d\n", global_var); // 可以访问全局变量 } -
函数原型作用域:在函数原型中声明的参数,作用域仅限于该函数原型
void func(int x); // x的作用域仅限于此函数原型 -
文件作用域:使用
static关键字声明的全局变量,作用域仅限于当前文件static int file_var = 50; // 文件作用域变量,只能在当前文件中访问
2. 存储类别
变量的存储类别决定了变量的存储位置和生命周期:
| 存储类别 | 关键字 | 存储位置 | 生命周期 | 作用域 | 初始化 |
|---|---|---|---|---|---|
| 自动存储 | auto(默认) | 栈 | 函数调用期间 | 局部 | 随机值(未初始化) |
| 静态存储 | static | 数据段 | 整个程序运行期间 | 局部或文件 | 0(未初始化) |
| 寄存器存储 | register | 寄存器(或栈) | 函数调用期间 | 局部 | 随机值(未初始化) |
| 外部存储 | extern | 数据段 | 整个程序运行期间 | 全局 | 0(未初始化) |
3. 静态变量的特点
static关键字可以用于局部变量和全局变量:
-
静态局部变量:
- 存储在数据段,生命周期为整个程序运行期间
- 只初始化一次,多次调用函数时保持上次的值
- 作用域仍然是局部的,只能在声明它的函数内部访问
-
静态全局变量:
- 存储在数据段,生命周期为整个程序运行期间
- 作用域仅限于当前文件,不能被其他文件访问
- 避免了全局变量的命名冲突
示例:静态局部变量
#include <stdio.h>
void count() {
static int cnt = 0; // 静态局部变量,只初始化一次
cnt++;
printf("cnt = %d\n", cnt);
}
int main() {
count(); // 输出:cnt = 1
count(); // 输出:cnt = 2
count(); // 输出:cnt = 3
return 0;
}
2.3.5 变量的使用最佳实践
-
始终初始化变量,避免使用未初始化的变量
-
选择合适的数据类型,避免内存浪费和溢出问题
-
使用有意义的变量名,提高代码可读性
-
尽量使用局部变量,减少全局变量的使用
- 全局变量会占用整个程序运行期间的内存
- 全局变量容易导致命名冲突
- 全局变量会使函数之间产生耦合,不利于代码维护
-
避免变量作用域过大,尽量缩小变量的作用域
-
合理使用静态变量,只在需要时使用
-
不要使用
register关键字,现代编译器会自动优化寄存器分配
疑难解析:局部变量和全局变量的区别
局部变量和全局变量的主要区别在于作用域和生命周期:
- 作用域:局部变量只在声明它的函数或语句块内有效;全局变量在整个程序中有效。
- 生命周期:局部变量在函数调用时创建,函数返回时销毁;全局变量在程序启动时创建,程序结束时销毁。
- 存储位置:局部变量通常存储在栈上;全局变量存储在数据段。
- 初始化:局部变量如果未初始化,其值是随机的;全局变量如果未初始化,会被自动初始化为0。
易错部分:变量使用误区
- 使用未初始化的局部变量:未初始化的局部变量值是随机的,使用它会导致未定义行为。
- 变量名与关键字或函数名冲突:例如,使用
int float;或int printf;会导致编译错误。 - 全局变量命名冲突:不同文件中的全局变量如果同名,会导致链接错误。
- 变量作用域过大:变量作用域过大容易导致意外修改,降低代码的可维护性。
2.3.6 变量的使用示例
#include <stdio.h>
// 全局变量
int global_var = 100;
void func() {
// 局部变量
int local_var = 200;
// 访问全局变量和局部变量
printf("global_var = %d\n", global_var);
printf("local_var = %d\n", local_var);
// 修改全局变量
global_var = 300;
}
int main() {
// 访问全局变量
printf("初始 global_var = %d\n", global_var);
// 调用函数
func();
// 查看修改后的全局变量
printf("修改后 global_var = %d\n", global_var);
// 局部变量
int a = 10;
int b = 20;
int sum = a + b;
printf("a = %d, b = %d, sum = %d\n", a, b, sum);
return 0;
}
运行结果:
初始 global_var = 100
global_var = 100
local_var = 200
修改后 global_var = 300
a = 10, b = 20, sum = 30
2.4 sizeof运算符
sizeof是C语言的一个运算符,用于获取数据类型或变量占用的内存字节数。其语法格式:
sizeof(数据类型) 或 sizeof(变量名)
例如:
#include <stdio.h>
int main() {
printf("sizeof(char) = %d\n", sizeof(char));
printf("sizeof(short) = %d\n", sizeof(short));
printf("sizeof(int) = %d\n", sizeof(int));
printf("sizeof(long) = %d\n", sizeof(long));
printf("sizeof(long long) = %d\n", sizeof(long long));
printf("sizeof(float) = %d\n", sizeof(float));
printf("sizeof(double) = %d\n", sizeof(double));
int a = 10;
printf("sizeof(a) = %d\n", sizeof(a));
return 0;
}
运行结果(不同编译器可能有所不同):
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4
sizeof(long) = 4
sizeof(long long) = 8
sizeof(float) = 4
sizeof(double) = 8
sizeof(a) = 4
章节趣事:sizeof运算符的特殊性
sizeof运算符是C语言中唯一的一个以关键字形式出现的运算符,它不是函数。因此,在计算变量的大小时,可以省略括号,例如sizeof a等价于sizeof(a)。
易错部分:sizeof运算符使用误区
- 将sizeof用于表达式时:sizeof运算符在编译时求值,因此
sizeof(i++)不会导致i的值增加。 - 混淆sizeof返回值的类型:sizeof运算符返回的是
size_t类型,而不是int类型,在printf中应该使用%zu格式化输出。 - 错误计算数组元素个数:对于数组
int arr[10];,sizeof(arr)返回的是整个数组的大小,而不是元素个数,元素个数应该是sizeof(arr) / sizeof(arr[0])。
2.5 常量
常量是程序中固定不变的值,分为字面常量和符号常量。
2.5.1 字面常量
字面常量是直接出现在程序中的值,例如:
- 整型常量:
100、-20、0 - 浮点型常量:
3.14、-0.5、1.23e4 - 字符常量:
'A'、'1'、'\n' - 字符串常量:
"Hello"、"C Language"
2.5.2 符号常量
符号常量是用一个标识符来表示的常量,使用#define预处理命令定义。语法格式:
#define 常量名 常量值
例如:
#include <stdio.h>#define PI 3.14159
#define MAX_AGE 120
int main() {
float radius = 5.0;
float area = PI * radius * radius;
printf("圆的面积 = %.2f\n", area);
printf("最大年龄 = %d\n", MAX_AGE);
return 0;
}
运行结果:
圆的面积 = 78.54
最大年龄 = 120
2.5.3 const关键字
C语言还可以使用const关键字来定义常量,这种常量称为常变量。语法格式:
const 数据类型 常量名 = 常量值;
例如:
#include <stdio.h>
int main() {
const float PI = 3.14159;
const int MAX_AGE = 120;
float radius = 5.0;
float area = PI * radius * radius;
printf("圆的面积 = %.2f\n", area);
printf("最大年龄 = %d\n", MAX_AGE);
return 0;
}
注意事项
- 使用
#define定义的符号常量没有数据类型,只是简单的文本替换 - 使用
const定义的常变量有数据类型,编译器会进行类型检查 - 建议优先使用
const定义常量,因为它更安全
疑难解析:#define和const的区别
主要区别如下:
#define是预处理命令,在编译前进行文本替换;const是C语言关键字,用于定义常变量。#define没有数据类型,只是简单的文本替换;const有数据类型,编译器会进行类型检查。#define定义的常量没有内存地址;const定义的常变量有内存地址。#define可以定义宏,带有参数;const不能定义带参数的常量。
章节练习:变量与常量
- 声明并初始化各种类型的变量,并输出它们的值。
- 使用
#define和const分别定义常量,并比较它们的使用方式。 - 编写程序,演示静态变量的特性。
- 使用sizeof运算符计算不同数据类型的大小,并使用正确的格式化字符输出结果。
- 编写程序,演示全局变量和局部变量的作用域和生命周期。
章节练习
-
声明并初始化以下变量:
- 一个整型变量
num,初始值为100 - 一个浮点型变量
price,初始值为9.99 - 一个字符型变量
ch,初始值为'X' - 一个布尔型变量
is_valid,初始值为false
- 一个整型变量
-
编写程序,使用
sizeof运算符输出各种数据类型的内存大小,并比较不同编译器下的结果。 -
使用
#define定义一个符号常量MAX_LENGTH,值为100,然后在程序中使用它。 -
使用
const定义一个常变量MIN_AGE,值为18,然后在程序中使用它。 -
编写一个程序,演示局部变量和全局变量的作用域和生命周期。
-
编写一个程序,计算并输出不同数据类型的取值范围(例如:char类型的取值范围是-128到127)。
疑难解析
Q: 为什么不同编译器下,int类型的大小可能不同?
A: C标准只规定了各数据类型的最小大小,而没有规定具体大小。例如,C标准规定int类型的大小至少为16位,但具体大小由编译器决定。在32位系统上,int类型通常为32位;在64位系统上,int类型也可能为32位或64位,这取决于编译器的实现。
Q: signed和unsigned关键字有什么区别?
A: signed关键字表示有符号类型,可以表示正数、负数和零;unsigned关键字表示无符号类型,只能表示非负数。例如,signed char的取值范围是-128到127,而unsigned char的取值范围是0到255。
Q: #define和const有什么区别?
A: 主要区别如下:
#define是预处理命令,在编译前进行文本替换;const是C语言关键字,用于定义常变量。#define没有数据类型,只是简单的文本替换;const有数据类型,编译器会进行类型检查。#define定义的常量没有内存地址;const定义的常变量有内存地址。#define可以定义宏,带有参数;const不能定义带参数的常量。
Q: 什么是类型转换?什么时候会发生类型转换?
A: 类型转换是指将一种数据类型转换为另一种数据类型。类型转换可以分为隐式转换和显式转换。隐式转换是由编译器自动完成的,例如将int类型的值赋值给float类型的变量时,编译器会自动将int类型转换为float类型。显式转换是通过强制类型转换运算符()来实现的,例如将float类型的值转换为int类型:int a = (int)3.14;。
易错部分
- 变量未初始化:在C语言中,局部变量如果未初始化,其值是不确定的(垃圾值),使用这样的变量可能会导致程序出错。因此,使用局部变量前一定要初始化。
- 类型不匹配:例如,将一个超过int类型取值范围的数赋值给int类型的变量,会导致溢出错误。或者将float类型的值直接赋值给int类型的变量,会导致小数部分丢失。
- 混淆字符和字符串:字符使用单引号括起来(如'A'),而字符串使用双引号括起来(如"A")。字符变量只能存储一个字符,而字符串需要使用字符数组或指针来存储。
- 全局变量使用不当:过多使用全局变量会导致程序的可维护性降低,因为全局变量可以被任何函数修改,容易导致意外的副作用。因此,应该尽量少使用全局变量。
- sizeof运算符使用错误:sizeof运算符返回的是数据类型或变量占用的内存字节数,而不是元素个数。例如,对于数组
int arr[10];,sizeof(arr)返回的是整个数组占用的内存字节数(40字节,假设int为4字节),而不是数组的元素个数(10)。
章节趣事
C语言中的布尔类型(bool)是在C99标准中才引入的,在此之前,C语言没有专门的布尔类型,通常使用int类型来表示布尔值,0表示false,非0表示true。
在C语言中,字符类型(char)实际上是一种整数类型,它占用1字节内存,可以表示ASCII码表中的字符。例如,字符'A'的ASCII码值是65,因此char ch = 'A';等价于char ch = 65;。
sizeof运算符是C语言中唯一的一个以关键字形式出现的运算符,它不是函数。因此,在计算变量的大小时,可以省略括号,例如sizeof a等价于sizeof(a)。
C语言中的变量命名规则是:变量名只能由字母、数字和下划线组成,且不能以数字开头。但实际上,C语言还支持使用Unicode字符作为变量名,只要编译器支持。不过,为了代码的可移植性,通常只使用字母、数字和下划线。