C++基础复习

来源:互联网 发布:midascft是什么软件 编辑:程序博客网 时间:2024/04/19 16:06

4.C++类

1.4.1.基本class语法
class由member(成员)构成,member可以是数据或函数(method)。
一般:data——private
method——public

1.4.2.特别的构造函数语法与访问函数

/** * A class for simulation an integer memory cell. * /class IntCell{  public:    explicit IntCell( int initialValue = 0 )    :storedValue( initialValue )//带参构造函数(单参数——initialValue——initializer list)    {    }    int read() const    {        return storedValue;    }    int write( int x )     {        storedValue = x;    }  private:    int storedValue;}

1).默认参数
默认值0意味着没有确定的参数,那么就使用0作为默认参数,默认参数可以在任何函数中使用,常用于构造函数。

2).initializer list(初始化列表)
用来直接初始化数据成员。
a.在数据成员是一个类时(一般的初始化过程较复杂),使用initializer list代替一般方法的代码体中的赋值语句可以节省很多时间。
b.某些情况必须用initializer list:例如一个数据成员是const的(意味着对象被构造后就不能修改),那么,数据成员的值就只能在initializer list里进行初始化;另外,如果一个数据成员是不具有零参数的构造函数的类类型,那么,该数据成员也必须在initializer list里进行初始化。

3).explicit构造函数
上面的IntCell构造函数是explicit(显式)的,所有的单参数的构造函数都必须是explicit的,以避免后台的类型转换。
例如:

  IntCell obj;    //obj is an IntCell  obj = 37;    //Should not compile: type mismatch

该代码段构造了一个IntCell对象obj,并进行了赋值。但该赋值语句并不能工作,因为在赋值符号右侧并不是另一个IntCell对象。然而,C++有宽松的规则,通常,单参数构造函数定义了一个implicit type conversion(隐式类型转换)。该转换创建了一个临时对象,从而使赋值(或函数参数)变成兼容的。在本例中,compiler试图将

  obj = 37;

转换为:

 IntCell temporary = 37; obj = temporary;

注意:
临时对象的构造也可以通过使用单参数构造函数来实现。使用explicit单参数构造函数不能用来创建隐式临时对象,所以这段代码不能工作。

4).常量成员函数
访问函数(accessor)——只检测不改变对象的状态 用于常量对象 结尾圆括号后加const
修改函数(mutator)——改变对象的状态 不能用于常量对象

1.4.3.接口与实现分离

接口——.h文件
实现——.cpp文件
1).预处理命令

#ifndef IntCell_H#define IntCell_H  ...  接口内容  ...#endif

2).默认参数在接口中被定义,在实现中则被忽略

1.4.4.vector&string
1).向量类vector
用于替代无比麻烦的C++内置数组
2).字符串类string

1.5.C++细节

1.5.1.指针
1).声明

IntCell *m;  //m的声明m = new IntCell( 0 );m -> write( 5 );delete m;/***在C++中,在使用指针前并不对指针是否初始化进行检查(但有些供货商的compiler进行附加的检查)。使用未初*始化的指针通常会破坏程序,因为这些指针会导致对不存在的存储地址的访问。***改进方式1:*IntCell *m = NULL; **m = new IntCell( 0 );*m -> write( 5 );**delete m;****改进方式2:*IntCell *m = new IntCell( 0 );*m -> write( 5 );**delete m;**/

2).动态对象创建
在C++中,new返回指向新建对象的指针。
在C++中,有2种方式可以使用0参数构造函数来创建对象。下面2种写法都是合法的,由于第一种可能出现函数声明的问题,一般采用第2种写法。

m = new IntCell();  // OKm = new IntCell;  //Preferred in this text

3).垃圾收集及delete
new——delete;如果不delete,该对象所占的内存就不能释放(直到程序结束),这称为内存泄漏。

一个重要规则——能用自动变量的时候就不用new。

4).通过指针访问对象的成员
如果指针变量指向类类型的对象,那么该对象的(可见的)成员就可以通过->进行访问。

1.5.2.参数传递

如果形参必须能够改变实参的值,那么就必须使用引址调用(call by reference)。否则,实参的值不能被改变,如果参数类型是简单类型,使用按值调用(call by value);如果参数类型是复杂类型(类类型),一般按常量引用调用(call by constant reference)来传递。
即:
a.call by value——适用于不被函数更改的小对象
b.call by constant reference——适用于不被函数更改的大对象
c.call by reference——适用于所有被函数更改的对象

1).传值——把实参值拷贝给形参。
2).传地址——把实参地址赋值给形参,形参和实参指向相同的变量地址。
3).传名——命别名,把实参的名字传给形参,形参就相当于实参的小名,二者是同一人。
4).得结果——把实参拆成(k,v)对,k(key)是变量地址,v(value)是变量地址里放的值;把实参地址k赋给形参
,形参把v(address对应的value)放在相应函数里执行相关操作,操作完后把最终的value_result存到(刚进入子函数时,子函数拿到的k)相应变量的k(地址)里。这样一来,实参的k对应的v就被修改了。

1.5.3.返回值传递
1).对象的返回也可以是引址返回、按值返回、常量引用返回。
2).多数情况下,不要使用引址返回(尽管有些时候很有用,但很少这样做)。
3).如果返回的对象是类类型的,更好的办法是使用按常量引用返回,以节省复制的开销。然而,这只在下面情况下才可能:必须确保返回语句中的表达式在函数返回时依然有效,这在C++中是非常棘手的问题(许多compiler不能对错误的应用给出报警信息)。

例子:

//在数组中查找最大(按字母顺序)的string的两个函数,两个都按常量引用返回常量         引用const string & findMax( const vector<string> & arr )//vector<string>——字符串数组,每一个元素都是一个字符串{    int maxIndex = 0;    for( int i = 1; i < arr.size(); i++ )    {        if( arr[maxIndex] < arr[i] )        {             maxIndex = i;        }    }    return arr[maxIndex];}const string & findMaxWrong( const vector<string> & arr ){    string maxValue = arr[0];    for( int i = 1; i < arr.size(); i++ )    {        if( maxValue < arr[i] )        {             maxValue = arr[i];        }    }    return maxValue;}/**第一个版本findMAX是可用的,表达式a[MaxIndex]索引的vector是在findMAX外部的,并且存活时间长于调*用返回时的时间。**第二个版本错误,maxValue是一个局部变量,但函数返回的时候就不复存在了。这样一来,没有进行复制之前*就返回是不恰当的。如果compiler没有查出这个问题,那么返回值既可能是对的也可能是错的,这会取决于*compiler释放maxValue所使用的内存的速度,这会使调试工作极为困难。*Test方法:*将函数返回值输出2次,第一次输出可能是对的,第2次输出可能就错了。*/

1.5.4.引用变量

引用变量和常量引用变量常用语参数传递,它们也可以用作局部变量或类的数据成员。在这些情况下,变量名就是它所引用的对象名的同义词(很像在引址调用中,许多形参名都是实参名的同义词)。作为局部变量,它们避免了复制的成本,因此在对含有类类型集合的数据结构进行排序时非常有用。

例子:

许多情况下,客户端代码string x = findMax(a);//x直接引用函数返回结果。变量名x就是函数findMax(a)的返回对象名的同义词,避免了复制。...cout << x << endl;比下面的代码好://假设findMax(a)的结果是const string & result(即arr[maxIndex]=result)const string & x = findMax(a);//需要复制,成本更高——const string & x = const string & result...cout << x << endl;//x = result

第二种方法是为了重命名一个具有复杂表达式的对象而使用的局部引用变量。其代码与下面的代码相似:

list<T> & whichList = theLists[hash( x, theLists.size() )];if( find( whichList.begin(), whichList.end(), x ) != whichList.end() ){    return false;}else{    whichList.push_back(x);}

使用引用变量后,复杂得多的表达式theLists[hash( x, theLists.size() )]就不需要写(然后求值)4次了。

1.5.5.三大函数:析构函数、复制构造函数、operation=

1).析构函数
析构函数唯一任务就是释放使用对象时所占用的所有资源。包括为每一个new调用delete,以及关闭所有打开的文件。默认操作是每一个数据成员都使用析构函数。

2).默认值带来的问题
假设类仅包含一个指针数据成员,并且这个指针指向一个动态分配地址的对象。默认的析构函数不对指针进行任何操作(一个好理由就是释放这个指针就必须删除自身)。而且,复制构造函数和operation=都不复制指针所指向的对象,而是简单地复制指针的值。这样一来,就得到了两个类实例,它们包含的指针都指向了同一个对象,这被称为shallow copy(浅拷贝)。一般,我们期望得到的是对整个对象进行克隆的deep copy(深拷贝)。于是,当一个类含有的数据成员为指针并且深拷贝很重要的时候,一般的做法就是必须实现析构函数、operator=和拷贝构造函数(复制构造函数)。
对IntCell,这些运算的签名是:

  ~IntCell();    //destructor  IntCell( const IntCell & rhs );    //copy constructor  const IntCell & opreator= (const IntCell & rhs);

对IntCell类来说默认值是可用的,如下:

//三大函数的默认值IntCell::~IntCell(){    //Does nothing ,since IntCell contains only an int data member.     //If IntCell contained any class objects,their destructors would be called.}IntCell::IntCell( const IntCell & rhs ) : storedValue( rhs.storedValue ){}const IntCell & IntCell::operator=( const IntCell & rhs ){    if( this != &rhs )//Standard alias test——line14    {        storedValue = rhs.storedValue;//——line15    }    return *this;//——line16}

对析构函数来说,程序体执行完毕后,就会为数据成员自动调用析构函数。因此,默认值就是空程序体。对复制构造函数来说,默认值就是跟随程序体执行的复制构造函数的初始化列表。
注意:
如果在初始化列表里什么都没有,没有执行复制的话,那么,每个成员函数就按默认值(零参数)初始化。

operator=是我们最感兴趣的,在line14是一个别名测试,以确保我们没有复制自身。假设没有复制自身,对每一个数据成员应用operator(在line15)。然后返回一个对当前对象的引用(在line16)。于是赋值可以链状进行,如a=b=c。
在上面的例程中,如果默认值有意义,就总是接受默认值。然而,如果默认值没有意义,就必须实现析构函数、复制构造函数和operator=。当默认值不能正常工作时,复制构造函数一般来说都可以通过模拟正常的构造,然后再调用operator=来实现。另一个常用方法是给出一个合理的复制构造函数的实现,然后将其放在private部分里以屏蔽按值调用。

3).当默认值不可用时
最常见的默认值不可用的情况是,数据成员是指针类型,并且被指对象通过某些对象成员函数(例如构造函数)来分配地址。例如:
//假设IntCell是通过动态分配一个int来实现的

class IntCell//数据成员是指针,默认值不适用{  public:    explicit IntCell( int initialValue = 0 )    {        storedValue = new int( initialValue );    }    int read() const    {        return *storedValue;    }    void write( int x )    {        *storedValue = x;    }  private:    int *storedValue;};int f(){    IntCell a(2);    IntCell b = a;    IntCell c;    c = b;    a.write(4);    cout << a.read() << endl << b.read() << endl << c.read() << endl;    return 0;    /**    *虽然逻辑上只有a为4,但实际上输出了3个4。问题在于默认的operator=和复制构造函数都是复制指针storedValue。    *这样一来,a.storedValue、b.storedValue和c.storedValue都指向了同一个int变量。这些复制都是shallow copy:    *指针被复制而指针所指的对象没有被复制。    *其次,还有个不明显的问题是内存泄漏。a的构造函数初始化的int变量依然存在,其内存需要释放。c的构造函数初始化的    *int变量不再有任何指针来引用。这也需要将其内存释放,但是已经没有指针存储该地址了。    *    **/}  

///////////////////////////////////////////////////////////////////////////////////////////////////////////我们应用三大函数来解决这些问题,如下所示(接口和实现分离):

class IntCell{  public:    explicit IntCell( int initialValue = 0 );    IntCell( const IntCell & rhs );    ~IntCell( );    const IntCell & operator=( const IntCell & rhs );    int read() const;       void write( int x );      private:    int *storedValue;};IntCell::IntCell( int initialValue )//带参构造函数(单参数){    storedValue = new int( initialValue );}IntCell::IntCell( const IntCell & rhs )//拷贝构造函数{    storedValue = new int( *rhs.storedValue );}IntCell::~IntCell(){    delete storedValue;}const IntCell & IntCell::operator=( const IntCell & rhs ){    if( this != &rhs )//Standard alias test——line14    {        storedValue = rhs.storedValue;//——line15    }    return *this;//——line16}int IntCell::read( ) const{    return *storedValue;}void IntCell::write( int x ){    *storedValue = x;}

一般来说,如果析构函数是释放内存所必须的,那么复制赋值和复制构造函数的默认值就不适用。如果类所包含的成员函数不能复制自身,那么operator=的默认值就不可用。

1.5.6.C风格的数组和字符串

1.6.模板
1.7.使用矩阵

原创粉丝点击