运算符
# 03.运算符
# 目录介绍
- 3.0 运算符介绍
- 3.1 自增运算符
- 3.2 算术运算符
- 3.3 关系运算符
- 3.4 逻辑运算符
- 3.5 条件运算符
- 3.6 位运算符
- 3.7 运算符优先级
- 3.8 sizeof运算符
- 3.9 其他运算符
# 3.0 运算符介绍
在 C 语言中,运算符(Operators)用于对变量和值执行各种操作。C 语言提供了丰富的运算符,包括算术运算符、关系运算符、逻辑运算符、位运算符、赋值运算符等。
# 3.1 自增运算符
# 3.1.1 自增自减运算
C 语言提供两个运算符,对变量自身进行+ 1和- 1的操作。
| 运算符 | 描述 | 示例 |
|---|---|---|
++ | 自增(前/后) | a++ 或 ++a |
-- | 自减(前/后) | a-- 或 --a |
i++; // 等同于 i = i + 1
i--; // 等同于 i = i - 1
2
这两个运算符放在变量的前面或后面,结果是不一样的。++var和--var是先执行自增或自减操作,再返回操作后var的值;var++和var--则是先返回操作前var的值,再执行自增或自减操作。
int i = 42;
int j;
j = (i++ + 10);
// i: 43
// j: 52
j = (++i + 10)
// i: 44
// j: 54
2
3
4
5
6
7
8
9
10
# 3.1.2 前缀后缀区别
注意:++ 和 -- 如果 ==不是单独使用==(如:用在表达式中),前缀和后缀 ==差异巨大==
放在变量前,先 +1、-1 再取值使用。
int a = 10;
int res = ++a; // 先 +1,再取 a 值给 res。 (先加再用)
2
放在变量后,先 取值用,再 +1、-1。
int b = 10;
int res2 = b--; // 先取 b 值给 res2, 而后 b 再 -1 (先用再减)
2
# 3.1.3 增减注意事项
- 不能用于常量
- 不能用于表达式
- 优先级:整体高于算数运算符(必然高于比较、赋值);后缀高于前缀
- 不要在一个表达式中,对同一变量 多次 ++、-- 运算。可读性差,且不用编译系统结果不同。
// 目标: 掌握 ++/-- 使用注意事项
int main(void) {
// 1. 不能用于常量
//printf("%d\n", 10++);
// 2. 不能用于表达式
int a = 10;
// (a - 1)++;
// ++(-a);
// 3. 全部 高于算数运算
//int b = ++a * 3;
int b = a-- * 3;
printf("b = %d\n", b);
// 3. 后缀高于前缀
printf("a = %d\n", -a++);
// 对同一变量 多次 ++、-- 运算。可读性差,且不同编译系统结果不同。
int i = 10;
int j = 5;
int res = i++ + ++i - --j - ++j + 3 + i--;
printf("res = %d\n", res); // 26 --- Linux下gcc编译器: 28
printf("i = %d\n", i); // 11
printf("j = %d\n", j); // 5
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 3.1.4 综合案例与思考
综合案例:自增自减运算的各种场景
#include <stdio.h>
int main() {
// 场景1:循环中的自增
printf("for循环中的i++:\n");
for (int i = 0; i < 5; i++) {
printf("%d ", i);
}
printf("\n");
// 场景2:前缀和后缀在赋值中的区别
int a = 10, b = 10;
int x = a++; // x=10, a=11(先用再加)
int y = ++b; // y=11, b=11(先加再用)
printf("后缀: x=%d, a=%d\n", x, a);
printf("前缀: y=%d, b=%d\n", y, b);
// 场景3:在printf中使用
int n = 5;
printf("n=%d, n++=%d, 之后n=%d\n", n, n++, n);
// 注意:上面的行为是未定义的!参数求值顺序不确定
// 正确做法:分开写
n = 5;
printf("当前n=%d\n", n);
n++;
printf("自增后n=%d\n", n);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
原理说明:i++ 和 ++i 在底层实现上,i++ 需要先保存原值到临时变量,然后再自增;而 ++i 直接自增后返回。虽然现代编译器会优化两者的性能差异,但在复杂表达式中,它们的语义区别可能导致难以发现的bug。
思考题:
- 在
for循环中使用i++和++i结果一样吗?哪个更好?为什么? - 为什么不推荐在一个表达式中对同一变量多次使用
++或--? printf("n=%d, n++=%d", n, n++)的输出结果能确定吗?为什么?
# 3.2 算术运算符
# 3.2.1 基本数学运算
用于执行基本的数学运算。
| 运算符 | 描述 | 示例 |
|---|---|---|
+ | 加法 | a + b |
- | 减法 | a - b |
* | 乘法 | a * b |
/ | 除法 | a / b |
% | 取模(取余) | a % b |
示例:
int a = 10, b = 3;
printf("%d\n", a + b); // 输出 13
printf("%d\n", a % b); // 输出 1
//运算符/用来完成除法。注意,两个整数相除,得到还是一个整数。
float x = 6 / 4;
printf("%f\n", x); // 输出 000000
2
3
4
5
6
7
注意点:变量x的类型是float(浮点数),但是6 / 4得到的结果是0,而不是5。原因就在于 C 语言里面的整数除法是整除,只会返回整数部分,丢弃小数部分。
如果希望得到浮点数的结果,两个运算数必须至少有一个浮点数,这时 C 语言就会进行浮点数除法。
float x = 6.0 / 4; // 或者写成 6 / 4.0
printf("%f\n", x); // 输出 500000
2
# 3.2.2 赋值算术运算
赋值运算的简写形式: 如果变量对自身的值进行算术运算,C 语言提供了简写形式,允许将赋值运算符和算术运算符结合成一个运算符。
| 运算符 | 描述 | 示例 |
|---|---|---|
= | 简单赋值 | a = b |
+= | 加后赋值 | a += b |
-= | 减后赋值 | a -= b |
*= | 乘后赋值 | a *= b |
/= | 除后赋值 | a /= b |
%= | 取模后赋值 | a %= b |
&= | 按位与后赋值 | a &= b |
| = | 按位或后赋值 | a |= b |
^= | 按位异或后赋值 | a ^= b |
<<= | 左移后赋值 | a <<= b |
>>= | 右移后赋值 | a >>= b |
**注意:**赋值运算符,会修改变量的原始值。 赋值符左侧,必须可修改(变量)。
int main(void) {
// = 赋值符, 自右向左
int a = 2, b = 1, c = 0;
a = a + b;
printf("a = %d\n", a);
b = a < c;
printf("b = %d\n", b); // 直接 打印 b = a < c
a = b = c = 8;
printf("a = %d, b = %d, c = %d\n", a, b, c);
// += -= 复合赋值符。需求:微信钱包有 15.8 元, 发 2.9 元红包,收 13.6 元红包。
double money;
money = 15.8;
money += 2.9; // 等价于: money = money + 2.9;
printf("money = %lf\n", money);
// 需求: 发红包
money -= 13.6; // 等价于: money = money - 13.6;
printf("money = %lf\n", money);
// 其他 复合赋值符。
int i, j;
i = 10, j = 5;
//i *= j; // 等价于:i = i * j;
//i /= j; // 等价与:i = i / j;
//i %= j; // 等价与:i = i % j;
printf("i = %d\n", i);
// 思考:
i %= j - 2; // 等价于 i = i % (j - 2); %= 右侧看做一个整体.
printf("i = %d\n", i);
// 注意:赋值符,左侧必须可修改
3 += 2;
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 3.2.3 综合案例与思考
综合案例:用算术运算实现温度转换计算器
#include <stdio.h>
int main() {
double celsius = 36.5;
// 摄氏度转华氏度: F = C * 9/5 + 32
double fahrenheit = celsius * 9.0 / 5.0 + 32.0;
printf("%.1f°C = %.1f°F\n", celsius, fahrenheit);
// 注意整数除法的陷阱
double wrong = celsius * 9 / 5 + 32; // 9/5=1 (整数除法!)
double right = celsius * 9.0 / 5.0 + 32; // 9.0/5.0=1.8
printf("错误(整数除法): %.1f°F\n", wrong);
printf("正确(浮点除法): %.1f°F\n", right);
// 取模运算的应用:判断奇偶
for (int i = 1; i <= 10; i++) {
printf("%d是%s数 ", i, (i % 2 == 0) ? "偶" : "奇");
}
printf("\n");
// 复合赋值运算
int score = 60;
score += 10; // 加分
score -= 5; // 扣分
score *= 2; // 翻倍
printf("最终分数: %d\n", score);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
思考题:
5 / 2和5.0 / 2的结果分别是什么?为什么不同?- 负数取模
-7 % 3的结果是什么?C语言中取模运算的符号规则是什么? a *= b + c等价于a = a * (b + c)还是a = a * b + c?为什么?
# 3.3 关系运算符
# 3.3.1 比较表达式
C 语言用于比较的表达式,称为“关系表达式”(relational expression),里面使用的运算符就称为“关系运算符”(relational operator),主要有下面6个。
用于比较两个值的大小关系,返回 1(真)或 0(假)。
| 运算符 | 描述 | 示例 |
|---|---|---|
== | 等于 | a == b |
!= | 不等于 | a != b |
> | 大于 | a > b |
< | 小于 | a < b |
>= | 大于等于 | a >= b |
<= | 小于等于 | a <= b |
示例:
int a = 12, b = 20;
printf("%d\n", a > b); // 输出 0(假)
printf("%d\n", a != b); // 输出 1(真)
2
3
关系表达式通常返回0或1,表示真伪。C 语言中,0表示伪,所有非零值表示真。比如,20 > 12返回1,12 > 20返回0。
# 3.3.2 综合案例与思考
综合案例:关系运算在实际判断中的应用
#include <stdio.h>
#include <math.h>
int main() {
int age = 20;
int score = 85;
// 基本比较
printf("age >= 18: %d\n", age >= 18); // 1 (真)
printf("score == 100: %d\n", score == 100); // 0 (假)
// 常见错误:== 写成 =
int x = 5;
// if (x = 3) // 这是赋值!不是比较!永远为真
if (x == 3) {
printf("x等于3\n");
}
// 浮点数比较的陷阱
double a = 0.1 + 0.2;
double b = 0.3;
printf("0.1+0.2 == 0.3? %d\n", a == b); // 0 (假!因为精度问题)
printf("正确比较: %d\n", fabs(a - b) < 1e-9); // 1 (真)
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
思考题:
if (x = 5)和if (x == 5)有什么区别?为什么前者是常见的bug?- 为什么不能用
==直接比较两个浮点数是否相等? - C语言中比较运算的结果是什么类型?返回值有哪些?
# 3.4 逻辑运算符
# 3.4.1 逻辑运算案例
用于组合多个条件,返回 1(真)或 0(假)。逻辑运算符提供逻辑判断功能,用于构建更复杂的表达式,主要有下面三个运算符。
| 运算符 | 描述 | 示例 |
|---|---|---|
&& | 逻辑与(AND) | a && b |
|| | 逻辑或(OR) | a || b |
! | 逻辑非(NOT) | !a |
示例:
int a = 1, b = 0;
printf("%d\n", a && b); // 输出 0(假)
printf("%d\n", a || b); // 输出 1(真)
printf("%d\n", !a); // 输出 0(假)
2
3
4
# 3.4.2 短路运算
逻辑运算符还有一个特点,它总是先对左侧的表达式求值,再对右边的表达式求值,这个顺序是保证的。如果左边的表达式满足逻辑运算符的条件,就不再对右边的表达式求值。这种情况称为“短路”。
在程序中:
- 判断 表达式 a && (b+c) 的结果:当 a 为假时,不必计算 b+c,可直接得出表达式为假的结论。
- 判断 表达式 a || (b+c) 的结果:当 a 为真时,则不必计算 b+c,就能判断出表达式的值为真。
if (number != 0 && 12/number == 2)
上面示例中,如果&&左侧的表达式(number != 0)为伪,即number等于0时,右侧的表达式(12/number == 2)是不会执行的。因为这时左侧表达式返回0,整个&&表达式肯定为伪,就直接返回0,不再执行右侧的表达式了。
# 3.4.3 短路运算作用
在代码中利用短路运算,可 避免不必要运算,提高程序执行效率。 比如有如下代码:
int a = 5, b = 28;
int c = (a > 3) || ((b / 7)*4 % 2 != 0)
printf("c = %d\n", c);
2
3
现要计算 c 值。= 右侧表达式含有逻辑或 || 运算符,运算特性 有真为真。|| 左边表达式很容易判断为 真,可确定整个表达式结果为 真。 右侧 b 相关的 复杂表达式 不需要计算 。
小知识:合理利用短路运算特性,提高程序执行效率
- 编写含**==&&==**表达式时,&& 同真为真,左表达式值为假时,右表达式不计算, 建议将 ==易假 值放左边==。
- 编写含**==||==**表达式时,|| 有真为真,左表达式值为真时,右表达式不计算。 建议将 ==易真 值放左边==。
# 3.4.4 综合案例与思考
综合案例:利用短路运算安全地进行除法和数组访问
#include <stdio.h>
int main() {
// 短路运算避免除零错误
int divisor = 0;
int dividend = 10;
// 如果divisor为0,&&右边不会执行,避免了除零
if (divisor != 0 && dividend / divisor > 2) {
printf("商大于2\n");
} else {
printf("除数为0或商不大于2\n");
}
// 短路运算避免空指针访问
int *ptr = NULL;
if (ptr != NULL && *ptr > 0) {
printf("值为正数\n");
} else {
printf("指针为空或值非正\n");
}
// 逻辑运算综合:判断闰年
int year = 2024;
// 闰年:能被4整除但不能被100整除,或者能被400整除
int isLeap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
printf("%d年%s闰年\n", year, isLeap ? "是" : "不是");
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
思考题:
- 短路运算在实际编程中最常见的应用场景是什么?请举两个例子。
if (a++ && b++)中,如果a初始值为 0,b的值会被自增吗?为什么?- 逻辑运算符
&&和位运算符&有什么区别?能互相替代吗?
# 3.5 条件运算符
# 3.5.1 基础语法
语法:表达式1 ?表达式 2:表达式 3
- 表达式 1 一定起 判别 作用。表达式 2 和 表达式 3 只能有一个执行。
- 三目运算的结果,必须被使用。
# 3.5.2 三目运算符
用于简化 if-else 语句。
| 运算符 | 描述 | 示例 |
|---|---|---|
? : | 条件运算符 | a > b ? a : b |
示例:
int a = 10, b = 20;
int max = (a > b) ? a : b;
printf("%d\n", max); // 输出 20
2
3
# 3.5.3 综合案例与思考
综合案例:三目运算符的实用场景
#include <stdio.h>
int main() {
int a = 15, b = 28, c = 9;
// 求三个数中的最大值
int max = (a > b) ? ((a > c) ? a : c) : ((b > c) ? b : c);
printf("最大值: %d\n", max);
// 求绝对值
int num = -42;
int abs_val = (num >= 0) ? num : -num;
printf("|%d| = %d\n", num, abs_val);
// 判断奇偶
for (int i = 1; i <= 5; i++) {
printf("%d是%s数\n", i, (i % 2 == 0) ? "偶" : "奇");
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
思考题:
- 三目运算符
a ? b : c可以完全替代if-else语句吗?什么情况下不适合使用? - 嵌套三目运算符
a > b ? a : b > c ? b : c的执行顺序是什么?如何加括号使其更清晰?
# 3.6 位运算符
用于对二进制位进行操作。
| 运算符 | 描述 | 示例 |
|---|---|---|
& | 按位与 | a & b |
| | 按位或 | a | b |
^ | 按位异或 | a ^ b |
~ | 按位取反 | ~a |
<< | 左移 | a << 1 |
>> | 右移 | a >> 1 |
示例:
int a = 5, b = 3; // 5: 0101, 3: 0011
printf("%d\n", a & b); // 输出 1 (0001)
printf("%d\n", a | b); // 输出 7 (0111)
printf("%d\n", a << 1); // 输出 10 (1010)
2
3
4
# 3.6.1 取反运算符~
取反运算符~是一个一元运算符,用来将每一个二进制位变成相反值,即0变成1,1变成0。
// 返回 01101100
~ 10010011
2
上面示例中,~对每个二进制位取反,就得到了一个新的值。
注意,~运算符不会改变变量的值,只是返回一个新的值。
# 3.6.2 与运算符&
与运算符&将两个值的每一个二进制位进行比较,返回一个新的值。当两个二进制位都为1,就返回1,否则返回0。
// 返回 00010001
10010011 & 00111101
2
上面示例中,两个八位二进制数进行逐位比较,返回一个新的值。
与运算符&可以与赋值运算符=结合,简写成&=。
int val = 3;
val = val & 0377;
// 简写成
val &= 0377;
2
3
4
5
# 3.6.3 或运算符|
或运算符|将两个值的每一个二进制位进行比较,返回一个新的值。两个二进制位只要有一个为1(包含两个都为1的情况),就返回1,否则返回0。
// 返回 10111111
10010011 | 00111101
2
或运算符|可以与赋值运算符=结合,简写成|=。
int val = 3;
val = val | 0377;
// 简写为
val |= 0377;
2
3
4
5
# 3.6.4 异或运算符^
异或运算符^将两个值的每一个二进制位进行比较,返回一个新的值。两个二进制位有且仅有一个为1,就返回1,否则返回0。
// 返回 10101110
10010011 ^ 00111101
2
异或运算符^可以与赋值运算符=结合,简写成^=。
int val = 3;
val = val ^ 0377;
// 简写为
val ^= 0377;
2
3
4
5
# 3.6.5 左移运算符<<
左移运算符<<将左侧运算数的每一位,向左移动指定的位数,尾部空出来的位置使用0填充。
// 1000101000
10001010 << 2
2
上面示例中,10001010的每一个二进制位,都向左侧移动了两位。
左移运算符相当于将运算数乘以2的指定次方,比如左移2位相当于乘以4(2的2次方)。
左移运算符<<可以与赋值运算符=结合,简写成<<=。
int val = 1;
val = val << 2;
// 简写为
val <<= 2;
2
3
4
5
# 3.6.6 右移运算符>>
右移运算符>>将左侧运算数的每一位,向右移动指定的位数,尾部无法容纳的值将丢弃,头部空出来的位置使用0填充。
// 返回 00100010
10001010 >> 2
2
上面示例中,10001010的每一个二进制位,都向右移动两位。最低的两位10被丢弃,头部多出来的两位补0,所以最后得到00100010。
注意,右移运算符最好只用于无符号整数,不要用于负数。因为不同系统对于右移后如何处理负数的符号位,有不同的做法,可能会得到不一样的结果。
右移运算符相当于将运算数除以2的指定次方,比如右移2位就相当于除以4(2的2次方)。
右移运算符>>可以与赋值运算符=结合,简写成>>=。
int val = 1;
val = val >> 2;
// 简写为
val >>= 2;
2
3
4
5
# 3.6.7 综合案例与思考
综合案例:位运算的实际应用
#include <stdio.h>
// 用位运算交换两个数(不需要临时变量)
void swapXOR(int *a, int *b) {
*a ^= *b; // a = a ^ b
*b ^= *a; // b = b ^ (a ^ b) = a
*a ^= *b; // a = (a ^ b) ^ a = b
}
// 判断一个数是否是2的幂
int isPowerOfTwo(int n) {
return n > 0 && (n & (n - 1)) == 0;
}
// 统计一个整数的二进制中有多少个1
int countBits(int n) {
int count = 0;
while (n) {
count += n & 1;
n >>= 1;
}
return count;
}
int main() {
// 用位运算交换
int a = 10, b = 20;
printf("交换前: a=%d, b=%d\n", a, b);
swapXOR(&a, &b);
printf("交换后: a=%d, b=%d\n", a, b);
// 判断2的幂
for (int i = 1; i <= 16; i++) {
printf("%d %s 2的幂\n", i, isPowerOfTwo(i) ? "是" : "不是");
}
// 统计二进制1的个数
int num = 255;
printf("%d 的二进制有 %d 个1\n", num, countBits(num));
// 用左移实现乘法(效率高于普通乘法)
int x = 5;
printf("%d << 3 = %d (等于 %d * 8)\n", x, x << 3, x * 8);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
原理说明:位运算直接操作数据的二进制位,是最接近硬件的操作,效率极高。n & (n-1) 能消除 n 的最低位的 1,因此 2 的幂只有一个 1,消除后变为 0。异或运算满足交换律和结合律:a ^ b ^ b = a,因此可用于不需要临时变量的交换。
思考题:
- 为什么
n & (n-1) == 0可以判断 n 是否是 2 的幂?请用二进制说明。 - 用位运算实现
x * 7且不使用乘法运算符,该怎么写? - 右移运算对有符号负数的处理方式在不同系统上可能不同,这叫什么?如何避免?
# 3.7 运算符优先级
运算符的优先级决定了表达式中运算的顺序。以下是一些常见运算符的优先级(从高到低):
()(括号)++、--(自增、自减)*、/、%(乘法、除法、取模)+、-(加法、减法)<<、>>(位左移、位右移)<、<=、>、>=(关系运算符)==、!=(相等性运算符)&、|、^(位运算符)&&、||(逻辑运算符)? :(条件运算符)=、+=、-=等(赋值运算符)
示例:
int a = 10, b = 20, c = 30;
int result = a + b * c; // 先计算 b * c,再计算 a + (b * c)
printf("%d\n", result); // 输出 610
2
3
# 3.8 sizeof运算符
# 3.8.1 sizeof的作用
在 C 语言中,sizeof 是一个非常重要的运算符,用于获取数据类型或变量在内存中所占的字节数。它的参数可以是数据类型的关键字,也可以是变量名或某个具体的值。
| 运算符 | 描述 | 示例 |
|---|---|---|
sizeof | 获取变量或类型的大小 | sizeof(int) |
- 获取数据类型或变量在内存中的大小(以字节为单位)。
- 可以用于静态类型(如
int、float)和动态分配的内存(如数组、结构体)。 - 在编译时计算,不会影响程序的运行时性能。
目标:会查看变量、类型占用内存大小
# 3.8.2 获取数据类型大小
sizeof(type)
type 是数据类型,如 int、float、char 等。
#include <stdio.h>
int main() {
printf("Size of int: %zu bytes\n", sizeof(int));
printf("Size of float: %zu bytes\n", sizeof(float));
printf("Size of char: %zu bytes\n", sizeof(char));
printf("Size of double: %zu bytes\n", sizeof(double));
return 0;
}
2
3
4
5
6
7
8
9
输出:
Size of int: 4 bytes
Size of float: 4 bytes
Size of char: 1 bytes
Size of double: 8 bytes
2
3
4
# 3.8.3 获取变量大小
sizeof(variable)
variable 是变量名,可以是基本类型变量、数组、结构体等。
#include <stdio.h>
int main() {
int a = 10;
double b = 3.14;
char c = 'A';
printf("Size of a: %zu bytes\n", sizeof(a));
printf("Size of b: %zu bytes\n", sizeof(b));
printf("Size of c: %zu bytes\n", sizeof(c));
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
输出:
Size of a: 4 bytes
Size of b: 8 bytes
Size of c: 1 bytes
2
3
获取数组的大小
#include <stdio.h>
int main() {
int arr[10];
printf("Size of arr: %zu bytes\n", sizeof(arr)); // 数组总大小
printf("Size of arr[0]: %zu bytes\n", sizeof(arr[0])); // 单个元素大小
printf("Number of elements in arr: %zu\n", sizeof(arr) / sizeof(arr[0])); // 数组元素个数
return 0;
}
2
3
4
5
6
7
8
9
输出:
Size of arr: 40 bytes
Size of arr[0]: 4 bytes
Number of elements in arr: 10
2
3
获取结构体的大小
#include <stdio.h>
struct Student {
int id;
char name[20];
float score;
};
int main() {
struct Student s;
printf("Size of struct Student: %zu bytes\n", sizeof(s));
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
输出:
Size of struct Student: 28 bytes
获取指针的大小
#include <stdio.h>
int main() {
int *ptr;
printf("Size of ptr: %zu bytes\n", sizeof(ptr)); // 指针的大小
return 0;
}
2
3
4
5
6
7
输出:
Size of ptr: 8 bytes
# 3.8.4 sizeof注意事项
sizeof是编译时运算符:sizeof在编译时计算,不会在运行时执行。- 例如,
sizeof(int)在编译时就已经确定。
sizeof与数组:sizeof可以用于获取数组的总大小,但不能用于获取动态分配数组的大小。- 例如:
int *arr = malloc(10 * sizeof(int)); printf("%zu\n", sizeof(arr)); // 输出指针的大小,而不是数组的大小1
2sizeof与结构体:- 结构体的大小可能包含填充字节(Padding),因此
sizeof返回的值可能大于成员大小的总和。
- 结构体的大小可能包含填充字节(Padding),因此
sizeof与字符串:sizeof可以用于获取字符数组的大小,但不能用于获取字符串的长度。- 例如:
char str[] = "Hello"; printf("%zu\n", sizeof(str)); // 输出 6(包括 '\0') printf("%zu\n", strlen(str)); // 输出 5(不包括 '\0')1
2
3
# 3.8.5 sizeof返回值
sizeof 的返回值**
sizeof返回一个size_t类型的值,表示数据类型或变量占用的字节数。size_t是一个无符号整数类型,通常定义在<stddef.h>头文件中。
# 3.8.6 综合案例与思考
综合案例:用sizeof实现安全的数组操作
#include <stdio.h>
#include <string.h>
// 安全地遍历数组(利用sizeof计算长度)
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
int main() {
int numbers[] = {10, 20, 30, 40, 50, 60};
int len = ARRAY_SIZE(numbers);
printf("数组元素个数: %d\n", len);
printf("数组总大小: %zu 字节\n", sizeof(numbers));
printf("单个元素大小: %zu 字节\n", sizeof(numbers[0]));
// 安全遍历
for (int i = 0; i < len; i++) {
printf("numbers[%d] = %d\n", i, numbers[i]);
}
// sizeof与字符串
char str[] = "Hello";
printf("\nsizeof(str) = %zu (包含'\\0')\n", sizeof(str)); // 6
printf("strlen(str) = %zu (不包含'\\0')\n", strlen(str)); // 5
// sizeof与指针
int *p = numbers;
printf("\nsizeof(p) = %zu (指针大小,不是数组大小!)\n", sizeof(p));
printf("sizeof(numbers) = %zu (数组大小)\n", sizeof(numbers));
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
思考题:
sizeof是在编译时还是运行时计算的?这意味着什么?- 数组作为函数参数传入后,在函数内部用
sizeof能获取数组的真实大小吗?为什么? sizeof("Hello")的结果是 5 还是 6?为什么?
# 3.9 其他运算符
| 运算符 | 描述 | 示例 |
|---|---|---|
& | 取地址 | &a |
* | 指针解引用 | *ptr |
-> | 结构体指针成员访问 | ptr->member |
示例:
int a = 10;
int *ptr = &a;
printf("%d\n", *ptr); // 输出 10
2
3