本章重点
详细介绍各种操作符,对操作符加深了解
对表达式求值的一些注意事项
操作符
算术操作符
- / %
对于除号,保留的是商,取模保留的是余数
#include <stdio.h>
int main()
{
int n = 7;
printf("%d\n", 7 / 2);
//除号操作数有一个是浮点数,就执行的是浮点数的除法
printf("%.2lf\n", 7 / 2.0);
//printf("%d\n",7/0); 不能除0,会报错
//printf("%d\n",7%2); 不能%0,也会报错
//printf("%d\n", 7 % 5.0); 不能用取模计算浮点数,会报错,取模操作符两端只能是整数
return 0;
}
移位操作符
左移操作符<<
右移操作符 >>
左移操作符
正数
#include <stdio.h>
int main()
{
int a = 1;
int b = a << 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
//b结果为2,移动一位结果是a的值乘以2
//a<<1,a的值没有变,还是1
负数
#include <stdio.h>
int main()
{
int a = -1;
a = a << 1;
printf("%d\n", a);
//10000000000000000000000000000001 -1的原码
//11111111111111111111111111111110 -1的反码,反码是原码按位取反得到的,最高位不变,其他位取反
//11111111111111111111111111111111 -1的补码,补码是反码加一得到的
//表示在屏幕上的是十进制,从内存中拿出来是原码
//左移一位
//11111111111111111111111111111110 补码
//11111111111111111111111111111101 反码
//10000000000000000000000000000010 原码-2
return 0;
}
//结果为-2
右移操作符
算术右移
左边补原来的符号位,右边丢弃
逻辑右移
右边丢弃,左边补0
#include <stdio.h>
int main()
{
int a=-1;
printf("%d\n",a>>1);
//结果为-1
//11111111111111111111111111111111 -1的补码
//向右移动1位,根据结果反推,是算术右移
//右边丢弃,左边补0,结果还是-1
return 0;
}
//除了-1,其他的数向右移动1位,结果为原来的值除以2
对于左移和右移不要移动移动位
位操作符
& 按位与
| 按位或
^ 按位异或
注:他们的操作数必须是整数。
& 按(二进制)位与
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
int c = a & b;
//00000000000000000000000000000011
//00000000000000000000000000000101
//00000000000000000000000000000001
//按位与,两个数同为1则1,其他为0
printf("%d\n", c);
return 0;
}
//结果为1
| 按位或
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
int c = a | b;
//00000000000000000000000000000011
//00000000000000000000000000000101
//00000000000000000000000000000111
//按位或,有1则1
printf("%d\n", c);
return 0;
}
//结果为7
^ 按位异或
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
int c = a ^ b;
//00000000000000000000000000000011
//00000000000000000000000000000101
//00000000000000000000000000000110
//按位异或,相同为0,相异为1
printf("%d\n", c);
return 0;
}
//结果为6
交换两个整数
1.临时变量
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
int tmp = a;
a = b;
b = tmp;
printf("%d %d\n", a, b);
return 0;
}
2.不创建临时变量
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
a = a + b; //8
b = a - b; //3
a = a - b; //5
printf("%d %d\n", a, b);
return 0;
}
这种会造成溢出
3.用异或操作符
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
//都是正数,就算计最后一个字节,好看出结果
//00000011 a
//00000101 b
a = a ^ b; //00000110 a^b a=6
b = a ^ b; //00000011 a^b b=3
a = a ^ b; //00000101 a^b a=5
printf("%d %d\n", a, b);
return 0;
}
求一个整数在内存中存放的二进制有几个1
int main()
{
int i = 0;
int number = 0;
int count = 0;
scanf("%d", &number);
for (i = 1; i <= 32; i++)
{
if (((number>>i) & 1) == 1)
{
count++;
}
}
printf("%d\n", count);
return 0;
}
赋值操作符
int a = 10; //初始化
a = 20; //赋值
连续赋值
int a=10;
int b=20;
int c=20;
a=b=c+1; //从右向左运算
复合操作符
+= -= *= /= %= >>= <<= &= |= ^=
#include <stdio.h>
int main()
{
int a = 1;
a += 1; //a=a+1;
a *= 2; //a=a*2;
return 0;
}
单目操作符
! //逻辑反操作
- //负值
+ //正值
& //取地址
sizeof //操作数的类型长度(以字节为单位)
~ //对一个数的二进制按位取反
-- //前置、后置--
++ //前置、后置++
* // 间接访问操作符(解引用操作符)
//(类型) 强制类型转换
!单目操作符
#include <stdio.h>
int main()
{
int flag = 0;
if (!flag)
{
printf("表示真\n");
}
else
{
printf("表示假\n");
}
return 0;
}
//!flag flag=0,为假,加上!,表示真
-和+ 单目操作符
#include <stdio.h>
int main()
{
int a = 1;
a = -a;
printf("%d\n", a);
a = +a; //很少用,不会改变负数的值,正数前面默认有+
printf("%d\n", a);
return 0;
}
&和* 单目操作符
#include <stdio.h>
int main()
{
int a = 10; //& 取地址操作符 - 把a的地址拿出来
int* p = &a; //int* 表示整型指针,p是变量
*p = 20; //指针是用来存放地址的,把a的地址放在指针变量p里面
//* 解引用操作符-间接访问操作符
//*p-表示通过指针变量p里存放的地址找到变量a的值
printf("%d\n", a);
printf("%d\n", *p);
return 0;
}
sizeof 操作符
#include <stdio.h>
int main()
{
int a = 10;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof a); //这种情况可以省略
//printf("%d\n", sizeof int); //不能省略
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(&a));
int arr[10] = { 0 };
printf("%d\n", sizeof(arr)); //整个数组的大小
printf("%d\n", sizeof(int[10]));//数组的类型
return 0;
}
s是short类型,a+5是四个字节,会发生截断
sizeof里面的表达式是不计算的
sizeof(s=a+5)在编译期间就算出来了
a+5的运算是在程序运行时计算的
练习
~
#include <stdio.h>
int main()
{
int a = 1;
//00000000000000000000000000000001
//11111111111111111111111111111110 //按位取反
//
//10000000000000000000000000000010 //-2的原码
//11111111111111111111111111111101 //反码
//11111111111111111111111111111110 //补码
printf("%d\n", ~a);
return 0;
}
scanf的多组输入
#include <stdio.h>
int main()
{
int a=0;
while(~scanf("%d",&a))
{
;
}
return 0;
}
当scanf返回EOF(-1)时,在按位取反~得到0,终止循环
前置++操作符
#include <stdio.h>
int main()
{
int a = 0;
printf("%d\n", ++a);
printf("%d\n", a);
return 0;
}
//1 1
//前置++,前++后使用
后置++操作符
#include <stdio.h>
int main()
{
int a = 0;
printf("%d\n", a++);
printf("%d\n", a++);
return 0;
}
//0和1
//后置++,先使用后++
强制类型转换
#include <stdio.h>
int main()
{
int a = (int)3.14;
printf("%d\n", a);
return 0;
}
关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
逻辑操作符
&& 逻辑与
|| 逻辑或
//多用在if里面
练习题
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("i = %d\na = %d\nb = %d\nc = %d\nd = %d\n", i, a, b, c, d);
return 0;
}
// 0 1 2 3 4
//&& 从左向右运算,左边为假后边不执行
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
printf("i = %d\na = %d\nb = %d\nc = %d\nd = %d\n", i, a, b, c, d);
return 0;
}
// 1 1 3 3 4
//左边为真,右边不执行
条件操作符
exp1 ? exp2 : exp3
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("%d\n", a > b ? a : b);
return 0;
}
//如果a大于b,输出a的值,否则输出b的值
逗号表达式
从左到右依次计算,最后一个表达式是结果
#include <stdio.h>
int main()
{
int a = 3;
int b = (a = a + 1, a = a + 2, a = a + 1);
printf("%d\n", b);
return 0;
}
//7
下标引用操作符
#include
int main()
{
int arr[10]={0};
arr[4]=5; //把5赋值给数组的第5个元素
return 0;
}
函数调用操作符
#include <stdio.h>
void test()
{
printf("hello world\n");
}
int main()
{
test(); //()函数调用操作符
return 0;
}
结构成员访问操作符
#include <stdio.h>
struct Stu
{
char name[20];
int age;
char sex[5];
};
int main()
{
struct Stu s = { "zhangsan",18,"man" };
printf("%s\n",s.name);
printf("%d\n", s.age);
printf("%s\n", s.sex);
struct Stu* p = &s;
printf("%s\n", (*p).name);
printf("%s\n", p->name);
return 0;
}
表达式求值
注意优先级和结合性
隐式类型转换
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
cpu内整型运算器的操作数的字节长度一般是int的字节长度,同时也是CPU的通用寄存器的长度,因此char类型的相加,会变成int型相加
#include <stdio.h>
int main()
{
char a = 3;
char b = 127;
char c = 0;
c = a + b;
//整型提升补原符号位
//00000011
//00000000000000000000000000000011 a
//01111111
//00000000000000000000000001111111 b
//00000000000000000000000010000010 a+b
//10000010
//打印出来的是int类型,在发生提升
//11111111111111111111111110000010 补码
//11111111111111111111111110000001 反码
//10000000000000000000000001111110 原码 - -126
printf("%d\n", c);
return 0;
}
//-126
无符号数高位补0
#include <stdio.h>
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
{
printf("a\n");
}
if (b == 0xb600)
{
printf("b\n");
}
if (c == 0xb6000000)
{
printf("c\n");
}
return 0;
}
//打印c a,b会发生整型提升
#include <stdio.h>
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
//printf("%u\n", sizeof(!c)); !c不算数据运算
return 0;
}
算术转换
long double
double
float
unsigned long int
long int
unsigned int
int
// 转换是向着高字节转换
//比如int和long int会转换成long int
float f = 3.14;
int num = f;//隐式转换,会有精度丢失
操作符的属性
对于复杂表达式的求值要注意下面三个问题:
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序。
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
//这种在不同的编译器下计算的结果也不一样
#include <stdio.h>
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer = 0;
answer = fun() - fun() * fun();
printf("%d\n", answer);
return 0;
}
// -10
//这也是有问题的,-10是2-3*4
//也可以是4-2*3=-2
#include <stdio.h>
int main()
{
int i=1;
i=(++i)+(++i)+(++i);
printf("%d\n",i);
return 0;
}
//在vs2019上结果为12,先计算三个++i,i=4,然后三个4相加
//如果在linux上结果为10,先算计前两个++i,i=3,前两个i相加
//6+(++i)=10
表达式要有唯一计算路径