C语言操作符详解(和bug郭一起学C系列)
@TOC
操作符和表达式
我们在初始C语言已经大致了解了操作符,我们今天一起详细解剖操作符。
操作符
C语言操作符很多,但大致进行分类后,有以下几种操作符
//算数操作符
+ - * / %
//移位操作符
<< >>
//位操作符
& |
//赋值操作符
= += -= *= /= ...
//单目操作符
sizeof() ! ++ -- & *
//关系操作符
> >= < <= ==
//逻辑操作符
&& ||
//条件操作符
?:
//逗号表达式
,
//其他操作符
[] () -> . ...
算数操作符
算数操作符再常见不过,
加减乘除,取余。
+ - * /
操作符和我们数学上的一样。
值得注意的是
/
int c=10/3;
c=10.0/3;
c=10/3.0; //c结果为3
(double)c=10.0/3; //能计算出小数值
C语言中,/
需要至少一个操作数为浮点数,才能使结果为浮点数,并且记得存在浮点数中。
%
求余(取模)操作符
只能计算两个整型之间的结果,结果也为整型。
移位操作符
这里说的移位,指的是移动二进制位。
有左移,右移操作符。
<<
左移操作符
向左移动二进制位
可以看到二进制左移后,a<<1
是a
的2
倍,所以我们可以知道,左移一位,扩大2
倍。
左移n
位扩大2^n
倍。
>>
右移操作符
可想而知,右移操作符,也与左移效果类似,二进制位向右移动一位。
右边的二进制丢弃,右边的二进制位,并不是添像左移操作符一样添零,需要分情况讨论。
移位又分为算数移位和逻辑移位。
算数移位就是右移时,左边添加那一位是需要看二进制的符号位,添加。添加的哪一位和符号位相同。
逻辑移位就是不管左移还是右移操作,添加哪一位都是添0
。
但我们需要移动一个负数时,显然逻辑移位会改变原数值得正负。
所以在一般的编译器下都采用算数移位。
可以看到右移操作原来的值缩小了2^n
倍。
注意:左移和右移都要考虑移位后是否会溢出。
移位操作是针对移动正数位,
a>>-1
这样移位错误,C语言未定义。
位操作符
位操作,有&
(按位与) ,|
(按位或),^
(按位异或)~
(按位取反)。
位操作符顾名思义,是针对二进制位的操作,有两个操作数进行,二进制位进行操作运算。
这里我们的二进制位都是指的补码
,因为一个数以补码的形式存放在内存中。
// 00000000 00000000 00000000 00100010
// 00000000 00000000 00000000 11010110
// & 00000000 00000000 00000000 00000010
// | 00000000 00000000 00000000 11110110
// ^ 00000000 00000000 00000000 11110100
位操作符 | 作用 |
---|---|
& |
两操作数二进制位都为真(1)结果为真(1)否者为假(0) |
| |
两操作数二进制位为假(0)结果为假(0)否者为真(1) |
^ |
一真(1)一假(0)结果为真(1),否者为假(0) |
~ |
二进制位按位取反,1变0,0变1 |
位操作符的应用
//尝试写一下这个代码
include <stdio.h>
int main()
{
int num1 = 1; //00000000 00000000 00000000 00000001
int num2 = 2; //00000000 00000000 00000000 00000010
num1 & num2; // 00000000 00000000 00000000 00000000
num1 | num2; // 00000000 00000000 00000000 00000011
num1 ^ num2; // 00000000 00000000 00000000 00000011
return 0;
}
一道面试题小试牛刀
不创建新的变量,实现两个变量的交换。
//方法一
#include<stdio.h>
int main()
{
int a=3; // 00000000 00000000 00000000 00000011
int b=5; // 00000000 00000000 00000000 00000101
a=a^b; // 00000000 00000000 00000000 00000110
b=a^b; // 00000000 00000000 00000000 00000011
a=a^b; // 00000000 00000000 00000000 00000101
}
有趣的一道代码,利用^
按位异或实现了两数的交换。
^
异或操作符的性质
a^a=0;
a^0=a;
经常利用这两条性质解题,写出优秀的代码!
//方法二
#include<stdio.h>
int main()
{
int a=3;
int b=5;
a=a+b; //a=8
b=a-b; // b=3
a=a-b; // a=5
}
求一个整数存储在内存中二进制1的个数
//方法一
#include<stdio.h>
int main()
{
int n=10;
int count=0;
while(n)
{
if(n%2==1)
{
count++;
}
n>>=1;
}
printf("输入二进制位1的个数:%d",count);
}
思考上面的代码是否存在问题
当n
为负数时?
可以看到程序将会一直死循环下去。
我们优化一下!
//方法二
#include<stdio.h>
int main()
{
int i=0;
int count=0;
int num=-3;
for(i=0;i<32;i++)
{
if((num>>i)&1==1) //移位并且判断最后一位是否为1
count++;
}
return 0;
}
每次都要进行32
次循环,我们是否可以再次优化一下!
//方法三
#include <stdio.h>
int main()
{
int num = -1; // 10000000 00000000 00000000 00000001
//补码 11111111 11111111 11111111 11111111
int i = 0;
int count = 0;//计数
while(num)
{
count++;
num = num&(num-1);//丢弃最后一位1
}
printf("二进制中1的个数 = %d\n",count);
return 0;
}
上面这个代码是不是很神奇,一般人想不到,这就是代码的魅力!
赋值操作符
赋值操作符,我们再熟悉不过了。
我们可以通过赋值操作符,将一个变量改变成你想要的值!
#include<stdio.h>
int main()
{
int weight=180;
weight=125; //不满意可以改变
//连续赋值
int a=13,b=0,c=0;
a=b=c=6;
//连续赋值操作缺点不易调试
}
复合赋值操作符
+=
-=
*=
/=
%=
…
可以看到很多复合赋值操作符
a+=2; ===> a=a+2;
a*=3; ===> a=a*3;
//其他运算符一个道理
....
这边是复合赋值操作符,使用起来很简单,也很方便!
单目操作符
//单目操作符就是只有一个操作数的操作符!
+ - ! sizeof() ++ -- ~ * (类型)
+
-
这里的+ -
都是单目操作符,并不是算数操作符中的+-
!
a=-5; //-5这里的-指的是单目操作符!
b=+5; //+5 +可以省略!
!
逻辑反操作符
while(a!=0) //这里就是!逻辑反操作符
{
count++; //a不为0count++;
}
while(!a)
{
count++; //a为0count++;
}
sizeof
是否感到很诧异,sizeof
居然是操作符!
sizeof
是比较特殊的一个操作符,并不是函数!
我们知道sizeof
可以计算一个变量和类型的所占空间内存大小!
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;
}
可以看到当sizeof
计算一个类型时,不添加括号,就会报错,然而计算一个变量的大小时却可以省略括号!
总结:sizeof
计算类型所占内存大小时,括号不可省略。sizeof(类型)
,计算变量所占内存大小时,sizeof(变量)
,sizeof变量
将错误更改一下,看一下运行结果!
sizeof和数组
我们知道sizeof
可以计算变量的空间大小,所以我们经常通过sizeof
计算一个数组的元素个数!
公式:sizeof(数组)/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)两个地方分别输出多少?
我们先通过自己计算一下!
计算结果!
(1)40 (2) 40 (3)10 (4) 10
而运行结果!
可以看到,运行结果并不是那样!
我们在思考一下这个结果,为啥结果会是4
?
我们明明是将数组,直接传参过去 ,而通过sizeof
计算的内存大小却不是,难道我们只传参了一个地址过去?
我们调试一下,发现就是假设的这样,数组传参并没有将整个数组传参过去,而是传参了一个指针!
而我们在x86
也就是32
位平台下,指针所占内存空间大小为4
个字节。
++ --
//前置++和--:
#include <stdio.h>
int main()
{
int a = 10;
int x = ++a;
//先对a进行自增,然后对使用a,也就是表达式的值是a自增之后的值。x为11。
int y = --a;
//先对a进行自减,然后对使用a,也就是表达式的值是a自减之后的值。y为10;
return 0;
}
//后置++和--
#include <stdio.h>
int main()
{
int a = 10;
int x = a++;
//先对a先使用,再增加,这样x的值是10;之后a变成11;
int y = a--;
//先对a先使用,再自减,这样y的值是11;之后a变成10;
return 0;
}
总结: 前置++
,--
先进行自加操作,再使用!
后置++
,--
先使用再进行自加操作!
关系操作符
>
>=
<
<=
==
(判断是否等于)!=
(判断不等于)
这些这是基本的关系操作符!
我们已经很常见了,我们看一下关系操作符的运行结果!
可以看到,当判断结果为真是,vs
用1
代表真,用0
代表假。
注意:我们在测试,结果是否相等时,用==
而不是赋值操作符=
。
逻辑操作符
逻辑操作符,有逻辑与
&&
,逻辑或||
当我们要测试两个表达式结果时,如果要同时满足,使用逻辑与&&
只需满足其中一个表达式结果时使用逻辑或||
我们要区分逻辑操作符和位操作符按位与&
,按位或|
区别!
#include<stdio.h>
int main()
{
int a=3;//00000000 00000000 00000000 00000011
int b=1;//00000000 00000000 00000000 00000001
printf("%d\n",a&b);
printf("%d\n",a|b);
printf("%d\n",a&&b);
printf("%d\n",a||b);
return 0;
}
位操作符和逻辑操作符截然不同,一个是对整数的二进制进行操作,另一个是对表达式的结果进行判断!
&&
只有当两个表达式结果同时为真,结果才为真!
||
只有当两个表达式结果同时为假,结果才为假!
逻辑表达式的特性!
#include<stdio.h>
int main()
{
int a=3,b=5,c=6,i=0;
i=a++&&++b;
printf("%d %d\n",a,b);
i=a++||++b;
printf("%d %d\n",a,b);
return 0;
}
我们可以看到,逻辑或||
第二个表达式,并没有执行。
这是为什么呢!
总结: 逻辑与&&
当执行到表达式结果为假,便停止执行,后面的表达式!
逻辑或||
当执行到表达式结果为真,便停止执行后面的表达式!
这就是我们常说的逻辑短路特点!
条件操作符
exp1 ? exp2 : exp3
条件操作符通常由3
个表达式构成!又叫(三目操作符)!
如果exp1
表达式结果为真,执行exp2
,否者执行exp3
可以看到与我们的判断语句if
类似!
#include<stdio.h>
int main()
{
int a=5,b=3,max=0;
//if判断语句求最大值
if(a>b)
{
max=a;
}
else
{
max=b;
}
//条件表达式
a>b?max=a:max=b;
return 0;
}
可以看到条件表达式的优点,可以大大的简化代码!
逗号表达式
exp1,exp2,exp3...expN
表达式之间用,
分隔开,这就是逗号表达式。
表达式特点
#include<stdio.h>
int main()
{
int a=2,b=3,c=5;
int i=(a++,b++,c);
printf("a=%d b=%d c=%d i=%d",a,b,c,i);
return 0;
}
可以看到表达式i=(a++,b++,c);
结果i=5
也就是最后一个表达式c
的值。
逗号表达式运算特点:
表达式从左往右依次计算,最后一个表达式的值,便是整个逗号表达式结果的值!
其他操作符
[]
下标引用操作符()
函数调用操作符.
->
结构成员访问操作符
[]
下标引用操作符
操作数:一个数组名+一个索引值
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
// [ ]的两个操作数是arr和9。
既然是两个操作数,那么两个操作数可以交换位置吗?
可以看到arr[9]
等价9[arr]
但是我们并不介意用9[arr]
()
函数调用操作符
( )
函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
#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;
}
.
:结构体.
成员名
->
:结构体指针->
成员名
#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;
}
- 点赞
- 收藏
- 关注作者
评论(0)