Download presentation
Presentation is loading. Please wait.
1
第六章 面向对象程序设计基础
2
本章内容 6.1 类和对象的基本特性 6.2 类的建立和实例化 6.3 继承和派生 6.4 综合应用 6.5 自主学习—重载与重写
3
面向过程 面向对象 面向对象 将现实世界的事物抽象成对象,现实世界中的关系抽象成类、继承,帮助人们实现对现实世界的抽象与数字建模。 用“对象”的思想方法来分析和设计程序。 分析解决问题所需步骤 用函数实现步骤 使用时按顺序依次调用函数 例如: 汽车启动; 汽车到站; 什么是对象
4
对象 颜色 型号 出厂日期 开机 切换频道 关机 生活中的“对象”就是东西,任何东西都可以看成 是对象
把一个东西看成对象,可以孤立地观察它的性质、 行为,进而研究它与其他对象的关系。 颜色 型号 出厂日期 开机 切换频道 关机
5
对象 处理文件 在程序中使用对象的概念 程序看成是很多对象相互作用 的结果。 播放声音 显示文件
6
类 类是对象的“蓝图”。 类规定了对象的特征。 对象是根据“蓝图”生产出的产品。
从同一个类产生出的对象具有相同的结构和规格, 却在某些特性上有其不同之处。 总之,类是现实生活中一类具有共同特征的事物的 抽象。 类是对象的模板,对象是类的实例。 实例化 月饼模型(类) 月饼(对象)
7
面向对象的特点—封装 遥控器控制电视,只 需按下按钮就能切换 频道 不需要知道电视如何 接收信号和搜索频道
使用者把电视看成一 个对象,如同一个 “黑盒子”,只需要 知道如何操作它即可。
8
将数据、属性、事件和方法集合在一个类中。
笔记本电脑的封装性: 封装了CPU、内存、硬盘、网卡、声卡等 提供了USB接口、网卡接口、音频接口等 面向对象程序设计的封装性: 将数据、属性、事件和方法集合在一个类中。 数据 属性 事件 方法
9
如:“TextBox”类 } 类的定义 对象的声明 TextBox textBox1;
Class TextBox { …… } 类的定义 对象的声明 TextBox textBox1; 对象实例化 textBox1 = new TextBox(); 对象属性设置 调用方法 textBox1.focus(); 响应事件 textBox1.Name = "textBox1"; textBox1.Text = "我是No.1"; void textBox1_KeyPress(……) { …… }
10
面向对象的特点—继承 基类(父类)派生类(子类) 电视机不断发展 黑白彩色高清 但声音、图像的功能一 直延续下来
后一代“继承”前一代 的功能并有所发展 旧电视派生出新电视 新电视继承了旧电视 派生 继承 继承 派生 基类(父类)派生类(子类)
11
继承性示例: 继承 派生 CSCircle: 继承了CSPoint所有成员 新增了数据成员r 方法Set
12
(续) 我的对外“接口”稳定 旧款录像机与旧款电视机相连 旧款录像机也可与新款电视机相连 电视的音频/视频输入输出接口基本不变
对象的对外接口稳定 对象在各自发展的同时, 还可以进行交互。 我的对外“接口”稳定
13
面向对象的特点—多态 同一操作作用于不同的类的对象,将产生不同的执 行结果,即不同类的对象收到相同的消息时,得到 不同的结果。 汪 喵
14
多态(2) 开始讲课 开始听课
15
默认(省略)时,为internal,只能在当前项目中使用;若为public则可在其他项目中使用
6.2类的定义 class 类名 [:基类] { 数据成员的说明 属性的定义 方法的定义 事件的定义 } 默认(省略)时,为internal,只能在当前项目中使用;若为public则可在其他项目中使用
16
在哪里定义类?(1) 放在命名空间(顶级类) 创建类文件(顶级类)
17
在哪里定义类?(2) 在类Program中定义(嵌套类)
18
“轿车”类的数据成员 颜色 排量 类型 字段名称 数据成员(字段):在类中存储信息的成员 class Car {
Private string color; Private float displacement; Private Datetime releasedate; } 排量 出厂日期 类型 访问修饰符 字段名称
19
成员的访问修饰符 成员声明的可选部分 指明程序的其他部分如何访问成员 手机 private protected internal
访 问 权 限 private 私有的,只能在其声明的类中可以访问,不能在类之外访问。 protected 保护的,只能在其声明的类及其派生类中可以访问。 internal 仅限于此程序(exe或dll) protected internal protected或internal,仅限于此程序或派生类中 public 公共的,访问不受限制。 公用电话
21
“轿车”类的方法 动作:行驶 返回值类型 形式参数列表 访问修饰符 方法名 设计“方法”用于表达对象的动作。 Class Car { ……
public void Run() Console.WriteLine(“Running now.”); } 返回值类型 形式参数列表 访问修饰符 方法名
22
6.2.2 方法 方法实例1:mymove() 方法实例2:printa()
23
调用方法所需的参数及每个参数的数据类型 返回结果的数据类型 [访问修饰符] 返回值类型 方法名([形式参数列表]) { 方法体 } 方法体中使用return语句返回函数值: return 表达式; 或 return(表达式); 或 return;//可在无返回值时使用 无返回值,返回值类型为void,可省略return语句; 有返回值,返回值类型不为void,方法中必须有return语句
24
参数与返回值 参数1 返回值 方法 参数2
25
形式参数(形参) 形参列表位于方法名后的括号内,指明调用该方法所 需参数及每个参数的数据类型 多个参数间用逗号分隔 无参数时,括号不能省略
public void Run(string road) { Console.WriteLine(“我在”+road+”上行驶”); }
26
Public double sin(double x)
形参 方法的调用 Public double sin(double x) { sx=…x.. ; return sx; } static void Main(string[] args) { y=sin(3.14/2); Console.Write (“结果为”+y); } 调用指定对象的方法: 对象.方法名([实在参数表]) 方法调用时: 按照实在参数(实参)的顺序,依次将实参传递给 对应的形参,二者的数据类型保持一致 执行方法中的语句, 在遇到return语句时,或者执行完所有语句后,返 回调用此方法的程序代码段,同时返回一个值。 实参
27
例 方法的调用 形参 实参 class Myclass { public int Sum(int x, int y) return x + y; } public float Avg(float input1, float input2) return (input1 + input2) / 2.0F; static void Main(string[] args) { Myclass myC = new Myclass(); int z = 8; Console.WriteLine("Sum:{0} and {1} is {2}", 4, z, myC.Sum(4, z)); Console.WriteLine("Avg:{0} and {1} is {2}",4,z,myC .Avg (4,z)); }
28
例6.4 方法的调用 static void Main(string[] args) {
CSPoint1 p1 = new CSPoint1(); CSPoint1 p2 = new CSPoint1(); p1.SetValue(0, 0); p2.SetValue(1, 1); double s = p1.Distance(p2); Console.WriteLine(s); Console.ReadKey(); } 例6.4 方法的调用 class CSPoint1 { private double x; private double y; public void SetValue(int a, int b) x = a; y = b; } public double Distance(CSPoint1 p) double s = Math.Sqrt((x - p.x) * (x - p.x) + (y - p.y) * (x - p.y)); return s;
29
public void Run(string road)
参数传递 road “S20高速” 调用方法时(通过参数)将数据传递给被调用方法 值形参:参数声明时无修饰符 引用形参:用ref修饰符说明 输出形参:用out修饰符说明 形参数组:用params修饰符说明 public void Run(string road) car1.Run(“S20高速”);
30
值形参 无修饰符 按值传递过程中,实参和形参各自占用不同的内存空 间 只把实参的值复制给对应的形参 执行期间,形参的值改变对实参无任何影响
例6.5
31
class Test { public static void swap(int x, int y) int temp; temp = x; x = y; y = temp; } 例6.5 开始调用时: 3 k x 4 l y static void Main(string[] args) { int k = 3; int l = 4; Test.swap(k, l); Console.WriteLine("k = {0},l = {1}", k, l); Console.Read(); } 3 4 k x l y 结束调用时:
32
引用形参 ref修饰符 值传递时,实参与形参使用的是相同的内存单元 在执行期间,若形参的值发生变化,实参的值也 会发生相同的变化
引用形参实质上是对应实参的别名,他们相当于 同一个变量或对象 当形参是引用形参时,定义和调用时都必须使用 ref关键字。 例6.6
33
例6.6 k x y l k x y l class Test { int temp; temp = x; x = y; y = temp;
public static void swap(ref int x, ref int y) int temp; temp = x; x = y; y = temp; } 例6.6 开始调用时: k x 3 y l 4 static void Main(string[] args) { int k = 3; int l = 4; Test.swap(ref k, ref l); Console.WriteLine("k = {0},l = {1}", k, l); Console.Read(); } 结束调用时: k x 4 y l 3
34
输出形参 m=7,n=-1 out修饰符 主要用于将数据从被调用函数传递到调用函数。 定义和调用时都必须使用out关键字。
class Test { public static void arith(int x, int y, out int sum, out int difference) sum = x + y; difference = x - y; } 输出形参 out修饰符 主要用于将数据从被调用函数传递到调用函数。 定义和调用时都必须使用out关键字。 m=7,n=-1 static void Main(string[] args) { int k = 3, l = 4; int m, n; Test.arith(k, l, out m, out n); Console.WriteLine("m = {0},n = {1}", m, n); Console.Read(); }
35
学生类属性例6.9 属性的定义格式 访问修饰符同数据成员的5种修饰符 根据get和set访问器是否存在,属性分为:
get访问器:定义属性的读操作!相当于一个具有返回值的无参数方法,需要有return语句返回属性值 属性的定义格式 访问修饰符同数据成员的5种修饰符 根据get和set访问器是否存在,属性分为: [访问修饰符] 类型 属性名 { [ get{get访问器体}] [ set{set访问器体}] } set访问器:定义属性的写操作!相当于一个只有一个参数并且返回值为void类型的方法 读写属性:包含get和set访问器的属性 只读属性:只有get访问器的属性 只写属性:只有set访问器的属性 学生类属性例6.9
36
属性和字段的区别 Value:表示对属性进行写操作时提供的参数 class Car
car1. Color =“red”;时,value即“red” class Car { private string color1; private float displacement1; private Datetime releasedate; public string Color get {return color1;} set {color1=value;} } public string Displacement get {return displacement1}; set {displacement1 =value}; 属性和字段的区别
37
属性与字段的区别 既然可以声明一个public字段以实现读写,为什么 还要采用属性? public字段完全放开,任何人都可以对它进行读写
class Car { …… public DateTime ReleaseDate get return releasedate; } 既然可以声明一个public字段以实现读写,为什么 还要采用属性? public字段完全放开,任何人都可以对它进行读写 而属性控制灵活,若只提供get方法则只支持读; 只提供set方法则只支持写;都提供则支持读写。 1.属性比字段读写提供更多的控制 如Car类的releasedate字段,一旦出厂便已确定,我们希望它只读而不能修改。所以把releasedate字段设为private,而提供一个只读的ReleaseDate属性
38
续 例如,希望为Car类提供一个Age(使用年份)属 性,而且随着时间发展,这个属性会不断变化而更 新。 年份=当前日期-出厂日期
2.属性拥有一些方法的特征,让它达到直接读写字段无法达到的效果 例如,希望为Car类提供一个Age(使用年份)属 性,而且随着时间发展,这个属性会不断变化而更 新。 年份=当前日期-出厂日期 提供Age属性: 属性的读操作中包含了计算的车龄的操作。且车龄 只读,不能人为设置。 public TimeSpan Age { get return(DateTime.today-ReleaseDate); }
39
6.2.5 对象 类Car的对象 car1 car1 创建对象 如Car car1 = new Car();
40
Font f2 = new Font("黑体", 20,FontStyle.Bold); 构造函数的特点:
构造函数的作用:对象初始化的专用函数 Font f1 = new Font("黑体", 20); Font f2 = new Font("黑体", 20,FontStyle.Bold); 构造函数的特点: 1)构造函数名:构造函数的名称与类名相同 , 并且不能指定函数类型(也不能使用void) 2) 可以重载:可以定义多个构造函数 总要有不同的地方:或参数个数不同,或参数类型不 同的 3)构造函数调用: 创建对象时系统自动调用,程序中不能直接调用。 新车出厂的时候,通过构造函数来初始化其颜色、排量、出厂日期。 class Car {…… public Car() //构造函数 { color=“gray”;//设置新Car对象的颜色 displacement=2.0;//设置排量 Releasedate=DateTime.today;//设置出厂日期 }
41
构造函数(续) 构造函数不设返回值 可以编写有参数的构造函数,让它根据参数来初始 化类的字段 构造函数在创建类的对象时,通过new来调用
public Car() //无参数的构造函数 { color=“gray”;//设置新Car对象的颜色 displacement=2.0;//设置排量 Releasedate=DateTime.today;//设置出厂日期 } public Car(string Color,float Diaplacement) //有参数的构造函数 color=Color;//设置新Car对象的颜色 displacement= Diaplacement;//设置排量 构造函数不设返回值 可以编写有参数的构造函数,让它根据参数来初始 化类的字段 构造函数在创建类的对象时,通过new来调用 car1:gray,2.0 Car2:red,2.4 Car car1 = new Car(); //调用无参数的构造函数 Car car2 = new Car(“red”,”2.4”) //调用有参数的构造函数
42
例6.11 创建并访问对象 应用例6.5所完成的Student类,当选择“数据赋予 对象”按钮时,若成绩小于60分则用MessageBox 提示“成绩不及格”;当选择“输出对象数据”按 钮时,用MessageBox输出各属性的值。
43
例6.12 通过代码在窗体上生成文本框
44
6.2.6 构造函数 构造函数的作用:对象初始化的专用函数 构造函数的特点: 1)构造函数名:构造函数的名称与类名相同 ,
构造函数 public Form1() { InitializeComponent(); } 构造函数的作用:对象初始化的专用函数 构造函数的特点: 1)构造函数名:构造函数的名称与类名相同 , 并且不能指定函数类型(也不能使用void) 2) 可以重载:可以定义多个构造函数 总要有不同的地方:或参数个数不同,或参数类型不 同的 3)构造函数调用: 创建对象时系统自动调用,程序中不能直接调用。 新车出厂的时候,通过构造函数来初始化其颜色、排量、出厂日期。 class Car {…… public Car() //构造函数 { color=“gray”;//设置新Car对象的颜色 displacement=2.0;//设置排量 Releasedate=DateTime.today;//设置出厂日期 }
45
构造函数(续) 构造函数不返回值 可以编写有参数的构造函数,让它根据参数来初始 化类的字段 构造函数在创建类的对象时,通过new来调用
public Car() //无参数的构造函数 { color=“gray”;//设置新Car对象的颜色 displacement=2.0;//设置排量 Releasedate=DateTime.today;//设置出厂日期 } public Car(string Color,float Diaplacement) //有参数的构造函数 color=Color;//设置新Car对象的颜色 displacement= Diaplacement;//设置排量 构造函数不返回值 可以编写有参数的构造函数,让它根据参数来初始 化类的字段 构造函数在创建类的对象时,通过new来调用 car1:gray,2.0 Car2:red,2.4 Car car1 = new Car(); //调用无参数的构造函数 Car car2 = new Car(“red”,”2.4”) //调用有参数的构造函数
46
继承和派生 基类(父类)派生类(子类) 后一代“继承”前一代的 功能 但是在功能上又有所发展 旧电视派生出新电视 新电视继承了旧电视 派生
定时开机 自动搜索
47
6.3.1 基类和派生类 派生类是基类的一个子集 定义:在已有类的基础上构造新的类, 新类继承了原有类的数据成员、属性、方法和事件
原有的类称为基类,新类称为派生类 派生类是基类的一个子集 面向对象程序设计中的继承性 派生类 派生 继承 基类 派生类 基类 注意:箭头方向 Mammal 哺乳动物 Horse 马 Whale 鲸鱼 Mammal 哺乳动物 基类 派生类
48
哺乳动物的继承关系 class 派生类名 : 基类名 { //… }
马 鲸鱼 齿鲸 须鲸 豹子 System.Object类是所有类的基类,所有类都隐式派生于Object类。C#编译器会悄悄将Mammal类写为以下代码 class Mammal : Object { //… } C#中定义的所有类会自动继承Object类的特性,如ToString()方法
49
public void SuckleYoung() //哺乳
class Mammal { public void Breath()//呼吸 … } public void SuckleYoung() //哺乳 class Horse : Mammal { public void Trot() //小跑 … } class Whale: Mammal { public void Swim ()//游泳 … }
50
派生类的对象 派生类的对象可以作为基类的对象处理 例 定义的派生类对象可以访问基类的公用成员 允许将派生类对象赋值给基类对象
子类调用父类的非私有成员 派生类的对象 class Mammal { public string name; public void Breath() Console.WriteLine(name + "正在呼吸"); } public void SuckleYoung() Console.WriteLine(name + "正在哺乳"); static void Main(string[] args) { Mammal mammal = new Mammal(); mammal.name=“mammal”; mammal.Breath(); mammal.SuckleYoung(); Horse horse = new Horse(); horse.name=“horse”; horse.SuckleYoung(); horse.Trot(); } 派生类的对象可以作为基类的对象处理 定义的派生类对象可以访问基类的公用成员 允许将派生类对象赋值给基类对象 例 class Horse : Mammal { public void Trot() this.Breath();//调用父类的public方法 Console.WriteLine(name + "正在奔跑"); //调用父类的public数据成员 }
51
无参数构造函数 在建立派生类的实例对象时,先调用基类的构造函 数,再执行派生类的构造函数。 例 class Mammal {
public Mammal() Console.WriteLine("我是一只哺乳动物"); } //… static void Main(string[] args) { Horse horse = new Horse(); } 在建立派生类的实例对象时,先调用基类的构造函 数,再执行派生类的构造函数。 例 class Horse : Mammal { public Horse() Console.WriteLine("我是一匹马"); } //…
52
有参数构造函数 : base(基类构造函数参数表) 用base关键字来指定调用一个基类的构造函数。 先调用基类的有参数构造函数;
public 派生类名(派生类构造函数总参数表) : base(基类构造函数参数表) { 派生类数据成员初始化 } 例 用base关键字来指定调用一个基类的构造函数。 先调用基类的有参数构造函数; 然后调用派生类的有参数构造函数
53
Console.WriteLine("我是一只哺乳动物"); } public Mammal(string mammalName)
static void Main(string[] args) { Mammal mammal = new Mammal("mammal"); Horse horse = new Horse("horse"); Console.ReadLine(); } class Mammal { private string name; public Mammal() Console.WriteLine("我是一只哺乳动物"); } public Mammal(string mammalName) name = mammalName; Console.WriteLine(mammalName +"是一只哺乳动物"); class Horse : Mammal { public Horse(string name): base(name) Console.WriteLine(name+"是一匹马"); } //…
54
例 6.2 定义一个学生类Student 学号 私有 数据成员 姓名 成绩 设置数据成员值 公有 方法 输出数据成员值
55
自行定义类的事件 引发(或发送)事件的类:“发行者”(生产者) 处理(或接收)事件的类:“订户”(消费者) 事件:类/对象向其他类/对象通知
嘀嘀 在类外声明委托 public delegate void 委托; 在类定义中: 用public Event 委托 事件名 语句声明事件 合适地方引发 事件名() 语句引发事件 在对象实例化时: 对象.事件名+=new 委托(处理方法)添加事件 处理方法代码编写 事件:类/对象向其他类/对象通知 发生的相关事情 Form1_Click事件是如何定义的? Form1_Click事件应用 例6.10 设置学生的成绩,当<60时引发事件 引发(或发送)事件的类:“发行者”(生产者) 处理(或接收)事件的类:“订户”(消费者)
56
事件 委托除了用于“回调”,最大的用处是支持事件机制 嘀嘀
public Form1() { this.Click += new EventHandler(this.Form_C); } //自编写方法 private void Form_C (object sender, EventArgs e) MessageBox.Show("您在窗体上单击了鼠标"); 类Form1在构造函数中,为事件Click添加了一个处理程序Form1_C ()。 “+=“ 作为委托串联运算符 把一个方法指派给一个委托,使得调用这个委托就相当于调用这个方法。
57
一个委托可以被指派给多个方法 一个事件就可以同时拥有多个处理程序 两个方法的调用顺序就是它们被指派给Click事件的顺序。
public Form1() { this.Click += new EventHandler(this.Form_Click); this.Click += new EventHandler(this.Form_Click2); } private void Form_Click(object sender, EventArgs e) MessageBox.Show("您在窗体上单击了鼠标"); private void Form_Click2(object sender, EventArgs e) MessageBox.Show("Form_Click2的响应"); 两个方法的调用顺序就是它们被指派给Click事件的顺序。 构造函数为对象的Click事件指定了两个事件处理程序
58
移除委托 C#允许使用+=运算符来进行委托串联; 也允许使用-=运算符来移除委托。 public Form1() {
EventHandler eh = new EventHandler(this.Form_Click); this.Click += eh; this.Click += new EventHandler(this.Form_Click2); this.Click -= eh; }
59
public delegate void FailHandler() ; public event FailHandler Fail ;
在命名空间内: 关键字:委托 声明一个委托的实例,命名为FailHandler 声明一个委托实例(定义一个委托类型) public delegate void FailHandler() ; 在事件生产者处: 委托名 在事件消费者处: 事件名 订阅事件 在类中声明这个事件(即产生委托的一个实例) 订阅或取消事件 s.Fail += new FailHandler(Fail_Handler); public event FailHandler Fail ; 在合适的地方加入引发事件的代码 编写执行事件的代码 订阅后,只要事件Fail被触发,就会调用Fail_Handler Public static void Fail_Handler() { Console.Write(“不及格”); } public int 成绩 { get { return Score; } set { Score=value; if(Score<60)Fail(); } }
60
(1)创建应用程序,添加类Student class Student { private string No; // 学号
private string Name; // 姓名 private int Score; // 成绩 public void SetValue(string a, string b, int c) // SetValue方法用于设置各数据成员的值 { No = a; Name = b; Score = c; } public void Print() // Print方法用于输出各数据成员的值 { Console.WriteLine(No); Console.WriteLine(Name); Console.WriteLine(Score); public string 学号 // 定义学号属性 { get { return No; } set { No = value; } public string 姓名 // 定义姓名属性 { get { return Name; } set { Name = value; }
61
(2)声明事件处理委托 namespace T6._10 { public delegate void FailHandler();
//其他语句 }
62
(3)在类Student的定义中 声明事件类型 编写引发事件的代码
class Student { //。。。。。。其他语句 public event FailHandler Fail; //声明事件Fail为FailHandler类型 public int 成绩 // 定义成绩属性 get { return Score; } set Score = value; if (Score < 60) Fail(); // 当成绩小于60分时引发Fail事件 }
63
(4)在类Program中 订阅事件 编写执行事件的代码
class Program { static void Main(string[] args) Student s = new Student(); s.Fail += new FailHandler(Fail_Handler); s.学号 = "9803"; s.姓名 = "王涛"; s.成绩 = 56; } public static void Fail_Handler() Console.WriteLine("不及格");
64
事件的本质是委托 在类中加入事件: 定义一个委托类型 在类中加入这个委托的一个实例(即事件) 在合适的地方加入对这个委托的调用(触发事 件)
65
6.4 综合应用 例6.15 定义一个时间类Date,包含年、月、日三 个数据成员,以及一个求第二天日期的方法和输出 日期的方法。
66
综合应用(续) 例6.16 定义一个汽车类vehicle,其数据成员有车 轮个数wheels和车重weight;再定义一个派生类 —卡车类truck,包含新的数据成员载重量payload 及成员函数载重效率。每个类都有相关数据的输出 方法。 其中:载重效率=载重量/(载重量+车重)
Similar presentations