当前位置: 首页 > news >正文

《C++ primer》第四章

一、基础

  • 表达式由一个或多个运算对象组成。字面值和变量是最简单的表达式。
  • 一元运算符、二元运算符、三元运算符,分别由一个、两个、三个运算对象
  • 对于含有多个运算对象的复杂表达式,要理解运算符的优先级、结合率和运算对象的求值顺序。如果优先级不同,则先算优先级高的运算;如果优先级相同,则按照结合律确定如何计算。在大多数情况下,不会明确指定求值顺序。括号无视优先级和结合律
/*有四种运算符规定嘞运算对象的求值顺序
 *&&      //先求左侧运算对象的值,只有当左侧运算对象为真时才会继续求右侧运算对象的值
 *||      //先求左侧运算对象的值,只有当左侧运算对象为假时才会继续求右侧运算对象的值
 *?:     //
 *,       //从左到右*/

/*int i = f1() * f2();                    //如果f1和f2之间相互影响,那么求值顺序会影响i的结果
 *std::cout << i << " " << ++i << endl;   //先求++i,还是先输出,对输出结果有影响*

/*两条经验规则:
 *拿不准优先级、结合律时,使用括号强制让表达式的组合关系符合程序逻辑要求
 *如果改变了运算对象的值,在表达式的其它地方不要再使用这个运算对象。当然,*++iter这种情况除外*/
  • 在表达式求值时,运算对象的类型也许并不相同,此时要转化成同一种类型,转换规则有哪些?
  • 重载运算符时,运算对象的类型和返回值类型可以自定义,但是运算对象个数、运算符优先级和结合率是无法改变的
  • 右值是取不到地址的表达式;左值是能取到地址的表达式。
  • 一个对象被用作右值时,用的是对象的值;一个对象被用作左值时,用的是对象的身份(内存中的位置)。当一个左值被当成右值时,实际上使用的是它的内同。
  • 内置类型和迭代器的递增递减运算符作用域左值运算对象,前置所得的也是左值,而后置所得是右值(可以理解为得到的一个变化前的副本)
  • 对于int *p,decltype(&P)得到的是int**,是左值;decltype(*P)得到的是int&,是左值。

二、运算符

2.1、算数运算符

/*算术运算符包括:
 * +(一元正号)、-(一元负号)
 *
 * *、/、%
 *
 * +(加号)、-(减号)
 *
 *算术运算符是从左向右结合
 *加、减可以运用于指针,加减单位为指针指向的数据单元的类型所占的空间大小*/

/*算术表达式可能产生未定义结果:
 *Ⅰ、数学性质本身,例如除0操作
 *Ⅱ、计算机表示溢出*/

/*取模操作,两个运算对象必须是整数,如何理解有的运算对象是负数的情况呢?
 *如果m、n皆为整数,并且n非0时,满足(m/n)*n + m%n即可
 *-21%-8 = -5; -21/-8 = 2;
 *-21%8 = -5;  -21/8 = -2;*/

2.2、逻辑和关系运算符

/*逻辑与关系元素运算符包括:(除!外,均为从左向右结合)
 *!,逻辑非,     !expr
 *
 *<,小于,        expr1 < expr2
 *<=,小于等于,   expr1 <= expr
 *>,大于,        expr1 > expr2
 *>=,大于等于,   expr1 >= expr
 *
 *==,等于,       expr1 == expr2
 *!=,不等于,     expr1 != expr2
 * 
 *&&,逻辑与,     expr1 && expr2
 *||,逻辑或,     expr1 || expr2*/

/*逻辑与和逻辑或,用来判断若干条件是否同时成立\部分成立
 *逻辑非,会将运算对象的值取反后返回
 *关系元素符,会返回布尔值,所以几个运算符连写在一起会产生意想不到的结果
 *进行比较时,除非比较的对象时布尔类型,否则尽量不用true和false与之比较*/

2.3、赋值运算符

/*赋值运算符的左侧运算对象必须是一个可修改的左值
 *若赋值运算符的左右两个运算对象类型不一致,则右侧运算对象转化为左侧运算对象的类型
 *C++11允许使用花括号括起来的初始值列表作为右侧的运算对象,此时,如果存在窄化转化,将会报错
 *赋值运算符满足从右向左的运算顺序,对于多重赋值语句,左侧类型要么与右侧一致,要么可有右侧转化得到
 *复合运算符:对对象施以某种运算,然后把计算结果再赋给该对象
 *+=、-=、*=、/=、%=、<<=、>>=、&=、^=、|=均为复合运算符*/


int k{3.12};        //错误,浮点数的表达范围比int要大,窄化转换
int val, *pval;
val = pval = 0;     //错误,pval是int*,val是int,类型不同并且int无法通过int*转化得到
string s1, s2;
s1 = s2 = "ok";     //正确,虽然ok是字面值常量,但是可以转化成string

2.4、递增和递减运算符

/*递增和递减分为前置和后置两种:
 *前置:对对象进行自增或自减之后,返回对象,可以作为左值
 *后置,对对象进行自增或自减,返回对象修改之前的副本,是右值
 *在不需要用到修改前的值时,选用前置,可以减少副本的开销*/

vector<int> vec;
……                  //往vec中加入一些对象
auto pbeg = vec.begin();
while (pbeg != v.end() && *beg >= 0)
    std::cout << *pbeg++ << std::endl;
/*后置递增优先级高于解引用,故而*pbeg++相当于*(pbeg++)
 *如果使用前置,那么无法输出第一个元素,而且如果vec中没有负值,程序可能试图访问一个不存在的元素*/

2.5、成员访问运算符

/*点运算符和箭头运算符均可用于访问成员
 *箭头运算符作用于一个指针类型的对算对象,结果是左值
 *点运算符的成员所属的对象是左值,则结果为左值,否则为右值*/

struct Point {
    int x;
    int y;
};

Point getPoint() {
    return Point{1, 2};  // 返回一个临时对象(右值)
}

int a = getPoint().x;  // getPoint().x 是右值,因为 getPoint() 返回的是右值
getPoint().x = 10;     // 错误:getPoint().x 是右值,不能赋值

2.6、条件运算符

/*条件运算符的(?:)允许我们把简单的if-else逻辑嵌入单个表达式
 *嵌套条件运算符中,条件运算符是从右向左开始结合,所以靠右的条件运算构成靠左条件运算的:分支
 *条件运算符优先级非常低,通常在其两侧加上括号*/

std::cout << ((grade < 60) ? "fail" : "pass") << std::endl;   //正确
std::cout << (grade < 60) ? "fail" : "pass";                 //输出0或者1
std::cout << grade < 60 ? "fail" : "pass" << std::endl;       //错误,试图比较cout和60

2.7、位运算符

/*位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合
 *位运算提供检查和设置二进制位的功能*/

/*位运算符包括:满足左结合率
 *~,位取反       ~expr
 *
 *<<,左移        expr1 << expr2
 *>>,右移        expr1 >> expr2
 *
 *&,位与         expr1 & expr2
 *
 *^,位异或       expr1 ^ expr2
 * 
 *|,位或         expr1 | expr2*/

/*关于符号位的处理没有明确规定,因此建议仅将位运算符用于处理无符号数
 *若运算对象为“小整型”,则其值会被自动提升成int型
 *在移位运算符中,右侧的数必须严格小于结果的位数,否则将产生未定义行为
 *移位运算符的优先级比算术运算符优先级低,比关系、赋值、条件运算符高*/

unsigned long qiuz = 0;             //将长整型数置为0
quiz != 1UL << 27;                  //将quiz的位置27置为1
quiz &= ~(1UL << 27);               //将quiz的位置27置为0
bool flag = quiz & (1UL << 27)      //得到quiz的位置27的状态

2.8、sizeof运算符

/*sizeof运算符的两种形式:
 *Ⅰ、sizeof(type),即sizeof(类型),要求某个类型所占空间大小时,一定要用括号括住类型名,否则会报错
 *Ⅱ、sizeof expr,即sizeof(表达式)
 *
 *sizeof运算符的结果:
 *Ⅰ、对char或者char类型表达式执行sizeof运算,结果为1
 *Ⅱ、对引用类型执行sizeof,得到被引用类型所占空间的大小
 *Ⅲ、对指针执行sizeof,返回指针所占空间的大小
 *Ⅳ、对解引用指针执行sizeof,得到指针指向的对象所占空间大小,指针不需要有效
 *Ⅴ、对数组执行sizeof,不会把数组转换成指针进行处理,得到数组所有元素所占的空间大小
 *Ⅵ、对string和vector进行sizeof,没有太大意义,不能得到对象中元素占用的空间大小*/

int arr[3];
int *p;
int &ref = arr[0];
sizeof(arr);        //大小为3个int型大小
sizeof(*p);         //大小为1个int型大小
sizeof(p);          //大小为1个int*型大小
sizeof(ref);         //大小为1个int型大小

2.9、逗号运算符

  • 逗号运算符含有两个运算对象,按照从左到右一次求值,逗号运算符的最终结果是右侧表达式的值

2.10、类型转换

  • 隐式类型转化
/*隐式类型转换:
 *Ⅰ、在大多数表达式中,比int型小的整型值首先提升为较大的整型
 *Ⅱ、在条件中,非布尔值转化为布尔值
 *Ⅲ、初始化和赋值时,右侧对象转换成左侧运算对象的类型
 *Ⅳ、若算数运算或关系运算的运算对象有多种类型时,需要转化成一种类型,尽量不损失精度
 *Ⅴ、函数调用时,设计的类型转换*/

/*算术转换:
 *运算符的运算对象将转换成最宽的类型
 *Ⅰ、对于bool、char、signed char、unsigned char、short、unsigned short,转换成较大的整型
 *Ⅱ、一个对象是无符号,一个是有符号,如果无符号类型不小于有符号类型,则将带符号类型转化成无符号
 *Ⅲ、如果有符号类型大于无符号,如果无符号类型的所有值均可存于有符号类型,则无->有,否则有->无*/
3.14159L + 'a';       //'a'被提升为int,然后int转化为long double
dval + ival;          //ival转化为double
dval + fval;          //fval转化为double
ival = dval'          //dval转化成int
flag = dval;          //dval转化成bool
cval + fval;          //cval提升为int,int转化为float
sval + cval;          //两者均被提升为int
cval + lval;          //cval被转化为long
ival + ulval;         //ival被转化为unsigned long
usval + ival;         //根据unsigned short和int所占空间大小进行转化
uival + lval;         //根据unsigned int和long所占空间大小进行转化

/*Ⅰ、数组转化成指针:大多数情况下,数组自动转化成指向数组首元素的指针
 *数组被当作decltype的参数,作为取地址符、sizeof以及typeid等的运算对象时,不转化成指针
 *Ⅱ、指针转化:0和nullptr可以转换成任意指针类型,任意类型的非常量指针能转化成void*,任意类型指针可以转化成const void* 
 *Ⅲ、类类型定义的转化,如while(cin >> s) {},istream类型的值被转化成布尔值*/
  • 显示转化
/*强制类型转化形式:cast-name<type>(expression);
 *其中cast-name有static-cast、dynaic-cast、const-cast、reinterpret-cast四种*/

/*static-cast:任何具有明确定义的类型转化,只要不包含底层const,均可使用该方式*/
int i, j;
double dval = static_cast<double>(j) / i;
void *p = &dval;                             //正确,任何非常量对象的地址都能存入void*
double *dp = static_cast<double*>(p);        //正确,将void*转化回初始的指针类型

/*const-cast:只能改变运算对象底层的const*/
const char *pc;
char *p = const-cast<char*>(pc);        //正确,但是通过p写值是未定义行为
string *ps = const-cast<string>(pc);    //错误,const-cast只能改变常量属性

/*reinterpret-cast:为运算对象的位模式提供较底层次上的重新解释*/
不建议使用,不学了,啊哈哈哈哈哈

/*能不进行强制类型转化就不进行强制类型转换*/

/*旧时的强制类型转化:
 *Ⅰ、type (expr)
 *Ⅱ、(type) expr*/

2.11、运算符优先级

太多了,不记了,不知道咋办就用括号

提示:

  • void*是一种无类型指针,编译器无法知道它指向的具体数据类型,无法直接解引用

相关文章:

  • FOC 控制笔记【二】无感控制、滑膜观测器和PLL
  • 用《设计模式》的角度优化 “枚举”
  • Python零基础学习第三天:函数与数据结构
  • PyTorch深度学习在硬件与资源限制下分布式训练和多GPU加速等技术的实例代码
  • 从零构建高可用MySQL自动化配置系统:核心技术、工具开发与企业级最佳实践
  • Linux 指定命令行前后添加echo打印内容
  • Unity URP渲染管线烘焙场景教程
  • docker装Oracle
  • Spring MVC源码分析のinit流程
  • Rust语言:开启高效编程之旅
  • 线程安全---java
  • 阿里发布新开源视频生成模型Wan-Video,支持文生图和图生图,最低6G就能跑,ComFyUI可用!
  • 比特币中的相关技术
  • Oracle数据恢复:闪回查询
  • 工程化与框架系列(26)--前端可视化开发
  • 【芯片验证】verificationguide上的36道UVM面试题
  • 模型压缩技术(二),模型量化让模型“轻装上阵”
  • USB2.0 学习(1)字段和包
  • 游戏官方网站:pc页面与移动端布局做到响应式的因素
  • 点云从入门到精通技术详解100篇-基于深度学习的三维点云分类分割
  • 张汝伦:康德和种族主义
  • 刘晓庆被实名举报涉嫌偷税漏税,税务部门启动调查
  • 讲武谈兵|视距外的狙杀:从印巴空战谈谈超视距空战
  • 普京批准俄方与乌克兰谈判代表团人员名单
  • 科技部等七部门:优先支持取得关键核心技术突破的科技型企业上市融资
  • 首个偏头痛急性治疗药物可缓解前期症状