C# Notizen 2 理解C#类型

一、类型
C#是一种类型安全的静态语言。这要求创建任何变量时,都必须将其数据类型告知编译器;编译器将确保只能将兼容的数据类型存储到变量中。这有助于避免常见的编程错误,让应用程序更稳定、更安全。
类型分为三大类:

  • 值类型
  • 引用类型
  • 类型参数

ps:实际上还有第4种类型—指针,但核心C#语言不支持。指针包含数据在内存中的实际位置(地址);还可对指针执行算术运算,就像它们是数字一样。虽然指针功能强大,但要正确、安全地使用它们也很难。

为提供指针的灵活性(这也会带来危险),C#允许您编写不安全的代码,在这些代码中可创建和操作指针。使用不安全的代码和指针时,务必认识到垃圾收集器不会跟踪指针,您必须负责分配和释放内存。从某种意义上说,这类似于在C#程序中编写C语言代码。

除显式的不安全代码块外,C#不允许在其他地方使用指针,这可以完全避免一类常见的错误,让C#更安全得多。

简单地说,值类型是完全独立的,它“按值”复制。这意味着值类型变量包含其数据,不会因为处理一个变量而影响另一个变量。值类型又分为结构、枚举类型和可以为null的类型。

引用类型包含指向实际数据的引用,这意味着两个变量可能指向同一个对象,而操作其中一个变量将影响另一个变量。引用类型又分为类、数组、接口和委托。

ps:同一类型系统
虽然对类型进行了上述分类,但 C#有一个统一类型系统,使得可将任何非指针类型值视为对象。这让值类型获得了引用类型的优点,而不会增加不必要的开销;并可对任何值(包括预定义值类型)调用对象方法。

二、预定义类型
2.1 预定义类型
C#预定义了一组类型,这些类型对应于通用类型系统中的类型。

Paste_Image.png
Paste_Image.png

通过包括表示布尔值的类型(取值为true或false),可避免混淆布尔变量和整数变量。这有助于消除多种常见的编程错误,让编写含义不言自明的代码更容易。

ps:在C语言中,布尔值表示为整数,并让程序员去决定0表示true还是false。通常,C语言程序定义表示整数值0和1的名称常量,以帮助消除这种模糊性,但并没有禁止使用整数表示布尔值。

  • 类型decimal最少包含28个有效位,旨在避免金融计算的误差。double类型主要用于物理计算,以减少表示误差。
  • object是其他所有引用类型和值类型的基类;string用于表示一系列Unicode字符,这种变量赋值后就不能修改,因此string变量是不可修改的。
  • 除无符号整数类型和sbyte外,其他所有预定义类型都符合CLS,但是使用这些
  • 类型时,只要不将其声明为公有的,您的代码就符合CLS。如果这些类型的变量必须是公有的,那么可转而使用相应的符合CLS的类型。
  • 对于sbyte,可使用符合CLS的类型short代替。
  • 对于uint,通常使用符合CLS的类型long代替;如果存储的值小于2147483647.5,那么也可使用int代替。
  • 对于 ulong ,通常可使用符合 CLS 的类型 decimal 代替,如果存储的值小于9223372036854775807.5,那么也可使用long代替
  • 对于ushort,通常可使用符合CLS的类型int代替,如果存储的值小于32767.5,那么也可使用short代替。
  • System.Object:所有的值类型以及类、数组和委托等引用类型都是从 object 派生而来的。接口类型可能是从其他接口类型派生而来的,但可转换为object。
  • 类型参数实际上不是从任何类型派生而来的,但可转换为object。
  • 不安全的指针类型既不是从object派生而来的,也不能转换为object,因为它们不受常规C#类型规则的管辖。
  • 这一切意味着C#中的所有非指针类型都可转换为object,但可能并不是从object派生而来的。
  • C#还有一些特殊的类型,其中最常见的是void。void表示不知道类型。dynamic类型类似于 object,主要的不同之处在于,对这种类型执行的所有操作都将在运行阶段(而不是编译阶段)解析。
  • 虽然void和dynamic都是类型,但var是隐式类型,让编译器根据赋给变量的数据确定变量的类型。

ps:var并非Variant的缩写
最初引入 var类型时,很多人认为它相当于Visual Basic中的Variant类型。Variant 变量可用于存储其他任何数据类型的值,因此不属于强类型;而var类型仍是强类型,因为在编译阶段将用特定数据类型替换它。尽管如此,过度使用var可能降低代码的可读性,因此应慎用。

2.2 其他的常用类型
除标准的预定义类型外,.NET Framework还提供了用于表示其他常用值的类型。不同于预定义类型,这些类型没有C#别名,但对其可执行的操作不受影响。

2.2.1 日期和时间
要处理日期和时间值,可使用结构DateTime,它能够创建表示日期和时间、仅日期或仅时间的变量。新建DateTime变量时,最常见的方法有两种:使用各种重载的构造函数之一;使用4个静态的分析方法之一—Parse、ParseExact、TryParse或TryParseExact。

DateTime的常用属性

Paste_Image.png
Paste_Image.png

对日期或时间值进行加减运算时,可使用相应的实例方法,它们返回一个新的DateTime值,而不是修改原来的值。

DateTime常用的算术运算方法:

Paste_Image.png

还可使用减法运算符将两个DateTime值相减,结果为一个TimeSpan实例。TimeSpan实例表示时间间隔,以天数、小时数、分钟数、秒数和毫秒数表示,可正可负。为确保一致性,时间间隔以天数为单位。还可将DateTime与TimeSpan相加或相减,结果为一个新的DateTime实例。

TimeSpan常用的方法和属性

Paste_Image.png
Paste_Image.png

2.2.2 全局唯一标识符(GUID)
GUID是一个128位的整数值,重复的可能性很小,每当需要唯一标识符时就可以使用它。结构System.Guid、能够创建和比较GUID值
常用成员:

Paste_Image.png

2.2.3 统一资源标识符(URI)
URI是内联网或Internet上可用资源的简洁表示,可以是绝对URI(如网页地址),也可以是相对URI,后者必须根据基本URI进行扩展。
Uri类让您能够新建URI以及访问URI的成员,它还提供了处理URI所需的方法,如分析、比较和合并。
常用成员:

Paste_Image.png

Uri实例是不能修改的。要创建可修改的URI,可使用UriBuilder。UriBuilder类让您能够轻松地修改URI的属性,而无需每次修改时都新建实例。
Uri和UriBuilder都有的属性(在Uri中,这些属性是只读的):

Paste_Image.png

2.2.4 BigInteger
类型 System.Numerics.BigInteger 表示任意大的整数值,从理论上说,没有上限和下限。创建BigInteger实例后,可以像使用其他整数类型一样使用,可对其进行基本的数学运算和比较。
结构BigInteger包含其他整数类型的方法和Math类的方法,还有专门针对BigInteger的成员。
这个结构的常用成员:

Paste_Image.png
Paste_Image.png

2.3 运算符
C#支持大量的运算符,但是这里只介绍比较常用的运算符。运算符是一种特殊符号,用于在表达式中指出要执行哪种运算。所有C#预定义类型都支持运算符,但是并非所有类型支持的运算符都相同。

按优先级顺序列出了所有的C#运算符。在每个类别中,运算符的优先级顺序相同。

Paste_Image.png

2.3.1 算术运算符和赋值运算符
C#提供了支持标准数学运算的运算符:加法(+)、减法(−)、乘法(*)和除法(/)。根据相除的数据类型的不同,除法的行为也稍有不同:将两个整数相除时,结果为整数,并将余数丢弃。要获得整数除法的余数,必须使用求模运算符%。

C#还支持复合赋值运算符,这种运算符将算术运算和赋值合而为一。对于每个标准算术运算符和求模运算符,都有相应的复合赋值运算符,分别是+ =、− =、* =、/ =和% =,这些运算符将赋值分别与加法、减法、乘法、除法和求模合而为一。

2.3.2 关系运算符
关系运算符用于比较两个值,结果为布尔值。

Paste_Image.png

2.3.3 逻辑运算符
逻辑运算符用于布尔表达式中,结果为true或false。

Paste_Image.png
Paste_Image.png

逻辑运算符的行为规则很容易总结。假设x和y都是布尔表达式,逻辑运算的结果如下:

Paste_Image.png

2.3.4 条件运算符
条件运算符(也称为三目运算符,因为它涉及3项)对编写简洁表达式很有帮助,它对条件进行评估,并根据结果返回两个值之一。
条件运算符的格式如下:
condition ? consequence : alternative
当condition为true时,将计算consequence并返回结果;如果condition为false,则计算alternative并返回结果。

ps:三目运算符常见的问题
这个运算符是右结合的,这与其他大部分运算符(左结合的)不同。这意味着对于下面的表达式相当于 a ? b : ( c ? d : e ):
a ? b : c ? d : e
条件表达式的类型是由consequence和alternative的类型决定的,而不是它被赋给的变量决定的。
因此,consequence 和 alternative 的类型必须相同,这意味着下面这样的表达式无法通过编译,因为其consequence的类型为int,而alternative的类型为string:
object x = b ? 0 : "hello";
虽然上述代码不实用,根本不应在其他地方使用,但是其正确的书写方式可能如下:
object x = b ? (object)0 : (object)"hello";

2.4 默认值
各种预定义数据类型的默认值:

Paste_Image.png

对于整型类型,默认值为零。char类型的默认值为空字符,而bool类型的默认值为false。类型object和string的默认值为null,即没有指向任何对象。

2.5 null和可以为null的类型
这些默认值意味着值类型不能为 null,看起来好像是合理的。然而,当使用数据库、其他外部数据源或其他可能没有指定值的数据类型时,会带来一定的限制。一个典型的示例就是数据库中的数值字段,它可以存储任何整型数据,也可能没有值。
对于这种问题,可以为null的数据类型提供解决方案。可以为null的类型是这样一种值类型:可表示其底层类型指定范围内的值以及null值。可以为null的类型用语法Nullable<T>或T?表示,其中T是一种值类型。语法T?使用更广泛。给可以为null的类型的变量赋值时,方法与给其他变量赋值相同:
int = 10;
int? = 10;
int? = null;
要获取可以为 null 的类型的变量的值,应使用方法 GetValueOrDefault,它返回赋给变量的值,如果没有赋值,就返回底层类型的默认值。另外,还可以使用属性 HasValue (如果给变量赋值了,该属性将为true)和Value(它返回变量的实际值,如果值为null,就将引发异常)。
所有可以为 null 的类型(包括引用类型)都支持 null 合并运算符(??)。将可以为 null的类型的变量赋给不能为null的类型的变量时,可使用该运算符指定要返回的默认值。如果该运算符左边的操作数为 null,就返回右边的操作数;否则,返回左边的操作数。程序清单2.4演示了如何使用null合并运算符。

2.6 强制转换与转换
作为统一类型系统的一部分,所有值类型都可以转换为 object。值类型变量需要用作引用类型时,将自动创建一个对象“箱”,并将值复制到箱子中。装箱后,对一个变量的操作不会影响另一个。将对象箱变回到原来的值类型时,将把箱子内的值复制到变量中。

ps:装箱与取消装箱
值类型和引用类型之间的转换通常称为强制转换(cast),因为这种转换使用C# cast运算符,但是相应的CIL指令为 box和 unbox。
装箱转换总是隐式的,它将值类型转换为引用类型。取消装箱总是显式的,它将装箱的值类型(引用类型)转换为值类型。

装箱和取消装箱操作占用的资源很多,开销也很大,应尽可能避免并确保使用正确的类型来解决问题。
如下是各种预定义类型支持且能成功完成的隐式转换。之所以允许这些隐式转换,是因为从原始数值类型转换为新的数值类型时不会降低量级。

Paste_Image.png

ps:隐式转换
隐式转换可能降低精度,但是不应降低量级。以将 int 值转换为 float值为例,它们都是32位的,但是并非每个int值都可以精确地表示为float,这将导致精度降低。然而,由于float的取值范围比int大,因此这种转换不会降低量级。
在转换可能降低精度时,必须进行显式转换,此时需要指定要将原始值转换为哪种类型。显式转换的形式为(T) E,如图所示,它将E的值转换为类型T。

Paste_Image.png

显式转换存在的问题是,如果不小心,代码就可能能够编译,但是运行时会失败。显式转换告诉编译器,您定这种转换能够成功,如果不成功,导致的运行阶段错误也是可以接受的。
为降低显式转换在运行阶段失败的可能性,C#提供了 as运算符,其形式为 e as T,其中e是一个表达式,而T必须是引用类型或可以为null的类型。as运算符告诉编译器,有充分的理由相信转换将成功,它试图将值转换为类型T并返回结果,如果转换失败,就返回null。
为利用as运算符,可将上图所示的代码重写为如下形式:
int? i = 36;
object boxed = i;
int? j = boxed as int?;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,581评论 18 399
  • 标签: 我的笔记 ---学习资料:http://javascript.ruanyifeng.com/ 1. 导论 ...
    暗夜的怒吼阅读 793评论 0 1
  • 有多少一直不敢表白的人借着愚人节说出那些情话 01 我喜欢你!嗯?嗯!啊哈,今天愚人节?对啊,哈哈,差点被骗了吧!...
    学长talk阅读 643评论 0 3
  • 这次尝试使用代码在LaTex中绘制树状图,然而过程竟然颇为曲折。于是作此小记。 tikz 最初找到了这个包,于是使...
    Sicuso阅读 1,978评论 1 0