表达式
由多个运算对象组成,对表达式求值将得到一个结果。如字面值及变量,和运算符组合起来就是较为复杂的表达式。
基础
一元运算符和二元运算符
一元运算符:作用于一个运算对象
二元运算符:作用于两个运算对象
存在三元运算符,函数调用也是一种特殊的运算符,对运算对象数目无要求。
类型转换
关联
如果两种类型有关联,需要其中一种类型的运算对象时,可以用另一种关联类型对象或值来替代,二者可以相互转换。
隐式转换
- 类型转换自动执行,不需要程序员介入。
- 算数转换
整型提升:把小整数转换成较大的整数。对于无符号类型,需要进行整型提升,再将小符号转换成较大的类型。
数组变指针:使用数组名,将自动转换成指向数组首元素的指针。当使用decltype、取地址(&)、sizeof和typeid时,上述转换不会发生。
指针转换:0/nullptr能转成任意类型指针,任意非常量指针能转换成void*,指向任意对象的指针能转换成 const void*。
布尔转换:判断语句(if/while)
常量转换:非const到const
类类型
显式转换
- 旧式强转换模式
type(expr)
(type)expr
- 对象显式强制转换,命名强制类型转换
cast-name<type>(expression);
- cast-name
static_cast:不包含底层const时使用,可以用来强制转换类型,或者找回存在于void*中的指针。
int a = 1;
double b = static_cast<double>(a);
int* p = &a;
void* vp = p;
int *ip = static_cast<int*>(vp); //vp中类型必须是int*
const_cast:只改变对象的底层const
int a = 2;
const int c = 10;
const int* p1 = &c;
int* p2 = const_cast<int*>(p1);
cout << *p2 << endl; //10
*p2 = 1;
cout << *p2 << endl; //1
cout << *p1 << endl; //1
p2 = &a;
cout << *p2 << endl; //2
reinterpret_cast:为运算对象为模式提供较低层次上的重新解释。(不推荐使用)
dynamic_cast:
重载运算符
运算符作用于类类型运算对象时,用户可以自定义其含义,为已存在运算符赋予另一层含义,称为重载运算符。
eg:IO库里的“>>”和“<<”,压入压出,逻辑运算里是右移和左移。
左值右值
- 右值用的是对象的值(内容),左值用的是对象的身份(在内存中存在的地址)。
- 需要右值的地方可以用左值代替,实际使用的是它的值(内容)。
int *p;
decltype(*p) //解引用是左值,地址是p的地址,返回类型int&
decltype(&p) //返回p的地址,取地址符生成右值,内容是指向整形指针的指针,返回类型是int**
运算符优先级、结合律
优先级:高优先级使对象更紧密组合在一起。
结合律:优先级相同,组合规则由结合律确定。
括号无视优先级和结合律 。
运算优先级表:
C++运算符优先级
求值顺序
大多数情况下,不会指明求值顺序,且求值顺序和优先级结合律无关。考虑到这个问题,如果运算过程中改变了某个运算对象的值,在表达式其他地方不要再使用这个对象。除非求值顺序已知。
int i= f1()*f2(); //无法知道f1()和f2()谁先被调用
运算符
1.算术运算符
- 优先级:一元“+”“-” > “*”“/”“%” > “+”“-”
- 算术运算符运算对象和求值结果都是右值。
- 对于布尔类型运算:
bool f = 0;
bool f2 = -f; //f2为0
bool f3 = !f; //f3为1
- 对于负数商:(-m)/n= m/(-n) = -(m/n)
- 对于负数余数:m%(-n) = m%n, (-m)%n=(-m)%(-n)=-(m%n)
2.逻辑关系运算符
- 优先级:逻辑非(!)>比较(>,<,>=,<=)>相等(==,!=)>逻辑与(&&)>逻辑或(||)
- 运算对象和求值结果都是右值。
- 对于逻辑与和逻辑或,“&&”和“||”具有短路求值策略。对于与,左侧为真才判断右侧;对于或,左侧为假才判断右侧。左侧运算是为了保证右侧运算对象求值过程的正确性和安全性。
- 比较运算符:返回布尔值
3.赋值运算符
- 优先级:关系运算符>赋值运算符
- 左侧运算对象是可修改左值,结果是左值,类型是左侧运算对象的类型。
- 赋值运算符满足右结合律
int i,j;
i = j = 0; //三者类型相同,或者可以类型转换
- 复合赋值:普通运算符求值两次,复合运算符求值一次。
a += b;
4.递增递减运算符
++i; --i; //前置版本
i++; i--; //后置版本
- 除非必须,不使用后置版本。当既想将对象加一或减一,又想引用它原来的值时,用后置版本。
5.成员访问运算符
- 点运算符和箭头运算符
- 解引用运算符优先级低于点运算符
p->mem;
(*p).mem;
6.条件运算符
- 条件运算符
cond?expr1:expr2;
- 嵌套条件运算符:嵌套最好不要超过2~3层
cond1?expr1:cond2?expr2:expr3
- 条件运算符优先级非常低,使用时通常需要加括号。
cout<<((grade<60)?"fail":"pass")<<endl;
cout<<(grade<60)?"fail":"pass"<<endl; //输出grade<60的结果。
cout<<grade<60?"fail":"pass"<<endl; //输出grade结果,然后比较cout<60。
7.位运算符
- 用于整形类型对象,把对象看作二进制位集合
- 优先级:位取反(~)>左右移(>>,<<)>位与(&)>位亦或(^)>位或(|)
- 对于符号位的位运算取决于机器,所以建议仅将位运算符用于处理无符号整型
8.sizeof运算符
- 返回一条表达式或一个类型名字所占字节数。满足右结合律。所得类型size_t类型常量表达式。
sizeof(type)
sizeof expr //返回表达式结果类型大小
- 使用起来和deltype很像,sizeof不实际计算运算对象的值。所以对于类成员,不需要提供一个具体对象,就能获得类成员大小。
int a = 1;
int* p;
int& refa = a;
cout << sizeof(a) << endl; //int型大小,4
cout << sizeof(p) << endl; //int型指针大小,8
cout << sizeof(*p) << endl; //int型大小,4
cout << sizeof(refa) << endl;//int型大小,4
char b = '9';
cout << sizeof(b) << endl; //char型大小,1
int c[] = { 1,2,3,4,5 };
cout << sizeof(c) << endl; //数组大小,4*5 = 20
char e[] = { 'h','h','d','y','z','w','h','y','\0'};
cout << sizeof(e) << endl; //9
cout << strlen(e) << endl; //8,不算空格
string s = "hhdyzwhy";
cout << sizeof(s) << endl;//string类型固定部分大小,40
vector<int> d = {1};
cout << sizeof(d) << endl;//vector类型固定部分大小,32
9.逗号运算符
for(int i = 0;i<nums.size();++i,--cnt) //每次循环后i和cnt会发生改变