2168:36 亲情奉献全套精品.Net基础视频教程(1-9)
-------------------------------------
https://www.bilibili.com/video/av8915750?p=115
继承
访问修饰符
public:所有皆可访问.
internal:在同一程序集的可访问
protected:保护的,仅在当前类或派生类可访问.
private:私有的,仅在当前类或结构可访问
类的访问修饰符可以为 public或internal. 默认为 internal
类成员的访问修饰符可以为 public, protected internal, protected, internal, private, private protected.
默认为private
子类成员无法继承父类的private成员.它可以继承父类的public, internal, protected成员
问:子类有没有继承父类的构造函数?
答:子类并没有继承父类的构造函数.但是子类会默认的调用父类的无参数的构造函数(因为要)先创建父类对象,让子类可以使用父类中的成员.如果父类还有父类,依此类推先创建父类的父类对象...
所以,如果在父类中重写了1个有参数的构造函数之后,那么(系统默认生成的)无参数的就被干掉了,子类就调用不到了,所以子类会报错.
解决方法:1.在父类中的写一个无参数的构造函数.(不推荐)
解决方法:2.在子类中显示的调用父类的构造函数(改变默认调用),使用关键字:base()
例如下例-子类使用base关键字显式调用父类有参数构造函数,因此父类中无须添加无参数构造函数(附类图):
namespace App010_继承 {
public class Program {
static void Main(string[] args) {
人类 某甲 = new 人类("张三", '男', 66);
某甲.打招呼();
学生 学生甲 = new 学生("张三丰", '男', 18, 10086);
学生甲.打招呼();
教师 教师甲 = new 教师("张果老", '男', 500, 10000);
教师甲.打招呼();
Console.ReadKey();
}
}
public class 人类 {
private string _姓名;
private char _性别;
private int _年龄;
public string 姓名 { get => _姓名; set => _姓名 = value; }
public char 性别 { get => _性别; set => _性别 = value; }
public int 年龄 { get => _年龄; set => _年龄 = value; }
//public 人类() {//必须有无参数构造函数,因为该类的子类new时会调用父类(即本类)的无参数构造函数
//除非-->该类的子类使用关键字base显式的调用父类(即本类)有参数构造函数
//}
public 人类(string 姓名, char 性别, int 年龄) {
this.姓名 = 姓名;
this.性别 = 性别;
this.年龄 = 年龄;
}
public void 打招呼() {
Console.WriteLine("你好,我叫{0},性别{1},今年{2}岁,我属于[{3}]类.很高兴认识你!",
姓名, 性别, 年龄, this.GetType());
}
}
public class 学生 : 人类 {
private int _学号;
public int 学号 { get => _学号; set => _学号 = value; }
//使用base显式调用父类有参数构造函数,则不需要父类的无参数构造函数
public 学生(string 姓名, char 性别, int 年龄, int 学号) : base(姓名, 性别, 年龄) {
this.学号 = 学号;
}
public new void 打招呼() {//使用new关键字隐藏基类同名方法
Console.WriteLine("你好,我叫{0},性别{1},今年{2}岁,我的学号{3},我属于[{4}]类.很高兴认识你!",
姓名, 性别, 年龄, 学号, this.GetType());
}
}
public class 教师 : 人类 {
private int _薪水;
public int 薪水 { get => _薪水; set => _薪水 = value; }
//使用base显式调用父类有参数构造函数,则不需要父类的无参数构造函数
public 教师(string 姓名, char 性别, int 年龄, int 薪水) : base(姓名, 性别, 年龄) {
this.薪水 = 薪水;
}
public new void 打招呼() {//使用new关键字隐藏基类同名方法
Console.WriteLine("你好,我叫{0},性别{1},今年{2}岁,我的薪水每月{3}元.我属于[{4}]类.很高兴认识你!",
姓名, 性别, 年龄, 薪水, this.GetType());
}
}
}
输出结果为:
你好,我叫张三,性别男,今年66岁,我属于[App010_继承.人类]类.很高兴认识你!
你好,我叫张三丰,性别男,今年18岁,我的学号10086,我属于[App010_继承.学生]类.很高兴认识你!
你好,我叫张果老,性别男,今年500岁,我的薪水每月10000元.我属于[App010_继承.教师]类.很高兴认识你!
new关键字
在子类中使用new关键字隐藏从父类继承的同名成员.
以上例为例,学生.打招呼()方法前加关键字new隐藏父类同名方法"人类.打招呼()":
public void 打招呼() {
Console.WriteLine("你好,我叫{0},性别{1},今年{2}岁,我属于[{3}]类.很高兴认识你!",
姓名, 性别, 年龄, this.GetType());
}
上面是父类-人类的同名方法,下面是子类-学生类的同名方法:
public new void 打招呼() {//使用new关键字隐藏基类同名方法
Console.WriteLine("你好,我叫{0},性别{1},今年{2}岁,我的学号{3},我属于[{4}]类.很高兴认识你!",
姓名, 性别, 年龄, 学号, this.GetType());
}
里氏转换
看下例:
再简单举例如下:
类型转换-使用is,as关键字
转换类型前使用is判断:
使用as转换类型:(转换成功返回该对象,转换失败则返回null)
Person p=new Student();
Student s= p as Student;//转换成功则返回该对象,转换失败返回null
Protected-仅当前类和派生类允许访问
ArrayList集合-元素类型不限
使用ArrayList集合可不拘泥于元素类型,即元素什么类型都可以.
Add方法:添加单个元素
现在我们往ArrayList里添加各种类型的元素,既可以 添加简单类型,也可以添加对象和数组,如下:
上面的代码不符合我们心中的预期(打印输出list集合各元素的内容).
将for循环代码修改如下,判断当前list元素是对象还是简单类型,根据类型执行相对应的处理代码.
现在可以正确的显示list集合各元素的内容(值)了:
代码:
namespace App011_ArrayList {
class Program {
static void Main(string[] args) {
ArrayList list = new ArrayList();
//数组:类型单一,长度固定不变
//ArrayList集合:类型随便 长度可以改变
list.Add(1);
list.Add(3.14);
list.Add(true);
list.Add("甲乙丙丁");
list.Add('男');
list.Add(5000m);
list.Add(new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
list.Add(new Person());
for (int i = 0; i < list.Count; i++) {
if (list[i] is Person) {//如果元素是Person对象
((Person)list[i]).SayHi();
Console.WriteLine(" <==类型 " + list[i].GetType());
}else if(list[i] is int[]){ //如果元素是int数组
for (int j = 0; j < ((int[])list[i]).Length; j++) {
Console.WriteLine(((int[])list[i])[j]);
}
}else{
Console.WriteLine(list[i] + " <==类型 " + list[i].GetType());
}
}
Console.ReadKey();
}
}
class Person{
public void SayHi() {
Console.Write("我是Person类的对象,我和你SayHi!.");
}
}
}
AddRange()方法-添加集合
使用AddRange()方法可以添加集合
示例如下:
ArrayList其他常用方法
ArrayList集合长度
练习1:
练习2:
HashTable-键值对集合
简单使用Hashtable的例子,使用foreach循环遍历输出-注意只能使用foreach循环:
练习1-简体转换繁体
如下所示1个简体字数组,1个繁体字数组,数量相同,简繁字在两个数组中一一对应.
用户输入一句话,是汉字则转换为繁体字.如果繁体字数组中没有包含对应繁体字,则显示原字:
运行效果如下:
var关键字
从 Visual C# 3.0 开始,在方法范围中声明的变量可以具有隐式类型 var。 隐式类型的本地变量是强类型变量(就好像您已经声明该类型一样),但由编译器确定类型。 下面的两个 i 声明在功能上是等效的:
var i = 10; // implicitly typed
int i = 10; //explicitly typed
var的缺点-必须赋初值,不能直接声明而不赋值:
for和foreach循环对比
泛型集合
泛型集合List和ArrayList差不多,方法也大致一样.只不过List泛型集合定义时指定了类型,使用它的时候则只能添加该类型的元素.
我们在读取List集合元素的时候,不需要像ArrayList读取元素时候强行转换类型那么麻烦了.
下面是泛型集合的简单示例:
装箱和拆箱
装箱:将值类型转为引用类型
拆箱:将引用类型转为值类型
*两种类型是否发生装箱拆箱,要看它们是否存在继承关系.如果有继承关系,那么有可能发生拆装箱.
拆箱装箱简单示例:
Dictionary字典集合
Dictionary简单示例:
文件
Path类-常用方法
File类-注意不能操作大文件!
File类常见方法:
File.WriteAllBytes(String, Byte[]) 方法-创建一个新文件,在其中写入指定的字节数组,然后关闭该文件。 如果目标文件已存在,则覆盖该文件。
//写入文件-二进制方式
string str = "关关雎鸠 在河之洲";
byte[] buffer = Encoding.UTF8.GetBytes(str);
File.WriteAllBytes(@"C:\Users\heros\Desktop\1.txt", buffer);
File.ReadAllBytes(String) 方法-打开一个二进制文件,将文件的内容读入一个字节数组,然后关闭该文件.
//读取文件-二进制方式
byte[] buffer = File.ReadAllBytes(@"C:\Users\heros\Desktop\1.txt");
string str = Encoding.GetEncoding("UTF-8").GetString(buffer);
Console.WriteLine(str);
编码历史和乱码原因
再示例一下ReadAllBytes和WriteAllBytes方法:
在中文windows平台上,使用Default编码(默认应该是GB2312/GBK/GB18030)保存文件,读取的时候则只能中文操作系统(或显式的设置正确的编码)才能正确显示.
ReadAllLines方法-把文本文件读入字符串数组,每个元素是每行字符串:
ReadAllText方法-把整个文本文件读入1个字符串中:
WriteAllLines方法-把字符串数组覆盖写入1个文本文件:
WriteAllText方法-把字符串覆盖写入文本文件:
文件添加方法
AppendAllText方法-给文件添加字符串
AppendAllLines方法-给文件添加字符串数组
文件流
File类是一次性把文件读入内存,对大文件来说是个严重负荷.所以我们使用文件流.
文件流是一部分一部分操作文件的.
FileStream流:操作字节.
StreamReader和StreamWriter:操作字符(文本文件).
FileStream-操作字节
以下为FileStream读取数据简单示例:
以下是FileStream写入数据的简单示例:
以下是使用FileStream复制文件的简单示例:
namespace App016_文件流 {
class Program {
static void Main(string[] args) {
#region FileStream读取数据简单示例
FileStream-用来操作字节的
创建FileStream对象
//FileStream fsRead = new FileStream(@"C:\Users\heros\Desktop\1.txt",
// FileMode.OpenOrCreate,FileAccess.Read);
byte[] buffer = new byte[fsRead.Length];//设置缓冲字节为文件实际长度
//byte[] buffer = new byte[1024*1024*5];//设置缓冲字节为5MB
Read方法()的3个参数:缓冲字节数组,起始读取索引(*注意*第1个字节的索引是0不是1),读取字节数(长度)
返回值:返回实际读取的长度.例如本例一次读取5MB字节,如果读取文件只有1KB(不够5MB),则返回1KB.
//int r = fsRead.Read(buffer, 0,buffer.Length);//读取缓冲字节数组从0号索引开始的所有字节
//string str = Encoding.UTF8.GetString(buffer,0,r);//从头(0号索引起)解码r长度的字节数组
//fsRead.Close();//关闭文件流
//fsRead.Dispose();//释放内存资源
//Console.WriteLine("本文件字节长度=" + r);
//Console.WriteLine("本文件字符长度={0};内容=\n{1}",str.Length,str);
#endregion
#region FileStream写入数据简单示例
//using ( FileStream fsWrite = new FileStream(@"C:\Users\heros\Desktop\2.txt",
// FileMode.OpenOrCreate,FileAccess.Write))
//{
// string str = "南山经之首曰鹊山。其首曰招瑶之山,临于西海之上。";
// byte[] buffer = Encoding.UTF8.GetBytes(str);
// fsWrite.Write(buffer,0,buffer.Length);
// //这种方式写入的字符串会从头覆盖原文件(因为文件指针默认从头开始).
//}
#endregion
#region FileStream的文件Copy简单示例
string source = @"C:\Users\heros\Desktop\1.flv";
string target = @"C:\Users\heros\Desktop\new1.flv";
CopyFile(source,target);
Console.WriteLine("复制完成.");
#endregion
Console.ReadKey();
}
public static void CopyFile(string source,string target) {
using (FileStream fsRead=new FileStream(source,FileMode.Open,FileAccess.Read)) {
using (FileStream fsWrite=new FileStream(target,FileMode.OpenOrCreate,FileAccess.Write)) {
byte[] buffer = new byte[1024*1024*5];
while (true) {
//r为返回值:返回本次读取的实际字节数
int r = fsRead.Read(buffer, 0, buffer.Length);
//如果r返回0,也就意味什么都没有读到,文件已经读完了
if (r==0) {
break;//跳出while循环
}
fsWrite.Write(buffer,0,r);//写入实际读取的长度
//(长度不能为buffer.length,因为最后1次读取长度不足buffer.length)
}
}
}
}
}
}
StreamReader和StreamWriter:操作字符(文本文件)
StreamReader和StreamWriter简单示例:
#region StreamReader和StreamWriter简单示例
//使用StreamReader读取文件
using (StreamReader sr=new StreamReader(@"C:\Users\heros\Desktop\1.txt",
Encoding.UTF8))
{
while (!sr.EndOfStream) {//使用while循环读取所有行
Console.WriteLine(sr.ReadLine());
}
//ReadToEnd方法从文件当前位置读取到结尾所有部分
//Console.WriteLine(sr.ReadToEnd());
}
//使用StreamWriter写入文件,第二个参数bool设置是否追加到文件。true追加,false或省略则覆盖
using (StreamWriter sw=new StreamWriter(@"C:\Users\heros\Desktop\2.txt",
true))
{
sw.Write("StreamWriter写入文件测试~~");
}
#endregion