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
2
object obj;
obj = 100; // 这是装箱

动态(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 枚举
整数、浮点数、字符型、布尔型
  1. 值类型分为以下两种: 结构和 枚举
    值类型中的”结构”又包括了13种基本数据类型:整数、浮点数、字符型、布尔类型四种数据
  2. 这13种基本的数据类型,存储在栈内存中,且存储的是具体的数据值
  3. 不同数据类型占据的内存大小各不相同,合理的确定数据的类型可避内存资源被无谓浪费。

引用类型

引用类型
object
string
class 类
  1. 引用类型可以处理以下两种类型的数据,分别是:
    object、string
  2. 这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
2
3
4
5
6
7
8
9
int divRes1 = 6/3;
Console.WriteLine( divRes1);//divRes1值2
int divRes2 = 5/3 ;
Console.WriteLine( divRes2);//divRes2值1
int num1 = 87 , num2 = 23;
int per1 = num1 / num2 *100 ;
Console.WriteLine( per1 ); //per1值300
per1 = num2 / num1 *100;
Console.WriteLine( per1 ); //per1值0

两个整数进行算术运算的溢出:
两个整数进行运算时,结果可能会超过整数范围,造成数据溢出的情况。例如:

1
2
3
4
5
6
int oneNum = 2147483647;
int twoNum = -2147483648;
oneNum = oneNum + 1;
twoNum = twoNum - 1;
Console.WriteLine( "oneNum {0}",oneNum );
Console.WriteLine( "twoNum {0}",twoNum );

float 类型

float 是C#中用于表示浮点型数的一种类型,”单精度”;
float 一般用于表示较小的浮点数,且精度较低;
一个float类型的数据,在内存占用 4 个字节;
浮点数默认的直接量(字节量)是 double 双精度类型,所以使用float 定义一个浮点数时,需要在具体数字后面加上float的表示符f,(大小写均可)
float 类型的数据的赋值方法:

  1. 常用写法,如: float height = 1.75f ;
  2. 科学计数法,使用 E 或 e,如:
    float f = 1.25E02 ; // 表示1.25乘以10的2次方

定义两个float类型的变量 n1和n2,用于存储两个操作数,然后计算这两个数字的和与差(n1的值1.1,n2的值2.2)。 

1
2
3
4
5
6
float n1 = 2.0F;
float n2 = 0.9f ;
float addResult = n1 + n2 ;
float subResult = n1 - n2;
Console.WriteLine( addResult );
Console.WriteLine( subResult );

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种值类型中大多都支持MaxValueMinValue 字段,表示给定的类型的存储范围。如下

1
2
3
4
5
6
7
static void Main( stringlargs ){
int intMax = int.MaxValue ;//获取int型最大值
int intMin = int.MinValue ;//获取int型最小值
double doubleMax = double.MaxValue ;
double doubleMin = double.MinValue ;
double positive = double.PositiveInfinity ;
double negative = double.NegativeInfinity ;

值类型占用字节数

基本语法如下:sizeof ( 类型说明符 )
 用于返回一个对象或者类型在内存中所占的字节数。

1
2
3
4
5
static void Main( stringlargs )
{
//获取int类型在内存中占用的字节数
Console.WriteLine( sizeof ( int ) ); // 4
}

栈和堆

程序运行时,它的数据必须存储在内存中,一个数据项需要多大的内存、存储在什么地方、以及如何存储都依赖于数据项的类型。
运行中的程序使用两个内存区域来存储数据:栈和堆。

  栈是一个内存数组,是一个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#验证)

  1. 它们存储的位置不一样
  2. 如果是引用类型,当两个对象指向同一个地方,修改某一个的时候,其它对象的值会发生改变
  3. 如果是值类型,将互不干扰

我们来看下面一段代码:

  • 首先在类中声明一个class类,和一个struct结构:
1
2
3
4
5
6
7
8
9
10
11
12
namespace refTypeAndvalueType
{
///<summary>
///定义一个结构,结构名为SomeValue
///</summary>
public struct SomeValue
{
//定义一个整数
public int NurmberA;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
namespace refTypeAndvalueType
{
///<summary>
///定义一个类,类名为SomeClass
///</summary>
public class SomeClass
{
//定义一个整数
public int NurmberA;
}
}

并使用在程序入口调用它们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 namespace refTypeAndvalueType
{
class program{
static void Main(string[] args)
{
// 第一步:使用SomeClass类,创建一个名为s1的对象,并为对象中的变量赋值
SomeClass s1 = new SomeClass();
s1.NumberA = 12;
//第二步:使用SomeValue结构,创建一个名为r1的对象,并为对象中的变量赋值
SomeValue r1 = new SomeValue();
r1.NumberA = 16;
}
}
}

现在我们来看一看,它们在内存当中是如何存储的?

从这张图可以看出,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,
但是它会在线程栈中分配一个独立的空间,
当修改某一个对象的值的时候,不会影响到另一个对象