菜鸟笔记
提升您的技术认知

动态特性是什么(C++)

什么是动态特性?动态,顾名思义,一直处于变化之中,以程序为例,如果程序的功能是在运行时刻才确定下来的,则称为动态特性。

动态特性是面向对象语言最强大的功能之一,因为它在语言层面上支持程序的可扩展性,而可扩展性是软件设计追求的重要目标之一。

在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在某些情况下非常有用,但需要谨慎使用,因为它可能会引入一些运行时开销。通常情况下,尽量避免对类型进行运行时的检查和转换,而是通过良好的设计和使用虚函数来实现多态性

有动态特性,那么肯定有静态特性,下次在给大家安利下静态特性。