C语言之操作符详解

本章重点

1.各种操作符详解

2.表达式求值

目录

1.操作符

1.1 算术操作符

1.2 移位操作符

1.3 位操作符

1.4 赋值操作符

1.5 单目操作符

1.6 关系操作符

1.7 逻辑操作符

1.8 条件操作符

1.9 逗号表达式

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

2.表达式求值

2.1 隐式类型转换

2.1.1 整型提升

 2.1.2 算术转换

2.2 操作符的属性


1.操作符

1.1 算术操作符

+  -  *  /  %

1.除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。

2.对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。

3.% 操作符的两个操作数必须为整数,返回的是整数之后的余数 

下面是 / 操作符 和 % 操作符的代码示例 

//   /  除法   
//   1.整数除法  (除号的两端都是整数)
//   2. 浮点数除法  (除号的两端一个是小数就是浮点数除法)

#include <stdio.h>

int main()
{
	int r = 7 / 2;//3   整数除法
	printf("%d\n", r);//3

	double d = 7 / 2;// 3.000000    整数除法
	printf("%lf\n", d);// 3.0

	double d1 = 7 / 2.0;  // 3.5   浮点型除法
	printf("%lf\n", d1);  

	//被除数不能是 0 
	//int r = 5 / 0;  报错

	return 0;
}


//  %  取模操作符

#include<stdio.h>

int main()
{
	int r = 17 % 8;// %  得到的是整除以后的余数
	printf("%d\n", r);

	// %  两个操作数必须都是整数

	return 0;
}

1.2 移位操作符

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

移位操作符是对二进制位进行操作,计算机能处理的是二进制的信息。 所以在进行学习移位操作符是,我们先来学习一下整数在内存中的存储方式,即二进制的表示形式。

整数二级制有三种表示形式

1.原码

2.反码

3.补码

1.正整数的原码,反码,补码是相同的。

2.负整数的原码,反码,补码是要计算的。

首先不管是正整数还是负整数都是可以写出二进制原码,根据正负直接写出的二级制序列就是原码

// 1个整形是4个字节 == 32个bit位
// 一个二进制位是一个bit位

// 15
// 00000000000000000000000000001111   15的原码
//第一位是符号位  1 是负数  0是整数

// -15
//10000000000000000000000000001111    -15的原码

>> 右移操作符

右移操作有两种,

1.算术右移 (二级制右边丢弃,左边补原来的符号位)

2.逻辑右移(二进制右边丢弃,左边直接补0)

C语言没有明确规定到底是算术右移,还是逻辑右移,一般编译器采用的是算术右移,左边补符号位。

反码 由原码符号位不变,其他位按位取反得到

补码 由反码加一得到

整数是以补码的方式在内存中存储的。打印的时候才会以原码的方式打印

 示例:

//正整数
#include<stdio.h>

int main()
{
	int a = 15;
	//00000000000000000000000000001111  -  原码
	//正整数原码 反码 补码都相同


	//整数在内存中存储的是补码
	//计算的时候也是使用补码计算的

	int c = a >> 1;//移动的是a中的 2进制 信息    a是不变的
	
	printf("%d\n", c);//7
	printf("%d\n", a);//a不变,还是15
	
  //可以发现正整数右移有除2的效果
 
	return 0; 
}

//负整数
#include<stdio.h>

int main()
{
	int a = -15;
    //10000000000000000000000000001111  -  原码
    //11111111111111111111111111110000  -  反码  (原码的符号位不变,其他位按位取反得到的就是反码)
    //11111111111111111111111111110001  -  补码  (反码+1)

	int b = a >> 1;

	//11111111111111111111111111110001   -15补码
	//11111111111111111111111111111000   右移 左边补符号位
	//11111111111111111111111111110111   反码  (补码-1)
	//10000000000000000000000000001000   原码  -8

	printf("%d\n", a);//a不变  还是-15
	printf("%d\n", b);//-8

	//通过运行,可以发现VS采用的逻辑右移

	return 0;
}

  << 左移操作符

左移操作符 比较简单,规则是左边抛弃,右边补0

示例:

#include<stdio.h>

int main()
{
	int a = 7;
	//整数原码反码补码相同
	//00000000000000000000000000000110  

	//左移  也是操作补码  左边丢弃  右边补0

	int b = a << 1;
	//00000000000000000000000000001100  //12

	printf("%d\n", a);
	printf("%d\n", b);

	//正整数负整数左移1位都有乘二效果

	//C语言只能是移正数位

	//a = a << 1;  等价于  a <<= 1;
	//a = a + 1;   等价于  a += 1;

	return 0;
}

注意:对于移位运算符,不要移动符号位,这时C语言标准未定义的。例如:

int num = 10;

num>>-1;   // error

1.3 位操作符

&     //按位与

|      //按位或

^     //按位异或

注:他们的操作数必须是整数,操作的是二进制位,补码

 示例:

#include<stdio.h>

int main()
{
	int a = 3;
	//00000000000000000000000000000011
	int b = -5;
	//10000000000000000000000000000101  原码
	//11111111111111111111111111111010  反码
	//11111111111111111111111111111011  补码

	int c = a & b;
	//& -- 按(二进制)位与  -- 对应二进制位有0则有0,两个同时为1 才为1
	//00000000000000000000000000000011    3
	//11111111111111111111111111111011  -5 补码
	//&  按位与
	//00000000000000000000000000000011  3  

	printf("%d\n", c);  //3

	c = a | b;
	//  | -- 按(2进制)位或 -- 对应的二进制位有1则为1,两个同时为0才是0 
	//00000000000000000000000000000011    3
	//11111111111111111111111111111011  -5 补码
	//  |  按位或
	//11111111111111111111111111111011  这是补码
	//11111111111111111111111111111010   反码 (补码-1)
	//10000000000000000000000000000101   原码  (反码除符号位按位取反)

	printf("%d\n", c);//-5

	c = a ^ b;
	//  ^  按(二进制位)位异或  -- 对应的二进制位相同为0,相异为1
	//00000000000000000000000000000011    3
	//11111111111111111111111111111011  -5 补码
	//  ^  按位异或
	//11111111111111111111111111111000  这是补码
	//11111111111111111111111111110111   反码 (补码-1)
	//10000000000000000000000000001000   原码 (反码除符号位按位取反)

	printf("%d\n", c);//-8

	return 0;
}

 异或的应用:一道面试题,不创建临时变量,交换两个变量的值

//不创建临时变量 交换两个整型变量
//方法一
#include<stdio.h>

int main()
{
	int a = 3;
	int b = 5;
	printf("交换前:a = %d b = %d\n", a, b);

	a = a + b;
	b = a - b;
	a = a - b;

	printf("交换后:a = %d b = %d\n", a, b);
	return 0;
}
//假设a特别大 b 特别大    a+b 可能超过int 大小 会发生截断,下面有更好的方法


//方法二
#include<stdio.h>

int main()
{
	int a = 3;
	int b = 5;
	printf("交换前:a = %d b = %d\n", a, b);

	a = a ^ b;
	b = a ^ b;  //a^b^b == a
	a = a ^ b;  //a^b^a == b

	printf("交换后:a = %d b = %d\n", a, b);
	return 0;
}


#include<stdio.h>

int main()
{
	int a = 3;
	int b = 5;

	printf("%d\n", a ^ b ^ a);
	printf("%d\n", a ^ a ^ b);

	//a^a -> 0;
	//a^0 -> a;
	//a^b^a -> b
	//a^a^b -> b
	//异或是支持交换率的

	return 0;
}

1.4 赋值操作符

=

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

#include<stdio.h>

int main()
{
	int a = 10;
	int x = 0;
	int y = 20;
	
	//连续赋值
	a = x = y + 1;
	//将y+1的值赋给x,再赋给a

	x = y + 1;
	a = x;
	//这种写法更加清晰

	return 0;
}

复合赋值

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

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

int x = 10;

x = x + 10;

x += 10;//与上面一行相同 ,复合赋值

//其他运算符是一样的道理

1.5 单目操作符

!                逻辑反操作

-                   负值

+                  正值

&                 取地址

sizeof          操作数的类型长度(以字节为单位)

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

--                 前置、后置--

++                前置、后置++

*                   间接访问操作符(解引用操作符)

(类型)          强制类型转换

!逻辑反操作

#include<stdio.h>

int main()
{
	int flag = 0;
	if (flag == 0)
	{
		printf("hehe\n");
	}
	if (!flag)//flag 为假进行
	{
		printf("hehe\n");
	}
	if (flag)//flag 为真进行
	{
		printf("haha\n");
	}
	return 0;
}

- 负值 + 正值

#include<stdio.h>

int main()
{
	int a = 5;//+ 可以省略不写
	int b = -a; //-5

	return 0;
}

&  *  应用于指针

#include<stdio.h>

int main()
{
	int a = 10;

	//这里 * 是为了说明pa是指针变量  不是操作符
	int* pa = &a;//&-取地址操作符   取出a的地址

	// *  解引用操作符 (间接访问操作符)  单目操作符 
	*pa = 20;// 通过pa中存放的地址,找到指向的空间(内容)
	int c = *pa;

	printf("%d\n", c);//20;
	return 0;
}

sizeof 操作符  不是函数

sizeof计算的是类型创建变量的大小  单位是字节

#include<stdio.h>

int main()
{
	int a = 10;
	printf("%d\n", sizeof(int));//4
	printf("%d\n", sizeof(a));//4
	printf("%d\n", sizeof a);
	//sizeof(变量)  变量的括号可以省略 可以说明sizeof不是函数
    //因为函数调用时()不可以省略
	return 0;
}

sizeof 计算数组大小

#include<stdio.h>

int main()
{
	int arr[10] = { 0 };
	printf("%d\n", sizeof(arr));//40计算整个数组的大小 单位字节
	printf("%d\n", sizeof(int[10]));//去掉名字 就是数组类型  还是40
	return 0;
}

~ 按(二进制)位取反

#include<stdio.h>

int main()
{
	int a = 0;
	printf("%d\n", ~a);//-1
	//00000000000000000000000000000000  
	// ~ 按位取反   补码按位取反
	//11111111111111111111111111111111  补码
	//11111111111111111111111111111110  反码  补码-1
	//10000000000000000000000000000001  原码  -1

	return 0;
}

~ 在多组输入时的应用

#include<stdio.h>

int main()
{
	int a = 0;
    //1.
	//scanf读取失败返回 EOF
	while (scanf("%d", &a) == 1)
	{
		printf("%d\n", a);
	}
    //2.
	while (scanf("%d", &a) != EOF)
	{
		printf("%d\n", a);
	}

    //3.
	//假设读取失败  返回的是EOF ----> -1
	//10000000000000000000000000000001  -1原码
	//11111111111111111111111111111110  -1反码
	//11111111111111111111111111111111  -1补码
	//~-1
	//00000000000000000000000000000000  0 为假

	while (~scanf("%d", &a))
	{
		printf("%d\n", a);
	}
	return 0;
}

-- 前置   后置--

++前置  后置++

#include<stdio.h>

int main()
{
	int a = 1;
	int b = a++;//后置++, 先使用,后++
	printf("a = %d b = %d\n", a, b);//2  1
	b = a--;
	printf("a = %d b = %d\n", a, b);//1  2

	return 0;
}

#include<stdio.h>

int main()
{
	int a = 1;
	int b = ++a;//后置++, 先使用,后++
	printf("a = %d b = %d\n", a, b);//2  2
	b = --a;
	printf("a = %d b = %d\n", a, b);//1  1

	return 0;
}


#include<stdio.h>

int main()
{
	int a = 10;
	printf("%d\n", a++);//10
	printf("%d\n", a);//11
	return 0;
}

()  强制类型转换

#include<stdio.h>

int main()
{
	//3.14是浮点型
	int a = (int)3.14;//强制类型转换  只保留 整数部分
	printf("%d\n", a);//3
	return 0;
}

sizeof和数组

#include<stdio.h>

void test1(int arr[])
{
	printf("%d\n", sizeof(arr));//int * 指针的大小 8/4
}

void test2(char ch[])
{
	printf("%d\n", sizeof(ch));//char * 指针的大小  8/4   不要以为是1
}

int main()
{
	int arr[10] = { 0 };
	char ch[10] = { 0 };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(ch));
	test1(arr);
	test2(ch);
	return 0;
}

1.6 关系操作符

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

关系运算符比较简单,但要注意的是它们只能用到合适的类型上

比如,不能用于字符串比较,因为字符串比较有特定的函数。整形可以跟整形比较。

1.7 逻辑操作符

&& 逻辑与   并且   两个条件都要满足才为真
|| 逻辑或   或者   一个条件满足就为真

360笔试题

#include<stdio.h>

int main()
{
	int i = 0, a = 0, b = 2, c = 3, d = 4;
	i = (a++ && ++b && d++);  //当计算完a为假 不在向后计算   i是0
	printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);//1 2 3 4
	return 0;
}

注意:

||  逻辑与操作符的左边为真,右边不再计算  为真

&& 逻辑或操作符的左边为假,右边不再计算  为假

逻辑操作符如果计算结果是真,用1表示,假用0表示

1.8 条件操作符

条件操作符又称三目操作符,有三个操作数

exp1 ?exp2  :  exp3

当exp1为真时,计算exp2,否则计算exp3

 示例:

#include<stdio.h>

int main()
{
	int a = 0;
	int b = 0;
	int max = 0;
	scanf("%d %d", &a, &b);
	if (a > b)
		max = a;
	else
		max = b;
	printf("%d\n", max);
	//与下面等价
	max = (a > b ? a : b);
	printf("%d\n", max);

	return 0;
}

1.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);//13
	return 0;
}

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

[ ] 下标引用操作符

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

#include<stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5 };
	//下标          0 1 2 3 4  
	//数组是有起始下标的,下标是0开始的
	printf("%d\n", arr[2]);//[]  下标引用操作符,arr和2是[]的两个操作数
	return 0;
}

() 函数调用操作符

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

#include<stdio.h>
#include<string.h>

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int len = strlen("abc");//()  函数调用操作符
	// ()  的操作数是 :strlen 和"abc"   函数名和参数
	printf("%d\n", len); 

	int c = Add(3, 5);
	//Add 3 5 函数调用操作符
	printf("%d", c);

	return 0;
}

printf() 函数的参数是可变参数列表,参数的个数是可以变化的。

cplusplus网站上对于printf()函数的介绍:

访问结构体成员操作符

结构体用来定义复杂数据类型,是自定义类型

而 char short int long float double 等都是内置类型

. 结构体.成员名

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

示例:

struct Book//书结构体
{
	char name[30];//书名
	char author[20];//作者
	float price;//价格
};

#include<stdio.h>

void Print(struct Book* p)
{
	// .  结构体.成员名 
	printf("%s %s %.2f\n", (*p).name, (*p).author, (*p).price);

	//->   结构体指针->成员名
	printf("%s %s %.2f\n", p->name, p->author, p->price);
}

int main()
{
	struct Book b1 = { "C语言详解","西兰花",66.66f };
	struct Book b2 = { "数据结构","西兰花",88.88f };
	printf("%s %s %.2f\n", b1.name, b1.author, b1.price);
	printf("%s %s %.2f\n", b2.name, b2.author, b2.price);

	Print(&b1);
	Print(&b2);

	return 0;
}

2.表达式求值

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

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

2.1 隐式类型转换

2.1.1 整型提升

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 c1 = 5;
	//00000000000000000000000000000101  // 5整形 存到char中  发生截断
	//00000101
	char c2 = 127;
	//00000000000000000000000001111111  //截断
	//01111111
	char c3 = c1 + c2;
	//00000000000000000000000000000101  //c1   补码
	//00000000000000000000000001111111  //c2   补码
	//00000000000000000000000010000100   补码  c3  截断
	//10000100    阶段后的c3
	// 以%d打印整型提升  补码高位补符号位
	//11111111111111111111111110000100   补码
	//11111111111111111111111110000011   反码
	//10000000000000000000000001111100   原码    
	printf("%d\n", c3);//-124
	return 0;
}

证明存在整型提升

#include<stdio.h>

int main()
{
	char a = 0xb6;//10110110 一个16进制数是4个比特位  b6刚好8个bit位,一个字节可以放下
	//截断后符号位是1  整型提升后是负数
	short b = 0xb600;

	int c = 0xb6000000;
	if (a == 0xb6)//整型提升后是负数
	{
		printf("a\n");
	}	
	if (b == 0xb600)//整型提升后是负数
	{
		printf("b\n");
	}	
	if (c == 0xb6000000)
	{
		printf("c\n");//不用整型提升,可以打印
	}

		return 0;
}

 只要参与表达式运算就会整型提升:

#include<stdio.h>
//%d  十进制的形式打印有符号的数
//%u  十进制的形式打印无符号的数
//%u  输出无符号整形数  因为sizeof是返回的无符号数
int main()
{
	char c = 1;
	printf("%u\n", sizeof(c));//1
	printf("%u\n", sizeof(+c));//发生运算后整型提升    4
	printf("%u\n", sizeof(-c));//发生运算后整型提升    4
	return 0;
}

 2.1.2 算术转换

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

long double

double

float

unsigned long int

long int

unsigned int 

int

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

2.2 操作符的属性

复杂表达式的求值有三个影响的因素。
1.操作符的优先级 
2.操作符的结合性

3.是否控制求值顺序   , &&  ||  (?:) 四个操作符控制
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性 

优先级
( )  括号
( ) 函数调用
[ ] 下标引用
访问结构体操作符
单目操作符
算术操作符
移位操作符
关系操作符
位操作符
逻辑操作符
三目操作符符
赋值操作符
逗号

 总结∶我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个

表达式就是存在问题的。

猜你喜欢

转载自blog.csdn.net/qq_72916130/article/details/130801663
今日推荐