程序设计语言中大部分程序都在进行表达式的求值操作, 例如求两个数的和,求一个表达式的逻辑结果,或者通过输入输出表达式语句进行输入和输出。

这里我们对表达式进行讨论。

一、表达式

1、表达式

  表达式由一个操作数或者多个操作数同操作符组合而成; 字面值是一个最简单的表达式。  表达式都会产生一个结果,如果表达式中没有操作符,则表达式

的值是操作数本身, 如果表达式中有操作符,则表达式的结果是操作符对操作数进行操作后的结果。

  一般而言表达式的返回的是右值,不能给表达式赋值, 但可以获取表达式的值。

Exp:

  "volcanol";   表达式语句,"volcanol"是一个表达式,这个表达式的值就是字符串字面值本身

  if(iVar)    ;   if语句需要一个条件表达式,iVar是一个表达式, 返回值是iVar本身, 这里会将表达式的值进行转换,转换规则后面介绍。

2、算术操作符

  C++提供了多种算术操作符,这些操作符可以对所有的算术类型进行操作。 算术操作符有以下一些:

  +  一元操作符正号

  -  一元操作符负号

  *  二元操作符乘法

  /  二元操作符除法

  %  二元操作符,求余数操作

  +  二元操作符加法

  -  二元操作符减法

  这些操作符具有与算术负号基本一致的意思,因此这里不过多的进行解释,需要注意的 /  和 % 两个操作符。

要点:

  1、 % 操作符只能用于整型操作数, 并且求取的结果与具体系统相关。

  2、 / 操作符的两个操作数都是整型时则其结果也是整型,就算是商包含小数部分,其结果也是整型; 如果其中一个操作数是浮点型,则其结果是浮点型。

Exp:

int main()
{
cout<<"sizeof(3.14f) is:"<<sizeof(3.14f)<<endl;
cout<<"sizeof(3.14) is:"<<sizeof(3.14)<<endl;
cout<<"sizeof(3.14/2) is:"<<sizeof(3.14/)<<endl;
cout<<"sizeof(3.14f/2) is:"<<sizeof(3.14f/)<<endl; return ;
}

程序的执行结果如下所示:

[root@localhost cpp_src]# g++ test.cpp
[root@localhost cpp_src]# ./a.out
sizeof(3.14f) is:
sizeof(3.14) is:
sizeof(3.14/) is:
sizeof(3.14f/) is:

这里需要注意一点: 浮点型字面值默认为double类型, 如果操作数有一个double型,则结果是double型; 而如果有一个操作数是float类型,一个是整型,则结果是

float类型, 这里不会和C语言一样进行数据类型的隐式转换,即不会有浮点数就全转换为double类型进行计算。

要点:   / 和 % 的操作数的正数和负数问题

  1、如果两个操作数都是正数、负数,则 /的结果为正数

  2、如果两个操作数是正数则%操作的结果是正数; 如果两个操作数都是负数,则操作结果是负数。

  3、如果有一个操作数是负数,那么 / 的结果是负数或者0, 而 % 的结果由系统决定。

3、关系操作符和逻辑操作符

  关系操作符和逻辑操作符返回的结果类型是bool型,这一点需要注意。

  逻辑操作符:     !         逻辑非   一元操作符

          &&      逻辑与  二元操作符

          ||  逻辑或  二元操作符

  逻辑非返回与操作数相反的逻辑值, 如果操作数为false,则逻辑非返回true, 如果操作数为true,则逻辑非返回false,

  逻辑与操作符的结果与两个操作数的逻辑值相关, 当两个操作数都是逻辑true的时候,逻辑与返回true,其他情况返回false

  逻辑或操作符的结果与两个操作数的逻辑值有关,当两个操作数都是逻辑false的时候,逻辑与返回false,其他情况返回true。

  关系操作符:   <  小于操作符,  二元操作符

           <=  小于等于操作符,  二元操作符  

         >   大于操作符,二元操作符 

         >=  大于等于操作符,二元操作符

          !=   不等于操作符,二元操作符

  这些操作符都具有和数学计算中一致的意义, 具体这里就进行细说啦。

要点:

  1、关系运算符的优先级高于逻辑&&、||的优先级,  而逻辑非的优先级高于关系运算符。

  2、逻辑运算符 && 和 || 在计算的时候,先求左操作数的值, 然后再求右操作数的值,并且当计算左操作数以后就可以求出整个表达式的值的情况下,将

不再对右操作数进行求值, 这就是通常说的:短路求值。 这一点要引起注意。

  3、不能串接使用关系运算符, 关系运算符在使用的过程中不能和数学表达式一样,连续使用多个关系运算符。例如:

    if(3 < iVar < )     //错误的条件表达式
      .........

  这里会得到一个永远的true表达式,   首先计算  3<iVar ,无论3与iVar的关系,返回的值将为0、或者1 ,这样再用0、1与10比较,则无论如何都是成立的

就会返回一个永远的 true 值, 因此需注意这个地方。

  正确的用法如下:

if( < iVar  && iVar < )
.........

  4、不能将一个值 与 bool 值 true 和false 进行相等测试,这样会造成错误。例如:

if(var == true)

    .....
  else     .....

  虽然关系表达式的结果是bool值,但是关系操作符的操作数是非bool量, 当关系操作符的操作数存在bool量时,则会将bool量转换为整型量, true转换为1,而

false转换为0, 这样本来是想判断 var 是true 或者false,结果变成了var与1或者0进行比较,因此这里就不能得到正确的结果。为了改变这种逻辑上的错误,我们可以

如下所示:

  if(var)

    ....
  else     .....

  这也算是一种技巧吧, 这种技巧在C语言中经常会用到。

4、位操作符

  C语言中提供了可以对位进行操作的操作符,C++继承了这种机制,也提供了很多的位操作符,如下所示:

  ~   按位取反操作符

  <<  按位左移操作符

  >>   按位右移操作符

  &  按位与操作符

   |  按位或操作符
^ 按位异或操作符

  这些操作符的意义很直观,这里不进行介绍啦,值得一说的按位 ^ 操作符有一些特殊的用法,因为按位异或操作具有一些特殊的性质。

要点:

  1、要注意的是<< 和 >>操作符,<< 移位的时候是低位补零操作,  而 >> 移位操作分为两种情况,一种补零操作,一种是进行符号位扩展,这需要通过程序进行判

断。C++并没有规定是按何种方式进行处理因此在程序中需要进行判断。

   if ((0xFFFF FFFF >>) & 0x80000000) )

    符号位扩展执行的语句

   else

    高位补零操作执行的语句 

具体的实例代码如下所示:

int main()
{
if( (0xFFFFFFFF>>) & 0x80000000)
cout<<"fu hao kuo zhan"<<endl;
else
cout<<"gao wei bu ling"<<endl; return ;
}

程序的执行结果如下所示:

[root@localhost cpp_src]# ./a.out
gao wei bu ling

这里就是说,我的系统利用个>>移位操作进行的是高位补零操作, 这一点在有些系统可能与这个结果正好相反。

  2、这里还有一点, 左移 或者 右移的位数,必须在数据类型的位宽之类,否则结果未定义。

int main()
{
cout<<(0x1<<)<<endl;
cout<<(0x1>>)<<endl; return ;
}

编译的时候会有警告信息如下所示:

[root@localhost cpp_src]# g++ test.cpp
test.cpp: In function ‘int main()’:
test.cpp:: 警告:左移次数大于或等于类型宽度
test.cpp:: 警告:右移次数大于或等于类型宽度

这就是提示操作可能未定义,因此在使用的时候,需要注意这一点。

  这里给一个关于异或操作的实例:

int main()
{
int a= ;
int b= ; a = a^b;
b = a^b;
a = a^b; cout << a << endl;
return ;
}

程序的执行结果如下:

[root@localhost cpp_src]# g++ test.cpp
[root@localhost cpp_src]# ./a.out

  发现一个非常有趣的地方:

[root@localhost cpp_src]# vim op_>>_bit_big.cpp
Vim: 警告: 输出不是到终端(屏幕)

  哈哈,不知道有人遇到过这样情况没有,打错字的时候,就是回出现一些异常的情况。

   这里还要说的一个情况就是因为C++标准库提供了一个bitset集进行功能扩展,这个bitset类的作用就是提供一种更加高效的替代C++中位操作符的运算的机制,

因此在一般的时候建议使用bitset类进行相关的操作,这样可以简化位操作。

Exp:

int main()
{
bitset<> bitVar;
bitVar.set();
cout<<bitVar<<endl; bitset<> bitVar1(0x0 | (0x1<<16));
cout<<bitVar1<<endl; return ;
}

程序的执行结果为:

[root@localhost cpp_src]# g++ test.cpp
[root@localhost cpp_src]# ./a.out

  为了将0的第16位设置为1, 利用位操作符需要一个复杂的表达式,而且还可能出错,而用bitset则调用objX.set()很容易就可以进行设置。 同时由于bitset类

提供了一个objX.to_ulong()的操作,因此很容易在整型和bitset中进行转换,这样可以简化编程工作。

  对于移位操作符,还有一点需要注意,因为标准的IO流重载了<<和>>两个操作符,因此在cin 和cout的操作中如果有左移和右移操作符,则需要注意加上圆括

号改变运算符的优先级,否则容易引起一些异常。

5、赋值操作符

  赋值操作符需要说明的就是赋值操作符的优先级比逗号操作符的优先级高,比其他的运算符的优先级都低。

  需要注意的是:赋值操作符返回的是左值, 而且其左操作数必须是左值,否则不能进行运算。

例如:

  int i = ; //注意这个地方不是赋值操作符,存在细微的差别,需要注意

  i = ;  //right

   = ; //error

  要点:  赋值操作符有一些复合赋值符,这些赋值符具有特别的意义。

   +=  、 -= 、 *= 、 /= 、 %=  算术运算符结合赋值操作符得到的复合赋值操作符

   <<= 、  >>=  、  &= 、|=、^=  位操作符结合赋值操作符得到的复合操作符,这里需要注意没有提供按位取反的复合赋值操作符。

Exp:

int main()
{
unsigned long int uLVar = ; uLVar ~= 0x1;
cout << uLVar<<endl; return ;
}

程序编译的结果如下所示:

[root@localhost cpp_src]# g++ test.cpp
test.cpp: In function ‘int main()’:
test.cpp:: 错误:expected `;' before ‘~’ token

说 ~ 操作符前面缺少分号,这就是说没有~和= 结合组成的复合赋值操作符,这个地方需要特别的注意。

6、 ++ 和 -- 操作符

  ++ 和 -- 分为两种情况,  前缀形式 prefix  和 后缀形式 postfix , 这个不需要特别的介绍,++ 前缀返回的是增加后的对象,   ++后缀返回的是增加前的对象

而--的情况一样。

  需要注意的是后缀的优先级高于前缀的优先级。

7、域访问操作符  .  和 ->

 .  操作符可以用于对象的成员的访问, 可以用于对象名,或者指向对象的指针以及指向对象的迭代器中来访问对象的成员。

Exp:

  string  strVar;

  strVar.size() ;  通过域访问操作符访问对象strVar的成员函数size().

  而如果用迭代器或者指针来访问对象,则在使用域访问操作符之前需要对迭代器或者指针进行解引用,如下所示:

  string  strVar;

  string *pStr;

  pStr = &strVar;

  (*strVar).size();

  这样有时候在程序中,很容易忘记对指针进行解引用,而这种操作又比较频繁,因此C++提供了一种更加简便的方式, 通过 -> 操作符来进行访问,可以不进行解引用

就可以访问对象的成员, 如下所示:

  vector<string>  iVecStrVar;

  vector<string>::iterator iter = iVecStrVar.begin();

  (*iter).size();  //利用解引用操作符以及. 域访问操作符来访问成员

  iter->size();   //利用->域访问操作符访问成员

  这样可以简化操作。

8、条件操作符

  C++也和C语言一样提供了一个三目运算符,就是条件运算符:  操作数1 ? 操作数2 : 操作数3 ; 条件操作符根据操作数1的真假返回操作数2或者操作数3的值,

如果操作数1为真,返回操作数2的值,如果操作数1为假,返回操作数3的值。

  这里不举例子啦,可以参考其他的文章。

9、sizeof操作符

  在国内一些教材中,经常将sizeof操作符看成函数, 这归结于:sizeof操作符的特殊性, sizeof操作符后面可以直接跟操作数,也可以将操作数用圆括号括起来,

这实际上是一种错误的认识, 必须纠正这种看法。  正确的是 sizeof 为操作符。

sizeof有三种用法:

  sizeof(数据类型)

  sizeof(对象)

  sizeof 对象或者表达式

Exp:

  sizeof(vector<int>);

  sizeof(int)

  sizeof(UL1);

  sizeof 为编译时计算的,它计算操作数在内存中占用的内存的字节数, 这里需注意: 是字节数, 而不是长度。

具体的例子就不举啦,在前面的随笔中,我们已经多次使用这个操作符。

10、逗号操作符

  逗号也是一种操作符,逗号操作符用来分隔表达式;逗号表达式经常用于for循环中。C++规定了逗号表达式的求值顺序为从左到右求值,返回值为最右边的表达式

的值。

  逗号表达式经常用于for循环,例如:

  for(int i=, j = ; i != str.size(); i++, j-- )

    ....... 

要点:

  int  i = ,  j = ; //  这里的逗号不是逗号表达式, 因为这里没有返回一个值

11、操作符的优先级和结合性

  操作符具有优先级,高优先级的操作符先对操作数进行操作, 结合性描述的是当操作符的优先级相同时先对哪一个操作符进行计算;这里不对操作符的优先级

和结合性进行描述。

  利用() 可以规避操作符的优先级, 可以让低优先级的操作符先进性计算。

Exp:

  1 + 2 * 3  :  先对* 操作符进行计算,求出 2 * 3 = 6 ,然后对 + 操作符进行计算,为 1 + 6  = 7 ,因此表达式的值为 7

  (1+2)  * 3 : 因为使用了() 所以先对 + 操作符进行操作计算,  1  + 2 = 3 ,然后对 * 进行计算,为 3 * 3  = 9 ;

可以发现当通过() 规避了操作符的优先级后,一个表达式的值就可能完全改变, 因此需要注意( ) 的使用带来的效果。

12、 new 和 delete 表达式

  new 和delete也是操作符,要点是: new 和 delete 的操作对象是系统的堆区域,  new用于从堆区域申请空间,而delete则用于将不再使用的堆内存

重新返回到堆区域。

  1、动态创建对象

    int   i();
    int *pi = new int(); //动态创建对象,并初始化对象
    string strVar(, '');
    string *pStr = new string(, ''); //动态创建对象并初始化对象,

  对于内置类型,动态创建对象并初始化的时候,就和定义变量一样; 对于类类型,则会调用对应的构造函数进行初始化。

   2、动态创建对象默认初始化

  动态创建对象默认初始化,与局部变量和对象的定义的初始化一样。

  int i;

  int  *pI = new int;   //不进行初始化,

  string  strVar;

  string *pStr = new string;   //调用默认构造函数进行初始化

  3、撤销动态创建的对象

  通过delete操作符,可以撤销动态创建的对象,如下所示:

  int  *pI = new int;  //动态创建对象, 并将指针返回赋值给pI

  .....

  delete  pI;   //撤销动态创建的对象,将内存返还给系统的堆区域 

要点:

  1、撤销零指针

  在C++中撤销 0 指针是安全的。

  #define  NULL  ((void *)0)

  int *p = NULL;

  delete p;  //撤销0指针, 为正常的操作

  2、动态创建 const对象

  C++允许动态创建const对象, 这一点需要引起注意。

  const  int  *pI = new const int();

   不能改变动态创建的const对象的值,这一点与静态定义的cosnt对象一样。

  动态创建的cosnt对象同样可以利用delete操作符回收内存,如下所示:

  const string  *pStr = new const string;   //具有默认构造函数的类类型,可以不提供初始化式

  ......

  delete pStr;   //回收pStr指向的动态创建的const对象的内存

 

  关于操作符,还有一部分类型转换的内容,这部分内容,后面专门用一篇随笔进行介绍,待续.................

C++_系列自学课程_第_10_课_表达式_《C++ Primer 第四版》的更多相关文章

  1. C++_系列自学课程_第_7_课_数组_《C++ Primer 第四版》

    说到数组,大家应该都很熟悉,在C.Pascal.Java等语言中,都有数组的概念.在C++中也提供了对数组的支持.数组简单来说就是一堆相同 数据类型对象的集合. 这里要把握住两个要点: 相同的数据类型 ...

  2. C++_系列自学课程_第_6_课_bitset集_《C++ Primer 第四版》

    在C语言中要对一个整数的某一个位进行操作需要用到很多的技巧.这种情况在C++里面通过标准库提供的一个抽象数据类型 bitset得到了改善. 一.标准库bitset类型 1.bitset的作用 bits ...

  3. C++_系列自学课程_第_5_课_vector容器_《C++ Primer 第四版》

    再一次遇到 vector 这个单词; 每一次见到这个单词都感觉这个单词非常的 "高大上"; 数字遇到vector马上就可以360度旋转: 当 "电" 遇到vec ...

  4. C++_系列自学课程_第_3_课_变量和基本类型_《C++ Primer 第四版》

    最近复习C++相关内容,决定在这里记录自己复习的过程. 以前写过部分文字,但是没有坚持连续写,因此学完后 基本又忘光啦,主要是没有实践,这一次决定自学完后,在这里在复习一遍增强自己的记忆和理解程度. ...

  5. C++_系列自学课程_第_12_课_结构体

    #include <iostream> #include <string> using namespace std; struct CDAccount { double bal ...

  6. C++_系列自学课程_第_12_课_语句_《C++ Primer 第四版》

    前面的文章说完了表达式和类型转换的部分内容,在我参考的书里面,接下来讨论的是各种语句,包括:顺序语句.声明语句.复合语句(块语句).语句作用域 .if语句.while语句.for语句.do...whi ...

  7. C++_系列自学课程_第_11_课_类型转换_《C++ Primer 第四版》

    上次说了关于表达式的一些内容,说到还有一些关于数据类型转换的内容,今天我们接着八一八C++中的数据类型转换. 一.隐式类型转换 在表达式中,有些操作符可以对多种类型的操作数进行操作, 例如 + 操作符 ...

  8. C++_系列自学课程_第_9_课_C语言风格字符串_《C++ Primer 第四版》

    前面说了写关于数组和指针的内容,这次在这里讨论一下字符串,讨论一下C语言风格的字符串. 在C语言里面我们利用字符数组来对字符串进行处理, 在C++里面我们前面说过一种类类型string可以对字符串进行 ...

  9. C++_系列自学课程_第_8_课_指针和引用_《C++ Primer 第四版》

    C语言最富有迷幻色彩的部分当属指针部分,无论是指针的定义还是指针的意义都可算是C语言中最复杂的内容.指针不但提供给了程序员直接操作硬件部分的操作接口,还提供给了程序员更多灵活的用法.C++继承这一高效 ...

随机推荐

  1. C#设计模式之外观

    IronMan之外观 接着上篇观察者内容的“剧情”,没看过的朋友也没关系,篇幅之间有衔接的关系但是影响不大. 需求: 为"兵工厂"提供各种支持,生产了各式各样的"Iron ...

  2. python数据类型详解

    目录1.字符串2.布尔类型3.整数4.浮点数5.数字6.列表7.元组8.字典9.日期 1.字符串1.1.如何在Python中使用字符串a.使用单引号(')用单引号括起来表示字符串,例如:str='th ...

  3. Spring学习记录(三)---bean自动装配autowire

    Spring IoC容器可以自动装配(autowire)相互协作bean之间的关联关系,少写几个ref autowire: no ---默认情况,不自动装配,通过ref手动引用 byName---根据 ...

  4. 关于laravel 5.3 使用redis缓存出现 找不到Class 'Predis\Client' not found的问题

    昨天使用5.3.版本的laravel框架开发公司新项目, 发现将cache和session设置为了redis,执行了一下首页访问. 如图: laravel 版本号 简单配置一下控制器路由, Route ...

  5. SSIS Data Flow 的 Execution Tree 和 Data Pipeline

    一,Execution Tree 执行树是数据流组件(转换和适配器)基于同步关系所建立的逻辑分组,每一个分组都是一个执行树的开始和结束,也可以将执行树理解为一个缓冲区的开始和结束,即缓冲区的整个生命周 ...

  6. javascript运动系列第二篇——变速运动

    × 目录 [1]准备工作 [2]加速运动 [3]重力运动[4]减速运动[5]缓冲运动[6]加减速运动[7]往复运动[8]变速函数 前面的话 前面介绍过匀速运动的实现及注意事项,本文在匀速运动的基础上, ...

  7. 深入理解脚本化CSS系列第三篇——脚本化CSS类

    前面的话 在实际工作中,我们使用javascript操作CSS样式时,如果要改变大量样式,会使用脚本化CSS类的技术,本文将详细介绍脚本化CSS类 style 我们在改变元素的少部分样式时,一般会直接 ...

  8. document.getElementById()与 $()区别

    document.getElementById()返回的是DOM对象,而$()返回的是jQuery对象 什么是jQuery对象? ---就是通过jQuery包装DOM对象后产生的对象.jQuery对象 ...

  9. .NET平台开源项目速览(6)FluentValidation验证组件介绍与入门(一)

    在文章:这些.NET开源项目你知道吗?让.NET开源来得更加猛烈些吧!(第二辑)中,给大家初步介绍了一下FluentValidation验证组件.那里只是概述了一下,并没有对其使用和强大功能做深入研究 ...

  10. scikit-learn 支持向量机算法库使用小结

    之前通过一个系列对支持向量机(以下简称SVM)算法的原理做了一个总结,本文从实践的角度对scikit-learn SVM算法库的使用做一个小结.scikit-learn SVM算法库封装了libsvm ...