c++primer笔记四、表达式

4.1基础

基本概念

一元运算符:作用在一个运算对象(&,)
二元运算符:作用于两个运算对象(==,
)
函数调用也是一种特殊的运算符,对运算对象的数量没有限制。

重载运算符:用户自定义作用于类类型的作用对象,比如IO库的>><<

左值与右值

当一个对象被用作右值, 用的是值(内容);被用作左值时,用的是对象的身份(内存中的位置)。
需要右值的地方可以用左值替代,但不能把右值当作左值使用。左值当被右值使用时,实际使用的是内容(值)。

1.复制运算符需要左值,结果也是左值
2.取地址符作用域左值,返回一个指针是右值
3.内置的解引用、下标u你算符、迭代器解引用、string和vector的下标运算符结果都是左值
4.内置类型和得带起的递增递减运算符,作用域左值对象,结果也是左值。
int *p; //p为指针
decltype(*p);   //*生成左值,结果是int&,
decltype(&p);   //&生成右值,结果是int**
优先级与结合律

根据优先级和结合律来决定运算对象组合的方式
对于没有执行顺序的运算符来说,如果表达式指向并修改了同一个对象,将引发错误并产生未定义的行为。

int i = 0;
cout << i << "" << ++i << endl;//未定义的

4种运算符只有当左侧对象值为真才继续求右侧的值。

&&
||
?:
,

4.2算数运算符

优先级如下

+,-    //一元正负号
*,/,% //乘法除法
+,-     //加减法

注意:bool值运算是true是1false是0

bool b = true;
bool b1 = -b;   //b1也是true,1取负为-1,-1还是true

算数表达式可能会有异常,如除数是0或者溢出。
取余操作的对象都必须是整数类型。
在C++11中,整数的商一律向0取整。
取余操作的时候,正负结果取决于m/n中m的符号

21 % -5 = 1;
-21 % -8 = -5;

4.3逻辑关系运算符

作用于算术类型或指针类型,作用结果能转换成布尔值,值为0的运算对象表示假。
运算对象和求值结果都是右值

! < <= > >= == != && ||

&&和||时,都会短路求值。仅当左侧表达式无法确定结果时,才计算右侧的值。

expr && expr 左侧为true右侧才执行
expr || expr 左侧为false右侧才执行

关系运算符不要连用

if(i < j < k)   //若k大于1则为真
if(i < j && j < k)  //正确使用

4.4 赋值运算符

赋值预算符左侧必须是可修改的左值

int i = 0;            //是初始化不是赋值
const int ci = i;    //是初始化不是赋值

1025 = i;       //非法
i = 3.1415;     //类型是int,结果为3

允许用花括号的初始值列表作为赋值的右侧运行对象

vector<int> vi;
vi = {0,1,2,3,4,5,6};
赋值满足右结合律

前提是类型必须相同

int a,b;
a = b = 0;  //都是0

int ival,*pval;
ival = pval = 0;    //错误,指针的值不能赋给int

赋值运算的优先级很低

复合赋值运算
+= -= *= /= %=   //算术运算发
<<= >>= &= ^= |=    //位运算符

都等价于
a = a op b;

唯一的区别是复合运算符只求值1次,普通运算符求值2次。

4.5递增递减

两种++i和i++;
前置版本把对象本身作为左值返回
后置版本把对象原始值的副本作为右值返回

除非必须,尽量用前置版本

auto pbeg = v.begin();
while (pbeg != v.end && *beg >= 0)
    cout << *pbeg++ << endl;

结果是输出当前的值并将pbeg向前移动一个元素

后置递增运算符优先级高于解引用,等价于*(pbeg++);
先把pbeg的值加一,再返回初始值的副本,因此解引用的对象是pbeg未增加之前的值

c++混用解引用和递增可以更简洁

cout << *iter++ <<endl;
等效
cout << *iter <<endl;
iter++;

如果一条表达式改变了运算对象的值,另一个表达式又要使用,容易出现错误。

while(beg != s.end() && !isspace(*beg)))
    *beg = toupper(*beg++); //错误,赋值语句未定义

4.6成员访问运算符

.(点)和->(箭头)都可用于访问。
点获取类对象的一个成员
箭头是点运算的一种简化

ptr->mem 等价与 (*prt).mem

解引用优先级低于点运算,因此必须要括号。

4.7条件运算符

cond ? expr1 :expr2

cond是判断条件的表达式,如果真执行expr1否则执行expr2.

string finalgrade = (grade < 60) ? "fail" : "pass";
//低于60是fail,否则就pass

条件运算符优先级很低,如果嵌套再其他语句,需要加括号

cout << ((grade < 60) ? "fail" : "pass");

4.8位运算

作用于整数类型,看成二进制位的集合。
提供检查和设置二进制位的功能。

`       //位求反
<<      //左移
>>      //右移
&       //位与
^       //位异或
|       //位或

左移右移时,边界职位的都被舍弃

优先级介于中间,比算数低比关系高,因此要适当加括号

cout << 42 + 10;     //正确,结果为10
cout << 10 < 42     //错误,试图比较cout和42

4.9sizeof运算符

返回表达式或类型名字所占的字节数,返回值时size_t的常量表达式。

sizeof (type)   //返回类型大小
sizeof expr     //返回所占空间大小

sizeof结果依赖于其作用的类型

1.char结果为1
2.引用类型的结果时被引用对象所占空间的大小
3.对指针运算得到指针本身所占空间大小
4.对解引用指针得到指针指向对象所在空间的大小,指针不需要有效
5.数组返回数组所占空间大小
6.string或vector结果返回该类型固定部分的大小,不会计算对象中元素占用了多少空间。

可以用数组大小除以单个元素大小得到数组元素个数

constexpr size_t sz = sizeof(ia)/sizeof(*ia);
int arr2[sz];

4.10逗号运算符

含有两个运算对象,从左向右依次求值。
首先对左侧求值,将结果丢弃。真正结果是右侧表达式的值。
如果右侧运算对象是左值,最终结果也是左值。
常常用于for循环

vector<int> :: size_type cnt = ivec.size();
for(vector<int>::size_type ix = 0; ix != ivec.size(); ++ix, --cnt)
    ivec[ix] = cnt;

每次循环迭代ix和cnt相应改变。

4.11类型转换

如果两种类型有关联,可以相互转换。
隐式转换:根据类型转换规则将运算对象类型同意后进行算术求值。
隐式转换会尽可能避免损失精度,如果有浮点和整型,整型会变浮点型。

在以下情况下,编译器会自动转换

1.大多数表达式中,比int类型小的整型提升为较大的整型
2.在条件中,非布尔值转成布尔类型
3.初始化过程中,初始值转成遍历的类型;赋值语句中,右侧转换成左侧的类型
4.如果运算中有多种类型,都需要转成同一种。
算术转换

比如一个对象是long double,则另一个都会转成long double。
整型也会变浮点型

整数提升

对于bool、char、signed char、unsighed char、short、unsigned short等类型、只要它们的所有可能指都能存在int中,就会提升会int。否则提升为unsigned int。
较大的char类型(wchar_t,char16_t,char32_t)提升成int、unsigned int、long、unsigned long、long long 和unsigned longlong中最小的一种。

无符号类型的运算对象

如果一个运算对象无符号,一个有符号,而无符号的类型不小于带符号的类型,则带符号的会转换成无符号来的。
带符号的大于无符号的,则转换结果依赖于机器。如果无符号的所有制都能存放在带符号类型中,则无符号转换成带符号。否则就带符号类型的运算对象转换成无符号的。

例如:

long和unsigned int, int和long大小相同,则long转成unsigned int;如果long类型占用的空间比int多,则unsigned int转成long。
其他隐式类型的转换
数组转换指针

数值会自动转换成首元素的指针

int *ip = ia;
指针的转换

0或者nullptr可以转换成任意指针类型;
指向任意非常累的指针能转换成void
指向任意对象的指针能转换成const void

转成布尔类型

如果指针或算数类型为0,转成false。

转成常量

允许将指向非常量类型的指针转换成指向常量类型的指针,引用同理。

类类型定义的转换

有编译器自动执行,但是每次只执行一种

显式转换

也称强制类型转换(cast)

int j,j;
double slope = i / j;

命名的强制类型转换如下形式

cast-name<type>(exoression);

tpye是转换的目标类型
expression是要转换的值。
如果type是引用,则结果是左值。
cast-name是 static_cast,dynamic_cast,const_cast和reinterpret_cast
static_cast

任何明确定义的类型转换,只要不包含底层const,都可以使用static_cast。

double slope = static_cast<double>(j) / i;

当需要把较大的算术类型赋值给小类型,很有用。
强制类型转换告诉读者和编译器,不在乎精度损失,因此警告会被关闭。

对于编译器无法自动执行的,static_cast也能做到。例:

//找回存在void*指针的值
void *p = &d;
double *dp = static_cast<double*>(p);
//把指针存放在void*中,并用static_cast强转回来是,应该确保指针值不变。
//即转换的结果和原始地址相等,因此要确保转换后所得类型就是指针所指类型。
const_cast

只能改变运算对象的底层const
用于去掉const性质。

const char *cp;
const_cast<char*>(cp);      
//但是不用用来挟制,是未定义的行为
char *p = const_cast<char*>(pc);
static_cast<string>(cp);    //正确,字符串字面值转换成string
const_cast<string>(cp);     //错误,const_cast只能改变常量属性
reinterpret_cast

为运算对象的位模式提供较低层次上的重新解释

int num = 0x00636261;//用16进制表示32位int,0x61是字符'a'的ASCII码
int * pnum = &num;
char * pstr = reinterpret_cast<char *>(pnum);
cout<<"pnum指针的值: "<<pnum<<endl;
cout<<"pstr指针的值: "<<static_cast<void *>(pstr)<<endl;//直接输出pstr会输出其指向的字符串,这里的类型转换是为了保证输出pstr的值
cout<<"pnum指向的内容: "<<hex<<*pnum<<endl;
cout<<"pstr指向的内容: "<<pstr<<endl;

//结果为
0x7ffcfa7e861c
0x7ffcfa7e861c
636261
abc

两个指针的值是完全相同的,只是内容不一样。

字符串指针要遇到""才表示结束。

如果num值为0x63006261,输出为ab
如果num值为0x64636261,输出就不确定,可能出错。

尽量少用。

dynamic_cast

支持运行时的类型识别

原文地址:https://www.cnblogs.com/aqq2828/p/13964239.html