6e61fbe263cb5729060ac3128cf75638.ppt
- Количество слайдов: 124
第 9章 托管C++程序设计 托管C++能够充分应用. NET Framework提供 的功能,并能允许用C++编写的模块与其它语 言(如C#、Visual Basic等)编写的模块组合。 本章主要介绍托管C++程序设计的基础知识, 包括CLR、托管程序、垃圾回收、托管数据 类型、程序集、托管类、托管继承、接口、 抽象类、托管引用类型和值类型、委托和事 件等概念和技术。这些内容是Windows程序 设计的基础,也是. NET环境中C#、Visual Basic等语言中的通用编程概念和技术。
9. 1 CLR与托管程序 1、CLR与托管程序 – CLR即公共语言运行库,它为. NET中的 每种编程语言提供了一个共同的程序执 行环境。 – 在 CLR 中运行的代码称为托管代码, 不在CLR中运行的代码称为非托管代码。
9. 1 CLR与托管程序 2、NET托管与非托管程序的关系 托 管 Web应 用 VB、MC++、C#、J#. . 程序 公共语言规范(CLS) 非托管应用程序 ASP. NET运 行. NET 3. 5开发件 库 WPF、WCF、WWF、WCF Internet信 息 服务 ADO. NET Win. Form… 基础类型(FCL) 公共语言运行库(CLR) 操作系统(OS) MFC 标准C++
9. 1 CLR与托管程序 2、CTS与元数据 • CTS即公共类型系统,其主要作用: – 定义了一套供所有. NET编程语言共用的. NET数据类型, 以及这些类型的内部格式。这些类型中的大多数都符 合CLS规范,可在多种托管语言中共用,实现语言的 互操作性。 – 建立一个支持跨语言集成、类型安全和高性能代码执 行的框架。 – 提供一个支持完整实现多种编程语言的面向对象的模型。 – 定义各语言必须遵守的规则,有助于确保用不同语言 编写的对象能够交互作用
9. 1 CLR与托管程序 3、元数据 – 元数据用于描述CLR在 JIT 编译MSIL、加载类、 执行代码以及与本机领域进行交互时使用的运 行库类型(类、接口和值类型)、字段、方法 以及内部实现和布局信息。 – 元数据包括在每个 CLR 组件中,并可供CLR、 具和服务使用。它通过定义统一的存储和检 索类型信息的机制使语言互操作性成为可能。
9. 1 CLR与托管程序 4、CLS – 即公共语言规范,是CTS的子集,所有适用于 CTS的规则都适用于CLS,它描述了一组基本 的语言功能,并定义了程序语言间互操作的规 则,保障多语言互操作的实现。 – CLS是微软公司定义的所有基于. NET Framework语言必须支持的最小功能集。包括: 变量的命名规则,定义了基本数据类型如Int 32、 Int 64、Single、Double、Boolean等,禁止 无符数值数据类型,指定了函数列表的规范, 事件名和事件参数的定义和传递规范,禁止内 存指针和函数指针等内容。
9. 1 CLR与托管程序 5. MSIL – Microsoft Intermediate Language Microsoft,中间 语言. – MSIL是CLR提供的一组可以有效地转换为本机 代码且独立于 CPU 的指令, 要使代码可运行, 还必须先将 MSIL 转换为特定于 CPU 的代码 6. JIT编译 – CLR编 译. NET程 序 时 , 会 首 先 把 它 编 译 成 MSIL代码。在该程序被执行时,那些被调用的 函数代码会被CLR从MSIL代码编译成本机代码 (即机器指令代码)执行,这种编译方式称为 JIT(Just-in-time )编译。
9. 1 CLR与托管程序 7. 垃圾回收 在托管程序中, 堆空间由CLR管理,称为托管堆。 托管堆由程序员用gcnew分配,如同C++中的 new一样,但托管堆不需要程序员用free之类 的命令回收。 当内存不足时,CLR就会自动搜查托管堆中那些 没有指针指向或被未被引用的对象(因其无用, 徒占内存空间,故称之为垃圾),并释放它们, 将它们所占据的内存空间归还系统,以被其它 程序使用。这种方法称为垃圾回收, 即GC( Garbage Collection)。
9. 1 CLR与托管程序 8、托管程序的执行过程 VB. NET、C#、J#、MC++…… 编译 中间语言代码(即MSIL代码) JIT编译 Windows UNIX CLR Linux
9. 2 托管数据类型 1、托管数据类型的概念和概况. NET Framework的CTS定义了一些类型, 如Int 16,Double,Char等,其中的许多 满足CLS规范,可用于. NET环境中的托管 C++、C#、Visual Basic程序设计,称为 托管数据类型。
9. 2 托管数据类型 2. 值类型 –. NET 也提供了许多类似于C++中的int、char、 double的类型,如Int 32、Char、Double等, 这些类型在它自己的内存分配单元中存储数据, 称为值类型。 – 每个值类型变量都有自己的数据副本,在作为 参数传递给函数时,值类型会将其值拷贝给参 数变量,因此对一个值类型变量的操作不会影 响其他值类型变量。 – 值类型比C语言中的int、char等内置类型功能 更强大,它是一种类,所有的值类型都隐式派 生于 Value. Type类,能够重载Value. Type中的 功能。
9. 2 托管数据类型. NET中的值类型具有如下特殊性: – 值类型没有与存储类的对象相关的系统开销,并 且它们不需要构造函数。 – 值类型是密封的,它们不能够被继承。 – 所有的值类型都默认从System: : Value. Type类 派生。 – 值类型可以有字段、属性和事件。它们也有静态 和非静态方法。当它们被装箱时,会从 System. Value. Type 继承虚方法,并可实现零 个或更多接口。
9. 2. 2 引用类型 1、引用类型的概念 – 引用类型的一个变量存储对象在内存中的地址, 通过该地址就能够访问对应内存单元中的对象。 – 引用类型实际上就是指针,它无论从定义、访 问方法、内存空间的分配等方面都与在C++或C 语言的指针类型相似。
9. 2 托管数据类型 2、值类型与引用类型的区别 – 值类型对象存储CLR管理的栈中,其值可以直 接访问。引用类型存储在CLR所管理的堆( heap)中,基于引用类型的变量包含的是一个 托管堆中的地址,只能通过指针或引用来访问 它所指向的托管堆中的对象。 – 分配于堆中的引用类型的对象,并不会在其所 属的函数调用结束时自动释放它所占用的内存, 其内存管理由CLR完成。
9. 2 托管数据类型 2、值类型与引用类型的区别 Int 32: 56 Int 16: 23 mangle C++ String ^ s struct: a 托管栈:存放值类型 托管堆:存放引用类型
9. 2. 3 装箱与拆箱 • 1、概念 – 装箱就是把值类型打包到 Object 引用类型的一个对象 中,使得值类型能够被视为对象,可以存储于托管堆 中。对值类型装箱会在托管堆中建立一个新对象,并 将值类型的值复制到新对象中。 – 拆箱(取消装箱)是从 object 类型到值类型或从接口 类型到实现该接口的值类型的显式转换。 – 装箱相当于把一个值类型对象放进一个箱子,而这个 箱子是可以放在托管堆中的引用类型。从技术上讲, 无论值类型还是引用类型都是从Object类派生的,装 箱是值类型到 object 类型或到此值类型所实现的任何 接口类型的隐式转换。
9. 2. 3 托管数据类型 2、装箱与拆箱的过程 ^是VC 2005之后定义托管引用的指针。
9. 2. 3 MC++引用类型与托管堆 1、MC++托管指针语法 – 早期(VC++2003版之前)用_gc表示托管数据定义: Int arr. A _gc[]; //定义arr. A为托管数组 arr. A=new int _gc[20] //在托管堆中建议托管数组arr. A int _gc *ptr; //定义ptr为托管指针 – 2005版VC++之后,用^表示托管引用指针,用genew 分配托管内存。因此,在托管C++中就存在两种 类型的指针: *:本地指针,用new在本地堆中分配内存单元。 ^:托管指针,用gcnew在托管堆中分配内存。
托管旧语法 __gc class __gc struct __value class __value struct __sealed VC++200 8托管 托管旧语法 新语 法 __try_cast sealed ref struct __event __pin pin_ptr value class __value enum class __abstract new gcnew __typeof typeid __property VC++2008托 管新语法 ref class value struct 托管旧语 法 __gc[] __interface class VC++2008 托管新 语法 safe_cast array
下面的示例代码演示了MC++中托管堆对象的建立和访问方法: struct X{ int a; }; int main(array<System: : String ^> ^args) { int i = 123; // 值类型 Object ^o = i; // i被先装箱,然后才赋值与托管对象o int j = (int)o; // 拆箱,从o中复制值到j int *ptr=new int(32); //在本地堆中建立对象 int ^rptr=gcnew int(98); //L 1 在托管堆中建立引用对象,会将值类型int装箱 X *px=new X; //在本地堆中建立对象 px->a=10; // X ^rx=gcnew X; //L 2: 错误,X是值类型,且不能装箱 // String *s 1=new String("no permission!"); //String只能在托管堆中建立对象 String ^s 2=gcnew String("this is ok!"); //建立String的引用对象 s 2 ->To. Upper(); //引用对象成员以指针形式访问,To. Upper是String的成员函数。 delete ptr; //必须显示释放本地堆中的对象 // delete rptr; //托管堆中的对象由CLR清理,不须程序员费心 return 0; }
9. 3. NET程序集与命名空间 1、. NET程序集 • 程序集是构成托管程序的基本单位,它们构成了. NET应用程序。程序集包含CLR执行的MSIL代码, 描述程序集及其内容的元数据,以及CLR执行程 序所需要的其它文件。 • 程序集是. NET对程序进行部署、版本控制、重用、 激活范围和安全权限的基本单元,是为共同运行和 形成功能逻辑单元而生成的类型和资源的集合。一 个托管程序总是驻留在一个或多个程序集中。 • 与标准的Windows程序或COM对象相比较,程序 集更具独立性,因为它不依赖外部信息。比如,标 准Windows程序需要Windows注册表信息才能运 行,而程序集不需要,所有的信息都包括在自身中, 简化了程序的部署。
2、常用命名空间 System 包含基本类和基类,这 些类定义 常用的值 和引用数据类型(如Char、 Double、Single、Int 32、Date. Time、Time. Span、String、Object 等)、事件和事件处 理程序、接口、属性和异常处 理。 System: : Collections 包含定义 各种对 象(如堆栈 、列表、队 列、数组 、字典)集合的接 口和类 System: : IO 包含允许对 数据流和文件进 行同步和异步读 写的类型 System: : Text 包含表示ASCII、Unicode、UTF-7和UTF-8字符编码 的类 System: : Threading 提供支持多线 程编 程的类和接口 System: : Windows: : Fo rms 包含用于创 建基于Windows的应 用程序的类,这 些应 用程序可以充 分利用Microsoft Windows操作系统 中的丰富用户 界面功能 System: : Drawing 提供了对 GDI+ System: : Web基本图 形功能的访问 。 System: : Web: : UI 包含用于创 建Web窗体页 的类,包括Page类和用于创 建Web用户 界 面的其他标 准类 System: : Data 包含组 成大部分ADO: : NET结 构的类 System: : Component. M 提供支持CLR应 用程序中GUI组 件操作的类 odel
9. 4 简单的MC++程序设计 1、第一个托管C++程序 【例9 -1】 现在,来建立第一个MC++程序。在 Visual Studio 2008环境中利用向导建立第一个 最简单的托管C++程序“Hellow MC++!”。 过程如下: (1)启动Visual Studio 2008,选择“新建”|“项目” ,并在弹出的“新建项目”对话框中“项目类型” 列表中选择“Visual C++”|“CLR”。 在“模板”列表中选择“CLR控制台应用程序”,如图 9 -6所 示。
9. 4 简单的MC++程序设计
9. 4 简单的MC++程序设计 向导产生如下文件: resource. h,包含项目使用的资源定义的资源头文件; stdafx. h,标准的系统包含头文件,它与stdafx. cpp是 Visual C++项目一般情况下都会特定的预编文件,它们 一起生成预编译文件,为程序提供需要的类型和数据。 app. ico,应用程序图标文件; app. rc,项目的资源脚本文件。资源脚本文件包含如下内容 (具体取决于项目的类型和为项目选择的支持,例如 具栏、对话框或 HTML):默认菜单定义、快捷键和字 符串表、对话框、图标文件、版本信息、位图、 具栏、 HTML文件等内容。 Assembly. Info. cpp,此文件包含用于修改项目的程序集元 数据的信息,即属性、文件、资源、类型、版本信息、 签名等信息。
9. 4 简单的MC++程序设计 向导产生托管C++程序:Hellow. cpp,主项目文件, 其内容如下: // Hellow. cpp: 主项目文件。 #include "stdafx. h" using namespace System; int main(array<System: : String ^> ^args) { Console: : Write. Line(L"Hello World"); return 0; }
9. 4. 2 MC++的数据类型 1、System 命名空间与数据类型 – System 命名空间是. NET Framework 中基本类型的 根命名空间。此命名空间定义了可在所有应用程序使 用的基本数据类型的类:Object(继承层次结构的根)、 Byte、Char、Array、Int 32、String 等。 – VC++. NET支持标准C++和托管C++程序设计,并允许 在托管程序中添加非托管代码。因此,以前介绍的标 准C++中用到的数据类型与程序代码和. NET中定义的 类型和类都可以添加到托管程序中。
2、MC++的常用数据类型 类名 说明 C++ 数据类型 Byte 8 位的无符号整数。 char SByte 8 位的有符号整数,不符合 CLS。 signed char Int 16 16 位的有符号整数。 short Int 32 32 位的有符号整数。 int 或long Int 64 64 位的有符号整数。 __int 64 UInt 16 16 位无符号整数。不符合 CLS。 unsigned short UInt 32 32 位无符号整数。不符合 CLS。 unsigned int / unsigned long UInt 64 64 位无符号整数。不符合 CLS。 unsigned __int 64 Single 单精度(32 位)浮点数字。 float
【例9 -2】用托管C++设计判断一个整数是否素数的函数,并 找出 100以内的素数。建立一个Visual C++ CLR控制台应 用程序项目CH 9 -2,修改其中的主项目文件为: // CH 9 -2. cpp: 主项目文件。 #include "stdafx. h" using namespace System; Boolean is. Prime(Int 32 n) { Int 32 m=Math: : Sqrt (n); Int 32 i; for(i=2; i<=m; i++) if(n % i==0) break; if(i >=m+1) return true; else return false; } int main(array<System: : String ^> ^args) { for(Int 32 n=2; n<=100; n++) if (is. Prime(n)) Console: : Write. Line(n) ; return 0; }
9. 4. 3 基于控制台程序的数据输入与输出 1、Console类 • Console是System命名空间中的一个类,提供了 多个用于数据输出的函数,可将值类型的实例、 字符数组以及对象集自动转换为格式化或未格式 化的字符串,然后将该字符串输出到控制台。 • Console 类还提供一些属性,用于获取或设置控 制台窗口的大小、位置、前景色和背景色,获取 或设置光标的位置,以及播放提示音等功能。
9. 4. 3 基于控制台程序的数据输入与输出 2、用Read和Read. Line输入数据 Read(),从输入流中读取一个字符。 Read. Line(),输入流中读取一行字符。返回System: : String 类型的字符串,如引输入流中字符行,就返回 null。 Read. Line读取回车、换行符就结束。 3、用write和Write. Line输出数据 Write(exp),将表达式exp的值以文本形式写入标准输出流。 Write. Line(exp),将表达式的值以文本形式(后跟当前行终 止符)写入标准输出流。 Console类对Write()和Write. Line()函数进行了重载,能够 输出Boolean、Char、array、Decimal、Double、Int 32、 Int 64、Object、Single、String、UInt 32、UInt 64等类 型的数据。
【例9 -3】用Read. Line()从键盘输入整数、浮点数、双精度数、字符串、布尔 类型及字符等不同类型的数据,并用Write和Write. Line将它们输出到屏幕上 // CH 9 -3. cpp: 主项目文件。 #include "stdafx. h" using namespace System; Convert是一个数据转 int main(array<System: : String ^> ^args) 换类,它能够将一种基 { 本数据类型转换成另一 int arg 0 ; 种数据类型 long arg 1 ; float arg 2 ; double arg 3 ; String ^opt 4 ; wchar_t opt 5 ; bool opt 6 ; Console: : Write. Line("--------------读数取数据------------"); Console: : Write("输入整数 :"); arg 0=Convert: : To. Int 32(Console: : Read. Line()); Console: : Write("输入长整数 :"); arg 1=Convert: : To. Int 32(Console: : Read. Line()); Console: : Write("输入浮点数 :"); arg 2=Convert: : To. Single(Console: : Read. Line()); Console: : Write("输入双精度数:"); arg 3=Convert: : To. Double(Console: : Read. Line()); Console: : Write("输入字符串 :"); opt 4=Console: : Read. Line(); Console: : Write("输入字符 :"); opt 5=Convert: : To. Char(Console: : Read. Line()); Console: : Write("输入布尔值 :"); opt 6=Convert: : To. Boolean(Console: : Read. Line());
Console: : Write. Line("-----输出读数取的数据-------"); Console: : Write("arg 0="); Console: : Write. Line(arg 0); Console: : Write("arg 1="); Console: : Write. Line(arg 1); Console: : Write("arg 2="); Console: : Write. Line(arg 2); Console: : Write("arg 3="); Console: : Write. Line(arg 3); Console: : Write("opt 4="); Console: : Write. Line(opt 4); Console: : Write("opt 5="); Console: : Write. Line(opt 5); Console: : Write("opt 6="); Console: : Write. Line(opt 6); return 0; }
• 程序结果
9. 4. 3 基于控制台程序的数据输入与输出 4、输出格式化数据 • Console中的Write和Write. Line能够应用表 9 -3 (Page 287)和表 9 -4(P 287)中的格式说明符对数 据进行格式化输出。它们的语法形式如下: Write(String^ format, Object^ arg 0, Object^ arg 1, ……); Write. Line(String^ format, Object^ arg 0, Object^ arg 1, ……); 语句中的第一个参数format用于定义其后各输出 数据arg 0、arg 1、arg 2……的输出格式。
• format是一个复合格式字符串,它由固定文本和 索引占位符混和组成,其中索引占位符称为格式 项,它从{0}开始,对于第 1个输出对象,接下来 是{1}、{2}、{3}……依次对应第 2、3、4……个 输出列表中的对象。 • 格式设置操作产生的结果字符串由原始固定文本 和列表中对象的字符串表示形式混和组成。假设 有如下的语句段: int x=90; double d=87. 23; Console: : Write. Line("x={0}, d={1}", x, d); • 该语句最终的输出结果是: X=90, d=87. 23
• Format复合格式的完整形式是: { 索引[, 对齐][: 格式字符串]} – “索引”即前面介绍的索引占位符,除了上面的介绍外, 还可以通过指定相同的索引号,多个格式项可以引用同 一个输出对象。例如,通过指定类似于“{0: X} {0: E} {0: N}”的复合格式字符串,可以将同一个数值设置为十 六进制、科学记数法和数字格式。 – “对齐”是可选项,它是一个带符号的整数,用于设置对 应输出数据所占据的字符个数。如果“对齐”为正数,表 示输出为右对齐;如果为负数,表示输出为左对齐;如 果小于输出的字符个数,它将被忽略,按照字符的实情 输出数据。 – “格式字符串”是可选项,它是适合正在设置格式的对象 类型的格式字符串。对于数值类型,它可以是表 9 -3中的 格式说明符;对于日期类型,它可以是表 9 -4中的格式说 明符。
9. 4. 3 基于控制台程序的数据输入与输出 【例9 -4】设计一个简单的程序,测试用复合格式字 符串设置数值、日期型数据的输出格式。 // CH 9 -4. cpp: 主项目文件。 #include "stdafx. h" using namespace System; enum class Color {Yellow = 1, Blue, Green}; int main() { Date. Time this. Date = Date. Time: : Now; //获取当前系统时间 int x 1=90, x 2=100, x 3=9; Console: : Write. Line("------使用左对齐输出数据--------"); Console: : Write. Line("x 1={0, -15}x 2={1, -15}x 3={2, -15}", x 1, x 2, x 3);
Console: : Write. Line("n------使用数值格式说明符输出数据---------"); Console: : Write. Line("(C) Currency: . . . . {0: C}n" + "(E) Scientific: . . . {1: E}n" + "(F) Fixed point: . . . {1: F}n" + "(G) General: . . . . {0: G}n" + "(N) Number: . . . {0: N 4}n" + "(P) Percent: . . . . {1: P}n" + "(X) Hexadecimal: . . . . {0: X}n" , -123, 123. 45 f); Console: : Write. Line("-----使用日期格式说明符输出数据--------"); Console: : Write. Line( "(d) Short date: . . . . {0: d}n" + "(D) Long date: . . . {0: D}n" + "(t) Short time: . . . {0: t}n" + "(T) Long time: . . . {0: T}n" + "(G) General date/long time: . {0: G}n" , this. Date); }; 程序运行结果见P 289图 9 -10
9. 4. 4托管与非托管代码的混合 • 在. NET C++环境中,可以在非托管程序中编写托 管代码,即在标准C++程序中添加托管代码;也 可以在托管C++中添加非托管代码,即在基于 CLR的托管C++程序中添加标准C++程序代码。 • 可以使用以下的预编译处理指令来实现同时包括 托管和非托管代码的程序. – #pragma managed,设置为托管代码编译方式 – #pragma unmanaged,设置为非托管代码编译方式
9. 4. 4托管与非托管代码的混合 【例9 -5】设计一个基于CLR的C++程序,在其中用 #pragma unmanaged和#pragma managed预编译指令 设置程序代码为非托管和托管方式。 // CH 9 -5. cpp: 主项目文件。 #include "stdafx. h" #include <stdio. h> #include<iostream> using std: : cout; using std: : endl; using namespace System;
#pragma managed void func 1() { // 托管方式 System: : Console: : Write. Line("In managed function. . "); int a=10; int *p=&a; cout<<"a="<<a<<"t p="<<p<<"t *p="<<*p<<endl; int ^ptr_i, ptr_j; ptr_i=gcnew int(23); ptr_j=a; System: : Console: : Write. Line("ptr_i={0} t ptr_j={1}", ptr_i, ptr_j); // cout<<"ptr_i"<<ptr_i<<"t ptr_j="<<ptr_j<<endl; //L 1, cout不能输出托管对象 }
#pragma unmanaged //设置为非托管方式 void func 2() { //func 2为非托管函数 printf("In unmanaged function. . . . . n"); int a=10; int *p=&a; cout<<"a="<<a<<"t p="<<p<<"t *p="<<*p<<endl; // int ^ptr_i, ptr_j; // ptr_i=gcnew int(23); //L 2: 错误 //L 3: 错误 // System: : Console: : Write. Line("error, can't use mananged type in here!"); //L 4 } #pragma managed int main() { func 1(); func 2(); } //设置为托管方式 //main为托管函数
9. 5 托管类与托管继承 1. 托管类和结构的比较 – 托管C++也提供了对结构与类的支持。同标准 C++一样,托管C++中的结构和类的唯一区别 在于结构成员默认都是公有的,而类成员默认 都是私有的。除此之外,无论在功能、结构、 定义形式,还是对象定义及成员访问方法都是 相同的。 – 在托管C++中,有两种不同的类型的结构和类, 它们是值类型的结构与类和引用类型的结构与 类。
9. 5 托管类与托管继承 2. 托管类和结构同非托管C++中的结构与类相比, 具有以下限制: – 托管类和托管结构的成员函数不允许声明为const; – this指针在值类型T的类或结构中,是interior_ptr<T> 类型的内部指针;而在引用类型T的类或结构,this指 针是T^类型的跟踪句柄; – 托管类或托管结构中的字段(即属性)不能是本地C++ 数组或本地C++类的对象; – 托管类或托管结构不允许定义友元函数,也不能定义 位字段成员;
9. 5. 1值类型的结构与类 1、定义形式 value class name {……}; value struct name {……}; 2、对值类型的结构和类的限制 • 不能作为其它结构或类的基类、不能包括复制构造函数; • 不能重写赋值运算符函数、不能定义纯虚函数。 • 不能重定义默认构造函数(包括无参数及提供了所有参数 默认值的构造函数)。值类型将自动包含一个无参数的默 认构造函数,该构造函数把所有的数值字段初始化为 0, 把所有的跟踪句柄字段初始化为nullptr,不能用自定义的 版本重写这个默认构造函数;
9. 5. 1值类型的结构与类 【例9 -6】设计一个简单的值类型的复数类Complex。 // CH 9 -6. cpp: 主项目文件。 #include "stdafx. h" using namespace System; value class Complex{ private: double r; double i;
public: // Complex(): r(0), i(0){} // Complex(double real=0, double image=0): r(real), i(image){} Complex(double real, double image): r(real), i(image){} void init(double rr, double ii){r=rr; i=ii; } double real() {return r; } double image() {return i; } virtual String ^To. String() override { String ^t=r +"+" +i +"i"; return t; } // ~Complex(){} };
int main(array<System: : String ^> ^args) { Complex c 1, *c 2, ^c 3; c 1. init(2. 3, 4. 5); c 2=new Complex(); c 2 ->init(9, 10); c 3=gcnew Complex(); c 3 ->init(20, 21); Console: : Write. Line("c 1={0}+{1}i", c 1. real(), c 1. image()); Console: : Write. Line("c 2={0}+{1}i", c 2 ->real(), c 2>image()); Console: : Write. Line("c 3={0}+{1}i", c 3 ->real(), c 3>image()); Console: : Write. Line(“c 1‘type is: {0} nc 1. To. String() is: {1}”, c 1. Get. Type(), c 1. To. String()); return 0; }
9. 5. 2引用类型的结构与类 1、引用类型的结构与类的声明形式 ref class name {…… }; ref struct name {……}; • 注意:结构与类的声明前面还可以加上访问授权关 键字public或private,即在ref前面可加上public或 private权限。若指定为public权限,则表明该类或 结构可被它所在的程序集之外的函数访问,若为 private,表示该类或结构只能够在本程序集内可用。
9. 5. 2引用类型的结构与类 2、引用类型的结构和类在功能上相当于标准 C++的结构与类。但有如下限制: – 不能显示定义默认复制构造函数; – 不能显示定义默认赋值运算符函数; – 只能定义其普通对象和跟踪句柄,不能定义本 地指针,也不能用new建立对象; – 不能为成员函数(包括构造函数)指定默认参 数值; – 不能重载运算符&和new。
【例9 -7】设计一个简单的引用类型 的复数类Complex。 // CH 9 -7. cpp: 主项目文件。 #include "stdafx. h" using namespace System; ref class Complex{ private: double r; double i;
public: Complex(): r(0), i(0){Console: : Write. Line("in constructor. . . "); } // Complex(double real=0, double image=0): r(real), i(image){} //L 1 // void f(int i=0, int j=6){r=i; i=j; } //L 2 Complex(double real, double image): r(real), i(image){} void init(double rr, double ii){r=rr; i=ii; } double real() {return r; } double image() {return i; } virtual String ^To. String() override { String ^t=r +"+" +i +"i"; return t; } ~Complex(){ Console: : Write. Line("in destructor. . . "); } };
int main(array<System: : String ^> ^args) { Complex c 1, ^c 2; //Complex *c 3; //L 3 //c 2=new Complex(); c 2 ->init(9, 10); //L 4 c 1. init(2. 3, 4. 5); c 2=gcnew Complex(); c 2 ->init(20, 21); Console: : Write. Line("c 1={0}+{1}i", c 1. real(), c 1. image()); Console: : Write. Line("c 2={0}+{1}i", c 2 ->real(), c 2 ->image()); Console: : Write. Line("c 1'type is: {0} nc 1. To. String() is: {1}", c 1. Get. Type(), c 1. To. String()); delete c 2; //L 5,若无此语句,也不会产生内存泄漏,CLR会执行垃圾回收 return 0; }
9. 5. 3结构与类的属性 1、托管类中的成员类型 – 字段,在托管C++及多数面向对象程序设计语 言(如java、Visual Basic、C#、J#等)的结 构或类中(包括值类型和引用类型),数据成 员称作字段 – 方法,以前在C++类中称作成员函数的类成员 则称作方法。 – 属性,是一种特殊的方法,用于访问类中的字 段。它与字段的主要区别在于:字段名代表保 存数据的内存单元,而属性名则代表函数名。
9. 5. 3结构与类的属性 2、属性定义 • 一个属性有get()和set()访问函数用于存取字段的值。 • 如果一个属性只有set()方法则称为只写属性,如是一个属 性只有get方法则称为只读属性。 • 属性的定义方式如下: property Type name{ void set(Type value){ fieldname=value; } Type get(){return fieldname; } }
【例9 -8】设计一个简单的引用类型的复数类 Complex,并设计实部属性读取属性real、虚部的 只读属性Rimage及虚部只写属性Wimage。 // CH 9 -8. cpp: 主项目文件。 #include "stdafx. h" using namespace System; ref class Complex{ private: double r; double i;
public: Complex(): r(0), i(0){} Complex(double real, double image): r(real), i(image){} property double real{ void set(double real){ r=real; } double get(){return r; } } property double Rimage{ double get(){return i; } } property double Wimage{ void set(double d){i=d; } } };
int main(array<System: : String ^> ^args) { Complex c 1, ^c 2; //L 1 c 2=gcnew Complex(10, 20); //L 2 c 1. real=3. 2; //L 3 //c 1. Rmage=4. 1; //L 4, 错误, imag是只读属性 Console: : Write("1: t c 1. r={0}t", c 1. real); //L 5 Console: : Write. Line("c 1. i={0}", c 1. Rimage); //L 6 Console: : Write("2: t c 2 ->r={0}t", c 2 ->real); //L 7 Console: : Write. Line("c 2 ->i={0}", c 2 ->Rimage); //L 8 c 2 ->real=50; //L 9 c 2 ->Wimage=100; //L 10 Console: : Write("3: t c 2 ->r={0}t", c 2 ->real); //L 11 Console: : Write. Line("c 2 ->i={0}", c 2 ->Rimage); //L 12 //Console: : Write. Line("c 2 ->i={0}", c 2 ->Wimage); //错误,image是写属性 return 0; }
9. 5. 4 运算符重载和静态构造函数 1、托管C++运算符重载规则 与标准C++基本相同。 2、托管C++与标准C++运算符重载的区别 – 不能在值类型的类或结构中重载赋值运算符; – 引用类没有默认的赋值运算符,如果需要以赋值运算 符来处理引用类对象,必须实现适当的函数; – 不能用友元函数重载类或结构的运算符; – 虽然标准C++允许重载new,但托管C++不允许重载 gcnew运算符。
2、托管C++与标准C++运算符重载的区别 • 托管C++中可以用成员函数、静态成员函数重载 运算符,重载二元运算符函数为静态成员函数时 需要两个参数。 • 以定义静成构造函数对静态成员进行初始化,静 态构造函数没有参数,也不能有初始化列表。静 成构造函数总是私有的。不能直接调用静态构造 函数,它在普通构造函数被调用之前执行。 • 对于++和--两个既有前缀、又有后缀的运算符而 言,一方面可以用类似于标准C++的成员函数重 载方式:无参数的重载表示前缀、有一个无用参 数的重载形式表示后缀,另一方面可以可用静态 方式重载。若用静态方式重载,只需要一个参数, 它既是前缀运算的重载、又是后缀运算的重载。
• 【例9 -9】设计一个引用类Point,并用成员 函数重载operator+()、operator++()用静 态成员函数重载operator-()、operator--实 现两个点的加、减、自增、自减运算;设 计一个复数类,用静态成员函数重载 operator+(),用类成员函数重载operator()、operator=()实现两个复数的加、减 和赋值运算。并重载复数类的静态构造函 数,静态数据成员的初始化。
// CH 9 -9. cpp: 主项目文件。 #include "stdafx. h" using namespace System; ref class Point { private: int x, y; public: Point(){x=0; y=0; } Point(int a, int b){x=a; y=b; } Point^ operator+(Point^ a){ return gcnew Point(x+a->x, y+a->y); } static Point^ operator-(Point^ a, Point ^b) {return gcnew Point(a->x - b->x , a->y - b->y); } void display(){Console: : Write. Line("({0}, {1})", x, y); } Point^ operator ++() { return gcnew Point(++this->x, ++this->y); } Point^ operator ++(int) { return gcnew Point(this->x++, this->y++); } static Point^ operator--(Point^ me) { return (gcnew Point(me->x - 1, me->y-1)); } Point^ operator=(Point^ a){ x=a->x; y=a->y; return this; } };
value class Complex { private: double r, i; static int n; static Complex(){n=10; } public: //void operator=(Complex a){r=a. r; i=a. i; } //错误,值类型不允许重载= //Complex(){r=0; i=0; } //错误,不允许为值类型指定默认构造函数 //Complex op_Subtraction(Complex a, Complex b){} // 早期VC++. net 中的减法重载 Complex (double R, double I): r(R), i(I){ }; static Complex operator+(Complex a , Complex b); //复数加法 Complex operator-(Complex a); //复数减法 void display(); };
Complex: : operator+(Complex a, Complex b){ return Complex(a. r+b. r, a. i+b. i); } Complex: : operator -(Complex a){ return Complex(r-a. r, i-a. r); } void Complex: : display(){ Console: : Write("n={0}t{1}", n, r); //输出静态变量n和复数实部 if (i>0) Console: : Write("+"); //如果虚部为正数,则输出“+” if (i==0) Console: : Write. Line(); //如果虚部为,就不输出 if (i!=0) Console: : Write. Line("{0}i", i); //如果虚部不为,就不输出 } void main(void) { Complex c 1(1, 2), c 2(3, -4), c 3, c 4; c 1. display(); c 2. display(); c 3=c 1+c 2; c 3. display(); c 4=c 1 -c 2; c 4. display();
Point^ p 0=gcnew Point(1, 1); Point^ p 1=gcnew Point(2, 2); Point^ p 2=p 0+p 1; Console: : Write("p 0="); p 0 ->display(); Console: : Write("p 1="); p 1 ->display(); Console: : Write("p 2="); p 2 ->display(); Console: : Write("p 1+p 2="); (p 1+p 2)->display(); Console: : Write("p 2 -p 1="); (p 2 -p 1)->display(); p 1=p 2+p 1; p 1 ->display(); Console: : Write("p 0="); p 0 ->display(); Console: : Write("p 0++="); (p 0++)->display(); Console: : Write("++p 0="); (++p 0)->display(); Console: : Write("--p 0="); (--p 0)->display(); Console: : Write("p 0 --="); (p 0 --)->display(); Console: : Write("p 0=") ; p 0 ->display(); }
9. 6 托管继承 • 托管类(包括托管结构)包括值类型和引用类型, 它们都从System: : Object类派生,继承了Object 类的成员函数Equals()、To. String()等成员函数。 其中Equals()能够实现两个对象的相等比较,默 认To. String()函数返回对象所属类或结构的名称, 可以重载它以返回对象值的字符串表示。 • 不能为值类型的类(结构)指定任何基类(即值 类型的类与结构只能从Object类继承),它也不 能作为其它类的基类。因此,只有引用类才能继 承。
9. 6. 1 托管继承与标准C++继承的区别 1、托管继承的限制 • 托管继承只支持单一的public继承,不支持 多重继承,也不支持protected和private继 承,因此继承方式可以省略。 • 引用类也不能从非托管类(标准C++类)继 承。
9. 6 托管继承 2、托管继承的执行次序 – 在标准 C++及以前的托管继承中,构造函数的初始化 按以下顺序进行: (1)如果存在基类的构造函数则调用该构造函数。 (2)执行该类的初始化列表。 (3)执行该类构造函数的代码正文。 – 在Visual C++2008中,托管继承的构造函数次序: (1)执行本类的初始化列表。 (2)如果存在基类的构造函数则调用该构造函数。 (3)执行本类构造函数的代码正文。
9. 6 托管继承 【例9 -10】设计托管类,验证基类与派生类的构造函数执行 次序。 // CH 9 -10. cpp: 主项目文件。 #include "stdafx. h" #include<iostream> using namespace System; using std: : cout; using std: : endl; class nomang{ //标准C++类(本地类,非托管类) public: nomang(){ cout<<"In nonmangangled class. . . "<<endl; } };
//ref class D: nomang{}; nomang是本地类,ref class不能从它继承 ref class A{ //托管类 public: A(int i) : n(i){ Console: : Write. Line(L"In Constructor. . . A. . . n={0}", n); } void f(){ Console: : Write. Line(L"In Constructor. . . A: : f()"); } protected: int n; }; ref class B : public A{ //public可略,因public是唯一托管继承方式 public: B(int n) : m(n) , A(m+10) { Console: : Write. Line(L"In Constructor. . . B. . . m={0}", m); } void g(){ Console: : Write. Line(L"In Constructor. . . B: : g()"); } private: int m; };
int main(array<System: : String ^> ^args){ B b(1); b. f(); B^ b 2=gcnew B(10); b 2 ->f(); b 2 ->g(); return 0; }
9. 6. 2 虚函数与抽象类 1、托管C++中虚函数的重载限制 – 在托管C++中,派生类若要重写基类的虚函数,它必须 在重写的虚函数前面加上virtual关键字,在函数原型 的参数表后面加上override关键字。 – 在托管C++中,属性也可以是虚函数,虚函数称为抽象 方法。抽象类必须为其方法和属性指定原型,具体派 生类可以覆盖抽象类的virtual方法和属性,并为那些 方法或属性提供具体实现。
9. 6. 2 虚函数与抽象类 2、托管C++中虚函数或抽象类的定义方法 (1)在类声明中用abstract声明抽象类。 ref class Swimmer abstract{ //被指定为抽象类,不能被实例化 public: void swim(){ Console: : Write. Line("Hellow, Wellcome to swim. . . "); } void dive(){ Console: : Write. Line("under water 50 m. . . "); } };
9. 6. 2 虚函数与抽象类 (2)用abstract指定类的方法为抽象方法, 具有abstract方法的类就是抽象类。 ref class Swimmer{ public: virtual void swim()abstract; //抽象方法 void dive(){ Console: : Write. Line("under water 50 m. . . "); } };
9. 6. 2 虚函数与抽象类 (3)具有纯虚函数的类是抽象类 ref class Swimmer{ public: virtual void swim()=0; //纯虚函数 void dive(){Console: : Write. Line("under water 50 m. . . "); } };
【例9 -11】设计一抽像类Swimmer,其中包括纯虚方法swim(), 和虚方法speed()、diver();设计Swimmer的派生类Fish, 并在其中重写swim()、speed()方法,重载dive()方法。 // CH 9 -11. cpp: 主项目文件。 #include "stdafx. h" using namespace System; ref class Swimmer{ public: virtual void swim()=0; //纯虚函数 virtual void speed(){Console: : Write. Line("Swimmer: t 5 m every second. . . "); } virtual void dive(){Console: : Write. Line("Swimmer: tunder water 50 m. . . "); } };
ref class Fish: Swimmer{ public: virtual void swim() override { Console: : Write. Line("Fish: t. I'm a fish, i can swimming!"); } virtual void speed() override { Console: : Write. Line("Fish: t 10 m every second. . . "); } //void dive(){ //错误,重写要加上virtual Console: : Write. Line("fishe: under water 50 m. . . "); } void dive(int a){ //重载,参数表不同 Console: : Write. Line("fish: tunder water {0} m. . . ", a); } };
int main(array<System: : String ^> ^args) { Swimmer ^s; Fish fish 1; fish 1. swim(); fish 1. speed(); fish 1. dive(100); fish 1. Swimmer: : speed(); s=gcnew Fish; s->swim(); s->dive(); s->speed(); s->Swimmer: : dive(); return 0; }
注意 (1)在托管类中,所有的虚函数必须用 virtual明确指出。派生类重写基类的虚函 数时,必须在该函数的声明中同时加上 virtual和override头键字。 (2)virtual和override头键字只能类成员函 数的声明时,在类的内部使用,不能用于 类体外的成员函数定义中 。 (3)如果派生类需要自定义与基类某个虚函 数的函数原型相同的成员函数(该函数与 基类的虚函数无关),必须在函数声明的 原型后面加上new关键字。
9. 6. 3 sealed类和sealed方法 • 将sealed关键字放在类名之后,并且在类 主体、基类派生列表或分号之前,就将该 类指定义成了密封类。密封类不能被其它 类继承。Page 302 • 在一个方法后的原型后面加上sealed就将 它指定成了密封方法,sealed方法不能在 派生类中被重写。
9. 6. 4接口类 1、接口类的概念 • 接口类是一种抽象类。它可以包含有属性、方法和事件。 接口中不能包含任何实现代码,也不能包括任何字段(即 数据成员)。实现接口的任何类必须为接口中声明的抽象 成员提供定义。 2、接口类的定义 interface class 接口名{ 成员列表 }; 接口类定义以关键字interface class(或interface struct)开头, 紧接其后的通常是用大写字母I(表示接口)开始的标识符作为接 口名,并包含一个由public方法、属性和事件构成的成员列表。
9. 6. 4接口类 3、接口类的注意事项 – 接口类成员列表中的所有方法、属性和事件都是public 类型的纯virtual方法。因此,接口类中的public和 virtual关键字,以及用于成员是纯虚函数的“=0”初始 值都可以省略。 – 接口类不能包括数据成员、静态成员、构造函数、析 构函数和运算符重载函数,不能包括任何实现代码( 即只能包括函数原型),也不能使用sealed关键字限 定接口。 – 接口类可以从其它接口类继承,但不能够从一般类继 承。要使用接口的类必须指出它实现了接口,也称为 继承接口。 – 在托管C++中,一个类只能从一个基类继承,但一个类 可以从多个接口类继承。
【例9 -12】设计一电源接口ISwitch其中包括开和关两个方法, 再设计一个打印接口IPrint,其中包括一个打印方法print()和 一个设置打印页的属性page。并设计两个类,实现这两个接 口的功能。 // CH 9 -12. cpp: 主项目文件。 #include "stdafx. h" using namespace System; interface class ISwitch{ void on(); void off(); }; interface class IPrint{ void print(); property unsigned page{ unsigned get(); void set(unsigned ); } };
ref class Doc. Print: public IPrint{ public: virtual void print(){ Console: : Write. Line("Print Micorsoft Word Document Page {0}", page); } property unsigned page{ virtual unsigned get(){return pages; } virtual void set(unsigned p){pages=p; } } private: unsigned pages; }; ref class Exl. Print: public IPrint, public ISwitch{ public: virtual void print(){ Console: : Write. Line("Print Micorsoft Excel Page {0}", page); } property unsigned page{ virtual unsigned get(){return pages; } virtual void set(unsigned p){pages=p; } } virtual void on(){ Console: : Write. Line("Micorsoft Excel , the printer is on. . . "); } virtual void off(){Console: : Write. Line("Micorsoft Excel , the printer is off. . . "); } private: unsigned pages; };
int main(array<System: : String ^> ^args) { Doc. Print ^doc 1=gcnew Doc. Print; doc 1 ->page=10; doc 1 ->print(); Exl. Print ^excel 1=gcnew Exl. Print; excel 1 ->page=20; excel 1 ->print(); excel 1 ->on(); IPrint ^iptr=gcnew Doc. Print; iptr->page=9; iptr->print(); iptr=excel 1; iptr->page=15; iptr->print(); return 0; }
9. 7托管数组 1、托管数组的基本概念 – 托管数组是指由CLR管理的数组,它在托管堆 中创建,能够自动执行垃圾回收。 – 托管数组与标准C++数组存在较大区别,托管 数组从System: : Array类派生,从该类继承了 许多方法和属性,如排序、查找、清空数组、 数组拷贝及计算数组长度等方法。
2、托管数组基类的常用方法 说明 方法 Clear 将数组 的一系列元素设 置为 零、false或 null Clone 创 建Array的浅表副本 Copy 将一个Array的一部分元素复制到另一个Array中 Copy. To 已重载 。 将当前一维 Array 的所有元素复制到指定的一维 Array 中 Find(T) 搜索与指定谓词 定义 的条件匹配的元素,然后返回整个Array中的第一个 匹配项 Find. All(T) 检 索与指定谓词 定义 的条件匹配的所有元素 Get. Length 获 取一个 32位整数,该 整数表示Array的指定维 中的元素数 Get. Value 获 取当前Array中指定元素的值 Resize(T) 将数组 的大小更改为 指定的新大小 Reverse 反转 一维 Array或部分Array中元素的顺 序 Sort 已重载 。对 一维 Array对 象中的元素进 行排序 To. String 返回表示当前 Object 的 String Binary. Search 使用二进 制搜索算法在一维 的排序 Array 中搜索值 。
9. 7托管数组 3、托管数组的内容及定义 • 托管数组可以分为值类型、引用类型、及本地 指针类型(即数组元素保存本地对象的指针)。 • 托管数组通用定义形式如下: array<type 1[, dimension]>^var = gcnew array<type 2[, dimension]>(val[, val. . . ])
9. 7托管数组 4、一给托管数组定义 array<type 1>^var = gcnew array<type 2>(val) • 定义一维值类型托管数组v 0、v 1、v 2、v 3。 array<int>^ v 0 = gcnew array<int>(10){0, 1, 2}; array<int>^ v 1 = gcnew array<int>{0, 1, 2}; array<Int 32>^ v 2 = {0, 1, 2}; array<int> ^v 3=gcnew array<int>(3); v 1[0]=0; v 1[1]=2; v 1[2]=3;
9. 7托管数组 • 引用类型托管数组定义形式如下 array<type 1^>^var = gcnew array<type 2^>(val) 引用类型与值类型数组的差别在于数组的元素是引用。 array<String^>^ gc 1 = gcnew array<String^>(10){"one", "two", "three"}; array<String^>^ gc 2 = {"one", "two", "three"}; array<String^>^ gc 3=gcnew array<String^>(3); String ^s="three"; gc 3[0]=gcnew String("one"); gc 3[1]=gcnew String("two"); gc 3[2]=s; for(int i=0; i<10; i++) Console: : Write("{0, 8}", gc 1[i]);
9. 7托管数组 • 引用类型托管数组定义形式如下 array<type 1^>^var = gcnew array<type 2^>(val) 引用类型与值类型数组的差别在于数组的元素是引用。 array<String^>^ gc 1 = gcnew array<String^>(10){"one", "two", "three"}; array<String^>^ gc 2 = {"one", "two", "three"}; array<String^>^ gc 3=gcnew array<String^>(3); String ^s="three"; gc 3[0]=gcnew String("one"); gc 3[1]=gcnew String("two"); gc 3[2]=s; for(int i=0; i<10; i++) Console: : Write("{0, 8}", gc 1[i]);
9. 7托管数组 本地指针类型的托管数组定义形式如下: array<type 1*>^var = gcnew array<type 2*>(val) 例如: array<int*>^a 0=gcnew array<int*>{new int(1), new int(2), new int(3)}; array<int*>^a 1=gcnew array<int*>(10); for(int i=0; i<10; i++){ a 1[i]=new int(10); Console: : Write("{0, 5}", *a 1[i]); }
【例9 -13】用托管数组管学生成绩。设计托 管数组name保存学生姓名,score保存学 生成员。利用托管数组的排序功能,对学 生成绩进行排序。设计姓名和成绩的输出 函数,该函数接收托管数组类型的参数, 输出托管数组中的内容。然后从键盘输入 一个学生的姓名,查询该学生的成绩。
// 9 -13. cpp: 主项目文件。 #include "stdafx. h" using namespace System; void outname(array<System: : Object^>^a) //输出引用数组的函数 { for(int i=0; i<a->Length; i++) Console: : Write("{0, 4}", a[i]); Console: : Write. Line(); } void outscore(array<double>^a) //输出值类型数组的函数 { for(int i=0; i<a->Length; i++) Console: : Write("{0, 6}", a[i]); Console: : Write. Line(); }
int main(array<System: : String ^> ^args) { array<String^>^name=gcnew array<String^>(10){"张三", "李四", "王十", "黄五", "刘七", "李一", "赵八", "蔡二", "花九", "刘六"}; array<double>^score=gcnew array<double>(10); Random rseed; //=gcnew Random; for(int i=0; i<10; i++) score[i]=rseed. Next()% 100 ; //产生 100以内的随机整数作为成绩 outname(name); //输出学生姓名 outscore(score); //输出学生成绩 Console: : Write. Line("n-------排序后的成绩: 升序----------"); System: : Array: : Sort(score, name); //升序排序学生成绩, outname(name); outscore(score); name->Reverse(name); //逆转姓名数组,按升序排序 score->Reverse(score); //逆转换成绩数组,按升序排序 Console: : Write. Line("n-------排序后的成绩:降序----------"); outname(name); outscore(score); String ^stu; //存放查找学生姓名 Console: : Write("输入学生姓名:"); stu=Console: : Read. Line(); //输入查找学生姓名 int n=Array: : Binary. Search(name, stu); //在name数组中查找stu, 入n if(n<0) Console: : Write. Line("查无此人!"); //Binary. Search在没有找到时返回负数 else Console: : Write. Line("{0}t{1}", name[n], score[n]); return 0; }
9. 7. 2 二维托管数组 • 二维托管数组也包括引用、数值和本地指 针三种类型,它们在定义形式上,除了比 一维托管数组多一个下标维数之外,其余 方面都相同,如下所示。 (1)array<type 1, 2>^var = gcnew array<type 2>(val 1, val 2) (2)array<type 1^, 2>^var = gcnew array<type 2>(val 1, val 2) (3)array<type 1*, 2>^var = gcnew array<type 2>(val 1, val 2)
下面的程序段示例了引用类型托管二维数组的定义与访问方 法: array<String^, 2>^ gc 1 = gcnew array<String^, 2>{ {"one", "two"}, {"three", "four"} }; array<String^, 2>^ gc 2 = { {"one", "two"}, {"three", "four"} }; array<String^, 2>^ gc 3=gcnew array<String^, 2>(3, 3); gc 3[0, 0]="one-1"; gc 3[0, 1]="one-2"; gc 3[0, 2]="one-3";
• 下面的程序段示例了值类型二维数组的定 义与访问方法,定义的数组是val[10, 10], 但只初始化了前面 6个元素,其余元素被初 始化为 0。 array<Int 32, 2>^ val 1 = gcnew array<Int 32, 2>(10, 10){ {0, 1, 2}, {2, 3, 4} }; for(int i=0; i<10; i++) for(int j=0; j<10; j++) Console: : Write("{0}t", val[i, j]);
• 下面的程序段示例了本地指针类型托管二维数组的定义与 访问方法。 array<int*, 2>^ nat = gcnew array<int*, 2>(5, 5){ {new int(0), new int(1), new int(2)}, {new int(2), new int(3), new int(4)} }; for(int i=2; i<5; i++) for(int j=2; j<5; j++) nat[i, j]=new int(i*j);
【例9 -14】设计一个学生类Student,其中包 括的字段有学生的姓名、年龄、及一个保 存英语、数学、语文三科成绩的一维数组, 并具有输入数据和显示数据的方法。用二 维数组保存各班的学生数据,每个班的学 生占据二维数组的一行。
// CH 9 -14. cpp: 主项目文件 #include "stdafx. h" using namespace System; ref class Student { String ^name; //姓名 array<int > ^score; //保存成绩的一维数组 int age; //年龄 public: Student(){ name=gcnew String(""); score=gcnew array<int>(3); //建立成绩数组 age=0; }
void in. Data(){ //Read. Line()读入String型字符串,用Convert转换成数值 Console: : Write("姓名: "); name=Console: : Read. Line(); Console: : Write("年龄: "); age=Convert: : To. Int 32(Console: : Read. Line()); Console: : Write("语文: "); score[0]=Convert: : To. Int 32(Console: : Read. Line()); Console: : Write("数学: "); score[1]=Convert: : To. Int 32(Console: : Read. Line()); Console: : Write("英语: "); score[2]=Convert: : To. Int 32(Console: : Read. Line()); } static void head(){ //全类共用静态成员函数,输入学生信息的表头 Console: : Write. Line("姓名t年龄t语文t数学t英语"); } void display(){ //输出学生的各项数据 Console: : Write. Line("{0}t{1}t{2}t{3}t{4}", name, age, score[0], scor e[1], score[2]); } };
int main() { array<Student^, 2>^ stu=gcnew array<Student^, 2>(2, 2); for(int i=0; i<2; i++) //i代表第i班 for(int j=0; j<2; j++){ //j代表第i班的第j个学生 stu[i, j]=gcnew Student; //在托管堆中建立一个学生 stu[i, j]->in. Data(); //输入学生的各项数据 } Console: : Write. Line("-------------------------"); Student: : head(); //输出表头,head为static方法,可用类名调用 for(int i=0; i<2; i++) for(int j=0; j<2; j++) stu[i, j]->display(); //输出每个学生的成绩,每个学生的数据占一行 }
9. 8委托与事件 1、委托的概念 所谓委托,就是封装了一系列方法指针的类, 它能够绑定到托管类中的一个或多个方法 上。包含方法指针的一个委托对象可以传 给另一个方法。这样一来,对象就不必直 接发送方法指针给其它方法,它可以将需 要发送的方法指针包装在委托对象中发送 给其它方法,其它方法在接收到指向委托 对象后,就能调用委托所包含的方法。
9. 8. 1委托 2、委托的声明 用delegate声明委托。在委托声明中,要指定一个方法原型( 参数和返回值)。在程序编译时,编译器会使用委托的声明 来创建一个从Multicast. Delete类(System中的一个类)继 承的托管类。该类具有与委托声明相同的名称. 例如: delegate double defg(double a, double b); 委托定义了一个以System: : Delegate作为基类的引用类,它继 承了Delegate类中的方法。因此,defg是从Delegate派生的 一个类,它的一个对象可以包括一个具有两个double类型的 参数,且返回double类型的值的方法指针
9. 8. 1委托 2、委托的声明 用delegate声明委托。在委托声明中,要指定一个方法原型( 参数和返回值)。在程序编译时,编译器会使用委托的声明 来创建一个从Multicast. Delete类(System中的一个类)继 承的托管类。该类具有与委托声明相同的名称. 例如: delegate double defg(double a, double b); 委托定义了一个以System: : Delegate作为基类的引用类,它继 承了Delegate类中的方法。因此,defg是从Delegate派生的 一个类,它的一个对象可以包括一个具有两个double类型的 参数,且返回double类型的值的方法指针
9. 8. 1委托 3、委托的创建 • 委托声明定义了一个委托类,并且确定了它能够传 递的方法类型,只要与委托声明的形参表和返回类 型相同的方法都可通过该委托进行调用。 • 委托类具有两个构造函数,一个构造只需要一个参 数,该参数是一个静态方法的地址;另一个构造函 数需要两个参数,第一个参数是一个托管对象,第 二个参数则是该托管对象的某个方法,该方法必须 与委托声明中的形参表和返回类型一致
9. 8. 1委托 3、委托的创建 • 委托声明定义了一个委托类,并且确定了它能够传 递的方法类型,只要与委托声明的形参表和返回类 型相同的方法都可通过该委托进行调用。 • 委托类具有两个构造函数,一个构造只需要一个参 数,该参数是一个静态方法的地址;另一个构造函 数需要两个参数,第一个参数是一个托管对象,第 二个参数则是该托管对象的某个方法,该方法必须 与委托声明中的形参表和返回类型一致
委托创建的例子 ref class dele. Class 1{ //一个普通的托管类 public: double max(double a, double b){return a>b? a: b; } double min(double a, double b){return a<b? a: b; } static double add(double a, double b){return a+b; } }; delegate double defg(double a, double b); //声明委托 dele. Class 1 ^obj 1=gcnew dele. Class 1; defg ^df=gcnew defg(obj 1, &dele. Class 1: : max); //建立委托对象 defg ^df 0=gcnew defg(&dele. Class: : add); //创建委托对象
• 4、委托调用 • 委托对象是由委托类创建的,委托类总是直接派 生于System: : Multicast. Delegate类,该类又派 生于System: : Delegate类型。这两个类提供了 Invoke方法,通过Invoke方法可以调用绑定到委 托的方法。 • Invoke和被调用的方法具有相同的参数,并且返 回相同的类型。例如,可以用下面的代码调用 obj 1对象的max方法: df->Invoke(4, 5); • 由于df是绑定到obj 1对象的max方法的委托,因 此df->Invoke(4, 5)实际等效于obj 1 ->max(4, 5)调 用。
5、多播委托 • 使用委托的Invoke方法一次可以调用一个 或多个方法,一次调用多个方法的委托称 为多播委托。 • 委托类重载了“+”和“-”运算符,应用它能够 将能够将两个委托的调用列表组合成一个 新的委托对象,应用“-”能够从委托中移除 移除方法指针,实现多播委托的管理 . • 请看下面的例子
5.多播委托 例如,对于前面定义的委托df,可以用“+”为它添加更多的 方法指针: df+=gcnew defg(obj 1, &dele. Class 1: : max); df+=gcnew defg(obj 1, &dele. Class 1: : min); df+=gcnew defg(obj 1, &dele. Class 1: : max); df->Invoke(9, 2); df->Invoke将依次调用:obj 1 ->max(9, 2),obj 1 ->min(9, 2),obj 1>max(9, 2),相当于3次函数调用。 df-=gcnew defg(obj 1, &dele. Class 1: : min); 现在df中就只包括obj 1对象的两个max方法的指针。df->Invoke将依次 调用:obj 1 ->max(9, 2),只有2次函数调用。
【例9 -15】委托应用举例。 // CH 9 -15. cpp: 主项目文件。 #include "stdafx. h" using namespace System; ref class dele. Class 1{ double x, y; public: double max(double a, double b){ Console: : Write. Line("invoke dele. Class 1: : max({0}, {1})", x, y); return a>b? a: b; } double min(double a, double b){ Console: : Write. Line("invoke dele. Class 1: : min({0}, {1})", x, y); return a<b? a: b; } static double add(double a, double b){ Console: : Write. Line("invoke dele. Class 1: : add"); return a+b; } static double sub(double a, double b){ Console: : Write. Line("invoke dele. Class 1: : sub({0}, {1})"); return a-b; } };
ref class dele. Class 2{ public: double max(double a, double b){ Console: : Write. Line("invoke dele. Class 2: : max"); return a>b? a: b; } double min(double a, double b){ Console: : Write. Line("invoke dele. Class 2: : min"); return a<b? a: b; } static double add(double a, double b){ Console: : Write. Line("invoke dele. Class 2: : add"); return a+b; } }; delegate double defg(double a, double b); //L 0
void main() { dele. Class 1 ^obj 1=gcnew dele. Class 1 ; //L 1 dele. Class 2 ^obj 2=gcnew dele. Class 2; defg ^df=gcnew defg(obj 1, &dele. Class 1: : max); //L 3 df->Invoke(2, 3); //L 4 df=gcnew defg(obj 1, &dele. Class 1: : min); //L 5 df->Invoke(6, 7); //L 6 df+=gcnew defg(obj 1, &dele. Class 1: : max); //L 7 df+=gcnew defg(obj 2, &dele. Class 2: : min); //L 8 Console: : Write. Line(df->Invoke(10, 100)); //L 9 df-=gcnew defg(obj 2, &dele. Class 2: : min); //L 10 df-=gcnew defg(obj 1, &dele. Class 1: : max); //L 11 Console: : Write. Line(df->Invoke(10, 100)); //L 12 //下面的代码示例用于包装静态方法的委托创建和应用方法 defg ^df 1=gcnew defg(&dele. Class 1: : add); //L 13 df 1 ->Invoke(3, 4); //L 14 df 1+=gcnew defg(&dele. Class 2: : add); //L 15 df 1 ->Invoke(3, 3); //L 16 } //L 2
9. 8. 2事件 1、事件的概念 • 托管C++中的事件包括事件源、事件、事件接收者、 事件处理四要素,事件源又称为事件发布者。事件 处理的逻辑是:由事件源定义和发布事件,事件接 收者接收事件,并对事件进行处理。 • 托管C++采用发布——订阅的方式处理事件。由事 件源公布事件,即发布事件,然后事件接收者告诉 事件源它所感兴趣的事件,即订阅事件(将事件接 收者对象及它对事件的处理方法添加到处理该事件 的委托中)。当接收者对某事件不再感兴起时,也 可以取消订阅。事件接收者只有订阅事件后,事件 才能传达到该接收者,接收者才能处理事件。
9. 8. 2事件 2、事件源、事件接收者和委托的关系 事件源和事件接收者都是普通类,事件源 利用委托定义事件,再通过委托为事件接 收者订阅事件,当事件发生时,会通过委 托调用接收者处理该事件的方法。 事件接收者 三者关系如图所示。 事件源 事 件 委 托 事件接收者
9. 8. 2事件 【例9 -16】设计一个模拟鼠标左击和右击的 事件处理程序。定义一个事件源类 Event. Source,它可以引发鼠标左击、右 击事件On. LClick和On. RClick。定义两个事 件接收者类Event. Receiver 1和 Event. Receiver 2,Event. Receiver 1能够处 理鼠标左击和右击事件,Event. Receiver 2 只能处理鼠标左击事件。
// CH 9 -16. cpp: 主项目文件。 #include"stdafx. h" using namespace System; delegate void LClick. Handler(int, double); // 处理鼠标左击事件的委托 delegate void RClick. Handler(String^); // 处理鼠标右击事件的委托 ref class Event. Source{ // 事件源类 public: event LClick. Handler^ On. LClick; // 定义鼠标左击事件 event RClick. Handler^ On. RClick; // 定义鼠标右击事件 void Fire. Events(int i, double d, String ^s) { // 激发事件的方法 On. LClick(i, d); // 发生左击事件,与委托LClick. Handler的参数表匹配 On. RClick(s); // 右击事件,与委托RClick. Handler的参数表匹配 } };
ref class Event. Receiver 1 { //事件接收者类 1 public: void On. LClick(int i, double d) { //左击委托LClick. Handler的参数表匹配 Console: : Write. Line("On. Click: {0}, {1}", i, d); } void On. RClick(String^ str) { //右击与委托RClick. Handler的参数表匹配 Console: : Write. Line("On. Dbl. Click: {0}", str); } }; ref class Event. Receiver 2 { //事件接收者类 2 public: void on. My. Click(int i, double d) {//左击与委托LClick. Handler的参数表匹配 Console: : Write. Line("On. Click: {0}, {1}", i, d); } };
int main() { Event. Source ^ Source = gcnew Event. Source(); //Source为事件源对象 Event. Receiver 1^ Rever 1 = gcnew Event. Receiver 1(); //Rever 1,Rever 2为 事件接收者对象 Event. Receiver 2^ Rever 2 = gcnew Event. Receiver 2(); // 为接收者订阅事件,或者说为事件指定事件处理方法。Rever 1订阅了On. LClick、On. RLick事件, //能够处理此二事件;Rerver 2订阅了On. LClick事件,因些它只 Source->On. LClick += gcnew LClick. Handler(Rever 1, &Event. Receiver 1: : On. LClick); Source->On. LClick += gcnew LClick. Handler(Rever 2, &Event. Receiver 2: : on. My. Click); Source->On. RClick += gcnew RClick. Handler(Rever 1, &Event. Receiver 1: : On. RClick); Source->Fire. Events(3, 3. 33, "Hellow"); //激发事件,将依次调用事件接收对象中的事件处理程序 //取消事件订阅,当事件源对象Source中的On. LClick事件发生时,没有任何事件接收者会处理它 Source->On. LClick -= gcnew LClick. Handler(Rever 1, &Event. Receiver 1: : On. LClick); Source->On. LClick -= gcnew LClick. Handler(Rever 2, &Event. Receiver 2: : on. My. Click); Source->Fire. Events(9, 9. 99, "Hi, Eevery one"); }
6e61fbe263cb5729060ac3128cf75638.ppt