电商网站开发平台哪个好,奈曼旗建设局网站,门户网站建设的作用及意义,上海公司牌照价格最新价格什么是面向对象#xff08;Object-Oriented Programming, OOP#xff09;
1.面向对象是一种编程范式#xff0c;它通过将软件系统的设计和开发分解为“对象”#xff08;Object#xff09;的方式来实现更好地组织代码。面向对象的核心思想是将程序的结构分为对象#xf…什么是面向对象Object-Oriented Programming, OOP
1.面向对象是一种编程范式它通过将软件系统的设计和开发分解为“对象”Object的方式来实现更好地组织代码。面向对象的核心思想是将程序的结构分为对象这些对象包含数据和操作这些数据的函数即方法。每个对象是类的实例而类定义了对象的属性和行为。OOP有助于提高代码的可维护性、可重用性和扩展性。 2. 面向过程和面向对象的区别 面向过程根据业务逻辑从上到下写代码
面向对象将数据与函数绑定到一起进行封装加快开发程序减少重复代码的重写过程 面向对象的四大基本特性 封装Encapsulation 定义封装是将对象的属性和方法包装起来使得对象内部的数据只能通过定义的方法来访问或修改从而保护数据的完整性。目的隐藏内部实现防止外部直接访问对象内部数据提供一个接口与对象交互。示例 class Person { private: string name; int age; public: // 设置名称 void setName(string n) { name n; } // 获取名称 string getName() { return name; } }; 继承Inheritance 定义继承是从现有类中派生新类的过程新类可以继承父类的属性和方法并且可以扩展或修改父类的行为。目的复用代码减少重复实现代码的层次化结构。示例 #include iostream using namespace std; class Base { public: int pub_member; protected: int prot_member; private: int priv_member; }; class PublicDerived : public Base { public: void accessBase() { pub_member 10; // 可以访问 prot_member 20; // 可以访问 // priv_member 30; // 无法访问编译错误 } }; class ProtectedDerived : protected Base { public: void accessBase() { pub_member 10; // 可以访问但变为 protected prot_member 20; // 可以访问 // priv_member 30; // 无法访问编译错误 } }; class PrivateDerived : private Base { public: void accessBase() { pub_member 10; // 可以访问但变为 private prot_member 20; // 可以访问 // priv_member 30; // 无法访问编译错误 } }; int main() { PublicDerived pubObj; pubObj.pub_member 10; // 可以访问public // ProtectedDerived protObj; // protObj.pub_member 10; // 无法访问protected // PrivateDerived privObj; // privObj.pub_member 10; // 无法访问private return 0; } public 继承 公开继承 (public) 是最常见的继承方式表示“is-a”关系派生类对象可以被当作基类对象使用。 基类的 public 成员在派生类中仍然保持 publicprotected 成员保持 protected。 基类的 private 成员对派生类不可见。 protected 继承 保护继承 (protected) 限制了派生类对基类成员的访问范围。 基类的 public 和 protected 成员在派生类中都变为 protected。 外部无法访问派生类中的这些成员但派生类的子类仍然可以访问它们。 private 继承 私有继承 (private) 是最为封闭的继承方式表示“implemented-in-terms-of”关系。 基类的 public 和 protected 成员在派生类中都变为 private外部无法访问派生类的子类也无法访问。 通常用于当派生类不想暴露基类的接口时。 多态Polymorphism 定义多态允许对象以不同的形式出现具体表现为同样的方法可以作用于不同的对象而产生不同的行为。多态分为编译时多态函数重载、运算符重载和运行时多态虚函数。目的通过统一接口处理不同的对象提高代码的扩展性和灵活性。也可以理解为用父类型的指针指向其子类的实例然后通过父类的指针调用实际子类的成员函数。实现多态有二种方式重写重载示例 class Animal { public: virtual void makeSound() { cout Some generic sound endl; } }; class Dog : public Animal { public: void makeSound() override { cout Bark! endl; } }; class Cat : public Animal { public: void makeSound() override { cout Meow! endl; } }; void playSound(Animal* animal) { animal-makeSound(); } 抽象Abstraction 定义抽象是指从复杂的现实问题中提取出关键特性而忽略掉具体的细节。它通过接口或抽象类来定义一组必须被实现的方法。目的隐藏复杂实现仅保留相关功能从而简化程序结构。示例 class Shape { public: virtual void draw() 0; // 纯虚函数 }; class Circle : public Shape { public: void draw() override { cout Drawing a circle endl; } }; 在 C 中重载Overloading和重写Overriding是两种不同的概念它们都允许函数的行为在某种程度上发生变化但在使用方式和适用场景上有显著的不同。
2.1 重载Overloading
重载是指在同一个作用域中允许定义多个同名函数但它们的参数列表必须不同。重载函数可以根据传递的不同类型或数量的参数执行不同的功能。重载常见于函数重载和运算符重载。
函数重载的实现
函数重载要求函数名称相同但参数类型、参数个数或参数顺序必须不同。
示例
#include iostream using namespace std;
class Calculator { public: // 重载加法函数处理整数加法 int add(int a, int b) { return a b; } // 重载加法函数处理浮点数加法 double add(double a, double b) { return a b; } // 重载加法函数处理三个数的加法 int add(int a, int b, int c) { return a b c; } };
int main() { Calculator calc; cout calc.add(10, 20) endl; // 调用整数加法 cout calc.add(10.5, 20.3) endl; // 调用浮点数加法 cout calc.add(1, 2, 3) endl; // 调用三个整数加法 return 0; }
运算符重载的实现
运算符重载允许开发者为自定义类型定义特定的操作符行为如 、-、 等。
示例
#include iostream using namespace std;
class Complex { private: double real; double imag;
public: Complex(double r, double i) : real(r), imag(i) {} // 重载加法运算符 Complex operator(const Complex other) { return Complex(real other.real, imag other.imag); } void display() { cout real imag i endl; } };
int main() { Complex c1(3.0, 4.0); Complex c2(1.0, 2.0); Complex c3 c1 c2; // 使用重载的加法运算符 c3.display(); // 输出4 6i return 0; }
2.2 重写Overriding
重写是指在继承关系中子类重新定义从父类继承而来的函数以实现不同的行为。重写通常用于运行时多态通过父类指针或引用调用子类的重写方法。这涉及到虚函数virtual的使用。
重写的实现
重写的前提是函数必须在父类中标记为 virtual。子类的重写方法必须和父类的虚函数函数签名完全一致包括返回类型、参数类型等。
示例
#include iostream using namespace std;
class Animal { public: // 定义虚函数 virtual void makeSound() { cout Some generic animal sound endl; } };
class Dog : public Animal { public: // 重写父类的虚函数 void makeSound() override { cout Bark! endl; } };
class Cat : public Animal { public: // 重写父类的虚函数 void makeSound() override { cout Meow! endl; } };
int main() { Animal* animal1 new Dog(); Animal* animal2 new Cat(); animal1-makeSound(); // 输出Bark! animal2-makeSound(); // 输出Meow! delete animal1; delete animal2; return 0; } 在这个例子中Dog 和 Cat 都重写了 Animal 类中的 makeSound 方法。当我们通过父类指针调用 makeSound 时调用的是子类的版本这就是运行时多态的效果。
2.3 重载与重写的区别
特性重载Overloading重写Overriding发生时机编译时在编译时通过函数签名区分不同的函数。运行时通过虚函数表在运行时选择合适的重写方法。适用范围同一个类中或者在全局作用域中定义的多个同名函数。继承体系中的子类对父类虚函数的重新定义。函数签名要求必须有不同的参数类型、数量或顺序返回值可以相同或不同。函数签名必须与父类完全一致包含返回类型。使用场景当同一个功能可以通过不同的参数类型或数量实现时。当子类需要修改父类的行为时使用且通常伴随多态实现。是否依赖继承关系不依赖继承。依赖继承必须在子类中定义。是否需要虚函数不需要。需要虚函数或纯虚函数virtual 关键字。执行效率在编译时就确定调用哪个重载函数因此效率较高。需要在运行时通过虚函数表查找效率相对较低。
2.4 总结
重载是通过改变参数列表来实现相同函数名的不同功能适用于同一作用域中。重写是子类对父类虚函数的重新定义适用于继承关系并且通常伴随着多态的实现。编译时 vs 运行时重载是编译时行为重写是运行时行为。
图表总结
特性重载Overloading重写Overriding时机编译时运行时作用域同一类或全局范围继承关系中的子类和父类参数要求参数类型、数量或顺序不同与父类方法签名完全一致虚函数无需虚函数必须是虚函数或纯虚函数主要功能通过不同参数处理相同逻辑子类修改父类的行为 3.1 构造函数的种类及作用
构造函数是类的一部分用于在对象创建时初始化对象的状态。C 中有以下几种类型的构造函数
1. 默认构造函数Default Constructor
默认构造函数是不带参数或所有参数都有默认值的构造函数用于在没有显式传递参数时初始化对象。
作用默认构造函数用于初始化对象为一个默认状态。当没有自定义构造函数时编译器会自动生成一个默认的构造函数。
示例
class Person { public: Person() { cout Default constructor called! endl; } };
int main() { Person p; // 调用默认构造函数 }
2. 带参数的构造函数Parameterized Constructor
带参数的构造函数允许在对象创建时传递参数并根据参数的值初始化对象的属性。
作用带参数构造函数用于灵活地初始化对象传递不同的参数以定制化对象的状态。
示例
class Person { public: string name; int age; Person(string n, int a) { // 带参数构造函数 name n; age a; } };
int main() { Person p(Alice, 30); // 创建对象时传递参数 cout p.name , p.age endl; }
3. 拷贝构造函数Copy Constructor
拷贝构造函数用于通过已有对象初始化新对象即使用一个对象的值来创建另一个相同类型的对象。其形式为 ClassName(const ClassName other)。
作用在需要创建一个新对象并将已有对象的数据复制给它时使用常见于对象作为函数参数或返回值的场景。
示例
class Person { public: string name; // 拷贝构造函数 Person(const Person other) { name other.name; } };
int main() { Person p1(Alice); Person p2 p1; // 调用拷贝构造函数 cout p2.name endl; }
4. 移动构造函数Move Constructor
移动构造函数用于将资源从一个对象“移动”到另一个对象避免不必要的复制。其形式为 ClassName(ClassName other)传递的是右值引用。
作用在需要“移动”资源如堆内存而不是复制时可以显著提高性能。
示例
class Person { public: string* name; Person(string n) { name new string(n); } // 移动构造函数 Person(Person other) { name other.name; other.name nullptr; // 释放原对象的资源 } ~Person() { delete name; } };
int main() { Person p1(Alice); Person p2 std::move(p1); // 调用移动构造函数 }
3.2 析构函数Destructor
析构函数在对象的生命周期结束时被调用用于释放资源。析构函数的名称与类名相同但前面有个波浪符号 ~且不接受参数。
作用
用于清理对象在生存期内分配的资源如内存、文件句柄、网络连接等防止资源泄露。
示例
class Person { public: Person() { cout Constructor called! endl; } ~Person() { cout Destructor called! endl; } };
int main() { Person p; // 构造函数会在对象创建时调用析构函数会在对象销毁时调用 }
3.3 只定义析构函数时自动生成哪些构造函数
如果在类中只定义析构函数而没有定义构造函数编译器将自动生成以下几种构造函数
默认构造函数如果类中没有任何构造函数编译器将生成一个默认构造函数用于默认初始化对象。拷贝构造函数如果类没有自定义的拷贝构造函数编译器将生成一个浅拷贝的拷贝构造函数。拷贝赋值运算符如果没有自定义的赋值运算符编译器会生成一个默认的赋值运算符进行浅拷贝。移动构造函数如果需要编译器可能会根据需要生成一个默认的移动构造函数。移动赋值运算符如果需要类似于移动构造函数。
注意如果类中有指针或其他需要深拷贝的资源管理默认生成的构造函数和赋值运算符可能会导致资源管理问题如重复释放内存。
3.4 一个类默认会生成哪些函数
当你定义一个类但没有显式定义构造函数、析构函数或赋值运算符时编译器会自动生成以下函数 默认构造函数用于在不传递参数时初始化对象。如果类没有定义任何构造函数编译器会生成一个默认构造函数。 拷贝构造函数用于通过另一个同类型的对象初始化新对象。默认的拷贝构造函数执行浅拷贝将对象的所有成员逐字复制。 拷贝赋值运算符operator用于将一个对象赋值给另一个相同类型的对象。编译器生成的默认赋值运算符同样执行浅拷贝。 析构函数用于销毁对象并释放资源。默认析构函数不会执行任何特定操作只会清理对象的内存。 移动构造函数如果类使用了动态资源管理如指针并且你未定义移动构造函数编译器可能会自动生成一个移动构造函数。 移动赋值运算符同样地如果类涉及动态资源编译器可能会生成一个移动赋值运算符。
3.5 总结
函数类型自动生成的条件作用默认构造函数如果没有任何构造函数初始化对象所有成员初始化为默认值拷贝构造函数如果没有自定义拷贝构造函数使用已有对象初始化新对象浅拷贝拷贝赋值运算符如果没有自定义赋值运算符将一个对象赋值给另一个对象浅拷贝析构函数如果没有自定义析构函数在对象生命周期结束时调用释放资源移动构造函数如果没有自定义移动构造函数且需要时生成将资源从一个对象“移动”到另一个对象避免不必要的复制移动赋值运算符如果没有自定义移动赋值运算符且需要时生成将资源从一个对象移动给另一个对象 4.1 C 类对象的初始化顺序
在 C 中类对象的初始化顺序是确定的并且遵循以下规则 基类先于派生类 如果类有继承关系则先初始化基类的部分再初始化派生类的部分。初始化顺序是基类先于派生类。 成员变量按照声明顺序初始化 类的成员变量按照它们在类中的声明顺序进行初始化而不是按照它们在初始化列表中的顺序。 初始化列表优先于构造函数体 如果类的构造函数包含初始化列表成员变量在进入构造函数体之前就会被初始化。
示例
#include iostream using namespace std;
class Base { public: Base() { cout Base class initialized endl; } };
class Derived : public Base { public: int x; int y; // 使用初始化列表 Derived(int a, int b) : x(a), y(b) { cout Derived class initialized endl; } };
int main() { Derived d(10, 20); return 0; }
输出顺序
Base class initialized Derived class initialized
这里的顺序是
基类 Base 先被初始化。派生类 Derived 中的成员 x 和 y 按照声明顺序初始化。构造函数体最后执行。
4.2 多重继承下的初始化顺序
在多重继承中基类的初始化顺序与它们在类声明中的顺序一致而不是在初始化列表中的顺序。
示例
#include iostream using namespace std;
class Base1 { public: Base1() { cout Base1 class initialized endl; } };
class Base2 { public: Base2() { cout Base2 class initialized endl; } };
class Derived : public Base1, public Base2 { public: Derived() { cout Derived class initialized endl; } };
int main() { Derived d; return 0; }
输出顺序
Base1 class initialized Base2 class initialized Derived class initialized
在多重继承的情况下Base1 和 Base2 的初始化顺序与它们在 Derived 类中的声明顺序一致即从左到右而派生类 Derived 最后被初始化。 4.3 类型转换上向下转型和向下转型
4.3.1 上向转型Upcasting
上向转型是指将派生类对象的指针或引用转换为基类类型。这种转换是安全的因为派生类对象包含基类的部分。
特点 隐式转换编译器自动进行上向转型无需显式指定。转换后只有基类的成员可以通过该指针或引用访问。
示例
class Base {
public:void show() {cout Base class endl;}
};class Derived : public Base {
public:void show() {cout Derived class endl;}
};int main() {Derived d;Base* basePtr d; // 上向转型basePtr-show(); // 调用基类的 show() 方法
}
输出
Base class
在上向转型时即使派生类重写了基类的方法使用基类指针调用的仍然是基类的方法除非基类的方法是虚函数virtual。 4.3.2 下向转型Downcasting
下向转型是指将基类对象的指针或引用转换为派生类类型。这种转换不安全因为基类对象可能并不包含派生类的部分因此需要使用 dynamic_cast 来确保转换的安全性。
特点 下向转型需要显式进行。使用 dynamic_cast 来确保安全性失败时返回 nullptr。通常涉及多态和虚函数。
示例
class Base { public: virtual void show() { cout Base class endl; } };
class Derived : public Base { public: void show() override { cout Derived class endl; } };
int main() { Base* basePtr new Derived(); // 上向转型 Derived* derivedPtr dynamic_castDerived*(basePtr); // 下向转型 if (derivedPtr) { derivedPtr-show(); // 输出Derived class } else { cout Failed to cast endl; } delete basePtr; }
在这个例子中dynamic_cast 确保下向转型的安全性如果转换失败derivedPtr 会是 nullptr。
4.4 深拷贝和浅拷贝
4.4.1 浅拷贝Shallow Copy
浅拷贝只复制对象的值对于指针成员它只复制指针本身的地址而不复制指针所指向的对象。这可能导致多个对象指向同一个内存区域进而引发资源共享问题或重复释放的问题。
示例
class Person { public: char* name; Person(const char* n) { name new char[strlen(n) 1]; strcpy(name, n); } // 浅拷贝构造函数默认 Person(const Person other) { name other.name; // 只复制指针地址未分配新内存 } ~Person() { delete[] name; // 重复释放可能会导致错误 } };
问题当一个对象被销毁时指针所指向的内存会被释放如果有多个对象共享相同的指针地址会导致重复释放内存的错误。
4.4.2 深拷贝Deep Copy
深拷贝不仅复制对象本身的值还为每个指针成员分配新的内存并复制指针指向的内容。这样每个对象都有独立的资源不会共享同一个内存区域。
实现深拷贝的拷贝构造函数
class Person {
public:char* name;Person(const char* n) {name new char[strlen(n) 1];strcpy(name, n);}// 深拷贝构造函数Person(const Person other) {name new char[strlen(other.name) 1]; // 分配新内存strcpy(name, other.name); // 复制内容}~Person() {delete[] name; // 正常释放内存}
};int main() {Person p1(Alice);Person p2 p1; // 调用深拷贝构造函数return 0;
}
深拷贝的优点
每个对象都拥有独立的内存资源不会互相影响。避免了共享指针带来的资源管理问题如重复释放。
4.5 总结
概念描述初始化顺序基类先于派生类成员按照声明顺序初始化初始化列表先于构造函数体。多重继承初始化顺序基类按声明顺序初始化派生类最后初始化。上向转型将派生类对象转换为基类类型隐式转换常用于多态。下向转型将基类对象转换为派生类类型使用 dynamic_cast 进行安全转换。浅拷贝只复制指针地址多个对象共享同一块内存可能导致资源管理问题。深拷贝为每个指针成员分配新的内存并复制内容确保每个对象有独立的资源避免共享指针问题。