C#自学笔记:数据类型原理与实践
基础
复杂数据类型特点
- 数据集合
一般是多个数据(变量)集合在一起构成的数据
- 自定义
一般可以自己取名字,可以自定义的数据(变量)
类型:
- 枚举:整形变量的集合,可以自定义
- 数组:任意变量类型顺序存储的数据
- 结构体:任意变量的数据集合,可以自定义
枚举
整形常量的集合,可以自定义基本概念
枚举是一个比较特别的存在它是一个被命名的整形常量的集合
一般用它来表示状态、类型等待
注意:声明枚举和声明枚举变量是两个概念
- 声明枚举:相当于是创建一个自定义的枚举类型
- 声明枚举变量:使用声明的自定义枚举类型,创建一个枚举变量
声明枚举语法
enum E_自定义枚举名
{
//自定义枚举项名字, 枚举中包裹的整形常量,第一个默认值是0,下面的变量会依次累加
//自定义枚举项名字1,
//自定义枚举项名字2
}
声明位置
- namespace语句块中(常用)
- class语句块中或者struct语句块中
注意:枚举不能在函数语句块中声明!!
namespace {
enum E_MonsterType
{
Normal, //0
Boss, //1
}
enum E_PlayerType
{
Main,
Other,
}
}
枚举的使用
枚举和switch是天生一对E_PlayerType playerType = E_PlayerType.Main;
switch(playerType) {
case Main:
//代码
break;
case Other:
//代码
break;
default:
break;
}
也可以用if进行判断类型
枚举的类型转换
- 枚举和int互转
直接强转
int i = (int)playerType;
int转枚举
playerType = (E_PlayerType)0;
- 枚举和string相互转换(极少用)
转换之后是类型的名字,不是值
string str = playerType.ToString();
Console.WriteLine(str);//Main;
string转枚举
//Parse方法
//第一个参数:要转换的枚举类型
//第二个参数:用于转换的枚举变量名
//转换完毕后是一个通用类型,也就是Object
playerTypr = (E_PlayerType)Enum.Parse(typeof(E_PlayerType), "Other");
Console.WriteLine(playerType);//Other
枚举的作用
在游戏开发中,对象很多时候会有许多的状态
枚举可以帮助我们清晰地分清楚状态的含义
数组
任意变量类型顺序存储的数据类型:一维、多维、交错数组
一般情况下一维数组就简称为数组
(一维)数组的声明
//(用的比较少)
//变量类型[] 数组名;
int[] arr1;
arr1 = new int[5];
//(比较严谨)
//变量类型[] 数组名 = new 变量类型[数组长度]
int[] arr2 = new int[5];
//(比较严谨)
//变量类型[] 数组名 = new 变量类型[数组长度]{内容1, 内容2, ...}
int[] arr3 = new int[5]{1, 2, 3, 4, 5};
//变量类型[] 数组名 = new 变量类型[]{内容1, 内容2, ...}
int[] arr4 = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
//(最方便,也是用的最多的)
//变量类型[] 数组名 = {内容1, 内容2, ...}
int[] arr5 = {1, 2, 3, 4, 5, 6};
- 数组的长度
数组名.Length
arr.Length
- 获取数组元素
arr[0]
- 遍历数组
for (int i = 0; i < array.Length; i++) {
Console.WriteLine(array[i]);
}
- 增加数组的元素
// 数组初始化以后 是不能够直接添加新的元素的
int[] array2 = new int[6];
//搬家
for(int i=0;i<array.Length; i++) {
array2[i]= array[i];
}
array = array2;
array[5] = 999;
- 删除数组的元素
// 数组初始化以后 是不能够直接删除元素的
// 搬家的原理
int[] array3 = new int[5];
//搬家
for(int i=0;i< array3.Length; i++) {
array3[i]= array[i];
}
array = array3;
二维数组的声明
//(用的比较少)
//变量类型[,] 二维数组名;
int[,] arr1;
arr1 = new int[5, 5];
//(比较严谨)
//变量类型[,] 二维数组名 = new 变量类型[行, 列];
int[,] arr2 = new int[3, 3];
//(比较严谨)
//变量类型[,] 二维数组名 = new 变量类型[行, 列]{{0行内容1, 0行内容2, ...}, {1行内容1, 1行内容2,...}}
int[,] arr3 = new int[3, 3] { {1, 2, 3},
{4, 5, 6},
{7, 8, 9} };
//变量类型[,] 二维数组名 = new 变量类型[,]{{0行内容1, 0行内容2, ...}, {1行内容1, 1行内容2,...}}
int[,] arr4 = new int[,] { {1, 2, 3},
{4, 5, 6},
{7, 8, 9} };
//(最方便,也是用的最多的)
//变量类型[,] 二维数组名 = {{0行内容1, 0行内容2, ...}, {1行内容1, 1行内容2,...}}
int[,] arr5 = { {1, 2, 3},
{4, 5, 6},
{7, 8, 9} };
- 二维数组的长度
得到多少行
array.GetLength(0)
得到多少列
array.GetLength(1)
- 获取二维数组的元素
array[0, 1]//第一行第二列
- 遍历二维数组
for (int i = 0; i < array.GetLength(0); i++)
{
for (int j = 0; j < array.GetLength(1); j++)
{
Console.WriteLine(array[i, j]);
}
}
- 增加数组的元素
// 数组初始化以后 就不能在原有的基础上 直接添加新的元素的或者删除
int[,] array2 = new int[3, 3];//原来的数组是2行3列
for (int i = 0; i < array.GetLength(0); i++)
{
for (int j = 0; j < array.GetLength(1); j++)
{
array2[i, j] = array[i, j];
}
}
array = array2;
array[2, 0] = 7;
交错数组(用的极少)
- 基本概念
交错数组是数组的数组,每一个维度的数量可以不同
- 假设int数组的每一个元素是int变量,而int数组的数组,也就是交错数组的每一个元素是一个int数组
注意:二维数组的每行的列数相同,交错数组每行的列数可能不同
- 数组的声明
//变量类型[][] 交错数组名;
int[,] arr1;
arr1 = new int[5, 5];
//变量类型[][] 交错数组名 = new 变量类型[行][];
int[][] arr2 = new int[3][];
//变量类型[][] 交错数组名 = new 变量类型[行][]{一维数组1, 一维数组2,...}
int[][] arr3 = new int[3][] { new int[] {1, 2, 3},
new int[] {4, 5,},
new int[] {6} };
//变量类型[][] 交错数组名 = new 变量类型[][]{一维数组1, 一维数组2,...}
int[][] arr4 = new int[][] { new int[] {1, 2, 3},
new int[] {4, 5,},
new int[] {6} };
//变量类型[][] 交错数组名 = {一维数组1, 一维数组2,...}}
int[][] arr5 = { new int[] {1, 2, 3},
new int[] {4, 5,},
new int[] {6} };
- 数组的长度
array.GetLength(0)//行
array[0].Length//第一行的列数
- 获取交错数组的元素
array[0][1]
值类型和引用类型
分类
值类型:- 无符号: byte,ushort,uint,ulong
- 有符号: sbyte,short,int,long
- 浮点数: float,double,decimal
- 特殊: char,bool
- 枚举: enum
- 结构体: struct
引用类型:string、数组、class、interface、委托
区别
- 使用上的区别
int a = 10;
int[] arr = new int[] {1, 2, 3, 4};
int b = a;
//引用赋值会指向同一个内存空间,值是开辟两个内存空间
int[] arr2 = arr;
Console.WriteLine("a = " + a + ", b = " + b);//a = 10, b = 10
Console.WriteLine("arr[0]=" + arr[0] + ", arr2[0]=" + arr2[0]);//arr[0]=1, arr2[0]=1
b = 20;
arr2[0] = 5;
Console.WriteLine("a = " + a + ", b = " + b);//a = 10, b = 20
Console.WriteLine("arr[0]=" + arr[0] + ", arr2[0]=" + arr2[0]);//arr[0]=5, arr2[0]=5
- 为什么有以上区别
值类型存储在栈空间 —— 系统分配,自动回收,小而快
引用类型存储在堆空间 —— 手动申请和释放,大而慢
如何判断值类型和引用类型
F12进到类型的内部去查看- 是class就是引用
- 是struct就是值
特殊的引用类型string
string具有值类型的特征,string不可变,当重新赋值时会开辟一个新的空间
string虽然方便,但是有一个小缺点,频繁的改变string时会产生内存垃圾
语句块
命名空间 -> 类、接口、结构体 -> 函数、属性、索引器、运算符重载等(类、接口、结构体) -> 条件分支、循环- 上层语句块: 类、结构体
- 中层语句块: 函数
- 底层的语句块: 条件分支,循环等
我们的逻辑代码写在哪里?
函数、条件分支、循环-中底层语句块中
我们的变量可以申明在哪里?
上、中、底都能申明变量
上层语句块中:成员变量
中、底层语句块中:临时变量
变量的生命周期
在中底层声明的临时变量(函数、条件分支、循环语句块等),语句块执行结束,没有被记录的对象将被回收或变成垃圾值类型:被系统自动回收
引用类型:栈上用于存地址的房间被系统自动回收,堆中具体内容变成垃圾,待下次GC回收
想要不被回收或者不变成垃圾,必须将其记录下来
如何记录?
在更高级记录或者使用静态全局变量记录
结构体中的值和引用
结构体本身是值类型前提:该结构体没有作为其他类的成员
在结构体中的值,栈中存储值具体的内容
在结构体中的引用,堆中存储引用具体的内容
引用类型始终存储在堆中
真正通过结构体使用其中引用类型时只是顺藤摸瓜
类中的值和引用
类本身是引用类型在类中的值,堆中存储具体的值
在类中的引用,堆中存储具体的值
值类型跟着大哥走,引用类型一根筋。意思就是值类型如果在类中的话就在堆里,如果在结构体中的话就在栈里
数组中的存储规则
数组本身是引用类型值类型数组,堆中房间存具体内容
引用类型数组,堆中房间存地址
结构体继承接口
利用里氏替换原则,用接口容器装载结构体存在装箱拆箱函数
ref和out
目的是为了改变传入的参数自己的值ref使用范例:
//函数参数的修饰符
//当传入的值类型参数在内部修改时 或者引用类型参数在内部重新申明时,外部的值会发生变化
static void ChangeValueRef(ref int value)
{
value = 3;
}
static void Main(string[] args)
{
int a = 1;
ChangeValue(ref a);
Console.WriteLine(a);//3,没有用ref时打印出来是1
}
out使用和声明和ref一模一样
区别:
- ref传入的变量必须初始化 out不用
- out传入的变量必须在内部赋值 ref不用
简单来说,ref就是引用,out就是指针
还有一种形容是买票上车和上车买票
变长参数
关键字params举例:函数要计算n个整数和
static int Sum(int a, int b,...)
用变长参数关键字params
static int Sum(params int[] arr)
{
int sum = 0;
for (int i = 0; i < arr.Length; i++)
{
sum += arr[i];
}
return sum;
}
注意:
- params关键字后面必须是数组,意味着只能是同一种类型的可变参数
- 数组的类型可以是任意类型
- 函数参数可以同时有params关键字修饰的参数和别的参数
- 函数参数中最多只能出现一个params关键字,并且一定是在最后一组参数
- 是一个可变参数数组,它要求你提供一个数组或多个对象。即使你没有传入任何参数,也必须按照
params的要求提供一个参数:即null或者空数组。否则,编译器会提示你需要传递一个参数。
参数默认值
有参数默认值的参数一般称为可选参数作用是可以不传入参数,不传的话就会使用默认值作为参数的值
例子:
static void Speak(string str = "我没什么话可说")
{
Console.WriteLine(str);
}
static void Main(string[] args)
{
Speak();//我没什么话可说
Speak("112233");//112233
}
注意:
- 支持多参数默认值,每一个参数都可以有默认值
- 如果要混用,可选参数必须写在普通参数后面
重载
- 重载和返回值类型无关,只和参数类型,个数,顺序有关
- 调用时,程序会自己根据传入的参数类型判断使用哪一个重载
例子:
//初始函数
static int CalcSum(int a, int b);
{
return a + b;
}
//参数数量不同
static int CalcSum(int a, int b, int c)
{
return a + b + c;
}
//参数数量相同,返回类型不同
//初始函数
static float CalcSum(float a, float b)
{
return a + b;
}
//参数数量相同,参数类型不同
static float CalcSum(int a, float f)
{
a + f;
}
//参数数量相同,参数顺序不同
static float CalcSum(float f, int a)
{
return f + a;
}
//ref和out(相当于传入的是变量的地址,而不是变量的值)
//不能同时在一个地方修饰
static float CalcSum(ref float f, int a)
{
return f + a;
}
//可变参数
static float CalcSum(int a, int b, params int[] arr)
{
return 1;
}
//参数默认值不算重载
结构体
任意变量的数据集合,可以自定义基本概念
是数据和函数的集合在结构体中,可以声明各种变量和方法
作用:用来表现存在关系的数据集合,比如学生、动物、人类等等
基本语法
- 结构体一般写在namespace语句块中
- 结构体关键字struct
- 不写访问修饰符的话默认就是private
例子:
struct Student
{
//变量
//在结构体中声明的变量不能直接初始化
//变量类型可以是任意类型,包括结构体,但是不能是自己的结构体
private int age;
private bool sex;
private string name;
//构造函数(可选)
//函数方法
//表现这个数据结构的行为
public void Speak()
{
//函数中可以直接使用结构体内部声明的变量
Console.WriteLine("我的名字是{0},我今年{1}岁", name, age);
}
}
结构体的使用
class Program
{
static void Main(string[] args)
{
//变量类型 变量名
Student s1;
s1.Speak();
}
}
结构体和类的区别
结构体声明的变量不能直接初始化,不能声明无参构造函数,是值类型
类的变量可以直接初始化,可以声明无参构造函数,是引用类型
构造函数
- 没有返回值
- 函数名必须和结构体名相同
- 必须有参数
- 如果声明了构造函数,那么必须在其中对所有变量数据初始化
- 可以重载(比较少用,一般都是所有属性一起赋值)
struct Student
{
//.....
public Student(int age, bool sex, int number, string name)
{
this.age = age;
this.sex = sex;
this.number = number;
this.name = name;
}
}
class Program
{
static void Main(string[] args)
{
Student s2 = new Student(18, true, 2, "柠凉");
s2.Speak();//我的名字是柠凉,今年18岁
}
}

浙公网安备 33010602011771号