Visual C++ 2010 第9章 类继承 和 虚函数

9.2 类的继承

  • 当以一个类为基础定义另一个类时,后者称为派生类。
  • 派生类自动包含用来定义自己的那个类的所有数据成员,还有条件的包含了函数成员。即该类继承了基类的数据成员和函数成员。
  • 派生类不继承的基类成员:析构函数、构造函数以及任何重载赋值运算符的成员函数。所有其他函数成员,连同基类的所有数据成员,都将由派生类继承。

9.2.1 基类的概念
任何用作定义其他类的基础类都是基类。
在这里插入图片描述

9.2.2 基类的派生类

//Box.h
#pragma once //确保在编译过程中CBox的定义只能出现一次

class CBox
{
public:
	double m_Length;
	double m_Width;
	double m_Height;

	CBox(double lv = 1.0,double wv = 1.0,double hv = 1.0):
	m_Length(lv),m_Width(wv),m_Height(hv){}
};

// CandyBox.h
#pragma once
#include "Box.h"

class CCandyBox: CBox //    派生类   : 基类
{
public:
	char* m_Contents;

	CCandyBox(char* str = "Candy") 
	{
		m_Contents = new char[strlen(str) + 1];
		strcpy_s(m_Contents,strlen(str)+1,str);
	}

	~CCandyBox()
	{
		delete[] m_Contents;
	}
};
// 使用派生类
#include <iostream>
#include <cstring>
#include "CandyBox.h"
using std::cout;
using std::endl;

int main()
{
	CBox myBox(4.0,3.0,2.0);
	CCandyBox myCandyBox;
	CCandyBox myMintBox("Wafer Thin Mints");

	cout << endl
		 << "myBox occupies " << sizeof myBox
		 << " bytes" << endl
		 << "myCandyBox oppcupies "<< sizeof myCandyBox
		 << " bytes" << endl
		 << "myMintBox oppcupies " << sizeof myMintBox
		 << " bytes" ;

	cout << endl
		 << "myBox length is " << myBox.m_Length;
	myBox.m_Length = 10.0;
 // myCandyBox.m_Length = 10.0; //m_Length在派生类中已经成为私有成员,不可访问
	cout << endl;
	return 0;
}

通过给基类指定public 访问说明符,原来在基类中被指定为public成员,在被派生类继承之后将仍然具有相同的访问级别。

class CCandyBox: public CBox //    派生类   : 基类
{
public:
	char* m_Contents;

	CCandyBox(char* str = "Candy") 
	{
		m_Contents = new char[strlen(str) + 1];
		strcpy_s(m_Contents,strlen(str)+1,str);
	}

	~CCandyBox()
	{
		delete[] m_Contents;
	}
};

9.3 继承机制下的访问限制

  • 只有通过不属于基类private部分的基类函数成员,才能在派生类中访问它们。
  • 将Volume()函数定义 移到CBox的public部分则可以访问
#pragma once
class CBox
{
public:
	CBox(double lv = 1.0,double wv = 1.0,double hv = 1.0):
	m_Length(lv),m_Width(wv),m_Height(hv){}

	double Volume() const
	{ return m_Length*m_Width*m_Height;}

private:
	double m_Length;
	double m_Width;
	double m_Height;
};

// CandyBox.h
#pragma once
#include "Box.h"

class CCandyBox: public CBox //    派生类   : 基类
{
public:
	char* m_Contents;

	CCandyBox(char* str = "Candy") 
	{
		m_Contents = new char[strlen(str) + 1];
		strcpy_s(m_Contents,strlen(str)+1,str);
	}

	~CCandyBox()
	{
		delete[] m_Contents;
	}
};
// 使用派生类
#include <iostream>
#include <cstring>
#include "CandyBox.h"
using std::cout;
using std::endl;

int main()
{
	CBox myBox(4.0,3.0,2.0);
	CCandyBox myCandyBox;
	CCandyBox myMintBox("Wafer Thin Mints");

	cout << endl
		 << "myBox occupies " << sizeof myBox
		 << " bytes" << endl
		 << "myCandyBox oppcupies "<< sizeof myCandyBox
		 << " bytes" << endl
		 << "myMintBox oppcupies " << sizeof myMintBox
		 << " bytes" ;

	cout << endl
		 << "myMintBox volume is " << myMintBox.Volume();
 
	cout << endl;
	return 0;
}

9.3.1 派生类中构造函数的操作

#pragma once
#include <iostream>
using std::cout;
using std::endl;

class CBox
{
public:
	CBox(double lv = 1.0,double wv = 1.0,double hv = 1.0):
					m_Length(lv),m_Width(wv),m_Height(hv)
	{ cout << endl << "CBox constructor called.";}

	double Volume() const
	{ return m_Length*m_Width*m_Height;}
private:
	double m_Length;
	double m_Width;
	double m_Height;
};
// CandyBox.h
#pragma once
#include <iostream>
#include "Box.h"
using std::cout;
using std::endl;

class CCandyBox: public CBox //    派生类   : 基类
{
public:
	char* m_Contents;

	CCandyBox(double lv,double wv,double hv,char* str = "Candy") 
													: CBox(lv,wv,hv)
	{
		cout << endl << "CCandyBox constructor2 called";
		m_Contents = new char[strlen(str) + 1];
		strcpy_s(m_Contents,strlen(str)+1,str);
	}

	CCandyBox(char* str = "Candy") 
	{
		cout << endl << "CCandyBox constructor1 called";
		m_Contents = new char[strlen(str) + 1];
		strcpy_s(m_Contents,strlen(str)+1,str);
	}


	~CCandyBox()
	{
		delete[] m_Contents;
	}
};
// 使用派生类
#include <iostream>
#include <cstring>
#include "CandyBox.h"
using std::cout;
using std::endl;

int main()
{
	CBox myBox(4.0,3.0,2.0);
	CCandyBox myCandyBox;
	CCandyBox myMintBox(1.0,2.0,3.0,"Wafer Thin Mints");

	cout << endl
		 << "myBox occupies " << sizeof myBox
		 << " bytes" << endl
		 << "myCandyBox oppcupies "<< sizeof myCandyBox
		 << " bytes" << endl
		 << "myMintBox oppcupies " << sizeof myMintBox
		 << " bytes" ;

	cout << endl
		 << "myMintBox volume is " << myMintBox.Volume();
 
	cout << endl;
	return 0;
}

在这里插入图片描述
当执行派生类的构造函数时,总是要调用基类的构造函数来构造派生类的基类部分。
注意,基类构造函数总是先于派生类构造函数被调用。因为基类是派生类构建的基础。

9.3.2 声明类的保护成员

  • 类成员声明为protected。 在类的内部,protected关键字与private关键字具有相同的效果。
  • 类的保护成员只能被类的成员函数和类的友元函数访问。
  • 每个派生类对象都是分两步被销毁的:
    创建对象时首先调用基类的构造函数,然后调用派生类的构造函数;而销毁对象时首先调用派生类的析构函数,然后才调用基类的析构函数。

9.3.3 继承类成员的访问级别
在这里插入图片描述

  • 如果将基类的成员声明为private,则他们在派生类中永远都不可访问。
  • 如果将基类声明为public,其成员在派生类的访问级别保持不变。
  • 如果将基类声明为protected,其public成员在派生类中将成为protected。

9.4 派生类中的复制构造函数

  • 在声明用相同的类对象初始化的对象时自动调用复制构造函数。
CBox myBox(2.0,3.0,4.0); // called constructor
CBox copyBox(myBox);    // called copy constructor

如果不提供自己的复制构造函数,则编译器将提供一个默认的复制构造函数,将初始化对象的成员逐一复制到新对象的对应成员中。

// Box.h
#pragma once
#include <iostream>
using std::cout;
using std::endl;

class CBox
{
public:
	CBox(double lv = 1.0,double wv = 1.0,double hv = 1.0):
					m_Length(lv),m_Width(wv),m_Height(hv)
	{ cout << endl << "CBox constructor called.";}

	CBox(const CBox& initB)// 复制构造函数
	{
		cout << endl << "CBox copy constructor called";
		m_Length = initB.m_Length;
		m_Width = initB.m_Width;
		m_Height = initB.m_Height;
	}

	~CBox()
	{ cout << "CBox destrctor called"<< endl;}
protected:
	double m_Length;
	double m_Width;
	double m_Height;
};

// CandyBox.h
// CandyBox.h
#pragma once
#include <iostream>
#include "Box.h"
using std::cout;
using std::endl;

class CCandyBox: public CBox //    派生类   : 基类
{
public:
	char* m_Contents;

	double Volume() const
	{ return m_Length*m_Width*m_Height;}

	CCandyBox(double lv,double wv,double hv,char* str = "Candy") 
													: CBox(lv,wv,hv)
	{
		cout << endl << "CCandyBox constructor2 called";
		m_Contents = new char[strlen(str) + 1];
		strcpy_s(m_Contents,strlen(str)+1,str);
	}

	CCandyBox(char* str = "Candy") 
	{
		cout << endl << "CCandyBox constructor1 called";
		m_Contents = new char[strlen(str) + 1];
		strcpy_s(m_Contents,strlen(str)+1,str);
	}

	CCandyBox(const CCandyBox& initCB):CBox(initCB) 
	// 以对象initCB 为实参调用了CBox类的复制构造函数
	{
		cout << endl << "CCandyBox copy constructor called ";
		m_Contents = new char[strlen(initCB.m_Contents)+ 1];
		strcpy_s(m_Contents,strlen(initCB.m_Contents)+1,initCB.m_Contents);
	}

	~CCandyBox()
	{
		cout << "CCandyBox destructor called " << endl;
		delete[] m_Contents;
	}
};

#include <iostream>
#include <cstring>
#include "CandyBox.h"
using std::cout;
using std::endl;

int main()
{
	CCandyBox chocBox(2.0,3.0,4.0,"Chockies");
	CCandyBox chocolateBox(chocBox);

	cout << endl
		 << "Volume of chocBox is " << chocBox.Volume()
		 << endl
		 << "Volume of chocolateBox is " << chocolateBox.Volume()
		 << endl;
	return 0;
}

在这里插入图片描述

在为派生类编写构造函数时,需要初始化包括继承成员在内的派生类对象的所有成员。

9.5 友元类成员

// Bottle.h
#pragma once

class CBottle;//CBottle类的前向声明,因为友元函数引用它

class CCarton
{
public:
	CCarton(const CBottle& aBottle);
private:
	double m_Length;
	double m_Width;
	double m_Height;
};

class CCarton; // CCarton类的前向声明,因为友元函数引用它
class CBottle
{
public:
	CBottle(double height,double diameter)
	{
		m_Height = height;
		m_Diameter = diameter;
	}

private:
	double m_Height;
	double m_Diameter;

	friend CCarton::CCarton(const CBottle& aBottle);
	// 为了识别友元函数而必须在其名称前面添加类名和作用域解析运算符
};

#include "Bottle.h"

CCarton::CCarton(const CBottle& aBottle)
	{
		m_Height = aBottle.m_Height;
		m_Length = 4.0*aBottle.m_Diameter;
		m_Width = 3.0*aBottle.m_Diameter;
	}
  • CCarton类定义引用CBottle类,而CBottle类又添加了友元函数来引用CCarton类,因此这里出现了循环依赖关系。
  • 可以将CCarton类的前向声明放在CBottle类中,反之亦然,但仍然不允许编译类。问题在于CCarton类的构造函数。此函数在CCarton类定义中,而编译器如果不先编译CBottle类,就不能编译此函数。另一方面,编译器不编译CCarton类,就不能编译CBottle类。 解决此问题的唯一方式:将CCarton构造函数定义放在.cpp文件中。

9.5.1 友元类
可以使某个类的所有函数成员都有权访问另一个类的数据成员,即 将该类声明为友元类。

friend CCarton;
// 在CBottle 类定义内添加一条又元说明;
// 将CCarton类定义为CBottle类的友元

CBottle类中有了这条声明语句之后,CCarton类的所有函数成员就都能自由访问CBottle类的所有数据成员。

9.5.2 对类友元关系的限制

  • 类友元关系不是互惠的。
    即使CCarton类称为CBottle类的友元,并不意味着CBottle类也是CCarton类的友元。
  • 类友元关系是不可继承的。
    如果以CBottle为基类定义了另一个类,则CCarton类的成员将无权访问该派生类的数据成员。

9.6 虚函数

  • 虚函数是以virtual 关键字声明的基类函数。
  • 如果在基类中将某个函数指定为virtual,并且派生类 中有该函数的另外一个定义,则编译器将知道我们不想静态连接该函数。
//Box.h
#pragma once
#include <iostream>
using std::cout;
using std::endl;

class CBox
{
public:
	void ShowVolume()const
	{
		cout << endl
			 << "CBox usable volume is "<< Volume();
	}

	virtual double Volume() const
	{
		return m_Length*m_Width*m_Height;
	}

	CBox(double lv = 1.0,double wv = 1.0,double hv = 1.0):
					m_Length(lv),m_Width(wv),m_Height(hv)
	{ }

protected:  // 指定为protected ,任何派生类的成员函数都可以访问它们
	double m_Length;
	double m_Width;
	double m_Height;
};

// GlassBox.h
#pragma once
#include "Box.h"

class CGlassBox: public CBox
{
public:
	virtual double Volume() const
	{
		return 0.85*m_Length*m_Width*m_Height;
	}

	CGlassBox(double lv,double wv,double hv):CBox(lv,wv,hv){}
};

// mian
#include <iostream>
#include "GlassBox.h"
using std::cout;
using std::endl;

int main()
{
	CBox myBox(2.0,3.0,4.0);
	CGlassBox myGlassBox(2.0,3.0,4.0);

	myBox.ShowVolume();
	myGlassBox.ShowVolume();

	cout << endl;
	return 0;
}
// 第一次用CBox对象myBox调用ShowVolume()函数时,该函数调用了CBox类的Volume()版本;
// 第二次用CGlassBox对象myGlassBox调用ShowVolume()函数时,该函数调用了派生类中的Volume()版本;
  • 注意,要使某个函数表现出虚函数的行为,该函数在任何派生类中都必须有与基类函数相同的名称、形参列表和返回类型。如果基类函数时const,那么派生类中的函数也必须是const。如果试图使用不同的形参或返回类型,或者将一个声明为const,另一个不是const,则虚函数机制将不能工作。函数的行为将在编译时通过静态连接确定。

9.6.2 使用指向类对象的指针

//Box.h 与上例相同
// GlassBox.h 与上例相同
// main
#include <iostream>
#include "GlassBox.h"
using std::cout;
using std::endl;

// 使用基类的指针
int main()
{
	CBox myBox(2.0,3.0,4.0);
	CGlassBox myGlassBox(2.0,3.0,4.0);
	CBox* pBox(nullptr);

	pBox = &myBox;
	pBox->ShowVolume();
	pBox = &myGlassBox;
	pBox->ShowVolume();

	cout << endl;
	return 0;
}

虚函数机制借助于指向基类的指针同样能够正常工作,实际调用的函数是基于被指向的对象类型而选择的。
在这里插入图片描述

  • 这意味着:即使不知道程序中某个基类指针所指对象的准确类型(如某个指针作为实参传递为函数时)虚函数机制也能够确保调用正确的函数。

9.6.3 使用引用处理虚函数
如果定义一个形参为基类引用的函数,则可以给该函数传递派生类的对象作为实参。该函数在执行的时候,将自动为传递进来的对象选择适当的虚函数。

//Box.h 与上例相同
// GlassBox.h 与上例相同
// main
#include <iostream>
#include "GlassBox.h"
using std::cout;
using std::endl;

void Output(const CBox& aBox);

int main()
{
	CBox myBox(2.0,3.0,4.0);
	CGlassBox myGlassBox(2.0,3.0,4.0);

	Output(myBox);
	Output(myGlassBox);

	cout << endl;
	return 0;
}

void Output(const CBox& aBox)
{
	aBox.ShowVolume();
}

因为形参是基类的引用,所以Output()函数可以接这2种类对象实参,并根据初始化引用的对象种类,调用适当的虚函数Volume().

不完整定义
为了处理Output函数的原型声明,编译器需要知道CBox类的定义,因为形参属于CBox&。在不使用头文件包含的情况下,可以使用 CBox类的不完整定义:
在Output函数的原型前提供CBox类的不完整定义

class CBox;

该语句只是将名称CBox标识为此刻还没有被定义的类,但已经足够使编译器知道CBox是个类名,同时使编译器能够处理Output()函数的原型。如果不知处CBox是一个类,那么Output()的原型声明将导致编译器生成一条错误信息。

9.6.4 纯虚函数

//Container.h
#pragma once
#include <iostream>
using std::cout;
using std::endl;

class CContainer
{
public:
	virtual double Volume() const = 0; //将函数定义成没有任何内容,纯虚函数

	virtual void ShowVolume() const
	{
		cout << endl
			 << "Volume is " << Volume();
	}
};
// Can.h
#pragma once
#include "Container.h"
//PI 是个在其他地方定义的const double类型的全局变量
extern const double PI;

class CCan: public CContainer
{
public:
	virtual double Volume() const
	{
		return 0.25*PI*m_Diameter*m_Height;
	}

	CCan(double hv = 4.0,double dv = 2.0):m_Height(hv),m_Diameter(dv){}

protected:
	double m_Height;
	double m_Diameter;
};

//Box.h
#pragma once
#include "Container.h"
#include <iostream>
using std::cout;
using std::endl;

class CBox: public CContainer
{
public:
	virtual void ShowVolume() const
	{
		cout << "CBox usable volume is " << Volume();
	}

	virtual double Volume() const
	{
		return m_Length*m_Width*m_Height;
	}

	CBox(double lv = 1.0,double wv = 1.0,double hv = 1.0):
					m_Length(lv),m_Width(wv),m_Height(hv)
	{ }

protected:  
	double m_Length;
	double m_Width;
	double m_Height;
};

// main
#include "Box.h"
#include "Can.h"
#include <iostream>
using std::cout;
using std::endl;

const double PI = 3.14159265;

int main()
{
	CContainer* pC1 = new CBox(2.0,3.0,4.0); //pC1赋值为在空闲存储器中由new操作符创建的CBox对象的地址
	CContainer* pC2 = new CCan(6.5,3.0);

	pC1->ShowVolume();
	pC2->ShowVolume();
	return 0;
}
  • 定义虚函数Volume()的语句通过在函数头中添加等号和0,将该函数定义成没有任何内容。这样的函数称为纯虚函数。
  • 该类的任何派生类都必须要么定义Volume()函数,要么重新将其定义成纯虚函数。
  • 注意,即使名称和形参列表相同,但函数的const 和 非const变体也是不同的函数。

9.6.5 抽象类

  • 包含纯虚函数的类称为抽象类,因为不能定义包含纯虚函数的类的对象,抽象类存在的唯一用途,就是定义派生类。
  • 虽然不能定义CContainer对象,但仍然可以定义指向CContainer的指针,然后可以使用该指针来存储派生类对象的地址。

9.6.6 间接基类
子类的基类可能是从另一个基类派生出来的。
在这里插入图片描述

// Container.h
#pragma once
#include <iostream>
using std::cout;
using std::endl;

class CContainer
{
public:
	virtual double Volume() const = 0; //将函数定义成没有任何内容,纯虚函数

	virtual void ShowVolume() const
	{
		cout << endl
			 << "Volume is " << Volume();
	}

	virtual ~CContainer(){}
};
//Box.h
#pragma once
#include "Container.h"
#include <iostream>
using std::cout;
using std::endl;

class CBox: public CContainer
{
public:
	virtual void ShowVolume() const
	{
		cout << "CBox usable volume is " << Volume();
	}

	virtual double Volume() const
	{
		return m_Length*m_Width*m_Height;
	}

	CBox(double lv = 1.0,double wv = 1.0,double hv = 1.0):
					m_Length(lv),m_Width(wv),m_Height(hv)
	{ }

protected:  
	double m_Length;
	double m_Width;
	double m_Height;
};

// Can.h
#pragma once
#include "Container.h"
//PI 是个在其他地方定义的const double类型的全局变量
extern const double PI;

class CCan: public CContainer
{
public:
	virtual double Volume() const
	{
		return 0.25*PI*m_Diameter*m_Height;
	}

	CCan(double hv = 4.0,double dv = 2.0):m_Height(hv),m_Diameter(dv){}

protected:
	double m_Height;
	double m_Diameter;
};

// GlassBox.h
#pragma once
#include "Box.h"

class CGlassBox: public CBox
{
public:
	virtual double Volume() const
	{
		return 0.85*m_Length*m_Width*m_Height;
	}

	CGlassBox(double lv,double wv,double hv):CBox(lv,wv,hv){}
};
// main.cpp
#include "Box.h"
#include "Can.h"
#include "GlassBox.h"
#include <iostream>
using std::cout;
using std::endl;

const double PI = 3.14159265;

int main()
{
	CContainer* pC1 = new CBox(2.0,3.0,4.0); //pC1赋值为在空闲存储器中由new操作符创建的CBox对象的地址
	
	CCan myCan(6.5,3.0);
	CGlassBox myGlassBox(2.0,3.0,4.0);

	pC1->ShowVolume();
	delete pC1;

	pC1 = &myCan;
	pC1->ShowVolume();

	pC1 = &myGlassBox;
	pC1->ShowVolume();

	cout << endl;
	return 0;
}

9.6.7 虚析构函数
将基类函数声明virtual足以确保任何派生类中所有名称、形参列表和返回类型与其相同的函数也都是virtual。 这条规则也适用于析构函数。
为确保调用正确的析构函数,在Container.h头文件包含的CContainer类定义中,给析构函数的定义添加virtual关键字即可:

virtual ~CContainer(){cout<< "CContainer destructed called" << endl;}

9.7 类类型之间的强制转换

CContainer* pContainer = new CGlassBox(2.0,3.0,4.0);
CBox* pBox = dynamic_cast<CBox*>(pContainer);
CGlassBox * pGlassBox = dynamic_cast<CGlassBox*>(pContainer);

第1条语句:将在堆上创建的CGlassBox对象的地址存储在一个CContainer类型的基类指针中。
第2条语句:将类层次结构中底层的pContainer转换为CBox
类型。
第3条语句:将pContainer中的地址强制转换为实际的类型CGlassBox*。

也可以对引用应用dynamic_cast操作符。

  • dynamic_cast 与 static_cast 区别:

    dynamic_cast 操作符在运行时检查转换的有效性,而static_cast 则不然。如果dynamic_cast 操作无效,则结果为空。编译器依赖编程人员来保证static_cast 操作的有效性。

9.8 嵌套类

  • 可以将某个类的定义放在另一个类定义的内部,这种情况就是定义嵌套类。
  • 嵌套类具有封装它的类的静态成员的外部特征,而且就想任何其他类成员一样,受成员访问说明符的支配。
    • 如果将嵌套类的定义放在类的私有部分,将只能从封装类的作用域内部引用嵌套类。
    • 如果将嵌套类指定为public,将可以从封装类的外部访问嵌套类,但这种情况下嵌套类的名称必须用外部类的名称加以限定。

嵌套类可以空闲访问封装类的所有静态成员。通过封装类的对象或指向封装类对象的指针或引用还可以访问所有实例成员。封装类只能访问嵌套类的公有成员,但在封装类所私有的嵌套类中,通常都将类成员声明为public,以使封装类的函数能够自由访问整个嵌套类。

当需要定义一个只能在另一个类型内部使用的类型时,嵌套类特别有用。

// Container.h
#pragma once
#include <iostream>
using std::cout;
using std::endl;

class CContainer
{
public:
	virtual double Volume() const = 0; //将函数定义成没有任何内容,纯虚函数

	virtual void ShowVolume() const
	{
		cout << endl
			 << "Volume is " << Volume();
	}

	virtual ~CContainer(){}
};

// Box.h
#pragma once
#include "Container.h"
#include <iostream>
using std::cout;
using std::endl;

class CBox: public CContainer
{
public:
	virtual void ShowVolume() const
	{
		cout << endl << "CBox usable volume is " << Volume();
	}

	virtual double Volume() const
	{
		return m_Length*m_Width*m_Height;
	}

	CBox(double lv = 1.0,double wv = 1.0,double hv = 1.0):
					m_Length(lv),m_Width(wv),m_Height(hv)
	{ }

protected:  
	double m_Length;
	double m_Width;
	double m_Height;
};

// GlassBox.h
#pragma once
#include "Box.h"

class CGlassBox: public CBox
{
public:
	virtual double Volume() const
	{
		return 0.85*m_Length*m_Width*m_Height;
	}

	CGlassBox(double lv,double wv,double hv):CBox(lv,wv,hv){}
};

//Stack.h
#pragma once

class CStack
{
private:
	struct CItem // 定义嵌套结构 ,结构的成员默认都是公有的
	{
		CBox* pBox;  //CBox对象的指针
		CItem* pNext; // 栈中下一个CItem对象的地址

		CItem(CBox* pB,CItem* pN):pBox(pB),pNext(pN){}
	};

	CItem* pTop; 

public:
	CStack():pTop(nullptr){}

	void Push(CBox* pBox) //将某个CBox对象压到栈顶
	{
		pTop = new CItem(pBox,pTop);
	}

	CBox* Pop() //将栈顶的对象弹出
	{
		if(!pTop)
			return nullptr;

		CBox* pBox = pTop->pBox;
		CItem* pTemp = pTop;
		pTop = pTop->pNext;
		delete pTemp;
		return pBox;
	}

	~CStack()
	{
		CItem* pTemp(nullptr);
		while(pTop)
		{
			pTemp = pTop;
			pTop->pNext;
			delete pTemp;
		}
	}
};
// main.cpp
#include "Box.h"
#include "GlassBox.h"
#include "Stack.h"
#include <iostream>
using std::cout;
using std::endl;

int main()
{
	CBox* pBoxes[] = { new CBox(2.0,3.0,4.0),
					   new CGlassBox(2.0,3.0,4.0),
					   new CBox(4.0,5.0,6.0),
					   new CGlassBox(4.0,5.0,6.0)
					  };
	int nBoxes = sizeof pBoxes/sizeof pBoxes[0]; // 计算pBoxes数组元素的个数
	cout << "The array of boxes have the following volumes:";
	for(int i = 0;i < nBoxes; i++)
		pBoxes[i]->ShowVolume();
	cout << endl << endl
		 << "Now pushing the boxes on the stack..."
		 << endl;

	CStack* pStack = new CStack; // 创建CStack 对象
	for (int i = 0; i < nBoxes ; i++)
		pStack->Push(pBoxes[i]);

	cout << "Poping the boxes off the stack presents them in reverse order:";
	CBox* pTemp(nullptr);
	while(pTemp = pStack->Pop())
		pTemp->ShowVolume();

	cout << endl;
	delete pStack;
	for (int i = 0; i < nBoxes; i++)
		delete pBoxes[i];
	return 0;
}

在这里插入图片描述

9.9 C++/CLI编程

  • 包括用户定义的类在内的所有C++/CLI类,默认情况下都是派生类,因为值类和引用类都是以System::Object这个标准类作为基类的。

9.9.1 装箱与拆箱

  • 值类型实例的装箱:将它转换为垃圾回收堆上的一个对象,因此他会与基本值一起承载完整的类型信息。拆箱是装箱的逆向操作。
  • 装箱/拆箱的功能意味着:基本类型的数值可以表现对象的行为,但在参与数值运算时又能抛掉对象应有的系统开销。
  • 基本类型的值只是作用于常规运算的数值存储在栈上,当它们需要表现出对象的行为时,只能被转换为堆上有System::Object^ 类型的句柄引用的对象。
    (1) 通过将值赋给一个Object^ 类型的变量来强制装箱。
double value = 3.14159265;
Object^ boxedValue = value ;
// 强制装箱value值,装箱后的表示由句柄boxedValue 引用

(2) 用gcnew 强制装箱一个值,以便在垃圾回收堆上创建一个装箱过的值。

long^ number = gcnew long(999999L);
// 隐式装箱值999999L,并将它存储在堆上句柄number 所引用的位置中。

(3) 用解引用操作符对值类型执行拆箱操作

Console::WriteLine(*number);
// 句柄number所指向的值被拆箱,然后作为一个值传递个WriteLine()函数。

(4) 用safe_cast 对一个已装箱的值执行拆箱操作:

long n = safe_cast<long>(number);
//对number拆箱并将值存储在n中。

注意,如果没有safe_cast,就不能编译这个语句,因为在这种情况下没有发生隐式转换。

9.9.2 C++/CLI类的继承

  • 值类中的多态性仅限于在System::Object 类中被定义为虚函数的那些函数。
  • 所有值类都要从System::Object类继承的虚函数:
    在这里插入图片描述
    在这里插入图片描述

C++/CLI类同本地C++类的区别:

  • 只有引用类可以是派生类。
  • 派生引用类的基类都是public。
  • 引用类中没有定义的函数是抽象函数,必须使用abstract关键字声明。
  • 必须通过在类名后面放上abstract关键字,将包含一个或多个抽象函数的类显示指定为抽象类。
  • 也可以将不包含抽象函数的类指定为abstract,这种情况下将不能定义该类的实例。
  • 当指定某个重写基类的函数时,必须显式的使用override 关键字。
// Container.h
#pragma once
using namespace System;

// 如果某个C++/CLI类包含等价于本地C++类中纯虚函数的函数,那么必须将该累指定为abstract
// 也可以将不包含任何抽象函数的类指定为abstract,这样可以阻止创建该类的对象。
ref class Container abstract
{
public:
	virtual double Volume() abstract; //abstract指出该函数不是为本抽象类定义的

	virtual void ShowVolume()
	{
		Console::WriteLine(L"Volume is {0}",Volume());
	}
};

//Box.h
#pragma once
#include "Container.h"

ref class Box:Container //引用类的基类总是公有的,默认情况下认为是有public关键字的
{
public:
	virtual void ShowVolume() override
	{
		Console::WriteLine(L"Box usable volume is {0}",Volume());
	}

	virtual double Volume() override
	{
		return m_Length*m_Width*m_Height;
	}

	Box(): m_Length(1.0),m_Width(1.0),m_Height(1.0){}

	Box(double lv,double wv,double hv):m_Length(lv),m_Width(wv),m_Height(hv){}
protected:
	double m_Length;
	double m_Width;
	double m_Height;
};

// GlassBox.h
#pragma once
#include "Box.h"

ref class GlassBox:Box
{
public:
	virtual double Volume() override
	{
		return 0.85*m_Length*m_Width*m_Height;
	}

	GlassBox(double lv,double wv,double hv): Box(lv,wv,hv){}
};
// Stack.h
#pragma once
// 函数的形参和字段都是句柄
ref class Stack
{
private:
	ref struct Item
	{
		Object^ Obj;  
		Item^ Next;

		Item(Object^ obj,Item^ next):Obj(obj),Next(next){}
	};
	Item^ Top;
public:
	void Push(Object^ obj) // 形参为Object^类型,可接受任何类类型的实参
	{
		Top = gcnew Item(obj,Top);
	}

	Object^ Pop()
	{
		if(Top == nullptr)
			return nullptr;
		Object^ obj = Top->Obj;
		Top = Top->Next;
		return obj;
	}
};
// Ex9_14.cpp: 主项目文件。

#include "stdafx.h"
#include "Box.h"
#include "GlassBox.h"
#include "Stack.h"

using namespace System;

int main(array<System::String ^> ^args)
{
	// 创建Box对象的句柄数组
	array<Box^>^ boxes = { gcnew Box(2.0,3.0,4.0),
							gcnew GlassBox(2.0,3.0,4.0),
							gcnew Box(4.0,5.0,6.0),
							gcnew GlassBox(4.0,5.0,6.0)
						};

	Console::WriteLine(L"The array of boxes have the following volumes:");
	for each(Box^ box in boxes)
		box->ShowVolume();

	Console::WriteLine(L"\nNow pushing the boxes on the stack...");
	Stack^ stack = gcnew Stack; // 创建Stack对象
	for each(Box^ box in boxes)
		stack->Push(box);

	Console::WriteLine(L"Popping the boxes off the stack presents them in reverse order:");
	Object^ item;
	while((item = stack->Pop())!= nullptr)
		safe_cast<Container^>(item)->ShowVolume(); //将item中存储的句柄强制转换为Container^类型。
//item变量属于Object^类型,因为Object类没有定义ShowVolume()函数,所以不能使用该类型的句柄调用ShowVolume()函数。
	Console::WriteLine(L"\nNow pushing integers onto the stack:");
	for(int i = 2;i <= 12; i += 2)
	{
		Console::WriteLine(L"{0,5}",i);
		stack->Push(i);
	}

	Console::WriteLine(L"\n\nPopping integers off the stack produces:");
	while((item = stack->Pop())!= nullptr)
		Console::WriteLine(L"{0,5}",item);

    Console::ReadLine();
    return 0;
}

在这里插入图片描述

9.9.3 接口类

  • 接口类指定一组将由其他类实现的函数,以提供标准化的、可提供某种具体功能的方法。
  • 数值类和引用类都可以实现接口。接口不定义任何自由的函数成员,其指定的函数是由实现该接口的各个类定义的。

为上一示例中的Box类实现System::IComparable接口:

ref class Box:Container,IComparable
{
public:
	virtual int CompareTo(Object^ obj)
	{
		if(Volume() < safe_cast<Box^>(obj)->Volume())
			return -1;
		else if(Volume() > safe_cast<Box^> (obj)->Volume())
			return 1;
		else 
			return 0;
	}	
	// Rest of the class as before...
}

接口名跟在基类名Container后面,如果没有基类,则这里单独出现接口名。

9.9.4 定义接口类

  • 使用关键字interface class 或 interface struct定义接口类。接口的所有成员默认都是公有的,不能将它们指定为其他类型。
  • 几口的成员可以是函数——包括运算符函数、属性、静态字段和事件。
  • 注意,可以从一个接口派生出另一个接口,方法基本上与派生引用时使用的方法相同。
interface class IController : ITelevison,IRecorder
{
	// members of IController
}
// IController 接口包含自己的成员,还继承了ITelevison 和 IRecorder 接口的成员。
// 实现IController 接口的类必须定义来自IController 、ITelevison 、IRecorder  的成员函数。

可以使用接口 代替Ex9_14中的基类 Container,定义如下:

#pragma once 

interface class IContainer
{
	double Volume();
	void ShowVolume();
};
  • 按照惯例,C++/CLI中的接口名以 I 打头。

任何实现 IContainer 接口的类如果不是抽象类的话,就必须实现这2个函数。

#include "IContainer.h"

using namespace System;

ref class Box:IContainer //引用类的基类总是公有的,默认情况下认为是有public关键字的
{
public:
	virtual void ShowVolume() // 没有 override关键,因为不是在重写现有的函数定义
	{
		Console::WriteLine(L"Box usable volume is {0}",Volume());
	}

	virtual double Volume() 
	{
		return m_Length*m_Width*m_Height;
	}

	Box(): m_Length(1.0),m_Width(1.0),m_Height(1.0){}

	Box(double lv,double wv,double hv):m_Length(lv),m_Width(wv),m_Height(hv){}
protected:
	double m_Length;
	double m_Width;
	double m_Height;
};
  • GlassBox类是从Box类派生的,因此将继承IContainer的实现,完全不需要修改GlassBox类的定义,就能适应IContainer 接口类的引入。
  • 在多态性方面,IContainer 接口类扮演者与基类相同的角色。可以使用 IContainer 类型的句柄,存储任何实现了该接口的类类型的对象地址。

9.9.5 类和程序集
C++/CLI 应用程序总是驻留在一个或多个程序集中,因此C++/CLI类也要驻留在某个程序集中。

  • 类驻留的程序集 称为 该类的父程序集。

1.类和接口的可见性说明符。
可以将非嵌套类、接口 或枚举类型的可见性指定为

  • private:private类只能从父程序集中访问。类、接口和枚举类类型默认都是私有的,因此仅在其父程序集中才可见。
  • public:public类在驻留的程序集外部时可见的和可以访问的。

将某个类指定为public:

public interface class IContainer
{
	//Details of the interface
};

如果省略public 关键字,则该接口默认将是私有的,只能在其父程序集内部使用。

2.类和几口成员访问说明符

public ref class MyClass
{
public:可从父程序集内和外的类访问的成员

internal:可从父程序集内部的类访问的成员

public protected:成员可以在父程序集外部的MyClass派生的类型中访问,也可以在父程序集内部的任何类中访问

private protected:可以在父程序集中从MyClass派生的类型中访问成员
}

创建类库:
使用Class Library 模板创建CLR项目

// Ex9_16_1lib.h

#pragma once

using namespace System;

namespace Ex9_16_1lib {

	// Container.h
	public interface class IContainer
	{
		virtual double Volume();
		virtual void ShowVolume();
	};

	// Box.h
	public ref class Box:IContainer
	{
	public:
		virtual void ShowVolume()
		{
			Console::WriteLine(L"CBox usable volume is {0}",Volume());
		}

		virtual double Volume() 
		{
		return m_Length*m_Width*m_Height;
		}

		Box(): m_Length(1.0),m_Width(1.0),m_Height(1.0){}

		Box(double lv,double wv,double hv):m_Length(lv),m_Width(wv),m_Height(hv){}
	public protected: // 派生类继承的这些字段将变为protected字段。
		double m_Length;
		double m_Width;
		double m_Height;
	};

	// Stack.h
	public ref class Stack
	{
	private:
		ref struct Item
		{
			Object^ Obj;  
			Item^ Next;

			Item(Object^ obj,Item^ next):Obj(obj),Next(next){}
		};
		Item^ Top;
	public:
		void Push(Object^ obj) // 形参为Object^类型,可接受任何类类型的实参
		{
			Top = gcnew Item(obj,Top);
		}

		Object^ Pop()
		{
			if(Top == nullptr)
				return nullptr;
			Object^ obj = Top->Obj;
			Top = Top->Next;
			return obj;
		}
	};
}

成功编译该项目后,包含该类库的程序集在Ex9_16_1lib.dll 文件中。.dll 扩展名意味着这是个动态链接库或DLL。

使用类库:

// GlassBox.h
#pragma once
#using <Ex9_16_1lib.dll>
using namespace Ex9_16_1lib;

ref class GlassBox:Box
{
public:
	virtual double Volume() override
	{
		return 0.85*m_Length*m_Width*m_Height;
	}

	GlassBox(double lv,double wv,double hv): Box(lv,wv,hv){}
};

// Ex9_16.cpp: 主项目文件。

#include "stdafx.h"
#include "GlassBox.h"
#using <Ex9_16_1lib.dll>

using namespace System;
using namespace Ex9_16_1lib;


int main(array<System::String ^> ^args)
{
	array<IContainer^>^ containers = { gcnew Box(2.0,3.0,4.0),
							gcnew GlassBox(2.0,3.0,4.0),
							gcnew Box(4.0,5.0,6.0),
							gcnew GlassBox(4.0,5.0,6.0)
						};

	Console::WriteLine(L"The array of boxes have the following volumes:");
	for each(IContainer^ container in containers)
		container->ShowVolume();

	Console::WriteLine(L"\nNow pushing the boxes on the stack...");
	Stack^ stack = gcnew Stack; // 创建Stack对象
	for each(IContainer^ container in containers)
		stack->Push(container);

	Console::WriteLine(L"Popping the boxes off the stack presents them in reverse order:");
	Object^ item;
	while((item = stack->Pop())!= nullptr)
		safe_cast<IContainer^>(item)->ShowVolume(); 

    Console::ReadLine();
    return 0;
}

构建项目时,Ex9_16_1lib.dll 文件必须可用,因此将Ex9_16_1lib.dll 复制到Ex9_16 项目包含源文件的子目录中。构建成功后,将Ex9_16_1lib.dll 复制到包含Ex9_16.exe文件的目录中。

Ex9_16_1lib.dll 文件在Ex9_16_1lib 解决方案目录的debug子目录中,将这个库文件复制到Ex9_16解决方案目录的debug子目录中。

9.9.6 被指定为new 的函数
可以在派生类中将某个函数指定为new,这样可以隐藏基类中签名相同的函数,而且新函数将不参与多态的行为。

为派生自Box的NewBox 类的Volume()函数指定为new

ref class NewBox:Box
{
	public:
		virtual double Volume() new
		{ return 0.5*m_Length*m_Width*m_Height;}
		NewBox(double lv,double wv,double hv): Box(lv,wv,hv){}
};

该函数版本隐藏了Box中定义的Volume()函数版本,因此如果使用NewBox^类型的句柄调用Volume()函数,则调用的将是新版本函数。

NewBox^ newBox = gcnew NewBox(2.0,3.0,4.0);
Console::WriteLine(newBox->Volume()); // 输出为12

新的Volume()函数不是多态函数,因此使用指向基类类型的句柄进行多态调用时,不会调用新的版本

Box^ newBox = gcnew NewBox(2.0,3.0,4.0);
Console::WriteLine(newBox->Volume());// 输出为24

9.9.7 委托和事件

  • 事件是类的成员,这种成员使对象能够在特定事件发生时发出信号,而为某个事件发出信号的过程涉及委托,委托提供了以某种方式响应事件的机制。
  • 委托是能够封装一个或多个指针的对象,这些指针指向具有特定形参列表和返回类型的函数。

1. 声明委托
委托的声明 (使用关键字delegate),定义了2件事:

  • 委托对象的引用类型名;
  • 可能与该委托有关的函数的形参列表和返回类型。
    委托的引用类型以System::Delegate 作为基类,因此委托类型总是要继承该基类的成员。
public delegate void Handler(int value);
// 将委托的引用类型定义为Handler,这里的Handler类型是从System::Delegate派生的。
// 类型为Handler 的对象可以包含拥有一个int 类型形参、返回类型为void 的一个或多个函数的指针。

委托指向的函数可以是实例函数,也可以是静态函数。

2.创建委托
委托的构造函数有2种选择:

  • 一种是接受单个实参;
  • 另一种是接受两个实参;

(1) 接受单个实参的委托构造函数,必须是具有委托声明中指定的返回类型和形参列表的静态类函数成员或全局函数。

public ref class HandlerClass
{
public:
	static void Fun1(int m)
	{Console::WriteLine(L"Function1 called with value {0}",m);}

	static void Fun2(int m)
	{Console::WriteLine(L"Function2 called with value {0}",m);}

	void Fun3(int m)
	{Console::WriteLine(L"Function3 called with value {0}",m+value);}

	void Fun4(int m)
	{Console::WriteLine(L"Function4 called with value {0}",m+value);}

	HandlerClass():value(1){}

	HandlerClass(int m):value(m){}
protected:
	int value;
};

//创建委托
	Handler^ handler = gcnew Handler(HandlerClass::Fun1);
// 对象handler 包含HandlerClass 类中静态函数Fun1 的地址。
// 如果调用这个委托,则调用HandlerClass::Fun1()函数

// 委托调用 1 :
	handler->Invoke(90);
// 将调用所有在委托handler的调用列表中的函数。
// 本例中只有一个函数在调用列表中,即HandlerClass::Fun1()函数
// 输出:Function1 called with value 90

// 委托调用 2 :
	handler(90);

// 将两个委托的调用列表组合成一个新的委托对象,委托类型重载了+运算符:

	handler += gcnew Handler(HandlerClass::Fun2);

// handler变量现在将引用一个其调用列表包含Fun1 和Fun2 两个函数的委托对象。
// 然而这是个新的委托对象,不能修改委托的调用列表,因此+ 运算符的工作方式类似于处理string对象的方式
// 方式类似于处理string对象的方式,即 总是创建一个新对象。

// 再次调用该委托
	handler(80);
// 现在的输出:
// Function1 called with value 80
// Function2 called with value 80

// 使用 - 运算符,可以从委托的调用列表中有效的删除某一项:
   handler -= gcnew Handler(HandlerClass::Fun1);
// 该语句将新建一个调用列表中仅包含HandlerClass::Fun2()的委托对象,
// 其作用是从handler的调用列表中删除右边的调用列表(HandlerClass::Fun1)包含的函数,
// 并创建一个新的对象指向剩下的函数。
  • 委托的调用列表必须至少包含一个函数指针。如果使用减法运算符删除所有函数指针,则结果将是nullptr。

(2) 使用有2个形参的委托构造函数时,第一个实参是CLR堆上某个对象的引用,第二个实参是该对象所属的类型中某个实例函数的地址。
因此,该构造函数创建的委托将包含一个由第二个实参指定的实例函数的指针,以便供第一个实参指定的对象使用。

// 创建委托
   HandlerClass^ obj = gcnew HandlerClass;
   Handler^ handler2 = gcnew Handler(obj,&HandlerClass::Fun3);
// 第1条语句:创建一个HandlerClass 类型的对象;
// 第2条语句:为HandlerClass对象obj 创建一个Handler类型的指向Fun3()函数的委托。

// 委托调用:
   handler2(70);
// 输出:Function3 called with value 71

// 两个委托对象的类型相同,可以合并 handler与handler2 委托的调用列表:
   Handler^ handler = gcnew Handler(HandlerClass::Fun1);
   handler += gcnew Handler(HandlerClass::Fun2);

   HandlerClass^ obj = gcnew HandlerClass;
   Handler^ handler2 = gcnew Handler(obj,&HandlerClass::Fun3);
   handler += handler2;

   handler(50); //调用委托
// 输出:
// Function1 called with value 50
// Function2 called with value 50
// Function3 called with value 51
  • 可以在委托的单个调用列表中合并静态和非静态的函数。
#include "stdafx.h"

using namespace System;

public ref class HandlerClass
{
public:
	static void Fun1(int m)
	{Console::WriteLine(L"Function1 called with value {0}",m);}

	static void Fun2(int m)
	{Console::WriteLine(L"Function2 called with value {0}",m);}

	void Fun3(int m)
	{Console::WriteLine(L"Function3 called with value {0}",m+value);}

	void Fun4(int m)
	{Console::WriteLine(L"Function4 called with value {0}",m+value);}

	HandlerClass():value(1){}

	HandlerClass(int m):value(m){}
protected:
	int value;
};

public delegate void Handler(int value);


int main(array<System::String ^> ^args)
{
	Handler^ handler = gcnew Handler(HandlerClass::Fun1);
	Console::WriteLine(L"Delegate with one pointer to a static function:");
	handler->Invoke(90);

	handler += gcnew Handler(HandlerClass::Fun2);
	Console::WriteLine(L"\nDelegate with two pointer to a static function:");
	handler->Invoke(80);

	HandlerClass^ obj = gcnew HandlerClass;
	Handler^ handler2 = gcnew Handler(obj,&HandlerClass::Fun3);
	handler += handler2;
	Console::WriteLine(L"\nDelegate with three pointer to a static function:");
	handler(70);

	Console::WriteLine(L"\nShortening the invocation list...");
	handler -= gcnew Handler(HandlerClass::Fun1);
	Console::WriteLine(L"\nDelegate with pointers to one static and one instance function:");
	handler(60);

    Console::ReadLine();
    return 0;
}

在这里插入图片描述

委托可以指向有任意多个实参的函数,如:

delegate void MyHandler(double x ,String^ description);
// 声明的委托类型MyHandler 只能指向2个实参,返回类型为void的函数,
// 其中第一个实参是double类型
// 第二个实参是String^ 类型

3.无约束的委托

  • 委托的调用列表中都有一组固定的函数,称之为有约束的委托。
  • 无约束的委托: 无约束的委托指向特定对象类型的形参列表和返回类型与委托声明一致的实例函数。
	public delegate void UBHandler(ThisClass^,int value);
//  第一个参数指定this指针的类型,UBHandler类型的委托可以为该指针调用某个实例函数
//  该函数必须有int类型的单个形参,其返回类型必须是void。
//  UBHandler 类型的委托只能为ThisClass类型的对象调用函数,但该类型的对象可以是任意一个

// 创建无约束委托
   UBHandler^ ubh = gcnew UBHandler(&ThisClass::Sum);

// ThisClass定义
   public ref class ThisClass
   {
   public:
	   void Sum(int n)
	   {
		   Console::WriteLine(L"Sum result = {0}",value + n);
	   }

	   void Product(int n)
	   {
			Console::WriteLine(L"Product result = {0}",value * n);
	   }

	   ThisClass(double v):value(v){}
   private:
	   double value;
   };

// 调用某个无约束委托时,第一个实参是调用列表内函数的当前对象,
// 随后的实参是传递给被调用函数的实参。
   ThisClass ^obj = gcnew ThisClass(99.0);
   ubh(obj,5);
// 第1个实参:指向某个ThisClass对象的句柄,该对象是通过给类的构造函数传递值99.0
// 而在CLR堆上创建的。
// 第2个实参是5,因此结果 是以5作为实参,为obj引用的对象调用Sum()函数。
// Ex9_17.cpp: 主项目文件。

#include "stdafx.h"

using namespace System;

public ref class ThisClass
   {
   public:
	   void Sum(int n)
	   {
		   Console::WriteLine(L"Sum result = {0}",value + n);
	   }

	   void Product(int n)
	   {
			Console::WriteLine(L"Product result = {0}",value * n);
	   }

	   ThisClass(double v):value(v){}
   private:
	   double value;
   };

public delegate void UBHandler(ThisClass^,int value);

int main(array<System::String ^> ^args)
{
	array<ThisClass^>^ things = { gcnew ThisClass(5.0),gcnew ThisClass(10.0),
									gcnew ThisClass(15.0),gcnew ThisClass(20.0),
									gcnew ThisClass(25.0)
								};

	UBHandler^ ubh = gcnew UBHandler(&ThisClass::Sum);

	for each(ThisClass^ thing in things)
		ubh(thing,3);
	ubh += gcnew UBHandler(&ThisClass::Product);

	for each(ThisClass^ thing in things)
		ubh(thing,2);

    Console::ReadLine();
    return 0;
}

4. 创建事件
事件是使用event 关键字和委托类名定义的引用类成员:

public delegate void DoorHandler(String^ str);

public ref class Door
{
public:
	event DoorHandler^ Knock; 
	//定义Knock 的事件成员,该事件对应于DoorHandler 类型的委托
	
	void TriggerEvents() //触发2次Knock事件的公有函数
	{
		Knock("Fred");
		Knock("Jane");
	}
};
// Knock 是Door类的实例成员,可以使用static关键字将某个时间指定为静态类成员
// 还可以将某个事件声明为virtual。当触发某个Knock事件时,它可以调用具有DoorHandler
// 委托指定的形参列表和返回类型的函数。

// 定义能够处理Knock事件的类:
public ref class AnswerDoor
{
public:
	void ImIn(String^ name)
	{
		Console::WriteLine(L"Come in {0},it's open.",name);
	}

	void ImOut(String^ name)
	{
		Console::WriteLine(L"Go away {0},I'm out.",name);
	}
};

// 在能够注册接收Knock事件通知的函数之前,需要创建一个Door对象。
	Door^ door = gcnew Door;

// 注册用来接收Door对象中Knock 事件通知的函数:
	AnswerDoor^ answer = gcnew AnswerDoor;
	door->Knock += gcnew DoorHandler(answer,&AnswerDoor::ImIn);

处理事件:

// Ex9_18.cpp: 主项目文件。

#include "stdafx.h"

using namespace System;

public delegate void DoorHandler(String^ str); //声明委托

public ref class Door
{
public:
	event DoorHandler^ Knock; 
	//定义Knock 的事件成员,该事件对应于DoorHandler 类型的委托
	
	void TriggerEvents() //触发2次Knock事件的公有函数
	{
		Knock("Fred");
		Knock("Jane");
	}
};

public ref class AnswerDoor
{
public:
	void ImIn(String^ name)
	{
		Console::WriteLine(L"Come in {0},it's open.",name);
	}

	void ImOut(String^ name)
	{
		Console::WriteLine(L"Go away {0},I'm out.",name);
	}
};

int main(array<System::String ^> ^args)
{
	Door^ door = gcnew Door;
	AnswerDoor^ answer = gcnew AnswerDoor;

	door->Knock += gcnew DoorHandler(answer,&AnswerDoor::ImIn);

	door->TriggerEvents(); // 引起2次Knock 事件

	door->Knock -= gcnew DoorHandler(answer,&AnswerDoor::ImIn);
	door->Knock += gcnew DoorHandler(answer,&AnswerDoor::ImOut);
	door->TriggerEvents();

    Console::ReadLine();
    return 0;
}

在这里插入图片描述

9.9.8 引用类的析构函数和终结器

  • 当某个引用类的句柄离开其作用域时,或者当该类的对象是另一个正在被销毁的对象的组成部分时,将调用类的析构函数。
  • 也可以对引用类对象的句柄应用delete操作符,这样也会调用析构函数。
  • 当类对象要使用不被垃圾回收器管理的其他资源(例如,当销毁对象时需要有条理的关闭的文件)时,才可能需要定义析构函数。
  • 也可以使用另一种名为终结器的类成员来清除这样的资源。终结器是一种特殊的引用类函数成员,是在销毁对象的时候由垃圾回收器自动调用的。终结器的作用是处理引用类对象使用的不被垃圾回收器管理的资源。
  • 注意,如果显式调用析构函数,或者因为对某个对象应用delete操作符而被调用,那么垃圾回收器将不会为该对象调用终结器。
  • 派生类中调用终结器的顺序:
    首先调用最后派生的那个类的终结器,然后调用类层次结构中上一层父类的终结器,最后调用最基本的类的终结器。
// 定义类终结器:
public ref class MyClass
{
	!MyClass()
	{
		// Code to clean-up when an object is destroyed...
	}
	// Rest of the class defination...
};
// Ex9_19.cpp: 主项目文件。

#include "stdafx.h"

using namespace System;

ref class MyClass
{
public:
	MyClass(int n): value(n){}

	~MyClass()
	{
		Console::WriteLine(L"MyClass Object({0}) destructor called.",value);
	}

	!MyClass()
	{
		Console::WriteLine(L"MyClass Object({0}) finalizer called.",value);
	}
private:
	int value;
};

int main(array<System::String ^> ^args)
{
	MyClass^ obj1 = gcnew MyClass(1);
	MyClass^ obj2 = gcnew MyClass(2);
	MyClass^ obj3 = gcnew MyClass(3);
	delete obj1;
	obj2->~MyClass();

    Console::WriteLine(L"End Program");
	
    return 0;
}
  • 如果无论对象以怎样的方式终止,都希望确保对象使用的不受管理的资源能够得到清理,则应该在类中既实现析构函数,又实现终结器。
  • 在类中实现终结器会导致很大的系统开销,因此,应该只有在必要时才定义类终结器。

9.9.9 泛型类

generic<typename T> ref class Stack
{
private:
	ref struct Item
	{
		T Obj;
		Item^ Next;

		Item(T obj,Item^ next):Obj(obj),Next(next){}
	};
	Item^ Top;

public:
	void Push (T obj)
	{
		Top = gcnew Item(obj,Top);
	}

	T Pop()
	{
		if(!Top)
			return T(); // 等价于 为值类型返回0,为句柄类型返回nullptr
		T obj = Top->Obj;
		Top = Top->Next;
		return obj;
	}
};

1. 泛型接口类
泛型接口定义:

generic<typename T> public interface class IStack
{
	void Push (T obj);
	T Pop();
};
// 该接口由2个标识栈的入栈和出栈操作的函数。

实现IStack<>通用接口的泛型Stack<>类的定义如下:

generic<typename T> ref class Stack: IStack<T> //类型形参T 用作接口IStack 的类型实参
{
private:
	ref struct Item
	{
		T Obj;
		Item^ Next;

		Item(T obj,Item^ next):Obj(obj),Next(next){}
	};

	Item^ Top;
public:
	virtual void Push(T obj)
	{
		Top = gcnew Item(obj,Top);
	}
	
	virtual T Pop()
	{
		if(!Top)
			return T(); // 等价于 为值类型返回0,为句柄类型返回nullptr
		T obj = Top->Obj;
		Top = Top->Next;
		return obj;
	}
}

2. 泛型集合类
集合类:以特定方式组织和存储对象的类,链表和栈都是典型的集合类示例。
System::Collections::Generic 名称空间包含许多实现了强类型集合的泛型集合类。
在这里插入图片描述

通用列表List< T >

  • List< T >定义了一个通用的列表,该列表的大小可以在需要时自动增长。可以使用Add()函数给列表添加项,还可以使用索引访问List< T >
    中存储的项。
  • 定义存储int 类型值的列表的方法:
List<int>^ numbers = gcnew List<int>;
  • 列表有默认的容量,但也可以指定所需的容量。
List<int>^ numbers = gcnew List<int>(500);

使用Add()函数向列表中添加对象:

for(int i = 0; i < 1000; i++)
	numbers->Add( 2*i + 1 );
// 该循环向列表中添加1000个整数。

当需要向现有列表中插入数据时,可以使用Insert()函数。该函数将第二个实参指定的项,插入到第一个实参指定的索引位置。

求出列表内容的总和:

int sum = 0;
for (int i = 0;i < numbers->Count; i++)
	sum += numbers[i];

Count 是一个返回列表项当前数量的属性。

另一种求列表项和的方法:

for each(int n in numbers)
	sum += n;

通用双链表LinkedList< T >

  • LinkedList< T > 函数定义了一个包含前向和后向指针的链表。
  • 定义存储浮点数的链表:
LinkedList<double>^ values = gcnew LinkedList<double>;
// 向链表中添加数值:
for(int i =0; i < 1000; i++)
	values->AddLast(2.5*i);
// AddLast()函数在链表的最后添加某项。

AddFirst()函数,可以在链表的开始处添加新项。
Find()函数将返回一个LinkedListNode< T >^ 类型的句柄,该句柄指向的链表节点包含给Find()传递的实参数值。该句柄可用来在找到的节点之前或之后插入新值。

LinkedListNode<double>^ node = values->Find(20.0); //寻找包含值20.0的节点。
// 如果不存在这样的节点,Find()函数就返回nullptr
// 
if(node)
	values->AddBefore(node,19.9); // 在node之前存入新值19.9

// 求出链表中所有向的总和:
double sumd = 0;
for each(double v in values)
	sumd += v;

存储键/值对的通用词典 Dictionary<TKey, TValue>
通用的Dictionary< >集合类要求提供2个类型实参:
第1个:键的类型
第2个:与键相关的值类型

当需要存储的是对象对,而其中一个对象又是访问另一个对象的键时,词典就特别有用。

假设已定义了Name和PhoneNumber类来分别封装姓名和电话号码,定义存储姓名/号码对的词典:

Dictionary< Name^,PhoneNumber^> ^ phonebook = gcnew Dictionary <Name^,PhoneNumber^>;

键是 姓名的句柄,值是电话号码的句柄。

在phonebook 中添加新的记录项:

Name^ name = gcnew Name("Jim","Jane");
PhoneNumber^ number = gcnew PhoneNumber(914,316,2233);
phonebook-> Add(name,number);

想检索词典中的记录项,则可以使用默认的索引属性。

try
{
	PhoneNumber^ theNumber = phonebook[name];
}
catch(KeyNotFoundException^ knfe)
{
	Console::WriteLine(knfe);
}

本例中键是某个Name对象的句柄。如果存在该键,则返回对应的值。如果在集合中没有找到该键,则抛出一个KeyNotFoundException类型的异常。

Dictionary< > 对象的

  • Keys 属性返回包含词典中所有键的集合
  • Values 属性返回包含词典中所有值的集合
  • Count属性返回词典中键/值 对的数量
// Ex9_21.cpp: 使用集合类

#include "stdafx.h"

using namespace System;

using namespace System::Collections::Generic;

ref class Name
{
public:
	Name(String^ name1,String^ name2):First(name1),Second(name2){}
	virtual String^ ToString() override{ return First + L" " + Second;}
private:
	String^ First;
	String^ Second;
};

ref class PhoneNumber
{
public:
	PhoneNumber(int area,int local,int number):
	  Area(area),Local(local),Number(number){}

	virtual String^ ToString() override
	{ return Area + L" " + Local + L" " + Number;}
private:
	int Area;
	int Local;
	int Number;
};
int main(array<System::String ^> ^args)
{
	Console::WriteLine(L"Creating a List<T> of integers:");
	List<int>^ numbers = gcnew List<int>;
	for(int i = 0; i < 1000; i++)
		numbers->Add(2*i+1);

	int sum = 0;
	for (int i = 0; i < numbers->Count ; i++)
		sum += numbers[i];
	Console::WriteLine(L"Total = {0}",sum);

	Console::WriteLine(L"\nCreating a LinkedList<T> of double values:");
	LinkedList<double>^ values = gcnew LinkedList<double>;
	for(int i = 0; i < 1000; i++)
		values->AddLast(2.5*i);

	double sumd = 0;
	for each(double v in values)
		sumd += v;
	Console::WriteLine(L"Total = {0}",sumd);

	LinkedListNode<double>^ node = values->Find(20.0);
	values->AddBefore(node,19.9); 
	values->AddAfter(values->Find(30.0),30.1);

	sumd = 0;
	for each(double v in values)
		sumd += v;
	Console::WriteLine(L"Total after adding values = {0}",sumd);

	Console::WriteLine(L"\nCreating a Dictionary<K,V> of name/number pairs: ");
	Dictionary< Name^,PhoneNumber^> ^ phonebook = gcnew Dictionary <Name^,PhoneNumber^>;

	Name^ name = gcnew Name("Jim","Jane");
	PhoneNumber^ number = gcnew PhoneNumber(914,316,2233);
	phonebook-> Add(name,number);
	phonebook-> Add(gcnew Name("Fred","Fong"),gcnew PhoneNumber(123,234,3456));
	phonebook-> Add(gcnew Name("Janet","Smith"),gcnew PhoneNumber(515,224,6864));

	Console::WriteLine(L"List all the numbers:");
	for each(PhoneNumber^ number in phonebook->Values)
		Console::WriteLine(number);

	Console::WriteLine(L"Access the keys to list all name/number pairs:");
	for each(Name^ name in phonebook->Keys)
		Console::WriteLine(L"{0}:{1}",name,phonebook[name]);


    Console::ReadLine();
    return 0;
}

在这里插入图片描述

9.10 本章小结
在这里插入图片描述

To be continue…

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

madao1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值