您的位置:首页 > 编程语言 > C语言/C++

C#与C++中的类的对比分析

2009-08-30 13:13 288 查看



>与C++中的类的对比分析

类是面向对象程序设计方法的核心,利用它可以对数据的封装、隐藏、通过类的继承与派生,能够实现对问题的深入抽象描述。在面向对象的程序设计中,程序模块是由类构成的。函数或者方法是逻辑上相关的语句与数据的封装,用于完成特定的功能。类是逻辑上相关的函数与数据的封装,它是对所要处理的问题的抽象描述。
抽象,是人类认识问题的最基本手段之一。面向对象方法中的抽象是指对具体问题(对象)进行概括,抽出一类对象的公共性质并加以描述的过程。抽象的过程也就是对问题进行分析和认识的过程。在面向对象的软件开发中,首先注意的是问题的本质及描述,其次是解决问题的具体过程。一般来讲,对一个问题的抽象应该包含两个方面——数据抽象和代码抽象。前者描述某类对象的属性或状态,也就是此类对象区别彼类对象的特征物理量;后者描述某类对象的共同行为特征或具有的共同功能。
与C#中关于定义类的对比分析
提出问题:密码认证类
在大多数大型应用程序中,都需要用户登录的功能,提供一个密码才能完成登录。下面就分别采用C#和C++语言实现Authenticator这样一个很简单的密码认证类,它需要能设置一个新密码,以及检查密码是否有效。
C++源代码
// Authenticator.h
#ifndef AUTHENTICATOR_H
#define AUTHENTICATOR_H
#include<string>
using namespace std;
class Authenticator{
public:
Authenticator(string Password);
bool IsPasswordCorrect(string password);
bool ChangePassword(string oldPassword,string newPassword);
void foo(void){
string aaa="ABC";
};
private:
string Password;
};
#endif

// Authenticator.cpp
#include "Authenticator.h"
Authenticator::Authenticator(string initialPassword)
{
password = initialPassword;
}
bool Authenticator::IsPasswordCorrect(string password)
{
return (password==Password)?true:false;
}
bool Authenticator:: ChangePassword(string oldPassword,string newPassword)
{
if(oldPassword==Password){
Password=newPassword;
return true;
}
else return false;
}

//main.cpp
#include<iostream>
#include " Authenticator.cpp"
void main()
{
Authenticator simon,bill;
string l;
cout<<sizeof(l)<<endl;
cout<<"simon's size is:"<<sizeof(simon)<<endl;
bool Finish;
Finish = simon.ChangePassword("","Howells");
simon.foo();
bill.foo();
if(Finish == true)
cout<<"Password for simon changed successfully !/n";
else
cout<<"Failed to change password for Simon!/n";
if(simon.IsPasswordCorrect("Zhonghao"))
cout<<"Verified simon's password!/n";
else
cout<<"Failed to verify simon's password!";
}
C#源代码
using System;
namespace Authenticators
{
class Class1
{

[STAThread]
static void Main(string[] args)
{
Authenticator simon = new Authenticator("");
bool Finish;
Finish = simon.ChangePassword("","Howells");
if(Finish == true)
Console.WriteLine("Password for simon changed successfully !");
else
Console.WriteLine("Failed to change password for Simon!");
if(simon.IsPasswordCorrect("Zhonghao"))
Console.WriteLine("Verified simon's password!");
else
Console.WriteLine("Failed to verify simon's password!");
}
}
public class Authenticator
{
private string password;
public Authenticator(string initialPassword)
{
password = initialPassword;
}
public bool IsPasswordCorrect(string Password)
{
if(password == Password)
return true;
else
return false;
}
public bool ChangePassword(string oldPassword,string newPassword)
{
if(oldPassword == password)
{
password = newPassword;
return true;
}
else
{
return false;
}
}
}

上面的C++程序以两个预处理指令开头来包含一些头文件:
#include<iostream>
#include<string>
C#中不包含这些预处理指令,这显示了C#访问库的方式的一个要点。C++中包含头文件,就使得编译器能够识别代码中相关的符号。我们需要单独地知道这些链接来的引用库——通过命令行参数传递到链接程序来实现。C#并不像C++那样将编译与链接分离。在上面的C++代码中,第一条#include语句访问的是一个ANSI标准库。尤其是C#不支持C++中最常用的#include指令,这是因为C#不需要前向声明。
虽然上面的C#代码没有#include指令,但是其中一些预处理指令在C#中仍能使用,而且保留了#语法。如C#中同样有#if ,#else ,#endif,#define等预处理命令,而且与C++中的使用方法相同。从这一点可以看出C#其实是借鉴了C++的语法的。预处理指令的使用语法在两个语言中是基本相同的。C#支持的预处理指令如下表所示:
表1-1 预处理指令表

指令

含义
#define / #undef
含义与在C++中相同,但它必须出现在文件的开头,在所有C#代码前
#if/#elif/#else/#endif
与C++的#if/#elif/#else/#endif相同
#line
与C++的#line相同
#warning / #error
与C++的#warning/#error相同
#region / #endregion
将一个代码快标记为一个区域。一些特定的编辑器能识别区域(如,Visual Studio.NET的折叠式编译器),当进行编辑时,使用这些编辑器可以改善所给出的代码的布局

我们看到了上面的程序中都使用到了namespace这样一个关键字,如在C++中的” using namespace std;”;还有他们都使用了 ”namespace Authenticators”。这叫做名称空间,目的是提供一种组织相关类和其他类型的方式。
u C# 中的名称空间的作用:
(1) 避免冲突
例如:在一个大型的项目中,一个汽车经销商向某汽车制造公司购买汽车。试着采用面向对象的分析方法来对“汽车经销商”和“汽车制造商”中都涉及到的“汽车”这一客观实体进行抽象。
² 从“汽车经销商”的角度来抽象,可以得出这样一个关于汽车对象的类:
public class car
{
private string carName; //购买的汽车的名字;
private string carNo; //购买的汽车的编号;
private string unitPrice;   //购买的该汽车的单位价格;
}
² 从“汽车制造商”的角度出发来抽象,可以得出这样一个关于汽车对象的类:
public class car
{
private string carName; //生产的汽车的名字;
private string carNo; //生产的汽车的型号;
private Engine engine; //生产的汽车所用引擎;
private string carColor; //生产的汽车的颜色;
}
可以看到从不同的角度出发抽象得出的类模型是不同的,但是他们又都具有同样的类名称,当在一个项目中恰好要同时用到这两种同名但不同性质的类的时候该怎么办呢?而名称空间正好是解决这个问题的办法!
我们可以给两个同名但不同性质的car类加上不同的名称空间,这样在调用它们时就不会引起编译器的误解了。
例如:我们可以用carDealer这样一个名称空间来包含从“汽车经销商”这个客观实体中抽象出的所有类,当然也包括car类。
Namespace CarDealer
{
public class car
{
………
}
}
同样,我们也可以用carManufacturer这样一个名称空间来包含从“汽车制造商”这个客观实体中抽象出的所有类,当然也包括car 类。
Namespace CarManufacturer
{
public class car
{
……….
}
}
这样两个拥有相同名称的car类就可以在同一个程序中被正确的访问了,如:申明一个“汽车制造商”中抽象出来的car 类的一个对象:
CarManufacturer.car mycar = new carManufacturer.car();
而申明一个“汽车经销商”中抽象出来的car类的一个对象:
CarDealer.car yourcar = new carDealer.car();
(2) 组织代码
在C#中,名称空间是C#类,接口,委托,枚举和其他类型的一个逻辑上的组合。例如,需要使用绘画功能的类来作图时,可以在System . Drawing 名称空间中查找和它相关的类型;需要使用正则表达式的时候,可以在System.Text.RegularExpressions名称空间中查找相关类型。
名称空间提供了一种组织相关类和其他类型的方式,名称空间只是一种逻辑组合,不是物理组合。CLR不承认名称空间的存在。例如,它不知道Console是System名称空间的一个成员。它认为System . Console是类的名称。
u C++ 中的名称空间的作用:
C++中的名称空间和C#中的名称空间几乎是一样的,同样也是为了方便组织代码和避免冲突。
u C#和C++中的名称空间的区别:
1) 虽然在C++中的名称空间具有和C#中的名称空间同样的功能,但C#(确切的说是 .NET )把所有的“基类”都采用名称空间的形式组织起来,而C++并没有这样做。造成这方面差别的主要原因还是因为C++不是所谓的“完全面向对象”的程序语言,而C#可以称得上是完全面向对象的。
2) C#中名称空间的名称中的每个单词的第一个字母应该大写(非必须)。而在C++中并没有这样的要求(如:using namespace std)。
3) C#中调用名称空间中的类时是采用:名称空间的名称 .类名;而C++中调用名称空间中的类时是采用:名称空间的名称 ::类名。
4) 在C++中的名称空间还有个有趣的现象,如:
namespace me {
class us {
//……
friend void you(void);
};
}
me :: void you(void)
{
cout<<”I am a firend of me !”;
}
这样you()函数被申明成了名称空间me 的一员,虽然它也是在us类中定义的,但是它不再属于us类,它直接由名称空间me来控制。如果尝试这样来在名称空间以外来定义you()函数:me::us::void you(void ){….};将导致编译失败。

(1) 从Car类的源代码中可以看出,C++和C#语言关于访问限制修饰符申明的位置是有区别的。C++中的访问限制修饰符(如:public , private )等是要放在所有具有同样的访问限制的字段的上方。如:在Authenticator类中。
public:
bool IsPasswordCorrect(string password);
bool ChangePassword(string oldPassword,string newPassword);
而C#中的访问限制修饰符是要放在所申明的每个字段前。如:在Authenticator类中。
public bool IsPasswordCorrect(string password){……};
public bool ChangePassword(string oldPassword,string newPassword);
(2) 在C#和C++中,所定义的类中的字段的默认访问限制都是private。
(3) 在C#中有internal 和 protected internal 访问限制符,在C++中是没有的。下面我将详细讲解它们:
Ø internal只可以被本组合体(Assembly)内所有的类存取,组合体是C#语言中类被组合后的逻辑单位和物理单位,其编译后的文件扩展名往往是“.DLL”或“.EXE”。可以看出,C#只是用internal扩展了C++原来的friend修饰符。在有必要使两个类的某些域互相可见时,我们将这些类的域声明为internal,然后将它们放在一个组合体内编译即可。
Ø protected internal是唯一的一种组合限制修饰符,它只可以被本组合体内所有的类和这些类的继承子类所存取。当需要对两个类的继承子类也可见的话,声明为protected internal即可。实际上这也是组合体的本来意思——将逻辑相关的类组合封装在一起。
(4) 在C#中的Authenticator类被设置为了public的访问限制;而C++不能对整个类作限制。在C#中对类设置了共8个不同的访问限制类型,下面我将详细讲述当Authenticator类被分别设置为8个访问限制类型时的作用:
Ø 公有(public):Authenticator类可以被所有其它的类访问。没有其它限制修饰语,它的公有性质就一直是缺省的。
Ø 私有(private):只有当Authenticator类作为其他类的成员才能访问。
Ø 保护(protected):当Authenticator类作为其他类的成员或者Authenticator类的继承类成员可以访问。
Ø 内部(internal):只有汇编里的成员才能访问(C#的汇编是代码和资源数据的结合,以asmx作文件后缀)。
Ø 内部保护(protected internal):类成员和继承类成员可以访问。
Ø 密封(sealed):所有继承类都不能访问。无论直接或间接地将它作为基类,C#编译器都会报错。
Ø 抽象(abstract):与C++的虚(virtual)类或虚方法相似,抽象类不能直接实例化,抽象函数含有函数名。但在作为基类或继承类时可以使用。
Ø 新建(new):用new创建嵌套类,可以隐藏继承方式,告诉编译器创建一个类的新版本。

C#和C++中都有程序入口点的概念,而且它们的形式上也比较相似。在C++中,它是全局函数,命名为main()。C#则命名为Main(),但作用基本相同。在C++中,main()定义在类的外部,而C#中则将它定义为类的一个静态成员,因为C#要求所有的函数和变量都必须为类或者结构的成员。除了类和结构外,在程序中,C#不允许有任何顶层存在,从某种意义上来说,C#的面向对象的编程比C++更加严格。在C++中,过多的全局或者静态的变量和函数是不良的程序设计思路。
同时,因为要求所有的元素都为类的成员也会引发一个问题:程序的入口点在哪里?在C#中程序的入口点是静态的Main()成员来作为程序的入口点,它可以是源代码中任何类的一个成员,但一般只允许一个类包含这个方法,如果多个类包含了这个方法,就需要用编译器开关来指定作为程序入口点的编译器。

u 相同点
C++,C#的构造函数都是同样的功能,既为类的数据成员赋初值。在声明的语法以及方式上均相同。
u 不同点
(1)  多个构造函数可相互调用
当一个类中定义了多个构造函数的时候,我们是否可以让其中的一个构造函数去调用另外一个构造函数呢?在C# 中是允许的,但在C++中,却不行。
在C#中,一个构造函数也可通过关键字 this 调用同一个类的另一个构造函数
我们来看下面的例子:
using System;
namespace MultiConstructors
{class Class1
{ static void Main(string[] args)
{
Point bill = new Point(1,2);
Console.WriteLine("bill.x = {0}, bill.y = {1}",bill.x,bill.y);
}
}
class Point
{ public int x,y;
public Point(int X)
{x=X;}
public Point(int X,int Y):this(X) //(A)行
{ y = Y;}
}
}
在源程序的第A行我们使用了this指针来调用Point类的另一个构造函数Point(int X)。然而这在C++中是不允许的。
(2)   构造函数的参数默认值
在C#中的默认构造函数会对类中字段进行默认值初试化,然而C++中的默认构造函数并不会对类中的未初始化成员进行默认值初始化。C#中默认值初试化情况如下:
Ø 对于值类型:
表1-2 字段默认值表

值类型
默认值
bool
False
byte
0
char
'/0'
decimal
0.0M
double
0.0D
float
0.0F
int
0
long
0L
sbyte
0
Short
0
struct
将所有的值类型字段设置为默认值并将所有的引用类型字段设置为 null 时产生的值。
uint
0
ulong
0
ushort
0
Ø 对于引用类型,全部初试化为null。

C#中包含静态构造函数,它在运行时只执行一次,用来初始化静态变量。C++中不直接存在这个等同的概念。如:
Class MyClass
{
static MyClass()
{
//static construction code
}
}
静态构造函数允许使用在运行时获得的值对静态字段进行初始化;C++ 中也能实现这一功能,最常见的方法就是定义一个访问静态成员变量的函数,当它第一次调用时,执行对变量的赋值。此外,静态构造函数不需要访问限定符——public ,private等。访问修饰符对于一个静态构造函数没有意义,因为静态构造函数只能在类定义加载时被.NET运行时调用。它不能被其他的C#代码调用。静态构造函数通常在下面情况下被调用:在创建第一个实例或引用任何静态成员之前,将自动调用静态构造函数来初始化类。

C++ 有拷贝构造函数这一概念,其作用是使用一个已经存在的对象去初始化一个新的同类的对象。如果程序员没有定义类的拷贝构造函数,系统会自动生成一个默认的,这个默认的拷贝构造函数的功能是把初始值对象的每个数据成员都复制到新建立的对象中去;但在C#中并没有所谓的拷贝构造函数,系统也不会自动生成一个拷贝构造函数来提供用一个初始值对象去建立另外一个新的对象。但是在C#中也可以模拟一个C++中的拷贝构造函数。但他们的功能是有区别的,具体区别如下:
在C++中,系统会生成默认的拷贝构造函数;但是C#中并不会生成默认的拷贝构造函数,可以说在C#中并没有所谓的拷贝构造函数。例如:
#include<iostream.h>
class Test
{ public:
Test(int = 0);
void print() const;
private:
int x;
};
Test::Test(int a){ x = a ;}
void Test::print() const
{cout<<" x = "<<x<<endl;}
void main(void)
{
Test simon(12);
Test bill(simon);
cout<<"The variable x of simon is : ";
simon.print();
cout<<"The variable x of Bill is : " ;
bill.print();
}

程序运行的结果是:
The variable x of simon is : x = 12
The variable x of Bill is : x = 12
Press any key to continue

代码中我们没有申明拷贝构造函数,但是仍然可以用一个对象去初始化另外一个对象。但是如果我们试图编写以下的C#代码,用一个对象去初试化另一个对象。
using System;
namespace Test
{
class Class1
{
static void Main(string[] args)
{
Test simon,bill,kill;
simon = new Test(12);
bill = new Test(simon);
Console.Write("The variable x of simon is : ");
simon.print();
Console.Write("The variable x of bill is : ");
bill.print();
}
}
class Test
{
private int x;
public Test(int initialX)
{
this.x = initialX;
}
public void print()
{
Console.WriteLine(this.x);
}
}
}
你会得到: Class1.cs(12): 与“Test.Test.Test(int)”最匹配的重载方法具有一些无效参数; Class1.cs(12): 参数“1” : 无法从“Test.Test”转换为“int”。的编译错误报告。
虽然C#中没有默认的拷贝构造函数,但我们可以模仿C++中的拷贝构造函数来模拟一个C#的“拷贝构造函数”。具体实现如下C#代码:
using System;
namespace Test
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
Test simon,bill,kill;
simon = new Test(12);
bill = new Test(simon);
Console.Write("The variable x of simon is : ");
simon.print();
Console.Write("The variable x of bill is : ");
bill.print();
}
}
class Test
{
private int x;
public Test(int initialX)
{
this.x = initialX;
}
public Test(Test p)
{
this.x = p.x;
}
public void print()
{
Console.WriteLine(this.x);
}
}
}

程序运行结果如下:
The variable x of simon is : 12
The variable x of bill is : 12

我们为类Test添加了另外一个构造函数实现了使用一个已经存在的对象去初始化一个新的同类的对象。但它与C++中的构造函数有很大的分别。C++中的拷贝构造函数在以下情况中将被调用:其中后两种情况归根结底还是第一种情况。
(1) 用一个对象去初试化该类的另外一个对象时。
(2) 如果函数的形参是类的对象,调用函数时,进行形参和实参结合时。
(3) 如果函数的返回值是类的对象,函数执行完成返回调用者时。
我们在C#中模拟的拷贝构造函数除了第一种情况下会被调用,其他情况均不会被调用。具体示例如下:
#include <iostream.h>
class Point //Point 类的定义
{
public: //外部接口
Point(int xx=0, int yy=0) {X=xx;Y=yy;} //构造函数
Point(Point &p); //拷贝构造函数
int X,Y;
};
//成员函数的实现
Point::Point(Point &p)
{
X=p.X;
Y=p.Y;
cout<<"拷贝构造函数被调用"<<endl;
}
//形参为Point类对象的函数
void fun1(Point p)
{
cout<<"The p in the fun1(Point p) has address :"<<&p<<endl;
}
//返回值为Point类对象的函数
Point fun2()
{
Point A(1,2);
cout<<"the A in the fun2() has address :"<<&A<<endl;
return A;
}

//主程序
void main()
{
Point A(4,5); //第一对象A
Point B(A); //情况一,用A初始化B。第一次调用拷贝构造函数
cout<<"Before call fun1(A),the A has address :"<<&A<<endl;
fun1(A); //情况二,对象B作为fun1的实参。第二次调用拷贝构造函数
B=fun2(); //情况三,fun2的返回值赋对象B。第三次调用拷贝构造函数
cout<<"After call fun2(),the B has address :"<<&B<<endl;
}

程序运行结果如下:
拷贝构造函数被调用
Before call fun1(A),the A has address :0x0013FF78
拷贝构造函数被调用
The p in the fun1(Point p) has address :0x0013FF10
the A in the fun2() has address :0x0013FF04
拷贝构造函数被调用
After call fun2(),the B has address :0x0013FF70
Press any key to continue

我们再来编写一个与之对应的C#程序,如下:
using System;
namespace Cs
{
class Class1
{
static void Main(string[] args)
{
Point A,B;
A = new Point(4,5);
B = new Point(A);
FunClass tryfun = new FunClass();
unsafe
{
fixed(int *px = &(A.x))
{
Console.WriteLine("Before call fun1(A),A.x has address :"+(uint)px);
};
}
tryfun.fun1(A);
B = tryfun.fun2();
unsafe
{
fixed(int *px = &(B.x))
{
Console.WriteLine("After all fun2(),B.x has address :"+(uint)px);
};
}
}
}
class FunClass
{
public void fun1(Point p)
{
unsafe
{
fixed(int *px = &(p.x))
{
Console.WriteLine("The p.x in the fun1(Point p) has address :"+(uint)px);};
}
}
public Point fun2()
{
Point A = new Point(1,2);
unsafe
{
fixed(int *px = &(A.x))
{
Console.WriteLine("The A.x in the fun2() has address :"+(uint)px);
};
}
return A;
}
}
class Point
{
public int x,y;
public Point(int xx ,int yy)
{
x=xx;
y=yy;
}
public Point(Point p)
{
x = p.x;
y = p.y;
Console.WriteLine("拷贝拷贝构造函数被调用!");
}
}
}

程序运行结果:
拷贝拷贝构造函数被调用!
Before call fun1(A),A.x has address :79436400
The p.x in the fun1(Point p) has address :79436400
The A.x in the fun2() has address :79444848
After all fun2(),B.x has address :79444848

该程序证明了: C#中模拟的拷贝构造函数只会当我们用一个对象去初试化同类的另外一个对象时被调用,其他情况都不会调用该函数。
那么到底是什么原因造成了C++与C#的拷贝构造函数的差别呢?下面我们来分析一下:
1) 当函数的形参是类的对象,调用函数时,进行形参和实参结合时,C++就会
调用拷贝构造函数。
void main()
{
Point A,B;
。。。。。。。。。。。。
fun1(A);
。。。。。。。。。。。。
}
void fun1(Point p)
{

}
当我们调用函数fun1(B)时,编译器首先在内存中开辟一个临时空间以供函数使用。所以当对象是作为形参传过来时,fun1函数会在把对象A实体拷贝到函数体所在内存空间并且将其赋值给形参p。可以看到对象A和p的内存地址不相同。由于牵涉到用一个已经存在的对象去初试化另外一个同类对象,所以C++中会去调用拷贝构造函数。而在C#中,在调用fun1函数时却不会把对象A实体拷贝到方法体所在的内存空间,这是因为.NET运行时把对象A的引用拷贝赋值给形参p。可以看到对象A与形参p的地址是一样的:
Before call fun1(A),A.x has address :79436400
The p.x in the fun1(Point p) has address :79436400
2) 如果函数的返回值是类的对象,函数执行完成返回调用者时。C++会调用构造函

void main()
{
Point A,B;
……………
B=fun2();
……………
}
Point fun2()
{
Point A(1,2);
…………….
return A;
}
当我们调用fun2()函数时,在函数内部又申明一个新的Point类的对象A。由于待函数执行完毕后,其函数中所申明的所有变量都会从内存中释放掉。所以为了返回对象A就必须调用拷贝构造函数,把函数内申请的对象A赋值给主程序中的变量B,所以其实也属于用一个对象去初试化另外一个同类对象。当然会调用拷贝构造函数了。可以看到在程序中函数fun2()中的对象A的地址与主程序中对象B的内存地址不相同。
3) 但是在C#中,也不会调用拷贝构造函数,这是为什么呢?我们来看下面
的代码:
…………….
tryfun.fun2(B);
…………….
class FunClass
{
public Point fun2(Point p)
{
……………
}
}
在C#中类被申明后,类的实体将被放在堆中。它将一直放在堆中,除非C#垃圾回收器发现.NET运行时确定不再需要这个类的实例,才会采取行动将其从内存中清除。所以当我们调用fun2()函数申明了一个A对象时,编译器就会将A的实例放在堆上,把堆上A的实例的引用赋给A变量。当我们返回对象A时,其实返回的是指向A实例所在堆上的内存地址。由于C#垃圾回收器发现函数体外仍然对对象A有引用(B=func2()),所以就不会删除对象A。可以看到A.x的地址和B.x的地址一样:
The A.x in the fun2() has address :79444848
After all fun2(),B.x has address :79444848

u 相同点:
C++和C#中的析构函数,都是在类实例越界时,或者要从内存中删除时调用这个析构函数。此外析构函数还可以用来清理资源,提供调试信息或其他任务。并且他们都具有同样的语法形式。提供包含类的名称,前面有一个音标符号~。
class MyClass
{
~MyClass()
{
//do whatever cleanup is needed
}
}
u 不同点:
C++中当对象超出作用域或者被破坏时,C++的析构函数就一定会被调用。但在C#中您不必像使用 C++ 时那样关注内存管理,这是因为 .NET垃圾回收器会隐式地管理对象的内存分配和释放,所以不能预计他们会什么时候运行,一般情况下是不鼓励使用它们的。在C#中的.NET垃圾回收器的一个优点是通常不需要使用析构函数,因为编程人员一般不需要显示地释放内存。不过也有几种情况需要析构函数,例如:当应用程序封装窗口,文件和网络连接这类非托管资源时,应当使用析构函数释放这些资源。当对象符合销毁条件时,垃圾回收器会运行对象的析构函数。C#中用户定义的类的析构函数实际上是重写了System.Object中的虚方法Finalize,但C# 程序中不允许重写此方法或直接调用它(或它的重写)。例如,下面代码:
class A
{
override protected void Finalize() { } // error
public void F() {
this.Finalize(); // error
}
}
包含两个错误:
编译器的行为就像此方法和它的重写根本不存在一样。因此,以下程序:
class A
{
void Finalize() {} // permitted
}
是有效的,所声明的方法覆盖了 System.Object 的 Finalize 方法。

与C#中静态成员初始化区别

在C#中的静态数据成员我们可以在类的封装符内初始化,然而在C++中只有const类型的静态数据成员才能进行类内初始化。例如:
class MyClass
{
static const int p=10; //(A)
//static int q =10; //(B)
}
在C++中,A处的代码是合法的,B处的代码是不合法的。在C++中,静态字段的初始化是通过类外部的一个单独的语句来实现的,如:
int MyClass::MyStaticField = 10;
在C++中,即使不对静态成员进行初始化,也必须包括这条语句,以防止链接错误。而C#则不需要这条语句,因为C#中的变量只能在一处声明,可以在声明的同时对其进行初始化,也可以在静态构造函数中对其初始化。

静态对象的初始化和销毁

C++中的静态对象有一处特别有趣的地方,当一个程序包含了静态类成员并且在全局作用域中对它们进行了初始化的时候。我们来看看下面这个程序:
#include <iostream.h>
class Point
{
public:
Point(){cout<<"Point's constructor invoked"<<endl;}
~Point(){cout<<"Point's destructor invoked"<<endl;}
};
class Line
{
public:
static Point x1;
Point x2;
};
Point Line::x1;
int main()
{
cout<<"This is the first statement of main"<<endl; //(A)
return 0;
}
这段程序相当的简单,main()函数完成的任务就是打印下面这个字符串:
“This is the first statement of main”
然而,如果我们编译运行这个程序,我们就会发现他并不是这样的简单。程序输出是:
Point's constructor invoked
This is the first statement of main
Point's destructor invoked
Press any key to continue
另外两条输出来自何方呢?其实它来自与静态初始化和静态销毁。当一个程序在全局作用域中对静态类成员进行了初始化之后,C++编译器会在 main函数中调用额外的代码,它出现在程序员所编写的第一条可执行语句之前,用于静态对象的创建。至于按照这种方法所创建的对象的销毁,编译器会在main函数中、程序员所编写的最后一条可执行语句之后紧接着插入一些额外的代码用于这个任务。在上面这个程序中,第B行完成初始化任务的Point的构造函数调用出现在第A行的语句之前,而Point的析构函数将出现在程序员编写的最后一条语句之后(A行之后),就是它们产生了如上图所示的输出。归根结底是因为在C++中,静态字段的初始化必须通过类外部的一个单独的语句来实现。这就使得其成为全局变量,所以会在main()函数之前初始化;由于在C#中没有所谓的全局变量,所以C#中的静态字段不会有这种现象。

与C#中const静态数据

此外在C++中可以定义一个静态的常量,而其声明的静态常量可以在定义处初始化,也可以在类外进行初始化 。如:
class MyClass
{
public:
static const int MyDataField;
}
但在C#中,不能将常量显示地声明为静态常量——这样会导致编译错误,但是其实它隐式的就是静态常量,并且必须在声明常量的同时对其进行初始化。我们来看下面这段代码:
using System;
namespace constData
{
class MyClass
{
public const int MyConstant = 10 ;
}
class Class1
{
static void Main(string[] args)
{
int howells;
MyClass bill = new MyClass(); (A)
//howells = bill.MyConstant; (B)
howells = MyClass.MyConstant; (C)
}
}
}
我们声明了一个MyClass类的对象bill,如果我们调用第(B)行代码,通过bill去访问MyConstant字段将得到以下的编译错误报告:“fig2-20.cs(17): 无法使用实例引用访问静态成员“constData.MyClass.MyConstant”;改用类型名来限定它“;正确的调用MyClass类的常量字段MyConstant是通过类名来访问,如(C)行代码所示。
在C#中有另外一个关键字“readonly”也可以声明为静态常量static readonly,但它也可以在静态构造函数中进行初始化。因此,根据所使用的构造函数,readonly 字段可能具有不同的值。另外,const 字段是编译时常数,而 readonly 字段可用于运行时常数。
与C#中的this指针比较
This操作符的含义与C++中的this是完全相同的,但是在C#中它是引用而不是指针。例如,在C++中可以编写:
this->My_DataField = 10;
而在C#中,必须编写为:
this.My_DataField = 10;
使用this的方式在两种语言中也是完全相同的。例如,在方法的调用中,可以将它作为参数来传递,也可以用它来显示表示:我们正在访问一个类的字段成员。在C#中,还有另外两种情况需要使用this:一是调用同一个类中的其他构造函数;二是索引器。
与C#一些基本语法上的比较
C#和C++的语法实际上是完全相同的。它们都忽略了语句之间的空白,都用分号来分隔语句,用大括号来括住块语句。但是它们仍存在一些细微的差别。
(1) 在定义类的语句末尾,C++要用分号来表示,而C#则不需要。
(2) C++允许表达式作为语句,即使它们没有任何意义。如:i+1;在C#中,这是错误的语法。
(3) 在C#中,不支持也不需要支持向前声明,因为源文件中成员定义的顺序并没有受到约束。在文件中,可以引用还没有定义其他文件的成员,只要在某处确实定义了该成员即可。而在C++中,只能引用一个在本文件或者子文件中已经声明过的成员。
(4) 在C#中,任何项目的定义和声明都结合在一起。但是在C++中类的头文件中只需要给出成员函数的签名,而在其他地方给出它的完整定义;而在C#中,方法的完整定义总是在类的定义中就给出的,如:
class Point
{
public getX(){ return x; }
public int x;
}
其实这种方式的代码并不容易读懂,相比起C++程序时,不需要读函数的执行部分,只要浏览头文件就知道类中都包含了哪些公共函数。但是C#有了非常现在和人性化的编译器:Visual Studio.NET编译器是折叠式编译器,可以将方法的执行折叠起来,而且C#还有自动为代码生成XML格式的文档说明工具。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: