栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言操作符详解

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言操作符详解

文章目录
  • 操作符分类
  • 算术操作符
  • 移位操作符
    • 左移操作符
    • 右移操作符
  • 位操作符
  • 赋值操作符
  • 单目操作符
    • sizeof和数组
  • 关系操作符
  • 逻辑操作符
  • 条件操作符
  • 逗号表达式
  • 下标引用、函数调用、和结构成员
  • 表达式求值
    • 隐式类型转换
    • 算术转换
    • 操作符的属性
    • 操作符的属性

操作符分类

算术操作符

移位操作符

位操作符

赋值操作符

单目操作符

关系操作符

逻辑操作符

条件操作符

逗号表达式

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

算术操作符
+	-	*	/	%

同时也是双目操作符,意思是有两个操作数

  1. %操作符的两个操作数必须是整数,结果是整除之后的余数
  2. 除了%操作符之外,其它的几个操作符可以作用于整数和浮点数
  3. /操作符的两个操作数若都是整数,执行整数除法,结果是整除之后的商
  4. /操作符的两个操作数若至少有一个是浮点数,执行浮点数除法,结果是浮点数

移位操作符
<<	左移操作符
>>	右移操作符
//操作数只能是整数

移动的是该数的二进制位

左移操作符

移位规则:

左边抛弃、右边补0

右移操作符

移位规则:

首先右移运算分两种:

  1. 逻辑移位

​ 左边用0填充,右边丢弃

  1. 算术移位

    左边用原该值的符号位填充,右边丢弃

逻辑移位和算数移位是由编译器来决定的,但一般情况下,所能用到的编译器都是逻辑移位,道理很简单,一个负数经过移位后应该还是一个负数,所以用原符号位来填充,也就是算术右移。

注意:

对于移位运算符,不能移动负数位,这个是标准未定义的。

例如:

int num = 10;
num >> -1//error
位操作符
&	//按位与	
|	//按位或
^	//按位异或
//操作数只能是整数

&

两个数相同位上的二进制位都是1时,该位的结果为1,其余情况都为0

|

两个数相同位上的二进制位都是0时,该位的结果为0,其余情况都为1

^

两个数相同位上,相同的数该位的结果为0,相异的数该位的结果为1

赋值操作符

可以对变量进行重新的赋值

int a = 10;//a初始化为10
a = 20;//对a重新赋值为20
a = 30;//对a重新赋值为30
赋值操作符可以连续使用:
int a = 10;
int b = 20;
int c = 30;
a = b = c;//把c的值也就是30赋值给b,b就变为30,b再把值赋给a,a就为30
但是这样写的格式风格是不良好的
建议这样写
b = c;
a = b;

复合赋值操作符

+=

-=

*=

/=

%=

<<=

>>=

&=

|=

^=

效果

int a = 10;
int b = 20;
a = a + b;//a就变为了30
不同的写法,相同的效果
a += b;//a也变为30
//其它操作符一样的道理,这样写更加简洁
单目操作符
!			逻辑反操作
-			负值
+			正值
&			取地址
sizeof		操作数的类型长度(以字节为单位)
~			对一个数的二进制按位取反
--			前置、后置--
++			前置、后置++
*			间接访问操作符(解引用操作符)
(类型)	   强制类型转换

在C语言中0为假,非0为真,而!就是对真和假进行取反,把假的变成真的,把真的变成假的

可以看到,当a == 1时,很显然表达式为真,那么就打印hello world!

但是加了一个!,那么!(a == 1) 表达式就为假,那么就不打印hello world!

- +

跟数学上是一样的,在一个数前面加一个负号,该数就变为相反数,在一个数前面加正号,无效果

10是一个正数,那么-10就是负数;+10还是原本的10
-10是一个负数,那么-(-10)也就是10,是一个正数;+(-10)还是原本的-10

&

可以把一个元素的地址给取出来

~

对一个数的二进制位整体取反

-- ++

//后置++、--
#include 
int main()
{
    int a = 10;
    int b = 10;
    int x = a++;//先使用a,a再自增1,也就是先把a的值10赋给x,然后a再变为11
    //结果为x = 10	a = 11
    int y = a--;//先使用b,b再自减1,也就是先把b的值10赋给y,然后b再变为9
    //结果为y = 10	b = 9
    return 0;
}
//前置++、--
#include 
int main()
{
    int a = 10;
    int b = 10;
    int x = ++a;//a先自增1,再赋值给x,也就是a先变为11,然后再把11赋给x
    //结果为x = 11	a = 11	
    int y = --b;//b先自减1,再赋值给y,也就是b先变为9,然后再把9赋给y
    //结果为y = 9	b = 9
    return 0;
}

*

*一个地址,可以把该地址处的值取到,然后进行赋值,输出等操作

(类型)

可以使两个不同类型的值进行更好的运算等操作

sizeof

sizeof是一个操作符,不是函数

函数是必须要有函数名称和()的,而sizeof在计算大小时(不包括类型)可以不需要括号,这也证明了sizeof不是函数而是操作符

sizeof和数组
#include 
void test1(int arr[])
{
    printf("%dn", sizeof(arr));//(1)
}
void test2(char ch[])
{
    printf("%dn", sizeof(ch));//(2)
}
int main()
{
    int arr[10] = { 0 };
    char ch[10] = { 0 };
    test1(arr);
    test2(ch);
    printf("%dn", sizeof(arr));//(3)
    printf("%dn", sizeof(ch));//(4)
    return 0;
}

  1. (1)的结果是4,是因为形参arr是一个指针,sizeof一个指针的大小,在32位平台上是4,64位平台上是8
  2. (2)的结果是4,是因为形参ch是一个指针,sizeof一个指针的大小,在32位平台上是4,64位平台上是8
  3. (3)的结果是40,是因为此时数组名arr是整个数组,sizeof数组名,计算整个数组元素的类型大小,arr里有10个int类型空间,所以结果是10 * 4 = 40
  4. (4)的解雇偶数10,是因为此时数组名ch是整个数组,sizeof数组名,计算整个数组元素的类型大小,ch里有10个char类型空间,所以结果是10 * 1 = 10
关系操作符
>
>=
<
<=
!=
==

这里需要注意的是,=是赋值操作符,==才是判断相等的操作符

逻辑操作符
&&		逻辑与		两个操作数都为真时,表达式结果为真
||		逻辑或		两个操作数都为假时,表达式结果为假

注意:

  1. 逻辑与&时,从前向后判断,若遇到为假的条件,则后面的表达式不再判断

​ 因为有一个条件为假,则整个表达式结果为假,所以后面表达式就不再判断了

  1. 逻辑或|时,从前向后判断,若遇到为真的条件,则后面的表达式不再判断

​ 因为有一个条件为真,则整个表达式结果为真,所以后面表达式就不再判断了

条件操作符

格式:

exp1 ? exp2 : exp3

​ 当exp1为真时,exp2的结果就是整个表达式的结果

​ 当exp1为假时,exp3的结果就是整个表达式的结果

逗号表达式

格式:

exp1, exp2, exp3, ..., expN

逗号表达式,就是用逗号隔开的多个表达式。

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

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

1.[ ]下标引用操作符

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

int arr[10];//创建数组
//实用下标引用操作符
arr[5] = 8;//给数组下标为5的地方赋值为8
arr[8] = 9;//给数组下标为5的地方赋值为8

其实[ ]的两个操作数是可以互换位置的

推导过程如下

arr[5] -> *(arr + 5) -> *(5 + arr) -> 5[arr]

2.()函数调用操作符

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

#include 
void test1()
{
    printf("hello world!n");
}
void test2(int* str)
{
    printf("%sn", str);
}
int main()
{
    char* str = "hello world!";
    test1();
    test2(str);
}

3.访问一个结构的成员

. 结构体.成员名

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

表达式求值

表达式求值的顺序一部分是由操作符的优先级和结合性决定。

同样,有些表达式的操作数在求值的过程中可能需要转换为其它类型。

隐式类型转换

C的整型算术运算总是至少以缺省(默认)整型类型的精度来进行的。

为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

整型提升的意义:

表达式的整型运算要在CPU的相应运算期间内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。

因此,即使两个char类型的元素相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。

通用CPU(general-purpose CPU)难以直接实现两个8比特位字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。

char a, b, c;
a = b + c;

b和c的长度小于int长度,所以先整型提升,转换为普通整型,然后再执行加法运算,加法运算完成之后,结果将被截断,然后再存储于a中。

如何进行整型提升

整型提升分为两种

  1. 有符号,高位补符号位
  2. 无符号,高位直接补0
有符号整型提升
//负数的整型提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位
11111111
所以运算时进行整型提升,转换为普通整型长度,
char是有符号的char,符号位为1,所以高位补1
整型提升之后的结果是:
11111111 11111111 11111111 11111111
//正数的整型提升;
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位
00000001
所以运算时进行整型提升,转换为普通整型长度,
char是有符号的char,符号位为0,所以高位补0
整型提升之后的结果是:
00000000 00000000 00000000 00000001
无符号整型提升
直接高位补0即可

实例1

先看a,a的二进制位(补码)是
10110110
然后在a == 0xb6表达式中进行整型提升变为
11111111 11111111 11111111 10110110
然后与0xb6进行比较,而0xb6的二进制位(补码)是
00000000 00000000 00000000 10110110
所以,a == 0xb6为假,也就不输出a
再看b,b的二进制位(补码)是
10110110 00000000
然后在b == 0xb600表达式中进行整型提升变为
11111111 11111111 10110110 00000000
然后与0xb600进行比较,而0xb600的二进制位(补码)是
00000000 00000000 10110110 00000000
所以,b == 0xb600为假,也就不输出b
最后看c,c的二进制位(补码)是
10110110 00000000 00000000 00000000
与0xb6000000的长度一样,所以不发生整型提升
而0xb6000000的二进制位(补码)是
10110110 00000000 00000000 00000000
所以,c == 0xb6000000为真,输出c

实例2

sizeof(c)并没有进行整型提升,所以c此时长度为1个字节
sizeof(+c)以及sizeof(-c),都分别进行了+c和-c的运算,所以进行整型提升,长度变为4个字节
算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。

long double
double
float
unsigned long int
long int
unsigned int
int

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另一个操作数的类型后再执行运算。

注意:

但是算术转换要合理,要不然会有一些潜在的问题。

float f = 3.14;
int num = f;//隐式转换,会有精度丢失
操作符的属性

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

  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序

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

实例1

//表达式的求值部分由操作符的优先级决定。
a * b + c * d + e * f
由于*的优先级高于+,所以只能保证先进行*,再进行+,但是并不能决定第三颗*比第一颗+早执行
//可能的顺序有
//顺序一:
a * b
c * d
e * f
(a * b) + (c * d)
(a * b) + (c * d) + (e * f)
//顺序二:
a * b
c * d
(a * b) + (c * d)
e * f
(a * b) + (c * d) + (e * f)

实例2

c + --c;
//只能保证--的运算在+号之前,但是不能保证+左操作数的获取是在--之前还是之后

注意:*

但是算术转换要合理,要不然会有一些潜在的问题。

float f = 3.14;
int num = f;//隐式转换,会有精度丢失
操作符的属性

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

  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序

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

实例1

//表达式的求值部分由操作符的优先级决定。
a * b + c * d + e * f
由于*的优先级高于+,所以只能保证先进行*,再进行+,但是并不能决定第三颗*比第一颗+早执行
//可能的顺序有
//顺序一:
a * b
c * d
e * f
(a * b) + (c * d)
(a * b) + (c * d) + (e * f)
//顺序二:
a * b
c * d
(a * b) + (c * d)
e * f
(a * b) + (c * d) + (e * f)

实例2

c + --c;
//只能保证--的运算在+号之前,但是不能保证+左操作数的获取是在--之前还是之后

**总结:**我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/884020.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号