什么是动态特性?动态,顾名思义,一直处于变化之中,以程序为例,如果程序的功能是在运行时刻才确定下来的,则称为动态特性。
动态特性是面向对象语言最强大的功能之一,因为它在语言层面上支持程序的可扩展性,而可扩展性是软件设计追求的重要目标之一。
在C++中,动态特性可以有多种形式,其中一些包括:内存分配和释放、C++虚函数与动态绑定、多态、RTTI构成了出色的动态特性,下面针对每个特性简单表达下自己的观点。
1.内存分配和释放
内存分配和释放: 动态内存分配是指在程序运行时根据需要分配内存。这通过 new 和 delete 运算符来实现。动态内存的分配和释放允许程序在运行时动态管理内存,这对于处理未知大小或数量的数据非常有用。
内存分配:
在C++中,常见的内存分配方式是使用 new 操作符来动态分配内存。new 操作符用于在堆上分配内存,并返回指向新分配的内存的指针。分配的内存可以是基本数据类型、自定义对象或数组等。
// 动态分配一个整数
int* ptr = new int;
if (ptr) {
// 内存分配成功
*ptr = 10;
std::cout << "Dynamically allocated integer: " << *ptr << std::endl;
// 记得在不再需要时释放内存
delete ptr;
ptr == nullptr;
} else {
// 内存分配失败
std::cerr << "Memory allocation failed!" << std::endl;
}
除了单个对象外,还可以使用 new 操作符动态分配数组:
// 动态分配一个整数数组
int size = 5;
int* arr = new int[size];
if (arr) {
// 内存分配成功
for (int i = 0; i < size; ++i) {
arr[i] = i * 2;
}
// 输出数组元素
for (int i = 0; i < size; ++i) {
std::cout << "arr[" << i << "] = " << arr[i] << std::endl;
}
// 记得在不再需要时释放内存
delete[] arr;
arr == nullptr;
} else {
// 内存分配失败
std::cerr << "Memory allocation failed!" << std::endl;
}
内存释放:
在C++中,使用 delete 操作符来释放之前动态分配的内存。对于动态分配的单个对象,使用 delete,而对于动态分配的数组,使用 delete[]。
// 动态分配一个对象
SomeClass* obj = new SomeClass;
// 使用对象
// ...
// 不再需要对象时释放内存
delete obj;
// 动态分配一个数组
int* arr = new int[5];
// 使用数组
// ...
// 不再需要数组时释放内存
delete[] arr;
注意:
使用 delete 或 delete[] 释放动态分配的内存非常重要,否则可能导致内存泄漏。
动态分配的内存只能在不再需要时手动释放,否则会导致内存泄漏或内存溢出的问题。
在释放内存后,确保将指针设置为 nullptr,以避免悬空指针的问题。
2.c++虚函数与动态绑定
在C++中,虚函数通过在基类中使用 virtual 关键字来声明,派生类中的相同函数可以使用 override 关键字来明确指示其覆盖了基类的虚函数。
当基类指针或引用调用虚函数时,会根据实际对象的类型动态绑定到正确的函数实现,这种行为称为动态多态性。
虚函数是一种特殊的成员函数,它允许在派生类中重写(覆盖)基类的同名函数,并且可以通过基类指针或引用来调用派生类的函数实现。这种特性是C++中实现多态性的关键。
#include <iostream>
// 基类
class Base {
public:
// 声明虚函数
virtual void display() {
std::cout << "Base::display()" << std::endl;
}
};
// 派生类
class Derived : public Base {
public:
// 覆盖基类的虚函数
void display() override {
std::cout << "Derived::display()" << std::endl;
}
};
int main() {
Base* ptr = new Derived(); // 使用基类指针指向派生类对象
ptr->display(); // 调用虚函数,动态绑定到派生类的实现
delete ptr; // 释放内存
return 0;
}
在这个示例中,Base 类中的 display() 函数被声明为虚函数,而 Derived 类覆盖了该虚函数。在 main() 函数中,通过基类指针 ptr 指向派生类对象,并调用 display() 函数。由于 display() 是虚函数,因此会动态绑定到 Derived 类中的实现,最终输出 “Derived::display()”。这就是虚函数的动态特性。
3.多态性:
多态性(Polymorphism)是面向对象编程中的一个重要概念,允许同一类的不同对象对同一个消息作出不同的响应。这使得程序能够根据对象的实际类型来调用适当的函数。多态性使得相同的代码可以根据上下文采取不同的行为,这提高了代码的灵活性、可扩展性和可维护性。
在C++中,多态性主要通过两种方式实现:
编译时多态性(静态多态性):
由函数重载和运算符重载实现。编译时多态性发生在编译阶段,通过函数或运算符的重载,根据参数的类型或数量来选择调用适当的函数或运算符重载。
#include <iostream>
// 函数重载
void print(int num) {
std::cout << "Integer: " << num << std::endl;
}
void print(double num) {
std::cout << "Double: " << num << std::endl;
}
int main() {
print(5); // 调用第一个 print() 函数
print(3.14); // 调用第二个 print() 函数
return 0;
}
运行时多态性(动态多态性):
由继承和虚函数实现。运行时多态性发生在程序运行时,通过基类指针或引用调用虚函数时,根据对象的实际类型来确定调用的函数实现,这称为动态绑定。
#include <iostream>
// 基类
class Animal {
public:
// 虚函数
virtual void makeSound() {
std::cout << "Animal makes a sound" << std::endl;
}
};
// 派生类
class Dog : public Animal {
public:
// 覆盖基类的虚函数
void makeSound() override {
std::cout << "Dog barks" << std::endl;
}
};
// 派生类
class Cat : public Animal {
public:
// 覆盖基类的虚函数
void makeSound() override {
std::cout << "Cat meows" << std::endl;
}
};
int main() {
Animal* ptr;
Dog dog;
Cat cat;
ptr = &dog;
ptr->makeSound(); // 动态绑定到 Dog 类中的实现
ptr = &cat;
ptr->makeSound(); // 动态绑定到 Cat 类中的实现
return 0;
}
4.运行时类型信息(RTTI):
RTTI 允许程序在运行时获取对象的类型信息。在C++中,可以使用 typeid 运算符和 dynamic_cast 运算符来实现这一点。RTTI 可以用于实现各种动态行为,如类型安全的向下转型。
dynamic_cast:用于在继承关系中进行安全的向下转型(downcast),即将基类指针或引用转换为派生类指针或引用。
class Base {
virtual void foo() {}
};
class Derived : public Base {
// ...
};
int main() {
Base* basePtr = new Derived;
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
if (derivedPtr) {
// 转型成功
// 使用 derivedPtr 进行操作
} else {
// 转型失败
// basePtr 实际指向的对象不是 Derived 类型
}
delete basePtr;
return 0;
}
在这个示例中,dynamic_cast 将 Base* 类型的指针 basePtr 转换为 Derived* 类型的指针 derivedPtr,如果转型成功,则可以安全地使用 derivedPtr 进行操作;如果转型失败,则 derivedPtr 会被设置为 nullptr。
typeid:用于获取对象的类型信息。
#include <typeinfo>
#include <iostream>
class Base {
virtual void foo() {}
};
class Derived : public Base {
// ...
};
int main() {
Base* basePtr = new Derived;
if (typeid(*basePtr) == typeid(Derived)) {
std::cout << "basePtr 指向的对象是 Derived 类型" << std::endl;
} else {
std::cout << "basePtr 指向的对象不是 Derived 类型" << std::endl;
}
delete basePtr;
return 0;
}
在这个示例中,typeid 运算符用于获取 basePtr 指向的对象的类型信息,并与 Derived 类型进行比较。如果它们相同,则输出相应的信息。
RTTI在某些情况下非常有用,但需要谨慎使用,因为它可能会引入一些运行时开销。通常情况下,尽量避免对类型进行运行时的检查和转换,而是通过良好的设计和使用虚函数来实现多态性
有动态特性,那么肯定有静态特性,下次在给大家安利下静态特性。