目录
操作符的分类
一、算数操作符
难度不大通过简单的代码来介绍
float a = 3.0 / 2; //得到的是商
printf("%0.2f", a);
int b = 9 % 2; //得到的是取模 左右两边必须为整型 取值范围 如 m%n(0~n);
printf("%d", b);
二、移位操作符
<< 表示左移
>> 表示右移
这里面的移位都是在二进制的方向上考虑的 所以必须先知道二进制 操作的二进制位
int a = 5;
int b=a << 1; //(不能移动负数位置)
printf("%d\n", a);
printf("%d\n", b);
代码解释
整数的二进制表示有3种形式: 1.原码 2.反码 3.补码 正整数的 原码、反码、补码 是相同的 负整数的 原码、反码、补码 是要计算的
例子:
int a = 5; 二进制形式为 101 因为a是个整型,所以应该有4个字节32个比特位 所以要存放a的时候应该有三十二个位置 即:00000000 00000000 00000000 00000101 则如果是有符号位的时候 00000000 00000000 00000000 00000101 这里面的最高位:0 代表的是符号位 符号位:0 表示整数 1 表示负数 则对于正整数 a 的 原码、反码、补码都相同 均为:00000000 00000000 00000000 00000101、如果 a = -5 则其原码:10000000 00000000 00000000 00000101 (-5的原码) 反码:符号位不变,其他位 按位取反:11111111 11111111 11111111 11111010 (-5的反码) 补码:反码加一 11111111 11111111 11111111 11111011 (-5的补码) //****整数在内存中存储的是补码的二进制序列****
回到代码中
//a<<1 的解释 a向左移动1位 :00000000 00000000 00000000 00000101 (5的补码,内存中以补码形式存储)
//00000000 00000000 00000000 00000101 --> 0000000 00000000 00000000 00000101 0 向左移动一位,左边丢了一位,右边补上一位0
//则 a = 5 b = 10 a本身是不变的
//****打印出来的是原码****
//****左边的丢弃,右边的补零 先丢符号位****
int c = -5;
int d = c << 1;
printf("%d\n", c);
printf("%d\n", d); //-5 的 原码 10000000 00000000 00000000 00000101
//反码 11111111 11111111 11111111 11111010
//补码 11111111 11111111 11111111 11111011 <--- c 在内存中存储的序列
//则 c<<1 解释:1111111 11111111 11111111 11111011 0 ——> c 左移后的补码
//需要计算出原码:
//1.先计算出反码:补码减一 :1111111 11111111 11111111 11111010 1
//2.反码取反的到原码,符号位不变:1000000 00000000 00000000 000001010
// d = -10 c = -5 ;
// * 左移有*2的效果 * (移动一次)
// 右移
int e = 5;
//00000000 00000000 00000000 00000101 原码
//00000000 00000000 00000000 00000101 反码
//00000000 00000000 00000000 00000101 补码
int f = e >> 1;
//对于右移操作符有两种形式:(用哪一种形式是取决与编译器)
//1.算术右移 :右边的丢弃 ,左边补原符号位
//2.逻辑右移 :右边的丢弃, 左边的补0 <——(无论正负)
printf("%d\n", e);
printf("%d\n", f);
//从运行结果来看,此编译器采用的是算数右移
// * 右移有/2的效果 * (移动一次)但并不总是有/2的效果
三、位操作符
& 按(2进制)位与 //a & b ; 双目操作符 | 按(2进制)位或 ^ 按(2进制)位异或 //&n; 单目操作符 他们的操作数必须是整数
下面通过代码来具体介绍
//& 运算
int h = 3;
int g = -5;
int k = h & g;
//操作肯定是按照内存中的存储方式进行运算, 内存中是以补码的形式存储
// h = 3 补码: 00000000 00000000 00000000 00000011
// g = -5 补码: 11111111 11111111 11111111 11111011
//则 k 补码 : 00000000 00000000 00000000 00000011 (与操作 都假才为假,0为真,1为假,)
//(只要有0就是0,必须同时为1 才为1)
printf("%d\n", k); //打印是按照原码打印,手写的时候需要再计算为原码
// | 运算
int h1 = 3;
int g1 = -5;
int k1 = h1 | g1;
// h1 = 3 补码: 00000000 00000000 00000000 00000011
// g1 = -5 补码: 11111111 11111111 11111111 11111011
//则 k1 补码 : 11111111 11111111 11111111 11111011
//(两位只要有1就是1,两位同时为1也为1,必须同时为0才为0)
printf("%d\n", k1);
// ^运算
int h2 = 3;
int g2 = -5;
int k2 = h2 ^ g2;
printf("%d\n", k2);
// h2 = 3 补码: 00000000 00000000 00000000 00000011
// g2 = -5 补码: 11111111 11111111 11111111 11111011
//则 k2 补码 : 11111111 11111111 11111111 11111000
//(对应的二进制位,相同为0,相异为1)
关于异或操作的一个技巧
a^a=0 0^a=a
例题:
//例题:在一组数列中: 1 2 3 4 5 4 3 2 1 找出呢个只出现一次的数
//解题思路:将其全部异或在一起就行了
//例题2: 不能创建临时变量(第三个变量),实现两个数的交换
int a1 = 3;
int b1 = 5;
printf("a1=%d b1=%d\n", a1, b1);
//方法一:加减法
//a1 = a1 + b1;
//b1 = a1 - b1;
//a1 = a1 - b1; //潜在的问题:当两个数字过大的时候两个数字加在一起会溢出
//方法二:异或
a1 = a1 ^ b1;
b1 = a1 ^ b1;
a1 = a1 ^ b1; //用二进制解释 :
// a1: 011 —> a1= 110 —> b1= 011 —> a1 = 101
// b1: 101 | 101 | a1= 110 |
//或者将a1、b1一个一个带进去翻译
printf("a1=%d b1=%d\n", a1, b1);
//代码的实现,求一个数在内存当中的二进制数的1的个数
//例 : 5
//101 //00000000 000000000 0000000000 00000101
//补码一样
//想要确定5的二进制数中 1 的个数
//方法就是, 将 5 &上 1 可以求出5 的二进制数的最后一位是否为1,然后再将5右移后&上1,求得二进制的倒数第二位
//以此类推 执行32次
int num = 0;
scanf("%d", &num);
int i = 0;
int count = 0;
for (i = 0; i < 32; i++)
{
if (1 == ((num >> i) & 1))
{
count++;
}
}
printf("%d ", count);
四、赋值操作符
赋值操作符没有什么特别强调的地方
有一些特别要求和例子
//连续赋值 a=x=y+1; 实则是a变成了y+1
//复合赋值符
int a = 10;
a = a + 2;
//等价于
a += 2; //同理 a>>1 ==> a>>=1。a=a&4 ==> a&=4
五、单目操作符
! 逻辑反操作 - 负值 + 正值 & 取地址 sizeof 操作数的类型长度(以字节为单位) ~ 对一个数的二进制按位取反 -- 前置、后置-- ++ 前置、后置++ * 间接访问操作符(解引用操作符) (类型) 强制类型转换
struct S
{
char name[20];
int age;
};
int main(){
// !:C语言中 0 表示假 非零表示真
// & 取出谁谁在内存当中的地址 取地址操作符
int c = 10;
int* pc = &c;
int arr[10] = { 0 };
//&arr;//取出数组的地址,数组的地址应该放在数组的指针中去
//也可以对结构体类型取地址
struct S s = {0};
&s;
struct S* ps = &s;
//* 解引用操作符(间接访问操作符)
//解析:例如上面的 pc ,pc 里面现在存放的是 c的地址 ,若想通过pc找到c是一种间接访问
//所以
//*pc;//意思是通过pc里面所存的地址,找到pc所指的对象 *pc其实就是c *pc就是间接访问操作符
//& 和 * 是一来一回的形式 a == *&a
//sizeof ,sizeof是操作符不是函数,单位为字节
//例子:
int d = 10;
short ss = 0;
printf("%d\n", sizeof(ss = d + 2));
//sizeof内部不参与计算,d和2均为整型,大小为四个字节
// //而 ss 只有两个字节,硬要写成ss=d+2的形式,则会产生截断,大小还是ss的字节大小
printf("%d\n", ss); //ss的值不变
// ~按位取反
int e = 0;
printf("%d ", ~e); //每一位都要取反,包括符号位,而取反后的二进制是在内存当中存放的,表示的是补码,如果想要打印,(手写计算)必须要变成原码
// 按位与和按位或
int a1 = 11;
//将a1变成15
a1 |= (1 << 2);
printf("%d\n", a1);
//再将a1从15变回11则
a1 &= (~(1 << 2)); //在二进制位中考虑
printf("%d\n", a1);
//++ --操作符
int a = 3;
int b = ++a ;// 前置++,先++后使用//a = a +1 ;b=a
int b = a++ ;//后置++ ,先使用后++//b=a;a=a+1;
--同理
//强制类型转换,不鼓励去使用
//例子:
//int a = 3.14;
//若想赋值
int a = (int)3.14;
printf("%d", a);
return 0;
}
六、关系操作符
>= <= == != > <
唯一的要求就是在==里面,两个字符串是不能够用==的,还有结构体
if ("abcdef" == "abcdef") // 这样写的话 比较的不是字符串,而是首字符的地址
{
strcmp - 专门用来比较字符串大小的函数;
比较的是对应位置上字符的大小,不是比较长度、
}
七、逻辑操作符
没有什么过多的要求,下面用例子来说明原理
&& ||
区分按位与和逻辑与
区分按位或和逻辑或
1&2 ----> 0 比较的是二进制位
1&&2----> 1 比较的是真假
1|2 ----> 3
1||2----> 1
八、条件操作符
exp1?exp2:exp3
对exp1进行判断,如果对了就执行exp2否则就执行exp3。
代码例子:
int a = 3;
int b = 0;
int m = (a > b ? a : b);
printf("%d", m);
九、逗号表达式
就是用逗号隔开的多个表达式。 逗号表达式,从左到右依次执行。整个表达式的结果是最后一个表达式的结果。
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);//求c
printf("%d\n", c); //a>b 对表达式没有任何影响
if(a=b+1,c=a/2,d>0) 此表达式只以最后一个表达式来判断
十、下标引用操作符、函数调用操作符和结构成员
1.[ ]下标引用操作符
操作数:一个数组名+一个索引值
int arr[10] = { 0 };
arr[4] = 5; //[] 下标引用操作符,操作数是arr 4
//且 arr[4] <==> *(arr+4) <==> *(4+arr) <==> 4[arr]
2.函数调用操作符
#include<stdio.h>
int ADD(int x,int y)
{
return x+y;
}
int main()
{
int ret = ADD(3, 4);//()函数调用操作符,操作数是ADD,4,3但是不能跟下标操作符一样随便换位置
printf("%d", ret);
//至少应该有一个操作数
return 0;
}
3.访问结构体成员
. 结构体.成员名 -> 结构体指针->成员名
下面通过代码来具体介绍
#include<stdio.h>
#include<string.h>
struct Stu
{
char name[20];
int age;
float score;
};
void print1(struct Stu ss)
{
printf("%s %d %f\n", ss.name, ss.age, ss.score);
}
void print2(struct Stu* ps)
{
printf("%s %d %f\n", (*ps).name, (*ps).age, (*ps).score);
//打印3:
printf("%s %d %f\n", ps->name, ps->age, ps->score);
}
int main()
{
struct Stu s = { "张三",20,90.5f };
//打印1:
print1(s);
//打印2:
print2(&s);
//打印3: ->
//修改
//s.name = "张三丰"; //直接这样是不行的,因为数组名是一个地址
// *(s.name)="张三丰"对此解引用也是不行的,因为只能解引用一个字符,所以存放 张三丰 是不行的
//解决方法1:
strcpy(s.name , "张三丰");
//解决方法2:
scanf("%s", s.name);
print2(&s);
}
操作符的内容大概就讲完了
下面是表达式的求值问题
二.表达式的求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样。有些表达式的操作数在求值的过程中可能需要转换为其他类型。
一.隐式类型转换
c的整型算数运算总是至少以缺省整型类型的精度来进行的。 为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为:整型提升
例子:
char c1 = 3;
// 00000000 00000000 00000000 00000011
// 因为c1是char类型的,只能存放8个二进制位,所以要产生截断,存放的是00000011 - c1
char c2 = 127;
//同理 01111111 - c2
char c3 = c1 + c2;
//所以要计算的时候,要先把c1,c2进行整型提升
printf("%d\n", c3);//在运算的时候要先转换成整型;
//如何进行整型提升
//整型提升是按照变量的数据类型的符号位来提升的
//负数整型提升
//整型提升的时候,高位补充符号位,即为1
//正数整型提升
//整形提升的时候,高位补充符号位,即为0
//所以c1提升后 00000011 -> 00000000 00000000 00000000 00000011
// c2 -> 00000000 00000000 00000000 01111111
// c3 -> 00000000 00000000 00000000 10000010
//c3里面存放为 10000010
//c3是char类型的要打印为%d类型,所以
//要整型提升:11111111 11111111 11111111 10000010 -补码
// 10000000 00000000 00000000 01111110 -原码
二.算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另一个操作数的类型,否则操作就无法进行 下面的层次体系称为寻常算数转换 long double double float unsigned int long int unsigned int int 如果一个操作数的类型在上面的排列中排名较低,呢么首先要转换为另一个操作数类型(排名较高的)后再执行运算 要转换合理float f = 3.14; int num = f;//隐式转换,会有精度丢失
三.操作符的属性
复杂表达式的求值有三个因素: 1.操作符优先级 2.操作符的结合性 3.是否控制求值顺序
优先级从上到下依次递减,最上面具有最高的优先级,逗号操作符具有最低的优先级。表达式的结合次序取决于表达式中各种运算符的优先级。优先级高的运算符先结合,优先级低的运算符后结合,同一行中的运算符的优先级相同。