学前预知
OOP的不同点
需要转换学习的思路
- 传统C#语法:需要记住语法规则。
- OOP:需要深入理解和记忆编程的思想和原则。
OOP学什么?
- 学习各种编程的原则、方法、技巧、经验、模式、架构等。
OOP干什么?
- 没有OOP:无法用C#写出优质的程序。
- 比喻:就像厨师做出来的菜品是有“讲究的”,而一个什么都不懂的人做出的饭仅仅是“糊口用”。
OOP怎么学?
- 第一步:先学类和对象的使用基础,设计简单的类。
- 第二步:理解为什么要这么设计类,然后接着练习。
- 第三步:总结分析各种原则的要求,不断实践巩固。
OOP注意什么?
- 不要着急!
- 不要轻视!
结论
- 好的OOP程序:模块合理、结构清晰、程序规范、注释明确、运行流畅、维护容易、扩展方便。
OOP中的类与对象
记住
所有面向对象的编程语言,都是把我们要处理的“数据”和“行为”封装到类中。
面向对象编程(OOP)
- 设计类:根据需求设计各种类,为每一个类设计对应的“数据存储”和“操作内容”。
- 关联类:我们所设计的对象,它们之间是有一定关系的,正是按照这种关系,完成对象的交互。
- 使用类:根据我们的需要,使用我们所设计的类,使用的时候是通过对象方式调用。
代码演示
设计类
public class Person
{
// 数据存储:属性
public string Name { get; set; }
public int Age { get; set; }
// 操作内容:方法
public void Greet()
{
Console.WriteLine($"Hello, my name is {Name} and I am {Age} years old.");
}
}
关联类
public class Company
{
public string CompanyName { get; set; }
public List<Person> Employees { get; set; }
public void AddEmployee(Person employee)
{
if (Employees == null)
{
Employees = new List<Person>();
}
Employees.Add(employee);
}
public void IntroduceEmployees()
{
foreach (var employee in Employees)
{
employee.Greet();
}
}
}
使用类
class Program
{
static void Main(string[] args)
{
// 创建Person对象
Person person1 = new Person { Name = "Alice", Age = 30 };
Person person2 = new Person { Name = "Bob", Age = 25 };
// 创建Company对象并添加员工
Company company = new Company { CompanyName = "TechCorp" };
company.AddEmployee(person1);
company.AddEmployee(person2);
// 介绍公司员工
Console.WriteLine($"Welcome to {company.CompanyName}!");
company.IntroduceEmployees();
}
}
总结
- 设计类:封装数据和行为。
- 关联类:对象间的关系和交互。
- 使用类:通过对象调用类的方法和属性。
类的基本组成与对象
类的定义结构
访问修饰符 class 类名
{
// 定义字段部分
字段1的类型 字段1;
字段2的类型 字段2;
…
// 定义属性部分
属性1的类型 属性1 { get; set; }
属性2的类型 属性2 { get; set; }
…
// 定义方法部分
方法1;
方法2;
…
}
类名定义
- 类名定义:名词,要求首字母大写,避免单词缩写。
- 字段和属性:用来描述这个对象的静态信息(数据存储)。
- 方法和接口:用来说明这个对象的行为特征(方法操作)。
示例代码
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 { age = value; }
}
// 方法部分
public void Greet()
{
Console.WriteLine($"Hello, my name is {Name} and I am {Age} years old.");
}
}
总结
- 类:相关数据和特定方法“结构的封装”。
- 对象:按照类创建的一个具有“特定数据的实例”,通过对象调用类所封装的数据和方法。
使用对象的示例
class Program
{
static void Main(string[] args)
{
// 创建Person对象
Person person = new Person();
person.Name = "Alice";
person.Age = 30;
// 调用方法
person.Greet(); // 输出:Hello, my name is Alice and I am 30 years old.
}
}
这个示例展示了如何定义类及其字段、属性和方法,以及如何通过对象调用这些成员。
访问修饰符
类的访问修饰符
- 作用:限制这个类可被使用的范围。
- 类型:
- public:公开的,可以被项目中任何代码访问。
- internal:内部的,仅限于同一个程序集(项目)中访问。
类成员访问修饰符
- 作用:限制类的字段、属性、方法的可访问范围。
- 类型:
- private:私有的,仅供类的内部使用。
- public:公有的,可以通过对象从外部访问。
注意事项
- 方法内部的变量,我们称为“局部变量”,是没有访问修饰符的。
示例代码
// 类访问修饰符
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 { age = value; }
}
// 方法访问修饰符
public void Greet()
{
Console.WriteLine($"Hello, my name is {Name} and I am {Age} years old.");
}
}
// internal 类
internal class InternalClass
{
public void DisplayMessage()
{
Console.WriteLine("This is an internal class.");
}
}
使用示例
class Program
{
static void Main(string[] args)
{
// 创建public类的对象
Person person = new Person();
person.Name = "Alice";
person.Age = 30;
person.Greet(); // 输出:Hello, my name is Alice and I am 30 years old.
// 创建internal类的对象
InternalClass internalClass = new InternalClass();
internalClass.DisplayMessage(); // 输出:This is an internal class.
}
}
在这个示例中:
Person
类是公开的,可以在整个项目中访问。InternalClass
类是内部的,只能在同一个程序集(项目)中访问。name
和age
字段是私有的,仅在Person
类内部可以访问。Name
和Age
属性是公开的,可以通过Person
对象从外部访问。Greet
方法是公开的,可以通过Person
对象从外部访问。
认识类的属性(Property)
属性的使用
- 作用:在面向对象编程(OOP)中,属性用于封装数据,控制对字段的访问。
- 要求:
- 一般采用Pascal命名法(首字母大写)。
- 属性的数据类型与其对应的字段的数据类型一致。
- 通常使用
public
修饰符,以允许外部访问。
属性的定义
- 读取(get方法):属性通过
get
方法返回私有字段的值。 - 赋值(set方法):属性通过
set
方法借助value
关键字为私有字段赋值。 - 本质:属性本身不保存数据,而是提供了对私有字段的访问控制。字段才是实际的数据存储单元。
示例代码
public class Person
{
// 私有字段
private string name;
private int age;
// 公有属性
public string Name
{
get
{
return name; // 返回私有字段的值
}
set
{
name = value; // 借助value给私有字段赋值
}
}
public int Age
{
get
{
return age; // 返回私有字段的值
}
set
{
if (value >= 0) // 可选:添加值的验证逻辑
{
age = value; // 借助value给私有字段赋值
}
}
}
// 构造方法
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
说明
- 私有字段:
name
和age
是私有字段,用于实际的数据存储。 - 公有属性:
Name
和Age
是公有属性,用于控制对私有字段的访问。Name
的get
方法返回name
的值,set
方法设置name
的值。Age
的get
方法返回age
的值,set
方法在赋值前对value
进行验证,然后设置age
的值。
- 构造方法:用于初始化对象的属性。
使用示例
class Program
{
static void Main(string[] args)
{
// 创建Person对象并通过属性访问字段
Person person = new Person("Alice", 30);
Console.WriteLine(person.Name); // 输出:Alice
Console.WriteLine(person.Age); // 输出:30
person.Name = "Bob";
person.Age = 35;
Console.WriteLine(person.Name); // 输出:Bob
Console.WriteLine(person.Age); // 输出:35
}
}
在这个示例中,通过Person
对象的Name
和Age
属性,我们能够读取和设置私有字段name
和age
的值。这展示了属性如何在不直接访问字段的情况下封装数据。
属性特性1 - 扩展业务逻辑
在属性的get
和set
方法中添加业务逻辑
在属性的get
和set
方法中,我们可以添加任何需要的业务逻辑,以有效避免非法数据或实现其他功能。
示例代码
private int courseld = 0;
public int CourseId
{
get { return courseld; }
set
{
if (value < 0)
courseld = 1000;
else
courseld = value;
}
}
根据需要设置为只读属性
// 只读属性1,直接去掉set方法,并在定义的时候初始化
public string CourseName { get; } = ".NET全栈开发课程";
// 只读属性2,直接去掉set方法,并在get中添加业务逻辑
public string CourseInfo
{
get { return $"课程名称:{CourseName} 课程编号:{Courseld}"; }
}
属性和字段的总结
字段(成员变量)
-
内部使用:
- 字段主要用于类的内部数据交互,一般是私有的(
private
)。
- 字段主要用于类的内部数据交互,一般是私有的(
-
数据存储:
- 字段仅用于保存数据,其生命周期与对象相同,除非是静态字段。
-
读写不限:
- 我们可以对字段赋值或获取其值,常见且广泛使用(除了
readonly
)。
- 我们可以对字段赋值或获取其值,常见且广泛使用(除了
属性(字段封装)
-
外部使用:
- 属性通常用于向外提供数据访问接口,描述对象的静态特征,因此一般是公共的(
public
)。
- 属性通常用于向外提供数据访问接口,描述对象的静态特征,因此一般是公共的(
-
业务扩展:
- 属性内部可以包含业务逻辑,以确保数据的有效性和安全性。
-
读写可控:
- 属性可以设置为只读,从而更好地体现面向对象的封装特性和安全性。
使用
-
常规化使用:
- 对象的对外数据存储通常通过属性完成,以便调用者使用。
-
强制性使用:
- 公共字段在某些情况下可能导致数据解析问题(例如在数据网格视图(dgv)或下拉框中显示),此时使用属性可以更好地控制访问和展示。
什么是方法
关于方法
-
概念:
- 方法表示对象能够执行的操作或行为,封装了对象的功能。
-
类型:
- 实例方法:与对象实例关联,可以通过对象实例调用。
- 静态方法:与类本身关联,可以通过类名直接调用,不依赖于对象实例。
- 抽象方法:在抽象类中声明,没有实现,由子类提供具体实现。
- 虚方法:可以在基类中定义,并允许子类重写实现。
- 构造方法:特殊的方法,用于在对象创建时初始化对象的状态。
方法的使用示例
public class ExampleClass
{
// 实例方法
public void InstanceMethod()
{
Console.WriteLine("This is an instance method.");
}
// 静态方法
public static void StaticMethod()
{
Console.WriteLine("This is a static method.");
}
// 虚方法
public virtual void VirtualMethod()
{
Console.WriteLine("This is a virtual method in the base class.");
}
// 构造方法
public ExampleClass()
{
Console.WriteLine("Constructor: Object is created.");
}
}
// 继承 ExampleClass 的子类
public class DerivedClass : ExampleClass
{
// 重写虚方法
public override void VirtualMethod()
{
Console.WriteLine("This is a overridden virtual method in the derived class.");
}
}
class Program
{
static void Main(string[] args)
{
// 调用构造方法
ExampleClass example = new ExampleClass();
// 调用实例方法
example.InstanceMethod();
// 调用静态方法
ExampleClass.StaticMethod();
// 调用虚方法
example.VirtualMethod();
// 调用重写后的虚方法
DerivedClass derived = new DerivedClass();
derived.VirtualMethod();
}
}
方法特性总结
- 实例方法:通过对象实例调用,通常用于操作对象的状态或执行与对象相关的任务。
- 静态方法:通过类名调用,通常用于执行不依赖于对象实例的任务,或提供与特定对象无关的功能。
- 抽象方法:在抽象类中声明,强制子类实现,用于定义子类必须提供的功能。
- 虚方法:允许子类重写,以实现多态行为,基类提供默认实现。
- 构造方法:在对象创建时调用,用于初始化对象的状态。
构造方法的使用
构造方法的作用
- 初始化:
- 因为构造方法在对象创建时被调用,我们可以在此时完成一些初始化任务。
- 初始化任务包括给对象的属性赋值,或者从其他对象、文本等获取基础数据。
构造方法的类型
-
无参数的构造方法:
- 通常用于直接初始化对象的属性或某些不变的数据。
-
有参数的构造方法:
- 允许对象创建者传递要初始化的相关数据,使对象在创建时具有自定义的初始状态。
构造方法的使用示例
public class Person
{
// 属性
public string Name { get; set; }
public int Age { get; set; }
// 无参数的构造方法
public Person()
{
// 初始化属性为默认值
Name = "Unknown";
Age = 0;
Console.WriteLine("No-parameter constructor called.");
}
// 有参数的构造方法
public Person(string name, int age)
{
// 使用传递的参数初始化属性
Name = name;
Age = age;
Console.WriteLine("Parameterized constructor called.");
}
// 显示信息的方法
public void DisplayInfo()
{
Console.WriteLine($"Name: {Name}, Age: {Age}");
}
}
class Program
{
static void Main(string[] args)
{
// 调用无参数的构造方法
Person person1 = new Person();
person1.DisplayInfo();
// 调用有参数的构造方法
Person person2 = new Person("Alice", 30);
person2.DisplayInfo();
}
}
总结
-
无参数的构造方法:
- 适用于直接初始化对象属性为默认值或不变的数据。
- 示例:
Person person1 = new Person();
-
有参数的构造方法:
- 适用于在对象创建时提供自定义初始化数据。
- 示例:
Person person2 = new Person("Alice", 30);
构造方法的使用使得对象在创建时可以被合理地初始化,从而确保对象处于有效的初始状态。
对象初始化器的使用
关于对象初始化器
- 引入:对象初始化器在 C# 3.0 时代引入。
- 作用:允许在对象创建时,更加灵活地初始化对象的属性。
对象初始化器的使用使得代码更加简洁和易读,不需要为每个属性赋值调用构造方法或者单独的赋值语句。
对象初始化器的示例
基本示例
public class Person
{
// 自动属性
public string Name { get; set; }
public int Age { get; set; }
}
class Program
{
static void Main(string[] args)
{
// 使用对象初始化器初始化属性
Person person = new Person
{
Name = "Alice",
Age = 30
};
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
}
结合构造方法的示例
对象初始化器可以与构造方法结合使用,以提供更灵活的对象初始化方式:
public class Person
{
// 自动属性
public string Name { get; set; }
public int Age { get; set; }
// 有参数的构造方法
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
class Program
{
static void Main(string[] args)
{
// 使用对象初始化器和构造方法
Person person = new Person("Alice", 30)
{
// 可以在构造方法之后进一步初始化属性
Name = "Bob",
Age = 35
};
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
}
总结
- 对象初始化器:
- 允许在创建对象时直接设置属性值,语法更加简洁。
- 可以与构造方法结合使用,提供更灵活的初始化方式。
对象初始化器使得对象的初始化过程更加直观和简洁,尤其是在需要初始化多个属性时非常有用。
构造方法和对象初始化器总结
构造方法
- 存在的必要性:每个类至少有一个构造方法,用于初始化对象。
- 调用的特殊性:只能在对象创建时通过
new
关键字调用。 - 使用的强制性:对象的创建必须调用指定的构造方法,并且构造方法参数必须匹配。
- 语法的特殊性:构造方法没有返回值,方法名称必须与类名相同。
构造方法 VS 对象初始化器
- 相同点:都用于完成对象属性的初始化。
- 不同点:
- 有无强制性:构造方法是必须的,而对象初始化器是可选的,可以根据需要选择使用。
- 使用的范围:对象初始化器仅用于初始化属性,而构造方法可以执行更复杂的初始化逻辑,包括计算、赋值等操作。
- 使用的位置:对象初始化器在创建对象时使用,而构造方法必须在类的定义中声明。
- 出现的时间:构造方法从 .NET 1.0 版本开始就存在,而对象初始化器是在 .NET 3.0 版本引入的新特性。
构造方法提供了初始化对象所需的基本机制,而对象初始化器则为简化对象初始化过程提供了便利,使得代码更加清晰和易于维护。在选择使用时,可以根据需求和代码风格来决定使用哪种方式进行对象属性的初始化。
对象的销毁和垃圾回收(GC)
对象的生命周期
- 生命周期:对象在内存中存在一段时间,具有其生命周期。
- 状态:对象可能处于以下状态之一:
垃圾回收机制(GC)
- 作用:.NET虚拟机的垃圾回收机制自动运行,并监控对象的引用状态。
- 回收过程:当垃圾回收器发现对象不再被任何引用时,它将释放对象占据的内存空间,这个过程称为垃圾回收或销毁。
- 释放内存:垃圾回收器确保已经不再使用的对象被及时地销毁,以释放内存供其他对象使用。
总结
对象在内存中的生命周期和状态由垃圾回收机制(GC)管理和决定。该机制负责监控和释放不再被引用的对象,以确保内存的有效利用和程序的稳定性。程序员无需手动管理对象的销毁,垃圾回收机制会自动处理这些事务,提高了代码的可维护性和开发效率。
析构函数
析构函数在C++中用于对象资源的释放,当使用delete
删除对象时会自动调用。在托管环境(如.NET)中,对象的销毁由垃圾回收器负责,虚拟机会处理资源释放,不同于显式的析构函数
方法定义与使用规范
- 定义规范:
- 访问修饰符: 默认
private
,建议显式声明,可根据需要定义为public
。 - 方法名: 一般用动词或动宾短语,采用Pascal命名法(首字母大写),不能以数字开头。
- 方法参数: 根据需要添加参数,可以没有。
- 返回值: 使用
return
返回值,return
语句后不能有其他语句;如果没有返回值,用void
。
- 访问修饰符: 默认
public int Add(int a, int b) {
return a + b;
}
public void PrintMessage() {
Console.WriteLine("Hello, World!");
}
- 调用规范:
- 语法:
对象名.方法名(参数1, 参数2, ...)
- 语法:
MyClass obj = new MyClass();
int result = obj.Add(5, 3);
obj.PrintMessage();
方法重载
好处
- 减少类的对外接口: 可以只暴露一个方法名,减少类的复杂度。
- 便于用户使用: 提供相同功能的方法名称,方便用户识别和调用。
条件
- 方法重载需要满足以下条件:
- 方法的名称必须一样。
- 方法的参数个数或类型至少有一个不同。
方法重载的无关性
- 与返回值无关: 方法重载与返回值无关,可以有相同或不同的返回值类型。
示例:
// 方法重载示例
public class Calculator {
public int Add(int a, int b) {
return a + b;
}
public double Add(double a, double b) {
return a + b;
}
public int Add(int a, int b, int c) {
return a + b + c;
}
}
在上述示例中,Calculator
类展示了三个重载的 Add
方法,分别接受不同数量或类型的参数,但都提供了相同的功能。
静态方法及重载
关键字 static
的使用
static
可以修饰类、方法和成员变量。修饰后的类、方法和字段称为静态类、静态方法、静态字段。
静态方法的调用
- 使用格式:
类名.方法名
静态成员使用经验
- 静态成员在程序运行时被加载到内存中,并在系统关闭前不会被垃圾回收(GC)回收。
- 类的成员如果使用频繁,可以考虑用
static
修饰,但不要使用过多。 - 静态成员不能直接调用实例成员(静态方法不能直接调用实例方法)。
- 静态方法也可以重载。
示例:
public class MathUtils {
// 静态方法重载
public static int Multiply(int a, int b) {
return a * b;
}
public static double Multiply(double a, double b) {
return a * b;
}
public static int Multiply(int a, int b, int c) {
return a * b * c;
}
}
// 调用静态方法
public class Main {
public static void main(String[] args) {
int product1 = MathUtils.Multiply(2, 3); // 调用第一个重载方法
double product2 = MathUtils.Multiply(2.5, 3.5); // 调用第二个重载方法
int product3 = MathUtils.Multiply(2, 3, 4); // 调用第三个重载方法
Console.WriteLine((product1); // 输出:6
Console.WriteLine((product2); // 输出:8.75
Console.WriteLine((product3); // 输出:24
}
}
在上述示例中,MathUtils
类包含了多个重载的 Multiply
静态方法,可以通过类名直接调用这些方法。
2024-8-2 未完待续