基础系列(2)—— 常用数据类型
一、数据类型初探
电脑是由什么来存储所使用的数据? 这个问题用一句话比较笼统的概括,那就是:电脑使用内存来记忆计算时所使用的数据。在现实生活中的数据各种各样,整数、小数、字符串、字符等等,它们的类型是不一样的,所以你要想在计算机中使用这些类型,就必须在内存中为它申请一块合适的空间。
数据类型总结起来有以下几点:
(1)C程序是一组函数和数据类型,C++程序是一组函数和类,而C#程序是一组类型声明;
(2)类型是一种模板:模板本身不是数据结构,但它详细说明了由该模板构造的对象的特征;
(3)C#提供了16种预定义类型:13种简单类型(数值类型:int,float,double,decimal等;非数值类型:bool,char),3种非简单类型(object,string,dynamic);
所有的预定义类型都直接映射到底层的.NET类型。C#的类型名称其实就是.NET类型的别名,所以使用.NET的类型名称也符合C#语法,不过并不鼓励这样做。在C#程序中,应当尽量使用C#类型名称而不是.NET类型名称;
(4)除了上面提到的16种预定义类型外,还可以创建自己的用户定义类型,一共有6种用户定义类型可以由用户自己创建,它们是:类(Class)、结构体(Struct)、数组(Array)、枚举(Enum)、委托(Delegate)和接口(Interface);
二 数据类型详解
(一)整型
(二)浮点类型
float数据类型用于较小的浮点数,因为它要求的精度较低。
double数据类型比float数据类型大,提供的精度也大一倍(15位)。
如果在代码中没有对某个非整数值(如12.3)硬编码,则编译器一般假定该变量是double。
如果想指定该值为float,可以在其后加上字符F(或f),如:float f = 12.3F;
(三)decimal类型
decimal类型专门用于进行财务计算,使用decimal类型提供的28位的方式取决于用户。
要把数字指定为decimal类型,可以在数字的后面加上字符M或(m),如:decimal d=12.30M;
(四)bool(布尔)类型
(五)char字符类型
char类型的字变量是用单引号括起来的,如'A'。如果把字符把在"A"(双引号)内,编译器会把它看作是字符串,从而产生错误。
(六)引用类型(Object类型和字符串类型)
三 数据类型存储双雄:栈和堆
(一) 栈
(1)栈是一个内存数组,是一个LIFO(Last In First Out,后进先出)的数据结构。
(2)栈存储几种类型的数据:某些类型变量的值(主要是值类型);程序当前的执行环境;传递给方法的参数;
(3)栈具有几种显著的特征:数据只能从栈顶插入和删除;将数据放到栈顶叫做入栈;将数据从栈顶移除叫做出栈;
(二)堆
(1)堆是一块内存区域,在堆里可以分配大块的内存用于存储某类型(主要是引用类型)的数据对象;与栈不同,堆里的内存能够以任意的顺序插入或移除;
(2)堆中的数据不能显示地删除,CLR中的自动GC(Garbage Collector,垃圾收集器)会自动清除无主(判断程序代码是否将不再访问某数据项的时候)的堆内存对象。因此,我们可以骄傲地说:妈妈再也不用担心我的垃圾了。
四、值类型和引用类型:屌丝和高富帅
(一)值类型:只需要一段单独的内存,用于存储实际的数据;TIP:对于值类型,数据存放在栈里;(byte,int,long,float,double,struct,enum等)
(二)引用类型:需要两段内存,第一段存储实际的数据,它总是位于堆中;第二段是一个引用,指向数据在堆中的存放位置;TIP:对于引用类型,实际数据存放在堆里,而引用存放在栈里。(object,string,dynamic,class,interface,delegate,array)
(三)引用类型对象的数据始终存放在堆里,无论它们是值类型还是引用类型。
五 、 数据类型转换
数据类型在一定的条件下是可以相互转换的,如将int型数据转换成double型数据。C#允许使用两种转换方式:隐式转换和显式转换。
(一)隐式转换
隐式转换:从类型A到类型B的转换可以在所有情况下进行,执行转换的规则非常简单,可以让编译器执行转换。隐式转换不需要做任何工作,也不需要另外编写代码。如将int型数据转换成double型数据:
int a = 10;double b = a;//隐式转换
隐式转换规则是:任何类型A,只要其取值范围完全包含在类型B的取值范围内,就可以隐式转换为类型B。基于这个转换规则,C#的隐式转换不会导致数据丢失。需要注意的是我们最常用的简单类型bool和string没有隐式转换。
(二)显式转换
显式转换:从类型A到类型B的转换只能在某些情况下进行,转换规则比较复杂,应进行某种类型的额外处理。显式转换又叫强制类型转换,显式转换需要用户明确的指定转换类型。如将double类型数据转换成int类型数据:
double c = 10.5; int d = (int)c;//显示转换
提醒:
(1)、显式转换可能会导致错误。进行这种转换时编译器将对转换进行溢出检测。如果有溢出说明转换失败,就表明源类型不是一个合法的目标类型。无法进行类型转换。
(2)、强制类型转换会造成数据丢失,如上面的例子中,最终得到的d值为10。
(三)通过方法进行类型转换
(1)、使用ToString()方法。所有类型都继承了Object基类,所以都有ToString()这个方法(转化成字符串的方法)。
(2)、通过int.Parse()方法转换,参数类型只支持string类型。注意:使用该方法转换时string的值不能为为NULL,不然无法通过转换;另外string类型参数也只能是各种整型,不能是浮点型,不然也无法通过转换 (例如int.Parse("2.0")就无法通过转换)。
int i; i = int.Parse("100");
(3)、通过int.TryParse()方法转换,该转换方法与int.Parse()转换方法类似,不同点在于int.Parse()方法无法转换成功的情况该方法能正常执行并返回0。也就是说int.TryParse()方法比int.Parse()方法多了一个异常处理,如果出现异常则返回false,并且将输出参数返回0。
int i; string s = null; int.TryParse(s,out i);
(4)、通过Convert类进行转换,Convert类中提供了很多转换的方法。使用这些方法的前提是能将需要转换的对象转换成相应的类型,如果不能转换则会报格式不对的错误。注意:使用Convert.ToInt32(double value)时,如果 value 为两个整数中间的数字,则返回二者中的偶数;即 4.5 转换为 4,而 5.5 转换为 6。
(5)、实现自己的转换,通过继承接口IConventible或者TypeConventer类,从而实现自己的转换。
六、值传递
(一)定义:
值类型在复制的时候,传递就是这个值得本身,例如:A同学的笔记复印了一份,给B同学,B同学任意修改笔记的内容,A同学都不会受到影响。
(二)例子:
int n1 = 10; int n2 = n1; n2 = 20; Console.WriteLine(n1); Console.WriteLine(n2); Console.ReadKey(); // 输出结果:n1=10; n2=20;
分析:变量n1在栈区开辟一块空间,其地址为0x000000e4a99fe094 ,并且为其赋初值10;
变量n2在栈区也开辟了一块空间,其地址为0x000000e4a99fe090,并n1值赋给了 n2,n2:10
变量n2的值被重新赋值20,n2:20
(三)内存代码:
&n1 0x000000e4a99fe094 n1: 0 &n1 0x000000e4a99fe094 n1: 10 &n2 0x000000e4a99fe090 n2: 0 &n2 0x000000e4a99fe090 n2: 10 &n2 0x000000e4a99fe090 n2: 20
由此可以看出:n1和n2在栈上的内存地址并不一样,所以在改变任意其中的而一个值,另一个并不受其影响
(四) 内存示意图:
七 、引用传递
(一)定义:
引用类型在复制的时候,传递的是对这个对象的引用
(二)例子:
Person p1 = new Person(); p1.Name = "张三"; Person p2 = p1; p2.Name="李四"; Console.WriteLine(p1.Name); Console.WriteLine(p2.Name);// 输出结果:李四 李四
分析:
变量p1在栈中开辟一块空间,地址为:0x00000095e5efdfd ,并且在堆中也有一块空间,存放的是new Person();这个对象,其内存空间地址为:0x0000000000000000,并且为p1.name赋值张三
变量p2在栈中也开辟一块空间,地址为:0x00000095e5efdfc8 ,此时,把p1对象赋值给p2对象,即p2也指向new Person()开辟的这块空间,其内存空间地址为:0x0000000000000000,随后,我们把p2.name赋值为 李四,那么由于p1.name也指向这块空间,所以,p1.name=”李四”
(四)内存代码:
&p1 0x00000095e5efdfd p1: 0x0000000000000000 &p2 0x00000095e5efdfc8 p2: 0x0000000000000000
(五)内存示意图:
八 、特殊的引用类型
例子:
string s1 = "张三"; string s2 = s1; s2 = "李四"; Console.WriteLine(s1) Console.WriteLine(s2);
结果:s1=张三 s2=李四 string虽然为引用类型,但是string具有不可变性
九 、方法的参数类型
(一) 两类参数的定义:
实参:具有实际意义的参数,一般在调用方法时,在方法的括号里面传递的参数为实参
形参:在方法声明时,方法名后面括号里面的参数为形参,一般(值传递)形参是接收实参传过来的值
(二) out参数:
1、定义:
在一个方法需要返回多个参数的时候,一般在参数列表里面声明out类型的参数,以便输出多个返回值。其中,out参数只负责把结果输出,不负责输入参数。一般out声明的变量需要在方法体内部赋值
2、例子:
class Program { static void Main(string[] args) { int[] ShuZi = { 1,2,3,4,5,6}; int max1; int min1; int sum1; JiSuan(ShuZi,out max1,out min1,out sum1); Console.WriteLine("数组的最大值为:{0},数组的最小值为:{1},数组的和为:{2}",max1,min1,sum1); Console.ReadKey(); } public static void JiSuan(int []number,out int max,out int min,out int sum) { max = number[0]; min = number[0]; sum = 0; for (int i = 0; i < 6; i++) { sum += number[i]; if (number[i] > max) { max = number[i]; } else { min=number[i]; } } } }
输出结果为:最大值:6,最小值:1,和为21
3 注意
out指定的参数必须在函数定义的时候就赋初值。否则则出现错误。对比ref指定的参数则可以不在函数内部进行赋初值,在函数调用时候再赋初值也可以。
(二)ref参数
1、定义:
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 Program pg = new Program();
6 int x = 10;
7 int y = 20;
8 pg.GetValue(ref x, ref y);
9 Console.WriteLine("x={0},y={1}", x, y);
10
11 Console.ReadLine();
12
13 }
14
15 public void GetValue(ref int x, ref int y)
16 {
17 x = 521;
18 y = 520;
19 }
20 }
:
代码②:
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 Program pg = new Program();
6 int x = 10;
7 int y = 20;
8 pg.GetValue(ref x, ref y);
9 Console.WriteLine("x={0},y={1}", x, y);
10
11 Console.ReadLine();
12
13 }
14
15 public void GetValue(ref int x, ref int y)
16 {
17 x = 1000;
18 y = 1;
19 }
20 }
由代码① 和②的运行结果可以看出,在方法中对参数所做的任何更改都将反映在该变量中,而在main函数中对参数的赋值却没有起到作用,这是不是说明不需要进行初始化呢?来看第二点
1 class Program
2 {
3 static void Main(string[] args)
4 {
5 Program pg = new Program();
6 int x;
7 int y; //此处x,y没有进行初始化,则编译不通过。
8 pg.GetValue(ref x, ref y);
9 Console.WriteLine("x={0},y={1}", x, y);
10
11 Console.ReadLine();
12
13 }
14
15 public void GetValue(ref int x, ref int y)
16 {
17 x = 1000;
18 y = 1;
19 }
20 }
出现的错误为:使用了未赋值的局部变量“x”,“y”。故可以说明ref指定的参数无论在函数定义的时候有没有赋予初值,在使用的时候必须初始化。
(四)总结
参考资料:《c#图解教程》