C++ Primer第四章①

C++ Primer

第四章 表达式

C++ 提供了一套丰富的运算符,可以用于内置类型对象的操作。即便运算对象是自定义的类类型,C++也允许程序员指定上述运算符的含义(真的给了程序员很大的自由和挑战)。本章主要介绍由语言本身定义,并用于内置类型运算对象的运算符,同时,简单介绍几种标准库定义的运算符,关于类类型的自定义运算以后再介绍。
表达式由一个或多个运算对象组成,对表达式求值将得到一个结果。

基础

有几个基础概念对表达式的求值过程有影响,本节先简单介绍一下它们。

基本概念

C++定义了两个概念:

  • 一元运算符:作用于一个运算对象,如取地址符&
  • 二元运算符:作用于两个运算对象,如乘法运算符 一些符号两种都行,比如乘法是二元,解引用是一元。
    左值和右值
    这是两个非常难搞的概念,这里我就借用作者的话,他概括得很好:
    当一个对象被用作右值的时候,用的是对象的值;当对象被用作左值的时候,用的是对象的身份。
    不同的运算符对运算对象的要求各不相同,有的需要左值运算对象,有的需要右值运算对象;返回值也有差异,有的得到左值结果,有的得到右值结果。一个重要的原则是:在需要右值的地方可以用左值代替,但不能把右值当成左值使用,也就是说,身份可以当值,值不能表示身份。
    怎么理解上面一堆话呢?身份就是内存中的位置,值就是内存中的内容,你知道了左值,也就是内存中的位置,当然可以拿到那个内容,所以左值能代替右值,但是你知道了内容,你是找不到地址的,因为可能很多内存地址的内容都是这个右值啊。
    到目前为止,已经有几种我们熟悉的运算符是需要左值的:
  • 赋值运算符需要左值作为其左侧运算对象,得到的结果也是左值
  • 取地址作用于左值,返回一个指向该运算对象的指针,这个指针是右值
  • 内置解引用,下标,迭代器解引用的求值结果都是左值
  • 递增递减作用于左值,前置版本例如++i得到结果也是左值
    接下来在介绍运算符的时候,我会注明该运算符的运算对象是否必须是左值以及求值结果是否是左值。 使用关键字decltype的时候,左值和右值也有所不同:
    int a = 0;
    int *p = &a;
    decltype(*p) b = a; //p是指针,对p解引用生成左值,所以b的类型是int&
    decltype(&p) c = &p; //取地址符生成右值,c的类型是int**
    运算符的优先级和结合律与我们在数学中的习惯一致,不再赘述
求值顺序

即便有了上面那些规定,我们对于求值顺序还是不能确定的,比如

int i = f1() * f2();

我们不知道f1和f2哪个先调用,所以我们平时在写代码的时候要注意,不能确定执行顺序的表达式,不要去修改里面的对象,比如:

int i = 0;
cout << i << "  " << ++i << endl;

可能输出0 1,也可能输出1 1,最好不要这样写,我试了下devC++上输出1 1 也有一些运算符规定了运算对象的求值顺序,后面会介绍的。
黄金法则:不确定的时候用括号啊~

算术运算符

直接甩代码解释:

int i = 1024;
int j = -i; //j=-1024,i和j的地址不同
bool b = true;
bool b2 = -b //b2还是true,因为-b=-1,只要非零都是true,所以不要用布尔值参与计算

算术表达式可能会产生未定义的结果,有两个原因,一方面是数学原因,比如除数为0,一方面是计算机的特点,比如溢出了,就是一个特别大的数,超过int范围,这二者都要求我们程序员去避免

int a = 21 / 6; //3,余数不要了
int b = 42;
double pi = 3.14;
int c = b % 12; //c=6,求余运算
int d = b % pi; //报错,求余必须是整型

求余还有些注意的地方: m%(-n) = m%n; (-m)%n = -(m%n) 简单来说,就是n的符号省略,m的符号放括号外面。

逻辑和关系运算符

运算符 功能
! 逻辑非
&& 逻辑与
两个竖杠 逻辑或

其他如 <,>,<=,>=,!=,==都可以望文生义 &&和||都是短路求值,什么意思呢,比如求a&&b,如果a为假,编译器就不会去判断b的真假,因为结果肯定是假的,是不是很机智啊?

关系运算符

因为关系运算符的求值结果是布尔值,所以将几个关系运算符连写在一起会产生意向不到的结果:

int i = 0, j = 5, k = 2;
if(i<j<k) //条件为真,i<j为真,值为1,1<k,对的,其实不管i和j的值,这个条件都对
if(i<j && j<k) //正确写法,这里就是false了
相等性测试与布尔字面值
if(val) //只要val不是零,都是true就算val是double的0.5也是真

赋值运算符=

抛代码:

int i = 0, j = 0, k = 0;
const int ci = i;
12 = k; //错误:字面值是右值
i + j = k; //错误:算术表达式是右值
ci = k; //错误:ci是const左值
k = 3.14; //正确:k = 3
k = {3.14}; //错误:因为列表初始化不允许损失精度
i = j = 1; //正确:i和j都被赋值为1,类似cin >> i >> j;
int ival, *pval;
ival = pval = 0; //错误:不能把指针的值赋值给int
string s1, s2;
s1 = s2 = "OK"; //字符串字面值"OK"转换成string对象
i += j; //等价于i = i + j;
递增和递减运算符
int i1 = 1, j1 = 1;
int i2 = ++i1; //i2 = 2, i1 = 2
int j2 = j1++; //j2 = 1, j1 = 2,注意与上一句话的差别

在for循环中单用i++和++i完全一样,只有在上面代码那种情况下才不一样,单用的时候我们推荐++i,因为++i将对象本身作为左值返回,节省资源吧,i++则将对象原始值的副本作为右值返回。

在一条语句中混用解引用和递增运算符
//输出元素直到遇到第一个负值为止
auto beg = v.begin();
auto end = v.end();
while(beg != end && *beg >= 0)
{
    cout << *beg++ << endl; //按照优先级先输出当前值,再++(简洁是一种美德)
}
成员访问运算符

点运算符合箭头运算符都可用于访问成员,如下:

string s = "hey", *p = &s;
//以下三个n等价:
auto n1 = s.size();
auto n2 = (*p).size(); //括号不能少哦,因为解引用的优先级低
auto n3 = p->size(); //ptr->mem 等价于 (*ptr).mem

条件运算符

举个例子你一下子就明白了:

string res = grade < 60 ? "fail" : "pass";
//如果grade小于60,则res="fail",否则就为pass,其实也就是个偷懒的写法
全部评论
我只想要问下左值和右值,怎么越说越糊涂了呢、、、
点赞 回复 分享
发布于 2018-10-03 17:03

相关推荐

06-19 13:40
武汉大学 Java
点赞 评论 收藏
分享
兄弟们你们进大厂靠的是什么项目啊
DOTPHTP:课设改。其实项目什么的如果不是实习里面的生产项目的话,建议✍️那种自己想要做的。突出个人自驱力,而不是为了找工作不得不随波逐流这种
点赞 评论 收藏
分享
06-20 14:27
中山大学 C++
rt,day3就开始接需求
星际探神:你就想 你是水货他们都没面出来 他们也水 管他呢
点赞 评论 收藏
分享
评论
5
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务