类与对象和封装
(定义类,实例化对象,类的属性(字段)和方法,及访问修饰符)
|
在 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() 是已实现的方法,派生类可以选择是否重写。
- 抽象类不能实例化,必须通过派生类来实例化。
|