首页 > 编程语言 >Learning Hard C# 学习笔记: 5.C#中的面向对象编程

Learning Hard C# 学习笔记: 5.C#中的面向对象编程

时间:2023-10-05 22:13:56浏览次数:45  
标签:Animal 面向对象编程 C# 子类 class 继承 Learning 基类 public

目前除C#外流行的面向对象编程的几个语言分别是:Java, C++等;

面向对象的语言都具有以下特征:

  • 封装 - 将客观事物封装成类, 并将类内部的实现隐藏,以保证数据的完整性;
  • 继承 - 子类通过继承可以复用父类的代码;
  • 多态 - 允许将子对象赋值给父对象的一种能力.

5.1 封装

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

为什么要将类内部的数据封装起来呢?下面通过一个例子来解释其必要性,具体的代码如下。

// 不使用封装特性来定义一个Person类
public class Person
{
    public string _name;
    public int _age;
}

当把字段定义为公共类型时,外部对象可以对类内部的数据进行任意的操作,很可能导致当前值不符合系统的业务逻辑。下面的代码演示了公共数据存在的问题。

class Program
{
    static void Main(string[] args)
    {
        Person p = new Person();
        p._name = "Learning Hard";

        // -5赋给age字段显然是不符合业务逻辑的,因为人的年龄不可能为负数
        p._age = -5;
    }
}

在以上代码中,尽管把-5 赋给Person 的_age 属性没有引起编译错误,但这并不符合业务逻辑,因为在现实生活中,人的年龄不可能为负值。当我们把类的字段定义为公共类型时,外部对象可以直接对类内部的数据进行操作,此时无法对这些操作进行一些逻辑判断,这就是公共数据的问题所在。

面向对象编程中的封装特性,是一种保护状态数据完整性的方法,在面向对象编程中,应更多地定义私有数据字段。C#提供属性机制来对这种私有字段数据进行间接的操作,并且可以在属性的定义中加入更多的逻辑判断。利用封装技术,我们可以有效地对外部隐藏类内部的数据,从而避免数据损坏。

下面的代码演示了在C#中,使用封装技术后类的定义过程。

public class Person
{
    private string _name;
    private int _age;

    public string Name
    {
        get { return _name; }
        set { _name = value; }
    }

    public int Age
    {
        get { return _age; }
        set
        {
            // 在属性定义中,可以根据系统的业务逻辑添加逻辑代码
            if (value < 0 || value > 120)
            {
                throw (new ArgumentOutOfRangeException("AgeIntPropery", value, "年龄必须在0-120之间"));
            }
        _age=valuel;
        }
    }
}        

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

简而言之, 封装是为了更好地将代码与现实业务结合.


5.2 继承

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

例如,牛、羊、马等都是动物,但它们是不同种类的动物,除了具有动物的共性外,它们还具有各自的特点,如不同的用途、不同的发声方式等。我们可以把动物定义为牛、羊、马的父类,这样子类不但继承了基类除构造函数和析构函数以外的所有成员,还可以添加自己特有的成员。

通过继承,程序可实现对父类代码的复用。因为子类可继承父类的所有成员,父类中定义的代码便不需要在子类中进行重复定义了。

5.2.1 CSharp中的继承

C#与C++不同,C#仅支持派生于一个基类,而C++则支持多重继承。但C#可以继承多个接口,接口的内容会在第6章中介绍。

下面的代码演示了C#中继承的使用方法。

class Program
{
    static void Main(string[] args)
    {
        Horse horse = new Horse();
        horse.Age = 2;
        Console.WriteLine("马的年龄为:{0}", horse.Age);

        Sheep sheep = new Sheep();
        sheep.Age = 1;
        Console.WriteLine("羊的年龄为:{0}", sheep.Age);
        Console.Read();
    }
}

// 基类
public class Animal
{
    private int _age;
    public int Age
    {
        get { return _age; }
        set
        {
            // 这里假设牛的寿命为10年
            if (value < 0 || value > 10)
            {
                throw (new ArgumentOutOfRangeException("AgeIntPropery", value, "年龄必须在0-10之间"));
            }

            _age = value;
        }
    }
}

// 马 (子类)
public class Horse:Animal
{
}

// 羊 (子类)
public class Sheep:Animal
{
}            

在以上代码中,虽然各个子类并没有定义Age 属性,但由于它们都继承自基类Animal ,基类中又定义了Age 属性,所以子类也就继承了父类中的Age 。通过继承,避免了在子类中重复定义Age ,从而达到代码复用的目的。

需要注意的是,子类并不能对父类的私有成员进行直接访问,它只可对保护成员和公有成员进行访问。如果把上面代码中的Age 属性定义为私有属性,Main 函数就不能对该属性进行赋值操作了。私有成员也会被子类继承,但子类不能直接访问私有成员,子类可以通过调用公有或保护方法来间接地对私有成员进行访问。

5.2.2 密封类

密封类不可以被另一个类继承, 强行继承编译会产生错误.

作用:

  • 防止继承:密封类不能被其他类继承,这样可以防止其他类对该类进行修改或继承并重写其方法,确保类的完整性和稳定性。

  • 提高性能:由于密封类不能被继承,编译器可以对其进行一些优化,提高代码的执行效率。

  • 安全性:密封类可以防止其他类通过继承和重写方法来篡改其内部逻辑,增强了代码的安全性。

应用场景:

  • 工具类:一些工具类不需要被继承或修改,可以将其定义为密封类,以确保其功能的完整性和稳定性。

  • 不可变类:一些不可变类,如字符串类,不希望被继承或修改,可以将其定义为密封类,以确保其不被修改。

  • 性能敏感类:对于一些性能关键的类,如数学计算类,为了提高性能,可以将其定义为密封类,以允许编译器进行优化。


5.2.3 子类的初始化顺序

子类继承父类之后, 当我们初始化子类时, 除了会调用子类的构造函数外,同时也会调用基类的构造函数。

子类的初始化顺序如下:

  • 初始化类的实例字段;

  • 调用基类的构造函数,如果没有指明基类,则调用System.Object 的构造函数;

  • 调用子类的构造函数。

下面例子演示了子类的初始化顺序.

class Program
{
    static void Main(string[] args)
    {
        // 初始化子类实例
        ChildA child = new ChildA();
        child.Print();
        Console.Read();
    }
}

public class Parent
{
    // ②调用基类构造函数
    public Parent()
    {
        Console.WriteLine("基类构造函数被调用");
    }
}

public class ChildA : Parent
{
    // 创建一个ChildA对象时,
    // ①初始化它的实例字段
    private int FieldA = 3;

    // ③调用子类构造函数
    public ChildA()
    {
        Console.WriteLine("子类构造函数被调用");
    }

    public void Print()
    {
        Console.WriteLine(FieldA);
    }
}

输出结果如下:

基类构造函数被调用
子类构造函数被调用
3

5.3 多态

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


5.3.1 使用virtual 和override 关键字实现方法重写

只有基类成员声明为virtual 或abstract 时,才能被派生类重写;而如果子类想改变虚方法的实现行为,则必须使用override 关键字。下面的代码演示了C#对多态的支持:

class Program
{
    static void Main(string[] args)
    {
        Animal horse = new Horse();
        horse.Voice();

        Animal sheep = new Sheep();
        // 相同类型的对象调用相同的方法表现出不同的行为
        sheep.Voice();
        Console.Read();
    }
}
// 动物基类
public class Animal
{
    private int _age;
    public int Age
    {
        get { return _age; }
        set
        {
            // 这里假设牛的寿命为10年
            if (value < 0 || value > 10)
            {
                throw (new ArgumentOutOfRangeException("AgeIntPropery", value, "年龄必须在0-10之间"));
            }

            _age = value;
        }
    }

    // 几乎所有动物都具有发出声音的能力
    // 但是对于动物的子类来说,每个子类发出的声音都是不一样的
    public virtual void Voice()
    {
        Console.WriteLine("动物开始发出声音");
    }
}

// 马 (子类),子类应重写基类的方法,以实现自己特有的行为
public class Horse : Animal
{
    // 通过override关键字来重写父类方法
    public override void Voice()
    {
        // 调用基类的方法
        base.Voice();
        Console.WriteLine("马发出嘶……嘶……嘶……的声音");
    }
}

// 羊 (子类)
public class Sheep : Animal
{
    // 重写父类方法
    public override void Voice()
    {
        // 通过base语句来调用父类的方法
        base.Voice();
        Console.WriteLine("羊发出咩……咩……咩……的声音");
    }
}    

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

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

代码运行结果如下:

动物开始发出声音
马发出嘶...嘶..嘶...的声音
动物开始发出声音
羊发出咩...咩...咩...的声音

从上面的运行结果可以看出,相同类型的对象调用相同的方法确实表现了不同的行为,这就是多态的精髓所在。

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

对于C#,可以使用abstract 关键字来防止在代码中直接创建这样类的实例,正如下面的代码所示:

public abstract class Animal
{
    …
}

如果尝试创建Animal 实例, 则会报错:无法创建抽象类或接☐"_4_6.Animal"的实例


5.3.2 阻止派生类重写虚成员

前面曾介绍到,用sealed 关键字可以防止一个类被其他类继承。同样,也可以使用sealed 关键字来阻止派生类重写虚成员。例如,我们希望Horse 的继承类不再具有扩展Voice 方法的行为,则可以使用sealed 关键字来停止虚拟继承,如以下代码所示:

public class Horse : Animal
{
    // 通过override关键字来重写父类方法
    public sealed override void Voice()
    {
        // 调用基类的方法
        base.Voice();
        Console.WriteLine("马发出嘶……嘶……嘶……的声音");
    }
}

尝试在Horse 的派生类中重写Voice 方法,则会收到“无法对密封成员进行复写”的错误信息。


5.3.3 使用新成员隐藏基类成员

如果想在派生类中定义与基类成员同名的成员,则可以使用new 关键字把基类成员隐藏起来。

public class Animal
{
    public void Eat()
    {
        Console.WriteLine("动物吃方法");
    }
}

public class Horse : Animal
{
    // 想在派生类中也定义一个Eat方法,则会收到一个警告信息
    public void Eat()
    {
        Console.WriteLine("马吃的方法");
    }
}

如果不使用new 关键字,在派生类中定义一个与基类成员同名的成员,编译器将产生警告信息。

Horse.Eat0将隐藏继承的成员”Animal.Eat()",若要使当前成员重写该实现,请添动加关键字override,否则,添加关键字new.

在实际的软件系统中,若确实需要添加某个方法,但是该方法又与基类的方法同名,那么可以使用new 关键字把基类成员隐藏。下面的代码演示了使用new 关键字来隐藏基类成员的方法:

public class Horse : Animal
{
    // 使用new关键字进行修饰,从而隐藏了基类中同名成员
    public new void Eat()
    {
        Console.WriteLine("马吃的方法");
    }
}    

如果此时仍然想访问基类的成员,则可使用强制类型转换,把子类强制转换成基类类型,从而访问隐藏的基类成员(类型转换的内容将在第10章中介绍)。具体的实现代码如下:

class Program
{
    static void Main(string[] args)
    {
        // 调用Horse中Eat方法
        Horse horse = new Horse();
        horse.Eat();

        // 调用基类的Eat方法
        ((Animal)horse).Eat();
        Console.Read();
    }
}

5.4 所有类的父类:System.Object

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

public class Object
{
    // 方法
    // 构造函数
    public Object();

    // 虚成员,子类可以重写这些方法
    public virtual bool Equals(object obj);
    protected virtual void Finalize();
    public virtual int GetHashCode();
    public virtual string ToString();

    // 实例成员
    public Type GetType();
    protected object MemberwiseClone();

    // 静态成员
    public static bool Equals(object objA, object objB);
    public static bool ReferenceEquals(object objA, object objB);
}

本章详细介绍了C#中面向对象的3个特性——封装、继承和多态。通过这些内容,我们了解了将字段定义为私有的原因,学习了如何去继承一个类,以及如何去覆写和隐藏基类成员。最后,本章还简单地介绍了.NET中所有类的父类——System.Object 。

标签:Animal,面向对象编程,C#,子类,class,继承,Learning,基类,public
From: https://www.cnblogs.com/cogito/p/LearningHardCSharpStudyNote_5.html

相关文章

  • Learning Hard C# 学习笔记: 6.C#中的接口
    目的:由于C#中的类只能单个继承,为了满足多重继承(一个子类可以继承多个父类)的需求,所以产生了接口.多重继承是指一个类可以从多个父类继承属性和方法。在C#中,只允许单继承,即一个类只能有一个直接父类。这是因为多继承在某些情况下可能导致代码的复杂性和不确定性增加,容易引......
  • QT5.14: 打开文件出错warning: format '%s' expects argument of type 'char*'
    错误提示信息:D:\Demo\QT5.14\CH5\CH501\imgprocessor.cpp:158:warning:format'%s'expectsargumentoftype'char*',butargument2hastype'QChar*'[-Wformat=]printf("fileName:%s\n",filename.data());原函数代码:......
  • 14_C++对c的扩展
    c++对c的扩展::作用域运算符::使用全局变量usingnamespacestd;inta=10;voidtest01(){inta=20;cout<<a<<endl;//20cout<<::a<<endl;//10}命名空间namespacenamespace和c语言中static只在本源文件中有效差不多命名空间使用语法创建一......
  • BizTalk Visual Studio 各版本自动部署GAC命令
     BizTalk20161"C:\ProgramFiles(x86)\MicrosoftSDKs\Windows\v10.0A\Bin\NETFX4.6Tools\gacutil.exe" /i "$(TargetPath)" /F  BizTalk20201"C:\ProgramFiles(x86)\MicrosoftSDKs\Windows\v1......
  • typescript: Prototype Pattern
     /***PrototypePattern原型是一种创建型设计模式,使你能够复制对象,甚至是复杂对象,而又无需使代码依赖它们所属的类。*Theexampleclassthathascloningability.We'llseehowthevaluesoffield*withdifferenttypeswillbecloned.*/classPrototype......
  • chisel安装和使用+联合体union的tagged属性+sv读取文件和显示+sv获取系统时间+vcs编译
    chisel安装和使用sbt:scalabuildtool,是scala的默认构建工具,配置文件是build.sbt。mill:一个新的java/scala构建工具,运行较快,与sbt可以共存,配置文件是build.sc。chisel的安装可以参考这篇文章。安装过程务必联网,而没有联网情况下的安装,按照其它的说明,如移动缓存文件等,并无法正常......
  • ChatGPT入门实战课 AI时代更具竞争力的开发者(完结)
    点击下载:ChatGPT入门实战课AI时代更具竞争力的开发者(完结)提取码:bx1lFlink是一款基于流处置的散布式计算框架,能够完成高性能、低延迟的实时数据处置和剖析。下面是一个示例代码,用于展现如何运用Flink从零开端构建实时风控系统。首先,我们需求在pom.xml文件中添加Flink的依......
  • Service mesh 学习05 istio初步使用
    一、初步感受istio在docker中是通过container来部署业务的,在k8s里面是通过pod来部署业务的,那么在istio里面如何体现sidecar呢?猜想:会不会在pod中除了业务需要的container之外还会有一个sidecar的container存在呢?准备资源vifirst-istio.yamlapiVersion:apps/v1##定义了一个版本......
  • c# winfom从0学习开发开发OA、BPM工作流程与自定义表单系统(二)部门树形结构和下拉框的
    c#winfom从0学习开发开发OA、BPM工作流程与自定义表单系统(二)部门树形结构和下拉框的结构设计 具体的代码usingSystem;usingSystem.Collections;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;usingSystem......
  • C++ 数据结构插入效率学习
    转自:https://blog.csdn.net/breaksoftware/article/details/829478381.总结在头部插入。元素数量>15k时,效率unordered_set>set,unordered_map>map。元素数量<1024时,效率unordered_set>set,map> unordered_map。元素数量<256时,效率unordered_set>set,map> unorder......