zoukankan      html  css  js  c++  java
  • C++ 表达式

    《C++ Primer 4th》读书摘要

    C++ 提供了丰富的操作符,并定义操作数为内置类型时,这些操作符的含义。除此之外,C++ 还支持操作符重载,允许程序员自定义用于类类型时操作符的含义。标准库正是使用这种功能定义用于库类型的操作符。

    操作符的含义——该操作符执行什么操作以及操作结果的类型——取决于操作数的类型。除非已知道操作数的类型,否则无法确定一个特定表达式的含义。

    按优先级来对操作符进行分组——一元操作符优先级最高,其次是乘、除操作,接着是二元的加、减法操作。高优先级的操作符要比低优先级的结合得更紧密。这些算术操作符都是左结合,这就意味着当操作符的优先级相同时,这些操作符从左向右依次与操作数结合。

    逻辑与和逻辑或操作符总是先计算其左操作数,然后再计算其右操作数。只有在仅靠左操作数的值无法确定该逻辑表达式的结果时,才会求解其右操作数。我们常常称这种求值策略为“短路求值(short-circuit evaluation)”。

    由于 true 转换为 1,因此要检测某值是否与 bool 字面值true 相等,其等效判断条件通常很难正确编写:如果 val 不是 bool 值,val 和 true 的比较等效于:

    if (val == 1) { /* ... */ }

    这与下面的条件判断完全不同:

    // condition succeeds if val is any nonzero value

    if (val) { /* ... */ }

    此时,只要 val 为任意非零值,条件判断都得 true。如果显式地书写条件比较,则只有当 val 等于指定的 1 值时,条件才成立。

    位操作符使用整型的操作数。位操作符将其整型操作数视为二进制位的集合,为每一位提供检验和设置的功能。另外,这类操作符还可用于bitset 类型的操作数,该类型具有这里所描述的整型操作数的行为。

    操作符

    功能

    用法

    ~

    bitwise NOT(位求反)

    ~expr

    <<

    left shift(左移)

    expr1 << expr2

    >>

    right shift(右移)

    expr1 >> expr2

    &

    bitwise AND(位与)

    expr1 & expr2

    ^

    bitwise XOR(位异或)

    expr1 ^ expr2

    |

    bitwise OR(位或)

    expr1 | expr2

    移位操作的右操作数不可以是负数,而且必须是严格小于左操作数位数的值。否则,操作的效果未定义。位异或(互斥或,exclusive or)操作符(^)也需要两个整型操作数。在每个位的位置,如果两个操作数对应的位只有一个(不是两个)为 1,则操作结果中该位为 1,否则为 0。

    一般而言,标准库提供的 bitset 操作更直接、更容易阅读和书写、正确使用的可能性更高。而且,bitset 对象的大小不受 unsigned 数的位数限制。通常来说,bitset 优于整型数据的低级直接位操作。

    输入输出标准库(IO library)分别重载了位操作符 >> 和 << 用于输入和输出。移位操作符具有中等优先级:其优先级比算术操作符低,但比关系操作符、赋值操作符和条件操作符优先级高。

    与其他二元操作符不同,赋值操作具有右结合特性。当表达式含有多个赋值操作符时,从右向左结合。多个赋值操作中,各对象必须具有相同的数据类型,或者具有可转换为同一类型的数据类型。

    复合赋值操作符的一般语法格式为:

    a op= b;

    其中,op= 可以是下列十个操作符之一:

    += -= *= /= %= // arithmetic operators

    <<= >>= &= ^= |= // bitwise operators

    这两种语法形式存在一个显著的差别:使用复合赋值操作时,左操作数只计算了一次;而使用相似的长表达式时,该操作数则计算了两次,第一次作为右操作数,而第二次则用做左操作数。除非考虑可能的性能价值,在很多(可能是大部分的)上下文环境里这个差别不是本质性的。

    自增操作使其操作数加1,操作结果是修改后的值。它的后置形式同样对其操作数加 1(或减 1),但操作后产生操作数原来的、未修改的值作为表达式的结果:

    int i = 0, j;

    j = ++i; // j = 1, i = 1: prefix yields incremented value

    j = i++; // j = 1, i = 2: postfix yields unincremented value

    因为前置操作返回加1 后的值,所以返回对象本身,这是左值。而后置操作返回的则是右值。

    建议:只有在必要时才使用后置操作符

    有使用 C 语言背景的读者可能会觉得奇怪,为什么要在程序中使用前自增操作。道理很简单:因为前置操作需要做的工作更少,只需加 1 后返回加 1 后的结果即可。而后置操作符则必须先保存操作数原来的值,以便返回未加 1 之前的值作为操作的结果。对于 int 型对象和指针,编译器可优化掉这项额外工作。但是对于更多的复杂迭代器类型,这种额外工作可能会花费更大的代价。因此,养成使用前置操作这个好习惯,就不必操心性能差异的问题。

    vector<int>::iterator iter = ivec.begin();

    // prints 10 9 8 ... 1

    while (iter != ivec.end())

    cout << *iter++ << endl; // iterator postfix increment

    由于后自增操作的优先级高于解引用操作,因此 *iter++ 等效于*(iter++)。子表达式 iter++ 使 iter 加 1,然后返回 iter 原值的副本作为该表达式的结果。因此,解引用操作 * 的操作数是 iter 未加 1 前的副本。

    C++ 语言为包含点操作符和解引用操作符的表达式提供了一个同义词:箭头操作符(->)。点操作符用于获取类类型对象的成员

    假设有一个指向类类型对象的指针(或迭代器),下面的表达式相互等价:

    (*p).foo; // dereference p to get an object and fetch its member named foo

    p->foo; // equivalent way to fetch the foo from the object to which p points

    sizeof 操作符的作用是返回一个对象或类型名的长度,返回值的类型为size_t,长度的单位是字节。

    结合性

    操作符

    功能

    用法

    L

    ::

    global scope(全局作用域)

    :: name

    L

    ::

    class scope(类作用域)

    class :: name

    L

    ::

    namespace   scope(名字空间作用域)

    namespace :: name

    L

     .

     member selectors(成员选择)

    object . member

    L

     ->

     member selectors(成员选择)

    pointer -> member

    L

     []

     subscript(下标)

    variable [ expr ]

    L

     ()

     function call(函数调用)

    name (expr_list)

    L

     ()

    type   construction(类型构造)

    type (expr_list)

    R

     ++

    postfix   increment(后自增操作)

    lvalue++

    R

    --

    postfix   decrement(后自减操作)

    lvalue--

    R

    Typeid

     type ID(类型 ID)

    typeid (type)

    R

    typeid

    run-time type   ID(运行时类型 ID)

    typeid (expr)

    R

    explicit   cast(显式强制类型转换)

    type conversion(类型转换)

    cast_name<type>(expr)

    R

    sizeof

    size of object(对象的大小)

    sizeof expr

    R

    sizeof

    size of type(类型的大小)

    sizeof(type)

    R

    ++

    prefix   increment(前自增操作)

    ++ lvalue

    R

    --

    prefix   decrement(前自减操作)

    -- lvalue

    R

    ~

    bitwise NOT(位求反)

    ~expr

    R

    !

    logical NOT(逻辑非)

    !expr

    R

        
           
      •  
      •   

    unary minus(一元负号)

    -expr

    R

    +

    unary plus(一元正号)

    +expr

    R

    *

    dereference(解引用)

    *expr

    R

    &

    address-of(取地址)

    &expr

    R

    ()

    type conversion(类型转换)

    (type) expr

    R

    new

    allocate object(创建对象)

    new type

    R

    delete

    deallocate   object(释放对象)

    delete expr

    R

    delete[]

    deallocate   array(释放数组)

    delete[] expr

    L

    ->*

    ptr to member   select(指向成员操作的指针)

    ptr ->*   ptr_to_member

    L

    .*

    ptr to member   select(指向成员操作的指针)

    obj .*ptr_to_member

    L

    *

    multiply(乘法)

    expr * expr

    L

    /

    divide(除法)

    expr / expr

    L

    %

    modulo   (remainder)(求模(求余))

    expr % expr

    L

    +

    add(加法)

    expr + expr

    L

        
           
      •  
      •   

    subtract(减法)

    expr - expr

    L

    <<

    bitwise shift   left(位左移)

    expr << expr

    L

    >>

    bitwise shift   right(位右移)

    expr >> expr

    L

    <

     less than(小于)

    expr < expr

    L

    <=

    less than or   equal(小于或等于)

    expr <= expr

    L

    >

    greater than(大于)

    expr > expr

    L

    >=

    greater than or   equal(大于或等于)

    expr >= expr

    L

    ==

    equality(相等)

    expr == expr

    L

    !=

    inequality(不等)

    expr != expr

    L

    &

    bitwise AND(位与)

    expr & expr

    L

    ^

    bitwise XOR()

    expr ^ expr

    L

    |

    bitwise OR(位异或)

    expr | expr

    L

    &&

    logical AND(逻辑与)

    expr && expr

    L

    ||

    logical OR(逻辑或)

    expr || expr

    R

    ?:

    conditional(条件操作)

    expr ? expr : expr

    R

    =

    assignment(赋值操作)

    lvalue = expr

    R

    *=, /=, %=,

    +=, -=,

    <<=, >>=,

    &=,|=, ^=

    compound   assign(复合赋值操作)

    lvalue += expr, etc.

    R

    throw

    throw exception(抛出异常)

    throw expr

    L

     ,

    comma(逗号) expr ,

    expr

    以什么次序求解操作数通常没有多大关系。只有当操作符的两个操作数涉及到同一个对象,并改变其值时,操作数的

    计算次序才会影响结果。如果一个子表达式修改了另一个子表达式的操作数,则操作数的求解次序就变得相当重要:

    // oops! language does not define order of evaluation

    if (ia[index++] < ia[index])

    此表达式的行为没有明确定义。问题在于:< 操作符的左右操作数都使用了index 变量,但是,左操作数更改了该变量的值。假设 index 初值为 0,编译器可以用下面两种方式之一求该表达式的值:

    if (ia[0] < ia[0]) // execution if rhs is evaluated first

    if (ia[0] < ia[1]) // execution if lhs is evaluated first

    一个表达式里,不要在两个或更多的子表达式中对同一对象做自增或自减操作。

    下面两个指导原则有助于处理复合表达式:

    1. 如果有怀疑,则在表达式上按程序逻辑要求使用圆括号强制操作数的组合。

    2. 如果要修改操作数的值,则不要在同一个语句的其他地方使用该操作数。如果必须使用改变的值,则把该表达式分割成两个独立语句:在一个语句中改变该操作数的值,再在下一个语句使用它。

    第二个规则有一个重要的例外:如果一个子表达式修改操作数的值,然后将该子表达式的结果用于另一个子表达式,这样则是安全的

    而动态创建对象时,只需指定其数据类型,而不必为该对象命名。取而代之的是,new 表达式返回指向新创建对象的指针,我们通过该指针来访问此对象:

    int i; // named, uninitialized int variable

    int *pi = new int; // pi points to dynamically allocated,unnamed, uninitialized int

    如果不提供显式初始化,动态创建的对象与在函数内定义的变量初始化方式相同。对于类类型的对象,用该类的默认构造函数初始化;而内置类型的对象则无初始化。

    string *ps = new string; // initialized to empty string

    int *pi = new int; // pi points to an uninitialized int

    正如我们(几乎)总是要初始化定义为变量的对象一样,在动态创建对象时,(几乎)总是对它做初始化也是一个好办法。

    int *pi = new int; // pi points to an uninitialized int

    int *pi = new int(); // pi points to an int value-initialized to 0

    动态创建的对象用完后,程序员必须显式地将该对象占用的内存返回给自由存储区。C++ 提供了 delete 表达式释放指针所指向的地址空间。

    delete pi;

    如果指针指向不是用 new 分配的内存地址,则在该指针上使用delete 是不合法的。

    删除指针后,该指针变成悬垂指针。悬垂指针指向曾经存放对象的内存,但该对象已经不再存在了。悬垂指针往往导致程序错误,而且很难检测出来。

    一旦删除了指针所指向的对象,立即将指针置为 0,这样就非常清楚地表明指针不再指向任何对象。

    C++ 允许动态创建 const 对象:

    // allocate and initialize a const object

    const int *pci = new const int(1024);

    与其他常量一样,动态创建的 const 对象必须在创建时初始化,并且一经初始化,其值就不能再修改

    警告:动态内存的管理容易出错

    下面三种常见的程序错误都与动态内存分配相关:

    1. 删除( delete )指向动态分配内存的指针失败,因而无法将该块内存返还给自由存储区。删除动态分配内存失败称为“内存泄漏(memory leak)”。内存泄漏很难发现,一般需等应用程序运行了一段时间后,耗尽了所有内存空间时,内存泄漏才会显露出来。

    2. 读写已删除的对象。如果删除指针所指向的对象之后,将指针置为 0 值,则比较容易检测出这类错误。

    3. 对同一个内存空间使用两次 delete 表达式。当两个指针指向同一个动态创建的对象,删除时就会发生错误。如果在其中一个指针上做 delete 运算,将该对象的内存空间返还给自由存储区,然后接着 delete 第二个指针,此时则自由存储区可能会被破坏。

    尽管程序员不能改变 const 对象的值,但可撤销对象本身。如同其他动态对象一样, const 动态对象也是使用删除指针来释放的:

    delete pci; // ok: deletes a const object

    如果两个类型之间可以相互转换,则称这两个类型相关。

    包含 short 和 int 类型的表达式, short 类型的值转换为 int 。如果int 型足够表示所有 unsigned short 型的值,则将 unsigned short 转换为int,否则,将两个操作数均转换为 unsigned int 。

    指向任意数据类型的指针都可转换为void* 类型;整型数值常量 0 可转换为任意指针类型。

    算术值和指针值都可以转换为 bool 类型。如果指针或算术值为 0,则其bool 值为 false ,而其他值则为 true

    当使用非 const 对象初始化 const 对象的引用时,系统将非 const 对象转换为 const 对象。此外,还可以将非 const 对象的地址(或非 const 指针)转换为指向相关 const 类型的指针:

    int i;

    int ci = 0;

    const int &j = i; // ok: convert non-const to reference to const int

    const int *p = &ci; // ok: convert address of non-const to address of a const

    显式转换也称为强制类型转换(cast),包括以下列名字命名的强制类型转换操作符:static_cast、dynamic_cast、const_cast 和 reinterpret_cast。

    虽然有时候确实需要强制类型转换,但是它们本质上是非常危险的。显式使用强制类型转换的另一个原因是:可能存在多种转换时,需要选择一种特定的类型转换。

    命名的强制类型转换符号的一般形式如下:

    cast-name<type>(expression);

    其中 cast-name 为 static_cast、dynamic_cast、const_cast 和reinterpret_cast 之一,type 为转换的目标类型,而 expression 则是被强制转换的值。强制转换的类型指定了在 expression 上执行某种特定类型的转换。

    const_cast ,顾名思义,将转换掉表达式的 const 性质.只有使用 const_cast 才能将 const 性质转换掉。在这种情况下,试图使用其他三种形式的强制转换都会导致编译时的错误。类似地,除了添加或删除const 特性,用 const_cast 符来执行其他任何类型转换,都会引起编译错误。

    const char *pc_str;

    char *pc = string_copy(const_cast<char*>(pc_str));

    编译器隐式执行的任何类型转换都可以由 static_cast 显式完成:

    double d = 97.0;

    // cast specified to indicate that the conversion is intentional

    char ch = static_cast<char>(d);

    如果编译器不提供自动转换,使用 static_cast 来执行类型转换也是很有用的。例如,下面的程序使用 static_cast 找回存放在 void* 指针中的值:

    void* p = &d; // ok: address of any data object can be stored in avoid*

    // ok: converts void* back to the original pointer type

    double *dp = static_cast<double*>(p);

    可通过 static_cast 将存放在 void* 中的指针值强制转换为原来的指针类型,此时我们应确保保持指针值。

    建议:避免使用强制类型转换

    强制类型转换关闭或挂起了正常的类型检查(第 2.3 节)。强烈建议程序员避免使用强制类型转换,不依赖强制类型转换也能写出很好的 C++程序。

    这个建议在如何看待 reinterpret_cast 的使用时非常重要。此类强制转换总是非常危险的。相似地,使用 const_cast 也总是预示着设计缺陷。设计合理的系统应不需要使用强制转换抛弃 const 特性。其他的强制转换,如 static_cast 和 dynamic_cast,各有各的用途,但都不应频繁使用。每次使用强制转换前,程序员应该仔细考虑是否还有其他不

    同的方法可以达到同一目的。如果非强制转换不可,则应限制强制转换值的作用域,并且记录所有假定涉及的类型,这样能减少错误发生的机会。

    旧式强制类型转换

    在引入命名的强制类型转换操作符之前,显式强制转换用圆括号将类型括起来实现:

    char *pc = (char*) ip;

    虽然标准 C++ 仍然支持旧式强制转换符号,但是我们建议,只有在 C 语言或标准 C++ 之前的编译器上编写代码时,才使用这种语法。

    旧式强制转换符号有下列两种形式:

    type (expr); // Function-style cast notation

    (type) expr; // C-language-style cast notation

  • 相关阅读:
    2018-8-10-win10-uwp-重启软件
    jquery动画滑入滑出
    jquery类操作
    jquery类操作
    jquery手风琴
    jquery突出显示
    jquery隔行变色
    jquery下拉菜单
    jquery筛选选择器
    jquery过滤选择器
  • 原文地址:https://www.cnblogs.com/1zhk/p/4989794.html
Copyright © 2011-2022 走看看