C#复习之面向对象

2021-12-04
5 min read
Featured Image

面向对象

  • 枚举类型是值类型, 读取速度要快很多.
  • 因为是值类型所以可以和整形数相互转换.
  • 注意: 枚举是和类平行的, 写的时候写在类的外面
enum Color {
  red, Blue, Green, Yellow, 红
};

Color red = Color.红;
int a = Convert.ToInt32(Color.蓝)

OOP

  • OOP = OOA + OOD + OOP
  • 封装, 继承, 多态

类的成员

九个成员: 字段, 构造函数, 属性, 方法, 运算符, 索引器, 事件, 内部类.

1. 封装

封装字段

  • 私有字段无法被类的外部直接访问, 所以需要封装成属性.
private int age;
public int Age {
  get { return age;}
  set { return age = value;}
}
  • C# 3.0 之后出现了自动属性
    • 只有属性, 没有字段.
 public int Age {get;set;}
  • 自动属性更简洁, 但是想要在 set 里面加条件判断的话, 需要用复杂点的那个.
public int Age {
  get { return age;}
  set { 
    if (value < 0 || value > 130) {
      age = 20
    }
   // age = value;
  		}
}
疑问(未解决)
  • 这里有一个疑问, 就是为什么不直接用 public 字段, 而是 private 字段封装成属性加 get , set.
    • 感觉起到的效果差不多
    • 唯一的区别就是, 如果属性不加 set, 可以设置为只读.

构造方法

  • 作用: 实例化对象时, 对其进行初始化.

  • 如果不定义, 就是默认无参构造方法.(基本要保证有一个无参, 一个有参)

  • 特点:

    1. 与类同名
    2. 没有返回值关键字(void 也不行)
    3. 可以定义多个构造方法( 重载: 名字相同, 参数不同)
    4. this 关键字, 对当前实例对象的引用
    5. 私有构造函数不能创建对象(那有啥用呢)

析构方法

  • 对象被销毁前执行, 做一些善后工作.
  • 特点:
    1. 类只能定义一个析构方法.
    2. 无访问修饰符, 没有参数, 没有返回值.
    3. 波浪号开头.
    4. 对象销毁前, 自动调用.
~ Product() {
  console.WriteLine("对象即将被销毁");
}

方法

  • 首字母大写
参数
  1. 可缺省的参数: 参数可定义默认值
public void fn(int val = 5){}
  1. 输出参数: 当方法需要返回多个值, 类型可以不同

    • 输出类型的参数需要初始化

    • 可以用来获取商品信息

// 此时函数有 三个 返回值   可为 void
public string fn( out int i, out int j){
  i = 0;
  j = 0;
  return "返回的字符串"
}
  1. params 参数: 参数的数量不确定
    1. 前面可以定义参数
    2. 后面不允许有其他参数
    3. params 关键字只能有一个
// params
public int fn(params int[] nums) {
  int sum = 0;
  foreach(var item in nums) {
    sum += item;
  }
  return sum;
}

int res = fn(2, 3, 4, 5);
  1. ref 引用
    • 修饰参数按地址传递, 默认都是按值传递
// ref 来完成 a 和 b 的互换
public void fn(ref int a, ref int b) {
  int temp;
  temp = a;
  a = b;
  b = temp;
}

类为元素的数组

Student s1 = new Student();
Student s2 = new Student();
Student[] students = { s1, s2 };

2. 继承

  • 好处: 实现代码的重用.

  • 类只能是单继承, 一个类只能有一个父亲.

  • 子类的名称的最后一部分就是父类的名称.

  • 子类构造函数加 base 关键字: 在子类中, base 代表对父类的引用, 也包含列祖列宗.

    • 如果是子类的子类在引用父类的时候, 不用写 base.base.
  • 子类构造函数的参数需要包含自身的属性加上父类的属性.(字段也可以, 主要是完成初始化)

    • 父类参数不加变量类型
  • 和父类一样, 同样需要两个构造函数

    • 无参对无参, 有参对有参
public class UniStudent : Student
    {
        public string Major { get; set; }
        public string Group { get; set; }

        public UniStudent() : base()
        {
        }
  
        public UniStudent(int id, DateTime birthday, string name, string major, string group)
            : base(id,birthday,name)
        {
            this.Major = major;
            this.Group = group;
        }
    }
  • 修饰符 base 关键字用来修饰父类的字段.

  • 只有子类或者子类的子类可以访问和修改.

public void fn()
        {
            base.pint = 0;
        }
  • internal 只能在一个命名空间内使用, 同一个解决方案里面不能跨命名空间使用.

  • protected internal 组合修饰符

  • 所以一共有五类访问修饰符: private public protected internal, 组合.

虚方法

  • 在父类中定义的,专门给子类去重新设计方法的功能

  • 应该是类似 java 的抽象方法?

  • 在子类中对父类中的虚方法进行重写 override

    • virtual 和 override 需要成对出现
  • 主要作用就是规范子类的方法名称, 大型项目管理常用.

public class Graph {
  public double[] Length {get;set;}
  public virtual double GetLength() { return 0;}
  public virtual double GetArea() { return 0;}
  public Graph(int length) {
    this.Length = new double[length];
  }
}

public class Rectangular {
  public Rectangular(int length, int width, int height) : base(length) {
    base.Length[0] = height;
    base.Length[1] = width;
  }
  public override double GetLength() {
    return 2 * (base.Length[0] + base.Length[1]); 
  }
  public override double GetArea() {
    return base.Length[0] + base.Length[1];
  }
  
}

new 关键字

  • 子类用来隐藏父类的同名方法
  • 在使用多态的时候, 用子类来创建父类对象
    • 如果调用子类同名重写的方法, 就是直接调用子类方法
    • 如果调用子类同名 new 方法, 就是调用父类的方法.

密封类

  • sealed 关键字: 父类使用 sealed 关键字, 无法产生子类, 太监.
  • 出于安全性考虑, 有时候会用密封类

静态

  • static 关键字: 可以修饰类, 字段, 属性, 方法, 构造函数.
    • 静态方法用的最多, 可以在静态类, 也可以在实例类(public)
  • 特点:
    1. 为所有的类所共享 ( 猜着是对一个命名空间内的所有类所共享 ).
    2. 静态构造函数无论实例化多少次, 只会调用一次. 先调用静态构造函数, 再调用实例构造函数.
    3. 静态方法可以通过 类名.方法名 来调用, 不能通过实例名调用.
    4. 实例类可以定义静态方法.
    5. 声明周期和应用程序的声明周期是一致的, 而实例类一段时间后就会自动销毁对象并释放内存.
    6. 静态方法只能只能访问方法内的字段, 不能访问方法外类里定义的字段和实例方法.
    7. 实例方法内可以访问同一个类中的静态方法
    8. 静态类里面所有的成员都是静态的. 静态类的效率很高.
      • 不能实例化
      • 所有成员都被全部对象共享

抽象类 (abstract)

  1. 抽象方法 (abstract method): 只有声明, 没有定义, 没有大括号.

  2. 也可以有普通成员, 非抽象的字段或者方法, 静态也可以.

  3. 但抽象方法不能存在普通类中.

  4. 抽象类不能被实例化.

  5. 抽象类的子类(非抽象)必须要重写全部的抽象类的方法.

    1. 即使子类是抽象类, 也都要实现
    2. 要写 override 关键字
  6. 子类如果是抽象类, 则不用重写所有的抽象类.

接口 (interface) : 引用类型

  1. 抽象类里可以有非抽象的成员, 但接口内的成员全部是抽象的.
  2. 不能被实例化.
  3. 成员默认都是 public , 但是不能写任何的访问修饰符.
  4. 方法只能声明不能定义, 不能写大括号
  5. 所有的方法必须被子类实现
  6. 多继承, 可以有多个父级
    • 如果同时继承类和接口, 要先写父类, 再写接口
public interface IMobile {
  void Insert();
}

public interface INet {
  void Net();
}

public interface IIntelMobile {
  void Pay();
  void Chat();
}

public interface IIPhone : Inet, IMobile, IIntelMobile{
  	
}

显式接口

多个接口中包含相同方法的声明, 同名同参, 而同时被一个类去实现.

public interface Ichinese {
  void Speak();
}

public interface IUSA {
  void Speak();
}

public class Person : Ichinese, IUSA {
  
  void Speak() {
    
  };
  void IChinese.Speak() {
    
  };
  
  void IUSA.Speak() {
     
  }
}

public void Main(string[] args) {
  // 多态向上转型调用不同接口中同名同参的方法
  IChinese ic = new Person();
  ic.Speak();
  IUSA iu = new Person();
  iu.Speak();
}

抽象类和接口的区别

  1. 抽象类有构造方法, 接口没有
  2. 抽象类中有实例成员, 接口中没有, 全部抽象
  3. 抽象类中可以有静态方法, 接口中不能定义静态方法
  4. 抽象类是单继承, 接口是多继承
  5. 类可以实现多个接口, 但只能继承一个抽象类
  6. 类必须实现接口的全部成员, 抽象子类可以实现 0 或者多个父类抽象成员

3. 多态

  1. 方法的重载 (overload)
  2. 重写 (override)
  3. 向上转型: 父类引用
// 向上转型 父类 Graphic 可以是抽象类 和 接口
Graphic g = new Circle(10);
//调用子类的方法
g.GetArea();
  1. 向下转型:
Mobile mobile = new IntellMobile();
IntellMobile mobile2 = (IntellMobile)mobile; // 向下转型
IntellMobile mobile2 = mobile as IntellMobile; // 向下转型 
mobile2.Message(); // 调用的是 intellmobile 的方法
  1. 父类作为方法的参数
Avatar
Aaron Fan My research interests include machine learning, signal processing, web development and robotics.