C++primer学习笔记(三)

没啥好说的,就是+-/&=、==等。

左值和右值

简单归纳:当一个对象被用作右值的时候,用的是对象的值(内存);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。假设p的类型是一个int*,则decltype如果作用于一个表达式时如decltype(*p)得到的是一个引用类型,而decltype(&p)的结果是一个int **

运算符优先级

逻辑和关系运算符

其中逻辑与运算符(&&)和逻辑或(||)运算符是短路求值运算符:

  • 对于逻辑与运算符来说,当且仅当左侧运算对象为真时才对右侧运算对象求值。
  • 对于逻辑或运算符来说,当且仅当左侧运算对象为假时才对右侧运算对象求值。

赋值运算符

  • 赋值运算符满足右结合律,即
1
2
int a,b,c;
a = b = c =3;//可以连续赋值
  • 赋值运算优先级较低,所以在条件语句中,赋值部分通常加上括号,如: cpp int i; while((i=get_value())!=42){ //anything }

递增递减运算符

分前置版本和后置版本。前置版本先加1,然后将改变后的对象作为求值结果,后置版本也会将运算对象加1,但是求值结果是运算对象改变之前那个值的副本。

如果想测试一个算术对象或者指针对象的真值,最直接的方法就是将其作为if条件

1
2
3
4
if(val);//如果val是任意的非0值,条件为真
if(!val);//如果val是0,条件为真
有时我们试图将上面的真值测试写成如下形式:
ifval==true//只有当val等于1时条件才为真,这是因为,在进行比较之前会首先把true转换成val的类型,即ture换成1,false换成0

进行比较运算时除非比较的对象是布尔类型,否则不要使用布尔字面值true和false作为运算对象。

成员访问运算符

1
ptr->mem等价于(*ptr).mem;

条件运算符

cond ? expr1 : expr2
例如: string finalgrade = (grade<60)?“fail”:“pass”;
grade<60为真则取fail,为假则取pass。条件运算符的优先级太低,可能导致如下几种情况

1
2
3
cout << ((grade<60)? "fail : "pass");//输出pass或者fail
cout << (grade<60)? "fail : "pass";//输出1或者0!
cout << ((grade<60)? "fail : "pass");//错误:试图比较cout和60

位运算符

异或相异为1相同为0

sizeof运算符

sizeof运算符返回一条表达式或一个类型名字所占的字节数,返回的是值是一个size_t类型。运算符的运算对象有两种形式:
sizeof (type)
sizeof expr 第二种形式中,sizeof返回的是表达式结果类型的大小,并不实际计算其运算对象的值。 sizeof运算符的结果部分地依赖于其作用的类型:

  • 对char或者类型为char的表达式执行sizeof运算,结果得1
  • 对引用类型执行sizeof运算得到被引用对象所占空间的大小。
  • 对指针执行sizeof运算得到指针本身所占空间的大小
  • 对解引用指针执行sizeof运算得到指针指向的对象所占空间的大小,指针不需有效。
  • 对数组执行sizeof运算得到整个数组所占空间的大小,等价于对数组中所有元素各执行一次sizeof运算并将所得结果求和。注意,sizeof运算不会把数组转换成指针来处理。
  • 对string和vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间。

逗号运算符

逗号运算符含有两个运算对象,按照从左到右的顺序依次求值。
对于逗号运算符,首先对左侧的表达式求值,然后将求值结果丢弃掉,逗号运算符真正的结果是右侧表达式的值。如果右侧运算对象是左值,那么最终的求值结果也是左值。

类型转换

隐式类型转换

数组转换成指针:在大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针。当数组被用作decltype关键字的参数,或者作为取地址符(&)、sizeof及typeid等运算符的运算对象时,上述转换不会发生。用一个引用来初始化数组时,上述转换也不会发生。

指针的转换:C++还规定了几种其它的指针转换方式,包括常量整数值0或者字面值nullptr能转换成任意指针类型;指向任意非常量的指针能转换成void*;指向任意对象的指针能转换成const void*。

显示转换

强制类型转换:

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

注意:虽然有时不得不使用强制类型转换,但这种方法本质上是非常危险的。

命名的强制类型转换:cast-name<type>(expression);

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

static_cast:

任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。 例如,通过将一个运算对象强制转换成double类型就能使表达式执行浮点数除法;

1
2
3
double slope = static_cast<double>(j)/i;
void *P=&d;//正确,任何非常量对象的地址都能存入void*
double *dp = static_cast<double*>(p);//正确,将void*转换回初始的指针类型,转换应确保转换后所得的类型就是指针所指的类型。否则将产生未定义的后果。

const_cast:

const_cast只能改变运算对象的底层const

1
2
const char *pc;
char *p = const_cast<char*>(pc);//正确,但是通过p写值是未定义的行为

注:对于将常量对象转换成非常量对象的行为,我们一本称为“去掉const性质”。一旦我们去掉了某个对象的const性质,编译器就不再阻止我们对该对象进行写操作了。如果对象本身不是一个常量,使用强制类型转换获得写权限是合法的行为。然而如果对象是一个常量,再使用const_cast执行写操作就会产生未定义的后果。只有const_cast能改变表达式的常量属性,使用其他形式的命名强制类型转换改变表达式的常量属性都将引发编译器错误。同样的,也不能用const_cast改变表达式的类型:

1
2
3
4
5
string s="hello";
const char *cp=&s[0];
char *q = static_cast<char*>(cp);
static_cast<string>(cp);//正确:字符串字面值转换成string类型
const_cast<string>(cp);//错误:const_cast只改变常量属性

const_cast常常用于有函数重载的上下文中。

reinterpret_cast:

reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。举个例子,假设有如下的转换

1
2
3
int *ip;
char *pc = reinterpret_cast<char*>(ip);/*我们必须牢记pc所指的真实对象是int而非字符,如果把pc当成普通的字符指针使用就可能在运行时发生错误。例如:*/
string str(pc);/*将会导致异常的运行时行为。使用reinterpret——cast是非常危险的。用pc初始化str的例子很好地证明了这一点。编译器无法知道pc实际指向的是int的指针。*/

旧式的强制类型转换

在早期版本的c++语言中,显式地进行强制类型转换包含两种形式:

type(expr);//函数形式的强制类型转换

(type) expr;//c语言风格的强制类型转换

根据所涉及的类型不同,旧式的强制类型转换分别具有与const_cast、static_cast或reinterpret_cast相似的功能。如果替换不合法,则旧式强制类型转换执行与reinterpret_cast类似的功能。

原文:大专栏  C++primer学习笔记(三)


猜你喜欢

转载自www.cnblogs.com/chinatrump/p/11588810.html