笔记相关–C#入门到进阶
前记:自己学C#时候的一些笔记,内容可能较多,在vscode里面写的,3000行左右。
---------------------------目录-------------------------
面向对象–封装
类和对象
成员变量和访问修饰符
成员方法
构造函数和析构函数
成员属性
索引器
静态成员
静态类和静态构造函数
拓展方法
运算符重载
内部类和分部类
面向对象–继承
继承的基本规则
里氏替换原则
继承中的构造函数
万物之父和装箱拆箱
密封类
面向对象–多态
Vob
抽象类和抽象方法
接口
密封方法
面向对象关联知识点
命名空间
万物之父中的方法
String
StringBuilder
结构体和类的区别
构造体和接口的区别
面向对象的三大特征:
封装+继承+多态
封装:用程序语言形容对象
继承:复用封装对象的代码,复用现成代码
多态:同样行为的不同表现,儿子继承父亲的基因但是有不同的行为表现
面向对象的七大原则:
开闭原则、依赖倒转原则、里氏替换原则、单一职责原则、接口隔离原则、合成复用原则、迪米特原则
命名:帕斯卡命名法:首字母大写
驼峰命名法
new分配的空间在堆上,如果没有用new则只是在栈上分配了一个堆的空引用
构造函数:
特别注意点:
class Person
{
public string name;
public int age;
public Person():this.(“Haoye”,10)
{
name="";
age=0;
}
public Person(string _name):this()
{
name=_name;
}
public Person(string _name,int _age):this(_name)
{
name=_name;
age=_age;
}
}
析构函数:
当引用类型的堆内存呗回收时,会调用该函数;
对于需要手动管理内存的语言(如c++),需要在析构函数中做一些内存回收处理
但是c#中存在自动垃圾回收的机制GC
基本语法:
~类名{ }
析构函数时当垃圾真正被回收的时候才会调用该函数
垃圾回收机制:
垃圾回收的过程是遍历堆上(Heap)的动态分配的所有对象
通过识别他们是否被引用来确定哪些对象是垃圾,哪些对象仍要被使用
所谓的垃圾就是没有被任何变量、对象引用的内容
垃圾就是需要被回收释放
垃圾回收算法:
引用计数(Reference Counting):
标记清除(Mark Sweep):
标记整理(Mark Compact):
复制整合(Copy Collection):
注意:
Gc只负责堆(Heap)中的垃圾回收
引用类型都是存在堆上(Heap)的,所以它的分配和释放都是通过垃圾回收机制来管理的
栈(Stack)上的内存是由系统自动管理的
值类型是在栈(Stack)中分配的内存,他们有自己的生命周期,不用对他们进行管理,系统会自动分配和释放
c#中内存回收机制的大概原理:
0代内存 1代内存 2代内存
代的概念:
代的是垃圾回收机制使用的一种算法(分代算法)
新分配的对象都会被配置在第0代内存中
每次分配都可能会进行垃圾回收以释放内存(0代内存满时)
在一次内存回收过程开始时,垃圾回收器会认为堆中全是垃圾,会进行以下两步:
1.标记对象,从根(静态字段、方法参数)开始检查引用对象,标记后为可达对象,未标记为不可达对象
2.不可达对象就会被认为是垃圾
3.搬迁对象压缩堆(挂起执行托管代码线程),释放未标记的对象,搬迁可达对象,修改引用地址
大对象总会被认为是第二代内存,目的是减少性能消耗,提高性能
不会对大对象进行搬迁压缩, 85000字节(83 kb)以上的对象成为大对象
GC的人为主动调用是在loading条中调用
-----博主:mx
成员属性
成员属性的基本语法
class Person
{
private string name;
private int age;
private int money
public string name
{
get{
//可以在返回之前添加一些逻辑规则,可以进行加密处理
//意味着这个属性可以就iu哦谢内容
return name;
}
set{
//可以在设置之前添加一些逻辑规则
//value 关键字 用于表示 外部传入的值
name=value;
}
}
public int money
{
get{
//加密处理
return money;
}
set{
if (value<0)
{
value=0;
}
money=value;
}
}
}
成员属性中get和set前可以加访问修饰符
注意:
1.默认不加,会使用属性声明时的访问权限
2.加的访问修饰符要低于属性的访问权限
3.不能让get和set的访问权限都低于属性的权限
get和set可以只有一个
自动属性
作用:外部能得不能改的特征
如果类中有一个特征是只希望外部能得不能改的,有没有什么特殊处理的,那么可以直接使用自动属性
public int Height
{
//会自动生成一个int型数值来给他
//没有在get和set中写逻辑的需求或者想法
get;
private set;
}
总结:
1.成员属性作用:一般用来保护成员变量
2.get中需要return内容;set中value表示传入的内容
3.get和set语句块中可以加逻辑处理
4.get和set可以加访问修饰符,但要按照一定的规则进行添加
5.get和set可以只有一个
6.get和set可以加访问修饰符,但是要按照一定的规则进行添加
7.自动属性是属性语句块中只有get和set,一般用于外部能得不能改的情况
索引器
索引器基本概念:
让对象可以像数组一样通过索引访问其中元素,使程序看起来更直观、更容易编写
索引器语法:
访问修饰符 返回值 this[参数类型 参数名,参数类型 参数名…]
{
内部的写法和规则 与索引器相同
get{}
set{}
}
例子:
class Person
{
private string name;
private int age;
private Person[] friends;
public Person this[int index]
{
get{ return friends[index];}
set{ friends[index]=value;}
}
}
索引器中可以写逻辑:
在get和set中可以写一些逻辑
索引器重载:
class Person
{
private string name;
private int age;
private Person[] friends;
private int[,] array;
public int this[int i,int j]
{
get{
return array[i,j];
}
set{
array[i,j]=value;
}
}
public Person this[int index]
{
get{ return friends[index];}
set{ friends[index]=value;}
}
}
总结:
索引器对我们来说的主要作用:
可以让我们以中括号的形式范围自定义类中的元素,规则自己定义,访问时和数组一样
比较适用于在类中有数组变量时使用,可以方便的访问和进行逻辑处理
静态成员:
概念:用static修饰的成员变量、方法、属性等,成为静态成员
生命周期:和程序同生共死
程序运行后就会一直存在内存中,直到程序结束后才会释放,因此静态成员具有唯一性
注意:
1.静态函数中不能直接使用非静态成员
2.非静态函数中可以直接使用静态成员
const和static的不同点:
1.const必须初始化不能被修改,static没有这个规则
2.const只能修饰变量,static可以修饰很多
3.const不能写在访问修饰符前面,一定是写在变量声明前面,static没有这个
静态类和静态构造函数:
静态类
静态构造函数:
概念:在构造函数前加上static修饰
特点:
1.静态类和普通类都可以有
2.不能使用访问修饰符
3.不能有参数
4.只会自动调用一次
作用:
在静态构造函数中初始化,静态变量
使用:
1.静态类中的静态构造函数
2.普通类中的静态构造函数
class Test
{
public static int testInt=200;
static Test()
{
Console.WriteLine("静态构造");
}
public Test()
{
Console.WriteLine("普通构造");
}
}
拓展方法
基本概念:
概念:为现有非静态变量类型添加新方法
作用:
1.提升程序拓展性
2.不需要在对象中重新写方法
3.不需要继承来添加方法
4.为别人封装的类型写额外的方法
特点:
1.一定是写在静态类中
2.一定是个静态函数
3.第一个参数为拓展目标
4.第一个参数用this修饰
基本语法:
访问修饰符 static 返回值 函数名(this 拓展类名 参数名,参数类型 参数名,参数类型 参数名......)
实例:
static class Tools
{
//为int拓展了一个成员方法
//成员方法是需要实例化对象后才能使用
//value代表使用该方法的实例化对象
public static void SpeakValue(this int value)
{
Console.WriteLine("为Int拓展的方法"+value);
}
public static void SpeakStringInfo(this string str,string str2,string str3)
{
Console.WriteLine("为string拓展的方法"+str);
Console.WriteLine("传的参数"+str2+str3);
}
//为自定义的类拓展方法
//注意如果拓展方法名与原有方法相同,则拓展失败会使用原方法
public static int Func3(this Test t)
{
Console.WriteLine("为test拓展的方法"+str);
return 0;
}
}
使用:
int i=10;
i.SpeakValue();
-----博主:mx
封装——运算符重载
基本概念:
概念: 让自定义类和结构体可以使用运算符
使用关键字:operator
特点:
1.一定是一个公共的静态方法
2.返回值写在operator前
3.逻辑处理自定义
作用: 让自定义类和结构体可以使用运算符
注意:
1.条件运算符需要成对实现
2.一个符号可以多个重载
3.不能使用ref和out
基本语法:
public static 返回类型 operator 运算符(参数列表)//二元运算符的参数类型必须包含至少一个类型
实例:
class Point
{
public int x;
public int y;
public static Point operator +(Point p1,Point p2)
{
Point p=new Point();
p.x=p1.x+p2.x;
p.y=p1.y+p2.y;
return p;
}
}
可重载和不氪重载的运算符:
逻辑运算符:
可:! 不可:&& ||
位运算符:
可:| & ^ ~ << >>
条件运算符:
返回值一般是bool值,也可以是其他
相关符号必须配对出现
可:> < <= >= ==
不可以重载的符:
逻辑与&& 逻辑或||
索引符[]
强转运算符()
特殊运算符
点 . 三目运算符?: 赋值符号=
内部类和分部类:
内部类:
概念:在一个类中再声明一个类
特点:使用时要用包裹着点出自己
作用: 亲密关系的表现
注意:访问修饰符作用很大
class Person
{
public int age;
public string name;
public Body body;
public class Body
{
Arm leftarm;
Arm rightarm;
class Arm
{
}
}
}
class Program
{
static void Main(string[] args)
{
Person p=new Person();
Person.Body body =new Person.Body();
}
}
分部类:
概念:把一个类分成几部分声明
关键字:partial
作用:
1.分部描述一个类
2.增加程序的拓展性
注意:
1.分部类可以写在多个脚本中
2.分部类的访问修饰符要一致
3.分布类中不能有重复成员
示例:+++++
partial class Student
{
public bool sex;
public string name;
分部方法:
partial void Speak();
}
partial class Student{
public int number;
partial void Speak(string str)
{
}
}
分部方法:
概念:将方法的声明和实现分离
特点:
1.不能加访问修饰符,默认私有
2.只能在分布类中声明
3.返回值只能是void
4.可以有参数但是不用out关键字
局限性较大
----------------------------------------面向对象–继承------------------------------------
继承的基本概念
基本概念:
一个类A继承一个类B
类A将会继承类B的所有成员
A类将拥有B类的说有特征和行为
被继承的类称为父类、基类、超类
继承的类被称为子类、派生类
子类可以有自己的特征和行为
特点:
1.单根性:子类只能由一个父类
2.传递性:子类可以间接继承父类的父类
基本语法:
class 类名:被继承的类名
{
}
访问修饰符的影响:
public 公共的 ,内外部访问
private 私有的 ,内部访问
protected 保护的 ,内部和子类访问
internal 内部的, 只有在同一个程序集的文件中,内部类型或者是成员才可以访问
子类和父类的同名成员:
c#中允许子类存在和父类同名的成员,但是极不建议认识
-----博主:mx
里氏替换原则
基本概念:
里氏替换原则是面向对象七大原则中最重要的原则
概念:
任何父类出现的地方,子类都可以代替
重点:
语法表现–父类容器装子类对象,因为子类对象包含了父类的所有内容
作用:
方便进行对象的储存和管理
基本实现:
class GameObject
{
}
class Player:GameObject
{
public void PlayerATK
{
Console.WriteLine("玩家攻击");
}
}
class Boss:GameObject
{
public void PlayerATK
{
Console.WriteLine("玩家攻击");
}
}
class Monster:GameObject
{
public void PlayerATK
{
Console.WriteLine("玩家攻击");
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("里氏替换原则\n");
GameObject player=new Player();
GameObject monster=new Monster();
GameObject boss=new Boss();
GameObject[] objects=new GameObject[] {new Player(),new Monster(),new Boss()};
}
}
is和as:
基本概念:
is:判断一个对象是否为指定类对象
返回值:bool 是为真,不是为假
as:将一个对象转换为指定类对象
返回值:指定类型对象 成功返回指定类型对象,失败返回null
基本语法:
类对象 is 类名 该语句块会有一个bool返回值 true和false
类对象 as 类名 该语句块会有一个对象返回值 对象和null
示例:
if(player is Player)
{
Player p= player as Player;
p.PlayerATK();
}
继承中的构造函数
基本概念:
特点:当申明一个子类对象时,先执行父类的构造函数、再执行子类的构造函数
继承中构造函数的执行顺序:
父类的父类的构造函数 > 父类的构造函数 > 子类的构造函数
父类的无参构造函数:
默认调用父类的无参构造函数,如果没有声明无参构造函数就会报错
class Father
{
public Father()
{
}
public Father(int i)
{
Console.WriteLine("Father构造");
}
}
class Son:Father
{
//使用base向父类的构造函数传参并调用
public Son(int i):base(i)
{
Console.WriteLine("Son的一个参数构造");
}
public Son(int i,string str):this(i)
{
Console.WriteLine("Son的两个参数构造");
}
}
通过base调用指定父类构造:
上面的示例
装箱拆箱
万物之父:
关键字:object
概念:object是所有类型的基类,他说一个类(引用类型)
作用:
1.可以利用里氏替换远测,用object容器装所有的对象
2.可以用来表示不确定的类型,作为函数参数类型
万物之父的使用:
引用类型:
object o=new Son();
用is as 来判断和转换即可
if(o is Son)
{
(o as Son).Speak();
}
值类型
object o2=1f;
用强转
float i=(float)o2;
特殊的string类型
object str ="123123";
string str2=str as string;
object arr=new int[10];
int[] ar=arr as int[];
装箱拆箱:
发生条件:
用object存值类型(装箱)
用object转换为值类型(拆箱)
装箱:把值类型用引用类型存储,栈内存会迁移到堆内存
拆箱::把引用类型存储的值类型取出来,堆内存会迁移到栈内存中
好处:不确定类型时可以方便参数存储和传递
坏处:存在内存迁移,增加性能消耗
不是不用,尽量少用
密封类:
基本概念:
是使用sealed密封关键字修饰的类
作用:让类无法再被继承
示例:
class Father
{
}
sealed class Son:Father
{
}
作用:
1.在面向对象程序设计中,密封类的主要作用就是不允许最底层子类被继承
2.可以保证程序的规范性、安全性
(目前来说可能作用不大,但是当制作复杂系统或者程序框架时,就能慢慢感受)
----------------------------------------面向对象–多态------------------------------------
多态Vob
多态的概念:让继承同一父类的子类们,在执行相同方法时有不同的表现(状态)
主要目的:同一父类的对象,执行相同行为(方法)有不同的表现
解决的问题:让同一个对象有唯一的行为特征
多态的实现:
编译时的函数重载
v:virtual(虚函数)
o:override(重写)
b:base(父类)
示例:
public GameObject
{
public string name;
public GameObject(string name)
{
this.name=name;
}
//虚函数:可以被子类重写
public virtual void Atk()
{
Console.WriteLine(“游戏对象进行攻击”);
}
}
class player:GameObject
{
public player(string name):base(name)
{
}
public override void Atk()
{
//base的作用:
//代表父类,可以通过base来保留父类的行为
//base.Atk();
Console.WriteLine("玩家对象进行攻击");
}
}
class Monster:GameObject
{
public Monster(string name):base(name)
{
}
public override void Atk()
{
Console.WriteLine("怪物对象进行攻击");
}
}
class Program
{
string void Main(string[] args)
{
Console.WriteLine("多态Vob");
GameObject p=new Player("主角");
p.Atk();
(p as Player).Atk();
GameObject m=new Monster("小怪物");
m.Atk();
(m as Monster).Atk();
}
}
总结:
多态:让同一类型的对象,执行相同行为时有不同的表现
解决的问题:让同一个对象有唯一的行为特征
vob:
v:virtual 虚函数
o:override 重写
b:base 父类
v和o一定时结合使用的,来实现多态
b是否使用根据实际需求,保留父类行为
多态-抽象类和抽象方法
抽象类:
概念:
被抽象关键字abstract修饰的类
特点:
1.不能被实例化
2.可以包含抽象方法
3.继承抽象类必须重写其中的抽象方法
抽象函数(抽象方法):
又叫纯虚方法
用abstract关键字修饰的方法
特点:
1.只能在抽象类中声明
2.没有方法体
3.不能是私有的
4.击沉后必须实现,用override重写
示例:
abstract class Thing
{
//抽象类中,封装的所有知识点都可以在其中书写
public string name;
//可以在抽象类中书写抽象方法
}
class Water:Thing
{
}
class Program
{
string void Main(string[] args)
{
Console.WriteLine("抽象类和抽象方法");
//抽象类不能被实例化
//Thing t=new Thing();
//但是,可以遵循里氏替换原则,用父类容器装子类
Thing t=new Water();
}
}
总结:
抽象类:被abstract修饰的类,不能被实例化,可以包含抽象方法
抽象方法:没有方法体的纯虚方法,继承后必须实现的方法
注意:
1.如何选择普通类还是抽象类
2.不希望被实例化的对象,相对比较抽象的类可以用抽象类
3.父类重点行为不需要被实现的,只希望子类去定义具体规则的可以选择抽象类然后使用其中的抽象方法来定义规则
作用:整体框架设计时会使用
多态-接口
接口的概念:
概念:
接口是行为的抽象规范
它也是一种自定义类型
关键字:interface
接口的声明规范:
1.不包含成员变量
2.只包含方法、属性、索引器、时间
3.成员不能被实现
4.成员可以不用写访问修饰符,不能是私有的
5.接口不能继承类,但是可以继承另一个接口
接口的使用规范:
1.类可以继承多个接口
2.类继承接口后,必须实现接口中的所有成员
特点:
1.它和类的声明类似
2.接口是用来继承的
3.接口不能被实例化,但是可以作为容器存储对象
接口的声明:
接口关键字:interface
语法:
interface 接口名
{
}
一句话记忆:接口时抽象行为的“基类”
接口命名规范,帕斯卡前面加一个I
interface IFly
{
void Fly();
string name
{
get;
set;
}
int this[int index]
{
get;
set;
}
event Action doSomthing;
}
接口的使用:
接口是用来继承:
1.类可以继承1个基类,n个接口
2.继承了接口后,必须实现其中的内容,并且必须是public的
3.实现接口的函数,可以假virtual再在子类里重写
4.接口也遵循里氏替换原则
class Animal
{
}
class Person:Animal ,IFly
{
public void Fly()
{
}
public string name
{
get;
set;
}
public int this[int index]
{
get;
set;
}
public event Action doSomthing;
}
接口可以继承接口:
接口继承接口时,不需要实现
带类继承接口后,类需要去实现所有的内容
显示实现接口:
当一个类继承两个接口
但是接口中存在着同名方法时,注意:显示实现接口时,不能写访问修饰符
示例:
interface IAtk()
{
void Atk();
}
interface ISurperatk()
{
void Atk();
}
class Player :IAtk ,ISurperatk
{
void IAtk.Atk()
{
}
void ISurperatk.Atk()
{
}
}
总结:
继承类:是对象间的继承,包括特征行为等等
继承接口:是行为间的继承,继承接口的行为规范,按照行为规范去实现内容
由于接口也是遵循里氏替换原则,所以可以用接口容器装对象
那么久可以实现,装载各种毫无关系但是却有相同行为的对象
注意:
1.接口值包含:成员方法、属性、索引器、事件,并且都不实现,且没有访问修饰符
2.可以继承多个接口,但是只能继承一个类
3.接口可以继承接口,相当于在进行行为合并,带子类继承是再去实现具体的行为
4.接口可以被显示实现,主要用于实现不同接口中的同名函数的不同表现
5.实现接口的方法,可以假virtual关键字,之后子类再重写
多态-密封方法
基本概念:
概念:
用密封关键字sealed修饰的重写函数
作用:
让虚方法或者抽象方法之后不再被重写
特点:
和override一起出现
示例:
public class Animal
{
public string name;
public abstract void Eat();
public virtual void Speak()
{
Console.WriteLine(“叫”);
}
}
class Person:Animal
{
public sealed override void Eat()
{
}
public sealed override void Speak()
{
}
}
总结:
1.密封方法,可以让虚方法和抽象方法不能再被子类重写
2.特点:一定是和override一起出现
-----博主:mx
----------------------------------------面向对象关联知识点------------------------------------
命名空间
基本概念:
概念:
命名空间是用来组织和重用代码的,并且可以复用单词
作用:
就像一个工具包,类就像是一件一件的工具,都是声明在命名空间中的
命名空间中的使用:
基本语法:
namespace 命名空间名
{
类
类
}
示例:
namespace MyGame
{
class GameObject
{
}
}
namespace MyGame
{
class Player:GameObject
{
}
}
不同命名空间中允许有同名类
namespace MyGame2
{
class GameObject
{
}
}
命名空间可以包裹命名空间
namespace MyGame3
{
namespace UI
{
class UI
{
class Image
{
}
}
}
namespace Game
{
class Game
{
class Image
{
}
}
}
}
关于修饰类的访问修饰符
public ---命名空间中的类默认为public
internal ---只能在该程序集中是Yoon
abstract ---抽象类
sealed ---密封类
partial ---分部类
总结:
1.命名空间是个工具包,用来管理类的
2.不同命名空间中,可以有同名类
3.不同命名空间中相互使用,需要using引用命名空间 或者 指明出处
4.命名空间可以包裹命名空间
万物之父中的方法
object中的静态方法
静态方法 Equals 判断两个对象是否相等
最终的判断权,交给左侧对象的Equals方法
不管值类型引用类型都会按照左侧对象Equals方法的规则进行比较
(引用类型比较是比较两个对象是否是同一个地址)
Test t=new Test();
Test t2=t;
Console.WriteLine(object.Equals(t,t2));
静态方法RefenceEquals
比较两个对象是否相同的引用,主要是用来比较引用类型的对象
值类型对象返回值始终是false
Console.WriteLine(object.RefenceEquals(t,t2));
object中的成员方法
普通方法GetType
该方法在反射相关知识中使非常重要的方法
该方法的主要作用就是获取对象运行时的类型Type
通过Type结合反射相关知识点可以做许多关于对象的操作
Test t=new Test();
Type type=t.GetType();
普通方法MemberwiseClone
该方法用于获取对象的浅拷贝对象,口语化的意识就是返回一个新对象,但是新对象中的引用变量会和老对象中一致,但是值类型对象不一样 会是新的
class Test
{
public int i=1;
public Test2 t2=new Test2();
public Test Clone()
{
return MemberwiseClone() as Test;
}
}
class Test2
{
public int i=2;
}
class TestClone
{
Test t=new Test();
Type type=t.GetType();
Test t2=t.Clone();
Console.WriteLine("克隆对象后");
Console.WriteLine("t.i = " + t.i);
Console.WriteLine("t.t2.i = " + t.t2.i);
Console.WriteLine("t2.i = " + t2.i);
Console.WriteLine("t2.t2.i = " + t2.t2.i);
t2.i=20;
t2.t2.i=21;
Console.WriteLine("t.i = " + t.i);
Console.WriteLine("t.t2.i = " + t.t2.i);
Console.WriteLine("t2.i = " + t2.i);
Console.WriteLine("t2.t2.i = " + t2.t2.i);
}
object中的虚方法
虚方法Equals
默认实现还是比较两者是否为同一个引用,机相当于RefenceEquals
但是微软在所有值类型的基类System.ValueType中重写了该方法,用来比较值相等
我们也可以重写该方法,定义自己的比较相等的规则
虚方法GetHashCode
该方法时获取对象的哈希码
(一种通过算法算出的,表示对象的唯一编码,不同对象哈希码又可以一样,具体值根据哈希算法决定)
我们可以通过重写该函数来定义对象的哈希码算法,正常情况下,我们使用的极少,基本不用。
虚方法ToString
该方法用于返回当前对象代表的字符串,我们可以重写它定义我们自己的对象转字符串规则
该方法非常常用,当我们调用打印方法时,默认使用的就是对象的ToString方法打印出来的内容
总结:
1.虚方法:ToString 转换成字符串(可以通过重写来自定义字符串转换规则)
2.成员方法:GetType 反射相关
3.成员方法:MemberwiseClone 浅拷贝
4.虚方法:Equals 判断相等(可以通过重写来自定义判断相等的规则)
string
字符串指定位置获取
字符串本质是char数组
转化为char数组
char[] chars=str.ToCharArray();
字符串拼接
str=strin1.Format("{0}{1}",1,3333);
正向查找字符的位置
string str="好耶针布戳";
int index=str.IndexOf("耶");
反向查找指定字符串位置
string str="好耶针布戳好耶";
int index=str.LastIndexOf("耶");
移除指定位置后的字符
string str="好耶针布戳好耶";
string str2= str.Remove(4);
Console.WriteLine(str2);
可以执行两个参数进行移除
参数一:开始位置
参数二:字符个数
str2=str.Remove(1,1);
Console.WriteLine(str);
替换指定字符串
string str="好耶针布戳好耶";
string str2= str.Replace("好耶","ky");
Console.WriteLine(str2);
大小写转换
string str="asdasdTSDAHsaddas";
string str2= str.ToUpper;
Console.WriteLine(str2);
str2=str.ToLower();
Console.WriteLine(str2);
字符串截取
string str="好耶针布戳好耶";
//截取从指定位置开始之后的字符串
string str2= str.Substring(2);
Console.WriteLine(str2);
//参数一:开始位置
//参数二:指定个数
//不会自动帮助你判断是否越界,需要自己判断
str2= str.Substring(2,3);
Console.WriteLine(str2);
字符串切割
string str="1,2,3,4,5";
string[] strs=str.Split(',');
for(int i=0;i
StringBuilder
知识回顾string
string是特殊的引用
每次重新赋值或者是拼接时会重新分配内存空间
如果一个字符串经常改变会非常浪费空间
基本概念
概念:
C#提供的一个用于处理字符串的公共类
主要解决的问题:
修改字符串而不是创建新的对象,需要频繁修改和拼接的字符串可以使用它,可以提升性能
使用前需要引用命名空间
初始化-直接指明内容
StringBuilder str=new StringBuilder("123123123");
Console.WriteLine(str);
容量
StringBuilder存在一个容量问题,每次往里增加时,会自动扩容
获取容量
Console.WriteLine(str.Capacity);
获取字符长度
Console.WriteLine(str.Length);
增删改查替换
增:
str.Append("4444");
Console.WriteLine(str);
Console.WriteLine(str.Length);
Console.WriteLine(str.Capacity);
str.AppendFormat("{0}{1}",100,999);
Console.WriteLine(str);
Console.WriteLine(str.Length);
Console.WriteLine(str.Capacity);
插入:
str.Insert(0,"123");
Console.WriteLine(str);
删:
str.Remove(0,10);
Console.WriteLine(str);
清空:
str.Clear();
Console.WriteLine(str);
查:
Console.WriteLine(str[1]);
改:Console.WriteLine(str[1]);
str[0]='A';
替换:
str.Replace("1","haoye");
Console.WriteLine(str);
重新赋值 StringBuilder
str.Clear();
str.Append("123123");
Console.WriteLine(str);
判断:
//必须使用Equals,无法使用== 会报错
if(str.Equals("123123"))
{
Console.WriteLine("相等");
}
【【【【【【面试常见】】】】】】】
结构体和类的区别
区别概述:
结构体和类的最大区别是在存储空间上,结构体是值类型,而类是引用类型
因此结构体是存储在栈上,而类是存储在堆上
结构体和类在使用上类似,结构体甚至可以用面向对象的思想类形容一类对象
结构体具备着面向对象思想中封装的特性,但是它不具备继承和多态的特性,因此大大减少了它的使用频率
由于结构体不具备继承的特性,所以他不能够使用protected保护访问修饰符
细节区别:
1.结构体是值类型,而类是引用类型
2.结构体存储在栈中,而类存储在堆中
3.结构体成员不能使用protected访问修饰符,而类可以
4.结构体成员变量声明不能指定初始值,而类可以
5.结构体不能声明无参的构造函数,而类可以
6.结构体声明有参数的构造函数后,无参构造不会被顶掉
7.结构体不能声明析构函数,而类可以
8.结构体需要在构造函数中初始化所有成员变量,而类随意
9.结构体不能被继承,而类可以
10.结构体不能被静态static修饰(不存在静态构造体),而类可以
11.结构体不能在自己内部声明和自己一样的结构体变量,而类可以
结构体的特别之处
结构体可以继承结构因为接口是行为的抽象
如何选择结构体和类
1.想要使用继承和多态是,直接淘汰结构体,比如玩家、怪物等等
2.对象是数据集合时,优先考虑结构体,比如位置、坐标等等
3.从值类型和引用类型赋值时的区别上去考虑,比如经常被赋值传递的对象,并且改变赋值对象,原对象不想跟着变化时,就用结构体,比如坐标、向量、旋转等等
抽象类和接口的区别
知识回顾:
抽象类和抽象方法
abstract修饰的类和方法
抽象类不能被实例化
抽象方法只能在抽象类中声明,是个纯虚方法,必须在子类中实现
接口
interface自定义类型
是行为的抽象
不包含成员变量
仅包含方法、属性、索引器 ; 事件,成员都不能实现,建议不写访问修饰符,默认public
相同点
1.都可以被继承
2.都不能直接实例化
3.都可以包含方法声明
4.子类必须实现未实现的方法
5.都遵循里氏替换原则
区别
1.抽象类中可以有构造函数,而接口中不能
2.抽象类只能被单一继承,而接口可以继承多个
3.抽象类可以有成员变量,而接口中不能
4.抽象类可以声明成员方法,虚方法,抽象方法,静态方法;而接口只能声明没有实现的抽象方法
5.抽象类方法可以使用访问修饰符,而接口中建议不写,默认public
如何旋转抽象类和接口
表示对象的用抽象类,表示行为的拓展用接口
不同对象拥有的共同行为,我们往往可以使用接口来实现
举个例子:
动物是一类对象,自然会优先想到抽象类;而飞翔是一个行为,我们自然会选择接口
目前学习的总结
面向对象–封装
类和对象
成员变量和访问修饰符
成员方法
构造函数和析构函数
成员属性
索引器
静态成员
静态类和静态构造函数
拓展方法
运算符重载
内部类和分部类
面向对象–继承
继承的基本规则
里氏替换原则
继承中的构造函数
万物之父和装箱拆箱
密封类
面向对象–多态
Vob
抽象类和抽象方法
接口
密封方法
面向对象关联知识点
命名空间
万物之父中的方法
String
StringBuilder
结构体和类的区别
构造体和接口的区别
-----博主:mx
--------------------------------C#进阶部分学习----------------------------------
哈希表Hashtable
Hashtable的本质
Hashtable(又称为散列表),是基于键的哈希代码组织起来的键值对
它的主要作用是提高数据的查询效率
使用键来访问集合中的元素
申明
必须:
需要引用命名空间System.Collections
Hashtable hashtable=new Hashtable();
增删改查
增
Hashtable.Add(1,"123");
Hashtable.Add(2,"1234");
Hashtable.Add("123",1);
Hashtable.Add("123",12);
Hashtable.Add(true,false);
Hashtable.Add(false,true);
//注意:不能出现相同的键
删
1.只能通过键去删除
hashtable.Remove(1);
2.删除一个不存在的键,没反应
hashtable.Remove(2);
3.或者直接清空
hashtable.Clear();
查
1.通过键查看值,如果找不到则返回空
2.查看是否存在
根据键检测
if(hashtable.Contains(2))
{
Console.WriteLine("存在键为2的键值对");
}
if(hashtable.ContainsKey(2))
{
Console.WriteLine("存在键为2的键值对");
}
根据值检测
if(hashtable.ContainsValue(12))
{
Console.WriteLine("存在值为12的键值对");
}
改
//只能改 键对应的值
hashtable[1]=100.5f;
Console.WriteLine(hashtable[1]);
遍历
得到键值对 对数
Console.WriteLine(hashtable.Count);
遍历所有键
foreach(object item in hashtable.Keys)
{
Console.WriteLine("键:"+item);
Console.WriteLine("值:"+hashtable[item]);
}
遍历所有值
foreach(object item in hashtable.Values)
{
Console.WriteLine("值:"+item);
}
键值对一起遍历
foreach(DictionaryEntry item in hashtable.Values)
{
Console.WriteLine("键:"+item.Key+"值:"+item.Value);
}
迭代器遍历法
IDictionaryEnumerator myEnumerator =hashtable.GetEnumerator();
bool flag=myEnumerator.MoveNext();
while(flag)
{
Console.WriteLine("键:"+myEnumerator.Key+"值:"+myEnumerator.Value);
flag=myEnumerator.MoveNext();
}
装箱拆箱
本质是一个可以自动扩容的object数组
由于用万物之父来存储数据,自然存在装箱拆箱
当往其中进行值类型存储时就是在装箱
当将值类型对象取出来转换使用时,就存在拆箱
泛型
泛型是什么
泛型实现了类型参数化,达到代码重用的目的
通过类型参数来实现同一份代码上操作多种类型
泛型相当于类型占位符
定义类或者方法时使用替代符代表变量类型
当真正使用类或者方法时再具体指定类型
泛型分类
泛型类和泛型接口
基本语法:
class 类名<泛型占位字母>
interface 接口名<泛型占位字母>
泛型函数:
基本语法:
函数名<泛型占位字母>(参数列表)
注意:泛型占位字母可以有多个,用逗号隔开
泛型类和接口:
class TestClass
{
public T value;
}
class TestClass
{
public T value;
public T1 value;
public T2 value;
public T3 value;
}
泛型方法
普通类中的泛型方法
class Test2
{
public void TestFun(T value)
{
Console.WriteLine(value);
}
public void TestFun()
{
//泛型类型在里面做一些逻辑处理
T t=default(T);
}
public void TestFun(String v)
{
return default(T);
}
public void TestFun(T value,T1 value1,T2 value2,T3 value3)
{
}
}
泛型类中的泛型方法
class Test2
{
public T value;
public void TestFun(M k)
{
}
//这个不是泛型方法,因为T是泛型类声明的时候就指定了,在使用这个函数的时候,我们不能再去动态的变化了
public void TestFun(T t)
{
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("泛型");
TestClass t=new TestClass();
t.value=10;
Console.WriteLine(t.value);
TestClass t2=new TestClass();
t2.value="123456";
Console.WriteLine(t2.value);
Test2 tt=new Test2();
tt.TestFun("123567");
Test2 tt2=new Test2();
tt2.TestFun(10);
tt2.TestFun("123");
tt2.TestFun(1.2f);
tt2.TestFun(20);
}
}
泛型的作用
1.不同的类型对象的相同逻辑处理可以选择泛型
2.使用泛型可以一定程度避免装箱拆箱
举例:优化ArrayList
总结:
1.声明泛型时,它只是一个类型的占位符
2.泛型真正起作用的时候,是在使用它的时候
3.泛型占位字符可以有n个用逗号分开
4.泛型占位字母一般是大写字母
5.不确定泛型类型时,获取默认值,可以用default(占位字符)
6.看到<>包裹的字母,那肯定是泛型
泛型约束
什么是泛型约束:
让泛型的类型有一定的限制
关键字:where
泛型约束一共有6种
1.值类型 where 泛型字母:struct
2.引用类型 where 泛型字母:class
3.存在无参公共构造函数 where 泛型字母:new()
4.某个类本身或者其派生类 where 泛型字母:类名
5.某个接口的派生类型 where 泛型字母:接口名
6.另一个泛型类型本身或者派生类型 where 泛型字母:另一个泛型字母
各类泛型约束讲解
值类型约束
class Test where T:struct
{
public T value;
public void TestFun(K v) where K : struct
{
}
}
引用类型约束
class Test where T:class
{
public T value;
public void TestFun(K v) where K : class
{
}
}
无参公共构造函数约束
//必须无参
//必须是公共的
class Test where T:new()
{
public T value;
public void TestFun(K v) where K : new()
{
}
}
class Test1
{
public Test1()
{
}
}
class Test2
{
public Test2(int a)
{
}
}
类约束
//必须是类本身或者是其子类(派生类)
class Test4 where T: Test1
{
public T value;
public void TestFun where K:Test1
{
}
}
class Test3:Test1
{
}
接口约束
//必须是接口派生出来的类型
interface IFly
{
}
class Test3:IFly
{
}
class Test4 where T: IFly
{
public T value;
public void TestFun where K:IFly
{
}
}
另一个泛型约束
class Test4 where T: U
{
public T value;
public void TestFun where K:V
{
}
}
约束的组合使用
class Test7 where T:class,new()
{
}
多个泛型有约束
class Test8 where T:class ,new() where K:struct
{
}
总结:
泛型约束:让类型有一定限制
class
struct
new()
类名
接口名
另一个泛型字母
注意:
1.可以组合使用
2.多个泛型约束,用where连接即可
LinkedList
基本介绍:
LinkedList是一个C#编写好的类
本质是一个可变类型的泛型双向链表
声明:
需要引用命名空间 using System.Collection.Generic
链表对象 需要掌握两个类
一个是链表本身,一个是链表节点类LinkedListNode
增删改查:
LinkedList linkedList1=new LinkedList();
增:
1.在链表尾部添加元素
linkedList1.AddLast(10);
2.在链表头部添加元素
linkedList1.AddFirst(20);
3.在某一个节点之后
要指定节点,先得得到一个节点
LinkedListNode n=linkedList1.Find(20);
linkedList1.AddAfter(n,15);
4.在某一个节点之前添加一个节点
要指定节点,先得得到一个节点
linkedList1.AddBefore(n,11);
删:
1.移除头节点
linkedList1.RemoveFirst();
2.移除尾节点
linkedList1.RemoveLast();
3.移除指定节点
无法通过位置直接移除
linkedList1.Remove();
4.清空
linkedList1.Clear();
查:
1.头节点
LinkedListNode first=linkedList1.First();
2.尾节点
LinkedListNode last=linkedList1.Last;
3.找到指定值的节点
无法直接通过下标获取中间元素
只有遍历查找指定位置元素
LinkedListNode node=linkedList1.Find(3);
Console.WriteLine(node.Value);
node = linkedList1.Find(5);
Console.WriteLine(node);
4.判断是否存在
if(linkedList1.Contains(1))
{
Console.WriteLine("链表中存在1");
}
改:
要得再改, 得到节点,再改变其中的值
Console.WriteLine(linkedList1.First.Value);
linkedList1.First.Value=10;
Console.WriteLine(linkedList1.First.value);
遍历
foreach遍历
foreach(int item in linkedList1)
{
Console.WriteLine(item);
}
通过节点遍历
从头到尾
Console.WriteLine("&&&&&&&&&&&&&&&&&");
LinkedListNode nowNode =linkedList1.First;
while(nowNode!=null)
{
Console.WriteLine(nowNode.Value);
nowNode=nowNode.Next;
}
从尾到头
Console.WriteLine("&&&&&&&&&&&&&&&&&");
LinkedListNode nowNode =linkedList1.Last;
while(nowNode!=null)
{
Console.WriteLine(nowNode.Value);
nowNode=nowNode.Previous;
}
委托
委托基本概念
委托是函数的容器
可以理解为表示函数的变量类型
用来储存、传递函数
委托本质是一个类,用来定义函数的类型(返回值和参数类型)
不同的函数必须对应和各自"格式"一致的委托
委托基本语法
关键字:delegate
语法:访问修饰符 delegate 返回值 委托名(参数列表);
写在哪里?
可以声明在namespace和class语句块中
更多的写在namespace中
简单记忆委托语法就是在函数声明语法前面加一个delegate关键字
定义自定义委托
范文修饰符默认不屑,为public ,在别的命名空间中也能使用
private 其他命名空间不能用了
一般使用public
//申明了一个可以用来存储无参无返回值函数的容器
//这里只是定义了规则,并没有使用
//委托规则的声明,是不能重名的(同一语句块中)
//表示用来装载或者传递,返回值int,有一个int参数的函数的委托,容器规则
delegate void MyFun()
delegate int MyFun2(int a);
使用定义好的委托
委托变量是函数的容器
委托常用在:
1.作为类成员
2.作为函数的参数
class Test
{
public MyFun fun;
public Myfun2 fun2;
public void TestFun(MyFun fun,Myfun2 fun2)
{
//可以先处理一些别的逻辑,当这些逻辑处理完了,再执行传入的函数
int i=1;
i+=1;
i*=2;
this.fun+=fun;
this.fun2+=fun2;
}
//增
public void AddFun(MyFun fun,Myfun2 fun2)
{
this.fun+=fun;
this.fun2+=fun;
}
//删
public void AddFun(MyFun fun,Myfun2 fun2)
{
this.fun-=fun;
this.fun2-=fun;
}
}
委托变量可以存储多个函数(多播委托)
系统定义好的委托
//使用系统自带的委托 ,需要引用using System;
Action action=fun;
action+=fun3;
action();
//可以指定返回值类型的 泛型委托
Func funcstring=fun4;
//可以传n个参数的 系统提供了1到16个参数的委托,直接用就行了
Action action =fun6;
//可以传n个参数的,并且有返回值的,系统也提供了16个参数的委托
Func func2=Func;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("委托");
MyFun f=new MyFun(fun);
Console.WriteLine("委托");
f.Invoke();
MyFun f2=Fun;
Console.WriteLine("委托");
f2();
MyFun2 f3=fun2;
Console.WriteLine(f3(1));
MyFun2 f4=new MyFun2(fun2);
Console.WriteLine(f4.Invoke(3));
Test t=new Test(fun().,fun2);
Console.WriteLine(“*****************”);
Myfun2 ff=null;
ff+=fun;
ff+=fun;
ff();
t.AddFun(fun,fun2);
t.fun();
t.fun2();
//移除指定函数
ff-=fun;
//多减,不会报错
ff-=fun;
ff();
//清空指定容器
ff=null;
if(ff!=null)
{
ff();
}
}
static void fun()
{
Console.WriteLine("123456");
}
static int fun2(int value)
{
Console.WriteLine("haoye"+value);
return value;
}
static void fun3()
{
}
static string fun4()
{
return "123";
}
static void fun6(int i,string str)
{
}
}
总结:
简单理解:委托就是装载、传递函数的容器而已
可以委托变量来存储函数或者传递函数的
系统其实已经可以提供了很多委托给我们用
Action:没有返回值,参数提供了0~16个委托给我们用
Func:有返回值,参数提供了0~16个委托给我们用
事件
事件是什么;
事件是基于委托的存在
事件是委托的安全包裹
让委托的使用更具有安全性
事件是一种特殊的变量类型
事件的使用
声明语法
访问修饰符 event 委托类型 事件名
事件的使用:
1.事件是作为成员变量存在于类中
2.委托怎么用,事件就怎么用
事件相对于委托的区别:
1.不能在类外部 赋值
2.不能在类外部 调用
注意:
他只能作为成员存在于类和接口以及结构体中
class Test
{
//委托成员用来存储函数的
public Action myFun;
//事件成员变量,用来存储函数的
public event Action myEvent;
public Test
{
//事件的使用和委托 一模一样。只有一些细微差别
myFun=TextFun;
myFun+=TextFun;
myFun-=TextFun;
myFun();
myFun.Invoke();
myFun=null;
myEvent=TestFun;
myEvent+=TestFun;
myEvent-=TestFun;
myEvent();
myEvent.Invoke();
myEvent=null;
}
public void DoEvent()
{
myEvent();
myEvent.Invoke();
}
public void TestFun()
{
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("事件");
t.myFun=null;
t.myFun=TestFun;
//事件是不能在外部赋值的
//t.myEvent=null;
//t.myEvent=TestFun;
//虽然不能直接赋值,但是可以加减去添加移除记录的函数
t.myEvent+=TestFun;
t.myEvent-=TextFun;
//t.myEvent=t.myEvent- TextFun; 报错,不能使用赋值=,只能用复合运算符-= +=
//委托是可以在外部调用的
t.Myfun();
t.Myfun.Invoke();
//事件不能在外部调用
//t.myEvent();
//只能在类的内部去封装调用
t.DoEvent();
Action a=TextFun;
//事件是不能作为临时变量在函数中使用的
//event Action ae=TextFun;
}
}
为什么有事件
1.防止外部随意置空委托
2.防止外部随意调用委托
3.事件相当于对委托进行了一次封装,让其更加安全
总结:
事件和委托的区别
事件和委托的使用基本一模一样
事件就是特殊的委托
主要区别:
1.事件不能在外部使用赋值=符号,只能用+ - ;委托到哪里都能用
2.事件不能在外部执行;委托到哪里都能执行
3.事件不能作为函数中的临时变量;委托可以
-----博主:mx
匿名函数
基本概念:
顾名思义就是没有名字的函数
匿名函数的主要作用就是配合委托和事件进行使用
脱离委托和事件是不会使用匿名函数的
基本语法:
delegate(参数列表)
{//函数列表}
何时使用?
1、函数中传递参数时
2、委托或者事件赋值时
使用
1.无参数无返回值
这样申明匿名函数只是在申明函数而已,还没有调用
真正调用他的时候,是这个委托容器啥时候调用,就什么时候调用这个匿名函数
Action a =delegate()
{
Console.WriteLine(“匿名函数逻辑”);
}
2.有参数
Action b =delegate(int a,string b)
{
Console.WriteLine(a);
Console.WriteLine(b);
}
b(100,"123");
3.有返回值
Func c = delegate()
{
return "123123";
}
Console.WriteLine(c);
4. 一般情况会作为函数参数传递,或者作为函数返回值
Test t=new Test() ;
参数传递
t. Dosomthing(100,delegate(){
Console. WriteLine("随参数传入的匿名函数逻辑") ;
});
返回值
Action ac2=t.GetFun();
ac2();
//一步到位直接调用返回的委托函数
t.GetFun()();
Class Test
{
public Action action;
public void Dosomthing(int a ,Action fun)
{
Console.WriteLine(a);
fun();
}
public Action GetFun()
{
return delegate()
{
Console. WriteLine("返回匿名函数") ;
};
}
}
匿名函数的缺点:
添加到委托或者事件容器后,不记录,无法单独移除
Action ac3=delegate()
{
Console.WriteLine("匿名函数一");
}
ac3+=delegate()
{
Console.WriteLine("匿名函数二");
}
ac3();
因为匿名函数没有名字,所以没有办法指定移除某一个匿名函数
此匿名函数非彼匿名函数,不能通过看逻辑是否一样,就证明是同一个
ac3=null;
总结:
匿名函数 就是没有名字的函数
固定写法:
delegate(参数列表){}
主要是在委托传递和存储时,为了方便可以直接使用匿名函数
缺点是没有办法直接删除
Lambda表达式
什么是lambda表达式
可以将lambda表达式理解为匿名函数的简写
除了写法不同外,使用上基本与匿名函数一样
都是和委托或者事件配合使用
Lambda表达式语法
匿名函数
delegate(参数列表)
{
};
Lambda表达式
(参数列表)=>
{
函数体
};
使用
1.无参无返回
Action a = ()=>{
Console.WriteLine("无参无返回值的lambda表达式");
}
2.有参数
Action a2= (int value)=>
{
Console.WriteLine("有参数Lambda表达式{0}",value);
}
3.甚至参数类型都可以省略,参数类型和委托或者事件容器一致
Action a3=(value)=>{
Console.WriteLine("有参数Lambda表达式{0}",value);
}
4.有返回值
Func a4=(value)=>{
Console.WriteLine("有参数Lambda表达式{0}",value);
return 1;
}
闭包:
内层函数可以引用包含在它外层的函数的变量
即使外层函数的执行已经终止
注意:
该变量提供的值并非变量创建时的值,而是父函数范围内的最终值
示例:
class Test
{
public event Action action;
public Test()
{
int value=10;
//这里形成了闭包
//因为当构造函数执行完毕时,其中申明的临时变量value的声明周期被改变了
action =()=>{
Console.WriteLine(value);
}
for(int i=0;i<10;i++)
{
action +=()=>{
Console.WriteLine(i);
}
}
}
public void DoEvent()
{
action =null;
}
}
总结:
匿名函数的特殊写法就是lambda表达式
固定写法就是 (参数列表)=>{}
参数列表可以直接省略参数类型
主要在委托传递和储存时为了方便可以直接使用匿名函数或者lambad表达式
缺点:无法指定移除
List排序
List自带的排序方法
List list=new List();
list.Sort();
自定义类的排序
class Item:IComparable
{
public int money;
public Item(int value)
{
money=value;
}
public int CompareTo(Item other)
{
//返回值的含义:小于0就放在传入对象的前面
// 等于0就保持当前位置不变
// 大于0就保持对象的后面
//可以简单理解传入对象的位置就是0,如果你的返回为负数,就放在它的左边,也就前面
//如果返回正数,就放在它的右边,也就是后面
if(this.money>other.money)
{
return 1;
}
else
{
return -1;
}
return 0;
}s
}
List- itemlist=new List
- ();
itemlist.Add(new Item(45));
itemlist.Add(new Item(4));
itemlist.Add(new Item(5));
itemlist.Add(new Item(15));
itemlist.Add(new Item(55));
itemlist.Add(new Item(65));
itemlist.Add(new Item(47));
itemlist.Add(new Item(40));
itemlist.Sort();
通过委托函数进行排序
List
- Shopitemlist=new List
- ();
Shopitemlist.Add(new Item(45));
Shopitemlist.Add(new Item(4));
Shopitemlist.Add(new Item(5));
Shopitemlist.Add(new Item(15));
Shopitemlist.Add(new Item(55));
Shopitemlist.Add(new Item(65));
Shopitemlist.Add(new Item(47));
Shopitemlist.Add(new Item(40));
Shopitemlist.Sort(SortShopItem);
Shopitemlist.Sort(delegate (Item a,Item b){
if(a.money>b.money)
{
return 1;
}
else
{
return -1;
}
});
Shopitemlist.Sort((Item a,Item b)=>{
if(a.money>b.money)
{
return 1;
}
else
{
return -1;
}
});
static int SortShopItem(Item a,Item b)
{
//传入的两个对象,为列表中的两个对象
//进行两两比较,用左边的和右边的条件 比较
//返回值规则和之前一样,0做标准,负数在前 正数在后
if(a.money>b.money)
{
return 1;
}
else
{
return -1;
}
}
总结
系统自带的变量(int double float...) 一般都可以直接Sort
自定义了Sort有两种排序方法
1.继承接口IComparable
2.在Sort中传入委托函数
协变逆变
什么是协变逆变
协变和逆变是用来修饰泛型的
协变:out
逆变:in
用在泛型中修饰泛型字母的
只有泛型接口和泛型委托能使用
作用:
1.返回值和参数
用out修饰的泛型只能作为返回值
delegate T TestOut();
用in修饰的泛型只能作为参数
delegate void TestIn(in T)(T t);
2.结合里氏替换原则理解
class Father
{
}
class Son:Father
{
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("协变逆变");
//协变 父类总是能被子类替换
TestOut os= ()=>
{
return new Son();
}
TestOut of=os;
Father f= of();
//实际上 返回的是os里面装的函数,返回的是Son
//逆变 父类总是能被子类替换
TestIn iF=(value)=>
{
};
TestIn iS=iF;
iS(new Son());//实际上调用的是iF
}
}
总结
协变:out
逆变: in
用来修饰泛型替代符的,只能修饰接口和委托中的泛型
作用两点:
1.out修饰的泛型类型,只能作为返回值类型,in修饰的泛型类型只能作为参数类型
2.遵循里氏替换原则,用out和in修饰的泛型委托,可以相互装载(有父子关系的泛型)
//口诀:协变 父类泛型委托装子类泛型委托, 逆变 子类泛型委托装父类泛型委托
多线程
了解线程前先了解进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动
是系统进行资源分配和调度的基本单位,是操作系统结构的基础
进程之间可以相互独立运行,互不干扰
进程之间可以相互访问、操作
什么是线程
操作系统能够进行运算调度的最小单位
他被包含在进程之中,是进程中的实际运作单位
一条线程指的是进程中一个单一的顺序的控制流,一个进程中可以并发多个线程
我们写的程序都在主线程中
什么是多线程
我们可以通过代码开启新的线程
语法相关
线程类 Thread
需要引用命名空间 using System.Threading;
1.声明一个新的线程
注意线程执行的代码需要封装到一个函数中
static bool IsRuning=true;
static object obj =new object();
Thread t= new Thread(NewThreadLogic);
static void NewThreadLogic()
{
if(IsRuning)
//新开线程,执行的代码逻辑,在该函数语句块中
Console.WriteLine("新开线程运行");
}
2.启动线程
t.Start();
3.设置为后台线程
当前台线程都结束了的时候,整个程序也结束了,即使后台线程正在运行也不会妨碍
后台线程不会防止应用程序的进程终止掉
如果不设置为后台线程,可能导致进程无法正常关闭
t.IsBackground=true;
4.关闭释放一个线程
如果开启的线程中不是死循环,是能够结束的逻辑,那么不用刻意的去关闭它
如果是死循环,想要中止这个线程有两种方式
(1).死循环中的bool标识
t=null;
Console.ReadKey();
IsRuning=false;
Console.ReadKey();
(2).通过线程提供的方法(注意在.Net core版本中无法终止,会报错)
//终止线程
t.Abort();
5.线程休眠
//让线程休眠多少毫秒 1s=1000毫秒
//在哪个函数中执行就休眠哪个线程多少毫秒
Thread.Sleep(1000);
线程之间共享数据
多个线程使用的内存是共享的们都属于该应用程序(进程)
所以要注意当多线程同时操作同一片内存区域时可能会出现问题
可以通过加锁的形式避免问题
lock
当我们在多个线程当中想要访问同样的东西的时候,进行逻辑处理时
为了避免不必要的逻辑顺序执行的差错
lock(引用类型)
{
}
多线程对于我们的一样
可以用多线程专门处理一些复杂耗时的逻辑
比如寻路、网络通信等
反射
什么是程序集
程序集是经由编译器编译得到的,供进一步编译执行的那个中间产物
在Windows系统中他的一般表现为.dll(库文件)或者是.exe(可执行文件)的格式
元数据
元数据就是用来描述数据的数据
这个概念不仅仅是用于程序上,在别的领域也有元数据
反射
程序在运行是,可以查看其他程序集或者自身的元数据
一个运行的程序查看本身或者其他程序的元数据的行为叫反射
反射的作用
因为反射可以在程序编译后获得信息,所以它提高了程序的拓展性和灵活性
1.程序运行时得到所有元数据,包括元数据特性
2.程序运行时,实例化对像,操作对象
3.程序运行时创建新对象,用这些多像执行任务
class Test
{
private int i=1;
public int j=0;
public string str="123";
public Test()
{
}
public Test(int i)
{
this.i=i;
}
public Test(int i,string str):this(i)
{
this.str=str;
}
public void Speak()
{
Console.WriteLine(i);
}
}
语法相关
Type
Type(类的信息类)
他是反射功能的基础
他是访问元数据的主要方式
使用Type的成员获取有关类型声明的信息
有关类型的成员(如构造函数、方法、字段、属性和类的属性)
获取Type
1.万物之父object中GetType()可以获取对象的Type
2.通过typeof关键字传入类名也可以获取到对象的Type
3.通过类的名字也可以获取到类型
注意:类名必须包含命名空间,不然找不到
得到类的程序集信息
可以通过Type可以得到类型所在程序集信息
获取类中的所有公共成员
首先得到Type
然后得到所有的公共成员
需要引用命名空间 using System.Reflection;
获取类的公共构造函数并调用
1.获取所有的构造函数
2.获取其中一个构造函数并调用
得构造函数传入 Type数组 数组中的内容按顺序是参数类型
执行构造函数传入 object数组 表示按顺序传入的参数
2-1 得到无参构造
2-2 得到有参构造
获取类的公共成员变量
1.得到所有的成员变量
2.得到指定名称的公共成员变量
3.通过反射获取和设置对象的值
3-1 通过反射获取对象的某个变量的值
3-2 通过反射 设置指定对象的某个变量的值
获取类的公共成员方法
通过Type类中的GetMethod方法得到类的方法1
Methinfo是方法的反射信息
1.如果存在方法重载,用Type数组表示参数类型
2.调用该方法
注意如果是静态方法,Invoke中的第一个参数传入null即可
其他
得枚举:
GetEnumName
GetEnumNames
得事件:
GetEvent
GetEvents
得接口:
GetInterface
GetInterfaces
得属性:
GetProperty
GetInterfaces
等等
Activator
用于快速实例化对象的类
用于将Type对象快速实例化为对象
先得到Type
然后快速实例化一个对象
1.无参构造
2.有参数构造
Assembly
程序集类
主要用来加载其它程序集,加载后才能用Type来使用其他程序集中的信息
如果想要使用不是自己程序集中的内容,需要先加载程序集
比如dll文件
三种加载程序集的函数
一般用来加载同一文件下的其他程序集
Assembly assembly2=Assembly.Load("程序集名称");
一般用来加载不同文件夹下的其他程序集
Assembly assembly=Assembly.LoadFrom("包含程序集清单的文件的名称或路径");
Assembly assembly3=Assembly.LoadFile("要加载的文件的完全限定路径");
1.先加载一个指定的程序集
2.在加载程序集中的一个类对象,之后才能使用反射
class Program
{
static void Main(string[] args)
{
Console.WriteLine("反射");
int a=42;
//获取Type
Type type=a.GetType();
Console.WriteLine(type);
Type type2=typeof(int);
Console.WriteLine(type2);
Type type3=Type.GetType("System.Int32");
Console.WriteLine(type3);
//得到类的程序集信息
Console.WriteLine(type.Assembly);
Console.WriteLine(type2.Assembly);
Console.WriteLine(type3.Assembly);
//获取类中的所有公共成员
Type t=typeof(Test);
MemberInfo[] infos=t.GetMember();
for(int i=0;i
-----博主:mx
特性
什么是特性:
特性是一种允许我们向程序的程序集添加元数据的语言结构
他是用于保存程序结构信息的某种特殊类型的类
特性提供功能强大的方法以将声明信息与c#代码(类型、方法、属性等)相关联
特性与程序实体关联后,即可再运行时使用反射查询特性信息
特性的目的是告诉编译器把程序结构的某组元数据嵌入程序集中
他可以放置几乎所有的声明中(类、变量、函数等等声明)
自定义特性
继承特性基类:Attribute
class MyCustomAttribute:Attribute
{
public string info;
public MyCustomAttribute(string info)
{
this.info=info;
}
}
特性的使用
基本语法:[特性名(参数列表)]
本质上 就是在调用特性类的构造函数
写在哪里?
类、函数、变量上一行,表示他们具有该特性信息
限制自定义特性的使用范围
通过为特性类加特性限制其使用范围
[AttributeUsage(AttributeTargets.Class)|AttributeTargets.Struct|AttributeTargets.Field,AllowMultiple=true,Inherited=true)]
参数一: AttributeTargets ---特性能够用在哪些地方
参数二: AllowMultiple---是否允许多个特性实例用在同一个目标上
参数三: Inherited---特性是否能被派生类和重写成员继承
系统自带特性---过时特性
过时特性
Obsolete
用于提示用户使用的方法等成员已经过时了,建议使用新方法
一般加在函数前的特性
参数一:调用过时方法时,提示的内容
参数二:true-使用该方法时会报错,false-使用该方法时直接警告
[Obsolete("OldSpeak方法已经过时了,请使用Speak方法"),true]
系统自带特性---调用者信息特性
哪个文件调用?
CallerFilePath特性
哪一行调用?
CallerLineNumber特性
哪个函数调用?
CallerMemberName特性
需要引用命名空间 using System.Runtime.ComilerServices;
一般作为函数参数的特性
[MyCustom("这个是我自己写的一个用于计算的类")]
class MyClass
{
[MyCustom("这个一个成员参数")]
public int value;
[MyCustom("这个一个用于计算加法的类")]
public void TestFun([MyCustom("函数参数")] int a)
{
}
}
系统自带特性---条件编译特性
条件编译特性
Conditional
它会和预处理指令 #define配合使用
需要引用命名空间 using System.Diagnostics
主要可以用在一些调试代码上
有时想执行有时不想执行的代码
系统自带特性---外部Dll包函数特性
DllImport
用来标记非.Net(c#)的函数,表名该函数在一个外部的DLL中特性
一般用来调用其他语言的Dll写好的方法
需要引用命名空间 using System.Runtime.InteropServices
[DllImport("Test.dll")]
public static extern int Add(int a,int b);
class Program
{
static void Main(string[] args)
{
Console.WriteLine("特性");
MyClass mc=new MyClass();
Type t=mc.GetType();
//t=typeof(MyClass);
//t=Type.GetType("路径名称");
//判断是否使用了某个特性
//参数一:特性的类型;参数二:代表是否搜索继承链(属性和事件忽略此参数)
if(t.IsDefined(typeof(MyCustomAttribute),false))
{
Console.WriteLine("该类型应用了MyCustomAttribute的特性");
}
//获取Type元数据中的所有特性
object[] array=t.GetCustonAttributes(true);
for(int i=0;i
迭代器
迭代器是什么
迭代器(iterator)有时又称光标(cursor)
是程序设计的软件设计模式
迭代器模式提供了一个顺序访问一个聚合对象中的各个元素
而又不保留其内部的表示
从表现效果上看
是可以在容器对象(例如链表或者数组)上遍历访问的接口
设计人员无需关心容器对象的内存分配的实现细节
可以用foreach遍历的类,都是实现了迭代器的
标准迭代器的实现方法
关键接口:IEnumerator,IEnumerable
命名空间:using System.Collections;
可以通过同时继承IEnumerable和IEnumerator实现其中的方法
class CustomList:IEnumerable,IEnumerator
{
private int[] list;
//从-1开始的光标用于表示数据得到了哪个位置
private int position=-1;
public CustomList()
{
list=new int[]{1,2,3,4,5,6,7,8};
}
public IEnumerator GetEnumerator()
{
Reset();
return this;
}
public object Current{get{return list[position];}};
public bool MoveNext()
{
++position;
//是否移除,溢出就不合法
return position:IEnumerable
{
private T[] list;
public CustomList3(params T[] array )
{
this.list=list;
}
public IEnumerator GetEnumerator()
{
for(int i=0;i
-----博主:mx
class Program
{
static void Main(string[] args)
{
Console.WriteLine(“迭代器”);
CustomList list =new CustomList();
//foreach本质
//1.先获取in后面这个对象的 IEnumerator
//会调用其中的GetEnumerator方法来获取
//2.执行得到这个IEnumerator对象中的MoveNext方法
//3.只要MoveNext方法的返回值时true,就会得到Current,然后赋值给item
foreach(int item in list)
{
Console.WriteLine(item);
}
CustomList2 list2 =new CustomList2();
foreach(int item in list2)
{
Console.WriteLine(item);
}
CustomList3 list2 =new CustomList3("123","123312","456");
foreach(string item in list2)
{
Console.WriteLine(item);
}
}
}
总结:
迭代器就是可以让我们在外部直接通过foreach遍历对象中元素而不需要了解其结构
主要的两种方式
1.传统方式 继承两个接口,实现里面的方法
2.用语法糖yield return 去返回内容,只需要继承一个接口
特殊语法
var隐式类型
var是一种特殊的变量类型
它可以用来表示任意类型的变量
注意:
1.var不能作为类的成员,只能用于临时变量声明时
也就是一般写在函数语句块中
2.var必须初始化
var i =5;
var s=“123”;
var array=new int[]{1,2,3,4,5};
var list =new List();
设置对象的初始值
声明对象时
可以通过直接写大括号的形式初始化公共成员变量和属性
Person p=new Person(){sex=true,age=15,name="好耶"};
设置集合初始值
声明集合对象时
也可以通过大括号,直接初始化内部属性
int[] array2=new int[]{1,2,3,4,5,6};
List listInt=new list(){1,2,3,4,5,6};
匿名类型
var变量可以声明为自定义的匿名类型,只能有成员变量,不能有函数
var v=new{age=10,money=11,name="小明"};
可空类型
1.值类型是不能赋值为空的
2.声明时再值类型后面加? 可以赋值为空
int? c=null;
3.判断是否为空
if(c.HasValue)
{
Console.WriteLine(c);
Console.WriteLine(c.Value);
}
4.安全获取可空类型值
4.1 如果为空,默认返回值类型的默认值
Console.WriteLine(value.GetValueOrDefault());
4.2 也可以指定一个默认值
Console.WriteLine(value.GetValueOrDefault(100));
//相当于是一种语法糖能够帮助我们自动判断o是否为空
//如果是null就不会执行tostring也不会报错
object o=null;
Console.WriteLine(o?.ToString());
int[] arrayInt=null;
Console.WriteLine(arrayInt?[0]);
Action action=null;
action?.Invoke();
空合并操作符
空合并操作符 ??
左边值 ?? 右边值
如果左边值为null 就返回右边值 否则返回左边值
只要是可以为null的类型就能用
int? intV=null;
//int intI= intV==null? 100 : intV.value;
int intI=intV ?? 100;
内插字符串
关键符号 $
用$来构造字符串,让字符串可以拼接变量
string name="好耶";
int age=18;
Console.WriteLine($"好好学习,{name},年龄:{age}");
单句逻辑的简略写法
循环,判断if只有一句时,只会执行接着的第一句
if(true)
Console.WriteLine("好耶");
while(true)
Console.WriteLine("好耶");
for(int j=0;j<10;j++)
Console.WriteLine("好耶");
函数只有一句时有返回值是直接返回,无返回值则执行操作
public int Add(int x,int y) => x+y;
public void Speak(string str) => Console.WriteLine(str);
值和引用类型
值类型:
无符号:byte,ushort,uint,ulong
有符号:sbyte,short,int,long
浮点数:float,double,decimal
特殊:char ,bool
枚举:enum
结构体:struct
引用类型:
string
数组
class
interface
委托
值类型和引用类型的本质区别
值类型的具体内容存在栈上
引用类型的具体内容存在堆上
问题一:如何判断值类型和引用类型
F12进入到类型内部去查看
是class就是引用类型
是struct就是值类型
问题二:语句块
命名空间
类、接口、结构体
函数、属性、索引器、运算符重载等(类、接口、结构体)
条件分支、循环
上层语句块:类、结构体
中层语句块:函数
底层语句块:条件分支 循环等
我们的逻辑代码写在哪里?
函数、条件分支、循环--中底层语句块中
我们的变量可以声明在哪里?
上中底都能声明变量
上层语句块中,成员变量
中底层语句块中:临时变量
问题三:变量的生命周期
编程时大部分都是临时变量
在中底层声明的临时变量(函数、条件分支、循环语句块等)
语句块执行结束
没有被记录的对象将会被回收或变成垃圾
值类型:被系统自动回收
引用类型:栈上用于存地址的房间会被系统自动回收,堆中具体内容垃圾,待下次gc回收
想要不被回收或者不变垃圾
必须将其记录下来
如何记录?
在更高层级记录或者使用全局变量记录
问题四:结构体中的值和引用
结构体本身是值类型
前提:该结构体没有作为其他类的成员
在结构体中的值,栈中存储的具体内容
在结构体中的引用,堆中存储引用的具体内容
引用类型始终存储在堆中
真正通过结构体使用其中的引用类型时只是顺藤摸瓜
问题五:类中的值和引用
类本身是引用类型
在类中的值,堆中存储具体的值
在类中的引用,堆中存储具体的值
值类型跟着大哥走,引用类型一根筋
问题六:数组中的存储规则
数组本身是引用类型
值类型数组,堆中空间存具体内容
引用类型,堆中空间存地址
问题七:结构体继承接口
利用里氏替换原则,用接口容器装载结构体存在装箱拆箱
-----博主:mx,看到这里了,不给个赞收藏评论吗?哈哈哈哈哈