.jpg)
C#面向对象基础
类与对象
什么是类?
- 在面向对象的思想中最核心的就是对象,为了在程序中创建对象,首先需要定义一个类;
- 类可以理解为一张设计图,描述了一个对象都有什么构成,能做到哪些事情;
- 类就是对象的抽象,用于描述一组对象的共同属性和行为。
对象内存布局
对象变量为引用类型,new出来的多个对象,分别布局在了不同的内存上
成员变量vs局部变量
this
this引用
this其实就是当前对象本身引用,调用对象函数时,可以使用其引用类内成员变量或者方法。
解决问题:
- 解决类内方法调用,局部变量与成员变量重名问题
this关键字使用方法:
- 引用本对象成员变量:this.类内成员变量名
- 引用本对象成员方法:this.类内成员方法名
变量名不冲突前提下,是可以省略的
构造方法
构造方法(Constructor):当对象被new创建出来的时间点,会自动调用该方法,没有编写的情况下,C#自动生成默认构造函数。
编写规范:
- 方法名与类名相同,大小一致
- 无返回值,无void
- 内部不得写return
调用细节
- 每创建一个对象,调用一次
- 无法手动调用
static
static关键字
static是静态的的意思,既然是静态的,就是不变的意思,而不变就是它不会因为类中实例化对象的不同而不同,它在类中永远只有一份就像中国人有许多,但是我们这么多人只有一个国家——中国。
static可以修饰类、可以修饰成员变量,也可以修饰成员方法。标记为static的就不用创建实例来调用了,如果外部要对其进行调用,可以直接通过(类名.方法名)来调用,内部就可以直接通过方法名调用。
需求:同一个类生成的对象们,能够共享此变量,一处更改,其他对象都可以感知
规则:
- 被类生成的所有对象共享
- 调用方法:类名.变量名 类名.方法名()
- 随着类加载而加载,优先于对象生成
- 静态方法内部,只能够访问静态成员变量,不能够访问普通成员变量
- 普通方法内部,可以访问静态成员变量和普通成员变量
static变量初始化
static变量初始化分为两种途径:
-
定义时初始化
- 缺点:不能够执行一些静态变量初始化的逻辑代码。
class Player { public static int count=0; }
-
静态构造方法初始化
- 定义:不允许加入访问修饰符
- 执行:类加载的时候自动执行
- 覆盖:覆盖变量定义初始化的值
- 禁止:其不可访问非静态成员
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进行生成实例
工具类:
特点:在当前类中,没有存储字段数据
- 不需要也不允许new出来对象实例
- 内部的方法都为静态方法
- 所做的计算都依赖于外界传入的参数,完成外界想让我们完成的任务
命名空间(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了
面向对象三大特征
封装、继承、多态
封装
设计类时:
- 将本类型相关数据消息都放入类内统一管理
- 将本类型相关方法都放入类内统一管理
- 将用户需要的内容暴露给用户,细节进行隐藏
封装权限控制
权限:用于规定类内部属性/方法,能否被特定区域访问的规则
权限修饰符
- 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);
}
}
属性用途:
-
属性值验证:通过get/set方法,对传入的数据进行参数化合法性的验证
class Student { private string score = 0; public string Score { set{ if(value<0) { Console.WriteLine("成绩不能小于0"); return; } score=value; } get{return name;} } }
-
字段访问形式:通过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;} } }
属性字段省略方式
通过定义属性,可以省略字段的定义。
- 在类外方法中,可以直接通过Money属性进行字段读写
- 在类内方法中,可以直接通过Money属性进行字段读写
- money字段被自然隐藏
属性访问省略规则
-
通过属性省略字段的情况下,不可省略get方法;如果省略了get,类内/类外方法均无法访问字段,无意义
-
通过属性省略字段情况下,可省略set方法
-
set方法省略后,可以通过构造方法赋予初值;类内其他方法不可对Money赋值,只能读取
class Player { public int Money {get;} public Player(){Money=100;} }
只读属性在构造方法初始化后,就无法更改了
get/set权限
属性(Property)当中,get/set也拥有权限控制,可以解决类内访问省略字段问题
访问器限定符的修饰权大于属性访问限定符
**注意:**不能为一个属性的两个访问器同时增加private限定符
属性的访问限定符:用于修饰属性的访问
——属性访问限定符能够同时对set跟get进行限定
- 如果get/set没有限定符,则沿用属性限定符
- 如果get/set拥有比属性更严格的限定符,则使用访问器自己的限定符
- 如果get/set拥有的限定符严格程度小于或等于属性的限定符,则出错
继承
概念
定义:用于描述两个类(Class)之间的父子关系,子类可以直接使用父类中的非私有成员
继承的格式
格式
格式:class 子类名:父类名 {}
Pet:是父类,也称为基类
Dog:是子类,也称为派生类
规范语言:
- Dog 继承自 Pet
- Pet 派生出了Dog
父类:是一个大的类型,封装了本类型通用的成员变量/成员方法,直接被子类继承使用
子类:是一个特殊的类(特例的类),封装了本子类独有的成员变量/成员方法
继承当中的成员变量与成员方法的访问规则
- 如果成员变量的名字于父类变量相同,则直接使用的时候采用子类的变量,父类变量隐藏
- 如果你是故意在子类中声明父类同名变量,加一个new表示你是有意为之
- 如果在子类中想要访问父类中的同名变量,可以加base.变量名
- 如果成员方法的签名与父类方法签名相同,则直接访问的时候采用子类的方法,父类方法隐藏
- 如果你是故意在子类中声明父类相同签名方法,加一个new表示你是有意为之
- 如果在子类中想要访问父类中的同签名方法,可以加base.方法名()
- 如果在子类中声名的方法签名与父方法不同(参数),且构成重载,则不会隐藏父类方法
继承成员访问权限
权限修饰符 | 同类中 | 子类 | 外部类 |
---|---|---|---|
private | √ | ||
protected | √ | √ | |
public | √ | √ | √ |
继承中的构造方法
-
当实例化一个子类对象的时候,首先调用其父类构造方法,再调用其子类构造方法
-
当实例化一个子类对象的时候,如果父类只拥有带参构造方法,则需要显示调用其父类构造方法
: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");
}
}
通过父类调用对应子类的方法实现
注意:
- 父类方法与子类方法签名一致
- 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");
}
}
多态细节
- 父类的虚拟方法必须在子类中”可见“,即不可为private
- 在多层继承环境中,多态会沿着继承链条进行重写方法的寻找锁定
抽象类与抽象方法
在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方法来计算面积,而不需要关心它们的具体实现细节。
抽象和多态的联系
- 抽象是多态的基础:多态的实现通常依赖于抽象类或接口。抽象类定义了子类必须实现的方法,而接口则定义了实现类必须遵循的规范。这些抽象方法或接口方法在不同的子类中可以有不同的实现,从而实现多态。
- 多态是抽象的应用:多态允许在运行时根据对象的实际类型调用相应的方法实现,这实际上是对抽象的一种应用。通过多态,可以将不同的子类对象视为相同的父类对象来处理,从而实现代码的通用性和灵活性。
接口
接口是什么
接口(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) |
---|---|
在接口中只能定义成员,但不能具体实现。 | 在抽象类中除抽象方法以外,其他成员有具体的实现 |
接口中没有实例构造函数,也就是说没有构造函数 | 抽象类中有构造函数 |
接口成员不能使用任何访问修饰符 | 抽象类中的类成员可以使用任意的访问修饰符 |
接口支持多继承 | 抽象类不能实现多继承,一个类只能继承一个抽象类 |
- 感谢你赐予我前进的力量