目录
4.2.位操作符示例(※)
6.单目操作符(※)
8.2.练习题(※)
1.操作符分类
算术操作符 (+ - * / %)移位操作符位操作符赋值操作符单目操作符关系操作符逻辑操作符条件操作符逗号表达式下标引用、函数调用和结构成员
2. 算术操作符
+ - * / %
注:
1.当 / 两端都是整数的时候,执行的是整数除法,两端只要有一个浮点数,执行的就是浮点数除法。
2.% 操作符的两个操作数必须为整数。返回的是整除之后的余数。(除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数)
3. 移位操作符
<< 左移操作符(二进制补码)>> 右移操作符(二进制补码)注:移位操作符的操作数只能是整数(不能是负整数,定义负整数会报错为标准未定义)。
3.1.补充内容
1.整数有3种二进制表示形式:原码、反码、补码。
正整数原码、反码、补码相同。
负整数原码、反码、补码不同,要进行计算。
2.
整数5原码:00000000000000000000000000000101
整数5反码:00000000000000000000000000000101
整数5补码:00000000000000000000000000000101
整数-5原码:10000000000000000000000000000101
整数-5反码:11111111111111111111111111111010(原码的符号位不变,其它位按位取反)
整数-5补码:11111111111111111111111111111011(反码的二进制+1)
3.整数在内存中存储的是补码,如下图,-1在内存中的存储应该位-1的补码(-1的补码为32个1) ,下图是8个f因为vs编译器在内存窗口是16进制展示的
3.2.左移操作符
1.正整数左移的例子
注:
1.a=5的补码为:00000000000000000000000000000101
b=a<<2的补码: 00000000000000000000000000010100 十进制为20
2.<<是一种运算,这里面不会真的把a进行改变。
2.负整数左移的例子
注:
1. a=-5的原码:10000000000000000000000000000101
a=-5的补码为:11111111111111111111111111111011
b=a<<2的补码:11111111111111111111111111101100
b=a<<2的反码:11111111111111111111111111101011
b=a<<2的原码:100000000000000000000000010100 十进制为-20
2.打印或者使用的时候,用的是原码的值(内存中存的是补码,运算的时候用补码运算,打印或者使用的时候,用的是原码)
3.3.右移操作符
右移操作符:
1.算数右移(右边丢弃,左边补原来的符号位)
2.逻辑右移(右边丢弃,左边补0)
到底是算数右移还是逻辑右移取决于编译器
我们常见的编译器下都是算数右移
1.正整数右移的例子
注:
1.a=5的补码为:00000000000000000000000000000101
b=>>1的补码: 00000000000000000000000000000010 十进制为2
2.可以看出对于正整数来说,右移的逻辑运算和算数运算相等。
2.负整数右移的例子
注:
a=-5的原码:10000000000000000000000000000101
a=-5的反码:11111111111111111111111111111010
a=-5的补码:11111111111111111111111111111011
b=a>>1的补码:11111111111111111111111111111101 (算数运算右移左边补1,逻辑运算右移左 b= a>>1的反码:11111111111111111111111111111100 边补0,vs编译器采用算数右移)
b= a>>1的原码:10000000000000000000000000000011 十进制为-3
4. 位操作符
4.1.三个位操作符介绍
& 按(2进制补码)位与:有0就是0,同时为1才为1| 按(2进制补码)位或:有1则为1,同时为0才为0^ 按(2进制补码)位异或:相同为0,相异为1注:他们的操作数必须是整数。
按位或代码示例:
按位异或代码示例:
注:
-5的原码:10000000000000000000000000000101
-5的反码:11111111111111111111111111111010
-5的补码:11111111111111111111111111111011
3的补码:00000000000000000000000000000011
3&-5结果的补码:00000000000000000000000000000011
3&-5结果的原码:00000000000000000000000000000011 该值为3 (正数原码反码补码相同)
3|-5结果的补码:11111111111111111111111111111011
3|-5结果的反码:11111111111111111111111111111010
3|-5结果的原码:10000000000000000000000000000101 该值为-5
3^-5结果的补码:11111111111111111111111111111000
3^-5结果的反码:11111111111111111111111111110111
3^-5结果的原码:10000000000000000000000000001000 该值为-8
4.2.位操作符示例(※)
描述:不能创建临时变量(第三个变量),实现两个数的交换
方法一:(相加的方法)(相加有溢出的问题)(可交换浮点数)
方法二:(异或的方法)(无溢出问题)(不能交换浮点数)
注:
1.异或操作符的特点:相同数字进行异或结果为0,0和任何数字异或结果为该数字
2.该异或方法只能针对整数进行交换,异或操作符不能对浮点数操作。
3.在实际开发中交换两个数还是以创建第三个变量为主,因为以上两种方法各有缺点。
5.赋值操作符
5.1.赋值操作符
=
int a = 10;
int x = 0;
int y = 20;
a = x = y+1; //连续赋值 x=21 a=21
知识点二:
注:左值是可以放在等号左边的,一般是一块空间
右值是可以放在等号右边的,一般是一个值,或者一块空间
5.2.复合赋值操作符
+=-=*=/=%=>>=<<=&=|=^=
6.单目操作符
! 逻辑反操作- 负值+ 正值& 取地址sizeof 操作数的类型长度(以字节为单位)~ 对一个数的二进制(补码的二进制位)按位取反-- 前置、后置 --++ 前置、后置 ++* 间接访问操作符 ( 解引用操作符 )( 类型 ) 强制类型转换注:单目操作符就是只有一个操作数
![](https://img-blog.csdnimg.cn/7ee52137a063451f9e416cefd456decd.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6ZqP6aOO5byg5bmU,size_20,color_FFFFFF,t_70,g_se,x_16)
注:
1.不管数组初始化了几个元素,数组占用内存只与[ ]内的数即申请空间数有关
2.和int a中a的类型为int一样,数组也是有类型的int arr[10]中arr数组的类型就是int [10]
3.sizeof操作内容若为变量,圆括号可以省略(不建议),操作内容若为数据类型(如int),圆括号不能省略
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr)); //32位(x86):4 64位(x64):8
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch)); //32位(x86):4 64位(x64):8
}
int main()
{
int arr[10] = {0};
char ch[10] = {0};
printf("%d\n", sizeof(arr)); // 40
printf("%d\n", sizeof(ch)); // 10
test1(arr);
test2(ch);
return 0; }
注:
1.在主函数中,sizeof()内部单独放一个数组名,数组名表示整个数组。
2.在定义的函数中,sizeof()内部单独放一个数组名,此时数组名表示数组首元素地址。
注:
1.a+4的结果是整型,s是个短整型,赋值给谁类型谁说了算,因此a+4结果进行截断然后放入短整型s里面。
2.sizeof括号内部的表达式是不去真实计算的。(sizeof只关注并提取的是数据的类型,不会去进行真实的数值计算)
知识点2(~):
注:
a=0的补码:00000000000000000000000000000000
~a的补码:11111111111111111111111111111111
将10的二进制位:00000000000000000000000000001010中倒数第三个0改成1再将得到的00000000000000000000000000001110倒数第三个1改成0
![](https://img-blog.csdnimg.cn/bfbf2df518504addb1cac60e93061676.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6ZqP6aOO5byg5bmU,size_14,color_FFFFFF,t_70,g_se,x_16)
00000000000000000000000000001010 数值为10
知识点4:
#include <stdio.h>
int main()
{
int a = -10;
int *p = NULL;
printf("%d\n", !2); //0
printf("%d\n", !0); //1
a = -a; //10
p = &a;
return 0;
}
注:
1.!为逻辑反操作,将非0转换成0,将0转换成1(1是规定死的)。
2.NULL是空指针的意思,用于指针变量初始化。
7.关系操作符
>>=<<=!= 用于测试 “ 不相等 ”== 用于测试 “ 相等 ”
8.逻辑操作符
8.1.逻辑操作符
&& 逻辑与|| 逻辑或
注:
1.区分逻辑与(&&)逻辑或(||)和按位与(&)按位或(|)的区别。按位与和按位或是针对二进制位的,逻辑与和逻辑或只关注真假。
2.对于逻辑与来说,若左操作表达式逻辑为假(0),将不再计算右操作表达式。
3.对于逻辑或来说,若左操作表达式逻辑为真(非零),将不再计算右操作表达式。
8.2.练习题(※)
代码1:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
//程序输出的结果是什么?
答案:
代码2:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include <stdio.h>
int main()
{
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
//程序输出的结果是什么?
答案:
代码3:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include <stdio.h>
int main()
{
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
//程序输出的结果是什么?
答案:
代码4:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
return 0;
}
//程序输出的结果是什么?
答案:
注:
代码1中a=0,a++是先使用后++,因此在逻辑与看来该表达式为0,后面不再计算。
代码2中a=1,a++是先使用后++,因此在逻辑与看来该表达式为1,后面进行计算;后面表达式结果均为真(非零),因此全部计算完。
代码3中a=1,a++是先使用后++,因此在逻辑或看来该表达式为1,后面不再计算。
代码4中a=0,a++是先使用后++,因此在逻辑或看来该表达式为0,后面进行计算;后面++b是先++再使用,此时在逻辑或看来该表达式为1,后面不再计算。
9. 条件操作符(三目操作符)
表达式1 ? 表达式2 : 表达式3表达式1为真,整个表达式结果为表达式2计算结果表达式1为假,整个表达式结果为表达式3计算结果
10.逗号表达式
表达式1, 表达式2, 表达式3, …表达式n
逗号表达式,就是用逗号隔开的多个表达式。逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果
代码2:
if (a =b + 1, c=a / 2, d > 0)
代码3:(逗号表达式可将代码变得更简洁)
a = get_val();
count_val(a);
while (a > 0)
{
//业务处理
a = get_val();
count_val(a);
}
如果使用逗号表达式,改写:
while (a = get_val(), count_val(a), a>0)
{
//业务处理
}
11. 下标引用、函数调用和结构成员
11.1.下标引用操作符
[ ]
注:
1.下标引用操作符([ ])的操作数不定,在上例中操作数是arr和7
2.arr[7]其实编译器是先编译成*(arr+7)再进行计算的,如下代码即可证明:
(下面7[arr]类似,编译器翻译成*(7+arr)因此两者输出结果相同)
11.2.函数调用操作符
( )
注:
1.函数调用操作符的操作数不定,在上例中Add后面的函数调用操作符操作数是Add、2和3;test后面的函数调用操作符操作数只有test
11.3.结构成员访问操作符
. 结构体.成员名-> 结构体指针->成员名
注:
1. .操作符左边的操作数是结构体变量,右边的操作数是结构体成员
2.->操作符左边的操作数是结构体指针,右边的操作数是结构体成员
12.表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
12.1.隐式类型转换(整型提升)
涉及范围:(4字节以内的)
char
short
12.1.1.整型提升
C 的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为 整型 提升 。整形提升是按照变量的数据类型的符号位来提升的(若是无符号的类型,那么直接补0)
整型提升的意义 :表达式的整型运算要在 CPU 的相应运算器件内执行, CPU 内整型运算器 (ALU) 的操作数的字节长度一般就是int 的字节长度,同时也是 CPU 的通用寄存器的长度。因此,即使两个 char 类型的相加,在 CPU 执行时实际上也要先转换为 CPU 内整型操作数的标准长度。通用 CPU ( general-purpose CPU )是难以直接实现两个 8 比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int 长度的整型值,都必须先转换为int 或 unsigned int ,然后才能送入 CPU 去执行运算。
计算机运算的过程详解:
1..char a=5 整数5是个整型,占四个字节32比特,要写出来的话应该是00000000000000000000000000000101,而a是字符类型占一个字节8比特,所以它只能存低八个比特位00000101,这叫做截断
2..char b=126 整数126是个整型,占四个字节32比特,要写出来的话应该是00000000000000000000000001111110,而b是字符类型占一个字节8比特,所以它只能存低八个比特位01111110
3.a和b都是char类型,当a和b相加的时候,表达式计算的时候就要发生整型提升。整形提升是按照变量的数据类型的符号位来提升的(符号位为1前面全补1,符号位为0前面全补0)。
此时a里面存的是00000101,b里面存的是01111110,char是有符号的其符号位是最高位。
a里面00000101的符号位为0,因此前面全补0为00000000000000000000000000000101
b里面01111110的符号位为0,因此前面全补0为00000000000000000000000001111110
4.此时a+b最终算出的结果为00000000000000000000000010000011,而c变量是char类型只有8个比特位,因此c的值为10000011
5.打印的时候是以%d打印的,即打印一个整数,而c是char类型,所以要发生整型提升
c提升的结果为11111111111111111111111110000011
这里的结果是在内存中的,所以是补码,打印的时候打印的应该是其原码,所以
补码:11111111111111111111111110000011
反码:11111111111111111111111110000010
原码:10000000000000000000000001111101 数值为 -125
所以打印出来的值为-125
12.1.2.整型提升的例子
例1:
注:
1.0xb6是整型放在字符型a里面需要截断,截断后a里面存的值和0xb6不一样了,在a==0xb6时,a再进行整型提升,提升后的数值不再是0xb6。b和a是同理的
2.0xb6000000是整型放在整型a里面,不需要截断,后面在c==0xb6000000时也不需要整型提升,因此是一样的
例2:
注:+c和-c其实是参与运算了,因此这里发生了整型提升,提升后便是四个字节。
12.2. 算术转换
涉及范围:(4字节及4字节以上的)(下表由下往上进行转换)
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。上面的层次体系称为寻常算术转换 。
12.3.操作符的属性
12.3.1.操作符的属性
复杂表达式的求值有三个影响的因素。1. 操作符的优先级2. 操作符的结合性3. 是否控制求值顺序。两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
下表中,从上往下优先级依次降低,结合性中N/A代表无结合性,L-R代表从左向右结合,R-L代表从右向左结合。
12.3.2.一些问题表达式
我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。
问题表达式1:
a*b + c*d + e*f
a
*
b
c
*
d
a
*
b
+
c
*
d
e
*
f
a
*
b
+
c
*
d
+
e
*
f
|
a
*
b
c
*
d
e
*
f
a
*
b
+
c
*
d
a
*
b
+
c
*
d
+
e
*
f
|
问题表达式2:
int c = 5;
c + --c;
5+4=9 | 4+4=8 |
问题表达式3:
int main()
{
int i = 10;
i = i-- - --i * ( i = -3 ) * i++ + ++i;
printf("i = %d\n", i);
return 0;
}
注:该表达式在不同编译器下测试的结果完全不同。
问题表达式4:
int fun()
{
static int count = 1;
return ++count;
}
int main()
{
int answer;
answer = fun() - fun() * fun();
printf( "%d\n", answer);
return 0;
}
注:上述代码我们只能通过操作符的优先级得知:先算乘法,再算减法。函数的调用先后顺序无法通过操作符的优先级确定。
问题表达式5:
#include <stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);
printf("%d\n", i);
return 0;
}
该代码vs编译是 12 4,linux编译是10 4
注:在一行行的调试中,无法看到某一表达式详细的计算过程,此时我们可以在调试界面点击右键,进行反汇编操作,如下图所示
1.常见的寄存器有:eax,ebx,ecx,edx,ebp,esp。其中ebp,esp存放的是地址,因此里面的ebp-8是一个地址,该地址就是i的地址,如下图
2.下图前九个步骤每三个步骤完成了一次++i,后四行步骤的前三行执行三个++i之和,最后一行的ebp-14h就是ret的地址,将该值赋值给ret。
因此vs编译器是直接先进行三次++i,此时i为4,然后再相加得到12