C# 学习笔记(4)—— C# 中的面向对象编程

2023-10-20 18:47:42 浏览数 (1)

C# 是面向对象语言,所有面向对象语言都有三个特征

封装

封装指的是把类内部的数据隐藏起来,不让对象实例直接对其操作。C# 中提供了属性机制来对类内部的状态进行操作。此外,封装可以通过 public、private、protected 和 internal 等关键字来体现

代码语言:javascript复制
public class Person
{
    private int _age;
    public int age
    {
        get
        {
            return _age;
        }
        set
        {
            if(value > 99 && value < 0)
                throw new Exception("年龄不得大于99且小于0");
            
            _age = value;
        }
    }
}

使用了封装技术后,外部数据只能对属性进行操作。如果把不符合逻辑的值赋给属性,我们可以在运行时抛出异常。客户端可以通过异常捕获来进行错误处理

继承

在 C# 中,一个类可以继承另一个已有的类(密封类除外),被继承的类成为基类(父类),继承的类称为派生类(子类),子类将获得基类除构造函数和析构函数以外的所有成员

C# 中的继承

C# 与 C 不同,C# 仅支持派生于一个基类,而 C 则支持多重继承。但 C# 可以继承多个接口

代码语言:javascript复制
public class Animal
{
    private int _age;
    public int age
    {
        get { return _age; }
        set { _age = value; }
    }
}

public class Horse : Animal
{
    
}

public class Sheep : Animal
{
    
}

这里虽然各个子类并没有定义 age 属性,但是由于它们继承各自基类 Animal,基类中又定义了 age 属性,所以子类也继承了父类的 age。这样避免了在子类中重复定义 age

密封类

密封类不可以被另外一个类继承,如果强行在代码中继承一个密封类,编译会报错

子类的初始化执行顺序

使用了继承之后,当我们初始化一个子类,除了会调用子类的构造函数外,同时也会调用基类的构造函数。子类的初始化顺序如下:

  1. 初始化类的实例字段
  2. 调用基类的构造函数,如果没有指明基类,则调用 System.Object 的构造函数
  3. 调用子类的构造函数
代码语言:javascript复制
namespace ConsoleApp1
{
    class Program
    {
        public class Model : Root
        {
            public Model()
            {
                Console.WriteLine("我是Model");
            }
        }

        public class Root
        {
            public string name { get; set; }

            public Root()
            {
                Console.WriteLine("我是Root");
            }
        }
        static void Main(string[] args)
        {
            var instance = new Model();
            Console.WriteLine(instance.name);
            Console.ReadKey();
        }
    }
}

多态

由于可以继承基类的所有成员,子类就有了相同的行为,但是有时候子类的某些行为需要相互区别,子类需要覆盖父类中的方法来实现子类特有的行为。这样的技术在面向对象的编程中就是多态。多态即相同类型的对象调用相同方法却表现出不同行为的现象

代码语言:javascript复制
class Program
{

    public class Animal
    {
        public virtual void Voice()
        {
            Console.WriteLine("动物发出叫声");
        }
    }

    public class Cat : Animal
    {
        public override void Voice()
        {
            Console.WriteLine("喵~");
        }
    }

    public class Sheep : Animal
    {
        public override void Voice()
        {
            Console.WriteLine("咩~");
        }
    }

    static void Main(string[] args)
    {
        var cat = new Cat();
        cat.Voice();
        var sheep = new Sheep();
        sheep.Voice();
        Console.ReadKey();
    }
}

上面代码通过使用virtual关键字,把需要在子类中表现为不同行为的方法定义为虚方法,然后在子类中使用override关键字对基类方法进行重写。这样,每个基类在调用相同方法时将表现出不同的行为,这段代码正是 C# 中多态的实现

如果子类还行继续访问基类定义的方法,则可以使用base关键字完成调用

抽象类

上面的代码存在一个问题:我们可以通过new操作符创建 Animal 基类的实例,可 Animal 基类的作用是为所有子类提供公共成员,它是一个抽象的概念,在实际的系统中我们希望能避免创建该类的实例,怎么办?

代码语言:javascript复制
public abstract class Animal
{
    
}

你会发现,如果你想实例化一个 Animal 会报错

阻止派生类重写虚成员

我们知道,用sealed关键字可以防止一个类被其他类继承。同样,也可以使用sealed关键字来阻止派生类重写虚成员

代码语言:javascript复制
public class Horse : Animal
{
    public sealed override void Voice()
    {
        base.Voice();
        Console.WriteLine("嘶嘶");
    }
}

public class Test : Horse
{
    // 编译时错误
    public override void Voice()
    {
        
    }
}

使用新成员隐藏基类成员

如果想在派生类中定义与基类成员同名的成员,则可以使用new关键字来把基类成员隐藏起来,如果不使用new 关键字,编译器会产生警告信息

代码语言:javascript复制
class Program
{

    public class Animal
    {
        public Animal()
        {
            Voice();
        }
        
        public void Voice()
        {
            Console.WriteLine("动物发出叫声");
        }
    }

    public class Cat : Animal
    {
        public Cat()
        {
            Voice();
        }
        
        public new void Voice()
        {
            Console.WriteLine("喵~");
        }
    }

    static void Main(string[] args)
    {
        var cat = new Cat();
        Console.ReadKey();
    }
}

这里注意一下overridenew的区别哦

代码语言:javascript复制
class Program
{

    public class Animal
    {
        public Animal()
        {
            Voice();
        }
        
        public virtual void Voice()
        {
            Console.WriteLine("动物发出叫声");
        }
    }

    public class Cat : Animal
    {
        public Cat()
        {
            Voice();
        }
        
        public override void Voice()
        {
            Console.WriteLine("喵~");
        }
    }

    static void Main(string[] args)
    {
        var cat = new Cat();
        Console.ReadKey();
    }
}

使用new的打印结果是:

动物发出叫声

喵~

使用override的打印结果是:

喵~

喵~

所有类的父类:System.Object

在 C# 中,所有类都派生自System.Object类。如果定义的类没有指定任何基类,编译器就会自动把Object类当作它的基类。和其他类一样,System.Object类也定义了一组共有的成员,定义如下:

代码语言:javascript复制
namespace System
{
    //
    // 摘要:
    //     Supports all classes in the .NET class hierarchy and provides low-level services
    //     to derived classes. This is the ultimate base class of all .NET classes; it is
    //     the root of the type hierarchy.
    public class Object
    {
        //
        // 摘要:
        //     Initializes a new instance of the System.Object class.
        public Object();

        //
        // 摘要:
        //     Allows an object to try to free resources and perform other cleanup operations
        //     before it is reclaimed by garbage collection.
        ~Object();

        //
        // 摘要:
        //     Determines whether the specified object instances are considered equal.
        //
        // 参数:
        //   objA:
        //     The first object to compare.
        //
        //   objB:
        //     The second object to compare.
        //
        // 返回结果:
        //     true if the objects are considered equal; otherwise, false. If both objA and
        //     objB are null, the method returns true.
        public static bool Equals(Object? objA, Object? objB);
        //
        // 摘要:
        //     Determines whether the specified System.Object instances are the same instance.
        //
        // 参数:
        //   objA:
        //     The first object to compare.
        //
        //   objB:
        //     The second object to compare.
        //
        // 返回结果:
        //     true if objA is the same instance as objB or if both are null; otherwise, false.
        public static bool ReferenceEquals(Object? objA, Object? objB);
        //
        // 摘要:
        //     Determines whether the specified object is equal to the current object.
        //
        // 参数:
        //   obj:
        //     The object to compare with the current object.
        //
        // 返回结果:
        //     true if the specified object is equal to the current object; otherwise, false.
        public virtual bool Equals(Object? obj);
        //
        // 摘要:
        //     Serves as the default hash function.
        //
        // 返回结果:
        //     A hash code for the current object.
        public virtual int GetHashCode();
        //
        // 摘要:
        //     Gets the System.Type of the current instance.
        //
        // 返回结果:
        //     The exact runtime type of the current instance.
        public Type GetType();
        //
        // 摘要:
        //     Returns a string that represents the current object.
        //
        // 返回结果:
        //     A string that represents the current object.
        public virtual string? ToString();
        //
        // 摘要:
        //     Creates a shallow copy of the current System.Object.
        //
        // 返回结果:
        //     A shallow copy of the current System.Object.
        protected Object MemberwiseClone();
    }
}

归纳总结

我们学习了面向对象的三个特性——封装、继承和多态。也了解了所有类的父类System.Object

面向对象的内容是后期学习设计模式和企业系统开发的基础,当然,我们也应该去了解一下面向过程,了解两者的区别

0 人点赞