目录
数据类型
(既然说C#程序是一组类型声明,那么认识类型就无比重要,本节将主要介绍类型的定义、类型的两种分类、并阐述两种类型的区别、最后阐述类型转换。)
类型的两种分类:
- C #是基于.NET框架的语言,它自然也定义了自己的数据类型,这种类型被称为预定义类型。C#同时也可以让程序员定义自己的数据类型,这种类型叫用户自定义类型。
- 在第一章我们讨论过通用类型系统CTS分为两大类:引用类型和值类型。
1.类型的定义及用法
1.1什么是类型?
- 类型的定义:
我们可以把类型想成一个用来创建数据结构的模板。就好比大象是一个类型,我们把 大象 特有的名称(即“大象”)、体型、行为作为一个模板。
注意模板本身不是数据结构,它只适用于描述由该模板建成的对象的特征。
类型由下面的元素组成:
- 类型名(名称)
- 类型存储数据成员的大小(体型)——(这里的数据成员下面会介绍)
- 类型的值范围(行为)
名称 int | 类型存储数据成员的大小 4字节 |
---|---|
类型的值范围 32位整数 |
1.2类型怎么用——实例化类型
实例化类型就是从某个类型模板创建实际的对象。用1.1的举例而言,就是创建一只名叫“小强”的大象。
这里有两个知识点:
- 通过实例化类型而创建的对象被称为类型的对象或类型的实例。
- 在C#程序中,每个数据项(注释1)都是某个类型的实例。这些类型可以是C#自带的,可以是其他库提供的,也可以是程序员自己定义的。
说白这里的对象、数据项就是标识符。
1.3数据成员和函数成员
我们知道,C#程序是一组类型声明。在这些类型之间存在包含关系,由此出现了成员这一概念。
详细的来说:在整个类型中,简单类型(见图1.2,在下面)只能存储一个数据项、其他类型则可以存储多个数据项(比如数组类型可以存储多个相同类型的 数据项——也被称为数组元素,可以通过数字来引用(注释2),这些数字称为索引),而在其他类型中的一些类型可以包含许多不同类型的数据项。
这些可以包含 不同类型的数据项的 类型中的数据项个体被称为成员,并且与数组中使用数字来引用成员不同,这些成员有独特的名称(标识符)。
两种成员
- 数据成员:保存了与这个类的对象或整个类相关数据。
- 函数成员: 执行代码。函数成员定义类型的行为。
代码示例:
图1.1
2.预定义类型和用户自定义类型
C#提供了16种预定义类型,其中包含13种简单型、3种非简单类型。
除了C#提供的预定义类型,我们可以创建的用户自定义类型有6种。
2.1预定义类型
所有预定义类型都由全小写字母组成。
与C和C++不同,在C#中的数值类型不具有布尔意义。
2.1.1 预定义类型的组成
13种简单类型由以下三种组成:
- 有符号和无符号整形类型(有无符号是指类型的值范围能否为正负号)
- 浮点类型float和double(赋的值可以为小数,float为单精度,double为双精度)
- 十进制类型 decimal(比浮点型精度更高和更小的范围,常用于财务和货币的计算)
3种非简单类型:
- string 它是一个Unicode字符数组,赋的值为字符串
- object 它是所有其他类型的基类
- dynamic 使用动态语言编写的程序集时使用
如图1.8:
图1.2
2.1.2 预定义类型的补充
所有预定义类型都直接映射搭配到底层的.NET类型,可以说C#类型名称就是.NET名称的引用。虽然你可以用.NET类型编写C#代码,但并不推荐你这么做。
下图1.9、1.10为预定义简单类型和非简单类型的取值范围和对应的底层.NET类型:
图1.3 简单类型
图1.4 非简单类型
2.2用户自定义类型
有6种类型可以由我们自己创建:
- 类 类型(class)
- 结构类型 (struct)
- 数组类型 (array)
- 枚举类型 (enum)
- 委托类型 (delegate)
- 接口类型 (interface)
2.2.1 用户定义的类型转换
- 可以为自己的类和结构定义隐式转换和显式转换。这允许我们把用户定义类型的对象转换成某个其他类型,也可以把其他类型的对象转换为用户定义的类型。
- C#提供的隐式转换和显示转换。
- 隐式转换
-
//语法:public static implicit operator TargetType (SourceType Identifier) //public static implicit operator 为必需 //TargetType 为目标类型 //SourceType 为原类型 //Identifier 为对象 //e.g class 第五章 { //类型转换 public static implict operator int (第五章 a) { return a.TheValue; } //类型转换 public staitic implict operator 第五章(int x) { 第五章 a=new 第五章(); a.TheValue=x; return a; } }
-
- 显示转换
-
//语法:public static explicit operator TargetType (SourceType Identifier) //public static explicit operator 为必需 //TargetType 为目标类型 //SourceType 为原类型 //Identifier 为对象 //e.g class 第五章 { //类型转换 public static explicit operator int (第五章 a) { return a.TheValue; } //类型转换 public staitic explicit operator 第五章(int x) { 第五章 a=new 第五章(); a.TheValue=x; return a; } }
-
- 隐式转换
2.3 预定义类型和用户自定义类型的区别
这里我们通过更进一步的刨析数据成员的图来说明,请看图1.5。
我们已知 简单类型a,b为简单类型。类类型 成员为用户自定义类型。通过观察不难发现:
对于预定义类型,我们可以直接实例化对象(也就是a、b)。
但对于用户自定义类型,我们必须先声明类型,然后实例化该类型的对象。
图1.5
简单的说:预定义类型只需要进行实例化;用户自定义类型需要:声明和实例化。
3.值类型 和 引用类型
- 值类型 和 引用类型 的 分类:
图1.6
3.1值类型 和 引用类型的定义
图1.7
3.2值类型和引用类型的区别
数据项的类型定义了存储数据需要的内存大小,若类型为非简单类型,则还定义了组成该类型的数据成员。
类型还决定了对象在内存中的存储位置——栈或堆。这正是值类型和引用类型的区别
要想更详细的了解值类型和引用类型的区别,我们首先要了解,什么是栈和堆。
3.2.1栈和堆
栈
一个内存数组,是一个LIFO(Last-In First-Out,后进先出)的数据结构。
栈存储三种类型的数据:
- 某些类型变量的值
- 程序当前的执行环境
- 传递给方法的参数
栈的特征:
- 数据只能从栈的顶端插入和删除
- 把数据放在栈顶称为入栈(push)
- 把数据从栈顶删除称为出栈(pop)
图示:
图1.8
堆
一个内存区域,在堆里可以分配大块的内存用于存储某些类型的数据成员。(与栈后进先出不同,堆里的内存能够以任意顺序存入和移除)
图示:图中展示一个堆里放了4个对象
图1.9
公共语言运行时(CLR)的自动垃圾收集器
虽然程序可以在堆里保存数据,但不能显示地删除它们。自动垃圾收集器的作用就是判断出程序不再访问某数据项时,会自动清除无主的堆对象。
图示:
图1.10
3.2.2 值类型和引用类型存储位置
我们知道数据类型可以分为:值类型和引用类型,这两种类型的对象在内存中的存储方式不同。
- 值类型存在栈中。(由操作系统负责管理)
- 其只需要一段单独的内存,用于存储实际的数据。
- 引用类型存在栈和堆中。(由垃圾回收器(又称Garbage Collection,GC)负责管理)
- 栈中存储引用,指向数据在堆中的存放位置。
- 堆中存储实际的数据。
如图:
图1.11
3.2.3 引用类型对象的成员的存储位置
从1.3数据成员和函数成员我们知道,有些引用类型中包含其他数据类型。
这些其他数据类型(无论是值类型还是引用类型)的存储位置跟引用类型一样,存在堆里。
如图:A为值类型,B为引用类型
图1.12
3.3 值类型和引用类型间的转换
3.3.1类型转换
类型转换指的是将一种数据类型转换成另一种数据类型的过程。
类型转换有以下四种方式:
- 隐式类型转换:由低级别类型向高级别类型转换。(装箱属于隐式类型转换)
- 显式类型转换:由高级别类型向低级别类型转换。但这种转换可能导致精度损失或运行异常。
- 通过is和as运算符进行安全的类型转换。(后续会讲)
- 通过.NET类库中的Convert类来完成类型转换。(参考)
如图:
图1.13
这里需要注意,不是所有的类型都能互相转换,类型之间不能完成转换时就会出现报错。
如图:
图1.14
3.3.2 拆箱和装箱
本章关于类型转换主要讲 值类型与引用类型间的一种转换——装箱和拆箱。
拆箱和装箱的定义:
- 装箱:将值类型转换为引用类型的过程。此过程中系统会在托管堆中生成一份堆栈(见1.3数据成员,堆中也可能有值类型的数据成员)中值类型对象的副本。
- 拆箱:将引用类型转换为值类型的过程。此过程中系统会从托管堆中将引用类型所指向的已装箱数据复制回值类型对象的过程
图1.14
拆箱和装箱的缺点:
在我们写代码的时候应尽量避免装箱和拆箱,最好使用泛型来编程。
这是因为装箱和拆箱对性能有很大的影响。如果程序大量执行装箱和拆箱,在装箱第一步,就会生成很多多余的对象,加重GC的负担;在第二步,会耗费大量运行时间。这两步都会导致程序的性能降低。
4.C#四种类型的分类
图1.13
注释:
1.数据项:数据项可以是字母、数字或两者的组合。通过数据类型及数据长度来描述。数据项用来描述实体的某种属性。
2.引用:在编程中,引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。