admin 管理员组

文章数量: 887021

(12)C#传智:File类,泛型,字典,FileStream,StreamReader,多态

内容有点多,重点:泛型、字典,流与多态。

    
    继续深入学习内容:List、Dictionary、using语句、FileStream
    
一、File类的继续学心

    File.ReadAllLines(string path,Encoding,encoding)指定编码读取返回行字串数组
    File.WriteAllText(string path,Encoding encoding)
    File.AppendAllText(string path,string contents) 追加文本
    File.ReadAllTexT(string path,Encoding encoding) 指定编码返回文本内所有内容
    File.WriteAllLines(string path,string[] contents) 将字串数组按行写入
    File.AppendAllLines(string path,string[] contents) 追加多行
    

    string p = @"E:\1.txt";string[] s = File.ReadAllLines(p, Encoding.UTF8);foreach (string item in s){ Console.WriteLine(item); }string s1 = File.ReadAllText(p, Encoding.UTF8);Console.WriteLine(s1);


    
    注意:1)若操作文本文件用ReadAllText或ReadAllLines;
            若操作音频等二进制文件,用ReadAllbytes。
          2)若文本文件需精准定到行字串用ReadAllLines,反之用ReadAllText.
    
    由于File类基本上是一次性读出或写入文件,易造成内存溢出(内部byte[]大小
    固定了),所以它适用于读写小的文件。大的文件应使用FileStream。
    
    


二、绝对路径与相对路径

    绝对路径:通过给定的路径,直接便可在电脑中找到该文件。
    相对路径:文件相对于应用程序或目录的路径。
    
    程序中不带路径时,直接用文件名时,用的是相对路径,即本程序当前目录。
    
    string str=File.ReadAllText(@"1.txt");//虽未指定,但与程序本身同目录
    
    相对路径更具优势,应尽量用相对路径。
    


    
三、List泛型集合

    由于ArrayList加入元素类型任意,在处理时非常不方便。
    泛型就是利用ArrayList的增加元素方便,容量自动扩展优点,但同时规定了
    加入元素的类型,以方便输出时的处理。
    
    泛型:Generic通用的,广泛的,一般的.即适用一切,专业上需指定一个特殊
    的类型。所以定义后面用<T>来指明这个广泛类型中的一个特殊的类型(Type):
    List<T>  T-Type类型   使用时需导入 using System.Collections.Generic;
    
    创建(构造):  List<int> list=new List<int>();//将List<int>整体当作类型
    
    方法:List.Add(T item)   单个元素,声明时限定了T
          List.Add(IEnumerable<T> collection)   添加集合
    
    基本上学会了ArrayList,就可以在List同样使用。
          

        List<int> lst = new List<int>() { 1, 2 };lst.Add(4); lst.AddRange(new int[] { 5, 6 });lst.AddRange(lst);//1,2,4,5,6,1,2,4,5,6Console.WriteLine(string.Join(",", lst.ToArray()));


    
    数组<-->List  可以相互转换。注意,类型须一致.
    ToArray()可以将List转换成一个数组,返回的类型与泛型规定的类型一致.
    ToList() 将数组转为List.
    

        int[] n = { 1, 2, 3, 4, 5, 6, 7, 8 };List<int> lst = n.ToList();Console.WriteLine(lst[2]);//3int[] m = lst.ToArray();Console.WriteLine(m[7]);//8


    
    为什么ArrayList与HashTable用得比较少,基本不用呢?
    
    

    
四、装箱与拆箱

    装箱:把值类型转换成引用类型。
    拆箱:将引用类型转换成值类型
    装箱与拆箱不停进行类型转换,所以影响速度,应尽量避免装箱或拆箱操作。
    
    注意:是否装箱或拆箱,两者没有继承关系,可能没有发生装箱或拆箱;
          若两者没有继承判断,那一定没有装箱或拆箱。
    
    以I开头的变量,是接口类型。接口类型也是引用类型
    

    int n = 0;object o = (object)n;//装箱int n1 = (int)o;//拆箱IComparable ic = n;//装箱string s = "123";int n2 = Convert.ToInt32(s);//没有拆箱,无继承


    //--------------------------------

    ArrayList alst = new ArrayList();Stopwatch sw = Stopwatch.StartNew();for (int i = 0; i < 1000000; i++){ alst.Add(i); }sw.Stop();Console.WriteLine(sw.Elapsed);//00:00:00.0942953List<int> lst = new List<int>();sw.Restart();for (int i = 0; i < 1000000; i++){ lst.Add(i); }sw.Stop();Console.WriteLine(sw.Elapsed);//00:00:00.0234275int[] n3 = new int[1000000];sw.Restart();for (int i = 0; i < 1000000; i++){ n3[i] = i; }sw.Stop();Console.WriteLine(sw.Elapsed);//00:00:00.0152354


    
    
五、字典Dictionary(对应前面Hashtable)

    类似List,固定了Hashtable中key与value的特殊类型.
    声明:Dictionary<Tkey,Tvalue> 
    
    在循环取值时,此时可以用键值对: KeyValuePair<Tkey,Tvalue>
       分别用点号取得对应的key与value,如:kv.key,kv.value
       

    Dictionary<int, string> dic = new Dictionary<int, string>();dic.Add(1, "张三");dic.Add(2, "李四");dic[3] = "王五";//dic.Add(1,"郑六";//错误key重复。用上句或ContainsKey判断foreach (string item in dic.Values){ Console.WriteLine(item); }foreach (KeyValuePair<int, string> kv in dic){ Console.WriteLine("{0}:{1}", kv.Key, kv.Value); }


    
    实例1:将一个数组中的奇数放到一个集合中,再将偶数放到另一个集合中;
          最终将两集合合并为一个集合,并且奇数显示在左边偶数显示在右边。
          

    int[] n = { 1, 2, 3, 4, 5, 22, 23, 45, 23 };List<int> lst1 = new List<int>();//奇List<int> lst2 = new List<int>();//偶foreach (int item in n){if (item % 2 == 0) lst2.Add(item);else lst1.Add(item);}lst1.AddRange(lst2);foreach (int item in lst1){ Console.Write(item + ","); }//1,3,5,23,45,23,2,4,22,


    
    实例2:提示用户输入一个字符串,通过foreach循环将用户输入的字符串赋值
           给一个字符数组
    

    Console.WriteLine("请输入一字符串:");string s = Console.ReadLine();while (s == null){Console.WriteLine("字串不能为空,请重新输入");s = Console.ReadLine();}char[] c = new char[s.Length];int i = 0;foreach (char item in s){c[i] = item;i++;}Console.WriteLine(c);//取首字符,一样显示全字串


    
    
    实例3:统计Welcome to china中每个字符出现的次数,不考虑大小写。
    

    string s = "Welcome to china";Dictionary<char, int> dic = new Dictionary<char, int>();char c1;foreach (char c in s){if (c == ' ') continue;c1 = char.ToUpper(c);if (dic.ContainsKey(c1)) dic[char.ToUpper(c)] += 1;else dic[c1] = 1;}foreach (KeyValuePair<char, int> kv in dic){ Console.WriteLine("字母{0}:{1}", kv.Key, kv.Value); }


    
    
    ArraryList与List<T>的区别:
    最主要的:泛型效率较高,操作不是装箱拆箱,也不涉及Object类型转换。
              ArrayList是Object类型,面对不同的类型,涉及到装箱与拆箱,效率低。
    System.Collections和System.Collections.Generic提供了很多列表、集合和数组。
    例如:List<T>、数组(int[],string[]...)、Dictionary<T,T>等,但这些列表、集合
    和数组都不是安全的,不能接受并发请求。
    1)数组。
        优点:连续存储,索引速度快,赋值修改简单方便;
        缺点:a.定义指定长度易浪费内存,或索引超限。
              b.插入、删除元素效率低且麻烦。
        不清楚数组长度时很尴尬。故C#提供ArrayList来解决此类问题。
    2)ArrayList
        优点:长度动态增加;实现Ilist接口,方便添加、插入、删除等。
        缺点:元素是object,处理操作可能出现类型不匹配,是非类型安全对象;
              处理对象时,要进行类型转换,涉及装箱拆箱,损耗性能。
        为了提高ArrayList的效率,C#又引出了泛型。
    3)List<T>
        具有ArrayList的大部分方法和优点。
        同时:List是强类型,编译器会验证类型安全,避免了类型的不安全,
              以及数组强制转换导致装箱拆箱损耗性能。
    

    
六、FileStream文件流

    将水缸A的满水,倒入到水缸B的空缸中,有两种方法:
    1)扛起整个水缸A,然后象霸王一样倒入到水缸B:痛快干脆,但得力气大。
    2)牵一水管由水缸A导流到水缸B中:   温柔便巧,不需要费大力。
    
    File类读写文件,犹如方法1,全部搬运倾泄,耗费内存。只适合小文件操作。
    FileStream类读写文件,犹如方法2,小量多次便捷高效,适宜大小文件操作。
    
    下面都适合操作大文件(几百k的文件直接用File类)
    1)FileStream :操作字节,适合任意类型的文件(因为都是字节).
    2)StreamReader和StreamWriter:操作字符,所以适合文本文件。
    
    所谓大牛,会的类肯定多,熟悉掌握多种类,也就是大牛了。
    
    FileStream类 : using System.IO;
    1、用FileStream读取文件.
    

    //初始化对象.指明路径、打开方式,访问权限FileStream fsread = new FileStream(@"E:\1.txt", FileMode.OpenOrCreate, FileAccess.Read);//创建缓冲字节数组,用来指读取。指定最大缓冲5Mbyte[] buffer = new byte[1024 * 1024 * 5];//read指定开始位置(0),及长度(b.length),内容返回到缓冲数组.//尽管指定是5M,但可能实际读取的只有2M,则下句的n就是2M.int n = fsread.Read(buffer, 0, buffer.Length);//n是实际返回的字节数//解码:按编码格式进行解码。需指明缓冲数组的起始及长度,否则解码所有含空的数组string s = Encoding.UTF8.GetString(buffer, 0, n);Console.WriteLine(s);//关闭文件流fsread.Close();//释放流所占用的资源fsread.Dispose(); 

   
    
    2、用FileStream写入文件
    1)using(声明变量)
      {
         执行语句;
      }

      .NET中,托管的资源都由.NET的垃圾回收机制GC来释放;而一些非托管的资源,则需要
      手动进行释放。.NET提供了主动和被动两种释放非托管资源的方式:
          第一种:IDisposable接口的Dispose方法;
          第二种:类型自己的Finalize方法。
      任何带有非托管资源的类型,都有必要实现IDisposable的Dispose方法,并且在使用完
      这些类型后,需要手动地调用对象的Dispose方法来释放对象中的非托管资源。
      若类型正确地实现了Finalize方法,那么即使不调用Dispose方法,非托管资源也最终会
      被释放,但到那时资源已经被很长时间无畏地占据了。
      
      using语句的作用就是提供了一个高效的调用对象Dispose方法的方式。
      对于任何IDisposable接口的类型,都可以使用using语句;
      而对于那些没有实现IDisposable接口的类型,使用using语句会导致一个编译错误。
      因此,应在using外面套用try-catch。
      
      简单地说,using能有效地释放资源。
      
    2)使用流写入文件
    

    using (FileStream fsw = new FileStream(@"E:\1.txt", FileMode.OpenOrCreate, FileAccess.Write)){string s = "遥远的救世主,浮生六记";byte[] buffer = Encoding.Default.GetBytes(s);fsw.Write(buffer, 0, buffer.Length);//仅覆盖数组数组长度的位置,其它可能乱码.}//无须手动释放,using语句自动释放    


    
    注:出现乱码,是读取与写入的编码不一致,应该注意统一。
    
    3、使用文件流实现多媒体文件的复制。
      将一个大文件复制到另一个地方
    

    string scr = @"E:\八段锦.mp4";string des = @"E:\New.mp4";using (FileStream fsr = new FileStream(scr, FileMode.OpenOrCreate, FileAccess.Read)){using (FileStream fsw = new FileStream(des, FileMode.OpenOrCreate, FileAccess.Write)){byte[] buffer = new byte[1024 * 1024 * 5];//缓冲5Mwhile (true){int n = fsr.Read(buffer, 0, buffer.Length);//预读5Mif (n == 0) break;//读取返回0字节,说明结束fsw.Write(buffer, 0, n);//应写入实际n长度}}}


    
    注意:.Net使用流读写文件时,可以将流视为一组连续的一维数据,包含开头和结尾。
          其中的游标指示了流的当前位置,所以读写都自动从游标处进行继续操作。
    
    
七、StreamReader与StreamWriter

    StreamReader与StreamWriter是操作字符的,与FileStream操作字节是有区别的。
    
    1、StreamReader
    

    StreamReader sr = new StreamReader(@"E:\1.txt", Encoding.Default);while (!sr.EndOfStream) Console.WriteLine(sr.ReadLine());sr.Close();//也可用using来省略这两句sr.Dispose();


    
    2、SteamWriter
    

    //编码方式只能在前面,这样方便后面不再进入编码方式设置StreamWriter sw = new StreamWriter(@"E:\1.txt", true, Encoding.Default);//sw.WriteLine("我是一行.");//后加行结束写入字串,即行写入sw.Write("只是字串写入");sw.Close(); sw.Dispose();


八、多态

    定义:让一个对象能够表现出多种的状态(类型)
    相同的消息给予不同的对象会引发不同的动作。
    
    
    实现多态的三种手段:1、虚方法;2抽象类;3、接口。
    
    1、问题抛出:
        有一父类Person,下面三子类China,Japan,Korea.

        public class Person{private string _name;public string Name{ get { return _name; } set { _name = value; } }public Person(string name){ this._name = name; }public void SayHello(){ Console.WriteLine("我是人类"); }}public class China : Person{public China(string name) : base(name){ }public void SayHello(){ Console.WriteLine("中国人:{0}", this.Name); }}public class Japan : Person{public Japan(string name) : base(name){ }public void SayHello(){ Console.WriteLine("日本人:{0}", this.Name); }}public class Korea : Person{public Korea(string name) : base(name){ }public void SayHello(){ Console.WriteLine("韩国人:{0}", this.Name); }}


    
        使用父类数组调用子类同名方法,只能通过判断。
        

        China c1 = new China("张三"), c2 = new China("李四");Japan j1 = new Japan("井边"), j2 = new Japan("村下");Korea k1 = new Korea("思密达"), k2 = new Korea("朴真");Person[] p = new Person[6] { c1, c2, j1, j2, k1, k2 };for (int i = 0; i < p.Length; i++){ p[i].SayHello(); }//显示六个“我是人类",无法显示对应国家.for (int i = 0; i < p.Length; i++){if (p[i] is China) ((China)p[i]).SayHello();else if (p[i] is Japan) ((Japan)p[i]).SayHello();else ((Korea)p[i]).SayHello();}//必须分别判断类型,才能显示国家


    
        这样调用同名方法显得臃肿。下面用虚方法
    
    2、虚方法
       在父类同名方法前面加Virtual,在子类同名方法前面加Override(重写)
        
        执行顺序,用父类方法调用同名方法时,将根据内装类型去具体调用对应方法。
        
        调用的方法取决于父类里真实装的是什么对象。内部装的是父类,则调用的是父类。
    内部装的子类,则调用的是子类。
    
        虚方法,避免的类别的判断,让一个类型表现出多种类型的状态(多态),由此写出
    通用代码(精减代码,减少冗余),最大取消它们之间的差异性(屏蔽子类间的差异性)。
    后面若需加入新子类,    只须重写一次同名方法即可。
    
    实现步骤:
        1)在父类同名方法前加入Virtual
 

        public class Person{private string _name;public string Name{ get { return _name; } set { _name = value; } }public Person(string name){ this._name = name; }public virtual void SayHello(){ Console.WriteLine("我是人类"); }}


    
        2)在子类同名方法前加入Override,同时再加一个英国人类
 

        public class China : Person{public China(string name) : base(name){ }public override void SayHello(){ Console.WriteLine("中国人:{0}", this.Name); }}public class Japan : Person{public Japan(string name) : base(name){ }public override void SayHello(){ Console.WriteLine("日本人:{0}", this.Name); }}public class Korea : Person{public Korea(string name) : base(name){ }public override void SayHello(){ Console.WriteLine("韩国人:{0}", this.Name); }}public class English : Person{public English(string name) : base(name){ }public override void SayHello(){ Console.WriteLine("英国人:{0}", this.Name); }}


        
        3)调用方法,用父类方法调用,将根据实际装的对象,调用对应方法
 

        China c1 = new China("张三"), c2 = new China("李四");Japan j1 = new Japan("井边"), j2 = new Japan("村下");Korea k1 = new Korea("思密达"), k2 = new Korea("朴真");English e1 = new English("汤姆"), e2 = new English("迈克");Person[] p = new Person[8] { c1, c2, j1, j2, k1, k2, e1, e2 };for (int i = 0; i < p.Length; i++){ p[i].SayHello(); }//不再显示“我是人类"。而显示对应国家.//由此,通过父类调用一种方法,显示不同状态


        
        练习题1:真的鸭子嘎嘎叫 木头鸭子吱叫 橡皮鸭子唧唧叫
        先定义虚方法

        public class ZSDuck{public virtual void Quark(){ Console.WriteLine("真实鸭子嘎嘎叫"); }}public class MTDuck : ZSDuck{public override void Quark(){ Console.WriteLine("木头鸭子吱吱叫"); }}public class XPDuck : ZSDuck{public override void Quark(){ Console.WriteLine("橡皮鸭子唧唧叫"); }}


        在主函数中调用:

        ZSDuck zS = new ZSDuck();MTDuck mT = new MTDuck();XPDuck xP = new XPDuck();ZSDuck[] z = { zS, mT, xP };for (int i = 0; i < z.Length; i++){ z[i].Quark(); }


    
        练习题2:经理十一点打卡 员工9点打卡 程序不打卡
        添加一父类两子类:

        public class Employee{public virtual void ClockIn(){ Console.WriteLine("员工9点打卡"); }}public class Manager : Employee{public override void ClockIn(){ Console.WriteLine("经理11点打卡"); }}public class Programmer : Employee{public override void ClockIn(){ Console.WriteLine("程序员不打卡"); }}


        在主程序中调用:

        Employee em = new Employee();Manager mg = new Manager();Programmer pg = new Programmer();Employee[] ems = { em, mg, pg };foreach (Employee item in ems){ item.ClockIn(); }


    
    3、抽象类
        上面很容易找到父类(添加virtual),定位子类同名重写(override)
        若题为"狗狗会叫,猫咪会叫",dog与cat都不宜作父类,
        应从两种中抽象出一种父类,但叫的动作却不知道具体实现,
        它只有在针对到具体的子类时才能具体的表现出来。
    
        定义:当父类中的方法不知道如何实现时,可以将父类写成抽象类,其方法写成抽象方法.
        因此上面可以写一个“动物”的抽象类,里面包含一个“叫”的抽象方法。
    
        语法:1)抽象类的前面用abstract限定,其内的抽象方法用abstract限定
              2)抽象方法不允许有方法体方法,声明仅以分号结尾,且签名后没有大括号。
              注意:没有花括号(无方法体)与有花括号但无语句(空实现),两者是有区别的。
              3)子类同名方法前加override。
              4)抽象方法是隐式的虚拟方法。在抽象方法中使用static或virtual是错误的。
              
        重要:抽象类与接口是不允许创建对象的。
        
        抽象类的特点:
        1)抽象成员必须标记为abstract,并且不能有任何买现。
        2)抽象成员必须在抽象类中;
        3)抽象类不能被例化(虽然有构造函数);
        4)子类继承抽象类后,必须把父类的所有抽象成员都复写。(除非子类也为抽象类)
        5)抽象成员的访问修饰符不能是private,因为要通过这个桥梁访问各重写的子类。
        6)在抽象类中可以包含实例成员。并且抽象类的实例成员可以不被子类实现。
        7)抽象类是有构造函数的,虽然不能被实例化。
        8)如果父类的抽象方法中有参数,那么,继承这个抽象父类的子类在重写父类方法时候,
           必须传入对应的参数。
           如果抽象父类的抽象方法中的返回值,那么子类在重写这个抽象方法的时候,也必须
           要传入返回值。
           一句话:抽象父类的抽象方法的签名必须与子类重写的方法签名一致。
        如果父类中的方法有默认的实现,并且父类需要被实例化,这时可以考虑将父类定义成
        普通类,可以用虚方法来实现。
        
        如果父类中的方法没有默认实现,父类也不需要被实例化,则可以将该类定义为抽象类。
                
        抽象类可以包括抽象方法,这是普通类所不能的。
        抽象方法只能声明于抽象类中,且不包含任何实现,派生类必须覆盖它们。
        普通类是具体实现抽象方法的类,是有构造函数的,能实例化,不能有抽象方法。
        
        抽象类父类Animal中有抽象方法Bark(无方法体)

        public abstract class Animal{public abstract void Bark();//必须末尾有分号}


        两子类具体实现,重写抽象方法

        public class Cat : Animal{public override void Bark(){ Console.WriteLine("小猫咪咪叫"); }}public class Dog : Animal{public override void Bark(){ Console.WriteLine("小狗旺旺叫"); }}


        主方法中实现

        //Animal a = new aninal();//错误,抽象类无构造函数不能自己初始化Animal a = new Cat();//但可以用子类来赋值a.Bark();//虽然调用的是父类bark,但仍取决内装的对象(cat)Animal a1 = new Dog();a1.Bark();


    
    虚方法与抽象类的区别:
        虚方法自身有实现,自身方法可用;所以可以被子类覆盖,也可以不覆盖。
        抽象类自身无实现,自身方法不可用;所以必须一定被子类覆盖,与接口类似。
    
    抽象类中可以拥有虚方法。因为抽象类可以包含实例成员,虚方法也是实例成员一种。
    一般使用较多的是抽象类。
    
    证明抽象类的存在构造函数

        internal class Program{private static void Main(string[] args){Student s = new Student();//此处下断点,可看到s构造前进入抽象类中构造Console.WriteLine(s.ID);Console.ReadKey();}}public abstract class Person{private Guid _id;public Person()//构造函数{ this._id = Guid.NewGuid(); }public Guid ID{ get { return this._id; } }}public class Student : Person{public Student(){ }}


    
    
    4、多态的练习题
    技巧:a.要实现抽象父类方法的子类时,写完“:子类名”时,在vs2022中按Alt+Shift+F10,
            选择实现抽象类,可以快速实现抽象方法的重写。
          b.对于类中的属性,当输入完毕属性名,如:public double Width时,按下
            Ctrl+R,再按Ctr+E,可以快速形成属性代码。
    1)使用多态求矩形的面积和周长,以及圆形的面积和周长。
        方法相同,形状不同,构造抽象类形状。至于参数,各找各妈自己构造。

        public abstract class Shape{public abstract double GetArea();public abstract double GetPerimeter();}public class Circle : Shape{private double _r;public double R { get => _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 Rectangle : Shape{private double _width;private double _height;public double Width { get => _width; set => _width = value; }public double Height { get => _height; set => _height = value; }public Rectangle(double width, double height){this.Width = width;this.Height = height;}public override double GetArea(){ return this.Width * this.Height; }public override double GetPerimeter(){ return 2 * (this.Width + this.Height); }}


        主程序中:

        Shape sc = new Circle(3);Shape sr = new Rectangle(3, 5);Console.WriteLine("圆{0},{1}", sc.GetPerimeter(), sc.GetArea());Console.WriteLine("矩形{0},{1}", sr.GetPerimeter(), sc.GetArea());


    
    2)用多态实现:将移动硬盘,或者U盘,或者Mp3,插到电脑上进行读写数据。
        移动介质抽象类,子类三个。电脑读取类一个

        public abstract class Shape{public abstract double GetArea();public abstract double GetPerimeter();}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 Mp3 : MobileStorage{public override void Read(){ Console.WriteLine("Mp3在读取..."); }public override void Write(){ Console.WriteLine("Mp3在写入..."); }public void Play(){ Console.WriteLine("MP3在播放音乐"); }}public class Udisk : MobileStorage{public override void Read(){ Console.WriteLine("U盘在读取..."); }public override void Write(){ Console.WriteLine("U盘在写入..."); }}public class Computer{public void CpuRead(MobileStorage ms){ ms.Read(); }public void CpuWrite(MobileStorage ms){ ms.Write(); }}


        主程序中实现:

        MobileDisk md = new MobileDisk();Udisk ud = new Udisk();Mp3 mp = new Mp3();Computer cp = new Computer();cp.CpuRead(md); cp.CpuRead(ud); cp.CpuRead(mp);cp.CpuWrite(md); cp.CpuWrite(ud); cp.CpuWrite(mp);MobileStorage ms1 = new Mp3();Computer cpu = new Computer();cpu.CpuRead(ms1);cpu.CpuWrite(ms1);


    
    

本文标签: (12)C传智File类 泛型 字典 FileStream StreamReader