类与对象

什么是类?

  • 在面向对象的思想中最核心的就是对象,为了在程序中创建对象,首先需要定义一个类;
  • 类可以理解为一张设计图,描述了一个对象都有什么构成,能做到哪些事情;
  • 类就是对象的抽象,用于描述一组对象的共同属性和行为。

对象内存布局

image-20241106112930954.png
对象变量为引用类型,new出来的多个对象,分别布局在了不同的内存上

成员变量vs局部变量

image-20241106102727286.png

this

this引用

this其实就是当前对象本身引用,调用对象函数时,可以使用其引用类内成员变量或者方法。

解决问题:

  • 解决类内方法调用,局部变量与成员变量重名问题

this关键字使用方法:

  • 引用本对象成员变量:this.类内成员变量名
  • 引用本对象成员方法:this.类内成员方法名

变量名不冲突前提下,是可以省略的

构造方法

构造方法(Constructor):当对象被new创建出来的时间点,会自动调用该方法,没有编写的情况下,C#自动生成默认构造函数。

编写规范:

  • 方法名与类名相同,大小一致
  • 无返回值,无void
  • 内部不得写return

调用细节

  • 每创建一个对象,调用一次
  • 无法手动调用

static

static关键字

static是静态的的意思,既然是静态的,就是不变的意思,而不变就是它不会因为类中实例化对象的不同而不同,它在类中永远只有一份就像中国人有许多,但是我们这么多人只有一个国家——中国。

static可以修饰类、可以修饰成员变量,也可以修饰成员方法。标记为static的就不用创建实例来调用了,如果外部要对其进行调用,可以直接通过(类名.方法名)来调用,内部就可以直接通过方法名调用。

需求:同一个类生成的对象们,能够共享此变量,一处更改,其他对象都可以感知

规则:

  1. 被类生成的所有对象共享
  2. 调用方法:类名.变量名 类名.方法名()
  3. 随着类加载而加载,优先于对象生成
  4. 静态方法内部,只能够访问静态成员变量,不能够访问普通成员变量
  5. 普通方法内部,可以访问静态成员变量和普通成员变量

static变量初始化

static变量初始化分为两种途径:

  1. 定义时初始化

    • 缺点:不能够执行一些静态变量初始化的逻辑代码。
    class Player
    {
    	public static int count=0;
    }
    
  2. 静态构造方法初始化

    • 定义:不允许加入访问修饰符
    • 执行:类加载的时候自动执行
    • 覆盖:覆盖变量定义初始化的值
    • 禁止:其不可访问非静态成员
    class Player
    {
        public static count=0;
        static Player(){
            //执行逻辑
            //在此处可以进行相关逻辑编写:比如从配置文件当中读取count的数值
            count=100;
        }
    }
    

    **注意:**静态构造方法是加载类的时候加载的,普通构造方法是new了一个对象的时候加载的,不冲突

static 静态类

static关键字可以修饰类(class),被其修饰的类,称为静态类(static class)

static class Player
{
	public static int count = 0;
    static Player()
    {
        count=100;
	}
    public static void Show()
    {
        Console.WriteLine(count);
    }
}

静态类规则:

  • 定义类:static class 类名
  • 成员:只允许加入静态成员变量/属性、静态成员方法
  • 实例化:不允许使用new进行生成实例

工具类:

特点:在当前类中,没有存储字段数据

  1. 不需要也不允许new出来对象实例
  2. 内部的方法都为静态方法
  3. 所做的计算都依赖于外界传入的参数,完成外界想让我们完成的任务

命名空间(namespace)

命名空间:用来在大范围上区别各个类名,可以防止类名重复带来的问题

namespace OOPNameSpace
{
    
    internal class Program
     {
         static void Main(string[] args)
         {
			Lisi.Person lisiperson= new Lisi.Person();
         }

     }
}

namespace Zhangsan
{
   class Person
   {
      public string Name {get;set;}
   }
}

namespace Lisi
{
   class Person
   {
      public string Id{get;set;}
   }
}

using 命名空间

using关键词:一旦使用了using 命名空间;将此命名空间下的内容,合并到了主命名空间下

可以直接使用此命名空间下的class了

面向对象三大特征

封装、继承、多态

封装

设计类时:

  1. 将本类型相关数据消息都放入类内统一管理
  2. 将本类型相关方法都放入类内统一管理
  3. 将用户需要的内容暴露给用户,细节进行隐藏
封装权限控制

权限:用于规定类内部属性/方法,能否被特定区域访问的规则

权限修饰符

  • private 私有访问权限: 只允许类内部方法访问、修改
  • protected 受保护访问权限:允许在声明它的类及其派生类内部访问,但仅限于同一程序集。
  • internal 内部访问权限:允许在同一程序集中的任何类访问,但不允许从其他程序集访问,即使是派生类也不行。
  • public 公开权限:从任何地方都可以对其进行访问、修改
  • protected internal:唯一的一种组合限制修饰符,可以被同一程序集中的任何代码或不同程序集中的派生类访问。

注:Class若未声明其访问权限,则默认为public,成员变量若未声明其访问权限,则默认为private

可以修饰:类、成员变量、成员方法

属性与字段

字段(Field):在类内定义的变量,用于确定数据在内存中的存储。

属性(Property):提供对字段的访问器(Accessor)。

class Student
{
    //Student类的字段name
 	private string name = "carma";
    
    //name对应的属性
    public string Name
    {	
        //set
        //value就是调用set方法的时候传入的字符串参数
        set{name=value;}
        
        //get
        get{return name;}
    }
}

internal class Program
{
    static void Main(string[] args)
    {
        Student s = new Student()
		s.Name="liuliu";
		Console.WriteLine(s.Name);
    }
}

属性用途:

  1. 属性值验证:通过get/set方法,对传入的数据进行参数化合法性的验证

    class Student
    {
     	private string score = 0;
        
        public string Score
        {	
            set{
                 if(value<0)
                	{
                    	Console.WriteLine("成绩不能小于0");
                   	 return;
               	 	}
                	score=value;
            	}
            get{return name;}
        }
    }
    
    
  2. 字段访问形式:通过get/set方法,可以给到用户合适的数据格式

    class Clock
    {
        private double seconds;
        
        public double Hours
        {
            get{return seconds/3600;}
            set{seconds=value*3600;}
        }
        public double Minutes
        {
            get{return seconds/60;}
            set{seconds=value*60;}
        }
    }
    
属性字段省略方式

通过定义属性,可以省略字段的定义。

image-20241107110754231.png

  • 在类外方法中,可以直接通过Money属性进行字段读写
  • 在类内方法中,可以直接通过Money属性进行字段读写
  • money字段被自然隐藏
属性访问省略规则
  1. 通过属性省略字段的情况下,不可省略get方法;如果省略了get,类内/类外方法均无法访问字段,无意义

  2. 通过属性省略字段情况下,可省略set方法

  3. set方法省略后,可以通过构造方法赋予初值;类内其他方法不可对Money赋值,只能读取

    class Player
    {
        public int Money {get;}
        public Player(){Money=100;}
    }
    

    只读属性在构造方法初始化后,就无法更改了

get/set权限

属性(Property)当中,get/set也拥有权限控制,可以解决类内访问省略字段问题

访问器限定符的修饰权大于属性访问限定符

image-20241107113252702.png

**注意:**不能为一个属性的两个访问器同时增加private限定符

属性的访问限定符:用于修饰属性的访问

——属性访问限定符能够同时对set跟get进行限定

  1. 如果get/set没有限定符,则沿用属性限定符
  2. 如果get/set拥有比属性更严格的限定符,则使用访问器自己的限定符
  3. 如果get/set拥有的限定符严格程度小于或等于属性的限定符,则出错

继承

概念

定义:用于描述两个类(Class)之间的父子关系,子类可以直接使用父类中的非私有成员

继承的格式

格式

格式:class 子类名:父类名 {}

Pet:是父类,也称为基类

Dog:是子类,也称为派生类

规范语言:

  • Dog 继承自 Pet
  • Pet 派生出了Dog

image-20241111110818210.png

父类:是一个大的类型,封装了本类型通用的成员变量/成员方法,直接被子类继承使用

子类:是一个特殊的类(特例的类),封装了本子类独有的成员变量/成员方法

继承当中的成员变量与成员方法的访问规则
  1. 如果成员变量的名字于父类变量相同,则直接使用的时候采用子类的变量,父类变量隐藏
    • 如果你是故意在子类中声明父类同名变量,加一个new表示你是有意为之
    • 如果在子类中想要访问父类中的同名变量,可以加base.变量名
  2. 如果成员方法的签名与父类方法签名相同,则直接访问的时候采用子类的方法,父类方法隐藏
    • 如果你是故意在子类中声明父类相同签名方法,加一个new表示你是有意为之
    • 如果在子类中想要访问父类中的同签名方法,可以加base.方法名()
    • 如果在子类中声名的方法签名与父方法不同(参数),且构成重载,则不会隐藏父类方法
继承成员访问权限
权限修饰符同类中子类外部类
private
protected
public
继承中的构造方法
  1. 当实例化一个子类对象的时候,首先调用其父类构造方法,再调用其子类构造方法

  2. 当实例化一个子类对象的时候,如果父类只拥有带参构造方法,则需要显示调用其父类构造方法

    :base()

注意:

  • 子类构造方法如果显示调用父类某一个构造方法,则遵从其调用选择
  • 子类构造方法没有显示调用父类的任何构造方法,则默认寻找其无参构造方法

多态

概念

多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。父类的同一种行为,在不同的子类上有不同的实现,这种实现叫做多态。

根据网上的教程,我们得知C#多态性分为两类,静态和动态。但实际上,C#没有严格的静态和动态多态性的分法。之所以这么分,还是为了我们便于理解,我们沿用这个思维来大概分类:

  • 采用函数重载或运算符重载方法的,属于静态多态性
  • 采用虚方法、抽象方法、接口等方式,属于动态多态性
虚方法

virtual关键字用于在基类中声明一个方法,允许在派生类中被重写。当基类中定义了一个virtual方法,派生类可以选择重写这个方法。如果派生类没有重写,那么调用该方法时将使用基类的实现。如果派生类使用override关键字重写了该方法,那么调用该方法时将使用派生类中的实现。这提供了一种多态性,允许派生类根据需要改变基类方法的行为。

public class BaseClass
{
public virtual void VirtualMethod()
{
Console.WriteLine("Base implementation of VirtualMethod");
}
}

public class DerivedClass : BaseClass
{
public override void VirtualMethod()
{
Console.WriteLine("Derived implementation of VirtualMethod");
}
}

通过父类调用对应子类的方法实现

image-20241112095447503.png

注意:

  • 父类方法与子类方法签名一致
  • virtual:修饰的方法为父类的虚拟方法,表示可以被子类方法完全抹杀
  • override:修饰的方法为子类的重写方法,表示可以完全抹杀父类对应方法
  • virtual与override成对存在才能构成多态,否则就是单纯子类隐藏父类方法
重写(override)与隐藏(hide)
class Pet
{
    //虚方法
    virtual public void shout()
    {
        Console.WriteLine("Pet");
    }
    virtual public void eat()
    {
        Console.WriteLine("Pet Eat");
    }
}

class Cat:Pet
{
    //多态重写
    override public void shout()
    {
        Console.WriteLine("Miao");
    }
    //隐藏父类方法
    new public void eat()
    {
        Console.WriteLine("Cat Eat");
    }
}

class Dog : Pet
{
    //隐藏父类方法
    new public void shout()
    {
        Console.WriteLine("Wang");
    }
    //多态重写
    override public void eat()
    {
        Console.WriteLine("Dog Eat");
    }
}
多态细节
  1. 父类的虚拟方法必须在子类中”可见“,即不可为private
  2. 在多层继承环境中,多态会沿着继承链条进行重写方法的寻找锁定

抽象类与抽象方法

在C#中,abstract 关键字用于定义抽象类和抽象方法。它是面向对象编程中的重要概念,允许你为子类提供基本框架,而不需要在基类中实现所有的功能。我们来具体看看如何使用 abstract 关键字。

抽象类(Abstract Class)

抽象类是一个特殊的类,它不能被实例化。抽象类的主要目的是作为其他类的基类,为派生类提供一个通用的模板。抽象类可以包含抽象方法和非抽象方法。

抽象类使用abstract关键字来声明

public abstract class Animal
{
    public abstract void MakeSound(); // 抽象方法

    public void Move() // 非抽象方法
    {
        Console.WriteLine("The animal moves.");
    }
}

注意:

  • 抽象类中可以有抽象方法,也可以没有抽象方法
  • 抽象方法只能存在抽象类中,不能存在普通类中
抽象方法(Abstract Method)

抽象方法是一个没有实现的方法,它必须在任何非抽象派生类中被重写。抽象方法使用abstract关键字声明,并且没有方法体(即没有花括号{}及其中的内容)。

public abstract class Animal
{
    public abstract void MakeSound(); // 抽象方法,没有方法体
}

//非抽象类中不能有抽象方法
//public class NonAbstract
//{
//    public abstract void display(string text);
//}

任何继承自抽象类的非抽象类都必须提供抽象方法的实现,例如:

public class Dog : Animal
{
    public override void MakeSound() // 重写抽象方法
    {
        Console.WriteLine("The dog barks.");
    }
}

在这个例子中,Dog类继承自Animal类,并提供了MakeSound方法的具体实现。注意,使用override关键字来表示该方法是对基类中抽象方法的重写。

使用抽象类和抽象方法

抽象类和抽象方法的主要用途是创建一个类结构,其中基类定义了一组通用的属性和方法,而派生类则提供这些方法的特定实现。这种方法允许代码重用和灵活性,因为你可以创建多个派生类,每个类都有自己独特的行为,同时共享基类的通用行为。

// 抽象类
public abstract class Shape
{
    public abstract double GetArea(); // 抽象方法
}

// 继承自Shape的非抽象类
public class Circle : Shape
{
    private double radius;
    
    public Circle(double radius)
    {
        this.radius = radius;
    }
    
    public override double GetArea() // 重写抽象方法
    {
        return Math.PI * radius * radius;
    }
}

// 另一个继承自Shape的非抽象类
public class Rectangle : Shape
{
    private double width;
    private double height;
    
    public Rectangle(double width, double height)
    {
        this.width = width;
        this.height = height;
    }
    
    public override double GetArea() // 重写抽象方法
    {
        return width * height;
    }
}

在这个例子中,Shape是一个抽象类,它有一个抽象方法GetArea。Circle和Rectangle类都继承自Shape类,并提供了GetArea方法的具体实现。这样,你就可以创建Circle和Rectangle对象,并调用它们的GetArea方法来计算面积,而不需要关心它们的具体实现细节。

抽象和多态的联系
  1. 抽象是多态的基础:多态的实现通常依赖于抽象类或接口。抽象类定义了子类必须实现的方法,而接口则定义了实现类必须遵循的规范。这些抽象方法或接口方法在不同的子类中可以有不同的实现,从而实现多态。
  2. 多态是抽象的应用:多态允许在运行时根据对象的实际类型调用相应的方法实现,这实际上是对抽象的一种应用。通过多态,可以将不同的子类对象视为相同的父类对象来处理,从而实现代码的通用性和灵活性。

接口

接口是什么

接口(Interface):用于规定一个Class必须要实现哪些方法的合同

在 C# 中,接口(Interface)扮演着非常重要的角色。它们定义了一个类或结构体必须遵循的模板,确保实现接口的类在形式上保持一致性。接口中声明的成员必须由继承接口的类或结构体具体实现,这样接口就像是一个契约,规定了“是什么”(What it is),而具体的类则定义了“怎么做”(How to do it)。

接口的声明与实现

接口使用 interface 关键字声明,通常接口命名以 I 字母开头。接口中可以包含方法、属性、事件,但不包含实现。例如,一个简单的接口声明如下所示:

interface IBark
{
	void Bark();
}

这个接口只有一个方法 Bark(),没有参数和返回值,当然我们可以按照需求设置参数和返回值。

接口的实现与类的继承语法格式类似,也是重写了接口中的方法,让其有了具体的实现内容。

实现这个接口的类必须提供 Bark方法的具体实现:

class Dog : IBark
{
	public void Bark()
	{
		Console.WriteLine("汪汪");
	}
}

在这个例子中,Dog 类实现了 IBark 接口,提供了 Bark 方法的具体行为。

接口的特点:

  • 只定义方法签名,不定义具体实现
  • 子类必须全部实现其定义方法
  • 接口方法不能用public abstract等修饰。接口内不能有字段变量,构造函数。
  • 接口可以多继承

接口与抽象类的区别

1. 接口与抽象类的相同点
  • 都不能使用new关键字来实例化
  • 成员方法都没有实现部分,或者说都没有方法体
  • 继承他们的,都必须实现他们的成员方法
2. 接口和抽象类的不同点
接口(interface)抽象类(abstract)
在接口中只能定义成员,但不能具体实现。在抽象类中除抽象方法以外,其他成员有具体的实现
接口中没有实例构造函数,也就是说没有构造函数抽象类中有构造函数
接口成员不能使用任何访问修饰符抽象类中的类成员可以使用任意的访问修饰符
接口支持多继承抽象类不能实现多继承,一个类只能继承一个抽象类