一.类的定义:
类的定义是以关键字 class 开始,后跟类的名称,包括方法、属性、字段、构造函数等等,定义类我们可以更好的定义自己想要的类型,类似于结构体,但是比结构体更高级。
class Animal
{
//构造函数
//初始化对象 当我们new创建一个对象的时候,回执行构造函数进行初始化
public Animal(string name, int age, int num1)
{
//this指向当前创建的对象
this.name = name;
this.age = age;
this.num1 = num1;
}
//字段
public string name;
public int age;
public int num1 = 1;
//属性
public int num
{
//get set 代码块 功能:权限控制功能、拦截代码功能
//注意:当存在get代码块,不存在set代码块的时候,只允许读取,不允许修改,相反也是这样
//在get,set代码块中的时候我们可以进行拦截,实现对应的业务
get
{
Console.WriteLine("只要属性被读取了,我就会被访问");
return num1; //表示属性被访问的时候会执行该代码,同时会返回一个值
}
set
{
num1 = value;
Console.WriteLine("新的值为{0}",value); //响应式数据
//set表示当设置num的值的时候会执行,value表示新的值
}
}
//方法
public void Show()
{
Console.WriteLine("我叫{0}会打篮球,今年{1}岁", name, age);
}
}
初始化类的字段,new一个动物类实例,可以使用构造方法进行赋值也可以使用对象.字段名进行赋值,相对于赋值代码量而言,使用构造方法大大简化了代码量
//1.有参构造函数进行对字段的赋值
Animal dog = new Animal("旺财",18,123456);
Animal cat = new Animal("猫咪",16,12345678);
//2.对象.字段名进行赋值
/* dog.name = "蔡徐坤";
dog.age = 10;*/
dog.Show();
cat.Show();
在类中,有属性,get set代码块,暂时在项目中还不明白用意何在
//get set 代码块 功能:权限控制功能、拦截代码功能
//注意:当存在get代码块,不存在set代码块的时候,只允许读取,不允许修改,存在set代码块,不存在get代码块的时候,只允许修改,不允许访问
//在get,set代码块中的时候我们可以进行拦截,实现对应的业务
dog.num = 10; //修改属性,会执行set代码块
Console.WriteLine(dog.num); //读取属性,会执行get代码块
二.类的继承 继承是面向对象程序设计中最重要的概念之一。继承允许我们根据一个类来定义另一个类,这使得创建和维护应用程序变得更容易。同时也有利于重用代码和节省开发时间。 当创建一个类时,程序员不需要完全重新编写新的数据成员和成员函数,只需要设计一个新的类,继承了已有的类的成员即可。这个已有的类被称为的基类,这个新的类被称为派生类。 继承的思想实现了 属于(IS-A) 关系。例如,哺乳动物 属于(IS-A) 动物,狗 属于(IS-A) 哺乳动物,因此狗 属于(IS-A) 动物。
//继承:
//创建一个基类:(父类)
public class Enemy
{
public Enemy()
{
Console.WriteLine("父类的构造方法");
}
public int life;
public virtual void move() //规定虚方法
{
Console.WriteLine("父类的move方法");
}
}
//派生类(子类)
class Boss:Enemy
{
public Boss()
{
Console.WriteLine("子类的构造方法");
}
//虚方法 重写父类的方法
public override void move()
{
Console.WriteLine("子类的move方法");
}
}
Enemy enemy = new Enemy();
enemy.move();
Console.WriteLine("----------------------------");
//若父类重写子类的方法
Enemy enemy1 = new Boss();
enemy1.move();
Console.WriteLine("----------------------------");
Boss boss = new Boss();
boss.move();
注意:C# 中的虚方法
多态的实现方法之一(虚方法,抽象类,接口)
如果父类中的方法有默认的实现,并且父类需要被实例化,这时可以考虑将父类定义成一个普通的类,用虚方法来实现多态。
定义方法: 将父类的方法标记为虚方法,使用关键字virtual,标记后这个函数就可以被子类重新写一遍,在子类方法前加上override即可
三.不同模块之间的相互类引用
我们在解决方案中定义不同模块,模块中有不同的类;当我们模块中的类要引用另一个模块中的类的时候,我们需要进行引用。 1.首先右击需要被引用模块的类-添加-项目引用
2.填写对应的类的命名空间
注意点:在引用其他模块的类的时候,类必须使用public修饰,否则会出现访问权限不够,无法访问到类。
4.编写MyMath类,计算圆的周长、面积、球的体积
public class MyMath
{
/* public MyMath(double PI)
{
this.PI = PI;
}*/
public const double PI = 3.1415926;
//求圆的周长
public static void getZhouChang(int r)
{
double zhouc = r * PI;
Console.WriteLine("圆的周长为{0}", zhouc);
}
//求圆的面积
public static void getArea(int r)
{
double area = r * r * PI;
Console.WriteLine("圆的面积为{0}",area);
}
//求圆的体积
public void gettiji(int r)
{
double tiji = r * r *r * 0.75 * PI ;
Console.WriteLine("球的体积为{0}", tiji);
}
}
//使用静态方法,直接类名.访问
MyMath.getZhouChang(n);
MyMath.getArea(n);
//调用非静态变量要使用实例化对象来调用
MyMath my = new MyMath();
my.gettiji(n);
四.里氏转换
当我们在定义一个Person类的时候,同时Teacher类继承Person的类,当我们new Teacher的实例的时候,想调用person独有的方法的话,这时候需要将new Teacher的实例转化为person的实例,可以类似于强制类型转化,采用(需要转化的类型)的方法进行转化
public class Person
{
public void saidhello()
{
Console.WriteLine("你好");
}
}
public class Teacher:Person
{
public void talk()
{
Console.WriteLine("上课上课");
}
}
Person p = new Teacher();
p.saidhello();
Teacher t = new Teacher();
t.talk();
Person p2 = (Person)t;
t.saidhello(); //里氏转换
结果如下:
五.new关键字的用法:
在 C# 中,new 关键字可用作运算符、修饰符或约束。
1)new 运算符:用于创建对象和调用构造函数。
2)new 修饰符:在用作修饰符时,new 关键字可以显式隐藏从基类继承的成员。
隐藏的后果就是子类调用不到父类的成员(根据遵循继承,子类继承父类的所有方法和属性,当子类对象调用new修饰的方法
和对象的时候,会发现调用不到父类的成员方法,在父类和子类又相同名称方法的时候,子类调用的时候会自动隐藏父类的同名方法
并且你不给子类的同名方法加new关键字的话,会发生警告)
3)new 约束:用于在泛型声明中约束可能用作类型参数的参数的类型。
namespace new关键字
{
internal class Program
{
public class Father
{
public string name;
public int age;
public void show()
{
Console.WriteLine("我是父类show");
}
public void sayhello()
{
Console.WriteLine("我在父类sayhello");
}
}
public class Son : Father
{
public new string name;
public new int age;
public new void show()
{
Console.WriteLine("我是子类show");
}
public new void sayhello()
{
Console.WriteLine("我在子类sayhello");
}
}
static void Main(string[] args)
{
Father f = new Father();
f.sayhello();
Son s = new Son();
s.sayhello();
Father f2 = new Son();
f2.sayhello();
}
}
}
六.继承、重写方法、virtual、new、overrid汇总
namespace demo1017
{
internal class Program
{
/*
public 公有访问。不受任何限制。
private 私有访问。只限于本类成员访问,子类,实例都不能访问。
protected 保护访问。只限于本类和子类访问,实例不能访问。
internal 内部访问。只限于本项目内访问,其他不能访问。
protected internal 内部保护访问。只限于本项目或是子类访问,其他不能访问
*/
public class M
{
public M(int num)
{
this.num = num;
}
public int num; //没有初始化的时候,也有默认值
static public int num2;
public string str
{
get;set; //相对于没有函数体的函数 相对于字段
}
static public void getNum()
{
/*Console.WriteLine(num);*/
Console.WriteLine(num2);
}
public virtual void show()
{
Console.WriteLine("1111父类");
}
//new 隐藏父类中的方法
public new void heihei()
{
Console.WriteLine("heihei父类");
Console.WriteLine("访问父类的{0}", num);
}
public void sayhello()
{
Console.WriteLine("我在父类说hello");
}
public void lala()
{
Console.WriteLine("父类lala");
}
//静态类中只能有静态成员,非静态类中可以有静态成员或非静态成员
//静态类要使用类名.方法名or属性名访问,非静态类要通过实例化对象进行访问
}
//创建一个子类(派生类)
class N : M
{
public N(int num,string str) : base(num){
this.str = str;
}
public string str;
//重写父类方法的前提,必须是virtual、abstract修饰的才可以被重写
public override void show()
{
/*base.show();*/ //base可以理解为就是父类
Console.WriteLine("我重写了111");
}
public void heihei()
{
Console.WriteLine("heihei子类");
}
public void lala()
{
Console.WriteLine("子类lala");
}
public void enen()
{
Console.WriteLine("我在子类说嗯嗯");
}
}
static void Main(string[] args)
{
//M是父类,N是子类
M m1 = new M(10);
m1.show();
m1.heihei();
Console.WriteLine("-------------------");
M m2 = new N(10,"哈哈");
m2.show(); //调用的是子类的方法,new的是子类
m2.heihei(); //heihei父类
//如果父类和子类都有单独的方法,这时候需要单独调用的时候,
//这个根据前面声明的是什么类型的,变量类型调用对应父类和子类的方法
//比如M m2中m2这个变量类型只包含sayhello()函数,没有enen()方法,enen在子类中
//如果子类重写了父类的方法,这时候虽然是父类的变量,但是new的是子类,所以调用的还是子类的方法
//比如子类N重写了父类M的show方法,在new N 的时候调用的是子类的show方法
m2.sayhello();
m2.lala();
Console.WriteLine("-------------------");
N n1 = new N(18, "嘿嘿");
Console.WriteLine(n1.str);
n1.enen();
n1.show();
n1.heihei();
}
}
}
七、里氏转化练习
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class test : MonoBehaviour
{
// Start:由于Unity的特殊机制,Start会首先执行
void Start()
{
// Animal animal = new Animal("某个动画", "我也不知道动物是咋叫的"); // 不允许实例化一个抽象类
KejiDog daHuang = new KejiDog("大黄", "汪汪汪", "柯基");
Cat xiaoBai = new Cat("小白", "喵喵喵");
DoCry(xiaoBai);// 隐式转换,子类xiaoBai对象是Cat类型转父类Animal类型
KejiMove(daHuang); // 传递参数时依然是子类转父类
}
public void DoCry(Animal animal)
{
Debug.Log("开始叫!");
animal.Cry();//形参虽然是父类,但是实参进来的子类,实际上父类的形参对象实际上调用的是子类的方法
}
public void KejiMove(Animal animal) // 此处约定传递过来的参数要么是Animal,要么是Animal的子类通过隐式转换传递过来
{
// 强制转换,会报错
KejiDog keji = (KejiDog)animal; // 显式转换,父类转子类,有风险,要确保类型兼容
KejiDog keji2 = animal as KejiDog; // 显式转换,父类转子类,有风险,要确保类型兼容
keji.Move();
//注意:因为animal对象中包含KejiDog的对象daHuang,这时候将父类Animal转化为子类Cat会报错,可能是因为中间
//Cat cat = (Cat)animal; // 类型不兼容会立刻报错
//cat.Move();
// as转换,不会报错
//Cat keji2 = animal as Cat; // 类型不兼容,不会立刻报错,但是返回的是null,也就是空数据
//keji2.Move(); // 空数据是没有Move函数的,所以报错!
}
}
// 父类/基类
// 抽象类
public abstract class Animal
{
public string Name;
public string CryString;
public Animal(string name, string cryString)
{
Name = name;
CryString = cryString;
}
// 虚函数
// 1.提供默认逻辑,因为有一些子类不想要重写可以不重写
// 2.提供可选的逻辑
public virtual void Cry()
{
// 下列逻辑是可选的
// 可选的:就是子类想要就要,不想要也可以不要
Debug.Log($"{Name}张嘴了!");
}
// 抽象函数
// 业务上存在飞翔、跑步、游泳等各种情况,我们并不知道具体的子类如何做实现
// 没有函数体
public abstract void Move();
}
// 子类/派生类
// 第二层,Dog也是抽象类
// 抽象类不需要实现其基类中的抽象函数(我也不知道我下面的子类要干嘛,我怎么给你实现?)
public abstract class Dog : Animal // 获的Animal的所有成员
{
public string dogType;
public Dog(string name, string cryString, string dogType) : base(name, cryString)
{
this.dogType = dogType;
//注意:子类继承父类,并且子类和父类都有构造方法的时候,new 子类的时候也会调用父类的构造方法
Debug.Log($"名字:{name},品种:{dogType}诞生了!");
}
// 重写
// 重载:是同名函数,但是不同参数
// 重写:是覆盖基类中的函数
public override void Cry()
{
Debug.Log($"{Name}:开始蓄力张嘴!");
base.Cry(); // 调用基类中的Cry函数,这个函数是没有被重写的
Debug.Log($"{Name}:汪汪汪");
Debug.Log($"小狗叫完还要跳一跳!");
}
// 重写的是基类中的抽象函数
public override void Move()
{
Debug.Log("小狗使用四肢奔跑!");
}
}
public class KejiDog : Dog
{
// 他的构造函数约束是自身基类的
public KejiDog(string name, string cryString, string dogType) : base(name, cryString, dogType)
{
Debug.Log("调完父类Dog才会执行子类KejiDog");
}
public override void Cry()
{
base.Cry();
Debug.Log("抖了抖小短腿!");
}
public override void Move()
{
base.Move(); // 因为基类做了基本实现,所以基类中Move函数对我们来说相当于是一个虚函数了,而不是抽象函数
Debug.Log("抖了抖科技翘臀");
}
}
public class Cat : Animal
{
public Cat(string name, string cryString) : base(name, cryString)
{
}
public override void Cry()
{
//base.Cry(); // 调用基类中的Cry函数,这个函数是没有被重写的
Debug.Log($"{Name}:喵喵喵");
Debug.Log($"小猫叫完还要跑跑跑!");
Debug.Log($"小猫叫完还要跑跑跑!");
Debug.Log($"小猫叫完还要跑跑跑!");
}
// 重写的是基类中的抽象函数
public override void Move()
{
Debug.Log("小猫上树了!");
}
}