1 C++/CLI标准
由ECMA最早于2003年开始设计,最新版ECMA-372 在2005年11月推出。
2 双关键字
enum calss
enum struct
for each
interface calss
interface struct
ref class
ref struct
value calss
value struct
3 Object对象
所有对象的根对象,可以调用ToString、GetType等方法。
Int n = 3;
Console::WriteLine(n.GetType());//输出:System.Int32
4特殊类型
System::Decimal,取值范围:±7.9×10(-28)~ ±7.9×10(28),具有28个有效位,适合用于要求有大量有效的整数及小数位数且没有舍入错误的财务计算。
Decimal dcm = System::Convert::ToDecimal(“123456789012345678901.23456789”);
public:Decimal( //构造函数
int lo, //96位整数低32位
int mid, //96位整数中32位
int hi, //96位整数高32位
bool isnegative, //是否为负,false为正
unsigned char scale //比例因子,0~28
)
Decimal d(0xeb1f0ad2, 0xab54a98c, 0, false, 0); //=12345678901234567890
5 for each循环
array<int>^ numbers=gcnew array<int>(1,2,3,4,5);
for each(int I in numbers)
{
Console::WriteLine(i);
}
6跟踪句柄^和跟踪引用%
int n = 10;
int %rn= n;
String^ str = “hello”;
String^ %rstr = str;
String^ nullStr = nullptr;
跟踪引用与指针很相似,表现在:
1) 通过函数参数传递跟踪句柄如果指针不会产生对象拷贝,函数内部对对象做的任何更改都会影响到外部对象。
2) 跟踪句柄之间的赋值也不会产生对象拷贝,都指向托管堆上的同一个对象。
不同于指针:
1) 不能进行指针加减运算
2) 不能直接传递到非托管函数内部
注意:跟踪句柄作为函数参数时,要得到在函数内部的改变,必须用跟踪引用
void fun(String^ %rstr)
{
rstr = “abc”;
}
7内部指针和固定指针
使用内部指针interior_prt可以进行地址运算,gc更新地址时会同时更新内部指针
array<int>^ arr = gcnew array<int>{1,2,3,4,5};
interior_ptr<int> p = &arr[0];
int n = *(p+3);
使用固定指针pin_ptr可以得到一个稳定的地址,gc不会改变该地址,可转换为普通指针
int^ i = gcnew int(5);
pin_ptr<int> p = &*i;
int *pi = p;
8数组
array<int>^ an= gcnew array<int>(20); //20个int元素
array<int>^ an= gcnew array<int>{1,2,3,4,5,6}; //定义并初始化
array<String^>^ astr = gcnew array<String^>{“aaa”,”bbb”}; //跟踪句柄数组
array<int, 2>^ ann = gcnew array<int,2>(3,4); //二维,3行4列
array<array<String^>^>^ aastr = gcnew array<array<String^>^>{ //数组的数组
gcnew array<String^>{“aaa”,”bbb”},
gcnew array<String^>{“ccc”,”ddd”,”eee”},
};
for each(array<String^>^ astr in aastr)
{
for each(String^ str in astr)
{
Console::WriteLine(str);
}
}
数组常用操作:Clear、Reverse、Sort、Resize、GetLength、Find、BinarySearch(二分查找)等,调用:Array::Sort(array<T>^ ar),从大到小排序
9字符串String
常用操作:
Clone
Compare
Concat
Contains
Copy
CopyTo
EndsWith
Equals
Format
IndexOf
IndexOfAny
Insert
IsNullOrEmpty
Join
LastIndexOf
LastIndexOfAny
PadLeft
PadRight
Remove
Replace
Split
StartsWith
Substring
ToCharArray
ToLower
ToUpper
Trim
10控制台输入输出
ReadLine //读一行,回车结束
Read //读一字符,回车结束
ReadKey //返回ConsoleKeyInfo,不会因为回车结束
WriteLine
Write
11 可变长参数的函数
void Say(...array<String^>^ astr)
{
for each(String^ str in astr)
{
Console::WriteLine(str);
}
}
Say("aaa","bbb","ccc","ddd","eee","ffff");
12 CLI结构和类
值结构类和类类:value struct,value class
引用结构类和引用类类:ref struct,ref class
与本地C++类不同:
1) 不能包含本地C++数组和C++类对象的数据成员
2) 不能包含友元函数
3) 不能包含类似于C语法中位字段的数据成员
4) 成员函数不能声明成const,而使用关键字literal
5)对于引用类,当有其他构造函数时,必须重新显式定义无参的默认构造函数
13 属性
关键字:property,set,get
类型:
1) 标量属性,单个属性值,根据需要定义set、get,不定义则为简单属性
2) 静态属性property static,对应静态数据成员
3) 索引属性,对应一组值,可通过[]访问,默认索引属性default和名称索引属性
ref class Numbers
{
public:
Numbers()
{
defaultArray = gcnew array<String^>{“aaa”,”bbb”,”ccc”}
}
property String^ default [int]
{
if(index<0) index=0;
else if(index > defaultArray->Length - 1)
index = defaultArray->Length-1;
return defaultArray[index];
}
private:
array<String^> defaultArray;
}
使用:
Numbers num;
Console::WriteLine(num[1]);
14 literal和initonly字段
在C++/CLI类中,数据成员不能直接初始化,即使使用const修饰。可以使用literal和initonly。
literal为字面字段,定义时必须初始化,不能被修改,是编译时常量且只能用于值类、引用类和接口类;
initonly为只构字段,仅在构造函数进行初始化,可赋值多次,静态只构字段只能在静态构造函数中初始化。
15 拷贝和静态构造
拷贝构造函数,左起第一个参数必须是引用类的引用对象或跟踪句柄
静态构造函数,用来初始化类的静态成员,不能带参数,必须私有,被系统自动调用。
16 析构和终结器
终结器finalizer,格式!ClassName(),当引用类对象被销毁时,由垃圾回收器自动调用。
只有当对引用类对象调用了delete时,析构函数才被调用,此时终结器将不再被调用,但对象本身的内存资源还是由垃圾回收器最终回收;
终结器中应当释放非托管资源;
析构函数中应当释放托管资源和非托管资源,其中释放非托管资源的部分功能可以调用终结器,避免重复代码。
参考:
http://msdn.microsoft.com/zh-cn/library/ms177197.aspx
http://technet.microsoft.com/zh-cn/windows/0s71x931(v=vs.110)
http://dev.yesky.com/msdn/450/2420450_2.shtml
17 继承和多态
可以使用new禁止虚函数的多态,virtual void Show() new{}
允许将基类的虚函数直接赋值给派生类中的虚函数,派生类的虚函数就拥有多个函数名,且还可以通过赋值来改变重载的次序。
ref class Animal
{
public:
virtual void Speak()
{
//动物叫
}
}
ref class Dog:public Animal
{
public:
virtual void Speak() override //标准重载
{
//大狗叫
}
}
ref class Puppy:public Dog
{
public:
virtual void Yip() = Dog::Speak //重置Dog的虚函数
{
//小狗叫
}
}
ref class Cat:public Animal
{
public:
virtual void Speak() new //重新指定,不具Animal的Speak虚函数多态
{
//小猫叫
}
}
ref class Tiger:public Cat
{
public:
//尽管该类从Cat派生,但重置虚函数Grow依次对Animal、Cat类的虚函数Speak进行重载
virtual void Growl()=Animal::Speak, Cat::Speak态
{
//老虎叫
}
}
array<Animal^>^ animals = gcnew array<Animal^>
{
gcnew Animal(),
gcnew Dog();
gcnew Puppy();
gcnew Cat();
gcnew Tiger();
}
for each(Animal ^a in animals)
{
a->Speak();
}
Animal^ cat1 = gcnew Cat();
Cat^ cat2 = gcnew Cat();
Cat^ tiger = gcnew Tiger();
cat1->Speak();
cat2->Speak();
tiger->Speak();
输出:
动物叫
大狗叫
小狗叫
动物叫
老虎叫
动物叫
小猫叫
老虎叫
18 抽象和密封
显示声明抽象类,需要关键字abstract
ref class B abstract{};
或者类中包含纯虚函数
virtual void Show() abstract;
密封类不能被派生,关键字sealed
ref class B sealed{};
也可以声明一个密封成员函数,该函数不能被派生类重写
virtual void Show() sealed;
19 接口、委托和事件
引用类,只需单继承,但可以继承多个接口,接口比类的内涵广,可以包含成员函数、属性、索引器、静态方法、静态字段、静态构造以及事件等,但接口方法不能包含任何实现。
关键字:interface class
不指定成员访问属性时,默认是公有public
约定类名前缀:接口I,值类V,简单类S,引用类R
派生类不能改变接口函数、属性等的公有属性,且必须在函数或属性前加上virtual关键字
委托是封装了函数指针的一种对象,CLI中不允许向函数传递函数指针,但可以传递委托对象。
两种委托类型:单函数委托Systme::Delegate和多播委托System::MulticastDelegate
委托步骤:创建委托->创建被委托的全局函数或全局成员->将函数放入委托中->向多播链中添加或移除委托->调用委托
创建一个委托:public delegate void MyFunDelegate(String^ str);
用于委托的方法必须是一个全局函数、一个静态方法或是一个公有的成员函数。
void MyFun(String^ str)
{
Console::WriteLine("MyFun:"+str);
}
ref class RClass
{
public:
static void StaticFun(String^ str)
{
Console::WriteLine("StaticFun:"+str);
};
void NormalFun(String^ str)
{
Console::WriteLine("NormalFun:"+str);
};
};
MyFunDelegate ^rmf = gcnew MyFunDelegate(&MyFun);
MyFunDelegate ^rmf2 = gcnew MyFunDelegate(&RClass:: StaticFun);
RClass ^rc = gcnew RClass();
MyFunDelegate ^rmf3 = gcnew MyFunDelegate(rc, &RClass:: NormalFun);
增加或去除一个委托
rmf += gcnew MyFunDelegate(&RClass:: StaticFun);
rmf -= gcnew MyFunDelegate(&RClass:: StaticFun);
委托调用
rmf->Invoke(“hello”);
或rmf(“hello”);
定义事件两个步骤:
1定义委托
2通过event关键字定义该委托引用类型的委托对象来声明事件
public delegate void DoorHandler(String^ name);
event DoorHandler^ Knock;
event DoorHandler^ Leave;
Door^ door = gcnew Door;
Knock += gcnew DoorHandler(door, &Door::Someone_Knock);
Leave += gcnew DoorHandler(door, &Door::Someone_Leave);
Knock(“someone”);会引发Knock事件,委托中的处理函数被执行
【疑问】:能调用Knock的地方为什么不能调用委托,或者干脆直接调用最终执行函数,如果说委托是为封装函数指针而用,那么事件真的想不通可用之处,事件可以添加多个委托,那么委托照样可以添加多个执行函数!
【领悟】困扰许久,终于发现一点事件和委托的差异,就是关于“封装设计”:
如果要很好的将委托属性封装到类内部,必须提供一个可向委托添加和删除执行函数的接口,可是要传递函数指针本身就需要依赖委托,所以接口中只能传递委托,这样一来,每次赋值都会完全覆盖之前的对象,这个内部委托对象便不能够很好地进行维护。所以当上升到事件层面之后,这个问题便迎刃而解。并且事件,本身已经具备了很好的可控的操作委托的接口,定义为类属性的事件只能被本类触发。就这一点看来,
执行函数—封装--->委托---封装-->事件,
他们位于不同层面的概念,根据自己的设计需要来使用。)
20 finally块
如果某些操作希望必须被执行,即使发生了异常,那么就用finally块
try{
}
catch()
{
}
finally
{
//执行完try块后不管是否跳进catch块,这里都会执行
}
当抛出一个不会被捕获的异常时,程序将崩溃,托管资源将不能优雅地释放。
只有当异常被捕获时,程序正常退出,托管资源才可以被正常释放。
也可以是以下组合
//申请资源
try
{
//使用资源
}
finally
{
//delete手动释放资源
}
21 文件和流
I/O类
File
FileInfo
DriveInfo
FileStream
Path
Directory
DirectoryInfo
DeflateStream //适用于流式的数据
GZipStream //适用于文件数据
SerialPort
流的读写类
BinaryReader、BinaryWriter:二进制数据流读取和写入
StreamReader、StreamWriter:带有特定编码格式的文本文件的读取和写入
StringReader、StringWriter:字符串中字符的读取和写入
通用I/O流
BufferStream
CryptoStream
MemoryStream
NetworkStream
目录操作
Directory::Exists(“c:\\aaa”); //判断目录存在
Directory::CreateDirectory(“c:\\aa”) //创建目录
Directory::Move //移动目录
Directory::Delete //删除目录
Directory::GetDirectories //获取子目录列表
Directory::GetFiles //获取目录中文件列表
文件操作
File::Exists
File::Create
File::CreateText
File::Open
File::OpenText
File::OpenRead
File::OpenWrite
File::Delete
File::Move
File::Copy
File::
File::
特性限定
在结构体、类或方法前可以用中括号进行某些特性限定或说明,例如MarshalAs属性用来指示如何在托管代码和非托管代码之间封送数据。如托管代码中调用WindowsAPI时需要使用。
[StructLayout(LayoutKind::Sequential)]
public value struc SS
{
//指明szDisplayName成员映射到非托管类型ByValTStr(定长字符数组)
[MarshalAs(UnmanagedType::ByValTStr, SizeConst=260)]
String^ szDisplayName;
}
限定结构体顺序布局,还有一种精确布局
[StructLayout(LayoutKind::Explicit)]
struct SS //相当于联合体union结构
{
[FieldOffset(0)]
int a;
[FieldOffset(0)]
int b;
}
[DllImport(“shell32.dll”)] 声明函数来自的动态库
文件流操作
FileStream支持同步、异步读写
FileStream::Read
FileStream::Write
FileStream::BeginRead //异步
FileStream::BeginWrite //异步
FileStream::EndRead //异步
FileStream::EndWrite //异步
FileStream::Seek //随机访问
FileStream::ReadByte
FileStream::WriteByte
FileInfo^ fi = gcnew FileInfo(“C:\\aa.txt”);
FileStream^ fs = fi->OpenRead();
int nCount = 100;
array<Byte>^ BArray = gcnew array<Byte>(nCount);
int nRead = fs->Read(BArray, 0, nCount);
if(fs->Length == fs->Position) //判断达到文件尾
fs->Close();
FileStream^ fo = File::Open(“C:\\aa.txt”, FileMode::Append);
array<Byte>^ info = (gcnew UTF8Encoding(true))->GetBytes(value);
fo->Write(info, 0, info->Length);
fo->Close();
文本流
StreamReader^ sreader = gcnew StreamReader(“C:\\aa.txt”, System::Text::Encoding::Default);
或 StreamReader^ sreader = gcnew StreamReader(File::OpenRead(“C:\\aa.txt”),
System::Text::Encoding::Default);
String^ strLine;
while(sreader->Peek()>=0)
{
strLine = sreader->ReadLine();
}
sreader->Close();
StreamWriter^ swriter = gcnew StreamWriter(“C:\\aa.txt”);
String^ logFile = DataTime::Now.ToShortDateString()->Replace(“/”,S”-”)->Replace(“\\”,S”-”);
logFile->Concat(“.log”);
FileStream^ fs = File::Open(logFile, FileMode::CreateNew, FileAccess::Write, FileShare::None);
StreamWriter^ sw = gcnew StreamWriter(fs);
sw->Flush();
sw->Close();
字节流和串行化
BinaryReader^ bs = gcnew BinaryReader(File::Open(logFile, FileMode::Open));
BinaryWriter^ bw = gcnew BinaryWriter(File::Open(logFile, FileMode::Create));
[Serializable]
ref class TestSimpleObject
{
private:
int m1;
String^ str1;
public:
[NonSerialized]
String^ str2;
Test()
{
m1 = 1;
str1= “aaa”;
str2 = “bbb”;
}
}
若从TestSimpleObject派生类,那么派生类也必须使用Serializable属性进行标记。
TestSimpleObject^ obj = gcnew TestSimpleObject;
FileStream^ stream = File::Open(“aa.dat”, FileMode::Create);
BinaryFormatter* formatter = new BinaryFormatter();
formatter->Serialize(stream, obj);
stream->Close();
“aaa”
obj = nullptr;
stream = File::Open("aa.dat", FileMode::Open);
formatter = new BinaryFormatter();
obj = dynamic_cast<TestSimpleObject^>(formatter->Deserialize(stream));
stream->Close();