C#基础结构与数据类型
C#基础结构与数据类型
一、C#基本结构
C#包含以下主要结构:
命名空间 Namespace 这是用于隔开不同文件中相同变量名,当有一个项目中有多个文件或者作者时使用
主要Class 用于运行程序
Class方法 用于实现程序的功能
Class属性 给Class中添加对应内容
Main方法 C#的main函数,但是在Unity中不使用
注释用于注释
示例一:C#主要结构
//以下是主要引入内容,
//(using xxx)可以使引入`xxx`命名空间内的所有类、结构体、枚举和接口
using System; // 主要用来获取基础数据如(DateTime.Now)
using System.Collections; // 用于支持协程
using System.Collections.Generic; // 用于支持泛型集合
using UnityEngine; // 用于支持所有Unity的核心功能
using System.Text; // 用来高性能字符串处理
using Unity.VisualScripting; // 用来与可视化节点联动
namespace sample //定义命名空间
{
public class Day9 : MonoBehaviour //主要Class MonoBehaviour是为Unity中的物体实现功能的主要核心
//Unity的固定函数,函数名一定相同才能执行
{
//以下是一些在Unity中默认的class方法
// 对象在创造出来的瞬间执行
public void Awake()
{
Debug.Log("Awake");
}
// 第一帧执行之前执行一次
public void Start()
{
Debug.Log("Start");
}
// 每帧执行
public void Update()
{
// Debug.Log("Update");
}
// 这个脚本被销毁的时候执行
public void OnDestroy()
{
Debug.Log("OnDestroy");
}
}
// C#的main函数,在Unity中用不到
// class Program
// {
// static void Main(string[] args)
// {
// Console.WriteLine("Hello");
// }
// }
}
二、基础数据类型
1.基础数据类型
| 数据类型 | 描述 | 作用 | 默认值 | 取值范围 |
|---|---|---|---|---|
bool |
布尔型 | 表示真或假 | False | true或false |
byte |
字节型 | 存储8 位无符号整数 | 0 | 0 到 255 |
char |
字符型 | 字符类型,通常用于存储 ASCII 字符 | ‘\0’ | U +0000 到 U +ffff |
int |
整型 | 存储数字 | 0 | -2,147,483,648 到 2,147,483,647 |
short |
短整型 | 存储数字 | 0 | -32,768 到 32,767 |
long |
长整型 | 存储数字 | 0L | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 |
float |
浮点型 | 单精度浮点数,存储带有小数点的数字 | 0.0F | -3.4 x 1038 到 + 3.4 x 1038 |
double |
双浮点型 | 双精度浮点数,存储带有小数点的数字 | 0.0D | (+/-)5.0 x 10-324 到 (+/-)1.7 x 10308 |
void |
无类型 | 无类型 | 0 | 无 |
2.sizeof
如需得到一个类型或一个变量在特定平台上的准确尺寸,可以使用 sizeof 。表达式 sizeof(type) 产生以字节为单位存储对象或类型的存储尺寸。
示例二:获取变量类型占用多少字节
using UnityEngine;
using System;
public class SizeTest : MonoBehaviour
{
void Start()
{
Debug.Log($"int 占用: {sizeof(int)} 字节"); // 输出: 4
Debug.Log($"float 占用: {sizeof(float)} 字节"); // 输出: 4
Debug.Log($"double 占用: {sizeof(double)} 字节"); // 输出: 8
Debug.Log($"bool 占用: {sizeof(bool)} 字节"); // 输出: 1
Debug.Log($"char 占用: {sizeof(char)} 字节"); // 输出: 2 (Unicode)
}
}
3.var
var 可以自动判断类型
例如var autoType = 10;//autoType会自动变为int
4.所有类型的父类
示例:object objectValue = IntValue;object是所有类型(包括值类型和引用类型)的父类
任何数据类型都可以赋值给 object 类型的变量。
装箱
示例(1):装箱
//假设 IntValue 是一个 int 类型(值类型):
int IntValue = 100;
object objectValue = IntValue; // 这里发生了“装箱”
发生了什么?
1.内存迁移:int 原本存储在栈上(速度快,生命周期短)。当它被赋值给 object 时,CLR(公共语言运行时)会在堆上开辟一块新的内存。
2.复制数据:将 int 的值复制到堆上的这块新内存中。
3.包装对象:创建一个包含该值的完整对象头(包含类型信息、同步索引等),此时它不再是一个单纯的数字,而是一个 System.Int32 对象。
4.引用赋值:objectValue 这个变量存储的是堆上那个对象的引用地址(指针)。
示例:图解
栈 (Stack) 堆 (Heap)
+----------------+ +-----------------------+
| IntValue | | [Object Header] |
| (值: 100) | -------> | [Type: System.Int32] | <--- objectValue 指向这里
+----------------+ 复制 | [Data: 100] |
+-----------------------+
拆箱
如果你想把 objectValue 变回 int 进行数学运算,必须进行拆箱。
示例(2):拆箱
// 错误情况:不能直接运算
// int result = objectValue + 10;
// 应该为显式强制转换
int backToInt = (int)objectValue;
int result = backToInt + 10;
三、字符串
// 字符串操作简单
{
// 字符串(String)类型的值可以通过两种形式进行分配:引号和 @引号。
// 在对有Windows地址目录的情况会便于使用(不需要转义符)
String sample1 = "C:\\Windows"; // 此处的(\\)中第一个(\)为转义符
string sample2 = @"C:\Windows"; // @(逐字字符串)将转义字符(\)当作普通字符对待
// 任意类型可以相加,只要那个类型有正确实现的ToString()
string a = "123" + 10 + "abc" + IntValue + StringValue;
// 获取字符串长度
int b = a.Length;
// 访问字符
char c = a[1]; // 但是不能通过这个方法修改,错误的:a[1] = 'b'
// 字符串插值
string d = $"IntValue的值是{IntValue}, FloatValue的值是{FloatValue}";
// int aaa = 10;
// UnityEngine.Debug.Log("aaa的值是: " + aaa + "。");
// UnityEngine.Debug.Log($"aaa的值是:{aaa}。");
// 字符串比对
if (a.Contains("abc"))
{
// 判断 a 有没有包含字符串 "abc"
}
if (a.StartsWith("hello") || a.EndsWith("world"))
{
// 判断 a 的开头和结尾是不是对应字符串
}
// 获取某个字符串第一次出现的位置
int f = a.IndexOf("c");
// 截取字符串
string g1 = a.Substring(1, 3); // 这个3是长度,1开始包括1的后面3个,1,2,3
string g2 = a[1..4]; // 这个4是结束下标,只有1,2,3
// 分割字符串
string h1 = "orange,banana,apple";
string[] h2 = h1.Split(",");
// 大小写转换
string i = "Word";
i = i.ToLower();
i = i.ToUpper();
// 显式的字符串本身也是对象,可以进行上面的所有操作
"word".ToUpper();
// 替换
string l = "hello".Replace("l", "o");
// 去空格
string j = " ab cd e ".Trim();
// 字符串内容相等比较
if (i == j)
{
}
// 字符串创建器,用于频繁加减或处理字符串
StringBuilder stringBuilder = new();
stringBuilder.Append("123");
stringBuilder.AppendLine("abc");
string k = stringBuilder.ToString();
}
四、数组
1.声明数组
主要结构
public datatype[] arrayName;
访问符 数据类型 大小 名称
// 数组
public int[] Array1; // 声明数组public int[] Array1 = { 0, 1, 2, 3, 4, 5, 6 };// 声明含有数据的数组
实例:使用
{
Array1 = null; // 清空数组
Array1 = new int[10]; // 初始化数组,分配内存大小
}
// 二维数组
public int[,] Array2; // 声明矩形数组
public int[][] Array3; // 声明交错数组
实例:使用
public void Array23()
{
//矩形数组(在内存上是一个连续片段)
// (想象为一个Excel表格矩阵:所有的行必须有相同的列数)
Array2 = new int[3, 4]; // 初始化一个3行4列的矩形数组
Array2[0, 0] = 1; // 赋值(第0行0列)
Array2[1, 2] = 5; // 赋值(第1行2列)
// 不能单独改变某一行的长度
// Array2[0] = new int[5]; // 编译报错
//交错数组(在内存上不连续)
// (第一行可以有2个元素,第二行可以有100个元素,第三行可以是 null)
// 1. 首先初始化外层数组(决定有多少行)
Array3 = new int[3][];
// 2. 然后分别初始化每一行(决定每行有多少列,可以不同!)
Array3[0] = new int[4]; // 第0行有4个元素
Array3[1] = new int[2]; // 第1行有2个元素
Array3[2] = new int[100]; // 第2行有100个元素
Array3[0][0] = 1; // 赋值
Array3[1][1] = 99; // 赋值
// 可以随时替换某一行
Array3[1] = new int[50]; // 把第1行换成一个长度为50的新数组
}
2.数组的语法糖
// 数组的语法糖
{
// 获取倒数第几位 Array1[Array1.Length - n]
int a = Array1[^1];
// 获取 index = 2, 3, 4 的数组
int[] b = Array1[2..5];
// 2 ~ 最后
int[] c = Array1[2..];
// 第一个 ~ 4
int[] d = Array1[..5];
}
// 数组的长度不会丢失了
int length = Array1.Length;
// foreach循环
for (int i = 0; i < Array1.Length; i++)
{
}
foreach (int each in Array1)
{
}
// Array类自带大量处理函数
// Array本身也是数组的父类
{
int[] a = { 0, 1 };
int[] b = { 2, 3 };
Array.Copy(a, b, 2); // 复制
Array.Sort(a); // 排序
Array.IndexOf(a, 1); // 查找
}
// 参数数组
F3(Array1);
F3(1, 2, 3, 4, 5, 6);
public void F3(params int[] a) // 处理多个数量的参数
// params用于传递任意数量的参数进入方法
// 传入的内容会被自动编成数组a
{
foreach (int each in a) // 遍历传入的数组a
{
}
}
// params的使用规则
// 正确方法:
//public void F3(string prefix, params int[] a) { }
// 错误情况1:params 后面不能再有其他参数
//public void F3(params int[] a, string suffix) { }
// 错误情况2:不能有两个 params
public void F3(params int[] a, params string[] b) { }
//params本质上还是会创建一个新的数组对象,因此如果在每帧运行的Update() 循环中会造成卡顿。
五、值类型和引用类型
C# 不通过指针来区别对象的类型,而是特定的定义类型生成特定的处理类型
值类型 -> 基础类型 + enum + struct
值类型变量可以直接分配给一个值。
值类型直接包含数据。比如 int、char、float,它们分别存储数字、字符、浮点数。
系统分配内存来存储值。
引用类型 -> class + string + 数组 + object + interface + delegate
引用类型不包含存储在变量中的实际数据,但它们包含对变量的引用。
它们指的是一个内存位置。使用多个变量时,引用类型可以指向一个内存位置。
public class Sample : MonoBehaviour
{
public void F4()
{
// 在C#中,使用class时,我们无法通过不new的方式直接分配栈内存了
Sample day; // 此时day == null,而不是一个对象
day = new Sample(); // 新建day分配内存空间
day = new(); // 如果接受的变量明确知道类型,可以不写new后面的类型
// 值类型一定是直接复制值
int a = 1;
int b = 2;
a = b;
// 引用类型一定是修改引用(类似修改指针指向)
Sample d1 = new();
Sample d2 = new();
d1 = d2; // d1 本来的内存被丢弃
// c#拥有 垃圾回收机制(GC),不需要自己释放内存
Sample d3 = new();
d3 = null;
// 任何情况下,只要 没有任何引用 指向d3的内存,d3就会被加入垃圾回收的队列
// 比如上面
d1 = d2;
d2 = null; // d1 仍然指向 d2,不会被回收
}
public void F5(int a, Sample b)
{
// 同样的,函数的参数也遵守这个规则,a作为基础类型是值传递,b是class为引用传递
}
}
六、this与引用传递
1.this
this 就是自身。
当你在一个类(Class)或结构体(Struct)的内部代码中使用 this 时,它指代的就是正在执行这段代码的那个具体对象。
核心作用是解决名字冲突
当局部变量或参数的名字和成员变量的名字一样时,编译器会优先使用局部变量。如果你想访问成员变量,就必须加上 this. 来明确告诉编译器要的是自己的变量,不是参数。
实例:this
public class Sample : MonoBehaviour
{
public void F1() //声明方法F1
{}
public int IntValue; //外部变量IntValue
IntValue = 123; //给IntValue赋值
public void This() //声明方法
{
// this 代表该对象,在使用this时,能明确重名变量的定义域
int IntValue = this.IntValue; //声明变量IntValue
this.F1(); //此处的 this 为 Sample
}
}
2.引用传递
主要为函数传入外部数据内存地址
实例:引用传递
public void F7(ref int a) //ref 传入已有数据的a内存地址
{
a = 123; //可修改也可不修改
}
public void F8(out int a) //out 传入a内存地址(由于会被强制覆盖,因此无所谓是否初始化)
{
a = 456; // 如果不赋值 a 会报错
}
public class Sample : MonoBehaviour
{
public void Refin()
{
// 如果想通过函数参数直接修改外部的 值类型
// 类似传入指针
int d = 0; //声明d
// ref 必须初始化,错误的:int d;(传入前没有给d任何值)
F7(ref d);
// out 不需要初始化,所以支持两种写法
// 但是必须在函数内赋值
F8(out d);
F8(out int e);
}
}
七、类型转换
1.隐式类型转换
将一个较小范围的数据类型转换为较大范围的数据类型
int a = 123;
float b = a;
2.显式转换
// 安全类型转换
//string str = "123"; //"123"是string类型
int c = Convert.ToInt32("123"); //转为int
//c == 123
| 格式说明符 | 描述 | 示例 | 输出 |
|---|---|---|---|
| C | 货币 | 15.ToString(“C”) | ¥15.00 |
| 15.ToString(“C1”) | ¥15.0 | ||
| 15.ToString(“C3”) | ¥15.000 | ||
| D | 十进制数 | 15.ToString(“D1”) | 15 |
| 15.ToString(“D2”) | 15 | ||
| 15.ToString(“D5”) | 00015 | ||
| E | 科学型 | 15.ToString(“E”) | 1.500000E+001 |
| 1500.ToString(“E”) | 1.500000E+003 | ||
| 0.15.ToString(“E”) | 1.500000E-001 | ||
| 0.0015.ToString(“E”) | 1.500000E-003 | ||
| 0.0015.ToString(“E0”) | 2E-003 | ||
| 0.0015.ToString(“E1”) | 1.5E-003 | ||
| 0.0015.ToString(“E2”) | 1.50E-003 | ||
| F | 固定点 | 15.ToString(“F1”) | 15.0 |
| 15.ToString(“F2”) | 15.00 | ||
| G | 常规 | 2.5.ToString(“G”) | 2.5 |
| N | 数字 | 1500000.ToString(“N”) | 1,500,000.00 |
| 1500000.ToString(“N0”) | 1,500,000 | ||
| 1500000.ToString(“N1”) | 1,500,000.0 | ||
| X | 十六进制 | 15.ToString(“X”) | F |
| 15.ToString(“X3”) | 00F |
八、枚举
枚举是一组命名整型常量。枚举类型使用 enum 关键字声明。
C# 枚举是值类型。也就是说,枚举包含自己的值,且不能继承或传递继承。
实例八(1):枚举主要结构
enum <enum_name>
{
enumeration list
};
其中enum_name 指定枚举的类型名称enumeration list 是一个用逗号分隔的标识符列表
实例八(2):枚举声明与使用
//如果没有显式指定值,枚举的第一个成员默认为 0,后续成员依次自动 +1。如果中间某个成员被显式赋值,后续成员会基于这个新值继续 +1
public enum ANIMAL_TYPE // 声明枚举ANIMAL_TYPE
{
NONE, // 第一个,未指定值 默认是0
DOG = 3, // 显式指定为3 值是3
CAT, // 未指定值 基于上一个(DOG=3)自动+1 值是 4
};
//public enum ANIMAL_TYPE{NONE,DOG = 3,CAT};
public class Sample : MonoBehaviour
{
public void Start()
{
// 枚举
ANIMAL_TYPE t = ANIMAL_TYPE.NONE; // 使用枚举类型声明t
if (t == ANIMAL_TYPE.DOG) // 如果t是动物中的狗
{
}
}
}
九、结构体
结构体是一种值类型,用于存储各种数据类型的相关数据。
结构体没有继承结构体的功能
实例九:结构体主要结构与使用
public struct MyVector2 // 声明结构体MyVector2
{
public float X; // 声明变量X
public float Y; // 声明变量Y
// 也可以写函数
public void Print() //声明函数Print()
{
UnityEngine.Debug.Log($"{X}/{Y}"); //输出字符串X/Y数值
}
}
public class Sample : MonoBehaviour
{
public void Start()
{
// 结构体可以直接定义使用,但是内部变量必须初始化
MyVector2 v1;
// UnityEngine.Debug.Log(v1.X); // 在这行会报错
v1.X = 2;
UnityEngine.Debug.Log(v1.X);
// 使用new会给所有成员变量自动初始化为默认值
// new与不new只有是否初始化,并不影响 内存位置分配/是否是值类型 等问题
MyVector2 v2 = new();
UnityEngine.Debug.Log(v2.X); // 不会报错
// v1 的成员变量只复制了 v2 的值
v1 = v2;
// 结构体一般用在 小数据/复制方便的数据 上
Color color; // 颜色
Rect rect; // 范围
Quaternion quaternion; // 四元数(Unity代表 旋转 的数据)
Vector2 vector; // 向量
}
}