4.1 基础
表达式
:对表达式求值将得到一个结果,字面值和变量是最简单的表达式,其结果就是字面值和变量的值
4.1.1 基本概念
组合运算符和运算对象
运算对象转换
小整数类型(bool,char,short)通常会被提升成较大的整数类型主要是int
重载运算符
- 我们使用承载运算符时,其包括运算对象的类型和返回值的类型.都是由该运算符定义的.但是运算对象的个数.运算符的优先级和结合律都是无法改变的.
左值和右值
- C++表达式要不是左值要不是右值
- 当一个对象被用作右值的时候,用的是对象的值.当对象被用作左值的时候,用的是对象的身份.(在内存中的位置)
- 在需要右值的地方可以用左值来代替,但是不能把右值当做左值来使用.
几种熟悉的运算符要用到左值
- 赋值运算符需要一个左值作为其左侧运算对象,得到结果页仍然是一个左值
- 取地址符作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值
- 内置解引用运算符,下标运算符,迭代器解引用运算符,string和vector的下标运算符的求值结果都是左值
- decltype作用于表达式求值结果是左值的将得到得到一个引用类型.
int * p;
decltype(*p);的结果是int&;
- decltype作用表达式求值结果是右值
decltype(&p) 得到int**
4.1.2 优先级和结合律
4.1.3 求值顺序
4.2算数运算符
bool b = true;
book b2 = -b; // b2是true
- 如上所示,参与运算时布尔将被提升成整数值1,然后对他求负值的结果是-1,将-1转换为bool值为true
4.3逻辑和关系运算符
4.4赋值运算符
int ival,jval;
ival=jval=0;
- 因为运算符对象满足右结合律,所以靠右的赋值运算jval=0作为靠左的赋值运算符的右侧运算对象.又因为赋值运算符返回的是左侧运算对象,所以靠右的赋值运算的结果(即jval)被赋值给ival
4.5递增和递减运算符
4.6成员访问运算符
4.7条件运算符
4.8位运算符
4.9sizeof运算符
- 对char或者类型为char的表达式执行sizeof运算结果得1
- 对引用类型执行sizeof得到被引用对象所占空间的大小
- 对解引用指针指向sizeof得到指针所指向的对象所占空间的大小,指针不需要有效
- 对数组执行sizeof得到整个数组所占空间的大小
- 对string对象或者vector对象执行sizeof只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间
int x[10];
int *p=x;
cout<<sizeof(x)/sizeof(*x);//10
cout<<sizeof(p)/sizeof(*x);//1
//sizeof(p)的运算对象是一个指针,求值结果是指针所占的空间的大小,sizeof(x)的运算对象x是数组的名字,求值结果是整个数组所占空间的大小
4.10逗号运算符
4.11类型运算符
- 如果两种类型可以相互转换,那么他们就是相互关联的
何时发生隐式类型转换
4.11.1算数转换
- 运算符的运算对象将转换成最宽的类型
整型提升
无符号类型的运算对象
- 如果一个运算对象是无符号类型,另外一个运算对象是带符号类型,而且其中的无符号类型不小于带符号类型,那么带符号的运算对象转换成无符号类型.例如:unsigned和int,则int类型转换为unsigned类型
4.11.2 其他隐式类型转换
数组转换成指针
- 在大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针
int a[10];
int * ip = ia;
- 当数组被用作decltype的参数时或者作为取地址符,sizeof及typeid(19.2.2节,732页)等运算符对象时,上述转换过程不会发生.同样的,如果用一个引用来初始化数组(3.5.1 102页),上述转换过程也不会发生,当在表达式中使用函数类型时会发生类似的指针转换(6.7节 221页)
int arr[10];
int *ptrs[10]; // ptrs是含有10个整型指针的数组
int &refs[10]; //error,不存在引用的数组
int (*Parray)[10] = &arr; //Parray指向一个含有10个整数的数组,用法在下面
int (&arrRef)[10] = arr; //arrRef引用一个含有10个整数的数组,用法在下面
int a[10] = {1,2,3};
int (&ptr)[10] = a; //相当于a的别名是ptr
cout<<ptr[0]<<endl;
int a[10] = {1,2,3};
int (*ptr)[10] = &a;
cout<<(*ptr)[0]; // === a[0]
int a[2][10] = {1,2,3};
int (*ptr)[10] = a;
指针的转换
- 常量整数值
0
或者字面值nullptr
能转换成任意指针类型 - 指向任意
非常量的指针
能换成void* - 指向
任意对象的指针
能转换成const void* - 15.2.2(530页)
转换成布尔类型
- 如果指针或者算术类型的值为0,转换结果为false,否则转换结果是true
char *cp=get_string();
if(cp) // 如果指针cp不是0,条件为真
while(*cp) //如果*cp不是空字符,条件为真
转换成常量
- 允许将指向非常量类型的指针转换成指向相应的常量类型的指针,对于引用也是这样.但是相反的转换并不存在,因为这试图删除底层的const
类类型定义的转换
- 类类型能定义由编译器自动执行的转换,不过编译器每次只能执行一种类类型的转换.
string s,t = "a value"; //字符串转换成string类型.
while( cin>>s)
4.11.3 显示转换
首先必须明白:强制类型转换非常危险
命名的强制类型转换
static_cast
- 任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast
- static_cast的常用情景:将一个较大的算术类型赋值给较小的类型
- 有时可以使用static_cast找回存在于void*中的指针
void *p = &d; //正确:任何非常量对象的地址都能存入void*
double *dp = static_cast<double*>(p);
//正确:将void*转换回初始的指针类型
const_cast
- const_cast
只能
改变运算对象的底层const
const char *pc;
char *p = const_cast<char*>(pc);
//正确:但是通过p写值是未定义的行为
- 去掉性质:将常量对象转换成非常量对象的行为.
- 只有const_cast能改变表达式的常量属性
const char *cp;
char *q = static_cast<char*>(cp);
//error:只有const_cast才能去掉const属性
static_cast<string>(cp);
//正确:字符串字面值转换成string类型
const_cast<string>(cp);
//error:const_cast只能改变常量属性
- const_cast常常用于函数重载的上下文中(6.4节,208也下面所示)
const string & shorterString(const string &s1,const string s2)
{
return s1.size() <= s2.size() ? s1:s2;
}
上面的函数对于常量调用好着,但是两个非常量对象调用却返回常量调用
const string & shorterString(const string &s1,const string s2)
{
return s1.size() <= s2.size() ? s1:s2;
}
// 上面的函数对于常量调用好着,但是两个非常量对象调用却返回常量调用
string & shorterString( string &s1, string s2)
{
auto &r = shorterString(const_cast<const string&>(s1),
const_cast<const string&>(s2));
return const_cast<string&>(r);
}
reinterpret_cast
- 为运算对象的位模式提供较低层次上的解释
int *ip;
char *pc = reinterpret_cast<char*>(ip);
//语法上正确,但是非常危险