目录
励志模块
日益努力 而后风生水起 众生皆苦 你也不能认输
1 操作符分类
操作符包括:算术操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符、条件操作符、逗号表达式、下标引用、函数调用和结构成员。
2 算术操作符
加 减 乘 除 取模(取余)
(1)除了%操作符之外,其他的几个操作符可以作用于整数和浮点数。
(2)对于/操作符,如果两个操作数都是整数,执行整数除法。只要有一个浮点数,执行的就是浮点数除法。
(3)%操作符的两个操作数必须为整数,返回的是整除之后的余数。
3 移位操作符
<< 左移操作符 >>右移操作符
移位操作符的操作只能是整数
左移和右移的都是补码,但是打印出来的都是原码对应的十进制。(补码减一,除符号位 其它按位取反)
知识点:
原码反码补码
整数有3种二进制的表示形式(任何整数都可以表示出原码 反码 补码)
整数在内存中储存的是补码
正整数:原码 反码 补码 相同
负整数: 原码 反码 补码不同,要进行计算
移位操作符,正数不用管 ,负数记得补码变原码。
3.1 左移操作符
移位规则: 左边抛弃、右边补零(指对应二进制的左移和右移)
知识点: a的值还是3,并没有发生变化,只是一种运算。 a<<1只是展现a左移一位的效果,a的值 在没有赋值的情况下 并没有发生变化。
3.2 右移操作符
移位规则:
首先右移运算分两种:(到底是逻辑右移还是算数右移,取决于编译器。我们常见的编译器都是算术右移)
1.逻辑移位:左边用0填补,右边丢弃
2.算术移位:左边用原该值的符号位 填补,右边丢弃。
(注意,负数,回归原码是10000000000000000000000000000001,这个数是-1,不是2^32+1)
警告:
可以左移正数,可以右移正数,但是不可以左移负数 ,右移负数。例如:a<<-3.这是标准为定义的。
4 位操作符
注意:(正负数都可以,补码进行操作,打印的是原码)
对应的操作也是二进制位的操作(补码操作)(int 32位)
上图都是正数操作,以便方便叙述,所以前面的0在这里省略。
4.1 不创建临时变量,进行两个数交换
代码1展示:(有局限性,不能解决全部情况,因为int是有范围限制的,万一a和b这两个数非常大,a+b超过int的范围,那么就会出现错误)
#include <stdio.h>
int main()
{
int a = 2;
int b = 3;
printf("%d %d\n", a, b);
a = a + b;
b = a - b;
a = a - b;
printf("%d %d", a, b);
return 0;
}
代码2展示:
两个相同的数异或,是0;0和任何数异或,还是任何数。a^b^b = a
#include <stdio.h>
int main()
{
int a = 2;
int b = 3;
printf("%d %d\n", a, b);
a = a ^ b;
b = a ^ b;//a^b^b = a
a = a ^ b;//a^b^(原来的a) = 原来的b
printf("%d %d", a, b);
return 0;
}
5 赋值操作符
= 赋值操作符
a = b = x+1; 分析:x+1的值赋给b,的值赋给a,但是不提倡用(不清晰爽朗 不易与调试)
知识点:
左值:是可以放在等号左边,一般是一块空间
右值:是可以放在等号右边的,一般是一个值(a = 10)或者是一块空间的内容(a = 10 b = a)
复合赋值符:
别的都类似于上图所示
6 单目操作符
a+b,+就是一个双目操作符,因为+两边都有数值,单目操作符就是操作符仅一个操作数
逻辑反操作(!):在C语言中,0表示假,!0表示真 。例如:10表示真,则!10输出的就是0。
此时友友们就会想,!0会不会就是任意一个数值,答案是不是的,!0的值是固定的,是1。
(! 真变成假,假变成真)
正值(+),负值(-),一个数a=10,那么+a就是10,-a就是-10;一个数b=-10,那么+b就是-10,-b就是10。
知识点:
abs(对整数取绝对值) fabs(对小数取绝对值)#include <math.h>
取地址符号(&),取地址用的,可以知道储存在内存中的地址。
操作数的类型长度(以字节为单位)(sizeof):计算的是变量或者类型所创建变量占据内存的大小。
上图中把a去掉,就是类型,sizeof(类型)也可以。int arr[10]呢?答案是也可以的
sizeof(类型)括号不可以省略,变量可以 但是中间需要一个空格
对一个数的补码的二进制按位取反(~)(0变成1,1变成0),打印的是原码
0的原码反码补码都是000000000000000000000000000000000
给一个数10 , 00000000000000000000000000001010,把倒数第三个0变成1,这个数按位或0000000000000000000000000000000100(也可以表示成1<<2)即可变成000000000000000000000000000001110
现在倒回去,把00000000000000000000000000001110的倒数第三个数字变成0,这个数按位与11111111111111111111111111011(按位取反 1<<2)即可
前置、后置++ 前置后置--
前置++:先++,后使用 后置++:先试用,后++
++a,那么结果都是11
*间接访问操作符(解引用操作符)
#include <stdio.h>
int main()
{
int a = 10;//在空间内存创建一个变量a,内存的地址叫做m,
int* pa = &a;//指针变量int* 创建一个变量pa存放地址m
*pa = 20;//通过指针找到a,用*pa,改变pa的值就是改变a的值
printf("%d ", a);
return 0;
}
*pa就是a,&*pa与&a相同。(不管什么元素的指针,它的大小4个字节就是8个字节,例如:不在主函数内的sizeof(ch)的值为4/8.。,char ch[10] ={0}, (×86环境——32个字节——4个字节)(×64环境——64个字节——8个字节))
强制类型转换(类型):例如,3.14是一个double类型数字,把它转换成int类型,即 (int)3.14
int b = 3.14会报错 ,为了不报错加上(int)
7 关系操作符
这个就比较简单了,就是大于,大于等于,小于,小于等于,不等于,等于(注意,判断相等,用两个等号)
8 逻辑操作符
按位与和按位或 关注二进制, 逻辑与和逻辑或只关注真假
1<a<5 这样写是错误的,如果a = 2,1<2为真,就为1,1<5为真。
因为a++为后置++,a++为1,a为2,所以逻辑或,一真就全部为真,所以++b,d++并没有计算,所以b和d仍然为2,4.所以a b c d为2 2 3 4.
总结:
&&左操作符为假,右边不计算 || 左操作为真,右边不计算
9 条件操作符
这个操作符也叫三目操作符
条件操作符就是简化if语句。exp1为真,就输出exp2, exp1为假,就输出exp3.
if语句
简化if语句
10 逗号表达式
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式会从左向右依次计算,整个逗号表达式的结果是最后一个表达式的结果。
if(a >b,c = b/2,d > 0) 这个语句中起作用的是d > 0.
11 下标引用、函数调用和结构成员
11.1 [ ] 下标引用操作符
操作数:一个数组名+一个索引值
int arr[ 10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%d\n",arr[7]);
上面的[ ]就是下标引用操作符,arr ,7 就是操作数
arr数组首元素地址,arr+7就是第8个元素的地址,*(arr+7)就是arr[ 7 ],*(arr+7)可以写成*(7+arr),那么就可以写成7[ arr ].
11.2 ( )函数调用操作符
test( )中的 函数调用操作符,在函数那个章节有详细说明,在这里就不赘述了
11.3 访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名
#include <stdio.h>
struct Stu
{
char name[20];
int age;
double score;
};
int main()
{
struct Stu s = { "zhangsan", 20, 85.5};
printf("%s %d %.1lf\n", s.name, s.age, s.score);//结构体变量.结构体成员
struct Stu* ps = &s;
printf("%s %d %.1lf\n", (*ps).name, (*ps).age, (*ps).score);
printf("%s %d %.1lf", ps->name, ps->age, ps->score);//结构体指针->结构体变量
return 0;
}
12 表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
char short(4字节以下) 进行整形提升(隐式类型转换) int long long long float double 等(4个以及4个字节以上)这些进行算数转换
12.1 隐式类型转换
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。
如何进行整体提升呢?
整形提升是按照变量的数据类型的符号位来提升的
代码展示:
#include <stdio.h>
int main()
{
char a = 5;//这个5是整数类型,但是存在在char类型
//0000000000000000000000000000101(整数5)
//00000101 char只能存在8个 截断
char b = 126;//00000000000000000000000001111110
//01111110 b
//00000101 a
//当a和b相加的时候,a和b都是char类型 表达式计算就要发生整形提升
char c = a + b;//a和b进行整形提升后,进行运算。a和b被提升为普通整形,然后进行加法运算
//因为这两个数的01111110 00000101 最高位都是0,(按照符号位来提升)所以进行整形提升后
//提升后变成了 00000000000000000000000001111110 0000000000000000000000000000101
//然后二进制数字进行相加,0000000000000000000000000010000011
//相加后要放进char ,所以截断,10000011(c)
//打印的是%d 又进行整形提升 11111111111111111111111110000011 这个是补码,打印为原码 变成原码即可
// 11111111111111111111111110000011 补码
// 11111111111111111111111110000010 反码
// 10000000000000000000000001111101 原码
// -125
printf("%d\n", c);
return 0;
}
12.2 算数转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为较高的另外一个操作数的类型后执行运算。
sizeof内部表达式不参与计算,仅仅判断类型即可
12.3 操作符的属性
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级(由高到低)下表中N/A (没有结合性) L-R(从左向右)
以下代码不提倡写,或者就是错误的
当表达式的路径不确定时,那么这个代码就是错误的。
c + --c;
注释:同上,操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得
知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义
的。
非法表达式 : 许多操作符进行干扰,导致在不同编译器下的值不同。
总结:
我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题。
操作符就到此结束了。