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)

引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用。

换句话说,它们指的是一个内存位置。使用多个变量时,引用类型可以指向一个内存位置。如果内存位置的数据是由一个变量改变的,其他变量会自动反映这种值的变化。内置的 引用类型有:objectdynamicstring

 

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();
            }

        }
    }
}

 

 

posted @ 2022-08-11 21:27  我用python写Bug  阅读(851)  评论(0编辑  收藏  举报