C# 普通类、抽象类、普通方法、抽象方法、虚方法、重写方法、接口及案例

C# 面向对象编程概念总结

本文总结了 C# 中的几个重要面向对象编程概念,包括普通类、抽象类、普通方法、抽象方法、虚方法、重写方法、接口等,帮助更好地理解它们的适用场景及用法。

1. 普通类(Class)

普通类是 C# 中最基础的类,用于创建对象和定义对象的属性、方法。普通类可以被实例化(使用 new 关键字),包含成员变量、方法、构造函数等,适用于描述具体的实体。

示例代码

public class Animal
{
    public string Name { get; set; }

    public void Speak()
    {
        Console.WriteLine($"{Name} is speaking.");
    }
}

使用场景:适用于描述具体对象的特性和行为。例如,定义 Animal 类,描述动物的名称、行为。

2. 抽象类(Abstract Class)

抽象类是一个不能直接实例化的类,通常用于定义一组通用属性和方法(包括抽象方法和虚方法),并在派生类中具体实现。抽象类可以包含已实现的方法和未实现的抽象方法。

示例代码

public abstract class Shape
{
    public string Color { get; set; }
    public abstract double CalculateArea();
}

使用场景:适用于定义通用特征和行为,供不同派生类实现。例如,Shape 类作为所有形状的抽象类,不同形状(如圆形、矩形)具体实现面积计算。

3. 普通方法(Regular Method)

普通方法是类中默认的非虚方法,可以直接被调用,且在派生类中无需重写。普通方法在编译时被确定,不支持多态。
示例代码

public class Car
{
    public void StartEngine()
    {
        Console.WriteLine("Engine started.");
    }
}

使用场景:适用于不需要在派生类中重写的方法,实现类的基础功能。

4. 抽象方法(Abstract Method)

抽象方法只能在抽象类中声明,且没有方法体(实现)。派生类必须重写抽象方法以实现具体功能。抽象方法定义的行为需在不同派生类中有不同实现。

public abstract class Animal
{
    public abstract void MakeSound();
}

public class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Bark");
    }
}

使用场景:适用于描述必须在派生类中实现的功能。例如 MakeSound 方法,要求不同动物有不同的叫声。

5.虚方法(Virtual Method)

虚方法是一种可被重写的方法,它有默认的实现,但允许派生类选择性地重写它。虚方法允许实现多态(polymorphism)。

public class Animal
{
    public virtual void Move()
    {
        Console.WriteLine("The animal moves.");
    }
}

public class Bird : Animal
{
    public override void Move()
    {
        Console.WriteLine("The bird flies.");
    }
}

使用场景:适用于希望在派生类中重写,但不强制要求重写的方法。

6.重写方法(Override Method)

重写方法用于在派生类中对基类虚方法或抽象方法进行重写,以实现具体行为。重写方法需要用 override 关键字,并且方法签名需与基类一致。

public class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("Meow");
    }
}

使用场景:适用于在派生类中提供基类方法的具体实现。

7. 接口(Interface)

接口定义了一组方法和属性的规范,但不包含实现。类可以实现一个或多个接口(多继承),从而保证实现接口所定义的所有成员。接口可以被实现类在任意层级实现,打破了类的单一继承限制。

public interface IFlyable
{
    void Fly();
}

public class Bird : IFlyable
{
    public void Fly()
    {
        Console.WriteLine("Bird is flying.");
    }
}

使用场景:适用于定义行为契约,实现解耦和灵活性。例如,IFlyable 接口用于所有可以飞的类,实现飞行功能。

概念 是否可实例化 是否可继承 是否必须重写 是否有方法实现 是否多继承
普通类(Class)
抽象类(Abstract Class) 抽象方法必须重写 是,部分实现
接口(Interface) 否,需实现

8.综合案例

为帮助更好地理解 C# 中的面向对象概念,我们将通过一个具体案例演示普通类、抽象类、普通方法、抽象方法、虚方法、重写方法、接口等概念,并延伸讨论相关问题。以下是一个宠物商店的案例,用于表示不同类型的宠物(如猫和狗)的行为和特征。
案例设计
假设我们要开发一个宠物商店管理系统,系统中包含不同类型的宠物,它们拥有一些共有特征和不同的行为。具体需求如下:

  1. 所有宠物都需要有一个名称和移动方式。
  2. 所有宠物都可以发出声音,但不同宠物的叫声不同。
  3. 会飞的宠物需要实现飞行功能。
  4. 具体宠物(如猫和狗)需要提供具体的实现。

通过这一案例,我们可以演示多个 C# 中的面向对象编程概念。

using System;

// 定义接口:用于具有飞行能力的宠物
public interface IFlyable
{
    void Fly();  // 没有实现的抽象方法,由实现类来具体定义
}

// 抽象类:表示宠物的通用特征
public abstract class Pet
{
    public string Name { get; set; }

    // 抽象方法:必须在派生类中实现
    public abstract void MakeSound();

    // 虚方法:派生类可以选择重写
    public virtual void Move()
    {
        Console.WriteLine("The pet moves in its own way.");
    }
}

// 普通类:表示具体的宠物类型“猫”,继承抽象类 Pet
public class Cat : Pet
{
    public override void MakeSound()
    {
        Console.WriteLine($"{Name} says: Meow");
    }

    public override void Move()
    {
        Console.WriteLine($"{Name} moves gracefully.");
    }
}

// 普通类:表示具体的宠物类型“狗”,继承抽象类 Pet
public class Dog : Pet
{
    public override void MakeSound()
    {
        Console.WriteLine($"{Name} says: Woof");
    }

    // 不重写 Move 方法,使用父类的默认实现
}

// 普通类:表示具有飞行能力的“鹦鹉”,同时继承 Pet 抽象类并实现 IFlyable 接口
public class Parrot : Pet, IFlyable
{
    public override void MakeSound()
    {
        Console.WriteLine($"{Name} says: Squawk");
    }

    public void Fly()
    {
        Console.WriteLine($"{Name} is flying high!");
    }
}

// 具体的宠物商店类,用于展示多态性
public class PetShop
{
    public void PetSound(Pet pet)
    {
        pet.MakeSound();  // 调用不同宠物的叫声
    }

    public void PetMove(Pet pet)
    {
        pet.Move();  // 调用不同宠物的移动方式
    }

    public void PetFly(IFlyable pet)
    {
        pet.Fly();  // 调用具有飞行能力宠物的飞行方法
    }
}

// 主程序
public class Program
{
    public static void Main()
    {
        PetShop shop = new PetShop();

        Cat cat = new Cat { Name = "Whiskers" };
        Dog dog = new Dog { Name = "Rex" };
        Parrot parrot = new Parrot { Name = "Polly" };

        shop.PetSound(cat);
        shop.PetSound(dog);
        shop.PetSound(parrot);

        shop.PetMove(cat);
        shop.PetMove(dog);
        shop.PetMove(parrot);

        shop.PetFly(parrot);  // 只有实现了 IFlyable 的宠物才能飞行
    }
}

代码解释

  • 接口 IFlyable:定义了 Fly() 方法,任何会飞的宠物类都可以实现该接口。
  • 抽象类 Pet:作为宠物的基类,包含所有宠物的共有特性 Name,并定义了 MakeSound() 抽象方法和 Move() 虚方法。
  • 具体类 Cat 和 Dog:继承自 Pet,分别重写 MakeSound() 方法来实现具体叫声。
  • 具体类 Parrot:不仅继承 Pet 类,还实现了 IFlyable 接口。它提供 Fly() 方法的实现来满足接口的要求,并提供 MakeSound() 和 Move() 的自定义实现。
  • 宠物商店 PetShop:展示了多态性的应用,能够接收不同类型的 Pet 对象并调用它们各自的行为。

问题与解答

  1. 普通类与抽象类的区别?何时选择继承普通类或抽象类?
    区别:
    普通类可以直接实例化,而抽象类不能直接实例化。
    抽象类可以包含抽象方法(没有方法体),必须在派生类中实现。普通类中的所有方法都必须具有实现。
    何时选择:
    如果父类只是提供一些具体功能而不需要被重写,则可以用普通类。
    如果父类作为一种通用类型用于定义共性,并需要子类提供具体实现时,选择抽象类。比如 Pet 是抽象类,用于定义宠物的共性和行为框架。

  2. 抽象方法和虚方法的区别?什么时候使用?
    区别:
    抽象方法没有方法体,必须在派生类中实现。
    虚方法有一个默认实现,子类可以选择是否重写。
    使用场景:
    当你想确保所有子类必须实现某个方法时,使用抽象方法(如 MakeSound())。
    当父类提供默认实现,但允许子类选择性覆盖时,使用虚方法(如 Move())。

  3. 接口和抽象类的区别?
    区别:
    接口定义了一组方法和属性,所有成员都是抽象的,不能包含任何实现。
    抽象类可以包含抽象方法和已实现的方法。
    一个类可以实现多个接口,但只能继承一个抽象类。
    使用场景:
    当需要多继承时,选择接口(如 IFlyable)。
    当只需要单一继承,并且希望包含部分实现和状态时,选择抽象类(如 Pet)。

  4. 为什么需要重写方法?
    重写方法使得子类可以对父类的行为进行更改,以满足具体子类的需求。例如,Cat 和 Dog 都重写了 MakeSound() 来发出不同的声音。这种机制称为多态性,允许同一个方法在不同对象上表现不同的行为。

  5. 是否可以同时使用接口和抽象类?
    可以。类可以继承一个抽象类并实现多个接口。例如 Parrot 类继承 Pet 抽象类(实现通用行为)并实现 IFlyable 接口(扩展飞行行为)。这种设计满足了代码复用和功能扩展的需求。

扩展概念:抽象工厂模式

如果我们希望根据不同条件创建不同的宠物,可以使用抽象工厂模式。例如,根据用户选择的宠物类型创建猫、狗或鹦鹉,可以创建一个 PetFactory 抽象工厂类,根据不同实现生成不同的宠物对象。这是一种常用的设计模式,用于封装对象创建逻辑,提升系统的灵活性和扩展性。

其它

TODO 将此鸭子代码和总结移动到单独的一个章节

这个代码展示了 C# 中通过继承和接口来实现多态的用法。通过 Duck 基类、IBark 接口以及各个具体鸭子的实现类,它区分了不同鸭子的“游泳”与“叫声”行为。

class Program
{
    static void Main(string[] args)
    {
        //有:rubberDuck橡皮鸭子 woodDuck木头鸭子 realDuck真实鸭子
        //三种鸭子都会游泳,橡皮鸭子和真实鸭子会叫,但是叫声不一样
        //橡皮鸭子:唧唧叫,真实鸭子:嘎嘎叫,木头鸭子不会叫
        Console.WriteLine("Hello World!");
        IBark bark1 = new RealDuck();
        bark1.Bark();

        IBark bark2 = new RubberDuck();
        bark2.Bark();

        Console.WriteLine("===========================================");
        //===========================================
       
        RubberDuck duck1 = new RubberDuck();
        RealDuck duck2 = new RealDuck();
        WoodDuck duck3 = new WoodDuck();
        //接口实现多态
        IBark bark = duck1;//接口指向实现类

        //虚方法实现多态
        Duck duck = duck1;

        duck.Swim();
        bark.Bark();

    }
}
public class Duck
{
    public virtual void Swim()
    {
        Console.WriteLine("鸭子会游泳");
    }
}

public interface IBark
{
    void Bark();
}

public class RubberDuck : Duck, IBark
{
    public void Bark()
    {
        Console.WriteLine("橡皮鸭子唧唧的叫");
    }

    public override void Swim()
    {
        Console.WriteLine("橡皮鸭子会游泳");
    }
}

public class RealDuck : Duck, IBark
{
    public void Bark()
    {
        Console.WriteLine("真实鸭子嘎嘎的叫");
    }
    public override void Swim()
    {
        Console.WriteLine("真实鸭子会游泳");
    }
}

public class WoodDuck : Duck
{
    public override void Swim()
    {
        Console.WriteLine("木头鸭子会游泳");
    }
}

代码分析
核心设计思路:

Duck 是所有鸭子的基类,实现了所有鸭子的共同行为 Swim(游泳)。不同鸭子的 Swim 行为可以通过 虚方法来重写。

IBark 是一个接口,代表鸭子的叫声行为。不强制所有鸭子必须实现叫声,因此 Duck 基类没有实现 IBark,而是让不同的鸭子类(如 rubberDuck 和 realDuck)自行实现。
类与接口定义:

Duck 基类:定义了一个虚方法 Swim,其子类可以重写该方法来实现不同的游泳行为。
IBark 接口:定义了 Bark 方法,要求实现类实现各自的“叫声”行为。

各种鸭子类(rubberDuck、realDuck 和 woodDuck):
rubberDuck 和 realDuck 实现了 IBark 接口,并实现了各自的 Bark 方法来模拟不同的叫声。

woodDuck 只重写了 Swim,不实现 IBark,因为木头鸭子不会叫。
主方法分析:

代码中使用了接口和基类引用来实现多态,具体如下:
通过 IBark 接口实现多态:bark1 和 bark2 分别指向了 realDuck 和 rubberDuck 实例,调用 Bark 方法时,会调用各自实现的叫声方法。
通过基类引用实现多态:Duck duck = duck1; 赋值语句将 rubberDuck 赋给 Duck 类型变量 duck,然后调用 Swim() 方法,这样 Swim 的实际调用取决于 duck1 的类型,即会调用 rubberDuck 的 Swim 重写方法。

核心概念
接口多态:

通过 IBark bark1 = new realDuck(); 赋值实现接口多态。这个接口可以指向任何实现了 IBark 接口的类的实例,并根据实例的不同调用不同的 Bark 方法。
接口多态的优点是:可以定义鸭子叫的统一接口,而不限定所有鸭子都必须实现此接口。
虚方法多态:

Duck 基类中的 Swim 方法是虚方法,各个鸭子类可以重写它,以实现自己的游泳方式。
在调用 duck.Swim() 时,系统会根据 duck 的实际类型调用相应的重写方法(如 rubberDuck 和 realDuck 各自的 Swim 实现)。

问题与解答
如果木头鸭子也实现 IBark 接口但不重写 Bark 方法,会怎样?
Bark 方法是接口中的方法,所有实现了 IBark 的类必须实现该方法,否则会编译错误。因此不能不重写 Bark 方法。

为什么要使用接口而不是直接在 Duck 基类中定义 Bark?
并非所有鸭子都需要“叫”行为(如木头鸭子)。使用接口 IBark 可以选择性地让特定鸭子实现 Bark,提高了设计的灵活性。

为什么使用虚方法而不是抽象方法?
虚方法 Swim 提供了一个默认实现。如果所有鸭子的游泳行为都需要自定义,Swim 可以定义为抽象方法,强制子类必须实现。

抽象方法是否必须在抽象类中定义?
是的,抽象方法必须在抽象类中定义。因为抽象方法没有实现,只提供方法签名,因此抽象类是未完全实现的类,不能直接实例化,必须通过继承来实现。

将 Duck 类改成抽象类的影响:
如果 Duck 变成抽象类,Swim 方法可以改为抽象方法,这样 Duck 本身将没有 Swim 的默认实现。
改成抽象类后,Duck 就不能实例化,所有子类必须实现抽象的 Swim 方法(如果它是抽象的),即每个具体鸭子类都要提供自己版本的 Swim。

普通方法与虚方法的选择:
普通方法:用于实现不希望被子类重写的逻辑。普通方法可以直接继承使用,但无法在子类中重写。
虚方法:用于定义在基类中有一个默认实现,但允许子类根据需要进行重写的方法。如果方法的逻辑可以在基类中直接复用,那么定义为普通方法即可;如果希望子类可以灵活调整实现,定义为虚方法。
虚方法提供了可扩展性,允许子类选择性地重写默认逻辑。

子类继承后对普通方法的处理:
子类可以直接使用父类的普通方法。
子类不能重写父类的普通方法,除非该方法被定义为虚方法。

将 Swim 改成抽象方法的效果:
如果 Swim 是抽象方法,则 Duck 也必须是抽象类,这样所有鸭子子类都必须实现 Swim 方法,而 Duck 中将不再有默认的 Swim 实现。
定义 Swim 为抽象方法适用于“所有子类的 Swim 行为都不同”的场景,如果有多个子类共享同样的 Swim 行为,虚方法更合适。

用接口代替抽象基类:
可以考虑定义一个鸭子接口,如 IDuck,包含 Swim 和 Bark 方法。每个具体的鸭子类可以实现该接口并提供自己的 Swim 和 Bark 实现。
如果鸭子的行为(如 Swim)需要不同实现,但不需要默认实现,那么接口是一个简洁的替代选择。

抽象方法与虚方法的区别与选择:
抽象方法:没有实现,需要每个子类必须提供实现。适用于父类中无法提供一个通用实现、所有子类都必须有不同实现的情况。
虚方法:有一个基类默认实现,允许子类选择性地重写。适用于父类中有通用的逻辑实现,但某些子类需要定制时。
选择规则:如果所有子类的实现完全不同,选择抽象方法;如果基类提供的默认实现适用于大部分情况,选择虚方法。

虚方法 vs. 接口默认实现(C# 8+ 和 .NET 8 中的接口默认实现):
虚方法:只能在类中使用;且类可以直接调用虚方法中的默认实现。
接口默认实现:允许在接口中提供方法的默认实现,类不必实现该方法即可使用。
场景区别:如果你在设计类继承层次,可以选择虚方法;而如果希望所有实现该接口的类都共享默认逻辑,可以选择接口的默认实现。

如果 Swim 是抽象方法是否可以用接口代替?
是的,如果没有默认实现需求,可以直接用接口定义 Swim。

  1. 抽象类的主要特性与区别
    抽象类不能被实例化,因此不能直接创建其对象。
    抽象类通常用于作为其他类的基类,表达一种通用的概念或抽象。
    即使只有普通方法,定义为抽象类仍会提示使用者“这个类是抽象的,应该被继承使用”。

  2. 普通类的特性
    普通类可以被实例化,并不要求被继承使用。
    普通类主要用于直接定义具体实现,且通常包含具体的功能逻辑。

  3. 适用场景与设计意图的不同
    抽象类的设计意图是为了作为基类,提供一种模板或行为的基础实现。
    普通类则用于直接实现具体功能,并且可以独立使用。

选择标准
如果类不应直接实例化,只用于提供通用行为的基础,则使用抽象类。
如果类既可以独立使用,也可以作为基类提供可选的行为覆盖,则使用普通类。

结论
在只有虚方法的情况下,抽象类和普通类在功能上的区别不大,但抽象类在设计上更能表达“仅供继承”的意图,适合设计规范和逻辑约束较强的代码。

接口主要是描述行为,有可能有的类不存在这个行为,抽象类主要是同一个类型的抽象。

鸭子的类设计总结

1. 如果基类鸭子(Duck)作为普通类

  • 普通类的Duck可以直接实例化,因此可以创建普通鸭子的对象。
  • 由于普通类不能包含抽象方法,所以Duck类中的所有方法都需要提供实现。
  • 虚方法的使用:假设所有鸭子都具备游泳的能力,那么可以在Duck类中将“游泳”定义为虚方法 (Swim),并提供一个默认实现。如果特定的鸭子子类(如木头鸭子)希望保留或自定义它们的游泳方式,可以选择性地重写 (override) Swim 方法。
  • 接口的使用:如果鸣叫行为 (Bark) 并不是所有鸭子的共性,可以将鸣叫定义在接口 (IBark) 中,而不在基类中实现。这样只有会鸣叫的鸭子(如真实鸭子、橡皮鸭子)才需要实现IBark接口,而不会鸣叫的鸭子(如木头鸭子)则无需实现。

2. 如果基类鸭子(Duck)作为抽象类

  • 抽象类Duck不能直接实例化,通常用于表达一种抽象的概念或模板。抽象类可以包含抽象方法和虚方法。
  • 抽象方法的使用:如果我们确定所有鸭子子类都需要实现某些行为(如“鸣叫”),可以在Duck类中将其定义为抽象方法 (abstract Bark()),这样子类必须实现该方法,从而保证所有会鸣叫的鸭子都有特定的实现。
  • 虚方法的使用:类似游泳的方法 (Swim) 可以被定义为虚方法,并提供一个默认实现。这样,子类可以根据需要选择性地重写Swim方法,而不会被强制实现。
  • 接口的配合:当某些行为(如鸣叫)不是所有鸭子的必备功能时,可以结合接口IBark,仅让具有该行为的鸭子子类实现该接口,以保持设计的灵活性。

3. 如果鸭子作为接口(IDuck

  • 如果我们将鸭子的特性全部定义在接口IDuck中,则IDuck只包含行为的声明,不包含实现。
  • 在接口中声明的所有方法(如SwimBark)必须由实现该接口的类来定义。因此,实现IDuck接口的类将拥有所有鸭子的功能,但需要各自定义行为。
  • 默认实现(C# 8.0+):在C# 8及以上版本中,接口可以有默认实现,因此可以在接口中直接定义Swim的默认实现,而子类可以选择性地使用或覆盖默认实现。

总结对比

  • 普通类:适合于可以直接使用的基类设计,允许包含虚方法,提供基本的默认实现,但不能包含抽象方法。
  • 抽象类:表达一种基础概念,不能直接实例化,适合用作模板类,可包含抽象方法,强制子类实现某些功能。
  • 接口:适合表达行为契约,允许多重实现,为类提供灵活的功能扩展。自C# 8起,接口可包含默认实现,从而提供更多的复用性。

续写建议:适用场景总结

  • 普通类的适用场景:当需要定义一个可直接使用的类,但希望允许其子类自定义某些行为(如默认的游泳方式)时,使用普通类加虚方法。
  • 抽象类的适用场景:当一个类主要作为基类使用,且存在一些必须由子类实现的特定功能(如抽象的Bark方法),则使用抽象类更合理。
  • 接口的适用场景:当某个功能(如鸣叫)适合多个类,但并不适合强制集成到某个特定的继承体系中时,使用接口是最佳选择。

在C# 8及以上版本中,接口可以有默认实现,是不是说,面对一些简单场景,我可以不写抽象类,只写接口就行了?

是的,在 C# 8 及以上版本中,接口可以提供默认实现,这使得在某些简单场景下,可以选择只使用接口而不必使用抽象类。以下是一些相关的要点和适用场景:

使用接口的优点
灵活性:接口允许不同的类实现同一组行为,而不需要通过单一的继承层次结构。这意味着你可以为不相关的类提供相同的功能。

多重实现:一个类可以实现多个接口,这使得代码的重用和灵活性更高。例如,一个类可以同时实现 IBark 和 IFly 接口,而不需要受到单一继承的限制。

默认实现:在接口中可以提供默认实现,这使得接口的使用更加方便。实现接口的类可以选择直接使用默认实现,也可以根据需要重写它们。这种灵活性使得接口在某些情况下可以替代抽象类。

适用场景
简单场景:如果你的场景仅需要定义一组方法,并且不需要共享任何状态或数据,那么使用接口可能是一个更简单的选择。例如,一个只需定义Bark和Swim行为的鸭子接口,可以通过接口实现。

避免不必要的继承:如果某个功能可能会被多种不相关的类实现,使用接口而不是抽象类可以避免创建不必要的继承关系。

渐进式改进:如果你在现有代码中引入新功能,可以考虑使用接口及其默认实现,来逐步更新类的行为,而不需要重构整个类层次结构。

注意事项
接口不能包含状态:接口不允许定义字段,只能包含方法的声明(和默认实现)。如果你需要共享状态或数据,抽象类可能更适合。

限制使用抽象类的功能:如果你需要在基类中实现一些公用的状态或行为,而不仅仅是方法,使用抽象类可能更合适。

public interface IDuck
{
    void Swim();

    // 默认实现
    void Bark()
    {
        Console.WriteLine("鸭子叫!");
    }
}

public class RubberDuck : IDuck
{
    public void Swim()
    {
        Console.WriteLine("橡皮鸭子会游泳");
    }

    // 可以选择重写 Bark 方法
    public void Bark()
    {
        Console.WriteLine("橡皮鸭子唧唧的叫");
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        IDuck rubberDuck = new RubberDuck();
        rubberDuck.Swim();
        rubberDuck.Bark(); // 会调用重写的方法

        IDuck defaultDuck = new RubberDuck();
        defaultDuck.Swim();
        defaultDuck.Bark(); // 默认实现
    }
}

在这个示例中,IDuck 接口提供了 Swim 方法的定义和 Bark 方法的默认实现,RubberDuck 类实现了接口,定义了自己的游泳方式,并重写了叫声。这展示了接口在 C# 8 中的新特性及其使用场景。

posted @   Smileher  阅读(406)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示