【C语言】操作符详解(下)

请添加图片描述
主页链接: LSR的主页
专栏链接: 《C语言》


前言

以下是对操作符讲解的续集,希望可以帮助到你。


一、单目操作符

单目操作符是一类只需要一个操作数就能完成运算的运算符,与需要两个操作数的双目操作符(如 +、-、*、/)和唯一的三目操作符(条件运算符?:)不同。它们广泛用于 C、C++ 等语言中,涉及算术、逻辑、指针、类型转换等多种操作。

单目操作符有以下类型:
!、++、–、&、、+、-、~ 、sizeof、(类型)
**这里重点介绍一下 & 、
、!和~:**

1.取地址符 &

作用:获取操作数(变量)的内存地址(返回指针类型)。
语法:& 变量名(操作数必须是变量,不能是常量或表达式)。
示例:

int num = 10;
int* p = #  // p存储num的地址 → p指向num

2.解引用符 * (间接访问符)

作用:通过指针获取其指向的变量的值(与取地址符 & 互为逆操作)。
语法:* 指针变量(操作数必须是指针类型)。
示例:

int num = 10;
int* p = #  // p指向num
int val = *p;   // *p获取p指向的num的值 → val=10
*p = 20;        // 通过*修改指向的变量 → num变为20

3.逻辑非 !

作用:对操作数的 “真假” 取反(逻辑取反)。
在 C/C++ 中,0 表示 “假”,非 0 值均表示 “真”;
!真 结果为 0(假),!假 结果为 1(真)。
语法:! 操作数。
示例:

int a = 5;    // 非0,视为“真”
int b = !a;   // !真 → 0 → b=0
int c = !0;   // !假 → 1 → c=1

4.按位取反 ~

作用:对操作数的每一个二进制位取反(0 变 1,1 变 0)。
注意:结果依赖操作数的类型(有符号 / 无符号),通常按补码计算。
语法:~ 操作数。
示例:
假设 char 为 8 位,a = 0x0f(二进制 00001111):

char a = 0x0f;  // 二进制:00001111
char b = ~a;    // 按位取反 → 11110000 → 十六进制0xf0 → b=0xf0

二、逗号表达式

逗号表达式是 C/C++ 中一种特殊的表达式,它由逗号分隔多个子表达式组成,整个表达式的结果是最后一个子表达式的值。其核心特点是 “顺序求值,取最后一个结果”,在简化代码、控制流程等场景中常用。

1.逗号表达式的定义与语法

定义:由逗号分隔的多个表达式组成的整体,称为逗号表达式(也叫 “顺序求值表达式”)。
语法:表达式1, 表达式2, …, 表达式n
其中,表达式1到表达式n可以是任意合法表达式(如赋值、算术运算、函数调用等)。


2.求值规则

逗号表达式的求值过程遵循 “从左到右依次执行,最终结果为最后一个表达式的值”

先计算表达式1,忽略其结果(但会执行其副作用,如赋值、自增等);
再计算表达式2,同样忽略其结果;
以此类推,直到计算表达式n;
整个逗号表达式的最终值是表达式n的值。


3.基础示例

例 1:简单的逗号表达式

#include <stdio.h>

int main() {
    int a, b, c;
    // 逗号表达式:依次执行a=1, b=2, c=3,最终值为c=3
    int result = (a=1, b=2, c=3); 
    printf("result = %d\n", result); // 输出:result = 3
    printf("a=%d, b=%d, c=%d\n", a, b, c); // 输出:a=1, b=2, c=3
    return 0;
}

说明:逗号表达式先执行a=1、b=2,最后执行c=3,整个表达式的值为3,并赋值给result。


例 2:包含副作用的子表达式

#include <stdio.h>

int main() {
    int x = 0;
    // 子表达式含自增(副作用),从左到右执行
    int val = (x++, x*2, x+5); 
    // 执行过程:
    // 1. x++ → x变为1(结果忽略)
    // 2. x*2 → 1*2=2(结果忽略)
    // 3. x+5 → 1+5=6(最终值)
    printf("val = %d, x = %d\n", val, x); // 输出:val = 6, x = 1
    return 0;
}

说明:子表达式的 “副作用”(如x++改变变量值)会被保留,且按顺序执行。


4.优先级与结合性(后面会详解)

优先级:逗号运算符是 C/C++ 中优先级最低的运算符,比赋值运算符(=)优先级还低。
若不加括号,逗号会被视为表达式分隔符,而非逗号表达式。
例如:x = a=3, b=a+2, b+1 等价于 (x = a=3), (b=a+2), (b+1),整个表达式的值是b+1,但x的值是3。
若加括号:x = (a=3, b=a+2, b+1),则整个逗号表达式作为赋值的右值,结果为b+1=3+2+1=6,x的值为6。
结合性:逗号运算符的结合性是从左到右,即多个逗号表达式会按顺序依次结合。
例如:a, b, c 等价于 (a, b), c。


5.使用场景

5.1简化多条语句为单一表达式

在仅允许写一个表达式的场景(如for循环的初始化 / 更新部分),用逗号表达式执行多个操作:

// for循环中用逗号表达式同时初始化i和j,更新时同时自增i和自减j
for (int i=0, j=10; i<j; i++, j--) {
    printf("i=%d, j=%d\n", i, j);
}

5.2在条件判断中执行预处理操作

先执行一些准备操作,再判断条件:

int x = 5;
if (x--, x > 0) { // 先执行x--(x变为4),再判断x>0(4>0为真)
    printf("x is positive\n"); // 会执行
}

6.注意事项

可读性优先:过度使用逗号表达式可能导致代码晦涩(如嵌套逗号表达式),需权衡简洁性与可读性。
例如:a = (b=3, c=b+2, d=c*5, d-1) 虽然合法,但不如拆分为多条语句清晰。
优先级陷阱:忘记加括号可能导致逻辑错误。例如:

int x, y;
x = 1, 2, 3; // 等价于 (x=1), 2, 3 → x=1,整个表达式值为3
x = (1, 2, 3); // x=3,整个表达式值为3

副作用依赖顺序:若子表达式的副作用存在依赖关系(如i++, i*2),需确保顺序正确,避免未定义行为(如i = i++是未定义的,但逗号表达式中顺序明确,无此问题)。


三、下标访问[]、函数调用()

1.[]下标引用操作符

操作数:⼀个数组名 + ⼀个索引值

int arr[10];//创建数组
arr[9] = 10;//实⽤下标引⽤操作符。
[ ]的两个操作数是arr和9。


2.函数调⽤操作符

接受⼀个或者多个操作数:第⼀个操作数是函数名,剩余的操作数就是传递给函数的参数。

#include <stdio.h>
void test1()
{
 printf("hehe\n");
}
void test2(const char *str)
{
 printf("%s\n", str);
}
int main()
{
 test1(); //这⾥的()就是作为函数调⽤操作符。
 test2("hello bit.");//这⾥的()就是函数调⽤操作符。
 return 0;
}

四、结构成员访问操作符

1.结构体

C语言已经提供了内置类型,如:char、short、int、long、float、double等,但是只有这些内置类型还是不够的,假设我想描述学生,描述⼀本书,这时单⼀的内置类型是不行的。描述⼀个学生需要名字、年龄、学号、身高、体重等;描述⼀本书需要作者、出版社、定价等。C语言为了解决这个问题,增加了结构体这种自定义的数据类型,让程序员可以自己创造适合的类型。


1.1结构的声明

struct tag
{
 member-list;//成员变量
}variable-list;//结构体变量(全局变量)

举个例子:
描述一个学生:

struct Stu
{
 char name[20];//名字
 int age;//年龄
 char sex[5];//性别
 char id[20];//学号
}; //分号不能丢

1.2结构体变量的定义和初始化

//代码1:变量的定义
struct Point
{
 int x;
 int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//代码2:初始化。
struct Point p3 = {10, 20};
struct Stu //类型声明
{
 char name[15];//名字
 int age; //年龄
};
struct Stu s1 = {"zhangsan", 20};//初始化
struct Stu s2 = {.age=20, .name="lisi"};//指定顺序初始化
//代码3
struct Node
{
 int data;
 struct Point p;
 struct Node* next; 
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

2.结构成员访问操作符

2.1结构体成员的直接访问

结构体成员的直接访问是通过点操作符(.)访问的。点操作符接受两个操作数。如下所示:

#include <stdio.h>
struct Point
{
 int x;
 int y;
}p = {1,2};
int main()
{
 printf("x: %d y: %d\n", p.x, p.y);
 return 0;
}

使用方式:结构体变量.成员名


2.2结构体成员的间接访问

有时候我们得到的不是⼀个结构体变量,而是得到了⼀个指向结构体的指针。如下所示:

#include <stdio.h>
struct Point
{
 int x;
 int y;
};
int main()
{
 struct Point p = {3, 4};
 struct Point *ptr = &p;
 ptr->x = 10;
 ptr->y = 20;
 printf("x = %d y = %d\n", ptr->x, ptr->y);
 return 0;
}

使用方式:结构体指针->成员名


五、操作符的属性:优先级、结合性

1.优先级

定义
优先级指不同操作符在表达式中执行的先后顺序,优先级高的操作符会先被执行,类似数学中的 “先乘除,后加减”。
作用
解决不同类型操作符混合出现时的执行顺序问题。例如,表达式a + b * c中,*的优先级高于+,因此先计算b * c,再计算与a的和,等价于a + (b * c)。

2.结合性

定义
当表达式中出现多个优先级相同的操作符时,结合性决定了它们的求值方向(从左到右或从右到左)。
作用
解决同优先级操作符的执行顺序问题。例如,表达式a - b - c中,-的结合性是 “从左到右”,因此先算a - b,再用结果减c,等价于(a - b) - c。
在这里插入图片描述


六、表达式求值

1.整型提升

定义
整型提升是指将较小的整数类型(如char、short、signed char、unsigned char、short int、unsigned short int等)在参与运算时,自动转换为int或unsigned int类型的过程。
为什么需要整型提升?
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度⼀般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送⼊CPU去执行运算。


//实例1
char a,b,c;
...
a = b + c;

b和c的值被提升为普通整型,然后再执行加法运算。
加法运算完成之后,结果将被截断,然后再存储于a中。


如何进行整体提升呢?

  1. 有符号整数提升是按照变量的数据类型的符号位来提升的
  2. 无符号整数提升,高位补0。
//负数的整形提升
char c1 = -1;
变量c1的⼆进制位(补码)中只有8个⽐特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,⾼位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的⼆进制位(补码)中只有8个⽐特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,⾼位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//⽆符号整形提升,⾼位补0

在这里插入图片描述


2.算数转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中⼀个操作数的转换为另⼀个操作数的类
型,否则操作就⽆法进行。下面的层次体系称为寻常算术转换。
在这里插入图片描述
如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转换为另外⼀个操作数的类型后执行运算。


总结

即使有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯⼀的
计算路径,那这个表达式就是存在潜在⻛险的,建议不要写出特别复杂的表达式。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值