C语言初阶深入探索C语言操作符的奥秘(上)!!
Posted Do
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言初阶深入探索C语言操作符的奥秘(上)!!相关的知识,希望对你有一定的参考价值。
目录
操作符和表达式
本章内容分为上下两篇,上篇主要讲解各种操作符的运用,下篇则主要讲涉及到操作符的表达式如何正确求值,以及一些题目的讲解。大家千万不要错过哦,每一篇内容都是博主辛苦总结的,真的是细节超多,干货满满!!
操作符
首先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语言操作符的奥秘(上)!!的主要内容,如果未能解决你的问题,请参考以下文章