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

C#面向对象编程基础

2024-03-271.7k 阅读

C# 面向对象编程基础

类与对象的概念

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

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

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

    // 构造函数
    public Person(string name, int age)
    {
        this.name = name;
        this.age = age;
    }

    // 方法
    public void Introduce()
    {
        Console.WriteLine($"我叫 {name},今年 {age} 岁。");
    }
}

然后在 Main 方法中创建 Person 类的对象:

class Program
{
    static void Main()
    {
        Person person1 = new Person("张三", 25);
        person1.Introduce();
    }
}

在上述代码中,Person 类定义了 nameage 两个字段,以及一个构造函数和一个 Introduce 方法。person1Person 类的一个对象,通过调用 Introduce 方法,输出对象的相关信息。

类的成员

  1. 字段:字段是类中的变量,用于存储数据。字段可以是任何数据类型,如上述 Person 类中的 nameage。字段可以有访问修饰符来控制其访问级别。例如,使用 private 修饰符,只有类内部的成员可以访问该字段。

  2. 属性:属性提供了一种灵活的机制来读取、写入或计算私有字段的值。属性具有访问器(get 和 set),可以控制对字段的访问逻辑。例如,修改 Person 类添加属性:

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)
            {
                age = value;
            }
            else
            {
                Console.WriteLine("年龄不能为负数。");
            }
        }
    }

    public Person(string name, int age)
    {
        Name = name;
        Age = age;
    }

    public void Introduce()
    {
        Console.WriteLine($"我叫 {Name},今年 {Age} 岁。");
    }
}

在这个例子中,NameAge 属性通过 getset 访问器来控制对 nameage 字段的访问。Age 属性的 set 访问器添加了年龄不能为负数的逻辑。

  1. 方法:方法是类中执行特定任务的代码块。方法可以有参数和返回值。例如 Person 类中的 Introduce 方法,它没有参数,返回值类型为 void,用于输出对象的信息。

  2. 构造函数:构造函数是一种特殊的方法,用于在创建对象时初始化对象的状态。构造函数与类名相同,没有返回值类型。如上述 Person 类中的构造函数,用于初始化 nameage 字段。

访问修饰符

  1. public:公共访问修饰符,被 public 修饰的成员可以从任何地方访问。例如,如果将 Person 类中的 Introduce 方法改为 public,在程序的任何地方创建 Person 对象后都可以调用该方法。

  2. private:私有访问修饰符,被 private 修饰的成员只能在类内部访问。如 Person 类中的 nameage 字段,外部无法直接访问,只能通过属性或方法间接访问。

  3. protected:受保护访问修饰符,被 protected 修饰的成员可以在类本身及其派生类中访问。例如,如果有一个 Student 类继承自 Person 类,那么 Student 类可以访问 Person 类中被 protected 修饰的成员。

  4. internal:内部访问修饰符,被 internal 修饰的成员可以在同一程序集内的任何类中访问。程序集可以理解为一个 DLL 文件或一个可执行文件。如果项目中有多个类库,不同类库中的类默认无法访问 internal 成员,除非它们在同一个程序集里。

  5. protected internal:这个修饰符表示成员既可以在同一程序集内访问,也可以在派生类中访问,即使派生类在不同的程序集里。

继承

继承是面向对象编程的重要特性之一,它允许一个类从另一个类中获取成员。被继承的类称为基类,继承的类称为派生类。派生类可以扩展和修改基类的成员。

例如,我们定义一个 Student 类继承自 Person 类:

public class Student : Person
{
    private string studentId;

    public Student(string name, int age, string studentId) : base(name, age)
    {
        this.studentId = studentId;
    }

    public void Study()
    {
        Console.WriteLine($"{Name} 正在学习,学号是 {studentId}。");
    }
}

在上述代码中,Student 类继承自 Person 类,通过 : base(name, age) 调用了 Person 类的构造函数来初始化从 Person 类继承的字段。Student 类还添加了自己的 studentId 字段和 Study 方法。

Main 方法中使用 Student 类:

class Program
{
    static void Main()
    {
        Student student1 = new Student("李四", 20, "1001");
        student1.Introduce();
        student1.Study();
    }
}

这里 student1 不仅可以调用 Student 类自身的 Study 方法,还可以调用从 Person 类继承的 Introduce 方法。

多态

多态是指同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在 C# 中,多态主要通过虚方法、重写和抽象类来实现。

  1. 虚方法与重写:在基类中使用 virtual 关键字修饰的方法称为虚方法,虚方法可以在派生类中使用 override 关键字进行重写。例如,修改 Person 类和 Student 类:
public class Person
{
    private string name;
    private int age;

    public Person(string name, int age)
    {
        this.name = name;
        this.age = age;
    }

    public virtual void Introduce()
    {
        Console.WriteLine($"我叫 {name},今年 {age} 岁。");
    }
}

public class Student : Person
{
    private string studentId;

    public Student(string name, int age, string studentId) : base(name, age)
    {
        this.studentId = studentId;
    }

    public override void Introduce()
    {
        Console.WriteLine($"我是学生 {base.Name},今年 {base.Age} 岁,学号是 {studentId}。");
    }

    public void Study()
    {
        Console.WriteLine($"{base.Name} 正在学习,学号是 {studentId}。");
    }
}

Main 方法中:

class Program
{
    static void Main()
    {
        Person person1 = new Person("张三", 25);
        Person student2 = new Student("李四", 20, "1001");

        person1.Introduce();
        student2.Introduce();
    }
}

这里 person1.Introduce() 调用的是 Person 类的 Introduce 方法,而 student2.Introduce() 调用的是 Student 类重写后的 Introduce 方法,尽管 student2 被声明为 Person 类型。这就是多态的体现,相同的方法调用,根据对象的实际类型执行不同的实现。

  1. 抽象类与抽象方法:抽象类是一种不能被实例化的类,它主要为其他类提供一个通用的基类。抽象类中可以包含抽象方法,抽象方法只有声明,没有实现,必须在派生类中重写。例如:
public abstract class Shape
{
    public abstract double Area();
}

public class Circle : Shape
{
    private double radius;

    public Circle(double radius)
    {
        this.radius = radius;
    }

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

public class Rectangle : Shape
{
    private double width;
    private double height;

    public Rectangle(double width, double height)
    {
        this.width = width;
        this.height = height;
    }

    public override double Area()
    {
        return width * height;
    }
}

Main 方法中:

class Program
{
    static void Main()
    {
        Shape circle = new Circle(5);
        Shape rectangle = new Rectangle(4, 6);

        Console.WriteLine($"圆的面积: {circle.Area()}");
        Console.WriteLine($"矩形的面积: {rectangle.Area()}");
    }
}

这里 Shape 是抽象类,Area 是抽象方法。CircleRectangle 类继承自 Shape 类并实现了 Area 方法。通过这种方式,不同形状的面积计算通过相同的 Area 方法调用,但根据对象的实际类型执行不同的计算逻辑,体现了多态性。

接口

接口是一种特殊的抽象类型,它只包含方法、属性、事件或索引器的签名,不包含实现。一个类可以实现多个接口,这使得 C# 能够实现类似于多重继承的功能。

例如,定义一个 IMovable 接口:

public interface IMovable
{
    void Move();
}

然后让 Car 类和 Person 类实现该接口:

public class Car : IMovable
{
    public void Move()
    {
        Console.WriteLine("汽车在行驶。");
    }
}

public class Person : IMovable
{
    public void Move()
    {
        Console.WriteLine("人在行走。");
    }
}

Main 方法中:

class Program
{
    static void Main()
    {
        IMovable car = new Car();
        IMovable person = new Person();

        car.Move();
        person.Move();
    }
}

这里 CarPerson 类都实现了 IMovable 接口的 Move 方法。通过接口,不同类型的对象可以具有相同的行为定义,这在大型项目中有助于代码的组织和扩展。例如,可以将实现了 IMovable 接口的对象存储在一个集合中,然后统一调用 Move 方法,而不需要关心对象的具体类型。

封装

封装是将数据和操作数据的方法绑定在一起,并对外部隐藏对象的内部实现细节。在 C# 中,通过访问修饰符和属性来实现封装。

Person 类为例,nameage 字段被声明为 private,外部无法直接访问。通过属性 NameAge 提供了对这些字段的访问接口,并且在属性的访问器中可以添加验证逻辑,如 Age 属性的 set 访问器对年龄进行了合法性检查。这样就保护了对象的内部状态,防止外部代码对其进行非法修改。

结构体

结构体是一种值类型,它与类类似,但有一些重要的区别。结构体在栈上分配内存,而类在堆上分配内存。

例如,定义一个 Point 结构体:

public struct Point
{
    public int X;
    public int Y;

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public void Print()
    {
        Console.WriteLine($"X: {X}, Y: {Y}");
    }
}

Main 方法中使用结构体:

class Program
{
    static void Main()
    {
        Point point1 = new Point(10, 20);
        point1.Print();
    }
}

结构体可以有字段、属性、方法和构造函数,但不能继承其他结构体或类(除了 System.ValueType),也不能被其他类继承。由于结构体是值类型,当将一个结构体变量赋值给另一个结构体变量时,会进行值的复制,而不是引用的复制。

枚举

枚举是一种用户定义的类型,它由一组命名的常量组成。枚举提供了一种方便的方式来定义一组相关的常量值。

例如,定义一个表示星期几的枚举:

public enum DayOfWeek
{
    Sunday,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

在类中使用枚举:

public class Schedule
{
    private DayOfWeek day;

    public Schedule(DayOfWeek day)
    {
        this.day = day;
    }

    public void PrintSchedule()
    {
        Console.WriteLine($"今天是 {day}。");
    }
}

Main 方法中:

class Program
{
    static void Main()
    {
        Schedule schedule1 = new Schedule(DayOfWeek.Monday);
        schedule1.PrintSchedule();
    }
}

枚举类型的默认基础类型是 int,每个枚举成员都有一个对应的整数值,默认从 0 开始依次递增。也可以显式指定枚举成员的值,例如:

public enum DayOfWeek
{
    Sunday = 1,
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday
}

这样 Sunday 的值为 1,Monday 的值为 2,以此类推。

总结

C# 的面向对象编程基础涵盖了类与对象、成员、访问修饰符、继承、多态、接口、封装、结构体和枚举等重要概念。这些概念相互配合,构成了 C# 强大的面向对象编程体系。通过合理运用这些知识,开发者可以创建出高效、可维护、可扩展的软件系统。无论是小型的控制台应用程序,还是大型的企业级项目,掌握这些基础都是至关重要的。在实际编程中,需要根据具体的需求和场景,灵活选择和应用这些面向对象的特性,以达到最佳的编程效果。例如,在设计一个游戏角色系统时,可以利用类的继承和多态来实现不同类型角色的共同行为和独特行为;在开发一个数据处理模块时,可以通过封装来保护数据的完整性和安全性。随着对 C# 面向对象编程的深入学习和实践,开发者能够更好地应对各种复杂的编程任务,创造出高质量的软件产品。