|
类与对象和封装
(定义类,实例化对象,类的属性(字段)和方法,及访问修饰符)
|
在 Python 中,使用class关键字,后接类名,定义一个类。类名通常使用 PascalCase(每个单词首字母大写)的命名约定,例如MyClass。__init__方法为构造函数,是初始化方法,在创建对象时自动执行,常用于传入参数和初始化对象属性。self表示实例本身,所有实例方法的第一个参数必须是self。实例化对象是指通过类生成对象。调用类名时会自定调用__init__方法。
类的属性和方法可以细分为实例属性和方法,及类属性和方法。实例属性和方法是每个对象都有自己独立的属性和方法,并通过self访问;类属性和方法,是所有对象共享的属性和方法,类属性和方法不仅可以通过实例访问,也可以直接通过类访问。类的方法是通过@classmethod装饰器定义,使用cls作为第一个参数。
# A-定义类。使用 class 关键字定义类
class Dog:
species = "Canis familiars" # 类属性
# __init__ 为初始化方法,可以传入初始化参数,定义属性
def __init__(self, name, breed):
self.name = name # 通过 self 将属性绑定到对象。为实例属性
self.breed = breed
# 实例方法。为定义的行为
def bark(self): # 用 self 作为方法的第一个参数
print(f"{self.name} says: Woof!")
# 设置年龄
def setAge(self, age):
self.age = age
# 获取年龄
def getAge(self):
return self.age
# 通过装饰器定义类方法
@classmethod
def describe(cls):
return cls.species
# B-创建对象。使用类创建对象
dog1 = Dog("Buddy", "Golden Retriever")
dog2 = Dog("Max", "Bulldog")
# 调用实例属性
print(dog1.name)
print(dog2.breed)
# 调用方法
dog1.bark()
dog2.bark()
# 调用类属性
print(Dog.species) # 通过类访问
print(dog1.species) # 通过实例访问
print(dog2.species)
# 调用类方法
print(Dog.describe()) # 通过类访问
print(dog1.describe()) # 通过实例访问
print(dog2.describe())
🡮
Buddy
Bulldog
Buddy says: Woof!
Max says: Woof!
3
Canis familiars
Canis familiars
Canis familiars
Canis familiars
Canis familiars
Canis familiars
🤖 逐行解释代码
class Dog:
species = "Canis familiars"
- 定义了一个名为
Dog的类,表示为狗的模型。
species是一个类属性,属于整个类Dog,而不是某个实例(对象)。其值为Canis familiars,表示狗的学名。
def __init__(self, name, breed):
self.name = name
self.breed = breed
__init__是构造函数,在创建类的实例(对象)时自动调用。
- 参数
name和breed分别表示狗的名字和品种,并通过self绑定到该对象,使每个实例拥有独立的name和breed属性;。
def bark(self):
print(f"{self.name} says: Woof!")
bark是一个实例方法,表示狗叫。
- 通过打印该实例的
name,然后加上"says: Woof!"来表示。
def setAge(self, age):
self.age = age
def getAge(self):
return self.age
setAge是用来给实例设置age(狗年龄)属性的函数。
getAge是用来获取实例的age属性的函数。
age属性在实例初始化时并未定义,而是通过调用setAge方法动态添加。
@classmethod
def describe(cls):
return cls.species
@classmethod装饰器表示这是一个类方法,而不是实例方法。
- 类方法的第一个参数是
cls,代表类本身,而不是具体实例;
describe返回类属性species的值。
dog1 = Dog("Buddy", "Golden Retriever")
dog2 = Dog("Max", "Bulldog")
- 使用
Dog类创建两个实例:dog1和dog2。
dog1的名字是"Buddy",品种是"Golden Retriever"。
dog2的名字是"Max",品种是"Bulldog"。
print(dog1.name)
print(dog2.breed)
- 打印
dog1的name属性,输出"Buddy"。
- 打印
dog2的breed属性,输出"Bulldog"
- 调用
dog1的bark方法,输出"Buddy says: Woof!"。
- 调用
dog2的bark方法,输出"Max says: Woof!"。
dog1.setAge(3)
print(dog1.getAge())
- 使用
setAge方法给dog1设置age属性为3。
- 使用
getAge方法获取dog1的age属性,输出3。
print(Dog.species)
print(dog1.species)
print(dog2.species)
- 直接通过类名
Dog访问类属性species,输出"Canis familiars";
- 通过实例
dog1和dog2访问类属性species,输出结果仍然是"Canis familiars",因为类属性是共享的。
print(Dog.describe())
print(dog1.describe())
print(dog2.describe())
- 通过类名
Dog调用类方法describe,返回species的值,输出"Canis familiars"。
- 通过实例
dog1和dog2调用类方法,效果与通过类名调用相同,输出"Canis familiars"。
类的私有属性和私有方法
在 Python 中,类的私有属性和私有方法是一种约定,通过前缀使用双下划线__来标识,命名规则如__attribute_name和__method_name。如果希望某个属性是私有的,使用单下划线_更加常见,表示“受保护”。但这种方法不能够完全阻止外部访问。实际上,Python 没有真正意义上的私有成员概念,但提供了名称改写(name mangling),以避免外部代码直接访问。双下划线会触发名称改写机制,使得私有属性和私有方法的名称被修改为_ClassName__AttributeName的形式,从而减少了命名冲突的危险。因此可以通过名称改写机制访问,如obj._MyClass__private_attr。但并不推荐使用这种方法,而是使用getter和setter方法来访问或修改私有属性。然而,过多的私有成员可能使代码难以测试和维护,只有在明确需要保护实现细节时才使用私有成员。
class MyClass:
def __init__(self):
self.__private_attr = 43 # 私有属性
self._protected_attr = "This is a convention-prottected attribute" #
def get_private_attr(self):
return self.__private_attr # 通过方法访问私有属性
def set_private_attr(self, value):
self.__private_attr = value
# 私有方法
def __private_method(self):
print("This is a private mehotd.")
def public_method(self):
self.__private_method() # 类内调用私有方法
obj = MyClass()
print(obj._MyClass__private_attr) # 可以通过名称改写机制访问,但不推荐
obj.set_private_attr(97) # 设置私有属性
print(obj.get_private_attr()) # 通过方法访问私有属性
|
C++ 使用 class关键字定义类,后接类名,类名通常使用 PascalCase(每个单词首字母大写)的命名约定,例如MyClass。类包括的访问修饰符有只能在类内访问的private;可以从类外部访问的public;及在继承中对子类可见的protected。构造函数的名称与类名相同,且没有返回值,用于传入参数和初始化对象的属性。定义类时包含属性(成员变量)和行为(成员函数),实例化对象后,即使用类创建对象后,可以通过对象访问类的公有成员。
关键字static定义类中的静态成员变量(Static Member Variable),为对所有对象共享。但,静态成员变量需要在类外进行定义和初始化;关键字static定义类中的静态成员函数,其可以通过类名直接调用,而不需要实例化对象。静态成员函数与具体的对象无关,因此无法使用this指针,不能是虚函数(virtual),也只能访问静态成员变量或其他静态成员函数,不能访问非静态成员。
#include <iostream>
#include <format>
// 定义类
class Dog {
// 私有成员变量和成员函数
private:
int age;
// 公有成员变量和成员函数
public:
//成员变量
static std::string species; // 静态成员变量
std::string name;
std::string breed;
// 构造函数
Dog(const std::string& name, const std::string& breed) {
this->name = name;
this->breed = breed;
this->age = 0;
}
//成员函数
void bark() const {
std::cout << std::format("{} syas: Woof!\n", name);
}
//设置年龄
void setAge(int age) {
this->age = age;
}
// 获取年龄
int getAge() const {
return age;
}
// 静态成员函数
static std::string describe() {
return species;
}
};
std::string Dog::species = "Canis familiaris";
int main() {
// 实例化对象
Dog dog1("Buddy", "Golden Retriever");
Dog dog2("Max", "Bulldog");
std::cout << dog1.name << std::endl;
std::cout << dog2.name << std::endl;
dog1.bark();
dog2.bark();
// 修改属性
dog1.setAge(3);
std::cout << dog1.getAge() << std::endl;
// 访问静态成员变量
std::cout << Dog::species << std::endl;
std::cout << dog1.species << std::endl;
std::cout << dog2.species << std::endl;
// 调用静态成员函数
std::cout << Dog::describe() << std::endl;
std::cout << dog1.describe() << std::endl;
std::cout << dog2.describe() << std::endl;
return 0;
}
🡮
Buddy
Max
Buddy syas: Woof!
Max syas: Woof!
3
Canis familiaris
Canis familiaris
Canis familiaris
Canis familiaris
Canis familiaris
Canis familiaris
🤖 逐行解释代码
类的定义
class Dog {
private:
...
public:
...
};
int age(私有成员):用于存储狗的年龄。
static std::string species(静态成员):表示狗的物种名称,所有类实例共享此属性。
std::string name和std::string breed(公有成员):分别表示狗的名字和品种。
Dog(const std::string& name, const std::string& breed)
- 初始化
Dog类的实例。
- 使用
this->关键字将参数name和breed赋值给当前对象的成员变量。
- 初始化
age为0.
void bark() const:打印出狗的名字和它发出的叫声。const表示该函数不会修改类成员变量。
void setAge(int age):设置狗的年龄。
int getAge() const:返回狗的年龄。const表示该函数不会修改类成员变量。
static std::string describe():静态成员函数,用于返回静态成员变量species的值。静态成员不依赖于具体对象,可以通过类名直接掉用。
静态成员变量初始化
std::string Dog::species = "Canis familiaris";
- 定义并初始化
species。必须在类的外部对静态成员变量进行初始化。
主函数
int main() {
Dog dog1("Buddy", "Golden Retriever");
Dog dog2("Max", "Bulldog");
- 创建两个
Dog对象:dog1名字为"Buddy",品种为"Golden Retriever";dog2名字为"Max",品种为"Bulldog"。
std::cout << dog1.name << std::endl;
std::cout << dog2.name << std::endl;
- 打印
dog1和dog2的名字。
dog1.bark();
dog2.bark();
- 掉用
dog1和dog2的bark方法。
dog1.setAge(3);
std::cout << dog1.getAge() << std::endl;
- 设置
dog1的年龄为3。
- 使用
getAge方法获取并打印输出3.
std::cout << Dog::species << std::endl;
std::cout << dog1.species << std::endl;
std::cout << dog2.species << std::endl;
- 直接通过类名
Dog访问静态成员变量species,输出Canis familiaris。
- 通过对象
dog1和dog2访问species,输出结果同。
std::cout << Dog::describe() << std::endl;
std::cout << dog1.describe() << std::endl;
std::cout << dog2.describe() << std::endl;
- 通过类名
Dog调用静态方法describe,输出species的值,输出为Canis familiaris。
- 通过对象
dog1和dog2调用静态方法,输出结果同。
|
在 C# 中,用class关键字定义类,后接类名,类名通常使用 PascalCase(每个单词首字母大写)的命名约定,例如MyClass。构造函数可用于传入值(如果带参数)和初始化对象的字段,其名称与类名同,并没有返回值。如果不显示定义, C# 会自动提供一个默认的无参构造函数。C# 的访问修饰符用于控制类成员的访问权限,private为只能在类内部访问(默认);public则可以从类外部访问;protected为子类可访问;internal为同一程序集内可访问;protected internal为同一程序集或子类中可访问。
using System;
// 定义类
class Dog
{
// 静态变量
public static string species = "Canis familiaris";
// 实例成员变量
public string name;
public string breed;
private int age;
// 构造方法
public Dog(string name, string breed)
{
this.name = name;
this.breed = breed;
this.age = 0; // 设置默认值
}
// 狗叫的实例方法
public void Bark()
{
Console.WriteLine($"{this.name} says: Woof!");
}
// 设置狗的年龄
public void SetAge(int age)
{
this.age = age;
}
// 访问狗的年龄
public int GetAge()
{
return this.age;
}
// 描述物种的静态方法
public static string Describe()
{
return species;
}
}
class Program
{
static void Main()
{
// 创建 Dog 的实例
Dog dog1 = new Dog("Buddy", "Golden Retriever");
Dog dog2 = new Dog("Max", "Bulldog");
// 访问狗的名字和品种
Console.WriteLine(dog1.name);
Console.WriteLine(dog2.breed);
// 调用狗的实例方法(狗吠)
dog1.Bark();
dog2.Bark();
// 设置和打印狗的年龄
dog1.SetAge(3);
dog1.GetAge();
// 打印静态变量
Console.WriteLine(Dog.species);
//Console.WriteLine(dog1.species); // 实例化对象不能访问静态变量
// 调用静态方法
Console.WriteLine(Dog.Describe());
// Console.WriteLine(dog1.Describe()); // 实例化方法不能调用静态方法
}
}
🡮
Buddy
Bulldog
Buddy says: Woof!
Max says: Woof!
Canis familiaris
Canis familiaris
🤖 逐行解释代码
- 引入
System命名空间,其包含基础的类库,如Console类,用于处理控制台输入和输出。
class Dog
{
public static string species = "Canis familiaris";
- 定义一个
Dog类,为狗的属性和行为。
species是一个由static关键字定义的静态成员变量,属于类本身,而不是类的实例。其初始值为"Canis familiaris",表示狗的物种名称。
public string name;
public string breed;
private int age;
name和breed是实例变量,分别表示狗的名字和品种。这些成员变量使用public关键字,使其对外部是公开的,为公有成员变量。
age使用private关键字,是私有成员变量,表示狗的年龄,只有类的内部方法可以访问。
public Dog(string name, string breed)
{
this.name = name;
this.breed = breed;
this.age = 0;
}
- 构造函数
Dog(string name, string breed)用来初始化Dog类的实例。
this.name和this.breed是通过构造函数的参数name和breed来初始化实例变量。
this.age = 0;将age实例变量初始化为0。
public void Bark()
{
Console.WriteLine($"{this.name} says: Woof!");
}
Bark是一个实例方法,描述狗叫的行为。
- 使用
Console.WriteLine输出狗的名字和叫声"says: Woof!"。
public void SetAge(int age)
{
this.age = age;
}
SetAge是一个实例方法,用来设置狗的年龄。
- 输入参数为
age,将其赋值给实例的age变量。
public int GetAge()
{
return this.age;
}
GetAge是一个实例方法,用于获取狗的年龄。
- 返回实例的
age变量的值。
public static string Describe()
{
return species;
}
Describe是一个静态方法,可以通过类名直接调用,而不需要实例化对象。
- 该方法返回静态成员
species,表示狗的物种名称。
class Program
{
static void Main()
{
Program是主程序类,其中包含Main方法,作为程序的入口点。
static关键字表示该方法是静态的,可以直接由 CLR(公共语言运行时)调用,而不需要创建Program类的实例。
Dog dog1 = new Dog("Buddy", "Golden Retriever");
Dog dog2 = new Dog("Max", "Bulldog");
Console.WriteLine(dog1.name);
Console.WriteLine(dog2.breed);
- 创建了两个
Dog对象dog1和dog2,分别表示一只名为"Buddy"、品种为"Golden Retriever"的狗和一只名为"Max"、品种为"Bulldog"的狗。
- 使用
Console.WriteLine输出dog1的名字(name)和dog2的品种(breed)。
dog1.Bark();
dog2.Bark();
- 调用
dog1和dog2的Bark方法。
dog1.SetAge(3);
dog1.GetAge();
- 调用
dog1.SetAge(3),将dog1的年龄设置为3。
- 调用
dog1.GetAge(),获取dog1的年龄,为返回值。
Console.WriteLine(Dog.species);
//Console.WriteLine(dog1.species);
- 使用
Console.WriteLine(Dog.species);输出species静态变量的值,即"Canis familiaris"。
- 注释掉了
//Console.WriteLine(dog1.species);代码行,因为species属于静态成员,可以通过类名直接访问,而不是通过实例访问。
Console.WriteLine(Dog.Describe());
// Console.WriteLine(dog1.Describe());
}
}
- 使用
Console.WriteLine(Dog.Describe());调用类方法Describe(),输出静态成员species的值。
- 注释掉了
// Console.WriteLine(dog1.Describe());代码行,因为Describe是静态方法,不需要通过实例对象调用。
Static解释
static关键字可用于声明类的静态成员、静态类和静态构造方法。静态成员中的静态变量和静态方法属于类本身,而不是类的实例,因此不需要通过实例化对象来访问或调用,而是通过类本身来调用,如上述示例代码。
静态类只能包含静态成员,不能实例化。静态类通常用于封装与对象状态无关的功能。示例中MathUtilities是静态类,不能创建该类的实例,所以方法和变量都是静态的。
using System;
public static class MathUtilities
{
public static double Pi = 3.14;
public static double Square(double x)
{
return x * x;
}
}
class Program
{
static void Main()
{
Console.WriteLine(MathUtilities.Pi);
Console.WriteLine(MathUtilities.Square(5));
}
}
静态构造函数用于初始化静态成员,并且只会在类的第一次访问时执行一次。静态构造函数没有访问修饰符,不能接受参数,通常用于静态字段的初始化。
using System;
public class Example
{
public static int counter;
// 静态构造函数
static Example()
{
counter = 10;
Console.WriteLine("Static constructor called.");
}
public static void ShowCounter()
{
Console.WriteLine($"Counter: {counter}");
}
}
class Program
{
static void Main()
{
Example.ShowCounter();
}
}
🡮
Static constructor called.
Counter: 10
静态属性类似于静态变量,通常提供对静态字段的封装访问。示例代码中count是静态属性,可以通过类名来访问和修改。
using System;
public class Counter
{
private static int _count;
public static int Count
{
get { return _count; }
set { _count = value; }
}
}
class Program
{
static void Main()
{
Counter.Count = 7;
Console.WriteLine(Counter.Count);
}
}
🤖 逐行解释代码
public class Counter
{
private static int _count;
- 定义一个
Counter类,使用public访问修饰符,表示该类对所有代码可见。
- 定义一个私有变量
_count,用于存储计数值。static表示这个变量属于类本身,而不是属于类的某个实例,所有实例共享这一变量。使用下划线_作为私有字段的命名约定,表明它是类的内部实现细节。
public static int Count
{
get { return _count; }
set { _count = value; }
}
- 定义一个公有的静态属性
Count,对外部代码公开。
get访问器(getter)用于返回_count的值。外部代码通过Counter.Count读取当前的计数值。
set访问器(setter)用于修改_count的值。外部代码通过Counter.Count = value;设置新的计数器。
静态属性Count和静态字段_count的区别在于,_count是类内部使用的实际存储变量;Count是对外的接口,提供对_count的受控访问。
|
|
继承和多态
在面向对象编程(OOP)中,继承是一个核心概念,但不同编程语言在实现继承时存在语法和行为上的差异。在 Python 中,继承通过在类定义时使用括号指定父类来实现。子类可以重写父类的方法,并使用super()调用父类方法(如super().speak())。C++ 中的继承通过在类声明中使用:来指定基类。C++ 支持公有继承(public)、保护继承(protected)和私有继承(private)。方法可以被重写,并且使用virtual关键字启动运行时多态,配合override重写基类的虚函数。C# 的继承通过冒号:类指定基类,方法可以使用virtual关键字声明虚方法,子类可以使用override关键字重写虚方法,并支持多态。
| 特性 |
Python |
C++ |
C# |
| 继承类型 |
单继承和多(重)继承 |
单继承和多(重)继承 |
单继承和接口实现多(重)继承 |
| 默认基类 |
object |
无默认基类 |
System.Object |
| 访问控制 |
没有明确访问修饰符 |
public、protected、private |
public、protected、private |
| 多继承支持 |
支持(MRO 管理) |
支持(但易出现菱形继承问题) |
不支持,使用接口实现 |
| 内存管理 |
自动 |
手动 |
自动 |
| 多态支持 |
动态(通过 duck typing) |
静态和动态(virtual/override) |
静态和动态(virtual/override) |
多态是面向对象编程的一个重要特性,指的是同一操作作用于不同类型对象时表现出不同的行为。通过多态,父类指针或引用可以指向子类对象,并调用被重写的方法。多态可以分为运行时多态(动态多态)和编译时多态(静态多态)。
| 特性 |
Python |
C++ |
C# |
| 支持的多态类型 |
运行时多态(通过 duck typing) |
运行时多态(虚函数/重写) + 编译时多态(函数重载/模板) |
运行时多态(虚方法/重写)+ 编译时多态(方法重载/泛型) |
| 动态绑定 |
自动 |
通过虚函数和动态绑定 |
通过虚方法和动态绑定 |
| 关键字 |
无显示关键字,动态解析 |
virtual(基类),override(派生类) |
virtual(基类),override(派生类) |
| 多态的实现方式 |
方法重写、duck typing |
通过虚函数和重写 |
通过虚方法和重写 |
| 编译时多态 |
不支持 |
支持(函数重载、模板) |
支持(方法重载、泛型) |
Python 中的多态被称为 duck typing(鸭子类型)是因为其基于对象的行为而非其类型。在 Python 中,如果一个对象像鸭子一样游泳、嘎嘎叫,就可以认为它是鸭子。这个概念用来形容 Python 中的动态类型和多态性,即只要对象实现了必要的方法或属性,就能够被当作某个类型来使用,而不关心其具体的类型是什么。这个术语来源于一句谚语“If it looks like a duck and quacks like a duck, it's a duck”。
|
Python 中,OOP 中的继承允许一个类从另一个类继承属性和方法。继承使得代码复用变得容易,并也支持多态等其他 OOP 特性。继承的基本概念包括父类(Base Class/Superclass),被继承的类;和子类(Drived Class/subclass),继承父类的类。
# 定义父类(基类)
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} makes a sound."
# 定义子类(派生类)
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 调用父类的构造方法
self.breed = breed
def speak(self): # 子类重新父类的方法
print(f"{self.name} barks.")
class Cat(Animal):
def speak(self): # 子类重新父类的方法
print(f"{self.name} meows.")
dog = Dog("Buddy", "Golden Retriever")
cat = Cat("Whiskers")
dog.speak()
cat.speak()
🡮
Buddy barks.
Whiskers meows.
🤖 要点释义
super():用于调用父类的方法。例如在子类中的构造函数中,使用super().__init__(name)来调用父类的构造方法。
- 方法重写(Override):子类可以重写父类的方法,修改其行为,例如子类重新父类的
speak()方法。
__init__构造方法:子类可以重写父类的构造方法,以便根据需要初始化子类特有的属性。通过super()可以调用父类的__init__方法,确保父类的初始化逻辑被执行。
Python 支持多重继承,即一个子类可以继承多个父类。在多重继承中,类的继承顺序非常重要,其遵循方法解析顺序(Method Rsolution Order,MRO)。MRO 确定了在调用方法时,Python 会按照什么顺序去查找方法和属性,尤其当涉及多个父类时。可以使用类的mro()方法或__mro__属性查看类的 MRO 顺序。
class Animal:
def __init__(self, name):
self.name = name
class Mammal:
def give_birth(self):
print(f"{self.name} gives birth.")
class Dog(Animal, Mammal):
def __init__(self, name, breed):
Animal.__init__(self, name)
self.breed = breed
def speak(self):
print(f"{self.name} barks.")
dog = Dog("Buddy", "Golden Retriever")
dog.speak()
dog.give_birth()
print(Dog.mro())
🡮
Buddy barks.
Buddy gives birth.
[<class '__main__.Dog'>, <class '__main__.Animal'>, <class '__main__.Mammal'>, <class 'object'>]
|
C++ 中,OOP 中的继承是将一个类的属性和方法,即成员变量和成员函数传递到另一个类的机制。继承允许在已有类基础上创建新类,增强代码复用性。继承的基本概念包括基类(Base Class),被继承的类和派生类(Derived Class),从基类继承的类。
#include <iostream>
#include <format>
// 基类
class Base {
public:
int baseValue;
Base(int val) : baseValue(val) {} // 基类构造函数
virtual void display() { // 使用 virtual(虚函数),允许派生类重写该方法
std::cout << std::format("Base class value: {}\n", baseValue);
}
};
// 派生类
class Derived : public Base { // 公有继承
public:
int derivedValue;
Derived(int baseVal, int derivedValue) : Base(baseVal), derivedValue(derivedValue) {} // 派生类构造函数
void display() override { // 使用 override,重写基类的虚函数 display
std::cout << std::format("Derived class value: {}\n", derivedValue);
}
};
int main() {
Derived instance(10, 20); // 创建 Derived 类对象 instance
// 调用 display 方法
instance.display();
return 0;
}
🡮
Derived class value: 20
🤖 逐行解释代码
class Base { ... };:定义一个名为Base的类,表示基类。
public::公共访问修饰符,接下来的成员对外部代码可访问。
int baseValue;:定义一个整型成员变量baseValue,表示基类的值。
Base(int val): baseValue(val) {}:与类名同来定义构造函数,接受一个参数val;用初始化列表: baseValue(val) {},直接初始化成员变量baseValue,提高效率。
virtual void display():定义虚函数display,用于输出baseValue的值。由virtual关键字定义的虚函数允许派生类重写该方法,并支持运行时多态。
std::format("Base class value: {}\n", baseValue):使用std::format格式化输出baseValue。std::cout将格式化后的字符打印到控制台。
class Derived : public Base { ... };:定义派生类Derived,继承自基类Base。使用public Base表示公开继承,基类的public成员和方法在派生类中仍然为public。
int derivedValue;:定义一个整型成员变量derivedValue,表示派生类的特有值。
Derived(int baseVal, int derivedValue):构造函数接受两个参数,baseVal(传递给基类的值)和derivedValue(派生类的值)。
: Base(baseVal):调用基类的构造函数,初始化baseValue.
, derivedValue(derivedValue):使用初始化列表初始化派生类的成员变量derivedValue。
void display() override:override关键字明确声明该方法是对基类虚函数的重写,以增强代码的可读性和安全性。重写基类的display虚函数。
Derived instance(10, 20);:创建Derived类的对象instance。调用Derived类的构造函数初始化Base::baseValue(baseVal = 10)和Derived::derivedValue(derivedValue = 20)。
instance.display();,调用instance对象的display方法。由于Derived类重写了display,运行时调用的是派生类的版本。
又例如,
#include <iostream>
#include <format>
// 基类
class Animal {
public:
Animal(const std::string& name):name(name){}
virtual void speak() {
std::cout << std::format("{} makes a sound.\n", name);
}
protected:
std::string name;
};
// 派生类
class Dog : public Animal {
public:
Dog(const std::string& name,const std::string& breed):Animal(name),breed(breed){}
void speak()override {
std::cout << std::format("{}, barks!\n", name);
}
private:
std::string breed;
};
int main() {
Dog dog("Buddy", "Golden Retriever");
dog.speak();
return 0;
}
🤖 要点释义
- 继承方式有公有继承(
public)、保护继承(protected)和私有继承(private)。公有继承为派生类公开继承基类的公有成员,是最常见的继承类型;保护继承为派生类以受保护的方式继承基类的公有和保护成员;私有继承为派生类以私有的方式继承基类的公有和保护成员。
- 构造函数与初始化列表:在派生类构造函数中,必须显示地调用基类地构造函数来初始化基类部分。并通过初始化列表来初始化基类部分。
- 方法重写(Override):派生类可以重写(覆盖)父类的方法,例如
speak()方法在Dog类中被重写。override关键字可以明确告诉编译器正在重写基类的虚函数(virtual)。基类的方法如果声明为virtual,则可以实现运行时多态,意味着通过基类指针或引用调用派生类的方法时,实际调用的是派生类的方法,而不是基类中的方法。
C++ 支持多重继承,一个派生类可以继承多个基类,之间使用逗号分隔。但多重继承可能会导致“菱形继承问题”(或“钻石问题”),为多个基类继承自同一祖先类,而派生类又同时继承这些基类,导致派生类中会有多个相同的基类成员副本(冗余继承);派生类无法明确选择哪一个基类成员(歧义问题)。为了解决菱形问题,C++ 提供了虚继承(virtual inheritance),使基类的成员在派生类中只有一份共享的副本,语法为class DerivedClass : virtual public BaseClass { };。多重继承实现了代码复用,也更贴近某些场景中一个类具有多基类的特性。但是也带来了复杂性,可维护性差和性能影响等问题。因此,尽量避免不必要的多重继承,优先选择单继承或组合;并使用虚继承来解决菱形继承问题;明确指定基类作用域,避免成员访问的歧义;并对复杂继承关系进行清晰的设计和注释,确保代码可读性。
#include <iostream>
#include <format>
class A {
public:
void displayA() {
std::cout << "Class A" << std::endl;
}
};
class B {
public:
void displayB() {
std::cout << "Class B" << std::endl;
}
};
class C : public A, public B {
public:
void displayC() {
std::cout << "Class C" << std::endl;
}
};
int main() {
C obj;
obj.displayA();
obj.displayB();
obj.displayC();
return 0;
}
🡮
Class A
Class B
Class C
析构函数(Destructor)是一个特殊的成员函数,用于在对象的生命周期结束时自动执行清理工作,用来释放对象占用的资源,例如内存、文件句柄或网络连接等。析构函数的名称与类名同,但是需要在前面加一个波浪号~,以区分构造函数,例如~Base();析构函数不能接受参数,也不能返回任何值;当对象的生命周期结束时,析构函数会被自动调用,无需手动;且析构函数不能被重载。
#include <iostream>
class MyClass {
public:
MyClass() {
std::cout << "Constructor called!" << std::endl;
}
~MyClass() {
std::cout << "Destructor called!" << std::endl;
}
};
int main() {
MyClass obj; // 创建对象,调用构造函数
std::cout << "Inside main function." << std::endl;
// main 函数结束,obj 的析构函数被调用
return 0;
}
🡮
Constructor called!
Inside main function.
Destructor called!
需要注意,不要在析构函数中抛出异常,这可能会导致未定义的行为,尤其在栈展开(stack unwinding)过程中。当对象数组销毁时,析构函数会被依次调用。
#include <iostream>
#include <format>
class MyClass {
public:
MyClass() {
std::cout << "Constructor called!" << std::endl;
}
~MyClass() {
std::cout << "Destructor called!" << std::endl;
}
};
int main() {
MyClass arr[3];
std::cout << "Inside main function." << std::endl;
return 0;
}
🡮
Constructor called!
Constructor called!
Constructor called!
Inside main function.
Destructor called!
Destructor called!
Destructor called!
如果一个类有多态行为(即通过基类指针或引用操作派生类对象),析构函数应声明为虚函数(virtual),成为虚析构函数,以确保销毁对象时调用正确的析构函数。
#include <iostream>
#include <format>
class Base {
public:
Base() {
std::cout << "Constructor called!" << std::endl;
}
virtual ~Base() { std::cout << "Base destructor" << std::endl; }
};
class Derived : public Base {
~Derived() override { std::cout << "Derived destructor" << std::endl; }
};
int main() {
Base* obj = new Derived();
delete obj;
return 0;
}
🡮
Constructor called!
Derived destructor
Base destructor
|
在 C# 中,继承是面向对象编程(OOP)的核心特征之一,允许一个类继承另一个类的成员(属性和方法),从而实现代码的复用、扩展和多态性。OOP 的基本概念包括基类(Base Class/Parent Class)和派生来(Derived Class/Child Class)。C# 中的继承使用:来表示,派生类可以继承基类的公有和受保护成员。
using System;
class BaseClass
{
public string Name { get; set; }
public void Display()
{
Console.WriteLine($"Name: {Name}");
}
}
class DerivedClass : BaseClass
{
public int Age { get; set; }
public void Show()
{
Console.WriteLine($"Name: {Name}; Age: {Age}");
}
}
class Program
{
static void Main()
{
// 创建 BaseClass 类的实例,设置字段值,并调用 Show 和 Display 方法
DerivedClass derivedObj=new DerivedClass();
derivedObj.Name = "Bob";
derivedObj.Age = 29;
derivedObj.Show();
derivedObj.Display();
}
}
🡮
Name: Bob; Age: 29
Name: Bob
🤖 逐行解释代码
- 定义基类
BaseClass和属性Name及方法Display
class BaseClass { ... }:定义一个名为BaseClass的类,这个类包含一个属性Name和一个方法Display。
public string Name { get; set; }:定义一个public的自动实现属性Name,类型为string。自动属性意味着 C# 会自动为该属性提供一个私有字段,并提供默认的get和set访问器。get访问器返回字段的值;set访问器设置字段的值。
public void Display():定义一个公有方法Display,没有返回值(void),仅使用Console.WriteLine($"Name: {Name}");输出格式化的字符串,包含Name字段的值。
- 定义派生类
DerivedClass和属性Age及方法Show
class DerivedClass : BaseClass { ... }:定义一个名为DerivedClass的类,继承自基类BaseClass,意味着DerivedClass拥有BaseClass的所有公有(公共)成员(包括Name字段和Display方法)。DerivedClass还新增了一个Age属性和一个show方法。
public int Age { get; set; }:定义一个public的自动实现属性Age,类型为int。这个属性用于存储派生类中的年龄数据。
public void Show():定义了一个公共方法Show,没有返回值(void),仅用Console.WriteLine($"Name: {Name}; Age: {Age}");打印Name和Age字段值。
又例如,
using System;
// 基类
class Animal
{
public string Name { get; set; }
public Animal(string name)
{
Name = name;
}
public virtual void Speak()
{
Console.WriteLine($"{Name} makes a sound.");
}
}
// 派生类
class Dog : Animal
{
public Dog(string name) : base(name) { }
public override void Speak() // 重写基类方法
{
Console.WriteLine($"{Name} barks.");
}
}
// 派生类
class Cat : Animal
{
public Cat(string name) : base(name) { }
public override void Speak() // 重写基类方法
{
base.Speak();
Console.WriteLine($"{Name} meows.");
}
}
class Program
{
static void Main()
{
Animal dog = new Dog("Budyy");
Animal cat = new Cat("Whiskers");
dog.Speak();
cat.Speak();
}
}
🡮
Budyy barks.
Whiskers makes a sound.
Whiskers meows.
🤖 要点释义
- 构造函数与基类调用:派生类的构造函数通常需要调用基类的构造函数,用
base关键字实现。
- 方法重写:在基类中用
virtual关键字声明可以被重写的方法;在派生类中使用override关键字重写该方法;如果派生类不希望其方法在被重写,可以使用sealed关键字,如public sealed override void Speak() {}。
base关键字:用于访问和调用基类的成员(属性或方法),如base.Speak();。
- 多态(Polymorphisum):多态通过基类引用调用派生类对象的方法。使用虚方法(
virtual)和重写(override)实现运行时多态。
- 访问修饰符:基类的
public和protected成员可以被派生类访问;private成员不能被派生类直接访问;protected修饰符允许派生类访问,但外部类无法访问。
接口(Interface)和多重继承(Multiple Inheritance)是常见的面向对象编程概念。C# 不支持类的多重继承(即一个类直接继承多个类),但是支持实现多个接口。这种机制允许获得类似多重继承的功能,同时避免多重继承带来的复杂性和潜在问题。接口是定义一组方法签名(没有实现)的结构,任何类或结构体都可以实现一个或多个接口。接口可以包含方法、属性、事件和索引器等成员,但不能包含字段。接口的特点有,不能包含实现,只有方法签名;类必须实现接口中定义的所有方法(除非类是抽象类);一个类可以实现多个接口,相当于多重继承;接口本身可以继承多个其他接口。
using System;
// 定义接口
public interface IDrivable
{
void Drive(); // 接口方法没有实现
}
public interface IFlyable
{
void Fly(); // 接口方法没有实现
}
// 实现多个接口
public class Vehicle : IDrivable, IFlyable
{
public void Drive()
{
Console.WriteLine("Driving the vehicle");
}
public void Fly()
{
Console.WriteLine("Flying the vehicle");
}
}
class Program
{
static void Main()
{
Vehicle v = new Vehicle();
v.Drive(); // 调用 Drive 方法
v.Fly(); // 调用 Fly 方法
}
}
🡮
Driving the vehicle
Flying the vehicle
IDriveable和IFlyable是两个接口,分别定义了Drive和Fly方法。
Vehicle类同时实现了IDriveable和IFlyable这两个接口,并提供了具体实现。
- 在
Main方法中,创建了Vehicle类的对象v,然后调用了Drive()和Fly()方法。
接口可以继承其他接口。这样一个接口可以继承多个接口的方法签名。
using System;
// 定义基础接口
public interface IShape
{
void Draw();
}
public interface IColorable
{
void Color();
}
// 定义一个继承多个接口的接口
public interface IColoredShape : IShape, IColorable
{
void GetShapeDetails();
}
// 实现接口
public class Circle : IColoredShape
{
public void Draw()
{
Console.WriteLine("Drawing Circle");
}
public void Color()
{
Console.WriteLine("Coloring Circle");
}
public void GetShapeDetails()
{
Console.WriteLine("This is a colored circle");
}
}
class Program
{
static void Main()
{
Circle c = new Circle();
c.Draw(); // 绘制圆形
c.Color(); // 给圆形上色
c.GetShapeDetails(); // 获取圆形细节
}
}
🡮
Drawing Circle
Coloring Circle
This is a colored circle
IShape和IColorable是两个基础接口。
IColoredShape继承自这两个接口,要求实现它们的方法。
Circle类实现了IColoredShape接口,并提供了相应的实现。
C# 中, 析构函数(Destructor)是一种特殊的成员方法,用于在对象销毁之前执行清理工作,通常用于释放非托管资源,例如文件句柄、数据库连接或其他系统资源等。C# 的垃圾回收机制会自动管理托管资源的内存,因此不需手动释放托管对象,但对于非托管资源,依然需要显示清理。如果类持有非托管资源,推荐实现 IDisposable 接口,并在 Dispose 方法中显示释放资源,避免依赖析构函数。
|
|
抽象
抽象是面向对象编程(OOP)的一个重要概念,指的是隐藏对象的具体实现。通过提供一个简洁的接口,让用户只关注功能的使用,而无需了解内部实现细节。抽象通常通过抽象类和接口来实现。
| 特性 |
Python |
C++ |
C# |
| 定义抽象类 |
class Animal(ABC) |
class Animal {virtual void speak() = 0;}; |
abstract class Animal {} |
| 抽象方法 |
@abstractmethod def speak(self): |
virtual void speak() = 0 |
public abstract void Speak(); |
| 抽象类实例化 |
不能实例化抽象类 |
不能实例化包含纯虚函数的类 |
不能实例化抽象类 |
| 继承与实现 |
子类必须实现抽象方法 |
子类必须实现纯虚函数 |
子类必须实现抽象方法 |
| 已实现方法 |
可以包含普通方法 |
可以包含已实现方法 |
可以包含已实现方法 |
| 关键字 |
@abstractmethod,ABC |
virtual,= 0 |
abstract, override |
|
在 Python 中,抽象类是通过 abc(Abstract Base Class)模块来实现的。抽象类(Abstract Class)是不能实例化的类,其定义了一个接口(方法),但这些方法并没有具体实现,子类必须实现这些抽象方法。具体实现是使用ABC类(abc.ABC)和@abstractmethod装饰器来定义抽象类和抽象方法。抽象类可以包含非抽象方法(既有实现的方法)。
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def speak(self):
pass
class Dog(Animal):
def speak(self):
return "Woof"
class Cat(Animal):
def speak(self):
return "Meow"
# animal = Animal() # 错误,不能实例化抽象类,返回错误信息为 TypeError: Can't instantiate abstract class Animal without an implementation for abstract method 'speak'
dog = Dog()
cat = Cat()
print(dog.speak())
print(cat.speak())
Animal类是抽象类,不能直接实例化。
speak()方法是抽象方法,必须由子类实现。
@abstractmethod装饰器标记抽象方法,确保子类实现这些方法。
|
在 C++ 中,抽象类通过纯虚函数(pure virtual function)来实现。一个包含至少一个纯虚函数的类就是一个抽象类,不能实例化。纯虚函数使用= 0语法类定义。派生类必须实现所有的纯虚函数,除非子类本身也是抽象类。
#include <iostream>
#include <format>
class Animal {
public:
virtual void speak() = 0; // 纯虚函数
virtual ~Animal(){} // 虚析构函数
};
class Dog : public Animal {
public:
void speak() override {
std::cout << "Woof" << std::endl;
}
};
class Cat : public Animal {
public:
void speak() override {
std::cout << "Meow" << std::endl;
}
};
int main() {
// Animal animal; // 错误:不能实例化抽象类,返回错误信息为,object of abstract class type "Animal" is not allowed
Animal* dog = new Dog();
Animal* cat = new Cat();
dog->speak();
cat->speak();
delete dog;
delete cat;
return 0;
}
speak()是一个纯虚函数,Animal类是抽象类。
Dog和Cat必须实现speak()方法,才能实例化。
- 纯虚函数使用
= 0来定义,表示该函数没有实现,必须由子类实现。
|
在 C# 中,抽象类使用abstract关键字定义。抽象类可以包含抽象方法和已实现的方法。抽象方法在类中声明,但没有实现,必须在子类中实现。使用abstract关键字定义抽象类和抽象方法。抽象类可以包含已经实现的方法,派生类可以选择重写这些方法。
using System;
abstract class Animal
{
public abstract void Speak(); // 抽象方法
public void Move()
{
Console.WriteLine("Moving...");
}
}
class Dog : Animal
{
public override void Speak()
{
Console.WriteLine("Woof");
}
}
class Cat : Animal
{
public override void Speak()
{
Console.WriteLine("Meow");
}
}
class Program
{
static void Main()
{
// Animal animal = new Animal(); // 错误:不能实例化抽象类,返回错误信息为,Cannot create an instance of the abstract type or interface 'Animal'
Animal dog = new Dog();
Animal cat = new Cat();
dog.Speak();
cat.Speak();
dog.Move();
}
}
Speak()是抽象方法,必须在派生类中实现。
Move()是已实现的方法,派生类可以选择是否重写。
- 抽象类不能实例化,必须通过派生类来实例化。
|