C++(8)表达式

来源:互联网 发布:java汉字转拼音首字母 编辑:程序博客网 时间:2024/06/17 00:48

表达式--算术、关系、逻辑、位、赋值、自增/自减操作符



引:

   除了特殊用法,表达式的结果为右值:可以读取结果值,但是不能对其进行赋值。

   高优先级的操作符要比低优先级的结合得更紧密。


正文:

1、某些算术表达式的求解结果未定义:一部分有数学特性决定,如除0操作;另一部分则归咎于计算机特性,如溢出。


2、除法和求模操作:

   1)如果两个操作数都是负数:则除法操作的结果为正数,而求模操作结果为负数

   2)如果只有一个操作数是负数:这两种操作的结果都要取决于机器:求模结果的符号要取决于机器,除法操作的值则是负数。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. cout << -21 % -8 << endl;  
  2. cout << 21 % -5 << endl;    //1 or -4  
  3.   
  4. cout << -21 / -8 << endl;  
  5. cout << 21 / -5 << endl;    //-4 or -5  

3、关系操作符和逻辑操作符的返回结果都是bool类型的值

    短路求值-逻辑与&&与逻辑或||,都是只有仅靠左操作数的值无法确定该逻辑表达式的结果时,才会求解其右操作数。


4、一个&&操作符的使用技巧:如果某边界条件使得expr2的计算变得非常危险,那么应该在该条件出现之前,先让expr1成为false

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. string s("Expressions in C++ are composed...");  
  2. string::iterator iter = s.begin();  
  3.   
  4. while (iter != s.end() && ! isspace(*iter))  
  5. {  
  6.     *iter = toupper(*iter);  
  7.     ++ iter;  
  8. }  
  9. cout << s << endl;  

5、如果逻辑非的操作数为非零值,则!操作的结果为false

6、不应该串接使用关系操作符,如:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if (i < j < k)  
  2. {  
  3.     //...  
  4. }  

该表达式的结果往往是出人意料的:这种写法只要k值大于1,则该表达式的结果就为true


[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P133 习题5.7  
  2. int main()  
  3. {  
  4.     int val;  
  5.     while (cin >> val && val != 42)  
  6.     {  
  7.         //...  
  8.     }  
  9. }  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //习题 5.8  
  2.     if (a > b && b > c && c > d) {//...}  

7、位操作符可以将整型操作符视为二进制位的集合,而且还可以作用于bitset类型。

8、如果位操作符的操作数为负数,则位操作符如何处理其操作数的符号要依赖于机器。因此,强烈建议使用unsigned整型操作数。

   1<< n :相当于1* 2^n

   m<< n :相当于m* 2^n


9bitset对象与整型值的使用对比

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //bitset对象的每一位都默认为0  
  2. bitset<30> bitset_quizl;  
  3. unsigned long int_quizl = 0;  
  4.   
  5. //设置第27位为1  
  6.    bitset_quizl.set(27);  
  7.    int_quizl |= 1UL << 27;  
  8.   
  9.    //设置第27位为0  
  10.    bitset_quizl.reset(27);  
  11.    int_quizl &= ~(1UL << 27);  
  12.   
  13.    //获得第27位上的取值  
  14.    bool status;  
  15.    status = bitset_quizl[27];  
  16.    status = int_quizl & (1UL << 27);  

    通常来说:bitset优于整型数据的低级直接位操作。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P136 习题5.9  
  2. int main()  
  3. {  
  4.     unsigned int ul1 = 3,ul2 = 7;  
  5.   
  6.     cout << (ul1 & ul2) << endl;  
  7.     cout << (ul1 | ul2) << endl;  
  8.     cout << (ul1 && ul2) << endl;  
  9.     cout << (ul1 || ul2) << endl;  
  10. }  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //习题5.10  
  2. int main()  
  3. {  
  4.     bitset<30> bitset_quizl;  
  5.   
  6.     //设置第27位为1  
  7.     bitset_quizl[27] = 1;  
  8.     cout << bitset_quizl << endl;  
  9.   
  10.     //设置第27位为0  
  11.     bitset_quizl[27] = 0;  
  12.     cout << bitset_quizl << endl;  
  13.   
  14.     cout << bitset_quizl[27] << endl;  
  15. }  

10、移位操作符用于I/O

    移位操作符具有中等优先级:其优先级比算术运算符低,但比(1)关系操作符,(2)赋值操作符,(3)条件运算符优先级高。

11、赋值运算符具有右结合性,如:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int ival1,ival2;  
  2. ival1 = ival2 = 0;  
  3. //等价与  
  4. (ival1 = (ival2 = 0));  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P139 习题5.11 i、d的值分别为?  
  2.     int i;  
  3.     double d;  
  4.     d = i = 3.5;  
  5.     i = d = 3.5;  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //习题5.12 下面程序会有什么结果  
  2. //(1)  
  3. int main()  
  4. {  
  5.     int i;  
  6.     if (42 = i)  
  7.     {  
  8.         //...  
  9.     }  
  10. }  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //(2)  
  2. int main()  
  3. {  
  4.     int i;  
  5.     if (i = 42)  
  6.     {  
  7.         //...  
  8.     }  
  9. }  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P140 习题5.14 下面程序会有什么输出结果  
  2. int main()  
  3. {  
  4.     int ival = 2;  
  5.     ival += (ival + 1);  
  6.     cout << ival << endl;  
  7. }  

12、建议:只有在必要时才使用后置自增/自减操作符

    前置操作符需要做的工作更少,而后置操作符必须先保存操作数原来的值,以便返回未加/1之前的结果。【P140建议...值得仔细品读!】


13、后自增操作符的优先级高于解引用操作符,因此*iter++等价与*(iter++).

    

箭头、条件、sizeof、逗号表达式与复合表达式求值


1C++为包含点操作符和解引用操作符的表达式提供了一个同义词:箭头操作符(->)

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Sales_item *sp = &item;  
  2. (*sp).same_isbn(item_other);  
  3. sp -> same_isbn(item_other); //与上一条语句相同  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P142 习题5.18  
  2. int main()  
  3. {  
  4.     vector<string *> strVec;  
  5.     string value;  
  6.   
  7.     while (cin >> value)  
  8.     {  
  9.         string *pstr = new string;  
  10.         *pstr = value;  
  11.         strVec.push_back(pstr);  
  12.     }  
  13.   
  14.     for (vector<string *>::iterator iter = strVec.begin(); iter != strVec.end(); ++iter)  
  15.     {  
  16.         cout << **iter << " size: " << (**iter).size() << endl;  
  17.     }  
  18.   
  19.     for (vector<string *>::iterator iter = strVec.begin(); iter != strVec.end(); ++iter)  
  20.     {  
  21.         delete *iter;  
  22.     }  
  23.   
  24.     return 0;  
  25. }  

2、条件表达式的优先级相当低,通常都需要用圆括号将表达式扩起来

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. cout << (i < j ? i : j) << endl;  
  2. cout << (i < j) ? i : j << endl; //ERROR  
  3. cout << i < j ? i : j << endl;       //ERROR  

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P144 习题5.21  
  2. int main()  
  3. {  
  4.     vector<int> ivec;  
  5.     int value;  
  6.   
  7.     freopen("input.txt","r",stdin);  
  8.     while (cin >> value)  
  9.     {  
  10.         ivec.push_back(value);  
  11.     }  
  12.   
  13.     for (vector<int>::iterator iter = ivec.begin(); iter != ivec.end(); ++iter)  
  14.     {  
  15.         *iter = (*iter % 2 ? *iter * 2 : *iter);  
  16.         cout << *iter << ' ';  
  17.     }  
  18.     cout << endl;  
  19. }  

3sizeof操作符

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

    sizeof表达式的结果是编译时常量。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. Sales_item item,*pi;  
  2. cout << sizeof(Sales_item) <<  endl;  
  3. cout << sizeof item << endl;  
  4. cout << endl;  
  5.   
  6. cout << sizeof(pi) << endl;  
  7. cout << sizeof(*pi) << endl;  
  8. cout << endl;  
  9.   
  10. cout << sizeof(9 + 8) << endl;  
  11. cout << endl;  
  12.   
  13. char *p = new char[50];  
  14. cout << sizeof(p) << endl;  
  15. cout << sizeof(*p) << endl;  
  16. cout << endl;  
  17.   
  18. char arr[50];  
  19. cout << sizeof(arr) << endl;  
  20. cout << sizeof(*arr) << endl;  
  21. cout << sizeof(char) << endl;  
  22. cout << endl;  
  23.   
  24. Sales_item &refiterm = item;  
  25. cout << sizeof(refiterm) << endl;  

4、逗号操作符

    逗号表达式是一组由逗号分割的表达式,这些表达式从左到右进行计算。然而,逗号表达式的结果是其最右边表达式的值。


5、复合表达式中优先级和结合性决定了表达式的哪个部分用作哪个操作符的操作数,但是还可以通过使用圆括号强制实现某个特殊的分组,因为圆括号凌驾于优先级之上。


6、求值顺序

    除了&&||?:运算符外,未指定操作符的运算顺序。

    当操作符的两个操作数涉及到同一个对象,并改变其值时,操作数的计算顺序才会影响结果。

    C++并不保证计算是从左到右的!

P149建议:复合表达式的处理值得一读!】

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


new/delete表达式与类型转换



1、在创建对象时必须指定其类型与名称,而创建动态对象时,程序指定其数据类型而不必为对象命名;new表达式返回指向新对象的指针,我们通过此指针访问该对象。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int *p = new int;  

new表达式在自由存储区中分配创建了一个整型对象,并返回该对象的地址,并用该地址初始化指针。


2、动态对象的初始化

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int *pi = new int(65535);  
  2. string *ps = new string(9,'H');  

3、动态对象的默认初始化方式与在函数内定义的变量的初始化方式相同。如内置类型并不进行初始化...

但是,对于一个动态创建的对象,(几乎)总是对它做初始化也是一个好办法。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. string *ps = new string();  
  2. int *pi = new int();  

但是,对于提供了默认构造函数的类类型,其实没有必要对其对象进行值初始化,而对于内置类型或没有定义默认构造函数的类类型,采用不同初始化方式则有明显的不同:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int *pi = new int;  //没有初始化  
  2. int *ip = new int();    //初始化为0  

4、动态创建的对象用完之后,一定要显式的将该对象的内存返回给自由存储区:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. delete pi;  

但是如果指针指向的不是由new分配的内存地址,则在该地址上使用delete是不合法的,但是编译器是不容易察觉出的:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int i;  
  2. int *pi = &i;  
  3. delete pi;  //ERROR,但是在大多数编译器上能够通过!小心!  
  4. string str = "Hi~";  
  5. delete str; //ERROE  

5、在执行了语句:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. delete p;  

之后,尽管p值没有了明确定义,但是仍然存放了它之前的所指向的对象的指针,因此,一旦释放了指针所指向的值,立即将指针设置成为0,这样就非常清楚的表明该指针不再指向任何对象。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int *p = new int;  
  2. delete p;  
  3. p = 0;  

6C++允许创建const对象:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. const int *pci = new const int(1024);  

动态创建的const对象必须在创建时初始化,并且一旦初始化,其值就不能再修改;如果对于类类型的const动态对象,该类类型提供了默认的构造函数,则此对象可以隐式的初始化:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. const string *pcs = new const string;  

删除const对象【与普通对象相同】:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. delete pcs;  

7、动态内存管理的常见错误

    1)删除(delete)指向动态分配内存的指针失败,因而无法将该内存返还给自由存储区,通常称为“内存泄漏(memoryleak)”,这类错误很难发现,需要直到所有内存耗尽,内存泄漏才会显露出来!

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

    3)对同一个内存空间delete两次。当两个指针执行同一个动态创建的对象,删除时就发生错误,此时自由存储区可能会被破坏!

P153警告:~非常精彩,值得仔细品读!!!】

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P153 习题5.30  
  2.     vector<string> svec(10);  
  3.     vector<string> *pvec1 = new vector<string>(10);  
  4.     vector<string> **pvec2 = new vector<string>[10];    //ERROE  
  5.     vector<string> *pv1 = &svec;  
  6.     vector<string> *pv2 = pvec1;  
  7.   
  8.     delete svec;    //EEEOR  
  9.     delete pvec1;  
  10.     delete []pvec2; //ERROR  
  11.     delete pv1;     //ERROR,但是编译器有可能检测不出  
  12.     delete pv2;     //ERROR,但是编译器有可能检测不出  

8C++定义了算术类型之间的内置转换以尽可能防止精度损失,如算术转换:保证在执行操作之前,将二元操作符的两个操作数转换为同一类型,并使表达式的值也有相同的类型。  

   【注意:】对于signedunsignedint型的表达式,转换非常虐心...

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. unsigned int i = 5;  
  2. int j = -6;  
  3. cout << i + j << endl;  

程序的输出结果为:42949672952^32– 1sizeof(i+ j) = 4,表达式中的signed类型值会被转换为unsigned类型,并使i+j也拥有unsigned特性!


9、在大多数情况下,数组都会转换成为指向第一个元素的指针,但是也有例外情况:

    1)数组用作取地址操作符的操作数;

    2sizeof操作符的操作数;

    3)用数组对数组的引用进行初始化时.


10、另外两种指针间的转换:

    1)指向任意类型的指针都可以转换为void*类型;

    2)整型数值0可以转换为任意指针类型。


11C++自动将枚举类型的对象或枚举成员转换成为整型

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. enum Points {point2d = 2,point2w,  
  2.              point3d = 3,point3w  
  3.             };  
  4. const size_t arr_size = 1024;  
  5. Points tmp = point3d;  
  6.   
  7. int chunk_size = arr_size * point2w;  
  8. int arr_3d = chunk_size * tmp;  
  9. cout << arr_3d << endl;  

12、显式转换/强制类型转换:static_cast,dynamic_case,const_cast,reinterpret_cast

13const_cast,转换掉表达式的const性质

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. char *string_copy(char *temp)  
  2. {  
  3.     return temp;  
  4. }  
  5.   
  6. int main()  
  7. {  
  8.     const char *pc_str = "Hello";  
  9.     char *pc = string_copy(pc_str);             //ERROR  
  10.     char *pc = string_copy(const_cast<char *>(pc_str));  
  11.   
  12. }  

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

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. double d = 97.00;  
  2. char ch = static_cast<char>(d);  
  3.   
  4. //找回存放在void *指针中的值  
  5. void *p = &d;  
  6. double *dp = static_cast<double *>(p);  

该语句可以替换任何旧式强制类型转换【下文】!


15reinterpret_cast通常为操作数的位模式提供较低层次的重新解释,如:

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. int *ip;  
  2. char *pc = reinterpret_cast<char *>(ip);  
  3. string str(pc);     //此处编译器并未报错  

编译器确实无法知道pc实际上指向的是int型的对象的指针,所以用pc初始化str的值是完全正确的– 虽然实际上是无意义的或是错误的。

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //执行下面程序  
  2. int main()  
  3. {  
  4.     int i = 1024;  
  5.     int *ip = &i;  
  6.     char *pc = reinterpret_cast<char *>(ip);  
  7.     string str(pc);  
  8.     cout << str << endl;  
  9. }  

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


16、旧式强制类型转换

    虽然标准C++仍然支持旧式的强制类型转换标号,但是我们建议:只有在C或者标准C++之前的编译器上使用这种语法!

[cpp] view plaincopy在CODE上查看代码片派生到我的代码片
  1. //P161 习题5.33  
  2.     int ival;  
  3.     double dval;  
  4.     const string *ps;  
  5.     char *pc;  
  6.     void *pv;  
  7.   
  8.     pv = static_cast<void *>(pc);  
  9.     ival = static_cast<int>(*pc);  
  10.     pv = static_cast<void *>(&dval);  
  11.     pc = static_cast<char *>(pv);  

本文借鉴:http://blog.csdn.net/column/details/zjf666.html?&page=5

1 0