//一个类A继承一个类B
//类A将会继承类B的所有成员
//A类将拥有B类的所有特征和行为
//被继承的类
//称为 父类、基类、超类
//继承的类
//称为子类、派生类
//子类可以有自己的特征和行为
//特点
//1.单根性 子类只能有一个父类
//2.传递性 子类可以间接继承父类的父类
//class 类名 : 被继承的类名
//{
//}
class Teacher
{
//姓名
public string name;
//职工号
protected int number;
//介绍名字
public void SpeakName()
{
number = 10;
Console.WriteLine(name);
}
}
class TeachingTeacher : Teacher
{
//科目
public string subject;
//介绍科目
public void SpeakSubject()
{
number = 11;
Console.WriteLine(subject + "老师");
}
}
class ChineseTeacher:TeachingTeacher
{
public void Skill()
{
Console.WriteLine("一行白鹭上青天");
}
}
//public - 公共 内外部访问
//private - 私有 内部访问
//protected - 保护 内部和子类访问
//之后讲命名空间的时候讲
//internal - 内部的 只有在同一个程序集的文件中,内部类型或者是成员才可以访问
//class 类名 : 被继承的类名
//{
//}
//概念
//C#中允许子类存在和父类同名的成员
//但是 极不建议使用
继承基本语法
class 类名:父类名
1.单根性:只能继承一个父类
2.传递性:子类可以继承父类的父类。。。的所有内容
3.访问修饰符 对于成员的影响
4.极奇不建议使用 在子类中申明和父类同名的成员(以后学习了多态再来解决这个问题)
//写一个人类,人类中有姓名,年龄属性,有说话行为
//战士类继承人类,有攻击行为
class Warrior : Person
{
public void Atk( Warrior otherWarrior )
{
Console.WriteLine("{0}打了{1}", Name, otherWarrior.Name);
}
}
class Person
{
public string Name
{
get;
set;
}
private int Age
{
get;
set;
}
public void Speak(string str)
{
Age = 10;
Console.WriteLine(Age);
Console.WriteLine(str);
}
}
// 里氏替换原则是面向对象七大原则中最重要的原则
// 概念:
// 任何父类出现的地方,子类都可以替代
// 重点:
// 语法表现——父类容器装子类对象,因为子类对象包含了父类的所有内容
// 作用:
// 方便进行对象存储和管理
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)
{
Console.WriteLine("里氏替换原则");
//里氏替换原则 用父类容器 装载子类对象
GameObject player = new Player();
GameObject monster = new Monster();
GameObject boss = new Boss();
GameObject[] objects = new GameObject[] { new Player(), new Monster(), new Boss() };
}
}
//基本概念
// is:判断一个对象是否是指定类对象
// 返回值:bool 是为真 不是为假
// as:将一个对象转换为指定类对象
// 返回值:指定类型对象
// 成功返回指定类型对象,失败返回null
//基本语法
// 类对象 is 类名 该语句块 会有一个bool返回值 true和false
// 类对象 as 类名 该语句块 会有一个对象返回值 对象和null
if( player is Player )
{
//Player p = player as Player;
//p.PlayerAtk();
(player as Player).PlayerAtk();
}
for (int i = 0; i < objects.Length; 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();
}
}
//概念:父类容器装子类对象
//作用:方便进行对象的存储和管理
//使用:is和as
// is 用于判断
// as 用于转换
// 注意:不能用子类容器装父类对象
1.练习题1
//is和as的区别是什么
// is 用于判断 类对象判断是不是某一个类型的对象 bool
// as 用于转换 把一个类对象 转换成一个指定类型的对象 null
2.练习题2
//写一个Monster类,它派生出Boss和Goblin两个类,
//Boss有技能;小怪有攻击;
//随机生成10个怪,装载到数组中
//遍历这个数组,调用他们的攻击方法,如果是boss就释放技能
class Monster
{
}
class Boss : Monster
{
public void Skill()
{
Console.WriteLine("boss技能");
}
}
class Goblin:Monster
{
public void Atk()
{
Console.WriteLine("哥布林攻击");
}
}
class Program
{
static void Main(string[] args)
{
//随机生成10个怪物
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();
}
}
//遍历10个怪物 执行对应方法
for (int i = 0; i < monsters.Length; i++)
{
if( monsters[i] is Boss )
{
(monsters[i] as Boss).Skill();
}
else
{
(monsters[i] as Goblin).Atk();
}
}
}
}
3.练习题3
//FPS游戏模拟
//写一个玩家类,玩家可以拥有各种武器
//现在有四种武器,冲锋枪,散弹枪,手枪,匕首
//玩家默认拥有匕首
//请在玩家类中写一个方法,可以拾取不同的武器替换自己拥有的武器
class Weapon
{
}
//冲锋枪
class SubmachineGun : Weapon
{
}
//散弹枪
class ShotGun : Weapon
{
}
//手枪
class Pistol : Weapon
{
}
//匕首
class Dagger:Weapon
{
}
class Player
{
private Weapon nowHaveWeapon;
public Player()
{
nowHaveWeapon = new Dagger();
}
public void PickUp(Weapon weapon)
{
nowHaveWeapon = weapon;
}
}
class Program
{
static void Main(string[] args)
{
Player p = new Player();
SubmachineGun s = new SubmachineGun();
p.PickUp(s);
ShotGun sg = new ShotGun();
p.PickUp(sg);
}
}
//构造函数
//实例化对象时调用的函数
//主要用来初始化成员变量
//每个类 都会有一个默认的无参构造函数
//语法
// 访问修饰符 类名()
// {
// }
//不写返回值
//函数名和类名相同
//访问修饰符根据需求而定,一般为public
//构造函数可以重载
//可以用this语法重用代码
//注意
//有参构造会顶掉默认的无参构造
//如想保留无参构造需重载出来
class Test
{
public int testI;
public string testStr;
public Test()
{
}
public Test(int i)
{
this.testI = i;
}
public Test(int i, string str) : this(i)
{
this.testStr = str;
}
}
//特点
//当申明一个子类对象时
//先执行父类的构造函数
//再执行子类的构造函数
//注意:
//1.父类的无参构造 很重要
//2.子类可以通过base关键字 代表父类 调用父类构造
// 父类的父类的构造——>。。。父类构造——>。。。——>子类构造
class GameObject
{
public GameObject()
{
Console.WriteLine("GameObject的构造函数");
}
}
class Player:GameObject
{
public Player()
{
Console.WriteLine("Player的构造函数");
}
}
class MainPlayer : Player
{
public MainPlayer()
{
Console.WriteLine("MainPlayer的构造函数");
}
}
//子类实例化时 默认自动调用的 是父类的无参构造 所以如果父类无参构造被顶掉 会报错
class Father
{
//public Father()
//{
//}
public Father(int i)
{
Console.WriteLine("Father构造");
}
}
class Son:Father
{
#region 知识点四 通过base调用指定父类构造
public Son(int i) : base(i)
{
Console.WriteLine("Son的一个参数的构造");
}
public Son(int i, string str):this(i)
{
Console.WriteLine("Son的两个参数的构造");
}
#endregion
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("继承中的构造函数");
MainPlayer mp = new MainPlayer();
Son s = new Son(1,"123");
}
}
总结
继承中的构造函数
特点
执行顺序 是先执行父类的构造函数 再执行子类的 从老祖宗开始 依次一代一代向下执行
父类中的无参构造函数 很重要
如果被顶掉 子类中就无法默认调用无参构造了
解决方法:
1.始终保持申明一个无参构造
2.通过base关键字 调用指定父类的构造
注意:
区分this和base的区别
//概念:父类容器装子类对象
//作用:方便进行对象存储和管理
//使用:
//is和as
//is用于判断
//as用于转换
class Father
{
}
class Son : Father
{
public void Speak()
{
}
}
//万物之父
//关键字:object
//概念:
//object是所有类型的基类,它是一个类(引用类型)
//作用:
//1.可以利用里氏替换原则,用object容器装所有对象
//2.可以用来表示不确定类型,作为函数参数类型
static void Main(string[] args)
{
Father f = new Son();
if( f is Son )
{
(f as Son).Speak();
}
//引用类型
object o = new Son();
//用is as 来判断和转换即可
if( o is Son )
{
(o as Son).Speak();
}
//值类型
object o2 = 1f;
//用强转
float fl = (float)o2;
//特殊的string类型
object str = "123123";
string str2 = str as string;
object arr = new int[10];
int[] ar = arr as int[];
}
static void Main(string[] args)
{
//发生条件
//用object存值类型(装箱)
//再把object转为值类型(拆箱)
//装箱
//把值类型用引用类型存储
//栈内存会迁移到堆内存中
//拆箱
//把引用类型存储的值类型取出来
//堆内存会迁移到栈内存中
//好处:不确定类型时可以方便参数的存储和传递
//坏处:存在内存迁移,增加性能消耗
//装箱
object v = 3;
//拆箱
int intValue = (int)v;
TestFun(1, 2, 3, 4f, 34.5, "123123", new Son());
static void TestFun( params object[] array )
{
}
}
万物之父:object
基于里氏替换原则的 可以用object容器装载一切类型的变量
它是所有类型的基类
装箱拆箱
用object存值类型(装箱)
把object里面存的值 转换出来(拆箱)
好处
不去定类型时可以用 方便参数存储和传递
坏处
存在内存的迁移 增加了性能消耗
不是不用,尽量少用
1.练习题1
请口头描述什么是装箱拆箱
//发生条件
//用object存值类型(装箱)
//在把object里存的这个值类型转为值对象时(拆箱)
// 装箱
// 把值类型用引用类型来存储
// 栈内存迁移到堆内存中
// 拆箱
// 把引用存储的值类型取出来放入值对象中
// 堆内存迁移到了栈内存中
// 好处:不确定类型时可以使用 object来存储任何类型 方便存储和传递
// 坏处:存在内存迁移,增加性能消耗
2.练习题2
请用代码描述装箱拆箱
//装箱
int i = 10;
object obj = i;
//拆箱
i = (int)obj;
密封类 是使用 sealed密封关键字修饰的类
作用:让类无法再被继承
class Father
{
}
sealed class Son:Father
{
}
在面向对象程序的设计中,密封类的主要作用就是不允许最底层子类被继承
可以保证程序的规范性、安全性
目前对于大家来说 可能用处不大
随着大家的成长,以后制作复杂系统或者程序框架时 便能慢慢体会到密封的作用
关键字:sealed
作用:让类无法再被继承
意义: 加强面向对象程序设计的 规范性、结构性、安全性
定义一个载具类
有速度,最大速度,可乘人数,司机和乘客等,有上车,下车,行驶,车祸等方法,
用载具类声明一个对象,并将若干人装载上车
class Person
{
}
class Driver:Person
{
}
class Passenger:Person
{
}
class Car
{
public int speed;
public int maxSpeed;
//当前装载了多少人
public int num;
public Person[] persons;
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;
break;
}
}
}
public void Move()
{
}
public void Boom()
{
}
}
#endregion
class Program
{
static void Main(string[] args)
{
Console.WriteLine("封装继承综合练习");
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);
}
}