C++/CLI中可以定義兩種類型的struct和class類型,一種為數值類(或數值結構):value class(value struct);一種是引用類(或引用結構):ref class(ref value)。與本地C++一樣,class與struct的區別在于前者的成員默認為私有,后者默認為公有。下面僅以類來介紹,內容同樣適用于結構。

value class與ref class組成的是雙關鍵字,也就是說,單獨的value、ref并不是關鍵字。數值類與引用類的區別,以及它們與本地C++類的區別主要包括以下幾個方面:

  • 數值類的對象包含自己的數據,引用類的對象只能用句柄來訪問
  • 在C++/CLI中,函數成員不能聲明為const類型,取而代之的是字面值類型,修飾詞關鍵字為 literal。
  • 在非靜態函數成員中,this指針類型與本地C++不同:數值類的this指針為內部指針類型(interior_ptr<T>),而引用類的this指針為句柄類型(T^)。
  • C++/CLI類的數據成員不能包含本地C++數組或本地C++類對象。
  • C++/CLI類無友元函數。
  • C++/CLI類的數據成員不能包含位類型的數據成員。(什么是位數據類型)
  • C++/CLI類的函數成員不能有默認的形參。

此外,在C++/CLI中,不推薦類命名時使用前綴‘C’,其成員變量命名也不用前綴’m_’。

一、定義數值類

數值類主要用于表示具有有限個數據成員的簡單對象,其定義方法與本地C++類基本相同。首先看一個定義數值類,以及使用類的完整例子。

- - - - - - - - - - - - - - - - <<== 華麗的分割線 ::開始==>> [Ex7_14.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex7_14.cpp : main project file.

#include "stdafx.h"

using namespace System;

// class representing a height
value class Height
{
private:
	// Records the height in feet and inches
	int feet;
	int inches;

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

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

int main(array<System::String ^> ^args)
{
	Height myHeight = Height(6, 3);
	Height^ yourHeight = Height(70);
	Height hisHeight = *yourHeight;

	Console::WriteLine(L"My height is {0}", myHeight);
	Console::WriteLine(L"Your height is {0}", yourHeight);
	Console::WriteLine(L"His height is {0}", hisHeight);
    return 0;
}
- - - - - - - - - - - - - - - - <<== 華麗的分割線 ::結束==>> [Ex7_14.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

輸出為

My height is Height
Your height is Height
His height is Height

在上面的例子中,myHeight和hisHeight被分配在堆棧上,yourHeight被分配到了CLR堆上。其中hisHeight是yourHeight的一個副本,當向 hisHeight賦值時,需要用*操作符對句柄yourHeight進行解除引用計算。這是因為數值類對象總是包含自己的數據,因此它們不能引用同一個對象,在賦值時總是采用復制的方式進行。注意:在C++/CLI中,不能重寫默認構造函數。默認構造函數將所有的值類型數據成員初始化為0,將引用類型(句柄)初始化為nullptr。同樣,也不能重載復制構造函數和賦值操作符。默認的復制操作是將每一個數據成員進行復制,對象間的賦值也是如此

C++/CLI中的類都有一個成員函數ToString(),它返回一個表示對象的字符串句柄。默認情況下,該字符串為類名。這從上面的輸出可以看出:傳遞給WriteLine()函數的是Height對象,結果輸出的并非對象所包含的高度值,而是類名Height,這是因為編譯器認為此處需要調用該對象的字符串表示法,因此安排的ToString()函數調用,這個過程可以顯示的表達為

double pi = 3.142;
Console::WriteLine(pi.ToString());

double類型被映射到System命名空間中的System::Double類,該類實現了ToString方法,因此可以正確的輸出變量pi的數值3.142而非類名Double。在上面的例子中,為了正確地輸出高度,可給Height定義中增加ToString()的重載函數。

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

現在可以正確的輸出為

My height is 6 feet 3 inches
Your height is 5 feet 10 inches
His height is 5 feet 10 inches

在定義數值類時,如果數據成員為常量,C++/CLI中將其定義為”字面值”(literial)。在上面的例子中,將12定義為字面值,可以使得代碼的可讀性更高,避免“幻數”的出現(意指程序代碼中難以理解其來源或意義的常數,如上面例子中的12)。定義字面值的方法如下

value class Height
{
	int feet;
	int inches;
	literial int inchesPerFoot = 12;
	
	// Other code...
};

這樣就可以在其后直接使用該字面值,而非難以理解的12了

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

利用”字面值”leterial來定義常量的一個缺點是:必須在定義常量的同時指定它的值。另外一種定義常量的方法是使用initonly修飾符,使用該修飾符的常量變量只能在構造函數的初始化表,或者構造函數體內進行一次初始化, 之后再也不能被修改。注意:不能在聲明非靜態initonly常量時指定初值,而必須是在構造函數的初始化表或構造函數體內。下面的例子描述了onlyinit的用法

value class Length
{
private:
	int feet;
	int inches;
	
public:
	initonly int inchesPerFoot;
	
	// Constructor
	Length(int ft, int ins) : 
		feet(ft), inches(ins),
		inchesPerFoot(12);
}
上面的構造函數也可以寫成
Lenght(int ft, int ins) :
	feet(ft), inches(ins)
{
	inchesPerFoot = 12;
}

如果是靜態地initonly變量,則只能在定義時指定初值。因為如果自構造函數中定義,則每次創建類實例都將對靜態變量賦值,這顯然與靜態、常量這樣的概念沖突。解決的辦法是,如果一定要在構造函數中初始化initonly類型的靜態常量,則可定義一個靜態構造函數。

value class Length
{
private:
	int feet;
	int inches;

	static Length() { inchesPerFoot = 12; }

public:
	initonly static int inchesPerFoot;

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

靜態構造函數函數沒有形參,且沒有初始化表,總是被聲明為private。它不能被直接調用,而是由普通構造函數在調用之前自動調用。這種方法與在定義靜態initonly變量時指定初始值的唯一區別是,初始化值可以是在運行時確定的。

二、定義引用類

引用類更加類似于本地C++類,它沒有數值類那么多的限制。但引用類沒有默認的復制構造函數和賦值運算符,如果定義的類需要進行復制或賦值,必須顯式地添加相應的函數成員。下面的例子定義了一個引用類及其使用方法。

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

// Ex7_15.cpp : main project file.

#include "stdafx.h"

using namespace System;

ref class Box
{
public:
	// No-arg constructor supplying default field values
	Box():Length(1.0), Width(1.0), Height(1.0)
	{
		Console::WriteLine(L"No-arg constructot called.");
	}
	// Constructor definition using an initialisation list
	Box(double lv, double bv, double hv):Length(lv), Width(bv), Height(hv)
	{
		Console::WriteLine(L"Constructor called.");
	}

	// Function to calculate the volume of a box
	double Volume()
	{
		return Length*Width*Height;
	}

private:
	double Length;
	double Width;
	double Height;
};

int main(array<System::String ^> ^args)
{
	Box^ aBox;
	Box^ newBox = gcnew Box(10, 15, 20);
	aBox = gcnew Box;

	Console::WriteLine(L"Default box volume is {0}", aBox->Volume());
	Console::WriteLine(L"New box volume is {0}", newBox->Volume());
    return 0;
}

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

輸出為

Constructor called.
No-arg constructot called.
Default box volume is 1
New box volume is 3000

在上面的例子中,main()函數的第一句沒有創建任何對象,僅僅聲明了一個句柄,并被默認的賦值成nullptr。此外,引用對象總是在堆上創建,因此總是用gcnew來調用其構造函數,并用句柄來跟蹤引用對象。