C# 数据类型与类型转换
原文:https://www.runoob.com/csharp/csharp-data-types.html
在 C# 中,变量分为以下几种类型:
- 基本数据(值类型)(Value types)
- 引用类型(Reference types)
- 指针类型(Pointer types)
一、值类型(Value types)
值类型变量可以直接分配给一个值。它们是从类 System.ValueType 中派生的。
值类型直接包含数据。比如 int、char、float,它们分别存储数字、字符、浮点数。当您声明一个 int 类型时,系统分配内存来存储值。
下表列出了 C# 2010 中可用的值类型:
类型 | 描述 | 范围 | 默认值 |
---|---|---|---|
byte | 8 位无符号整数,1个字节 | 0 到 255 | 0 |
sbyte | 8 位有符号整数类型,1个字节 | -128 到 127 | 0 |
short | 16 位有符号整数类型,2个字节 | -32,768 到 32,767 | 0 |
ushort | 16 位无符号整数类型,2个字节 | 0 到 65,535 | 0 |
int | 32 位有符号整数类型,4个字节 | -2,147,483,648 到 2,147,483,647 | 0 |
uint | 32 位无符号整数类型,4个字节 | 0 到 4,294,967,295 | 0 |
long | 64 位有符号整数类型,8个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 | 0L |
ulong | 64 位无符号整数类型,8个字节 | 0 到 18,446,744,073,709,551,615 | 0 |
float | 32 位单精度浮点型,4个字节 | -3.4 x 1038 到 + 3.4 x 1038 | 0.0F |
double | 64 位双精度浮点型,8个字节 | (+/-)5.0 x 10-324 到 (+/-)1.7 x 10308 | 0.0D |
decimal | 128 位精确的十进制值,28-29 有效位数,16个字节 | (-7.9 x 1028 到 7.9 x 1028) / 100 到 28 | 0.0M |
char | 16 位 Unicode 字符,2个字节 | U +0000 到 U +ffff | '\0' |
bool | 布尔值 | True 或 False | False |
如需得到一个类型或一个变量在特定平台上的准确尺寸,可以使用 sizeof 方法。表达式 sizeof(type) 产生以字节为单位存储对象或类型的存储尺寸。下面举例获取任何机器上 int 类型的存储尺寸:
using System; namespace DataTypeApplication { class Program { static void Main(string[] args) { Console.WriteLine("Size of int: {0}", sizeof(int)); Console.ReadLine(); } } }
当上面的代码被编译和执行时,它会产生下列结果:Size of int: 4
二、引用类型(Reference types)
引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用。
换句话说,它们指的是一个内存位置。使用多个变量时,引用类型可以指向一个内存位置。如果内存位置的数据是由一个变量改变的,其他变量会自动反映这种值的变化。内置的 引用类型有:object、dynamic 和 string。
1、对象(Object)类型
对象(Object)类型 是 C# 通用类型系统(Common Type System - CTS)中所有数据类型的终极基类。Object 是 System.Object 类的别名。所以对象(Object)类型可以被分配任何其他类型(值类型、引用类型、预定义类型或用户自定义类型)的值。但是,在分配值之前,需要先进行类型转换。
当一个值类型转换为对象类型时,则被称为 装箱;另一方面,当一个对象类型转换为值类型时,则被称为 拆箱。
装箱:值类型转换为对象类型
int val = 8; object obj = val; //整型数据转换为了对象类型(装箱)
拆箱:之前由值类型转换而来的对象类型再转回值类型
/*只有装过箱的数据才能拆箱*/ int val = 8; object obj = val; //先装箱 int nval = (int)obj; //再拆箱
2、动态(Dynamic)类型
您可以存储任何类型的值在动态数据类型变量中。这些变量的类型检查是在运行时发生的。
声明动态类型的语法:
dynamic <variable_name> = value;
例如:
dynamic d = 20;
动态类型与对象类型相似,但是对象类型变量的类型检查是在编译时发生的,而动态类型变量的类型检查是在运行时发生的。
3、字符串(String)类型
字符串(String)类型 允许您给变量分配任何字符串值。字符串(String)类型是 System.String 类的别名。它是从对象(Object)类型派生的。字符串(String)类型的值可以通过两种形式进行分配:引号和 @引号。
例如: 引号 字符串 string str = "zzz.com"; 一个 @引号字符串: string str = @"zzz.com"; C# string 字符串的前面可以加 @(称作"逐字字符串")将转义字符(\)当作普通字符对待,比如: string str = @"C:\Windows"; 等价于: string str = "C:\\Windows"; @ 字符串中可以任意换行,换行符及缩进空格都计算在字符串长度之内。 string str = @"<script type=""text/javascript""> <!-- --> </script>"; 注意: c#中转义双引号",使用的转义字符仍然是\。 string str = "\"www.zzz.com\""; 但是,在c#的逐字字符中,双引号的转义字符不能用\了,会报错,而是用两个双引号""来表示"。 string str1 = @"""www.zzz.com""";
用户自定义引用类型有:class、interface 或 delegate。我们将在以后的章节中讨论这些类型。
三、指针类型(Pointer types)
指针类型变量存储另一种类型的内存地址。C# 中的指针与 C 或 C++ 中的指针有相同的功能。
声明指针类型的语法:
type* identifier;
例如:
char* cptr;
int* iptr;
我们将在章节"不安全的代码"中讨论指针类型。
四、拓展
1、C# 中char与string 区别
char 属于值类型(Value Type),char 类型的长度是固定的,永远是2个字节,可以使用sizeof获取char的尺寸。
string 属于引用类型(Reference types),string 的长度是无法明确获取的,也就是无法使用sizeof获取,因为它不是一个基础类型,本身并不固定长度,而是取决于内部包含的字符。
简单来说:
- char 是字符,string是字符串。
- char 一次只能赋值一个字符,用两个字节存储,存储的是该字符的Unicode编码,这里要特别注意的是,char类型的赋值只能用单引号。
- string类型用于表示字符串,字符串可以是只包含一个字符,也可以是包含多个字符,它是一串字符,string类型的赋值要用双引号,不能用单引号。
/* char与string之间的转换 */
// string转化成char
string str = "balala"; // 双引号
char chr = str[1];
Console.WriteLine(str);
Console.WriteLine(chr);
// char转化为string
char chr2 = 'z'; // 单引号
string str2 = chr2.ToString();
Console.WriteLine(chr2);
Console.WriteLine(str2);
2、基元类型(primitive)与FCL(Framework Class Library)
基元类型(primitive),简单来说就是编译器直接支持的数据类型。基元类型直接映射到Framework类库(FCL)中的类型,例如C#中一个基元类型int直接映射到System.Int32类型。
因此我们在使用c#时候,很多人会有一个小疑问:为啥一个类型会有两种写法,比如string和String, object和Object。
其实他们都是一样的,都是指String类,String是System.String类,string是别名,就像bool/Boolean, int/int32, long/int64 都是一样的,例如:
using System;
namespace FclApplication
{
class Program
{
static void Main(string[] args)
{
// 以下两种写法是等价的
int a = 1;
Console.WriteLine(a);
Int32 a1 = 1;
Console.WriteLine(a1);
}
}
}
下表为C#基元类型对应的FCL类型:
C#基元类型 |
FCL类型 |
符合CLS |
sbyte |
System.SByte |
否 |
byte |
System.Byte |
是 |
short |
System.Int16 |
是 |
ushort |
System.UInt16 |
否 |
int |
System.Int32 |
是 |
uint |
System.UInt32 |
否 |
long |
System.Int64 |
是 |
ulong |
System.UInt64 |
否 |
float |
System.Single |
是 |
double |
System.Double |
是 |
decimal |
System.Decimal |
是 |
bool |
System.Boolean |
是 |
object |
System.Object |
是 |
dynamic |
System.Object |
是 |
char |
System.Char |
是 |
string |
System.String |
是 |
建议还是使用string或int这些别名,因为这些别名是根据framework的不同而可能和不同的类相对应的。
假如以后.net framework 运行在16位的操作系统,整型 int 默认变为 System.Int16,如果你使用了int别名,那么你的代码就不需要改了,int自动映射成System.Int16,
如果你使用的是System.Int32,那么你代码都得去修改为System.Int16。
3、关于值类型、引用类型以及“栈”跟“堆”的关系
1.值类型,声明一个值类型的时候,是在“栈”中开辟一个内存空间来存放对应的值,当值类型的值发生改变的时候,则直接修改该内存空间所保存的值。例:
int n1 = 5; int n2 = n1; Console.WriteLine(n1 + " "+ n2); // 5 5 n2 = 7; Console.WriteLine(n1 + " " + n2) // 5 7
这里首先在“栈”中开辟一个内存空间用来保存 n1 的值 5,接着再在“栈”中开辟一个新的内存空间用来保存 n2 的值 5,所以显示出来的结果是 5 5。然后将 n2 在“栈”中对应的内存空间保存的值修改成 7,故显示出来的结果是 5 7。
2.引用类型,声明一个引用类型的时候,首先是在“堆”中开辟一个内存空间来存放对应的值,然后在“栈”中开辟一个内存空间用于保存在“堆”中开辟的内存空间的地址。当系统调用引用类型的时候,首先去“栈”中获取到地址,然后根据地址在“堆”中找到对应的内存空间来获取到对应值。像数组这样的引用类型
string[] a1 = new string[]{ "a" , "b" , "c" }; string[] a2 = a1; for(int i = 0; i < a2.Length; i++) { Console.Write(a2[i] + " "); //a b c } a1[2] = "d"; Console.WriteLine(); for(int i = 0; i < a1.Length; i++) { Console.Write(a1[i] + " "); //a b d } Console.WriteLine(); for(int i = 0; i < a2.Length; i++) { Console.Write(a2[i] + " "); //a b d }
这里首先是在“堆”中开辟一个内存空间(假设:0X55)用来保存数组a1的值,然后在“栈”中开辟一个内存空间(a1)用于保存地址 0X55。当将 a1 赋给 a2 时,是将地址赋给 a2,即在“栈”中开辟一个内存空间(a2)用于保存地址 0X55,所以输出 a2 的值是 a b c。当将 a1[2]修改成”d”的时候,修改的是“堆”中 0X55 内存空间保存的值,因为 a2 的地址和 a1 的地址一样,所以输出结果是 a b d。
3.而 string 是一个特殊的引用类型,先看下面代码
string a = "123"; string b = a; Console.WriteLine(a+" "+b); //123 123 string b = "456"; Console.WriteLine(a+" "+b); //123 456
和数组类似的,这里首先在“堆”中开辟一个内存空间(假设:0X88)用来保存 a 的值 123,然后在“栈”中开辟一个内存空间(a)用于保存地址 0X88。
和数组不同的是,当将 a 赋给 b 的时候,首先是在“堆”中开辟一个新的内存空间(假设:0X101)用于保存值 123,然后在“栈”中开辟一个内存空间(b)用于保存地址 0X101,所以输出的结果是 123 123。
当修改 b 值时,并不是修改“堆”中 0X101 内存空间的值,而是在“堆”中重新开辟一个新的内存空间(假设:0X210)用于保存 b 修改后的值,然后将 b 在“栈”中对应的内存空间的所保存的地址修改成 0X210,所以输出的结果是 123 456。而“堆”中的 0X101 内存空间将在下次的垃圾回收中被回收利用。
五、类型转换
https://www.runoob.com/csharp/csharp-type-conversion.html
1、类型转换介绍
类型转换从根本上说是类型铸造,或者说是把数据从一种类型转换为另一种类型。在 C# 中,类型铸造有两种形式:
- 隐式类型转换 - 这些转换是 C# 默认的以安全方式进行的转换, 不会导致数据丢失。例如,从小的整数类型转换为大的整数类型,从派生类转换为基类。
- 显式类型转换 - 显式类型转换,即强制类型转换。显式转换需要强制转换运算符,而且强制转换会造成数据丢失。
下面的实例显示了一个显式的类型转换:
namespace TypeConversionApplication { class ExplicitConversion { static void Main(string[] args) { double d = 5673.74; int i; // 强制转换 double 为 int i = (int)d; Console.WriteLine(i); Console.ReadKey(); } } } // 当上面的代码被编译和执行时,它会产生下列结果:5673
2、C# 类型转换方法
C# 提供了下列内置的类型转换方法:
方法 | 描述 |
---|---|
ToType | 把类型转换为指定类型。 |
ToBoolean | 如果可能的话,把类型转换为布尔型。 |
ToString | 把类型转换为字符串类型。 |
ToChar | 如果可能的话,把类型转换为单个 Unicode 字符类型。 |
ToDateTime | 把类型(整数或字符串类型)转换为 日期-时间 结构。 |
ToSingle | 把类型转换为小浮点数类型。 |
ToDouble | 把类型转换为双精度浮点型。 |
ToDecimal | 把浮点型或整数类型转换为十进制类型。 |
ToByte | 把类型转换为字节类型。 |
ToSbyte | 把类型转换为有符号字节类型。 |
ToInt16 | 把类型转换为 16 位整数类型。 |
ToUInt16 | 把类型转换为 16 位无符号整数类型。 |
ToInt32 | 把类型转换为 32 位整数类型。 |
ToUInt32 | 把类型转换为 32 位无符号整数类型。 |
ToInt64 | 把类型转换为 64 位整数类型。 |
ToUInt64 | 把类型转换为 64 位无符号整数类型。 |
下面的实例把不同值的类型转换为字符串类型:
using System; namespace TypeConversionApplication { class StringConversion { static void Main(string[] args) { int i = 75; float f = 53.005f; double d = 2345.7652; bool b = true; Console.WriteLine(i.ToString()); Console.WriteLine(f.ToString()); Console.WriteLine(d.ToString()); Console.WriteLine(b.ToString()); Console.ReadKey(); /* 当上面的代码被编译和执行时,它会产生下列结果: 75 53.005 2345.7652 True */ } } }
3、隐式转换和显式转换详解
隐式转换:C# 默认的以安全方式进行的转换。本质是从小存储容量数据类型自动转换为大存储容量数据类型,从派生类转换为基类。
namespace TypeConvertion { class Class1 { } class Class2 : Class1 //类Class2是类Class1的子类 { } class Program { static void Main(string[] args) { int inum = 100; long lnum = inum; // 进行了隐式转换,将 int 型(数据范围小)数据转换为了 long 型(数据范围大)的数据 Class1 c1 = new Class2(); // 这里也是隐式转换,将一个新建的 Class2 实例转换为了其基类 Class1 类型的实例 C1 } } }
显式转换:通过用户使用预定义的函数显式完成的,显式转换需要强制转换运算符。
转换类型的范围大小和从属关系和隐式转换相反。显式转换可能会导致数据出错,或者转换失败,甚至无法编译成功。
using System; namespace TypeConvertion { class Class1 { } class Class2 : Class1 //类Class2是类Class1的子类 { } class Program { static void Main(string[] args) { double dnum = 100.1; int ifromd = (int)dnum; //double类型显式转换转为int类型 Class1 c11 = new Class1(); Class2 c22 = c11 as Class2; //使用as进行显式转换 Console.WriteLine(c22 is Class1); // False Console.WriteLine(c22 is Class2); // False } } }
4、强制类型转换注意点
1.几种转换的方法:
using System; namespace TypeConvertion { class Program { static void Main(string[] args) { /* int强转 */ double a = 1.35; int a1 = (int)(a); Console.WriteLine("使用int强制double转换成int: {0}", a1); // string snum = "123"; // int inum = (int) snum; // 会报错,int不能强转string // Console.WriteLine(); /* string转int */ string strNum = "123"; //方法一: 用 Convert int i1 = Convert.ToInt32(strNum); Console.WriteLine("使用Convert转换成int: {0}", i1); //方法二: 用 int.Parse(string s) int i2 = int.Parse(strNum); Console.WriteLine("使用int.Parse转换成int: {0}", i2); //方法三: 用 int.TryParse(string s,out int i) int i3; bool bo = int.TryParse(strNum, out i3); Console.WriteLine("使用int.TryParse转换成结果: {0},{1}", bo, i3); } } }
2.C# 中对 double 类型的数据取整,可以使用 convert.toint32() 方法,也可使用 int 强制转换为整数,使用 int 时并不存在四舍五入的情况,而是直接将后面的小数位数丢掉。比如:
class Program { static void Main(string[] args) { double a = 1.35; double b = 1.65; int a1 = Convert.ToInt32(a); int a2 = (int)(a); int b1 = Convert.ToInt32(b); int b2 = (int)(b); Console.WriteLine("{0}使用convert方法转化的结果为:{1}",a,a1); Console.WriteLine("{0}使用int强制转换的结果为:{1}",a,a2); Console.WriteLine("{0}使用convert方法转化的结果为:{1}", b, b1); Console.WriteLine("{0}使用int强制转换的结果为:{1}", b, b2); Console.ReadKey(); } }
程序运行结果如下:
1.35使用convert方法转化的结果为:1
1.35使用int强制转换的结果为:1
1.65使用convert方法转化的结果为:2
1.65使用int强制转换的结果为:1
3.Convert.ToInt32() 、 int 、 int.Parse() 、int.TryParse 的区别
1.对于转换对象
int:只能是数字类型(float,int,uint等),但不能是字符串。
int.TryParse() 和 int.Parse():只能是整型字符串(即各种整型 ToString() 之后的形式)不能为浮点型字符串,否则 int.Parse() 就会出现输入的字符串格式不正确的错误,int.TryParse() 也会返回 false,输出参数为 0 。
Convert.ToInt32():可以为多种类型(例 float,string,bool,DateTime 等)。如果是数字类型,可以是float浮点型等等,如果是字符串,只能是整型字符串。
2.null空值处理:
Convert.ToInt32(null) 会返回 0 而不会产生任何异常。
(int)null 强制转换和 int.Parse(null) 都会抛出异常,一般需要搭配try catch使用。
int.TryParse(null) 其实是对 int.Parse() 做了一个异常处理,如果出现异常则返回 false,并且将输出参数返回 0;
3.对浮点型数据进行四舍五入时候的区别
浮点型只有 Convert.ToInt32() 和 (int) 能进行转换。
a. Convert.ToInt32(double value) 如果 value 为两个整数中间的数字,则返回二者中的偶数;即 3.5 转换为 4,4.5 转换为 4,而 5.5 转换为 6。不过 4.6 可以转换为 5,4.4 转换为 4 。
b. int.Parse("4.5") 直接报错:"输入字符串的格式不正确"。
c. int(4.6) = 4 Int 转化其他数值类型为 Int 时没有四舍五入,直接截取浮点型的整数部分,忽略小数部分。
4.类似的还有Convert.ToDouble 与 Double.Parse 的区别。
实际上 Convert.ToDouble 与 Double.Parse 较为类似,实际上 Convert.ToDouble内部调用了 Double.Parse:
(1)对于参数为null的时候:
- Convert.ToDouble参数为 null 时,返回 0.0;
- Double.Parse 参数为 null 时,抛出异常。
(2)对于参数为""的时候:
- Convert.ToDouble参数为 "" 时,抛出异常;
- Double.Parse 参数为 "" 时,抛出异常。
(3)其它区别:
- Convert.ToDouble可以转换的类型较多;
- Double.Parse 只能转换数字类型的字符串。
- Double.TryParse 与 Double.Parse 又较为类似,但它不会产生异常,转换成功返回 true,转换失败返回 false。最后一个参数为输出值,如果转换失败,输出值为 0.0。
附测试代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { try { //string a = "0.2"; //string a = null; string a = ""; try { double d1 = Double.Parse(a); } catch (Exception err) { Console.WriteLine("d1转换出错:" + err.Message); } try { double d2 = Convert.ToDouble(a); } catch (Exception err) { Console.WriteLine("d2转换出错:" + err.Message); } try { double d3; Double.TryParse(a,out d3); } catch (Exception err) { Console.WriteLine("d3转换出错:" + err.Message); } } finally { Console.ReadKey(); } } } }