C++Primer学习笔记:第4章 表达式

  • 表达式由一个或多个运算对象组成,对表达式求值将得到一个结果。字面值和变量是最简单的表达式,其结果就是字面值和变量的值。把一个运算符和一个或多个运算对象组合起来可以生成较复杂的表达式。

  • 重载运算符包括运算对象的类型和返回值的类型,都是由该运算符定义的;但是运算对象的个数、运算符的优先级和结合律都是无法改变的

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

    • 赋值运算符需要一个(非常量)左值作为其左侧运算对象,得到的结果也仍然是一个左值
    • 取地址符作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值
    • 内置解引用运算符下标运算符迭代器解引用运算符stringvector的下标运算符的求值结果都是左值
    • 内置类型和迭代器的递增递减运算符作用于左值运算对象,其前置版本所得到的结果也是左值
  • 如果表达式的求值结果是左值,decltype作用于该表达式得到一个引用类型

    int a = 1;
    int *p = nullptr;
    decltype(*p) b = a;	//b是int &类型
    decltype(&p) c = nullptr;	//c是int **类型 	
    
  • 优先级规定了运算对象的组合方式,但是没有说明运算对象按照什么顺序求值,在大多数情况下,不会明确指定求值的顺序。对于表达式int i = f1() * f2(),我们知道f1()f2()一定在执行乘法之前被调用,但是我们无法直到到底f1f2的执行先后顺序。对于那些没有指定执行顺序的运算符来说,如果表达式指向并修改了同一个对象将会引发错误并产生未定义行为(UB)。因为<<运算符没有明确规定何时以及如何对运算对象求值,因此cout << i << " " << ++i << endl是未定义的。

  • 有四种运算符明确规定了运算对象的求值顺序:逻辑与&&、逻辑或||、条件(三元)运算符?:、逗号运算符,

  • 运算对象的求值顺序与优先级和结合律无关,对每个运算对象的运算结果的计算顺序是通过优先级和结合律决定,但是对运算对象的运算顺序是不确定的。如果在一个表达式中有多个运算对象涉及对同一个对象的运算,那么很容易产生未定义的行为。例如f() + g() * h() + j()中,对这些函数的返回值的运算顺序是确定的,但是对这些函数的运算顺序是不确定的。

  • 书写复合表达式的准则:

    • 拿不准的时候最好使用括号来强制让表达式的组合关系复合程序逻辑的要求
    • 如果改变了某个对象的值,在表达式的其他地方不要再使用这个运算对象。这个其他地方是不包括当改变运算对象的子表达式本身就是另外一个子表达式的运算对象。例如:*++iter
  • 算数运算符的运算对象和求值结果都是右值。

  • 一元正号运算符、加法运算符和减法运算符都能作用于指针。当一元正号运算符作用于一个指针或者算术值时,返回运算对象的一个(提升后的)副本

    bool b = true;
    bool b2 = -b;	//相当于b2 = -1,所以b2为真	
    
  • 整数相除结果还是整数,参与取余运算的运算对象必须是整数类型

  • C++11新标准规定商一律向0取整(即直接切除小数部分)

  • 根据取余运算的定义,如果mn是整数且n非0,则表达式(m/n)*n+m%n的值和m相同。在C++11新标准中,除了-m导致溢出的特殊情况,其他时候(-m)/nm/(-n)都等于-(m/n)m%(-n)等于m%n(-m)%n等于-(m%n)

  • 逻辑运算符作用于任何能转换成布尔值的类型。逻辑运算符和关系运算符的返回值都是布尔类型。运算对象和求值结果都是右值

  • 逻辑与与逻辑或都采用短路求值。可以用左侧运算对象来保证右侧运算对象求值过程的正确性和安全性

  • 使用范围for循环时尽可能声明成引用类型,能够避免对元素的拷贝

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

  • char *cp; if (cp && *cp) {}表示判断指针cp所指向的数组是否为空,如果C字符串数组为空,则数组中只有一个空字符\0

  • 复制运算的结果是它左侧的运算对象,并且是一个左值。如果赋值运算符左右两个运算对象类型不同,则右侧运算对象将转换成左侧运算对象的类型。

  • C++11新标准允许使用花括号括起来的初始值列表作为赋值语句的右侧运算对象。如果左侧运算对象是内置类型,那么初始值列表最多只能包含一个值,而且该值不能有丢失信息的风险(所占用的空间不应该大于目标类型的空间)。对于类类型来说,赋值运算的细节由类本身决定。vector模板重载了赋值运算符而且可以接收初始值列表,当赋值发生时用右侧运算对象的元素替换左侧运算对象的元素。无论左侧运算对象的类型是什么,初始值列表都可以为空,此时编译器创建一个值初始化的临时量并将其赋给左侧运算对象(我认为没有默认构造函数的类可能不行)

    vector<int> a;
    a = {1, 2, 3, 4};			
    a = {1};
    a = {1, 2, 3, 4, 5, 6, 7};
    
  • 赋值运算满足右结合律。因为赋值运算符的优先级低于关系运算符的优先级,所以在条件语句中,赋值部分通常应该加上括号

    int i;
    while(1 == (i = getValue())) {//
    }
    
  • 使用复合运算符只求值一次,使用普通的运算符则求值两次(一次计算一次赋值)。因此尽量使用赋值运算符。

  • ++--可以用于迭代器,很多迭代器本身不支持算术运算。前置版本将对象本身作为左值返回,后置版本则将对象原始值的副本作为右值返回。如果不需要保存未修改版本的值,尽量使用前置版本不使用后置版本cout << *iter++ << endl;是一种被广泛使用的有效的写法。C++程序追求简洁、摒弃冗长,因此C++程序员应该习惯于这种写法。

  • 因为大多数的运算符都没有规定求值顺序,因此同一条表达式中最好在一个地方修改对象的值(且在改变这个对象以后就不要再进行使用),否则很容易产生未定义的行为(&& || , ?:除外)

  • ptr->item等价于(*ptr).item.运算符的优先级更高,因此括号必不可少。箭头运算符作用于一个指针, 结果是一个左值。点运算符作用于左值则结果是左值,作用于右值则结果是右值

  • 条件运算符cond ? expr1 : expr2,其中cond是判断条件的表达式,而expr1expr2是两个类型相同或者可能转换为某个公共类型的表达式。条件运算符是有求值顺序的,而且条件运算符只会对expr1expr2中的一个求值。当条件运算符的两个表达式都是左值或者能够转换成同一种左值类型时,运算的结果是左值,否则运算结果是右值

  • 条件运算符满足右结合律,意味着运算对象一般按照从右往左的顺序结合。条件运算的嵌套最好别超过二到三层。

    sting final_grade	= (grade > 90) ? "high pass": (grade < 60) ? "fail" : "pass";
    

    条件运算符的优先级比较低,因此最好在复合表达式中使用条件运算符的时候加上括号

  • 位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合(bitset可以表示任意大小的二进制位集合,也可以使用位运算符)

    ~ 		位求反		~expr
    <<		左移			expr1 << epxr2
    >>		右移
    &		位与
    ^		位异或
    |		位或
    

    如果运算对象是“小整型”,则它的值会被自动提升成较大的整数类型,如果运算对象是带符号的,有可能产生未定义的行为(如果有可能为负数),因此建议将位运算用于处理无符号类型

  • 移位运算符将经过移动的(可能进行了提升)左侧运算对象的拷贝作为球直接过。其中右侧的运算对象一定不能为负,而且值必须严格小于结果的位数,否则就会产生未定义的行为。二进制位或者向左移(<<)或者向右移(>>),移出边界外的位就被舍弃掉了。<<在右侧插入值为0的二进制位,>>对无符号数来讲是 在左侧添加0,但是对带符号类型依赖环境

  • unsigned char在位运算中会被提升为unsigned intunsigned long在任何机器上都至少拥有32位。1UL << x制造一个第x为1,其他位都为0的数字,对这个数字取反可以得到第x位为0,其他位都为1的数字。然后通过|&进行操作

  • sizeof运算符返回一条表达式或一个类型名字所占的字节数。sizeof运算符满足又结合律,得到的是一个size_t类型的常量表达式。运算符的运算对象有两种形式:

    sizeof (type)
    sizeof expr	
    

    在第二种类型中,sizeof返回的是表达式结果类型的大小,与众不同的一点是sizeof并不实际计算其运算对象的值。因此解引一个无效指针仍然是一种安全的行为,因为指针实际上没有被真正使用。sizeof不需要真的解引用指针也能知道它所指向对象的类型。

    Sales_data data, *p;
    sizeof *p;		//返回Sales_data的空间大小
    sizeof data.revenue
    sizeof Sales_data::revenue	//同上	
    

    在C++11新标准匀速我们使用作用域运算符来获取类成员的大小。通常情况下只有通过类的对象才能访问到类的成员,但是sizeof运算符无需我们提供一个具体的对象

  • 对数组执行sizeof运算得到整个数组所占空间的大小,等价于对数组中所有元素执行一次sizeof运算并将所得的结果求和。注意,sizeof运算符不会把数组转换成指针来处理。因此可以用数组的大小除以单个元素的大小得到数组中元素的个数

    constexpr size_t sz = sizeof(ia)/sizeof(*ia);
    int arr2[sz];		//arr2的大小和ia一样	
    
  • string对象或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间。实测了一下发现对vectorstring应该也一样)使用sizeof返回固定的大小,对于vector<int>返回24

  • 逗号运算符有两个运算对象,按照从左往右的顺序依次求值(同&& || ?:一样规定了运算顺序)。先对左侧的运算对象求值,然后将求值结果抛弃,真正的运算结果是右侧表达式的值,如果右侧运算对象是左值,则最终的求值结果也是左值。

  • 隐式类型转换:

    • 大多数表达式中,比int类型小的整型值首先提升为较大的整数类型
    • 在条件中,非布尔值转换为布尔值
    • 初始化和赋值语句中,右侧运算对象转换成左侧运算对象的类型
    • 如果算术运算或关系运算的运算对象有多种类型,需要转换为同一种类型
  • 整型提升负责把小整数类型转换成较大的整数类型,整数类型中比int小的类型如果参与运算,如果可以放到int里就会提升成int,否则提升成unsigned int

  • 隐式类型转换:

    • 数组转换成指针:在大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针。当数组被用作decltype关键字的参数,或者作为取地址符&sizeof以及typeid等运算符的运算对象时,以及用一个引用来初始化数组时,上述转换不会发生
    • 常量整数值0或者字面值nullptr能够转换为任意指针类型,指向任意非常量的指针能够转化成void*,指向任意对象的指针能够转化为const void*
  • 如果有的转换不会隐式自动转换就需要强制类型转换,虽然有时候不得不使用强制类型转换,但是这种方法本质上是非常危险的。

  • 一个命名的强制类型转换具有如下形式:

    cast-name<type>(expression);
    

    type是要转换的类型,expression是要转换的值,如果type是引用类型,则结果是左值。

    cast-namestatic_castdynamic_castconst_castreinterpret_cast中的一种

    • static_cast:任何具有明确定义的类型转换,只要不包含底层const,就可以使用static_cast

      • 把较大的算数类型赋给较小的类型

        double slope = static_cast<double>(j)
        
      • 无法自动执行的类型转换

        void *p = &d;
        double *dp = static_cast<double*>(p);
        
    • const_cast只能改变运算对象的底层const,用于将常量对象转换为非常量对象

      const char *pc;
      char *p = const_cast<char*>(pc);
      

      只有const_cast可以改变表达式的常量属性

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

    • reinterpret_cast:通常为运算对象的位模式提供较低层次上的重新解释,不太好用,不建议使用

  • 应该尽量避免使用强制类型转换,且不推荐使用旧式类型转换:与命名的强制类型转换相比,旧式的强制类型转换从表现形式上来说不那么清晰明了,容易被看漏。

    int x;
    char y = (char)x;
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383568.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++Primer学习笔记:第5章 语句

一个表达式末尾加上分号就变成了表达式语句。最简单的语句是空语句&#xff08;一个单独的分号&#xff09;&#xff1a;语法上需要一条语句但是逻辑上不需要 复合语句是指用花括号括起来的&#xff08;可能为空&#xff09;语句和声明的序列&#xff1a;用在语法上需要一条语…

z3 C++学习笔记

因为项目需要使用z3库来解决问题&#xff0c;所以自己学习了一下&#xff0c;结果发现网上教程比较少&#xff0c;而且大部分都是使用Python&#xff0c;而我本人是C的忠实信徒&#xff0c;在知道C也可以使用z3库以后我毫不犹豫地着手用C使用z3&#xff0c;但是我很快发现&…

C++Primer学习笔记:第6章 函数

通过调用运算符()调用函数 函数的调用完成两项工作&#xff1a; 用实参初始化函数对应的形参将控制权转移给被调用函数&#xff1a;主调函数的执行被暂时中断&#xff0c;被调函数开始执行 尽管实参与形参存在对应关系&#xff0c;但是并没有规定实参的求值顺序。编译器能以任…

C++Primer学习笔记:第8章 IO库

C语言不直接处理输入输出&#xff0c;而是通过一族定义在标准库中的类型来处理IO iostream定义了用于读写流的基本类型fstream定义了读写命名文件的类型sstream定义了读写内存string对象的类型 标准库使得我们能够忽略这些不同类型的流之间的差异&#xff0c;是通过继承机制实…

C++Primer学习笔记:第7章 类

类的基本思想是数据抽象data abstraction和封装encapsulation。数据抽象是一种依赖于接口interface和实现implementation分离的编程技术 在类中&#xff0c;由类的设计者负责考虑类的实现过程&#xff0c;使用该类的程序员只需要抽象地思考类型做了什么&#xff0c;而无须了解…

每日一题:leetcode191.位1的个数

题目描述 题目分析 很自然地想到了二进制枚举&#xff0c;直接循环检查每一个二进制位。 class Solution { public:int hammingWeight(uint32_t n) {int ret 0;uint32_t t 1;for (int i 0; i < 32; i, t << 1) {if (n & t) {ret;}}return ret;} };AC之后看了…

每日一题:leetcode341.扁平化嵌套列表迭代器

题目描述 题目分析 这个题目自己大概花了一个小时&#xff0c;虽然是一遍AC&#xff0c;但是速度有点慢&#xff0c;太长时间不写代码导致自己对代码不太敏感&#xff0c;写起来慢腾腾的。 看到这个的想法就是&#xff0c;要用栈来保存列表的迭代器&#xff0c;这样将孩子列表…

每日一题:leetcode82. 删除排序链表中的重复元素 II

题目描述 题目分析 这才是正常的中等题难度嘛&#xff0c;昨天的中等题题解我半天看不懂。。。 首先&#xff0c;需要增加一个哑节点&#xff08;操作链表的常规操作&#xff09;&#xff0c;因为有可能删除首节点&#xff0c;我们不想要为首节点添加单独的逻辑。其次&#xf…

每日一题:leetcode456.132模式

题目描述 题目分析 我觉得这道题应该是我做过最难的中等题之一了&#xff0c;这是昨天的每日一题&#xff0c;但是昨天用nlogn的做法做出来以后在看题解&#xff0c;发现有些看不懂&#xff08;觉得题解有点故弄玄虚&#xff09;。然后今天中午又花了一点时间才搞懂&#xff0…

leetcode283.移动零

题目描述 题目分析 在写简单题放松&#xff0c;看到这道题第一个想法是用STL库函数&#xff0c;虽然知道大概要用双指针之类的&#xff0c;但是库函数爽哇。 class Solution { public:void moveZeroes(vector<int>& nums) {stable_sort(nums.begin(), nums.end(), …

每日一题:leetcode61.旋转链表

题目描述 题目分析 很容易发现&#xff0c;如果k是n的整数倍&#xff0c;相当于没有移动。这样直接对k%n使得k在一个可以接受的范围。 因为是顺序移动&#xff0c;各元素之间的相对位置保持不变&#xff0c;所以就想着将链表先变成一个环。然后再移动头指针&#xff0c;最后再…

每日一题:leetcode173.二叉搜索树迭代器

题目描述 题目分析 更加地觉得编程重要的不在于如何写代码&#xff0c;用什么具体的技巧&#xff0c;编码本身只是一种将思维呈现的方式&#xff0c;但是如果思维是不清晰的&#xff0c;那么就算懂得再多的编码的奇技淫巧也是没有什么帮助的。相反&#xff0c;如果有一个清晰的…

Ubuntu20.04 Clion/Pycharm/IDEA 输入中文+光标跟随解决方案

ibus输入法&#xff08;弃用&#xff09; 之前一直用的搜狗输入法&#xff0c;但是搜狗输入法无法在Jetbrains全家桶下使用&#xff0c;但是又需要输入中文&#xff0c;没有办法我只能下载了谷歌输入法&#xff0c;十分难用&#xff0c;但是也没有其他办法&#xff0c;经常到网…

leetcode11.盛最多水的容器

题目描述 题目分析 看到题目后第一个想法当然是O(n2)O(n^2)O(n2)的&#xff0c;但是数据范围是3e4&#xff0c;应该会超时&#xff0c;而且这种数据范围也不是让暴力求解的 。 相当于求解∑i<jmax((j−i)∗min(a[i],a[j]))\sum_{i<j}{max((j-i)*min(a[i],a[j]))}∑i<…

每日一题:leetcode190.颠倒二进制位

题目描述 题目分析 题目本身很简单&#xff0c;没觉得有什么技巧可以再进行优化了&#xff0c;觉得位运算是无法打乱相对顺序的&#xff0c;而这里需要进行镜像颠倒的操作。因此就踏实地写了一个循环。 在使用位运算得到每一位的时候&#xff0c;我吸取了经验&#xff0c;用一…

结构屈曲分析

结构屈曲分析主要用于判定结构受载后是否有失稳风险&#xff0c;作为工程应用&#xff0c;一般分为线性屈曲分析和非线性屈曲分析。 线性屈曲分析需要具备较多的前提条件&#xff0c;如载荷无偏心、材料无缺陷等&#xff0c;在实际工程应用中结构制作过程和加载方式很难达到线性…

每日一题:leetcode74.搜索二维矩阵

题目描述 题目分析 感觉这是一个放错标签的简单题。题目非常简单&#xff0c;思路应该很明确是二分&#xff0c;我很快写了一个&#xff08;虽然不小心把!打成调试了一会&#xff09;。 class Solution { public:bool searchMatrix(vector<vector<int>>& mat…

每日一题:leetcode90.子集贰

题目描述 题目分析 感觉这道题让自己对枚举排列有了一个更好的认识&#xff0c;感觉自己的这种思路不错。 假设没有重复元素&#xff08;退化成78.子集&#xff09;&#xff0c;我们应该怎么做&#xff1f;初始的时候幂集中只有一个空集&#xff0c;然后对每个元素&#xff0…

每日一题:leetcode1006.笨阶乘

题目描述 题目分析 因为顺序一定且没有括号&#xff0c;所以逻辑很简单。我们要顺序处理的矛盾在于&#xff0c;减号后面会再出现乘法和除法&#xff0c;我们不妨将对乘法和除法用一个临时值进行计算&#xff0c;计算结束后再合并到值里面&#xff0c;一般来讲乘法和除法的处理…

每日一题:leetcode80.删除有序数组中的重复元素贰

题目描述 题目分析 又是一道贴错标签的简单题&#xff0c;很明显的双指针&#xff0c;我的做法是用两个变量保存是否需要记录&#xff0c;官方题解的做法是直接判断&#xff0c;人家的高明一些 class Solution { public:int removeDuplicates(vector<int>& nums) {…