`
clskkk2222
  • 浏览: 33918 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论
  • NeuronR: 引用因为用于向函数传递对象和从函数返回对象,该构造函数一般不应 ...
    复制构造函数

表达式

    博客分类:
  • C++
J# 
阅读更多

表达式由一个或多个操作数通过操作符组合而成。

最简单的表达式仅包含一个字面值常量或变量。

每个表达式都会产生一个结果。

当一个对象用在需要使用其值的地方,则计算该对象的值。

除了特殊用法外,表达式的结果是右值,可以读取该结果值,但是不允许对它进行赋值。

操作符的含义——该操作符执行什么操作
操作结果的类型——取决于操作数的类型


回绕
1. 无符号整数变量的值超过它能保存的最大值后,会发生回绕,回到 0 重新开始;
2. 有符号变量回绕是从正的极端回绕到负的极端;

对两个整数做除法,结果仍为整数,如果它的商包含小数部分,则小数部分会被截除

操作符 % 称为“求余(remainder)”或“求模(modulus)”操作符
该操作符的操作数只能为整型,包括 bool、char、short 、int 和 long 类型,以及对应的 unsigned 类型

当只有一个操作数为负数时,求模操作结果值的符号可依据分子(被除数)或分母(除数)的符号而定。如果求模的结果随分子的符号,则除出来的值向零一侧取整;如果求模与分母的符号匹配,则除出来的值向负无穷一侧取整。

关系操作符和逻辑操作符

expr1 && expr2 // logical AND
expr1 || expr2 // logical OR
仅当由 expr1 不能确定表达式的值时,才会求解 expr2。

对于逻辑与操作符,一个很有价值的用法是:如果某边界条件使 expr2 的计算变得危险,则应在该条件出现之前,先让 expr1 的计算结果为 false。

不应该串接使用关系操作符

对于位操作符,由于系统不能确保如何处理其操作数的符号位,所以强烈建议使用unsigned整型操作数。

左移操作符(<<)在右边插入 0 以补充空位。对于右移操作符(>>),如果其操作数是无符号数,则从左边开始插入 0;如果操作数是有符号数,则插入符号位的副本或者 0 值,如何选择需依据具体的实现而定。移位操作的右操作数不可以是负数,而且必须是严格小于左操作数位数的值。

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

IO 操作符为左结合

赋值操作符

赋值操作符的左操作数必须是非 const 的左值

数组名是不可修改的左值:因此数组不可用作赋值操作的目标。

赋值表达式的值是其左操作数的值,其结果的类型为左操作数的类型。

赋值操作具有右结合特性

赋值操作具有低优先级

谨防混淆相等操作符和赋值操作符

复合赋值操作符
+=   -=   *=   /=   %=   // arithmetic operators
<<= >>=   &=   ^=   |=   // bitwise operators

左操作数只计算了一次;而使用相似的长表达式时,该操作数则计算了两次,第一次作为右操作数,而第二次则用做左操作数

自增和自减操作符

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

建议:只有在必要时才使用后置操作符(为前置操作需要做的工作更少,只需加 1 后返回加 1 后的结果即可。而后置操作符则必须先保存操作数原来的值,以便返回未加 1 之前的值作为操作的结果)

后置操作符返回未加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

条件操作符是 C++ 中唯一的三元操作符
cond ? expr1 : expr2;

避免条件操作符的深度嵌套

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

将 sizeof 用于 expr 时,并没有计算表达式 expr 的值。特别是在 sizeof *p 中,指针 p 可以持有一个无效地址,因为不需要对 p 做解引用操作。

使用 sizeof 的结果部分地依赖所涉及的类型:
1. 对 char 类型或值为 char 类型的表达式做 sizeof 操作保证得 1
2. 对引用类型做 sizeof 操作将返回存放此引用类型对象所需的内在空间大小
3. 对指针做 sizeof 操作将返回存放指针所需的内在大小;注意,如果要获取该指针所指向对象的大小,则必 须对指针进行引用
4. 对数组做 sizeof 操作等效于将对其元素类型做 sizeof 操作的结果乘上数组元素的个数

sizeof 返回整个数组在内存中的存储长度,所以用 sizeof 数组的结果除以 sizeof 其元素类型的结果,即可求出数组元素的个数

逗号表达式是一组由逗号分隔的表达式,这些表达式从左向右计算。逗号表达式的结果是其最右边表达式的值。

含有两个或更多操作符的表达式称为复合表达式,注意优先级和结合性!

圆括号凌驾于优先级之上

求值顺序

C++中,规定了操作数计算顺序的操作符还有条件(?:)和逗号操作符。除此之外,其他操作符并未指定其操作数的求值顺序。

建议:复合表达式的处理
1. 如果有怀疑,则在表达式上按程序逻辑要求使用圆括号强制操作数的组合。
2. 如果要修改操作数的值,则不要在同一个语句的其他地方使用该操作数。如果必须使用改变的值,则把该表达式分割成两个独立语句:在一个语句中改变该操作数的值,再在下一个语句使用它。
3.一个表达式里,不要在两个或更多的子表达式中对同一对象做自增或自减操作。
第二个规则有一个重要的例外:如果一个子表达式修改操作数的值,然后将该子表达式的结果用于另一个子表达式,这样则是安全的,例如,*++iter 。

new 和 delete 表达式动态创建和释放单个对象

C++ 使用直接初始化(direct-initialization)语法规则初始化动态创建的对象。如果提供了初值,new 表达式分配到所需要的内存后,用给定的初值初始化该内存空间。

如果不提供显式初始化,对于类类型的对象,用该类的默认构造函数初始化;而内置类型的对象则无初始。

可对动态创建的对象做值初始化(value-initialize)
值初始化的 () 语法必须置于类型名后面,而不是变量后。

内容为空的圆括号表示虽然要做初始化,但实际上并未提供特定的初值。对于提供了默认构造函数的类类型(例如 string),没有必要对其对象进行值初始化:无论程序是明确地不初始化还是要求进行值初始化,都会自动调用其默认构造函数初始化该对象。而对于内置类型或没有定义默认构造函数的类型,采用不同初始化方式则有显著的差别.

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

C++ 保证:删除 0 值的指针是安全的。

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

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

C++ 允许动态创建 const 对象
动态创建的 const 对象必须在创建时初始化,并且一经初始化,其值就不能再修改
由于 new 返回的地址上存放的是 const 对象,因此该地址只能赋给指向 const 的指针。

对于类类型的 const 动态对象,如果该类提供了默认的构造函数,则此对象可隐式初始化
内置类型对象或未提供默认构造函数的类类型对象必须显式初始化。

即使 delete 表达式的操作数是指向 int 型 const 对象的指针,该语句同样有效地回收 pci 所指向的内容。

下面三种常见的程序错误都与动态内存分配相关:
1. 删除( delete )指向动态分配内存的指针失败,因而无法将该块内存返还给自由存储区。删除动态分配内存失败称为“内存泄漏(memory leak)”。内存泄漏很难发现,一般需等应用程序运行了一段时间后,耗尽了所有内存空间时,内存泄漏才会显露出来。
2. 读写已删除的对象。如果删除指针所指向的对象之后,将指针置为 0 值,则比较容易检测出这类错误。
3. 对同一个内存空间使用两次 delete 表达式。当两个指针指向同一个动态创建的对象,删除时就会发生错误。如果在其中一个指针上做 delete 运算,将该对象的内存空间返还给自由存储区,然后接着 delete 第二个指针,此时则自由存储区可能会被破坏。

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

C++ 定义了算术类型之间的内置转换以尽可能防止精度损失

如果赋值操作的左右操作数类型不相同,则右操作数会被转换为左边的类型。

在下列情况下,将发生隐式类型转换:
1. 在混合类型的表达式中,其操作数被转换为相同的类型
2. 用作条件的表达式被转换为 bool 类型
3. 用一表达式初始化某个变量,或将一表达式赋值给某个变量,则该表达式被转换为该变量的类型
4. 另外,在函数调用中也可能发生隐式类型转换

算术转换:最简单的转换为整型提升,对于所有比 int 小的整型,包括 char、signed char、unsigned char、short 和 unsigned short,如果该类型的所有可能的值都能包容在 int 内,它们就会被提升为 int 型,否则,它们将被提升为 unsigned int。如果将 bool 值提升为 int ,则 false 转换为 0,而 true 则转换为 1。

若表达式中使用了无符号( unsigned )数值,所定义的转换规则需保护操作数的精度。unsigned 操作数的转换依赖于机器中整型的相对大小.

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

long 和 unsigned int 的转换也是一样的。只要机器上的 long 型足够表示 unsigned int 型的所有值,就将 unsigned int 转换为 long 型,否则,将两个操作数均转换为 unsigned long 。

在 32 位的机器上,long 和 int 型通常用一个字长表示,因此当表达式包含 unsigned int 和 long 两种类型,其操作数都应转换为 unsigned long 型。

对于包含 signed 和 unsigned int 型的表达式,其中的 signed 型数值会被转换为 unsigned 型。

其他隐式转换

在使用数组时,大多数情况下数组都会自动转换为指向第一个元素的指针

不将数组转换为指针的例外情况有:数组用作取地址(&)操作符的操作数或 sizeof 操作符的操作数时,或用数组对数组的引用进行初始化时,不会将数组转换为指针。

指向任意数据类型的指针都可转换为 void* 类型

整型数值常量 0 可转换为任意指针类型

算术值和指针值都可以转换为 bool 类型

C++ 自动将枚举类型的对象或枚举成员( enumerator )转换为整型,其转换结果可用于任何要求使用整数值的地方

将 enum 对象或枚举成员提升为什么类型由机器定义,并且依赖于枚举成员的最大值。无论其最大值是什么, enum 对象或枚举成员至少提升为 int 型。

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

显式转换也称为强制类型转换(cast)
虽然有时候确实需要强制类型转换,但是它们本质上是非常危险的。

因为要覆盖通常的标准转换,所以需显式使用强制类型转换
显式使用强制类型转换的另一个原因是:可能存在多种转换时,需要选择一种特定的类型转换。

命名的强制类型转换
cast-name<type>(expression);
其中 cast-name 为 static_cast、dynamic_cast、const_cast 和reinterpret_cast 之一,type 为转换的目标类型,而 expression 则是被强制转换的值。

dynamic_cast 支持运行时识别指针或引用所指向的对象

const_cast 将转换掉表达式的 const 性质。只有使用 const_cast 才能将 const 性质转换掉。除了添加或删除 const 特性,用 const_cast 符来执行其他任何类型转换,都会引起编译错误。

编译器隐式执行的任何类型转换都可以由 static_cast 显式完成.当需要将一个较大的算术类型赋值给较小的类型时,使用强制转换非常有用。当我们显式地提供强制类型转换时,警告信息就会被关闭.

reinterpret_cast 通常为操作数的位模式提供较低层次的重新解释,本质上依赖于机器。

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

旧式强制类型转换用圆括号将类型括起来实现

#include <iostream>
#include <vector>
using namespace std;

int main()
{
    // max value if shorts are 8 bits
    short short_value = 32767;
    short sv = 1;
    // this calculation overflows
    short_value += sv;
    cout << "short_value: " << short_value << endl; 

    unsigned short ushort_value = 65535;
    ushort_value += sv;
    cout << "ushort_value: " << ushort_value << endl;
    
    int ival1 = 21/6;  //  integral result obtained by truncating the remainder
    int ival2 = 21/7;  //  no remainder, result is an integral value
    cout << "21/6 = " << ival1 << endl;
    cout << "21/7 = " << ival2 << endl;
    
    int iv = 42;
    double dv = 3.14;
    cout << iv % 12 << endl;   //  ok: returns 6
    // iv % dv;  error: floating point operand
    
    cout << "21 % 6 = " << 21%6 << endl;   //  ok: result is 3
    cout << "21 % 7 = " << 21%7 << endl;   //  ok: result is 0
    cout << "-21 % -8 = " << -21%-8 << endl;; //  ok: result is -5
    cout << "21 % -5 = " << 21%-5 << endl;  //  machine-dependent: result is 1 or -4
    cout << "21 / 6 = " << 21/6 << endl;   //  ok: result is 3
    cout << "21 / 7 = " << 21/7 << endl;   //  ok: result is 3
    cout << "-21 / -8 = " << -21/-8 << endl; //  ok: result is 2
    cout << "21 / -5 = " << 21 / -5 << endl;  //  machine-dependent: result -4 or -5

    unsigned char bits = 0227;   // 100 100 111
    bits = ~bits;                // 001 011 000 (104 h)
    cout << bits << endl;        //will print h
    
    bits = 9;                    //   00001001
    cout << (bits << 1) << endl; // left shift  00010010  will print 18
    cout << (int)bits << endl;    //bits isn't changed 
    cout << (bits << 2) << endl; // left shift  00100100  will print 36
    cout << (int)bits << endl;    //bits isn't changed 
    cout << (bits >> 3) << endl; // right shift   00000001 will print 1
    cout << (int)bits << endl;    //bits isn't changed 
    
    unsigned char b1 = 0145;         // 01100101
    unsigned char b2 = 0257;         // 10101111
    unsigned char result1 = b1 & b2; // 00100101   
    unsigned char result2 = b1 | b2; // 11101111   
    unsigned char result3 = b1 ^ b2; // 11001010   
    cout << (int)result1 << endl;    
    cout << (int)result2 << endl;
    cout << (int)result3 << endl;
    
    vector<int> ivec(10,8);
    vector<int>::iterator iter = ivec.begin();
    while (iter != ivec.end())
    {
     cout << *iter++ << endl; // iterator postfix increment
    }
    
    cout << ( 1 < 2 ? "1<2" : "1 > 2") << endl;
    
    int ia[] = {1,2,3,4,5,6};
    // sizeof(ia)/sizeof(*ia) returns the number of elements in ia
    cout << sizeof(ia) << endl;
    cout << sizeof(*ia) << endl;
    int sz = sizeof(ia)/sizeof(*ia);  //the length of array ia
    cout << sz << endl;
    
    int *pi = new int(1024);  // object to which pi points is 1024
    string *ps = new string(10, '9');    // *ps is "9999999999"
    cout << *pi << endl;
    cout << *ps << endl;
    delete pi;
    pi = 0;
    delete ps;
    ps = 0;
    
    int i;
    int *pi2 = &i;
    string str = "dwarves";
    double *pd = new double(33);
    //delete str;  error: str is not a dynamic object
    /**
     * delete pi2;   error: pi refers to a local  
     * Most compilers will accept this code, even though it is in error.
     */
    delete pd;  // ok
    pd = 0;

    int *ip = 0;
    delete ip; // ok: always ok to delete a pointer that is equal to 0
    
    // allocate and initialize a const object
    const int *pci = new const int(1024);
    delete pci ;
    pci = 0;
    
    bool           flag;         
    char           cval;
    short          sval;        
    unsigned short usval;
    int            ival;         
    unsigned int   uival;
    long           lval;         
    unsigned long  ulval;
    float          fval;         
    double         dval;
    3.14159L + 'a'; // promote 'a' to int, then convert to long double
    dval + ival;    // ival converted to double
    dval + fval;    // fval converted to double
    ival = dval;    // dval converted (by truncation) to int
    flag = dval;    // if dval is 0, then flag is false, otherwise true
    cval + fval;    // cval promoted to int, that int converted to float
    sval + cval;    // sval and cval promoted to int
    cval + lval;    // cval converted to long
    ival + ulval;   // ival converted to unsigned long
    usval + ival;   // promotion depends on size of unsigned short and int
    uival + lval;   // conversion depends on size of unsigned int and long
                 
    int ci;
    const int &j = ci;   // ok: convert non-const to reference to const int
    const int *p = &ci; // ok: convert address of non-const to address of a const
    
    ival *= static_cast<int>(dval);  //cast
    
    return 0;   
}

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics