MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C#中的类与对象定义及实例化

2021-02-164.4k 阅读

C# 中的类与对象定义及实例化

类的定义基础

在 C# 编程中,类是一种用户自定义的数据类型,它封装了数据(字段)和操作这些数据的方法。可以把类看作是一种蓝图,用于创建对象。对象是类的具体实例,每个对象都拥有类中定义的属性和方法的副本。

类的定义语法如下:

[访问修饰符] class [类名]
{
    // 字段
    [字段修饰符] [数据类型] [字段名];
    // 方法
    [方法修饰符] [返回类型] [方法名]([参数列表])
    {
        // 方法体
    }
}

其中,访问修饰符用于控制类、字段和方法的访问级别,常见的有 public(公共的,任何代码都能访问)、private(私有的,只有类内部能访问)、protected(受保护的,只有类及其子类能访问)等。类名要遵循 C# 的命名规范,通常采用 Pascal 命名法,即每个单词的首字母大写。

例如,我们定义一个简单的 Person 类:

public class Person
{
    // 字段
    private string name;
    private int age;

    // 方法
    public void SetName(string newName)
    {
        name = newName;
    }

    public void SetAge(int newAge)
    {
        age = newAge;
    }

    public void DisplayInfo()
    {
        Console.WriteLine($"Name: {name}, Age: {age}");
    }
}

在上述 Person 类中,定义了两个私有字段 nameage,分别用于存储人的姓名和年龄。同时定义了三个公共方法 SetNameSetAgeDisplayInfo,用于设置姓名和年龄以及显示个人信息。由于字段是私有的,外部代码不能直接访问和修改它们,必须通过公共方法来操作,这体现了面向对象编程中的数据封装原则。

构造函数

构造函数是类中的一种特殊方法,用于在创建对象时初始化对象的状态。构造函数的名称与类名相同,并且没有返回类型(包括 void 也没有)。

  1. 默认构造函数 如果在类中没有显式定义任何构造函数,C# 编译器会自动为该类生成一个默认构造函数。默认构造函数没有参数,并且会将对象的所有字段初始化为它们的默认值。例如,数值类型初始化为 0,引用类型初始化为 null
public class Point
{
    private int x;
    private int y;
    // 编译器会自动生成默认构造函数
}

在上述 Point 类中,虽然没有显式定义构造函数,但编译器会生成如下默认构造函数:

public Point()
{
    x = 0;
    y = 0;
}
  1. 自定义构造函数 当需要在创建对象时进行特定的初始化操作时,就需要定义自定义构造函数。
public class Rectangle
{
    private int width;
    private int height;

    // 带参数的构造函数
    public Rectangle(int w, int h)
    {
        width = w;
        height = h;
    }

    public int CalculateArea()
    {
        return width * height;
    }
}

在上述 Rectangle 类中,定义了一个带两个参数的构造函数 Rectangle(int w, int h),用于在创建 Rectangle 对象时初始化 widthheight 字段。这样在创建 Rectangle 对象时,就可以直接传入宽度和高度的值。

Rectangle rect = new Rectangle(5, 10);
int area = rect.CalculateArea();
Console.WriteLine($"The area of the rectangle is: {area}");

类的继承

继承是面向对象编程的重要特性之一,它允许一个类(子类)继承另一个类(父类)的成员(字段、方法等)。通过继承,子类可以复用父类的代码,并且可以添加自己特有的成员或重写父类的方法。

在 C# 中,使用 : 符号来表示继承关系。例如:

public class Animal
{
    protected string name;

    public Animal(string n)
    {
        name = n;
    }

    public virtual void MakeSound()
    {
        Console.WriteLine("The animal makes a sound.");
    }
}

public class Dog : Animal
{
    public Dog(string n) : base(n)
    {
    }

    public override void MakeSound()
    {
        Console.WriteLine("The dog barks.");
    }
}

在上述代码中,Dog 类继承自 Animal 类。Dog 类通过 : base(n) 调用了父类 Animal 的构造函数来初始化 name 字段。Animal 类中的 MakeSound 方法被声明为 virtual,表示该方法可以在子类中被重写。Dog 类使用 override 关键字重写了 MakeSound 方法,提供了自己特有的实现。

Animal animal = new Animal("Generic Animal");
animal.MakeSound();

Dog dog = new Dog("Buddy");
dog.MakeSound();

Animal dogAsAnimal = new Dog("Max");
dogAsAnimal.MakeSound();

上述代码中,首先创建了一个 Animal 对象并调用其 MakeSound 方法,输出 “The animal makes a sound.”。然后创建了一个 Dog 对象并调用其 MakeSound 方法,输出 “The dog barks.”。最后,将 Dog 对象赋值给 Animal 类型的变量 dogAsAnimal,并调用 MakeSound 方法,由于实际对象是 Dog,所以仍然会调用 Dog 类中重写的 MakeSound 方法,输出 “The dog barks.”。这体现了 C# 中的多态性,即同一个方法在不同的对象上表现出不同的行为。

静态类和静态成员

  1. 静态类 静态类是一种特殊的类,它不能被实例化,并且只能包含静态成员(静态字段、静态方法等)。静态类主要用于包含一些与特定功能相关的工具方法或常量。定义静态类使用 static 关键字。
public static class MathUtils
{
    public static int Add(int a, int b)
    {
        return a + b;
    }

    public static int Multiply(int a, int b)
    {
        return a * b;
    }
}

在上述 MathUtils 静态类中,定义了两个静态方法 AddMultiply,用于执行加法和乘法运算。由于是静态类,不能通过实例化对象来调用这些方法,而是直接通过类名来调用。

int sum = MathUtils.Add(3, 5);
int product = MathUtils.Multiply(4, 6);
  1. 静态成员 在普通类中,也可以定义静态字段和静态方法。静态字段属于类本身,而不是类的实例,所有对象共享同一个静态字段。静态方法只能访问静态成员,不能访问实例成员(字段和方法)。
public class Counter
{
    private static int count = 0;

    public Counter()
    {
        count++;
    }

    public static int GetCount()
    {
        return count;
    }
}

在上述 Counter 类中,定义了一个静态字段 count,用于统计创建的 Counter 对象的数量。每次创建 Counter 对象时,构造函数会将 count 加 1。GetCount 方法是一个静态方法,用于获取当前的对象数量。

Counter counter1 = new Counter();
Counter counter2 = new Counter();
int totalCount = Counter.GetCount();
Console.WriteLine($"Total number of counters: {totalCount}");

对象的实例化

  1. 使用 new 关键字 在 C# 中,最常见的对象实例化方式是使用 new 关键字。new 关键字不仅会为对象分配内存空间,还会调用对象的构造函数进行初始化。
Person person = new Person();
person.SetName("John");
person.SetAge(30);
person.DisplayInfo();

上述代码中,首先使用 new 关键字创建了一个 Person 对象,然后通过调用对象的公共方法设置姓名和年龄,并显示个人信息。

  1. 对象初始化器 对象初始化器是一种便捷的语法,允许在创建对象时同时初始化对象的属性。
public class Book
{
    public string Title { get; set; }
    public string Author { get; set; }
}

Book book = new Book
{
    Title = "C# Programming",
    Author = "Some Author"
};

在上述代码中,定义了 Book 类,并使用对象初始化器在创建 Book 对象时直接设置 TitleAuthor 属性的值。

  1. 集合初始化器 对于包含对象的集合,如 List<T>,可以使用集合初始化器来快速创建并初始化集合中的对象。
using System.Collections.Generic;

public class Product
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

List<Product> products = new List<Product>
{
    new Product { Name = "Laptop", Price = 1000m },
    new Product { Name = "Mouse", Price = 50m }
};

上述代码中,使用集合初始化器创建了一个 List<Product> 集合,并在集合中添加了两个 Product 对象,同时初始化了它们的属性。

类与对象的内存管理

  1. 栈和堆 在 C# 中,对象的内存分配涉及到栈和堆两个内存区域。栈主要用于存储局部变量和方法调用的上下文信息,它的特点是存取速度快,但空间有限。堆则用于存储对象实例,它的空间相对较大,但存取速度相对较慢。 当定义一个引用类型的变量时,变量本身存储在栈上,而变量所引用的对象实例存储在堆上。例如:
Person person; // 变量 person 存储在栈上,此时未指向任何对象
person = new Person(); // 使用 new 关键字在堆上创建 Person 对象,并将对象的引用赋给栈上的 person 变量
  1. 垃圾回收 C# 采用自动垃圾回收机制来管理对象的内存释放。当一个对象不再被任何变量引用时,它就成为了垃圾回收的候选对象。垃圾回收器会在适当的时候扫描堆内存,回收这些不再使用的对象所占用的内存空间,从而避免内存泄漏。
{
    Person person = new Person();
    // 对 person 进行操作
} // person 离开作用域,不再被引用,成为垃圾回收候选对象

在上述代码中,当 person 变量离开其作用域后,它所引用的 Person 对象不再被任何变量引用,垃圾回收器会在后续的某个时刻回收该对象所占用的内存。

嵌套类

嵌套类是指在一个类的内部定义另一个类。嵌套类可以访问外部类的所有成员,包括私有成员。嵌套类主要用于将相关的类组织在一起,提高代码的封装性和可读性。

public class OuterClass
{
    private int outerField = 10;

    public class InnerClass
    {
        public void DisplayOuterField()
        {
            OuterClass outer = new OuterClass();
            Console.WriteLine($"Outer field value: {outer.outerField}");
        }
    }
}

在上述代码中,InnerClassOuterClass 的嵌套类。InnerClassDisplayOuterField 方法可以访问 OuterClass 的私有字段 outerField。使用嵌套类时,需要通过外部类来创建内部类的对象。

OuterClass.InnerClass inner = new OuterClass.InnerClass();
inner.DisplayOuterField();

抽象类和抽象方法

  1. 抽象类 抽象类是一种不能被实例化的类,它主要用于作为其他类的基类,为子类提供一个通用的框架。抽象类可以包含抽象方法和非抽象方法。定义抽象类使用 abstract 关键字。
public abstract class Shape
{
    public abstract double CalculateArea();
    public void DisplayShapeType()
    {
        Console.WriteLine("This is a shape.");
    }
}

在上述 Shape 抽象类中,定义了一个抽象方法 CalculateArea,该方法没有方法体,子类必须重写该方法。同时还定义了一个非抽象方法 DisplayShapeType

  1. 抽象方法 抽象方法是在抽象类中声明的没有实现的方法,它只有方法签名,没有方法体。子类继承抽象类后,必须重写抽象类中的抽象方法,除非子类本身也是抽象类。
public class Circle : Shape
{
    private double radius;

    public Circle(double r)
    {
        radius = r;
    }

    public override double CalculateArea()
    {
        return Math.PI * radius * radius;
    }
}

在上述 Circle 类中,继承自 Shape 抽象类,并实现了 CalculateArea 抽象方法,提供了计算圆面积的具体实现。

密封类和密封方法

  1. 密封类 密封类使用 sealed 关键字修饰,它不能被其他类继承。密封类通常用于防止意外的继承,确保类的行为和实现不会被修改。
public sealed class FinalClass
{
    // 类的成员
}

在上述代码中,FinalClass 是一个密封类,任何试图继承它的操作都会导致编译错误。

  1. 密封方法 密封方法是在子类中使用 sealed 关键字修饰的重写方法,它阻止子类的子类进一步重写该方法。
public class BaseClass
{
    public virtual void SomeMethod()
    {
        Console.WriteLine("Base class method.");
    }
}

public class DerivedClass : BaseClass
{
    public sealed override void SomeMethod()
    {
        Console.WriteLine("Derived class method.");
    }
}

public class FurtherDerivedClass : DerivedClass
{
    // 以下代码会导致编译错误,因为 SomeMethod 在 DerivedClass 中已被密封
    // public override void SomeMethod()
    // {
    //     Console.WriteLine("Further derived class method.");
    // }
}

在上述代码中,DerivedClass 重写了 BaseClassSomeMethod 方法,并将其声明为密封方法。因此,FurtherDerivedClass 不能再重写 SomeMethod 方法。

通过以上对 C# 中类与对象的定义、实例化以及相关特性的详细介绍,希望能帮助开发者更深入地理解和运用 C# 进行面向对象编程,编写出更加健壮、可维护的代码。在实际编程中,合理运用这些知识,可以优化代码结构,提高代码的复用性和可扩展性。例如,在开发大型项目时,通过类的继承和多态,可以有效地组织和管理复杂的业务逻辑;利用静态类和静态成员,可以实现一些通用的工具方法,提高代码的执行效率和可维护性。同时,对对象实例化和内存管理的深入理解,有助于避免内存泄漏等问题,提升程序的性能。在编写嵌套类、抽象类和密封类时,要根据具体的业务需求进行合理设计,以达到最佳的编程效果。总之,熟练掌握 C# 中类与对象的相关知识,是成为一名优秀 C# 开发者的重要基础。