目录
操作符分类:
算术操作符
移位操作符
原码
反码
补码
左移操作符
用负数进行左移
右移操作符
错误写法
位操作符
不能创建临时变量(第三个变量),实现两个数的交换
编写代码实现:求一个整数存储在内存中的二进制中1的个数。
赋值操作符
单目操作符
~按位取反讲解
sizeof 和 数组
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员
( ) 函数调用操作符
访问一个结构的成员
表达式求值
隐式类型转换
整型提升的意义:
算术转换
操作符的属性
操作符优先级
操作符分类:
算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员
算术操作符
+ - * / %
1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。
+ - * / %
1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。 2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。 3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。
移位操作符
<< 左移操作符
>> 右移操作符
注:移位操作符的操作数只能是整数。
原码
计算机中对数字的二进制定点表示方法。原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数 该位为0,负数该位为1(0有两种表示:+0和-0),其余位表示数值的大小。
反码
正数的反码是它本身,负数的反码是符号位不变,其它位取反
补码
整数的补码是它本身,负数的补码是反码+1,整数是以补码的形式进行存储的
左移操作符
计算机中对数字的二进制定点表示方法。原码表示法在数值前面增加了一位符号位(即最高位为符号位):正数 该位为0,负数该位为1(0有两种表示:+0和-0),其余位表示数值的大小。
反码
正数的反码是它本身,负数的反码是符号位不变,其它位取反
补码
整数的补码是它本身,负数的补码是反码+1,整数是以补码的形式进行存储的
左移操作符
正数的反码是它本身,负数的反码是符号位不变,其它位取反
整数的补码是它本身,负数的补码是反码+1,整数是以补码的形式进行存储的
左移操作符
此时a的数值并未发生变化。而是把操作完的数字赋值给了b
用负数进行左移
此时a的数值并未发生变化。而是把操作完的数字赋值给了b
总结:左移操作符,左边丢弃,右边补0
右移操作符
对a右移一位之后,我们发现此时符号位的位置空缺了一位,此时应该补0或者1
算数位移:左边丢弃,右边补原符号位
逻辑位移:左边丢弃,右边补0
我们用负数测试后,发现vs2019用的是算术位移, 因为逻辑位移只补0,补0之后就是正数了,大多数编译器用的都是算术位移,
错误写法
位操作符
& //按位与
| //按位或
^ //按位异或
注:他们的操作数必须是整数。
负数运算也是如此,按位与&:有0则结果为0
按位或| :有1则结果为
按位异或^:不同则为1
运算法则:补码相运算,然后把补码根据原码反码补码的关系,转为原码,则最终得到的数字就是原码所代表的数字,只不过是以补码的形式进行存储
不能创建临时变量(第三个变量),实现两个数的交换
#includeint main() { int a = 3; int b = 5; printf("交换前:%d %d", a, b); a = a + b; b = a - b; a = a - b; printf("交换后:%d %d", a, b); return 0; }
#includeint main() { int a = 3; int b = 5; printf("交换前:%d %d", a, b); a = a ^ b; b = a ^b; a = a ^b; printf("交换后:%d %d", a, b); return 0; }
#includeint main() { int a = 3; int b = 5; a = 3^ 3; b = 3 ^0; printf("%d %d", a, b); return 0; }
任何数和0异或都为任何数,任何数和它本身异或都为0
异或支持交换律
编写代码实现:求一个整数存储在内存中的二进制中1的个数。
#includeint main() { int a = 3; int sum = 0; for (int i = 0; i < 32; i++) { if ((a >> i) & 1) { sum++; } } printf("%d", sum); return 0; }
#includeint main() { int num = 10; int count = 0;//计数 while (num) { if (num % 2 == 1) count++; num = num / 2; } printf("二进制中1的个数 = %dn", count); return 0; }
#includeint main() { int num = -1; int i = 0; int count = 0;//计数 while(num) { count++; num = num&(num-1); } printf("二进制中1的个数 = %dn",count); return 0; }
赋值操作符
赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。
int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值。
赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y+1;//连续赋值
x = y+1;
a = x;
复合赋值:
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
单目操作符
| ! | 逻辑反操作 |
| - | 负值 |
| + | 正值 |
| & | 取地址操作符 |
| sizeof | 操作数的类型长度 |
| ~ | 对一个数的二进制进行按位取反 |
| -- | 前置、后置-- |
| ++ | 前置、后置++ |
| * | 间接解引用操作符 |
| (类型) | 强制类型转换 |
~按位取反讲解
按位取反是按照补码进行取反,之后转换到原码,输出的是原码的值
若想把红色位置的0变为1,我们可进行下面的操作
将1的二进制位左移1位,然后进行按位或
当我们想把下面的红色1,改为0,也就是恢复上面的操作时,我们可这样做
如何得到最后一行数字? 我们对下面这个数字按位取反即可
sizeof 和 数组
size of是操作符,不是函数,size of()的括号可以去除,但是在使用的时候建议加上()
#includevoid test1(int arr[]) { printf("%dn", sizeof(arr));//(2) } void test2(char ch[]) { printf("%dn", sizeof(ch));//(4) } int main() { int arr[10] = { 0 }; char ch[10] = { 0 }; printf("%dn", sizeof(arr));//(1) printf("%dn", sizeof(ch));//(3) test1(arr); test2(ch); return 0; }
++a与a++
关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等
比较字符串是否相等时不能用==,用strcmp函数,头文件是sting.h
逻辑操作符
&& 逻辑与
|| 逻辑或
&& 左边为假,右边就不计算了
|| 左边为真,右边就不计算了
比较字符串是否相等时不能用==,用strcmp函数,头文件是sting.h
&& 逻辑与 || 逻辑或
&& 左边为假,右边就不计算了 || 左边为真,右边就不计算了
条件操作符
exp1 ? exp2 : exp3
逗号表达式
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
下标引用、函数调用和结构成员
[ ] 下标引用操作符
操作数:一个数组名 + 一个索引值
exp1 ? exp2 : exp3
逗号表达式,就是用逗号隔开的多个表达式。 逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
下标引用、函数调用和结构成员
[ ] 下标引用操作符
操作数:一个数组名 + 一个索引值
[ ] 下标引用操作符
操作数:一个数组名 + 一个索引值
arr[7]=7[arr]=*(arr+7)=*(7+arr) ,但是定义数组的时候不能把arr[10]写作10[arr]。
( ) 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名
#include
#include
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 %lfn", ps->name, ps->age, ps->score);
}
int main()
{
struct Stu s = { 0 };
set_stu(&s); //进行传参,这里采取了传地址
print_stu(&s); //同样进行传地址
return 0;
}
访问结构体有俩种形式.和->
表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
隐式类型转换
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。
访问结构体有俩种形式.和->
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
隐式类型转换
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。
我们可以看到5+126结果为-125,这是因为发生了整形提升
所以这里可以看到C输出的是负数。这是因为char类型占一个字节8个比特位,而在运算的时候,CPU一般处理int类型,而int类型占四个字节,32个比特位,此时8个比特位显然不够,编译器会自动补充其余24个比特位,补充的内容为符号位,无符号整形高位补0
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if(a==0xb6)
printf("a");
if(b==0xb600)
printf("b");
if(c==0xb6000000)
printf("c");
return 0; }
代码会打印C,这是因为a,b要进行整形提升,但是c不需要整形提升 a,b整形提升之后,变成了负数
c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字
节.
表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof(c) ,就是1个字节
算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类
型,否则操作就无法进行。下面的层次体系称为寻常算术转换
long double
double
float
unsigned long int
long int
unsigned int
int
优先级自上而下逐渐降低,但存在精度丢失的风险
操作符的属性
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类
型,否则操作就无法进行。下面的层次体系称为寻常算术转换
long double
double
float
unsigned long int
long int
unsigned int
int
优先级自上而下逐渐降低,但存在精度丢失的风险
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级
#includeint main() { int a = 1; int b = 2; int c = 3; int d = 4; int e = 5; int f = 6; int h=a * b + c * d + e * f; printf("%d", h); return 0; }
这种运算方式,只能保证,*的计算是比+早,但是优先级并不 能决定第三个*比第一个+早执行
c + --c;
操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得
知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。(先算--c,然后算加法的时候不能保证C是--c之前的值还是--c之后的值)
#includeint main() { int a = 1; int b = (++a) + (++a) + (++a); printf("%dn", b); return 0; }
不能保证先算哪个括号里面的,还有无法确定先算哪一个+
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %dn", i);
return 0; }
编译器会凌乱
int fun()
{
static int count = 1;
return ++count; }
int main()
{
int answer;
answer = fun() - fun() * fun();
printf( "%dn", answer);//输出多少?
return 0; }
但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法, 再算减法。 函数的调用先后顺序无法通过操作符的优先级确定。



