作为一个不写代码的人,面试中还是会下意识地问起C基础的东西,下面我们就来说一说关于c语言中常见的运算符:详解C语言中的各种操作符?我们一起去了解并探讨一下这个问题吧!
c语言中常见的运算符:详解C语言中的各种操作符
作为一个不写代码的人,面试中还是会下意识地问起C基础的东西。
而基础的操作符相关部分都是一个被忽略的重点,我们每天写代码一定会用到一些操作符,可是它的原理我们真的了解得很透彻么?
在复习的过程中也是自我反省的过程,好多的细节我之前没学到位,甚至整型提升这一个复杂的过程之前都没怎么听说过。
C 语言提供了丰富的运算符,共有10种,分别是:
算术操作符,移位操作符,位操作符,赋值操作符,单目操作符,关系操作符,逻辑操作符,条件操作符,逗号表达式,下标引用、函数调用和结构成员。
接下来一一介绍每种操作符:
算术操作符
符号: - * / %
符号说明:
---> 分别用于整数及浮点数的加法
- ---> 分别用于整数及浮点数的减法
* ---> 分别用于整数及浮点数的乘法
/ ---> 分别用于整数及浮点数的除法
% ---> 用于返回两个整数相除的余数
注意:
1. 、-、* 这三个操作符其实没有什么值得一提的,进行加法,减法,乘法运算。
2.当使用/运算符时:
1)如果两个操作数均为整型,那么执行整数除法,运算结果也为整型;
2)如果两个操作数至少一个为浮点数,那么执行浮点数运算,运算结果为浮点型。
结果类型与操作数有关,与类型无关
int ret=9/2; //ret=4
double ret=9/2 //ret=4.000000
double ret=9/2.0 //ret=4.500000
int ret=9/2.0 //编译器会报错
3.%运算符只能用于两个整数相除,返回余数。
ret=9%2; //ret=1
注意'%'的操作数只能是整型
移位操作符符号:<< >>
符号说明:
<< ---> 左移运算符,用于将整数左移指定位数
>> ---> 右移运算符,用于将整数右移指定位数
移位规则:
<< (左移运算符):不论算术移位还是逻辑移位,均将左边的数舍弃,右边空缺位补0(左边丢弃,右边补0)
>>(右移运算符): 当进行逻辑移位时,右边位丢弃,左边空缺位补0(右边丢弃,左边补0) ;
当进行算术移位时,右边位丢弃,左边位补原数的符号位(右边丢弃,左边补符号位)。
谈到移位操作符和位操作符,有必要复习一下原码,反码,补码的知识
对于整数的二进制存储有三种形式,原码,反码,补码。而在计算机中存储的数据为补码。
1.正数
正数的原码,反码,补码都是相同的,就是将该正数转换为二进制,以整型为例
10的原码,反码,补码是00000000 00000000 00000000 00001010 由于是整型,所以一共32个二进制位,第一位是符号位,所以为0,代表正数。
2.负数
负数的原码是将第一位符号位置为1,其他位按照十进制转换为二进制运算即可。
负数的反码是指符号位1不变,其他位取反。
负数的补码是由反码加一得到的。
以 -10 为例
原码:10000000 00000000 00000000 00001010
反码:11111111 11111111 11111111 11110101(0比1宽,视觉误差。。。我还查了一遍)
补码:11111111 11111111 11111111 11110110
重要的事情说两遍,计算机存的是补码。
a) << :左移操作符
顾名思义,就是将二进制的补码向左移动n位,也就是将最左边n个二进制位删掉,右边补了n个0。
int a=10;
int b=a<<1;
这段代码,b的值怎么算呢?
首先写出a的补码 :00000000 00000000 00000000 00001010
然后移位 :00000000 00000000 00000000 00010100
得到b的补码,由于是正数所以也是原码。b的值就算出来了是20。
b) >> :右移操作符
右移操作符和左移操作符基本相似,但是有不同。
同样最右边的数丢弃,但是最左边补什么由你使用的编译器决定。
(1)正数最左边补的位一定是0
(2)负数则需要看编译器支持哪种右移,支持逻辑右移则补0,支持算数右移则补1,大部分编译器支持的是算数右移
以-10为例,先写出-10的补码
补码:11111111 11111111 11111111 11110110
逻辑右移:011111111 11111111 11111111 11111011
算数右移:11111111 11111111 11111111 11111011
两种方式算出的结果是不同的,所以当你用不同编译器算出的结果不同,请不要惊讶。
注意:
1.移位操作不改变原值。
2.移位时不能移负数位。
位操作符符号:& | ^
符号说明:
& ---> 按位与(有0出0)
| ---> 按位或(有1出1)
^ ---> 按位异或(相同为0,相异为1)
举例说明:
int a=3;
int b=-2;
用这两个数字举例,先写出他们的补码
a : 00000000 00000000 00000000 00000011
b : 11111111 11111111 11111111 11111110
1.&按位与
有0则0,同1则1。
a&b=00000000 000000000 00000000 00000010
2.|按位或
有1则1,同0则0
a|b=11111111 11111111 11111111 11111111
由于每一位都有1存在,所以都是1,若有一位两者都是0的话,则为0
3.^按位异或
相同为0,不同为1。
a^b=11111111 11111111 11111111 11111101
注意:这三种位运算计算出的结果仍为补码。只要是用补码当操作数的,结果均为补码。
4.这三种位运算的用处
学习到这里你可能会问,这些运算在写程序的时候有什么用呢,似乎用的也不是很多。
(1)取到二进制位的最低位
只需要a&1,如果得到的结果是1,则最低位为1;如果得到的是0,则最低位是0。
(2)计算一个未知数的值
利用循环语句,每次循环将a的二进制序列向右移一位(>>),同时进行a&1,这样每一次循环都能得到a的一个二进制位,循环执行32次,则a的二进制序列便可得到,进而得到a的值。
(3)交换两个数的数值
通常情况下我们交换两个数的数值应该是这样的
int a=10;
int b=20;
int temp;
temp=a;
a=b;
b=temp;
我初学的时候一直觉得再创建一个变量挺麻烦的,不知道你们是否有同感。那有没有不用创建temp的方法呢?有,'^'呀。
int a=10;
int b=20;
a=a^b;
b=a^b;
a=a^b;
这样两个数就交换了,至于原理,你可以写一写二进制序列自己试一试。
注意:
1.位操作符的操作数必须为整数。
2.计算机中存的是二进制的补码,所以进行的是补码运算,再转化成原码可得最终结果 。
3.一个数的二进制&1可得该数二进制最低位是0还是1.
4.若想将一个数的二进制第N位 置为0,则可将1左移N-1位后按位取反,再与该数二进制做与运算,即(~(1<<(N-1)))& 该数二进制。
5.若想将一个数的二进制第N位 置为1, 则可将1左移N-1位后与该数二进制做或运算,即(1<<(N-1)) | 该数二进制。
赋值操作符符号:= = -= *= /= %= >>= <<= &= |= ^=
符号说明:
= ---> 进行简单赋值操作
=、-=、*=、/=、%=、>>=、<<=、&=、|=、^= ---> 复合赋值符,进行复合赋值操作
举例说明:
=(简单赋值) :
int x = 10;
x = 20; //简单赋值操作
double y = 10.0;
y = 20.0; //简单赋值操作
int a = 5;
int b = 7;
int c = 9;
c = b = a 1; //连续赋值操作
=、-=、*=、/=、%=、>>=、<<=、&=、|=、^=(复合赋值):
int x = 10;
x = x 5;
x = 5; //复合赋值
int y = 10;
y = y 5;
y -= 5; //复合赋值
其它复合赋值符也是相同用法,此处不再一一列举。
注意:赋值操作符可以连续使用,字符串的赋值不能用=,要用strcpy。
单目操作符所谓单目,就是只有一个操作数的意思
符号: ! - & sizeof ~ -- * (类型)
符号说明:
! ---> 逻辑反操作
- ---> 负值
---> 正值
& ---> 取地址
sizeof ---> 操作数的类型长度(单位:字节)
~ ---> 对一个数的二进制按位取反
-- ---> 前置、后置--
---> 前置、后置
* ---> 解引用操作符
(类型) ---> 强制类型转换
举例说明:
1)!(逻辑反操作):
int a = 10;
if(!a) //!:逻辑反操作
{
//doSomething;
}
2)-(负值)、 (正值):
int a = 10; //正值
int b = -10; //负值
3)& *取地址操作符和解引用操作符:
&取出某一个变量的地址,*通过地址找到变量的值。
int a=10;
int* p=&a;//取出a的地址放在指针p中
*p=20;//通过解引用操作符修改a的值
printf("%d",a);//a=20
这里需要一些指针的知识,注意一下(int*)代表指针的类型,这里的‘*’和后面的解引用操作符‘*’不同。
4)sizeof(求操作数的类型长度):
int a = 10;
printf("%d\n",sizeof(a)); //结果为4(字节)
printf("%d\n",sizeof(int)); //结果为4(字节)
printf("%d\n",sizeof a); //结果为4(字节) 求变量的长度时可以省略括号
printf("%d\n",sizeof int); //错误,求类型的长度时不能去掉括号
5)~(对一个数的二进制按位取反):
符号位也要取反
int a=0;//00000000 00000000 00000000 00000000
int b=~a;//11111111 11111111 11111111 11111111
用处:一般在修改单个二进制位的时候使用
6)--(前置、后置--)、 (前置、后置 ):
int a = 5;
int b = a ; //此时a=6,b=5 前置 :先使用,后
int c = a; //此时a=6,c=6 后置 :先 ,后使用
前置--和后置--分别同前置 、后置 ,此处不再详述。
记住一点就可以了,前置 先计算后赋值,后置 先赋值后计算,--也是一样的
7)(类型)(强制类型转换):
srand((unsigned int)time(NULL));
int a=3.14;//编译器会报错
int a=(int)3.14//a=3,编译器不会报错
注意:
1.sizeof是一个操作符,关键字,而不是函数,求的是操作数的类型长度(以字节为单位)。
2.sizeof求类型的长度时不可省略括号,求变量的长度时可以省略括号。
关系操作符符号:· > < >= <= != ==
符号说明:
> ---> 测试“大于”
< ---> 测试“小于”
>= ---> 测试“大于等于”
<= ---> 测试“小于等于”
!= ---> 测试“不等于”
== ---> 测试“等于”
注意:
1.判断字符串是否相同应该用strcmp()函数,而不能用==比较。
2.编写程序代码时一定注意=和==的区别,不要写错,=是做赋值操作,而==才是判断是否相等。
逻辑操作符符号: && ||
符号说明:
&& ---> 逻辑与操作(只要有一个表达式为假便为假,不再执行后面的表达式)
|| ---> 逻辑或操作(只要有一个表达式为真便为真,不再执行后面的表达式)
举例说明:
1 && 2 = 1; //逻辑与,只要有一个为假便为假
1 & 2 = 0; //按位与(二进制位)
1 || 2 = 1; //逻辑或,只要有一个为真便为真
1 | 2 = 3; //按位或(二进制位)
int i = 0,a = 0,b = 2,c = 3,d = 4;
i = a && b && d ; //前置 是先使用再 ,所以此时先使用a = 0,由于进行的是逻辑与操作,只要有一个表达式为假,便不再执行后面的表达式,直接返回假。而后a ,a变成1,b,c,d仍为原值
printf("a = %d b = %d c = %d d = %d\n",a,b,c,d); //a = 1,b = 2,c = 3,d = 4
int i = 0,a = 0,b = 2,c = 3,d = 4;
i = a || b || d ; //前置 是先使用再 ,所以此时先使用a = 0, b后b变成3,此表达式为真。由于进行的是逻辑或操作,只要有一个表达式为真,便不再执行后面的表达式,直接返回真。而后a ,a变成1,不再执行d 。
printf("a = %d b = %d c = %d d = %d\n",a,b,c,d); //a = 1,b = 3,c = 3,d = 4
注意:
1.一定注意按位与和逻辑与,按位或和逻辑或的区别。
2.逻辑与中,只要有一个表达式为假,便不再执行后面的表达式,直接返回假;
逻辑或中,只要有一个表达式为真,便不再执行后面的表达式,直接返回真。
条件操作符符号:exp1 ? exp2 : exp3
举例说明:
int a = 5;
int b = 9;
int max = (a>b) ? a : b; //如果a>b,则max = a;否则max = b
符号:exp1, exp2, exp3, ..., expN
符号说明:
exp1, exp2, exp3, ..., expN ---> 逗号表达式(用逗号隔开的表达式),从左往右依次执行。整个表达式的结果为最后一个表达式的结果。
逗号表达式中也有一些细节是我们没怎么注意到的。
(1)逗号表达式由左向右执行,整个表达式的结果是最后一个表达式的结果
int a = 5;
int b = 1;
int c = (a = b 1,b = a - 2);
printf("%d", c);//c=0
c的结果是0,这说明逗号表达式确实从左到右执行了,而且整个逗号表达式的值是最后一个表达式的值
(2)根据逗号表达式的特性可以简化循环语句
a=get();
count(a);
while(a>0)
{
a=get();
b=count(a);
}
用逗号表达式可以这样写:
while(a=get(),count(a),a>0)
{ }
但其实不太建议这样去做,虽然很秀,但是容易把自己绕晕,而且do while语句可以达到同样的效果
注意:逗号表达式的结果虽然是最后一个表达式的结果,但不可认为与前面的表达式就无关了,因为前面表达式可能会影响最后一个表达式的结果。
下标引用、函数调用和结构成员1)[]下标引用操作符
符号说明:
[ ] ---> 下标引用操作符,有两个操作数(数组名和索引值)
举例说明:
int arr[10] = {0};
arr[3] = 7; //[ ]:下标引用操作符,其两个操作数为arr和3
注意:下标引用共有两个操作数(数组名和索引值)。
2)()函数调用操作符
符号:()
符号说明:
() ---> 函数调用操作符,有一个或多个操作数(函数名和参数)。
void test1()
{
printf("fine day!\n");
}
void test2(char *ch)
{
printf("%s\n",ch);
}
int main()
{
test1(); //():函数调用操作符
test2("fine day!"); //():函数调用操作符
}
注意:函数调用操作符有一个或多个操作数。
3)结构体成员的引用操作符
符号: . ->
符号说明:
. ---> 结构体对象.成员名
-> ---> 结构体指针->成员名
struct Person
{
char name[10];
char sex[5];
int age;
double height;
}
int main()
{
struct Person person1;
struct Person person2;
struct Person *pperson = &person2;
strcpy(person.name,"zhangsan"); //结构体成员访问
person.age = 20; //结构体成员访问
strcpy(pperson->name,"lisi"); //结构体成员访问
pperson->age = 23; //结构体成员访问
}
注意:当结构体中有数组成员时,给该成员赋值用用strcpy()函数,将目标串拷贝给该数组成员。
,