万江建筑培训中心,重庆seo代理价格,餐饮网站制作,旅游网站开发需求目录
继承和派生
基类 派生类
访问控制和继承
派生类的构造函数
派生类的析构函数
继承类型
多继承
重载运算符和重载函数
函数重载
运算符重载
可重载运算符/不可重载运算符
运算符重载实例 继承和派生
先来说继承#xff0c;这与现实生活中的继承意思差不…目录
继承和派生
基类 派生类
访问控制和继承
派生类的构造函数
派生类的析构函数
继承类型
多继承
重载运算符和重载函数
函数重载
运算符重载
可重载运算符/不可重载运算符
运算符重载实例 继承和派生
先来说继承这与现实生活中的继承意思差不多比如一个人继承另一个人的财产、以及子承父业等等是一个意思拥有的这个过程就叫做继承。同样在C中比如有两个类新类拥有原有类的全部属性叫做继承原有类产生新类的过程叫做派生而我们把原有的这个类称之为父类或基类由基类派生出的类叫做派生类或者叫做子类。大家从名字上就可以看出他们的关系。 // 基类
class Animal {// eat() 函数// sleep() 函数
};//派生类
class Dog : public Animal {// bark() 函数
}; 那么继承和派生有什么好处呢为什么C要有这种机制呢
体现面向对象的编程思想更好的表达各类型之间的关系。派生类除了可以继承基类的全部信息外还可以添加自己的那些不同的、有差异的信息就像生物进化的道理一样派生类在拥有基类的全部基础之上还将更强大。派生类继承到基类的成员是自动、隐藏的拥有即不需要我们重新定义这就节省了大量的代码体现了代码重用的软件工程思想。 继承和派生可以实现代码的模块化和分层使得程序的结构更加清晰易于理解和维护。通过继承和派生我们可以将相似的代码抽象出来形成一个基类然后派生出不同的子类分别实现各自的特定行为。 继承和派生可以实现多态性使得程序的功能更加灵活和可扩展。通过基类的指针或引用我们可以调用派生类的方法从而实现对不同类型对象的统一操作。 C引入继承和派生机制是为了继承C语言的优点同时弥补其不足。C语言没有面向对象的概念缺乏代码重用和抽象能力。通过继承和派生C可以更好地实现抽象、封装和多态等面向对象编程的特性。
基类 派生类
基类和派生类是面向对象编程中的两个重要概念。基类是指在继承关系中处于上层的、最通用的类它定义了一组通用的属性和方法派生类则是指在继承关系中处于下层、更具体的类它从基类继承通用的属性和方法并可以添加自己的特定属性和方法。
基类的概念源于面向对象编程的封装和抽象原则其目的是将相似或相关的代码抽象出来形成一个通用的类避免代码冗余提高代码的可维护性和可扩展性。基类通常包含一些数据成员和函数成员而且这些成员的访问权限可以是public、protected或private。
派生类则是基于基类构建的通过继承和派生机制它可以从基类继承代码和数据但也可以添加自己的特有信息。派生类可以覆盖或增加基类成员函数重载基类成员函数添加新的成员函数以及定义新的数据成员等。派生类可以进一步派生出更加具体的子类形成类似于树状结构的类层次体系。形式如下
class derived-class: access-specifier base-class其中访问修饰符 access-specifier 是 public、protected 或 private 其中的一个base-class 是之前定义过的某个类的名称。如果未使用访问修饰符 access-specifier则默认为 private。
在C中使用关键字class或struct定义一个类它可以从一个或多个基类继承例如
class Animal {
public:void eat();void move();
protected:int age;
};class Mammal : public Animal {
public:void giveBirth();void nurse();
private:int numOfPaws;
};class Dog : public Mammal {
public:void bark();
private:string breed;
};在上面的例子中Animal是一个基类它包含两个公共成员函数和一个保护的数据成员age。Mammal是一个派生类它从Animal继承了eat()、move()和age成员并添加了giveBirth()、nurse()和numOfPaws成员。Dog是Mammal的派生类它除了继承Mammal的所有成员之外还添加了一个私有成员breed和一个公共成员函数bark()。
基类和派生类的概念是面向对象编程中非常重要的基础它们提供了代码重用、层次化组织、多态性等重要特性。
访问控制和继承
派生类可以访问基类中的所有非私有成员这意味着基类成员如果不想被派生类的成员函数访问应该将其声明为私有(private)。
根据访问权限可以总结出以下不同的访问类型 同一个类在同一个类中所有成员无论是public、protected还是private都是可以访问的。这意味着类中的任何成员函数都可以访问该类的所有成员。 派生类派生类可以访问基类中所有的非私有(public和protected)成员包括变量和函数。但是对于基类中声明为私有(private)的成员在派生类中是无法直接访问的。 外部的类外部的类只能访问基类中声明为公共(public)的成员对于受保护(protected)和私有(private)成员是无法访问的。这意味着如果一个类想要访问另一个类的受保护和私有成员那么它必须通过该类的公共成员函数来实现。
此外在派生类继承基类时派生类会继承基类的所有方法但有几个例外情况
基类的构造函数、析构函数和拷贝构造函数派生类需要定义自己的构造函数、析构函数和拷贝构造函数来处理自己的特定需求而不是直接继承基类的相应函数。基类的重载运算符派生类可以定义自己的重载运算符但不会继承基类的重载运算符。基类的友元函数派生类不能访问基类的友元函数因为友元函数只对基类可见。
以下是一个示例
#includeiostreamclass BaseClass {
public:void publicMethod() { /* 实现代码 */ }
protected:void protectedMethod() { /* 实现代码 */ }
private:void privateMethod() { /* 实现代码 */ }
};class DerivedClass : public BaseClass {// 可以访问继承的 public 和 protected 成员public:void derivedMethod() {publicMethod(); // 可以访问继承的 public 成员protectedMethod(); // 可以访问继承的 protected 成员// privateMethod(); // 不能访问继承的 private 成员}
};int main() {DerivedClass obj;obj.publicMethod(); // 可以通过对象访问继承的 public 成员// obj.protectedMethod(); // 不能通过对象访问继承的 protected 成员// obj.privateMethod(); // 不能通过对象访问继承的 private 成员return 0;
}在上面的示例中DerivedClass 是一个派生类它继承了 BaseClass 的成员函数 publicMethod() 和 protectedMethod()。在派生类中它们保持了原有的访问权限。派生类可以在自己的成员函数中使用继承的公共和受保护的成员函数。但是私有成员函数 privateMethod() 对于派生类来说是不可见的。
总的来说通过继承派生类可以访问基类中的成员但对于私有成员、友元函数以及一些特殊情况构造函数、析构函数、拷贝构造函数和重载运算符存在一些限制。理解这些概念对于正确设计和使用继承关系是非常重要的。
派生类的构造函数
派生类的构造函数是在继承了基类的构造函数的基础上对派生类自身进行初始化的一种特殊方法。派生类的构造函数可以调用基类的构造函数来初始化从基类继承而来的成员变量和方法。同时派生类的构造函数也可以扩展或修改基类的构造函数。
派生类的构造函数的语法形式如下
class Derived : public Base {
public:Derived(参数列表) : Base(参数列表) {// 派生类自身操作}
};其中Derived 是派生类的名称Base 是基类的名称参数列表 是传递给构造函数的参数: Base(参数列表) 表示调用基类的构造函数进行初始化// 派生类自身操作 则是派生类自己的初始化代码。
需要注意的是如果派生类没有显式地调用基类的构造函数则编译器会自动调用基类的默认构造函数进行初始化。如果基类没有默认构造函数或者基类的构造函数不可访问比如是私有构造函数则派生类必须显式地调用基类的构造函数否则会编译错误。
此外在使用派生类的构造函数时还需要遵循一些规则和约定例如构造函数的名称与类名相同构造函数不能有返回值等。
代码示例
#include iostream
using namespace std;class Base {
public:Base(int a) { // 基类构造函数this-a a;cout Base构造函数被调用 endl;}void print() {cout Base::a a endl;}
private:int a;
};class Derived : public Base {
public:Derived(int a, int b) : Base(a) { // 派生类构造函数this-b b;cout Derived构造函数被调用 endl;}void print() {Base::print(); // 调用基类的 print 方法cout Derived::b b endl;}
private:int b;
};int main() {Derived d(1, 2);d.print();return 0;
}在上面的代码中Base 是基类Derived 是派生类。Base 类有一个带有整型参数的构造函数Derived 类继承了 Base 类并添加了一个整型成员变量 b同时还定义了一个带有两个整型参数的构造函数来初始化 b。
在 Derived 构造函数的初始化列表中先调用了基类 Base 的构造函数来初始化从基类继承而来的成员变量 a然后再将参数 b 赋值给派生类自身的成员变量 b。在构造函数中输出一些调试信息以便观察构造函数的执行情况。
在 Derived 类中还重写了基类 Base 的 print 方法在输出基类 Base 的成员变量 a 的值后再输出派生类 Derived 的成员变量 b 的值。在 main 函数中创建了一个 Derived 类型的对象 d 并调用了它的 print 方法。
当程序运行时先输出 Base构造函数被调用 和 Derived构造函数被调用然后输出基类 Base 的成员变量 a 和派生类 Derived 的成员变量 b 的值。最终输出
Base构造函数被调用
Derived构造函数被调用
Base::a 1
Derived::b 2 总结调用顺序是先调用基类的构造函数再调用派生类的构造函数。
派生类的析构函数
派生类的析构函数是在派生类对象被销毁时调用的一种特殊成员函数。与构造函数相对应在析构函数中可以进行清理操作例如释放动态分配的内存、关闭文件等等。
派生类的析构函数与基类的析构函数的关系比较特殊。如果基类有虚析构函数那么派生类的析构函数也应该是虚析构函数如果基类没有虚析构函数那么派生类的析构函数就不必是虚析构函数。这是因为只有当我们使用基类的指针或引用来删除一个派生类对象时才需要调用虚析构函数来确保正确地释放内存。
派生类的析构函数的语法形式如下
class Derived : public Base {
public:~Derived() {// 派生类自身的清理操作}
};以下是一个派生类析构函数的代码示例它与前面的派生类构造函数的示例非常类似
#include iostream
using namespace std;class Base {
public:Base(int a) { // 基类构造函数this-a a;cout Base构造函数被调用 endl;}virtual ~Base() { // 基类虚析构函数cout Base析构函数被调用 endl;}void print() {cout Base::a a endl;}
private:int a;
};class Derived : public Base {
public:Derived(int a, int b) : Base(a) { // 派生类构造函数this-b b;cout Derived构造函数被调用 endl;}~Derived() { // 派生类析构函数cout Derived析构函数被调用 endl;}void print() {Base::print(); // 调用基类的 print 方法cout Derived::b b endl;}
private:int b;
};int main() {Base* p new Derived(1, 2); // 使用基类指针来管理派生类对象p-print();delete p; // 删除派生类对象return 0;
}在上面的代码中Base 和 Derived 类的定义与前面的示例相同。不同之处在于在 Base 类中添加了一个虚析构函数在 Derived 类中添加了一个析构函数。在 main 函数中使用基类指针 p 来管理一个 Derived 类型的对象并调用它的 print 方法。最后通过 delete 运算符删除 p 指向的对象观察析构函数的执行情况。
当程序运行时先输出 Base构造函数被调用 和 Derived构造函数被调用然后输出基类 Base 的成员变量 a 和派生类 Derived 的成员变量 b 的值。接着当 delete p 执行时先调用派生类 Derived 的析构函数再调用基类 Base 的虚析构函数输出 Derived析构函数被调用 和 Base析构函数被调用。最终输出
Base构造函数被调用
Derived构造函数被调用
Base::a 1
Derived析构函数被调用
Base析构函数被调用
总结
在创建派生类对象时首先会调用基类的构造函数然后再调用派生类自身的构造函数。这是因为派生类继承了基类的成员变量和成员函数需要先初始化基类的部分然后才能执行派生类的构造过程。因此在构造对象时构造函数的调用顺序是从基类到派生类。
而在销毁派生类对象时先调用派生类的析构函数然后再调用基类的析构函数。这是因为派生类继承了基类的资源需要先释放派生类自身的资源然后再释放基类的资源。因此在销毁对象时析构函数的调用顺序是从派生类到基类。
继承类型
在C中有三种基本的继承类型公共继承、保护继承和私有继承。它们决定了派生类如何继承基类的成员。
1、公共继承public inheritance 公共继承是最常用的继承方式。在公共继承中基类中的公共成员和受保护成员都会成为派生类的公共成员和受保护成员。而基类中的私有成员仍然是基类自己的私有成员无法被派生类访问。
公共继承的语法格式为
class DerivedClass : public BaseClass
{//...
};2、保护继承protected inheritance 在保护继承中基类中的所有成员都将成为派生类的受保护成员。这意味着基类中的公共成员将变为派生类的受保护成员而基类中的私有成员仍然是基类自己的私有成员无法被派生类访问。
保护继承的语法格式为
class DerivedClass : protected BaseClass
{//...
};3、私有继承private inheritance 在私有继承中基类中的所有成员都将成为派生类的私有成员。这意味着基类中的公共成员和受保护成员都将变为派生类的私有成员而基类中的私有成员仍然是基类自己的私有成员无法被派生类访问。
私有继承的语法格式为
class DerivedClass : private BaseClass
{//...
};需要注意的是无论是公共继承、保护继承还是私有继承都只影响到成员的访问权限对于成员函数的重载、重写、隐藏等行为都没有影响。此外派生类可以通过 using 关键字来改变从基类继承的成员的访问权限。
总的来说继承类型决定了派生类如何继承基类的成员理解它们的区别很重要以便正确地设计和使用继承关系。在大多数情况下公共继承是最常用的继承方式。
多继承
在面向对象编程中一个类可以派生自多个基类这被称为多重继承。通过使用类派生列表我们可以指定派生类的多个基类。
类派生列表的语法如下
class DerivedClass : access-specifier BaseClass1, access-specifier BaseClass2, ...
{// 类成员声明和定义
};其中access-specifier 是访问修饰符可以是 public、protected 或 private用于指定从每个基类继承的成员的访问权限。如果没有显式地指定访问修饰符则默认为 private 访问权限。
通过多重继承派生类可以从每个基类继承其成员函数和数据成员。需要注意的是当多个基类拥有同名的成员函数或数据成员时派生类必须通过作用域解析运算符明确指定要使用的成员。
下面是一个示例
class BaseClass1 {// 基类1的成员和方法
};class BaseClass2 {// 基类2的成员和方法
};class DerivedClass : public BaseClass1, public BaseClass2 {// 派生类的成员和方法
};在上述示例中DerivedClass 是从 BaseClass1 和 BaseClass2 这两个基类中进行多继承的派生类。派生类将同时拥有这两个基类的成员和方法。
需要注意的是在多继承中可能存在以下问题和注意事项
1、名称冲突如果多个基类具有相同的成员或方法名称派生类在访问该成员或方法时需要进行限定以避免歧义。
2、菱形继承问题当多个基类之间存在继承关系时派生类可能会继承相同的成员和方法多次这被称为菱形继承问题。为了解决这个问题可以使用虚继承virtual inheritance来避免重复继承。
class BaseClass {// 基类的成员和方法
};class IntermediateClass1 : public virtual BaseClass {// 中间类1的成员和方法
};class IntermediateClass2 : public virtual BaseClass {// 中间类2的成员和方法
};class DerivedClass : public IntermediateClass1, public IntermediateClass2 {// 派生类的成员和方法
};通过在基类之间使用 virtual 关键字可以确保在派生类中只有一个实例共享基类的成员和方法。
多继承是一种强大的工具但需要谨慎使用特别是在存在名称冲突或复杂继承关系的情况下。
代码示例
#includeiostream
using namespace std;// 定义基类
class BaseClass {
public:// 基类的公有方法void baseFunc() {cout 调用了基类的方法 endl;}
};// 定义中间类1使用虚继承 BaseClass
class IntermediateClass1 : public virtual BaseClass {
public:// 中间类1的公有方法void intermediate1Func() {cout 调用了中间类1的方法 endl;}
};// 定义中间类2使用虚继承 BaseClass
class IntermediateClass2 : public virtual BaseClass {
public:// 中间类2的公有方法void intermediate2Func() {cout 调用了中间类2的方法 endl;}
};// 定义派生类从中间类1和中间类2进行多继承
class DerivedClass : public IntermediateClass1, public IntermediateClass2 {
public:// 派生类的公有方法void derivedFunc() {cout 调用了派生类的方法 endl;}
};int main() {// 创建派生类对象DerivedClass obj;// 调用派生类的方法obj.derivedFunc();// 调用中间类1的方法obj.intermediate1Func();// 调用中间类2的方法obj.intermediate2Func();// 调用基类的方法obj.BaseClass::baseFunc();return 0;
}在上述示例中我们定义了一个基类 BaseClass然后分别定义了两个中间类 IntermediateClass1 和 IntermediateClass2它们都从基类 BaseClass 进行虚继承。最后我们定义了派生类 DerivedClass它从这两个中间类进行多重继承。
在 main 函数中我们创建了 DerivedClass 的一个对象并且分别调用了它自己的方法、以及从中间类和基类继承过来的方法。此外我们还使用作用域限定符 :: 来标识调用基类 BaseClass 的方法。
编译并运行上述代码可以得到如下输出
调用了派生类的方法
调用了中间类1的方法
调用了中间类2的方法
调用了基类的方法这表明虚继承和作用域限定符都起到了作用确保派生类只继承了一份基类的成员和方法。
重载运算符和重载函数
C 允许在同一作用域中的某个函数和运算符指定多个定义分别称为函数重载和运算符重载。
重载声明是指一个与之前已经在该作用域内声明过的函数或方法具有相同名称的声明但是它们的参数列表和定义实现不相同。
当调用一个重载函数或重载运算符时编译器通过把您所使用的参数类型与定义中的参数类型进行比较决定选用最合适的定义。选择最合适的重载函数或重载运算符的过程称为重载决策。
函数重载
函数重载是指在同一个作用域内定义多个同名但参数列表不同的函数。通过函数重载可以根据不同的参数类型和数量来调用相应的函数。
下面是一个使用函数重载的示例代码
#include iostream
using namespace std;// 函数重载示例
void print(int num) {cout 整数: num endl;
}void print(double num) {cout 浮点数: num endl;
}void print(char ch) {cout 字符: ch endl;
}int main() {int a 10;double b 3.14;char c A;// 调用不同参数类型的重载函数print(a);print(b);print(c);return 0;
}在上述示例中我们定义了三个重载的 print 函数分别接受整数、浮点数和字符作为参数并打印出对应的类型和值。
在 main 函数中我们声明了一个整数变量 a、一个浮点数变量 b、一个字符变量 c然后分别调用 print 函数来打印它们的值。由于参数类型不同编译器会自动匹配调用相应类型的函数。
编译并运行上述代码可以得到如下输出
整数: 10
浮点数: 3.14
字符: A
这表明函数重载使得根据传入的参数类型和数量自动选择合适的函数成为可能。注意函数重载的条件是参数列表不同包括参数类型、参数个数或参数顺序。返回类型不同的函数不能构成重载。
运算符重载
运算符重载是指在类中重新定义和使用已有的运算符使其适用于自定义类型的对象。通过运算符重载可以方便地对对象执行各种操作提高代码的可读性和易用性。
下面是一个使用运算符重载的示例代码
#include iostream
using namespace std;// 定义一个复数类
class Complex {
private:double real;double imaginary;public:// 构造函数Complex(double r 0.0, double i 0.0) : real(r), imaginary(i) {}// 运算符重载Complex operator(const Complex other) const {return Complex(real other.real, imaginary other.imaginary);}// 运算符重载-Complex operator-(const Complex other) const {return Complex(real - other.real, imaginary - other.imaginary);}// 运算符重载*Complex operator*(const Complex other) const {double r real * other.real - imaginary * other.imaginary;double i real * other.imaginary imaginary * other.real;return Complex(r, i);}// 运算符重载friend ostream operator(ostream os, const Complex c) {os ( c.real , c.imaginary i);return os;}
};int main() {Complex c1(2.0, 3.0);Complex c2(1.0, 4.0);// 使用重载的运算符进行操作Complex sum c1 c2;Complex diff c1 - c2;Complex product c1 * c2;// 输出结果cout c1 c2 sum endl;cout c1 - c2 diff endl;cout c1 * c2 product endl;return 0;
}在上述示例中我们定义了一个复数类 Complex其中包含实部和虚部。然后我们通过运算符重载重新定义了 、-、* 运算符使其适用于 Complex 类型的对象。
此外我们还通过友元函数重载了输出运算符 以便能够通过 cout 打印复数对象。
在 main 函数中我们创建了两个复数对象 c1 和 c2并使用重载的运算符对它们进行操作将结果存储到相应的变量中。最后我们使用 cout 打印出运算结果。
编译并运行上述代码可以得到如下输出
c1 c2 (3, 7i)
c1 - c2 (1, -1i)
c1 * c2 (-10, 11i)
这表明通过运算符重载我们可以像使用内置类型一样对自定义类型的对象执行各种操作。
可重载运算符/不可重载运算符
下面是可重载的运算符列表
双目算术运算符 (加)-(减)*(乘)/(除)% (取模)关系运算符(等于)! (不等于) (小于) (大于)(小于等于)(大于等于)逻辑运算符||(逻辑或)(逻辑与)!(逻辑非)单目运算符 (正)-(负)*(指针)(取地址)自增自减运算符(自增)--(自减)位运算符| (按位或) (按位与)~(按位取反)^(按位异或), (左移)(右移)赋值运算符, , -, *, / , % , , |, ^, , 空间申请与释放new, delete, new[ ] , delete[]其他运算符()(函数调用)-(成员访问),(逗号)[](下标)
下面是不可重载的运算符列表
成员访问运算符 . 用于访问类或结构体的成员。成员指针访问运算符 .* 和 -* 用于访问类成员指针指向的成员。域运算符 :: 用于访问命名空间、类、结构体、枚举等的成员。长度运算符 sizeof 用于获取类型或表达式的字节大小。条件运算符 ?: 是三元运算符用于根据条件选择两个表达式之一的值。预处理符号 # 用于预处理指令中的字符串化操作。
运算符重载实例
下面提供了各种运算符重载的实例帮助您更好地理解重载的概念。
序号运算符和实例1一元运算符重载2二元运算符重载3关系运算符重载4输入/输出运算符重载5 和 -- 运算符重载6赋值运算符重载7函数调用运算符 () 重载8下标运算符 [] 重载9类成员访问运算符 - 重载