C语言初阶深入探索C语言操作符的奥秘(上)!!

Posted  Do

tags:

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

目录

操作符和表达式

操作符

算术操作符

移位操作符

1.左移操作符

2.右移操作符

位操作符

赋值操作符

单目操作符

1.sizeof

2.--,++

3.&,*

4.(类型)       强制类型转换

5.sizeof和数组

关系操作符

逻辑操作符

条件操作符

逗号表达式

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

1.[ ] 下标引用操作符

2.( ) 函数调用操作符

3.访问一个结构的成员

总结


操作符和表达式

本章内容分为上下两篇,上篇主要讲解各种操作符的运用,下篇则主要讲涉及到操作符的表达式如何正确求值,以及一些题目的讲解。大家千万不要错过哦,每一篇内容都是博主辛苦总结的,真的是细节超多,干货满满!!

操作符

首先c语言操作符可分为以下几类:

算术操作符

移位操作符

位操作符

赋值操作符

单目操作符

关系操作符

逻辑操作符

条件操作符

逗号表达式

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


算术操作符

+    -   *   /   %

知识要点

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

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

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

 

代码及图片示例:

#include<stdio.h>
int main()
{
	int a = 12;
	int b = 10;
	double c = 10.0;

	printf("%d\\n", a + b);
	printf("%d\\n", a - b);
	printf("%d\\n", a * b);
	printf("%d\\n", a / b);
	printf("%lf\\n", a / c);//默认double类型
	printf("%d\\n", a % b);
	//printf("%d", a % c);

}


移位操作符

<<左移操作符

>>右移操作符

知识要点

1.不管是左移还右移,都不会改变原来的数值,且操作数必须是整型。

2.整数的二进制有三种表现形式:

原码:将一个整数转换成二进制形式,就是其原码。例如short a = 6;a的原码就是0000 0000 0000 0110;更改a的值a = -18,此时a的原码就是1000 0000 0001 0010。

反码:谈到反码,需要将正数和负数区别对待,因为它们的反码不一样。对于正数,它的反码就是其原码(原码和反码相同);负数的反码就是将原码中除符号位以外的所有位(数值位)取反,也就是0变成1,1变成0.。例如short a = 6,a的原码和反码都是0000 0000 0000 0110;更改a的值a = -18,此时a的反码是 1111 1111 1110 1101。

补码:正数和负数的补码也不一样,也要区别对待。对于正数,它的补码就是其原码(原码、反码、补码都相同);负数的补码是其反码加 1。例如short a = 6;,a 的原码、反码、补码都是0000 0000 0000 0110;更改 a 的值a = -18;,此时 a 的补码是1111 1111 1110 1110。可以认为,补码是在反码的基础上打了一个补丁,进行了一下修正,所以叫“补码”。原码、反码、补码的概念只对负数有实际意义,对于正数,它们都一样。

也就是说,在计算机内存中,整数一律采用补码的形式来存储。这意味着,当读取整数时还要采用逆向的转换,也就是将补码转换为原码。将补码转换为原码也很简单:先减去 1,再将数值位取反即可。

1.左移操作符

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

代码及图片示例

#include<stdio.h>
int main()
{
	int num = 10;
	printf("%d", num << 1);//打印20,20=10*2^1;
}

 


2.右移操作符

首先右移运算规则分两种:

1. 逻辑移位 左边用0填充,右边丢弃

2. 算术移位 左边用原该值的符号位填充,右边丢弃

对于正数,逻辑移位和算术移位结果都一样,对于负数则不同

代码及图片示例:

正数拿10举例:

#include<stdio.h>
int main()
{
	int num = 10;
	printf("%d\\n", num >> 1);//打印5,5=10/(2^1);
}

负数拿-1举例;

#include<stdio.h>
int main()
{
	int num = -1;
	printf("%d\\n", num >> 1);
}

对于-1,其补码为11111111111111111111111111111111,如果按照逻辑移位则应该为0111111111111111111111111111111,结果是一个很大的正数,但是打印出来的结果是-1(原码),说明右移移位按照原符号位填充,即按照算术移位来右移

注:对于移位运算符,不要移动负数位,这个是标准未定义的。

从上面我们可以总结出移位操作符的运算公式:

对于左移:int num=a;int num1=a<<n;num1=a*(2^n);

对于右移:int num=a;int num1=a>>n;num1=a/   (2^n)   ;


位操作符

& //按位与

| //按位或

^ //按位异或

知识要点

位操作符运算法则(对于二进制原码):

&(按位与):有0则0

|(按位或):有1则1

^(按位异或):相同为0,不同为1

注:操作数必须是整数

下面我们来看看为什么是这样运算的:

 

 

对于负数,我们则要先转化为其补码再进行位运算,最后将结果转为原码就是打印结果。


这就证明了上面给出的运算法则,掌握运算法则之后我们来看看一道题目:

不使用第三个变量交换两个数的值

假定这两个数为3和5,我们最容易想到的就是创建一个临时变量temp,使temp=a,a=b,b=temp就能实现a,b的交换,但是这创建了第三个变量,不符合题意。那么要想不创建第三个变量,就先考虑到用加减法,先将a+b赋值给a,再将a-b赋值给b,最后将a-b赋值给a,简单来说就是a=a+b,b=(a+b)-b=a,a=a-(a-b)=b,就实现了交换。

请看代码:

#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
	a = a - b;//b为上一次b
	printf("交换后:a=%d,b=%d\\n", a, b);
	return 0;

}

 

但是如果a和b的数过大时,并不能成功完成交换,结果会发生溢出,并将结果截断:

所以说这个方法可以完成但是有缺陷,然而我们就要想一种不管是什么数,怎么运算都不会溢出的方法,因此我们可以从数值的存储形式即二进制的形式计算,这样就固定了位数,不发生溢出,只需要将运算后的二进制原码转换成整数就行。我们可以采用按位异或的方法运算

#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=a^b^b
	a = a ^ b;//上一次的b,a=a^a^b
	printf("交换后:a=%d,b=%d\\n", a, b);
	return 0;

}

 

从以上我们可以得出一些结论:

b=a^b^b=a^0=a

a=a^a^b=0^b=b

即a^a=0;

    a^0=a;

 


赋值操作符

简单赋值操作符:=  ==

复合赋值操作符:+=  -=  *=  /=  %=  >>=  <<=  &=  |= 

1.简单操作符:=表示赋值,==判断相等

int weight = 120;//体重
weight = 89;//赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值。
赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y+1;//连续赋值
这样的代码感觉怎么样?
那同样的语义,你看看:
x = y+1;
a = x;
这样的写法是不是更加清晰爽朗而且易于调试。

2.复合操作符:


单目操作符

!           逻辑反操作

-           负值

+           正值

&           取地址

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

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

--          前置、后置--

++          前置、后置++

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

(类型)       强制类型转换

 

知识要点

1.sizeof

sizeof用于计算变量,数组所占空间,单位是字节。

计算变量a的空间大小:

由此我们可以知道,sizeof是操作符不是函数,如果是函数的话,那么()能省略。

计算数组arr的大小

这里的arr指的是整个数组,所以整个数组大小应该是4*10=40;

下面来看看关于sizeof易错的一道题:

#include<stdio.h>
int main()
{
	short s = 5;
	int a = 10;
	printf("%d\\n", sizeof(s = a + 2));
	printf("%d\\n", s);


}

这里大家很容易就会说打印4和5,但是打印的是2和5:

虽然a为int型,但是结果最终放到short中,而short型的字节大小是2个字节。

2.--,++

分为前置和后置,前置是先自增或自减,再使用。后置是先使用再自增或者自减。

代码举例:

(1)

#include<stdio.h>
int main()
{
	int a = 10;
	int b1 = a++;
	int b2 = ++a;
	printf("%d\\n", b1);//先使用a的值
	printf("%d\\n", b2);//a++后是11,11再++赋值b2中
	printf("%d\\n", a);//a为两次++的值
	return 0;

}

(2)

#include<stdio.h>
int main()
{
	int a = 10;
	printf("%d\\n", a--);//先使用a的值
	printf("%d\\n", a);//再打印a--后的值
	return 0;

}

 

 

3.&,*

&:取地址操作符,获取变量的地址。

*:解引用操作符,将变量的地址解引用出来得到变量的数值

代码举例:

#include<stdio.h>
int main()
{
	int a=10;
	printf("%p\\n", &a);//取出a的地址
	int* pa = &a;//指针存储数组
	*pa = 20;//解引用得到a,并将20赋值给a
	printf("%d\\n", a);
	return 0;


}

 

 

4.(类型)       强制类型转换

为了达到某些要求,我们可以强制转换类型

#include<stdio.h>
int main()
{
	double a =3.14;//a为double型
	printf("%d\\n",(int) a);//要打印整型a,则需要强制转换

}

 

5.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)、(2)两个地方分别输出多少?
(3)、(4)两个地方分别输出多少?

关系操作符

>

>=

<

<=

!=   用于测试“不相等”

==      用于测试“相等”

 

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

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

比如在比较字符串相等时,不能用==,要用strcmp函数


逻辑操作符

&&     逻辑与

||          逻辑或

区分逻辑与和按位与 区分逻辑或和按位或

1&2 ----->0
1&&2 ---->1
1|2 ----->3
1||2 ---->1

知识要点

1.&&,逻辑与,在计算表达式时,有一个假则为假,不用算后面的表达式

2.||,逻辑或,在计算表达式时,有一个真则为真,不用算后面的表达式
 

代码举例:

 

 

掌握了基本的逻辑法则之后,我们来看看一道经典笔试题:

#include <stdio.h>
int main()
{
    int i = 0,a=0,b=2,c =3,d=4;
    i = a++ && ++b && d++;
    printf("a = %d\\n b = %d\\n c = %d\\nd = %d\\n", a, b, c, d);
    return 0;
}
//程序输出的结果是什么?

这里最关键的是要明白后置++的原理,这样才能更好的判断表达式的真假。

我们可以改变一些条件来加强对逻辑操作符的理解,比如把&&改成||

把a改成1:

不难发现。其实计算复合表达式的值并不难,只要掌握逻辑运算法则和其他运算操作符的原理即可。


条件操作符

exp1 ?  exp2 : exp

条件操作符的计算原理就是,如果exp1的值为真就将exp2的值作为结果,如果为假就将exp的值作为结果。

代码示例:


逗号表达式

exp1, exp2, exp3, …expN

知识要点

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

 

代码举例:


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

1.[ ] 下标引用操作符

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

int arr[10];//创建数组
 arr[9] = 10;//实用下标引用操作符。
 [ ]的两个操作数有两个,是arr和9。

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;
 }

3.访问一个结构的成员

.  结构体.成员名

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

代码举例:
定义一个书类结构,分别用.操作符和->操作符访问结构体变量

#include<stdio.h>
struct Book//自定义一个书类
{
	//自定义结构体成员变量
	char name[20];
	char id[20];
	int price;
};//创建结构体类型时括号外部需要分号
int main()
{
	int num = 10;
	struct Book b = { "c语言","c202109",55 };//结构体变量b
	struct Book* pb = &b;
	printf("书名:%s\\n", b.name);
	printf("书号:%s\\n", b.id);
	printf("价格:%d\\n", b.price);
	printf("***********\\n");
	printf("书名:%s\\n", (*pb).name);//地址解引用
	printf("书号:%s\\n", (*pb).id);
	printf("价格:%d\\n", (*pb).price);
	printf("***********\\n");
	printf("书名:%s\\n", pb->name);//指针访问
	printf("书号:%s\\n", pb->id);
	printf("价格:%d\\n", pb->price);


}


总结

本篇内容到此结束啦,博主讲解了各种操作符的运用,只有熟悉了操作符的运算法则我们才能更好的求解表达式的值。所以下篇内容也很重要,大家要认真总结上篇的内容,将它转化为自己的知识,这样我们才能越来越强。如果觉得博主的内容写的不错,请给博主一键三连,谢谢大家的阅读啦。我们下篇见!!

 

 

 

 

以上是关于C语言初阶深入探索C语言操作符的奥秘(上)!!的主要内容,如果未能解决你的问题,请参考以下文章

C语言初阶深入探索C语言操作符的奥秘(下)!!

C语言初阶笔记深入探索C语言操作符的奥秘(下)!!

C语言初阶笔记深入探索C语言操作符的奥秘(下)!!

初始C语言(C语言初阶)

C语言初阶笔记初识结构体

C语言初阶笔记初识结构体