操作符详解

前言:在前面的学习中,我们不仅学习了函数,数组等,也了解过一些操作符。今天就让我们一起来深入学习操作符。

1 操作符的分类

. 算术操作符+、-、*、/、%

. 移位操作符<<、>>

. 位操作符&、|、^、~

. 赋值操作符=、+=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=

. 单目操作符!、++、--、&、*、+、-、~、sizeof、(类型)

. 关系操作符>、<、>=、<=、==、!=

. 逻辑操作符&&、||

. 条件操作符? :

. 逗号表达式,

. 下标引用[]

. 函数调用()

. 结构成员访问. 、->

在学习今天的操作符之前,先来学习一下二进制和进制转换的知识

2 二进制

在生活中,最常见的就是10进制了。除此之外,还有2进制,8进制,16进制。这些进制都是数值的不同表现形式而已。

比如:数值15的各种进制表示形式

15的2进制:1111
15的8进制:17
15的10进制:15
15的16进制:F

计算机能够识别的指令就是二进制,因此我们重点来介绍二进制。

首先我们从10进制讲起,也是生活中最常用的。

. 10进制中满10进1

. 10进制中的每一位数字都是由0~9的数字组成

二进制也是一样的。

. 二进制中满2进1

. 二进制中每一位数字都是由0~1的数字组成

特别要注意的是16进制。

. 16进制中的每一位数字都是由0~9和A,B,C,D,E,F组成的

3 进制转换

10进制的123表示的值是一百二十三,为什么是这个值呢?其实10进制的每一位都是有权重的。10进制从右向左分别是个位,十位,百位…,每一位的权重分别是10^0,10^1,10^2

在这里插入图片描述
二进制和十进制是类似的。只不过二进制的每一位权重从右向左分别是2^0,2^1,2^2

在这里插入图片描述
. 10进制转为2进制

比如:10进制的125

在这里插入图片描述
. 2进制转换8进制

8进制中的每一位数字都是由0~7组成的,0~7中的每一个数字都可以用3位二进制位表示。所以2进制转换8进制的时候,从二进制序列中右边低位开始向左每3个二进制位会换算一个8进制位,剩余不够3个二进制位的直接换算。

2进制------8进制
 000--------0
 001--------1
 010--------2
 011--------3
 100--------4
 101--------5
 110--------6
 111--------7

例如:2进制的01101011转换为8进制:0153,0开头的数字,会被当做8进制。

在这里插入图片描述
. 2进制转16进制

16进制的数字每一位都是由0~9,a~f组成的,16进制的每一位数字都可以用4位2进制位表示。所以在2进制转16进制的时候,从2进制序列中右边低位开始向左每4位会转换一个16进制位,剩余不够4个二进制位的直接换算。

  2进制---------16进制
   0000----------0
   0001----------1
   0010----------2
   0011----------3
   0100----------4
   ......
   1010----------a
   1011----------b
   1100----------c
   1101----------d
   1110----------e
   1111----------f

例如:2进制的01101011转换为16进制:0x6b,16进制表示的时候前面加0x

在这里插入图片描述

4 原码,反码,补码

整数的2进制表示方法有3种:原码,反码,补码

有符号整数的3种表示方法均有符号位数值位两部分,在2进制序列中最高位的一位是符号位,剩余位是数值位。

符号位都是用0表示“正”,用1表示“负”。

正整数的原,反,补码都相同

负整数的3种表示方法各有不同

. 原码根据数值按照正负数的形式直接翻译成二进制序列得到的就是原码

. 反码原码的符号位不变,其他位依次按位取反

. 补码反码+1

通过补码得到原码也可以通过取反,+1的操作得到原码。

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

为什么呢?

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

#include<stdio.h>
int main()
{
	int a = 10;
	//正数的原码,反码,补码是相同的
	//00000000000000000000000000001010
	int b = -5;
	//10000000000000000000000000000101原码
	//11111111111111111111111111111010反码
	//11111111111111111111111111111011补码
	return 0;
}

5 移位操作符

. <<左移操作符将二进制补码序列的每一位向左移动,右 边补0

. >>右移操作符分为逻辑右移和算术右移

逻辑右移二进制补码序列的每一位向右移动,左边补0

算术右移二进制补码序列的每一位向右移动,左边用该值的符号位补充(大多数编译器采用算术右移)

它们的操作数必须是整数

#include<stdio.h>
int main()
{
	int a = 10;
	//00000000000000000000000000001010
	printf("%d\n", a << 1);//20
	//00000000000000000000000000010100 ------- 20
	return 0;
}

画图分析

在这里插入图片描述

#include<stdio.h>
int main()
{
	int a = 10;
	//00000000000000000000000000001010
	printf("%d\n", a >> 1);//5
	//00000000000000000000000000000101
	return 0;
}

观察上述代码,可以看到<<左移操作符有*2的效果,>>右移操作符有/2的效果

6 位操作符

. &按位与有0为0,全1为1

. | 按位或有1为1,全0为0

. ^按位异或相同为0,相异为1。(支持交换律)

. ~按位取反二进制序列中的每一位都要进行取反操作

它们的操作数必须是整数

#include<stdio.h>
int main()
{
	int a = 10;
	//00000000000000000000000000001010
	int b = -5;
	//10000000000000000000000000000101原码
	//11111111111111111111111111111010反码
	//11111111111111111111111111111011补码
	printf("a&b=%d\n", a & b);//10
	//  00000000000000000000000000001010
	// &11111111111111111111111111111011
	//  00000000000000000000000000001010   //10


	int n = -2;
	//10000000000000000000000000000010原码
	//11111111111111111111111111111101反码
	//11111111111111111111111111111110补码
	//00000000000000000000000000000001    ~a
	printf("~n=%d\n", ~n);//1
	int m = 0;
	//00000000000000000000000000000000
	//11111111111111111111111111111111 补码  ~b
	//10000000000000000000000000000000
	//10000000000000000000000000000001   //-1
	printf("~m=%d\n", ~m);//-1

	return 0;
}

接下来我们来看一道变态的面试题。

不创建临时变量,实现两个整数的交换。

第一种方法

#include<stdio.h>
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d,b=%d\n", a, b);
	a = a ^ b;//此时原来a的值已经被覆盖
	b = a ^ b;//a^b就相当于a^b^b,根据按位异或的规则,此时b的值被改成a,b里面存放原来a的值
	a = a ^ b;//a^b就等于a^b^b=a^b^a,a的值被改成b的值,a里面存放原来b的值
	printf("交换后:a=%d,b=%d\n", a, b);
	return 0;
}

第二种方法

#include<stdio.h>
int main()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, &b);
	printf("交换前:a=%d,b=%d\n", a, b);
	a = a + b;//此时a里面存储的是a+b的值,原来a的值已经被覆盖
	b = a - b;//a-b等价于a+b-b=a(这里的a是原来a的值),b的值已经被原来a的值覆盖
	a = a - b;//a-b等价于a+b-b就等于a+b-a,所以a里面存储的是原来b的值
	printf("交换后:a=%d,b=%d\n", a, b);
	return 0;
}

7 逗号表达式

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

exp1,exp2,exp3,...expN

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

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

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

. [ ]下标引用操作符

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d\n", arr[6]);//7
	//[]下标引用操作符,arr,6是操作数
	return 0;
}

. 函数调用操作符()

#include<stdio.h>
int Fact(int n)
{
	if (n <= 2)
	{
		return 1;
	}
	else
	{
		return Fact(n - 1) + Fact(n - 2);
	}
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int ret = Fact(n);//()是函数调用操作符,Fact,n是操作数,函数调用操作符至少有一个操作数,是函数名,可以无参,也可以有多个参数
	printf("第%d个斐波那契数是%d\n", n, ret);
	return 0;
}

9 结构成员访问操作符

C语言已经提供了内置类型(char,short,int,float,double等),但是只有这些是不够的。描述一个学生需要姓名,,性别,年龄,学号,身高,体重等。单一的内置类型是不行的。C语言为了解决这个问题,增加了结构体这种自定义类型。

. 结构的声明

struct tag
{
	member_list;
}variable_list;

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

#include<stdio.h>
struct stu//结构体类型的声明
{
	char name[15];
	int age;
}s1;//结构体变量s1
struct book
{
	int price;
	char author[20];
	struct stu s3;//结构体嵌套
};
int main()
{
	struct stu s1 = { "zhangsan",20 };//初始化
	struct stu s2 = { .age = 18,.name = "xiaoming" };//指定初始化顺序
	printf("%s,%d\n", s1.name, s1.age);
	printf("%s,%d\n", s2.name, s2.age);
	struct book b1 = { 66,"luoguanzhong",{"lisi",16} };
	//结构体嵌套初始化
	printf("%d,%s,%s,%d", b1.price, b1.author, b1.s3.name, b1.s3.age);
	return 0;
}

. 结构成员访问操作符

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

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

结构体成员的间接访问

有时候我们得到的不是一个结构体变量,而是一个指向结构体的指针。

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

#include<stdio.h>
#include<string.h>
struct stu
{
	char name[20];//名字
	int age;//年龄
};
void set_stu(struct stu* ps1)//结构体指针
{
	strcpy(ps1->name, "lisi");//字符串拷贝函数,是一个库函数,需要包含头文件string.h
	ps1->age = 22;
}
void print_stu(struct stu s1)
{
	printf("%s,%d", s1.name, s1.age);
}
int main()
{
	struct stu s1 = { "zhangsan",20 };
	set_stu(&s1);//设置学生的信息
	print_stu(s1);//打印信息
	return 0;
}

10 操作符的属性:优先级,结合性

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

. 优先级

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

. 结合性

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

下面是部分运算符的优先级顺序(按照优先级由高到低排列)。

. 圆括号(())

. 自增运算符(++),自减运算符(--)

. 单目运算符(+和-)

. 乘法(*),除法(/)

. 加法(+),减法(-)

. 关系运算符(>,<等)

. 赋值运算符(=)

参考:https://zh.cppreference.com/w/c/language/operator_precedence?

11 整型提升

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

整型提升的意义

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

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

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

如何进行整型提升呢?

. 有符号整数提升是按照变量的数据类型的符号位来提升的

. 无符号整数提升,高位补0

#include<stdio.h>
int main()
{
	char a = 1;
	//00000000000000000000000000000001------- 1是一个整型
	// char类型,1个byte,8个bit
	//00000001发生截断

	char b = -1;
	//10000000000000000000000000000001原码
	//11111111111111111111111111111110反码
	//11111111111111111111111111111111补码
	//char类型,1个byte,8个bit
	//11111111
	char c = a + b;
	//相加之前,先整型提升
	// a ---- 00000000000000000000000000000001整型提升
	// b ---- 11111111111111111111111111111111整型提升
	//       100000000000000000000000000000000
	// char类型只能够存储8个bit
	// c ---- 00000000
	printf("%d\n", c);//0
	//%d打印整数,先发生整型提升
	//00000000000000000000000000000000
	return 0;
}

12 算术转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的类型转换为另一个操作数的类型,否则操作就无法执行。

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

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

#include<stdio.h>
int main()
{
			//%d应该写成%f,否则会报错,这里只是为了验证类型的转换
	printf("%d\n", 3 + 6.12);//int转换为double
	return 0;
}

在这里插入图片描述

结语:今天的操作符详解到此告一段落,觉得还不错的为小编点点赞吧。

  • 15
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值