Visual C++ 2010 第7章 自定义数据类型

7.1 C++中的结构

  • 结构能够实现 使用一种数据类型来包含所有需要的信息。
  • 结构是使用关键字struct 定义的用户定义类型。
    7.1.2 定义结构
struct BOOK // 创建一种新的变量类型,名称是BOOK
{
	char Title[80];
	char Author[80];
	char Publisher[80];
	int Year;  // { }内的元素称作BOOK结构的成员或字段,BOOK类型的每个对象都包含{ }内的成员
};
// struct 关键字 将BOOK 定义成类型名称;
// 构成本类型对象的元素是在大括号内定义的;
// 注意,结构内的元素可以是除所定义的结构类型以外的任何类型,即不能包含类型为BOOK的元素。 但可以包括BOOK类型变量的指针。

// 创建BOOK类型的变量:
BOOK Novel; //声明了一个名为Novel的BOOK类型的变量

7.1.3 初始化结构

  • 在声明语句中定义初始值:
BOOK Novel:
{
	"Paneless Programming",
	"I.C. Fingers",
	"Gutter Press",
	1981
};

7.1.4 访问结构的成员
使用成员选择操作符“ .

Novel.Year = 1988 ; // 将Year成员的值设置为1988
// 可以完全像使用与成员类型相同的其他变量那样使用该结构成员
Novel.Year += 2 ;
// 可能在庭院里发现的对象
#include <iostream>
using std::cout;
using std::endl;

struct RECTANGLE
{
	int Left; 
	int Top;
	int Right;
	int Bottom;
};

long Area(const RECTANGLE& aRect);

void MoveRect(RECTANGLE& aRect, int x, int y);

int main()
{
	RECTANGLE Yard = {0,0,100,120};
	RECTANGLE Pool = {30,40,70,80};
	RECTANGLE Hut1,Hut2;

	Hut1.Left = 70;
	Hut1.Top = 10;
	Hut1.Right = Hut1.Left + 25;
	Hut1.Bottom = 30;

	Hut2 = Hut1;
	MoveRect(Hut2,10,90);

	cout << endl
		 << "Coordinates of Hut2 are "
		 << Hut2.Left << "," << Hut2.Top << " and "
		 << Hut2.Right << "," << Hut2.Bottom;

	cout << endl
		 << "The area of the yard is "
		 << Area(Yard);

	cout << endl
		 << "The area of the pool is "
		 << Area(Pool)
		 << endl;
	
	return 0;
}

long Area(const RECTANGLE& aRect)
{
	return (aRect.Right - aRect.Left)*(aRect.Bottom - aRect.Top);
}

void MoveRect(RECTANGLE& aRect, int x, int y)
{
	int length(aRect.Right - aRect.Left);
	int width(aRect.Bottom - aRect.Top);

	aRect.Left = x;
	aRect.Top = y;
	aRect.Right = x + length;
	aRect.Bottom = y + width;
	return;
}

在这里插入图片描述

7.1.6 RECT结构
windows.h头文件中有一个预定义的RECT结构:

struct RECT
{
	LONG left;
	LONG top;
	LONG right;
	LONG bottom;
}
// 类型LONG 是等价于基本类型long的Windows类型
// 该结构通常用来定义显示器上用于各种目的的矩形区域

InflateRect()函数:使矩形尺寸增加
EqualRect()函数:比较2个矩形

7.1.7 使用指针处理结构
定义指向RECT对象的指针:

RECT* pRect(nullptr);
//使用取址运算符将aRect变量的地址赋予pRect指针:
pRect = &aRect;

struct 包含指向struct 的指针

struct ListElement
{
	RECT aRect;
	ListElement* pNext; // 指向ListElement类型结构的指针
};

该定义使 ListElement 类型的对象可以菊连接到一起,其中每个ListElement对象可以包含下一个ListElement对象的地址,最后一个对象包含的指针为nullptr。
在这里插入图片描述
图中每个方框代表1个ListElement类型的对象,除最后一个对象的pNext是nullptr外,每个对象的pNext成员都存储着链内下一个对象的地址,这种安排称为链表

  • 优点:只要知道表中第一个元素,就能找到所有元素。

1.通过指针访问结构成员

RECT aRect = {0,0,100,100};
RECT* pRect (&aRect);
(*pRect).top += 10;

2.间接成员选择符
间接成员选择符 ->

pRect->Top += 10;

7.2 数据类型、对象、类 和实例

  • C++ 语言引入新关键字 class 来描述类这一概念。 其与struct基本相同。
class CBox  //定义CBOX类,定义新的数据类型 CBOX
{
	 public:
	 	double m_Length;  // 类的数据成员
	 	double m_Width;
	 	double m_Height;
}

CBox bigBox; // 声明一个表示CBox类实例的变量bigBox
  • 类名都以C作为前缀;
  • 数据成员添加m_ 前缀;
  • public:以该关键字定义的类成员通常是可以访问的,类成员默认是私有的。

7.2.2 类的操作
在C++中,可以创建新的数据类型——类,来表示任何希望表示的对象。类,不仅仅限于容纳数据,还可以定义成员函数,甚至可定义在类对象之间使用标准C++ 运算符执行的操作。

CBox box1;
CBox box2;
if(box1 > box2);
	box1.fill();
else 
	box2.fill();

7.2.3 术语

  • 类:是用户定义的数据类型。
  • 面向对象编程(OOP): 是一种编程风格,它基于将自己的数据类型定义成类的思想,这些数据类型专用于打算解决的问题的问题域。
  • 声明类的对象:称作实例化,因为这是在创建类的实例。
  • 类的实例:称为对象。
  • 对象 在定义中隐式的包含数据和操作数据的函数,这种思想称为 封装。
  • 对象的实际用途: 根据问题域所特有的对象编写程序,围绕C++类的所有功能都是为了使之尽可能的全面和灵活。

7.3 理解类

  • 类组合了构成对象的元素数据的定义和处理本类对象中数据的方法。
  • 类中的数据和函数称为类的成员。数据的类成员称为数据成员;函数的类成员称为函数成员或成员函数。
  • 类的成员函数 有时也称作方法。
  • 数据成员还称作字段。

7.3.1 定义类

class CBox  //定义CBOX类,定义新的数据类型 CBOX
{
	 public:
	 	double m_Length;  // 类的数据成员
	 	double m_Width;
	 	double m_Height;
}

public 关键字决定着后面那些类成员的访问属性。将数据成员指定为public ,意味着在包含这些成员的类对象的作用域内任何位置都可以访问它们。还可以将类成员指定为private 或 protected ,如果省略访问说明,默认是private。

7.3.2 声明类的对象

CBox box1;
CBox box2;

在这里插入图片描述

7.3.3 访问类的数据成员

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

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

int main()
{
	CBox box1;
	CBox box2;

	double boxVolume(0.0);

	box1.m_Height = 18.0;
	box1.m_Length = 78.0;
	box1.m_Width = 24.0;

	box2.m_Height = box1.m_Height - 10;
	box2.m_Length = box1.m_Length/2.0;
	box2.m_Width = 0.25*box1.m_Length;

	boxVolume = box1.m_Height*box1.m_Length*box1.m_Width;

	cout << endl
		 << "Volume of box1 = " << boxVolume;

	cout << endl
		 << "box2 has sides which total "
		 << box2.m_Height + box2.m_Length + box2.m_Width 
		 << "inches.";

	cout << endl
		 << "A CBox object occupies "
		 << sizeof box1 << "bytes.";

	cout << endl;
	return 0;

}

7.3.4 类的成员函数

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

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

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

int main()
{
	CBox box1;
	CBox box2;

	double boxVolume(0.0);

	box1.m_Height = 18.0;
	box1.m_Length = 78.0;
	box1.m_Width = 24.0;

	box2.m_Height = box1.m_Height - 10;
	box2.m_Length = box1.m_Length/2.0;
	box2.m_Width = 0.25*box1.m_Length;

	boxVolume = box1.Volume();

	cout << endl
		 << "Volume of box1 = " << boxVolume;

	cout << endl
		 << "Volume of box2 = "
		 << box2.Volume();
		

	cout << endl
		 << "A CBox object occupies "
		 << sizeof box1 << "bytes.";

	cout << endl;
	return 0;

}

在这里插入图片描述

7.3.5 成员函数定义的位置
成员函数可以放在类定义的外部,但需要将函数原型放在类内部。

class CBox
{
public:
	double m_Length;
	double m_Width;
	double m_Height;
	double Volume(void);
};
//函数名加上类名作为前缀,并用作用域解析运算符::将二者分开
// 告诉编译器该函数属于CBox类
double CBox::Volume ()
{
	return m_Length*m_Width*m_Height;
}

7.3.6 内联函数
在内联函数中,编译器设法以函数体代码代替函数调用。这样可以避免调用函数时的大量系统开销。
在这里插入图片描述

如果函数定义位于类定义外部时, 在函数头前面加上关键字inline,即告诉编译器将函数视为内联函数。

inline double CBox::Volume()
{
	return m_Length*m_Width*m_Height;
}
  • 注意,内联函数 最适合用于短小的简单程序。

7.4 类构造函数
7.4.1 类构造函数的概念

  • 类构造函数是类的特殊函数,在创建新的类对象时调用它。
  • 类构造函数提供了创建对象时进行初始化的机会,并确保数据成员只包含有效值。
  • 类可以有多个构造函数,以允许我们以不同方式创建对象。
  • 类个构造函数总是与所属类的名称相同。
  • 构造函数没有任何返回类型,给构造函数指定返回类型是错误的,即使写成void 也不行。
#include <iostream>
using std::cout;
using std::endl;

class CBox
{
public:
	double m_Length;
	double m_Width;
	double m_Height;
	// CBox类的构造函数
	CBox(double lv,double bv,double hv)
	{
		cout << endl << "Constructor called."; //此句输出可以辨别调用构造函数的时间,常用于测试程序
		m_Length = lv;
		m_Width = bv;
		m_Height = hv;
	}
	// CBox类的成员函数
	double Volume()
	{
		return m_Length*m_Width*m_Height;
	}
};

int main()
{ 
	CBox box1(78.0,24.0,18.0);   //声明并初始化CBox类的实例
	CBox cigarBox(8.0,5.0,1.0);

	double boxVolume(0.0);

	boxVolume = box1.Volume();

	cout << endl
		 << "Volume of box1 = " << boxVolume;

	cout << endl
		 << "Volume of cigarBox = "
		 << cigarBox.Volume();

	cout << endl;
	return 0;

}

在这里插入图片描述

7.4.2 默认的构造函数

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

class CBox
{
public:
	double m_Length;
	double m_Width;
	double m_Height;
	// CBox类的构造函数
	CBox(double lv,double bv,double hv)
	{
		cout << endl << "Constructor called."; //此句输出可以辨别调用构造函数的时间,常用于测试程序
		m_Length = lv;
		m_Width = bv;
		m_Height = hv;
	} 
	// 默认构造函数 :无实参构造函数,被调用时不要求提供实参
	CBox()
	{
		cout << endl << "Default constructor called.";
	}
	// CBox类的成员函数
	double Volume()
	{
		return m_Length*m_Width*m_Height;
	}
};

int main()
{ 
	CBox box1(78.0,24.0,18.0);   //声明并初始化CBox类的实例
	CBox box2;					 //声明box2 未初始化
	CBox cigarBox(8.0,5.0,1.0);

	double boxVolume(0.0);

	boxVolume = box1.Volume();

	cout << endl
		 << "Volume of box1 = " << boxVolume;

	box2.m_Height = box1.m_Height - 10;
	box2.m_Length = box1.m_Length/2.0;
	box2.m_Width = 0.25*box1.m_Length;

	cout << endl
		 << "Volume of box2 = "
		 << box2.Volume();

	cout << endl
		 << "Volume of cigarBox = "
		 << cigarBox.Volume();

	cout << endl;
	return 0;
}

在这里插入图片描述

7.4.3 在类定义中指定默认的形参值

  • 将成员函数的定义放在类定义内部,将形参的默认值放在函数头中。
  • 类定义中仅包括函数原型,则默认形参值应该放在原型中。

class CBox
{
public:
	double m_Length;
	double m_Width;
	double m_Height;
	// CBox类的构造函数
	CBox(double lv = 1.0,double bv = 1.0 ,double hv = 1.0)  // 构造函数头中设置默认值
	{
		cout << endl << "Constructor called."; 
		m_Length = lv;
		m_Width = bv;
		m_Height = hv;
	} 
	CBox()
	{
		cout << endl << "Default constructor called.";
	}
	double Volume()
	{
		return m_Length*m_Width*m_Height;
	}
};

7.4.4 在构造函数中使用初始化列表

CBox(double lv = 1.0,double bv = 1.0,double hv = 1.0):
			m_Length(lv),m_Width (bv),m_Height (hv)
{
	cout << endl << "Constructor called."; 
}

7.4.5 声明显式的构造函数
构造函数声明为explicit ,则只能显式的调用它,且不用于隐式转换。

explicit CBox (double side):m_Length(side),m_Width (side),m_Height (side)
{ }

7.5 类的私有成员

  • private 类成员只能被类的成员函数访问。
  • 指定private类成员 能够将类的接口与类的内部实现分开
  • 类的接口由public成员,特别是public成员函数组成,因为它们可以提供对包括private成员在内的所有类成员的间接访问。
    在这里插入图片描述
#include <iostream>
using std::cout;
using std::endl;

class CBox
{
public:
	//构造函数声明为explicit ,避免不期望的隐式转换
	explicit CBox(double lv = 1.0,double bv = 1.0,double hv = 1.0):
		m_Length(lv),m_Width(bv),m_height(hv)
		{
			cout << endl << "Constructor called.";
		}
	double Volume()
	{
		return m_Length*m_Width*m_height;
	}
private: //3个数据成员只能被本类的成员函数访问
	double m_Length;
	double m_Width;
	double m_height;
};
// 现在使用构造函数或成员函数是给对象的私有数据成员赋值的唯一方法
int main()
{
	CBox match(2.2,1.1,0.5);
	CBox box2;

	cout << endl
		 << "Volume of match = "
		 << match.Volume();

	cout << endl
		 << "Volume of box2 = "
		 << box2.Volume();
	cout << endl;

	return 0;
}

在这里插入图片描述

7.5.1 访问私有类成员
返回数据成员值的成员函数:

inline double CBox::GetLength()
{
	return m_Length;
}
// 类的public 部分声明了该函数后,通过如下语句来使用:
double len = box2.GetLength();

需要为每个希望在外部使用的数据成员编写一个类似的函数,这样既可以访问这些成员的值又不会危及类的安全性。

7.5.2 类的友元函数

  • 某些虽然不是类成员的函数能够访问类的所有成员——它们拥有特殊权限,这样的函数称为类的友元函数,使用friend来定义。
  • 可以在类定义中添加友元函数的原型,也可以条件整个函数定义。在类定义内定义的友元函数默认也是内联函数。
  • 友元函数不是类的成员,因此访问特性不适用于它们,这些函数只是拥有特殊权限的普通全局函数。
#include <iostream>
using std::cout;
using std::endl;

class CBox
{
public:
	//构造函数声明为explicit ,避免不期望的隐式转换
	explicit CBox(double lv = 1.0,double bv = 1.0,double hv = 1.0)
	{
		cout << endl << "Constructor called.";
		m_Length = lv;
		m_Width = bv;
		m_height = hv;
	}
	double Volume()
	{
		return m_Length*m_Width*m_height;
	}
private: //3个数据成员只能被本类的成员函数访问
	double m_Length;
	double m_Width;
	double m_height;
friend double BoxSurface(CBox aBox); //声明CBox类的友元

// 友元函数放在类定义内部,影响程序可读性,不建议使用
/*friend double BoxSurface(CBox aBox)
{
	return 2.0*(aBox.m_Length * aBox.m_Width + 
				aBox.m_Length * aBox.m_height +
				aBox.m_height * aBox.m_Width);
}*/
};

double BoxSurface(CBox aBox)
{
	return 2.0*(aBox.m_Length * aBox.m_Width + 
				aBox.m_Length * aBox.m_height +
				aBox.m_height * aBox.m_Width);
}

int main()
{
	CBox match(2.2,1.1,0.5);
	CBox box2;

	cout << endl
		 << "Volume of match = "
		 << match.Volume();

	cout << endl
		 << "Surface area of match = "
		 << BoxSurface(match);

	cout << endl
		 << "Volume of box2 = "
		 << box2.Volume();

	cout << endl
		 << "Surface area of box2 = "
		 << BoxSurface(box2);
	cout << endl;

	return 0;
}

在这里插入图片描述

7.5.3 默认复制构造函数

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

class CBox
{
public:
	//构造函数声明为explicit ,避免不期望的隐式转换
	explicit CBox(double lv = 1.0,double bv = 1.0,double hv = 1.0)
	{
		cout << endl << "Constructor called.";
		m_Length = lv;
		m_Width = bv;
		m_height = hv;
	}
	double Volume()
	{
		return m_Length*m_Width*m_height;
	}
private: //3个数据成员只能被本类的成员函数访问
	double m_Length;
	double m_Width;
	double m_height;
};

int main()
{
	CBox box1(78.0,24.0,18.0);
	CBox box2 = box1; 
	//通过用同类的现有对象进行初始化来创建类对象,复制构造函数

	cout << endl
		 << "box1 volume = " << box1.Volume()
		 << endl
		 << "box2 volume = " << box2.Volume();
	cout << endl;

	return 0;
}

7.6 this 指针

  • 任何成员函数执行时,都自动包含一个名为this的隐藏指针,它指向调用该函数时使用的对象。
    Volume()函数在执行期间访问m_Length成员时,实际上是在引用this->m_Length (被使用的对象成员的全称)。编译器负责在函数中给成员名添加必要的指针名this。

显示的使用this:

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

class CBox
{
public:
	//构造函数声明为explicit ,避免不期望的隐式转换
	explicit CBox(double lv = 1.0,double bv = 1.0,double hv = 1.0)
	{
		cout << endl << "Constructor called.";
		m_Length = lv;
		m_Width = bv;
		m_height = hv;
	}
	double Volume()
	{
		return m_Length*m_Width*m_height;
	}

	bool Compare(CBox &xBox)
	{
		return this->Volume() > xBox.Volume();
		// return Volume() > xBox.Volume(); 换成此句 仍能正常工作
		// 任何对不加限定的成员名的引用,编译器将自动认为是引用this指向的那个对象成员
	}
private: //3个数据成员只能被本类的成员函数访问
	double m_Length;
	double m_Width;
	double m_height;
};

int main()
{
	CBox match(2.2,1.1,0.5);
	CBox cigar(8.0,5.0,1.0);

	if(cigar.Compare(match))
		cout << endl
			 << "match is smaller than cigar";
	else
		cout << endl
			 << "match is equal to or larger than cigar"; 
	
	cout << endl;
	return 0;
}

通过对象的指针访问成员时使用间接成员访问操作符 ->.

7.7 类的const对象
将某个CBox 对象定义成const :
const CBox standard(3.0,5.0,8.0);
将某个类对象声明为const,则编译器将不允许对象调用任何可能修改它的成员函数。

7.7.1 类的const 成员函数
为了使成员函数中的this 指针称为const,必须在类定义内将该函数声明为const。

class CBox
{
public:
	explicit CBox(double lv = 1.0,double bv = 1.0,double hv = 1.0)
	{
		cout << endl << "Constructor called.";
		m_Length = lv;
		m_Width = bv;
		m_height = hv;
	}
	double Volume() const 
	{
		return m_Length*m_Width*m_height;
	}

	bool Compare(const CBox &xBox) const
	{
		return this->Volume() > xBox.Volume();
	}
private: 
	double m_Length;
	double m_Width;
	double m_height;
};
  • 要指定const 成员函数,只需在函数头后面附加const关键字即可。注意,只能对类成员函数这么做。

  • 作用:使该函数中的this指针称为const,意味着不能在该函数的定义内在赋值语句左边写上类的数据成员。

  • const成员函数不能调用同类的非const成员函数。
    Compare() 调用 Volume() ,所以Volume()成员也必须声明为const。由于Volume()声明为const,因此可以使Compare()函数的形参为const。

  • 当将某个对象声明为const 之后,该对象可以调用的成员函数也都必须声明为const。

7.7.2 类外部成员函数定义
当const成员函数的定义出现在类外部时,函数头必须添加const。

class CBox
{
public:
	explicit CBox(double lv = 1.0,double bv = 1.0,double hv = 1.0);
	double Volume() const;
	bool Compare(const CBox &xBox) const;
private:
	double m_Length;
	double m_Width;
	double m_height;
};
double CBox::Volume() const
	{
		return m_Length*m_Width*m_height;
	}
bool CBox::Compare(CBox &xBox) const
	{
		return this->Volume() > xBox.Volume();
	}
CBox::CBox(double lv,double bv,double hv):
	m_Length(lv),m_Width(bv),m_height(hv)
{
	cout << endl << "Constructor called.";
}

7.8 类对象的数组
类对象数组的每个元素都将调用默认构造函数。

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

class CBox
{
public:
	CBox(double lv = 1.0,double bv = 1.0,double hv = 1.0)
	{
		cout << endl << "Constructor called.";
		m_Length = lv;
		m_Width = bv;
		m_height = hv;
	}
	CBox()
	{
		cout << endl
			 << "Default constructor called.";
		m_Length = m_Width = m_height = 1.0;
	}
	double Volume() const
	{
		return m_Length*m_Width*m_height;
	}
private: //3个数据成员只能被本类的成员函数访问
	double m_Length;
	double m_Width;
	double m_height;
};

int main()
{
	CBox boxes[5];
	CBox cigar(8.0,5.0,1.0);

	cout << endl 
		 << "Volume of boxes[3] = " << boxes[3].Volume()
		 << endl
		 << "Volume of cigar = " << cigar.Volume();

	cout << endl;
	return 0;
}

7.9 类的静态成员

7.9.1 类的静态数据成员
将类的某个数据成员声明为static时,将只能定义一次该静态数据成员,而且要被同类的所有对象共享。每个静态数据成员只有一个实例存在。
在这里插入图片描述

  • 静态数据成员的用途之一:统计实际存在多少个对象。
  • 添加静态数据成员:
    static int objectCount;
  • 初始化静态数据成员:在类定义外部进行初始化:
    int CBox::objectCount = 0;
#include <iostream>
using std::cout;
using std::endl;

class CBox
{
public:
	static int objectCount; //定义静态数据成员

	CBox(double lv = 1.0,double bv = 1.0,double hv = 1.0)
	{
		cout << endl << "Constructor called.";
		m_Length = lv;
		m_Width = bv;
		m_height = hv;
		objectCount++;
	}
	CBox()
	{
		cout << endl 
			 << "Default constructor called.";
	    m_Length = m_Width = m_height = 1.0;
		objectCount++;
	}

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

private: //3个数据成员只能被本类的成员函数访问
	double m_Length;
	double m_Width;
	double m_height;
};

int CBox::objectCount(0); //初始化静态数据成员

int main()
{
	//CBox boxes[5];
	CBox cigar(8.0,5.0,1.0);

	cout << endl << endl 
		 << "Number of objects(through class) = "
		 << CBox::objectCount;
	
	cout << endl;
	return 0;
}

7.9.2 类的静态函数成员

  • 将某个函数成员声明为static ,将使该函数独立于本类的任何具体对象。
  • 静态成员函数优点:即使本类的任何对象都不存在,它们也能存在并被调用。这种情况下,静态成员函数只能使用静态数据成员,因为后者是唯一存在的数据成员。
  • 静态函数原型:static void Afunction(int n);
  • 通过对象调用静态函数:aBox.Afunction(10);
  • 不通过对象调用静态函数:CBox::Afunction(10);

7.10 类对象的指针和引用

7.10.1 类对象的指针

  • 声明指向CBox类对象的指针: CBox* pBox(nullptr);
  • 用该指针存储CBox对象的地址: pBox = &cigar;
  • 使用对象的指针调用函数:cout << pBox->Volume();

7.10.2 类对象的引用

  • 声明对象cigar的引用:CBox& rcigar(cigar);
  • 使用引用计算对象cigar的体积,只需在应该出现对象名的位置适应引用名即可:
    cout << rcigar.Volume();

实现复制构造函数

  • 复制构造函数的原型:
    CBox(const CBox& initB);
    函数的形参是引用,则调用该函数时不需要复制实参。函数直接访问调用函数中的实参变量。const 限定符用来确保该函数不能修改实参。

  • 实现复制构造函数:
    CBox::CBox(const CBox& initB)
    {
    m_Length = initB.m_Length ;
    m_Width = initB.m_Width ;
    m_Height = initB.m_Height ;
    }

7.11 C++/CLI 编程

  • 在C++/CLI 中结构和类的区别:
    结构成员默认都是公有的;
    类成员默认都是私有的;

  • 注意:
    1.C++/CLI类的成员函数不能声明为const。
    2.在值类类型T的非静态函数成员中,this指针是interior_ptr< T > 类型的内部指针,而在引用类类型T中,this指针是T^ 类型的句柄。

  • 值类和引用类的3项其他限制:

  • 值类或引用类不能包含是本地C++数组或本地C++类的字段。

  • 友元函数是不允许的。

  • 值类或引用类不能有位字段成员。

7.11.1 定义值类类型

  • value struct 和 value class类型的区别:
    value struct 类型的成员默认都是公有的;
    value class 类型的成员默认都是私有的;
value class Height // 定义名为Height的值类类型
{
private:
	int feet;
	int inches;

public:
	// 根据提供的实参——英寸数创建Height对象的构造函数
	Height(int ins) 
	{
		feet = ins/12;
		inches = ins%12;
	}
	// 根据英尺和英寸两项 创建Height对象的构造函数
	Height(int ft,int ins):feet(ft),inches(ins){}
};
// 创建 Height类型的变量:
Height tall = Height(7,8);

Height baseHeight; // 该语句创建的变量将自动初始化为高度0.
  • Height类没有指定的无参数构造函数,因为它是值类,不允许在类定义中提供那样的构造函数,值类中将自动包括一个无参数的构造函数,该函数将把所有值类型的字段初始化为0, 把所有句柄字段初始化为nullptr,不能重写这个隐含的构造函数。
  • 关于值类的2项限制:
    1.值类定义中不能包括赋值构造函数;
    2.不能在值类中重写赋值运算符;
	Height myHeight(Height(6,3)); //栈变量
	Height^ yourHeight(Height(70)); // Height^ 类型的句柄 ,堆变量
	Height hisHeight(*yourHeight); 
	// 栈变量,是yourHeight引用对象的副本,因此要先解除引用才能将其赋值给hisHeight

1. 类的ToString()函数

  • ToString()函数:
    • 函数返回一个用来表示类对象的字符串的句柄。
    • 编译器只要认为需要某个对象的字符串表示法,就安排调用ToString()函数。
double pi(3.142);
Console::WriteLine(pi.ToString());

该语句以字符串的形式输出pi的值,如果不显式的调用ToString()函数,也能得到同样的输出。

value class Height // 定义名为Height的值类类型
{
private:
	int feet;
	int inches;

public:
	// 根据提供的实参——英寸数创建Height对象的构造函数
	Height(int ins) 
	{
		feet = ins/12;
		inches = ins%12;
	}
	// 根据英尺和英寸两项 创建Height对象的构造函数
	Height(int ft,int ins):feet(ft),inches(ins){}

	virtual String^ ToString() override
	{
		return feet + L" feet"+inches + L" inches";
	}
};

virtual 关键字 结合函数形参列表后的override关键字,辨明该版本的ToString()函数重写了类中默认给出的函数版本。

2.字面值字段

  • C++/CLI 具有的 字面值字段功能可以给类中引入命名常量。
value class Height // 定义名为Height的值类类型
{
private:
	int feet;
	int inches;
	literal int inchesPerFoot = 12; //使用inchesPerFoot代替12
public:
	// 根据提供的实参——英寸数创建Height对象的构造函数
	Height(int ins) 
	{
		feet = ins/inchesPerFoot;
		inches = ins%inchesPerFoot;
	}
	// 根据英尺和英寸两项 创建Height对象的构造函数
	Height(int ft,int ins):feet(ft),inches(ins){}

	virtual String^ ToString() override
	{
		return feet + L" feet"+inches + L" inches";
	}
};
  • 注意,不能使用函数表示法来初始化字面值字段。

7.11.2 定义引用类类型

  • 引用类在功能上相当于本地C++类,而且没有值类收到的那些限制。但与本地C++类不同,引用类没有默认的复制构造函数或默认的赋值运算符。
  • 在C++/CLI类中不推荐使用:类名的C前缀 和 成员名的m_前缀。
  • 不能在C++/CLI 类中给函数和构造函数的形参指定默认值,因此必须给Box类添加无参数的构造函数来实现该功能。
#include "stdafx.h"

using namespace System;

ref class Box
{
public:
	Box():Length(1.0),Width(1.0),Height(1.0) //无参数构造函数
	{
		Console::WriteLine(L"No-arg constructor called.");
	}

	Box(double lv,double bv,double hv):
				Length(lv),Width(bv),Height(hv)
	{
		Console::WriteLine(L"Constructor called.");	
	}

	double Volume()
	{
		return Length*Width*Height;
	}
private:
	double Length;
	double Width;
	double Height;
};


int main(array<System::String ^> ^args)
{
	Box^ aBox; //创建跟踪句柄aBox 默认初始化为nullptr
	Box^ newBox = gcnew Box(10,15,20);  //创建新的Box对象,并将对象的地址存入句柄newBox中
	aBox = gcnew Box; //调用无参数的构造函数创建Box对象,并将对象的地址存入句柄aBox中
	Console::WriteLine(L"Default box volume is {0}",aBox->Volume());	
	Console::WriteLine(L"New box volume is {0}",newBox->Volume());

	Console::ReadLine();
    return 0;
}

在这里插入图片描述

7.11.3 定义引用类类型的复制构造函数

  • 复制构造函数的形参必须为const引用。
  • 定义Box类的复制构造函数:
Box(const Box% box):Length(box->Length),Width(box->Width),Height(box->Height)
{}
  • 允许将T类型的引用类型对象按值传递给函数的引用类T的复制构造函数的形式:
T(const T% t)
{
	//code to make copy
}

一个采用句柄做实参的复制构造函数:

Box (const Box^ box):Length(box->Length),Width(box->Width),Height(box->Height)
{}

7.11.4 类属性

  • 属性是值类或引用类的成员,可以把属性当作字段来访问,但它们实际上不是字段。

  • 属性与字段的首要区别:字段名是引用某个存储位置,属性名是调用某个函数。

  • 属性拥有分别获取和设定属性值的get() 和 set()访问器函数。

    • 当使用属性名获取属性值时,后台是在调用该属性的get()函数;
    • 当在赋值语句左边使用属性名时,实际上是在调用该属性的set()函数。
    • 如果某个属性只提供了get()函数的定义,则称为只读属性。
    • 某个属性只有set()函数的定义,称为只写属性。
  • 类可以包含2种不同的属性:标量属性和索引属性。

    • 标量属性:可以通过属性名访问的单值;
    • 索引属性:是一组值,需要在属性名后面的方括号内使用索引来访问。

1.定义标量属性

  • 标量属性是一个单值,在类定义中使用property关键字定义标量属性。
    • 标量属性的get()函数必须有与属性类型相同的返回类型
    • set()函数必须有其类型与属性相同的形参。
value class Height
{
private:
	int feet;
	int inches;
	literal int inchesPerFoot = 12;
	literal double inchesToMeters = 2.54/100;

public:
	Height(int ins)
	{
		feet = ins / inchesPerFoot;
		inches = ins % inchesPerFoot;
	}

	Height(int ft,int ins):feet(ft),inches(ins){}

	property double meters //定义名为meters的属性
	{
		double get()
		{
			return inchesToMeters*(feet*inchesPerFoot+inches);
		}
	}

	virtual String^ToString() override
	{
		return feet + L" feet" + inches + L" inches";
	}
};
// 访问属性
Height ht(Height(6,8));
Console::WriteLine(L"The height is {0} meters",ht.meters);

在类定义外部定义get() \ set()函数:

value class Height
{
// code as before...
public:
// code as before...
	property double meters //定义名为meters的属性
	{
		double get();
	}
// code as before...
};
// Height 表明该函数属于Height类;meters 表明该函数属于类中的meters属性
double Height::meters::get()
{
	return inchesToMeters*(feet*inchesPerFoot+inches);
}

2. 普通标量属性
在定义类的标量属性时不提供get()和set()函数的定义,这样的属性被称作普通标量属性。 要指定普通标量属性,只需省略包含get()和set()函数定义的大括号,并以 ; 结束属性声明。

value class Point
{
public:
	property int x;
	property int y;
	
	virtual String^ ToString() override
	{
		return L "(" + x + L"," + y + L")";
	}
}

编译器自动为每个普通标量属性提供默认的 get()和set()函数定义。get() 将返回属性值,set()将把属性值设定为与该属性类型相同的实参。

#include "stdafx.h"

using namespace System;

value class Height
{
private:
	int feet;
	int inches;
	literal int inchesPerFoot = 12;
	literal double inchesToMeters = 2.54/100;
public:
	Height(int ins)
	{
		feet = ins / inchesPerFoot;
		inches = ins % inchesPerFoot;
	}

	Height(int ft,int ins):feet(ft),inches(ins){}

	property double meters //定义名为meters的属性
	{
		double get()
		{
			return inchesToMeters*(feet*inchesPerFoot+inches);
		}
	}
	virtual String^ToString() override
	{
		return feet + L" feet" + inches + L" inches";
	}
};

value class Weight
{
private:
	int lbs;
	int oz;
	literal int ouncesPerPound = 16;
	literal double lbsToKg = 1.0/2.2;

public:
	Weight(int pounds,int ounces)
	{
		lbs = pounds;
		oz = ounces;
	}

	property int pounds
	{
		int get(){return lbs;}
		void set(int value){lbs = value;}
	}

	property int ounces
	{
		int get(){return oz;}
		void set(int value){oz = value;}
	}

	property double kilograms
	{
		double get(){return lbsToKg * (lbs + oz/ouncesPerPound);}
	}

	virtual String^ToString() override
	{
		return lbs + L" pounds" + oz + L" ounces";
	}
};

ref class Person
{
private:
	Height ht;
	Weight wt;
public:
	property String^ Name; // Name是普通属性
	Person(String^ name,Height h,Weight w):ht(h),wt(w)
	{
		Name = name;
	}

	property Height height
	{
		Height get(){return ht;}
	}

	property Weight weight
	{
		Weight get(){return wt;}
	}
};

int main(array<System::String ^> ^args)
{
	Weight hisWeight(185,7);
	Height hisHeight(6,3);
	Person^ him(gcnew Person(L"Fred",hisHeight,hisWeight));

	Weight herWeight(Weight(105,3));
	Height herHeight(5,2);
	Person^ her(gcnew Person(L"Freda",herHeight,herWeight));

	Console::WriteLine(L"She is {0}",her->Name);
	Console::WriteLine(L"Her weight is {0:F2} kilograms.",her->weight.kilograms);
	Console::WriteLine(L"Her height is {0} which is {1:F2} meters.",her->height,her->height.meters);

	Console::WriteLine(L"He is {0}",him->Name);
	Console::WriteLine(L"His weight is {0:F2} kilograms.",him->weight.kilograms);
	Console::WriteLine(L"His height is {0} which is {1:F2} meters.",him->height,him->height.meters);

    Console::ReadLine();
    return 0;
}

3.定义索引属性

  • 索引属性是类的一组属性值,通过在引用对象的变量名后面加上包含索引的[ ]来访问。
  • 拥有属性名的索引属性称为命名索引属性。
ref class Name
{
private:
	array <String^>^ Names;
public:
	Name(...array<String^>^ names): Names(names){}

	property String^ default[int] // default说明是默认索引属性
	{
		String^ get(int index)
		{
			if(index >= Names->Length)
				throw gcnew Exception(L"Index out of range");
			return Names[index];
		}
	}
};
  • default关键字后面的方括号表明,该属性实际上是索引属性。[ ] 包围的类型是获取属性值时要使用的索引值的类型。
  • 对于使用当索引访问的索引属性来说,get()函数必须有一个类型与属性名后面方括号内出现的类型 相同的形参,以用来指定索引。
  • 索引属性的 set()函数必须有2个形参:第1个是 索引;第2个 是用来设定第1个形参对应的属性的新值。
#include "stdafx.h"

using namespace System;

ref class Name
{
private:
	array <String^>^ Names;
public:
	Name(...array<String^>^ names): Names(names){} //形参列表以省略号开始,可接受任意数量的实参

	property int NameCount //NameCount 标量属性
	{
		int get(){return Names->Length;}
	}

	property String^ default[int] // default说明是默认索引属性
	{
		String^ get(int index)
		{
			if(index >= Names->Length)
				throw gcnew Exception(L"Index out of range");
			return Names[index];
		}

		void set(int index,String^ name) 
		{
			if(index >= Names->Length)
				throw gcnew Exception(L"Index out of range");
			Names[index] = name;
		}
	}
};
int main(array<System::String ^> ^args)
{
	Name^myName = gcnew Name(L"Ebenexer",L"Isaiah",L"Ezra",L"Inigo",L"Whelkwhistle");
	myName [myName->NameCount - 1]= L"Oberwurst";

	for(int i=0 ;i < myName->NameCount ; i++)
		Console::WriteLine(L"Name {0} is {1}",i+1,myName[i]);

    Console::ReadLine();
    return 0;
}

给Name类添加一个命名索引属性:

ref class Name
{
	// Code as before
	property wchar_t Initials[int] //该索引属性名为Initials
	{
		wchar_t  get(int index)
		{
			if(index >= Names->Length)
				throw gcnew Exception(L"Index out of range");
			return Names[index] [0];
		}
	}
}

4. 更复杂的索引属性
将索引属性定义成要求使用多个索引才能访问属性值,属性可以不是数字。

enum class Day{Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday};

ref class Shop
{
public:
	property String^ Opening[Day,String^]
	{
		String^ get(Day day,String^ AmOrPm)
		{
			switch(day)
			{
			case Day::Saturday:
				if(L"am"== AmOrPm)
					return L"9:00";
				else 
					return L"14:30";
				break;
			case Day::Sunday:
				return L"closed";
				break;
			default:
				if(L"am" == AmOrPm)
					return L"9:30";
				else 
					return L"14:00";
				break;
			}
		}
	}
};

int main(array<System::String ^> ^args)
{
	Shop^ shop(gcnew Shop);
	Console::WriteLine(shop->Opening[Day::Saturday,L"pm"]);
    Console::ReadLine();
    return 0;
}

5. 静态属性

  • 使用static 创建静态属性
value class Height
{
public:
	static property String^ Units
	{
		String^ get(){return L"feet and inches";}
	}
};
  • 通过使用以类名限定的属性名来访问静态属性:
    Console::WriteLine(L"Class units are {0}.",Height::Units);
  • 如果已经定义过类对象,则可以使用变量名访问静态属性:
    Console::WriteLine(L"Class units are {0}.",myHt.Units);
  • 如果要通过对象句柄访问引用类中的静态属性,则应该使用->操作符。

6.属性的保留名称

  • 虽然属性与字段不同,但属性仍然必须存储在一地方,而存储位置也需要以某种方式标识。在内部,属性包含几个为所需的存储位置创建的名称,它们在包含属性的类中属于保留名称,因此决不能为其他目的而使用它们。
  • 如果在类中用名称NAME定义标量或命名索引属性,则get_NAME 和 set_NAME是奔雷的保留名称。
  • 无论是否为NAME属性定义get() 和 set() 函数,get_NAME 和 set_NAME 两个名称都是保留名称。
  • 当在类中定义默认索引属性时,get_Item 和 set_Item 是保留名称。

注意,C++/CLI 程序中可能存在使用下划线字符的保留名称,因此我们应该避免在自定义的名称中使用下划线。

7.11.5 initonly 字段
C++/CLI 提供了再构造函数中进行初始化的initonly 常量类字段。

value class Height
{
private:
	int feet;
	int inches;
public:
	initonly int inchesPerFoot; 

	Height(int ft,int ins):
	feet(ft),inches(ins),inchesPerFoot(12){}
};
  • initonly 字段在初始化以后不能再被修改,它始终是固定不变的。
  • 注意,决不能再声明非静态initonly 字段时指定初始值,而必须在构造函数中初始化所有的非静态initonly 字段。

还可以在构造函数体中,初始化非静态initonly 字段

Height(int ft,int ins):
	feet(ft),inches(ins),
	{ inchesPerFoot = 12;}

可以将类的initonly 字段定义为static,这样该字段将被所有类成员共享。如果它还是公有字段,使用以类名限定的字段名就可以访问它。

value class Height
{
private:
	int feet;
	int inches;
public:
	initonly static int inchesPerFoot = 12; 

	Height(int ft,int ins):
	feet(ft),inches(ins)
	{ }
};

7.11.6 静态构造函数

  • 静态构造函数是使用static 关键字声明的构造函数,用来初始化静态字段 和静态initonly 字段。
  • 静态构造函数没有形参,也不能有初始值设定列表。
  • 静态构造函数总是私有的,与是否将其放在类的公有部分无关。
  • 不能直接调用静态构造函数,它将在普通构造函数执行之前被自动调用。
  • 任何声明中制定了初始值的静态字段,都将在静态构造函数执行之前被初始化。
value class Height
{
private:
	int feet;
	int inches;

	static Height(){ inchesPerFoot = 12;}

public:
	initonly static int inchesPerFoot;

	Height(int ft,int ins):
	feet(ft),inches(ins)
	{ }
};

7.14 本章主要内容
在这里插入图片描述
在这里插入图片描述

To be continue…

猜你喜欢

转载自blog.csdn.net/madao1234/article/details/85243901