CH03-深入CSharp的数据类型
CH03-深入CSharp的数据类型
- 理解C#中的值类型和引用类型的概念
- 理解装箱和拆箱操作
- 理解值类型和引用类型作为方法参数的区别
- 值类型转换成引用类型传参的关键字ref和out
- 理解C#中的结构
- 理解C#中的枚举
- 理解C#中的可空类型
- 理解C#中的静态类型var和动态类型dynamic
本章内容
1、C#中的值类型和引用类型
数据类型的分类:
数据类型按存储方式可分为两类:值类型和引用类型
值类型的概念:
值类型:不同的变量会分配不同的存储空间,存储空间中存储的是该变量的值,改变一个变量值不会影响另一个变量值。
【案例:】张三和李四去年身高都是170cm,今年李四长到了180cm,张三没有变化,输出去年和今年两人身高。
分析:定义两个变量保存身高,修改变量的值,输出变量的值,观察最终结果。
1
2
3
4
5
6
7
8
9int zhangSan = 170;
int liSi = zhangSan;
Console.WriteLine("去年张三和李四的身高分别为:{0},{1}",zhangSan,liSi);
//今年李四长到180
liSi = 180;
Console.WriteLine("今年张三和李四的身高分别为:{0},{1}", zhangSan, liSi);
Console.ReadLine();输出结果:
引用类型的概念:
引用类型:赋值是把原对象的引用传递给另一个引用,两个引用指向同一块内存空间。
引用相当于引用的是存储空间的内存地址
【案例:】张浩和李明去年身高与体重均为170cm和60kg,李明今年身高和体重变为180cm和70kg,张浩无变化。输出两人的身高和体重
分析:如何保存同一个人的身高和体重呢?使用数组来保存
1
2
3
4
5
6
7
8
9
10
11int[] zhangSan = { 170,70};
int[] liSi = zhangSan;
Console.WriteLine("去年张三,李四的身高和体重分别为:{0},{1},{2},{3}", zhangSan[0], zhangSan[1], liSi[0], liSi[1]);
//李四身体和体重发生变化
liSi[0] = 180;
liSi[1] = 80;
Console.WriteLine("今年张三,李四的身高和体重分别为:{0},{1},{2},{3}", zhangSan[0], zhangSan[1], liSi[0], liSi[1]);
Console.ReadLine();问题:引用类型如何如何只复制数据(值)
通过数组复制解决以上问题
1
2
3
4
5
6
7int[ ] infoZhang = new int[ ]{170,60};
int[ ] infoLi = new int[2];
for(int i = 0; i < infoZhang.length; i++){
infoLi[i] = infoZhang[i];
}
infoLi[0] = 180; //今年李明的身高变为180
infoLi[1] = 70; //今年李明的体重变为70
2、装箱和拆箱
装箱:
将值类型转换成引用类型,称为装箱
【案例:】装箱操作
1
2int num=10;
object obj=num;//将值类型转换成引用类型拆箱:
将引用类型转换成值类型,称为拆箱
【案例:】拆箱操作
1
2object obj=100;
int num=(int)obj;//将引用类型转换成值类型注意事项:
1、拆箱操作,变量的类型要一致。
2、在实际的开发中,应该尽量减少不必要的装箱和拆箱,因为二者的存储方式不同,转换时性能损失较大
3、值类型和引用类型的方法参数传递
值类型作为参数传递
值方式传递值类型参数,传递的是值的副本,在方法中修改数据,对原来的值不会有任何影响
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16static void Main(string[] args)
{
int num = 10;
Console.WriteLine("调用方法前的值:{0}",num);
//调用方法
ChangeNum(num);
Console.WriteLine("调用方法后的值:{0}",num);
Console.ReadLine();
}
static void ChangeNum(int num)
{
num = 100;//在方法中修改参数的值
Console.WriteLine("方法中修改参数后的值:"+num);
}输出结果:
引用类型作为参数传递
引用类型作为参数传递,传递的引用的地址,则在方法中修改数据,则修改的是该地址指向的同一个空间,也就是说原来的值会发生变化。
1
2
3
4
5
6
7
8
9
10
11
12
13static void Main(string[] args)
{
int[] nums = { 10, 20 };
Console.WriteLine("调用方法前的值:{0},{1}", nums[0], nums[1]);
ChageNums(nums);
Console.WriteLine("调用方法后的值:{0},{1}", nums[0], nums[1]);
Console.ReadLine();
}
static void ChageNums(int[] nums)
{
nums[0] = 1;
nums[1] = 2;
}输出结果:
4、ref和out关键字的使用
概念:在C#中,ref和out关键字用于按引用传递变量,它们在变量传递、输出参数、返回值以及异常处理等方面有一些重要区别。
ref关键字
ref关键字在方法调用时创建了一个引用,该引用指向调用方法时传递的变量。在方法内部,可以通过引用修改变量的值,并且这些更改将反映在原始变量上。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15static void Main(string[] args)
{
int num1 = 10;//使用ref关键字,必须给参数赋初始值,不然出报错
int num2 = 20;
Console.WriteLine("调用方法前:num1:{0},num2:{1}", num1, num2);
ChangeNum(ref num1, ref num2);
Console.WriteLine("调用方法后:num1:{0},num2:{1}",num1,num2);
Console.ReadLine();
}
static void ChangeNum(ref int num1,ref int num2)
{
num1 = 100;
num2 = 200;
}注意事项:
- 定义方法时,在形参前面指定ref,调用此方法时,必须在实参前加上ref;
- 在调用方法前,必须给指定ref的实参赋初始值;
- 指定ref参数,在方法中修改参数的值会保留。
out关键字
out关键字也在方法调用时创建了一个引用,但是与ref不同,out参数在方法内部不需要初始化。然而,out参数必须在方法返回之前被赋值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16static void Main(string[] args)
{
int num1 = 10;
int num2 ;//使用out关键字,在调用方法前可以不给参数赋初始值
Console.WriteLine("调用方法前:num1:{0},num2:{1}", num1, num2);
ChangeNum(out num1, out num2);
Console.WriteLine("调用方法后:num1:{0},num2:{1}",num1,num2);
Console.ReadLine();
}
static void ChangeNum(out int num1,out int num2)
{
//定义out关键字,必须保证参数在方法中赋值(更改)
num1 = 100;
num2 = 200;
}注意事项:
- 定义方法时,在形参前面指定out,调用此方法时,必须在实参前加上out;
- 在方法中,指定out的参数,必须保证都都赋值;
- 指定out参数,在方法中修改参数的值会保留。
5、C#中的结构
结构的概念?
在 C# 中,结构是值类型数据结构。
它使得一个单一变量可以存储各种数据类型的相关数据。
struct 关键字用于创建结构。
定义结构的语法:
关键字:struct
语法:
1
2
3
4
5
6
7
8public struct 结构名{
字段1;
字段2;
方法(){
}
}例如:创建一个学生的结构类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public struct Student
{
public string StudentName;//可以设置字段
public int Age { get; set; }//可以添加属性
public void SayHi()//可以定义方法
{
Console.WriteLine("大家好,我叫:{0},今年:{1}岁",StudentName,Age);
}
}
static void Main(string[] args)
{
Student student = new Student();
student.StudentName = "张三";
student.Age = 20;
student.SayHi();
Console.ReadLine();
}
结构的特点:
结构是值类型,则具有较快数据提取速度
结构可带有方法、字段、索引、属性、运算符方法和事件。
结构在使用时可以使用new,也可以不使用new,但是结构中有定义属性就必须使用new
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public struct Student{
public string studentName;
public int age;
}
static void Main(string[] args)
{
//Student student = new Student();//可以通过new关键字创建
Student student;//也可以不使用new关键字创建
student.studentName = "张三";
student.age = 20;
Console.ReadLine();
}如果在使用结构中,未使用new创建,则必须先给结构中的字段赋完初始值,才能使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public struct Student{
public string studentName;
public int age;
public void SayHi()//可以定义方法
{
Console.WriteLine("大家好,我叫:{0},今年:{1}岁",studentName,age);
}
}
static void Main(string[] args)
{
Student student;//未使用new关键字创建
//student.studentName = "张三";
student.age = 20;
student.SayHi();//会报错,studentName未赋值
Console.ReadLine();
}定义结构时,不能直接给结构中的字段赋初始值
1
2
3
4public struct Student
{
public string StudentName="";//报错:可以设置字段,但不能给字段赋初始值
}结构可定义构造函数,但不能定义析构函数。但是,您不能为结构定义默认的构造函数。默认的构造函数是自动定义的,且不能被改变。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public struct Student
{
~Student(){//报错:不能定义析构函数
}
//报错
//public Student()//不能给结构创建默认构造函数
// {
//}
public Student(string studentName)//正确,可以给结构定义带参数的构造函数
{
this.StudentName = studentName;
}
public string StudentName;//可以设置字段
}与类不同,结构不能继承其他的结构或类。
结构不能作为其他结构或类的基础结构。
结构可实现一个或多个接口。
结构成员不能指定为 abstract、virtual 或 protected。(因为结构不能被继承)
总结:结构和类的区别:
- 定义方式不同:结构体使用 struct 关键字定义,而类使用 class 关键字定义。
- 内存分配方式不同:结构体是值类型,它的实例被分配在栈上,而类是引用类型,它的实例被分配在堆上。
- 继承性不同:结构体不支持继承,而类可以继承其他类或抽象类。
- 针对默认构造函数的处理不同:结构体默认有一个无参的构造函数,且不能声明无参构造函数,而类如果没有显式定义构造函数,就会默认有一个无参的构造函数。
- 析构函数:结构体不支持析构函数,而类支持。
- 赋值方式不同:结构体赋值时是按值传递,即会复制一份,而类赋值时是按引用传递,即会复制一个引用。
- 性能不同:由于结构体的实例被分配在栈上,所以在一些情况下,使用结构体比使用类更高效,比如在大量创建小对象时。但是,结构体也有一些限制,比如它的大小不能超过 16KB。
怎么选择结构或类呢?
当对象需要用较少的字段来表示时,可以选用结构结构是值类型,数据提取速度快但是频繁的赋值操作会占用较大空间, 在开发中多数情况下都定义为类!!!
6、C#中的枚举类型
什么是枚举?
枚举是一组命名整型常量。枚举类型是使用 enum 关键字声明的。
C# 枚举是值数据类型。换句话说,枚举包含自己的值,且不能继承或传递继承。
声明枚举的语法:
1
2
3enum 枚举名{
枚举的标识符列表;
};例如:定义性别的枚举和定义颜色的枚举
1
2
3
4
5
6enum Gender{
男,女
}
enum Color{
red,blue,white,green
}具体说明:
- 枚举列表中的每个符号代表一个整数值,一个比它前面的符号大的整数值。默认情况下,第一个枚举符号的值是 0 。
如何使用枚举:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30//定义性别的枚举
public enum Gender
{
男, 女
}
public class Student
{
public string StudentName { get; set; }
public Gender Gender { get; set; }//声明枚举的属性
public void SayHi()
{
Console.WriteLine("姓名:{0},性别:{1}",StudentName,Gender);
}
}
internal class Program
{
static void Main(string[] args)
{
Student student = new Student();
student.StudentName = "张三";
student.Gender = Gender.男;//可以直接赋枚举值,比较方便
student.SayHi();
Console.ReadLine();
}
}枚举的类型转换
枚举转化成字符串:直接调用ToString()方法。
1
string str=Gender.男.ToString();
字符串转换成枚举:
语法:
1
(枚举类型)Enum.Parse(typeof(枚举类型), 字符串);
案例:
1
2
3
4string gender = "男";
Gender g=(Gender)Enum.Parse(typeof(Gender), gender);//将字符串转换成枚举类型
Console.WriteLine(g);
枚举转成成int类型
1
int n=(int)Gender.男;
int转换成枚举类型
1
2int n=0;
Gender g=(Gender)n;
C#中的数据类型总结:
7、C#中的可空类型
什么是可空类型?
C# 提供了一个特殊的数据类型,nullable 类型(可空类型),可空类型可以表示其基础值类型正常范围内的值,再加上一个 null 值。
可空类型的应用场景
在处理数据库和其他包含可能未赋值的元素的数据类型时,将 null 赋值给数值类型或布尔型的功能特别有用。例如,数据库中的布尔型字段可以存储值 true 或 false,或者,该字段也可以未定义。
语法:
1
2
3
4
5
6
7Nullable<data_type> 变量名=null;
或
data_type? 变量名=null;
例如:
Nullable<int> num=null;
或
int? num=null;Null 合并运算符( ?? )
说明:
Null 合并运算符用于定义可空类型和引用类型的默认值。Null 合并运算符为类型转换定义了一个预设值,以防可空类型的值为 Null。Null 合并运算符把操作数类型隐式转换为另一个可空(或不可空)的值类型的操作数的类型。
如果第一个操作数的值为 null,则运算符返回第二个操作数的值,否则返回第一个操作数的值。
例如:
1
2
3
4
5
6
7
8int? num1 = null;
int? num2 = 10;
int num3 = num1 ?? 100;//相当于:num3=num1==null?100:num1;
Console.WriteLine(num3);//输出:100
num3=num2??100;
Console.WriteLine(num3);//输出:10
8、C#中的静态类型var
概念:
var是根据所赋的值来推断变量的数据类型。你不需要显式指定数据类型,而是让编译器根据上下文来确定初始值的数据类型。
应用场景:
var可以理解为匿名类型,我们可以认为它是一个声明变量的占位符。它主要用于在声明变量时,无法确定数据类型时使用。
语法:
1
2
3var 变量名=值;
例如:
var num=10;//可以根据值,推断类型为int使用var定义变量时有以下四个特点:
- 必须在定义时初始化。也就是必须是var s = “abcd”形式,而不能是如下形式: var s; s = “abcd”;
- 一但初始化完成,就不能再给变量赋与初始化值类型不同的值了。
- var要求是局部变量。
- 使用var定义变量和object不同,它在效率上和使用强类型方式定义变量完全一样。
注意事项:
- 无法使用var来定义一个全局变量,只能定义在方法的内部(因为预先不可知,所以预先不可置)
- 不能用来定义函数的签名,包括返回值,参数类别
- 在定义变量的时候,必须先给值,不能为null,也不能只定义不给值。
- 一旦一个变量被定义成var类型,并确定了指定的类型以后,不能再给这个变量其他类型的值。例: var a=”1”;a=1;(错误:无法将类型“XX”隐式转换成“YY”)
9、C#中的动态类型dynamic
概念:
动态类型语言是指在运行时执行类型检查的语言。如果您不知道您将获得或需要分配的值的类型,则在此情况下,类型是在运行时定义的。
语法:
1
2
3
4dynamic 变量名 = 值;
例如:
dynamic str="abc";
dynamic num=10;注意事项:
动态类型是在运行时,再进行类型检测,则定义了动态类型后,那么您将无法获得任何智能提示。
动态类型可以定义在方法的返回类型或参数列表中,作为定义时的数据类型。
如果在操作过程中,类型不匹配,则在运行时会报错,编译时不会报任何错误,如下代码:
1
2
3
4
5
6
7
8static void Main(string[] args)
{
dynamic str = "abc";//定义的是字符串的动态类型
str++;//对变量进行++操作,在此不会报错
Console.WriteLine(str);
Console.ReadLine();
}运行后的效果如下:
10、var和dynamic类型的对比:
C# var和dynamic的用法和理解:
var和dynamic的本质区别是类型判断的时间不同,前者是编译时,后者是运行时。
- var在声明变量方面简化语法(只能是局部变量),在编译时交给编译器推断。
- dynamic也是为简化语法而生的,它的类型推断是交给系统来执行的(运行时推断类型)。
- var不能用于字段、参数等,而dynamic则可以。
- var在初始化的时候就确定了类型。
- dynamic可以用于方法字段、参数、返回值以及泛型参数,把动态发挥的淋漓尽致。
- var是C# 3.0的产物,dynamic是C# 4.0的产物。
var和dynamic的区别和使用场景:
1、类型确定性
var是编译时类型推断,具有类型确定性。适用于静态类型的情况,其中类型不会改变。
dynamic是运行时类型推断,具有较低的类型确定性。适用于需要处理不同类型数据的情况,但需要小心类型错误。
2、类型安全性
var变量在编译时会进行类型检查,并且编译后根据上下文推断出准确的类型,因此具有更高的类型安全性。
dynamic变量在编译时不进行类型检查,而是编译后赋值为object类型,因此具有较低的类型安全性。
3、性能方面
从上面的情况可以看出var具有更好的性能,dynamic由于是运行时类型推断,性能较之var差一些。
4、适用场景
使用var当你能够在编译时确定变量类型,或者在需要显式类型声明的情况下。
使用dynamic当你需要处理不同类型的数据,或者需要动态类型的话可以使用它。
本章总结
- 理解值类型和引用类型的区别
- 掌握装箱和拆箱操作
- 掌握值类型和引用类型作为参数传递的区别
- ref和out关键字的如何使用
- C#中的几种特殊类型:
- 结构类型
- 枚举类型
- 可空类型
- 静态类型
- 动态类型
本章作业
更新会员积分
需求说明
- 使用数组存放五位会员的积分
- 系统升级,请将原有积分进行备份,然后赠送每位会员500积分,编写程序输出积分情况
编写一个存储长方形(Rec)属性的结构
- 长(Length)
- 宽(Width)
- 实例化一个结构长方形,并计算面积
编写程序使用数组存储班级5名学员的成绩,实现如下功能
- 因为某次计分失误,需要为每位学员成绩提高5分
- 如果提分后学员成绩高于100分,按100分计
- 分别输出提分前和提分后每位学员的成绩
生成设备ID
需求说明
- 为公司购买的3台不同型号的计算机进行编号
- 编号的规则是“计算机型号+4位随机号”
- 编号完毕后,输出每台计算机的信息
效果图:
项目经理评分
需求说明
- 显示员工信息
- 实现项目经理给员工评分
实现思路
编写SE类查看评分窗体中创建公有成员变量保存员工信息
SE对象数组
初始化员工信息并使用ListView控件显示
编写Init()方法和UpdateView()方法
PM类添加Judge(SE se)方法,实现为员工评分
选中某员工,双击打开评分窗体,实现事件处理方法
效果图: