C#中struct和class的区别详解

本文详细分析了C#中struct和class的区别,对于C#初学者来说是有必要加以了解并掌握的。

简单来说,struct是值类型,创建一个struct类型的实例被分配在栈上。class是引用类型,创建一个class类型实例被分配在托管堆上。但struct和class的区别远不止这么简单。

概括来讲,struct和class的不同体现在:

● 类是引用类型,struct是值类型
● 在托管堆上创建类的实例,在栈上创建struct实例
● 类实例的赋值,赋的是引用地址,struct实例的赋值,赋的是值
● 类作为参数类型传递,传递的是引用地址,struct作为参数类型传递,传递的是值
● 类没有默认无参构造函数,struct有默认无参构造函数
● 类支持继承,struct不支持继承
● 类偏向于"面向对象",用于复杂、大型数据,struct偏向于"简单值",比如小于16字节,结构简单
● 类的成员很容易赋初值,很难给struct类型成员赋初值
● 类的实例只能通过new SomeClass()来创建,struct类型的实例既可以通过new SomeStruct()来创建,也可以通过SomeStruct myStruct;来创建

一、从赋值的角度体验struct和class的不同

引用类型赋值,是把地址赋值给了变量

class Program{static void Main(string[] args){SizeClass sizeClass = new SizeClass(){Width = 10, Length = 10};Console.WriteLine("赋值前:width={0},length={1}", sizeClass.Width, sizeClass.Length);var copyOfSizeClass = sizeClass;copyOfSizeClass.Length = 5;copyOfSizeClass.Width = 5;Console.WriteLine("赋值后:width={0},length={1}",sizeClass.Width, sizeClass.Length);Console.ReadKey();}}public class SizeClass{public int Width { get; set; }public int Length { get; set; }}public struct SizeStruct{public int Width { get; set; }public int Length { get; set; }}

运行结果如下图所示:

 

以上,当把sizeClass赋值给copyOfSize变量的时候,是把sizeClass所指向的地址赋值给了copyOfSize变量,2个变量同时指向同一个地址。所以,当改变copyOfSizeClass变量的值,也相当于改变了sizeClass的值。

struct类型赋值,是完全拷贝,在栈上多了一个完全一样的变量

class Program{static void Main(string[] args){SizeStruct sizeStruct = new SizeStruct(){Length = 10, Width = 10};Console.WriteLine("赋值前:width={0},length={1}", sizeStruct.Width, sizeStruct.Length);var copyOfSizeStruct = sizeStruct;copyOfSizeStruct.Length = 5;copyOfSizeStruct.Width = 5;Console.WriteLine("赋值后:width={0},length={1}", sizeStruct.Width, sizeStruct.Length);Console.ReadKey();}}

程序运行结果如下图所示:

 

以上,当把sizeStruct赋值给copyOfSizeStruct变量的时候,是完全拷贝,改变copyOfSizeStruct的值不会影响到sizeStruct。

二、从参数传值角度体验struct和class的不同

引用类型参数传递的是地址

class Program{static void Main(string[] args){List<string> temp = new List<string>(){"my","god"};temp.ForEach(t => Console.Write(t + " "));Console.ReadKey();}public static void ChangeReferenceType(List<string> list){list = new List<string>(){"hello", "world"};}}

运行结果:my god

为什么不是hello world?
→栈上的temp指向托管堆上的一个集合实例
→当temp放到ChangeReferenceType(temp)方法中,本质是把temp指向的地址赋值给了变量list
→在ChangeReferenceType(List<string> list)方法内部,又把变量list的指向了另外一个集合实例地址
→但temp的指向地址一直没有改变

我们再来改变ChangeReferenceType(List<string> list)内部实现方式,其它不变。

class Program{static void Main(string[] args){List<string> temp = new List<string>(){"my","god"};ChangeReferenceType(temp);temp.ForEach(t => Console.Write(t + " "));Console.ReadKey();}public static void ChangeReferenceType(List<string> list){list.Clear();list.Add("hello");list.Add("world");}}

运行结果:hello world

为什么不是my god?  
→栈上的temp指向托管堆上的一个集合实例
→当temp放到ChangeReferenceType(temp)方法中,本质是把temp指向的地址赋值给了变量list
→在ChangeReferenceType(List<string> list)方法内部,把temp和list共同指向的实例清空,又添加"hello"和"world"2个元素
→由于list和temp指向的实例是一样的,所以改变list指向的实例就等同于改变temp指向的实例

以上,很好地说明了:引用类型参数传递的是地址。

值类型struct参数传递的是值

class Program{static void Main(string[] args){Size s = new Size(){Length = 10, Width = 10};ChangeStructType(s);Console.Write("Length={0},Width={1}", s.Length,s.Width);Console.ReadKey();}public static void ChangeStructType(Size size){size.Length = 0;size.Width = 0;}}public struct Size{public int Length { get; set; }public int Width { get; set; }}

运行结果如下图所示:

 

为什么Length和Width不是0呢?
→在栈上变量size
→当通过ChangeStructType(size),把s变量赋值给ChangeStructType(Size size)中的size变量,其本质是在栈上又创建了一个变量size,size的值和s是完全一样的
→在ChangeStructType(Size size)内部改变size的值,与变量s毫无关系

三、从struct类型的struct类型属性和struct引用类型属性体验struct和class的不同

假设有一个struct,它有struct类型的属性

以下, struct类型Room有struct类型的属性TableSize和TvSize,我们如何通过Room实例来修改其struct类型的属性值呢?

class Program{static void Main(string[] args){Room r = new Room(){TableSize = new Size(){Length = 100, Width = 80},TvSize = new Size(){Length = 10, Width = 8}};r.TableSize.Length = 0;Console.WriteLine("table目前的尺寸是:length={0},width={1}", r.TableSize.Length, r.TableSize.Width);Console.ReadKey();}}public struct Size{public int Length { get; set; }public int Width { get; set; }}public struct Room{public Size TableSize { get; set; }public Size TvSize { get; set; }}

以上,r.TableSize.Length = 0;此处会报错:不能修改r.TableSize的值,因为不是变量。的确,r.TableSize只是Size的一份拷贝,而且也没有赋值给其它变量,所以r.TableSize是临时的,会被自动回收,对其赋值也是没有意义的。

如果要修改r.TableSize,只需把

r.TableSize.Length = 0;

改成如下:

r.TableSize = new Size(){Length = 0, Width = 0};

运行结果如下图所示:

 

可见,改变struct类型的struct类型属性的某个属性是行不通的,因为像以上r.TableSize只是一份拷贝,是临时的,会被自动回收的。要改变struct类型的struct类型属性,就需要像上面一样,给r.TableSize赋上一个完整的Size实例。

假设有一个struct,它有引用类型的属性呢?

以下,struct类型的Room有引用类型属性,TableSize和TvSize,如何通过Room实例来修改其引用类型的属性值呢?并且,我们在类Size中定义了一个事件,当给Size的属性赋值时就触发事件,提示size类的属性值发生了改变。

class Program{static void Main(string[] args){var oneSize = new Size() {Length = 10, Width = 10};var twoSize = oneSize;oneSize.Changed += (s, e) => Console.Write("Size发生了改变~~");oneSize.Length = 0;Console.ReadKey();}}public class Size{private int _length;private int _width;public event System.EventHandler Changed;public int Length{get { return _length; }set{_length = value;OnChanged();}}public int Width{get { return _width; }set { _width = value; OnChanged(); }}private void OnChanged(){if (Changed != null){Changed(this, new EventArgs());}}}public struct Room{public Size TableSize { get; set; }public Size TvSize { get; set; }}

运行,显示:Size发生了改变~~

对oneSize.Length的修改,实际上修改的是oneSize.Length指向托管堆上的实例。

四、从构造函数体验struct和class的不同

struct类型包含隐式的默认无参构造函数

class Program{static void Main(string[] args){var size = new SizeStruct();Console.WriteLine("length={0},width={1}", size.Length, size.Width);Console.ReadKey();}}public struct SizeStruct{public int Length { get; set; }public int Width { get; set; }}

运行结果如下图所示:

 

为什么我们没有给SizeStruct定义无参构造函数,而没有报错?
--因为,struct类型有一个隐式的无参构造函数,并且给所有的成员赋上默认值,int类型属性成员的默认值是0。

类不包含隐式无参构造函数

class Program{static void Main(string[] args){var size = new SizeClass();Console.WriteLine("length={0},width={1}", size.Length, size.Width);Console.ReadKey();}}public class SizeClass{public int Length { get; set; }public int Width { get; set; }public SizeClass(int length, int width){Length = length;Width = Width;}}

运行,报错:SizeClass不包含0个参数的构造函数

五、从给类型成员赋初值体验struct和class的不同

如果直接给字段赋初值。

public struct SizeStruct{public int _length = 10;}

运行,报错:结构中不能有实例字段初始值设定项

如果通过构造函数给字段赋初值。

public struct SizeStruct{public int _length;public SizeStruct(){_length = 10;}}

运行,报错:结构中不能包含显式无参数构造函数

可见,给struct类型成员赋初值是不太容易的,而给class成员赋初值,no problem。

何时使用struct,何时使用class?

在多数情况下,推荐使用class类,因为无论是类的赋值、作为参数类型传递,还是返回类的实例,实际拷贝的是托管堆上引用地址,也就大概4个字节,这非常有助于性能的提升。

而作为struct类型,无论是赋值,作为参数类型传递,还是返回struct类型实例,是完全拷贝,会占用栈上的空间。根据Microsoft's Value Type Recommendations,在如下情况下,推荐使用struct:

● 小于16个字节
● 偏向于值,是简单数据,而不是偏向于"面向对象"
● 希望值不可变

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/438865.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python面向对象编程的语言_怎么使用python面向对象编程

怎么使用python面向对象编程发布时间&#xff1a;2020-08-25 13:42:19来源&#xff1a;亿速云阅读&#xff1a;89作者&#xff1a;Leah本篇文章给大家分享的是有关怎么使用python面向对象编程&#xff0c;小编觉得挺实用的&#xff0c;因此分享给大家学习&#xff0c;希望大家阅…

软件测试计划时要记住什么

测试计划是软件测试生命周期的早期阶段之一。良好的计划是平稳执行阶段的关键&#xff0c;而糟糕的计划可能导致不必要的时间消耗。测试用例编写&#xff0c;资源分配等是作为测试计划的一部分执行的一些活动。 在规划过程中应注意并注意一些关键点&#xff0c;以避免将冗余问题…

ssm框架搭建连接mysql_从零开始搭建SSM框架(Spring + Spring MVC + Mybatis)

最近在回顾和总结一些技术&#xff0c;想到了把之前比较火的 SSM 框架重新搭建出来&#xff0c;作为一个小结&#xff0c;同时也希望本文章写出来能对大家有一些帮助和启发&#xff0c;因本人水平有限&#xff0c;难免可能会有一些不对之处&#xff0c;欢迎各位大神拍砖指教&am…

基于TFS实践敏捷-可视化管理

TFS是基于微软平台一套不错的系统&#xff0c;支持源码管理运行调试持续集成自动化测试Bug管理代码评审任务项管理文档管理沟通管理。 基于TFS 2015实践看板管理&#xff0c;让团队的数据可视化&#xff0c;让大家更多的关心产品、关注团队的努力&#xff0c;增强沟通、及时反映…

linux查看java版本_linux 查看java版本

inux查看java jdk安装路径和设置环境变量windows&#xff1a;set java_home:查看JDK安装路径java -version:查看JDK版本linux&#xff1a;whereis javawhich java (java执行路径)**echo $JAVA_HOME****echo $PATH**二&#xff1a;下面是配置linux的环境变量&#xff1a;(记得so…

[转载]基于TFS实践敏捷-修复Bug和执行代码评审

本主题阐释了这些功能&#xff0c;以继续这一关注虚拟敏捷团队成员的一天的教程。 Peter 忙于编写一些代码以完成积压工作 (backlog) 项任务。但是&#xff0c;他的同事发现了一个阻碍他们工作的 Bug&#xff0c;他想立即修复此 Bug。 他暂停了手中的工作并修复此 Bug。 他请求…

lzw压缩 java_java实现的LZW 压缩算法源码 | 学步园

导读&#xff1a;/** LZW.java** Created on 01 Dec 2005** Implementation of LZW compression/decompression algorithm*/import java.io.* ;/**** author Moshe Fresko* courseAlgorithmic Programming 1* exercise3*/public class LZW implements Compression{boolean stop…

单元测试入门

https://docs.microsoft.com/zh-cn/visualstudio/test/getting-started-with-unit-testing?viewvs-2019 使用 Visual Studio 定义和运行单元测试&#xff0c;使代码保持正常运行、确保代码覆盖率并在客户之前找到错误和缺陷。 经常运行单元测试&#xff0c;确保代码正常运行。…

java中乐观锁_Java中乐观锁与悲观锁的实现

锁(locking)业务逻辑的实现过程中&#xff0c;往往需要保证数据访问的排他性。如在金融系统的日终结算处理中&#xff0c;我们希望针对某个cut-off时间点的数据进行处理&#xff0c;而不希望在结算进行过程中(可能是几秒种&#xff0c;也可能是几个小时)&#xff0c;数据再发生…

.net开源框架简介和通用技术选型建议

.net体系 .net core .net 类库 asp.net mvc asp.net webapi asp.net core EF 跨平台和运行时解决方案&#xff08;解决方案&#xff09; Katana&#xff1a;微软基于OWIN规范实现的非IIS寄宿ASP.NET和MVC等。 MONO.NET&#xff1a;跨平台的.NET运行环境&#xff0c;让.NE…

JAVA 2048源码_java实现2048游戏源代码

本文实例为大家分享了java实现2048游戏源代码&#xff0c;供大家参考&#xff0c;具体内容如下一.主要功能&#xff1a;1、游戏初始化&#xff1a;新建游戏44的16宫格画布&#xff0c;随机格子上生成2或者4两个数字2、格子的移动&#xff1a;先判断能否移动&#xff0c;移动后判…

.Net开源的跨平台爬虫框架 DotnetSpider

项目详细介绍 DotnetSpider是开源的.NET跨平台数据采集爬虫框架。需要 Scheduler&#xff0c;Downloader &#xff0c;Processor&#xff0c;Pipeline 四部分。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39…

开源干货!.NET Core + Vue.js通用动态权限(RBAC)管理系统框架[DncZeus]开源

DncZeus 前言 关于 DncZeus DncZeus Dnc Zeus "Dnc"--.Net Core 的缩写&#xff1b; "Zeus"--中文译为宙斯&#xff0c;是古希腊神话中的众神之王&#xff0c;奥林匹斯十二主神之首&#xff0c;统治宇宙万物的至高无上的主神&#xff08;在古希腊神…

浏览器使用java_java如何调用本地的浏览器

1.调用本地的浏览器 import java.io.IOException;import java.net.MalformedURLException;import java.net.URL;public class UseBrowser {public static void main(String[] args) {URL url null;try {url new URL("http://www.baidu.com");} catch (MalformedURL…

java input函数怎么用_Java函数习惯用法详解

在Java编程中&#xff0c;有些知识 并不能仅通过语言规范或者标准API文档就能学到的。在本文中&#xff0c;我会尽量收集一些最常用的习惯用法&#xff0c;特别是很难猜到的用法。我把本文的所有代码都放在公共场所里。你可以根据自己的喜好去复制和修改任意的代码片段&#xf…

十大开源的.NET用户界面框架 让GUI设计不再犯难

选择一款合适的GUI框架是.NET开发中比较重要但又很棘手的问题&#xff0c;因为用户界面相当于一款应用的"门面"&#xff0c;直接面向用户。好的UI更能吸引用户&#xff0c;有时甚至成为决定一款应用成败的关键。下面小编整理出十大应用最广泛.NET开源用户界面框架&am…

mysql一对一级联_MySQL 表的一对一、一对多、多对多问题

将实体与实体的关系&#xff0c;反应到最终数据库表的设计上&#xff0c;将关系分为三种&#xff1a;一对一&#xff0c;一对多(多对一)和多对多&#xff0c;所有的关系都是表与表之间的关系;一对一一对一&#xff1a;一张表的一条记录只能与另外一条记录进行对应&#xff0c;反…

.Net开源框架列表

API 框架 NancyFx&#xff1a;轻量、用于构建 HTTP 基础服务的非正式&#xff08;low-ceremony&#xff09;框架&#xff0c;基于.Net 及 Mono 平台。官网ASP.NET WebAPI&#xff1a;快捷创建 HTTP 服务的框架&#xff0c;可以广泛用于多种不同的客户端&#xff0c;包括浏览器…

java的基础类型和字节大小_java的基础类型和字节大小

学习c语言的时候&#xff0c;这些个东西起码还是很明确的&#xff0c;因为上课的时候&#xff0c;老师经常问&#xff0c;现在时间久了&#xff0c;见的语言多了&#xff0c;也就有点乱了&#xff0c;也没怎么在意过。(武藏) 既然你诚心诚意的发问了&#xff0c;(小次郎) 我…

python flv转mp4_ffmpeg将多个flv文件合成为mp4(python版)

需求直播生成的flv片段需要做个归档&#xff0c;把指定的文件夹中的flv合并成一个mp4&#xff0c;简单的转码合并操作直接用命令行调用来实现。注意事项flv文件直接合并生成mp4的话只有第一个flv的内容才能播放&#xff0c;需要先转换成ts再合成mp4使用的第三方库ffmpy&#xf…