C语言 操作符详解

Posted 跳动的bit

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言 操作符详解相关的知识,希望对你有一定的参考价值。

前言

在之前学习的过程中,有大致了解过一些操作符,但还有一些操作符没见过,所以这篇文章将会把操作符都过一遍。本文章所有代码依赖的平台:(Visual Studio2017 && Windows)

一、算术操作符

 +   -  *  /  %

算术操作符这里加减乘除就不过多的了解了
这里需要注意的有2点:
a. / 和 %的区别

#include<stdio.h>
int main()
{
	int a1 = 3 / 5;//取商
	int a2 = 3 % 5;//取余
	printf("a1 = %d\\na2 = %d\\n", a1, a2);//a1 = 0; a2 = 3;
	return 0;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
int main02()
{
	int a = 5 / 3.0;//ok
	//int a = 5 % 3.0;//err
	return 0;
}

b. 小数除法

#include<stdio.h>
int main()
{
	//观察a1、a2、a3的结果
	float a1 = 3.0 / 5;
	float a2 = 3 / 5.0;
	float a3 = 3.0 / 5.0;
	printf("a1 = %.1f\\na2 = %.1f\\na3 = %.1f\\n", a1, a2, a3);//a1,a2,a3 = 0.6
	return 0;
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//当执行上面上面这段程序时 - warning:从double到float截断 
//因为直接写6.0(没有定义的情况)这种小数时,编译器会默认为double类型
//更适合的写法:
//float a1 = 3.0f / 5f;
//double a2 = 3.0 / 5;

小结:

  • / 和 % 都是执行除法运算,但是 / 算的是商,%算的是余数
  • 除了 % 操作符之外 ,其它的操作符可以用于整数和浮点数;% 操作符的2个操作数必须为整数
  • / 操作符如果2个操作数都为整数,执行整数除法;而只要有任何1个操作数为float类型或doble类型,执行的是小数除法

二、移位操作符

<<  >>

<<:左移操作符

#include<stdio.h>
int main()
{
	int a = 2;
	//把a的二进制位向左移动1位
	int b = a << 1;
	printf("b = %d\\n", b);//4
	//注意在对a进行移位后,并不会改变a本身
	printf("a = %d\\n", a);//2
	return 0;
}

在这里插入图片描述


`>>:右移操作符:
1.算术右移:右边丢弃,左边补原符号位
2.逻辑右移:右边丢弃,左边补0
/***********************************************************************
目的:验证当前机器是算术右移还是逻辑右移
分析:在右移操作符下,正数的算术右移和逻辑右移是一样的。所以这里采用负数来验证。(对于移位操作时,移动的是二进制补码:而正数的原反补相同,移动原码 = 移动补码;而负数需要转换为补码再移动)
平台:Visual studio 2017 && windows
*************************************************************************/
整数的二进制表示形式:
原码:直接根据数值写出的二进制序列就是原码(相当于屏幕上能看到的数)
反码:原码的符号位不变,其它位按位取反
补码:反码+1 (内存中的存储形式)

#include<stdio.h>
int main()
{
	int a = -1;
	//把a的二进制位向右移动1位
	//由些可见,当前编译器采用的是算术右移
	int b = a >> 1;
	printf("b = %d\\n", b);//-1
	return 0;
}

在这里插入图片描述


注意:
int a = 10;
int b = a << -5; // err,这是标准未定义的语法。属于垃圾代码

小结:

  • 移位操作符移动的是二进制位
  • 左移操作符遵循左边丢弃,右边补0的原则
  • 右移操作符则有2个情况:1.算术右移(右边丢弃,左边补原符号位)  2.逻辑右移(右边丢弃,左边补0)
    经验证当前机器使用算术右移

三、位操作符

&   |  ^

#include<stdio.h>
//&:按(二进制位)位与 -> 对应的二进制位有1个或2个为0,则为0;否则为1
int main()
{
	int a = 3;
	int b = 5;
	//...011 -> 3
	//...101 -> 5
	//...001 -> 1
	int c = a & b;
	printf("c = %d\\n", c);//1
	return 0;
}
//|:按(二进制位)位或-> 对应的二进制位有1个或2个为1,则为1;否则为0
int main02()
{
	int a = 3;
	int b = 5;
	//...011 -> 3
	//...101 -> 5
	//...111 -> 7
	int c = a | b;
	printf("c = %d\\n", c);//7
	return 0;
}
//^:按(二进制)位异或 -> 对应的二进制位,相同为0,相异为1
int main03()
{
	int a = 3;
	int b = 5;
	//...011 -> 3
	//...101 -> 5
	//...110 -> 6
	int c = a ^ b;
	printf("c = %d\\n", c);//6
	return 0;
}

小结:

  • &:按(二进制位)位与:对应的二进制位,同真为真,其余为假
  • | :按(二进制位)位或:对应的二进制位,同假为假,其余为真
  • ^:按(二进制)位异或 :对应的二进制位,相同为0,相异为1
  • 它们的操作数必须为整数

四、赋值操作符

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

1.连续赋值

int main()
{
	int a = 10;
	int x = 0;
	int y = 20;
	//a = x = y + 1;//连续赋值语法支持,但不建议这样写
	
	//这样写可读性更高
	x = y + 1;
	a = x;
	return 0;
}

2.复合赋值,这里就介绍1个,其它的复合赋值都是相同的用法

int main()
{
	int a = 10;
	a = 100;
	//a = a + 100;
	a += 100;//复合赋值 -> 同a = a + 100
	return 0;
}

3.注意:=是赋值操作符
    ==等于

五、单目操作符

!  -  +  &  sizeof  ~  –  ++  *  (类型)
注:单目操作符只有一个操作数

#include<stdio.h>
//!:非,即真为假,假为真
int main()
{
	int flag = 5;
	if(flag)
		printf("hehe\\n");
	if(!flag)
		printf("haha\\n");
	return 0;
}
//-----------------------------------------------------------------------------
//-:负
int main02()
{
	int a = 10;
	a = -a;
	printf("%d\\n", a);//-10
	return 0;
}
//-----------------------------------------------------------------------
//sizeof:计算变量所占内存的大小(单位:字节)
int main03()
{
	int a = 10;
	//sizeof的三种形式:
	printf("%d\\n", sizeof(a));
	printf("%d\\n", sizeof(int));
	printf("%d\\n", sizeof a);//有的人会认为sizeof是一个函数:函数名()。这种写法说明了sizeof是一个操作符
	//printf("%d\\n", sizeof int);//err
	return 0;
}
//sizeof计算数组大小
int main04()
{
	int arr[10] = {0};
	printf("%d\\n", sizeof(arr));
	printf("%d\\n", sizeof arr);
	printf("%d\\n", sizeof(int [10]));//sizeof(数据类型);对于数组的类型把数组名去掉,剩下的就是数据类型
	return 0;
}
//观察下面程序,分析结果?
int main05()
{
	short s = 5;
	int a = 10;
	printf("%d\\n", sizeof(s = a + 2));//2
	printf("%d\\n", s);//5
	return 0;
	//sizeof括号中放的表达式是不参与运算的
}
//-----------------------------------------------------------------------
//~:按(二进制位)位取反
int main06()
{
	int a = -1;
	//10000000 00000000 00000000 00000001 - 原
	//11111111 11111111 11111111 11111110 - 反
	//11111111 11111111 11111111 11111111 - 补
	//按位取反(包括符号位):
	//00000000 00000000 00000000 00000000
	int b = ~a;
	printf("%d\\n", b);//0
	printf("%d\\n", a);//-1 - 并不会改变a
	return 0;
}
//++:自增
int main07()
{
	int a1 = 10;
	int b1 = a1++;//后置++,先使用,再++
	printf("a1 = %d\\n", a1);//11
	printf("b1 = %d\\n", b1);//10
	//++++++++++++++++++++++++++++++++++++++++++++
	int a2 = 10;
	int b2 = ++a2;//前置++,先++,后使用
	printf("a2 = %d\\n", a2);//11
	printf("b2 = %d\\n", b2);//11
	return 0;
	//++++++++++++++++++++++++++++++++++++++++++++++
	//--也是如此
	int a3 = 10;
	printf("%d\\n", a3--);//10
	printf("%d\\n", a3);//9
}
//+++++++++++++++++++++++++++++++++++++++++++++++++++
//对于自增或自减,可能会见到很多一些没有意义的代码,甚至是错误的:
int main08()
{
	int a = 1;
	int b = (++a) + (++a) + (++a);
	printf("%d\\n", b);
	return 0;
	//结果(已验)
	//在VS2017下结果是12
	//在Linux_Ubuntu下结果是10
}
//--------------------------------------------------------
//&:取地址
//*:指针
int main09()
{
	int a = 10;
	printf("%p\\n", &a);//取a的地址以十六进制打印
	int* pa = &a;//将a的地址存放于pa,pa就是一个指针变量,pa的类型就是int*
	*pa = 20;//这里的* - 解引用操作符或间接访问操作符;这里就是通过pa存的地址找到a,并把a赋值20
	printf("%d\\n", 20);//20;
	return 0;
	//这里注意区别&:按位与;&:取地址。当&有2个操作数时,它就是按位与;当&仅有1个操作数时,它就代表取地址
}
//--------------------------------------------------------
//(类型):强制类型转换
int main10()
{
	int a1 = 3.14;//warning:从double类型转换到int可能会丢失数据
	int a2 = (int)3.14;//将double类型的数据强转为int 
	return 0;
}

六、关系操作符

 >   >=   <   <=   !=   ==

对于关系操作符这里没啥可讲的,相对简单。值的注意的是:

1.==是判断相等;而=是赋值
2.==不能比较2个字符串相等

七、逻辑操作符

&&   ||

#include<stdio.h>
int main()
{
	int a = 3;
	int b = 0;
	if(a && b)//同真为真,其余为假。这里只要有1个为假,则为假
	{
		printf("hehe\\n");
	}
	if(a || b)//同假为假,其余为真。这里只要有1个为真,则为真
	{
		printf("haha\\n");
	}
	
	return 0;
}

八、条件操作符

exp1 ? exp2 :exp3
注:也被称为三目操作符

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

	//上面的if...else语句使用三目操作符只要一句代码:
	//三目操作符:表达式1的结果为真,则执行表达式2,否则执行表达式3
	b = (a > 5 ? 1 : -1);
	return 0;
}

九、逗号表达式

exp1, exp2, exp3, … expN

#include<stdio.h>
int main()
{
	int a = 3;
	int b = 5;
	int c = 0;
	//逗号表达式:从左向右依次计算,但是整个表达式的结果是最后一个表达式的结果,而最后一个表达式的结果可能会受到前面表达式的影响
	int d = (c = 5, a = c + 3, b = a - 4, c += 5);
	printf("%d\\n", d);//10
	return 0;
}

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

[ ]   ()   .   ->

#include<stdio.h>
//[]:下标引用操作符,有两个参数:第一个是数组名,第二个是元素大小或索引值
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};//这里的[]里面是元素个数
	printf("%d\\n", arr[4]);//数组名[索引值] -> 可以访问数组元素
	return 0;
}
//--------------------------------------------------------
//():函数调用操作符,接受一个或多个操作数:第一个操作数是函数名,剩余的操作数是传递给函数的参数
//定义函数
int Add(int x, int y)
{
	return x + y;
}
int main01()
{
	int a = 10;
	int b = 20;
	//调用函数
	int ret = Add(a, b);//这里的()就是函数调用操作符
	printf("%d\\n", ret);
	return 0;
}
//--------------------------------------------------------
//.:
//->
//这里先介绍结构体:struct:
//定义结构体类型
struct Book
{
	char name[20];
	char id[20];
	int price;
};
int main02()
{
	//对于生活中的一些事物,我们要怎样描述它的信息:(这些信息又是不同的类型的,有字符串,整型等等。这时C语言提供了struct结构体关键字,它可以囊括不同的数据类型)
	//书:书名、书号、作者、定价、书号
	//人:名字、年龄、性别

	//初始化结构体:通过之前的了解我们知道初始化变量:int a = 10 -> 类型 + 变量 = 值 -> 所以这里结构体的类型就是struct Book
	struct Book b = {"C语言", "cc202005011", "55"};
	//打印结构体成员
	//1.使用.来访问结构体成员
	printf("书名:%s\\n", b.name);//通过(变量+.+成员名)可以访问结构体成员
	printf("书号:%s\\n", b.id);
	printf("定价:%d\\n", b.price);
	printf("-------------分割线-------------\\n");

	//2.使用*和.来访问结构体成员
	struct Book* pb = &b;//存储结构体的地址
	printf("书名:%s\\n", (*pb).name);
	printf("书号:%s\\n", (*pb).id);
	printf("定价:%d\\n", (*pb).price);
	printf("-------------分割线-------------\\n");

	//3.使用->来访问结构体成员
	printf("书名:%s\\n", pb -> name);//通过(指针变量 -> 成员名)可以访问结构体成员
	printf("书号:%s\\n", pb -> id);
	printf("定价:%d\\n", pb -> price);
	return 0;
}

十一、操作符的优先级

在程序设计中也是有操作符的优先级的,同加减乘除,优先级高的先算。
影响表达式求值的有三个因素:
1.操作符的优先级
2.操作符的结合性(优先级相同的情况下,结合性决定了运算顺序)
3.是否控制求值顺序(比如&&,如果左边为假,则整体为假)
以下整理了C语言中操作符的优先级:
注:N\\A是无结合性
  L-R是从左向右
  R-L是从右向左

在这里插入图片描述

1、一些有问题的表达式

此类表达式没有办法确定唯一的计算路径,未来应该避免这类的表达式

a * b + c * d + e * f;
这个表达式执行的顺序可能就有2种:
在这里插入图片描述


c + --c;
比如:c = 3时:
在这里插入图片描述

同上,操作符的优先级只能决定–的运算在+的运算的前面,但是我们不知道,+操作符的左操作数的获取是在右操作数的之前还是之后的值,所以是有歧义的。


此代码来自《C和指针》

int main()
{
  int i = 10;
  i = i-- - --i * (i = -3) * i++ + ++i;
  printf(“i = %d\\n”, i);
  return 0;
}
经作者的验证:相同的代码在不同的编译器下跑出的结果大不相同
在这里插入图片描述


int fun ()
{
  static int count = 1;//注意count通过static修饰后,下一次进来时并不会销毁
  return ++count;
}
int main()
{
  int answer;
  answer = fun() - fun() * fun();
  printf("%d\\n", answer);
  return 0;
}
在VS2017下的结果为2 - 3 * 4 = -10
在这里插入图片描述
3、… …
对于fun()函数的调用有可能会有不同的先后调用顺序


int main()
{
  int i = 1;
  int ret = (++i) + (++i) + (++i);
  printf("%d\\n", ret);
}
在VS2017中是12 -> 4 + 4 + 4:
在这里插入图片描述

在Linux下是10 -> 3 + 3 + 4:
在这里插入图片描述


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

十二、趁热打铁

1、交换变量,不能使用第3个变量(出自品茗股份c++笔试题)

/***********************************************************************
目的:不能创建临时变量(第3个变量),实现2个数的交换
分析:1.借助2数之和与变量的运算:
   2.使用异或:
在这里插入图片描述

平台:Visual studio 2017 && windows
*************************************************************************/
实现代码1:

#include<stdio.h>
int main()
{
	int a = 3;
	int b = 5;
	printf(a = %d b = %d\\n", a, b);//3 5
	a = a + b20160206.CCPP体系详解(0016天)

C语言 操作符详解

C语言代码片段

C语言基础学习笔记五操作符详解(详细讲解+练习巩固+记忆总结)

20160206.CCPP体系详解(0016天)

顺序表详解及其c语言代码实现