基础
- 运算符包含一元运算符和二元运算符,具体含义要按照上下文决定;
- 运算符重载:对于内置类型和复合类型,用户自己所定义的符号重载的行为;
- 对于重载运算符来说,运算对象的类型以及返回值的类型都是运算符定义的,但是不能够改变运算对象的个数,运算符的优先级以及结合律;
- 建议使用
()
来使运算符的结合顺序符合要求; - 如果这个表达式改变了某个对象的值,那么在这个表达式里面就不要再使用这个对象;
for(;;)
循环是个例外;
关于左值和右值
- 对于
C
语言:
- 左值来说,左值可以在等号的左边以及右边,但是右值只能够在等号的右边;
对于
C++
语言:- 需要右值的地方可以使用左值来代替,但是不能够把右值当成左值来使用;
- 当左值被当作右值来使用时,实际上使用的是它的值;
- 赋值运算符需要一个左值作为其左侧的运算对象,得到的结果也仍然是一个左值;
&
取地址运算符作用于一个左值运算对象,返回一个指向该运算对象的指针,得到的结果是一个右值;- 内置解引用运算符:
- 下标运算符:
- 迭代器运算符:
string
和vector
下标运算符得到的结果都是左值:- 内置类型和迭代器的自增,自减运算符作用于左值运算对象,得到的结果仍然是左值;
//需要左值的几种情况 //赋值运算符的左值 int ix = 12; int nix = ix; //对于ix来说,可以出现在等号的左边或者是右边,等式1的ix使用的是地址空间,等式2的 //ix使用的是里面的12的值; //nix以及ix本质上都是左值,但是ix被用右边; //取地址运算符 int *pix = &nix; //nix是一个左值,但是pix是一个右值,因为在使用pix时,使用的是pix对象的值; //内置解引用运算符 *pix; //*pix的得到的结果可以出现在等号的右边,也可以出现在等号的左边,是左值; ix = *pix; *pix = ix; //下标运算符: string mystring{"Hello,world"}; mystring.[i]可以出现在等号的左边,同时也可以出现在等号的右边;
对于
decltype
,如果作用于左值表达式得到的死引用类型;- 运算对象的求值顺序和优先级和结合律无关;
int i=12; cout << i << ++i << endl; //结果根据编译器而定;
- 对于
两种运算的等价
- 除数和被除数符号不一样时,有负为负,
m%-n
等价于m%n
,(-m)%n
等价于-(m%n)
; - 对于赋值运算符,表达式的值,就是执行赋值时所赋的值;
- 赋值运算符从右往左进行赋值运算;
C++
建议使用前缀的自增自减操作,原因有:
- 前置版本将对象本身作为左值返回,后置对象将对象原始值的副本作为右值返回;
- 前置可以避免对于原始对象进行复制的资源浪费,尤其是对于大规模的复杂迭代器;
- 后置还可能出现对于数组下表的越界访问;
- 后缀操作多用于需要在使用当前值之后进行自增操作;
expr1 逻辑运算符 expr2 ? expr1 : expr2
条件运算符不应该超过两层到三层;- 在输出表达式中使用条件表达式,应该使用
()
,否则输出的结果可能不理想; - 位运算符应该用于无符号类型;
#include<iostream> using namespace std; int main(){ cout << "21%6: " << 21%6 << endl; cout << "21/6: " << 21/6 << endl; cout << "21%7: " << 21%7 << endl; cout << "21/7: " << 21/7 << endl; cout << "-21%-8: " << -21%-8 << endl; cout << "-21/-8: " << -21/-8 << endl; cout << "21%-5: " << 21%-5 << endl; cout << "21/-5: " << 21/-5 << endl; cout << "-21%5: " << -21%5 << endl; }
C++11
允许使用花括号括起来的初始值列表作为等号的右侧对象;k = {3.14};
赋值运算符满足右结合律,
int ival,nival; ival=nival=0;
;
- 除数和被除数符号不一样时,有负为负,
- 位运算符
运算符 | 功能 |
---|---|
~ | 按位求反 |
<< | 向左移位,放大 |
>> | 向右移位,缩小 |
& | 位与运算符 |
^ | 位异或运算符 |
` | ` |
unsigned char bits=0233;
bits << 8;
bits << 11;
bits << 12;
bits = ~bits;
unsigned char b1 = 0145;
unsigned char b2 = 0245;
int next = b1 & b2;
int next1 = b1 | b2;
int next2 = b1 ^ b2;
- 左移位运算符向右插入
0
用于扩大数的位数,右移运算符,无符号左侧插入0
,有符号符号位或者0
; sizeof
运算符
- 满足有右结合率,返回值类型是
size_t
类型; sizeof(*p)
,在这个过程中是不会对p
进行解引用操作的,即使p
是一个无效的指针,也不会出错;C++11
允许无需提供具体的对象可以直接获得类成员的大小;- 对于
char
或者类型是char
的表达式得到的结果是1; - 对引用执行
sizeof
得到的是被引用对象所占有空间的大小; - 对指针执行
sizeof
得到的是指针本身空间的大小; - 对解引用指针执行
sizeof
得到的是指针指向的对象所占有的空间的大小,指针不需要进行解引用; - 对数组执行
sizeof
运算符得到的是整个数组所占空间的大小,sizeof
不会将数组转换成指针来进行处理; - 对
string
对象或者vector
对象执行sizeof
运算只会返回该类型固定范围的大小,不会计算对象中的元素占有了多少空间;
- 满足有右结合率,返回值类型是
- 逗号运算符
- 从左向右一次求值,并且丢弃左边的值,将右边的值作为表达式的值;
- 类型转换
- 左值只能在等号右边,右值可以在等号的左边或者右边;
- 隐式转换
- 隐式转换进最大可能的避免损失精度;
- 在初始化过程中,初始值被转换成变量的类型,在赋值语句中右侧对象转换成左侧对象类型;
- 数组转换成指针,数组自动被转换成数组首元素的指针;
- 指针转换:常量整数值0或者是字面值
nullptr
转换成任意指针类型; - 执行任意非常量的指针能够转换成
void*
,指向任意对象的指针都能够转换成const void*
- 转换成
bool
类型,存在一种从算术类型自动转换成bool
类型,0
转换成false
,否则转换成true
; - 转换成常量:允许将常量指针转换成对应的常量指针,对于引用来说也是这样;
- 在函数进行调用时,需要将函数转为函数指针来进行调用;
- 算术转换
- 整型提升,通常是将小类型提升为大类型,这样进行转换没有太大的精度损失,对于较小的
char
,signed char
,unsigned char
,unsigned char
,short
,unsigned short
,在保证数据精度的情况下,通常会转换为int
类型;
- 整型提升,通常是将小类型提升为大类型,这样进行转换没有太大的精度损失,对于较小的
- 显示的类型转换
- 关于命名的强制类型转换,
cast_name<type>(expression)
,cast_name
具有以下几种形式:
static_cast
:对于任何具有明确类型的转换,只要不包含底层const
是都可以进行转换的,主要用于将高精度的类型转换为小精度的类型时,可以用于忽略警告,还可以用于编译器无法转换的类型;
//对于这段代码来说,强制进行类型转换和不进行强制类型转换的结果是一样的;
int i=10;
int j=2.1;
double slope = static_cast<double> j/i;
double slope1 = j/i;
//上面两个的输出结果都是5;
//对于底下,没有static_cast是会出错的;
void *pi = &i;
double *dpi = static_cast<double*>(pi);
const_cast
:只能够用于改变运算对象的底层const
,这个以后解释;
- 关于命名的强制类型转换,
- *