『C』操作符详解

xiaoxiao2025-02-15  17

操作符

操作符分类

算数操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符、条件操作符、逗号表达式、下标引用、函数调用和结构成员。

算数操作符

+ - * / %

除了%操作符之外,其它的几个操作符都可以作用于整数和浮点数。 对于/操作符,如果两个操作数都为整数,执行整除除法。而只要有一个是浮点数执行的就是浮点数除法。 %操作符的两个操作数都必须为整数。返回的是整除之后的余数。

移位操作符

<< 左移操作符 >> 右移操作符

左移操作符移位规则: 左边抛弃,右边补0。 右移操作符移位规则: 首先右移运算分两种:

逻辑右移:左边用0填充,右边丢弃。算数右移:左边用符号位填充,右边丢弃。

注意:右移操作对于有符号数来说执行的是算数右移,对于无符号数来说是逻辑右移。 比如说,统计一个数的二进制位中1的数量。

int n; int cnt = 0; scanf("%d", &n); while(n){ if(n & 1){ ++cnt; } n >>= 1; }

如果输入的数是负数,这个程序就是死循环,因为n是有符号数,是算数右移,高位一直补1。将n定义为无符号型就可以了。

unsigned int n; int cnt = 0; scanf("%d", &n); while(n){ if(n & 1){ ++cnt; } n >>= 1; }

警告:对于移位运算符来说,不要移动负数位,这个是未定义行为。

位操作符

& | ^

注意:它们的操作数必须是整数。

来看几个代码

不创建临时变量,交换两个变量

#include <stdio.h> int main(){ int a = 10; int b = 20; a = a ^ b; b = a ^ b; a = a ^ b; printf("a = %d, b = %d\n", a, b); return 0; }

运行结果

[sss@aliyun operator]$ gcc swap_without_variable.c -o swap_without_variable [sss@aliyun operator]$ ./swap_without_variable a = 20, b = 10

求一个整数二进制形式中1的个数

#include <stdio.h> int main(){ int num = 0; int cnt = 0; printf("Please input the num: \n"); scanf("%d", &num); while(num){ ++cnt; num &= (num - 1); } printf("cnt 1: %d\n", cnt); return 0; }

运行结果

[sss@aliyun operator]$ gcc count_ont.c -o count_ont [sss@aliyun operator]$ ./count_ont Please input the num: 10 cnt 1: 2

赋值操作符

赋值操作符是一个很棒的操作符,它可以让你对你之前得到的不满意的值,重新自己赋值。

int weight = 120; weight = 89; double salary = 10000.0; salary = 20000.0;

赋值操作可以连续使用:

int a = 10; int x = 0; int y = 20; a = x = y + 1;

拆开写:

x = y + 1; a = x;

复合赋值符

+= -= *= /= %= >>= <<= &= |= ^=

这些运算符都可以写成复合的效果。比如:

int x = 10; x = x + 10; x += 10;

单目操作符

!逻辑反操作-负值+正值&取地址sizeof操作数的类型长度(以字节为单位)~对一个二进制数按位取反--前置、后置--++前置、后置++*间接访问操作符(解引用操作符)(类型)强制类型转换

代码演示

#include <stdio.h> int main(){ int a = -10; int* p = NULL; printf("!2: %d\n", !2); printf("!0: %d\n", !0); a = -a; p = &a; printf("sizeof(a): %ln\n", sizeof(a)); printf("sizeof(int): %ln\n", sizeof(int)); printf("sizeof a: %ln\n", sizeof a); printf("sizeof int: %ln\n", sizeof int); return 0; }

运行结果

[sss@aliyun operator]$ gcc single_operator.c -o single_operator single_operator.c: In function ‘main’: single_operator.c:15:37: error: expected expression before ‘int’ printf("sizeof int: %ln\n", sizeof int);

结论:sizeof 变量;可以,但是sizeof 类型;不行。建议全部带括号。

sizeof和数组

代码演示

#include <stdio.h> void test1(int arr[]){ printf("test1:sizeof(arr): %lu\n", sizeof(arr)); } void test2(char ch[]){ printf("test2:sizeof(ch): %lu\n", sizeof(ch)); } int main(){ int arr[10] = {0}; char ch[10] = {0}; printf("sizeof(arr): %lu\n", sizeof(arr)); printf("sizeof(ch): %lu\n", sizeof(ch)); test1(arr); test2(ch); return 0; }
运行结果
[sss@aliyun operator]$ !gcc gcc sizeof_arr.c -o sizeof_arr [sss@aliyun operator]$ ./sizeof_arr sizeof(arr): 40 sizeof(ch): 10 test1:sizeof(arr): 8 test2:sizeof(ch): 8

结论:sizeof();括号内是数组名的话,求的是整个数组所占的空间。后两个之所以为8,因为数组名隐式转换为指针。

再来看一段代码

#include <stdio.h> #include <stdlib.h> #include <string.h> void StringTest(){ char str1[] = "hehe\0"; char str2[] = "hehe\\\0"; printf("sizeof(str1): %lu\n", sizeof(str1)); printf("strlen(str1): %lu\n", strlen(str1)); printf("sizeof(str2): %lu\n", sizeof(str2)); printf("strlen(str2): %lu\n", strlen(str2)); } int main(){ StringTest(); system("pause"); return 0; }
运行结果

这是为什么呢?/开头的位转义字符,所以\\会被当做一个字符处理。

前置++和后置++

前置++,是先++,再使用。后置++,是先使用,再++。C++程序猿推荐使用前置++。因为后置++需要对内容进行备份。前置后置--同理。

奇怪的程序

同样的代码,产生不同的结果,这是为什么呢? 这段代码中的第一个+在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定的。 总结:

这个代码本身不是一个好代码。结果跟表达式的求值顺序有关系。表达式的求值依赖于运算符的优先级和结合性,但操作符的优先级和结合性又不能确定唯一的计算路径。这样的表达式产生的结果是严重依赖于表达式的求值顺序,所以不要写出这样的表达式。

关系操作符

> >= < <= != ==

这些关系运算符比较简单,没什么可讲的,但是我们要注意一些运算符使用时候的陷阱。 警告:在编程过程中==和=不小心写错,导致的错误。

逻辑操作符

&& ||

注意:区分逻辑与和按位与,逻辑或和按位或。

1 & 2 --> 0 1&& 2 --> 1 1 | 2 --> 3 1 || 2 --> 1

代码演示

#include <stdio.h> int main(){ int i = 0, a = 0, b = 2, c = 3, d = 4; i = a++ && ++b && d++; printf("a = %d, b = %d, c = %d, d = %d\n", a, b, c, d); return 0; }

运行结果

[sss@aliyun operator]$ !gcc gcc and_or.c -o and_or [sss@aliyun operator]$ ./and_or a = 1, b = 2, c = 3, d = 4

分析:逻辑与&&的短路求值。a = 0;后面的条件不再判断,直接为假。

将&&改为||运行结果

[sss@aliyun operator]$ !gcc gcc and_or.c -o and_or [sss@aliyun operator]$ ./and_or a = 1, b = 3, c = 3, d = 4

分析:逻辑或||的短路求值,b为真,后序条件不再判断。

条件操作符

exp1 ? exp2 : exp3

简单练习

将if语句转成条件操作符

if (a > 5){ b = 3; } else{ b = -3; } a > 5 ? b = 3 : b = -3;

使用条件表达式求两个数中的最大值。

a > b ? a : b;

逗号表达式

exp1, exp2, exp3, exp4, ...expN

逗号表达式,就是用逗号隔开的多个表达式。逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。

下标引用、函数调用和结构成员

下标引用操作符[]

操作数:一个数组名 + 一个索引值。

int arr[10]; arr[9] = 10; []的两个操作数是arr和9

函数调用操作符()

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

代码演示

#include <stdio.h> #include <stdlib.h> void test1(){ printf("hello, "); } void test2(const char* str){ printf("%s\n", str); } int main(){ test1(); test2("world!"); system("pause"); return 0; }
运行结果
hello, world!

结构体成员访问操作符

. 结构体.成员名 -> 结构体指针->成员名

代码演示

#include <stdio.h> #include <string.h> typedef struct Student{ char name[1024]; int age; } Student; void setName(Student* pstu){ char str[1024] = "hehe"; strcpy(pstu->name, str); } void setAge(Student* pstu){ pstu->age = 18; } int main(){ Student stu; Student* pstu = &stu; setName(pstu); setAge(pstu); printf("name: %s\n", stu.name); printf("age: %d\n", pstu->age); return 0; }
运行结果
[sss@aliyun operator]$ !gcc gcc struct.c -o struct [sss@aliyun operator]$ ./struct name: hehe age: 18

表达式求值

表达式求值顺序一部分是由操作符的优先级和结合性决定。 同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。

隐式类型转换

C的整型算数总是以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

短数据类型扩展为长数据类型

要扩展的短数据类型为有符号数

短数据类型的符号位填充到长数据类型的高字节位(比短数据类型多出的部分)。 例:

char num1 = 128; short num2 = num1; char num3 = 127; short num4 = num3; num1: 1000 0000 num2: 1111 1111 1000 0000 num3: 0111 1111 num4: 0000 0000 0111 1111
代码示例
int main() { char num1 = 128; short num2 = num1; char num3 = 127; short num4 = num3; }
查看num
(gdb) x/10xb &num1 0x80 (gdb) x/10xb &num2 0x80 0xff (gdb) x/10xb &num3 0x7f (gdb) x/10xb &num4 0x7f 0x00

要扩展的短数据类型为无符号数

用零来填充到长数据类型的高字节位(比短数据类型多出的部分)。 例:

unsigned char num5 = 128; short num6 = num5; unsigned char num7 = 127; short num8 = num7; num5: 1000 0000 num6: 0000 0000 1000 0000 num7: 0111 1111 num8: 0000 0000 0111 1111
代码示例
int main() { unsigned char num5 = 128; short num6 = num5; unsigned char num7 = 127; short num8 = num7; }
查看num
(gdb) x/10xb &num5 0x80 (gdb) x/10xb &num6 0x80 0x00 (gdb) x/10xb &num7 0x7f (gdb) x/10xb &num8 0x7f 0x00

长数据类型缩短为短数据类型

如果长数据类型的高字节位(比短数据类型多出的部分)全为1或全为0,则会直接截取低字节位赋给短数据类型。如果长数据类型的高字节位(比短数据类型多出的部分)不全为1或0,转换就会发生错误。

相同长度的数据类型中有符号数和无符号数相互转换

直接将内存中的数据赋给要转化的类型,数值大小则会发生变化。短类型扩展为长类型时,但短类型与长类型分属有符号数与无符号数时,则先按一进行类型的扩展,再按本规则直接将内存中的数值原封不动的赋给对方。

算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。 如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。 警告:但是算术转换要合理,要不然会有一些潜在的问题。

float f = 3.14; int num = f;

操作符的属性

复杂表达式的求值有三个影响因素:

操作符的优先级。操作符的结合性。是否控制求值顺序。

两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于它们的结合性。

操作符优先级

操作符描述结合性是否控制求值顺序( )聚组N/A否( )函数调用L-R否[ ]下标引用L-R否.访问结构成员L-R否->访问结构体指针成员L-R否++后缀自增L-R否--后缀自减L-R否!逻辑取反R-L否~按位取反R-L否+单目,表示正值R-L否-单目,表示复制R-L否++前缀自增R-L否++前缀自减R-L否*间接访问R-L否&取地址R-L否sizeof取其长度,单位字节R-L否(类型)类型转换R-L否*乘法L-R否/除法L-R否%整数取余L-R否+加法L-R否-减法L-R否<<左移位L-R否>>右移位L-R否>大于L-R否>=大于或等于L-R否<小于L-R否<=小于或等于L-R否==等于L-R否!=不等于L-R否&按位与L-R否^按位异或L-R否|按位或L-R否&&逻辑与L-R是||逻辑或L-R是? :条件操作符N/A是=赋值R-L否+=以...加R-L否-=以...减R-L否*=以...乘R-L否/=以...除R-L否%=以...取模R-L否<<=以...左移R-L否>>=以...右移R-L否&=以...与R-L否^=以...异或R-L否|=以...或R-L否,逗号L-R是

一些问题表达式

表达式一

a * b + c * d + e * f

该表达式在计算的时候,由于*比+的优先级高,只能保证*的计算比+早,但是优先级并不能决定第三个*比第一个+早执行。 所以表达式的计算顺序就可能是:

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

表达式二

c + -- c;

同上,操作符的优先级只能决定自减–的运算在+运算的前面,但是我们没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。

表达式三

表达式四

#include <stdio.h> #include <stdlib.h> int fun(){ static int count = 1; return ++count; } int main(){ int answer; answer = fun() - fun() * fun(); printf("answer: %d\n", answer); system("pause"); return 0; }

注意:虽然在大多数的编译器上求得的结果是相同的。 但是上述代码answer = fun() - fun() * fun();中我们只能通过操作符的优先级得知:先算乘法,在算减法。但是函数调用的先后顺序通过操作符的优先级无法确定。

转载请注明原文地址: https://www.6miu.com/read-5024836.html

最新回复(0)