本章重点
1.各种操作符详解
2.表达式求值
目录
1.操作符
1.1 算术操作符
+ - * / %
1.除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
2.对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
3.% 操作符的两个操作数必须为整数,返回的是整数之后的余数
下面是 / 操作符 和 % 操作符的代码示例
// / 除法
// 1.整数除法 (除号的两端都是整数)
// 2. 浮点数除法 (除号的两端一个是小数就是浮点数除法)
#include <stdio.h>
int main()
{
int r = 7 / 2;//3 整数除法
printf("%d\n", r);//3
double d = 7 / 2;// 3.000000 整数除法
printf("%lf\n", d);// 3.0
double d1 = 7 / 2.0; // 3.5 浮点型除法
printf("%lf\n", d1);
//被除数不能是 0
//int r = 5 / 0; 报错
return 0;
}
// % 取模操作符
#include<stdio.h>
int main()
{
int r = 17 % 8;// % 得到的是整除以后的余数
printf("%d\n", r);
// % 两个操作数必须都是整数
return 0;
}
1.2 移位操作符
<< 左移操作符
>> 右移操作符
移位操作符是对二进制位进行操作,计算机能处理的是二进制的信息。 所以在进行学习移位操作符是,我们先来学习一下整数在内存中的存储方式,即二进制的表示形式。
整数二级制有三种表示形式
1.原码
2.反码
3.补码
1.正整数的原码,反码,补码是相同的。
2.负整数的原码,反码,补码是要计算的。
首先不管是正整数还是负整数都是可以写出二进制原码,根据正负直接写出的二级制序列就是原码
// 1个整形是4个字节 == 32个bit位
// 一个二进制位是一个bit位// 15
// 00000000000000000000000000001111 15的原码
//第一位是符号位 1 是负数 0是整数// -15
//10000000000000000000000000001111 -15的原码
>> 右移操作符
右移操作有两种,
1.算术右移 (二级制右边丢弃,左边补原来的符号位)
2.逻辑右移(二进制右边丢弃,左边直接补0)
C语言没有明确规定到底是算术右移,还是逻辑右移,一般编译器采用的是算术右移,左边补符号位。
反码 由原码符号位不变,其他位按位取反得到
补码 由反码加一得到
整数是以补码的方式在内存中存储的。打印的时候才会以原码的方式打印
示例:
//正整数
#include<stdio.h>
int main()
{
int a = 15;
//00000000000000000000000000001111 - 原码
//正整数原码 反码 补码都相同
//整数在内存中存储的是补码
//计算的时候也是使用补码计算的
int c = a >> 1;//移动的是a中的 2进制 信息 a是不变的
printf("%d\n", c);//7
printf("%d\n", a);//a不变,还是15
//可以发现正整数右移有除2的效果
return 0;
}
//负整数
#include<stdio.h>
int main()
{
int a = -15;
//10000000000000000000000000001111 - 原码
//11111111111111111111111111110000 - 反码 (原码的符号位不变,其他位按位取反得到的就是反码)
//11111111111111111111111111110001 - 补码 (反码+1)
int b = a >> 1;
//11111111111111111111111111110001 -15补码
//11111111111111111111111111111000 右移 左边补符号位
//11111111111111111111111111110111 反码 (补码-1)
//10000000000000000000000000001000 原码 -8
printf("%d\n", a);//a不变 还是-15
printf("%d\n", b);//-8
//通过运行,可以发现VS采用的逻辑右移
return 0;
}
<< 左移操作符
左移操作符 比较简单,规则是左边抛弃,右边补0
示例:
#include<stdio.h>
int main()
{
int a = 7;
//整数原码反码补码相同
//00000000000000000000000000000110
//左移 也是操作补码 左边丢弃 右边补0
int b = a << 1;
//00000000000000000000000000001100 //12
printf("%d\n", a);
printf("%d\n", b);
//正整数负整数左移1位都有乘二效果
//C语言只能是移正数位
//a = a << 1; 等价于 a <<= 1;
//a = a + 1; 等价于 a += 1;
return 0;
}
注意:对于移位运算符,不要移动符号位,这时C语言标准未定义的。例如:
int num = 10;
num>>-1; // error
1.3 位操作符
& //按位与
| //按位或
^ //按位异或
注:他们的操作数必须是整数,操作的是二进制位,补码
示例:
#include<stdio.h>
int main()
{
int a = 3;
//00000000000000000000000000000011
int b = -5;
//10000000000000000000000000000101 原码
//11111111111111111111111111111010 反码
//11111111111111111111111111111011 补码
int c = a & b;
//& -- 按(二进制)位与 -- 对应二进制位有0则有0,两个同时为1 才为1
//00000000000000000000000000000011 3
//11111111111111111111111111111011 -5 补码
//& 按位与
//00000000000000000000000000000011 3
printf("%d\n", c); //3
c = a | b;
// | -- 按(2进制)位或 -- 对应的二进制位有1则为1,两个同时为0才是0
//00000000000000000000000000000011 3
//11111111111111111111111111111011 -5 补码
// | 按位或
//11111111111111111111111111111011 这是补码
//11111111111111111111111111111010 反码 (补码-1)
//10000000000000000000000000000101 原码 (反码除符号位按位取反)
printf("%d\n", c);//-5
c = a ^ b;
// ^ 按(二进制位)位异或 -- 对应的二进制位相同为0,相异为1
//00000000000000000000000000000011 3
//11111111111111111111111111111011 -5 补码
// ^ 按位异或
//11111111111111111111111111111000 这是补码
//11111111111111111111111111110111 反码 (补码-1)
//10000000000000000000000000001000 原码 (反码除符号位按位取反)
printf("%d\n", c);//-8
return 0;
}
异或的应用:一道面试题,不创建临时变量,交换两个变量的值
//不创建临时变量 交换两个整型变量
//方法一
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("交换前:a = %d b = %d\n", a, b);
a = a + b;
b = a - b;
a = a - b;
printf("交换后:a = %d b = %d\n", a, b);
return 0;
}
//假设a特别大 b 特别大 a+b 可能超过int 大小 会发生截断,下面有更好的方法
//方法二
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("交换前:a = %d b = %d\n", a, b);
a = a ^ b;
b = a ^ b; //a^b^b == a
a = a ^ b; //a^b^a == b
printf("交换后:a = %d b = %d\n", a, b);
return 0;
}
#include<stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("%d\n", a ^ b ^ a);
printf("%d\n", a ^ a ^ b);
//a^a -> 0;
//a^0 -> a;
//a^b^a -> b
//a^a^b -> b
//异或是支持交换率的
return 0;
}
1.4 赋值操作符
=
赋值操作符是一个很棒的操作符,它可以让你得到一个你之前不满意的值,也就是你可以给自己重新赋值。
#include<stdio.h>
int main()
{
int a = 10;
int x = 0;
int y = 20;
//连续赋值
a = x = y + 1;
//将y+1的值赋给x,再赋给a
x = y + 1;
a = x;
//这种写法更加清晰
return 0;
}
复合赋值
+= -= *= /= %= >>= <<= &= |= ^=
这些运算符都可以写成复合的效果,比如
int x = 10;
x = x + 10;
x += 10;//与上面一行相同 ,复合赋值
//其他运算符是一样的道理
1.5 单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
!逻辑反操作
#include<stdio.h>
int main()
{
int flag = 0;
if (flag == 0)
{
printf("hehe\n");
}
if (!flag)//flag 为假进行
{
printf("hehe\n");
}
if (flag)//flag 为真进行
{
printf("haha\n");
}
return 0;
}
- 负值 + 正值
#include<stdio.h>
int main()
{
int a = 5;//+ 可以省略不写
int b = -a; //-5
return 0;
}
& * 应用于指针
#include<stdio.h>
int main()
{
int a = 10;
//这里 * 是为了说明pa是指针变量 不是操作符
int* pa = &a;//&-取地址操作符 取出a的地址
// * 解引用操作符 (间接访问操作符) 单目操作符
*pa = 20;// 通过pa中存放的地址,找到指向的空间(内容)
int c = *pa;
printf("%d\n", c);//20;
return 0;
}
sizeof 操作符 不是函数
sizeof计算的是类型创建变量的大小 单位是字节
#include<stdio.h>
int main()
{
int a = 10;
printf("%d\n", sizeof(int));//4
printf("%d\n", sizeof(a));//4
printf("%d\n", sizeof a);
//sizeof(变量) 变量的括号可以省略 可以说明sizeof不是函数
//因为函数调用时()不可以省略
return 0;
}
sizeof 计算数组大小
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%d\n", sizeof(arr));//40计算整个数组的大小 单位字节
printf("%d\n", sizeof(int[10]));//去掉名字 就是数组类型 还是40
return 0;
}
~ 按(二进制)位取反
#include<stdio.h>
int main()
{
int a = 0;
printf("%d\n", ~a);//-1
//00000000000000000000000000000000
// ~ 按位取反 补码按位取反
//11111111111111111111111111111111 补码
//11111111111111111111111111111110 反码 补码-1
//10000000000000000000000000000001 原码 -1
return 0;
}
~ 在多组输入时的应用
#include<stdio.h>
int main()
{
int a = 0;
//1.
//scanf读取失败返回 EOF
while (scanf("%d", &a) == 1)
{
printf("%d\n", a);
}
//2.
while (scanf("%d", &a) != EOF)
{
printf("%d\n", a);
}
//3.
//假设读取失败 返回的是EOF ----> -1
//10000000000000000000000000000001 -1原码
//11111111111111111111111111111110 -1反码
//11111111111111111111111111111111 -1补码
//~-1
//00000000000000000000000000000000 0 为假
while (~scanf("%d", &a))
{
printf("%d\n", a);
}
return 0;
}
-- 前置 后置--
++前置 后置++
#include<stdio.h>
int main()
{
int a = 1;
int b = a++;//后置++, 先使用,后++
printf("a = %d b = %d\n", a, b);//2 1
b = a--;
printf("a = %d b = %d\n", a, b);//1 2
return 0;
}
#include<stdio.h>
int main()
{
int a = 1;
int b = ++a;//后置++, 先使用,后++
printf("a = %d b = %d\n", a, b);//2 2
b = --a;
printf("a = %d b = %d\n", a, b);//1 1
return 0;
}
#include<stdio.h>
int main()
{
int a = 10;
printf("%d\n", a++);//10
printf("%d\n", a);//11
return 0;
}
() 强制类型转换
#include<stdio.h>
int main()
{
//3.14是浮点型
int a = (int)3.14;//强制类型转换 只保留 整数部分
printf("%d\n", a);//3
return 0;
}
sizeof和数组
#include<stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//int * 指针的大小 8/4
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//char * 指针的大小 8/4 不要以为是1
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(ch));
test1(arr);
test2(ch);
return 0;
}
1.6 关系操作符
> | |
>= | |
< | |
<= | |
!= | 用于测试“不相等” |
== | 用于测试”相等“ |
关系运算符比较简单,但要注意的是它们只能用到合适的类型上
比如,不能用于字符串比较,因为字符串比较有特定的函数。整形可以跟整形比较。
1.7 逻辑操作符
&& | 逻辑与 并且 两个条件都要满足才为真 |
|| | 逻辑或 或者 一个条件满足就为真 |
360笔试题
#include<stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = (a++ && ++b && d++); //当计算完a为假 不在向后计算 i是0
printf("a=%d\nb=%d\nc=%d\nd=%d\n", a, b, c, d);//1 2 3 4
return 0;
}
注意:
|| 逻辑与操作符的左边为真,右边不再计算 为真
&& 逻辑或操作符的左边为假,右边不再计算 为假
逻辑操作符如果计算结果是真,用1表示,假用0表示
1.8 条件操作符
条件操作符又称三目操作符,有三个操作数
exp1 ?exp2 : exp3
当exp1为真时,计算exp2,否则计算exp3
示例:
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
int max = 0;
scanf("%d %d", &a, &b);
if (a > b)
max = a;
else
max = b;
printf("%d\n", max);
//与下面等价
max = (a > b ? a : b);
printf("%d\n", max);
return 0;
}
1.9 逗号表达式
exp1,exp2,exp3,...expN
逗号表达式,就是用逗号隔开的多个表达式。逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
printf("%d\n", c);//13
return 0;
}
1.10 下标引用,函数调用和结构成员
[ ] 下标引用操作符
操作数:一个数组名+一个索引值
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5 };
//下标 0 1 2 3 4
//数组是有起始下标的,下标是0开始的
printf("%d\n", arr[2]);//[] 下标引用操作符,arr和2是[]的两个操作数
return 0;
}
() 函数调用操作符
可以接受一个或多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数
#include<stdio.h>
#include<string.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int len = strlen("abc");//() 函数调用操作符
// () 的操作数是 :strlen 和"abc" 函数名和参数
printf("%d\n", len);
int c = Add(3, 5);
//Add 3 5 函数调用操作符
printf("%d", c);
return 0;
}
printf() 函数的参数是可变参数列表,参数的个数是可以变化的。
cplusplus网站上对于printf()函数的介绍:
访问结构体成员操作符
结构体用来定义复杂数据类型,是自定义类型
而 char short int long float double 等都是内置类型
. 结构体.成员名
-> 结构体指针->成员名
示例:
struct Book//书结构体
{
char name[30];//书名
char author[20];//作者
float price;//价格
};
#include<stdio.h>
void Print(struct Book* p)
{
// . 结构体.成员名
printf("%s %s %.2f\n", (*p).name, (*p).author, (*p).price);
//-> 结构体指针->成员名
printf("%s %s %.2f\n", p->name, p->author, p->price);
}
int main()
{
struct Book b1 = { "C语言详解","西兰花",66.66f };
struct Book b2 = { "数据结构","西兰花",88.88f };
printf("%s %s %.2f\n", b1.name, b1.author, b1.price);
printf("%s %s %.2f\n", b2.name, b2.author, b2.price);
Print(&b1);
Print(&b2);
return 0;
}
2.表达式求值
表达式求值的顺序一部分是由操作符的优先性和结合性决定的
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型
2.1 隐式类型转换
2.1.1 整型提升
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整形提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU ( general-purpose CPU )是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
有符号整型提升,高位补符号位
无符号整型提升,高位直接补0
示例:
#include<stdio.h>
int main()
{
char c1 = 5;
//00000000000000000000000000000101 // 5整形 存到char中 发生截断
//00000101
char c2 = 127;
//00000000000000000000000001111111 //截断
//01111111
char c3 = c1 + c2;
//00000000000000000000000000000101 //c1 补码
//00000000000000000000000001111111 //c2 补码
//00000000000000000000000010000100 补码 c3 截断
//10000100 阶段后的c3
// 以%d打印整型提升 补码高位补符号位
//11111111111111111111111110000100 补码
//11111111111111111111111110000011 反码
//10000000000000000000000001111100 原码
printf("%d\n", c3);//-124
return 0;
}
证明存在整型提升
#include<stdio.h>
int main()
{
char a = 0xb6;//10110110 一个16进制数是4个比特位 b6刚好8个bit位,一个字节可以放下
//截断后符号位是1 整型提升后是负数
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;
}
只要参与表达式运算就会整型提升:
#include<stdio.h>
//%d 十进制的形式打印有符号的数
//%u 十进制的形式打印无符号的数
//%u 输出无符号整形数 因为sizeof是返回的无符号数
int main()
{
char c = 1;
printf("%u\n", sizeof(c));//1
printf("%u\n", sizeof(+c));//发生运算后整型提升 4
printf("%u\n", sizeof(-c));//发生运算后整型提升 4
return 0;
}
2.1.2 算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。
2.2 操作符的属性
复杂表达式的求值有三个影响的因素。
1.操作符的优先级
2.操作符的结合性3.是否控制求值顺序 , && || (?:) 四个操作符控制
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性
优先级 |
( ) 括号 |
( ) 函数调用 |
[ ] 下标引用 |
访问结构体操作符 |
单目操作符 |
算术操作符 |
移位操作符 |
关系操作符 |
位操作符 |
逻辑操作符 |
三目操作符符 |
赋值操作符 |
逗号 |
总结∶我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个
表达式就是存在问题的。