好的,我们来逐一详细解答这些问题。这些是C语言乃至很多编程语言中非常核心的基础概念。
1. 标识符、关键字和预定义标识符的区别
- 标识符
- 定义:由程序员自行定义的名称,用于命名变量、函数、数组、结构体等各种程序实体。
- 规则:通常以字母或下划线
_开头,后跟字母、数字或下划线。(注意:C语言标准规定标识符不能以数字开头)。 - 示例:
myVariable,sum,_count,MAX_SIZE。
- 关键字
- 定义:也称为“保留字”。是语言本身预先定义好的、具有特殊含义和功能的单词。
- 特点:绝对不能用作标识符。编译器会将这些词作为指令来处理。
- 示例:
int,if,else,for,while,return,sizeof。
- 预定义标识符
- 定义:在标准库中预先定义好的标识符,通常用于库函数名或宏名。
- 特点:理论上你可以用自己的标识符去覆盖它们(重定义),但强烈不建议这样做,否则会导致对应的库函数无法使用,引发混乱和难以调试的错误。
- 示例:
printf,scanf,main,EOF,NULL。
| 特性 | 标识符 | 关键字 | 预定义标识符 |
|---|---|---|---|
| 定义者 | 程序员 | 语言标准 | 标准库 |
| 是否可自定义 | 是 | 否 (保留字) | 技术上可以,但绝对不要 |
| 用途 | 命名变量、函数等 | 构成程序的控制结构、类型等 | 提供标准库功能 |
| 示例 | myVar | int, if | printf, main |
核心区别:关键字是语言的“语法骨架”,不可更改;标识符是程序员的“命名标签”,自由定义;预定义标识符是标准库的“工具名称”,最好不要去重新定义它们。
2. 语法检查 vs. 语义检查
这两个检查是编译过程中的重要阶段。
- 语法检查
- 检查内容:程序代码的结构是否符合语言的文法规则。它只关心形式,不关心含义。
- 类比:检查一句英语的单词顺序、标点符号是否正确。例如,“Apple red is.” 在语法上是错误的(正确的语法应该是“Apple is red.”)。
- 编译器行为:发现语法错误会报错(如
error: expected ';' before '}' token)。 - 示例:缺少分号
;、括号不匹配、关键字拼写错误。
- 语义检查
- 检查内容:在语法正确的基础上,检查代码的含义是否合法、是否有意义。
- 类比:检查一句语法正确的英语是否有逻辑错误。例如,“The rock is eating an apple.” 语法完全正确,但语义荒谬(石头不会吃苹果)。
- 编译器行为:发现语义错误会报错或警告(如
warning: assignment to 'int' from 'float' truncates value)。 - 示例:类型不匹配、使用未声明的变量、给函数传入不匹配的参数、除数为零(常量情况下)。
| 方面 | 语法检查 | 语义检查 |
|---|---|---|
| 关心什么 | 形式(Form) | 含义(Meaning) |
| 检查层次 | 表面结构 | 深层逻辑 |
| 类比 | 单词拼写和句子结构 | 句子的逻辑和常识 |
| 错误示例 | int a = 5 (缺分号) | int b = "hello"; (类型不匹配) |
核心区别:语法是关于“写法对不对”,语义是关于“意思通不通”。
3. 表达式、语句和代码块
- 表达式
- 定义:由运算符、操作数和函数调用组成的有值的代码单元。表达式的主要目的是计算出一个值。
- 示例:
5 + 3 * 2(值为11)x = 10(值为10,同时有副作用)printf("Hello")(值为返回值,即打印的字符数)x(值为变量x的值)a > b(值为1或0)
- 语句
- 定义:构成程序执行流程的完整指令。C语言中,语句通常以分号
;结尾。 - 关键:表达式加上分号就构成了表达式语句。但语句不一定是表达式(如控制流语句
if,while等)。 - 示例:
;(空语句)x = 10;(表达式语句)printf("Hello\n");(表达式语句)if (x > 0) { ... }(控制语句)return 0;(跳转语句)
- 定义:构成程序执行流程的完整指令。C语言中,语句通常以分号
- 代码块
- 定义:由一对花括号
{}括起来的多条语句的集合。也称为复合语句。 - 作用:1. 将多条语句在语法上合并为一条语句(这样
if、for后面就能跟多条语句了);2. 创建一个新的作用域(在块内声明的变量通常在块外不可访问)。 - 示例:
c { // 代码块开始 int temp = a; a = b; b = temp; // 这三条语句合成为一个代码块 } // 代码块结束
- 定义:由一对花括号
关系:多个表达式可以组成一个语句,多个语句可以用 {} 组成一个代码块。
4. 左值、右值、对象、副作用、未定义行为
这些是C语言标准文档中的术语,描述了程序执行模型。
- 对象
- 定义:C语言中的“对象”指的是内存中一个可标识的、具有特定类型和大小的存储区域,用于存储值。它不仅仅指“面向对象编程”中的对象。
- 示例:
int a;定义后,a就是一个(int类型的)对象。
- 左值
- 原意:指可以出现在赋值运算符左边的表达式。它标识了一个对象,而不仅仅是一个值。
- 现代理解:一个指定了一个对象的表达式,代表了一个可被引用的内存位置。简单说,有地址、有名字、持久存在的实体。
- 示例:变量名(
a)、数组元素(arr[0])、通过指针解引用(*p)。
- 右值
- 原意:指只能出现在赋值运算符右边的表达式。它提供了一个值,但不标识一个存储位置。
- 现代理解:一个仅提供值的临时表达式。它通常是短暂的,没有可被程序显式使用的地址。
- 示例:字面量(
10,‘A’)、算术表达式的结果(a + b)、函数返回值(非左值引用时)。
- 副作用
- 定义:对执行环境的状态的改变。表达式求值的主要目的是得到结果值,但如果它顺便修改了某个对象的值,就产生了副作用。
- 示例:
- 赋值运算符
=:主要副作用是修改左操作数的值。 - 自增/自减
++/--:主要副作用是修改操作数的值。 - 函数调用:如果函数内部修改了全局变量或静态变量,或者通过指针修改了参数,也产生了副作用。
- 赋值运算符
- 未定义行为
- 定义:C语言标准没有明确规定行为方式的一些代码写法。编译器遇到UB时,可以做出任何处理,包括产生看似正常的结果、崩溃、产生不可预测的结果等。
- 原因:为了给编译器实现最大的自由度以进行优化,并适应不同的平台。
- 常见示例:
- 解引用空指针或野指针
- 数组越界访问
- 有符号整数溢出
- 修改字符串字面量
- 在同一表达式中多次修改同一个变量而没有序列点(如
i = i++;)
5. 结合性、左结合、右结合
这个概念决定了运算符的求值顺序当它们具有相同的优先级时。
- 结合性
- 定义:当表达式中连续出现多个相同优先级的运算符时,规定了运算的分组方向。
- 左结合
- 定义:运算符从左向右进行分组和求值。
- 示例:
a + b + c等价于(a + b) + c。大多数运算符是左结合的,如+,-,*,/,%。
- 右结合
- 定义:运算符从右向左进行分组和求值。
- 示例:
- 赋值运算符:
a = b = c等价于a = (b = c)。先执行b = c,再执行a = (b=c的结果)。 - 单目运算符:
*p++等价于*(p++)(++和*优先级相同,但结合性是从右向左)。 - 条件运算符:
a ? b : c ? d : e等价于a ? b : (c ? d : e)。
- 赋值运算符:
总结:优先级决定“先算谁后算谁”,结合性决定“当优先级相同时,是从左往右算还是从右往左算”。