CH05-深入类的方法
CH05-深入类的方法
本章目标
- 理解并会编写类的构造函数
- 会实现方法重载
- C#方法的可选参数和命名参数
- C#扩展方法
- 理解类之间的通信
本章内容
1、类的构造函数
为什么要使用构造函数?
1
2
3
4
5
6
7
8
9static void Main(string[] args)
{
//创建Student对象,new Student()就是在调用系统提供的无参构造函数
Student student=new Student();
student.StudentNo="1001";
student.StudentName="张三";
student.Gedner="男";
}以上代码,当创建对象的时候,给对象的属性初始值,如果有属性未赋值,则系统会给默认值,那么当创建对象的时候,就指定初始值,有没有更简单的方式呢?
构造函数可以解决以上问题。
什么是构造函数?
概念:
构造函数是类中一种特殊的方法,方法具备如下特征:
- 方法名与类名相同
- 方法无返回值
作用:
完成对象的初始化工作
执行时机:
创建对象时执行,通过关键new调用。
演示案例:
给学生类添加构造函数,指定默认属性
1
2
3
4
5
6
7
8
9
10
11
12
13public class Student
{
//创建一个无参构造函数,给属性赋初始值
public Student()
{
this.StudentNo = 1001;
this.StudentName = "张三";
this.Age = 18;
}
public int StudentNo { get; set; }
public string StudentName { get; set; }
public int Age { get; set; }
}具体说明:
- 在构造函数中,可以给属性设置默认值
- this指代当前对象
编写带参构造函数
不同对象实例化后,仍需设置不同属性值;能否实例化时一次性设置对象真正的属性值?
带参数的构造函数,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18public class Student
{
//指定参数,给对应属性赋值
public Student(int studentNo,string studentName,int age)
{
this.StudentNo = studentNo;
this.StudentName = studentName;
this.Age = age;
}
public int StudentNo { get; set; }
public string StudentName { get; set; }
public int Age { get; set; }
}
static void Main(){
//创建对象时,必须按构造函数定义参数传入初始化值
Student student=new Student(1001,"张三",20);
}类的隐式构造函数
演示代码,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class Student
{
//指定参数,给对应属性赋值
public Student(int studentNo,string studentName,int age)
{
this.StudentNo = studentNo;
this.StudentName = studentName;
this.Age = age;
}
public int StudentNo { get; set; }
public string StudentName { get; set; }
public int Age { get; set; }
}
static void Main(){
Student student=new Student();//创建无参构造函数,则报错。编译错误:不包含采用“0”参数的构造函数
}注意事项:
每个类都默认有一个无参数的构造函数,一旦手动添加了构造函数,系统就不会再给类添加无参构造函数。
通过方法重载可以解决以上问题。
2、方法重载
概念:
在一个类中,方法名相同,参数列表不同(参数类型不同,参数个数不同),与返回类型无关
生活中的方法重载
代码演示1:
1
2
3
4
5
6public class Player
{
public void Play(歌曲){ //演唱歌曲}
public void Play(钢琴){ //弹奏钢琴}
public void Play(剧本){ //根据剧本表演}
}代码演示2:
通过方法重载实现问好功能:
1
2
3
4
5
6
7
8
9public class Test{
static void SayHi(Student stu){
Console.WriteLine("大家好,我叫:{0},今年:{1}岁!",stu.StudentName,stu.Age);
}
static void SayHi(Teacher tea){
Console.WriteLine("大家好,我叫:{0},今年执教:{1}年!",tea.TeacherName,stu.TeacherYear);
}
}
3、方法可选参数和命名参数
可选参数
概念:
可选参数是.Net4.0才加入的,在调用方法的时候可以包含这个参数,也可以省略它。
为了表明某个参数是可选的,你需要在方法声明的时候为参数提供默认值。制定默认值的语法和初始化本地变量的语法一样。
语法:
1
2
3访问修饰符 返回类型 方法名(数据类型 参数名=值){
......
}案例:
1
2
3
4
5
6
7
8
9
10
11
12static void Main(string[] args)
{
SayHi("张三");
SayHi("李四",20);
Console.ReadLine();
}
static void SayHi(string name, int age = 16, string gender = "男")
{
Console.WriteLine($"大家好,我叫:{name},今年:{age},性别:{gender}");
}注意事项:
1.不是所有的参数类型都可以作为可选参数。
1)只要值类型的默认值在编译的时候可以确定,就可以使用值类型作为可选参数。
2)只有在默认值是null的时候,引用类型才可以作为可选参数来使用。
2.所有必填参数必须在可选参数声明之前声明,如果有params参数,必须在所有可选参数之后声明。
3.必须从可选参数列表的最后开始省略,一直到开头,否则会造成参数歧义。
4.若想消除参数歧义,可以结合命名参数和可选参数的特性。
命名参数:
概念:
有了命名实参,将不再需要将实参的顺序与所调用方法的形参列表中的形参顺序相匹配。 每个形参的实参都可按形参名称进行指定。
优点:
- 命名实参还可以标识每个实参所表示的含义,从而改进代码的可读性
- 如果不记得形参的顺序,但却知道其名称,则可以按任意顺序发送实参。
语法:
1
2
3
4
5
6
7访问修饰符 返回类型 方法名(数据类型 参数名=值 ...){
......
}
方法名(参数名:值1,参数名:值2....){
}案例:
1
2
3
4
5
6
7
8
9
10
11static void SayHi(string name, int age = 16, string gender = "男")
{
Console.WriteLine($"大家好,我叫:{name},今年:{age},性别:{gender}");
}
static void Main(string[] args)
{
SayHi(name: "李四", 18, gender: "男");//按参数顺序可任意指定命名参数
SayHi(name: "小利", gender: "女",age: 20);//未按形参顺序,则都需要指定参数名
Console.ReadLine();
}
4、扩展方法
为什么要使用扩展方法:
问题:我们想要向一个类型中添加方法,可以通过以下两种方式:
修改源代码。
在派生类中定义新的方法。
但是这两种方式都有缺点:
- 如果是别人的代码,你对其直接进行修改,可能破坏代码的完整性,使代码无法编译
- 会增加代码的维护成本,修改功能时父类子类可能都要修改
则通过扩展方法可以解决以上问题。
扩展方法概述和基本准则
概述:
(1).C#只支持扩展方法,不支持扩展属性、扩展事件、扩展操作符等。
(2).扩展方法(第一个参数前面是this的方法)必须在非泛型的静态类中声明,扩展方法必须有一个参数,而且只有第一个参数使用this标记。
(3).用一个扩展方法扩展一个类型时,同时也扩展了派生类型。
扩展方法是C#中一种特殊的静态方法,它定义在一个静态类中,但是可以像实例方法一样被调用,使得代码更加简洁、易读。
扩展方法有以下特点:
- 它必须在一个静态类中定义。
- 它必须有至少一个参数。
- 第一个参数必须有this前缀,并且指定了要扩展的类型。
- 第一个参数不能有任何其他的修饰符(如out或ref)。
案例:
给字符串string类添加一个扩展方法,实现统计单词的个数
定义扩展方法:
1
2
3
4
5
6
7
8
9
10
11/// <summary>
/// 定义一个静态类,实现扩展方法功能
/// </summary>
public static class ExtensionMethod
{
public static int GetWordCount(this string s)
{
return s.Split(new char[] { ' ', ',', '.' }, StringSplitOptions.RemoveEmptyEntries).Length;
}
}调用方法:
1
2
3
4
5
6static void Main(string[] args)
{
string str = "hello every one";
int wordCount=str.GetWordCount();
}
给自定义类添加扩展方法
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
29public static class ExtensionMethod
{
public static string SayHello(this Student stu)
{
string str = string.Format($"Name:{stu.Name},Age:{stu.Age},Gender:{stu.Gender}");
return str;
}
}
public class Student
{
public int Age { get; set; }
public string Name { get; set; }
public string Gender { get; set; }
public void SayHi()
{
Console.WriteLine($"大家好,我叫{Name},年龄:{Age},性别:{Gender}");
}
}
static void Main(string[] args)
{
Student stu = new Student() { Name = "zhangsan", Age = 20 };
string str = stu.SayHello();
Console.WriteLine(str);
Console.ReadLine();
}
5、对象交互
对象交互的作用:
面向对象的方法实现了对象相互协同工作,共同完成软件功能!
简化的面向对象软件开发过程
- 分析需求和总体任务
- 设计合适的类,满足需求和任务
- 实例化对象
- 通过外部触发,驱动对象操作
对象之间是如何进行交互的呢?
- 封装:隐藏实现细节,公开某种功能作为与外界通信的通道
- 消息传递:每个对象都具有特定功能,相对其他对象而言,它的功能就是为其他对象提供的服务
演示案例:模拟遥控器控制电视机开、关、换台
分析:找出合适的类
电视类
找属性:
电视机的状态(开,关)
找方法:
- 开机
- 关机
- 换台
遥控器
找方法:
- 打开电视
- 关闭电视
- 换电视台
类图如下 :
具体分析:
参考代码:
创建电视类:
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/// <summary>
/// 电视机类
/// </summary>
public class Television
{
private Boolean isOn = false;//是否开机
//打开电视
public void Open()
{
if (isOn)
{
Console.WriteLine("电视机已打开!");
}
else
{
Console.WriteLine("成功打开电视!");
isOn = true;
}
}
//关机
public void TurnOff()
{
if (isOn)
{
Console.WriteLine("正在关机...");
isOn = false;
}
else
{
Console.WriteLine("电视机已关闭!");
}
}
//换台
public void Change(string channelNo)
{
if(isOn)
{
Console.WriteLine("正在切换到{0}台", channelNo);
}
}
}
创建遥控器类:
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/// <summary>
/// 遥控器类
/// </summary>
public class RemoteControl
{
//开机
public void TurnOn(Television tv)
{
tv.Open(); //调用电视机对象的开机方法
}
//关机
public void TurnOff(Television tv)
{
tv.TurnOff(); //调用电视机对象的关机方法
}
//换台
public void ChangeChannel(Television tv)
{
Console.Write("请输入频道号:");
string channelNo = Console.ReadLine();
tv.Change(channelNo);
}
}测试功能:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17static void Main(string[] args)
{
RemoteControl controler = new RemoteControl();
Television tv = new Television() ;
//开机
controler.TurnOn(tv);
//切换频道
controler.ChangeChannel(tv);
//关机
controler.TurnOff(tv);
Console.ReadLine();
}演示结果如下:
演示案例,通过对象交互实现OOP方法模拟顾客点餐
演示代码:
菜单类
1
2
3
4
5
6
7//菜单类
public class Order
{
public Client customer; //顾客
public int id; //餐桌号
public string mealList; //点的菜单
}厨师类
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//厨师类
public class Chef
{
private Order order;
/// <summary>
/// 获得菜单
/// </summary>
public void GetOrder(Order order)
{
this.order = order;
}
/// <summary>
/// 厨师做菜
/// </summary>
public void Cook()
{
Console.WriteLine("厨师烹制:{0}",order.mealList);
Console.WriteLine("制作完毕");
}
/// <summary>
/// 提醒饭菜制作完毕
/// </summary>
/// <param name="waitress"></param>
public void SendAlert(Waitress waitress)
{
Console.WriteLine("厨师提示服务员取菜!");
waitress.GetOrder(order);
}
}服务员类
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/// <summary>
/// 服务员类
/// </summary>
public class Waitress
{
private Order order;
/// <summary>
/// 记录客人的点餐
/// </summary>
/// <param name="order"></param>
public void GetOrder(Order order)
{
this.order = order;
}
/// <summary>
/// 给厨师提交菜单
/// </summary>
///<param name="client">点菜顾客的对象</param>
public void SendOrder(Chef chef)
{
Console.WriteLine("服务员将菜{0}传给厨师", order.mealList);
chef.GetOrder(order);
}
/// <summary>
/// 传菜
/// </summary>
public void TransCook()
{
Console.WriteLine("服务员将菜{0}送给客户{1}!",order.mealList,order.id);
order.customer.Eat();
}
}顾客类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//顾客类
public class Client
{
/// <summary>
/// 点菜
/// </summary>
public void Order(Waitress waitress,Order order)
{
Console.WriteLine("顾客开始点菜:{0}!", order.mealList);
waitress.GetOrder(order);
}
/// <summary>
/// 用餐
/// </summary>
public void Eat()
{
Console.WriteLine("客人用餐!");
}
}测试类
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
35class Program
{
static void Main(string[] args)
{
//初始化客户、服务员、厨师
Client wang = new Client();
Waitress waitress = new Waitress();
Waitress zhang = new Waitress();
Chef chef = new Chef();
//初始化点菜单
Order order = new Order();
//设置订了该菜单的顾客
order.customer = wang;
order.id = 100;
order.mealList = "水煮鱼";
//顾客wang选中waitress服务员给自己服务
wang.Order(waitress,order);
//服务员将菜单信息告知厨师chef
waitress.SendOrder(chef);
//厨师根据菜单做菜
chef.Cook();
chef.SendAlert(waitress);
waitress.TransCook();
Console.Read();
}
}演示结果 :
本章总结
本章作业
通过构造函数实现问好功能,具体说明如下:
需求说明
给SE类和PM类添加带参构造函数
属性值在实例化时初始化
实例化对象并实现问好
类说明如下:
PM经理类:
属性:工号(ID),姓名(name),年龄(age),性别(gender),资历(yearOfExperience)
方法:问好(SayHi)
SE员工类
属性:号(ID),姓名(name),年龄(age),性别(gender),人气值(popularity)
方法:问好(SayHi)
使用方法重载,计算公司不同级别员工薪水
需求说明:
员工薪水计算方式:
项目经理:基础工资+项目奖金+分红
属性:基本工资(BasePay),项目奖金(MgrPrize ),分红(Bonus)
程序员:基础工资+考核工资
属性:基本工资(BasePay),考核工资(MeritPay)
薪水计算类,通过方法重载实现项目经理和员工的薪水计算
实现思路
为SE类添加属性
基础工资、考核工资
为PM类添加属性
基础工资、项目奖金、分红
添加新的构造函数
编写薪水计算类CompSalary,实现重载的Pay()方法Pay(PM pm) 和 Pay(SE se)
实例化对象,输出所得薪水
测试结果 :
模拟汽车奔跑
需求说明
编写控制台程序,模拟小汽车Car奔跑
- 编写Car类(车名、颜色、产地),实现Run()方法
- 添加有参构造函数,在构造函数中给成员赋值,通过构造函数创建对象,并调用Run()方法
- 重载Run()方法,给Run()传递一个int类型参数 speed(最高车速),输出信息,车速通过参数传递
测试结果:
模拟顾客点餐系统,讲演示案例自己理解后,写出程序。