C语言之操作符详解

操作符的分类:

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

1.算数操作符

不做过多介绍,无非就是加减乘除,外加一个求余(+ - * / %),不过这里要提几个注意事项:

1.除了求余操作符(%)之外,其他的几个操作符都阔以作用于整数和浮点数。

2.对于操作符“/”如果两个操作数都是整数的话,就执行整数除法。但是捏,但凡其中有一个操作数是浮点数,那就执行浮点数除法。(举个栗子)

eg1:

#include<stdio.h>
int main()
{
	float num = 0.0;
	num = 10 / 3;
	printf("num = %f", num);
	return 0;
}

eg2:

#include<stdio.h>
int main()
{
	float num = 0.0;
	num = 10.0 / 3;
	printf("num = %f", num);
	return 0;
}

 

3.“%”操作符的两个操作数必须是整数啊喂(返回的是整除后的余数)

2.移位操作符(<<、>>)

小提一嘴,移位操作符的操作数只能说整数昂~

所以在这里不得不提一句整数的二进制表示形式。

整数的二进制表示形式有三种:

原码

反码

补码

整数在计算机中存储的方式为二进制的补码,其中正整数的原码,反码,补码都是一样的,其中负整数的反码就是原码除符号位不变,其他位都改变。其中补码有负整数的反码加1所得到。下面举一个例子

-3的二进制原码表示为:

1000 0000 0000 0000 0000 0000 0000 0011

-3的二进制反码表示为:

1111 1111 1111 1111 1111 1111 1111 1100

-3的二进制补码表示为:

1111 1111 1111 1111 1111 1111 1111 1101

把话题扯回来,这里的移位操作符操作的就是整数的补码

2.1左移操作符<<

关于左操作数移位的规则。左边抛弃,右边补0

eg:

2.2右移操作符>>

右移操作符的移位规则有两种运算方式:

1.逻辑移位:左边雨0填充,右边丢弃。

2.算术移位:左边用原符号位填充,右边丢弃。

eg:

 由于标准未定义移动负数位,所以对于移位运算符来说,不要移动负数位

eg:

int num = 10;

num >> -1//error

3.位操作符

位操作符有以下三种:

1.“&”按位与

2.“|”按位或

3.“^”按位异或(这个腻害了,居然支持交换律)

同样他们的操作数必须是整数昂~

 下面做个“小小的练习”

题:在不创建临时变量的情况下,实现两个整数的交换。

大家阔以看到,其实交换整数并不难,难的是“不创建临时变量”,这怎么做呢?这里位操作符“^”就可以帮上忙了。

答案如下:

int main()
{
	int a = 10;
	int b = 20;
	printf("交换前\n");
	printf("a = %d,b = %d\n", a, b);
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	printf("交换后\n");
	printf("a = %d,b = %d\n", a, b);
	return 0;
}

运行结果

 做个正式的练习:

求一个整数存储内存中的二进制中1的个数。

.................................................................................................................................................................................................................................

先做个错误示范:

#include <stdio.h>
int main()
{
	int num = 10;
	int count = 0;//计数
	while (num)
	{
		if (num % 2 == 1)
			count++;
		num = num / 2;
	}
	printf("二进制中1的个数 = %d\n", count);
	return 0;
}

以10为例:运行结果:

 没毛病对吧,但要是以-10为例呢?

我们先看看运行结果:

这就明显有问题呀,-10在存储二进制的补码为:

11111111 11111111 11111111 11110110 

明显不是0呀,为什么呢?负整数求余结果是负的呀

正确做法

#include <stdio.h>
int main()
{
	int num = -1;
	int i = 0;
	int count = 0;//计数
	for (i = 0; i < 32; i++)
	{
		if (num & (1 << i))
			count++;
	}
	printf("二进制中1的个数 = %d\n", count);
	return 0;
}

运行结果

 这种方法都是我们可以想得到的,但是,有没有更好的方法呢?答案是有的。

不过在此之前要先跟大家分享一个小芝士:

某个数每次和比自己小1的数做一次&运算,其对应的二进制数会少一个1,直到最后变成0。

这是为什么捏?咱阔以先验证一下。

以int num = 10;为例

 此时就不难发现,每次num&(num-1)之后最后面的1被0代替掉了,所以每次之后二进制中1的数量都减少一个。好了,问题又扯回来,咱来看看最优解:

#include <stdio.h>
int main()
{
	int num = -1;
	int i = 0;
	int count = 0;//计数
	while (num)
	{
		count++;
		num = num & (num - 1);
	}
	printf("二进制中1的个数 = %d\n", count);
	return 0;
}

当二进制中每少一个1,count就++一下用来记录一共消掉多少个1,直到num为0时count的大小便是该整数存储二进制中1的个数咯~

4.赋值操作符

赋值操作符就不讲了,直接上段代码自己感受一下吧:

#include <stdio.h>
int main()
{
	int weight = 120;//体重
	weight = 89;//不满意就赋值
	double salary = 10000.0;
	salary = 20000.0;//使用赋值操作符赋值。
	return 0;
}

这里稍微提一嘴,大家要养成好的代码风格,尽量让自己的代码通俗易懂,不要折磨别人,也折磨自己,比如看下面这段代码:

int a = 10;
int x = 0;
int y = 20;
a = x = y+1;//连续赋值

同样的语义再看看这段:

x = y+1;
a = x;
这样的写法更加清晰爽朗而且易于调试,你值得拥有~

赋值运算符

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

这些运算符都可写成复合的效果,举个栗子:

int x = 10;
x = x+10;
x += 10;//复合赋值
其他运算符一样的道理。这样写更加简洁。

5.单目操作符

5.1单目操作符介绍

#include <stdio.h>
int main()
{
	int a = -10;
	int* p = NULL;
	printf("%d\n", !2);
	printf("%d\n", !0);
	a = -a;
	p = &a;
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(int));
	//printf("%d\n", sizeof a);//这样写行不行?
	printf("%d\n", sizeof int);//这样写行不行?
	return 0;
}

我们看看这种情况的编译结果:

 由此得知:

1.这种方法是错误的,不能这么写。

2.关于 sizeof 可以求变量(类型)所占空间的大小

5.2 sizeof与数组

我们先来看一个小问题,请看下列代码:

#include <stdio.h>
void test1(int arr[])
{
 printf("%d\n", sizeof(arr));//(2)
}
void test2(char ch[])
{
 printf("%d\n", sizeof(ch));//(4)
}
int main()
{
 int arr[10] = {0};
 char ch[10] = {0};
 printf("%d\n", sizeof(arr));//(1)
 printf("%d\n", sizeof(ch));//(3)
 test1(arr);
 test2(ch);
 return 0;
}

好的,让我们接下来思考两个问题:

1.(1)、(2)两个地方分别输出什么?

2.(3)、(4)两个地方分别输出什么?

好的,接下来答案揭晓:

 这是为什么呢?

sizeof(数组名)时,数组名表示整个数组。

数组名传参时,数组名表示首元素地址。

这里提一嘴前置++前置--与后置++后置--:

前置++(--)先将自身加一(减一)再参与运算;

后置++(--)先参与运算再自身加一(减一)。

6.关系操作符

关系操作符

 这些关系运算符比较简单,没什么可讲的,但是我们要注意一些运算符使用时候的陷阱。

警告:
在编程的过程中 == = 不小心写错,导致的错误

7.逻辑操作符

 逻辑操作符有哪些:

接下来思考一下程序输出来的结果

#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\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0;
}
#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;
}

好的,接下来我们看一看结果:

 

 由此我们可以得知逻辑与和逻辑或的特点:

1.当编译器遇到逻辑与(&&)时,若&&左边为假,则右边不再运算

2.当编译器遇到逻辑或(||)时,若||左边为真,则右边也不再计算

9.条件操作符

条件操作符表达式很简单,如下所示:

exp1:exp2?exp3

下面做个练习:

if (a > 5)
	b = 3;
else
	b = -3;

转换成条件表达式,是什么样?答案揭晓。

b = a > 5? 3 : -3

再做一个小练习

使用表达式实现找两个数中较大值。好的,下面直接给出答案。

max = a > b ? a : b;

10.逗号表达式

exp1, exp2, exp3, ...expN

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

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

int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式

c是多少?

让我们看看结果。

 显然,逗号表达式就是从左运算到右,最后的结果取最后一个表达式的值。

所以我们可以得出结论,逗号表达式从从左向右依次执行。整个表达式的结果是最后一个表达式的结果。

if (a =b + 1, c=a / 2, d > 0)

逗号表达式还可以简化代码,比如说:

while (a = get_val(), count_val(a), a>0)
{
         //业务处理
}

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

10.1下标引用就不多说了,数组里面经常要用到的

10.2()函数调用操作符

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

#include <stdio.h>
 void test1()
 {
 printf("hehe\n");
 }
 void test2(const char *str)
 {
 printf("%s\n", str);
 }
 int main()
 {
 test1();            //实用()作为函数调用操作符。
 test2("hello bit.");//实用()作为函数调用操作符。
 return 0;
 }

我们来看一下结果:

 10.3访问一个结构体的成员

.      结构体.成员名

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

举个栗子:

#include <stdio.h>
struct Stu
{
	char name[10];
	int age;
	char sex[5];
	double score;
};

void set_age1(struct Stu stu)
{
	stu.age = 18;
}
void set_age2(struct Stu* pStu)
{
	pStu->age = 18;//结构成员访问
}
int main()
{
	struct Stu stu;
	struct Stu* pStu = &stu;//结构成员访问

	stu.age = 20;//结构成员访问
	set_age1(stu);

	pStu->age = 20;//结构成员访问
	set_age2(pStu);
	return 0;
}

11.表达式求值

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

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

11.1隐式子类型转换

C的整形算术运算总是至少以缺省整型类型的精度来进行

为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

11.2算数类型转换

如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换
但是算术转换要合理,要不然会有一些潜在的问题
 

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

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

1.操作符的优先级..

2.操作符的结合性

3.是否控制求值顺序

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

 

 

 

 函数的调用先后顺序无法通过操作符的优先级确定。

表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值