C++/CLI :類的屬性

屬性是C++/CLI的類成員,它類似於成員變量,但實際上不是。其主要區別在於,字段名引用了某個存儲單元,而屬性名則是調用某個函數。屬性擁有訪問屬性的set()和get()函數。當我們使用屬性名時,實際上在調用該函數的get()或set()函數。如果一個屬性僅提供了get()函數,則它是隻讀屬性;如果一個屬性僅提供set()函數,則它是隻寫屬性。

類可以有2種不同的屬性:標量屬性和索引屬性。標量屬性是指通過屬性名來訪問的單值;索引屬性是利用屬性名加方框號來訪問的一組值。如 String類,其Length屬性爲標量屬性,用object->Length來訪問其長度,且Length是個只讀屬性。String還包含了索引屬性,可以用object[idx]來訪問字符串中第idx+1個字符。

屬性可以與類的實例(類對象)相關,此時屬性被稱爲實例屬性,如String類的Length屬性;如果用static修飾符指定屬性,則屬性爲類屬性,所有該類得實例都具有相同的屬性值。

一、標量屬性

標量屬性是單值,用property關鍵字可定義標量屬性,還需要定義其get()和set()函數,如下例所示

value class Height
{
private:
	// Records the height in feet and inches
	int feet;
	int inches;
	literal int inchesPerFoot = 12;
	literal double inchesToMeter = 2.54/100;

public:
	// Create a height from inches value
	Height(int ins)
	{
		feet = ins/inchesPerFoot;
		inches = ins%inchesPerFoot;
	}

	// Create a height from feet and inches
	Height(int ft, int ins) : feet(ft), inches(ins) {};

	// The height in meters as a property
	property double meters
	{
		double get()
		{
			return inchesToMeters * (feet*inchesPerFoot + inches);
		}
	}

	// Create a string representation of the object
	virtual String^ ToString() overrides
	{
		return feet + L" feet " + inches + L" inches";
	}
};

上面的例子定義了一個merters的屬性,下面是屬性的用法

Height ht = Height(6, 8);
Console::WriteLine(L"The height is {0} meters", ht->meters);

屬性不一定要定義成內聯函數,也可以在.cpp中外部定義它,如在上例的定義中僅保留get()函數聲明

	property double meters
	{
		double get();
	}

函數定義在.cpp中時,需要加類名和函數名的限定(但不需要返回值?),方法如下:

Height::meters::get()
{
	return inchesToMeters*(feet*inchesPerFoot+inches);
}

如果定義一個屬性時,不提供get()和set()函數定義,這種屬性被稱爲平凡標量屬性。對於此類屬性,編譯器將提供一個默認的get()和set()實現,如下例所示:

value class Point
{
public:
	property int x;
	proterty int y;

	virtual String^ ToString() overrides
	{
		return L"("+x+L","+y+")";		// Result is (x,y)
	}
};

下面是一個完整的例子,說明了標量屬性的聲明及其使用方法

- - - - - - - - - - - - - - - - <<== 華麗的分割線 ::開始==>> [Ex7_16.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Ex7_16.cpp : main project file.

#include "stdafx.h"

using namespace System;

// Class defining a person's height
value class Height
{
private:
	// Record the height in feet and inches
	int feet;
	int inches;

	literal int inchesPerFoot = 12;
	literal double inchesToMeters = 2.54/100;

public:
	// Create a height from inches value
	Height(int ins)
	{
		feet = ins/inchesPerFoot;
		inches = ins%inchesPerFoot;
	}

	// Create a height from feet and inches
	Height(int ft, int ins) : feet(ft), inches(ins) {};

	// The height in meters
	property double meters
	{
		double get()
		{
			return inchesToMeters*(feet*inchesPerFoot+inches);
		}
	}

	// Create a string representation of the object
	virtual String^ ToString() override
	{
		return feet + L" feet " + inches + L" inches";
	}
};

// Class defining a person's weight
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;

	Person(String^ name, Height h, Weight w) : ht(h), wt(w)
	{
		Name = name;
	}

	Height getHeight() { return ht; }
	Weight getWeight() { return wt; }
};

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

	Weight herWeight = Weight(105, 3);
	Height herHeight = Height(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->getWeight().kilograms);
	Console::WriteLine(L"Her height is {0} which is {1:F2} meters.", her->getHeight(), 
		her->getHeight().meters);

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

    return 0;
}

- - - - - - - - - - - - - - - - <<== 華麗的分割線 ::結束==>> [Ex7_16.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

輸出爲
She is Freda
Her weight is 47.73 kilograms.
Her height is 5 feet 2 inches which is 1.57 meters.
He is Fred
His weight is 185 pounds 7 ounces
His height is 6 feet 3 inches which is 1.91 meters.

二、索引屬性

索引屬性是類的一組屬性值,其訪問方法同數組元素那樣,在方括號內加索引值來訪問。如果在方括號前面的是類對象的名稱,則該索引屬性被稱爲默認索引屬性(如String^ obj可以用obj[idx]來訪問字符串中第idx+1個字符),如果用屬性名[idx]來訪問索引屬性值,則稱爲有名索引屬性。下面的代碼在類Name中定義了一個默認索引屬性,

ref class Name
{
private:
	array<String^>^ Names;

public:
	Name(...array<String^>^ names) : Names(names) {}
	
	// Indexed property to return any name
	property String^ default[int]
	{
		// Retrieve indexed property value
		String^ get(int index)
		{
			if(index >= Names->Length)
				throw gcnew Exception(L"Index out of range");
			return Names[index];
		}
	}
};

在上面的例子中,如果將default換成別的名字,則該屬性就成爲一個有名索引屬性。在定義索引屬性時,方括號內用int指定了索引的數據類型爲int型,它也可以是別的數據類型。訪問索引屬性的get()函數的形參其數據類型必須與屬性名後方括號內類型相同;set()函數必須有2個形參,第一個指定索引,第二個指定屬性元素的值。

下面是一個完整的例子,說明了索引屬性的定義與使用方法。

- - - - - - - - - - - - - - - - <<== 華麗的分割線 ::開始==>> [Ex7_17.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

// Ex7_17.cpp : main project file.

#include "stdafx.h"

using namespace System;

ref class Name
{
private:
	array<String ^>^ Names;

public:
	Name(...array<String ^>^ names) : Names(names) {}

	// Scalar property specifying number of names
	property int NameCount
	{
		int get() { return Names->Length; }
	}

	// Indexed property to return names
	property String^ default[int]
	{
		String ^ get(int index)
		{
			if(index >= Names->Length)
				throw gcnew Exception(L"Index out of range");
			return Names[index];
		}
	}
};

int main(array<System::String ^> ^args)
{
	Name^ myName = gcnew Name(L"Ebenezer", L"Isaiah", L"Ezra", L"Inigo", L"Whelkwhistle");

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

- - - - - - - - - - - - - - - - <<== 華麗的分割線 ::結束==>> [Ex7_17.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

輸出爲
Name 1 is Ebenezer
Name 2 is Isaiah
Name 3 is Ezra
Name 4 is Inigo
Name 5 is Whelkwhistle

索引屬性的索引也可以不是整型,甚至可以不是數字,下面的例子定義了一個商店類,其屬性Opening指定了商店的開門時間,訪問該屬性的索引有2個參數,如下面的例子所示

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

// class defining a shop
ref class Shop
{
public:
	property String^ Opening[Day, String^]	// Opening times
	{
		String ^ get(Day day, String^ AmOrPm)
		{
			switch(day)
			{
				case Day::Saturday:
					if(AmOrPm == L"am")
						return L"9:00";
					else
						return L"14:30";
					break;

				case Day::Sunday:
					return L"closed";
					break;

				default:
					if(AmOrPm == L"am")
						return L"9:30";
					else
						return L"14:00";
					break;
			}
		}
	}
};
使用該類的方法如下
Shop^ shop = gcnew Shop;
Console::WriteLine(shop->Opening(Day::Saturday, L"pm");

三、靜態屬性

靜態屬性爲類的所有實例共有,類似於類的靜態成員變量。通過在屬性定義前添加修飾符static來定義,如下面的例子所示

value class Length
{
public:
	static property String ^ Units
	{
		String ^ get() { return L"feet and inches"; }
	}
};

無論是否創建類實例,靜態屬性都存在。如果已經定義了類實例,則可以用實例名.屬性名來訪問靜態屬性。對於上面的例子如果已經定義了一個類對象len,則可以如此訪問其靜態屬性:

Console::WriteLine(L"Class units are {0}.", len.Units);

 注意:在定義了屬性之後,對應的get_屬性名和set_屬性名自動成爲系統保留名稱,不能爲其它目的而使用他們。如果定義了默認索引屬性,則set_Item和get_Item也成爲系統保留名稱,不可被使用。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章