第四章 面向对象与IO操作

一、类(类中可以写字段、属性、方法、构造函数)
1.定义一个类用关键字class,后面加类名,类名第一个字母用大写,可用private或public修饰符定义访问级别,类可定义在同一命名空间中,也可定义在单独一个文件中;
如:(private|public) class Person
2.类里面可以有类变量(也叫字段或成员变量),也可以有方法;
如:
class Person
{
private int age;
private string name;
public void setAge(int age)
{
this.age = age;//此处this.age指的是字段age,意思是将方法里面的age赋值给字段age
}
public void setName(string name)
{
this.name = name;
}
public void sayHello()
{
Console.WriteLind("大家好,我叫"+this.name+",我今年"+this.age+"岁了");
}
}
3.字段如果用private修饰时,只能在本类中访问此字段,当用public修饰时任何地方都可访问,一般字段是不被外界访问的,用private修饰,如定义为private,那年龄即可被外界设为小于于0的值,这是非法的;
4.如果字段不写访问修饰符时,默认为private;
5.当字段用private修饰时,因为外界不能访问,可以在类里面写一个方法,方法传入参数进行对字段赋值;
6.类的实例化,也叫创建对象;
语法:定义的类名 要创建的类名字 = new 定义的类名();
如:
Person yzk = new Person();//创建一个对象yzk
yzk.setAge(18);//调用Person类里面的setAge方法并传入一个int的参数,person里面的方法将此参数赋值给类里面的私有字段age;
yzk.setName("杨中科");//调用Person类里面的setName方法并传入一个string的参数,person里面的方法将此参数赋值给类里面的私有字段name;
yzk.sayHello();//调用Person类里面的sayHello方法;
7.new关键字:
1)new出的每个对象都是一个单独的实例,两个对象之间的成员变量是独立的两份,new出来的叫类对象或者实例(Instance),当new出两个对象时,两个对象的内存分配是根据模板拷贝两份;
2)new帮助我们做了3件事儿:
<1>在内存中开辟一块空间;
<2>在开辟的空间中创建对象;
<3>调用对象的构造函数进行初始化对象;
3)隐藏从父类继承过来的同名成员,隐藏的后果就是子类调用不到父类的成员;
8.null关键字,表示变量没有指向任何对象,当没有给类里面的字段赋值就使用时:
string输出为null
int、long、float、double输出为0
bool输出为false
char时不明白???
9.局部变量必须初始化,字段变量声明时默认初始化,基本数值类型默认初始化为int,string非基本类型(也叫引用类型)初始化为null,为什么string是引用类型?涉及到栈内存和堆内存;
10.this关键字:
1)this.代表当前对象,当成员变量和局部变量(方法里面的参数也可以看做是局部变量)重名的时候,被看做局部变量,因此为了避免混乱,建议访问成员变量的时候加上"this.";
2)在类当中显示的调用本类的构造函数,在重载的构造函数中用:加this(写变量名字)
如:public Person(int age,string name):this(age,name)
二、Field(字段),Method(方法),Properties(属性)
1.属性:为了避免外界给成员变量随便赋值值,必须把成员变量声明为private,然后再写两个方法给这两个字段赋值,这样写起来、调用起来都麻烦,因此提供了"属性"这样一个语法;

private int _age;
public int Age
{
    get
    {
        return this._age; //返回当前字段_age的值
    }
    set
    {
        this._age = value;//value是用户传过来的值,将用户的值赋给当前字段_age
    }
}

解释:属性就等于两个方法get和set,get方法是返回当前的字段,set方法是将用户传过来的值赋给当前字段
2.如果是简单set/get逻辑,可以使用更简单写的语法:
public int Age
{
  get;//相当于return this._age;
  set;//相当于this._age = value;
}
3.get、set可以有一个声明为private、protected,这样就可以设置不同的访问级别;
4.如果只有get或set就是只读或只写属性,只读只写不能简化写;
5.属性是用来保护字段的,可以在get或set里面设定保护值,给属性赋值也就相当于给字段赋值,属性等于中间过渡的意思,属性并不存值;

***创建对象的时候叫类的实例化,也就是创建一个对象;
***当我们创建好一个类的对象后,需要给这个对象的每个属性去赋值,我们管这个过程称这为对象的初始化;
如:
解释:先执行set方法,判断用户传过来的值,set方法是得到用户的值,再执行get方法,get方法也可以做判断再返回,set方法有一个参数value,get方法没有参数;

public class Person
{
    private int _age;
    public int Age
    {
        get
        {
            if (this._age < 0)
            {
                return _age = 0;
            }
            return _age;
        }
        set
        {
            if (value > 150)
            {
                value = 0;
            }
            _age = value;
        }
    }
}

6.构造函数:
1)作用:帮助我们初始化对象(给对象的每个属性依次的赋值)
2)是创建类对象,在创建完成前对类进行初始化的特殊函数(也就是创建对象时先去调用构造函数),如果定义类时没有声明构造函数,默认会给出一个无参数的构造函数,如果定义了任意一个构造函数,将不会提供默认的无参构造函数,要想有无参的构造函数,就要重新写一个;
2)构造方法格式及特点:
方法名必须和类名一致,没有返回值,连void都不用写,构造方法也可以重载,如:
Public Person()
{
  //这是一个无参数无方法体的构造函数
}
Public Person(int age)
{
  this._age = age;//构造函数初始化时将用户传过来的值赋给当前字段_age
}
3)创建对象时先执行构造函数;
4)可以在构造函数里面初始化对象,将用户传过来的值赋给属性,属性再返回字段的值;
5)构造函数的访问修饰符必须为public;
6)构造函数是可以有重载的;
8.static(静态)关键字:一些场景下会要求一个类的多个实例共享一个成员变量(字段),有时候想定义一些不和具体对象关联、不需要new就调用的方法就可以将字段或方法定义为静态形式的;
9.static方法不需要new就可以直接通过类名.方法名调用;
10.static变量不需要new就可以直接通过类名调用,static变量是共享的内存空间,非static变量则是对象隔离的;
11.static方法中无法使用this关键字,因为static独立于对象存在,不是任何人的唯一;
13.静态与非静态的区别:
1)在非静态类中,既可以有实例成员,也可以有非静态成员;
2)在调用实例成员的时候,需要使用对象名.实例成员名;
3)在调用静态成员的时候,需要使用类名.静态成员名;
4)只要是静态成员,都必须使用类名去调用;
6)static成员中只能访问static成员,不能直接访问非static成员,但非static成员可以访问static成员;
7)静态类中,只能出现静态成员,不能出现非静态成员;
8)只有在程序全部结束之后,静态类才会释放资源;
14.析构函数:
作用:帮助程序释放资源,当程序结束的时候才被执行,如果希望马上释放资源,可以调用析构函数
~Person()
{
  Console.WriteLine("这是一个析构函数");
}
15.命名空间
<1>可以认为类是属于命名空间的;
如果在当前项目中没有这个类的命名空间,需要手动的导入这个类所在的命名空间;
有多种方法:
1)用鼠标去点;
2)alt+shift+f10;
3)记住命名空间,手动的去引用;
<2>在一个项目中引用另一个项目的类
1)添加引用
2)引用命名空间;

16.单例模式
1)将构造函数私有化;
2)提供一个静态方法,返回一个对象;
3)创建一个单例;

public partial class Form2 : Form
{
    //全局唯一的单例
    public static Form2 FrmSingle = null;

    private Form2()
    {
        InitializeComponent();
    }

    public static Form2 GetSingle()
    {
        if (FrmSingle == null)
        {
            FrmSingle = new Form2();
        }
        return FrmSingle;
    }
}

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    private void button1_Click(object sender, EventArgs e)
    {
        Form2 frm2 = new Form2();
        frm2.Show();
    }
}

17.值类型与引用类型
区别:
1)值类型和引用类型在内存上存储的地方不一样;
2)在传递值类型和传递引用类型的时候,传递的方式不一样;
值类型我们称之为值传递,引用类型我们称之为引用传递
值类型:int、double、bool、char、decimal、struct、enum
引用类型:string、数组、自定义类
值类型的值是存储在内存的栈中;
引用类型的值是存储在内存的堆中;

18.字符串的不可变性
1)当给一个字符串重新赋值之后,老值并没有销毁,而是重新开辟一块空间存储新的值;
2)当程序结束后,GC扫描整个内存,如果发现有的空间没有被指向,则立即把它销毁;
3)可以将字符串看做是char类型的一个只读数组,可以通过下标去访问字符串的某一个元素;
如:
string s = "abcdefg";
Console.WriteLine(s[0]);//通过下标访问字符串的第一个元素
<1>若要将字符串的第一个元素改为b,首先将字符串转换为char类型的数组,通过调用ToCharArray()方法转换,此方法返回的是一个Char数组
char[] chs = s.ToCharArray();//To是转换为什么类型,Char是字符,Array是数组,意思就是转换为Char类型数组
<2>得到的字符数组即可通过下标改变元素的值,chs[0] = 'b';
<3>将字符数组转换为我们的字符串,通过new一个string即可,返回的是一个字符串,s = new string(chs);
<4>字符串大量操作时,可用StringBuilder;

StringBuilder sb = new StringBuilder();
//string str = null;
//创建一个计时器,用来记录程序运行的时间
Stopwatch sw = new Stopwatch();
sw.Start();//开始计时
for (int i = 0; i < 100000; i++)
{
    //str += i;
    sb.Append(i);
}
sw.Stop();//结束计时
Console.WriteLine(sb.ToString());
Console.WriteLine(sw.Elapsed);
Console.ReadKey();

字符串提供的各种方法:
1)ToCharArray();将字符串转换为char数组,如:string s="123456";char[] chs=s.ToCharArray();
2)new string(char[] chs);将字符数组转换为字符串,如:s = new string(chs);
3)ToUpper();转大写,如:string s="abc";s=s.ToUpper();
4)ToLower();转小写,如:string s="ABC";s=s.ToLower();
5)Equals(LessoneTwo,StringComparison.OrdinalIgnoreCase);比较两个字符串,忽略大小写,
如:
LessonOne.Equals(LessoneTwo,StringComparison.OrdinalIgnoreCase)
6)Split();分割字符串
如:
string s="a b dfd _ + = ,,, fdf";
char[] chs={' ','_','+',',','='};
string[] str = s.Split(chs,StringSplitOptions.RemoveEmptyEntries);
7)Replace();替换字符串
8)Contains();是否包含某字符串
如:
string str="国家关键人物老黄";
if (str.Contains("老黄"))
{
str = str.Replace("老黄","**");
}
9)Substring();截取字符串
string str="国家关键人物老黄";
str = str.Substring(1,2);//从索引1开始截取两个字符
10)StartsWith();判断是否以某字符串开头,返回bool类型
11)EndWith();判断是否以某字符串结尾,返回bool类型
12)IndexOf();从开头查找某字符串出现的位置,查不到时返回-1
13)LastIndexOf(0;从最尾查找某字符串出现的位置,查不到时返回-1
如:
string path=@"c:acdefg苍老师.wav";
int i = path.LastIndexOf("\");
path = path.Substring(i+1);
Console.WriteLine(path);
14)Trim();移除两边空格
15)TrimEnd();移除后面空格
16)TrimStrat();移除前面空格
17)IsNullOrEmpty();判断字符串是否为null或为空,返回bool类型
如:
string str=null;
string.IsNullOrEmpay(str)
18)Join();连接字符串
如:
string[] names={"张三","李四","王五"};
string str = string.Join("|",names);

练习:
<1>接收用户输入的字符串,将其中的字符经与输入相反的顺序输出,如"abc",cba;
<2>接收用户输入的一句英文,将其中的单词以反序输出,“hello c sharp”-->"sharp c hello";
<3>从Email中提取出用户名和域名,abc@163.com;
<4>文本文件中存储了多个文章标题、作者,标题和作者之间用若于空格(数量不定)隔开,每行一个,标题有的长有的短,输出到控制台的时候最多标题长度10,如果超过10,则截取长度8的子串并且最后添加"...",加一个竖线后输出作者的名字;
<5>让用户输入一句话,找出所有e的位置;
<6>让用户输入一句话,判断这句话有没有邪恶,如果有邪恶就替换成这种形式,如:"老牛很邪恶",输出后变成老牛很**;
<7>把{"诸葛亮","鸟叔","卡卡西","卡哇伊"}变成诸葛亮|鸟叔|卡卡西|卡哇伊,然后再把|切割掉

三、继承
1.我们可能会在一些类中,写一些重复的成员,我们可以将这些重复的成员,单独的封装到一个类中,作为这些类的父类;
Person 父类(基类)
Student、Teacher、Driver 子类(派生类)
子类继承了父类,那么子类从父类那里继承过来了什么呢?
首先,子类继承了父类的属性和方法,但是子类并没有继承父类的私有字段;
问题:子类有没有继承父类的构造函数?
答:没有,但是子类会默认的调用父类无参数的构造函数,因为要创建父类对象,让子类可以使用父类中的成员,所以,如果在父类中重写了一个有参数的构造函数之后,那个无参数的就被干掉了,子类就调用不到了,所以子类会报错;
解决方法:
1)在父类中重新写一个无参数的构造函数;
2)在子类中显示的调用父类的构造函数,使用关键字:base()
2.继承的特性
1)继承的单根性,一个子类只能有一个父类;
2)继承的传递性,祖爷爷-爷爷-爸爸-儿子,祖忠的迟早是儿子的;
3.通过查看类图可以清晰明了看到各个类的关系;
3.object是所有类的父类;

四、.Net类库
1.Path类,位于System.IO命名空间下;
<1>Path.GetFileName:获取文件名字;
<2>Path.GetFileNameWithoutExtension:获取不带扩展名的文件名字;
<3>Path.GetDirectoryName:获取文件所在的目录全路径;
<4>Path.GetExtension:获取文件扩展名;
<5>Path.GetFullPath:获取文件全路径(包含文件名);

2.File类,位于System.IO命名空间下;
<1>File.Create:在指定目录下创建一个文件;
<2>File.Delete:删除一个指定文件;
<3>File.Copy:复制一个文件到指定目录下;
<4>File.Move:移动一个文件到指定目录下;
<5>File.Exists:判断一个文件是否存在;

<6>File.ReadAllBytes:以字节形式读取一个文件(可以是文本文件、多媒体文件或其它文件),如:
byte[] buffer = File.ReadAllBytes(@"d:软件c#c#练习题汇总.txt");
//将字节数组中的每一个元素都要按照我们指定的编码格式解码成字符串
//UTF-8,GB2312,GBK,ASCII,Unicode
string s = Encoding.GetEncoding("GB2312").GetString(buffer);

<7>File.WriteAllBytes():以字节形式写入文件,如:
string str = "要插入的内容";
//需要将字符串转换成字节数组,用Encoding.Default.GetBytes()
byte[] buffer = Encoding.Default.GetBytes(str);
File.WriteAllBytes(@"d:软件c#c#1.txt",buffer);

<8>File.ReadAllLines():读取文本文件(只能读取文本文件),返回一个字符串数组,如:
string[] count = File.ReadAllLines(@"d:软件c#c#练习题汇总.txt",Encoding.Default);

<9>File.ReadAllText():读取文本文件(只能读取文本文件),返回一个字符串

<10>File.WriteAllLines():以字符串数组并且替换的形式写入文件,一行一行的写入,如果文件不存在将创建一个文件,如果文件存在将替换原来的文件;

<11>File.WriteAllLines():以字符串并且替换的形式写入文件,全部一起写入,如果文件不存在将创建一个文件,如果文件存在将替换原来的文件;

<12>File.AppendAllText():以字符串并且追加的形式写入文件,全部一起写入,如果文件不存在将创建一个文件,如果文件存在将在后面追加原来的文件;

<13>File.AppendAllLines():以字符串数组并且追加的形式写入文件,全部一起写入,如果文件不存在将创建一个文件,如果文件存在将在后面追加原来的文件;

3.绝对路径和相对路径
绝对路径:通过给定的这个路径直接能在我的电脑中找到这个文件,如:c:11.txt;
相对路径:文件相对于应用程序的路径,如:将要读取的文本文件拷到Debug下即可不用写全路就可读取;
我们在开发中应该去尽量的使用相对路径;

4.集合
<1>ArrayList集合,位于System.Collections命名空间下;
集合的长度可以任意改变,类型随便;
ArrayList list = new ArrayList();实例化一个对象
list.Add()方法:添加一个元素;
list.AddRange():添加一个集合或数组;
list.Clear():清空所有元素;
list.Remove():删除单个元素,写谁就删谁;
list.RemoveAt():根据下标删除元素;
list.RemoveRange(0,2):根据下标去移除一定范围的元素;
list.Sore():升序排序
list.Reverse();反转排序
list.Insert():在指定下标处前插入元素;
list.InsertRange():在指定的位置插入一个集合;
list.Contains():判断是否包含指定元素;
list.Count:获取集合长度;

ArrayList集合的长度问题:
每次集合中实际包含的元素个数(Count)超过了可以包含的元素的个数(Capcity)的时候,集合就会向存存中申请多开辟一倍的空间,来保证集合的长度一直够用;

ArrayList练习题:
1)创建一个集合,里面添加一些数字,求平均值与和,最大值,最小值
2)写一个长度为10的集合,要求在里面随机地存放10个数字(0-9),要求所有的数字不重复
Random:产生随机数的类,Random r = new Random();r.Next(0,10);产生一个0-9的数字

<2>Hashtable集合,键值对集合,位于System.Collections命名空间下;
注意:在键值对集合中,是根据键找值的,键必须是唯一的,而值是可以重复的;
Hashtable ht = new Hashtable();实例化一个对象
ht.Add(object key,object value):key->添加的键,value->添加的值
ht[1]=“新来的”;在指定下标处添加内容;
ht.Clear();移除集合中所有元素;
ht.ContainsKey();判断集合中是否存在某个元素
用foreach编历keys

<3>List泛型集合,在 System.Collections.Generic命名空间下
List<int> list = new List<int>();
所有方法跟ArrayList集合差不多
list.ToArray():将集合转换为当前类型的数组
.ToList():将数组转换为集合

<4>Dictionary集合,在 System.Collections.Generic命名空间下
所有方法跟Hashtable集合差不多,编历时foreach用KeyValuePair<T,T>,如:
Dictionary<int,string> dic =new Dictionary<int, string>();
dic.Add(1,"张三");
dic.Add(2,"李四");
dic.Add(3,"王五");
foreach (KeyValuePair<int,string> kv in dic)
{
Console.WriteLine("{0}---{1}",kv.Key,kv.Value);
}

泛型集合的练习:
1)将一个数组中的奇数放到一个集合中,再将偶数放到另一个集合中,最终将两个集合合并为一个集合,并且奇数显示在左边,偶数显示在右边
2)提示用户输入一个字符串,通过foreach循环将用户输入的字符串赋值给一个字符数组
3)统计Welcome to china中每个字符出现的次数,不考虑大小写


5.装箱与拆箱
装箱:就是将值类型转换为引用类型;
拆箱:将引用类型转换为值类型
看两种类型是否发生了装箱或者拆箱,要看这两种类型是否存在继承关系;
编程中尽量发生装箱或拆箱,否则会使程序变慢;
int n = 10;
object o = n;//装箱,值类型转换为引用类型
int nn = (int)o;//拆箱,引用类型转换为值类型

6.文件流
<1>FileStream,操作字节的,在System.IO命名空间下;
1)用FileStream以流的方式读入文件
FileMode:指定操作系统打开文件的方式,是一个枚举类型;
OpenOrCreate:如果文件存在就打开,否则就创建打开;
FileAccess:指定对文件的读取方式;
Read|Write:读入或写入数据
如:
using (FileStream fs = new FileStream(@"D:1.txt", FileMode.OpenOrCreate, FileAccess.Read))
{
byte[] buffer = new byte[1024 * 1024 * 5];
//返回本次实际读取到的有效字节数
int r = fs.Read(buffer, 0, buffer.Length);
//将字节数组中每一个元素按指定的编码格式解码成字符串
string s = Encoding.Default.GetString(buffer, 0, r);
Console.WriteLine(s);
}
2)用FileStream以流的方式写入文件
using (FileStream fs = new FileStream(@"d:1.txt", FileMode.OpenOrCreate, FileAccess.Write))
{
string str = "看我有木有把你覆盖掉";
//将字符串转换为字节数组
byte[] buffer = Encoding.Default.GetBytes(str);
//对buffer写入数据
fs.Write(buffer, 0, buffer.Length);
}


练习:
多媒体文件复制:
因为FileStream系统不能帮我们释放资源,需要手动的Close和Dispose,但可以写在using(){}里面,就可以帮我们自动的释放资源;
思路:先将要复制的多媒体文件读取出来,然后再写入到指定的位置

static void CopyFile(string source, string target)
{
    using (FileStream fsRead = new FileStream(source, FileMode.OpenOrCreate, FileAccess.Read))
    using (FileStream fsWrite = new FileStream(target, FileMode.OpenOrCreate, FileAccess.Write))
    {
        byte[] buffer = new byte[1024 * 1024 * 5];
        while (true)
        {
            int r = fsRead.Read(buffer, 0, buffer.Lenth);
            if (r == 0)
            {
                break;
            }
            fsWrite.Write(buffer, 0, r);
        }
    }
}

<2>StreamReader,操作字符的,在System.IO命名空间下;
1)读入文件:

StreamReader sr = new StreamReader(路径,编码方式);//如果的乱码情况,在编码方式写入Encoding.Default
sr.ReadLine():读取一行
sr.EndOfStream:判断是否读取到最后一行
using (StreamReader sr = new StreamReader(@"d:软件c#笔记1.txt"))
    {
        while (!sr.EndOfStream)//判断是否读取到最后一行
    {
        Console.WriteLine(sr.ReadLine());//一行一行的读取
    }
}

<3>StreamWriter,操作字符的,在System.IO命名空间下;
1)写入文件
StreamWrite sw = new StreamWrite(路径,true(追加));
sw.Write():写入内容

############################################
五、多态
1.概念:让一个对象能够表现出多种的状态(类型)
2.实现多态有三种手段:虚方法、抽象类、接口;
1)虚方法(父类的方法有实现,能直接找到父类)
原理:调的时候还是调父类方法,但被子类重写了;
实现步骤:
<1>将父类的方法标记为虚方法,使用关键字virtual,这个方法可以被子类重新写一遍;
子类的方法使用关键字override;
练习:
--1.用多态实现:真的鸭子嘎嘎叫,木头鸭子吱吱叫,橡皮鸭子唧唧叫;
--2.经理十一点打卡,员工9点打卡,程序员不打卡;

2)抽象类
当父类中的方法不知道如何去实现的时候,可以考虑将父类写成抽象类,将方法写成抽象方法;
实现步骤:
<1>使用关键字abstract将父类和方法标记为抽象类和抽象方法,方法不能有方法体,让子类去重写实现;
<2>子类方法用关键字override标记;
特点:
--1.抽象成员必须标记为abstract,并且不能有任何实现;
--2.抽象类无法创建对象,因为抽象方法没有实现,无法调用;
--3.如果一个子类继承了一个抽象的父类,那么这个子类必须要实现这个抽象父类当中的所有抽象成员(子类继承抽象类后,必须把父类中的所有抽象成员都重写),除非子类也是一个抽象类,则可以不重写;
--4.在抽象类中可以包含实例成员,并且抽象类的实例成员可以不被子类实现(抽象类中可以写非抽象成员);
--5.抽象成员必须在抽象类中;
--6.抽象类不能被实例化;
--7.抽象类是有构造函数的,虽然不能被实例化;
--8.如果父类的抽象方法中有参数,那么,继承这个抽象父类的子类在重写父类的方法的时候必须传入对应的参数,如果抽象父类的抽象方法中有返回值,那么子类在重写这个抽象方法的时候,也必须要传入返回值;
--9.如果父类中的方法有默认的实现,并且父类需要被实例化,这时可以考虑将父类定义成一个普通类,用虚方法来实现多态;
--10.如果父类中的方法没有默认实现,父类也不需要被实例化,则可以将该类定义为抽象类;

练习:
用抽象类实现:
---1.狗狗会叫,猫猫会叫(提示:用动物类作为父类);
---2.使用多态求矩形(Square)和圆形(Circle)的面积和周长(提示:用形状Shape做父类);
面积:GetArea
周长:GetPerimeter

public abstract class Shape
{
    public abstract double GetArea();
    public abstract double GetPerimeter();
}

public class Circle : Shape
{
    private double _r;
    public double R
    {
        get { return _r; }
        set { _r = value; }
    }
    public Circle(double r)
    {
        this.R = r;
    }
    public override double GetArea()
    {
        return Math.PI * this.R * this.R;
    }
    public override double GetPerimeter()
    {
        return 2 * Math.PI * this.R;
    }
}

public class Square : Shape
{
    private double _height;
    public double Height
    {
        get { return _height; }
        set { _height = value; }
    }
    private double _width;
    public double Width
    {
        get { return _width; }
        set { _width = value; }
    }
    public Square(double height, double width)
    {
        this.Height = height;
        this.Width = width;
    }
    public override double GetArea()
    {
        return this.Height * this.Width;
    }
    public override double GetPerimeter()
    {
        return (this.Height + this.Width) * 2;
    }
}

--3.用多态来实现,将移动硬盘或者U盘或者MP3插入电脑上进行读写数据
用画图描述出来(mspaint)
MobileStorage ms = new MP3();//new MobileDisk();//new UDisk();
Computer cpu = new Computer();
cpu.CpuRead(ms);
cpu.CpuWrite(ms);

//抽象父类
public abstract class MobileStorage
{
    public abstract void Read();
    public abstract void Write();
}

public class MobileDisk : MobileStorage
{
    public override void Read()
    {
        Console.WriteLine("移动硬盘在读取数据");
    }
    public override void Write()
    {
        Console.WriteLine("移动硬盘在写入数据");
    }
}

public class UDisk : MobileStorage
{
    public override void Read()
    {
        Console.WriteLine("U盘在读取数据");
    }
    public override void Write()
    {
        Console.WriteLine("U盘在写入数据");
    }
}

public class MP3 : MobileStorage
{
    public override void Read()
    {
        Console.WriteLine("MP3在读取数据");
    }
    public override void Write()
    {
        Console.WriteLine("MP3在写入数据");
    }
    public void PlayMusic()
    {
        Console.WriteLine("MP3自己可以播放音乐");
    }
}

public class Computer
{
    public void CpuRead(MobileStorage ms)//此处拿到父类对象,可以用属性或构造方法拿到
    {
        ms.Read();
    }
    public void CpuWrite(MobileStorage ms)//此处拿到父类对象
    {
        ms.Write();
    }
}

3)访问修饰符
private:私有的,只能在当前类的内部访问
public:公开的,公共的
protected:受保护的,只能在当前类的内部以及该类的子类中访问,如果出了项目但有继承关系也可访问;
internal:只能在当前程序集(项目内)中访问,在同一个项目中internal和public的权限是一样的;
protected internal:
--1.能够修饰类的访问修饰符只有public和internal
--2.可访问性不一致
子类的访问权限不能高于父类的访问权限

4)设计模式(23种),设计这个项目的一种方式
简单工厂设计模式
--1.父类写成抽象类,写一个抽象方法让子类重写;
--2.写一个方法,返回类型为父类,传一个参数让用户选择,根据此参数作switch多条件选择,如:

public static Smno GetSmno(string num)
{
    Smno nu = null;
    switch (num)
    {
        case "X":
            nu = new X420888A0();
            break;
        case "P":
            nu = new P520888A0();
            break;
    }
    return nu;
}

5)序列化与反序列化
序列化就是将对象转换为二进制;
反序列化就是将二进制转换为对象;
作用:传输数据
--1.序列化
将这个类标记为可以被序列化的

6)部分类
作用:共同组成一个类
使用关键字partial(部分)在class前面修饰
所有字段与方法都可一起调用;

7)密封类
作用:不能被子类继承,但可以继承别的类
使用关键字sealed在class前面修饰
如果不想被别人继承就可以写成密封类


8)接口(几个类中找不到父类,但他们都有共同的行为,共同的能力,可以拿接口实现多态)
使用关键字interface,接口名字以I开头,以able结尾
接口就是一个规范,一种能力
语法:

[public] interface I..able
{
    //成员;
    void Fly();
    string Test();
    public Fly
    {
        get;
        set;
    }
}

<1>接口中所有成员不允许有访问修饰符,默认就是public;
<2>不允许写具有方法体的函数;
<3>接口中不能包含字段(接口中并不存数据,用类来存);
<4>接口中可以写属性,但只能写自动属性;
<5>接口中只能有方法;
<6>只要一个类继承了一个接口,这个类就必须实现这个接口中所有的成员;
<7>为了多态,接口不能被实例化,也就是说接口不能new(不能创建对象)
<8>接口中的成员不能有任何实现,让子类去实现;
<9>接口中只能有方法、属性、索引器、事件,不能有“字段”和构造函数;
<10>接口与接口之间可以继承,并且可以多继承;
<11>接口不能继承一个类,而类可以继承接口;
<12>一个类同时继承一个类和一个接口,那么类名必须写在接口名字前面(一个类可以同时继承一个类并且实现多个接口,如果一个子类同时继承了父类A,并实现了接口IA,那么语法上A必须写在IA的前面);

9)显示实现接口
作用:是为了解决方法重名的问题

IFlyable fly = new Bird();//new Person();
fly.Fly();
Bird br = new Bird();
br.Fly();
Console.ReadKey();
public class Bird : IFlyable
{
    public void Fly()
    {
        Console.WriteLine("鸟在飞");
    }
    void IFlyable.Fly()
    {
        Console.WriteLine("我是接口的飞");
    }
}

public interface IFlyable
{
    void Fly();
}
原文地址:https://www.cnblogs.com/genesis/p/4904619.html