第三课.运算符与表达式

运算符与表达式

数学是科技发展的基础,数学公式的意义十分重要,比如改变世界的欧拉方程:
e π i + 1 = 0 e^{\pi i}+1=0 eπi+1=0
为了用机器创造世界,就需要机器能够表达公式;
运算符
运算符是编译器可识别并执行特定数学或逻辑操作的符号,C++内置丰富的运算符,并提供了以下类型的运算符:
1.算术运算符;
2.关系运算符;
3.逻辑运算符;
4.位运算符;
5.赋值运算符;
6.杂项运算符;
表达式
在程序中,运算符用于操作数据,数据被称为操作数,使用运算符将操作数连接而成的式子称为表达式;
表达式具有以下特点:
1.常量与变量都是表达式,比如,常量3.14,变量i;
2.运算符的类型对应表达式的类型,比如算术运算符对应算术表达式;
3.就像python中的表达式,每个C++表达式都有返回值,即表达式有运算结果;

算术运算符

有两个变量A和B:

int A=10;
int B=20;

有以下操作:

需要两个操作数才能完成:
+ 两个数相加
/ 分子除以分母,两个整型数得到整型,若含有浮点型,则结果为浮点型
% 取余数,两个数必须为整型

只要一个操作数就能完成(单目运算):
++ 自增运算,整数值增加1,++可以在操作数的前或者后
-- 自减运算,整数值减少1,--可以在操作数的前或者后
但++--位于操作数前后会有微小的差异,差异放到后期的面向对象阐述
//前置
std::cout << ++A << endl;
输出11
//后置
std::cout << A++ << endl;
输出10

关系运算符

关系运算符如下:

== 检查两个操作数的值是否相等,相等则为true,返回1,否则false返回0
!= 检查两个操作数的值是否不等,不等则为true
> 检查左操作数是否大于右操作数
< 检查左操作数是否小于右操作数

同理还有
>=<=

C++与python相比,关系运算的写法也更加严格,关系运算的表达式需要用括号包围,示例:

std::cout << (A == B) << endl;
运算结果为false,false是一个表达式,值为0,所以输出0

逻辑运算符

假设变量A的值为1,变量B的值为0,(用布尔型存储可以节省空间),存在以下逻辑运算:

&& 逻辑与,如果两个操作数都非0,则条件为真,返回1
|| 逻辑或,如果有一个操作数非0,则条件为真,返回1

//单目运算,可以不用括号包围
! 逻辑非,条件为真的逻辑表达式会转为假

在圣经中,有一句话:
To be or not to be,that’s a question
现在可以通过机器获得答案:

(A==true||A!=true)

德摩根律

A ∪ B ‾ = A ‾ ∩ B ‾ , A ∩ B ‾ = A ‾ ∪ B ‾ \overline{A\cup B}=\overline{A}\cap \overline{B},\overline{A\cap B}=\overline{A}\cup \overline{B} AB=AB,AB=AB
通过C++可以描述为:

(!(A||B)==(!A&&!B)) //1
(!(A&&B)==(!A||!B)) //1

使用断言assert

在开发测试环节,常常会用到断言assert判断样例是否能被正确处理:

#include <assert.h>

int main()
{
    bool A = true;//值为1
    bool B = false;//值为0
    //使用断言验证 德摩根律
    //断言1
    assert( (!(A || B) == (!A && !B)) );
    //断言2
    assert( ((A || B) == (!A && !B)) );
    return 0;
}

断言1是德摩根律,断言2不是德摩根律,在执行到断言1时,不会出错,执行到断言2会报错:
fig1
只有当断言assert()内的表达式为true(值为1)才会顺利执行,否则(表达式返回false,值为0)就会报错

位运算符

位运算符作用于位,并逐位执行操作,真值表如下:

p  q  p&q  p|q  p^q (异或)
0  0   0    0    0
0  1   0    1    1
1  0   0    1    1
1  1   1    1    0

位运算是用于操作数的bit逐位运算,逻辑运算的操作对象是布尔类型;
位运算符还有:

~ 取反
<< 左移
>> 右移

位运算符&,|,^属于双目运算符,其结合性都是从左到右,优先级高于逻辑运算符,低于关系运算符;
同一层的优先级:&>^>|;
优先级最好通过括号确定更不容易出错
实例:

int a=10;
int b=20;

cout<<(a&b)<<endl; //01010&10100=00000=>0
cout<<(a|b)<<endl; //01010|10100=11110=>30
cout<<(a^b)<<endl; //01010^10100=11110=>30
//与补码相关
cout<<(~a)<<endl; //~ 0000 0000 0000 1010=1111 1111 1111 0101=>-11
cout<<(a<<2)<<endl; //00001010<<2=00101000=>40
cout<<(a>>2)<<endl; //00001010>>2=00000010=>2

在取反操作上,反而得到一个负数,这与补码相关,因为C++内部数值以补码表达;除此之外,移位运算也与有无符号数相关,所以还需要补充补码与有无符号数相关的内容;

补码

在电路设计上,机器只能做加法不能做减法,所以需要找到一种方法(补码)让机器的加法实现减法;
机器数
一个数在机器中以二进制表示,机器数是带符号的,在机器中,用一个数的最高位保存符号位,正数为0,负数为1:
fig2以上的两个整型数,默认4个字节,即32位;
真值
真值是真正数学意义上的数值,由于机器数第一位是符号位,所以机器数的形式值不等于真值;
无符号数的补码
无符号数补码就是平时接触的二进制十进制转换:
b ( 1011 ) = 1 × 2 3 + 0 × 2 2 + 1 × 2 1 + 1 × 2 0 = 11 b(1011)=1\times 2^{3}+0\times 2^{2}+1\times 2^{1}+1\times 2^{0}=11 b(1011)=1×23+0×22+1×21+1×20=11
有符号数的补码
对于长度为 w w w的有符号数的补码,转为对应数值的计算为:
− x w − 1 2 w − 1 + ∑ i = 0 w − 2 x i 2 i -x_{w-1}2^{w-1}+\sum_{i=0}^{w-2}x_{i}2^{i} xw12w1+i=0w2xi2i
比如:
b ( 1011 ) = − 1 × 2 3 + 0 × 2 2 + 1 × 2 1 + 1 × 2 0 = − 5 b(1011)=-1\times 2^{3}+0\times 2^{2}+1\times 2^{1}+1\times 2^{0}=-5 b(1011)=1×23+0×22+1×21+1×20=5


可以发现,无符号数的补码等于原码;
有符号数的正数,其补码等于原码;如果是负数,补码等于原码取反再加一;


因为正数的补码等于原码,负数的补码则不同,正数相加还是正数,正数加负数即为减法,得到结果还是补码,而这个补码正好对应差的真值,这就是机器只保存补码的原因
补码对应的数值范围如下:
fig3U代表无符号,T代表有符号
补充:字节序
32位机器用32bit(32字长)即4byte作为一个字,8byte就称为双字,字是CPU一次性处理的单元,而一个字在内存中如何以byte存放?
假设现在有一个字的内容是整型数0x01234567,这个字的4个byte将被连续存于存储器的0x100,0x101,0x102和0x103的位置(存储器的单元是字节);
字节序即为多字节对象存储在内存中的字节顺序,有两种不同的存储方案:大端法和小端法;
1.大端法:最高有效字节在最前面的方式称为大端法,用于大多数IBM机器,internet传输
fig42.小端法:最低有效字节在最前面的方式成为小端法,用于Intel兼容的机器
fig5观察机器数
观察以下机器数:

int i1=0;
int i2=-1;
int i3=-2147483648; //32字长的最小有符号整型数
int i4=2147483647; //32字长的最大有符号整型数

unsigned int u1=0;
unsigned int u2=4294967295; //32字长的最大无符号整型数
unsigned int u3=2147483648;
unsigned int u4=2147483647;

cout << &i3 << endl;

在调试阶段,根据i3地址(0x0077F9A4)找到i3:
fig6i3=-2147483648,补码即0x8000 0000;
上图的visual studio设置为每行显示两字节,可以看出我的机器上字排序为小端法,因为我的机器是英特尔机器,英特尔机器都是小端法;
另外也反映了,机器内保存的数都是补码形式;

补码与位运算

验证真值
定义以下函数:

//二进制转无符号整型
unsigned int btou(unsigned int num)
{
	return (unsigned int)(num);
}
//二进制转有符号整型
int btot(int num)
{
	return (int)(num);
}

验证真值,体现有无符号数的区别:

    cout << btou(0xFFFFFFFF) << endl;
    cout << btot(0xFFFFFFFF) << endl;

输出分别为:
fig7另外补充一点,补码的编码与真实值的关系如下:
fig8

这是一个分段函数,每一段呈正比关系;

回顾之前的按位取反:

int a=10;
cout<<(~a)<<endl; //~ 0000 0000 0000 1010=1111 1111 1111 0101=> -11

现在进行探索:

int a = 10;
int b = ~a;

cout << &a << endl;
cout << &b << endl;

根据a的地址0x003CFDA0找到a:

0x003CFDA0  0a 00  ..
0x003CFDA2  00 00  ..

由于机器是英特尔架构,所以实际这个int的4字节(1个字)值为:0000 000a,这是补码,但a是正数,补码等于原码,所以a的值确实是10;
b的地址是0x003CFD94,找到b:

0x003CFD94  f5 ff  ?.
0x003CFD96  ff ff  ..

b的补码为FFFF FFF5,二进制形式为:

1111 1111 1111 0101

容易看出b的补码确实是a的补码按位取反,通过验证真值部分实现的函数int btot(int num),得到这个补码对应的有符号值为-11;现在就能清晰解释为什么对10取反会得到-11这样的奇怪结果了;
移位运算补充
左移运算情况单一:
fig9右移运算分两种情况,一个是逻辑右移,一个是算术右移;
逻辑右移:移走的位填充为0
fig10算术右移:移走的位填充与符号位有关,负数填充1
fig11对于有符号数,尽量不要使用右移运算,因为到底是逻辑右移还是算术右移完全取决于编译器的判断;

赋值运算符

赋值运算符有如下几种常用形式:

= 简单的赋值运算,右边操作数的值赋给左边操作数
+= 右边操作数的值加上左边操作数的值赋值给左边操作数
同样的还有:
-= 
*=
/=
%= 把两操作数的余数赋给左边操作数

回想python,这类似in-place(当然,python数值对象没有in-place之分,但tensor或ndarray对象存在in-place)
结合位运算,延伸出赋值位运算:

<<= 左移赋值运算  c<<=2等价于c=c<<2
>>= 右移赋值运算
&=  按位与再赋值  c&=2等价于c=c&2
^=
|=

实例:

int c=a+b;
cout<<c<<endl;
c+=a
cout<<c<<endl;

杂项运算符

杂项运算符有以下:

sizeof 返回变量占用空间的字节数,可见sizeof不是函数而是运算符

condition?x:y 条件运算符:C++内唯一一个三目运算符,如果condition为真,返回x,否则返回y

, 逗号运算符,顺序执行运算,整个逗号表达式的值是以逗号分隔的最后一个表达式的值
即:表达式 a,b,c 的值为c的值

.-> 成员运算符,用于引用类,结构和共用体的成员

Cast 强制转换运算符,比如int(2.2)会返回2

& 指针运算符&可以返回变量的地址
* 指针运算符*指向一个变量,例如*var将指向变量var

实例:

int x = 10, y = 20;

cout << sizeof(x) << endl;

int c = x > y ? 1 : 0;
    
int e = (x, y, c);
cout << e << endl;  //0

float f = float(e);

float* p = &f;
cout << *p << endl; //0

成员运算符举例:

typedef struct {
        short Sunday = 0;
        short Monday = 1;
        short Tuesday = 2;
        short Wednesday = 3;
        short Thursday = 4;
        short Friday = 5;
        short Saturday = 6;
}Week;

Week w;
cout << w.Friday << endl; //5
cout << sizeof(w) << endl; //14,因为short占用2byte

关于运算符优先级,需要记住:
1.一般,单目运算符优先级高于双目运算符;
2.最好加括号确保优先级的正确性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值