【C语言】操作符详解

前言

操作符在初始C语言的时候有介绍过,可以结合初识C语言五(操作符和关键字)的博客一起学习。

操作符的分类:

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

1. 算术操作符

+(加)  -(减)  *(乘)  / (除)  % (模)
  1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
  2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。除法想得到小数的结果,必须保证除数和被除数中至少有一个小数(浮点数)。
  3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数

2. 移位操作符

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

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

移位操作符,移动的是二进制,我们就来讲一下二进制位的表示。
一个数可以用不同的进制表示,这里用数值位15的数来举例:在这里插入图片描述

整型的二进制的表示分为原码、反码、补码三种。

  1. 整数在内存中存储的是二进制的补码,移位操作符,移动的是存储在内存中的补码。
  2. 整数在C语言可以存放到 int 类型的变量中,int 类型是4字节,32bit(比特位),32个二进制位中最高位为符号位。
    (1) 符号位是0,表示正数
    (2) 符号位是1,表示负数
  3. 正整数的原码、反码、补码相同

15 (正整数)
00000000000000000000000000001111 —— 原码
00000000000000000000000000001111 —— 反码
00000000000000000000000000001111 —— 补码

  1. 负整数
  • 反码 = 原码的符号位不变,其他位按位取反
  • 补码 = 反码 + 1

-15(负整数)
10000000000000000000000000001111 —— 原码
111111111111111111111111111111110000 —— 反码
111111111111111111111111111111110001 —— 补码

计算左移或右移操作符做题Tips:

  • 第一步:写出需要移动数 a 的原码,通过原码写出其补码,正整数直接写,负整数通过转换公式写;
  • 第二步:通左移右移移位规则对补码进行移位计算;
  • 第三步:补码计算完成后要转换为原码,正整数直接写,负整数通过转换公式写;转换成原码后直接换算成它的十进制就可以了。
    因为最终打印的结果的值对应的是原码,所以这里需要再转换一次。

2.1 左移操作符

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

示例1:(正整数左移)

#include <stdio.h>
int main()
{
	int a = 4;
	//00000000000000000000000000000100 —— 4的原码、反码、补码
	int b = a << 1;//把a向左移动一位
	//00000000000000000000000000001000 —— b的补码、反码、原码
	printf("a = %d b = %d", a, b);
	return 0;
}

运行结果:
在这里插入图片描述

图解:
在这里插入图片描述
示例2:(负整数左移)

#include <stdio.h>
int main()
{
	int a = -4;
	//10000000000000000000000000000100 —— -4的原码
	//11111111111111111111111111111011 —— -4的反码
	//11111111111111111111111111111100 —— -4的补码
	
	int b = a << 1;//把a向左移动一位
	//11111111111111111111111111111000 —— b中存储的补码
	//11111111111111111111111111110111 —— b的反码(补码-1)
	//10000000000000000000000000001000 —— b的原码 
	printf("a = %d b = %d", a, b);
	return 0;
}

运行结果:
在这里插入图片描述

图解:
在这里插入图片描述

2.2 右移操作符

移位规则:右移运算分为两种

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

注:C语言中没有规定右移是哪一种右移,这都取决于编译器,绝大多数编译器都是算术右移,所以我们用算术移位来讲解一下右移操作符

示例1:(正整数右移)

#include <stdio.h>
int main()
{
	int a = 4;
	//00000000000000000000000000000100 —— 4的原码、反码、补码
	
	int b = a >> 1;//把a向右移动一位
	//00000000000000000000000000000010 —— b的补码、反码、原码
	printf("a = %d b = %d", a, b);
	return 0;
}

运行结果:
在这里插入图片描述

图解:
在这里插入图片描述

示例2:(负整数右移)

#include <stdio.h>
int main()
{
	int a = -4;
	//10000000000000000000000000000100 —— -4的原码
	//11111111111111111111111111111011 —— -4的反码
	//11111111111111111111111111111100 —— -4的补码

	int b = a >> 1;//把a向右移动一位
	//11111111111111111111111111111110 —— b中存储的补码
	//11111111111111111111111111111101 —— b的反码
	//10000000000000000000000000000010 —— b的原码
	printf("a = %d b = %d", a, b);
	return 0;
}

运行结果:
在这里插入图片描述

图解:
在这里插入图片描述

总结:
实际上 a 在没被赋值的情况下,自身的值不会变化。
通过输出结果我们发现,左移操作符有扩大2倍的效果,乘2的效果。
右移操作符有缩小2倍的效果,除2的效果。

警告⚠:对于移位运算符,不要移动负数位,这个是标准未定义的
例如:

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

3. 位操作符

位操作符的位指的是二进制位
位操作符有:

&     //按位与(按二进制位与)
|     //按位或(按二进制位或)
^     //按位异或(按二进制位异或)

注:按位操作符的操作数必须是整数。

运算规则:

  • & 按位与:遇0则为0,同为1才为1
  • | 按位或:只要有1就为1(遇1则为1),同为0才为0
  • ^ 按位异或:相同为0,相异为1

示例1:

int main()
{
	int a = 3;
	int b = -5;
	printf("a & b = %d\n", a & b);//& 按(二进制)位与

	//00000000000000000000000000000011 ——  3的原码、反码、补码
	//10000000000000000000000000000101 —— -5的原码
	//11111111111111111111111111111010 —— -5的反码
	//11111111111111111111111111111011 —— -5的补码

	// &  按位与:遇0则为0,同为1才为1
	//00000000000000000000000000000011 ——  3的补码
	//11111111111111111111111111111011 —— -5的补码
	//00000000000000000000000000000011 —— 3 & -5的补码 =(3的补码)

	printf("a | b = %d\n", a | b);//| 按(二进制)位或
	//  |   按位或:只要有1就为1,同为0才为0
	//00000000000000000000000000000011 ——  3的补码
	//11111111111111111111111111111011 —— -5的补码
	//11111111111111111111111111111011 —— 3 | -5的补码 
	//11111111111111111111111111111010 —— 3 | -5的反码
	//10000000000000000000000000000101 —— 3 | -5的原码 = (-5的原码)

	printf("a ^ b = %d\n", a ^ b);//| 按(二进制)位异或
	//^ 按位异或:相同为0,相异为1
	//00000000000000000000000000000011 ——  3的补码
	//11111111111111111111111111111011 —— -5的补码
	//11111111111111111111111111111000 —— 3 ^ -5的补码
	//11111111111111111111111111110111 —— 3 ^ -5的反码
	//10000000000000000000000000001000 —— 3 ^ -5的原码  = -8
	return 0;
}

运行结果:
在这里插入图片描述

示例2:一道变态的面试题:
不能创建临时变量(第三个变量),实现两个数的交换。

这里由公式可以用:

  • a ^ a = 0
  • a ^ 0 = a
  • a ^ b ^ b = a
  • a ^ b ^ a = b (异或支持交换律)

代码如下:

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

运行结果:
在这里插入图片描述

4. 赋值操作符

 =  (赋值操作符)

赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。赋值操作符是把右边的值赋值给左边。

int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;//工资
salary = 20000.0;//使用赋值操作符赋值

赋值操作符可以连续使用
例:

#include <stdio.h>
int main()
{
	int a = 10;
	int b = 0;
	int c = 20;
	a = b = c + 1;//连续赋值
	printf("%d\n", a);
	return 0;
}

在这里插入图片描述

复合赋值符
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=

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

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

在这里插入图片描述

5. 单目操作符

单目操作符是只有一个操作数的操作符

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

这些我们大部分在初始C语言的时候介绍过,这里讲一下没有介绍过的& 取地址操作符和* 解引用操作符。

#include <stdio.h>
int main()
{
	int a = 10;//4字节
	int* pa = &a;//& 取地址操作符取出a的地址
	*pa;//* 解引用操作符 通过pa想找到它指向的对象a的时候就用解引用操作符
	return 0;
}

~ ( 对一个数的二进制按位取反)

#include <stdio.h>
int main()
{
	int a = 0;
	//00000000000000000000000000000000 —— 0的原码、反码、补码
	//11111111111111111111111111111111 —— ~a内存中->补码
	//11111111111111111111111111111110 —— ~a的反码
	//10000000000000000000000000000001 —— ~a的原码 //-1
	printf("%d\n", ~a);
	return 0;
}

在这里插入图片描述

6. 关系操作符

>    大于
>=   大于等于
<    小于
<=   小于等于
!=   用于测试“不相等”
==   用于测试“相等”

警告⚠:在编程的过程中 == 和 = 不小心写错,导致的错误
注:== 不能比较两个字符串的内容,实际上比较的是两个字符串的首字符的地址。

7. 逻辑操作符

&&  逻辑与(并且)
||  逻辑或(或者)
  • 区分逻辑与按位与
  • 区分逻辑或按位或
  • 按位与和按位或是针对二进制位来进行计算的,而逻辑与和逻辑或不关注二进制位,只关心值的真假性。

逻辑与和逻辑或的特点:

  • 逻辑与:必须两个条件都为真 if语句才会执行
  • 逻辑或:只有两个条件都为假 if语句才不会执行
  • a&b 只要 a 为假 这个表达式就为假,b 就不用计算了
  • a | b 只要 a 为真这个表达式就为真,b 就不用计算了

360笔试题

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

在这里插入图片描述

分析;
我们这里a++是后置加加,先使用后加加,这里a为0为假,这里是或运算,当a为假整个表达式都为假,后面的值就不用计算,a使用完后还要加加,所以a=1,b=2,c=3,d=4。

8. 条件操作符(三目操作符)

exp1 ? exp2 : exp3

<表达式1> ? <表达式2> : <表达式3>;
"?"运算符的含义是:先求表达式1的值,如果为真,则执行表达式2,并返回表达式2的结果;如果表达式1的值为假,则执⾏表达式3,并返回表达式3的结果。

练习:

#include <stdio.h>
int main()
{
	int a = 3;
	int b = 0;
	if (a > 5)
		b = 3;
	else
		b = -3;

	//这里将这个循环语句转换成条件表达式
	b = (a > 5) ? 3 : -3;

	//使用条件表达式实现找两个数中较大值
	int m = (a > b ? a : b);
	return 0;
}

9. 逗号表达式

exp1,exp2,exp3,...expn
  • 逗号表达式,就是用逗号隔开的多个表达式。
  • 逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果
#include <stdio.h>
int main()
{
	int a = 1;
	int b = 2;
	int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式
	printf("%d\n", c);
	return 0;
}

在这里插入图片描述

用逗号表达式对代码进行改写,会发现更简洁明了

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

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

[] () .  ->
  1. 下标引用
[ ] 下标引用操作符
操作数:一个数组名 + 一个索引值
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
 //[ ]的两个操作数是arr和9
  1. ( ) 函数调用操作符
    接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
#include <stdio.h>
void test1(int x, int y)
{

}
void test2()
{}

int main()
{
	test1(3, 4);//()函数调用操作符
	//操作数:test1,3,4
	test2();
	//操作数:test2
	return 0;
}
  1. 访问一个结构的成员
    . (结构体变量 . 结构体成员名)
    -> ( 结构体指针 -> 结构体成员名)
//书:书名,价格
#include <stdio.h>
struct Book
{
	char name[20];
	int price;
};
int main()
{
	struct Book sb = { "明解C语言",55 };
	printf("%s %d\n", sb.name, sb.price);//结构体变量.结构体成员名

	struct Book* ps = &sb;
	printf("%s %d\n", (*ps).name,(*ps).price);
	printf("%s %d\n", ps->name,ps->price);//结构体指针 -> 结构体成员名
	return 0;
}

在这里插入图片描述

11. 表达式求值

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

11.1 隐式类型转换

C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升
整型提升的意义
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度。
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。

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

#include <stdio.h>
int main()
{
	char a = 5;//截断
	char b = 126;//截断
	char c = a + b;//截断

	//char 1个字节,8个bit
	//00000000000000000000000000000101 —— 5的补码
	//00000101 —— a
	//00000000000000000000000001111110 —— 126的补码
	//01111110 —— b
	// 正整数整型提升
	// 因为 char 为有符号的 char
    //所以整形提升的时候,高位补充符号位,即为0
	// 提升之后的结果是:
	//00000000000000000000000000000101 —— a
	//00000000000000000000000001111110 —— b
	//00000000000000000000000010000011 —— c = a+b
	//10000011 —— c

	printf("%d\n", c);
	//%d 十进制的方式打印有符号的整数
	//c是char类型所以需要整型提升,c的符号位为1,所以是负数
	// 负整数整型提升
	// 因为 char 为有符号的 char
    //所以整形提升的时候,高位补充符号位,即为1
	//提升之后的结果是:
	//11111111111111111111111110000011 —— c的补码
	//11111111111111111111111110000010 —— c的反码
	//10000000000000000000000001111101 —— c的原码
	// -125
	return 0;
}

//无符号整形提升,高位补0

在这里插入图片描述

整型提升是针对自身大小是小于一个整型大小的变量。

示例2:

#include <stdio.h>
int main()
{
	char a = 0xb6;
	short b = 0xb600;
	int c = 0xb6000000;
	if (a == 0xb6)
		printf("a");
	if (b == 0xb600)
		printf("b");
	if (c == 0xb6000000)
		printf("c");
	return 0;
}

在这里插入图片描述

分析:
a,b都小于一个整型,所以a,b要进行整形提升,但是c不需要整形提升
a,b整形提升之后,变成了负数,所以表达式 a== 0xb6 , b == 0xb600 的结果是假,但是c不发生整形提升,则表达式 c==0xb6000000 的结果是真。

示例3:

#include <stdio.h>
int main()
{
	char c = 1;
	printf("%u\n", sizeof(c));
	printf("%u\n", sizeof(+c));
	printf("%u\n", sizeof(-c));
	return 0;
}

在这里插入图片描述

分析:
,c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节。表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof( c ) 就是1个字节。

11.2 算术转换

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

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

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
警告⚠:但是算术转换要合理,要不然会有一些潜在的问题。

float f = 3.14;
int num = f;//隐式转换,会有精度丢失

11.3 操作符的属性

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

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

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

操作符优先级:
在这里插入图片描述

上表中可以总结出如下规律:

  1. 结合方向只有三个是从右往左,其余都是从左往右。
  2. 所有双目运算符中只有赋值运算符的结合方向是从右往左。
  3. 另外两个从右往左结合的运算符也很好记,因为它们很特殊:一个是单目运算符,一个是三目运算符。
  4. C语言中有且只有一个三目运算符。
  5. 逗号运算符的优先级最低,要记住。
  6. 此外要记住,对于优先级:算术运算符 > 关系运算符 > 逻辑运算符 > 赋值运算符。逻辑运算符中“逻辑非 !”除外。

问题表达式:

//表达式的求值部分由操作符的优先级决定
a*b + c*d + e*f

计算机的计算机制:
方式1:
在这里插入图片描述
方式2:
在这里插入图片描述

分析:

  • 有人会说这两种计算机制不是一样吗?不一样的哦,我们不能想的太简单,我们把这些变量当初表达式的时候,他们的先后运算是会影响到后面的结果的。
  • 计算的时候,由于*比+的优先级高,只能保证,的计算是比+早,但是优先级并不能决定第三个比第一个+早执行。
  • 我们在写代码的时候要慎重,避免bug的发生。

本章到这里就结束啦,如果有哪里写的不好的地方,请指正。
如果觉得不错并且对你有帮助的话请给个三连支持一下吧!
Fighting!!!✊

  • 18
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 36
    评论
C语言中的string函数主要包括字符串的处理函数和字符串的操作函数。 字符串的处理函数主要有以下几个: 1. strlen:用于计算字符串的长度,即包含的字符数目。 2. strcpy:用于将一个字符串复制到另一个字符串中。 3. strcat:用于将两个字符串连接起来。 4. strcmp:用于比较两个字符串的大小关系。 5. strchr:用于在一个字符串中查找指定字符的位置。 6. strstr:用于在一个字符串中查找指定子串的位置。 字符串的操作函数主要有以下几个: 1. sprintf:用于将格式化的数据写入字符串中。 2. sscanf:用于从字符串中读取格式化的数据。 3. strtok:用于将一个字符串按照指定的分隔符进行分割。 4. strncmp:用于比较两个字符串的前n个字符的大小关系。 5. strncpy:用于将一个字符串的部分内容复制到另一个字符串中。 6. memset:用于给字符串的指定范围内的每个字符赋予相同的值。 这些函数可以帮助我们在C语言中方便地处理字符串,实现字符串的复制、连接、比较、查找等操作。通过这些函数,我们可以更高效地处理文本数据,提高代码的可读性和可维护性。 需要注意的是,使用这些函数时要确保输入的参数合法,以避免内存越界等错误。同时,字符串的内存空间需要提前分配好,以免出现不可预知的问题。在实际编程中,我们需要灵活运用这些函数,结合具体需求,进行字符串的处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 36
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值