目录
操作符分类
A.算术操作符
+ - * / %
/ 取整操作符,举例
1.0 / 2 = 0.5
1.0 / 2.0 = 0.5
% 取模操作符,得到的是计算后的余数
取模操作符的两端必须是整数
B.移位操作符
>>右移操作符
<<左移操作符
注:移位操作符的操作数只能是正整数
移位操作符移动的是二进制位
整数的二进制表示有三种,分别为原码,反码,补码
正的整数的原码,反码,补码相同
负的整数的原码,反码,补码,需要计算
举例
十进制的123为什么是123呢
因为123的个位的权重是10º,所以就是3*10º,十位的权重是10¹,所以就是2*10¹,百位的权重是10²,所以就是1*10²,相加就是123
二进制的7(111)也是相同的道理
分别是1*2²,1*2¹,1*2º,相加得7,
因为7是正数所以它的二进制补码最高位是0,
7是一个整数,站四个字节,共32bit,则
7的二进制序列是00000000 00000000 00000000 00000111 - 原码
00000000 00000000 00000000 00000111 - 反码
00000000 00000000 00000000 00000111 - 补码
-7的二进制序列是10000000 00000000 00000000 00000111 - 原码
11111111 11111111 11111111 11111000 - 反码(原码的符号位不变,其他位按位取反)
11111111 11111111 11111111 11111001 - 补码(在反码的基础上,把反码+1,就是补码)
整数在内存中的存放形式是补码
移位操作符在内存中移动的是补码
#define _CRT_SECURE_NO_WARNINRS
#include <stdio.h>
int main()
{
int a = 7;
int b = a << 1;
//左移操作符
printf("a = %d\n",a);
printf("b = %d",b);
return 0;
}
7的补码,左移一位,在后面补0,00000000 00000000 00000000 00001110
也就是1*2³,1*2²,1*2¹,0*2º,相加得14
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = -7;
int b = a << 1;
printf("%d\n",a);
printf("%d\n",b);
return 0;
}
-7的补码,左移一位,补0,得到-7的补码,然后-7的补码-1,按位取反,符号位不变,得到-7的原码10000000 00000000 00000000 00001110,得-14
结论:左移有*2的特点
右移操作符
算术移位:右边丢弃一位,左边补符号位(原符号)
逻辑移位:右边丢弃一位,左边补0(首位数字,0代表正数,1代表负数)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 7;
int b = a >> 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
正数看不出使用了哪种移位,因为首位都是0
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = -7;
int b = a >> 1;
printf("%d\n",a);
printf("%d\n",b);
return 0;
}
-7的补码是11111111 11111111 11111111 11111001,如果是
逻辑移位编码变成01111111 11111111 11111111 11111100,由于首位是0为正数,所以它的原,反,补码均为上面的01111111 11111111 11111111 11111100
算术移位变成10000000 00000000 00000000 00000100
vs编译器的右移操作符采用的是算术右移,大部分编译器采用的是算术右移
注意:移位操作数只能是正整数,不要移动负数位,负数在标准里未定义
C.位操作符
位操作符有:
按位与 & 按(2进制)位与
按位或 | 按(2进制)或
按位异或 ^ 按(2进制)异或
注:它们的操作数必须是整数
1.按位与
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 3;
int b = -5;
int c = a & b;
printf("c = %d\n",c);
return 0;
}
结果是3,为什么结果是3
3的原码00000000 00000000 00000000 00000011
-5的原码10000000 00000000 00000000 00000101
-5的反码11111111 11111111 11111111 11111010(符号位不变,按位取反)
-5的补码11111111 11111111 11111111 11111011(在反码的基础上+1)
3的补码00000000 00000000 00000000 00000011
当-5和3按位与的时候,两个位含有0的时候就是0,两个都是1的时候才是1
则-5和3按位与的结果是
00000000 00000000 00000000 00000011(补码)
%d是打印一个有符号的整数
求00000000 00000000 00000000 00000011的原码
首位是0,其原码为00000000 00000000 00000000 00000011
结果是3
结论:两个位含有0的时候就是0.两个都是1的时候才是1
2.按位或
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 3;
int b = -5;
int c = a|b;
printf("%d",c);
return 0;
}
3的补码,00000000 00000000 00000000 00000011
-5的补码,11111111 11111111 11111111 11111011
3和-5按位或后,得11111111 11111111 11111111 11111011
也就是-5
结论: 两个对应的位,含有1就是1,两个都为0才是0
3.按位异或:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 3;
int b = -5;
int c = a ^ b;
printf("%d",c);
return 0;
}
先得出3和-5的二进制补码
分别为
00000000 00000000 00000000 00000011
11111111 11111111 11111111 11111011
3和-5异或后得到
11111111 11111111 11111111 11111000(补码)
11111111 11111111 11111111 11110111(反码)
10000000 00000000 00000000 00001000(原码)
-8
结论:相同为0,相异为1
a ^ a = 0,a ^ 0 = a
3^3^5 = 5
3^5^3 = 5
异或操作符支持交换律
二进制的减法,向上一位借数的时候当成借2
比如100-001 = 011
操作符的应用:
法1.不创建临时变量,实现两个数的交换
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
a = a + b;
b = a - b;
a = a - b;
return 0;
}
如果a+b的值极大,就存在溢出的问题
法2.用按位异或操作符实现两个数的交换
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("交换前:%d %d",a,b);
a = a ^ b;//a = 3^5
b = a ^ b;//b = 3^5^5 = 3
a = a ^ b;//a = 3^5^3^5^5 = 5
printf("交换后:%d %d",a,b);
return 0;
}
练习:
编写代码实现:求一个整数存储在内存中的二进制中1的个数
求补码中二进制中1的个数
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int num = 10;
return 0;
}
D.赋值操作符
赋值操作符是一个很棒的操作符,它可以让你得到一个你之前不满意的值,也就是你可以给自己重新赋值
int weight = 109;//初始化
weight = 80;//赋值,不满意就换值
赋值操作符可以连续使用
复合赋值符:
+=,-=,*=,/=,%=,>>=,<<=,&=,|=,^=
a=a+5;
//等价于
a+=5;
E.单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置,后置--
++ 前置,后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
sizeof 计算的是变量所占内存空间的大小
即类型所创建的变量占内存空间的大小,单位是字节
sizeof 还可以直接统计数组总长度
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 2;
int c = sizeof(a);//计算的是a所占内存的大小,单位是字节
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(int));
printf("%d\n",sizeof a);//right,正确
//printf("%d\n",sizeof int);//err,错误
return 0;
}
sizeof是操作符,不是函数
strlen是库函数,用来求字符串长度
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test1(int arr[])
{
printf("%d",arr);//4
}
void test2(char ch[])
{
printf("%d",ch);//4,因为传入的参数是数组首元素的地址,指针变量的大小都是4字节
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n",sizeof(arr));//40
printf("%d\n",sizeof(ch));//10
return 0;
}
~ 按位取反操作符
按位取反后符号位也按位取反
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 0;
//00000000 00000000 00000000 00000000 //0的补码
printf("%d\n", ~a);
//按位取反
//11111111 11111111 11111111 11111111
//11111111 11111111 11111111 11111110
//10000000 00000000 00000000 00000001
//得到-1
int b = 3;
//00000000 00000000 00000000 00000011 //3的补码
//按位取反
//11111111 11111111 11111111 11111100
//11111111 11111111 11111111 11111011
//10000000 00000000 00000000 00000100
//结果是-4
return 0;
}
修改一个整数在内存中的二进制序列中的某位数
将某一位中的0换成1
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 13;
//00000000 00000000 00000000 00001101
//将第二位置换成1
//按位或有1就是1,两个都是0才是0
//让第二位与00000000 00000000 00000000 00000010按位或就可以得到所需结果
//也就是00000000 00000000 00000000 00001111 --- 补码,正数,原反补码相同
//15
//如何得到00000000 00000000 00000000 00000010
//可以理解为将1的二进制序列向左移动一位获取
//即1<<1
a |= (1<<1);
printf("%d",a);
return 0;
}
将某一位的1换成0
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 29;
//00000000 00000000 00000000 00011101
//将第五位换成0
//按位与,有0就是0,两个都是1才是1
//11111111 11111111 11111111 11101111
//可以通过00000000 00000000 00000000 00010000按位取反得到
//00000000 00000000 00000000 00010000可以通过1左移4位得到
a &= (~(1<<4));
printf("%d",a);
return 0;
}
++ 和 --
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 3;
printf("%d\n",a++);//结果是3,因为a++先取值再自增
printf("%d",a);//结果是4
return 0;
}
F.关系操作符
>
>=
<
<=
!= 用于测试不相等
== 用于测试相等
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
if("abc" == "abcde")
//这里不是比较两个字符串的长度,而是比较两个字符串首元素的地址是否相等
{
}
return 0;
}
G.逻辑操作符
&& 逻辑与
|| 逻辑或
区分逻辑与和按位与
区分逻辑或和按位或
逻辑与只关注真假,真为1,假为0,两个都为真输出1,有一个为假就是假,输出0
逻辑或只关注真假,有一个为真就是真输出1,两个都为假才为假,输出0
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
int c = a&&b;
printf("%d",c);
// if( c )
//{
// printf("%d",c);
//}
return 0;
]
练习
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("a= %d\nb = %d\nc = %d\nd = %d\n",a,b,c,d);
return 0;
}
在i = a++&&++b&&d++中,a先取值,后自增,此时a为0,逻辑或后,结果仍为假,则后面的自增不再执行,即只有a执行了自增,b和d没有执行自增
则输出结果是1,2,3,4
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++||++b||d++;
printf("a = %d\nb = %d\nc = %d\nd = %d\n",a,b,c,d);
return 0;
}
在i = a++||++b||d++中,当a++判定为真时,在逻辑或语句中,如果前面判定为真,后面的语句不再执行,即a为2,b为2,d为4
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++||++b||d++;
printf("a = %d\nb = %d\nc = %d\nd = %d\n",a,b,c,d);
return 0;
}
在i = a++||++b||d++中,a为0,继续向后执行,接着判断到b为3,结果为真,停止判断,输出结果是0,3,3,4
规则:&&,左边为假,右边就不计算了
||,左边为真,右边就不计算了
H.条件操作符(三目操作符)
表达式1?表达式2:表达式3
表达式1为真,就执行表达式2
表达式1为假,就执行表达式3
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 3;
int b = 0;
if(a>0)
b = 3;
else
b = -3;
//等价于
b = (a>5?b=3:b=-3);
return 0;
}
输出两个数中较大的一个
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int a = 1;
int b = 3;
int max = (a>b?a:b);
return 0;
}
I.逗号表达式
逗号表达式,是用逗号隔开的多个表达式
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果
代码1
int a = 1;
int b = 2;
int c = (a>b,a=b+10,a,b=a+1);
//输出13
代码2
a = get_val();
count_val(a);
while(a>0)
{
//业务处理
a = get_val();
count_val(a);
}
while(a=get_val(),count_val(a),a>0)
{
//业务处理
}
J.函数调用,下标引用,和结构成员
1.下标引用操作符
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
arr[7] = 8;
//这里的[]是操作数,arr和7都是操作数
//也可以写成
7[arr] = 19;//不推荐使用
//arr[7]也等价于*(arr+7)也等价于*(7+arr)
//arr是数组首元素地址
//arr+7就是跳过7个元素指向第八个元素
//*(arr+7)就是第八个元素
return 0;
}
2.函数调用操作符
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
int main()
{
int a = 10;
int b = 20;
//函数调用
//()就是函数调用操作符
int c = Add(a,b);
//操作数分别是Add,a,b
return 0;
}
3.结构成员访问操作符
. 结构体.成员名
-> 结构体指针->成员名
ps->age
等价于
(*ps).age
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
//定义类型
struct Stu
{
char name[20];
int age;
double score;
};
//赋值
void set_stu(struct Stu ss)
{
// ss.name = "zhangsan";
//会报错,因为数组名是地址,不能把字符串赋值给地址
//要把zhangsan放到name所指向的空间里
strcpy(ss.name,"zhangsan");
//strcpy把zhangsan拷贝放到name所指向的空间里
ss.age = 20;
ss.score = 100.0;
}
//打印
void print_stu(struct Stu ss)
{
printf("%s %d %lf\n",ss.name,ss.age,ss.score);
}
int main()
{
struct Stu s = { 0 };//初始化
set_stu(s);//赋值
print_stu(s);//打印
return 0;
}
打印的结果都是0,因为形参不改变实参,赋值只改变了形参的值,实参仍然处于初始化状态
要传实参的地址过去,来达到给实参赋值的功能
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
//定义类型
struct Stu
{
char name[20];
int age;
double score;
};
//赋值
void set_stu(struct Stu *ps)
{
strcpy((*ps).name,"zhangsan");
(*ps).age = 20;
(*ps).score = 100.0;
//等价于
/*strcpy(ps->name,"zhangsan");
ps->age = 20;
ps->score = 100.0;*/
}
//打印
void print_stu(struct Stu *ps)
{
printf("%s %d %lf\n",ps->name,ps->age,ps->score);
//比ss.name更加节省空间,因为ps->name直接从Stu中提取数据,省去了拷贝数据再提取的步骤
}
int main()
{
struct Stu s = { 0 };//初始化
set_stu(&s);//赋值
print_stu(s);//打印
return 0;
}