第四章 表达式

1.一般情况下,表达式由一个或多个操作数以及作用在这些操作数上的操作符所构成。很好理解,比如a+b,a和b是操作数,+是操作符。

2.作用在一个操作数上的操作符被称为一元操作符,比如取地址操作符(&)和解引用操作符(*)。作用在两个操作数以上的操作符,如假发,减法等操作符被称为二元操作符。

3.注意:/为相除操作不取余数。1/3=0;2/3=0;3/3=1;4/3=1;5/3=1;6/3=2;(当然这是对整型而言,对于浮点型,则算出确切的值)

              %为取余操作,1%3=1,2%3=2,3%3=0;4%3=1;5%3=2;6%3=0;

4.%操作符计算两个数相除的余数。第一个数被第二个数除该操作符只能被应用在整值类型char short int 和long 的操作数上。当两个操作数都是正数时结果为正。但是如果有一个或两个操作数为负余数的符号则取决于机器。

5.算术异常归咎于算数的自然本质(比如说除以0)或者归咎于计算机的自然本质——比如溢出(overflow).比如一个char类型的对象,它的长度是一个字节。根据它有符号或者无符号,它可以包含最大数是127或者255.当向他赋值超过这个大小的时候,将导致溢出。

6.在ASCII码中,0代表的是空字符。

7.while(!found)相当于while(found==false);同样的while(found)相当于while(found==true);

8.i++和++i的区别,前者是先使用i然后在自增,后者是先自增,在使用i.


9.条件操作符:条件操作符的语法格式如下:exprl?expr2:expr3;expr1的计算结果不是true就是false,如果它是true,则expr2被计算,否则expr3被计算。


10.sizeof操作符的作用是返回一个对象或者类型名的字节长度。它有以下三种形式:

     sizeof(type name);

     sizeof(object);

     sizeof object;

返回值的类型是size_t,这是一种与机器相关的typedef定义,我们可以在cstddef头文件中找到它的定义。下面的例子使用了sizeof的两种格式:

#include<cstddef>

int ia[]={0,1,2};

//sizeof 返回整个数组的大小

size_t array_size=sizeof ia;

//sizeof(int)返回int类型的大小,则array_size/sizeof(int)则计算数组中元素的个数

size_t element_size=array_size/sizeof(int)


如果是:int a;sizeof(a),则是返回a的长度,其实也是返回int 类型的长度

//如果是下面代码

int*pi=new int[3];

size_t pointer_size=sizeof(pi)

则是返回指针类型的长度。不管是任何类型的指针,其实使用sizeof()以后返回的都是四个字节。(注意,返回的长度都是以字节为单位的)


sizeof(short*)返回的是4

sizeof(short)返回的是2

sizeof(short&)返回的是2

sizeof(short[3])返回的是6

可见指针返回的该地址的长度,而引用返回的是所指对象的长度。

sizeof(char)//sizeof()操作符作用在char类型上时,在所有的C++实现中结果都是1.


11.new和delete表达式

系统为每个程序都提供了一个在程序执行时可用的内存池。这个可用内存池被称为程序的空闲存储区(free store)或堆(heap)。运行时刻的内存分配被称为动态内存分配(dynamic memory allocation).动态内存分配由new表达式应用在一个类型指示符上来完成,类型指示符可以使内置类型或者用户定义类型。new表达式返回指向新分配的对象的指针。

例如:int *pi=new int;

从空闲存储区中分配了一个int型的对象,并用它的地址初始化pi.在空闲存储区内实际分配的对象并没有初始化。我们可以这样来初始化:

int*pi=new int(1024);

它不但分配了这个对象而且用1024将其初始化。

要动态分配一个对象数组,我们可以写成:int*pia=new int[10];

它从空闲存储区中分配了一个数组,其中含有10个int型对象,并用它的地址初始化pia,而数组的元素并没有被初始化。没有语法能为动态分配的数组的元素制定一个显式的初始值集合。


如果是动态新建一个类对象,则会在分配给他地址的时候调用类的构造函数。

如:string *ps=new string;

类似的,string *psa=new string[10];

从空闲存储区分配了一个含有10个string类对象的数组,用它的地址初始化psa,然后依次在每个元素上调用string类的缺省构造函数。


注意,所有从空闲存储区分配的对象都是未命名的,这是它的另一个特点。new表达式并不返回实际被分配的uixiang,而是返回这个对象的地址。对象的所有操作都通过这个地址间接来完成。


当对象完成了使命时,我们必须显式地把对象的内存返还给空闲存储区。我们通过delete表达式应用在“指向我们用new比道士分配的对象指针”上来做到这一点(注意,delete表达式不应该被应用在“不是通过new表达式分配的指针上”)。

释放普通对象的语法:delete pi;

释放数组对象的语法:delete【】pia;


12.逗号表达式

逗号表达式是一系列由都好分开的表达式。这些表达式从左向右计算。逗号表达式的结果是最右边表达式的值。


13.位操作

标准库提供了一个bitset类,它支持位向量的类抽象。bitset对象封装了位向量的定义,它回答了诸如以下问题:有设置为1的位码?设置了多少位?它提供了一组用于管理位的设置,复位和测试的操作。但是我们有时候也直接按位操作整型数据类型。也就是说,有两种方法,一种是使用bitset类,一种是直接按位操作。

先介绍直接按位操作的方法:

用整型数据类型作为位向量时,类型可以是有符号的,也可以是无符号的,强烈建议使用无符号的类型。因为在大多数的位操作中,符号位的处理是未定义的,因此在不同的实现中符号位的处理可能会不同。在一个实现下可行的程序,在另一个实现下可能会失败。

有以下位操作符:~非,<<左移,>>右移,&与(记住与逻辑与&&分开来),|或(记住跟||逻辑或分开来),^异或。

下面是一个例子:unsigned int ui1=3,ui2=7;则ui1&ui2得到的是一个数,而ui1&&ui2得到的是一个bool值(true or false)


判断指定位是1还是0,只需两个数相与&。A&B(将A除了指定位设为1,其它均设为0)看结果是true还是false,true的话说明指定位为1,否则为0

将指定为设为1,只需A|B(将A设为指定位为1,其它均为0的数)

将指定位设置为0,只需A&B(将A设为指定位为0,其它位为1)

将指定位翻转,只需A^B(将A设为指定位为1,其它位为0)


现在介绍用bitset类来实现位操作,当然用这个方法更好,因为所有的方法都已经封装好了。

关于bitset类因为要记得东西太多了,详细的内容可参照教材。


14.优先级

记住赋值操作符是右结合的,而算术操作符(+,-,/,*)是左结合的。

关于优先级,也有表格,若记不清楚,直接翻阅资料即可。


15.类型转换

考虑到以下赋值:

int ival=0;

ival=3.56+3;

最终的结果是6.但是这里面编译器经过了一些运算:

首先,将3隐式转换为浮点型,这是因为编译器在做强制转换时,一般将低精度的转换为高精度的类型,这是为了避免精度损失。然后得到的结果是6.56.但是,赋值=左边的是int型,所编译器采用自动按截取将小数部分去掉(不是四舍五入)

当然还有显式的转换。ival = static_cast< int >( 3.541 ) + 3;

关于类型转换的具体内容,参照教材


显示转换

显式转换也被称为强制类型转换(cast),包括static_cast,dynamic_cast,const_cast和reinterpret_cast。虽然有时候确实需要强制转换,但是它们也是程序错误的源泉。通过使用它们,程序员关闭了C++语言的类型检查设施。我们先来看看何时需要这么做:

1.任何非const类型的指针都可以被赋值为void*类型的指针,void*型指针被用于“对象的确切类型未知”或者“在特定环境下对象的类型会发生变化”的情况下,有时候void*型的指针被称为泛型(generic)指针,因为它可以指向任意数据类型的指针。例如:

int  ival;

int  *pi=0;

char *pc=0;

void *pv;

pv=pi;//ok;隐式转换

pv=pc;//ok:隐式转换

const int*pci=&ival;

pv=pci;//错误:pv不是一个const void*

const void *pcv=pci;//OK

但是,void*指针不能直接被解除引用,因为没有类型信息可用来指导编译器怎样解释底层的位模式。相反,void*的指针必须先被转换成某种特定类型的指针。但是,在C++中,不存在从void*型指针到特殊指针之间的自动转换。


强制转换的一般形式如下:cast-name<type>(expression);

这里的cast-name是static_cast,const_cast,dunamic_cast和reinterpret_cast之一。

const_cast将转换掉表达式的常量性(也就是把常量的转换成非常量的)汝,const char *prt;const_cast<char*>(prt)将把prt的常量性给去掉。

试图用其他三种形式来转换掉常量性将引起编译错误,类似的,用const_cast来执行一般的类型转换,也会引起编译错误。


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

double d=97.0

char ch=static_cast<char>(d);

为什么要这样做呢?因为从一个较大类型转换到一个较小类型的赋值,会导致编译器产生一个警告以提醒我们潜在精度损失。当我们提供显式强制转换时,警告消息将被关闭。强制转换告诉编译器和程序的读者:我们不关心潜在的精度损失。


reinterpre_cast通常对于操作数的位模式执行一个比较低层次的重新解释,它的正确性很大程度上依赖于程序员的主动管理。例如,下列转换:

在引入这些强制转换操作符之前,显式强制转换由一对括号来完成(标准C++依然支持这种旧式的强制转换):

char *pc=(char*)pcom;

效果与使用reinterpret_cast符号相同,但强制转换的可视性非常差,这使跟踪错误的转换更加困难。



栈类实例

我们将用一个iStack(即只支持int型元素的栈)的设计与实现的简要过程来结束本章。


栈是计算机科学的一个基本数据抽象,它允许后进先出(lifo)的顺序嵌入和获取其中的值。

栈的两个基本操作是:向栈中压入(push)一个新值,以及弹出(pop)或获取最后压入的那个值。其他的一些操作包括:查询栈是否满(full)或空(empty()),以及判断栈的长度size()--即包含多少个元素。

下面是其公有接口声明:

#include<vector>


class iStack

{

public:

    iStack(int capacity):_stack(capacity),_top(0){}//这是相当于初始化的时候:_stack=capcity,_top=0;

    bool pop(int &value);

   bool push(int value);

   bool full();

   bool empty();

   void display();

   int size();

private:

   int _top;

   vector<int>_stack;

   }

我们把元素存储在一个int型的vector中,它的名字为_stack。_top含有下一个可用槽的值,push()操作会向该槽压入一个值。_top的当前值反映了栈中元素的个数,因此,size()只需简单的返回_top:

size()函数如下:iniline int iStack::size(){return _top;}

如果_top为0.。则empty()返回true,如果_top等于_stack.size()(注意这里的size跟类里面的方法size()不一样,这个size()是向量类的函数,返回向量的元素个数),则full()返回true.

inline bool iStack::empty()

{

return _top?false:true;

}


inline bool iStack::full()

{

  return _top<_stack.size()-1? false;true;

}


下面是pop()和push()的实现代码,我们加入了输出函数来跟踪他们的执行情况:

bool iStack::pop(int &top_value)

{

    if(empty())

            return false;

    top_value=_stack[--_top];//注意,出栈是先减1,因为_top代表的是元素个数,而我们需要读出的是元素的个数减一的那个数,比如有五个元素,现在减去一个,我们要读出减去的那个的值,就是stack[4],这样以后,_top也变为4了。

    cout<<"Istack::pop():"<<top_value<<endl;

    return true;

    

}


bool iStack::push(int value)

{

    cout<<"iStack::push("<<value<<")\n";

    if(full())

        return false;

   _stack[_top++]=value;//比如有5个元素,那么_top的值是5,我们要读出加进去那个的值,就是stack[5]。这样以后,top再加一。就是6了。

  return true;

}


display 用于输出栈的内容:

 void iStack::display()

{

    if(!size())

{

   cout<<"(0)\n";

   return;

}

  cout<<"("<<size()<<")(bot:";

  for(int ix=0;ix<_top;++ix)//注意_top是个数,而下表是从0开始的,所以ix只能到top-1,这样解释通了上述出栈和入栈采用--_top和_top++的原因。

      cout<<_stack[ix]<<" ";

 cout<<":top)\n";

}


某些用户需要peek()操作,以读出栈顶值,但并不把它从栈中移除,当然,条件是栈不为空。请提供peek()的实现:

bool iStack::peek()

{

   if(empty())

      return false;

  cout<<istack::peak():"<<_stack[_top-1];

  return true;

}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值