仅供参考,欢迎交流和指正 power by Qi
| 运算符类型 | 左右值说明 |
|---|---|
| 算术运算符 | 运算对象和求值结果都是右值 |
| 逻辑和关系运算符 | 运算对象和求值结果都是右值 |
| 赋值运算符 | 左侧对象为可修改的左值,求值结果为左值 |
| 递增递减运算符 | 运算对象为左值,前置递增和递减返回左值,后置返回右值 |
| 成员访问运算符 | 1.箭头运算符作用域一个指针对象返回一个左值2.点运算符,访问的所属对象为左值则运算结果为左值,访问对象为右值则结果为右值 |
| 条件运算符 | 如果条件运算符的两个表达式都为左值,或者表达式都可以转化为同一类型,则求值结果为左值,否则为右值 |
| sizeof 运算符 | 返回值为size_t类型,左值 |
| 逗号运算符 | 返回值为右侧的运行对象,右侧运行对象为左值则为左值,否则为右值 |
基础
需要弄清楚三个东西,表达式、运算符和运算对象
1.表达式由一个或者多个运算对象组成,字面值和变量是最简单的表达式,表达式将得到一个结果,得到这个结果的过程叫做求值。
2.运算对象在我的理解中就是定义的变量以及字面值
3.运算符就是±*/这些符号,运算符作用于运算对象,运算符和运算对象组成了表达式
我觉得不需要死记,但是需要知道有这么个概念存在。
1.运算符根据需要运算对象数量,分为一元,二元,三元,函数可以看成是一个多元运算符
2.表达式求值的过程中存在着运算对象的转换,具体怎么转换的,还得往下看
3.C++语言本身定义了运算符在内置类型和复合类型,int,int*等等中的操作action。如果想在类类型中使用,需要自己重载这些运算符的意义。
4.左值和右值,在C语言中,左值就是可以放在=左边的运算对象,右值只能放在=右边。在C++中,目前还不太清楚左值和右值的具体区分。我目前的理解是,左值其内容可以被修改,右值就是固定死的,没法被修改。比如1这个数字是固定死的,所以他是右值,但是int b=1;b的内容是可以修改的,所以b是左值。
左值不一定出现在=号的左边。
5.优先级和结合律,和学数学时一样,运算符有自己的优先级,C++的表达式计算规则是,先算优先级高的运算符,再算优先级低的运算符,如果有括号,先算括号里面的。相同优先级的运算符,按照从左至右的方法,依次调用
6.求值顺序。之前说了优先级相同,按照从左到右的算法计算运算符,但是对于二元运算符,比如f1()+f2(),f1()和f2()的调用顺序,c++语言本身是没有规定的。 所以这就很容易写错,在一个表达式中,如果运算对象操纵同一个对象,就会产生未定义的行为。比如
int i = 0;
cout << i << " " << ++i << endl;
输出的结果可能是0,1或者1,1,这取决于编译器是如何解析表达式的。感兴趣的可以查查编译原理的书籍。
为了避免这种未定义的行为,在书写一个表达式的时候,不要书写作用于同一个对象的运算对象。
在平时写表达式的时候,多写括号。。。这样可读性会更高
算术运算符
算术运算符有以下7个:

其中一元的+和-优先级最高,所有的算术运算符都能够作用于算术类型。
注意char和bool也算算术类型。
2.在表达式运算中,算术运算符左右两边的类型算术类型不一致时,会将较小整型转化为较大整型。
bool a = true;
int b = 12;
double c = 3.0;
auto d = a + b + c;
编译器自动推断的d的值为double类型,注意bool类型的
变量最好不要参与数值运算
3.所有的算术运算符都可以作用域算术类型,除此之外一元正号运算符、加法、减法运算符可以作用于指针类型,一元正号运算符返回指针的副本。
4.之前说了bool类型的变量最好不要参与数值计算。
bool a = true;
bool b = -a;
>>> b的值为true
解析,因为在进行算术运算时,bool类型的对象会被提升为int类型,a为true,按照C++的规定在int类型中为1;那么-a也就是-1;但是C++规定,int类型的变量为0的时候转化bool类型才是false,否则为true。所以这里就得到的true,并不是false
5.对于+、-、*、/、%,在算术运算中分别叫做,加减乘除和取余。
(1)注意对于除法运算,如果两个整数相除,得到的结果还是整数,会摒弃小数部分,而不是小数部分四舍五入。
cout << 59 / 10 << endl;
结果为5
(2)对于取余,要求左右两边必须为整数类型,且求值符号由分母决定,
cout<<233%2.33<<endl;
编译器会报错
cout << 59 % 10 << endl;
结果为9
cout << -59 % 10 << endl;
结果为-9
cout << 59 % -10<< endl;
结果为9
练习
4.4
(((12 / 3) * 4) + (5 * 15)) + ((24 % 4) / 2)
91
先算优先级大的运算符,优先级相同遵循左结合率
4.5
-86
-19
42
-2
4.6
int a;
while (cin >> a) {
int b = (a % 2) == 0 ? 1 : 0;
cout << b<< endl;
}
4.7
溢出我的理解是,想要表达的值超过了变量本身能够表示的范围
short a = 32767;
short b = a+1;
char a = 256
unsigned int a = -1;
4.3 逻辑和关系运算符

一种记忆方法就是下面的if判断语句里面的内容。先算!a,如果为true,再算b>c.
bool a=false;
int b=2;
int c=1;
if(!a&&b>c){
}
1.关系运算符表用于算术类型和指针类型,逻辑运算符作用于任意能够转化为bool类型的类型。关系运算符和逻辑运算符返回的类型都是bool类型。
2.逻辑与和逻辑或采用短路求值的策略,对于逻辑与,只有当左侧求值为true时,才计算右侧表达式,如果左侧表达式为false,则不再计算右侧表达式。逻辑或则是只有左侧表达式为false时,才计算右侧表达式。如果左侧表达式为true,则不计算右侧表达式。
经常有对于短路运算的考察。
3.在使用关系运算符时,一个容易犯错的地方是将多个变量连在一起比较大小,这个是错误的。因为表达式a<b返回的是他们之间的大小的bool类型,然后再将bool类型和c进行比较,这样是肯定会出错的。
if(a<b<c){
//todo
}
4.在比较大小时,最好不要使用bool类型进行比较,因为bool类型判定0为false,非0为true,比较时容易出问题,所以除非要比较的对象为bool类型,不然最好不要在比较运算符中使用bool类型。
5.逻辑和关系运算符的运算对象和求值结果都是右值。
练习
4.8
相等性运算符的优先级大于逻辑与和逻辑或
逻辑非 > 大小关系运算符 > == 和!= >&& >||
4.9
首先判断char型指针cp是否为空,然后再判断指针cp所指向的c风格字符串是否为空
在做的过程中发现一个问题,右值的空字符串,在逻辑运算符和转换bool类型时,会变为true。但是左值的空字符串不会。这里的右值空字符串,我理解的就是"",如果不对到时候会纠正
const char *cp = "";
if (cp&&*cp) {
cout<<"有字符"<<endl;
}
else {
cout<<"空字符"<<endl;
}
>>空字符
const char *cp = "";
bool b = *cp;
//cout<<b<<endl;
if (cp&&"") {
cout<<"有字符"<<endl;
}
else {
cout<<"空字符"<<endl;
}
>>有字符
const char *cp = "";
bool b = *cp;
cout<<b<<endl;
bool c = "";
cout << c << endl;
>>0
>>1
这个故事告诉我们,不要乱转换类型
4.10
int a = 0;
while (cin>>a) {
if (a==42) {
break;
}
}
4.11
if (a>b&&b>c&&c>d) {
}
4.12
首先比较j和k的大小,然后判断j<k返回的bool类型是否和i相等。
4.4赋值运算符
1.赋值运算符的左侧必须是可修改的左值,定义变量并赋予一个初始值,叫做初始化,不是赋值。
int a = 3;//初始化
1=a;//error 字面值是右值
i+j=a;//error //算术表达式也是右值
const int b = 344;//初始化
b= a;//error b是不可修改的左值
2.可以使用列表初始化的方式进行赋值,对于内置类型,初始化列表中只能有一个值,对于vector容器,初始化列表中的元素则可以有多个。
3.赋值运算符的运算结果是运算符的左侧对象,所以是一个左值。如果赋值语句的左右两个对象类型不一样,则会将右侧对象转化为左侧对象的类型。(而算术运算符一般是将较小整型转化为较大整型)。
int a = 1;
int b = 3.14;
a = b;//accept
a={3.14};//error 但是使用初始化列表时,类型转换如果存在数值丢失则会报错
4.赋值运算符遵循右结合律,即将右侧对象赋值给左侧对象,同时返回左侧对象,所以我们可以连续赋值。
int a,b;
a = b = 1;
复习:运算符在优先级相同的时候,遵循左结合律,先计算处于左侧的运算符,但是运算符两侧的表达式谁先执行是不一定的,由编译器决定。
5.C++允许使用赋值表达式作为条件,但是赋值运算符和==是不一样的。所以在使用==时,我们可以这样写,减少写错的概率。
if(1=="a"){
}
这种写法对于==是正确的,但是如果误写为=则会报错,因为赋值运算符的左侧对象必须是可修改的左值
6.复合赋值运算符,就是先对对象施加某种操作,再将对象赋值给自己本身。常用的+、-、*、/、%以及位运算符都有复合赋值运算符。他们和先做自己的操作,再赋值的唯一区别就是,复合赋值运算符只求值一次,而普通运算符则求值两次。
7.赋值运算符的优先级通常都很低,所以在和其他的运算符一起使用的时候,需要用括号括起来
int a = 0;
a = a+1;//首先a+1求值一次,然后是赋值运算符,还要求值一次
a +=1;//只求值一次
练习
4.13
a。3和3.0
b。3和3.5
4.14
第一句会报错,错误原因我已经写在赋值运算符相关的知识点部分了
第二句会执行if作用域内的内容,因为赋值运算符返回的结果为42,42不等于0,所以为true
4.15
不能够将int的指针类型,转化为int类型。
double dval; int ival; int *pi;
ival=dval = 0;
pi = 0;
4.16
a.赋值运算符的优先级较低,所以会先计算!=的部分,修改:需要加括号
b.赋值语句的运算结果返回左侧运算对象,所以返回的对象的值是1024,永远为true。
4.5 递增和递减运算符
1.++和–为对象提供+1和-1的操作,可以作用于迭代器,迭代器一般都支持++、–、==、!=这些操作符。
2.++和–分为前置++和后置++,其中前置++,将对象+1,并返回+1之后i的结果,后置对象将对象+1,返回+1之前的副本,一般情况下都是用前置++
3.在使用++和–的时候,注意最好不要在一个表达式中,多次使用同一变量,不然容器出错。
*beg=toupper(*beg++)
本意是先将当前字符转化为大写,指针再向前+1,
但是现在赋值运算符左边的beg已经向前移动了,但是后面的beg确实++之前的副本。
- 前置++和–返回运算对象的左值,后置++和–返回运算对象的右值。
练习
4.17
前置递增返回左值,后置递增返回右值
前置递增返回+1之后的值,后置递增返回+1之前的值
4.18
返回的+1之后的指针解引用的结果
4.19
a。写法是正确的,如果指向int类型的指针ptr,不为空,则将ptr+1,并解引用。
b。写法是正确的,因为&&是确定了调用顺序的,短路求值
c。写法不正确,因为无法确定比较运算符<=,左侧的运算对象先调用还是右侧先调用。
如果表达的意思为比较vec中第ival+1和val位置的元素的大小,则表达式可以修改为
vec[ival+1]<=vec[ival]
++ival;
4.5 成员访问运算符
1.成员访问运算符分为点运算符和箭头运算符,点运算符作用于对象,箭头运算符作用指向对象的指针,箭头运算符相当于先解引用,再指向对象的成员。
string a="hello ";
a.size();
string *b = &a;
b->size();
(*b).size();
以上三种访问成员函数的意思都是一样的
2.注意解引用的优先级低于点运算符,所以在一起使用点运算符和解引用的时候,需要为解引用运算符加上括号。
练习
4.20
我们假设这个迭代器就是begin()
a。合法,迭代器向前移动一个位置,并解引用移动前的迭代器
b。将iter指向的对象加1,但是不合法,因为iter指向的是string类型
c。不合法,点运算符的优先级比*要高
d。合法,访问iter所指向的对象的size()函数,但是如果iter为end(),vs2017下编译报错,这里就不考虑特殊情况了
e。合法,前置++,–和后置++,–运算符的优先级都比解引用运算符高。
f。合法
4.7 条件运算符
1.条件运算符为?:,相当于把if…else…的规则嵌入到了一个表达式中,形式如下:cond为判断条件的表达式,expr1和expr2为表达式,expr1和expr2他们的类型相同或者可以转化为相同的类型。,如果cond为真,则计算expr1,如果cond为假,则计算expr2.
cond ? expr1 : expr2
--example_1--
int i = 20;
string grade = i > 60 ? "accpet" : "reject";
cout<<grade<<endl;
如example1所示,条件运算符只执行expr1和expr2中的一个。
2.条件运算符可以嵌套使用,即一个条件运算符可以当做另外一个条件运算符的cond或者expr
---example_2---
string grade = (i > 80) ? "good" : (i > 60 ? "accpet" : "reject");
cout << grade << endl;
//一个条件表达式为另一个条件表达式的expr,正如书上写得那样,嵌套的条件运算符可读性下降了
//所以我在这里加上了几个括号,为了方便阅读
3.条件运算符遵循右结合律
关于结合律,结合律指的是一个表达式中,运算对象组合的顺序,并不是求值顺序,即
a+b+c+d+e+f
按照左结合律,是先组合a+b,再组合a+b的结果+c。但是a+b中,先调用a还是先调用b是由编译器的实现决定的,除非某些运算符它要求了调用的顺序,比如说&&和||。
那么这里说条件运算符遵循右结合律,说的是,条件运算符的表达式中,按照从右往左的顺序组合运算对象。
即
string grad = i > 90 ? "good" : i > 60 ? "accped" : i > 30 ? "bad" : "go home";
-->
string grad = i > 90 ? "good" : (i > 60 ? "accped" : (i > 30 ? "bad" : "go home"));
组合出运算对象之后,再按照运算符的求值顺序(如果有规定的话),求值。
以上是关于结合律我的理解,如有错误,欢迎讨论和纠正
4.条件运算符的优先级很低,所以直接使用cout输出,而不注意运算符优先级的话,很可能会出现意想不到的结果。
cout<< i > 60 ? "accpet" : "reject"<<endl;
这条语句是编译不过的,因为条件运算符的优先级很低,所以这个表达式等价于
cout<<i;
cout>60?"accpet":"reject"
在vs2017中的ostream是没有重载>运算符的,所以会编译出错。
cout<< (i > 60) ? "accpet" : "reject";
而这样写,等价于
cout<<(i>60)//根据i的结果得到1或者0,加入为1
1?"accped":"reject"
所以在直接输出包含条件运算符的表达式时,一定要打括号。
之前写的时候老是报错,原来是这个原因
5.条件运算符的两个表达式都为左值或者可以转化为统一类型时,运算结果为左值,否则为右值。
练习
4.21
vector<int> number_vec = {1,2,3,4,5,6,7,8,9,0};
for (auto &item:number_vec) {
item = (item % 2 == 1) ? item * 2 : item;
}
for (const auto &item :number_vec) {
cout<<item<<endl;
}
4.22
略,但是肯定是多个if的容易理解,之前也看到条件运算符的嵌入层次高了之后,可读性下降了很多
4.23
因为条件运算符的优先级较低,但是算术运算符优先级较高,所以算术运算符先运算,得到一个string对象,string对象和字符字面值作比较,从而导致错误。
修改如下
string p1 = s + (s[s.size() - 1] == 's' ? "" : "S");
4.24
string grad = i > 90 ? "good" : (i > 60 ? "accped" : (i > 30 ? "bad" : "go home"));
如果这个表达式按照左结合律来做得话, 得到得结果应该是
string grad = ((i > 90 ? "good" : i > 60) ? "accped" : i > 30) ? "bad" : "go home";
求值过程使用括号展示

本文总结了C++ Primer中关于运算符的知识,包括算术、逻辑和关系运算符,赋值运算符,递增和递减运算符,以及条件运算符。强调了左值和右值的概念,运算符的优先级和结合律,以及在使用中需要注意的潜在问题,如运算对象的转换和求值顺序。同时,通过练习题加深了对运算符的理解。

被折叠的 条评论
为什么被折叠?



