参考资料

[1] 毛星云【《Effective C#》提炼总结】 https://zhuanlan.zhihu.com/p/24553860

[2] 《C# 捷径教程》

[3] 什么时候使用值类型?什么时候使用引用类型?

https://www.cnblogs.com/LittleFeiHu/p/4489099.html

[4] 深入理解Java内存 https://www.cnblogs.com/lipeineng/p/8358601.html

[5] 栈内存 https://baike.baidu.com/item/栈内存/8596201

基础知识

  1. 在C#中,用struct创建的是值类型,继承于System.ValueType,class创建的类是引用类型,继承于System.Object。

疑难解答

  1. 值类型和引用类型有什么区别?
  2. 这两种类型的内存分配情况?
  3. 值类型与Null的关系?
  4. 何时用值类型,何时使用引用类型?

值类型与引用类型的区别

  1. 值类型是封闭类型,无法继承任何类(但可以实现接口),而引用类型则可以实现多态

  2. 值类型在充当函数参数、赋值时,传递的是值类型的副本,而引用类型则是传递的是对象的指针。《C# 捷径编程》对这个的描述是下面这样的:

这意味着每个引用类型的变量事实上包括应该指向堆上的对象的引用(或者,如果当时还没有引用对象的话,就是null)。当复制一个引用类型变量的值到另一个引用类型变量时,就创建了另一个指向同一对象的引用。

  1. 引用类型默认值是null,而值类型的默认值是其所定义的默认值(如int、float的默认值是0)。

  2. 引用类型必须用new关键字新建,而值类型则不必须,但如果要调用值类型中的方法(如简单的get、set属性),那么必须使用new关键字生成值类型。

  3. 在内存中,值类型一般分配在线程栈上,不受GC(垃圾回收器)管理,当离开了该值类型的作用域后,会自动释放(参考局部变量)。而引用类型一般分配在托管堆上,由GC负责释放。

值类型复制说明

可以看到值类型的复制是完全复制一个副本给另一个变量,而引用类型则是将指向对象的指针赋给变量,所以引用类型的赋值,本质还是同一个对象。下面上一段代码进行说明。

struct Value {
public int a, b; public override string ToString() {
return string.Format("[a:{0},b:{1}]",a,b);
}
}
class ValueRefer{
public int a, b;
public override string ToString() {
return string.Format("[a:{0},b:{1}]", a, b);
}
} public class MainProgram {
public static void Main(string[] args) {
Value value1 = new Value();
Value value2 = value1;
ValueRefer valueRefer1 = new ValueRefer();
ValueRefer valueRefer2 = valueRefer1; value2.a = 10;
valueRefer2.a = 10; Console.WriteLine(string.Format("value1:{0}\nvalueRefer1:{1}\nvalue2:{2}\nvalueRefer2:{3}",value1,valueRefer1,value2,valueRefer2));
}
}

运行结果:

value1:[a:0,b:0]
valueRefer1:[a:10,b:0]
value2:[a:10,b:0]
valueRefer2:[a:10,b:0]

可以看到更改Value2的值不影响Value1,而更改ValueRefer2的值则会影响到ValueRefer1。

用一个交换的例子也能说明这个问题。请看如下代码,对值类型和引用类型的a、b属性进行一次交换。

struct Value {
private int b;
private int a; public int A { get => a; set => a = value; }
public int B { get => b; set => b = value; } public override string ToString() {
return string.Format("[a:{0},b:{1}]",A,B);
}
}
class ValueRefer{
private int b;
private int a; public int A { get => a; set => a = value; }
public int B { get => b; set => b = value; } public override string ToString() {
return string.Format("[a:{0},b:{1}]", A, B);
}
} public class MainProgram {
public static void Main(string[] args) {
Value value = new Value();
value.A = 5;
value.B = 10;
ValueRefer valueRefer = new ValueRefer();
valueRefer.A = 5;
valueRefer.B = 10; Console.WriteLine(string.Format("value:{0}\nvalueRefer:{1}", value, valueRefer)); // 交换值类型内属性a、b的值
Swap(value); // 交换引用类型内属性a、b的值
Swap(valueRefer); Console.WriteLine(string.Format("\nvalue:{0}\nvalueRefer:{1}",value,valueRefer));
} static void Swap(Value value) {
int temp = value.A;
value.A = value.B;
value.B = temp;
} static void Swap(ValueRefer value) {
int temp = value.A;
value.A = value.B;
value.B = temp;
}
}

运行结果如下:

value:[a:5,b:10]
valueRefer:[a:5,b:10] value:[a:5,b:10]
valueRefer:[a:10,b:5]

可以看到引用类型的属性被交换了,而值类型则没有受影响,这说明了传给函数的只是值类型的副本,而非其本体。

值类型和引用类型内存分配情况

首先,可以明确的是,值类型一般都分配在线程栈上(并不总是,有时也可作为字段嵌入到引用类型的对象中),而引用类型的内存则必须从托管堆分配。在有些情况下,值类型可以提供更好的性能,这是由于它的内存从栈上分配。对于栈内存,百度百科的解释如下:

栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量(,int, short, long, byte, float, double, boolean, char)和对象句柄。栈有一个很重要的特殊性,就是存在栈中的数据可以共享

值类型与Null的关系

首先值类型永远不能赋值为null,因为值类型的值就是它本身。而对于引用变量来说,它的值则是对一个对象的引用,故可以用null(空引用)对其赋值。

大佬这个讲的很好 https://www.cnblogs.com/murongxiaopifu/p/4842375.html 。。。本菜鸡实在不知道如何归纳总结了~

概括一下就是,在实际编程中,可能需要让值类型的变量的值既不是负数也不是0,而是真正不存在。在这种情况下,可以使用可空类型来对值类型的空值进行表示。

何时使用值类型何时使用引用类型

值类型有时可以提供更好的性能,而引用类型则是我们习惯用的。那么如何权衡一个类应该为哪个类型呢?

根据参考资料[1][3]两位大佬的说法,只有当一个类型满足以下所有条件,我们才考虑是否将该类型声明为值类型。

  1. 类型不需要从其他类型继承,也不派生出其他任何类型
  2. 该类型的主要职责在于数据存储吗?
  3. 该类型的公有接口都是由访问其数据成员的属性定义的吗?

在满足上述条件的情况下,还必须满足以下任意条件:

  1. 类型的实例较小
  2. 类型的实例较大且不作为方法参数传递

这是因为值类型在方法中充当方法参数传递时,是将值类型中所有字段进行复制的,这会对性能造成影响。

C#基础复习(1) 之 Struct与Class的区别的更多相关文章

  1. C语言基础复习总结

    C语言基础复习总结 大一学的C++,不过后来一直没用,大多还给老师了,最近看传智李明杰老师的ios课程的C语言入门部分,用了一周,每晚上看大概两小时左右,效果真是顶一学期的课,也许是因为有开发经验吧, ...

  2. C#基础复习(4) 之 浅析List、Dictionary

    参考资料 [1] .netCore 源码 https://github.com/dotnet/corefx [2] <Unity 3D脚本编程 使用C#语言开发跨平台游戏>陈嘉栋著 [3] ...

  3. 《CSS权威指南》基础复习+查漏补缺

    前几天被朋友问到几个CSS问题,讲道理么,接触CSS是从大一开始的,也算有3年半了,总是觉得自己对css算是熟悉的了.然而还是被几个问题弄的"一脸懵逼"... 然后又是刚入职新公司 ...

  4. Java基础复习笔记系列 九 网络编程

    Java基础复习笔记系列之 网络编程 学习资料参考: 1.http://www.icoolxue.com/ 2. 1.网络编程的基础概念. TCP/IP协议:Socket编程:IP地址. 中国和美国之 ...

  5. Java基础复习笔记系列 八 多线程编程

    Java基础复习笔记系列之 多线程编程 参考地址: http://blog.csdn.net/xuweilinjijis/article/details/8878649 今天的故事,让我们从上面这个图 ...

  6. Java基础复习笔记系列 七 IO操作

    Java基础复习笔记系列之 IO操作 我们说的出入,都是站在程序的角度来说的.FileInputStream是读入数据.?????? 1.流是什么东西? 这章的理解的关键是:形象思维.一个管道插入了一 ...

  7. Java基础复习笔记系列 五 常用类

    Java基础复习笔记系列之 常用类 1.String类介绍. 首先看类所属的包:java.lang.String类. 再看它的构造方法: 2. String s1 = “hello”: String ...

  8. Java基础复习笔记系列 四 数组

    Java基础复习笔记系列之 数组 1.数组初步介绍? Java中的数组是引用类型,不可以直接分配在栈上.不同于C(在Java中,除了基础数据类型外,所有的类型都是引用类型.) Java中的数组在申明时 ...

  9. JS基础 复习: Javascript的书写位置

    爱创课堂JS基础 复习: Javascript的书写位置复习 js书写位置:body标签的最底部.实际工作中使用书写在head标签内一对script标签里.alert()弹出框.console.log ...

  10. MySQL学习笔记_8_SQL语言基础复习

    SQL语言基础复习 一.概述 SQL语句注释方式 1)以"#"开头直到行尾的所有内容都是注释 2)以"--"(--后还有一个空格)开头直到行尾的所有内容都是注释 ...

随机推荐

  1. Distributing Ballot Boxes

    Distributing Ballot Boxes http://acm.hdu.edu.cn/showproblem.php?pid=4190 Time Limit: 20000/10000 MS ...

  2. SpringMVC工作原理1(基础机制)

    图1.基本原理图 Spring工作流程描述       1. 用户向服务器发送请求,请求被Spring 前端控制Servelt DispatcherServlet捕获:       2. Dispat ...

  3. php5.4 trait 理解与学习

    Trait 是 php5.4引入的新特性,手册上说的一大段没看懂,这里直接来过来. Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制.Trait 为了减少单继承语言的限制,使开发人员 ...

  4. php在线支付流程

    1.企业与银行的两种接入方式: (1).企业直接与银行对接. 优点:直接与银行进行财务结算,资金安全,适合资金流较大企业.         缺点:开发和维护工作量较大,分别与每家银行签订合同,每年需交 ...

  5. SVN版本冲突中 Files 的值“ < < < < < < < .mine”无效路径中具有非法字符的解决办法

    .NET 中 SVN版本冲突中 Files 的值“ < < < < < < < .mine”无效路径中具有非法字符的解决办法: 一. 1.将项目逐个进行编译, ...

  6. 复利计算器4.0JUnit

    #因为是用IDEA首次写unit test,所以也是麻烦多多,于是就只写了一个函数的测试.... ##需要测试的代码如下 public class Calculator { // 本金为100万,利率 ...

  7. 05 Maven 生命周期和插件

    Maven 生命周期和插件 除了坐标.依赖以及仓库之外, Maven 另外两个核心概念是生命周期和插件.在有关 Maven 的日常使用中,命令行的输入往往就对应了生命周期,如 mvn package ...

  8. 构造函数constructor 与析构函数destructor(二)

    (1)转换构造函数 转换构造函数的定义:转换构造函数就是把普通的内置类型转换成类类型的构造函数,这种构造函数只有一个参数.只含有一个参数的构造函数,可以作为两种构造函数,一种是普通构造函数用于初始化对 ...

  9. jquery判断显示的元素并获取显示元素数据

    // 获取显示元素的数据 jQuery(this).find("a:visible").attr("href"); // 多级标签选择器 jQuery(&quo ...

  10. 2018.06.29 NOIP模拟 边的处理(分治+dp)

    边的处理(side.cpp) [问题描述] 有一个 n 个点的无向图,给出 m 条边,每条边的信息形如<x,y,c,r><x,y,c,r><x,y,c,r>. 给出 ...