有些时候,一些很简单的事情实现起来并不轻易。例如,我们想让一个变量值显示在屏幕上。也许你知道在C++中怎么做,但在VC++6中,要用下面的方法实现:
int x = 3;
cout << "x is " << x << endl;
就这么简单。不论你学的“C++入门课”怎样,我打赌你能发现的与这两行代码相似的东西不足你在课程中学到的10%,对吗?
输出到屏幕
现在,假如要在VC++.NET中创建可治理的C++程序该怎么做?下面是我创建的main():
int _tmain(void)
{
// TODO: Please replace the sample code below
// with your own.
Console::WriteLine(S"Hello World");
return 0;
}
现在你可以把应用Cout的代码拷贝到main()中,在加入了include声明后,就可以执行:
#include <iostream.h>
// ...
Console::WriteLine(S"Hello World");
int x = 3;
cout << "x is " << x << endl;
这时,你会看到一个警告:
warning C4995: '_OLD_IOSTREAMS_ARE_DEPRECATED':
name was marked as #pragma deprecated
解决的方法:借用STL中的IO流的代码,并且导入std 名称空间:
#include <iostream>
using namespace std;
现在编译并运行这段代码。但是让我不解的是,在程序中发现作为cout应用的Console::WriteLine()。另外,Console::WriteLine很整洁。就像printf,它在字符串中使用占位符显示变量值应该放到哪。下面是一个c#控制程序中的代码:
int x = 3;
Console.WriteLine("x is {0}",x);
{0}是一个占位符,第二个参数的值截止到占位符出现的位置。因此我想像在c#中一样,在可治理的c++程序中一直使用Console::WriteLine。但是假如你把代码直接拷到c++程序中,并将.改为::,程序不能通过编译。错误显示为:
error C2665: 'System::Console::WriteLine' : none of the 19
overloads can convert parameter 2 from type 'int'
boXPin.cpp(7): could be 'void System::Console::WriteLine(
System::String __gc *,System::Object __gc *)'
boxpin.cpp(7): or 'void System::Console::WriteLine(
System::String __gc *,System::Object __gc * __gc[])'
while trying to match the argument list '(char [9], int)'
现在,我固执地希望c++能做其他.net语言能做的所有事情,甚至更多。为什么这么简单的办法行不通?没别的办法,看看错误提示吧。我给第二个参数赋一个整数值,它就像一个指针。事实上,是一类指向System::Object的指针(当然,还有由其衍生出的类),一类指向__gc object的指针。而这个整数值两种都不是。你可以试着传递&x值,而非x,那样至少是一个指针,但还是无济于事。
WriteLine()需要的是一个指向对象的指针。你不能直接将整数值传递给WriteLine(),因为它是(处于整体性的考虑)用来处理指向垃圾收集对象的指针,而不是其他的。为什么?基本类库中的所有内容都是针对对象设计的,因为他们都可有成员函数——并不是所有的.net语言都支持模式化或是过载模式化运算符的思想。比如,由System::Object继续下来的所有对象都有一个ToString() 方法。你不想为一个非对象的整数写一个类,然后又写一个ToString()来处理它,在每次将它传递给像WriteLine()基本类库中的方法的时候,还要把它放入(或取出)。这时,你怎么把整数传递给WriteLine()?
_box要害字
可治理的c++也被称为c++可治理的扩展。扩展是指额外要害字,都是以双下划线开头,并被增加到语言中。和其他所有以双下划线开始的要害字一样,他们的编译器是特定的——不要在vc++6和其他产商的编译器中试用。在编译WriteLine() 时,你只会在错误信息中看到_gc。它代表着垃圾收集并且指向一个依靠堆栈类型,并由运行时间控制的对象。_box要害字可以解决我在上面提到的,如何将整数传给基本类库方法,它得到的是System::Object _gc而不是一个整数。下面是它的使用方法:
Console::WriteLine("x is {0}",__box(x));
封装一个值的类别就是把值放到一个临时对象(这个对象是System::Object继续类的实例,存于垃圾收集堆)中,然后再把临时对象的地址传递给方法调用。原有变量中所有的东西都被拷入临时对象中,这个对象提供WriteLine()需要的所有功能。__box要害字意味着值类型和可治理类型都适合于基本类库提供的所有服务。
封装的替代办法
装箱答应你在期待指向可管指针的基本类库的方法中使用值类型和可治理类型。这自然产生了一个问题:在值类型和可治理类型中究竟有什么区别?可治理类型存于垃圾收集堆中,并且被运行时间所治理。下面是一个例子:
__gc class Foo
{
// internals omitted
};
// ...
Foo* f = new Foo();
FOO类是可治理类型。你不能在堆栈上创建Foo f2;这样的实例:
假如你已经有一个类(也许是从以前的.net程序中获得),它一定不是一个可治理类型。它没有_gc要害字。当然,你可以加上要害字(假设类符合成为一个可治理类型的所有条件),但接下来你要找到所有创建类实例的地址,还有保证他们是在堆上创建的实例,比如:
OldClass* poc = new OldClass(); //maybe some parameters
//to the constrUCtor
你要记住,在代码中调用类方法的每一处,都要把.改为->。保持原来的类型,这样你可以按照你的意愿在堆栈或未治理的堆上分配实例:
class notmanaged
{
private:
int val;
public:
notmanaged(int v) : val(v) {};
};
// ...
notmanaged nm(4);
notmanaged *p = new notmanaged(5);
这并不难:这就是还没发布加入可治理扩