C#知识点

基本知识

被class修饰的叫做类

  • Console 控制台
    WriteLine(); ReadLine(); ReadKey();

三种注释:单行// 多行/* */ 概要 ///

  • int 整数
  • float 单精度小数(可以存储整数),在用于存储小数时需要在数字末尾加f
  • double 双精度小数
  • char 字符
  • string 字符串
  • Bool 布尔型 有True 和False 两个值
  • 对数组有 变量名.Length属性,是这个数组的元素数量

变量分为 值类型引用类型

  • 值类型即直接存储一个数值,存储于栈空间中
  • 引用类型类似于指针,存在于堆空间中,如int[] intA = new int[]{*,*,*,*,*}; int[] intB = intA; 可以认为单单数组名或者数组名[] 都是一个指针变量,因此上例中intB和intA指向同一段内存空间,类似于C语言的数组名字

字符串变量的特点:

  1. 字符串是引用类型(相当于C语言 char c[])的,变量名下只存储了一个字符串所在堆空间的地址
  2. 字符串是不可变的,对一个字符串的值进行修改时原值不会消失,而是重新开辟一块空间存储新值(连续存储)
  3. 字符串可以看作是自读的字符数组(类似C语言)

运算符和C语言一样:

  • 占位符:在字符串里加入{},为数字,指的是用后面提供的第几个参数,从0开始计算
  • 转义符\:\b退格 \t制表符 \n换行符 "英文双引号 @用在字符串的前面,作用:取消\在字符串内的转义作用 和 将字符串按原格式输出

隐式类型转换无感使用 显式类型转换为(目标类型名) Convert类型转换,对如string和int之间进行类型转换
常量声明关键字: const 数据类型 常量名 = 值,多用大写

if-else for{;;} while do-while switch-case-default 三种结构均是条件满足则执行条件下的语句(块)
break; continue;
关键字

一维数组

  • 数组定义数据类型[] 变量名;
  • 数组初始化数组名 = new 数据类型[\*];
  • 声明时初始化数据类型[] 数组名 = new 数据类型[\*];
        声明 初始化 赋值 数据类型[] 数组名 = new 数据类型[\*] {\*,\*,\*,\*,\*};
        数据类型[] 数组名 = new 数据类型[ ] {\*,\*,\*,\*,\*};
        单个赋值/取值 数组名[\*] = \*;

二维数组

  • 数组定义数据类型[,] 变量名;
  • 数组初始化数组名 = new 数据类型[\*,\*]; //两个*分别是行数和列数
  • 声明时初始化数据类型[,] 数组名 = new 数据类型[\*,\*];
        声明 初始化 赋值 数据类型[,] 数组名 = new 数据类型[\*,\*] { {\*,\*,\*,\*,\*},{\*,\*,\*,\*,\*},};//两层大括号
        数据类型[,] 数组名 = new 数据类型[, ] {\*,\*,\*,\*,\*};
        单个赋值/取值 数组名[\*,\*] = \*;
  • 对于没有进行赋值的元素系统自动给初始值(int float double 0 string NULL bool False)
  • 数组元素遍历: for循环(循环语句)//多维数组则可以使用嵌套实现
    foreach(数组数据类型 临时变量 in 数组名){...} //该语句效果:将数组内各个元素依次赋值给临时变量并在每赋一个值后都运行一次后面的语句块,在语句块内对临时变量进行操作即可获得或者使用每个元素

函数(方法)

Pascal命名法
 每个单词的首字母大写,其余字母小写,多用于给类或者函数命名
声明
 static 返回值类型 函数名([参数列表])
 形式参数与实际参数
 return关键字 不用括号,void函数不用return
函数重载
 函数名称相同而参数列表不同,调用这类函数时,会根据不同的参数,自动选择合适的函数重载形式,参数个数和类型只能有一个不同
高级参数
 普通参数:把调用时提供的数值赋值给被调用函数的参数(前者提供的值是实际参数,后者是形式参数,相当于一个变量)
ref参数(引用传递)
 在形式参数前加ref关键字:Fun(ref 变量名); static void Fun(ref 参数的数据类型 参数名){…} 可以理解为ref为取地址(?),这样就可以通过更改形参的值来改变实际参数了
out参数
 一个函数中需要返回多个不同的值时需要用到out参数
 static声明一个函数,参数列表里声明一些作为返回值使用的参数,在这些参数前面加上out关键字,在调用时在对应位置加上同类型变量,这些变量需要事先声明,在调用时,同样需要在这些变量名前加out关键字,这样可以实现被调用函数中的值(形参)回传给对应的实参,以此实现返回多个类型返回值的效果,如:

int a; int b; int c; //ab为提供的两个参数,c为存储运算结果的变量
add(a,b,out c); //即可将ab的和存进c
static void add(int a,int b,out int c)
{
    c = a + b;
}

递归调用
 自己调用自己

进阶部分

字符串操作常用方法

字母变大写/小写 字符串名字.ToUpper();/ToLower();
将字符串转换成大写形式,仅对字母有效,且不改变原变量,仅仅返回改变后的结果,需要专门保存
用法:字符串变量.ToUpper()/ToLower; 即ToUpper是字符串变量的一个方法名

字符串比较(是否相同) 字符串名字.Equals(string);
用法:字符串A.Equals(字符串B);

字符串分割 字符串名字.Split(char[]);
用法:字符串变量.Split(作为分隔符的字符数组);,返回值是一个字符串数组(string[]),需要声明一个数组来接收这个返回值,分隔符将被丢弃分隔符可以是字符数组也可以仅仅是一个字符

字符串截取 字符串名字.Substring();
用法:string.Substring(开始截取的位置角标);
string.Substring(开始截取的位置角标,截取长度);

字符串查找 字符串名字.IndexOf(string);``字符串名字.LastIndexOf(string);
查找某个字符串在整个字符串中首次/最后一次出现的位置,缺省值-1

判断是否包含某字符串 字符串名字.Contains(string);

判断是否以某字符串开始/结束 字符串名字.StartsWith(string);
字符出串名字.EndsWith(string);

字符串部分替换 字符串名字.Replace(string1,string2);将字符串中的string1替换成string2

去除字符串中前后空格 字符串名字.Trim();返回处理后的字符串
去掉字符串中前面的空格 字符串名字.TrimStart();
去掉字符串中后面的空格 字符串名字.TrimEnd();

判断字符串是否为Null或者空 string.IsNullOrEmpty(string); //Null不占空间而为空是占用空间的。
IsNullOrEmpty();是对string这个数据类型的方法,所以使用时应写 string.IsNullOrEmpty(变量名字);返回值为True表示为空或为Null

StringBuilder:字符串构建器,是一个类,该类型的变量是引用类型,它相当于一个字符串变量,在运行中一直操作同一块空间,执行效率也高于string类型的字符串变量。依赖System.Text命名空间,程序开头应有using System.Text

  • 使用:
  1. 创建StringBuilder类型变量
    StringBuilder sb = new StringBuilder();
  2. 追加数据
    sb.Append(); 追加数据
    这个方法有很多重载方法,所以可以提供各种类型的参数进行追加
    sb.ToString(); 转化为string字符串
    Console.WriteLine(sb.ToString()); 打印出来
  3. 清空数据
    sb.Clear();

StringBuilder效率测试(Stopwatch类的使用)

  1. Stopwatch类
    Stopwatch,秒表计时器,用来记录程序运行的时间,依赖System.Diagnostics命名空间
  2. 创建Stopwatch类型对象
Stopwatch sw = new Stopwatch();
sw.Start(); //计时器开始
sw.Stop(); //计时器结束
sw.Elapsed; //开始到结束之间的时长,注意这是一个属性,在进行过开始和结束后通过这个获取计时的时长
  1. 在计时器开始和结束之间分别进行一次对string和StringBuilder类的数据追加操作,然后输出sw.Elapsed

枚举类型

  • 枚举类型的定义:
  1. 定义在namespace内,对该namespace下的所有程序有效,用于在团队协作中对同一类事物的各个成员事先规定统一的称呼,各个值之间用 , 隔开
  2. 语法:
public enum 枚举名
{
    值1,
    值2,
    值N
}
  1. 例子:以一周七天的称呼为例
namespace First_C_Sharp
{
    public enum week
    {
        周一,
        周二,
        周三,
        周四,
        周五,
        周六,
        周末
    }
    internal class Program
    {
        static void Main()
        {
            week wk = week.周一;
            Console.WriteLine(wk);
            Console.ReadKey();
            return;
        }
    }
}

结构体

  • struct,是一种值类型,用于封装一些小型变量数据
  • Unity3D 中提供的一些结构体类型数据:Vector3(三维向量),Color(颜色),Quaternion(四元数),Ray(射线)等等
  • 声明:声明在namespace下
public struct 结构体名称
{
    public 数据类型 变量名;
    public 数据类型 变量名;
    public 数据类型 变量名;
}

结构体变量的创建
结构体类型 结构体变量名 = new 结构体类型();
数据的使用或者赋值
结构体变量名.成员变量名(在结构体声明里的变量名)

  • 可以声明结构体类型数组变量!

面向对象基础与类

类、对象、字段、对象使用方法
1、类

  • 具有相同特征的事物归为一类
  • 语法:
[访问修饰符] class 类名 //[访问修饰符]可有可无,非必要,有默认值
{
    类的描述信息 //描述这类事物的特征(字段、属性、方法)
    public int intA; //相当于对象的“属性”
    public string name; //这里定义的就是两个“字段”
}
  • eg:创建一个Apple类
  • 解决方案上右击–添加–类–命名

2、对象

  • 一个具体的个体就是一个对象,在程序中通过实例化出来的就是对象
  • 语法:类名 对象名 = new 类名();
       Apple a1 = new Apple(); new可以理解为实例化
  • 类 和 对象 的关系 类似结构体
  • 对象 是 类 的一个实例

3、字段

  • 字段 是变量在 面向对象的 类 中的称呼,作用和变量是一样的
  • 语法:public 数据类型 字段名

字段属性

1、访问修饰符

public 公开的,可以通过对象名.字段名 进行访问
private 私有的,不能通过对象名.字段名 进行访问

2、属性及其语法 保护字段的手段

  • 将需要进行操作合法性校验的字段设置为private,同时在class声明的语句块内写:
public 数据类型 属性名(一般可以命名为字段名的首字母大写的形式)
{
    get{return intA;} //通过对象名.属性名读取intA
    set{intA = value;} //通过对对象名.属性名赋值实现对intA的间接赋值,value是调用函数提供的参数(也就是想要给intA赋予的值)
}
  • get{…}和set{…}实质上是两个方法,分别在读取 对象名.属性名 时和对 对象名.属性名 赋值时被调用,这样我们就可以通过在这个方法内进行一些操作最终把我们要保护的字段的值反馈给上层调用函数或者把上层调用函数提供的参数赋值给我们要保护的字段

3、命名空间 namespace

  • 对代码文件进行分类管理
  • 语法:定义命名空间:
namespace 空间名称
{
    类
}
  • 引用命名空间
    using 空间名称

面向对象基础 三种方法

1、普通方法

  • 就是普通函数,可以在类内声明一些方法,然后使用实例化对象的这些方法进行调用而实现一些功能,例如系统提供的 Console.WriteLine(); 其中WriteLine就是对象Console的一个方法
  • 语法:
访问修饰符 返回值 方法名(参数列表)
{
    方法的具体功能代码;
}
  • 这些方法可以直接对类的private字段进行访问

2、构造方法

  • 具有“构造”功能/作用的方法,对实例化出来的对象进行初始化
public 类名(对应需要初始化的字段的名称的参数表) //必须用public,不能用private,没有返回值,不能写void,名称和类名一致,可以有重载方法,写在类的声明语句块里
{
    构造函数代码
}
  • this关键字,代表当前类的对象,当参数表里的参数名和类里对应字段重名时可以使用this.字段名指代要被赋值的字段,而形式参数则直接用参数名指代
  • 使用 类名 对象名 = new 类名(); 时,默认自动调用类里的构造函数,所以括号内要提供参数。如果声明类中不写构造方法,在编译时系统会自动为类添加一个空构造方法,写了构造方法则系统不再自动添加,通常会保留一种空参数的构造方法用于实例化空对象

3、析构方法

  • 用于清理一个对象
  • 语法: 不带参数表不带返回值,也没有访问修饰符,由系统自动调用,可以不写
~类名()
{
    析构方法代码体;
}

面向对象基础 堆栈关系

1、对象的赋值(对象间的赋值)

  • 对象的赋值也是引用类型
    类名 对象1名 = new 类名();
    类名 对象2名 = 对象1名;
  • 结果对对象2进行修改相当于对对象1进行修改

2、new关键字的作用

  • 在内存(堆空间)中开辟了一块空间
  • 在开辟出来的这个空间中创建对象数据
  • 调用对象的构造方法进行对象的初始化

3、面向对象编程:游戏里的一些事物都是对象,这些对象具备一些属性(字段)和能力(方法),提前做好规划(定义字段和定义普通方法),可以直接调用以修改其属性和使用其能力
总结:类和结构体类似,只是结构体仅存储一些数据,而类里还可以添加一些方法进行调用,结构体是值类型,类是引用类型

对象继承

1、什么是继承

  • 面向对象开发有三大特性/特征/特点:**封装、继承、多态**
  • 将一堆类中的一些共有成员单独抽取出来作为父类,然后这一堆类继承这个父类,共享父类资源,这就叫做继承

2、好处

  • 优化结构,使类与类之间产生关系,提高代码复用性,便于阅读,为多态提供前提

3、语法

  • 如游戏开发中把玩家操作的角色定义为“英雄”并且抽象出一个Hero类,然后可供选择的英雄类继承这个Hero类,再细分编写各个英雄类(独有信息用字段表示并使用属性进行封装,技能信息使用普通方法封装等),而公共的信息如名字、称号、背景信息、基础属性等在父类中进行定义
class 子类:父类
{
    //类成员
}

4、类视图

  • 在当前项目上右键–视图–查看类图
     可以看到类的继承关系图,以及类中的成员概况。可以多加留意字段、属性、方法和各自特有的图标

继承之构造方法

  在父类中构建了构造方法以后,在子类中的构造方法的括号之后可以加上:base(),并把构造方法中需要传递给父类的数据(形参名)依次写入括号,这相当于使用base()对父类的构造方法进行访问。只要在访问父类构造函数时传参顺序正确即可,子类构造方法里的参数表顺序并非必须和父类一致
  也可以在语句块内用 base.属性名/字段名(如果未被保护)对父类的字段进行赋值。

继承之成员继承

1、继承的效果
子类中可以访问到父类中定义的成员(字段、属性、方法),这个访问是有范围的

2、字段
由于大多数情况下字段都会使用private修饰,在子类中无法访问。使用public修饰的字段可以在子类中通过base.字段名进行访问

3、属性
大多数属性常用public修饰,可以通过base.属性名进行访问

4、普通方法
public修饰的可以通过base.方法名进行访问使用

5、构造方法

  • 绝大多数情况下使用public修饰,这种情况下可以使用base() 的形式进行访问
  • 子类并没有真正继承父类的构造方法
  • 另外,对子类实例化的对象也可以通过对象名.字段/属性/方法() 的方式使用父类的成员
  • 子类实例化的对象所占用的空间内包含了一个父类的实例化,这就是继承的实质

6、连续的继承关系
最下级的子类的实例也可以通过 对象名.成员名 来使用父类甚至更上层的父类的成员

多态

在继承关系的前提下,不同子类实例化的对象调用父类中相同的方法而实现不同的功能,这就叫做多态

1、虚方法

  • 在父类中使用virtual关键字修饰的方法,就是虚方法,在子类中使用override关键字对该虚方法进行重写
  • 语法:
父类 子类
public virtual 返回值类型 方法名() public override 返回值类型 方法名()
{ {
    方法的代码体; 方法的代码体;
} }
  • 理解:在继承中的函数重载
    在实际使用中可以在父类的虚方法中实现一些共有功能,如初始化等工作,然后在子类中重写的方法中调用一次父类的虚方法(使用base.方法名()进行调用),重写并非必须进行的,如果没有重写那么通过对象名.方法名进行操作会直接调用父类的方法

面向对象编程的六大原则
①单一职责原则;②开闭原则;③里氏转换原则;④依赖倒置原则;⑤接口隔离原则;⑥迪米特原则

  • 里氏转换原则:
    ①子类可以直接赋值给父类变量;
    父类名 变量名 = new 子类();使用此语句后,通过 变量名.方法名(); 可以实现子类对象.方法名(); 的功能。如果该方法被virtual修饰并在子类中进行了重写,执行后也会把子类中的代码实现
    ②子类对象可以调用父类中的成员,但是父类对象永远只能调用自己的成员;
    ③如果父类对象中装的是子类对象,可以将这个父类对象强转为子类对象
    如①中,可以new一个子类对象,并把(子类名)父类对象名赋值给新建的子类对象,这样新的子类对象就是一个已经初始化完成的子类。

  • 用于对象间强制转换的关键字is和as
    is:转换成功返回True,否则返回False,写法:父类对象 is 子类名 将会对父类对象是否可以转换为子类对象进行判定
    as:同上,可以转换返回转换结果,不可以转换则返回NULL

2、抽象类

  • 虚方法->抽象方法
  • 当父类中的虚方法虚到完全无法确定方法体的时候,就可以使用抽象方法的形式来表现
  • abstract放在返回值类型前修饰方法,且该方法没有方法体,即abstract 返回值类型 方法名();
  • 抽象类:在定义类的关键字class前加abstract,定义子类继承抽象类,并使用override继承抽象方法。父类中定义的抽象方法必须在子类内实现
    ①抽象类中可以没有抽象方法,但是抽象方法必须用在抽象类中
    ②抽象类不能实例化,因为其中可能包含无意义的抽象方法
  • 使用场景:
    父类的方法不知道如何实现或者父类不需要有默认实现且父类不需要实例化,使用抽象方法
    父类需要实例化,使用虚方法

3、多态之接口语法

  • 语法:抽象类->接口
  • 当抽象类中所有的方法都是抽象方法的时候,就可以把这个抽象类用另一种形式来表现,这种形式叫接口
  • 使用interface关键字定义接口,没有class关键字,接口一般采用“IXxxx”这种方式进行接口的命名,在一堆脚本中通过名字判断,I开头的都是接口
  • 接口中不能包含字段,但是可以包含属性
  • 详情:C#中有常规属性(get set)和自动属性。当属性的get和set只是完成字段的取值和赋值操作,而没有任何附加的逻辑代码时,可以使用自动属性,如public int Age { get; set; } 写好后C#编译器会为自动属性添加对应的字段。接口中只能使用这种自动属性,子类中则需要像重写方法一样在自己内部重写属性和对应的字段
  • 接口定义的方法不能有方法体,全是抽象方法,但是这些抽象方法不需要用abstract修饰
  • 接口的成员不允许添加访问修饰符,默认都是public
  • 一个类可以继承多个接口,在各个接口之间用 , 隔开即可
  • 接口也可以继承多个接口
  • 接口存在的作用是在需要添加一些具有特殊功能的实例时可以通过类对接口的继承来添加更多成员和实现

五种访问控制符

public 公开访问,继承其所在类的子类都可以直接访问到
private 私有访问,仅仅能在本体内部进行访问,子类不能访问到
protected 受保护的访问权限,能在当前类和子类中进行访问,不能在实例对象中访问到
internal 内部访问,只能在程序集(项目)中访问,在同一个项目中internal和public效果一样。项目:解决方案的内一级,每项是一个项目
protected internal 内部保护访问权限
修饰类的访问控制符:public 和 internal,后者是默认值
修饰类成员的访问修饰符:五种都可以

静态成员关键字static

1、关键字
static 静态,由 static 修饰的是静态**,静态成员先于类本身的存在而存在,且需要用类名.成员名进行调用,static关键字写在访问修饰符之后
2、静态字段

  • 要通过类名.静态字段的方式进行访问,不能使用对象名.字段名的方式进行访问,例如 对象名.字段名,可以直接指代在定义时赋给的值,也就是直接调用在声明类时为字段定义的值。静态字段只会在内存中存储一份
  • 静态字段重新赋值的方式:在类声明之外进行类名.字段名 = 新的值;

3、静态属性

  • 用于对被static修饰的字段进行封装,并保证静态字段的合法性。静态属性通过 类名.静态属性名 进行访问
  • 静态属性不能用于封装非静态字段,因为静态的类成员先于类存在于内存中

4、静态普通方法
静态方法中不能调用非静态方法(存在顺序)

5、静态构造方法
用于初始化静态成员,一个类中仅一个静态构造方法,可以在静态类和非静态类中声明。静态构造方法在引用任何类成员时,或者创建第一个实例时在其之前执行完成类中静态成员的初始化

6、静态类

  • 所有成员都是静态成员时可以把类声明为静态类
  • 静态类不能实例化对象

静态之设计模式

1、设计模式
针对某些问题总结的经验、解决方案。前人已经总结过很多设计模式,如《GoF23种设计模式》

2、单例设计模式

  • 设计一个类的时候,需要保证程序运行期间仅仅存在一个实例对象
  • 用于非静态类中,在静态类中使用无意义
  • 步骤:
    ①声明一个静态私有的当前类的类型的字段
    private static ClassName instance;
    ②创建私有无参构造方法,保证外部无法实例化这个类
    private ClassName() { }
    ③创建一个静态方法,用于创建此类的唯一对象
public static ClassName Instance()
{
    if(instance == null)
    {
        instance = new ClassName();
    }
    return instance;
}
  • 需要使用这个对象时,在需要的地方写:
    对象名 = ClassName.Instance();

  • 嵌套类:在一个类定义的内部再定义一个类,具备形式上嵌套的关系(通过外层类名.内层类名.成员名()进行访问,但是内层类仍具备完整的类的功能和地位
    匿名类var 对象名 = new{ Name = "..." , Age = 100 }; 用于存储一组只读属性
    密封类:由sealed关键字修饰过的类不可以被继承,也就是说不能有子类,这样的类称之为密封类
    Object类:在C#中,Object类是所有类的父类,无论是C#自带类还是用户创建的类,都直接或间接地继承这个类

  • 另外,object是类型,两者差不多,使用小写即可

  • C#中有一个内置的Object类,其中有一个ToString方法,直接使用对象调用这个方法会返回对应类所在命名空间和类名,为了辅助进行开发,通常在自己的类中重写ToString方法

  • 需要在非WriteLine中使用占位符时,写string.Format(); 其中括号像WriteLine里一样写即可

装箱与拆箱

装箱:值类型转换成引用类型
拆箱:引用类型转换成值类型
 在两者之间存在继承关系的时候,才可能出现装箱与拆箱操作

  • Copyrights © 2023-2025 LegendLeo Chen
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信