函数使用
定义函数和使用函数基本与C#相同,只不过C++/CLI可以像标准C++一样,可以先声明函数原型,再定义函数主体。值得注意的是,如果有默认参数,只能在函数原型中定义,不能在函数主体中重复定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include "pch.h" using namespace System;
double GetResult(double a=1, double b=1);
double GetResult(double a, double b) { return a + b; }
int main(array<System::String^>^ args) { double r = GetResult(100); Console::WriteLine(r); return 0; }
|
C++/CLI中的语句如if、switch、while、for、do while
等,均与C#用法相同。
类和对象
定义类
和标准C++一样,在.h头文件中声明原型,在.cpp源文件中定义实现。使用::
表示C++作用域解析符,->
表示调用成员操作符。
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #pragma once ref class CreditCardAccount { public: CreditCardAccount(); CreditCardAccount(long number, double balance, double limit); static CreditCardAccount(); void SetCreditLimit(double amount); bool MakePurchase(double amount); void MakeRepayment(double amount); void PrintStatement(); long GetAccountNumber(); static int GetNumberOfAccounts(); private: long accountNumber; double currentBalance; double creditLimit; static int NumberOfAccounts; };
|
源文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include "pch.h" #include "CreditCardAccount.h" using namespace System;
CreditCardAccount::CreditCardAccount() { }
static CreditCardAccount::CreditCardAccount() { }
CreditCardAccount::CreditCardAccount(long number, double balance, double limit):accountNumber(number), currentBalance(balance), creditLimit(limit) { } void CreditCardAccount::SetCreditLimit(double amount) { creditLimit = amount; } bool CreditCardAccount::MakePurchase(double amount) { if (currentBalance +amount>creditLimit) { return false; } else { currentBalance += amount; return true; } }
void CreditCardAccount::MakeRepayment(double amount) { currentBalance -= amount; }
void CreditCardAccount::PrintStatement() { Console::WriteLine(currentBalance); }
long CreditCardAccount::GetAccountNumber() { return accountNumber; }
int CreditCardAccount::GetNumberOfAccounts() { return 0; }
|
调用
1 2 3 4 5 6 7 8 9 10 11 12 13
| #include "pch.h" #include "CreditCardAccount.h" using namespace System;
int main(array<System::String^>^ args) { CreditCardAccount^ myAccount = gcnew CreditCardAccount(); myAccount->SetCreditLimit(1000); myAccount->MakePurchase(1000); myAccount->PrintStatement(); long num = myAccount->GetAccountNumber(); Console::WriteLine(num); }
|
类构造器
不同于标准C++,C++/CLI支持静态构造器,普通构造器是在对象创建时初始化实例成员,而静态构造器是在类可以使用前完成一些准备工作,这意味着在创建类的任何对象,或者在使用类的任何静态成员之前都会先调用类静态构造函数。
1 2 3 4 5 6 7 8 9 10 11
| ref class CreditCardAccount { public: static CreditCardAccount(); };
static CreditCardAccount::CreditCardAccount() {
}
|
类级常量
类级常量代表在类的所有实例中都不变的值,可以使用literal
关键字来创建
literal string^ name ="hello";
在标准的C++中可以使用static const
来表示类级别常量,虽然C++/CLI也支持这样写,但是如果类时通过一个#using
语句来访问,这种常量不被认为是编译时常量,所以推荐使用literal
。
实例常量
可以用initonly
关键字来标记实例常量,也就是在创建类的实例时由构造器赋值,之后就不能更改。
对象生存期
C++/CLI中垃圾回收器(GC)来清理不需要的对象,垃圾回收器有三点要注意:
- 对象总是通过句柄来管理,这是系统跟踪对象的方式
- 只要有一个句柄指向对象,该对象就不会被回收
- 无法判断对象的内存在什么时候回收,这有GC来决定
垃圾回收器有一个原则越老的对象存活时间越长
,因为gc有“代”的概念,0代满了就对0代进行回收,幸存下来的对象提升到1代,目前gc只支持3代。
析构器
标准C++和C#都具有析构器,只不过C#中的析构器不能显式调用,C++/CLI定义方式与之相同,使用方法和标准C++类似,使用delete
来调用析构器
1 2 3 4 5 6 7 8 9 10 11 12 13
| ref class MyClass { public: MyClass(); ~MyClass();
};
int main(array<System::String^>^ args) { MyClass^ c = gcnew MyClass(); delete c; }
|
终结器
垃圾回收器回收对象时调用的是终结器,如果使用了非托管资源,则要定义终结器,如果未使用非托管资源,则一般不需要定义终结器,与C#中的析构器类似,不能显式调用。
1 2 3 4 5 6
| ref class MyClass { public: MyClass(); !MyClass(); };
|
终结期的三个原则:
- 不要定义什么都不做的终结器,因为一旦定义了,GC就会在回收对象的内存中执行,这会有性能损耗
- 终结期的顺序不能确定,如A和B都有终结器,且都对同一数据进行操作,这是无法确定谁先执行的
- 如果整个应用程序已经终止,仍然存活的对象不会再调用终结器,这包括后台线程使用的对象,或者在终结器中创建的对象。
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| ref class MyClass { public: MyClass(String^ name); ~MyClass(); !MyClass(); void DoSomething();
private: String^ name; };
MyClass::MyClass(String^ name) { this->name = name; Console::WriteLine("构造函数已调用:{0}", name); }
MyClass::~MyClass() { Console::WriteLine("析构函数调用"); }
MyClass::!MyClass() { Console::WriteLine("终结期调用"); }
void MyClass::DoSomething() { Console::WriteLine("调用方法"); }
int main(array<System::String^>^ args) { MyClass^ m1 = gcnew MyClass("hello"); m1->DoSomething(); Console::WriteLine("程序结束"); }
|

修改案例
如果在调用时,显式调用delete
1 2 3 4 5 6 7
| int main(array<System::String^>^ args) { MyClass^ m1 = gcnew MyClass("hello"); m1->DoSomething(); delete m1; Console::WriteLine("程序结束"); }
|

此时程序没有调用终结器,因为垃圾回收器认为析构器执行完成后,对象已经得到清理,不需要执行终结器,所以一般要在析构器中显式调用终结器。
1 2 3 4 5
| MyClass::~MyClass() { Console::WriteLine("析构函数调用"); this->!MyClass(); }
|
在编写C++/cli程序时,要养成使用对象完毕后调用delete的习惯
栈语义
标准C++中,可以在栈上直接创建对象
1 2
| MyClass m("dd"); m.DoSomething();
|
这样在离开作用域后会自动销毁,因为是在栈上定义的对象,所以说对象具有栈的语义
C++/CLI支持相同的方式,不过,实际上并不是在栈上声明,只是为了兼容C++的写法,目前大多数类型对象都支持栈语义,除了字符串和数组,这些对象必须使用gcnew来获得。
拷贝构造器
标准C++的内存管理严重依赖拷贝构造器,如果没有定义则默认提供一个拷贝构造器,但是C++/CLI有了GC,并不会默认提供一个拷贝构造函数,而是需要自己写。
1 2
| MyClass^ a = gcnew MyClass(); MyClass^ b =a;
|
如何像上面这样写,则b和a其实指向的是同一个对象,拷贝的只是句柄而不是拷贝指向的对象。如果要完全拷贝,则要提供拷贝构造器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ref class MyClass { public: MyClass(const MyClass% other);
private: String^ name; int value; };
MyClass::MyClass(const MyClass% other) { value = other.value; name = other.name; }
|
%
指定了一个跟踪引用
,句柄间接引用对象,使用->操作符访问成员,而跟踪引用只是变量的别名,是变量的另一个名字。类似于标准C++中的引用,只不过为了应对GC操作,C++/CLI的引用为%
。
拷贝构造函数一般使用const来修饰参数,这样做的好处主要有2个:
- 引用不产生新的变量,减少形参与实参传递时的开销
- 由于引用可能导致实参随形参改变而改变,将其定义为常量引用可以消除这种副作用
与标准C++一样,C++/Cli也提供了*解引用符
1 2 3 4 5
| MyClass^ m = gcnew MyClass();
MyClass% rm = *m;
MyClass mm=*m;
|
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| ref class MyClass { public: MyClass(const MyClass% other); MyClass(int v); int GetValue(); Void SetValue(int v);
private: int value; };
MyClass::MyClass(const MyClass% other) { value = other.value; }
MyClass::MyClass(int v) { value = v; }
int MyClass::GetValue() { return value; }
Void MyClass::SetValue(int v) { value = v; }
int main(array<System::String^>^ args) { MyClass^ a = gcnew MyClass(1); MyClass^ b = a; b->SetValue(10); Console::WriteLine("a的值为{0}", a->GetValue()); Console::WriteLine("b的值为{0}", b->GetValue());
MyClass% rb = *a; rb.SetValue(20); Console::WriteLine("a的值为{0}", a->GetValue()); Console::WriteLine("rb的值为{0}", rb.GetValue());
MyClass c = *a; c.SetValue(100); Console::WriteLine("a的值为{0}", a->GetValue()); Console::WriteLine("c的值为{0}", c.GetValue());
MyClass^ d = gcnew MyClass(*a); d->SetValue(1000); Console::WriteLine("a的值为{0}", a->GetValue()); Console::WriteLine("d的值为{0}", d->GetValue());
Console::WriteLine("程序结束"); }
|

对象和栈语义关联
对象经常由其他对象构成,包含对象可以使用栈的语义来声明,也可以使用句柄来声明。在使用栈的语义来声明时,包含对象是在调用构造函数之前构造的,而析构器正好相反。那如何选择是使用栈的语义声明还是句柄来声明,要处理以下几个问题:
- 包含对象是否是容器对象的一部分,是否能独立存在;
- 包含对象是否要与其他对象共享;
- 包含对象是否可以与其他对象交换;
- 包含对象是否在容器对象销毁后可以继续存活;
如果这几个都不满足,则优先使用栈的语义声明方式。