重要说明:
4.1 按位与
|
| 操作符 | 功能 |
|---|---|
| += | 加等于 |
| -= | 减等于 |
| *= | 乘等于 |
| /= | 除等于 |
| %= | 模等于 |
| >>= | 右移等于 |
| <<= | 左移等于 |
| &= | 与等于 |
| |= | 或等于 |
| ^= | 异或等于 |
示例:
int x = 10;
x = x + 10; // 传统写法
x += 10; // 复合赋值,其他运算符一样的道理。这样写更加简洁
x = x * 2; // 传统写法
x *= 2; // 复合赋值
x = x >> 1; // 传统写法
x >>= 1; // 复合赋值

说明: 复合赋值符不仅让代码更简洁,而且在某些情况下,编译器可能会生成更高效的代码。
单目操作符也就是只接受一个操作数的操作符。它们就像是一元函数,只需要一个参数就能工作。
| 单目操作符列表: | 操作符 | 功能 |
|---|---|---|
| ! | 逻辑反操作 | |
| - | 负值 | |
| + | 正值 | |
| & | 取地址 | |
| sizeof | 操作数的类型长度(以字节为单位) | |
| ~ | 对一个数的二进制按位取反 | |
| – | 前置、后置– | |
| ++ | 前置、后置++ | |
| * | 间接访问操作符(解引用操作符) | |
| (类型) | 强制类型转换 |
!C99中引入了布尔类型(_Bool),这样和0、1相比可读性更高。使用 ! 逻辑反操作符可以得到相反的布尔类型。
#include <stdio.h>
#include <stdbool.h>
int main()
{
int flag = 0;
if(!flag) // 如果flag为0(假),!flag为1(真)
{
printf("flag is false\n");
}
bool b = true;
printf("!b = %d\n", !b); // 输出:0(false)
return 0;
}

&:取地址操作符,获取变量的地址。
#include <stdio.h>int main()
{
int a = 10;
int *p = &a; // p存储a的地址
printf("a = %d\n", a); // 直接访问:10
printf("*p = %d\n", *p); // 间接访问:10(通过指针p访问a的值)
*p = 20; // 通过指针修改a的值
printf("a = %d\n", a); // 输出:20
return 0;
}

### 6.3 sizeof 操作符
**`sizeof`**是一个**操作符**,不是函数!它可以求变量(类型)所占空间的大小,单位是字节。
**重要特性:**
* `sizeof` 是操作符,不是函数。对于类型名必须使用括号(如 `sizeof(int)`),对于变量名可以不用括号(如 `sizeof a`),但建议统一使用括号提高可读性。
* 特殊的是 `sizeof(long)` 在32位下为4字节,而在64位下为8字节,这个取决于编译器的实现。
* C语言只是规定 `sizeof(long) >= sizeof(int)`。
* **在C语言中,`sizeof` 操作符中括号内的表达式不参与计算。**
**示例:**
```c
#include <stdio.h>
void test1(int arr[]) // 本质上传过来是 int* arr
{
printf("%zu\n", sizeof(arr)); // 4/8
// 数组首元素的地址用指针变量来存储,指针变量的大小是4/8,取决于计算机
}
void test2(char ch[]) // 本质是传过来是 char* ch
{
printf("%zu\n", sizeof(ch)); // 4/8
}
int main()
{
int arr[10] = {0};
char ch[10] = {0};
printf("%zu\n", sizeof(arr)); // 40 ——数组的大小10*4
printf("%zu\n", sizeof(ch)); // 10 ——数组的大小10*1
test1(arr); // 输出:4或8(指针的大小)
test2(ch); // 输出:4或8(指针的大小)
// sizeof中表达式不参与计算
int a = 10;
int sz = sizeof(a++); // a++不会执行
printf("a = %d\n", a); // 输出:10(a没有自增)
return 0;
}

注意: 使用 %zu 来打印 size_t 类型(sizeof 的返回类型)。
~~操作符表示对一个数的二进制按位取反,即二进制每一位取反。
应用场景:
scanf() 读取失败的时候会返回 EOF(End Of File)。EOF 的值是 -1。~(-1) 对-1的二进制位取反得到0。while(~scanf("%d", &n)) 可以终止循环。EOF 的具体值由实现定义,但在大多数系统上确实是 -1。示例:
#include <stdio.h>
int main()
{
int a = 0;
printf("~a = %d\n", ~a); // 输出:-1(0的按位取反)
int b = -1;
printf("~b = %d\n", ~b); // 输出:0(-1的按位取反)
// 实际应用:循环读取直到EOF
int num;
while(~scanf("%d", &num)) // 等价于 while(scanf("%d", &num) != EOF)
{
printf("读取到:%d\n", num);
}
return 0;
}

++ 和 -- 是C语言中最容易出错的操作符之一,理解它们对于掌握C语言至关重要。
规则:
++a:前置++,先++后使用。a++:后置++,先使用,后++。--a:前置–,先–后使用。a--:后置–,先使用,后–。示例:
#include <stdio.h>
int main()
{
int a = 10;
printf("a = %d\n", a); // 输出:10
printf("a++ = %d\n", a++); // 输出:10(先使用,后++)
printf("a = %d\n", a); // 输出:11
int b = 10;
printf("++b = %d\n", ++b); // 输出:11(先++,后使用)
printf("b = %d\n", b); // 输出:11
return 0;
}

常见陷阱:
int i = 1;
int ret = (++i) + (++i) + (++i);
// 这个表达式的结果是未定义的,不同编译器可能产生不同的结果
// VS: 12 Linux: 10
强制类型转换允许我们显式地将一个类型转换为另一个类型。
int a = (int)3.14; // 3(将3.14强制转换为int,小数部分被截断)
double b = 3.14;
int c = (int)b; // 3
注意: 强制类型转换可能导致数据丢失或精度损失,使用时要谨慎。
关系操作符用于比较两个值的大小关系,就像数学中的比较符号。
| 操作符 | 功能 |
|---|---|
| > | 大于 |
| >= | 大于等于 |
| < | 小于 |
| <= | 小于等于 |
| == | 用于测试“相等” |
| != | 用于测试“不相等” |
重要提示:
== 用于判断相等,= 用于赋值,不要混淆。if (str1 == str2) 是错误的(如果str1和str2是字符数组),这样比较的是字符串首字符的地址,而不是字符串内容。strcmp 函数来比较字符串的大小,自左向右逐个按照ASCII码值进行比较,直到出现不同的字符或遇 \0 为止。示例:
#include <stdio.h>
#include <string.h>
int main()
{
int a = 10, b = 20;
if(a > b)
printf("a > b\n");
else if(a < b)
printf("a < b\n");
else
printf("a == b\n");
// 字符串比较
char str1[] = "abc";
char str2[] = "abc";
// 错误:比较的是地址
if(str1 == str2) // 这总是false,因为str1和str2是不同的数组
printf("相等\n");
// 正确:使用strcmp函数
if(strcmp(str1, str2) == 0) // 比较字符串内容
printf("字符串相等\n");
return 0;
}

逻辑操作符用于组合多个条件,它们就像逻辑学中的“与”和“或”。
| 操作符 | 名称 |
|---|---|
| && | 逻辑与 |
| || | 逻辑或 |
重要特性:
&&(逻辑与):如果左边为假,就不用算右边了(短路求值)。||(逻辑或):如果左边为真,就不用算右边了(短路求值)。短路求值的优势:
示例:
#include <stdio.h>
int main()
{
int a = 0;
int b = 10;
// && 短路求值
if(a != 0 && b / a > 5) // a != 0为假,不会执行b/a,避免除零错误
{
printf("条件成立\n");
}
// || 短路求值
if(a == 0 || b / a > 5) // a == 0为真,不会执行b/a
{
printf("条件成立\n");
}
return 0;
}

注意区分:
& 和 &&:& 是按位与,&& 是逻辑与。| 和 ||:| 是按位或,|| 是逻辑或。条件操作符是C语言中唯一的三目操作符,它就像是一个简洁的 if-else 语句。
语法: 表达式1 ? 表达式2 : 表达式3
执行逻辑:
示例:
#include <stdio.h>
int main()
{
int a = 3;
int b = 6;
int c = 0;
c = a > b ? a : b;
// a > b 为表达式1,a为表达式2,b为表达式3
// 该语句的意思为:如果a>b,则将a的值赋给c,否则将b的值赋给c
printf("c = %d\n", c); // 输出:6
// 嵌套使用
int max = a > b ? (a > 10 ? a : 10) : (b > 10 ? b : 10);
return 0;
}

使用建议:
逗号表达式就像是一个“序列”,它让我们可以在一个表达式中执行多个操作。
语法: exp1, exp2, exp3, …expN
规则:
示例:
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1); // 逗号表达式
printf("c = %d\n", c); // 输出:13
// 执行过程:
// 1. a > b (结果为0,但被丢弃)
// 2. a = b + 10 (a = 12)
// 3. a (值为12,但被丢弃)
// 4. b = a + 1 (b = 13,这是最后一个表达式,所以c = 13)
printf("a = %d, b = %d\n", a, b); // 输出:a = 12, b = 13
return 0;
}

实际应用:
逗号表达式常用于 for 循环中:
for(i = 0, j = 10; i < j; i++, j--)
{
// 循环体
}
[]下标引用操作符
操作数:一个数组名 + 一个索引值
int arr[10]; // 创建数组
arr[9] = 10; // 使用下标引用操作符
// []的两个操作数是arr和9
说明:
arr[9] 等价于 *(arr + 9)。()函数调用操作符
int add(int a, int b)
{
return a + b;
}int main()
{
int result = add(3, 5); // ()是函数调用操作符
return 0;
}

---
## 十三、结构成员访问操作符
结构成员访问操作符用来访问一个结构体的成员。
* **`.`**:直接成员访问操作符(用于结构体变量)。
* **`->`**:间接成员访问操作符(用于结构体指针)。
```c
#include <stdio.h>
struct Student
{
char name[20];
int age;
};
int main()
{
struct Student s = {"张三", 20};
printf("姓名:%s,年龄:%d\n", s.name, s.age); // 使用.访问成员
struct Student *p = &s;
printf("姓名:%s,年龄:%d\n", p->name, p->age); // 使用->访问成员
// p->name 等价于 (*p).name
return 0;
}

表达式求值的顺序一部分是由操作符的优先级和结合性决定,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
规则:
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器的操作数的字节长度,一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU是难以直接实现两个8比特位直接相加运算,所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
示例:
// 实例
char a, b, c;
a = b + c;
// b和c的值被提升为普通整型,然后再执行加法运算
// 加法运算完成之后,结果将被截断,然后再存储于a中
如何进行整型提升:
整型提升是按照变量的数据类型的符号位来提升的。
示例:
#include <stdio.h>
int main()
{
char a = 0xb6; // 10110110(符号位是1,表示负数)
short b = 0xb600;
int c = 0xb6000000;
if(a == 0xb6) // a会被整型提升,变成负数,所以不相等
printf("a");
if(b == 0xb600) // b会被整型提升,变成负数,所以不相等
printf("b");
if(c == 0xb6000000) // c不需要整型提升,所以相等
printf("c");
// 输出:c
return 0;
}

说明:
a、b 要进行整型提升,但是 c 不需要整型提升。a、b 整型提升之后,变成了负数,所以表达式 a==0xb6、b==0xb600 的结果是假。c 不发生整型提升,则表达式 c==0xb6000000 的结果是真。如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
类型转换层次(从低到高):
int
unsigned int
long int
unsigned long int
long long int
unsigned long long int
float
double
long double
示例:
int a = 10;
float b = 3.14;
double c = a + b;
// 执行过程:
// 1. a会被转换为float(因为float在转换层次中比int高)
// 2. 进行float加法运算,结果类型是float
// 3. 如果赋值给double,float结果会被转换为double
但是算术转换要合理,要不然会有一些潜在的问题:
float f = 3.14;
int num = f; // 隐式转换,会有精度丢失(3.14变成3)
复杂表达式的求值有三个影响的因素:
两个相邻的操作符先执行哪个,取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
重要原则: 如果写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。
表达式1:
a * b + c * d + e * f
注释: 代码1在计算的时候,由于 * 比 + 的优先级高,只能保证,* 的计算是比 + 早,但是优先级并不能决定第三个 * 比第一个 + 早执行。
所以表达式的计算顺序就可能是:
a * b
c * d
a * b + c * d
e * f
a * b + c * d + e * f
或者:
a * b
c * d
e * f
a * b + c * d
a * b + c * d + e * f
表达式2:
c + --c;
注释: 同上,操作符的优先级只能决定自减 -- 的运算在 + 的运算的前面,但是我们并没有办法得知,+ 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。
代码3-非法表达式:
#include <stdio.h>
int main()
{
int i = 10;
i = i-- - --i * (i = -3) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
说明: 表达式3在不同编译器中测试结果都不同。这是一个非法表达式,结果未定义。
代码4:
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf("%d\n", answer); // 输出多少?
return 0;
}

说明: 这个代码有没有实际的问题?
虽然在大多数的编译器上求得结果都是相同的,但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。函数的调用先后顺序无法通过操作符的优先级确定。
代码5:
#include <stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret); // VS:12 Linux:10
printf("%d\n", i); // 4
return 0;
}

说明: 看看同样的代码产生了不同的结果,这是为什么?
简单看一下汇编代码,就可以分析清楚。这段代码中的第一个 + 在执行的时候,第三个 ++ 是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序。
总结: 我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。在实际编程中,应该避免写出这样的表达式,使用明确的、可预测的代码。
操作符是C语言的基础,掌握各种操作符的使用方法和特性,对于编写高效、正确的代码至关重要。这不仅涉及到算法与数据结构中的表达式计算,也是理解网络与系统底层原理的重要一环。
关键要点:
% 只能用于整数。= 和 == 的区别。sizeof 是操作符不是函数,++ 和 -- 的前置后置区别。strcmp,不能用 ==。最佳实践:
希望这些知识能帮助你在C语言的学习路上走得更远!记住,多写代码、多实践,才能真正掌握这些操作符的精髓。