C#学习笔记 - C#基础知识 - C#从入门到放弃 - C# 结构、类与属性

C# 入门基础知识 - C# 结构、类与属性

  • [第9节 结构、类与属性](#第9节 结构、类与属性)
    • [9.1 结构的使用](#9.1 结构的使用)
    • [9.2 枚举](#9.2 枚举)
    • [9.3 面向对象概述](#9.3 面向对象概述)
    • [9.4 类与对象的关系](#9.4 类与对象的关系)
    • [9.5 类的声明](#9.5 类的声明)
    • [9.6 属性的使用](#9.6 属性的使用)
      • [9.6.1 属性](#9.6.1 属性)
      • [9.6.2 属性使用](#9.6.2 属性使用)
    • [9.7 构造函数和析构函数](#9.7 构造函数和析构函数)
      • [9.7.1 构造函数](#9.7.1 构造函数)
      • [9.7.2 析构函数](#9.7.2 析构函数)
    • [9.8 类的继承](#9.8 类的继承)
    • [9.9 类的封装](#9.9 类的封装)
    • [9.10 类的多态](#9.10 类的多态)

更多C#基础知识点可查看:C#学习笔记 - C#基础知识 - C#从入门到放弃

第9节 结构、类与属性

9.1 结构的使用

在C#中,结构(Struct)是一种用户自定义的数据类型,用于封装多个相关的值。与类(Class)不同,结构是值类型(Value Type),而不是引用类型(Reference Type)。

【示例程序代码】 一次性声明多个不同类型的变量

csharp 复制代码
namespace 结构的使用
{
    //结构放于此空间,所有类都可访问
    public struct ClerkInfo
    {
        public string name;
        public int age;
        public string department;
        public char sex;
    }
    class Program
    {
        //如果放于此位置,只有当前Program类或继承类可以访问
        static void Main(string[] args)
        {
            ClerkInfo c1 = new ClerkInfo();
            c1.name = "Flyer";
            c1.age = 24;
            c1.department = "数据库维护部";
            c1.sex = '男';

            Console.WriteLine("我是{0},{3}生,今年{1}岁,工作于{2}。",c1.name,c1.age,c1.department,c1.sex);

            Console.ReadKey();
        }
    }
}

运行程序:

csharp 复制代码
我是Flyer,男生,今年24岁,工作于数据库维护部。

在示例代码中出现的一些使用结构的常见情况和示例说明:
1、声明结构:

要声明一个结构,可以使用 struct 关键字,后面跟着结构的名称和定义结构的内容。

csharp 复制代码
    public struct ClerkInfo
    {
        public string name;
        public int age;
        public string department;
        public char sex;
    }

2、创建结构的实例:

可以使用 new 关键字创建结构的实例。

csharp 复制代码
 ClerkInfo c1 = new ClerkInfo();

3、结构的成员访问:

结构的成员可以通过点符号进行访问。

csharp 复制代码
 c1.name = "Flyer";
 c1.age = 24;
 c1.department = "数据库维护部";
 c1.sex = '男';

4、方法和属性:

结构可以包含方法和属性来定义结构的行为和操作。

可以为示例代码结构添加一个方法来打印结构的信息:

csharp 复制代码
    public struct ClerkInfo
    {
        public string name;
        public int age;
        public string department;
        public char sex;
        //可以在结构中添加一个打印信息功能
        public void PrintInfo()
    	{
        	Console.WriteLine("Name: {0}, Age: {1}",name,age);
    	}
    }

然后,可以通过结构的实例调用该方法:

csharp 复制代码
ClerkInfo c2 = new ClerkInfo()
{
     name = "程饱饱吃得好饱",
     age = 24
};
c2.PrintInfo();

运行程序:

csharp 复制代码
Name: 程饱饱吃得好饱, Age: 24

5、结构作为参数和返回值:

结构可以作为方法的参数和返回值。当作为参数传递时,会进行值的复制。

csharp 复制代码
        public void updateClerkInfoName(string newName)
        {
            c.name = newName;
        }

        //当作为返回值返回时,会复制整个结构的值
        // 将 getClerkInfo() 定义为静态方法
        public static ClerkInfo getClerkInfo()
        {
            return new ClerkInfo
            {
                name = "程饱饱",
                age = 25
            };
        }
csharp 复制代码
 // 调用 updateClerkInfoName() 方法
 c1.updateClerkInfoName("Choao");
 Console.WriteLine(c1.name);
 
 // 调用 getClerkInfo() 方法
 ClerkInfo clerk = ClerkInfo.getClerkInfo();
 Console.WriteLine(clerk.name);
 Console.WriteLine(clerk.age);

运行程序:

csharp 复制代码
Choao
Name: 程饱饱吃得好饱, Age: 24
程饱饱
25

6、结构的默认构造函数:

结构可以具有默认构造函数。如果没有定义任何构造函数,编译器将自动为结构生成一个默认的无参数构造函数,该构造函数将初始化结构的所有字段为其类型的默认值。

【示例完整代码展示】

csharp 复制代码
namespace 结构的使用
{
    //结构放于此空间,所有类都可访问
    public struct ClerkInfo
    {
        public string name;
        public int age;
        public string department;
        public char sex;
        public void PrintInfo()
        {
            Console.WriteLine("Name: {0}, Age: {1}", name, age);
        }

        //当作为参数传递时,会进行值的复制
        public void updateClerkInfoName(string newName)
        {
            name = newName;
        }

        //当作为返回值返回时,会复制整个结构的值
        // 将 getClerkInfo() 定义为静态方法
        public static ClerkInfo getClerkInfo()
        {
            return new ClerkInfo
            {
                name = "程饱饱",
                age = 25
            };
        }
    }
    class Program
    {
        //如果放于此位置,只有当前Program类或继承类可以访问
        static void Main(string[] args)
        {
            ClerkInfo c1 = new ClerkInfo();
            c1.name = "Flyer";
            c1.age = 24;
            c1.department = "数据库维护部";
            c1.sex = '男';            
            Console.WriteLine("我是{0},{3}生,今年{1}岁,工作于{2}。", c1.name, c1.age, c1.department, c1.sex);

            // 调用 updateClerkInfoName() 方法
            c1.updateClerkInfoName("Choao");
            Console.WriteLine(c1.name);

            // 调用 PrintInfo() 方法
            ClerkInfo c2 = new ClerkInfo
            {
                name = "程饱饱吃得好饱",
                age = 24
            };
            c2.PrintInfo();
        
            // 调用 getClerkInfo() 方法
            ClerkInfo clerk = ClerkInfo.getClerkInfo();
            Console.WriteLine(clerk.name);
            Console.WriteLine(clerk.age);

            Console.ReadKey();
        }
    }
}

运行程序:

csharp 复制代码
我是Flyer,男生,今年24岁,工作于数据库维护部。
Choao
Name: 程饱饱吃得好饱, Age: 24
程饱饱
25

9.2 枚举

在C#中,枚举(Enumeration)允许定义一组命名常量。它们为常用的一组特定值提供了一个描述性的名称。枚举在代码中提高可读性,并使代码更易于理解。

【代码示例】

csharp 复制代码
namespace 枚举
{
    //在此空间声明枚举,枚举作用与结构类似,可以在结构中被调用
    public enum Gender
    {
        男, 女
    }

    public enum Week
    {
        星期一,星期二,星期三,星期四,星期五,星期六,星期天
    }
    class Program
    {
        static void Main(string[] args)
        {
            //枚举调用
            Gender myGender = Gender.男;
            Week myWorkDay = Week.星期五;
            Console.WriteLine("我是{0}生,今天是{1}。",myGender,myWorkDay);	//我是男生,今天是星期五。

            //枚举中每个值会根据定义的顺序从0开始自动赋予每个值一个整型
            //(int)实现将枚举转换为整型
            Console.WriteLine((int)myGender);	//0
            Console.WriteLine((int)myWorkDay);	//4
            //
            Console.WriteLine(myGender.ToString());		//男
            Console.WriteLine(myWorkDay.ToString());	//星期五

            //将枚举转换为字符串 不能用(string)
            //只能用myWorkDay.ToString();或Convert.ToString(myWorkDay);
            Console.WriteLine(myGender);	//男
            Console.WriteLine(myWorkDay);	//星期五

            //(枚举名)实现将整型转换为枚举
            int myInt = 5;
            Console.WriteLine((Week)myInt); //星期六

            //将字符串转换为枚举值
            string myStr = "星期五";
            Console.WriteLine((Week)Enum.Parse(typeof(Week), myStr));	//星期五

            Console.ReadKey();
        }
    }
}

Note:

当枚举值没有明确赋值时,默认从0开始自增,所以 星期一 的值为0星期二 的值为1,依此类推。

枚举是用于表示一组有限的可能性的强类型标识符。

9.3 面向对象概述

C# 是一种面向对象的编程语言,它支持面向对象编程(OOP)的核心原则和概念。面向对象编程是一种用于构建软件系统的编程范式,它将现实世界中的问题分解为一组相关的对象,并通过对象之间的交互来解决这些问题。

以下是面向对象编程的一些核心概念和特性:

1、类(Class):

类是面向对象编程的基本构建块,它是对象的蓝图或模板。类定义了对象的属性(字段)和行为(方法)。

2、对象(Object):

对象是类的实例化,它是类定义的具体实体,具有特定的状态和行为。通过创建对象,我们可以使用类定义的属性和方法。

3、封装(Encapsulation):

封装是面向对象的一个重要概念,它将数据和方法封装在类中,并通过公共接口提供对它们的访问。封装隐藏了内部实现的细节,使对象的使用者只需关注公共接口。

4、继承(Inheritance):

继承允许一个类(子类)从另一个类(父类)继承属性和方法。子类可以扩展父类的功能,并添加自己的特定行为。继承实现了代码的重用和层次结构的建立。

5、多态(Polymorphism):

多态允许对象在不同的上下文中表现出不同的行为。通过多态,父类的引用可以指向子类的对象,使得在调用相同的方法时可以产生不同的结果。

9.4 类与对象的关系

对象是面向对象编程中两个重要的概念,它们之间存在着紧密的关系。

类(Class):

类是面向对象编程的基本构建块,它是对象的模板或蓝图,用于定义对象的属性(字段)和行为(方法)。类可以看作是一类对象的抽象表示,描述了对象的共同特性。

对象(Object):

对象是类的实例化,它是类的具体实体,具有特定的状态和行为。通过创建对象,可以使用类定义的属性和方法。

对象之间的关系如下:

类是对象的抽象,表示一类相似对象的定义。它描述了对象所具有的属性和方法,并提供了创建对象的模板。

对象是类的具体实例,它根据类的定义创建,并具有类所定义的属性和方法。可以通过关键字 new 来创建类的实例。

类是对象的定义;对象是类的实例。 类定义了对象的结构和行为,包括属性和方法等;而对象持有类定义的属性值和对方法的调用。

【示例代码】

csharp 复制代码
class Car
{
    public string brand;
    public string color;

    public void Start()
    {
        Console.WriteLine("The car starts.");
    }

    public void Accelerate()
    {
        Console.WriteLine("The car accelerates.");
    }
}

class Program
{
    static void Main(string[] args)
    {
        // 创建一个Car类的对象
        Car myCar = new Car();

        // 设置对象的属性值
        myCar.brand = "Toyota";
        myCar.color = "Red";

        // 调用对象的方法
        myCar.Start();
        myCar.Accelerate();

        Console.ReadKey();
    }
}

在上述代码中,定义了一个名为 Car 的类,它具有 brandcolor 属性 以及 Start( )Accelerate( ) 方法。在 Main 方法中,我们创建了一个 Car 类的对象 myCar,并设置了其品牌和颜色属性的值。然后,我们通过 myCar 对象调用了 Start( )Accelerate( ) 方法。

通过类和对象的关系,我们可以实现代码的封装性、复用性和可维护性。类定义了对象的结构和行为,而对象则代表了类的实例,具有类所定义的属性和方法。

9.5 类的声明

定义类的方式:

右击需要创建类的所在解决方案下的项目 --> 添加 --> 类 --> 命名、添加

完成后会打开一个新的页面,定义完成后保存,可返回Main方法中调用。

【类的声明】

csharp 复制代码
    public enum Gender
    {
        男,女
    }
    class Clerk
    {
        //定义字段,字段可以存放多个值命名规范:_camelCase,变量只能放一个值
        public string _name;
        public Gender _gender;
        public int _age;
        public string _department;
        public int _workYears;

        //定义一个非静态方法
        public void Print()
        {
            Console.WriteLine("我叫{0},{1}生,今年{2}岁,在{3}部门工作{4}年了。",this._name, this._gender, this._age, this._department, this._workYears);
        }
    }

【类的调用】

csharp 复制代码
        static void Main(string[] args)
        {
            //调用类需将类实例化 实例化:将类指定个某个对象
            Clerk c1 = new Clerk();
            Clerk c2 = new Clerk();

            //c1对象赋值
            c1._name = "Flyer.Cheng";
            c1._gender = Gender.男;
            c1._age = 24;
            c1._department = "数据库维护";
            c1._workYears = 2;
            //调用非静态方法
            c1.Print();

            //c2对象赋值
            c2._name = "Anna.Wang";
            c2._gender = Gender.女;
            c2._age = 25;
            c2._department = "财务";
            c2._workYears = 3;
            //调用非静态方法
            c2.Print();

            //声明一个变量
            string myStr = "程饱饱";
            Console.WriteLine(c1._name);
            Console.WriteLine(c2._name);    //_name同一个字段 不同对象
            Console.WriteLine(myStr);
            Console.WriteLine(myStr);       //说明字段中可以存储多个值,而变量中只能存一个值
            
            Console.ReadKey();
        }

运行程序:

csharp 复制代码
我叫Flyer.Cheng,男生,今年24岁,在数据库维护部门工作2年了。
我叫Anna.Wang,女生,今年25岁,在财务部门工作3年了。
Flyer.Cheng
Anna.Wang
程饱饱
程饱饱

9.6 属性的使用

9.6.1 属性

在C#中,属性(Properties)是一种特殊的成员,用于封装类的字段,以提供对字段的安全访问和控制。属性允许读取和写入私有字段的值,并提供了对字段的验证和计算的机会。

属性 具有类似字段 的语法,但在背后使用了getset访问器来实现对字段的访问和修改。通过属性,可以隐藏实际的字段,并在访问和修改字段时执行额外的逻辑。

以下是使用属性的基本语法:

csharp 复制代码
访问修饰符 数据类型 属性名
{
    get { return 字段名; }
    set { 字段名 = value; }
}
  1. 访问修饰符:指定属性的可访问性,可以是public、private等。
  2. 数据类型:指定属性的数据类型,例如int、string等。
  3. 属性名:给属性命名的标识符,按照命名约定应使用PascalCase(首字母大写)。
  4. get访问器:用于获取属性的值。它是一个返回属性类型的代码块。
  5. set访问器:用于设置属性的值。它是一个接受属性类型的参数的代码块。

以下是一个简单的使用属性的示例:

csharp 复制代码
class Person
{
    private string name;   // 私有字段

    public string Name   // 公共属性
    {
        get { return name; }
        set { name = value; }
    }
}

在上述的示例中,我们定义了一个名为Person的类,其中有一个私有字段 name 和一个公共属性 Name属性Name提供了对字段name的访问和修改

使用属性:

csharp 复制代码
Person person = new Person();
person.Name = "flyer";   // 设置属性的值
string name = person.Name;   // 获取属性的值

通过属性,可以对字段的值进行控制和验证,可以在set访问器中加入一些逻辑,以确保赋给属性的值满足一定的条件。以下是一个使用属性验证的示例:

csharp 复制代码
class Person
{
    private string name;   // 私有字段

    public string Name   // 公共属性
    {
        get { return name; }
        set 
        {
            if (value != null && value.Length > 0)
            {
                name = value;
            }
        }
    }
}

在上述示例中,添加了对属性 Name赋值的验证逻辑。只有当赋给属性的值不为空且长度大于0时,才赋给字段 name

属性还可以使用自动实现的方式来减少代码量。通过自动实现属性,不需要手动编写get和set访问器,编译器会自动生成默认的访问器。

以下是使用自动实现属性的示例:

csharp 复制代码
class Person
{
    public string Name { get; set; }   // 自动实现属性
}

在上述代码中,使用简化的语法声明了一个自动属性 Name 。编译器会自动创建一个名为 Name 的私有字段,并使用 getset 访问器对属性的值进行读取和写入。

9.6.2 属性使用

1、属性的声明:

C#中属性的声明类似于方法的声明,但使用 getset 访问器来定义属性的读取和写入行为。属性通常与一个私有字段相关联,以存储属性的值。

csharp 复制代码
public int MyProperty
{
    get { return myField; }  // 获取属性值并返回
    set { myField = value; } // 设置属性值
}

在上述代码中,声明了一个名为 MyProperty 的属性,它与一个名为 myField 的私有字段相关联。通过 get 访问器,返回字段的值,通过 set 访问器,设置字段的值。

2、自动属性:

C#中还提供了自动属性的简化语法,可以极大地简化属性的声明。自动属性会自动创建一个隐藏的私有字段,用于存储属性的值。下面是自动属性的示例:

csharp 复制代码
public int MyProperty { get; set; }

在上述代码中,使用简化的语法声明了一个自动属性 MyProperty。编译器会自动创建一个名为 MyProperty 的私有字段,并使用 getset 访问器对属性的值进行读取和写入。

使用自动属性时,可以提供简洁性和可读性;但是如果需要在获取或设置属性值时执行其他逻辑,还是需要使用完整的属性声明。

3、只读属性:

如果只需要创建一个只读属性,即只有 get 访问器而没有 set 访问器,可以省略 set 访问器。只读属性只能在构造函数或属性的初始值设定项中设置值。

csharp 复制代码
public int MyReadOnlyProperty { get; }

在上述代码中,声明了一个只读属性 MyReadOnlyProperty,只有 get 访问器。这意味着可以通过 get 访问器读取属性的值,但无法通过 set 访问器设置属性的值。

4、访问属性:

csharp 复制代码
// 创建类的实例
MyClass obj = new MyClass();

// 设置属性的值
obj.MyProperty = 10;

// 获取属性的值
int value = obj.MyProperty;

在上述代码中,创建了一个类的实例 obj。通过 obj 对象,我们可以使用点运算符 . 来访问和设置属性的值。

Note:

① 属性由 get 和 set 访问器组成,用于读取和写入属性的值。

② 访问器可以定义为 public、private、protected 或 internal 等修饰符,用于控制对属性的访问级别。

【综合代码示例】

1、Clerk类的定义:

csharp 复制代码
    class Clerk
    {
        //类中可以存放:
        //字段:采用_camelCase命名方式
        //属性:采用PascaCase命名方式
        //方法:

        //定义属性后往往都会通过属性来访问字段
        //通常情况下 属性声明为public 字段声明为private
        //在外部访问类中的字段都是通过属性实现

        public string _name;
        
        public string Name
        {
            get;
            set;    //自动属性进行预留,以防后期需要添加限定
        }
        public char _gender;
        //定义属性Gender
        //通常我们将get与set称为访问器,有四种:
        //1>既读又写 同时包含get和set
        //2>只读 只包含get
        //3>只写 只包含set
        //4>自动 get
        public char Gender
        {
            get     //get可以对取值进行限定
            {
                if (_gender != '女' && _gender != '男')
                    _gender = 'N';

                return _gender;
            }
            set     //set可以对赋值进行限定
            { _gender = value; }
        }
        public int _age;
        //定义属性Age
        public int Age
        {
            get     //get可以对取值进行限定
            {
                return _age;
            }
            set     //set可以对赋值进行限定
            {
                if (value < 0 || value > 120) value = 0;
                _age = value;
            }
        }
        public string _department;
        public string Department
        {
            get;
            set;    //自动属性进行预留,以防后期需要添加限定
        }
        public int _workYears;
        public int WorkYears
        {
            get;
            set;    //自动属性进行预留,以防后期需要添加限定
        }

        public void Print()
        {
            Console.WriteLine("我叫{0},{1}生,今年{2}岁,我已经在{3}工作{4}年了。", this.Name, this.Gender, this.Age, this.Department, this.WorkYears);
            //this关键字表示当前类对象
        }

    }

2、主体方法调用类:

csharp 复制代码
    class Program
    {
        static void Main(string[] args)
        {
            //将类实例化后分别赋值,调用方法
            Clerk c1 = new Clerk();
            
            //直接调用
            c1.Name = "程饱饱";
            c1.Gender = '男';
            c1.Age = 24;
            c1.Department = "数据库开发维护部";
            c1.WorkYears = 2;

            c1.Print();

            //通过属性调用
            Console.WriteLine("通过属性访问字段:Gender:{0},Age:{1}。",c1.Gender,c1.Age);

            Console.ReadKey();          
        }
    }

9.7 构造函数和析构函数

9.7.1 构造函数

构造函数 是用于初始化对象的特殊方法,它有以下特性:

① 它在创建对象时自动调用,并用于设置对象的初始状态。

② 名称与类的名称相同,并且没有返回类型。

③ 可以具有参数(参数化构造函数),用于在创建对象时传递参数。

④ 参数化构造函数可以接受不同类型和数量的参数。

⑤ 构造函数支持重载。

【示例代码】

1、类和方法的定义

csharp 复制代码
    public enum Gender
    {
        男,女
    }
    class Clerk
    {
        private string _name;
        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        private Gender _gender;
        public Gender Gender
        {
            get{ return _gender; }
            set{ _gender = value; }
        }      

        private int _age;
        public int Age
        {
            get{return _age;}
            set{_age = value;}
        }

        private string  _department;
        public string Department
        {
            get { return _department; }
            set { _department = value; }
        }

        private int _workYears;
        public int WorkYears
        {
            get { return _workYears; }
            set { _workYears = value; }
        }

        public void Print()
        {
            Console.WriteLine("我叫{0},{1}生,今年{2}岁,我已经在{3}工作{4}年了。", this.Name, this.Gender, this.Age, this.Department, this.WorkYears);
        }

        //构造函数:是一种特殊的方法,没有返回值但是不能使用void,必须public,且构造函数的名称必须跟类名一致
        public Clerk(string name,Gender gender,int age,string department,int workYears)
        {
            this.Name = name;
            this.Gender = gender;
            this.Age = age;
            this.Department = department;
            this.WorkYears = workYears;
        }
    }

2、主体方法的调用

① 如果没有定义构造函数Clerk,需要实例化类然后赋值并调用:

csharp 复制代码
    class Program
    {
        static void Main(string[] args)
        {
            Clerk c1 = new Clerk();
            c1.Name = "程饱饱";
            c1.Gender = Gender.男;
            c1.Age = 24;
            c1.Department = "数据库维护开发部";
            c1.WorkYears = 2;
            c1.Print();
            Console.ReadKey();

        }
    }
csharp 复制代码
我叫程饱饱,男生,今年24岁,我已经在数据库维护开发部工作2年了。

② 定义构造函数Clerk后,可简化实例化及赋值代码:

csharp 复制代码
    class Program
    {
        static void Main(string[] args)
        {
            Clerk c1 = new Clerk("程饱饱",Gender.男,24, "数据库维护开发部",2);
            c1.Print();

            Console.ReadKey();

        }
    }
csharp 复制代码
我叫程饱饱,男生,今年24岁,我已经在数据库维护开发部工作2年了。

new 关键字:

1)在内存中开辟空间

2)在开辟空间中创建对象

3)对对象进行初始化,将各个属性值赋值

在创建类中会存在一个默认的无参数的构造函数 ,一旦定义了新的构造函数,不论新的构造函数是否有有参数,原默认的无参数的构造函数都会被覆盖掉

9.7.2 析构函数

析构函数 是在销毁对象时自动调用的特殊方法:

① 它的名称与类的名称相同,但在方法名前加上一个波浪号(~)。

② 析构函数没有参数,没有访问修饰符和返回类型。

③ 析构函数不需要手动调用,CLR(Common Language Runtime)会自动在对象销毁时调用析构函数。

④ 析构函数常用于执行一些清理操作,例如释放非托管资源、关闭文件、释放数据库连接等。

需要注意:

C#中的析构函数不像其他语言(如C++)那样可靠,无法保证准确的释放资源。因此,在C#中常用的是使用 Dispose( ) 方法和 using 语句来释放资源。

以下是析构函数的基本语法:

csharp 复制代码
public class MyClass
{
    ~MyClass() // 析构函数的声明
    {
        // 析构函数的代码
    }
}

关于析构函数:

1、析构函数的执行时机:

① 析构函数在对象被销毁时自动调用,也就是当对象的生命周期结束时(例如,对象离开作用域、对象被赋予其他引用、程序终止等)。

② 析构函数不能被显式调用,也不能手动触发对象的销毁。

2、析构函数的清理操作:

① 析构函数主要用于执行清理操作,例如释放非托管资源(如文件、数据库连接等)或其他资源的释放和关闭。

② 在析构函数中可以使用任何合法的C#代码,具体的清理操作根据需求来决定。

3、析构函数的注意事项:

① 析构函数通常用于释放非托管资源。对于托管资源(例如,使用垃圾回收管理的资源),不需要在析构函数中进行额外的处理,因为垃圾回收器会自动释放托管资源。

② 由于析构函数的执行时间是无法确定的,不要在析构函数中依赖于外部资源或其他对象的状态。

析构函数不能被继承、重载或显式调用。

③ 一个类只能有一个析构函数。

4、垃圾回收器(Garbage Collector)和析构函数:

① 垃圾回收器负责管理和释放托管内存,它会自动回收不再使用的对象以释放内存空间。

② 与垃圾回收器不同,析构函数主要用于释放非托管资源,并在对象销毁时执行其他清理操作(如关闭文件、释放数据库连接)。

9.8 类的继承

面向对象编程(Object-oriented programming)的三个基本特征是封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。

在C#中,类的继承是一种基于已有类(称为基类或父类)创建新类(称为派生类或子类)的机制。派生类扩展了基类的功能,并可以添加自己的成员、方法和属性。

要实现类的继承,可以使用:符号,后跟要继承的基类的名称。派生类可以继承一个基类,但C#不支持多重继承(即一个类不能直接继承多个类)。

下面是一个简单的示例,演示了如何使用类的继承:

csharp 复制代码
// 基类
class Animal
{
    public string Name { get; set; }

    public void Sleep()
    {
        Console.WriteLine("The animal is sleeping.");
    }
}

// 派生类
class Dog : Animal
{
    public void Bark()
    {
        Console.WriteLine("The dog is barking.");
    }
}

在上述示例中,Animal类是基类,Dog类是派生类。派生类Dog继承了基类Animal的所有成员,包括属性Name和方法Sleep。此外,派生类Dog还添加了自己的方法Bark。

派生类可以使用继承的成员,也可以添加自己的成员,并且可以覆盖(重写)基类的虚方法。

以下是一些关于类继承的重要概念和用法:

1、访问修饰符:

派生类可以访问基类中的所有publicprotected成员。private成员对派生类不可见。派生类还可以具有自己的访问修饰符控制派生类成员的访问级别。

2、构造函数:

派生类可以调用基类的构造函数来初始化基类的成员。使用base关键字可以在派生类的构造函数中调用基类的构造函数。

3、方法重写:

派生类可以通过重写(override)基类中的虚方法来实现多态性。要重写基类的方法,派生类中的方法必须使用override关键字进行标记。

4、隐藏成员:

派生类中的成员可以隐藏基类中的同名成员。使用new关键字可以隐藏基类的成员,但不推荐在派生类中隐藏基类的成员,因为它可能引起混淆和意料之外的行为。

5、继承链:

派生类也可以作为更高级别派生出其他派生类的基类,形成继承链。

Note:

① 类的继承应该符合"is-a"的关系。也就是说,派生类应该是基类的一种特殊类型。例如,狗是一种动物,所以Dog类可以派生自Animal类。

② C#不支持多重继承,一个类只能直接继承自一个基类。但是,可以通过接口来实现多重继承的一部分功能。

③ 尽量避免过度嵌套和复杂的继承层次,以保持代码的可读性和可维护性。

④ 继承是面向对象编程的重要概念,可以提高代码的重用性和扩展性。通过派生类可以构建更加复杂和功能强大的系统。

9.9 类的封装

面向对象编程(Object-oriented programming)的三个基本特征是封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。

在C#中,类的封装是一种将数据和行为封装在一个独立的单元中的机制。封装将类的内部实现细节隐藏起来,只对外提供必要的接口,提供更好的安全性和代码维护性。

要实现类的封装,可以使用访问修饰符来管理类的成员的访问级别。C#提供了以下访问修饰符:
public:公共访问修饰符,可以在任何地方访问该成员,没有限制。
private:私有访问修饰符,只能在类的内部访问该成员,对外部不可见。
protected:受保护访问修饰符,只能在类内部和继承类中访问该成员,对外部不可见。
internal:内部访问修饰符,只能在同一程序集中的类中访问该成员,对外部不可见。
protected internal:受保护的内部访问修饰符,可以在同一程序集中的类和继承类中访问该成员,对外部不可见。

以下是一个示例,演示了如何使用访问修饰符来封装类的成员:

csharp 复制代码
class MyClass
{
    private int privateField;
    public int publicField;
    protected int protectedField;

    public void PublicMethod()
    {
        // 可以访问所有成员
        privateField = 1;
        publicField = 2;
        protectedField = 3;
    }

    private void PrivateMethod()
    {
        // 仅限类的内部访问
    }

    protected void ProtectedMethod()
    {
        // 仅限类的内部和继承类访问
    }
}

在上述示例中,privateField是一个私有字段,只能在类的内部访问;publicField是一个公共字段,可以在任何地方访问;protectedField是一个受保护字段,只能在类的内部和继承类中访问。同样PrivateMethod是一个私有方法,只能在类的内部访问;PublicMethod是一个公共方法,可以在任何地方访问;ProtectedMethod是一个受保护方法,只能在类的内部和继承类中访问。

通过封装,可以隐藏类的内部实现细节,只暴露必要的接口,从而提高代码的安全性和可维护性。封装还可以帮助我们模块化和组织代码,使其更易于使用和理解。

9.10 类的多态

面向对象编程(Object-oriented programming)的三个基本特征是封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。

实现多态性的多种途径:

1、虚方法:将父类的方法标记为虚方法,使用关键字virtual,此方法在子类中可以重写(override)

2、抽象类与抽象方法:

3、接口实现

抽象类:不需要使用基类实例化的对象

虚方法:需要使用基类实例化的对象

1、虚方法实现:

csharp 复制代码
   class Clerk
    {
        public virtual void WorkPlan()
        {
            Console.WriteLine("我是职员,我需要有工作计划。");
        }
    }

    class ProjectManager:Clerk
    {
        public override void WorkPlan()
        {
            Console.WriteLine("我是项目经理,我也需要有工作计划。");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Clerk c1 = new Clerk();
            ProjectManager p1 = new ProjectManager();
            	//c1.WorkPlan();
            	//p1.WorkPlan();
            Clerk[] clerk = { c1, p1 };
            foreach (Clerk outclerk in clerk)
                outclerk.WorkPlan();

            Console.ReadKey();
        }
    }
}

2、抽象类实现

csharp 复制代码
    abstract class Drink
    {
        public abstract void drink();
        //利用抽象来实现,类抽象化、方法抽象化,并且方法中不能有方法体{ }
        //{
        //    Console.WriteLine("我是饮料,我可以用来解渴。");
        //}
    }
    class Milk:Drink
    {
        public override void drink()
        {
            Console.WriteLine("我是牛奶,我也可以用来解渴。");
        }
    }
    class Tea:Drink
    {
        public override void drink()
        {
            Console.WriteLine("我是茶,我也可以用来解渴。");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Drink myMilk = new Milk();
            Drink myTea = new Tea();

            Drink[] drink = { myMilk, myTea };
            foreach (Drink outdrink in drink)
                outdrink.drink();

            Console.ReadKey();
        }
    }

【代码示例】

老鹰(eagle)、麻雀(sparrow)、鸵鸟(ostrich)都是鸟类(birds),根据三者的共性提取出鸟类作为父类,但是她们都有各自特点:老鹰吃小鸡,麻雀吃粮食,驼鸟吃青草。

csharp 复制代码
    abstract class Birds
    {
        public abstract void food();
    }
    class Eagle:Birds
    {
        public override void food()
        {
            Console.WriteLine("这是老鹰,老鹰爱吃小鸡。");
        }
    }
        class Sparrow:Birds
    {
        public override void food()
        {
            Console.WriteLine("这是麻雀,麻雀爱吃粮食。");
        }
    }
        class Ostrich:Birds
    {
        public override void food()
        {
            Console.WriteLine("这是鸵鸟,鸵鸟爱吃青草。");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Birds eagle = new Eagle();  //只能抽象派生类
            Birds sparrow = new Sparrow();
            Birds ostrich = new Ostrich();

            Birds[] birds = { eagle, sparrow, ostrich };
            foreach (Birds outbird in birds)
                outbird.food();

            Console.ReadKey();
        }
    }

在上述的代码示例中,存在一个基类 Birds 和 三个派生类 Eagle、Sparrow和Ostrich。基类Birds 中定义了一个 虚方法 food( ),而派生类中使用override关键字重写了该方法。

Main方法中,我们创建了三个Birds 类型的变量eagle、sparrow和ostrich,并将其实例化为Eagle、Sparrow和Ostrich类型的对象。由于Eagle、Sparrow和Ostrich都是Birds 的派生类,所以可以将它们赋值给Birds 类型的变量。

通过调用.food( )方法,编译器会根据实际对象的类型来确定调用的方法。即使使用的是基类的引用变量,但实际上调用的是派生类的方法。这就是类的多态性

多态性的好处是可以在不修改已有代码的情况下扩展程序的功能。如果新增一个派生类,只需要将其实例化为基类类型的对象 ,就可以通过基类类型的引用调用相应的方法,而无需修改调用代码。

总结:

类的多态性是通过继承和方法重写实现的。它允许我们通过使用基类类型的引用来引用派生类的对象,并根据实际对象的类型来调用相应的方法。多态性提供了灵活性和可扩展性,使得代码更加通用和可重用。

相关推荐
武子康几秒前
大数据-230 离线数仓 - ODS层的构建 Hive处理 UDF 与 SerDe 处理 与 当前总结
java·大数据·数据仓库·hive·hadoop·sql·hdfs
武子康3 分钟前
大数据-231 离线数仓 - DWS 层、ADS 层的创建 Hive 执行脚本
java·大数据·数据仓库·hive·hadoop·mysql
极客代码4 分钟前
【Python TensorFlow】进阶指南(续篇三)
开发语言·人工智能·python·深度学习·tensorflow
苏-言9 分钟前
Spring IOC实战指南:从零到一的构建过程
java·数据库·spring
土豆湿9 分钟前
拥抱极简主义前端开发:NoCss.js 引领无 CSS 编程潮流
开发语言·javascript·css
界面开发小八哥16 分钟前
更高效的Java 23开发,IntelliJ IDEA助力全面升级
java·开发语言·ide·intellij-idea·开发工具
草莓base30 分钟前
【手写一个spring】spring源码的简单实现--容器启动
java·后端·spring
Allen Bright43 分钟前
maven概述
java·maven
qystca1 小时前
洛谷 B3637 最长上升子序列 C语言 记忆化搜索->‘正序‘dp
c语言·开发语言·算法
编程重生之路1 小时前
Springboot启动异常 错误: 找不到或无法加载主类 xxx.Application异常
java·spring boot·后端