这里写目录标题
- 封装
- C语言封装风格
- C 封装
- 继承
- 多态
- 多态的实现
- 虚函数概念:
- 虚表指针
- overload
- overwrite
- override
- 抽象类
- 重载
封装
C 中的封装是一种面向对象编程的概念,它将数据(成员变量)和操作(成员函数)封装在一个类中,通过访问控制来限制对类内部实现的访问。封装提供了类与外部世界之间的接口,隐藏了类的内部实现细节,提高了代码的可维护性和安全性。
在C 中,封装可以通过使用访问修饰符(public、private、protected)来实现:
public(公有)访问修饰符允许类的成员在类的外部被访问,也可以在类的内部被访问。通常将公有成员函数作为类的接口,供外部使用。 private(私有)访问修饰符将类的成员隐藏在类的内部,外部无法直接访问私有成员变量和私有成员函数。通常将私有成员用于实现类的内部逻辑。 protected(保护)访问修饰符与私有访问修饰符类似,但允许派生类(子类)访问基类中的保护成员。 下面是一个简单的封装示例:
代码语言:javascript复制class MyClass {
private:
int privateData; // 私有成员变量
public:
void setPrivateData(int data) { // 公有成员函数
privateData = data;
}
int getPrivateData() const { // 公有成员函数
return privateData;
}
};
int main() {
MyClass obj;
obj.setPrivateData(42);
int data = obj.getPrivateData();
return 0;
}
在上面的示例中,私有成员变量privateData被封装在类MyClass中,外部无法直接访问。通过公有成员函数setPrivateData和getPrivateData来操作私有成员变量。
封装可以提供更好的代码组织和管理方式,同时也增强了代码的安全性,因为外部代码无法直接修改和访问类的内部数据。
C语言封装风格
当单一变量无法完成描述需求的时候,结构体类型解决了这问题。可以将多个类型打包成一体,形成新的类型,这是c语言中封装的概念。但是,新类型并不包含对数据类的操作,所有操作都是通过函数的方式,去进行封装。
代码语言:javascript复制1 #include<iostream>
2 #include<string>
3 using namespace std;
4
5 struct person
6 {
7 string name;
8 int age;
9 int height;
10 };
11 void init(person* p)
12 {
13 p->name = "jiejie";
14 p->age = 20;
15 p->height = 130;
16 }
17 void show(person p)
18 {
19 cout<<"p.name = "<<p.name<<endl;
20 cout<<"p.age = "<<p.age<<endl;
21 cout<<"p.height = "<<p.height<<endl;
22 }
23 int main()
24 {
25 person per;
26 init(&per);
27 show(per);
28
29 return 0;
30 }
把数据放到一起用struct包装,然后把数据以引用或指针的方式传给行为。
C 封装
C 的封装认为C语言的封装不彻底,对于数据和行为分类,没有权限控制。 C 则提供控制选择,将数据和行为放在一起,对内开放数据,逻辑抽象。对外提供接口
代码语言:javascript复制 1 #include<iostream>
2 using namespace std;
3
4 class person
5 {
6 public:
7 int num;
8 void init()
9 {
10 cin>>this->num;
11 cin>>this->age;
12 cin>>this->height;
13 }
14 void show()
15 {
16 cout<<num<<endl;
17 cout<<age<<endl;
18 cout<<height<<endl;
19 }
20
21 protected:
22 int age;
23 private:
24 int height;
25 };
26 int main()
27 {
28 person p;
29 p.init();
30 p.show();
31 return 0;
32 }
基本流程: 通过创建一个类,再通过创建类对象 再由对象调用行为,完成需求。
继承
C 中的继承是面向对象编程的一个重要概念,它允许一个类(派生类/子类)从另一个类(基类/父类)继承属性和行为。继承可以通过创建一个新类并从基类派生来实现,新类将自动获得基类的成员变量和成员函数,同时可以添加新的成员或重写基类的成员函数。
继承的主要目的是实现代码的重用和构建类之间的层次关系。在继承关系中,基类被称为超类或父类,派生类被称为子类。 一般定义格式如下:
代码语言:javascript复制class 派生类名标识符: [继承方式] 基类名标识符
{
[访问控制修饰符]
[成员声明列表]
};
继承方式有3种,分别为公有型,保护型和私有型,访问控制修饰符也是public,protected,private 类型。 成员声明列表中包含类成员变量以及成员函数,是派生类新增的成员。 “:”是一个运算符,表示基类和派生类之间的继承关系。
C 中的继承有以下几种类型:
公有继承(public inheritance):派生类继承了基类的公有成员和保护成员,并且这些成员在派生类中的访问权限与基类中的一样。派生类的对象可以直接访问基类的公有成员。
私有继承(private inheritance):派生类继承了基类的公有成员和保护成员,但是这些成员在派生类中的访问权限变为私有。派生类的对象不能直接访问基类的公有成员,只能通过派生类的成员函数来间接访问。
保护继承(protected inheritance):派生类继承了基类的公有成员和保护成员,但是这些成员在派生类中的访问权限变为保护。派生类的对象不能直接访问基类的公有成员,只能通过派生类的成员函数来间接访问。
在C 中,使用关键字class或struct来定义一个类,并使用关键字public、private或protected来指定成员的访问权限。
下面是一个简单的继承示例:
代码语言:javascript复制class Shape {
public:
void draw() {
std::cout << "Drawing a shape" << std::endl;
}
};
class Rectangle : public Shape {
public:
void draw() {
std::cout << "Drawing a rectangle" << std::endl;
}
};
int main() {
Shape shape;
shape.draw(); // 输出:"Drawing a shape"
Rectangle rectangle;
rectangle.draw(); // 输出:"Drawing a rectangle"
return 0;
}
在上面的示例中,Shape类是基类,它有一个公有成员函数draw。Rectangle类是派生类,通过public关键字继承了Shape类。Rectangle类重写了draw函数,实现了自己的绘制行为。在main函数中,我们分别创建了Shape对象和Rectangle对象,并调用了它们的draw函数。
继承是一种强大的代码复用工具,它可以使得类之间的关系更加清晰和有组织。通过继承,派生类可以继承基类的接口和实现,并且可以添加自己的功能或修改基类的行为。
继承是面对对象的主要特性之一,它使一个类可以从现有类中派生,而不必重新定义一个类。
实质:用已有的数据类型创建新的数据类型,并保留已有数据类型的特点,以旧类为基础创建新类,新类包含旧类的数据成员和成员函数。并且·可以在新类中添加新的数据成员和成员函数。 旧类被称为基类或者父类,新类被称为派生类或者子类。
多态
C 中的多态是面向对象编程的一个重要概念,它允许使用指针或引用来处理不同类型的对象,而实际上执行的是根据对象类型动态选择的相关操作。多态性可以提高代码的灵活性、可复用性和可扩展性。
C 中的多态性主要通过虚函数(virtual function)和运行时类型识别(runtime type identification)两个机制来实现:
虚函数:在基类中声明虚函数,并在派生类中进行重写。当通过指向基类的指针或引用调用虚函数时,将根据实际对象的类型来选择正确的函数实现。这种动态选择函数的机制称为动态绑定(dynamic binding)。虚函数通过关键字virtual进行声明。
运行时类型识别(RTTI):C 提供了dynamic_cast和typeid两个运算符来进行运行时类型识别。dynamic_cast用于将基类指针或引用转换为派生类指针或引用,如果转换成功,返回指向派生类的指针或引用;如果转换失败,则返回空指针或抛出bad_cast异常。typeid用于获取表达式的实际类型。
下面是一个简单的多态示例:
代码语言:javascript复制#include <iostream>
class Shape {
public:
virtual void draw() {
std::cout << "Drawing a shape" << std::endl;
}
};
class Rectangle : public Shape {
public:
void draw() override {
std::cout << "Drawing a rectangle" << std::endl;
}
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing a circle" << std::endl;
}
};
int main() {
Shape* shapePtr;
Rectangle rectangle;
Circle circle;
shapePtr = &rectangle;
shapePtr->draw(); // 输出:"Drawing a rectangle"
shapePtr = &circle;
shapePtr->draw(); // 输出:"Drawing a circle"
return 0;
}
在上面的示例中,Shape类有一个虚函数draw,Rectangle和Circle类分别是派生自Shape的两个子类,并重写了draw函数。在main函数中,我们使用指向基类的指针shapePtr来处理不同类型的对象。通过将shapePtr指向Rectangle对象和Circle对象,并调用draw函数,会根据对象的实际类型选择正确的函数实现。
多态性使得我们可以以一种统一的方式来处理不同类型的对象,而不需要关心对象的具体类型。这样可以使代码更具灵活性和可维护性,同时提供了一种机制来实现运行时的动态行为。
多态的实现
静态绑定:在编译期决定 函数重载 运算符重载 模板 动态绑定:在程序运行时执行 虚函数
虚函数概念:
在基类中冠以关键字virtual的成员函数 定义: virtual函数类型 函数名称(参数列表) 如果一个函数在基类中被声明为虚函数,则它在所有派生类中都是虚函数。 只有通过基类指针或引用调用虚函数才能引发动态绑定 虚函数不能声明为静态 如果一个类要做为多态基类,要将析构函数定义为虚函数
虚表指针
虚函数的动态绑定是通过虚表来实现的 包含虚函数的类头4个字节存放指向虚表的指针
overload
成员函数被重载的特征: 相同的范围(在同一类中) 函数名字相同 参数不同 virtual关键字可有可无
overwrite
覆盖是指派生类函数覆盖基类函数 特征是: 不同的范围 函数名字相同 参数相同 基类函数必须有virtual关键字
override
重定义(派生类与基类) 不同的范围(分贝位于派生类和基类) 函数名与参数都相同,无virtual关键字 函数名相同,参数不同,virtual可有可无
抽象类
作用: 抽象类作为抽象设计的目的而声明,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。 对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现 注意: 抽象类只能作为基类来使用 不能声明抽象类的对象,可以使用抽象类的指针和引用 构造函数不能是虚函数,析构函数可以是虚函数 可以试用指向抽象类的指针支持运行时多态性 派生类中必须实现基类中的纯虚函数,否则被看做一个抽象类
对于一个没有任何接口的类,如果想要将它定义成抽象类,只能将虚构函数声明为纯虚的 通常情况下在基类中纯虚函数不需要实现,例外是纯析构函数要给出实现
重载
在C 中,函数重载(Function Overloading)是指在同一个作用域内,可以定义多个具有相同名称但参数列表不同的函数。通过函数重载,可以根据参数的类型、顺序和个数来区分不同的函数,并且可以为相同的操作提供不同的实现。
函数重载的特点如下:
函数名称相同:重载的函数必须具有相同的名称。
参数列表不同:重载的函数必须具有不同的参数列表,可以通过参数的类型、顺序和个数的不同来区分。
返回值类型不同不足以实现函数重载:函数的返回值类型不能作为重载函数的区分标准,因为函数调用时通常会忽略返回值。
举例如下所示:
代码语言:javascript复制#include<iostream>
using namespace std;
class interage
{
int inter;
public:
interage(int in)
{
inter = in;
};
~interage(){};
interage& operator ()
{
inter;
return *this;
}
interage operator (int in)
{
interage temp(inter);
inter ;
return temp;
}
void display()
{
cout<<inter<<endl;
}
};
int main()
{
interage in(10);
in.display();
interage in2 = ( in);
in2.display();
interage in3 = in2 ;
in3.display();
in3.display();
return 0;
}
通过函数重载,我们可以在C 中实现更加灵活和易于使用的代码。函数重载允许我们使用相同的函数名,但根据参数的类型、顺序和个数来区分不同的函数。这样,我们可以提供不同的函数实现来处理各种情况,而无需为每种情况编写不同的函数名称。
函数重载使得代码更具可读性和可维护性,同时提供了一种方便的方式来进行函数扩展和适应不同的需求。然而,在使用函数重载时,需要注意避免产生歧义或混淆,确保函数之间的区分明确。
总而言之,函数重载是C 中一个强大的特性,可以使代码更加灵活和易于使用,为我们提供了更多的选择和可能性。合理地利用函数重载,可以使我们的代码更加清晰、高效,同时提高开发效率和代码的可维护性。