我的C#学习笔记(四)类和接口

作者 Zhendong Ho 日期 2018-09-12
C#
我的C#学习笔记(四)类和接口

访问修饰符

public:公开的公共的。

private:私有的,只能在当前类的内部访问。

protected:受保护的,只能在当前类的内部以及该类的子类中访问。

internal:只能在当前程序集(项目)中访问。在同一个项目中,internal和public的权限是一样的。

protected internal:protected的权限加上internal的权限。

注意:在同一个项目中,internal的权限比protected大。跨项目时,protected权限比internal大(继承的传递性)。

类的访问修饰符

能够修饰类的访问修饰符只有两个,public和internal,默认是internal。

可访问性不一致

子类的访问权限不能高于父类的访问权限,会暴露父类的成员。

简单工厂设计模式

设计模式

概念:设计这个项目的一种方式

情景

有一个工厂专门生产笔记本。现在有Lenovo,Acer,Dell,IBM品牌的笔记本。但是现在工厂不知道用户需要哪种品牌的笔记本,无法正确生产。例如用户需要Lenovo,但是工厂生产了Acer,用户不要,则无法达到目的。工厂生产哪种品牌的笔记本根据用户的需求决定。

思路

返回一个通用的父类给用户。虽然返回的是父类,但是里面装的是对应的子类的对象。父类屏蔽了各个对象之间的差异。

实现

抽象类实现多态

public abstract class NoteBook
{
public abstract void SayHello();
}

public class Lenovo : NoteBook
{
public override void SayHello()
{
Console.WriteLine("我是联想笔记本");
}
}

public class Acer : NoteBook
{
public override void SayHello()
{
Console.WriteLine("我是宏基笔记本");
}
}

public class Dell : NoteBook
{
public override void SayHello()
{
Console.WriteLine("我是戴尔笔记本");
}
}

public class IBM : NoteBook
{
public override void SayHello()
{
Console.WriteLine("我是IBM笔记本");
}
}

简单工厂的核心

/// <summary>
/// 简单工厂的核心 根据用户的输入创建对象赋值给父类
/// </summary>
/// <param name="brand"></param>
/// <returns></returns>
public static NoteBook GetNoteBook(string brand)
{
NoteBook nb = null;
switch (brand)
{
case "Lenovo":
nb = new Lenovo();
break;
case "Acer":
nb = new Acer();
break;
case "Dell":
nb = new Dell();
break;
case "IBM":
nb = new IBM();
break;
}
return nb;
}

Main方法

static void Main(string[] args)
{
Console.WriteLine("请输入你想要的笔记本品牌:");
string brand = Console.ReadLine();
NoteBook nb = GetNoteBook(brand);
nb.SayHello();
Console.ReadKey();
}

值传递和引用传递

值类型存储在栈中,引用类型存储在堆中。

值传递

  • 值类型在复制的时候,传递的是这个值的本身。
int n1 = 10;
int n2 = n1;
n2 = 20;
Console.WriteLine(n1);//10
Console.WriteLine(n2);//20

引用传递

  • 引用类型在复制的时候,传递的是对这个对象的引用。
Person p1 = new Person();
p1.Name = "张三";
Person p2 = p1;
p2.Name = "李四";
Console.WriteLine(p1.Name);//李四

p1和p2在栈中的地址不一样,在堆中的地址是一样的(指向同一块内存空间)

字符串的复制

string s1 = "张三";
string s2 = s1;
s2 = "李四";
Console.WriteLine(s1);//张三
Console.WriteLine(s2);//李四

对于字符串而言,虽然string是引用类型,但是由于字符串的不可变性,每次重新赋值都会开辟新的空间。所以s1和s2是属于两个不同的空间,改变s1的值并不会影响s2。

ref参数

将值传递改变为引用传递

int number = 10;
Method1(ref number);
Console.WriteLine(number);//20

Method1方法

public static void Method1(ref int n)
{
n += 10;
}

ref的原理是,使用ref参数传递引用,形参和实参在栈中的地址是一样的(地址相同),即number的地址和n的地址是一样的。所以,改变n的值,number的值也跟着改变。

注意

  1. 不用ref的时候,传递的是变量的值本身(number=10)。
  2. 使用ref的时候,传递的是变量的引用(number在栈中的地址)。

new关键字的作用

  1. 在堆中开辟存储空间
  2. 在开辟的空间中创建对象
  3. 调用对象的构造函数

序列化和反序列化

概念

序列化:就是将对象转换为二进制

反序列化:就是将二进制转换为对象

作用:传输数据

序列化

  1. 用[Serializable]标识类为可序列化
[Serializeable]
public class Person
{
public string Name { get; set; }
public char Gender { get; set; }
public int Age { get; set }
}
  1. 使用BinaryFomatter序列化
//要将p这个对象传输给对方
Person p = new Person();
p.Name = "张三";
p.Age = 19;
p.Gender = '男';
using (FileStream fsWirte = new FileStream(@"d:\a.txt", FileMode.OpenOrCreate, FileAccess.Write)
{
//开始序列化对象
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(fsWrite, p);
}
Console.WriteLine("序列化成功");

反序列化

//接收对方发送过来的二进制 反序列化成对象
Person p;
using (FileStream fsRead = new FileStream(@"d:\a.txt"), FileMode.OpenOrCreate, FileAccess.Read)
{
BinaryFormatter bf = new BinaryFormatter();
p = (Person)bf.DeSerialize(fsRead);
}
Console.WriteLine(p.Name);//张三
Console.WriteLine(p.Age);//19
Console.WriteLine(p.Gender);//男

部分类

partial关键字声明的两个Person类

  • 部分类的本质就是同一个类
public partial class Person
{
private string _name;
}

public partial class Person
{
public void Test()
{
//_name;//
}
}

密封类

sealed关键字标记密封类

  • 密封类不能够被其他类继承,但是可以继承于其他类
public sealed class Person : Test
{
//密封类可以继承于其他类
}

public class Test //: Person
{
//密封类不能够被其他类继承
}

重写ToString()方法

  1. 如果一个对象使用点ToString()方法,打印的是该类的命名空间。
  2. ToString()是Object类的虚方法(因为能调用object.ToString()方法),可以通过在子类中用override重写该方法。
public class Person
{
public override string ToString()
{
return "Hello world";
}
}

Main方法

static void Main(string args[])
{
Person p = new Person();
Console.WriteLine(p.ToString());//Hello world
Console.ReadKey();
}

接口

接口就是一个规范,能力。只要一个类继承了一个接口,这个类就必须实现这个接口中所有的成员。

接口的语法

  1. 接口中的成员不允许添加访问修饰符,默认就是public。(类中的成员默认是private)
  2. 接口中不允许写具有方法体的函数。
  3. 接口中不允许写字段和构造函数,但允许写自动属性(因为自动属性没有方法体,也没有字段)。
  4. 接口不允许被实例化,不能用new创建对象。
  5. 接口与接口之间可以继承,并且可以多继承。
  6. 接口并不能去继承一个类,而类可以继承接口。(接口只能继承于接口,而类既可以继承接口,也可以继承类)
  7. 实现接口的子类必须实现该接口的全部成员。
  8. 一个类可以同时继承一个类并实现多个接口,如果一个子类同时继承了父类A,并实现了接口IA,那么语法上A必须写在IA的前面。

总结:接口中只能有方法。(方法、自动属性、索引器、事件的本质都是方法)

自动属性和普通属性区别

  1. 写法上,自动属性没有字段,也没有方法体
  2. 本质没有区别,只是写法上的区别

显式实现接口

显式实现接口的目的是为了解决方法的重名问题

public class Bird : IFlyable
{
public void Fly()
{
Console.WriteLine("鸟会飞");
}

//显式实现接口
void IFlyable.Fly()//注意不能用public修饰,因为这是接口中的Fly方法,默认是public
{
Console.WriteLine("我是接口的飞");
}
}

Main方法

//调用接口中的Fly
Iflyable fly = new Bird();
fly.Fly();//我是接口的飞

//调用Bird类中的Fly
Bird bird = new Bird();
bird.Fly();//鸟飞会

MD5加密

加密方法GetMD5

public static string GetMD5(string str)
{
//创建MD5对象
MD5 md5 = new MD5.Create();
//开始加密
//需要将字符串转换成字节数组
byte[] buffer = Encoding.Default.GetByte(str);
//返回一个加密好的字节数组
byte[] MD5Buffer = md5.ComputeHash(buffer);

//将字节数组转换为字符串
string strNew = "";
for (int i = 0; i < MD5Buffer.Length; i++)
{
strNew += MD5Buffer[i].ToString("X2");//ToString方法的参数X2是指转换成16进制,并且进行对齐
}
return strNew;
}

注意:将字节数组转换成字符串的方法有3种

  1. 将字节数组中每个元素按照指定的编码格式解析成字符串
  2. 直接将数组ToString()
  3. 将字节数组中的每个元素ToString()