弄错的车站
孤独自由的旅人啊, 亲吻着睡梦中的她, 她的眼泪慢慢就变成了一朵花。
C++博客 | 首页 | 发新随笔 | 发新文章 | 联系 | 聚合 | 管理

2009年7月22日

C++/CLI学习入门(十):类的运算符重载

C++/CLI中可以对运算符进行重载,但与本地C++有所区别。同时对于运算符重载,数值类和引用类也存在不同要求。下面以例子开始,了解C++/CLI中重载运算符的方法。

一、数值类中重载运算符

下面的例子重载了加法运算符。

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

public:
	static initonly int inchesPerFoot = 12;
	
	Length(int ft, int ins) : feet(ft), inches(ins) {}
	
	virtual String^ ToString() override
	{ return feet+L" feet "+inches+L" inches"; }

	Length operator+(Length len)
	{
		int inchTotal = inches + len.inches + inchesPerFoot*(feet + len.feet);
		return Length(inchTotal/inchesPerFoot, inchTotal%inchesPerFoot);
	}
};

类的使用很简单,方法如下

Length len1 = Length(6, 9);
Length len2 = Length(7, 8);
Console::WriteLine(L"{0} plus {1} is {2}", len1, len2, len1+len2);

上面重载的加法运算符也可通过静态成员的方式实现。

static Length operator+(Length len)
{
	int inchTotal = inches + len.inches + inchesPerFoot*(feet + len.feet);
	return Length(inchTotal/inchesPerFoot, inchTotal%inchesPerFoot);
}

下面定义Length与数值的乘法重载运算符,它包括了数值*长度和长度*数值两种情况。这也是二元操作的一个例子

value class Length
{
	//...

	static Length operator*(double x, Length len);
	static Length operator*(Length len, double x);
};

   函数的定义如下

Length Length::operator*(double x, Length len)
{
	int ins = safe_cast<int>(x*len.inches + x*len.feet*inchesPerFoot);
	return Length( ins/12, ins%12 );
}

length Length::operator*(Length len, double x)
{
	return operator*(x, len);
}

下面定义递增运算符,这是一个一元运算符,可用同一个函数来定义前缀递增和后缀递增运算,编译器能够自动的根据调用情况来判断是先递增还是后递增。

static Length operator++(Length len)
{
	++len.inches;
	len.feet += len.inches/len.inchesPerFoot;
	len.inches %= len.inchesPerFoot;
	return len;
}

二、引用类中重载运算符

在引用类中重载运算符的方法与数值类基本相同,主要区别是形参和返回值一般都是句柄。这里就不再赘述了。

posted @ 2009-07-22 00:30 英勇的近卫军 阅读(1785) | 评论 (1) | 编辑 收藏
 

2009年7月20日

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也成为系统保留名称,不可被使用。

posted @ 2009-07-20 00:52 英勇的近卫军 阅读(1594) | 评论 (0) | 编辑 收藏
 

2009年7月12日

C++/CLI学习入门(八):类的分类与定义

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来调用其构造函数,并用句柄来跟踪引用对象。

posted @ 2009-07-12 16:45 英勇的近卫军 阅读(1488) | 评论 (0) | 编辑 收藏
 

2009年7月10日

C++/CLI学习入门(七):类函数

类函数是C++/CLI中引入的新概念,其功能类似于函数模板,但原理上却迥然不同。使用函数模板时,编译器根据模板生成函数源代码,然后将其与其它代码一起编译。这种方法可能会产生许多函数,从而使得生成模块的体积增加,这就是所谓的“代码膨胀”。类函数与之不同,类函数本身将被编译,在调用类函数时,实际类型在运行时取代了类函数的类型形参,这不会导致新增代码的问题。

一、类函数的定义

类函数与普通函数的区别在于:它需要定义一个特殊的形参——类型形参,该参数说明在运行时传递给函数的参数类型。下面的例子定义了一个类函数,用于找出某种数据类型数组中最大的元素。

generic<typename T> where T:IComparable
T MaxElement(array<T>^ x)
{
	T max = x[0];
	for(int i=1; i<x->Lenght; i++)
		if(max->CompareTo(x[i])<0)
			max = x[i];
	return max;
}

关键字generic规定后面的内容为类函数定义,尖括号内用关键字typename定义了一个类型形参T,如果有多个类型形参,它们都放在尖括号内,用逗号分开。

关键字where引入使用类函数时,传递给T的类型实参应满足的约束条件。这里的条件是:所有用于替代T的类型必须实现了IComparable接口。该约束也意味着,所有传入的类型实参都实现了CompareTo()函数,允许对该类型两个对象进行比较。

第二行定义了函数的返回值类型、函数名称以及形参,与普通函数定义类似,但有的类型用T来描述,它们将在运行时被确定。

二、使用类函数

调用类函数的方法与调用普通函数的方法一样。对上面声明的MaxElement()函数可如此调用:

array<double>^ data = {1.5, 3.5, 6.7, 4.2, 2.1};
double maxData = MaxElement(data);

 在这个例子中,编译器能够判断出该类函数的类型实参为double,生成调用函数的代码(并非是该类函数的double版),执行时再用 double代替T。注意:与函数模板不同,对于类函数编译器不创建函数实例,而是仅仅是使编译后的代码可以接受数据类型作为类型实参,实际的类型替换在运行时实现。

应该注意的是,如果以字符串常量作为实参传递给类函数,编译器将认为类型实参是String^,无论该字符串常量是窄字符串(“Hello”)还是宽字符串(L“Hello”)。

有些情况下,编译器无法根据函数调用来判断类型实参是什么,这时可用在函数名后面加尖括号和类型名称来显示的指定,上面的例子也可以写作

double maxData = MaxElement<double>(data);

另外需要注意的是,提供给类函数作为类型实参的不能是本地C++类类型、本地指针、引用,也不能是值类类型的句柄(如 int^)。而只能是值类型(如int、double)或引用类型的跟踪句柄(如String^)。

下面是一个使用类函数的完整示例。

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex6_10.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex6_10.cpp : main project file.
// Defining and using generic fuctions
#include "stdafx.h"

using namespace System;

// Generic function to find the maximum element in an array
generic<typename T> where T:IComparable
T MaxElement(array<T>^ x)
{
	T max = x[0];
	for(int i=1; i<x->Length; i++)
		if(max->CompareTo(x[i])<0)
			max = x[i];
	return max;
}

// Generic function to remove an element from an array
generic<typename T> where T:IComparable
array<T>^ RemoveElement(T element, array<T>^ data)
{
	array<T>^ newData = gcnew array<T>(data->Length-1);
	int Index = 0;		// Index to elements in newData array
	bool found = false;	// Indicates that the element to remove from was found
	for each(T item in data)
	{
		// Check for invalid index or element found
		if((!found) && item->CompareTo(element)==0 )
		{ 
			found = true;
			continue;
		}
		else
		{
			if(Index == newData->Length)
			{
				Console::WriteLine(L"Element to remove not found");
				return data;
			}
			newData[Index++] = item;
		}
	}
	return newData;
}

// Generic function to list an array
generic<typename T> where T:IComparable
void ListElements(array<T>^ data)
{
	for each(T item in data)
		Console::Write(L"{0, 10}", item);
	Console::WriteLine();
}

int main(array<System::String ^> ^args)
{
	array<double>^ data = {1.5, 3.5, 6.7, 4.2, 2.1};
	Console::WriteLine(L"Array contains:");
	ListElements(data);
	Console::WriteLine(L"\nMaximun element = {0}\n", MaxElement(data));
	array<double>^ result = RemoveElement(MaxElement(data), data);
	Console::WriteLine(L"After removing maximun, array contains:");
	ListElements(result);


	array<int>^ numbers = {3, 12, 7, 0, 10, 11};
	Console::WriteLine(L"Array contains:");
	ListElements(numbers);
	Console::WriteLine(L"\nMaximun element = {0}\n", MaxElement(numbers));
	Console::WriteLine(L"After removing maximun, array contains:");
	ListElements(RemoveElement(MaxElement(numbers), numbers));

	array<String^>^ strings = {L"Many", L"hands", L"make", L"light", L"work"};
	Console::WriteLine(L"Array contains:");
	ListElements(strings);
	Console::WriteLine(L"\nMaximun element = {0}\n", MaxElement(strings));
	Console::WriteLine(L"After removing maximun, array contains:");
	ListElements(RemoveElement(MaxElement(strings), strings));

	return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex6_10.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

输出如下

Array contains:
       1.5       3.5       6.7       4.2       2.1

Maximun element = 6.7

After removing maximun, array contains:
       1.5       3.5       4.2       2.1
Array contains:
         3        12         7         0        10        11

Maximun element = 12

After removing maximun, array contains:
         3         7         0        10        11
Array contains:
      Many     hands      make     light      work

Maximun element = work

After removing maximun, array contains:
      Many     hands      make     light
posted @ 2009-07-10 00:21 英勇的近卫军 阅读(1041) | 评论 (0) | 编辑 收藏
 

2009年7月9日

C++/CLI学习入门(六):函数

C++/CLI中函数的工作方式与ISO/ANSI C++完全相同,但由于在C++/CLI中用跟踪句柄和跟踪引用替代了本地指针和引用,因此也带来一些变化,主要包括

  • CLR程序中函数的形参与返回值可以是数值类型、跟踪句柄、跟踪引用和内部指针。
  • 如果某个形参是CLR数组,程序不需要另外的参数指定其大小,因为数组大小在属性Length中。
  • 在C++/CLI程序中,不能像C++一样进行地址的算术运算,而应使用数组索引。(内部指针不是可以算术操作吗?)
  • 可以方便的返回CLR堆上的句柄,因为CLR有垃圾回收机制自动清理无用的内存。
  • C++/CLI函数接收可变长度参数的机制与本地C++不同。
  • C++/CLI中main()函数访问命令行实参的机制与本地C++不同。

下面就最后2条进行说明。

一、接收可变长度参数的函数

C++/CLI允许将形参列表指定为数组,数组声明前面加省略号,从而实现实参的长度可变。

int sum(...array<int>^ args)
{
	// Code for sum
}

上面的sum()函数可以接收任意数量的整数作为实参,在函数内部,通过访问数组args的元素就可以实现对实参的处理,实参个数由args的属性Length得到。下面是一个完整的例子,描述了该机制的工作过程。

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex5_15.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex5_15.cpp : main project file.
#include "stdafx.h"
using namespace System;

double sum(...array<double>^ args)
{
	double sum = 0.0;
	for each(double arg in args)
		sum += arg;
	return sum;
}

int main(array<System::String ^> ^args)
{
	Console::WriteLine( sum(2.0, 4.0, 6.0, 8.0, 10.0, 12.0) );
	Console::WriteLine( sum(1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9) );
        return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex5_15.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

输出为

42
49.5

二、main()的实参

从前面的例子可以看出,CLR程序中main()函数仅有一个形参,它为String^类型的数组,这就将处理命令行实参简化为访问该数组的元素。下面的例子展示了这个用法。

此外还应注意:与本地C++中main()不同,命令行参数中不包括程序名称本身。

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex5_16.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex5_16.cpp : main project file.
#include "stdafx.h"
using namespace System;

int main(array<System::String ^> ^args)
{
	Console::WriteLine(L"There were {0} command line arguments.", args->Length);
	Console::WriteLine(L"Command line arguments received are:");
	int i=1;
	for each(String^ str in args)
		Console::WriteLine(L"Argument {0}: {1}", i++, str);
    
	return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex5_16.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
输出为
D:\My Documents\My Projects\Ex5_16\Debug>ex5_16 tring multiple "arguments values
" 4.5 0.0
There were 5 command line arguments.
Command line arguments received are:
Argument 1: tring
Argument 2: multiple
Argument 3: arguments values
Argument 4: 4.5
Argument 5: 0.0
posted @ 2009-07-09 22:33 英勇的近卫军 阅读(1674) | 评论 (0) | 编辑 收藏
 
C++/CLI学习入门(五):跟踪句柄、跟踪引用及内部指针

与本地C++自己维护堆不同,C++/CLI中动态分配的内存是由CLR来维护的。当不需要堆时,CLR自动将其删除回收,同时CLR还能自动地压缩内存堆以避免产生不必要的内存碎片。这种机制能够避免内存泄露和内存碎片,被称为垃圾回收,而由CLR管理的这种堆被称为CLR堆。它由操作符gcnew创建。

由于垃圾回收机制会改变堆中对象的地址,因此不能在CLR堆中使用普通C++指针,因为如果指针指向的对象地址发生了变化,则指针将不再有效。为了能够安全地访问堆对象,CLR提供了跟踪句柄(类似于C++指针)和跟踪引用(类似于C++)引用。

一、跟踪句柄

跟踪句柄类似于本地C++指针,但能够被CLR垃圾回收器自动更新以反映被跟踪对象的新地址。同时不允许对跟踪句柄进行地址的算术运算,也不能够进行强制类型转换。

凡是在CLR堆上创建的对象必须被跟踪句柄引用,这些对象包括:(1)用gcnew操作符显示创建在堆上的对象;(2)所有的引用数据类型(数值类型默认分配在堆栈上)。注意:所有分配在堆上的对象都不能在全局范围内被创建。

关于跟踪句柄的相关使用方法参见《C++/CLI学习入门(三):数组》

二、跟踪引用

跟踪引用类似于本地C++引用,表示某对象的别名。可以给堆栈上的值对象、CLR堆上的跟踪句柄创建跟踪引用。跟踪引用本身总是在堆栈上创建的。如果垃圾回收移动了被引用的对象,则跟踪引用将被自动更新。

跟踪引用用%来定义,下面的例子创建了一个对堆栈上值对象的跟踪引用:

int value = 10;
int% trackValue = value;

stackValue为value变量的引用,可以用stackValue来访问value:

trackValue *= 5;
Console::WriteLine(value);	// Result is 50

三、内部指针

C++/CLI还提供一种用关键字interior_ptr定义的内部指针,它允许进行地址的算术操作。必要时,该指针内存储的地址会由CLR垃圾回收自动更新。注意,内部指针总是函数的局部自动变量。

下面的代码定义了一个内部指针,它含有某数组中第一个元素的地址:

array<double>^ data = {1.5, 3.5, 6.7, 4.2, 2.1};
interior_ptr<double> pstart = %data[0];

必须给interio_ptr指定内部指针指向的对象类型。此外还应该给指针进行初始化,如果不提供初始值,系统将其默认初始化为nullptr。

内部指针在指定类型时应注意:可以包含堆栈上值类型对象的地址,也可以包含指向CLR堆上某对象句柄的地址,还可以是本地类对象或本地指针,但不能是CLR堆上整个对象的地址。也就是说,可以使用内部指针存储作为CLR堆上对象组成部分的数值类对象(如CLR数组元素)的地址,也可以存储System::String对象跟踪句柄的地址,但不能存储String对象本身的地址。

interior_ptr<String^> pstr1;	// OK -- pointer to a handle
interior_ptr<String>  pstr2;	// ERROR -- pointer to a String object

与本地C++指针一样,内部指针可以进行算术计算。可以通过递增或递减来改变其包含的地址,从而引用后面或前面的数据项;还可在内部指针上加上或减去某个整数;可以比较内部指针。下面的例子展示了内部指针的用法:

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex4_19.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex4_19.cpp : main project file.
#include "stdafx.h"
using namespace System;

int main(array<System::String ^> ^args)
{
	array<double>^ data = {1.5, 3.5, 6.7, 4.2, 2.1};
	interior_ptr<double> pstart = &data[0];
	interior_ptr<double> pend = &data[data->Length - 1];
	double sum = 0;

	while(pstart<=pend)
		sum += *pstart++;

	Console::WriteLine(L"Total of data array elements = {0}\n", sum);

	array<String^>^ strings = { L"Land ahoy!",
		L"Splice the mainbrace!",
		L"Shiver me timbers!",
		L"Never throw into the wind!"
	};

	for(interior_ptr<String^> pstrings = &strings[0]; pstrings-&strings[0] < strings->Length; ++pstrings)
		Console::WriteLine(*pstrings);

    return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex4_19.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

输出为

Total of data array elements = 18

Land ahoy!
Splice the mainbrace!
Shiver me timbers!
Never throw into the wind!
posted @ 2009-07-09 21:47 英勇的近卫军 阅读(2514) | 评论 (0) | 编辑 收藏
 

2009年7月7日

C++/CLI学习入门(四):字符串

C++/CLI字符串(Unicode字符组成的字符串)是指在System命名空间中定义的String类,即由System:Char类型的字符序列组成的字符串。它包含大量强大的功能,使得字符串的处理非常容易。创建一个String对象的方法如下例所示:

System::String^ saying = L"Many hands make light work.";

跟踪句柄saying用于访问String类对象。该对象的字符为宽字符,因为采用了前缀 “L”,如果省略“L”,该字符串由8位的字符组成,编译器将确保将其转换成宽字符。

访问字符串内字符可以像访问数组元素一样,使用索引来访问,首字符的索引为0。这种方法只能用于读取字符串内字符,但不能用于修改字符串的内容。

Console::WriteLine("The third character in the string is {0}", saying[2]);

利用Length属性,可以获取字符串内字符的数量(长度)。

Console::WriteLine("The saying has {0} charactors.", saying->Length);

一、连接字符串

利用 “+”可以连接字符串,形成新的字符串。执行下面的例子之后,name3将包含字符串 “Beth and Betty”。

String^ name1 = L"Beth";
String^ name2 = L"Betty";
String^ name3 = name1+L" and "+name2;

“+”还可以用来连接字符串与数值、bool值等非字符串变量,在连接之前,这些变量将自动的转换成字符串。

String^ str = L"Value: ";
String^ str1 = str + 2.5;	// str1 is "Value: 2.5"
String^ str2 = str + 25;	// str2 is "Value: 25"
String^ str3 = str + true;	// str3 is "Value: True"

“+”还可以用来连接字符串与字符,但要注意,结果字符串的形式取决于字符的类型。这是因为char类型的字符被视为数值,wchar_t与String对象的字符具有相同的类型(Char类型)。

char ch = 'Z';
wchar_t wch = 'Z';
String^ str4 = str + ch;	// str4 is "Value: 90"
String^ str5 = str + wch;	// str5 is "Value: Z"

String类型定义了Join()函数,用于将数组中的多个字符串连接成一个字符串,数组元素之间用分隔符隔开,如

array<String^>^ names = {"Jill", "Ted", "Mary", "Eve", "Bill"};
String^ seperator = " and ";
String^ joined = String::Join(seperator, names);	// joined is "Jill and Ted and Mary and Eve and Bill"

特别注意:String对象是固定不变的,一旦创建完毕后就不能再被修改了。这意味着所有的字符串操作都是在创建新的字符串。

下面的例子将整数数组内的元素按列整齐地输出。

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex4_17.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex4_17.cpp : main project file.
#include "stdafx.h"

using namespace System;

int main(array<System::String ^> ^args)
{
	array<int>^ values = { 2, 456, 23, -46, 34211, 456, 5609, 112098, 234,
		-76504, 341, 6788, -909121, 99, 10 };
	String^ formatStr1 = "{0, ";
	String^ formatStr2 = "}";
	String^ number;

	int maxLength = 0;
	for each(int value in values)
	{
		number = ""+value;
		if(maxLength<number->Length)
			maxLength = number->Length;
	}

	String^ format = formatStr1+(maxLength+1)+formatStr2;

	int numberPerLine = 3;
	for(int i=0; i<values->Length; i++)
	{
		Console::Write(format, values[i]);
		if((i+1)%numberPerLine == 0)
			Console::WriteLine();
	}
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex4_17.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

输出为

       2     456      23
     -46   34211     456
    5609  112098     234
  -76504     341    6788
 -909121      99      10

二、修改字符串

Trim()函数用于删除字符串头部和尾部的空格。不带参数调用该函数将删除字符串头、尾部的全部空格并返回一新字符串。

String^ str = {" Handsome is as handsome does...     "};
String^ newStr = str->Trim();

也可传递给Trim()函数字符数组作为参数,字符串将从头部和尾部开始删除数组中的字符。如果字符出现在字符串中间,则不会被删除。

String^ toBeTrimed = L"wool wool sheep sheep wool wool wool";
array<wchar_t>^ notWanted = {L'w', L'o', L'l', L' ' };
Console::WriteLine(toBeTrimed->Trim(notWanted));

上面的语句将输出
sheep sheep

如果在上面的语句中没有加前缀”L“,则字符为char类型,对应于System::SByte类型。不过编译器将自动地将其转换成wchar_t类型(即System::Char类型)。

Trim()函数也支持直接输入要删除的字符列表,下面的语句将产生同样的输出

Console::WriteLine(toBeTrimed->Trim(L'w', L'o', L'l', L' '));

如果仅仅想要删除头部或者尾部中的一端,可以使用TrimStart或者TrimEnd函数。

如果要在字符串的一端填充空格或其它字符(这一般用于以固定宽度靠左或靠右对齐输出文本),可使用PadLeft()和PadRight()函数。如果字符串长度大于指定的长度参数,则返回字符串为长度等于原来字符串的新字符串。

String^ value = L"3.142";
String^ leftPadded = value->PadLeft(10);	// Result is "    3.142"
String^ rightPadded = value->PadRight(10);	// Result is "3.142    "
String^ leftPadded2 = value->PadLeft(10, L'*');	// Result is "*****3.142"
String^ rightPadded2= value->PadRight(10,L'#');	// Result is "3.142#####"

如果需要将字符串转换成大写或小写,可使用ToUpper()或ToLower函数。

String^ proverb = L"Many hands make light work."
String^ upper = proverb->ToUpper();	// Result is "MANY HANDS MAKE LIGHT WORK."

如果需要在字符串中间插入一个字符串,可使用Insert()函数,第一个参数指定起始位置的索引,第二个参数指定要插入的字符串。

String^ proverb = L"Many hands light work.";
String^ newProverb = proverb->Insert(5, "deck ");

结果是

Many deck hands make light work.

如果要用另一个字符替换字符串中指定的字符,或者用另一个子串替换字符串中给定的子串,可使用Replace()函数。

String^ proverb = L"Many hands make light work."
Console::WriteLine(proverb->Replace(L' ', L'*');
Console::WriteLine(proverb->Replace(L"Many hands", L"Press switch");

输出为

Many*hands*make*light*work.
Pressing switch make light work.

三、搜索字符串

如果需要测试字符串是否以给定的子串开始或结束,可使用StartWith()或EndWith()函数。要寻找的子串句柄作为参数传递给函数,返回bool值。

String^ snetence = L"Hide, the cow's outside.";
if(sentence->StartWith(L"Hide"))
        Console::WriteLine("The sentence starts with 'Hide'.");

IndexOf()函数用于返回给定字符或子串在字符串中找到的第一个实例索引,如果未找到,则返回-1。

String^ sentence = L"Hide, the cow's outside.";
int ePosition = sentence->IndexOf(L'e');	// Return 3
int thePosition = sentence->IndexOf(L"the");	// Retuen 6

也可以指定IndexOf搜索的起始索引,这一般用于遍历整个字符串找出所有的实例,如下面的例子:

int index = 0;
int count = 0;
while((index=words->IndexOf(word, index))>=0)
{
    index += word->Length;
    count++;
}
Console::WriteLine(L"'{0}' was found {1} times in: {2}", word, count, words);

LastIndexOf()函数类似于IndexOf()函数,不过它用于从字符串尾部或指定索引位置开始,倒着向头部搜索。注意:如果从尾部开始的索引值是words->Lenght-1。

如果要搜索一个字符串数组中任意元素出现在字符串中的位置,可以使用IndexOfAny()函数。同样,它也有倒序搜索的版本。 下面的例子说明了IndexOfAny()的用法。下面的例子用于搜索字符串中的标点符号

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex4_18.CPP] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex4_18.cpp : main project file.
#include "stdafx.h"

using namespace System;

int main(array<System::String ^> ^args)
{
	array<wchar_t>^ punctuation = {L'"', L'\'', L'.', L',', L':',L'!', L'?'};
	String^ sentence = L"\"It's chilly in here\", the boy 's mother said coldly.";

	array<wchar_t>^ indicators = gcnew array<wchar_t>(sentence->Length){L' '};

	int index = 0;
	int count = 0;
	while((index=sentence->IndexOfAny(punctuation, index))>=0)
	{
		indicators[index] = L'^';
		++index;
		++count;
	}
	Console::WriteLine(L"There are {0} punctuation charactors in the string:", count);
	Console::WriteLine(L"\n{0}\n{1}", sentence, gcnew String(indicators));
    return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex4_18.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
输出为
There are 6 punctuation charactors in the string:
"It's chilly in here", the boy 's mother said coldly.
^  ^                ^^         ^                    ^
posted @ 2009-07-07 23:39 英勇的近卫军 阅读(1744) | 评论 (0) | 编辑 收藏
 

2009年6月29日

C++/CLI学习入门(三):数组

要学习数组,必须先了解跟踪句柄。

一、跟踪句柄

跟踪句柄类似于本地C++指针,但也有很大区别。跟踪句柄确实存储着某个对象的地址,但当CLR压缩堆过程中改变了该对象的地址,则垃圾回收器自动更新句柄所包含的地址。我们不能像本地指针那样用跟踪句柄来执行地址的算术运算,也不允许对跟踪句柄进行强制类型转换。

在CLR堆中创建的对象必须被跟踪句柄引用。所有属于引用类型的对象都存储在堆中,因此为引用这些对象所创建的变量都必须是跟踪句柄。例如,String类型是引用类型,因此引用String对象的变量必须是跟踪句柄。值类型默认分配在堆栈上,但也可以用gcnew操作符将其存储在堆上。此外必须注意,在堆上分配的变量——其中包括所有CLR引用类型——都不能在全局作用域内声明。

通过在类型名称后加”^”符号,用于声明一个该类型的句柄。下面的例子声明了一个String类型的跟踪句柄proverb。

String^ proverb;

在声明时句柄时,系统自动为其分配一个空值,该句柄不引用任何对象。也可显示地将某个句柄置为空值:

proverb = nullptr;

注意不能用0来表示空值。如果用0来初始化某个句柄,则0将自动转换为被引用类型的对象,而句柄则指向该对象。可以在声明句柄时显示的将其初始化:

String^ saying = L"I used to think I was indecisive but now I??¡ê¡èm not so sure."; 

该语句首先在堆上创建一个包含等号右边字符串的String对象,然后将该对象的地址存入saying中。注意字符串字面值的类型为const wchar_t*而非String。类String提供了这样的方法使得const wchar_t*类型的字符串可以用来创建String类型的对象。

下面这条语句创建了值类型的句柄:

int^ value = 99; 

      该语句在堆上创建一个Int32型的值类型变量,然后将该变量的地址存入句柄value中。由于value是一种指针,因此不能直接参与算术运算,可使用*运算符对地址求值(类似于本地C++指针那样):

int result = 2*(*value)+15;

      由于*value表示value所指向地址存储的数值,因此result的值为2*99+15=213。注意,当value作为运算式左值时,不需要*即可对value指向的变量赋值。

int^ result = 0;
result = 2*(*value)+15;

      首先创建了一个指向数值0的句柄result。(该语句会触发一条编译器警告,提示不能利用0来将句柄初始化为空值。)

      第2条语句=号右边为数值,而左边为句柄,编译器将自动将右值赋予句柄所指向的对象,即将其转换为如下语句

*result = 2*(*value)+15;

       注意,要采用上面的语句,result句柄必须实际定义过。如果仅仅声明了result,则会产生运行时错误

int^ result;
*result = 2*(*value)+15;

       这是因为第二句要对地址result求值,即意味着result指向的对象已经存在,但实际并非如此,因为声明该对象时系统默认赋予其空值(nullptr)。在这种情况下,采用下面的方法就可以正常工作了

int^ result;
result = 2*(*value)+15; 

二、数组

(一)数组句柄

       CLR数组是分配在可回收垃圾堆上的。必须用array<typename>指出要创建的数组,同其它CLR堆上的对象一样,需要用句柄来访问它,例子如下:

array<int>^ data;

      数组句柄data可用于存储对元素类型为int的一维数组的引用。下面的例子声明了一个句柄,并新建一个CLR数组来对此句柄初始化。

array<int>^ data = gcnew array<int>(100); 

       和本地C++数组一样,CLR数组中元素的索引值也是从0开始的,可以通过[ ]访问数组元素。数组元素都是CLR对象,在上面的例子中数组元素为Int32型对象,它们在算术表达式中就像普通的整数类型一样。

       Length属性是数组的一个重要属性,记录着数组元素的数量。保存了64位的数组长度。

for(int i=0; i<data->Length; i++)
	data[i] = 2*(i+1);

可以用for each循环遍历数组元素。

array<int>^ value = {3, 5, 6, 8, 6};
for each(int item in value)
{
	item = 2*item + 1;
	Console::WriteLine("{0, 5}", item);
}

该循环输出5字符宽度的字段,以右对齐的方式输出当前元素的计算结果,输出如下:

    7   11   13   17   13 

       数组句柄可以被重新赋值,只要保持数组元素类型和维数(等级)不变即可,在前面例子中的数组句柄data指向一个int类型的一维数组,可以重新给它赋值,使其指向另外的int类型1维数组:

data = gcnew array<int>(45);

       数组可以在创建时通过元素列表初始化,下例在CLR堆上创建了一个double类型的数组,并将引用赋值给了数组句柄:

array<double>^ sample = {3.4, 2.3, 6.8, 1.2, 5.5, 4.9, 7.4, 1.6};

       如果在声明数组句柄时不进行初始化,那么在给句柄赋值时不能采用上面的方法直接用元素列表用作右值,而必须采用显示创建的方式。即不能

array<double>^ sample;
sample = {3.4, 2.3, 6.8, 1.2, 5.5, 4.9, 7.4, 1.6}

       而必须采用如下方式

array<double>^ sample;
sample = gcnew array<double>{3.4, 2.3, 6.8, 1.2, 5.5, 4.9, 7.4, 1.6}

       对于字符串数组,注意每一个元素也是引用类型,这是因为每一个元素String也是在CLR堆上创建的,因此也要用String^来访问它。

array<String^>^ names = {"Jack", "John", "Joe", "Jessica", "Jim", "Joanna"};

       可以用Array类静态函数Clear()对数组中的连续数组元素清零。

Array::Clear(samples, 0, samples->Length);

       Clear()函数的第一个参数是被清零的数组,第二个参数是要清除地第一个元素的索引,第三个参数为要清除地元素数量。因此上述语句将samples数组的所有元素都置为0。如果Clear()清除的是某个跟踪句柄,则句柄所对应的元素都被应用Clear()函数。如果元素为bool型,则被置为false。

(二)数组排序

Array类还定义了一个Sort()静态函数,可用于对数组进行排序。如果以数组句柄作为参数,则对整个数组排序。如果要对数组部分排序,则还需要增加元素起始索引及数量,如下例

array<int>^ samples = {27, 3, 54, 11, 18, 2, 16};
Array::Sort(samples, 2, 3);

排序后数组元素变为{27, 3, 11, 18, 54, 2, 16}

Sort函数还有很多其它版本,下面的例子展示了如何排序两个相关的数组,即第一个数组中的元素是第二个数组对应元素的键。对第一个数组排序后,可对第二个数组进行相应的调整,使得键与值相互对应。

Sort()函数对2个数组排序时,用第一个数组参数来确定两个数组的顺序,以此保持2个数组元素对应关系不变。

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex4_13.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex4_13.cpp : main project file.
#include "stdafx.h"
using namespace System;

int main(array<System::String ^> ^args)
{
	array<String^>^ names = { "Jill", "Ted", "Mary", "Eve", "Bill", "Al" };
	array<int>^ weights = {103, 168, 128, 115, 180, 176};

	Array::Sort(names, weights);
	for each( String^ name in names )
		Console::Write(L"{0, 10}", name);
	Console::WriteLine();
	
	for each(int weight in weights)
		Console::Write(L"{0, 10}", weight);
	Console::WriteLine();
	
	return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex4_13.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
输出为: 
Al Bill Eve Jill Mary Ted 
176 180 115 103 128 168 

(三)数组搜索

Array类还提供了函数BinarySearch()以使用对分法搜索算法,对一维数组或给定范围内搜索特定元素的索引位置。使用该函数要求数组必须是顺序排列的,因此在搜索之前必须对数组进行排序。

array<int>^ value = { 23, 45, 68, 94, 123, 150, 203, 299 };
int toBeFound = 127;
int position = Array::BinarySearch(value, toBeFound);
if(position<0)
        Console::WriteLine(L"{0} was not found.", toBeFound);
else
        Console::WriteLine(L"{0} was found at index position {1}", toBeFound, position);

Array::BinarySearch()的第一个参数是被搜索数组的句柄,第二个参数是要查找的内容,返回值为int类型的数值。如果返回值小于0则说明未找到。如果要指定搜索范围,则需要传递4个参数,其中第2参数为搜索起始索引,第3参数为搜索的元素数量,第4个是要搜索的内容。下面的代码从第4个元素开始,一直搜索到结束位置。

array<int>^ value = { 23, 45, 68, 94, 123, 150, 203, 299 };
int toBeFound = 127;
int position = Array::BinarySearch(value, 3, 6, toBeFound);    
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex4_14.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex4_14.cpp : main project file.
#include "stdafx.h"
using namespace System;

int main(array<System::String ^> ^args)
{
	array<String^>^ names = { "Jill", "Ted", "Mary", "Eve", "Bill", "Al", "Ned", "Zoe", "Dan", "Jean" };
	array<int>^ weights = {103, 168, 128, 115, 180, 176, 209, 98, 190, 130};
	array<String^>^ toBeFound = {"Bill", "Eve", "Al", "Fred"};
	
	int result = 0;
	Array::Sort(names, weights);

	for each( String^ name in toBeFound )
	{
		result = Array::BinarySearch(names, name);
		if(result<0)
			Console::WriteLine(L"{0} was not found.", name);
		else
			Console::WriteLine(L"{0} weights {1} lbs.", name, weights[result]);
	}

	return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex4_14.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

        当搜索不到目标时,Array::BinarySearch()函数输出的并非任意负数,而是第一个大于该目标的元素索引值的按位补码。利用该方法,可以不打乱顺序在数组中插入新值。如,我们希望插入”Fred”到names数组中

array<String^>^ names = { "Jill", "Ted", "Mary", "Eve", "Bill", "Al", "Ned", "Zoe", "Dan", "Jean" }
Array::Sort(names);
String^ name = L"Fred";
int position = Array::BinarySearch(names, name);
if(position<0)
position = ~position;

此时,position保存的是大于Fred的第一个元素的位置,该数值可用于插入新值。

array<String^>^ newNames = gcnew array<String^>(names->Length+1);
for(int i=0;i<position;i++)
	newNames[i] = names[i];
newNames[position] = name;

if(position<name->Length)
	for(int i=position; i<names->Length; i++)
		newNames[i+1] = names[i];
names = nullptr;

注意:最后一句用于删除names数组。

(四)多维数组

C++/CLI中可以创建多维数组,最大维数32维。与ISO/ANSI C++不同的是,C++/CLI中的多维数组并非数组的数组,而是真正的多维数组,创建整数多维数组方法如下:

array<int 2>^ value = gcnew array<int, 2>(4, 5);

上面的代码创建了一个二维数组,四行五列,共20个元素。访问的方法是利用多个用逗号分隔的索引值来访问每一个元素,而不能用一个索引值访问一行

int nrows = 4;
int ncols = 5;
array<int, 2>^ value = gcnew array<int, 2>(nrows, ncols);
for(int i=0; i<nrows; i++)
    for(int j=0; j<ncols; j++)
        value[i, j] = (i+1)*(j+1);

上面的代码利用循环给二维数组value赋值。这里访问二维数组元素的符号与本地C++不同:后者实际上是数组的数组,而C++/CLI是真正的二维数组,不能用一个索引值来访问二维数组,那样是没有意义的。数组的维数被称为等级,上面value数组的等级为2。而本地C++数组的等级始终为1。当然,在C++/CLI中也可以定义数组的数组,方法见下例

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex4_15.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex4_15.cpp : main project file.
#include "stdafx.h"
using namespace System;

int main(array<System::String ^> ^args)
{
	const int SIZE = 12;
	array<int, 2>^ products = gcnew array<int, 2>(SIZE, SIZE);
	
	for(int i=0; i<SIZE; i++)
		for(int j=0; j<SIZE; j++)
			products[i, j] = (i+1)*(j+1);
	Console::WriteLine(L"Here is the {0} times table:", SIZE);

	// Write horizontal divider line
	for(int i=0; i<=SIZE; i++)
		Console::Write(L"_____");
	Console::WriteLine();

	// Write top line of table
	Console::Write(L"    |");
	for(int i=1; i<=SIZE; i++)
		Console::Write("{0, 3} |", i);
	Console::WriteLine();

	// Write horizontal divider line with verticals
	for(int i=0; i<=SIZE; i++)
		Console::Write("____|", i);
	Console::WriteLine();

	// Write remaining lines
	for(int i=0; i<SIZE; i++)
	{
		Console::Write(L"{0, 3} |", i+1);
		for(int j=0; j<SIZE; j++)
			Console::Write("{0, 3} |", products[i, j]);
		Console::WriteLine();
	}

	// Write horizontal divider line
	for(int i=0; i<=SIZE; i++)
		Console::Write("_____", i);
	Console::WriteLine();
	return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex4_15.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

上面的例子创建了一个12x12的乘法表,输出如下:

Here is the 12 times table:
_________________________________________________________________
    |  1 |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 | 10 | 11 | 12 |
____|____|____|____|____|____|____|____|____|____|____|____|____|
  1 |  1 |  2 |  3 |  4 |  5 |  6 |  7 |  8 |  9 | 10 | 11 | 12 |
  2 |  2 |  4 |  6 |  8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 |
  3 |  3 |  6 |  9 | 12 | 15 | 18 | 21 | 24 | 27 | 30 | 33 | 36 |
  4 |  4 |  8 | 12 | 16 | 20 | 24 | 28 | 32 | 36 | 40 | 44 | 48 |
  5 |  5 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 |
  6 |  6 | 12 | 18 | 24 | 30 | 36 | 42 | 48 | 54 | 60 | 66 | 72 |
  7 |  7 | 14 | 21 | 28 | 35 | 42 | 49 | 56 | 63 | 70 | 77 | 84 |
  8 |  8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 80 | 88 | 96 |
  9 |  9 | 18 | 27 | 36 | 45 | 54 | 63 | 72 | 81 | 90 | 99 |108 |
 10 | 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 |100 |110 |120 |
 11 | 11 | 22 | 33 | 44 | 55 | 66 | 77 | 88 | 99 |110 |121 |132 |
 12 | 12 | 24 | 36 | 48 | 60 | 72 | 84 | 96 |108 |120 |132 |144 |
_________________________________________________________________

其中创建二维数组的代码如下:

const int SIZE = 12;
array<int, 2>^ products = gcnew array<int, 2>(SIZE, SIZE);

第一行定义了一个整型常量SIZE,用于指定每一维数组的元素数量,第二行代码定义了一个等级2的数组,为12x12大小,该数组用于存储12x12的乘法表乘积。然后在嵌套循环中给数组赋值,大部分代码用于格式化输出以使其更加美观,这里就不再说明。

(五)数组的数组

如果数组的元素是引用数组的跟踪句柄,那么就可以创建数组的数组。同时,每一维数组的长度可以不同,即所谓的“锯齿形数组”。例如,用ABCDE来表示学生的成绩等级,根据等级分组存储班内学生的姓名,则可以创建一个包含5个元素的数组,每个元素为一个姓名数组(即字符串数组)

array<array<String ^>^>^ grades = gcnew array<array<String^>^>(5)

利用上面创建的数组,然后可以创建5个姓名数组了

grades[0] = gcnew array<String^>{"Louise", "Jack"};
grades[1] = gcnew array<String^>{"Bill", "Mary", "Ben", "Joan"};
grades[2] = gcnew array<String^>{"Jill", "Will", "Phil"};
grades[3] = gcnew array<String^>{"Ned", "Fred", "Ted", "Jed", "Ed"};
grades[4] = gcnew array<String^>{"Dan", "Ann"};

grades[n]访问grades数组的第n个元素,而各元素为指向String^类型数组的句柄,因此上面的语句用于创建了String对象句柄的数组,并将创建数组的地址赋值给了grades数组元素。同时,这些字符串数组的长度是不同的。

上面的语句也可以用一个初始化语句来实现

array<array<String^>^>^ grades = gcnew array<array<String^>^>
{
    gcnew array<String^>{"Louise", "Jack"},
    gcnew array<String^>{"Bill", "Maray", "Ben", "Joan"},
    gcnew array<String^>{"Jill", "Will", "Phil"},
    gcnew array<String^>{"Ned", "Fred", "Ted", "Jed", "Ed"},
    gcnew array<String^>{"Dan", "Ann"},
};

注意:元素的初值必须写在花括号里。

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex4_16.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex4_16.cpp : main project file.
#include "stdafx.h"
using namespace System;

int main(array<System::String ^> ^args)
{
	array<array<String^>^>^ grades = gcnew array<array<String^>^>
		{
			gcnew array<String^>{"Louise", "Jack"},
			gcnew array<String^>{"Bill", "Maray", "Ben", "Joan"},
			gcnew array<String^>{"Jill", "Will", "Phil"},
			gcnew array<String^>{"Ned", "Fred", "Ted", "Jed", "Ed"},
			gcnew array<String^>{"Dan", "Ann"}
		};
	
	wchar_t gradeLetter = 'A';
	for each(array<String^>^ grade in grades)
	{
		Console::WriteLine(L"Students with Grade {0}:", gradeLetter++);
		for each(String^ student in grade)
			Console::Write("{0, 12}", student);
		Console::WriteLine();
	}
	return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex4_16.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

输出为

Students with Grade A:
      Louise        Jack
Students with Grade B:
        Bill       Maray         Ben        Joan
Students with Grade C:
        Jill        Will        Phil
Students with Grade D:
         Ned        Fred         Ted         Jed          Ed
Students with Grade E:
         Dan         Ann
posted @ 2009-06-29 01:34 英勇的近卫军 阅读(4638) | 评论 (0) | 编辑 收藏
 

2009年6月27日

C++/CLI学习入门(二):控制与循环

一、基本控制结构

ISO/ANSI C++中的控制与循环全部适用于C++/CLI。下例展示了C++/CLI控制台程序中的控制循环:

例子:基本循环控制

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex3_15.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex3_15.cpp : main project file.
#include "stdafx.h"

using namespace System;

int main(array<System::String ^> ^args)
{
    wchar_t letter;

    Console::Write(L"Enter a letter:");
    letter = Console::Read();

    if(letter>='A')
        if(letter<='Z')
        {
            Console::WriteLine(L"You entered a captial letter.");
            return 0;
        }

    if(letter>='a')
        if(letter<='z')
        {
            Console::WriteLine(L"You entered a small letter.");
            return 0;
        }

    Console::WriteLine(L"You did not enter a letter.");
    return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex3_15.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

letter被声明为wchar_t类型,映射为C++/CLI中的System::Char类型,它具有一些特殊的功能,其中包括将字符代码转换为大写和小写的函数:Char::ToUpper()和Char::ToLower(),被转换的函数作为参数被传递给它:

wchar_t uppercaseLetter = Char::ToUpper(letter);

此外还包括检测字母是否大写或小写的函数:IsUpper()和IsLower(),因此上例可改为

wchar_t letter; 
wchar_t upper; 

Console::Write(L"Enter a letter:"); 
letter = Console::Read(); 
upper = Char::ToUpper(letter); 

if(upper>='A' && upper<='Z') 
    Console::WriteLine(L"You entered a {0} letter.", Char::IsUpper(letter) ? "Capital":"Small"); 
else 
    Console::WriteLine(L"You entered a small letter.");

Console::ReadKey()函数用于测试按键,并将结果存储在ConsoleKeyInfo类对象里。该类有3个可访问属性用于帮助确定被按下的键是哪个或哪些键。属性Key识别被按下的键是哪个,属性KeyChar是被按键的Unicode字符码,属性Modifiers表示Shift,Alt,Ctrl键的按位组合,它是定义在System命名空间中的枚举类型ConsoleModifiers的常量,包括Shift\Alt\Control。

应该注意的是,在C++/CLI中枚举常量在用作数值之前必须被显示的强制转换为值类型(整数类型)

例子:使用Console::ReadKey()函数

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex3_16.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex3_16.cpp : main project file.

#include "stdafx.h"

using namespace System;

int main(array<System::String ^> ^args)
{
	ConsoleKeyInfo keyPress;

	Console::WriteLine(L"Press a key combination - press Escape to quit.");
	do{
		keyPress = Console::ReadKey(true);
		Console::Write(L"You pressed");
		if(safe_cast<int>(keyPress.Modifiers)>0)
			Console::Write(L" {0}", keyPress.Modifiers);
		Console::WriteLine(L" {0} which is the {1} character", keyPress.Key, keyPress.KeyChar);
	}while(keyPress.Key != ConsoleKey::Escape);
    return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex3_16.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

该程序的输入示例如下:

Press a key combination - press Escape to quit.
You pressed Enter which is the  character
You pressed Spacebar which is the   character
You pressed Spacebar which is the   character

从输出中可以看出,当不只一个键被按下时,用一条语句就可以得到所有的键。这是因为Modifiers枚举类型是用FlagsAttribute属性定义的,该属性表明这种枚举类型是一组唯一的位标志。这使得该枚举类型的变量可以由若干与在一起的标志位组成,而Write()或WriteLine()函数可以识别并输出各标志位。

二、for each循环

以例子开始

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex3_17.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex3_17.cpp : main project file.

#include "stdafx.h"

using namespace System;

int main(array<System::String ^> ^args)
{
	int volwels = 0;
	int consonants = 0;
	String^ proverb = L"A nod is as good as a wink to a blind horse.";

	for each(wchar_t ch in proverb)
	{
		if(Char::IsLetter(ch))
		{
			ch = Char::ToLower(ch);
			switch(ch)
			{
			case 'a': case 'e': case 'i': case 'o': case 'u':
				++volwels;
				break;

			default:
				++consonants;
				break;
			}
		}
	}

	Console::WriteLine(proverb);
	Console::WriteLine(L"The proverb contains {0} volwels and {1} consonants.",
		volwels, consonants);
    return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex3_17.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

输出为:

A nod is as good as a wink to a blind horse. 
The proverb contains 14 volwels and 18 consanants. 

注意:由于proverb字符串中的字符都是Unicode字符,因此用wchar_t(映射为Char类型)类型的变量来存储这些字符。变量ch为循环内的局部变量。

posted @ 2009-06-27 16:46 英勇的近卫军 阅读(1382) | 评论 (0) | 编辑 收藏
 
C++/CLI学习入门(一):基础

从今日开始,将前期学习《Visual C++ 2005入门经典》(Ivor Horton著 清华大学出版社出版)的相关笔记整理到随笔中,希望能和C++/CLI爱好者分享学习过程中的心得。文中主要内容和例子摘自原书相关章节,如有侵权,请留言或来信告知。

相比于ISO/ANSI C++而言,C++/CLI进行了大量的扩充,并且提供了大量的附加功能。主要包括:

  • 在C++/CLI程序中,所有ISO/ANSI基本数据类型都可以使用,但在一些特殊的上下文环境中,它们具有一些额外属性;
  • 在控制台程序中,C++/CLI对键盘和命令行输出提供了自己的机制;
  • C++/CLI中引入了safe_cast运算符,确保强制类型转换操作能够生成可检验的代码;
  • C++/CLI提供了另外一种基于类的枚举功能,其灵活性超过了ISO/ANSI C++中的enum声明。

一、基本数据类型

C++/CLI中包括了所有ISO/ASNI C++中的基本数据类型,算术运算也和本地C++完全一样。除此之外,C++/CLI中还定义了2种整数类型,如表1所示:

表1:C++/CLI新增基本数据类型

类型

字节

值域

long long

8

从-9223372036854775808到9223372036854775807

Unsigned long long

8

  从0到18446744073709551615

指定long long数据类型时,需要在整数数值后面加LL或小写字母ll,如

longlong big = 123456789LL;

指定unsinged long long类型时,需要在整数数值后面加ULL或小写字母ull,如

unsigned long long huge = 123456789LL;

在C++/CLI中,每一个ISO/ANSI C++基本类型名称都映射到System命名空间中定义的值类类型。在C++/CLI程序中,ISO/ANSI C++基本类型名称都是CLI中对应值类类型的简略形式。表2给出了基本类型、占用内存以及对应的值类类型。

表2:基本类型与CLI值类型

基本类型

字节

CLI值类类型

bool
char
singed char
unsigned char
short
unsigned short
int
unsigned int
long
unsigned long
long long
unsigned long long
float
double
long double
wchar_t

1
1
1
1
2
2
4
4
4
4
8
8
4
8
8
2

System::Boolean
System::SByte
System::SByte
System::Byte
System::Int16
System::UInt16
System::Int32
System::UInt32
System::Int32
System::UInt32
System::Int64
System::UInt64
System::Single
System::Double
System::Double
System::Char

默认情况下,char类型被视为singed char,因此其关联的值类类型为System::SByte。如果编译选项/J,则char 默认为unsigned char,此时关联为System::Byte。System为根命名空间名,C++/CLI的值类类型在这个空间中定义。此外System空间中还定义了许多其他类型,如表示字符串的String类型、精确存储的十进制小数类型Decimal等等。

在C++/CLI中,关联的值类类型为基本类型添加了重要的附加功能。编译器在需要时,将安排原值与关联类型之间的自动转换,其中从原值转换为关联类型成为装箱(boxing),反之称为拆箱(unboxing)。根据上下文环境,这些变量将表现为简单的值或者对象。

由于ISO/ANSI C++基本类型的名称是C++/CLI程序中值类类型名称的别名,所以原则上C++/CLI代码中可用任何一种名称。

int count = 10; 
double value = 2.5;

与下面的代码是等价的

System::Int32 count = 10; 
System::Double value = 2.5;

上面2种代码是完全合法的,但应尽量使用基本类型名称,如int和double,而不是System::Int32和System::Double。这是因为上面描述的这种映射关系仅适用于Visual C++ 2005及以上版本的编译器,其他版本编译器未必实现这种映射关系。

将基本类型转换为值类类型是C++/CLI的一个重要特征。在ISO/ANSI C++中基本类型与类类型完全不同,而在C++/CLI中,所有数据都以类类型的形式存储,包括值类型(存储在堆栈上)和引用类型(存储在堆上)2种。

例子:Fruit CLR控制台项目

在Visual Studio 2005中创建CLR Console Application项目,输入名称Ex2_12,将生成如下文件

// Ex2_12.cpp : main project file. 
#include "stdafx.h" 
using namespace System; 
int main(array<System::String ^> ^args) 
{ 
    Console::WriteLine(L"Hello World"); 
    return 0; 
}

main函数后的参数为命令行参数。然后按如下方式改写代码

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex_12.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex2_12.cpp : main project file. 
#include "stdafx.h" 
using namespace System; 
int main(array<System::String ^> ^args) 
{ 
    int apples, oranges; 
    int fruit; 

    apples = 5; 
    oranges = 6; 
    fruit = apples + oranges; 
    Console::WriteLine(L"\nOranges are not the only fruit ..."); 
    Console::Write(L"- and we have "); 
    Console::Write(fruit); 
    Console::Write(L" fruit in all.\n"); 

    return 0; 
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex_12.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

编译后执行得到如下输出:

Oranges are not the only fruit?- 
- and we have 11 fuit in all. 

与ISO/ANSI C++版本比较,变量的类型int将成为C++/CLI类型System::Int32。如果用System::Int32替换代码中的int,然后重新编译运行,结果将没有变化。

WriteLine()函数为C++/CLI函数,定义在System命名空间的Console类中。Console表示标准输入输出流。Write()函数为该类的另一个输出函数,不会自动换行。下面专门讨论C++/CLI的控制台输入输出。     

二、控制台输出

C++/CLI中特有控制台格式化输出功能,例如可用下面的代码来输出字符串与变量混合文本。

Console::WriteLine(L"There are {0} fruit.", fruit);

Console::WriteLine()的第一个参数是L”There are {0} fruit.”,其中{0}为格式化占位符,表示在此处插入第二个参数的值,如果有更多需要插入的参数,则该参数对应的占位符编号继续增加:{1}、{2}、{3}…。在第一个字符串参数中,编号的顺序可以颠倒,如

Console::WriteLine(L"There are {1} packages weighting {0} pounds", packageWeight, packageCount);

格式化占位符还可以控制显示的格式,如{1:F2}表示第2个参数显示成有2位小数的浮点数,冒号后的为格式规范

Console::WriteLine(L"There are {0} packages weighting {1:F2} pounds ", packageCount, packageWeight);

输出为

There are 25 packages weighting 7.50 pounds.       

一般说来,可以编写格式为{n,w:Axx}的格式规范,其中n为索引值,用于选择逗号后的第几个参数; A为单个字母,表示如何对变量格式化;xx为1个或2个数字,指定参数的精度;w为有符号整数,表示可选的字段宽度范围,如果w为+,则字段右对齐,如果w为-,则左对齐。如果数值的位置数小于w指定的位置数,则多出来的用空格填充,如果数值的位置数大于w指定的位置数,则忽略w的限定:

Console::WriteLine(L"Packages: {0,3} Weight: {1,5£ºF2} pounds ", packageCount, packageWeight);

输出为(下划线表示空格填充位):

Packages: _25 Weight: __7.50 pounds

可选的格式说明符如表3所示

表3:格式说明符

格式说明符

说明

C或c

把值作为货币量输出

D或d

把整数作为十进制值输出。如果指定的精度大于位数,则在数值的坐标填充0

E或e

按照科学技术法输出浮点值

F或f

把浮点数作为±####.##的定点数输出

G或g

以最紧凑的形式输出,取决于是否指定了精度值,如果没有则适用默认精度

N或n

把值作为定点十进制值输出,必要时以3位一组用逗号分隔

X或x

把整数作为十六进制值输出。根据X或x,以大写或小写输出。

例子:计算地毯价格,演示CLR控制台程序的格式化输出

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex_13.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex2_13.cpp : main project file.
#include "stdafx.h"
using namespace System;
int main(array<System::String ^> ^args)
{
	double carpetPriceSqYd = 27.95;
	double roomWidth = 13.5;			// in feet
	double roomLength = 24.75;			// in feet
	const int feetPerYard = 3;
	double roomWidthYard = roomWidth/feetPerYard;
	double roomLengthYard = roomLength/feetPerYard;
	double carpetPrice = roomWidthYard*roomLengthYard*carpetPriceSqYd;

	Console::WriteLine(L"Room is {0:F2} yards by {1:F2} yards",
		roomWidthYard, roomLengthYard);
	Console::WriteLine(L"Room area is {0:F2} square yards", 
		roomWidthYard*roomLengthYard);
	Console::WriteLine(L"Carpet price is ${0:F2}", carpetPrice);
	return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex_13.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

三、控制台输入

.Net Framework的控制台键盘输入功能有限,可以适用Console::ReadLine()函数把整行输入作为字符串读取,或者使用Console::Read()读取单个字符,还可以适用Console::ReadKey()读取按键。ReadLine()的例子如下:

ReadLine()用于将整行文本存入字符串中,按下Enter键时,文本结束。变量line为String^型,表示String数据类型的引用,line为Console::ReadLine()函数读入字符串的引用。

String^ line = Console::ReadLine();

Read()用于逐字符的读入输入数据,并将其转换成对应的数字值。ReadKey的例子如下:

char ch = Console::Read();

ReadKey()用于读取按键值,并返回ConsoleKeyInfo对象,该为对象定义在System命名空间中的值类型。参数true表示按键不在命令行上显示出来,false则表示显示按键回显。按键对应的字符可用ConsoleKeyInfo对象的KeyChar得到。

ConsoleKeyInfo keyPressed = Console::ReadKey(true);
Console::WriteLine(L"The key press corresponds to the character: {0}", keyPress.KeyChar);

尽管C++/CLI控制台程序中不能格式化输入,但输入一般都通过窗口组件得到,因此这仅仅是一个小缺陷。

四、强制类型转换safe_cast

在CLR环境中safe_cast用于显示的强制类型转换。safe_cast用于将一种类型转换为另一种类型,在不成功时能够抛出异常,因此在C++/CLI中使用safe_cast是比较好的选择。其用法和static_cast一样:

double value1 = 10.5; 
double value2 = 15.5; 
int whole_number = safe_cast<int>(value1) + safe_cast<int>(value2);

五、枚举

C++/CLI的枚举与ISO/ANSI C++有较大的区别。下例为C++/CLI中的一个枚举类型:该语句定义了一个枚举类型Suit,该类型的变量只能被赋值枚举定义中的值,且必须用枚举类型名称限定枚举常数。

enum class Suit {Clubs, Diamonds, Hearts, Spades};
Suit suit = Suit::Diamonds;

注意class关键字跟在enum之后。说明该枚举类型为C++/CLI,该关键字还表明在定义中规定的常量: Clubs\Diamonds\Hearts\Spades都是类对象而非ISO/ANSI C++中的基本类型(整型)值。实际上,默认情况下这些都是Int32类型的对象。

由于C++/CLI枚举定义中的变量都是类对象,因此不能在函数内部定义。

(一)指定枚举常量的类型

枚举中常量的类型可以是下表中任一基本类型:

short

int

long

long long

signed char

char

unsigned short

unsigned int

unsigned long

unsigned long long

unsigned char

bool

要指定一个枚举常量的类型,可以在枚举类型名称之后写入常量类型名称(要用冒号隔开),下例枚举类型中的常量为Char类型,对应的基本类型为char。其中第一个常量默认情况下对应于代码值0,后面的依次递增。

enum class Face:char { Ace,Two,Three,Four,Five,Six,Seven,Eight,Nine,Ten,Jack,Queen,King};

(二)指定枚举常量的值

可以赋予枚举类型定义中的一个或全部常数对应的值,下例使得Ace获得1,Two获得2,其余依此类推,直到King=13。

enum class Face:char { Ace=1,Two,Three,Four,Five,Six,Seven,Eight,Nine,Ten,Jack,Queen,King};

如果想让Ace获得最大值,则可以如下定义:

enum class Face:Char { Ace=14,Two=2,Three,Four,Five,Six,Seven,Eight,Nine,Ten,Jack,Queen,King};

例子:使用枚举类型

- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::开始==>> [Ex_14.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Ex2_14.cpp : main project file.
#include "stdafx.h"
using namespace System;

enum class Suit {Clubs, Diamonds, Hearts, Spades};

int main(array<System::String ^> ^args)
{
	Suit suit = Suit::Clubs;
	int value = safe_cast<int>(suit);

	Console::WriteLine(L"Suit is {0} and the value is {1}", suit, value);
	suit = Suit::Diamonds;
	value = safe_cast<int>(suit);
	Console::WriteLine(L"Suit is {0} and the value is {1}", suit, value);
	suit = Suit::Hearts;
	value = safe_cast<int>(suit);
	Console::WriteLine(L"Suit is {0} and the value is {1}", suit, value);
	suit = Suit::Spades;
	value = safe_cast<int>(suit);
	Console::WriteLine(L"Suit is {0} and the value is {1}", suit, value);
    return 0;
}
- - - - - - - - - - - - - - - - <<== 华丽的分割线 ::结束==>> [Ex_14.cpp] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

该例子的输出为

Suit is Clubs and the value is 0 
Suit is Diamonds and the value is 1 
Suit is Hearts and the value is 2 
Suit is Spades and the value is 3 

例子说明

  • Suit为枚举类型,不能在函数main()内部定义,因此只能定义为全局作用域内。
  • 必须用类型名称Suit限定枚举常量,如Suit::Clubs,否则编译器将无法识别。
  • 变量suit的值为类对象,要获取其值必须显示的将其转换成int类型。
posted @ 2009-06-27 16:27 英勇的近卫军 阅读(6035) | 评论 (1) | 编辑 收藏
 
仅列出标题  下一页
随笔:12 文章:1 评论:3 引用:0
<2025年5月>
日一二三四五六
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

公告

这是最好的时代,也是最坏的时代; 这是智慧的年代,也是愚蠢的年代; 这是信仰的时期,也是怀疑的时期; 这是光明的季节,也是黑暗的季节; 这是希望的春天,也是失望的冬天; 大伙儿面前应有尽有,大伙儿面前一无所有; 大伙儿正在直登天堂;大伙儿正在直落地狱。

常用链接

  • 我的随笔
  • 我的评论
  • 我参与的随笔

留言簿(3)

  • 给我留言
  • 查看公开留言
  • 查看私人留言

随笔分类

  • .Net Framework (rss)
  • C++/CLI(10) (rss)
  • 那就唠十块钱的吧(2) (rss)

随笔档案

  • 2009年7月 (7)
  • 2009年6月 (5)

文章档案

  • 2009年6月 (1)

相册

  • 长城炫丽
  • 东元3岁

收藏夹

  • 开发Visio解决方案(3) (rss)

时事直击

  • 全金属外壳
  • 如何翻墙?
  • 因为Gmail,picasa都没法用了,所以... ... GWF、绿爸爸、草泥马~~~~

搜索

  •  

最新评论

  • 1. re: C++/CLI学习入门(十):类的运算符重载
  • len.feet += len.inches/len.inchesPerFoot 里面的.(len.inchesPerFoot) 是为什么呢?
  • --esp
  • 2. re: C++/CLI学习入门(一):基础
  • 重新编排了格式,果然好看了许多阿
  • --英勇的近卫军
  • 3. re: 纯粹为了凑数!
  • 这也是一个测试,目的是检测评论是否好用。
  • --英勇的近卫军

阅读排行榜

  • 1. C++/CLI学习入门(一):基础(6035)
  • 2. C++/CLI学习入门(三):数组(4638)
  • 3. C++/CLI学习入门(五):跟踪句柄、跟踪引用及内部指针(2514)
  • 4. C++/CLI学习入门(十):类的运算符重载(1785)
  • 5. C++/CLI学习入门(四):字符串(1744)

评论排行榜

  • 1. C++/CLI学习入门(一):基础(1)
  • 2. 纯粹为了凑数!(1)
  • 3. C++/CLI学习入门(十):类的运算符重载(1)
  • 4. 这是一个通过Windows Live Writer发布的标题(0)
  • 5. C++/CLI学习入门(二):控制与循环(0)

Powered by: 博客园
模板提供:沪江博客
Copyright ©2025 英勇的近卫军