C#学习笔记 12.01
(学习视频来自bilibili的传智播客赵老师基础教学视频)
FileStream 文件流(最常用)
这个是用来操作字节的(操作字节就是操作最基本单元,所以没限制)
这个东西需要实例化,不像 File 都是静态方法,接下来记录常用的三个玩法
FileStream fs = new FileStream(路径,FileMode.选择对文件的操作,FileAccess.选择对数据的操作) 后面这两个参数都是枚举型的。
byte[ ] 字节数组 = new byte[ 1024*1024*2 ] (这就是一个 2M 的字节数组了)
fs . Read(字节数组,0,字节数组.Length)
这个玩意返回的是一个 int 代表有效字节的个数,因为我们实例化字节数组的时候会给一个大小,而读取的文件未必是这么大,所以用这个来返回一下。当我们解码的时候如果没有这个数字来指导一下解码范围,就会造成多解码了好多东西无效字节的情况。
然后就有个很烦人的事情,有一张视频看不了了,后面专门开一章聊这个最最重要的东西,因为据说这个东西支持大文件,全类型,所以还是很重要的……
在用这个的时候我们要试着手动释放资源了,因为当我们用流的时候,大概是我们将一缸水一勺一勺的搬运到另一缸,那么如果没有去把咱的勺子清空,事情就会变得比较麻烦。。。
本来是要用 Close() 和 Dispose() 这两个方法,但是因为总会忘了写,所以微软提供了一个更方便的方法,就是把 实例化对象 这个过程写在 using 里面
using(实例化) {这里面写操作}
StreamReader 和 StreamWriter
这两个是来操作字符的(这个有点限制)
using(StreamReader sr = new StreamReader(路径))
{
while(!sr.EndOfStream){ string str = sr.ReadLine(); }
}
写的做法也是一样的,这个的好处是处理文本的时候不需要搞一个字节数组转来转去的。
装箱和拆箱
装箱: 将值类型转化成引用类型
拆箱: 将引用类型转化为值类型
装箱和拆箱需要同样的类型,比如 我们把一个 int 存进一个 object(装箱),那么我们拆箱时候应该用 (int)object 这样才能拆箱,一旦用(double)object 就会失败。
另外,如果两个东西不存在继承的关系,则不存在装箱拆箱的过程,因为不存在继承关系的话,二者在内存上是不会存在交集的。 比如我们把字符串 int.Parse(),这就没有装箱拆箱的过程。
因为我们之前的学习的两个集合都支持加入 object ,所以会存在不停地装箱过程,调用的时候又需要不停的拆箱,这就导致运行效率会比较低。
所以我们介绍一个更重要的东西泛型集合
List<>
List<int> l = new List<int>()
这玩意在实例化的时候就把类型写死了,那么只要我们声明时候考虑好,装箱拆箱的过程就可以省掉了。
而如果我们写成这样的话List<Object> 那 ArrayList 就没啥区别了。。。。。。
Dictionary<>
这个是代替之前的键值对集合的,同样是在声明的时候把类型定死
Dictionary<string,string> dic = new Dictionary<string,string>()
虽然老师给的例子是 <int,string> 但是这样就不太好体现这个的特点了,毕竟 List 的索引也就是个 int 嘛。
同理 Dictionary<Object,Object> 也就变成了 Hashtable
键值对集合有个很好玩的东西,KeyValueParil<string,string> 这个玩意可以取出来键值对中的一对,所以可以在 foreach 中使用,直接就取出来了,很洋气。。。。。。
一个经典的例子
统计 Welcome to China 中每个字符出现的次数,不考虑大小写。
using System;
using System.Collections.Generic;
namespace _15_ZiShuTongJi
{
class Program
{
static void Main(string[] args)
{
Dictionary<char, int> dic = new Dictionary<char, int>();
char[] ziMu = ("Welcome to China".ToLower()).ToCharArray();
foreach (char item in ziMu)
{
if (item == ' ')
{
continue;
}
try
{
dic.Add(item, 1);
}
catch (Exception)
{
dic[item]++;
}
}
foreach (KeyValuePair<char,int> item in dic)
{
Console.WriteLine("{0}有{1}个", item.Key, item.Value);
}
Console.ReadKey();
}
}
}
面向对象的重点 多态
让一个对象表现出多种状态(类型),将面向对象展现的淋漓尽致的东西。
多态是为了解决一种问题:在子类中同名写了了某一个父类中的方法,这样在会出现的情况是父类只能调用父类自己的(父类不能调用子类方法),而一旦我们有一个包含了各种子类的父类数组,我们就需要不停的强转,在强转之后才能调用子类相应的方法。
所以。。。比较麻烦。。。值得一提的是,我们在学习的过程中,完成一个事情总是有着不同的方法,比如上面那个例子,如果我们不用键值对这个东西可以么?
答案当然是肯定的,而我们要为之付出的就是多敲一些代码。换言之,我们用键值对的时候用 HashTable 可以么?也可以,但是我们要付出的是不断装箱拆箱的运行效率(装箱拆箱也要写代码。。。),所以不断的学习不同的类,不同的方法,我们在做的就是尽可能的减少代码量,尽可能的加快运行效率,机器对于人的意义本该如此,工程也可以玩成一种艺术。
回到正题,多态就是为了解决这一问题出现的,实现方法有三种:
1.虚方法 2.抽象类 3.接口
虚方法
在父类写函数的时候我们加一个关键字 virtual ,而在子类写这个东西的时候呢,就加一个 override 关键字,这样一来,就告诉了程序,执行到父类的这个方法的时候,去子类找一样名字的执行,从而实现用父类 . 出来的方法,执行的是子类的代码。
这样就增加了程序的可拓展性,也减少了调用方法时候进行子类判断的代码量(实际上运行还是要判断的,毕竟程序要自己判断过才能知道指向哪个子类重写的同名方法)。
举个例子
员工九点打卡,负责人八点打卡,程序员不打卡。
using System;
namespace _16_XuFangFa
{
class Program
{
static void Main(string[] args)
{
ChengXuYuan wang = new ChengXuYuan("小王");
FuZeRen zhang = new FuZeRen("小张");
YuanGong yuan = new YuanGong("小袁");
YuanGong[] yuanGongs = { wang, zhang, yuan };
foreach (var item in yuanGongs)
{
item.DaKa();
}
Console.ReadKey();
}
}
public class YuanGong
{
string _name;
public string Name
{
get
{
return _name;
}
}
public YuanGong(string name)
{
_name = name;
}
public virtual void DaKa()
{
Console.WriteLine("{0}是员工,九点打卡!",this.Name);
}
}
public class FuZeRen : YuanGong
{
public FuZeRen(string name) : base(name) { }
public override void DaKa()
{
Console.WriteLine("{0}是负责人,八点打卡!",this.Name);
}
}
public class ChengXuYuan : YuanGong
{
public ChengXuYuan (string name) : base(name) { }
public override void DaKa()
{
Console.WriteLine("{0}是程序员,不打卡!",this.Name);
}
}
}
抽象类
因为在父类中我们写出来的方法未必有用,前面例子里面因为我们知道应该怎么去实现,而有的时候,在做这个父类的时候可能根本不知道要怎么在子类中实现,所以就有了抽象这个玩意,嗯 。。就很抽象,因为这个类本身可能啥都没有,只是规定了子类要去实现什么方法,当这个类被创造的时候就是为了被继承的,当这个类中的抽象方法被写出来的时候,就是为了在子类中被重写的。
所以在父类中,我们不需要写方法的代码体,也就是这个方法压根不需要跟大括号,直接分号结束就行了。
语法上将,抽象方法用 abstract 修饰,只能写在抽象类中,子类一旦继承这个东西就必须将这个抽象类中的所有抽象方法重写。
和虚方法相比,抽象方法必须被重写,虚方法可以不重写,不重写的话调用时候就直接调用父类的了。
那么我们不妨听听这个例子:
欲练此功,必先自宫。。。。。。
练此功,也就是继承这个类,必先自宫,也就是必须重写自宫这个方法。。。所以这种情况就需要用到咱们的抽象类和抽象方法,如果只是用虚方法的话就达不到这个强制要求在子类中实现某种功能的目的。
在抽象方法定义时,签名就定下了(参数类型和返回值),子类在重写的时候签名也是一样的。。
举个栗子
使用多态实现计算圆形和矩形的面积和周长
求助各位大佬,我遇到了一个问题,所有的子类都需要有面积,周长,这两个属性,而且这两个属性都应该是只读的。
但是一旦我在父类里面写成只读属性,那么在子类就不能改变面积和周长了,这就意味着我不能通过计算进行赋值,这种情况要怎么解决?
一句话描述下我面对的问题:
有一个所有子类都有的只读属性,但是计算这个只读属性值的方法在每个子类中不同,那么这个只读属性怎么在父类中声明么?
扒拉扒拉百度,找到了一个可以实现这个要求的东西,protected 这个关键字修饰的字段,在其子类中可以 this. 出来,但是不是其子类的地方就整不出来了,所以就实现了父类中声明,子类中赋值,外部只读这样一个操作。。。
下面采用分别在子类中声明的做法,感觉这样应该是不对的,因为麻烦。。
using System;
using System.Runtime.CompilerServices;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
Yuan y = new Yuan(2);
y.QiuMianJi();
y.QiuZhouChang();
JuXing jx = new JuXing(2, 4);
jx.QiuMianJi();
jx.QiuZhouChang();
Console.WriteLine("半径为{0}的圆,面积是{1},周长是{2}。",y.BanJing,y.MianJi,y.ZhouChang);
Console.WriteLine("长{0}宽{1}的矩形,面积是{2},周长是{3}。", jx.Chang, jx.Kuan, jx.MianJi, jx.ZhouChang);
Console.ReadKey();
}
}
public abstract class TuXing
{
public abstract void QiuMianJi();
public abstract void QiuZhouChang();
}
public class Yuan : TuXing
{
double _mianJi;
public double MianJi
{
get { return _mianJi; }
}
double _zhouChang;
public double ZhouChang
{
get { return _zhouChang; }
}
double _banJing;
public double BanJing
{
get { return _banJing; }
set { _banJing = value; }
}
public Yuan(double banJing)
{
this._banJing = banJing;
}
public override void QiuMianJi()
{
this._mianJi = Math.Pow(_banJing, 2) * Math.PI;
}
public override void QiuZhouChang()
{
this._zhouChang = Math.PI * 2 * _banJing;
}
}
public class JuXing : TuXing
{
double _mianJi, _zhouChang, _chang, _kuan;
public double MianJi { get { return _mianJi; } }
public double ZhouChang { get { return _zhouChang; } }
public double Chang { get { return _chang; } set { _chang = value; } }
public double Kuan { get { return _kuan; } set { _kuan = value; } }
public JuXing(double chang, double kuan)
{
this._chang = chang;
this._kuan = kuan;
}
public override void QiuMianJi()
{
this._mianJi = this._chang * this._kuan;
}
public override void QiuZhouChang()
{
this._zhouChang = (this._chang + this._kuan) * 2;
}
}
}