[你必须知道的.NET] 第四回:后来居上:class和struct

发布日期:2007.4.24 作者:Anytao

©2007 Anytao.com ,原创作品,转贴请注明作者和出处。

本文将介绍以下内容:

• 面向对象基本概念

• 类和结构体简介

引用类型和值类型区别

1. 引言

提起class和struct,我们首先的感觉是语法几乎相同,待遇却翻天复地。历史将接力棒由面向过程编程传到面向对象编程,class和struct也背负着各自的命运前行。在我认为,struct英雄迟暮,class天下独行,最本质的区别是class是引用类型,而struct是值类型,它们在内存中的分配情况有所区别。由此产生的一系列差异性,本文将做以全面讨论。 

2. 基本概念

2.1. 什么是class?

class(类)是面向对象编程的基本概念,是一种自定义数据结构类型,通常包含字段、属性、方法、属性、构造函数、索引器、操作符等。因为是基本的概念,所以不必在此详细描述,读者可以查询相关概念了解。我们重点强调的是.NET中,所有的类都最终继承自System.Object类,因此是一种引用类型,也就是说,new一个类的实例时,对象保存了该实例实际数据的引用地址,而对象的值保存在托管堆(managed heap)中。 

2.2. 什么是struct?

struct(结构)是一种值类型,用于将一组相关的信息变量组织为一个单一的变量实体 。所有的结构都继承自System.ValueType类,因此是一种值类型,也就是说,struct实例分配在线程的堆栈(stack)上,它本身存储了值,而不包含指向该值的指针。所以在使用struct时,我们可以将其当作int、char这样的基本类型类对待。

3. 相同点,不同点

相同点:语法类似。

不同点:

  • class是引用类型,继承自System.Object类;struct是值类型,继承自System.ValueType类,因此不具多态性。但是注意,System.ValueType是个引用类型。
  • 从职能观点来看,class表现为行为;而struct常用于存储数据。
  • class支持继承,可以继承自类和接口;而struct没有继承性,struct不能从class继承,也不能作为class的基类,但struct支持接口继承(记得吗,《第二回:对抽象编程:接口和抽象类》也做过讨论)
  • class可以声明无参构造函数,可以声明析构函数;而struct只能声明带参数构造函数,且不能声明析构函数。因此,struct没有自定义的默认无参构造函数,默认无参构造器只是简单地把所有值初始化为它们的0等价值
  • 实例化时,class要使用new关键字;struct可以不使用new关键字,如果不以new来实例化struct,则其所有的字段将处于未分配状态,直到所有字段完成初始化,否则引用未赋值的字段会导致编译错误。
  • class可以实抽象类(abstract),可以声明抽象函数;而struct为抽象,也不能声明抽象函数。
  • class可以声明protected成员、virtual成员、sealed成员和override成员;而struct不可以,但是值得注意的是,struct可以重载System.Object的3个虚方法,Equals()、ToString()和GetHashTable()。
  • class的对象复制分为浅拷贝和深拷贝(该主题我们在本系列以后的主题中将重点讲述,本文不作详述),必须经过特别的方法来完成复制;而struct创建的对象复制简单,可以直接以等号连接即可。
  • class实例由垃圾回收机制来保证内存的回收处理;而struct变量使用完后立即自动解除内存分配。
  • 作为参数传递时,class变量是以按址方式传递;而struct变量是以按值方式传递的。

我们可以简单的理解,class是一个可以动的机器,有行为,有多态,有继承;而struct就是个零件箱,组合了不同结构的零件。其实,class和struct最本质的区别就在于class是引用类型,内存分配于托管堆;而struct是值类型,内存分配于线程的堆栈上。由此差异,导致了上述所有的不同点,所以只有深刻的理解内存分配的相关内容,才能更好的驾驭。本系列将再以后的内容中,将引用类型和值类型做以深入的比较和探讨,敬请关注。当然正如本文标题描述的一样,使用class基本可以替代struct的任何场合,class后来居上。虽然在某些方面struct有性能方面的优势,但是在面向对象编程里,基本是class横行的天下。

那么,有人不免会提出,既然class几乎可以完全替代struct来实现所有的功能,那么struct还有存在的必要吗?答案是,至少在以下情况下,鉴于性能上的考虑,我们应该考虑使用struct来代替class:

  • 实现一个主要用于存储数据的结构时,可以考虑struct。
  • struct变量占有堆栈的空间,因此只适用于数据量相对小的场合。
  • 结构数组具有更高的效率。
  • 提供某些和非托管代码通信的兼容性。

所有这些是struct有一席之地的理由,当然也许还有其他的更多说法,只是我不知道罢了:-)

4. 经典示例

4.1 小菜一碟

下面以示例为说明,来阐述本文的基本规则,详细见注释内容。
(1)定义接口

    interface IPerson
    {
        
void GetSex();
    }

(2)定义类

    public class Person
    {
        
public Person()
        { 
        }

        
public Person(string name, int age)
        {
            _name 
= name;
            _age 
= age;
        }

        
private string _name;

        
public string Name
        {
          
get { return _name; }
          
set { _name = value; }
        }

        
private int _age;

        
public int Age
        {
          
get { return _age; }
          
set { _age = value; }
        } 
    }

(3)定义结构

   //可以继承自接口,不可继承类或结构
    struct Family: IPerson
    {
        
public string name;
        
public int age;
        
public bool sex;
        
public string country;
        
public Person person;

        
//不可以包含显式的无参构造函数和析构函数
        public Family(string name, int age, bool sex, string country, Person person)
        {
            
this.name = name;
            
this.age = age;
            
this.sex = sex;
            
this.country = country;
            
this.person = person;
        }

        
//不可以实现protected、virtual、sealed和override成员
        public void GetSex()
        {
            
if (sex)
                Console.WriteLine(person.Name 
+ " is a boy.");
            
else
                Console.WriteLine(person.Name 
+ " is a girl.");
        }

        
public void ShowPerson()
        {
            Console.WriteLine(
"This is {0} from {1}"new Person(name, 22).Name, country);
        }

        
//可以重载ToString虚方法
        public override string ToString()
        {
            
return String.Format("{0} is {1}, {2} from {3}", person.Name, age, sex ? "Boy" : "Girl", country);
        }
    }

(4)测试结构和类


猜猜运行结果如何,可以顺便检查检查对这个概念的认识。

4.2 .NET研究

在.NET 框架中,System.Drawing命名空间中的有些元素,如System.Drawing.Point就是实现为struct,而不是class。其原因也正在于以上介绍的各方面的权衡,大家可以就此研究研究,可以体会更多。另外,还有以struct实现的System.Guid。

5. 结论

对基本概念的把握,是我们进行技术深入探索的必经之路,本系列的主旨也是能够从基本框架中,提供给大家一个通向高级技术的必修课程。本文关于class和struct的讨论就是如此,在.NET框架中,关于class和struct的讨论将涉及到对引用类型和值类型的认识,并且进一步将触角伸向变量内存分配这一高级主题,所以我们有必要来了解其运行机制,把握区别和应用场合,以便在平常的系统设计中把握好对这一概念层次的把握。

另外,请大家就以下问题进行讨论,希望能够更加清晰本文的拓展:

  • struct还主要应用在哪些方面?
  •  C++和C#中,关于struct的应用又有所不同,这些不同又有哪些区别?

参考文献

(USA)Stanley B.Lippman, C# Primer

(USA)C# in a Nutshell

 温故知新

[开篇有益]

[第一回:恩怨情仇:is和as]

[第二回:对抽象编程:接口和抽象类

[第三回:历史纠葛:特性和属性]

©2007 Anytao.com

原创作品,转贴请注明作者和出处,留此信息。

本贴子以“现状”提供且没有任何担保,同时也没有授予任何权利。
This posting is provided "AS IS" with no warranties, and confers no rights.

posted @ 2007-04-24 00:23 Anytao 阅读(8019) 评论(53)  编辑 收藏 所属分类: 01 [你必须知道的.NET]

  回复  引用    
#1楼 2007-04-24 00:31 | sopper [未注册用户]
学习了
不过说的这些还有特自己去验证
  回复  引用    
#2楼 2007-04-24 00:43 | snowdoggie [未注册用户]
图片的链接有问题
  回复  引用  查看    
#3楼 [楼主]2007-04-24 00:49 | Anytao      
@snowdoggie
谢谢提醒,马上查看
  回复  引用  查看    
#4楼 [楼主]2007-04-24 00:56 | Anytao      
@sopper
学习的过程正是不断探求的过程
  回复  引用  查看    
#5楼 [楼主]2007-04-24 01:00 | Anytao      
抱歉大家,因为代码粘贴总有问题,所以只好将部分做为图片处理,不知有什么解决办法没有,实在没法搞定。呵呵,希望dudu帮忙呀:-)
  回复  引用    
#6楼 2007-04-24 02:50 | ASP.NET2.0 [未注册用户]
特价:5999元主机租用,100M独享只要15000元/年
亚州最大机房多线ASP.NET2.0虚拟主机,SQL2005数据库,主机托管,主机租用,1元注册CN域名,买空间送域名,建网站送空间,
QQ:59716094
手机:13917868802
电话:021-64938114
MSN:ygxy2008@jx163.com
网址:http://www.newodong.com (新动互联)
  回复  引用  查看    
#7楼 2007-04-24 07:25 | 菌哥      
温故而知新,写得不错,支持一下!
  回复  引用  查看    
#8楼 2007-04-24 08:26 | Terry Sun      
很好的总结, 重新学习了一次, 期等后文
  回复  引用  查看    
#9楼 2007-04-24 09:12 | 玉开      
写的真好,期待下文
  回复  引用  查看    
#10楼 [楼主]2007-04-24 10:06 | Anytao      
@玉开
@Terry Sun
@菌哥

非常感谢大家的支持,我本人也在这个过程受益非浅,一定努力完成这个系列,感谢大家的关注:-)
  回复  引用  查看    
#11楼 2007-04-24 10:32 | Silent Void      
struct还主要应用在哪些方面?

C#的struct主要用在与非托管代码进行交互的情况。至于内存分配,我也曾写过两篇,分别介绍struct和class的Layout:
.net托管环境下struct实例字段的内存布局(Layout)和大小(Size)
http://www.cnblogs.com/happyhippy/archive/2007/04/12/710927.html

托管堆上对象的大小(Size)和Layout
http://www.cnblogs.com/happyhippy/archive/2007/04/17/717028.html



  回复  引用  查看    
#12楼 [楼主]2007-04-24 10:44 | Anytao      
@Silent Void
你太有才了,需要的就是这个,感谢那!
  回复  引用  查看    
#13楼 2007-04-24 14:52 | caipei      
关注此系列,期待下文……
  回复  引用  查看    
#14楼 2007-04-25 16:18 | 数据绑定者      
为了考mcpd,只好从基础从新看一遍,生怕漏掉知识点,看了楼主的文章,感觉更加清晰了类和结构的区别,致敬~
  回复  引用    
#15楼 2007-04-26 01:30 | readroom.cn [未注册用户]
谢谢你的文章,对我很有用..
  回复  引用  查看    
#16楼 [楼主]2007-04-26 09:36 | Anytao      
@caipei
@数据绑定者
@readroom.cn

非常感谢大家的关注,能带给大家方便我很满足,下一主题即将发布,敬请关注。
  回复  引用    
#17楼 2007-04-26 14:08 | qddxzl-302 [未注册用户]
的确好
  回复  引用  查看    
#18楼 [楼主]2007-04-29 11:03 | Anytao      
@qddxzl-302
支持就是信心,继续努力
  回复  引用  查看    
#19楼 2007-05-05 00:46 | 玩玩      
不错不错
会唱来看的
  回复  引用  查看    
#20楼 [楼主]2007-05-08 09:56 | Anytao      
@玩玩
谢谢了。
  回复  引用    
#21楼 2007-05-08 14:59 | coolyhtao [未注册用户]
很好的文章,继续关注后文,感谢楼主的无私。
  回复  引用  查看    
#22楼 [楼主]2007-05-24 22:33 | Anytao      
@coolyhtao
同样感谢你的关注。
  回复  引用  查看    
#23楼 2007-06-03 19:11 | Jeffrey Zhao      
“而struct可以不使用new关键字”为什么这么说呢?

“而struct变量使用完后立即自动解除内存分配”其实这个应该加一个条件,就是说是分配在栈上,没有经过boxing的struct。:)
  回复  引用  查看    
#24楼 2007-09-19 12:44 | AK47      
好!
  回复  引用  查看    
#25楼 2007-09-22 08:21 | 镜涛      
结构体主要用于小的,以数据为主的,不会被频繁复制的对象吧?不知道这样说是不是准确。
  回复  引用  查看    
#26楼 2007-10-28 12:18 | flankerfc      
结构体的方法呢?也是存在堆栈中吗?
  回复  引用  查看    
#27楼 [楼主]2007-10-29 16:40 | Anytao      
@Jeffrey Zhao
谢谢老赵讨论,呵呵:-)

1. “而struct可以不使用new关键字”为什么这么说呢?
----------------------------------------------------
我的理解是,Struct和Class都可以以new来完成实例化,但是二者是有区别的,在文中已经有所交代。另外,如果不以new来实例化struct,则其所有的字段将处于未分配状态,直到所有字段完成初始化,否则引用未赋值的字段会导致编译错误。例如:

struct MyStruct
{
public int i;
public string name;
public void ShowMsg()
{
Console.WriteLine("Hello");
}
}


public static void Main()
{
MyStruct ms;
ms.i = 10;
ms.name = "Anytao.com";
Console.WriteLine(ms.i);
//必须完成所有字段的初始化,才能调用ShowMsg()
ms.ShowMsg();
}


2. “而struct变量使用完后立即自动解除内存分配”其实这个应该加一个条件,就是说是分配在栈上,没有经过boxing的struct。:)
------------------------------------------------------
的确,如此,应该排除这个特殊情况,其实装箱之后的struct已经不能算是值类型了,其内存受GC控制。

  回复  引用  查看    
#28楼 [楼主]2007-10-29 16:45 | Anytao      
@念时
:-)
  回复  引用  查看    
#29楼 [楼主]2007-10-29 16:45 | Anytao      
@Stanley.Luo
:-)
  回复  引用  查看    
#30楼 [楼主]2007-10-29 16:45 | Anytao      
@chinaifne
:-)
  回复  引用  查看    
#31楼 [楼主]2007-10-29 16:46 | Anytao      
@AK47
:-),谢谢,欢迎讨论。
  回复  引用  查看    
#32楼 [楼主]2007-10-29 17:02 | Anytao      
@镜涛
某种程度上,的确如此,struct实例分配在堆栈上,适于数据量较小的场合:-)
  回复  引用  查看    
#33楼 [楼主]2007-10-30 11:49 | Anytao      
@flankerfc
对于方法的加载与运行机制,我也在探索中。
对于方法来说,我觉得既不在栈也不在堆,所有的方法在内存中只有一份,而是在调用时由CLR缓冲执行,对于引用类型和值类型,不同的是在实例中保存了指向方法表的指针是保存在托管堆或者线程的堆栈上有所区别。代码执行调用方法时,CLR按照该指针表来动态加载内存中的方法地址。

老实说,对于以上理解,也是我的一己之言(不知对错:-),详细的加载和分配机制来花点时间再求论证,希望届时一起讨论,只是我的简单理解。
  回复  引用  查看    
#34楼 [楼主]2007-12-11 11:37 | Anytao      
@flankerfc
我在本文给出了答案,详细请参见:
http://www.cnblogs.com/anytao/archive/2007/12/07/must_net_19.html

  回复  引用  查看    
#35楼 2007-12-11 20:00 | flankerfc      
@Anytao

谢谢 我看看
  回复  引用    
#36楼 2007-12-28 04:59 | babii [未注册用户]
文章不错,呵呵 学习


struct在声明时就进行了初始化过程,所有的成员变量均默认为0或null。

这句好像不对,吧.初始化有2种,一种是调用new,二是对struct 变量的成员直接赋值.
  回复  引用  查看    
#37楼 [楼主]2007-12-29 10:00 | Anytao      
@babii
抱歉,犯了一个想当然的错误,已做修改。
  回复  引用  查看    
#38楼 2008-03-05 16:43 | 喜欢吹风的感觉      
我常用struct做方法的多个参数的传递,这时候用class似乎有点大材小用
  回复  引用  查看    
#39楼 [楼主]2008-03-05 17:02 | Anytao      
@喜欢吹风的感觉
正如文章所言,struct和class在很多方面有所重叠,其应用的重心也视实际的情况而定。struct轻量,而class灵活。
  回复  引用    
#40楼 2008-05-07 10:57 | voodoost [未注册用户]
运行结果是什么?
  回复  引用    
#41楼 2008-05-07 10:58 | voodoost [未注册用户]
有些东西懂了 遇到例子又摸不着头脑 希望能结合例子再做详细解释 甚是感谢
  回复  引用  查看    
#42楼 [楼主]2008-05-07 20:22 | Anytao      
@voodoost
关于运行结果,请按照代码进行测试,多尝试尝试对于理解更有好处:-)

关于那个部分不够清楚,望具体指明,也好进行沟通和举例:-)
  回复  引用    
#43楼 2008-09-09 21:02 | flysun0311 [未注册用户]
thank you very much.
  回复  引用  查看    
#44楼 [楼主]2008-09-09 22:07 | Anytao      
@flysun0311
:-)
you are welcome.

标题  
姓名  
主页
Email (博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交      
该文被作者在 2008-09-22 13:43 编辑过


相关链接: