第3章 C语言教程 - 数据类型、运算符和表达式
第3章 数据类型、运算符和表达式
C 语言的数据类型
基本数据类型
C 语言的基本数据类型是构建其他数据结构的基础,包括整型、浮点型和字符型。以下是详细介绍:
整型
整型用于表示整数,包括有符号和无符号两种形式:
| 类型 | 说明 | 存储大小(典型) | 值范围 | 存储方式 |
|---|---|---|---|---|
char | 字符型 | 1 字节 | -128 到 127 或 0 到 255 | ASCII 编码 |
unsigned char | 无符号字符型 | 1 字节 | 0 到 255 | 无符号二进制 |
signed char | 有符号字符型 | 1 字节 | -128 到 127 | 补码表示 |
short | 短整型 | 2 字节 | -32768 到 32767 | 补码表示 |
unsigned short | 无符号短整型 | 2 字节 | 0 到 65535 | 无符号二进制 |
int | 整型 | 4 字节 | -2147483648 到 2147483647 | 补码表示 |
unsigned int | 无符号整型 | 4 字节 | 0 到 4294967295 | 无符号二进制 |
long | 长整型 | 4 或 8 字节 | -2147483648 到 2147483647 或更大 | 补码表示 |
unsigned long | 无符号长整型 | 4 或 8 字节 | 0 到 4294967295 或更大 | 无符号二进制 |
long long | 双长整型 | 8 字节 | -9223372036854775808 到 9223372036854775807 | 补码表示 |
unsigned long long | 无符号双长整型 | 8 字节 | 0 到 18446744073709551615 | 无符号二进制 |
浮点型
浮点型用于表示带有小数部分的数值:
| 类型 | 说明 | 存储大小 | 值范围 | 精度 |
|---|---|---|---|---|
float | 单精度浮点型 | 4 字节 | 约 ±3.4e-38 到 ±3.4e+38 | 约 7 位有效数字 |
double | 双精度浮点型 | 8 字节 | 约 ±1.7e-308 到 ±1.7e+308 | 约 15-17 位有效数字 |
long double | 长双精度浮点型 | 8、10 或 16 字节 | 因编译器而异 | 因编译器而异 |
浮点数的 IEEE 754 标准表示
浮点数在计算机中按照 IEEE 754 标准存储:
- 单精度 (float):1 位符号位,8 位指数位,23 位尾数位
- 双精度 (double):1 位符号位,11 位指数位,52 位尾数位
这种表示方法可以表示非常大或非常小的数值,但也会导致一些精度问题,例如:
1 | float a = 0.1; |
字符型
字符型用于表示单个字符:
- ASCII 编码:标准 ASCII 码使用 7 位,表示 128 个字符
- 扩展 ASCII:使用 8 位,表示 256 个字符
- Unicode:可以表示更多字符,C11 标准支持 Unicode 字符
类型修饰符
C 语言提供了以下类型修饰符,用于修改基本数据类型的属性:
signed- 有符号类型,表示可以存储正数、负数和零(默认)unsigned- 无符号类型,表示只能存储非负数short- 短类型,通常使用较少的内存long- 长类型,通常使用较多的内存
类型定义
使用 typedef 关键字可以为现有类型创建别名,提高代码的可读性和可维护性:
1 | // 为基本类型创建别名 |
派生类型
C 语言还支持以下派生类型:
- 数组 - 相同类型元素的集合
- 指针 - 存储内存地址的变量
- 结构体 - 不同类型元素的集合
- 联合体 - 可以存储不同类型值的特殊类型
- 枚举 - 一组命名的整型常量
- 函数 - 执行特定任务的代码块
变量和常量
变量
变量是存储数据的内存位置,具有名称、类型和值。
变量声明
变量声明告诉编译器变量的名称和类型,但不分配内存:
1 | // 声明单个变量 |
变量定义
变量定义不仅声明变量,还为其分配内存并可选地初始化:
1 | // 定义并初始化变量 |
变量命名规则
- 变量名只能包含字母、数字和下划线
- 变量名不能以数字开头
- 变量名区分大小写(如
count和Count是不同的变量) - 变量名不能是 C 语言的关键字
- 变量名应该具有描述性,便于理解代码
变量的作用域
变量的作用域是指变量在程序中可访问的区域:
- 局部变量:在函数或代码块内部定义,只在定义它的函数或代码块内可见
- 全局变量:在所有函数外部定义,在整个程序中可见
- 静态局部变量:在函数内部定义,使用
static修饰,在函数调用之间保持值 - 块作用域变量:在代码块内部定义,只在该代码块内可见
1 | // 全局变量 |
变量的生命周期
变量的生命周期是指变量存在的时间:
- 自动变量:局部变量默认是自动变量,在进入作用域时创建,离开作用域时销毁
- 静态变量:使用
static修饰的变量,在程序开始时创建,程序结束时销毁 - 全局变量:在程序开始时创建,程序结束时销毁
- 动态分配的变量:使用
malloc()等函数分配的变量,在显式释放前一直存在
常量
常量是固定值,在程序执行过程中不会改变。
字面常量
字面常量是直接出现在代码中的值:
1 | // 整数常量 |
符号常量
使用 #define 预处理指令定义符号常量:
1 | // 定义数值常量 |
const 修饰符
使用 const 修饰符创建常量:
1 | // 基本类型常量 |
枚举常量
使用 enum 关键字定义枚举类型,枚举类型的成员是常量:
1 | // 定义枚举类型 |
常量的使用场景
- 字面常量:直接在代码中使用的固定值
- 符号常量:使用
#define定义的常量,适用于在多个地方使用的值 - const 常量:使用
const修饰符定义的常量,具有类型检查 - 枚举常量:使用
enum定义的常量,适用于一组相关的常量值
运算符
算术运算符
算术运算符用于执行基本的数学运算:
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
+ | 加法 | 5 + 3 | 8 |
- | 减法 | 5 - 3 | 2 |
* | 乘法 | 5 * 3 | 15 |
/ | 除法 | 5 / 3 | 1(整数除法) |
/ | 除法 | 5.0 / 3.0 | 1.666...(浮点数除法) |
% | 取模(求余数) | 5 % 3 | 2 |
++ | 自增(前缀) | ++a | a+1,先自增后使用 |
++ | 自增(后缀) | a++ | a,先使用后自增 |
-- | 自减(前缀) | --a | a-1,先自减后使用 |
-- | 自减(后缀) | a-- | a,先使用后自减 |
赋值运算符
赋值运算符用于将值赋给变量:
| 运算符 | 描述 | 示例 | 等同于 |
|---|---|---|---|
= | 简单赋值 | a = b | a = b |
+= | 加法赋值 | a += b | a = a + b |
-= | 减法赋值 | a -= b | a = a - b |
*= | 乘法赋值 | a *= b | a = a * b |
/= | 除法赋值 | a /= b | a = a / b |
%= | 取模赋值 | a %= b | a = a % b |
<<= | 左移赋值 | a <<= b | a = a << b |
>>= | 右移赋值 | a >>= b | a = a >> b |
&= | 按位与赋值 | a &= b | a = a & b |
^= | 按位异或赋值 | a ^= b | a = a ^ b |
| ` | =` | 按位或赋值 | `a |
关系运算符
关系运算符用于比较两个值,返回布尔值(0 表示假,非 0 表示真):
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
== | 等于 | 5 == 3 | 0(假) |
!= | 不等于 | 5 != 3 | 1(真) |
< | 小于 | 5 < 3 | 0(假) |
> | 大于 | 5 > 3 | 1(真) |
<= | 小于等于 | 5 <= 3 | 0(假) |
>= | 大于等于 | 5 >= 3 | 1(真) |
逻辑运算符
逻辑运算符用于组合布尔值,返回布尔值:
| 运算符 | 描述 | 示例 | 结果 | 特点 |
|---|---|---|---|---|
&& | 逻辑与 | a && b | 只有当 a 和 b 都为真时才为真 | 短路求值 |
| ` | ` | 逻辑或 | `a | |
! | 逻辑非 | !a | 当 a 为假时为真,反之亦然 | 一元运算符 |
短路求值
逻辑与和逻辑或运算符具有短路求值的特点:
- 逻辑与:如果第一个操作数为假,则不会计算第二个操作数
- 逻辑或:如果第一个操作数为真,则不会计算第二个操作数
1 | int a = 0, b = 5; |
位运算符
位运算符用于操作整数的二进制位:
| 运算符 | 描述 | 示例 | 二进制表示 | 结果 |
|---|---|---|---|---|
& | 按位与 | 5 & 3 | 101 & 011 | 001 (1) |
| ` | ` | 按位或 | `5 | 3` |
^ | 按位异或 | 5 ^ 3 | 101 ^ 011 | 110 (6) |
~ | 按位取反 | ~5 | ~00000101 | 11111010 (-6) |
<< | 左移 | 5 << 1 | 101 << 1 | 1010 (10) |
>> | 右移(有符号) | 5 >> 1 | 0101 >> 1 | 0010 (2) |
>> | 右移(有符号) | -5 >> 1 | 1011 >> 1 | 1101 (-3) |
>>> | 右移(无符号,C++) | 5 >>> 1 | 0101 >>> 1 | 0010 (2) |
条件运算符
条件运算符(三元运算符)是 C 语言中唯一的三元运算符:
1 | condition ? expression1 : expression2 |
如果 condition 为真,则返回 expression1 的值,否则返回 expression2 的值。
1 | int x = 10, y = 20; |
逗号运算符
逗号运算符用于分隔表达式,从左到右计算,返回最后一个表达式的值:
1 | // 基本用法 |
地址运算符
地址运算符用于获取变量的内存地址:
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
& | 取地址 | &a | 变量 a 的内存地址 |
* | 间接寻址(解引用) | *p | 指针 p 指向的值 |
成员运算符
成员运算符用于访问结构体和联合体的成员:
| 运算符 | 描述 | 示例 | 结果 |
|---|---|---|---|
. | 直接成员访问 | s.member | 结构体 s 的成员 member |
-> | 间接成员访问 | p->member | 指针 p 指向的结构体的成员 member |
运算符优先级和结合性
运算符的优先级决定了表达式中运算的执行顺序,结合性决定了具有相同优先级的运算符的执行顺序:
| 优先级 | 运算符 | 结合性 | 描述 |
|---|---|---|---|
| 1 | () [] -> . | 从左到右 | 括号、数组下标、结构体成员 |
| 2 | ! ~ ++ -- - + * & sizeof (type) | 从右到左 | 逻辑非、按位取反、自增、自减、一元加减、间接寻址、取地址、 sizeof、类型转换 |
| 3 | * / % | 从左到右 | 乘法、除法、取模 |
| 4 | + - | 从左到右 | 加法、减法 |
| 5 | << >> | 从左到右 | 左移、右移 |
| 6 | < <= > >= | 从左到右 | 小于、小于等于、大于、大于等于 |
| 7 | == != | 从左到右 | 等于、不等于 |
| 8 | & | 从左到右 | 按位与 |
| 9 | ^ | 从左到右 | 按位异或 |
| 10 | ` | ` | 从左到右 |
| 11 | && | 从左到右 | 逻辑与 |
| 12 | ` | ` | |
| 13 | ?: | 从右到左 | 条件运算符 |
| 14 | = += -= *= /= %= <<= >>= &= ^= ` | =` | 从右到左 |
| 15 | , | 从左到右 | 逗号运算符 |
表达式
表达式是由运算符和操作数组成的序列,计算后产生一个值。
表达式的类型
根据表达式的结果类型,表达式可以分为:
- 算术表达式:结果为数值
- 关系表达式:结果为布尔值
- 逻辑表达式:结果为布尔值
- 位表达式:结果为整数
- 赋值表达式:结果为赋值后变量的值
- 条件表达式:结果为两个表达式之一的值
- 逗号表达式:结果为最后一个表达式的值
- 函数调用表达式:结果为函数的返回值
表达式求值
表达式求值的过程遵循以下规则:
- 运算符优先级:高优先级的运算符先执行
- 运算符结合性:相同优先级的运算符按照结合性执行
- 括号:括号内的表达式先执行
- 副作用:表达式执行过程中可能产生副作用,如修改变量的值
- 序列点:表达式中某些点,确保之前的副作用已完成
副作用和序列点
副作用是指表达式执行过程中对状态的修改,如修改变量的值:
1 | int a = 5; |
序列点是表达式执行过程中的某些点,确保之前的所有副作用已完成:
- 分号(;)
- 逗号运算符(,)
- 逻辑与运算符(&&)的左操作数之后
- 逻辑或运算符(||)的左操作数之后
- 条件运算符(?:)的第一个操作数之后
- 函数调用的所有参数求值之后,函数体执行之前
在序列点之间,多次修改同一个变量可能导致未定义的行为:
1 | int a = 5; |
类型转换
隐式类型转换
当不同类型的操作数进行运算时,C 语言会自动进行类型转换,称为隐式类型转换。转换规则如下:
- 整数提升:char、short 类型会被提升为 int 类型
- 常用算术转换:当两个操作数类型不同时,会转换为共同的类型
- 如果一个操作数是 long double,则另一个也转换为 long double
- 否则,如果一个操作数是 double,则另一个也转换为 double
- 否则,如果一个操作数是 float,则另一个也转换为 float
- 否则,进行整数提升,然后:
- 如果一个操作数是 unsigned long long,则另一个也转换为 unsigned long long
- 否则,如果一个操作数是 long long,另一个是 unsigned long,则都转换为 unsigned long long
- 否则,如果一个操作数是 long long,则另一个也转换为 long long
- 否则,如果一个操作数是 unsigned long,则另一个也转换为 unsigned long
- 否则,如果一个操作数是 long,另一个是 unsigned int,则都转换为 unsigned long 或 long(取决于大小)
- 否则,如果一个操作数是 long,则另一个也转换为 long
- 否则,都转换为 unsigned int
1 | int a = 10; |
显式类型转换(强制类型转换)
使用强制类型转换可以显式地将一个类型转换为另一个类型:
1 | // 基本类型转换 |
表达式示例
1 | // 算术表达式 |
类型限定符
C 语言提供了以下类型限定符,用于限定变量的属性:
const 限定符
const 限定符表示变量的值不能修改:
1 | // 基本类型常量 |
volatile 限定符
volatile 限定符表示变量的值可能被外部因素修改,如硬件、中断处理程序等:
1 | // 硬件寄存器 |
restrict 限定符
restrict 限定符表示指针是访问所指向对象的唯一方式,用于优化编译器生成的代码:
1 | // 内存复制函数 |
_Atomic 限定符
_Atomic 限定符(C11 标准)表示变量是原子的,用于多线程编程:
1 |
|
小结
本章详细介绍了 C 语言的数据类型、运算符和表达式,包括:
- 数据类型:基本数据类型(整型、浮点型、字符型)、类型修饰符、类型定义、派生类型
- 变量和常量:变量的声明和定义、变量的作用域和生命周期、常量的类型和使用场景
- 运算符:算术运算符、赋值运算符、关系运算符、逻辑运算符、位运算符、条件运算符、逗号运算符等
- 表达式:表达式求值、副作用和序列点、类型转换
- 类型限定符:const、volatile、restrict、_Atomic
这些是 C 语言编程的基础,掌握这些概念对于后续学习控制语句、函数、数组和指针等高级内容至关重要。在实际编程中,需要根据具体情况选择合适的数据类型,合理使用运算符和表达式,以及正确处理类型转换和副作用。
通过本章的学习,你应该能够:
- 理解 C 语言的数据类型系统
- 正确声明和使用变量和常量
- 掌握各种运算符的使用方法和优先级
- 理解表达式求值的规则和类型转换
- 合理使用类型限定符提高代码的安全性和效率
在接下来的章节中,我们将学习 C 语言的控制语句,包括条件语句、循环语句和跳转语句,这些是实现程序逻辑的重要工具。



