【C#学习笔记】值类型(2)

在这里插入图片描述

文章目录

  • Struct结构体类型
    • 为什么不推荐struct
  • 元组类型
  • 可为空的值类型
    • 从可为空的值类型转换为基础类型
    • 提升的运算符
    • 如何确定可为空的值类型
    • 为什么建议少用`T?`
      • 装箱和取消装箱


Struct结构体类型

结构类型(“structure type”或“struct type”)是一种可封装数据和相关功能的值类型 。 使用 struct 关键字定义结构类型:

public struct Coords
{public Coords(double x, double y){X = x;Y = y;}public double X { get; }public double Y { get; }public override string ToString() => $"({X}, {Y})";
}

结构类型具有值语义 。 也就是说,结构类型的变量包含类型的实例。 默认情况下,在分配中,通过将参数传递给方法并返回方法结果来复制变量值。 对于结构类型变量,将复制该类型的实例。

使用readonly关键字来保证结构体状态不可变。以此保证结构体内的成员不会修改结构体本身状态。正是由于它是值类型的,因此有可能会被修改,而我们又不希望它被修改。

为什么不推荐struct

这里也要点出为什么class往往优于struct,因为结构体是值类型的,一方面,结构体的赋值是通过复制整个结构体的值来实现的。这意味着当结构体的值较大时,赋值操作需要复制较多的数据,可能会消耗大量的内存和时间。

另一方面,结构体在作为参数传递给方法时,会进行值传递。这意味着传递的是结构体的一个副本,而不是原始的结构体实例。这会导致在方法内对结构体的修改不会影响到原始实例。

相比之下,使用类作为引用类型可以避免上述问题。类对象的赋值和传递只涉及引用的复制,而不是整个对象的复制。这样可以避免不必要的内存和时间消耗。而且类对象的传递是引用传递,这意味着方法内对对象的修改会影响到原始实例。

而一切的缺陷,本质根源于结构体是一个值类型,而class是引用类型。


元组类型

元组功能提供了简洁的语法来将多个数据元素分组成一个轻型数据结构。 下面的示例演示了如何声明元组变量、对它进行初始化并访问其数据成员:

(double, int) t1 = (4.5, 3);
Console.WriteLine($"Tuple with elements {t1.Item1} and {t1.Item2}.");
// Output:
// Tuple with elements 4.5 and 3.(double Sum, int Count) t2 = (4.5, 3);
Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}.");
// Output:
// Sum of 3 elements is 4.5.

若要定义元组类型,需要指定其所有数据成员的类型,或者,可以指定字段名称。 虽然不能在元组类型中定义方法,但可以使用 .NET 提供的方法,如下面的示例所示:

(double, int) t = (4.5, 3);
Console.WriteLine(t.ToString());
Console.WriteLine($"Hash code of {t} is {t.GetHashCode()}.");
// Output:
// (4.5, 3)
// Hash code of (4.5, 3) is 718460086.

使用元组类型的情况通常用于接受函数多返回值。如果想要一个可变动的,带有方法的数据结构,类还是优于元组的。


可为空的值类型

在值类型的变量中,大部分值是不允许为空的,因此我们可以使用Nullable<T>T?定义可为空的值类型。但基础值类型 T 本身不能是可为空的值类型。

需要表示基础值类型的未定义值时,通常使用可为空的值类型。 例如,布尔值或 bool 变量只能为 true 或 false。 但是,在某些应用程序中,变量值可能未定义或缺失。 例如,某个数据库字段可能包含 true 或 false,或者它可能不包含任何值,即 NULL。 在这种情况下,可以使用 bool? 类型。

也就是说,当我们需要一个不可为空的值,而实际情况下可能会出现为空值的情况,我们就需要用到T?

由于值类型可隐式转换为相应的可为空的值类型,因此可以像向其基础值类型赋值一样,向可为空值类型的变量赋值。 还可分配 null 值。 例如:

double? pi = 3.14;
char? letter = 'a';int m2 = 10;
int? m = m2;bool? flag = null;// An array of a nullable value type:
int?[] arr = new int?[10];

可为空值类型的默认值表示 null,也就是说,它是其 Nullable<T>.HasValue 属性返回 false 的实例。

通常判断可为空值内是否为空有三种做法:

int? a = 42;
if (a is int valueOfA) // valueOfA代表A的ASCII码对应值
{
}
if (a is null)
{
}
或者
if (a.HasValue)
{
}
或者
if (a != null)
{
}

从可为空的值类型转换为基础类型

如果要将可为空值类型的值分配给不可以为 null 的值类型变量,则可能需要指定要分配的替代 null 的值。

int? a = 28;
-- 使用??操作符,使用方法是a = x ?? y 或x ??= y
-- a = x??y当x为空,则a=y ,x非空则a= x
-- x??= y当x为空则x=y,非空则不处理
int b = a ?? -1;
Console.WriteLine($"b is {b}");  // output: b is 28int? c = null;
int d = c ?? -1;
Console.WriteLine($"d is {d}");  // output: d is -1

注意,实际上TT?不是同一种值类型,所以同为值类型如果使用强制转换是可以的,但是如果把一个空值转换给一个非空类型是会报错的:

int? n = null;//int m1 = n;    // Doesn't compile
int n2 = (int)n; // Compiles, but throws an exception if n is null

提升的运算符

任何T类型本身所支持的运算符,如果在运算时带有了T?类型,那么运算也是可以正常运行的。这些运算符将被提升,而运算结果将变为可为空值,但是类型还是需要符合T运算时的类型转换(例如int+float=浮点型,所以int?+folat?=浮点型?)。

int? a = 10;
float? b = null;
double? c = 0;c = a + b;  // a is null
print(c); --Null

而bool值的计算稍微特殊,总体上也是符合bool运算法则的(我在lua学习笔记中总结了Lua入门):

bool? a = true;
bool? b = null;
bool? c = true;
c = a & b;
Debug.Log(c); --null
c = a | b;
Debug.Log(c); --true

对于比较运算符<、>、<= 和 >=,如果一个或全部两个操作数都为 null,则结果为 false;否则,将比较操作数的包含值。而带有null值时唯一可以进行比较运算的只有==!=

int? a = 10;
Console.WriteLine($"{a} >= null is {a >= null}");
Console.WriteLine($"{a} < null is {a < null}");
Console.WriteLine($"{a} == null is {a == null}");
// Output:
// 10 >= null is False
// 10 < null is False
// 10 == null is Falseint? b = null;
int? c = null;
Console.WriteLine($"null >= null is {b >= c}");
Console.WriteLine($"null == null is {b == c}");
// Output:
// null >= null is False
// null == null is True

如何确定可为空的值类型

IsNullable(typeof(T?))

Console.WriteLine($"int? is {(IsNullable(typeof(int?)) ? "nullable" : "non nullable")} value type");
Console.WriteLine($"int is {(IsNullable(typeof(int)) ? "nullable" : "non-nullable")} value type");bool IsNullable(Type type) => Nullable.GetUnderlyingType(type) != null;// Output:
// int? is nullable value type
// int is non-nullable value type

在获取可为空的值类型的时候,注意只能使用typeof()不能使用GetType(),后者只能返回基类的类型:

int? a = 17;
Type typeOfA = a.GetType();
Console.WriteLine(typeOfA.FullName);
// Output:
// System.Int32

此外,is关键字无法判断 TT?,默认它们是同类型

int? a = 42;
if (a is int valueOfA)
{print(a); --结果打印 42
}

为什么建议少用T?

T?虽然可以避免值类型接受空值,但是我们应该尽量避免使用T?,这是因为这个类型实际上是对T类型的装箱和拆箱。当我们声明这个变量的时候,它会被编译器装箱为T?,而当我们操作T?的时候编译器又会对它拆箱,实际上它像是一个拥有T和另一个变量Null的类。为了避免装箱拆箱操作对内存的影响,能不用尽量不用。

装箱和取消装箱

由于 T?已装箱,因此如果我们再对其装箱则会产生以下的情况判断:

  • 如果 HasValue 返回 false,则生成空引用。
  • 如果 HasValue 返回 true,则基础值类型 T 的对应值将装箱,而不对 Nullable<T> 的实例进行装箱。(也就是重新对T类型的对应值装箱一次)

可将值类型 T 的已装箱值取消装箱到相应的可为空值类型 T?,如以下示例所示:

int a = 41;
object aBoxed = a; 
int? aNullable = (int?)aBoxed; -- 把已装箱的a取消装箱并重新装箱为int?
Console.WriteLine($"Value of aNullable: {aNullable}");object aNullableBoxed = aNullable;   -- HasValue=true,则基础类型int将重新被装箱
if (aNullableBoxed is int valueOfA)
{Console.WriteLine($"aNullableBoxed is boxed int: {valueOfA}");
}int? b = null;
object aNullableBoxed = b;   -- HasValue=false,则生成空引用
if (aNullableBoxed == null)
{Console.WriteLine($"aNullableBoxed is boxed int: {valueOfA}");
}
// Output:
// Value of aNullable: 41
// aNullableBoxed is boxed int: 41
// aNullableBoxed is boxed int: 41

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

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

相关文章

React Native从文本内容尾部截取显示省略号

<Textstyle{styles.mMeNickname}ellipsizeMode"tail"numberOfLines{1}>{userInfo.nickname}</Text> 参考链接&#xff1a; https://www.reactnative.cn/docs/text#ellipsizemode https://chat.xutongbao.top/

软考高级架构师——2、操作系统

一、进程管理 • 进程的状态&#xff08;★&#xff09; • 进程的同步与互斥&#xff08;★★★★&#xff09; 临界资源&#xff1a;诸进程间需要互斥方式对其进行共享的资源&#xff0c;如打印机、磁带机等 临界区&#xff1a;每个进程中访问临界资源的那段代码称为临界区…

致远A8+数据库账密信息泄露

声明 本文仅用于技术交流&#xff0c;请勿用于非法用途 由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;文章作者不为此承担任何责任。 文章作者拥有对此文章的修改和解释权。如欲转载或传播此文章&#xff0c…

遮挡边界处的深度补全和双曲面外推

论文地址&#xff1a;Depth Completion with Twin Surface Extrapolation at Occlusion Boundaries 论文代码&#xff1a;https://github.com/imransai/TWISE 深度补全是从稀疏的已知深度值开始&#xff0c;为其余图像像素估计未知深度。 大多数方法将其建模为深度插值&#x…

【云原生】k8s组件架构介绍与K8s最新版部署

个人主页&#xff1a;征服bug-CSDN博客 kubernetes专栏&#xff1a;kubernetes_征服bug的博客-CSDN博客 目录 1 集群组件 1.1 控制平面组件&#xff08;Control Plane Components&#xff09; 1.2 Node 组件 1.3 插件 (Addons) 2 集群架构详细 3 集群搭建[重点] 3.1 mi…

【暑期每日一练】 day15

目录 选择题&#xff08;1&#xff09;解析&#xff1a; &#xff08;2&#xff09;解析&#xff1a; &#xff08;3&#xff09;解析&#xff1a; &#xff08;4&#xff09;解析&#xff1a; &#xff08;5&#xff09;解析&#xff1a; 编程题题一描述输入描述输出描述 示例…

406 · 和大于S的最小子数组

链接&#xff1a;LintCode 炼码 - ChatGPT&#xff01;更高效的学习体验&#xff01; 题解&#xff1a;同向双指针 九章算法 - 帮助更多程序员找到好工作&#xff0c;硅谷顶尖IT企业工程师实时在线授课为你传授面试技巧 class Solution { public:/*** param nums: an array …

目标检测中 anchor base和anchor free

目标检测中两种不同anchor的生成 趋势&#xff1a;anchor free越来越受到实时性检测的青睐&#xff0c;&#xff0c;&#xff0c;

HTML5 Canvas(画布)

<canvas>标签定义图形&#xff0c;比如图表和其他图像&#xff0c;你必须用脚本来绘制图形。 在画布上&#xff08; Canvas &#xff09;画一个共红色矩形&#xff0c;渐变矩形&#xff0c;彩色矩形&#xff0c;和一些彩色文字。 什么是 Canvas&#xff1f; HTML5<c…

机器学习笔记之优化算法(十)梯度下降法铺垫:总体介绍

机器学习笔记之优化算法——梯度下降法铺垫&#xff1a;总体介绍 引言回顾&#xff1a;线搜索方法线搜索方法的方向 P k \mathcal P_k Pk​线搜索方法的步长 α k \alpha_k αk​ 梯度下降方法整体介绍 引言 从本节开始&#xff0c;将介绍梯度下降法 ( Gradient Descent,GD ) …

SpringCloud入门Day01-服务注册与发现、服务通信、负载均衡与算法

SpringCloudNetflix入门 一、应用架构的演变 伴随互联网的发展&#xff0c;使用互联网的人群越来越多&#xff0c;软件应用的体量越来越大和复杂。而传统单体应用 可能不足以支撑大数据量以及发哦并发场景应用的框架也随之进行演变从最开始的单体应用架构到分布式&#xff08…

openpnp - 8mm物料编带的厚度

文章目录 openpnp - 8mm物料编带的厚度概述笔记END openpnp - 8mm物料编带的厚度 概述 做了一个散料飞达, 回来后试了一下. 并不是所有8mm编带都能顺利插入散料飞达. 原来, 不同物料的8mm编带厚度是不一样的. 那就量一下. 笔记 参考电阻的厂家说明书(e.g. C2907561_贴片电阻…