在介绍本节内容之前先说下类(class)和结构(structure)之间的的联系.
关键字class与struct之间基本用法一样,
只是class将成员变量和成员函数区分为了private和public,且默认为private.
而struct默认为public, 且采用公有继承(public inheritance).
如果将之前所有的class Cat改为struct Cat. 运行后,
将输出进行比较,可以发现没有发生任何变化.
Q: 那为什么要用class和structure来定义相同的功能呢?
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,坐在电脑前就没动过.
不过还好,学到了很多,而且也终于写完了.
太不容易了.