C#中的类 class
类class:访问修饰符 class 类名{ 方法体....}
类的访问修饰符(两种):public internal(默认)
类的成员(方法和变量)访问修饰符(4种):public internal protected private(默认)
类分两种:普通类(非静态类)和静态类static:静态类中所有成员必须是静态的,关键字必须在访问修饰符后面;而普通类可以是静态也可以是动态没限制。
类有三个特性:
1、封装:封装与对象相关数据和动作组合,意思就是在一个类中写代码就叫封装。
2、继承:代码重用,继承父类的一些方法,避免在去写这个方法。
3、多态:多种表现,掉用父类的某个方法,实现不同的表现。比如:叫方法,狗去调用是一种叫法,猫去调用又呈现另一种叫法。
注意:
和类相似的:结构体struct。语法:访问修饰符 struct 结构名 { }
人们把内存分为五个区域【栈:存放值类型数据立刻初始化;堆:存放引用类型数据不会立刻初始化如无用会被gc垃圾回收;静态存储区域:存放的是静态成员一直存最新数据直到程序关闭为止;常量区和代码区】
结构体和类区别:1.结构是值类型存放栈中,类是引用类型存放堆中;2.结构体没有析构函数,因为值类型栈中立即释放不存在gc垃圾回收器。3.结构调用可new可不new,类必须new。4.结构不能定义无参构造,类可以。
静态和非静态的区别:静态都存放在静态存储区域,普通类与实例成员都是存放堆中。
值类型:用结构体struct定义的就是值类型,如:int的类型就是struct;f12转到代码定义就可以看到,注意【值类型不能被继承】
引用类型:用类class定义的类型就是引用类型,如:string的数据类型是class;【引用类型可以被继承】
1、封装字段与属性
字段为什么用到属性,属性可以对字段进行过滤,比如说,身份证号码,正常变量可以随便乱赋值,而属性就可以限制字段18位。字段默认私有的,需要公有属性给私有字段赋值。
字段和属性的区别:属性不能被传递,字段可以,作用于面向对象,类内部访问可以不需要属性,外部访问必须用属性,比如实体类字段必须定义为属性。
pro p = new();//实例化new:把类变为一个对象叫实例化过程。其中pro p开辟一个空间无数据,new才是创建一个对象。 Console.WriteLine(p.getStr = "55"); class pro { string str = "11";//在类叫字段,方法中叫变量,没区别,变量:在方法里叫局部,方法外叫全局 public string getStr { get { return str; } set { str = value; } } //属性c#5.0以前的语法,用公有属性访问私有字段。 public string _getStr { get => str; set => str = value; } //简写,注:value是set访问器的关键字,作用是将外部值赋值给私有字段。 public string __getStr => $"这是6.0的get只读语法{str},适合代码少用。";//和上面一句代码是一样的。不过只有get访问器,没有ste访问器。 //自动属性:prop+两下tab自动生成后备代码相对应私有字段,C#6以后支持合并 public int Pi { get; set; } //属性首字母大写,字段首字母小写。 还可以这样写:{ get; private set; } 只能当前类设置值。 public int pi;//字段和属性的区别:字段可以被当做参数传递;属性不可以做参数传递,相当于保护层。 }
2、构造方法
与类同名且没有返回值,的方法叫,构造方法:实例化对象new的时候自动调用,根据参数调用,作用于初始化类的成员。可以重载但不能有返回值。访问修饰符必须是public
每个类中默认自带一个无参构造。当我们定义了有参构造,无参构造就会消失,如果还需要无参构造时,需要手动写无参构造方法。
初始值的另一种方式:pro p = new pro(){ id =1, str ="字符串"..} 这种直接给字段赋值,不需要进入构造函数。
pro p = new pro("构造方法传初始化值");//一个括号就表示类的构造函数 //p[0]= "11"; Console.WriteLine(p[0]); class pro { public pro() { }//构造函数访问修饰符必须是public public pro(string str, int i,int a) //当定义了有参构造,默认的无参构造会消失,需要重新写无参构造 { this.str = str;//this指当前类,作用1:当全局变量与局部变量同名,this关键字可以指向类中的全局变量。 this.i = i; this.a = a; } public pro(string str) : this(str, 0, 0) { }//this作用2:调用多参数的构造函数,方法体不需要写。 public string str { get; set; } public int i { get; set; } public int a { get; set; } //this作用3:索引器 public string this[int index] //可以重载的 { get { return index switch { 0=>str, 1=>i.ToString(), 2=>a.ToString(), _=>"不在制定范围" }; } set { var v = index switch { 0 => str = value, _ => "不在制定范围" }; } } }
3、析构方法
语法:~类名(){ }
作用:回收内存空间,当对象执行结束后执行。并且每个类只能有一个析构方法。
Pro p = new Pro(); class Pro { public Pro(){ Console.WriteLine("构造执行前调用,new的时候调用,初始化数据.");} ~Pro() { Console.WriteLine("析构方法结束后自动调用,做一些垃圾处理。调用时间不确定。"); } }
4、索引器:用于封装数组的
pro[] p = new pro[3];//类也是一种引用类型,例如string,用类名定义数组就可以存对应数据类型字段。 pro p1 = new pro("张三", 1); p[0] = p1; pro p2 = new pro("张三", 22); p[1] = p2; foreach (pro pro in p) { if (pro != null) { Console.WriteLine(pro.i + "" + pro.str); } } class pro { public string str { get; set; } public int i { get; set; } public pro(string str, int i) { this.str = str;this.i = i; } public pro this[int index] //索引器是可以重载的 { get { return this[index]; } set { this[index] = value; } } }
5、继承与多态
继承:一个类继承另一个类,子类继承父类,可以使用父类里的成员方法(私有除外)。
语法:class B:A { } 这样B类叫子类(派生类),A类叫父类(基类),类之间就有了父子关系。
继承的两个特点:所有类默认继承object类,如果指明父类那就是父类。构造方法:先执行父类在执行子类,如果父类定义了构造,子类必须实现其中一个。
(1)单根性:一个子类只能有一父类,如果出现:class B:A,C 那么c只能是接口,父类只能有一个接口可以多个。
(2)传递性:如c类继承B类,B类继承A类,那么c类同时可以拥有B类和A类所有成员(私有除外)
B b = new B("张三", "1111111"); b.Jiao(); C c = new("李四", "2222222",1); c.Jiao(); class A //父类,有默认继承object类。 { public A(string type, string color)//构造函数,初始化值 { this.Type = type; this.Color = color; } public string Type { get; set; }//类型 public string Color { get; set; }//颜色 public void Jiao() { Console.WriteLine("{0}:{1},继承只是调用不改变方法体本身,需要改变那要定义为虚方法(重写)",Type,Color); } } class B : A //一个子类只能有一个父类,多余的是接口。 { public B(string type, string color): base(type,color){ } //base关键字:表示父类的构造方法传参 } class C : A { public C(string type, string color,int i) : base(type, color) { this.i = i; //父类有写的多余的代码不用写 } public int i { get; set; } }
多态(重写):不同子类继承父类的一个有不同实现。父类定义一个虚方法,将父类的虚方法重新写一次。调用子类虚方法,如果不重写自动调用父类虚方法。
父类定义虚方法:virtual,子类重写父类虚方法:override。重写规定只能改变方法体代码,其他一律不能更改(如访问修饰符,参数,返回值等)。
B b = new B("灰"); b.Jiao(); C c = new("白"); c.Jiao(); class A //父类,有默认继承object类。 { public A(string type, string color)//构造函数,初始化值 { this.Type = type; this.Color = color; } public string Type { get; set; }//类型 public string Color { get; set; }//颜色 //虚方法关键字virtual表示此方法可以被重写,其实和普通方法没有区别,普通方法默认sealed表示不能被重写 public virtual void Jiao() { Console.WriteLine("这是父类的虚方法,关键字:virtual,如果不重写默认调用父类虚方法。自动调用"); } } class B : A //一个子类只能有一个父类,多余的是接口。 { public B(string color): base("狗",color){ } //base关键字:表示父类的构造方法传参 public override void Jiao() { Console.WriteLine("这是一只{0}的{1},汪汪汪叫,重写关键字:override", Color, Type); } } class C : A { public C(string color) : base("猫", color) {}//子类重写父类的方法就叫多态。实现多种表现。 public override void Jiao() { Console.WriteLine("这是一只{0}的{1},喵喵喵叫,重写改变可方法体", Color, Type); } }
抽象abstract:抽象方法只能放抽象类中,抽象类也可以有普通方法体,抽象类因为有抽象方法没有被实现,使用抽象类不能被实例化,只能被子类继承去实现,重写抽象方法。
和虚方法相似,都是可以实现多态。虚方法有方法体,抽象方法没方法体;虚方法可以在普通类也可以是抽象类,而抽象方法只能定义在抽象类中;虚方法可重写可不重写,抽象方法必须实现重写。
抽象方法和虚方法应用场景一样,只是规范了,虚方法的一个抽象概念,避免实例化对象new的时候把虚方法也new了出来,用不到就没有意义。
二者的选择:如果重写的方法体代码差不多都一样用虚方法,完全不一样就用抽象方法。
B b = new(); b.Jiao(); abstract class A //抽象类abstract,跟普通类区别,因为有抽象方法,只能被子类继承去实现重写抽象方法,不能够单独实例化对象。 { public abstract void Jiao();//抽象方法只能定义在抽象类中,不能有方法体,子类继承后必须实现(重写)override, } class B : A //父类鼠标选择后,快捷键 Alt+Shift+F10,ctrl+. ,alt+enter,这三个都可以实现抽象类 { public override void Jiao() //override用于重写,父类虚方法virtual 或 抽象方法abstract { //throw new NotImplementedException();//这个代码无用自动生成的,代码意思是该方法没有实现,默认抛出异常。 Console.WriteLine("实现父类抽象方法,也是重写:override,和虚方法类似,只不过虚方法可以不用实现,默认调用父类虚方法。"); } }
6、接口 interface
接口名命名规范:必须大小I开头 ,后跟首字母大写名开头词,如:IBian; 语法:interface IBian { } 接口也属于【多态】
接口只能定义:【属性,方法,索引器】访问修饰符默认是public,方法可以是不实现让子类去实现,也可以是默认实现的子类可以不需要实现。
抽象类单继承,接口多实现:一个类只能继承一个父类,但可以实现多个接口。如:class D:A,B,C 其中只能有一个父类A,另外B和C都是接口。
A a = new(); a.jiao(); IBian i = new A(); //调用接口默认实现的方法 i.a(); interface IBian //接口定义是给子类去实现的,如果不实现就没有意义 { void jiao();//接口方法,和抽象方法一样没有方法体,但子类必须实现。不需要访问修饰符,默认public公共的。 void a() { Console.WriteLine("加方法体的默认自动实现接口。子类实现也可以,不实现也可以"); } } class A : IBian//接口鼠标选择后,快捷键 Alt+Shift+F10,ctrl+. ,alt+enter,这三个都可以实现接口方法 { public void jiao() { //throw new NotImplementedException(); Console.WriteLine("实现接口方法:因为抽象方法是单继承,而接口可以实现多个接口。"); } }
显示实现接口:当实现多个接口,出现同名方法时,就必须显示实现接口。
A a = new(); //调用同名方法的接口必须类型转换 ISql isql = (ISql)a; isql.Sql(); IMySql imysql = (IMySql)a; imysql.Sql(); interface ISql { void Sql();//定义两个接口方法名相同 } interface IMySql { void Sql(); } class A : ISql, IMySql //接口鼠标选择后,快捷键 Alt+Shift+F10,ctrl+. ,alt+enter,选择显示实现接口, { void IMySql.Sql() { Console.WriteLine("打开了MySql"); } void ISql.Sql() { Console.WriteLine("打开了Sql"); } }
接口和抽象类,作用于模块化设计,用接口来列举
void A(IInfo info) //将接口设计为方法通用。起到弱连接的作用:需要就连接不需要就断开。 { Console.WriteLine(info.GetName() + ":" + info.GetAge()); } Student s = new Student() { Name = "张三", Age = 18 }; A(s); Teacher t = new Teacher() { First = "c#", Last = "编程"}; A(t); interface IInfo //接口是引用类型传递 { string GetName(); string GetAge(); } class Student : IInfo { public string Name { get; set; } public int Age { get; set; } public string GetAge() { return Age.ToString(); } public string GetName() { return Name; } } class Teacher : IInfo { public string First { get; set; } public string Last { get; set; } public string GetAge() { return First; } public string GetName() { return Last; } }
7、扩展方法:【this 类名 变量名】给类添加新方法
A a = new A(); a.a("不改变类的代码在原理的类中添加新的方法。"); a.b("我在A对象里也可以使用扩展方法:","b"); int i = 1; i.ToInt();//这是下面定义的扩展方法。 class A { public void a(string str) { Console.WriteLine(str); } } static class B//扩展方法只能定义在静态类中,静态方法的参数中,语法:【this 类名 变量名】 { public static void b(this A ss,string str,string s) { Console.WriteLine(str + s); } public static void ToInt(this int i) { Console.WriteLine("给int类型添加,扩展方法。"); } }
8、分部类: partial 把多类合并起来
定义在class前面,类名必须一样,其实指定的就是同一个类,只是代码太多,通过partial分开来写
A s = new A(); s.a(); s.b(); partial class A { public void a() { Console.WriteLine("当代码太多一个类写不下来"); } } partial class A { public void b() { Console.WriteLine("两个方法就定义在同一个A类中"); } }
9、匿名对象
语法:var 变量名 = new { }; 在方法里面可以直接使用不需要传递,减少代码提高效率
var ff = new { name = "张三", age = 19 };//等同于类名中的字段 Console.WriteLine(ff.name);
关键字nameof(参数)叫同步变化,动态变化。就是外界名称发生改变时,参数随之改变。参数可以是:类名,方法名,字段名,属性名。
Console.WriteLine(nameof(ff.name));//就是当外面的ff变为fff时,nameof的参数也会自动变为fff