admin 管理员组文章数量: 1184232
2024年12月30日发(作者:head of)
第17章C#语言简介
C#(CSharp)是微软于2000年提出的一种源于C++、类似于Java的面向对象编程语
言,适合于分布式环境中的组件开发。C#是专门为.NET设计的,也是.NET编程的首选语
言。
17.1OOP语言
OOP(Object-OrientedProgramming,面向对象编程)是目前主流的编程技术,本节介
绍其中最重要的C++、Java和C#,重点是它们三者之间的关系与对比。
17.1.1OOP概述
程序设计语言,在思想上发展很快,在技术上也是不断创新。经历了手工操作(控制面
板)、机器语言、汇编语言、宏汇编语言、高级语言和第4代语言等多个阶段。OOP只是在
高级程序设计语言中,流行的多种编程技术之一。
1.基本特征
面向对象编程的三个基本特征是:
封装(encapsulation)——是对问题的抽象,以达到信息隐藏的目的。通过类和对
象,将属性(数据/变量)和方法(操作/函数)封装在一个黑箱内,将细节隐藏起
来。既可以保护数据不被他人恶意或大意地修改,又能防止其他程序员编写高耦合
度的代码,还方便了今后对对象内部的修改和升级。
继承(inheritance)——利用问题和事物的相似性,通过类的(多层)继承机制,
可以达到减少软件开发难度和重用已有对象的属性和方法之目的。
多态(polymorphism)——同一操作或响应,可以同时针对多个可选的类型和对
象,并且能够自动在它们中间选择最合适的来进行。多态可以分为:
编译时多态:包括函数和运算符的重载(overload),通过早期的静态绑定
(binding)来实现。
运行时多态:通过继承结合晚期动态绑定来实现——用同一基类的指针,可以
1
访问各个不同派生类中的同名方法和函数。
2.基本概念
面向对象编程的基本概念(类似的概念多、表达不够准确、各处的用法各异):
抽象(abstraction)——一个问题的本质特征。忽略一个对象的细节,致力于一般
的合适级别的程序能力。抽象是在事物的周围绘制透明的盒子,是定义事物接口的
行为。
信息隐藏(informationhiding)——限制外部对类属性(数据成员)的访问。
封装(encapsulation)——将某种保密和隐匿应用到类中的数据和一些方法(函数
或子例程)上。封装确保一个对象只能通过确定的渠道(即类的公用方法)才可被
修改。封装意味着将数据及相关函数打包到一个单一的单元(类)中。每个对象暴
露一个接口(公用方法),规定其他对象可以如何读取或修改它。
类(class)——问题的抽象,对象的模版,接口的实现。例如int是整数的模版、
CPoint是点的模版。
对象(object)——问题中的事物,类的实例(变量),拥有数据和功能。若inti;
CPointp;则i是int的实例变量,p是CPoint的实例对象。
实例(instance)——一个对象,是某个类的示例。
实例化(intantiate)——由类定义创建对象。
继承(inheritance)——创建子类的机制,一种“isa”或“islike”关系,形成类
层次结构。是OOP中对象重用的核心概念。
子类(subclass)——继承自另一个类的派生类(derivedclass)。
超类(superclass父类)——被另一个类继承的基类(baseclass)。
虚函数(virtualfunction)——可以在子类中被覆盖(override)的函数。
纯虚函数(purevirtualfunction)——只有声明没有定义的函数,必须在派生类中
被覆盖。
抽象类(abstractclass)——至少包含一个纯虚函数的类,不能被实例化。
具体类(concreteclass)——可以实例化的类,不包含纯虚函数。
接口(interface)——纯抽象的类,类的模版。接口中的所有的方法(成员函数)
都只有声明,没有定义(都是纯虚函数)。必须在接口的派生类中,实现接口的全
2
部方法。接口可以看成是一个,定义了一套内部行为的,若干操作特征标记的集合。
(在C++中,也把类的公用成员函数叫做接口)。
属性(attribute)——类和接口中的数据信息,又叫数据成员(datamember)或成
员变量(membervariable)。属性是类知道的事物。
方法(method)——类操作的实现,又叫成员函数(memberfunction)。方法是类
要做的事情。(在C++中,也把虚函数叫做方法)。
重载(overload)——一个类中的,参数的数目、类型或排列顺序不同的多个同名
方法(成员函数)。
覆盖(override)——在子类中重新定义(与父类中的定义有区别的)属性和方法。
持久化(persistence)——将对象存入永久存储中(如文件、数据库等)。
持久对象(persistentobject)——存入永久存储的对象。
3.简史
计算机科学中,对象的概念和实例,最早于1960年出现在MIT的PDP-1中,1963年
IvanSutherland在其博士论文中所编写的计算机程序Sketchpad中应用了对象技术。
但是,作为一种程序设计方法,OOP最早出现在1967年,由挪威计算中心的Ole-Johan
Dahl和KristenNygaard,在他们发明的Simula67语言中首先引入的。Simula语言是在Algol60
基础上,加入了对象、类和继承等概念后开发出来的,它在OOP的历史中占有重要地位。
Smalltalk是1970年代,由美国施乐(Xerox)公司PARC(PaloAltoResearchCenter帕
洛阿尔托研究中心)的AlanKay、DanIngalls、TedKaehler和AdeleGoldberg等人,开发的
一种面向对象和动态类型的交互式程序设计语言。它受到了Sketchpad和Simula的影响,但
是Smalltalk被设计成一个全动态系统的纯OOP,程序的所有成分都是对象(但是,为了提
高运行效率,C++/Java/C#中的基本数据类型都不是对象),对象可以被动态地创建、修改
和销毁。在Smalltalk中还创造出词汇“'inheritance(继承)”和“Object-orientedprogramming
(面向对象的程序设计)”。
使OOP走向辉煌的是1985年发明的C++,随着GUI(GraphicalUserInterfaces,图形
用户界面)的流行,C++的地位更加巩固。但是,因特网的发展和万维网的流行,又催生了
另一个重要的OOP语言——Java(1995年)。为了与Sun公司的Java竞争,微软公司也推
出了一种用于.NET组件编程环境的新OOP语言——C#(2000)。
3
4.语言谱系图
C语言系列的发展简图如图17-1所示。C语言是专门为编写操作系统而发明的,是一
种面向系统编程的中级语言。C++是带类的C,是为面向对象编程而设计的。Java最初是为
嵌入式设备创建的,后来改造成适用于跨平台的网络编程。C#则是专门为面向组件编程
的.NET而设计的一种源于C++、类似于Java的OOP语言。
C
(DennisRitchie1972)
面向系统编程
C++
(BjarneStroustrup1985)
面向网路编程
Java
(JamesGosling1995)
面向对象编程
面向组件编程
C#
(AndersHejlsberg2000)
图17-1C语言系列
图17-2给出了可用于.NET编程的主要高级语言(包括C#、VB、C++/CLI、J#和F#等)
的发展脉络。
17.1.2C++
C++是第一个广泛流行的OOP语言,C++是在C语言的基础上,添加了面向对象和泛
型编程的功能后,扩展而成的。C语言是C++的子集,C++是C语言的超集。
1.C
C语言是为编写操作系统而创建的一门高级语
言,语法简洁、表达自由、库函数丰富,具有强大
的功能和极大的灵活性,适用于高效的系统编程。
1)起源
KenThompson(左)与DennisRitchie
1970年,AT&T贝尔实验室的美国计算机科学家KenThompson和DennisRitchie等人
在DEC公司的PDP-7小型机上开发出了Unix操作系统,最初的实现是用汇编语言写成的。
4
为了使Unix操作系统具有可移植性,迫切需要一种高级语言工具。为此,DennisRitchie
以B语言为基础,参考了Algol68,于1972年设计出了C语言。1973年他们用C语言重写
了Unix,1975年又利用C语言将Unix移植到了PDP-11上。
Fortran
(IBM/JohnBacus1957)
Algol60
(PeterNaur1960)
Simula67
(Ole-JohanDahl&
KristenNygaard1967)
Smalltalk
(AlanKay
等
1972)
CPL
(MIT1963)
BCPL
(MartinRichards1967)
B
(KenThompson1970)
C
(DennisRitchie1972)
C++
(BjarneStroustrup1985)
Java
(Sun/JamesGosling1995)
VisualJ++
(Microsoft/Anders
Hejlsberg1998)
C++/CLI
VisualJ#
(Microsoft2003)
(Microsoft/Stanley
n2005)
.NET/CLI
(Microsoft/Anders
Hejlsberg2000)
ManagedC++
(Microsoft2002)
C#
(Microsoft/AndersHejlsberg2000)
(Microsoft/Paul
Vick2002)
Pascal
(NiklausWirth1970)
ObjectPascal
(NiklausWirth&
LarryTesler1985)
Delphi
(Borland/Anders
Hejlsberg1995)
VisualBasic
(Microsoft/Alan
Cooper1991)
Basic
(JohnKemeny&
ThomasKurtz1964)
ISWIM
(1966)
ML
(RobinMilner
等
1973)
Caml
(GérardHuet
等
1985)
OCaml
(XavierLeroy
等
1996)
F#
(Microsoft/Don
Syme2002)
图17-2
2)特点
主要编程语言工具的发展
C语言是一种可移植的系统语言,拥有充分的控制语句和数据结构功能,包含丰富的操
作符,从而能够提供强大的表达能力,可以用于许多不同的应用领域。但是,C语言并不是
面向科学家和计算机业余爱好者的,而是专门为程序员设计的。
5
为了进行高效的系统编程,C语言提供了强大的功能和极大的灵活性。与其它高级语言
相比,C语言的语法简洁、表达自由、库函数丰富。如果将编程比作造房子,则Fortran和
Basic等语言就像一些已经预先造好的大预制件,使用起来简单快捷,但是灵活性差、且功
能有限,只能造某些固定模式的房屋;而C语言就像一块块的小砖,使用起来虽然繁琐,
但是灵活性强、而且功能无限,能够造各式各样的建筑物,不过这就要求C语言程序员具
有很高的专业水平。
因此,C语言假设使用者都是计算机专家,采取的是程序员负责制。它不进行完备的类
型检查,对数组越界也没有限制。为了进行高效的系统编程,C语言还提供了指针和指针运
算,程序员可以随意操作全部内存,任意修改任何内容。
表达的自由性和操作的任意性,也给C语言带来了很多编程问题和安全隐患。特别是C
语言的++/--运算符和指针运算,更是倍受指责。
与其它高级语言相比,C语言提供了一些低级语言特征,更面向机器。所以,也有人称
C语言是介于高级语言和低级语言之间的一种中级语言。
3)标准
(1)K&RC
开始的很多年,C语言没有国际标准,只有一个事实标准——1978年BrianKemighan
和DennisRitchie编写的《C程序设计语言》(TheCProgrammingLanguage)一书,通常称
其为K&RC或经典C。该书的附录“C参考手册”(CReferenceManual)成为了C语言的
实现指南,但是书中缺少对库函数标准的描述,一般以Unix实现的库函数所为事实标准。
需要说明的是,因为C语言的语法成分简单,很多基本功能(例如I/O语句)都是靠库函数
来实现的。所以,C语言比其它高级语言更依赖于库函数。
(2)C90
1983年ANSI(AmericanNationalStandardsInstitute美国国家标准协会)设立了一个
X3J11小组,着手进行C语言的标准化。并最终于1989年推出ANSIC(ANSIX3.159-1989),
1990年它又成为国际标准ISOC(ISO/IEC9899:1990Programminglanguages–C,程序设计
语言——C),原来叫做ANSIC或ISOC,现在通常称其为C89或C90。
C90对K&RC的主要改变是,增加了函数原型(prototype),强调对函数的输入参数进
行严格的类型检查;允许将结构本身作为参数传递给函数(原来只允许传地址);并补充定
义了C语言的标准函数库。增加了关键字:const(常型变量)、enum(枚举类型)、signed
(有符号的,例如signedchar)、void(空/无,可用于函数返回值和形参、通用指针类型)、
6
volatile(易变变量,防止编译器错误的优化)等。还增加了预处理指令:#elif(elseif)、#error
(错误,强制编译停止)、#line(修改当前行号和源文件名)、#pragma(附注/编译指令,编
译器定义的与实现有关的指令)。
(3)C99
对C90的修订工作开始于1994年,在ISOC++(1998)标准推出之后,ISO又于1999
年12月16日,推出了C语言标准的第2版:ISO/IEC9899:1999Programminglanguages–C
(程序设计语言——C),一般称其为C99。
C99主要的修订目标有三点:①支持国际化编程,引入了支持国际字符集Unicode的数
据类型和库函数;②修正原有版本的明显缺点。如整数的移植方法,例如int8_t、int16_t、
int32_t和int64_t等类型;③针对科学和工程的需要,改进计算的实用性。例如添加了复数
类型和新数学函数。
C99对C89/C90的具体修改有:①增加了C++的//注释风格:原来C语言只支持多行注
释:/*……*/,C99现在也识别单行注释://……。②增加了关键字:inline(内联函数)、restrict
(限制)、_Bool(布尔类型)、_Complex(复数)、_Imaginary(虚数)。③增加了数据类型:
(unsigned)longlong[int](64位整数)(对应的打印输出格式为%lld或%llu)。④定义了可移
植整数类型,如:int8_t、int16_t、int32_t、int64_t,uint8_t、uint16_t、uint32_t、uint64_t;
intptr_t、uintptr_t。以及表示对应类型常量的方法,如INT8_C(128)、INT32_C(1234)。⑤增
加了浮点常量的十六进制格式:p或P表示后跟二进制指数(的十进制值)。例如:0xa.1cp10
=(10+1/16+12/256)*2
10
=10352.0。⑥增加了浮点数的十六进制打印格式符:%a或%A(代
替十进制的%e或%E)、%La或%LA(代替十进制的%Le或%LE)。⑦可指定初始化的条目
(未被初始化的条目全被置为0),如:intdays[12]={31,28,[4]=31,30,31,[1]=29};⑧支
持变长数组(即可用变量来定义数组的大小),如:floata[n],a2[m][n];⑨允许在代码块的任
何地方定义变量(似C++)。⑩允许在结构的最后定义一个大小可伸缩的弹性数组成员,可
以用于结构指针,根据允许情况来动态分配内存。但是,C99增加的新特性中,有许多当前
的C++标准还不支持。
2.C++起源
作为结构化的面向过程的编程语言,C已经是非常优秀的了,它简单、高效、灵活、功
能强大。但是,随着软件开发的规模越来越大,所针对的问题和系统越来越复杂,对软件维
7
护和重用的需求越来越高。仅仅靠面向过程的编程技术,就显
得有点力不从心了。因此,针对问题的面向对象编程技术,就
应运而生。
1985年,贝尔实验室的计算机科学家,丹麦人Bjarne
Stroustrup在Simula67的启发下,对C语言进行了扩充,在保
留C语言优点的基础上,添加了面向对象的特征和功能后,将C
语言扩展成为带类的C(CwithClasses),1983年RickMascitti
将其命名为C++。
1985年10月BjarneStroustrup实现了C++的第一个商用版本,与此同时他还出版了《The
C++ProgrammingLanguage(C++程序设计语言)》的第1版,成为C++的事实标准。C++
的第1版,提供了面向对象程序设计的基本特征:类和对象、单继承、虚函数、公有/私有
成员的访问控制、函数重载等。
1989年推出C++的第2版,增加了多继承、抽象类、静态和常型成员函数、保护成员
的访问控制、运算符重载等新特性,促使C++语言流行起来。1990年1月1日
和BjarneStroustrup出版了《TheAnnotatedC++ReferenceManual(带注释的C++参考手册)》。
1991年BjarneStroustrup推出了《C++程序设计语言》的第2版:TheC++Programming
Language(2ndedition)。
1993年推出C++的第3版,增加了模板、异常处理和嵌套类等新特性.1994年3月29
日BjarneStroustrup出版了《TheDesignandEvolutionofC++(C++的设计和演化)》。1997
年BjarneStroustrup推出了《C++程序设计语言》的第3版:TheC++ProgrammingLanguage
(3rdedition)。
3.C++标准
1994年7月,ANSI/ISOC++标准委员会,通过了AlexanderStepanov提出的泛型编程
方案STL(StandardTemplateLibrary,标准模板库)。
1998年9月1日C++成为国际标准(ISO/IEC14882:1998Programminglanguages--C++,
程序设计语言——C++),添加的新特性主要有:命名空间、新的强制类型转换、布尔类型
和STL。2000年2月15日BjarneStroustrup推出了《C++程序设计语言》的特别版:TheC++
ProgrammingLanguage(SpecialEdition)。
BjarneStroustrup
8
2003年10月15日ISO推出了C++标准的第2版ISO/IEC14882:2003,该版本在语法
特征上没有什么变化,只是纠正了原版的各种错误,并进行了一些技术上的修订。
2007年11月15日ISO又推出了C++的扩展库标准(TR1,技术报告1):ISO/IECTR
19768:2007Informationtechnology--Programminglanguages--TechnicalReportonC++
LibraryExtensions。微软公司在其VisualStudio2008SP1中的VisualC++2008功能包(Feature
Pack)支持此TR1。
现在的C++已经成为了一种同时具有面向过程、面向对象和泛型编程的特征,功能强大、
运行高效、使用广泛的混合型程序设计语言。
4.C++0x
C++0x是ISO计划于2011年底完成的下一版C++语言标准。从2004年2月7日起陆
续推出多个工作草案,当前最新的是2010年3月29日发布的
/JTC1/SC22/WG21/docs/papers/2010/
(/JTC1/SC22/WG21/docs/papers/2010/)。
新的C++标准将大幅改善C++内核语言领域,包括多线程支持、泛型编程、统一的初
始化,以及表现的加强等。具体的改进有:增加右值引用(typename&&)、扩展常数表达
式(引入新关键字constexpr)、放宽关于POD(PlainOldData,简单旧数据)型结构的定义、
引入外部模板概念(externtemplateclass)、将初始化列表的概念绑定到类型上
(std::initializer_list)、提供一种统一语法来初始化任意对象、可进行类型推导(auto)、支持
以范围为基础的for循环、允许定义λ(lambda)函数、引进一种新的函数定义与声明的语
法(template
->decltype(lhs+rhs){returnlhs+rhs;})、允许派生类手动继承基底类的构造函数(using基类
名::基类名;)、引入表示空指针常数的新关键字(nullptr)、引进一种特别的枚举类(enum
class)、关键字explicit修饰符可用于显示类型转换、可用于模板类型的别名定义
(template<…>usingTypedefName=SomeType<…>;)、移除所有对union的使用限制(除
了其成员仍然不能是引用类型)、加入(允许任意个数和任意类别的模板实参的)变长参数
模板、增加两种新的字符类别char16_t(UTF-16)和char32_t(UTF-32)(传统的char类型
对应于UTF-8)和新的字符串常量(u8"I'maUTF-8string."、u"ThisisaUTF-16string."、U"This
isaUTF-32string.")、允许用户自定义新的常量修饰符(literalmodifier)、引入支持多线程编
9
程的多任务存储器模型和线程区域的存储期限(thread_local)、可使用或禁用对象的默认函
数(default和delete)、(同C99)增加64位的整数类型(longlongint)、增加用于测试的静
态声明(assertion)、允许sizeof操作符作用在类的数据成员上(无须明确的对象)。
C++0x标准程序库将引进数个新功能,其中许多可以在现行标准下实现,而另外一些则
依赖于(或多或少)新的C++0x内核语言机能。新的程序库的大部分被定义于于2005年发布
发TR1(LibraryTechnicalReport,库技术报告),各式TR1的完全或部分实现目前提供在命
名空间std::tr1,C++0x会将其移置于命名空间std之下。例如,增加了处理正则表达式(regular
expression)的库(对应的头文件为
5.关键字
表17-1列出了标准C++(1998/2003)的63个关键字(keywords)。
表17-1
asm
auto
bool
break
case
catch
char
class
const
const_cast
continue
default
delete
do
double
dynamic_cast
else
enum
explicit
export
extern
false
float
for
friend
goto
if
inline
int
long
mutable
namespace
new
operator
private
protected
public
register
reinterpret_cast
C++关键字
return
short
signed
sizeof
static
static_cast
struct
switch
template
this
throw
true
try
typedef
typeid
typename
union
unsigned
using
virtual
void
volatile
wchar_t
while
其中:斜体为传统C++的关键字(15个)、粗体为ISO标准C++新增加的关键字(15个)、
其余(正常的)为C和C++共有的关键字(33个)。另外,标准C++没有的C99关键字有3
个:_Bool、_Complex、_Imaginary。除了这些关键字外,标准C++中还有11个保留标识符,
用作运算符的备选表示,参见表17-2。
10
表17-2C++的保留标识符
and
not_eq
and_eq
or
bitand
or_eq
bitor
xor
compl
xor_eq
not
6.命名空间
在C++中,名称(name)可以是符号常量、变量、宏、函数、结构、枚举、类和对象
等等。为了避免在大规模程序的设计中,以及在程序员使用各种各样的C++库时,这些标识
符的命名发生冲突,标准C++引入了关键字namespace(命名空间),可以更好地控制标识
符的作用域。(MFC中并没有使用命名空间,但是在.NET框架、MC++和C++/CLI中,都
大量使用了命名空间。)
C语言标识符有局部(代码块{…},如复合语句和函数体)和全局两种作用域,C++在
这二者之间引入了类作用域(如类变量和成员函数)。标准C++又在类和全局之间,新添加
了命名空间这一个作用域级别。
命名空间是一种描述逻辑分组的机制,可以将按某些标准在逻辑上属于同一个集团的声
明放在同一个命名空间中。命名空间可以是全局的,也可以位于另一个命名空间之中,但是
不能位于类和代码块中。所以,在命名空间中声明的名称(标识符),默认具有外部链接特
性(除非它引用了常量)。在所有命名空间之外,还存在一个全局命名空间,它对应于文件
级的声明域。因此,在命名空间机制中,原来的全局变量,现在被认为位于全局命名空间中。
标准C++库(不包括标准C库)中所包含的所有内容(包括常量、变量、结构、类和
函数等)都被定义在命名空间std(standard,标准)中了。
1)定义命名空间
有两种形式的命名空间——有名的和无名的,它们的定义方法分别为:
namespace命名空间名{//有名命名空间
[声明序列]
}
namespace{//无名命名空间
[声明序列]
}
命名空间的成员,是在命名空间定义中的花括号内声明了的名称。可以在命名空间的定
11
义内,定义命名空间的成员(内部定义)。也可以只在命名空间的定义内声明成员,而在命
名空间的定义之外,定义命名空间的成员(外部定义)。
命名空间成员的外部定义的格式为:
命名空间名::成员名……
注意:不能在命名空间的定义中声明(另一个嵌套的)子命名空间,只能在命名空间的
定义中定义子命名空间。也不能直接使用“命名空间名::成员名……”定义方式,为命名
空间添加新成员,而必须先在命名空间的定义中添加新成员的声明。另外,命名空间是开放
的,即可以随时把新的成员名称加入到已有的命名空间之中去。方法是,多次声明和定义同
一命名空间,每次添加自己的新成员和名称。例如:
namespaceA{inti;
namespaceA{intj;
2)使用命名空间
使用命名空间的方法有三种:
)
作用域限定(直接使用解析运算符“::”
对命名空间中成员的引用,需要使用命名空间的作用域解析运算符::。例如:
std::cout<<"Hello,World!"< using指令(usingnamespace) 为了省去每次调用命名空间成员和标准库的函数和对象时,都要添加“命名空间名::” 和“std::”的麻烦,可以使用标准C++的using编译指令来简化对命名空间中的名称的使用。 格式为: usingnamespace命名空间名[::子命名空间名……]; 在这条语句之后,就可以直接使用该命名空间中的标识符,而不必写前面的命名空间定位部 分。因为using指令,使所指定的整个命名空间中的所有成员都直接可用。例如: usingnamespacestd; cout<<"Hello,World!"< 又例如(.NET框架): usingnamespaceSystem::Drawing::Imaging; usingnamespaceSystem::Window::Forms::Design::Behavior; using声明(using) 除了可以使用using编译指令(组合关键字usingnamespace)外,还可以使用using声 12 voidf();}//现在A有成员i和f() voidg();}//现在A有成员i、f()、j和g() 明来简化对命名空间中的名称的使用。格式为: using命名空间名::[命名空间名::……]成员名; 注意,关键字using后面并没有跟关键字namespace,而且最后必须为命名空间的成员名(而 在using编译指令的最后,必须为命名空间名)。 与using指令不同的是,using声明只是把命名空间的特定成员的名称,添加该声明所 在的区域中,使得该成员可以不需要采用,(多级)命名空间的作用域解析运算符来定位, 而直接被使用。但是该命名空间的其他成员,仍然需要作用域解析运算符来定位。例如: usingstd::cout; cout<<"Hello,World!"< 使用方法的比较(参见表17-3) 表17-3 方法 作用域限定 using声明 using指令 格式 命名空间::成员 using命名空间::成员 usingnamespace命名空间名 命名空间使用方法的比较 特点 安全、麻烦 较安全、较麻烦 不安全、方便 适用 个别成员的偶尔使用 特定成员的经常使用 大量成员的经常使用 直接使用解析运算符“::”来访问命名空间成员,非常安全,没有命名冲突,但是书写 非常麻烦,只适用于个别成员的偶尔使用。而using编译指令和using声明,都可以简化对 命名空间中名称的访问。using指令使用后,可以一劳永逸,对整个命名空间的所有成员都 有效,非常方便。而using声明,则必须对命名空间的不同成员名称,一个一个地去声明, 也很麻烦。 但是,一般来说,使用using声明会更安全。因为,using声明只导入指定的名称,如 果该名称与局部名称发生冲突,编译器会报错。而using指令导入整个命名空间中的所有成 员的名称,包括那些可能根本用不到的名称,如果其中有名称与局部名称发生冲突,则编译 器并不会发出任何警告信息,而只是用局部名去自动覆盖命名空间中的同名成员。特别是命 名空间的开放性,使得一个命名空间的成员,可能分散在多个地方,程序员难以准确知道, 别人到底为该命名空间添加了哪些名称。 虽然使用命名空间的方法有多种可供选择,但是不能贪图方便,一味使用using指令, 这样就完全背离了设计命名空间的初衷,也失去了命名空间所应该具有的防止名称冲突的功 能。一般情况下,对偶尔使用的命名空间成员,应该使用命名空间的作用域解析运算符来直 13 接给名称定位。而对一个大命名空间中的经常要使用的少数几个成员,提倡使用using声明, 而不应该使用using编译指令。只有需要反复使用同一个命名空间的许多数成员时,使用 using编译指令,才被认为是可取的。 17.1.3Java Java是Sun公司于1995年5月推出的一种跨平台的面向对象的编程语言,主 要用于网络编程,特别是Web服务器端[JavaEE/JSP]。 Java标识 1.起源 Java是1991年6月由Sun公司的(加拿大人)JamesGosling等人 为小型消费品电子(如电视遥控器等)项目Green开发的一种简单程序 设计语言。开始叫Oak(橡树,因为在JamesGosling的办公室外有一棵 橡树),由于与Sun的另一产品商标同名,1994年改为Java(爪哇—— 位于印度尼西亚西南部的一个大岛,盛产咖啡豆;因为研究小组在讨论 新名称时,面前都放有冒着热气咖啡杯,这也是Java语言的标识)。虽然在小型消费品电子 产品上失败,但是Java却特别适合网络程序的开发。1994年秋,他们用Java开发出支持 Applet、交互功能强大的动态浏览器WebRunner,获得了极大成功。1995年5月WebRunner 改名为HotJava,并与Java语言一起,由Sun公司正式推向市场。 Java具有面向对象、跨平台、安全稳定、多线程等特点,特别适合于网络编程。Java 源自C++,它从C++中继承了大多数语言成分,但是它也对C++进行了大量简化(Sun的创 始人之一/首席科学家/Java的主要作者之一BillJoy称Java为C++--)。例如,它抛弃了C++ 中复杂和容易引起问题的头文件、编译指令、预处理器、指针、结构、隐式类型转换、操作 符重载、多重继承和goto语句等。增加了字节码、虚拟机、垃圾(内存)回收(garbage collection)、接口、GUI支持、多线程、异常处理、网络编程、各种类库等内容。 Java语言的跨平台和安全性,都依赖于它的JVM(JavaVirtualMachine,爪哇虚拟机)。 Java是一种编译型解释语言(对比:Fortran/Pascal/C/C++为编译语言,早期的Basic为解释 语言),它先将Java源代码编译成专用的字节码(bytecode),再利用各个计算机平台上安装 的JVM来解释并运行这些字节码。 Java通过取消指针和添加虚拟机,在增强了安全性的同时,也具备了跨平台的能力。但 14 JamesGosling 这些却是以牺牲编程的灵活性和程序的运行效率为代价换来的。所以,Java并不太适合于, 对运行效率要求很高的本地(客户端)的(系统)编程。 开始时,Java虽然从C++继承了大量面向过程和面向对象的特征,但是并没有继承模 板。只是到了JDK1.5(JavaSE5.0,2004年9月30日推出)才加入对编译时静态绑定的泛 型编程的支持。所以,Java现在与C++一样,也可以算是一种同时具有面向过程、面向对 象和泛型编程的特征的混合型程序设计语言。 2.设计目标 虽然催生Java的源动力是可移植性和安全性,但是在其最终成型的过程中其他因素也 起了重要作用,下面是Java开发小组总结的Java的关键特性(设计目标): 简单(Simple)——易学好用,继承了C/C++的语法和许多C++的面向对象特征。去 掉了C++中的一些复杂困难的东西(如头文件、预处理、结构、联合、多重继承、操 作符重载、[模板]等),使语言更加精简。 安全(Secure)——为了适应分布式网络环境,采用基于策略(policy)的沙箱(sandbox) 模型,在语言层次上实现了安全性。具有如下三个方面的安全保证:语言特性(包括 检查数组边界、限制类型转换、取消指针变量等)、资源访问控制(包括本地文件系 统访问和连接访问网络等)和代码数字签名(通过数字签名确认源代码源以及代码是 否完整)。 可移植(Portable)——定长的整数型类型,如int始终为32位,而C/C++中的int在 Win16、Win32和Win64平台上分别为16位、32位和64位。固定的多字节整数二进 制格式,采用统一的高位字节在前的方案(IntelCPU中的多字节整数的低位字节在 前)。统一的用户界面(AWT/Swing[/SWT])。 面向对象(ObjectOriented)——由于没有设计C++时兼容C的负担,从零开始设计 的Java具有更纯洁的OOP特性。但是为了高性能,仍然保留了基本数据类型的非对 象性。在OOP方面与C++的区别主要有单一类继承、接口、反射等。 健壮(Robust)——异构网络环境要求程序可靠运行。采用的措施有:避免内存错误 (取消指针变量、提供内存管理——自动分配和释放内存[垃圾内存自动回收])、强制 异常处理等,可对潜在的问题进行早期和动态的检查。 多线程(Multithreaded)——交互式网络化的多任务程序(如Web服务器)及多核 15 CPU都要求多线程。Java提供了语言级的多线程支持,JVM也具有成熟的线程同步 解决方案。 体系结构中立(ArchitectureNeutral)——通过JVM和字节码,Java实现了体系结构 中立(与计算机硬件和操作系统无关)。跨平台是Java最重要的特征和最大的优点, “一次编写、到处运行”(WriteOnce,RunAnywhere,WORA)是Java的口号。正是 跨平台的特点,使得Java语言特别适合于网络编程。 解释型(Interpreted)——与Fortran、Pascal、C/C++等编译型高级语言不同,Java是 一种(编译性)解释语言:Java语言的源程序(*.java),先通过Java编译器编译成特 殊的字节码(*.class),然后由目标机上JVM将字节码解释(翻译成本机CPU指令代 码)执行。解释型是JVM的需要,也是跨平台的基础。 高性能(HighPerformance)——与追求速度的编译型语言相比,解释型语言在本质 上是低性能的,这也是Java语言跨平台的代价。为了克服Java程序运行慢的缺点, 可用JIT(Just-In-Time,及时)编译器,在程序的第一次运行时将(部分)字节码先 转换成本机代码,以提高Java程序的运行速度。 分布式(Distributed)或理解网络(Network-Savvy)——Java是为因特网的分布式环 境设计的,所以它支持TCP/IP协议、URL资源访问和RMI(RemoteMethodInvocation, 远程方法调用)。 动态(Dynamic)——Java程序带有大量运行时的类型信息,用于在运行时的验证和 解决对象的访问问题。这使得在安全有效的方式下动态连接代码成为可能。程序员可 从因特网上下载代码运行(浏览器),可全面控制对象的行为,如在特定的系统要求 下,在运行时分析对象(如GUI构建器、智能调试器、可插式组件、对象数据库等)。 Java语言主要优势是跨平台和安全(及类库丰富),主要缺点是运行速度慢和图形界面 较差,主要适用范围是(服务器端的)网络编程。 3.JDK版本 Java是一种自由开发源码软件,由Sun公司主导的JCP(JavaCommunityProcess,Java 社团过程)组织控制。JDK(JavaDevelopmentKit,Java开发工具包)和JRE(JavaRuntime Environment,Java运行环境)会在网站/上公开发布,并可免费下载。 1)JDK包含四种版本: 16 JavaSE(StandardEdition,标准版)——是JDK的主要版本,主要用于客户机。也 是JavaEE的基础。 JavaEE(EnterpriseEdition,企业版)——是JDK的重要版本,用于服务器编程,建 立在JavaSE之上。它与JavaSE的区别在于它增加了提供开发和部署容错、分布式、 多层Java软件能力的库,主要基于运行在应用服务器上的模块化组件。 JavaME(MicroEdition,微型版)——为JavaAPI的一个子集,用于开发资源受限 的小型设备上使用的软件,目标设备包括工业控制和手机与机顶盒中的自动装置。 JavaCard(智能卡版)——与允许小应用程序(applets)在智能卡(smartcards)以 及类似的小内存设备(如SIM手机卡和ATM银行卡)上安全地运行相关,是Java 在嵌入式设备中的最小目标。 2)JDK演化 下面是JDK主要版本的演化历史: 1995年5月23日,Sun发布Java语言。 1996年1月23日,JDK1.0诞生。1996年4月,JDK1.0.2发布,增加了对数据库连 接及分布式对象的支持。1997年2月19日,JDK1.1发布,增加了健壮的事件模型、 内部类、反射机制、支持国际化、推出JavaBeans组件模型和JDBC。 1998年12月8日,JDK1.2发布,增加了Swing、集合框架、JavaIDL,将Swing图 形API集成进核心类中,发布Java插件程序(Plug-in)、JVM装备了JIT编译器,提 高了虚拟机和编程工具的性能。经过此次重大改进后的Java被更名为Java2,表示第 二代Java,标志Java新时代的开始。Sun还将JDK分成三个版本:标准版(J2SE, Java2PlatformStandardEdition)、企业版(J2EE,Java2PlatformEnterpriseEdition) 和微型版(J2ME,Java2PlatformMicroEdition)。 J2EE1.2SDK诞生,引入服务器端的组件技术EJB(EnterpriseJavaBeans, 1999年12月, 企业爪哇豆)、网页编程工具JSP(JavaServerPage,Java,服务器网页)、Servlet(小 服务器程序)和Container(容器)等。2000年5月8日,JDK1.3发布,增加了JNDI (JavaNamingandDirectoryInterface,Java命名与目录接口)、RMIoverIIOP、CORBA ORB、JavaSound和JPDA(JavaPlatformDebuggerArchitecture,Java平台调试器架 构)等,绑定了HotSpotJVM。2001年9月24日,J2EE1.3发布,新特性有:EJB2.0、 JMS(JavaMessageService,Java信息服务)、用JAXP(JavaAPIXML处理)建立 JSP、IIOP(InternetInter-OrbProtocol,因特网交互ORB协议,其中ORB=ObjectRequest 17 Brokers,对象请求代理)、EJBQL(QueryLanguage,查询语言)。2002年2月6日, JDK1.4发布,增加了assert关键字、涟式异常、基于通道的I/O子系统NIO(NewI/O)、 改进了集合框架和联网类。2003年11月24日,J2EE1.4发布,新特性有:JSP2.0EL (ElementLanguage,元素语言)、JSP标准标记库、Web服务、EJB计时器服务、EJB QL增强功能、增强的JMSAPI。 J2SE1.5发布,增加了泛型、注解、自动装箱和拆箱、枚举、for-each 2004年9月30日, 循环、变长变量、静态导入、格式化I/O、并发实用工具等,成为Java语言发展史上 的又一里程碑。为了表示该版本的重要性,Java2更名为Java5,J2SE1.5也更名为 JavaSE5.0。2005年6月,JavaOne大会召开,SUN公司公开JavaSE6。此时,Java 的各种版本已经更名,以取消其中的数字“2”:J2EE更名为JavaEE,J2SE更名为 JavaSE,J2ME更名为JavaME。 2006年5月11日,JavaEE5发布,新特性有:EJB3.0、JSF(JavaServerFaces,Java 服务器外观)、Java持久性、JSTL(JSPStandardTagLibrary,JSP标准标签库),Java 最新的Web服务API:JAX-WS(JavaAPIforXML-BasedWebServices,针对基于XML 的Web服务的JavaAPI)2.0、JAXB(JavaArchitectureforXMLBinding,针对XML 绑定的Java架构)2.0、WSMetadata(WebServicesMetadatafortheJavaPlatform,针 对Java平台的Web服务元数据)。 2006年12月11日,JavaSE6发布,新特性包括:Web服务API、支持脚本语言、 更新了JDBC、新的桌面API(如Swingworker、JTable排序与过滤和GroupLayout 等)、监视和管理、编译器访问、插件式的注释(在代码中定义自己的注释并处理它)、 桌面部署、安全(与像PKI、JavaGSS、Kerberos和LDAP这样的服务集成起来)、 性能提高一倍。目前的最新版是2009年3月25日推出的JDKSE6Update13。 JavaEE6发布,新特性有:可扩展性和协议子集、WebBeans1.0、 2009年12月10日, JAX-RS1.1、Servlet3.0、JASPIC(JavaAuthenticationServiceProviderInterfacefor Containers,Java验证服务提供者容器接口)。被砍掉的API有:JAX-RPC、y BeansCMP、JAXR、JavaEE应用开发(JSR-88)、JavaEE管理(JSR-77)。 2010年初,计划发布JavaSE7,主要新特性有:模块化(module关键字、与OSGi 绑定系统的相互操作性)、NIO2、数据和时间API、fork/join并行处理包、扩展Java 类型标注注释、Swing应用程序框架、JMX(Java管理扩展)2.0、动态语言支持。其 他可能的新特性有:闭包(Closure)、对Strings转换状态的支持、多异常捕获机制、对 18 集合的方括号标记、带有类型推导的简洁构造器、其他语言的名称调用。 4.Java平台 下面介绍JDK的两种主要平台JavaSE和JavaEE的架构。 1)JavaSE平台——图17-3是JavaSE6平台示意图。 Java语言 工具及其API 开发技术 JDK JRE 用户界面工具包 JavaSE API 综合库 其他基础库 语言和工具基础库 Java虚拟机 操作系统平台 a)详细 图17-3JavaSE6平台 b)简略 可见,JavaSE平台的底层为各种操作系统,包括主流的Windows、Linux、MacOS和 各类Unix;上面是JVM层,包括客户端和服务器端的HotSpot(热点,Sun公司的JVM实 现)虚拟机;再上面是JavaSEAPI的各种类库;API类库的上面是开发技术。Java运行环 境JRE包括JVM、API和开发技术。JRE上面是工具及其API、最上层为Java语言本身。 JDK包括JRE及其上面的两层。 2)JavaEE平台——JavaEE建立在JavaSE之上,图17-4是JavaEE6的体系结构图。 19 图17-4JavaEE6体系结构图 17.1.4C# C#与C++很相似,还借鉴了Java的许多特点。但是C#比C++更安全、比Java更高 效,特别适合于Windows环境下的.NET编程。 1.概述 眼看Java占领了利润丰厚的服务器端编程的大部分市场,微软公司 心有不甘。为了与Sun公司的Java和J2EE竞争,1996年BillGates用重 AndersHejlsberg 金从Borland公司挖来(TurboPascal和Delphi的开发者,丹麦人)AndersHejlsberg。Hejlsberg 转到微软后,先后主持开发和设计了VisualJ++(1997)和.NET框架中的CLI和C#语言 (2000)。 C#(CSharp)语言源自C++(数字符#Sharp升半音符,可以视为由4个加号组成,相 当于(C++)++),模仿了Java(微软不承认这点)。C#的很多内容都与Java相似,例如虚拟 机、垃圾内存收集、接口、GUI支持。但是,与Java不同的是,C#保留了指针,不过限制 了指针的使用。C#还引入了值类型、引用类型、元数据、装箱和拆箱转换等概念(这些概 念在最新版本的Java中也有,在C++0x中也会引入与元数据类似的“概念”)。 C#是一种面向对象的程序设计语言,最初是作为.NET的一部分而开发的。换句话 20 说,.NET是围绕C#而开发的。C#的面向过程和对象的语法,是基于C++的,但也包含 了另外几种程序设计语言的特征(其中最显著的是Delphi、VisualBasic和Java),C#特别 强调简易性(如所需符号比C++的少、所需修饰比Java的少)。 从某种意义上来说,C#是最直接地反映了底层CLI的一种程序设计语言,它非常依赖 于.NET框架,因为它被特地设计成能充分利用CLI所提供的特征的一种语言。例如,绝大 多数的C#内置类型,全都对应于CLI框架所实现的值类型(value-types)。 用C#编写的应用程序,需要CLR的一个实现,才能运行。这与Java的虚拟机JVM 有点相似,但是与Java不同的是,CLI应用程序要被编译两遍,第1遍由C#编译器,将 C#源程序被编译成平台抽象字节码,即IL(IntermediateLanguage,中间语言)代码,存 放在PE(PortableExecutable,可移植的可执行)文件中(似Java的.class);第2遍,在应 用程序首次运行时,由CLR的JIT(Just-In-Time即时/实时)编译器,将IL代码编译成本 地客户端的机器代码(.exe)。 C#语言,已于2001年12月成为欧洲标准:ECMA-334C#LanguageSpecification(C# 语言规范),2002年12月、2005年6月和2006年6月又分别推出ECMA-334的第2版、 第3版和第4版。2003年4月C#成为国际标准:ISO/IEC23270:2003Informationtechnology --C#LanguageSpecification(信息技术——C#语言规范),2006年9月又推出第2版ISO/IEC 23270:2006。 2.设计目标 C#语言的设计目标是: C#被确定为一种简单、现代、通用、面向对象的编程语言。 该语言及其实现应该为强类型检查、数组界限检查、发现使用未初始化变量、自动 垃圾回收等软件工程原则提供支持。 该语言适用于分布式环境中的软件组件开发。 源代码的可移植性是非常重要的,程序员的转移也同样重要,特别是对那些已经非 常熟悉C和C++的程序员。 支持国际化是非常重要的。 C#适用于为主机和嵌入式系统编写应用程序,范围从非常大的复杂操作系统,到 非常小的专用功能。 21 虽然C#应用程序致力于在内存和处理能力需求上是经济的,但是该语言并不想与 C或汇编语言在性能和大小方面进行直接竞争。 3.特点 C#具有如下主要特点: 简单——相对于复杂的C++,C#的语言简单,开发高效。例如,在安全上下文中, C#没有指针,不许直接存取内存。用统一的“.”操作符,代替了C++中的“::”、 “.”和“->”操作符。使用统一的类型系统,抛弃了C++的多变类型系统(如int 的字节数、0/1转布尔值等)。 现代——很大程度上由.NET框架体现。如支持组件编程、泛型编程、分布式计算、 XML处理和B/S应用等。 面向对象——C#全面支持面向对象的功能。与C++相比,C#没有全局变量和全 局函数等,所有的代码都必须封装在类中(甚至包括入口函数[方法]Main)、不能 重写非虚拟的方法、增加了访问修饰符internal、不支持多重类继承(似Java,用 多重接口实现来代替)。 类型安全——C#实施严格类型安全,如取消了不安全的类型转换,不允许使用未 初始化的变量,进行边界检查(如不让数组越界)。 4.优势 用C#进行托管代码编程,具有如下优点:完全面向对象的设计、非常强的类型安全、 很好地融合了VB和C++的强大功能、垃圾内存回收、类似于C++的语法和关键字、用委托 取代函数指针增强了类型安全、为程序员提供版本处理技术可解决老版本的程序不能在新 DLL下运行的“动态链接库地狱”(DLLhell)问题。 5.版本与功能 VisualC#随.NET框架及开发工具VisualStudio一起推出,有如下几个版本: 1.0——随.NET框架1.0和(2002)于2002年2月13日发布。 1.5——随.NET框架1.1和2003于2003年5月20日发布。 2.0——随.NET框架2.0和VisualStudio2005于2005年11月7日发布。 22 3.0——随.NET框架3.0和VisualStudio2008于2007年11月16日发布。 4.0——随.NET框架4.0和VisualStudio2010于2010年4月12日发布。 4.0版的英文版规范见VisualStudio2010英文版安装目录中的Word文档,默认路径为: “C:ProgramFilesMicrosoftVisualStudio10.0VC#Specifications1033CSharpLanguage ”。 下面罗列VisualC#的各个主要版本的新增的特点和功能。 1)C#1.0——与C和C++比较,C#在许多方面有所限制和增强,包括: 指针——C#是真正支持指针,但是其指针只能在非安全作用域中使用,而只有具 有适当权限的程序,才可以执行标记为非安全的代码。绝大多数对象的访问是通过 安全的引用(references)来进行的,而引用是不会造成无效的,而且大多数算法 都是要进行溢出检查的。一个非安全指针,不仅可以指向值类型,还可以指向子类 和。也可以使用指针()来编写安全代码。 托管(managed,受控)——在C#中,托管内存不能显式释放,取而代之的是(当 再没有内存的引用存在时的)垃圾收集。但是,引用非托管资源的对象,例如 HBRUSH,是可以通过标准的IDisposable接口的指示来释放指定内存的。 多重继承——在C#中多重继承被禁止(尽管一个类可以实现任意数目的接口,这 点似Java),这样做的目的是为了避免复杂性和“依存地狱”,也是为了简化对CLI 的结构需求。 转换——C#比C++更类型安全,唯一的默认隐式转换也是安全转换,例如加宽整 数和从一个派生类型转换到一个基类(这是在JIT编译期间间接强制进行的)。在 布尔和整数之间、枚举和整数之间都不存在隐式转换,而且任何用户定义的隐式转 换,都必须显式地标出。 数组声明——和C/C++的数组声明的语法不同,C#中用“int[]a=newint[5];”代 替了C/C++的“inta[5];”。 枚举——C#中的枚举被放入它们自己的命名空间。 特性——可在C#中可以使用特性(properties,属性集),访问类似于C++中成员 域,与VB相似。 类型反射与发现——在C#中可以使用完整的类型反射与发现,这些都会用到元数 据所提供的信息。 模板——为了简单性,C#1.0中不支持模板等泛型编程技术。 23 2)C#1.5版的新增功能——/***/文档注释符、#linehidden预处理指令、/nowarn和 /nostdlib编译指令、Web窗体、XMLWeb服务、、可用Windows窗体和框架创建 分布式应用程序的表示层、可创建各种Windows和应用程序和控件的项目模 板、可使用非可视组件和相关功能,将消息队列、事件日志和性能计时器等资源合并到应用 程序中、通过组件设计器和框架类为创建组件提供RAD支持。 3)C#2.0的新特征: 部分类——一个类可分开到多个文件中实现。 泛型——C#从2.0起,开始支持泛型或参数类型。C#还支持一些C++模板不支 持的特性,例如对泛型参数的类型约束。另一方面,C#的表达式不能用作泛型参 数,而这在C++中却是允许的。C#的参数化的类型为虚拟机的首个类对象,允许 优化和保存类型信息,这一点与Java不同。 关键字yield——迭代器的一种新形式,可通过功能类型的关键字yield,来使用协 同例程。 匿名委托——提供了闭包功能。 结合运算符??——返回表中的第一个非空值,例如: objectnullObj=null; objectobj=newObject(); returnnullObj??obj//returnsobj; (例如,int?i=null;),它可以改善与SQL 可空值类型——可空值类型由问号?来标记 数据库的交互。 4)C#3.0的新特征: LINQ(LanguageIntegratedQuery,语言集成查询)——"from,where,select"上下文 敏感的关键字,允许在SQL、XML、集合等之间进行查询。 对象初始化——如Customerc=newCustomer();="James";可被写成 Customerc=newCustomer{Name="James"};。 集合初始化——如MyListlist=newMyList();(1);(2);可被写成 MyListlist=newMyList{1,2};。 匿名类型——如varx=new{Name="James"};。 局部变量类型推论——如varx="hello";等价于stringx="hello";该特性在匿名类型 变量的声明中需要。 24 隐含类型的数组——数组的类型现在可以省略,所以int[]arr=newint[]{1,2,3}; 现在可以写成vararr=new[]{1,2,3};。 λ表达式——如(delegate(Foox){>10;})可被写成 (x=>>10);。 编译器推断——翻译λ表达式到强类型函数或强类型表达式树。 自动属性——编译器会自动生成一个私有实例变量,而且给出适当的获取器和设置 器代码,例如publicstringName{get;privateset;};。 扩展方法——通过在另一个静态类的一个方法的首个参数中包含this关键字,来将 方法添加到类中。如 publicstaticclassIntExtensions{ publicstaticvoidPrintPlusOne(thisintx){ ine(x+1); } } intfoo=0; lusOne(); 部分方法——允许代码生成器生成方法的声明作为扩展点,如果有人在另一个部分 类中实际实现它,则其只被包含在源代码编译中。 5)4.0版增加的新特性主要有: 动态支持——通过引进新类型dynamic来提供对动态类型延迟绑定的支持。 Office可编程性——通过添加命名和可选的参数、dynamic类型、索引属性和可选 的ref修饰符,大大增强了访问(包括Office自动化API在内的)COM接口的能 力。 类型等价支持——可配置应用程序的内置类型信息,以代替从PIA(PrimaryInterop Assembly,主互操作程序集)导入的类型信息。 协变与逆变——协变(covariance)是你能够使用更多的派生类型而不是由泛型参 数指定,而逆变(contravariance)则让你使用更少的派生类型。 25 17.1.5比较 总的来说,C++高效但是不安全,Java(跨平台)安全但是较低效,C#安全且较高效。 表17-4列出了这三种OOP语言在若干功能和特点方面的比较。 表17-4 功能 跨平台 执行方式 中间代码 运行环境 内存管理 多重类继承 操作符重载 对象访问 接口类型 属性成员 成员可用性 命名空间 指针 函数指针 全局函数与变量 无符号整数类型 大十进制数类型 强制类型转换 越界自动检查 多维数组 索引 泛型 泛型类型编译 线程同步 异常处理 标准类库 适用领域 C++ 源代码(部分) 编译 无 操作系统 直接分配和删除 支持 支持 地址/指针 无 无 不支持 支持 支持 支持 有 有 无 支持 无 数组的数组 支持 编译时模板 具体化 调用函数 可选 贫乏 面向对象的 系统和界面编程 C++、Java与C#的比较 Java 字节码 编译+解释 字节码Bytecode JRE/JVM 垃圾内存自动回收 不支持 不支持 引用 有 无 使用反射 包机制 不支持 适配器+监听程序 无 无 有(库) 不支持 有 数组的数组 不支持 运行时泛型(有限制) 类型擦除 语言内部 支持检查异常 丰富 跨平台(服务器端) 网络编程 C# CLI(不流行) 编译+JIT转换 中间语言MSIL CLR/VES 垃圾内存自动回收 不支持 部分支持 引用 有 有 使用反射 支持 部分支持(非安全代码) 委托 无 有 有(语言) 支持 有 真正多维数组 支持 编译时模板 具体化 语言内部 只支持非检查异常 庞大 Windows平台的 .NET和组件编程 17.2C#编程基础 本节先介绍编写简单控制台的C#程序的方法,然后系统讲解C#的基本语法,最后列 出C#的所有关键字。 26 17.2.1第一个C#程序 用VisualStudio2008创建一个“其他语言”中“VisualC#”的“控制台应用程序”模 板的项目HelloWorld,参见图17-5: 图17-5新建项目对话框 按“确定”按钮关闭对话框后,系统会在指定路径中创建名为HelloWorld的目录和项 目,并自动生成代码文件(C#的代码文件名的后缀为cs,是CSharp的首字母 缩写。另外,C#中没有.h头文件): usingSystem; c; ; namespaceHelloWorld { classProgram { staticvoidMain(string[]args) { } } } 27 其中,“using命名空间;”指令与标准C++的命名空间的using指令“usingnamespace命名 空间;”类似;代码都包含在与项目同名的命名空间中;程序类Program中含有程序入口的 主方法Main(该方法的参数似C/C++)。 在该程序的Main方法中,添加对控制台的写行方法调用,以输出字符串“Hello,World!”: ine("Hello,World!"); 注意,类与方法(成员函数)的分隔符不再是C++的“::”而是“.”,串直接量前没有标准 C++的普通字符串转换为宽字符串的L运算符。因为在C#中只有一种字符类型——16位 的宽字符类型char。编译并运行该程序,输出结果如图17-6所示。 图17-6HelloWorld程序运行结果 为了防止不在IDE环境下运行该程序时命令窗口会一闪而逝,可以在上面的语句后面 添加如下语句: ine("Pressanykeytoclose."); y(); 17.2.2基本语法 因为C#源于C++和Java,所以在语法方面与它们非常相似,但是也存在一些差别。下 面系统介绍C#语法的基本内容,重点放在与C++的不同上。 1.符号与标记 与C++一样,C#程序也采用区分大小写的自由书写格式,编译器会忽略多余的白空符; C#也支持“a”、“n”和“t”等转移字符;C#的每个语句也都以分号“;”结束;语句块 也用花括号对“{”和“}”来界定。表17-5列出了C#语言的符号与标记(粗体为C#特有 的)。 表17-5C#语言的符号与标记 说明符号与标记 28 空白 制表符 标点 行结束符 注释 预处理指令 块 泛型 可空类型 字符 转移字符 逐字符 数值后缀 运算符 空格SP 水平制表符HT(Tab)或垂直制表符VT .,:; 回车CR //、/*、*/、///、/** # {} <> ? Unicode字符 代码 @"……"、@标识符 dD、fF、lL、luLU、mM、uU、ulUL +、-、*、/、%、++、--、<、==、>、?:、??、()、… 1)文档注释符“///……”和“/**……*/” C#除了支持C++的单行注释“//……”和定界注释“/*……*/”外,还增加了与之 对应的如下两种文档注释方式: 单行文档注释“///……”——“//……”方式的扩展,增加了一个斜线符“/”。 定界文档注释:“/**……*/”——“/*……*/”方式的扩展,在起端增加了一 个星号符“*”。 这两种新注释,都用于C#项目说明文档的自动提取和生成,说明文档采用的是XML 格式,由C#编译器自动生成。但是需要添加/doc编译器选项,或在VisualStudio的IDE中 激活该功能。具体方法是:选“项目”菜单中的“*属性”菜单项;在IDE中部打开的项目 属性页中,选左列的“生成”选项,在右边下部的“输出”栏中,选中“XML文档文件” 复选框以激活文档自动生成功能,还可以输入或修改“输出路径”和文件名,默认为项目的 “binDebug”或“binRelease”子目录和“项目名.XML”。参见下图: 29 图17-7激活文档自动生成功能 例如,C#编译器为不加任何注释的HelloWorld项目所生成的说明文档为: // 如果你在Program类的定义前加上一行,在键入“///”后,系统会自动生成下面代码中 的灰色部分,然后你自己再添加黑色部分。 /// ///StartclassforsimpleHelloWorld /// 则编译器会自动生成如下说明文档: // 30 StartclassforsimpleHelloWorld 2)逐字符@ C#新引进的逐字符(verbatimcharacter)@,可以避免解析字符串中的转移字符和标 识符中的关键字等: 逐字字符串(verbatimstring)——@"……",不解析其中的转移序列。例如: stringfn=@"C:"; 逐字标识符(verbatimidentifier)——@标识符,不解析标识符,使关键字也可以 用作变量名、类名和方法名等普通标识符。例如:int@for=12; 3)可空类型符? 不能使用的对象,可以用设置其值为空null来标识。但是基本值类型是不能赋值为空 的,那会导致编译错误。传统的经验解决办法是赋给其一个取值范围之外的值,如“-1” (0xFFFFFFFF),但这种方法并不标准。 现在C#给出了一种完美的解决方案——可空类型(nullabletype):声明值类型的变量 时,在变量名后添加可空类型的问号符“?”,则该变量成为可空类型,可以被赋值为空。 在C#中,还可以通过可空类型的共用只读属性HasValue,来判断可空类型变量是否为 空。例如: int?x=123; int?y=null; if(ue)ine();//输出“123” 31 if(ue)ine();//无输出 2.类型 与C++一样,C#也是一种强类型语言,其每个变量和对象也都必须具有被预先声明的 类型。但是由于C#是专门为.NET设计的语言,所以它的类型体系与C++有很大的不同, 而与.NET框架一致。 C#有如下两大类型种类: 值类型——包括简单、枚举、结构和指针等类型。该类型的变量存储数据本身,对 值类型的变量进行赋值时,会复制变量所包含的值。其中的指针类型,仅可用于 unsafe(非安全)模式。 引用类型——包括类、接口和数组等类型。该类型(也称为对象)的变量存储对实 际数据的引用,对引用类型的变量进行赋值时,只复制对象的引用(指针/句柄), 而不会复制对象本身。 值类型可以通过装箱(boxing)转换成引用类型,然后再经过拆箱(unboxing)转换回 值类型。但是无法将原始的引用类型转换为值类型。 C#不支持联合类型,也没有关键字union。 3.简单类型 简单类型属于C#语言的值类型,对应于C++语言的基本类型,包括字符、布尔类型、 以及整数和实数等数值类型。与C++/CLI相似,C#中的基本类型都与.NET框架的System 命名空间中的对应类型等同,是它们的别名,参见表17-6。 表17-6 C#类型 bool char sbyte byte short ushort C++/CLI类型 bool wchar_t [signed]char unsignedchar [signed]short unsignedshort C#的简单类型 字节/位数 -/1 2/16 1/8 整数 1/8 2/16 2/16 32 .NET框架类型值类型 n真或假 16 16 字符 范围和精度 true或false 所有UTF-16编码(0~0xFFFF) -128~127 0~255 -32768~32767 0~65535 int uint long ulong float double [signed]int/long unsignedint/long [signed]longlong 32 32 64 4/32 4/32 8/64 8/64 浮点数 高精度 4/32 8/64 -2147483648~2147483647 0~4294967295 -9223372 ~9223372 0~18446744 ±1.5×10 -45 ~±3.4×10 38 ±5.0×10 -324 ~±1.7×10 308 64 float double l 十进制 小数 16/128±1.0×10 -28 ~±7.9×10 28 其中的sbyte、byte、short、ushort、int、uint、long、ulong和char等9种类型为整数类型(integral types)。 可见,C#的简单类型的名称,比C++的更简洁明了。如signed被省略;unsigned简写 成了u,从而unsignedshort、unsignedint和unsignedlonglong分别被改成了ushort、uint 和ulong;char对应于C++的wchar_t;sbyte部分对应于C++的char,但是sbyte只表示单字 节的有符号整数,不再表示单字节的普通字符,因为在C#不支持单字节字符。因此,在 C#中,不再需要C++中的L"……"运算符来进行普通字符串常量向宽字符串的转换。 与C++非常不同等一点是,C#中所有整数类型(如int和long)的字节数都是固定的, 不再像C/C++那样依赖于CPU的字长和操作系统的位数。 还有几点与C++不同的是: 可以用(从继承的)GetType()方法来获得指定变量或对象的类型名 称。简单类型返回的是.NET的类型名,对象则返回类或结构的名称。例如: inti=1; MyClassobj; ine("Thetypeofvariableiis{0}andobjectobj is{1}.",e(),e()); 的输出为:32andtheobjectobjisMyClass. 具有.NET的高精度十进制小数类型l的对应类型decimal,该类型主 要用于货币(money)的数量计算。在C#中可以用m或M后缀,将一个实数常 33 量指定为decimal类型。没有后缀的实数会被视为double类型,直接赋值给decimal 变量会导致编译错误。例如: decimalmoney=1234.5m;//正确 decimald=1234.5;//编译错误 3.运算符与表达式 C#运算符和表达式与C/C++几乎完全一样,仅有的区别如下: 1)基元(primary)运算符——C#新增加了如下两个基元表达式运算符: checked(已检验)——用于设置溢出检验上下文(overflow-checkingcontext),控 制对整型算术表达式中的操作和转换进行溢出检验,在出现溢出时会抛出异常。格 式为:checked(表达式)或checked{语句块}。 unchecked(未检验)——用于设置溢出检验上下文(overflow-checkingcontext), 控制对整型算术表达式中的操作和转换不进行溢出检验,在出现溢出时不抛出异 常,结果为截断后的整数。格式为:unchecked(表达式)或unchecked{语句块}。 例如: classTest{ staticreadonlyintx=1000000; staticreadonlyinty=1000000; staticintF(){ returnchecked(x*y);//ThrowsOverflowException } staticintG(){ returnunchecked(x*y);//Returns-727379968 } staticintH(){ returnx*y;//Dependsondefault } } 又例如: 34 try{ inti,i2=ue,i2=200; i=checked(i1*i2); }catch(Exceptione){ //(ng()); ine(ng()); } 2)一元运算符——全局命名空间限定符::。虽然C#不再支持全局变量和全局函数,但 是仍然有全局命名空间,所以还是保留了全局命名空间限定符“::”(类似于C++的全局运 算符::)。注意,因为在C#中,双冒号符“::”已经不再(似C++)作为命名空间之间和CLR 对象与成员之间的分隔符,而是专用的全局命名空间限定符。 3)二元运算符: 模运算符%——在C#中,%除了可以像C++那样用来求整数的余外,还可以用于 浮点数和decimal类型。例如5%1.5=0.5。 是运算符is——用于动态检验一个对象的运行时类型是否与指定类型兼容。格式 为:表达式is类型名。结果为一个布尔值,若表达式可以成功地转换成指定类型, 则为true,否则为false。 转为运算符as——用于将一个值显式地转换为一个指定的引用类型或可空(值) 类型。格式为:标识符as类型名。转换为引用类型时,采用的是引用转换或装箱 转换;转换为可空类型时,采用的是包装转换、拆箱转换或空类型转换。与强制转 换不同,as转换不会抛出异常。如果指定的转换是不可能的,则结果为空null。 is和as运算符在模板和泛型运算中非常有用。 空接合运算符(nullcoalescingoperator)??——用于在某个可空类型变量a的值为 空时,提供备用的值b。格式为:a??b。当a非空时结果为a,否则为b。功能相 对于:ue?a:b。例如: double?x=null; …… doubley=x??0; C#表达式中运算符优先级与C++的类似,参见表17-7(粗体的是C#特有的): 35 表17-7 优先级类型 C#运算符优先级表(从高到低) 运算符 .(成员,如x.y)、()(方法,如f(x))、[](数组,如a[i])、++ 1基元 (后缀增,如i++)、--(后缀减,如i--)、new(创建对象,如new MyClass)、typeof(类型,如typeof(x))、checked(已检验,如 checked(x*y))、unchecked(未检验,如unchecked(x*y)) +(正,如+x)、-(负,如-x)、!(逻辑非,如!x)、~(求补,如 2一元~x)、++(前缀加,如++i)、--(前缀减,如--i)、()(强制类型转 换,如(int)x) 3 4 5 6 7 8 9 10 11 12 13 14 乘性 加性 位移 关系和 类型测试 等性 逻辑与 逻辑异或 逻辑或 条件与 条件或 空接合 条件 *(乘,如x*y)、/(除,如x/y)、%(模,如x%y) +(加,如x+y)、-(减,如x-y) <<(左移,如i<<4)、>>(右移,如i>>4) <(小于,如x (大于等于,如x>=y)、is(是,如xisint)、as(转为,如xasint) ==(相等,如x==y)、!=(不等,如x!=y) &(位与,如x&4) ^(位异或,如x^y) |(位或,如x|y) &&(逻辑与,如x&&y) ||(逻辑或,如x||y) ??(空接合,如a??b) ?:(条件,如x?a:b) =(等于,如x=y)、*=(乘等于,如x*=y)、/=(除等于,如x/=y)、 15赋值 +=(加等于,如x+=y)、-=(减等于,如x-=y)、<<=(左移等于, 如x<<=y)、>>=(右移等于,如x>>=y)、&=(位与等于,如x&=y)、 ^=(位异或等于,如x^=y)、|=(位或等于,如x|=y) 4.枚举 与C/C++类似,C#中的枚举(enumeration)类型也是一个命名常量的集合。枚举声明 36 的完整格式为(灰色方括号[]中的内容是可选的): [[属性]][枚举修饰符]enum标识符[:整型基类型]{ [[属性]]标识符[=常量表达式], …… [[属性]]标识符[=常量表达式] }[;] 其中,枚举修饰符有:new、public、protected、internal、private,默认为private。默认的整 数基类型为int。常用格式为: enum枚举名[:基类型]{枚举成员名[=枚举值],……, 枚举成员名[=枚举值]}; 例如: enumColor{Red,Green,Blue} enumDays:byte{Sun,Mon,Tue,Wed,Thu,Fri,Sat}; C#的枚举类型都是抽象类类型结构的派生值类型。Enum的C#定义如下: [SerializableAttribute] [ComVisibleAttribute(true)] publicabstractclassEnum:ValueType,IComparable,IFormattable,IConvertible 包含多个公用方法成员(参见表17-8),可以在用户定义的枚举类型中,使用这些公用方法。 表17-8Enum类的部分静态公用方法 名称 Format GetName GetNames GetUnderlyingType GetValues IsDefined Parse 说明 根据指定格式将指定枚举类型的指定值转换为其等效 的字符串表示形式。 在指定枚举中检索具有指定值的常数的名称。 检索指定枚举中常数名称的数组。 返回指定枚举的基础类型。 检索指定枚举中常数值的数组。 返回指定枚举中是否存在具有指定值的常数的指示。 已重载。将一个或多个枚举常数的名称或数字值的字符 串表示转换成等效的枚举对象。 37 与C++不同,你不能用整数值来代替枚举值。而且枚举可以作为顶层类型,也可以作为 类的成员(嵌套类型),但是不能作为局部类型来定义,这点也与C++不同。还有一个与C++ 不同点是,在C#的枚举类型定义体后的分号是可选的,而在C++中却是必须的。 5.数组 C#中的数组与C++的很相似,但是其声明格式却与C++同。C#数组与C++的最大差 别是,C#支持多维数组(也支持C++的数组的数组)。 下面是C#数组的声明格式(其中的灰色方括号表示可选项)和使用格式: 一维数组——声明格式为: 类型[]数组名[=[new类型[[n]]]{表达式1,……,表达式n}]; 例如: int[]a={0,2,4,6,8}; string[]sa=newstring[5]; object[]array; 使用格式为:数组名[i]。例如: sa[2]="abcd"; 二维数组——声明格式为: 类型[,]数组名[=[new类型[[m,n]]]{{表达式11,……,表达式1n}, ……,{表达式m1,……,表达式mn}}]; 例如: int[,]b=newint[5,2]; int[,]b={{0,1},{2,3},{4,5},{6,7},{8,9}}; 多维数组——声明格式为: 类型[,,...,]数组名[=[new类型[[n1,n2,...,nm]]]{……}]; 例如: int[,,]a=newint[3,2,4]; 使用格式为:数组名[i1,i2,…,in]。 锯齿数组(jaggedarray)——即数组的数组。例如: int[][]={newint[]{1},newint[]{4,5,6},newint[]{1,5}, newint[]{6},newint[]{2,3}}; 注意:其中的每维的元素个数m、n、ni等,既可以是常数,也可以是变量。即C#支 持动态多维数组。 38 比较:C++只有一维数组,而且数组的声明格式左边内的方括号在数组名之后,而不是 在类型名之后。例如: inta[5];//C#不能如此简洁地声明数组,左边方括号内不允许指定元素数 int*a=newint[5];//对应于C#的int[]a=newint[5]; inta[]={1,3,5,7,9};//对应于C#的int[]a={1,3,5,7,9}; C#中的用户数组都是从.NET的类派生的。Array类的C#定义为: [SerializableAttribute] [ComVisibleAttribute(true)] publicabstractclassArray:ICloneable,IList,ICollection,IEnumerable 其公用属性和方法也可以被用户数组所使用,例如,可用只读属性:publicintLength{get;} 来获得数组中的元素总数,用方法:publicintGetLength(intdimension)来获得指定维的元素 数等。 6.变量和常量 与C++最大的不同是,C#不支持全局变量和常量,所有变量和常量都必须位于类或结 构之内,它们可以是类变量(字段)与类常量,也可以是方法(成员函数)和块语句中的局 部变量和常量。 与C++一样,C#的变量名也是以字母、下划线开头的字母、下划线和数字串。但是 C#支持逐字标识符,所以也可以在(与保留关键字同名的)变量名前加@字符,但是并不 提倡这样做。 与C++不同的是,(但是与Basic相似)C#会为每个未初始化的成员变量(字段)赋初 值:数值变量的初值为0、布尔变量的初值为false、对象实例的初值为空null。但是C#并 不为局部变量赋初值,所以方法体中的局部变量,必须先初始化后,才能被使用,不然为编 译错误。这一点也与C++不同,在C++中使用未初始化的变量是合法的(但是却是无意义 的,而且可能是一种很难排查的逻辑型错误),最多给出一个警告提示。 在C#中也用const关键字来定义常量,局部常量的声明格式为: const类型常量名[=常量表达式][,……,常量名[=常量表达式]]; 类的常量成员的声明格式为: [[属性]][常量修饰符]const常量名[=常量表达式][,……,常量名[=常量表达式]]; 39 其中,常量修饰符有:new(覆盖基类同名成员)、public、protected、internal(只能被本程 序访问)和private。常量名中的字母一般全大写,例如: publicconstdoublePI=3.14159,MAX=100.0; 与C++一样,C#中的常量也必须在编译时被初始化,而且在运行时不能被修改。但是 与C++不同的是,C#中的常量默认为静态的。 7.流程控制 程序默认是顺序执行的,在C#中可以像C++那样,用条件和开关语句来进行分支控制, 用各种迭代语句(包括C#新增的foreach)来进行循环控制。 1)分支 C#的if-else语句与C++的相同,switch-case语句也差不多,区别有: C#的switch-case语句中的每个case语句必须用break、return、throw或goto语句 跳出,不允许像C++那样,多个case语句使用同一个代码段。不过可以用“gotocase 标号;”来实现同样的功能。 C#除了允许整数和字符作为选择类型外,还允许用字符串作为选择类型。 例如: stringname; …… switch(name){ case" 张三 ":……break; case" 李四 ":……break; …… default:……break; } 2)循环 C#除了支持C++的for、while和do-while循环,还增加了与C++/CLI的foreach循环 类似的遍历循环foreach,格式为: foreach(typeidentifierinexpr)embedded-statement 其中的表达式的计算结果必须为数组或集合类型。例如: 40 string[]names={"张三","李四","王五",……}; foreach(stringnameinnames){……} 8.类型转换 与C++一样,C#中的数据在需要时可以进行类型转换(typeconversion),这种转换有 时是隐式的,也可以用显式的强制转换,还可以用类型转换方法来进行命令式显式转换。 1)隐式转换 与C++一样,当转换不会造成信息丢失时,C#一般会采用自动的隐式转换(implicit conversion)。隐式转换包括对数值、枚举和引用的隐式转换。隐式转换可以发生在方法调用、 表达式计算和分配时。但是不存在到字符类型char的隐式转换。数值类型的隐式转换参见 表17-9。 表17-9隐式数值类型转换 被转换类型 sbyte byte short ushort int uint long ulong float char 2)显式转换 与C++一样,C#中的显式转换(implicitconversion)也是指使用强制转换(cast)运算 符“(类型)”进行的类型转换。如果显式转换位于“已检验”(checked)上下文内,在被转 换的值超出目标类型的取值范围时,会导致OverflowException异常,这是C#新增的观念。 如果显式转换位于“未检验”(unchecked)上下文内,则不会抛出异常,但是转换行为会不 明确也不可靠,这点与C++类似。表17-10列出了可以安全进行显式转换的类型。 可安全转换到的类型 short、int、long、float、double、decimal short、ushort、int、uint、long、ulong、float、double、decimal int、long、float、double、decimal int、uint、long、ulong、float、double、decimal long、float、double、decimal long、ulong、float、double、decimal float、double、decimal float、double、decimal double ushort、int、uint、long、ulong、float、double、decimal 41 表17-10 被转换类型 sbyte byte short ushort int uint long ulong float double decimal char 显式数值类型转换 可安全转换到的类型 byte、ushort、uint、ulong、char sbyte、char sbyte、byte、ushort、uint、ulong、char sbyte、byte、short、char sbyte、byte、short、ushort、uint、ulong、char sbyte、byte、short、ushort、char sbyte、byte、short、ushort、int、uint、ulong、char sbyte、byte、short、ushort、int、uint、long、char sbyte、byte、short、ushort、int、uint、long、ulong、decimal、char sbyte、byte、short、ushort、int、uint、long、ulong、float、decimal、char sbyte、byte、short、ushort、int、uint、long、ulong、float、double、char sbyte、byte、short 3)命令转换 除了上面所介绍的隐式和显式转换外,在C#中还可以利用.NET的t类 提供的一套公用静态的转换方法,来进行显式类型转换,参见表17-11。 表17-11 方法 ToBoolean ToByte ToChar ToDateTime ToDecimal ToDouble ToInt16 ToInt32 ToInt64 ToSByte 显式类型转换的静态方法 功能 将指定的值转换为等效的布尔值。 将指定的值转换为8位无符号整数。 将指定的值转换为Unicode字符。 将指定的值转换为DateTime。 将指定值转换为Decimal数字。 将指定的值转换为双精度浮点数字。 将指定的值转换为16位有符号整数。 将指定的值转换为32位有符号整数。 将指定的值转换为64位有符号整数。 将指定的值转换为8位有符号整数。 42 ToSingle ToString ToUInt16 ToUInt32 ToUInt64 例如: usingSystem; inti=1234; 将指定的值转换为单精度浮点数字。 将指定值转换为其等效的String表示形式。 将指定的值转换为16位无符号整数。 将指定的值转换为32位无符号整数。 将指定的值转换为64位无符号整数。 16(i); 9.命名空间 与标准C++一样,C#也支持命名空间。定义和使用的方法也差不多,主要区别是分隔 符由C++的命名空间分隔符——双冒号“::”改成了C#的统一分隔符——句点“.”。 1)命名空间声明 C#也使用关键字namespace来定义命名空间。具体的声明格式为: namespace限定标识符{ [externalias标识符……]//外部别名指令 [using指令……] [类型声明……]//命名空间成员声明 } 其中,限定标识符为用句点分隔的命名空间名的层次系列;类型声明包括:类声明、结构声 明、接口声明、枚举声明和委托声明。using指令包括using命名空间指令和using别名指令, 将在下面介绍。例如: namespaceN{ usingList=ist; partialclassA{ Listx;//x 为 ist 类型 } } 43 namespaceN1{ usingN; usingA=N.A; classB:A{}//A等于N.A } 2)using命名空间指令 在C#中也是用using关键字来使用命名空间,但是不需要namespace关键字。格式为: using命名空间;//相当于C++的using指令“usingnamespace命名空间;” 例如: usingSystem; ; 注意,没有与C++的using声明“using命名空间::成员;”功能类似的C#指令。 3)命名空间的别名 可以用C#的using别名指令,来定义命名空间及其成员类型的别名。格式为: using标识符=命名空间或类型名; 例如: usingImg=g;//命名空间别名 usingGraph=cs;//类别名 10.Main方法 与C/C++的main函数类似,C#也有自己的程序入口函数(方法)Main。但是与C/C++ 不同的是,Main不能是全局函数,而必须是类或结构的静态成员方法,且在整个程序中必 须唯一。 与C/C++一样,Main方法必须返回int或void类型。与C/C++的main类似,Main方法 可以没有输入参数,也可以有一个命令行字符串参数。而C/C++的main则有两个参数,其 中一个为命令行中参数的个数,这在C#中已经变成多余,因为C#的数组本身就带有Length 属性。Main方法具有公用访问级别,但是public修饰符却是不必要的。 Main方法的可用签名(signature)格式为: staticvoidMain(){…} 44 staticvoidMain(string[]args){…} staticintMain(){…} staticintMain(string[]args){…} 例如: usingSystem; namespaceN{ classA{ staticintMain(string[]args){ for(inti=0;i<;i++) ine(args[i]); return0; } } } 在VisualSudio自动生成的C#程序中,Main方法一般位于与项目名同名的命名空间的 Program类中,参见17.2.1中的例子代码。 17.2.3关键字 C#有两类关键字,普通关键字(keyword)是不能用作标识符的保留字,上下文关键 字(contextualkeyword)不是保留字,只是在特定的上下文中提供特殊的代码含义。 C#3.0有77个普通关键字和14个上下文关键字(contextualkeyword),C#4.0增加了 将原有的两个普通关键字in和out作为泛型修饰符的用法,还增加了4个新的上下文关键 字。所以,C#有77个不同的关键字和18个不同的上下文关键字,共计95个不同的关键 字,参见表17-12和表17-13。表中,粗体的为C#特有的关键字,斜粗体的为4.0版新 增加的,其余(正常的)为与C++相同的关键字。 表17-12 abstract break char as byte checked C#关键字(77+2个) base case class bool catch const 45 continue do event finally foreach in internal namespace operator params readonly sealed static this typeof unsafe void decimal double explicit fixed goto in(泛型修饰符) is new out private ref short string throw uint ushort volatile default else extern float if int lock null out(泛型修饰符) protected return sizeof struct true ulong using while delegate enum false for implicit interface long object override public sbyte stackalloc switch try unchecked virtual 表17-13 add global let remov e var dynamic group(by) C#的上下文关键字(18+2个) from(in) into partial(类型) set get join(inonequals) partial(方法) value orderby(descending) select where(泛型类型约束)where(查询子句)yield 在上下文关键字中,partial(分部/部分)包含2.0版引进的partial(类型)和3.0版引 进的partial(方法),where(在哪里/什么地方)包含2.0版引进的where(泛型类型约束) 和3.0版引进的where(查询子句);3.0版引进的from、group、into、join、let、orderby、 select等7个用于LINQ查询;var用于定义隐式类型的本地变量(一种由编译器确定类型的 46 强类型变量)也是3.0版引进的;add和remove用于定义在用户代码预订(subscribesto) 和退订(unsubscribesfrom)你的事件时调用的定制事件访问程序(customeventaccessor)、 dynamic用于定义动态类型的变量、global::用于指明全局命名空间(为C#程序的默认无名 命名空间)它们4个都是4.0版新引进的。 C#中不支持的标准C++关键字有如下20个(其中粗体为常用的):asm、auto、const_cast、 delete、dynamic_cast、export、friend、inline、mutable、register、reinterpret_cast、signed、 static_cast、template、typedef、typeid、typename、union、unsigned、wchar_t。 17.3面向对象的C#编程 与C++一样,C#是一种面向对象的编程语言,它通过类、结构和接口来支持对象的封 装、继承和多态等特征,还利用委托和事件来支持对象的消息响应。 17.3.1对象和类型 与C++一样,C#也有两种主要的对象类型——类和结构。其中类是一种完整的对象类 型,结构可以视为是一种轻型的类。与C++不同的是C#中的结构不支持继承(所以它不具 备完整的对象特征),而且结构在C#中是一种值类型(C#中的类则是一种引用类型)。 与C++/CLI一样,C#中的类和结构都不支持多重类继承,但是都支持多重接口实现。 1.类 类类型定义包含数据成员、函数成员和嵌套类型的数据结构,其中数据成员包括常量和 字段,函数成员包括方法、属性、事件、索引器、运算符、实例构造函数、析构函数和静态 构造函数。类类型支持继承,继承是派生类可用来扩展和专门化基类的一种机制。 1)类声明 C#语言的类(class)声明完整格式如下(其中,灰色方括号“[]”内为可选内容、省 略号“…”表示重复、圆括号内的竖线(或)分隔项“(*|*|*)”表示多中选一): [[属性]][类修饰符][partial]class标识符[<类型参数[,…]>][(:基类|:接口类型 [,…]|:基类,接口类型[,…]][where类型参数:类型参数约束[,…]…]{ [类成员声明……] }[;] 47 其中: 属性(attributes)——用方括号括起的由程序自己定义和使用的额外说明信息,可 以附加到各种程序实体上,并可在运行时获取这些信息。例如: [AttributeUsage()] publicclassHelpAttribute:Attribute{ publicHelpAttribute(stringurl){ =url; } publicstringTopic=null; privatestringurl; publicstringUrl{ get{returnurl;} } } 类修饰符(class-modifiers)有(粗体为C#特有的): new(新的)——用于在嵌套类中掩藏继承的同名成员。 public(公用的)——可以在任何地方使用,不受访问限制。 protected(保护的)——只能被同一类和派生类中成员使用,派生类的父程序 (集)不会影响保护访问。 internal(内部的)——内部类只能被本程序(集)访问。 private(私有的)——只能被同一类中成员使用。 abstract(抽象的)——抽象类不允许实例化。 sealed(密封的)——密封类不允许派生。 static(静态的)——静态类没有也不允许有实例构造函数。 功能与C++/CLI的可见性说明符类似,只是增加了static修饰符。 partial(分部的)——用于定义分部类、结构和接口等对象类型,即将它们的定义 分成多个部分,保存到多个代码文件中。这是C#2.0新引进的功能,用于解决大 型对象类型的定义问题,因为C#不支持类体之外的方法和函数体定义。 class(类)——定义类的关键字。 标识符(identifier)——类的名称。 48 类型参数(type-parameter)——用于泛型类。 基类(basecalss)——唯一父类的名称。默认基类为object。 接口类型(interface-type)——C#支持多重接口的实现。 where(位于)——引导类型参数约束子句,对类型参数列表中的参数进行联系约 束。类型参数约束具体包括:class、struct、类类型名、接口类性名、类型参数名、 new()(构造函数约束)。例如: classDictionary whereK:IComparable //映射值必须是可打印、有键提供器和可创建的 whereV:IPrintable,IKeyProvider …… } 类成员的种类有:constant-declaration(常量声明)、field-declaration(字段声明)、 method-declaration(方法声明)、property-declaration(属性声明)、event-declaration (事件声明)、indexer-declaration(索引器声明)、operator-declaration(运算符声明)、 constructor-declaration(构造函数声明)、finalizer-declaration(终结器声明,即析构 函数定义)、static-constructor-declaration(静态构造函数声明)、type-declaration(类 型声明)。 ;(分号)——类体后的分号是可选的,在C#中一般不用分号,分号是为了与C++ 兼容而保留的。而在C++必须使用分号,则是为了在类型声明后可同时定义该类型 的实例对象(列表)。但是在C#中,已经不允许这样做了,所以这里的分号已经 是多余的了。 2)类成员 类中的数据和方法称为类成员(classmembers)。类中除了成员外,还可以包含嵌套的 类和结构等类型。 (1)访问修饰符 类成员可以有如下访问修饰符(accessmodifiers): public(公用的)——访问无限制。 protected(保护的)——只可被包含类(containingclass)或其派生的类型访问。 internal(内部的)——只能被此程序访问。 49 protectedinternal(保护内部的)——只能被此程序或其包含类所派生的类型访问。 private(私有的)——只能被其包含类访问,为默认的。 C#类的public、protected、private等成员修饰符,每次只能修饰一个成员,直接位于 成员声明的开始处,而且没有冒号分隔符。而不是像C++那样,一个修饰符可以修饰位于其 后(直到下一个修饰符为止)的若干成员。 (2)数据成员 类的数据成员(datamember)有: 字段(field)——类变量。声明格式为: [[属性]][字段修饰符]类型标识符[=表达式或数组初始化器][,……]; 这里的字段修饰符有:new(新)、public(公用)、protected(保护)、internal(内 部)、private(私有)、static(静态)、readonly(只读)、volatile(易变),及它们 的有效组合。其中,C#新引进了如下三种: 只读字段(readonlyfield)——可以在程序运行时由构造函数初始化的常量字 段(而普通常量只能在编译时初始化)。 新字段(newfield)——用于覆盖基类的同名变量。 内部字段(internalfield)——只能被本程序访问的字段。 常量(constant)——用const关键字定义的类常量。声明格式为: [[属性]][常量修饰符]const标识符[=常量表达式][,……]; 其中,常量修饰符有:new、public、protected、internal、private及它们的有效组合。 类的数据成员可以是静态数据(使用了static修饰符),也可以是实例数据(未使用static 修饰符)。静态数据为类的所有实例对象所共有,而实例数据则每个实例对象都自己的副本。 常量默认是(总是)静态的。 字段还可以是只读的(使用了readonly关键字,这是C#新增的)和易变的(使用了 volatile关键字,字段值一般位于CPU中的寄存器中,高效但是有效期短暂)。 (3)函数成员 类的函数成员(functionmember)有: 方法(method)——类的成员函数。声明格式为: [[属性]][方法修饰符]返回类型成员名[<类型参数,…>](形参,…)[where 类型参数:类型参数约束[,…]……]{……}; 方法修饰符有:new(新)、public、protected、internal、private、static、virtual(虚)、 50 sealed(密封)、override(重写)、abstract(抽象)、extern(外部)、partial(分 部),及它们的有效组合。成员名可以是标识符或接口类型.标识符。其中,C#新 增了如下5个方法修饰符: 新方法(newmethod)——隐藏继承的同名方法。 密封方法(sealedmethod)——不能被派生类重写。 重写方法(overridemethod)——覆盖基类的虚方法。 抽象方法(abstractmethod)——没有函数体的方法,需要在派生类中实现后 才能使用。 分部方法(partialmethod)——在一个分部类中定义,而在另一个分部类中实 现的方法。分部方法不能定义访问修饰符,隐含为private。分部方法的返回类 型必须是void,而且partial关键字必须紧靠void之前出现才有效。 与C++不同的还有,C#方法的输入参数,如果是参数的引用,不再使用&符号, 而是改用ref关键字;另外,C#还增加了形参的输出修饰符out,标明该参数用于 方法的输出,可以未初始化,但是在方法中必须进行有意义的赋值。例如: intf(refinti,outintj){i++;j=i+100;} 属性(property)——提供对对象或类的特性(characteristic)进行访问的成员。声 明格式为: [[属性]][属性修饰符]类型成员名{ [[属性]][访问修饰符]get{……}; [[属性]][访问修饰符]set{……}; } 其中,属性修饰符与方法修饰符相同,访问修饰符有protected、internal、private、 protectedinternal和internalprotected,与类成员的普通访问修饰符相比,没有public、 多了internalprotected(内部保护的)。 事件(event)——定义该类可生成的通知(notifications)。在发生某些行为时,可 让对象通知程序。客户程序可以调用事件处理程序代码来响应该事件。声明格式为: [[属性]][事件修饰符]event类型标识符[=表达式或数组初始化器][,……]; 或: [[属性]][事件修饰符]event类型成员名{ [[属性]]add{……} 51 [[属性]]remove{……} } 其中,事件修饰符与方法修饰符相同。 索引器(indexer)——允许对象像数组一样被索引的一种成员。声明格式为: [[属性]][索引器修饰符]类型成员名{ [[属性]][访问修饰符]get{……}; [[属性]][访问修饰符]set{……}; } 其中,索引器修饰符与方法修饰符类似,只是少了static。 用户定义的运算符(operator)——运算符重载函数。声明格式为: 一元运算符: [[属性]][运算符修饰符]类型operator一元运算符(类型标识符}{……}; 可重写的一元运算符有:+、-、!、~、++、--、true、false。其中true和false 必须同时重载。 二元运算符: [[属性]][运算符修饰符]类型operator二元运算符(类型标识符,类型标 识符}{……}; 可重写的一元运算符有:+、-、*、/、%、&、|、^、<<(右移)、==、!=、>、 <、>=、<=。 转换运算符: [[属性]][运算符修饰符]implicitoperator类型(类型标识符}{……}; [[属性]][运算符修饰符]explicitoperator类型(类型标识符}{……}; 其中,运算符修饰符有:public、static、extern,及它们的有效组合。 与C++相比,C#不能重载new、=、+=、-=、*=、/=、%=、^=、&=、|=、>>(左 移)、>>=、<<=、&&、||、,、[]、()(->、->*、new[]、delete、delete[])等运算符。 但是却可以重载true、false和隐式与显式强制转换运算符。 构造函数(constructor)——包括实例构造函数和静态构造函数。 实例构造函数(instanceconstructor)——实现初始化一个类实例所需的行为, 会在创建每个类实例时被系统调用。注意:因为C#会自动给字段初始化,所 以构造函数在C#中的作用没有在C++的大。格式为:类型名([参数列表]); 52 注意:即使调用没有任何参数的默认构造函数,也不能省去后面的圆括号“()”, 不然是语法错误。例如:Aa=newA(); 静态构造函数(staticconstructor)——实现初始化一个类所需的行为,会在程 序装入时被系统调用。为类编写无参数的具体构造函数是C#的一个新特征, 主要用于类的静态字段或属性的运行时初始化。格式为: static类型名([参数列表]); 终结器(finalizer)——在C#的旧版本中叫析构函数(destructor),但由于引用类 型的对象是位于具有垃圾内存自动回收功能的CLR堆上,所以C#中的析构函数 的功能与C++的很不一样。为了与C++的析构函数有所区别,在C#的新版本中, 改叫终结器。格式(与C++的析构函数相同)为:~类型名();。 2.结构 C#中的结构(struct)与C++中的类似,也可以封装数据和函数(属性和行为),是一 种轻量级的类。但是它与C#的(位于CLR堆中的)类(为引用类型)不同,它是一种(位 于局部栈中的)值类型。 1)结构与类的区别 C#中的结构与类的区别有: 结构是值类型,而类是引用类型。 结构是密封的(sealed),因此不能被继承。 结构不能继承类和其他的结构。 结构隐式地继承了ype类型。 结构的(无参数)默认构造函数不能被自定义的构造函数取代。 结构的自定义的构造函数,必须初始化结构中全部成员的值。 结构没有析构函数。 不允许初始化结构的字段,但是可以初始化结构的常量成员。 2)结构声明 结构的完整声明格式为: [[属性]][结构修饰符][partial]struct标识符[<类型参数列表>][:结构接口列表] [类型参数约束子句]{ 53 [结构成员声明……] }[;] 其中,结构的修饰符与枚举修饰符相同:new、public、protected、internal、private,但是与 类的有所不同:因为C#的结构不支持继承,所以没有类的sealed和abstract修饰符;也没 有static修饰符;还与C++一样,结构的默认修饰符为public(类的默认修饰符为private)。 结构成员声明与类的基本相同,只是没有finalizer-declaration(终结器声明,即析构函 数定义)。 C#的结构和类一样,其public、protected、private等成员修饰符,每次只能修饰一个 成员,也是直接位于成员声明的开始处,而且也没有冒号分隔符。而不是像C++那样,一个 修饰符可以修饰位于其后(直到下一个修饰符为止)的若干成员。例如: structPoint{//定义结构 publicintx,y; publicPoint(intx,inty){ this.x=x; this.y=y; } } //使用结构 Pointa=newPoint(10,10); Pointb=a; a.x=100; ine(b.x); 与枚举类型一样,结构可以作为顶层类型(这点与C++一致),也可以作为类的成员(嵌 套类型,这一点与C++不同),但是不能作为局部类型来定义(这点与C++一致)。还有一 个与C++不同点是,在C#的结构类型定义体后的分号是可选的,而在C++中是必须的。 3.分部类和静态类 分部类和静态类都是C#所特有的,而且都是C#2.0版新增加的。前者允许将一个类、 结构或接口的定义,放到多个文件中;后者则是仅包含静态成员的类。 54 1)分部类 分部类和结构是在类和结构的声明中,使用了partial修饰符的类和结构。位于不同文件 中的同一个分部类,必须位于同一个命名空间或包含类型(对嵌入式分部类)之中,而且在 所有类修饰符必须完全一致。对泛型类,还要求声明中的类型参数和对类型参数的约束也完 全一致。但是类的属性、基类和实现接口的列表却可以不同。另外,分部类也可以用作嵌套 类,不过其包含类必须也是分部类。 编译时,编译器会合并位于不同文件中的同一个分部类的XML注释、属性、基类、接 口和成员。例如: // [CustomAttribute] publicpartialclassCustomer:BaseClass,IInterface1 { privateintid; privatestringname; privatestringaddress; privateList publicCustomer(){ …… } } // [AnotherAttribute] publicpartialclassCustomer:IInterface2 { publicvoidSubmitOrder(OrderorderSubmitted){ (orderSubmitted); } publicboolHasOutstandingOrders(){ >0; } 55 } 编译时会合并成: [CustomAttribute] [AnotherAttribute] publicclassCustomer:BaseClass,IInterface1,IInterface2 { privateintid; privatestringname; privatestringaddress; privateList publicCustomer(){ …… } publicvoidSubmitOrder(OrderorderSubmitted){ (orderSubmitted); } publicboolHasOutstandingOrders(){ >0; } } 2)静态类 静态类是在类的声明中,使用了static修饰符的类。静态类中只能包含静态成员,可以 用静态构造函数来初始化这些静态成员。静态类是密封的(不能派生其他类),静态类不能 被实例化,也不能包含实例构造函数。 静态类一般用于定义公用的常数和方法(如.NET中的Math类),使用时不需要创建实 例,直接用“类名.字段”或“类名.方法”即可。例如: publicstaticclassMath{……} …… doubles=*r*r; intx=(x1,x2)*(y); 56 4.object与 C#中的关键字object是.NET的类的别名,它是所有C#类型和.NET类 型的根基类(ultimatebaseclass)。在声明时,没有给出基类的类,C#编译器都自动假定其 派生于Object类。 类是.NET框架中所有类的最终基类,是类型层次结构的根。它支持.NET 框架类层次结构中的所有类,并为派生类提供低级别服务。下面是Object类的定义: [SerializableAttribute] [ComVisibleAttribute(true)] [ClassInterfaceAttribute(al)] publicclassObject 表17-14是Object类的成员列表。 表17-14 分类 公共构造函数Object() publicstaticboolEquals(ObjectobjA [,ObjectobjB]) 名称 Object类的成员 说明 初始化Object类的新实例。 已重载。确定两个Object实例是否 相等。 用作特定类型的哈希函数,适合在 publicvirtualintGetHashCode() 公共方法 哈希算法和数据结构(如哈希表) 中使用。 publicTypeGetType() publicstaticboolReferenceEquals (ObjectobjA,ObjectobjB) publicvirtualStringToString() 获取当前实例的类型。 确定指定的Object实例是否是相同 的实例。 返回表示当前Object的String对 象。 允许Object在“垃圾回收”回收 受保护的方法 Finalizer(终结):~Object() Object之前尝试释放资源并执行其 他清理操作。 protectedObjectMemberwiseClone()创建当前Object的浅表副本。 57 17.3.2接口与继承 继承(inheritance)是面向对象的基本特征,所有的C#类和结构都是直接或间接地从 类Object派生的。C#中没有明确指出基类的类,都以Object作为其默认基类。C#中的结 构不支持继承,但是C#支持类的单一继承(singleinheritance),多重继承的问题(与Java 一样)需通过接口来解决。 1.接口 接口(interface,界面)的概念来源于组件编程的思想。接口定义统一的公用方法的原 型,但是不包含方法的具体实现代码,方法体(代码)由实现接口的类来定义(编写)。 所以接口定义的是一种契约(contract),实现该接口的类则遵守(adhere)此契约。接 口相当于组件的界面,供使用组件的用户查询和调用,而实现接口的类则是组件的编码模块 本身。 在C#中,一个接口可以继承自多个接口,一个类也可以实现多个接口。实现某个接口 的类,必须实现该接口中的所有方法。 C#中的接口声明格式为:(与类声明很相似) [[属性]][接口修饰符][partial]interface标识符<类型参数,…>[:基接口,…] [where类型参数:类型参数约束[,…]……]{ [接口成员声明……] }[;] 其中: 接口修饰符有:new、public、protected、internal、private。 接口成员声明包括:接口方法声明、接口属性声明、接口事件声明和接口索引声明。 这些声明中都没有方法体,例如: publicdelegatevoidStringListEventHandler( IStringListsender,ListEventArgse); publicinterfaceIStringList{ voidAdd(strings); intCount{get;} eventStringListEventHandlerChanged; 58 stringthis[intindex]{get;set;} } 接口声明中其余项的含义与类的相同,这里就不再赘述了。 2.继承的种类 C#中的继承有两种类型: 实现继承(类继承)——派生类型继承了基类型的所有成员,包括方法、属性、事 件和索引的实现代码,派生类既可以直接调用基类的各种函数成员的实现代码,也 可以自己编写代码来覆盖或重写它们,还可以为派生类添加新的数据和函数成员。 接口继承(非实现继承)——派生类型只继承了基类型中的函数签名(signatureof afunction),包括函数名、类型参数的个数、每个形参(formalparameter)的类型 和种类(kind,值、引用、输出),从左到右的顺序,但是不包括函数的返回类型、 参数名称、类型参数的名称、以及可对最右边的参数指定的参数修饰符。而没有继 承基类型中的实现代码。接口继承主要用于指定派生类应该具有什么特性和功能。 接口继承相当于接口的实现。 C++只支持实现继承,VB则只支持接口继承,C++/CLI、Java和C#都既支持实现继 承,也支持接口继承,所以非常适用于编写基于组件的分布式软件。 C#中的结构,支持(多重)接口继承,但是不支持实现继承。即,你可以在结构中实 现多个接口中定义的函数成员,但是你却不能从类或结构来派生新的结构,也不能从结构派 生新的类。不过,用户定义的结构,总是自动派生于ype抽象类,还可以派生 于(实现)多个接口。 C#中的类,则支持单一实现继承和多重接口继承。 3.方法的继承 C#中方法的继承关系非常多样,除了传统C++的虚函数(C#增加了override修饰符) 和纯虚函数(即C#的abstract抽象函数)外,还增加了隐藏方法(new)、密封方法(sealed) 和基类方法的调用指令(base.方法名)等诸多新方式。 1)虚方法override 虚函数是面向对象编程中实现运行时多态性的标准途径,而编译时的多态性则是由具有 59 同样函数名,但是函数签名不同的多个重载(overload)函数来实现的。 与C++不同的是,在C#中重写基类的虚方法时,必须使用C#特有的重写修饰符 override(覆盖)。例如: classMyBaseClass{ publicvirtualintVirtualMethod(){……} } classMyDrivedClass:MyBaseClass{ publicoverrideintVirtualMethod(){……} } 2)隐藏方法new 在C++中,不需要添加任何修饰符,就可以任意重载或覆盖基类的虚函数和非虚函数, 但是在C#中,这样做会得到一个编译警告。目的是,为了避免程序员的笔误,也为了适应 代码的维护与升级。解决办法是,上面介绍过的使用override修饰符来明确重载基类的虚方 法,以及使用new修饰符来显式隐藏(覆盖)基类中具有同样签名的(非虚)方法。例如: classHisBaseClass{ publicintGroovyMethod(){……} } classMyDrivedClass:HisBaseClass{ publicnewintGroovyMethod(){……} } 3)基类方法调用base.方法名 在C++中,没有直接调用基类函数的方法。但是在C#中,这可以用“base.方法名”的 方式来完成,这一点与Java中的super的功能类似。例如: Method();//在MyDrivedClass中调用HisBaseClass类的同名方法 4)抽象方法abstract 在C++中,利用“=0”的纯虚函数,表示该函数没有函数体定义,需要在派生类中自 己编写。含有纯虚函数的类,叫做抽象类,不能被实例化。 在C#中,则直接用关键字abstract(抽象)来定义抽象方法和抽象类。例如: abstractclassA{ publicabstractintf(); 60 } 5)密封方法sealed C#增加的sealed(密封的)关键字,用于修饰方法和类,表示继承来的虚方法不能被 其派生类再重写和类不能被继承。分别被称为密封方法和密封类,其中密封方法必须同 override一起使用。例如: usingSystem; classA{ publicvirtualvoidF(){ ine("A.F"); } publicvirtualvoidG(){ ine("A.G"); } } classB:A{ publicsealedoverridevoidF(){ ine("B.F"); } publicoverridevoidG(){ ine("B.G"); } } classC:B{ publicoverridevoidF(){//错误,不能覆盖密封方法 ine("C.F"); } publicoverridevoidG(){ ine("C.G"); } } 61 又例如: sealedclassA{} classB:A{}//错误,不能从密封类派生 4.修饰符 在C#的类型和成员的声明中,使用了大量的修饰符,其中有许多是C#特有的。下面 将它们分类汇总,并进行一些简单的比较和分析。其中用粗体表示的修饰符是C++中没有的。 1)类型可见性修饰符与成员访问修饰符——参见表17-15。 表17-15 修饰符 public protected internal private protectedinternal C#的类型可见性与成员访问修饰符 应用对象 所有的类型和成员 类型和内嵌类型的成员 类型和内嵌类型的成员 所有的类型和成员 类型和内嵌类型的成员 说明 任何代码都可以访问。 只有其派生的类型能访问。 只能在包含它的程序集中访问。 只能在它所属的类中访问。 只能在包含它的程序集和其派 生的类型中访问。 2)其他修饰符——参见表17-16。 表17-16 修饰符 new static virtual abstract override C#的其他修饰符 说明 隐藏继承的同签名的函数成员。 不在类型实例上执行。 可由派生类重写。 只定义了签名没有实现代码。 重写继承的虚拟或抽象函数成 员。 类不能被继承和方法不能被重 写。 成员在外部用另一种语言实现。 应用对象 函数成员 所有成员 仅类的函数成员 类和函数成员 函数成员 sealed extern 类和函数成员 仅静态的DllImport方法 62 17.3.3委托与事件 为了避免C++直接使用函数指针所带来的副作用,C#也用委托来封装事件处理函数的 指针,用委托来定义对象触发的事件。 1.委托 C#和.NET的委托(delegate)与C/C++中的回调函数类似,但是委托比函数指针更安 全。因为回调函数只是一个指向存储函数体的内存指针,无法知晓函数的形参和返回类型, 也不能保证指针所指的内容一定是所需要的函数体。 当需要将函数作为参数来传递时,才需要函数指针和委托。主要用于启动线程、编写通 用类库和事件处理。 C#中委托的声明格式为: [[属性]][委托修饰符]delegate返回类型标识符[<类型参数,…>](形参列表) [where类型参数:类型参数约束[,…]……]; 其中,委托修饰符有:new、public、protected、internal、private,及它们的有效组合。与常 量修饰符的相同。例如: delegateintD1(inti,doubled); classA{ publicstaticintM1(inta,doubleb){…} } classB{ delegateintD2(intc,doubled); publicstaticintM1(intf,doubleg){…} publicstaticvoidM2(intk,doublel){…} publicstaticintM3(intg){…} publicstaticvoidM4(intg){…} delegateobjectD3(strings); publicstaticobjectM5(strings){…} publicstaticint[]M6(objecto){…} } 63 委托的使用方法参见下例: delegatevoidD(intx); classTest{ publicstaticvoidM1(inti){…} publicvoidM2(inti){…} } classDemo{ staticvoidMain(){ Dcd1=newD(Test.M1);//静态方法 Testt=newTest(); Dcd2=newD(t.M2);//实例方法 Dcd3=newD(cd2);//另一个委托 } } 2)匿名方法 除了利用方法签名来定义委托之外,在C#中还可以通过匿名方法(anonymousmethod) 来使用委托。所谓匿名方法,是用作委托参数的一个代码块(代替原来用作定义委托实例时 的参数的处理函数[体])。其定义格式为: delegate[([形参列表])]{……} 例如: usingSystem; delegateintD(); classTest{ staticDF(){ intx=0; Dresult=delegate{return++x;}; returnresult; } 64 staticvoidMain(){ Dd=F(); ine(d()); ine(d()); ine(d()); } } 输出结果为: 1 2 3 3.事件 事件(event)被广泛用于对象间的通信,例如Windows的应用程序就是基于消息机制 的。C#事件除了表示发生了某个有意义的事件外,还表示已经定义了一个可以处理通知的 对象(委托)。 事件的两种声明格式在前面的类成员说明中已经给出。 即平凡事件格式: [[属性]][事件修饰符]event委托类型标识符[=表达式或数组初始化器][,……]; 或非平凡事件格式: [[属性]][事件修饰符]event委托类型事件名{ [[属性]]add{……} [[属性]]remove{……} } 其中,事件修饰符与方法修饰符的相同。 例如(鼠标单击事件Click): // 定义 publicdelegatevoidEventHandler(objectsender, rgse); 65 publicclassButton{ publiceventEventHandlerClick; publicvoidReset(){ Click=null; } } //使用 usingSystem; publicclassForm1{ publicForm1(){ //为Button1的Click事件添加事件处理程序Button1_Click +=newEventHandler(Button1_Click); } ButtonButton1=newButton(); voidButton1_Click(objectsender,EventArgse){ ine("Button1wasclicked!"); } publicvoidDisconnect(){ -=newEventHandler(Button1_Click); } } 复习思考题 1. 2. 3. 4. 5. 6. 给出OOP的英文原文、中文译文和三个基本特征。 C、C++、Java、C#分别面向什么编程?它们之间有什么关系? C和C++语言分别是谁、在什么时间、为什么目的发明的? C和C++语言有哪些国际标准? 如何定义C++的命名空间?什么是无名的命名空间? 如何使用C++的命名空间?三种使用方法各有什么特点和适用范围? 66 7. 8. 9. Java语言最初是为什么应用设计的?它有哪些设计目标和关键特性? Java语言有哪些主要优势和缺点?它适用于什么编程? Java语言是如何实现跨平台和安全性的? 10.C#和.NET的总设计师是谁?他以前还开发过哪些著名软件? 11.C#语言的设计目标有哪些? 12.C#的主要特点是什么? 13.C#编程有哪些优势? 14.C#2.0、3.0和4.0的主要改进分别是什么? 15.如何创建VisualC#的控制台应用程序? 16.C#有头文件吗?C#程序的入口函数是什么?放在什么地方? 17.与C++相比,C#增加了哪两种新注释格式?它们有什么用处? 18.逐字符‘@’有什么用处? 19.可空类型符‘?’有什么用处? 20.C#的类型分成哪两大类?列举一些各类的典型具体类型。 21.C#中有联合类型union吗? 22.给出装箱和拆箱的英文单词和用处。 23.C#的字符、整数和实数类型与C++的什么不同? 24.C#的decimal类型用多少字节存储?主要用于什么?其常量后缀是什么字母? 25.C#增加哪些运算符?主要用于什么方面? 26.C#中的模运算%与C++的有什么不同? 27.C#中的枚举与C++的有哪些不同? 28.C#中的数组定义和创建与C++的有哪些不同? 29.在C#中怎样获取数组的大小和维数? 30.C#中有全局的变量和常量吗? 31.C#中的switch语句与C++的有什么不同? 32.与C++相比,C#增加了什么循环? 33.C#中的命名空间的定义与使用与C++的有什么不同? 34.与C++相比,C#语言增加了哪些新关键字?它们各有什么用处? 35.与C++相比,C#语言删除了哪些关键字?为什么? 36.C#中类的声明(定义)格式与C++的有哪些不同? 67 37.与C++相比,C#语言增加了哪些新的类修饰符?它们各有什么用处? 38.与C++相比,C#语言增加了哪些新的类成员的访问修饰符?它们各有什么用处?使用 方法有哪些不同? l类和方法是什么版本引进的?有什么用处? 40.C#类的数据成员和函数有哪些?增加了哪些修饰符?它们各有什么用处? 41.C#中的属性(property)是什么?有什么用处?如何声明(定义)? 42.与Java相比,C#保留了C++的部分指针和运算符重载功能?为什么?与C++的有什么 区别? 43.为什么C#的类没有析构函数而只有终结器? 44.C#的结构和类有哪些区别? 45.为什么Java和C#都要去掉类的多重继承?类似的功能如何实现? 46.为什么Java和C#都要引入接口类型?它与C++中的什么类型类似? 47.什么是实现继承和接口继承?VB、C++、Java和C#分别支持什么继承? 48.什么是静态类?有什么用处?如何定义和使用? 49.C#和.NET中的委托与C/C++中的什么类似?如何什么声明和使用委托? 50.事件与委托有什么关系?如何定义和使用事件处理程序? 练习题 1. 2. 3. (Hello例)实现17.2.1中的第一个C#程序HelloWorld。 (选做题,例子)实现本章中的其他例子。 (大作业选题,OOP)探讨OOP语言,对比C++、Java和C#等。 68
版权声明:本文标题:C#语言 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/p/1735633225a1682335.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论