10.6 指针和引用关于类的遗留问题(★★★)

Posted: March 27, 2011 in C++

在介绍本节内容之前先说下(class)和结构(structure)之间的的联系.

关键字classstruct之间基本用法一样,

只是class将成员变量和成员函数区分为了privatepublic,且默认为private.

struct默认为public, 且采用公有继承(public inheritance).

如果将之前所有的class Cat改为struct Cat. 运行后,

将输出进行比较,可以发现没有发生任何变化.

Q: 那为什么要用classstructure来定义相同的功能呢?

A: 其实, C++ 是作为 C 的扩展开发.因而继承了 C语言 的特点,

但是结构中不能使用函数. 所以C++的创始对structure进行扩展.

从而将这种新的扩张功能命名为(class).同时修改了成员的可见性.

并运用到C++语言当中.

==========================================

之前, 介绍了如何新建和删除指针: http://wp.me/p14oGQ-7m

Q: 有没有什么方法可以在自由储存区创建对象呢?

A: 就像可以创建指向整型的指针一样, 也可以创建指向任何数据类型(包括类class)的指针.

比如: 声明一个Cat类, 则可以声明一个指向Cat类的指针, 并在自由存储区中实例化一个Cat对象,

就像在堆栈(stack)中, 创建Cat对象一样. 创建Cat指针的语法和创建int指针相同:

Cat *pCat = new Cat;

这将调用默认构造函数, 不接受任何参数的构造函数.

(关于默认构造函数请访问: http://wp.me/p14oGQ-7P)

每当对象被创建(无论是在自由存储区还是在堆栈中)都将调用构造函数.

然而需要注意的是:

使用new创建对象时, 不仅可以使用默认构造函数, 也可以使用任何构造函数.

_______________________________________________

说完了如何在自由存储区创建对象, 当然也要知道如何删除该对象.

delet对用于指向自由存储区中的对象的指针时, 在释放内存之前将调用对象的析构函数.

这个类提供了一个执行清理工作的机会(通常是释放从堆中分配而来的内存),

就像从堆栈中删除对象一样.

请查看参考代码: NewAndDeleteObject.h

==========================================

现在还要补充一个内容:(★★★) 非常重要且不简单

按值将对象(object)传递给函数(function), 都将创建该对象的副本. 而按值从函数返回一个对象时,将创建另个副本.对于小型对象而言,这样的开销是微不足道的.

而对于用户定义的大型对象(如结构或者类对象),复制的开销可能很高.用户定义的对象在堆栈(stack)中占据的空间是其所有的成员变量所占空间的总和,无论是在性能方面还是再内存方面都是非常昂贵的.

还有另一种开销.没创建这些临时副本时,都要调用一个特殊的构造函数:复制构造函数.函数返回时,临时对象将被销毁,这将调用对象的析构函数.如果函数按值返回对象,就需要创建和销毁该对象的一个副本.

对于大型的对象,调用构造函数和析构函数在速度和内存方面的开销都可能很大.

  

实际的对象可能更大,开销也可能更高,但使用它足以说明复制调用构造函数和析构函数的调用频率.

请查看参考代码: ReferenceEfficiency.h

==========================================

传递 const  指针 :

代码: ReferenceEfficiency.h 中,

虽然给Function2()传递指针的效率更高,但这样做也是危险的.

函数Function2()并不打算对传递给它的Dog对象进行修改,但仍获取了该对象的地址.

这就是原始对象暴露在被修改的危险之中,失去了按值传递提供的保护.

按值传递好比将Dog的资料交给兽医,而对于资料的任何操作都不会对Dog本身产生任何危害.

按引用传递将Dog的位置告诉兽医,让兽医直接到家里来给Dog看病.

(这里证实了我之前的猜测,即对对象本身进行操作.)

解决这个问题的方法是,传递一个指向const Dog对象的指针.

这样做可以防止对Dog对象调用任何非const方法,从而防止对象被改变.

传递const引用好比让兽医能够看到Dog原物, 但是不能对其做任何修改.

请查看参考代码: ConstObjectPointer.h

==========================================

在  9.2 引用=指针>函数? 介绍过引用和指针的相互转换, 那对于对象而言,

能否用对象引用代替对象指针呢? 接下来就介绍 如何用引用代替指针.

代码: ConstObjectPointer.h 虽然解决了创建副本的问题,

从而减少了对复制构造函数和析构函数的调用.它使用指向const对象的const指针,

因此也解决了在函数中可能修改对象的问题.

这里有个小问题,为什么要用const指针呢? 可能是为了防止指针被修改,永远指向该对象.

比如删除指针再分配新指针指向.

而在构造函数分配的内存中这么做,无意会使程序发生极其危险的后果.所以用const指针.

然而,这有些繁琐,因为传递给函数的是指向对象的指针.

由于对象不可能为空,如果传递引用而不是指针,则在函数中处理起来将更方便.

 请查看参考代码: ReferenceInsteadPointer.h

 

==========================================

Main.cpp :

#include <iostream>
using namespace std;

#include “NewAndDeleteObject.h”
#include “ReferenceEfficiency.h”
#include “ConstObjectPointer.h”
#include “ReferenceInsteadPointer.h”

void main()
{
           NewAndDeleteObject();
           cout<<endl;
           ReferenceEfficiency();
           cout<<endl;
           ConstObjectPointer();
           cout<<endl;
           ReferenceInsteadPointer();
           cout<<endl;

}

NewAndDeleteObject.h :

class Cat
{
private:
           int itsAge;

public:
          Cat(); //默认构造函数
          ~Cat(); //析构函数
};

Cat::Cat()
{
          cout<<“Call Constructor.”<<endl;
          itsAge=5;
}

Cat::~Cat()
{
          cout<<“Call Destructor.”<<endl;
}

void NewAndDeleteObject()
{
          cout<<“New A Cat Frisky”<<endl;
          Cat Frisky;//类中的对象被创建,
          cout<<“Cat *pCat = new Cat;”<<endl;
          Cat *pCat = new Cat;//类中新对象被创建,再次调用构造函数
          cout<<“Delect Pointer pCat.”<<endl;
          delete pCat;//删除指针,导致对象被删除,所以pCat指向的Cat对象内存被释放,调用析构函数
          cout<<“Delected.”<<endl;
}
//最后程序结束,需要释放构造函数的内存和资源,
//Frisky将被释放,所以再次调用析构函数.结束程序.

 

ReferenceEfficiency.h :

/*这个例子比较复杂,但是看明白之后,就会对引用与构造函数有更深刻的体会*/

class Dog
{
public:
           Dog();//创建构造函数
           Dog(Dog&);//创建复制构造函数
           ~Dog();//创建析构函数
};

Dog::Dog()
{
           cout<<“Call Constructor.”<<endl;//告知调用构造函数
}

Dog::Dog(Dog &)
{
           cout<<“Call Copy of Constructor.”<<endl;//告知调用复制构造函数
}

Dog::~Dog()
{
           cout<<“Call Destructor.”<<endl;//告知调用析构函数
}

Dog Funtion1(Dog theDog);
Dog *Funtion2(Dog *theDog);

void ReferenceEfficiency()
{
           cout<<“New a object named Wangwang”<<endl;
           Dog Wangwang;//创建Wangwang对象
           cout<<“Call the 1st function.”<<endl;
           Funtion1(Wangwang);
           cout<<“Call the 2nd function.”<<endl;
           Funtion2(&Wangwang);
           //cout<<&Wangwang<<endl;
}

Dog Funtion1(Dog theDog)
{
           cout<<“1st function is called.”<<endl;
           return theDog;
}

Dog *Funtion2(Dog *theDog)//对象指针直接指向对象引用
{
           cout<<“2nd function is called.”<<endl;
           return theDog;
}
/*
此程序创建了一个Dog对象,然后调用两个函数.
第一个函数按值接受一个Cat对象,然后按值返回它.
第二个函数接受一个对象指针而不是对象本身作为参数.
*/

 代码中,我注释掉了这个//cout<<&Wangwang<<endl;

 如果未被注释掉,这会出现这样的结果:

这是按引用传递, 而不是按值传递.所以没有多次调用复制构造函数.

  

 ConstObjectPointer.h :

class dog
{
private:
           int itsWeight;
public:
           dog();
           dog(dog&);//复制构造函数
           ~dog();
 
           void SetWeight(int Weight){itsWeight=Weight;}//类内联函数(inline)
           int GetWeight() const {return itsWeight;}//类内联函数(inline)和const(保护itsWeight不被修改)
};

dog::dog()
{
           cout<<“Call Constructor.”<<endl;//调用构造函数
           itsWeight=5;//初始化itsWeight值为5
}

dog::dog(dog &)
{
           cout<<“Call Copy Of Constructor.”<<endl;//调用复制构造函数
}

dog::~dog()
{
           cout<<“Call Destructor.”<<endl;//调用析构函数
}

const dog *const Function2(const dog * const thedog);
//dog *const Function2( dog * const thedog);

void ConstObjectPointer()
{
           cout<<“New a object of dog.”<<endl;
           dog Lucky;
           cout<<“Lucky is “<<Lucky.GetWeight()<<” kg.”<<endl;
           int Weight=6;//重新定义Weight变量;
           Lucky.SetWeight(Weight);
           cout<<“Lucky is “<<Lucky.GetWeight()<<” kg.”<<endl;
           cout<<“Call Function2().”<<endl;
           Function2(&Lucky);
           cout<<“Lucky is “<<Lucky.GetWeight()<<” kg.”<<endl;
}

const dog *const Function2(const dog * const thedog)
//dog *const Function2( dog * const thedog)
{
           cout<<“*Function2() was called.”<<endl;
           cout<<“*Now Lucky is “<<thedog->GetWeight()<<” kg.”<<endl;
           //thedog->SetWeight(80); 
           //这里试图将对象用存取器SetWeight函数赋值,但是由于是const的,所以不能修改.
           return thedog;
}

如果用非const对象指针的话,会出现什么情况?

 ReferenceInsteadPointer.h :

class cat
{
private:
            int itsWeight;
public:
            cat();
            cat(cat&);
            ~cat();

            void SetWeight(int Weight){itsWeight=Weight;}
            int GetWeight(){return itsWeight;}
};

cat::cat()
{
            cout<<“Call Constructor.”<<endl;
            itsWeight=2;
}

cat::cat(cat&)
{
            cout<<“Call Copy of Constructor.”<<endl;
}

cat::~cat()
{
            cout<<“Call Destructor.”<<endl;
}

/*const*/ cat &Function2 (/*const*/ cat &thecat);
//这里注掉的原因是为了验证对象引用对对象的修改功能,本应为const,防止对象被随意改动

void ReferenceInsteadPointer()
{
            cout<<“New a object of cat.”<<endl;
            cat mimi;
            cout<<“Mimi is “<<mimi.GetWeight()<<” kg.”<<endl;
            mimi.SetWeight(3);
            cout<<“Mimi is “<<mimi.GetWeight()<<” kg.”<<endl;
            cout<<“Call Function2().”<<endl;
            Function2(mimi);
            cout<<“Mimi is “<<mimi.GetWeight()<<” kg.”<<endl;
}

/*const*/ cat &Function2 (/*const*/ cat &thecat)
//这里注掉的原因是为了验证对象引用对对象的修改功能,本应为const,防止对象被随意改动
{
            cout<<“Function2 was called.”<<endl;
            cout<<“Mimi is “<<thecat.GetWeight()<<” kg.”<<endl;
            thecat.SetWeight(10);//因为不是const对象,所以这里itsWeight值被修改.
            return thecat;
}
/*
改程序的变化是,Function2()接受一个指向const对象的引用作为参数,并返回一个这样的引用.
同样,使用引用比使用指针更简单,但在内存节省和效率方面与指针相同,同时可以使用const来提供安全.
*/

=========================================================================

最后是所有代码一起运行以后的截图:

*******************************************

PS:这一节学的相当累, 写的也相当繁琐.花了近1天的时间来写这篇post,坐在电脑前就没动过.

不过还好,学到了很多,而且也终于写完了.

太不容易了.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s