1 表达式及语句
表达式:由操作数和运算符组成。例如:算术表达式A+B是由操作数A、B和二元运算符‘+’组成。
语句:语句通常以分号结尾,从功能上说,语句大体可分为执行性语句和说明性语句两大类。执行性语句旨在表述程序的动作,又可分为赋值语句、控制语句和输入输出语句。说明性语句旨在定义各种不同数据类型的变量或运算。
2 赋值语句
在赋值语句A=B中,对赋值运算符“=”右边的B,我们需要的是它的值(称为右值),对左边的A,我们需要的是它所代表的存储单元(地址)(称为左值)。
赋值运算符的左操作数必须是非const
的左值。
与其他二元操作符不同,赋值操作具有右结合特性,当表达式含有多个赋值操作时,从右向左结合。多个赋值操作中,各变量必须具有相同的数据类型,或者具有可转换为同一类型的数据类型。
3 自增、自减运算符
自增运算符:++
自减运算符:--
这两种运算符是一元算术运算符。
3.1 前缀运算与后缀运算
自增、自减运算符作用在变量之前时,称为前缀运算,如++i
,--i
。作用在变量之后时,称为后缀运算,如i++
,i--
。
注:前缀运算与后缀运算的区别在于:以++
操作为例,对于变量a,++a
表示取a的地址,增加它的内容,然后把指放在寄存器中;a++
表示取a的地址,把它的值装入寄存器中,然后增加内存中a的值。即前缀运算是“先变后用”,后缀运算是“先用后变”。
3.2 自增、自减运算符作用的对象
自增、自减运算符只作用于变量,而不能作用于常量或表达式。只要是标准类型的变量,不管是整型、实型,还是字符型,枚举型都可以作为运算对象。
如:i+++j++
、++i+(++j)
、++a+b++
、++array[--j]
都是合法的,而++6
、(i+j)++
、'A'++
、++i+++j
、(&p)++
这5个表达式则是不合法的。
注:为什么i+++j++
合法而++i+++j
不合法呢?这是因为C/C++的编译器对程序编译时,从左到右尽可能多地将字符组合成一个运算符或标识符,因此i+++j++
等效于(i++)+(j++)
,这是合法的;而++i+++j
相当于++(i++)+j
,第1个++
作用的对象是表达式i++
,是不合法的。
前缀自增和后缀自增有个重要的区别就是:前缀自增能够用做左值表达式,后缀自增只能用于右值表达式。这是因为,前缀自增返回操作数本身,而后缀自增返回一个临时变量。
3.3 自增、自减运算符的结合方向
自增、自减运算符及负号运算符的结合方向是从右向左。如k=-i++
等效于k=-(i++)
。
4 关系与逻辑运算符
4.1 关系操作符
关系操作符(<
、<=
、>
、>=
)具有左结合的特性。事实上,由于关系操作符返回bool
类型的结果,因此很少使用其左结合特性。当出现多个关系操作符串接起来使用时,如:
if (i < j < k){ /***/ }
只要k>1,上述表达式的值就为true
。这是因为第二个小于操作符的左操作数是第一个小于操作符的结果:true/false
。也就是该条件将k与0/1作比较。因此为了实现我们想要的条件检验,应该为:
if (i < j && j < k){ /***/ }
4.2 逻辑操作符
逻辑操作符&&
(与运算)、||
(或运算)。
逻辑与和逻辑或操作符总是先计算其左操作数,然后再计算其右操作数。只有在仅靠左操作数数的值无法确定该逻辑表达式的结果时,才会求解其右操作数。这种求值策略称为“短路求值”。
给定以下形式:
expr1 && expr2
expr1 || expr2
仅当expr1
无法确定表达式的值时,才会求解expr2
。
5 位运算符
位操作符使用整型的操作数。将其整型操作数视为二进制位的集合,为每一位提供检验和设置的功能。
操作符 | 功能 | 单双目 | 用法 |
---|---|---|---|
& | 按位与 | 双 | expr1 & expr2 |
| |
按位或 | 双 | expr1 | expr2 |
^ |
按位异或 | 双 | expr1 ^ expr2 |
~ |
取反 | 单 | ~expr |
<< |
左移 | 双 | expr1 << expr2 |
>> |
右移 | 双 | expr1 >> expr2 |
5.1 与、或、非及异或运算符
- 按位与:仅当两位都为1时,结果为1,否则为0。
- 按位或:仅当两位都是0时,结果为0,否则为1。
- 取反:将操作数每位取反。
- 按位异或:仅当两位不相同时,结果为1,否则为0。
判断一个整数n是否位2的正整数次幂:
if(n>1 && ((n & (n-1)) == 0))
cout << "YES";
两个相同的数异或后结果为0,且满足交换律。
异或的使用:
-
寻找数成对出现时缺失的某一个数:
对一组数A、B、C、D、A、C、B,则A^B^C^D^A^C^B=D,可以快速找到D只出现了1次。
-
不使用第三方变量交换两个变量的值:
a=a^b; b=a^b; a=a^b;
-
不用算数运算符实现两个数的加法:
int add_no_arithm(int a, int b) { if(b == 0) return a; int sum = a ^ b; int carry = a & b; return add_no_arithm(sum, carry); }
5.2 移位运算符
<<
和>>
分别为左移和右移操作符,移位时的补位规则为:
类型 | 左移 | 右移 |
---|---|---|
int |
低位补0 | 高位补符号位 |
unsigned int |
低位补0 | 高位补0 |
注:负数左移时有可能会变成正数。
5.3 返回n转化为二进制后包含1的数量
-
方法一:
int fun(unsigned int n) { int count = 0; while(n > 0) { n &= (n-1); count++; } return count; } //或 int fun(int n) { int count = 0; while(n != 0)//考虑负数 { n &= (n-1); count++; } return count; }
-
方法二:
int fun(unsigned int n) { int count = 0; while(n) { if(n & 1)//判断最低位是不是为1 count++; n >>= 1;//每次右移一位 } return count; }
-
方法三:
int fun(int n) { int count = 0; unsigned int flag = 1; while(flag) { if(n & flag) count++; flag <<= 1; } return count; }
5.4 优先级
~
运算符 > 移位运算符 > 与、或、异或运算符。
6 类型转换
6.1 赋值转换
赋值转换是指将一种类型的值赋给另一种类型的变量时,值将会转换为接收变量的类型。如:
int ival = 3.14;//3.14将被截断为3
int* ip=0;//0将转换为空指针
6.2 表达式的转换
当同一个表达式中出现不同类型的量时,C++会根据不同的情况对操作数进行自动转换,分为“整型提升”和“运算时的转换”两种。
-
整型提升:在表达式计算中,C++会将
bool
、char
、unsigned char
、signed char
、short
和signed short
型值都会自动转换成int型。注:
unsigned short
转int
比较特殊:如果系统中int
占4字节,unsigned short
会转换成int
,如果系统中int
占2字节,则unsigned short
会转换成unsigned int
,从而避免了数据的丢失。 -
运算时的转换:当运算涉及两种类型时,较小的类型将会被转换成较大的类型,即表达力低的类型将会被转换成表达力高的类型。各类型表达能力由低到高为:
int
(等价于signed int
)→unsigned int
→long
(等价于signed long
)→unsigned long
→float
→double
→long double
6.3 显式转换
C++基本类型的指针之间不含有隐式转换(void*
除外、const
的某些用法为了兼容C语言也可隐式转换),需要显式转换。
显式转换也称为强制类型转换,在引入命名的强制类型转换操作符之前,显式强制转换用圆括号将类型括起来实现:
int* ip;
char* pc = (char*)ip;
C++引进了如下4个强制类型转换操作符用于显式转换:static_cast
、dynamic_cast
、const_cast
和reinterpret_cast
。
命名的强制类型转换符号的一般形式为:
cast_name<type>(expression)
其中cast_name为static_cast
、dynamic_cast
、const_cast
和reinterpret_cast
之一,type为转换的目标类型,expression则是被强制转换的表达式。
6.3.1 static_cast
编译器隐式执行的任何类型转换都可以由static_cast
显式完成。
double d = 97.0;
int i = static_cast<int>(d);
//等效于
double d = 97.0;
int i = d;
仅当类型之间可隐式转换时(除类层次间的下行转换以外),static_cast
的转换才是合法的,否则将存在问题。
类层次间的下行转换不能通过隐式转换完成:
class base{};
class child: public base{};
base* b;
child* c;
c = static_cast<child*>(b);//下行转换,编译正确,但由于没有动态类型检查,因此不安全。
c=b;//编译不正确
6.3.2 dynamic_cast
dynamic_cast
的转换类型必须是类的指针、类的引用或者void*。如果转换类型是指针类型,那么表达式也必须是一个指针;如果转换类型是一个引用,那么表达式也必须是一个引用。
注:与其他强制类型转换不同,dynamic_cast
涉及运行时类型检查。dynamic_cast
运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的,对于没有虚函数表的类使用会导致dynamic_cast
编译错误。
如果绑定到引用或指针的对象的类型不是目标类型,则dynamic_cast
失败。如果转换到指针类型的dynamic_cast
失败,则dynamic_cast
的结果为0值;如果转换到引用类型的dynamic_cast
失败,则抛出一个bad_cast
类型的异常。
dynamic_cast
操作符执行两个操作:首先验证被请求的转换是否有效,只有转换有效,操作符才是进行转换。
一般而言,引用或指针所绑定的对象的类型在编译时是未知的,基类的指针可以赋值为指向派生类对象,同样,基类的引用也可以用派生类对象初始化。因此,dynamic_cast
操作符执行的验证必须在运行时进行。
dynamic_cast
主要用于类层次间的上行转换和下行转换。
dynamic_cast
运算符可以在执行期决定真正的类型。如果下行转换是安全的(也就是说,如果基类指针或者引确实指向一个派生类对象),这个运算符会传回转型过的指针。如果downcast不安全,这个运算符会传回空指针。在上行转换时,dynamic_cast
与static_cast
效果是一样的,但dynamic_cast
具有类型检查的功能,比static_cast
更安全。
6.3.3 const_cast
const_cast
可以将表达式的const
性质转换掉。也只有const_cast
才能将const
性质转换掉。用const_cast
来执行其他任何类型转换,都会引起编译错误。
const double value = 0.0;
double* ptr = nullptr;
//可以使用const_cast让ptr指向value
ptr = const_cast<double*>(value);
6.3.4 reinterpret_cast
reinterpret_cast
与使用圆括号将类型括起来的显式类型转换操作功能相同。
int* ip;
char* pc = (char*)ip;
//等效于
int* ip;
char* pc = reinterpret_cast<char*>(ip);
7 运算符优先级表
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
---|---|---|---|---|---|
1 | [] |
数组下标 | 数组名[常量表达式] |
左到右 | |
() |
圆括号 | (表达式)/函数名(形参表) |
左到右 | ||
. |
成员选择(对象) | 对象.成员名 |
左到右 | ||
-> |
成员选择(指针) | 对象指针->成员名 |
左到右 | ||
2 | - |
负号运算符 | -表达式 |
右到左 | 单目运算符 |
(类型) |
强制类型转换 | (数据类型)表达式 |
右到左 | ||
++ |
自增运算符 | ++变量名/变量名++ |
右到左 | 单目运算符 | |
-- |
自减运算符 | --变量名/变量名-- |
右到左 | 单目运算符 | |
* |
取值运算符 | *指针变量 |
右到左 | 单目运算符 | |
& |
取地址运算符 | &变量名 |
右到左 | 单目运算符 | |
! |
逻辑非运算符 | !表达式 |
右到左 | 单目运算符 | |
~ |
按位取反运算符 | ~表达式 |
右到左 | 单目运算符 | |
sizeof |
长度运算符 | sizeof(表达式) |
右到左 | ||
3 | / |
除 | 表达式/表达式 |
左到右 | 双目运算符 |
* |
乘 | 表达式*表达式 |
左到右 | 双目运算符 | |
% |
余数(取模) | 整型表达式%整型表达式 |
左到右 | 双目运算符 | |
4 | + |
加 | 表达式+表达式 |
左到右 | 双目运算符 |
- |
减 | 表达式-表达式 |
左到右 | 双目运算符 | |
5 | << |
左移 | 变量<<表达式 |
左到右 | 双目运算符 |
>> |
右移 | 变量>>表达式 |
左到右 | 双目运算符 | |
6 | > |
大于 | 表达式>表达式 |
左到右 | 双目运算符 |
>= |
大于等于 | 表达式>=表达式 |
左到右 | 双目运算符 | |
< |
小于 | 表达式<表达式 |
左到右 | 双目运算符 | |
<= |
小于等于 | 表达式<=表达式 |
左到右 | 双目运算符 | |
7 | == |
等于 | 表达式==表达式 |
左到右 | 双目运算符 |
!= |
不等于 | 表达式!=表达式 |
左到右 | 双目运算符 | |
8 | & |
按位与 | 表达式&表达式 |
左到右 | 双目运算符 |
9 | ^ |
按位异或 | 表达式^表达式 |
左到右 | 双目运算符 |
10 | | |
按位或 | 表达式|表达式 |
左到右 | 双目运算符 |
11 | && |
逻辑与 | 表达式&&表达式 |
左到右 | 双目运算符 |
12 | || |
逻辑或 | 表达式||表达式 |
左到右 | 双目运算符 |
13 | ?: |
条件运算符 | 表达式1?表达式2:表达式3 |
右到左 | 三目运算符 |
14 | = |
赋值运算符 | 变量=表达式 |
右到左 | |
/= |
除后赋值 | 变量/=表达式 |
右到左 | ||
*= |
乘后赋值 | 变量*=表达式 |
右到左 | ||
%= |
取模后赋值 | 变量%=表达式 |
右到左 | ||
+= |
加后赋值 | 变量+=表达式 |
右到左 | ||
-= |
减后赋值 | 变量-=表达式 |
右到左 | ||
<<= |
左移后赋值 | 变量<<=表达式 |
右到左 | ||
>>= |
右移后赋值 | 变量>>=表达式 |
右到左 | ||
&= |
按位与后赋值 | 变量&=表达式 |
右到左 | ||
^= |
按位异或后赋值 | 变量^=表达式 |
右到左 | ||
|= |
按位或后赋值 | 变量|=表达式 |
右到左 | ||
15 | , |
逗号运算符 | 表达式,表达式,... |
左到右 | 从左向右顺序运算 |
运算符优先级有几个简单的规则:
1)()
,[]
,->
和.
最高;
2)单目的比双目的高;算术双目比其他双目的高;
3)移位运算高于关系运算;关系运算高于按位运算(与,或,异或);按位运算高于逻辑运算;
4)三目的只有一个条件运算,低于逻辑运算。
5)赋值运算仅比,
高,且所有的赋值运算符优先级相同,结合访问位从右向左。