继承的基本规则

基本概念

继承的基本概念

就是,一个类A继承一个类B,类A将会继承类B的所有成员和所有的特征和行为

被继承的类:父类,基类

继承的类:子类,派生类

子类可以有自己的特征和行为

特点:

1.单根性 子类只能有一个父类

注意:C#中没有C++的多继承!!!

2.传递性 子类可以简介继承父类的父类

基本语法

C#的继承和C++的类似,都是通过使用:来实现。

1
2
3
4
class 类名 : 被继承的类名
{

}

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using System;
using System.Runtime.InteropServices;

namespace Homework
{
class Teacher
{
//姓名
public string name;
//职工号
public int number;
//介绍名字
public void SpeakName()
{
Console.WriteLine(name);
}
}
class TeachingTeacher : Teacher
{
//科目
public string subject;
//介绍科目
public void SpeakSubject()
{
Console.WriteLine(subject+"老师");
}
}
class ChineseTeacher : TeachingTeacher
{
public void Skill()
{
Console.WriteLine("我是语文老师");
}
}
class Program
{
static void Main(string[] args)
{
TeachingTeacher tt = new TeachingTeacher();
tt.name = "铅笔沫";
tt.number = 114514;
tt.SpeakName();
tt.subject = "Csharp";
tt.SpeakSubject();
//间接使用父类的父类
ChineseTeacher ct = new ChineseTeacher();
ct.name = "云笙繁华";
ct.number = 191981;
ct.subject = "语文";
ct.SpeakName();
ct.SpeakSubject();
ct.Skill();
}
}
}

访问修饰符的影响

类型 可访问对象
public 公共 内外部都可访问
private 私有 内部访问
protected 保护 内部和子类访问
internal(暂时了解) 内部的 在同一程序集的内部和成员

子类和父类的同名成员

C#中允许子类存在和父类同名的成员(默认使用子类)

但是 及其不建议这么做,因此也不在这里过多叙述。

解决方式

1
2
3
- public string name;(默认覆盖)
+ public new string name;(手写覆盖)
//本质上都没什么用,总之非常不建议这么写

练习题

Q1

写一个人类,人类中有姓名,年龄属性,有说话行为,战士类继承人类,有攻击行为

A1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;
using System.Runtime.InteropServices;

namespace Homework
{
class Person
{
public string name;
public int age;
public void Speak()
{
Console.WriteLine("我是" + name);
}
}
class Soldier : Person
{
public void Attack()
{
Console.WriteLine("冲啊");
}
}

class Program
{
static void Main(string[] args)
{
Soldier s = new Soldier();
s.name = "铅笔沫";
s.Speak();
s.Attack();
}
}
}

里氏替换原则

面向对象七大原则之一,最容易理解且最重要的原则

概念

任何父类出现的地方,子类都可以替代

重点

语法表现——父类容器装子类对象,因为子类对象中包含了父类的所有内容

作用

方便进行对象存储和管理

基本实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
using System;
using System.Runtime.InteropServices;

namespace Homework
{
class GameObject
{

}
class Player : GameObject
{
public void PlayerAtk()
{
Console.WriteLine("玩家攻击");
}
}
class Monster : GameObject
{
public void MonsterAtk()
{
Console.WriteLine("怪物攻击");
}
}
class Boss : GameObject
{
public void BossAtk()
{
Console.WriteLine("Boss攻击");
}
}
class Program
{
static void Main(string[] args)
{
//里氏替换原则 用父类容器 装载子类对象
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 判断一个对象是否为执行对象 布尔类型 是为真,不是为假
as 将一个对象转换为指定类对象 指定类型对象 成功,返回指定类型对象,失败返回Null

基本语法

  • 类对象 is 类名
  • 类对象 as 类名
1
2
3
4
5
6
7
if(player is Player)
{
Player p = player as Player;
p.PlayerAtk();

(player as Player).PlayerAtk();//完全等价于前两项
}

当父类数组里的内容不知道顺序是,可以使用for来遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for(int i=0;i<objects.Lenth;i++)
{
if (objects[i] is Player)
{
(objects[i] as Player).PlayerAtk();
}
else if (objects[i] is Monster)
{
(objects[i] as Monster).MonsterAtk();
}
else if (objects[i] is Boss)
{
(objects[i] as Boss).BossAtk();
}
}

练习题

Q1:is 和 as 的区别是什么

写一个Monster类,它派生出Boss和Goblin两个类,Boss有技能; 小有攻击;随机生成10个怪,装载到数组中,遍历这个数组,调用他们的攻击方法,如果是boss就释放技能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;

namespace Homework
{
class Monster
{
}
class Boss : Monster
{
public void BossSkill()
{
Console.WriteLine("Boss发动技能");
}
}
class Goblin : Monster
{
public void GoblinAtk()
{
Console.WriteLine("哥布林攻击");
}
}
class Program
{
static void Main(string[] args)
{
Random r = new Random();
int randomNum;
Monster[] monsters = new Monster[10];
for(int i= 0; i < monsters.Length;i++)
{
randomNum = r.Next(1,101);
if(randomNum < 50)
{
monsters[i] = new Boss();
}
else
{
monsters[i] = new Goblin();
}
}
for(int i=0;i<monsters.Length;i++)
{
if (monsters[i] is Boss)
{
(monsters[i] as Boss).BossSkill();
Console.WriteLine("发动技能成功");
}
else if (monsters[i] is Goblin)
{
(monsters[i] as Goblin).GoblinAtk();
}
}
}
}
}

Q2:Fps游戏模拟

写一个玩家类,玩家可以拥有各种武器现在有四种武器,冲锋枪,散弹枪,手枪,匕首玩家默认拥有匕首
请在玩家类中写一个方法,可以拾取不同的武器替换自己拥有的枪械

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;

namespace Homework
{
class Gamer
{
private Weapon nowHaveWeapon;
public Gamer()
{
nowHaveWeapon = new Dagger();
}
public void PickUp(Weapon weapon)
{
nowHaveWeapon = weapon;
}

}
class Weapon
{

}
class SubmachineGun : Weapon
{

}
class ShotGun : Weapon
{

}
class Pistol :Weapon
{

}
class Dagger : Weapon
{

}
class Program
{
static void Main(string[] args)
{
Gamer p = new Gamer();
SubmachineGun s = new SubmachineGun();
p.PickUp(s);
ShotGun sg = new ShotGun();
p.PickUp(sg);
}
}
}

继承中的构造函数

基本概念

当申明一个子类对象时,先执行父类的构造函数,再执行子类的构造函数

注意

  1. 父类的无参构造,很重要
  2. 子类可以通过base关键字调用父类构造。

执行顺序

父类的父类构造->父类构造->子类构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;

namespace Homework
{
class GameObject
{
public GameObject()
{
Console.WriteLine("GameObject的构造函数");
}
}
class Player : GameObject
{
public Player()
{
Console.WriteLine("Player的构造函数");
}
}
class MainPlayer:Player
{
public MainPlayer()
{
Console.WriteLine("MainPlayer的构造函数");
}
}
class Program
{
static void Main(string[] args)
{
MainPlayer player = new MainPlayer();

}
}
}

父类的无参构造函数重要

由于子类默认调用的是父类的无参构造函数,因此当父类有参构造函数,并且没有将无参构造空实现时,将无法正常构建子类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Father
{
public Father()//如果注释掉该段代码,Son子类将无法继承
{

}
public Father(int i)
{
Console.WriteLine("Father构造");
}
}
class Son : Father
{
public Son()
{
Console.WriteLine("Son的构造");

}
}

通过base调用指定父类函数

要是想要子类调用一个指定的构造函数(无参或者有参)该怎么办呢?

那我们可以选择通过base来进行调用,并且也可以解决刚才由于父类中没有无参构造,导致子类无法继承的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;

namespace Homework
{
class Father
{
//public Father()
//{

//}
public Father(int i)
{
Console.WriteLine("Father构造");
}
}
class Son : Father
{
//第一种解决方式,通过调用base
public Son(int i) : base(i)
{
Console.WriteLine("Son的第一个构造");
}
//第二种解决方式,通过this,
//但是this(i)调用的是 Son(int i) 这个有参构造,
//再间接调用base(i)
public Son(int i, string str) : this(i)
{
Console.WriteLine("Son的第二个构造");
}
}
class Program
{
static void Main(string[] args)
{
Son son = new Son(1, "123");

}
}
}

换而言之,base调用的是父类中的构造函数,而this只能调用属于自己的构造函数。

练习题

Q1:

有一个打工人基类,有工种,工作内容两个特征,一个工作方法。程序员、策划、美术,分别继承打工人,请用继承中的构造函数这个知识点,实例化3个上述对象。

A1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;

namespace Homework
{
class Worker
{
public string type;
public string content;
public Worker(string type, string content)
{
this.type = type;
this.content = content;
}
public void Work()
{
Console.WriteLine("{0} {1}",type ,content);
}
}
class Programmer:Worker
{
public Programmer() : base("程序员", "编程")
{

}

}
class Plan:Worker
{
public Plan():base("策划","设计游戏")
{

}
}
class Art : Worker
{
public Art() : base("美术", "画画")
{

}
}
class Program
{
static void Main(string[] args)
{
Programmer programmer = new Programmer();
programmer.Work();
Plan plan= new Plan();
plan.Work();
Art art= new Art();
art .Work();
}
}
}

万物之父和装箱拆箱

万物之父

关键字:objectObject

概念

object是所有类型的基类,它是一个类(引用类型)

作用

  1. 可以利用里氏替换原则,用object容器装所有对象
  2. 可以用来表示不确定类型,作为函数参数类型

万物之父的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Father
{

}
class Son : Father
{
public void Speak()
{

}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("万物之父和装箱拆箱");
Father f = new Son();//父类容器装载子类容器
if( f is Son )
{
(f as Son).Speak();
}
//引用类型
object o = new Son();//用object 装载
//用is as 来判断和转换即可
if( o is Son )
{
(o as Son).Speak();
}
//值类型
object o2 = 1f;//float类型
//用强转
float fl = (float)o2;//希望使用值类型,来进行计算
o = Son(o) //引用类型,也可以进行强转
//特殊的string类型
object str = "123123";
string str2 = str as string;
// string str2 = str.Tostring(); 这种做法也是等价于上一行代码的
//数组类型
object arr = new int[10];
int[] ar = arr as int[];//引用类型更推荐使用 引用类型
//int [] ar = (int[])arr; 这种做法等价于上一行代码
}
}

装箱拆箱

装箱和拆箱,大可以理解为一种行为

发生条件

用object存储值类型时,称为装箱。再将object转为值类型时,称为拆箱

装箱

把值类型用引用类型存储,栈内存会迁移到堆内存中。因为有内存的迁移,就会带来性能的消耗。

拆箱

把引用类型存储的值类型取出来,堆内存会迁移到栈内存中。

因此,要尽量少用装箱和拆箱,这两个功能。

好处

不确定类型时可以方便参数的存储和传递

坏处

存在内存迁移,增加性能消耗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Program
{
static void Main(string[] args)
{
//装箱
object v = 3;
int intV = (int)v;
TestFun(1,2,3,4,5,6);
TestFun_2(1,2,3f,"456")
}
// 只能传int 类型的参数
static void TsetFun(params int[] array)
{

}
//传什么类型的参数都可以
static void TsetFun_2(params object[] array)
{
//这里就可以去遍历,用里氏替换原则一个一个去判断,做对应的处理
}
}

密封类

基本概念

是一个使用 sealed密封关键字修饰类,使该类无法再被继承

实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Father
{

}
sealed class Son:Father//让儿子结扎,让儿子没有儿子,父类没有孙子
{

}
/*
报错,因为儿子被断后了
class T:son
{

}
*/

作用

在面向对象程序的设计中,密封类的主要作用就是不允许最底层子类被继承,可以保证程序的规范性、安全性。

练习题

Q1:

定义一个载具类,速度,最大速度,可乘人数,司机和乘客等,有上车,下车,行驶,车祸等方法,用载具声明一个对象,并将若干人装载上车。

A1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
using System;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;

namespace Homework
{
class Person
{

}
class Driver:Person
{

}
class Passenger : Person
{

}
class Car
{
public int speed;
public int maxspeed;
//当前装载的人数
public int num;
public Person[] persons;
//Car的初始化
public Car(int speed, int maxspeed, int num)
{
this.speed = speed;
this.maxspeed = maxspeed;
this.num = 0;
persons= new Person[num];//最多能装多少人
}
public void GetIn(Person p )
{
if(num>=persons.Length)
{
Console.WriteLine("满载");
return;
}
persons[num] = p;
++num;
}
public void GetOff(Person p)
{
for(int i=0;i<persons.Length;i++)
{
if (persons[i] == null)
{
break;
}
if (persons[i]==p)
{
//移动人的位置
for(int j=i;j<num-1;j++)
{
persons[j] = persons[j+1];
}
//其管控最后一个位置
persons[num - 1] = null;
--num;
Console.WriteLine("我下车了");
break;
}
}
}
public void Move()
{

}
public void Boom()
{

}
}

class Program
{
static void Main(string[] args)
{
Car c = new Car(10, 20, 20);
Driver d = new Driver();
c.GetIn(d);

Passenger p =new Passenger();
c.GetIn(p);
Passenger p2 =new Passenger();
c.GetIn(p2);
Passenger p3 = new Passenger();
c.GetIn(p3);

c.GetOff(p2);
}
}
}