浅谈操作符

目录:

一,基本操作符

①算数操作符:+,-,*,/,%

②赋值操作符:=

③关系操作符:>,<,>=,<=,==,!=

④自增、自减操作符:++,--

⑤条件操作符 

⑥逻辑运算符

(1)逻辑取反运算符!

(2)与运算符&&

(3)或运算符||

注意(短路):

二,移位操作符,位操作符

①二进制和进制转换

(1)进制:

(2)进制转换

②原码反码补码

③移位操作符,位操作符

(1)移位操作符

(2)位操作符

三,逗号表达式

四,下标访问[],函数调用()

(1)下标访问[]

(2)函数调用操作符()

五,结构体访问操作符·,->

 (1)定义和初始化:

(·)操作符:

(->)操作符:

六,操作符的属性

(1)优先级:

(2)结合性:

七,表达式求值

(1)转型提升:

(2)算术转换 :

总结一下:


闲聊一下:

        相信许多的网友都运用过操作符,但具体该怎么使用或者有一些疑问的本小编准备运用一下我自己的知识来帮助网友们解决一下心中的疑惑。

一,基本操作符

对于一些基础的操作符本主编把他们汇编在一起,以便大家先了解基础的操作符。以下是要介绍的六种基础的操作符:

①算数操作符:+,-,*,/,%

C语言提供所有常用的算术操作符:+,-,*,/,%。这些操作符叫双目操作符,也叫双元操作符。既然是双元操作符,那也许有人会疑惑会不会有一元操作符了。其实确实也有一元操作符的说法,一元操作符就只有+,-了,他们表示数值的正负,但是在经典C语言中是不存在,所以就不用在这个方向多加思考了。

现在我们开始步入正轨,其实算数操作符就是常见的加、减、乘、除、求余运算,下面让我们运用代码来实验一下:

#include<stdio.h>
int main() {
	int a = 6, b = 2;
	int ret = a+b;
	printf("%d\n", ret);
	 ret = a-b;
	printf("%d\n", ret);
	 ret = a*b;
	printf("%d\n", ret);
	 ret = a/b;
	printf("%d\n", ret);
	 ret = a % b;
	printf("%d\n", ret);
	return 0;
}

看看输出结果会怎么样了?

这个时候聪明的你就会发现最后的两个运算会有一点疑问?那就让我们再来看一组代码?

观看了代码的你也许会有这样的疑惑,为什么除法得到的结果不是3/2了? 

其实在/运算符来说,当两个操作数都是整数时,/运算符会丢失分数来“截取”结果。所以ret的结果是1,不是3/2。对于下一行来说,就是除%运算符以外(%运算符只能是整形),二元操作符既允许操作数是整形也允许是浮点数,两者混合也可以的。当把int型和double型混合在一起时,运算结果是double型。因此最后c的类型为double型。(具体原由见后算术转换)。

②赋值操作符:=

对于赋值运算符来说,它就相当于是把右值赋值给左值。下面是一些基本的赋值运算。

int a,b,c,d,e;
a = 5;//简单赋值
a = b = c = d = e = 6;//多个赋值串联在一起
return 0;

既然有基本的,那会不会有高级一点的了?

接下来就是复合赋值的到来了?

相信很多都这样写过代码吧    a=a+2,感觉这样写会不会有点啰嗦了?但对于C语言来说就有一种方法,可以让操作简单。下面让我们来看看?

+=

-=

*=

/=

%=

>>=

<<=

&=

|=

^=

上面的是赋值操作符,可以让运算变得简单,这可是对于喜欢偷懒的伙计的最好福祉。

就像a=a+2就可以写出a+=2了(对于其他的运算符也是这样的),这样就可以既表达到了意思也变得简便了。 

③关系操作符:>,<,>=,<=,==,!=

其实关系运算符就像大于号,小于号那样,将操作符的两边的值进行比大小。关系表达式通常返回 0 或 1 ,表示真假。 C语言中, 0 表示假,所有非零值表⽰真。比如, 20 > 12 返回 1 , 12 > 20 返回 0 。关系表达式常⽤于 if 或 while 结构。

int a = 5;
if (a == 5)//==表示相等,在if语句里面当作条件使用
	printf("hehe");
else if (a != 5)//!=表示不相等
	printf("haha");

关系运算符在一些语句中起到判断作用,具有非同凡响的作用。 

:有一些人会把=当成==使用这样就导致运算发生错误,=是赋值,==是等于。

④自增、自减操作符:++,--

相信大家在写C的时候都遇见过“++”,“--”这种的操作符,其实把它与之前的“=”和“+=”你可以发现这种操作符在某些方面可以把语句变得缩短一点。下面来看代码:

int a = 5;
int b, c;
b = a + 1;
c = a - 1;//一般的表达式
b = ++a;
c = --a;//使用自增、自减操作符

对自增和自减操作符来说,也分为两种一种前置++,一种是后置++(这里使用++来描述,--同理)。

⑤条件操作符 

条件操作符也叫三目操作符,需要接受三个操作数的,条件操作符是由符号?和符号:组成,形式如下:

条件表达式】=表达式1  ? 表达式2  :表达式3;

对于条件操作符的计算逻辑是 “如果表达式1的值不为0,那就计算表达式2的值,计算的结果是表达式的结果;如果表达式1的值为0,那就计算表达式3的值,计算的结果是表达式的结果.”下面来一个代码来表示一下:

//条件操作符 
//先来一个基本的操作
int main()
{

	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	if (a > 5)
		b = 3;
	else
		b = -3;
	printf("%d\n", b);
	return 0;
}

 使用条件运算符后的代码:

// 使用条件运算符后的代码:
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d", &a);
	b = a > 5 ? 3 : -3;
	printf("%d\n", b);
	return 0;
}

把这两给代码进行对比后可以知道使用条件运算符后的代码比不使用的代码量稍微减少了一下。

⑥逻辑运算符

 !                逻辑取反(可以理解为 取反 的意思)

&&                逻辑与(可以理解为 并且、与 的意思)

 ||                  逻辑或(可以理解为 或 的意思)

逻辑运算符提供逻辑判断功能,⽤于构建更复杂的表达式,主要有以上的三个运算符。那三个运算符的计算逻辑是什么了?

 ! :逻辑取反运算符(改变单个表达式的真假,原来的是真,使用后为假)。

 && :与运算符 两侧的表达式都为真,则为真,否则为假)。

 || :或运算符(两侧⾄少有⼀个表达式为真,则为真,否则为假)。

 注:C语⾔中,⾮0表⽰真,0表⽰假

接下来开始 逐步介绍逻辑运算符:

(1)逻辑取反运算符!

先来一个图来体会一下逻辑取反运算符!的运算规则。

   

此时。我们可以来使用一下a这个变量让他等于一下0,在使用一下运算符看看结果会咋样。

(2)与运算符&&


对于这种操作符,可以使用关于年龄的代码?

int main()
{
	int n = 15;
	if (n > 0 && n < 18)
		printf("未成年");
//&& 就是与运算符,也是并且的意思, && 是⼀个双⽬操作符,使⽤的⽅式是 a&&b ,
//&& 两边的表达 式都是真的时候,整个表达式才为真,只要有⼀个是假,则整个表达式为假。
	return 0;
}

因为n=15符合n>0和n<18,所以这个表达式就成立。 

(3)或运算符||

对于这种操作符,可以使用季节,就像冬天有12月,1月,2月语言类型的代码来描述一下? 

//或运算符||
int main() {
	int month = 0;
	scanf("%d", &month);
	if (month == 12 || month == 1 || month == 2)
	{
		printf("冬季\n");
	}
	//||就是或运算符,也就是或者的意思,||也是⼀个双⽬操作符,使⽤的⽅式是 a || b , 
	//两边的表达式只要有⼀个是真,整个表达式就是真,两边的表达式都为假的时候,才为假。
	//只有当month的值为12、1、2中的任意的一个,表达式才成立。
	return 0;
}

对于代码来说,只有当month的值为12、1、2中的任意的一个,表达式才成立。 

注意(短路):

C语⾔逻辑运算符还有⼀个特点,它总是先对左侧的表达式求值,再对右边的表达式求值,这个顺序是保证的(是根据结合性的,后面会说的)。

如果左边的表达式满⾜逻辑运算符的条件,就不再对右边的表达式求值。这种情况称为“短路”。

就像之前的代码 :

 if (n > 0 && n < 18) 

 表达式中&&的左操作数是 n>0 ,右操作数是 n<18 ,当左操作数 n>0 的 结果是0的时候,即使不判断n,整个表达式的结果也是0(成年)。

所以,对于&&操作符来说,左边操作数的结果是0的时候,右边操作数就不再执行。

既然弄清楚了&&操作符,那||操作符会是咋样了?

也来看一下之前的代码:

if (month == 12 || month == 1 || month == 2)

如果month==12,则不⽤再判断month是否等于1或者2,整个表达式的结果也是1(冬季)。 所以, || 操作符的左操作数的结果不为0时,就无需执行右操作数。

所以学完短路后是不是醍醐灌顶了?是不是想去写一下代码了?

二,移位操作符,位操作符

在讲解移位操作符和位操作符之前我们需要先了解一下二进制和进制转换,再了解一下原码反码补码。然后就可以使用移位操作符,位操作符。

①二进制和进制转换

(1)进制:

相信大家应该听说过二进制,十进制,八进制,十六进制的说法,那他们究竟是什么意思了?其实他们是对数值的不同的表达类型。

现在我们先来举一个列子:

15的2进制:1111

15的8进制:17

15的10进制:15

15的16进制:F

 那他们究竟是什么原理了?

十进制:十进制就是我们平常的计算一样,但他是只要满10就要向前加一位,他是由0~9组成的。但是一定不能以0开头;

二进制:而二进制就和十进制差不多了,只不过二进制是满2就要向前加一位,他是由0和1组成的;

八进制:八进制是满8就要向前加一位,他是由0~7组成的,八进制必须要以0开头;

十六进制:十六进制是满16就要向前加一位,他的组成就和之前的有所不同了;他是由0~9和a~f组成(可大写可小写),其中a~f相当于是10~15。在十六进制的开头就要有0x;

(2)进制转换

既然清楚了进制的原理,那这个时候就有可能有人会有疑问,进制这么多那可以在一起转换吗?

那现在就让我们了解一下是咋转换的?

对于这些进制,我认为先使用十进制去向其他的进制转换可以变得容易一下。

因为10进 制的数字从右向左是个位、⼗位、百位....,分别每⼀位的权重是10^0 , 10^1 , 10^2 ... ;

现在假设一个数为123,如下图:

2进制和10进制是类似的,只不过2进制的每⼀位的权重,从右向左是:2 , 2 , 2 ... 0 1 2

如果是2进制的1101,该怎么理解呢?

2进制1101每⼀位权重的理解

 为什么要先介绍十进制和二进制了?因为在进制转换中可以更快的转换。

那就让我们来先来了解10进制转2进制数字吧。

按照以上的这个图就可以清晰的理解了,其实使用权重也可以很好的在十进制和二进制之间转换。聪明的小伙伴就可以发现二进制的权重的相加后等于的就是十进制的值。所以这就是第二种方法使用权重和。

既然二转十的方法有了,那转转八和十六去试试。

对转八进制和十六进制直接使用十进制是不行的,要先使用二进制去转,然后在使用二进制去 转十进制,就可以得到十转八和十转十六了。

那我们就要先学一学二 转八和十六。

2进制转8进制

8进制的数字每⼀位是0~7的,0~7的数字,各自写成2进制,最多有3个2进制位就足够了,比如7的二进制是111,所以在2进制转8进制数的时候,从2进制序列中右边低位开始向左每3个2进制位会换算⼀ 个8进制位,剩余不够3个2进制位的直接换算。(其实我认为也是权重和的思想)。

举个列子:2进制的01101011,换成8进制:0153,0开头的数字,会被当做8进制。(这里之前有说过哦)。

 2进制转16进制

2进制转16进制和2进制转8进制的概念差不多。

16进制的数字每⼀位是0~9,a~f的,0~9,a~f的数字,各自写成2进制,最多有4个2进制位就足够了, 比如f的⼆进制是1111,所以在2进制转16进制数的时候,从2进制序列中右边低位开始向左每4个2进 制位会换算⼀个16进制位,剩余不够4个⼆进制位的直接换算。 

举个列子:2进制的01101011,换成16进制:0x6b,16进制表⽰的时候前⾯加0x(也有说过哦)。

②原码反码补码

在介绍完了进制的转换,现在来让我们迈入偏底层的知识点-------原码反码补码。

其实对于原码反码补码来说,是运用在二进制上面的,是整数二进制的不同表达方式。

有符号整数的三种表示方法均有符号位和数值位两部分,2进制序列中,最高位的一位是被当做符号位,剩余的都是数值位。符号位则都是用0表示“”,用1表示“”。

  • 正整数的原、反、补码都相同。
  • 负整数的三种表示方法各不相同。

原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。

反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。

补码:反码+1就得到补码。

其实对于原码反码补码的转换还有一种方法的。

就是补码得到原码也是可以使用:取反+1的操作。

对于整形来说:数据存放内存中其实存放的是补码

那为什么了?

在计算机系统中,数值⼀律⽤补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统⼀ 处理;同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算 过程是相同的,不需要额外的硬件电路。

③移位操作符,位操作符

在经过前面两节的铺垫,现在我们可以学习移位操作符和位操作符。

(1)移位操作符

对于移位操作符来说是在二进制上面进行操作的,移位操作符也分两种的。

<<左移操作符 

>>右移操作符

:移位操作符的操作数只能是整数。

 左移操作符

移位规则:左边抛弃、右边补0;

使用左移操作符的示意图:

右移操作符 

对于左移操作符来说,右移操作符还是会有一点不同的;

移位规则:首先右移运算分两种:

  1. 逻辑右移:左边用0填充,右边丢弃
  2. 算术右移:左边用原该值的符号位填充,右边丢弃

使用逻辑右移的示意图: 

使用算术右移的示意图:  

其实看到这个的时候聪明的你也许就已经发现了,其实移位操作符就相当于是把原数扩大或者缩小2的n次方(看移动了多少位)。

特殊情况:

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

举个列子:

  1. int num = 10;
  2. num>>-1;//error

(2)位操作符

在了解了移位操作符,就要开始了解位操作符了;

位操作符其实也是在二进制中去运算的。

位操作符有:

  1. &      //按位与(两个数中只要有0,就为0)
  2. |       //按位或(两个数中只有同时为0,才为0,有1则为1)
  3. ^      //按位异或(两个数中相同的则为0,相异的则为1)
  4. ~     //按位取反(将原本的取反,把0取反成1,把1取反成0)

:他们的操作数必须是整数。  

来上代码:

先来&按位与的:

因为一般的数字是使用原码的,因为整数的原码反码补码是一致的,负数的补码要原码取反+1后才可以开始计算。

| 按位或

大致和&按位与差不多 。但在得到负数的结果的时候要把他的补码取反+1切换成原码(打印出来的是以原码的形式出现的),正数就不用转换啦。所以做题的时候希望每次都遇到正数的。

^按位异或

~按位取反

三,逗号表达式

a=(x,y,z,...)

所谓的逗号表达式,就是使用逗号去分隔开多个表达式。

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

来个代码表示一下:

其实在运用逗号表达式时可以是一部分代码变得更简单。

a = get_val();
count_val(a);
while (a > 0)
{
	//业务处理 
	a = get_val();
	count_val(a);
}
//如果使⽤逗号表达式,改写:
while (a = get_val(), count_val(a), a > 0)
{
	//业务处理 
}

四,下标访问[],函数调用()

(1)下标访问[]

相信很多人都遇到过使用过[]这个符号,那它究竟是什么意思了?

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

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

(2)函数调用操作符()

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

#include <stdio.h>
void text1()
{
 printf("hehe\n");
}
void text2(int n)
{
    printf("%d\n",n);
}
int main()
{
 text1(); //这⾥的()就是作为函数调⽤操作符。 
 text2(2);//这⾥的()就是作为函数调⽤操作符。
 return 0;
}

五,结构体访问操作符·,->

在学习结构体访问操作符之前,我们需要了解一下什么是结构体再去了解结构体访问操作符。

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

结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如: 标量、数组、指针,甚⾄是其他结构体。

要学习一门新的知识就要首先学会声明和初始化。

 struct tag(名称)

{

member-list;

}variable-list;

通过上面的代码,可以得出结构体的声明是由三部分组成的:结构体的名称(tag),结构体的成员变量(member-list,可以多写),结构体的结构体变量(variable-list);注意:这里的声明和C语言中其他变量的声明格式一样,struct{......}指明了类型,variable-list是这种类型的变量。

明白原理后,来描述一个学生:

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

 (1)定义和初始化

在初始化的时候我们先定义一个结构体以方便后续的代码书写。

在初始化时,我们要在结构体变量后面使用花括号括起来(详细见代码),运用的可以把结构体变量嵌套在一起。

// 代码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 };//结构体嵌套初始化

初始化完成的结构体变量,我们如果要是使用其中的某个值,这个时候就要使用到“结构体访问操作符·,->”啦。

(·)操作符

(·)操作符,也叫结构体成员的直接访问,是通过点操作符(.)访问的。点操作符接受两个操作数。使用(·)操作符可以方便我们去寻找操作数,如下所示:

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

(->)操作符

(->)操作符,也叫结构体成员的间接访问,使用这个操作符的场景就有所不同了。有时候我们得到的不是⼀个结构体变量,而是得到了⼀个指向结构体的指针(指针有可能会有读者不是很清楚,这个在下一个博客就会发,请读者不用担心,如果有已经明白指针的读者就可以先看看下面的代码),如图所示:

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

关于结构体剩下的知识点,小编后面会出一个关于结构体的博客,还请各位读者不要太担心。

六,操作符的属性

C语言的操作符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序。

(1)优先级

优先级指的是,如果⼀个表达式包含多个运算符,哪个运算符应该优先执行。各种运算符的优先级是 不⼀样的。

eg:3 + 4 * 5;

上面示例中,表达式 3 + 4 * 5里面既有加法运算符( + ),还有乘法运算符( * )。由于乘法 的优先级高于加法,所以会先计算 4 * 5 ,而不是先计算 3 + 4 。

(2)结合性

如果两个运算符优先级相同,优先级没办法确定先计算哪个了,这时候就看结合性了,则根据运算符 是左结合,还是右结合,决定执行顺序。大部分运算符是左结合(从左到右执行),少数运算符是右 结合(从右到左执行),比如赋值运算符( = )

eg:5 * 6 / 2;

上面示例中, * 和 / 的优先级相同,它们都是左结合运算符,所以从左到右执,先计算 5 * 6 , 再计算 6 / 2 。

运算符的优先级顺序很多,下是部分运算符的优先级顺序(按照优先级从高到低排列),建议⼤概 记住这些操作符的优先级就行,其他操作符在使用的时候查看下面的图就可以了。  

  • 圆括号( () )
  • 自增运算符( ++ ),自减运算符( -- )
  • 单目运算符( + 和 - )
  • 乘法( * ),除法( / )
  • 加法( + ),减法( - )
  • 关系运算符( < 、 > 等)
  • 赋值运算符( = ) 由于圆括号的优先级最高,可以使用它改变其他运算符的优先级。

对于优先级来说就看一看下面的图吧。 

七,表达式求值

在经过了前面的铺垫,我们终于来到了最后的章节了------表达式求值。对于本章的内容有两点:整形提升和算数转换(前面有提到哦)。

(1)转型提升

相信许多的读者都见过下面的代码吧,那为什么会这样了?实际上就是运用了转型提升的思想。

那什么是转型提升了? 

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

那它有什么意义了?

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一 般就是int的字节长度,同时也是CPU的通用寄存器的长度。 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。 通用CPU(general-purposeCPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为 int或unsigned int,然后才能送入CPU去执行运算。

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

那我们既然知道了他的意义,那我们应该怎样进行转型提升了?

  1. 有符号整数提升是按照变量的数据类型的符号位来提升的
  2. 无符号整数提升,高位补0 

来看一看下面的代码: 

 

:有可能会有读者疑惑char是有符号是无符号了?其实这个不好确定的,要去取决于当前使用的编译器,像编者使用的是VS2022,它的上面char是有符号的。 

(2)算术转换 

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

  1. long double
  2. double
  3. float
  4. unsigned long int
  5. long int
  6. unsigned int
  7. int  

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

总结一下:

这次的详解操作符的博客内容挺多的但比较详细一点,本博客里面涉及到了许多的知识点,需要读者去一一思考然后去敲一敲,毕竟只要多敲才可以掌握知识点。在下一篇博客小编准备来一个操作符的题目类型的,来当成续集吧。最后希望我的博客可以对大家有帮助,如果觉得我的博客还可以的话可以关注一下,三起走起(你们的鼓励就是我的动力)。谢谢各位了!

  • 41
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 16
    评论
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值