C#笔记(5)数据类型
C# 数据类型
在 C# 中,变量分为以下几种类型:
- 值类型(Value types)
- 引用类型(Reference types)
- 指针类型(Pointer types)
注:后面我们看到的这些数据类型,实际上是简化符号,真正意义上这些数据类型在.NET Framework 中有具体的对应类,如:int 对应的是System.Int32
值类型(Value types)
值类型变量可以直接分配给一个值。
它们是从类 System.ValueType 中派生的。
值类型直接包含数据。比如 int、char、float,它们分别存储数字、字符、浮点数。当您声明一个 int 类型时,系统分配内存来存储值。
直接存储值,在栈上存储其值。值类型变量声明后,不管是否已经赋值,编译器为其分配内存。
如需得到一个类型或一个变量在特定平台上的准确尺寸,可以使用 sizeof
方法。表达式 sizeof(type)
产生以字节为单位存储对象或类型的存储尺寸。
整数(8种):
类型 | CTS | 占据空间 | 说明 | 数据范围 | 默认值 |
---|---|---|---|---|---|
sbyte | System.Sbyte | 1 Byte | 8位有符号的整数 | -128~127(-27——27-1) | 0 |
short | System.Int16 | 2 Byte | 16位有符号的整数 | -128~127(-216>——216-1) | 0 |
int | System.Int32 | 4 Byte | 32位有符号的整数 | -2147483648~2147483647(-231>——231-1) | 0 |
long | System.Int64 | 8 Byte | 64位有符号的整数 | -263>——263-1 | 0L |
byte | System. Byte | 1 Byte | 8位无符号的整数 | 0——255(0~28-1) | 0 |
ushort | System. UInt16 | 2 Byte | 16位无符号的整数 | 0——216-1 | 0 |
uint | System. UInt32 | 4 Byte | 32位无符号的整数 | 0——232-1 | 0 |
ulong | System. UInt64 | 8 Byte | 64位无符号的整数 | 0——264-1 | 0 |
存储范围(二进制 字节 和 位 )
数据范围不一样
有符号
无符号
C#整数的默认类型:int
浮点类型(3种):
类型 | CTS | 占据空间 | 说明 | 数据范围 | 默认值 |
---|---|---|---|---|---|
float | System.Single | 4 Byte | 32位单精度 | -3.4 x 1038 到 + 3.4 x 1038 | 0.0F |
double | System.Double | 8 Byte | 64位双精度 | (+/-)5.0 x 10-324 到 (+/-)1.7 x 10308 | 0.0D |
decimal | System.Decimal | 16 Byte | 128位高精度 十进制数表示法 |
±1.0x10e-28至±7.9x10e-28 | 0.0M |
单精度和双精度的区别:小数点后小数位的处理上
小数类型常用:float double
小数默认类型:double
如果要强调float,要数字的后面加大写或小写F
bool(布尔)类型
类型 | CTS | 占据空间 | 说明 | 默认值 |
---|---|---|---|---|
bool | System. Boolean | 1 Byte | 表示true或false | false |
布尔类型值只有两种:true false
真 假
场景:作为判断条件
char字符类型
类型 | CTS | 占据空间 | 说明 | 默认值 |
---|---|---|---|---|
char | System.Char | 2 Byte | 表示一个16位的(Unicode)字符 | ‘\0’ |
字符型只能表示任意的单个字符
这单个字符:汉字,字母,数字,符号,空格
将具体的单个字符放在一对单引号中。
1 | char c1 = 'a' ; |
‘我’ ‘!’ ‘(‘
注意和字符串区分
“ “ :双引号,放到双引号中的内容都原样打印字符串
引用类型(Reference types)
存储对其值的引用,在栈上存储地址,在堆上存储值。
当声明一个类时,只在栈中分配一小片内存用于容纳一个地址,而此时并没有为其分配堆上的内存空间。
当使用 new 创建一个类的实例时,分配堆上的空间,并把堆上空间的地址保存到栈上分配的小片空间中。
引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用。
换句话说,它们指的是一个内存位置。使用多个变量时,引用类型可以指向一个内存位置。如果内存位置的数据是由一个变量改变的,其他变量会自动反映这种值的变化。
内置的引用类型有:object、dynamic 和 string。
类型 | CTS | 说明 |
---|---|---|
object | System.0bject | 所有类型都是从它派生而来的 |
dynamic | ||
string | System.String | 字符串 |
对象(Object)类型
对象(Object)类型 是 C# 通用类型系统(Common Type System - CTS)中所有数据类型的终极基类。Object 是 System.Object 类的别名。所以对象(Object)类型可以被分配任何其他类型(值类型、引用类型、预定义类型或用户自定义类型)的值。但是,在分配值之前,需要先进行类型转换。
当一个值类型转换为对象类型时,则被称为 装箱;另一方面,当一个对象类型转换为值类型时,则被称为 拆箱。
1 | object obj; |
动态(Dynamic)类型
您可以存储任何类型的值在动态数据类型变量中。这些变量的类型检查是在运行时发生的。
声明动态类型的语法:dynamic <variable_name> = value;
例如:dynamic d = 20;
动态类型与对象类型相似,但是对象类型变量的类型检查是在编译时发生的,而动态类型变量的类型检查是在运行时发生的。
字符串(String)类型
字符串(String)类型 允许您给变量分配任何字符串值。字符串(String)类型是 System.String 类的别名。它是从对象(Object)类型派生的。字符串(String)类型的值可以通过两种形式进行分配:引号和 @引号。
例如:
String str = “runoob.com”;
一个 @引号字符串:
@”runoob.com”;
C# string 字符串的前面可以加 @(称作”逐字字符串”)将转义字符(\)当作普通字符对待,比如:
string str = @”C:\Windows”;
等价于:
string str = “C:\Windows”;
@ 字符串中可以任意换行,换行符及缩进空格都计算在字符串长度之内。
string str = @”<script type=””text/javascript””>
“;
用户自定义引用类型有:class、interface 或 delegate。我们将在以后的章节中讨论这些类型。
指针类型(Reference types)(不安全)
C#为了类型安全,默认并不支持指针。
定义指针 | 说明 |
---|---|
int* p | 整形指针 |
int** p | 指向整形指针的指针 |
char* c | 指向字符的指针 |
int*[] arr | 整形一维数组指针 |
使用指针的方法要加上unsafe
而且项目也要加上unsafe修饰才能编译
小结
值类型:所谓值类型,是以“栈” 的形式存储的,它将数据的地址,也可以认为是数据和命名放在一起的;
引用类型:所谓引用类型,是以“堆” ,它将数据的地址存储在“栈” 中,而数据具体内容存储在“堆” 中,然后将“栈” 中的数据名用一个指针指向“堆” 中它相应的内容,如此一来,它就将数据和内容分开存储了。
指针类型:指针类型变量存储另一种类型的内存地址。
值类型
值类型 |
---|
struct 结构 |
enum 枚举 |
整数、浮点数、字符型、布尔型 |
- 值类型分为以下两种: 结构和 枚举
值类型中的”结构”又包括了13种基本数据类型:整数、浮点数、字符型、布尔类型四种数据 - 这13种基本的数据类型,存储在栈内存中,且存储的是具体的数据值
- 不同数据类型占据的内存大小各不相同,合理的确定数据的类型可避内存资源被无谓浪费。
引用类型
引用类型 |
---|
object |
string |
class 类 |
- 引用类型可以处理以下两种类型的数据,分别是:
object、string - 这2种引用类型,存储在堆栈中,且存储的是在堆内存
中的引用(地址)
int 类型
int 是C#中最常用的整数类型。
声明一个 int 类型的变量后,在内存会开辟 4 个字节的空间来存储变量中的数据;
int 能够表示的数据范围:-2^31 —— 2^31-1 ,即:-2147483648 ~ 2147483648
整数直接量就是能直接写出的整数,又称为整数字面量;
如下: int age = 20 ,20 就是直接量
关于整数的直接量,需要注意如下要点:
整数的直接量的类型默认为int类型,如果直接写出的整 数超过了int所能表示的数据范围,则会出现错误;
如果一个整数是int、uint、long或是ulong没有任何显式的声明,则该变量默认为int类型。若要指定其他整数 类型,可在数字后面加类型的缩略表示
除将一整数赋值给int外,还可将二进制(以0B或0b开头)八进制(以0开头)、十六进制(以0X或0x)数据赋值。
int 运算
两个整数相除,如果结果是小数,会自动舍弃小数,只取整数部分(直接舍弃小数,不四舍五入)。
1 | int divRes1 = 6/3; |
两个整数进行算术运算的溢出:
两个整数进行运算时,结果可能会超过整数范围,造成数据溢出的情况。例如:
1 | int oneNum = 2147483647; |
float 类型
float 是C#中用于表示浮点型数的一种类型,”单精度”;
float 一般用于表示较小的浮点数,且精度较低;
一个float类型的数据,在内存占用 4 个字节;
浮点数默认的直接量(字节量)是 double 双精度类型,所以使用float 定义一个浮点数时,需要在具体数字后面加上float的表示符f,(大小写均可)
float 类型的数据的赋值方法:
- 常用写法,如: float height = 1.75f ;
- 科学计数法,使用 E 或 e,如:
float f = 1.25E02 ; // 表示1.25乘以10的2次方
定义两个float类型的变量 n1和n2,用于存储两个操作数,然后计算这两个数字的和与差(n1的值1.1,n2的值2.2)。
1 | float n1 = 2.0F; |
double 类型
double类型,是C#中默认的小数类型
double类型,称为双精度小数
使用double类型描述小数时,小数后面省略了大写或小写的字母D
C#中的double类型,运算时数据不准确
程序在操作数据时,底层处理的都是二进制数据。在二进制系统中,只能使用0和1,故二进制中是无法精确的表示1/10(十分之一),就好像十进制无法精确的表示1/3一样。所以在二进制中表示10进制的小数时,会存在一些舍入误差。所以一般不建议使用 double 或 float 来进行一些精确数据运算的场合或行业,如:银行系统,工程图纸等方面。
bool 类型
使用 bool 类型进行逻辑运算,表示给定的某条件是否成立。
bool 类型的运算结果只有两种类型: true 或 false ; true表示条件成立,false 表示条件不成立;
bool 类型的默认值为:false
bool 值不能和整数值进行相互转换;
bool 在程序中和生活中具体使用场景有以下两种:
- 场景一: 用于程序的流程控制中的条件(if|while等);
请看以下一个生活情景:
如果明天下雨,我们就在室内玩,不下雨,我们在室外玩。上面这个生活场景中,”明天是否下雨“成为问题的关键 - 场景二: bool 型变量经常用于存储关系表达式的运算结果;关系表达式,实质就是两个变量的值比较大小等。
string 类型
字符串(string)类型 允许您给变量分配任何字符串值。字符串(string)类型是 System.String 类的别名。它是从对象(Object)类型派生的。
初期阶段,对字符串的掌握只要明白字符串定义时使用双引号括起来,即“”,双引号中可以写任意长度的内容,当然也可以不写。例如:定义一个表示学生姓名的变量,变量名name,值为Lucy
1 | string name = “Lucy”; |
数据类型其它操作
值类型求最大值|最小值
上面学的13种值类型中大多都支持MaxValue
和MinValue
字段,表示给定的类型的存储范围。如下
1 | static void Main( stringlargs ){ |
值类型占用字节数
基本语法如下:sizeof
( 类型说明符 )
用于返回一个对象或者类型在内存中所占的字节数。
1 | static void Main( stringlargs ) |
栈和堆
程序运行时,它的数据必须存储在内存中,一个数据项需要多大的内存、存储在什么地方、以及如何存储都依赖于数据项的类型。
运行中的程序使用两个内存区域来存储数据:栈和堆。
栈
栈是一个内存数组,是一个LIFO(last-in、first-out,后进先出)的数据结构,栈存储3种类型的数据:分别是值类型变量的值;程序当前的执行环境;传递给方法的参数。
栈有如下几个普遍特征,分别是:数据只能从栈的顶端插入与删除;把数据放到栈顶称为入栈;从栈顶删除数据称为出栈。
堆
堆是一块内存区域,在堆里可以分配大块的内存用于存储引用类型对象,与栈不同,堆里的内存能够以任意顺序存入和移除。虽然程序可以在堆里保存数据,但并不能显式的删除它们,CLR的自动GC(Garbage Collector,垃圾收集器)在判断出程序的代码将不会再访问某数据项时,自动清除无主的堆对象。
C#中值类型和引用类型表格(含自定义)
值类型 | 引用类型 | |
---|---|---|
预定义类型 | Sbyte、byte、float、short、ushort、double、int、uint、char、long、ulong、decimal、bool | Object、string、dynamic |
用户定义类型 | Struct、enum | Class、interface、delegate、array |
值类型和引用类型的区别(C#验证)
- 它们存储的位置不一样
- 如果是引用类型,当两个对象指向同一个地方,修改某一个的时候,其它对象的值会发生改变
- 如果是值类型,将互不干扰
我们来看下面一段代码:
- 首先在类中声明一个class类,和一个struct结构:
1 | namespace refTypeAndvalueType |
1 | namespace refTypeAndvalueType |
并使用在程序入口调用它们
1 | namespace refTypeAndvalueType |
现在我们来看一看,它们在内存当中是如何存储的?
从这张图可以看出,class(类)实例化出来的对象,指向了内存堆中分配的空间
struct(结构) 实例化出来的对象,是在内存栈中分配
接下来,我们再来在上面的程序做如下修改:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 namespace refTypeAndvalueType
{
class program{
static void Main(string[] args)
{
// 第一步:使用SomeClass类,创建一个名为s1的对象,并为对象中的变量赋值
SomeClass s1 = new SomeClass();
s1.NumberA = 12;
//声明一个类对象,名为s2,把s1中的对象赋值给s1
SomeClass s2 = s1;
s2.NumberA = 222; // 更改s2中的变量值为222
Console.WriteLine("s1的值为:{0} \t s2的值为:{1}",s1.NumberA,s2.NumberA);
//第二步:使用SomeVvalue结构,创建一个名为r1的对象,并为对象中的变量赋值
SomeValue r1 = new SomeValue();
r1.NumberA = 16;
SomeValue r2 = r1;
r2.NumberA = 666;
Console.WriteLine("r1的值为:{0} \t r2的值为:{1}", r1.NumberA, r2.NumberA);
Console.ReadLine();
}
}
}
那它们输出的结果是多少呢?请选择( )
A、 s1的值为:12 s2的值为222 r1的值为:16 r2的值为666
B、 s1的值为:12 s2的值为222 r1的值为:666 r2的值为666
C、 s1的值为:222 s2的值为222 r1的值为:16 r2的值为666
D、 s1的值为:222 s2的值为222 r1的值为:666 r2的值为666
查看解析
为什么会这样呢?所以我们来看一看,多个值类型和引用类型在内存里面是如何存储的,如图:
从图中,可以看出,两个引用类型 s1,s2都指向了同一个拖管堆上的空间,
当某一个发生改变的时候,其于的会发生变化
而结构是值类型,虽然使用r2=r1,把r1对象赋值给r2,
但是它会在线程栈中分配一个独立的空间,
当修改某一个对象的值的时候,不会影响到另一个对象