C++ constexpr 虚函数

文章详细解释了C++中的constexpr关键字用于编译期优化,以及虚函数实现的动态多态。constexpr确保了变量和表达式在编译时计算,而虚函数则允许在运行时决定函数调用的对象。文章还探讨了静态多态(包括函数重载和运算符重载)以及类的内存布局,特别是虚函数表在多继承情况下的表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++ constexpr + 虚函数

constexpr的使用

constexpr在c++中的使用主要用于做一些编译优化,在编译期间即可确定变量或者表达式的值信息。
例如下面的代码片段:

int A() {
   return 4;}
// 使用下面的代码会报错
std::array<int, sizeA> a;  // call to non-'constexpr' function

因为A是在执行阶段才能够确定其值,所以报错调用非constexpr的函数,如果将A函数改成constexpr就不会有报错,并且能够得到性能的优化

const int A() {
   return 4;}
std::array<int, sizeA> a;

此外,如果constexpr修饰的值是一个函数的返回值,那么该函数也需要用constexpr修饰,例如下面的代码片段

int f1(int a, int b) 
{
   
	return a + b;
}

constexpr int f2(int a, int b) 
{
   
	return a + b;
}

constexpr int a = f1(1, 2); // 报错,同样是call to non-'constexpr' function
constexpr int b = f2(3, 4); // 不报错

虚函数

多态本义为多种不同的状态,即调用一个函数可能出现不同的结果,在c++中多态通常包含两种,静态多态动态多态。静态多态主要通过重载实现,即相同的函数名,不同的函数参数类型,参数顺序,参数数量。还有运算符重载也算是静态多态。

静态多态

静态多态包含两类,分别是运算符重载和函数重载,函数重载的形式较为简单,下面的例子是函数重载。

// base function
double func1(int a, double b)
{
   
    return a + b;
}
// 不同的参数类型
double func1(double a, double b)
{
   
    return a + b;
}
// 不同参数顺序
double func1(double a, int b)
{
   
    return a + static_cast<double>(b);
}
// 不同的参数数量
double func1(double a)
{
   
    return a;
}

下面的函数不构成重载

// base function
double func1(int a, double b)
{
   
    return a + b;
}
// 仅有返回值不同不构成重载
int func1(double a, int b)
{
   
    return static_cast<int>(a) + b;
}

// 参数类型相同,名字不同不构成重载
double func1(double c, int d)
{
   
    return c + d;
}

插一段:类中的函数分类及存储

在类中通常包含两类函数,分别是静态函数,非静态函数,C++的内存通常包含四个区域,分别是常量区、代码区、栈区、堆区。

  • 常量区主要用于存放全局变量以及类中声明的静态变量、常量
  • 代码区主要用于存放静态函数,非静态函数的代码内容
  • 栈区主要用于存放局部变量、函数参数、返回数据、返回地址
  • 堆区主要用于存放动态分配的内存信息

静态成员函数和非静态成员函数,是在类定义时存放在内存的代码区的,类可以直接调用静态函数,但类无法调用非静态成员函数,只有类的对象才能够调用非静态成员函数,这是因为非静态成员函数内部有一个指向类对象的指针类型参数(this),只有在类对象调用时,才能够使得this有实际的值。

动态多态

动态多态主要通过虚函数来实现,虚函数是类中的一类函数,通过virtual实现。虚函数也依靠继承实现。首先看一下普通的继承。

单继承
class Father
{
   
public:
    explicit Father(int a): a(a)
    {
   
        cout << "father execute" << endl;
    }
    ~Father()
    {
   
        cout << "father release" << endl;
    }
private:
    int a;
};

class Children: public Father
{
   
public:
    explicit Children(int a, int b): Father(a), b(b)
    {
   
        cout << "Children execute" << endl;
    }
    ~Children()
    {
   
        cout << "Children release" << endl;
    }

private:
    int b;
};

Children t(1, 2); 
cout << "Test" << endl;
// 上面的代码执行结果是什么?

执行结果输出的前两句是通过构造函数实现的,后面两句是通过析构函数实现的。在调用Children的构造函数时,通过Father(a)先调用了Father的构造函数,然后第一个输出输出(1) father execute,之后执行Children的构造函数代码体,输出(2) Children execute。在执行完main函数之后,输出(3) Test。之后进行回收工作,执行Children的析构函数,输出(4) Children release,最后还要执行父类的析构函数,输出(5) father release,最终的输出结果如下

father execute
Children execute
Test
Children release
father release

在类中加入虚函数后,如下

class Father
{
   
public:
    explicit Father(int a): a(a)
    {
   
        cout << "father execute" << endl;
    }

    virtual void f(){
   }
    virtual void g()
### 使用模板与虚函数C++中,模板和虚函数都是强大的特性,但它们的工作方式不同。模板提供编译时的多态性,而虚函数则实现运行时的多态性。 当希望结合两者来创建灵活且高效的代码结构时,可以考虑如下模式: #### 定义带虚拟成员函数的模板基类 ```cpp template<typename T> class Base { public: virtual ~Base() {} virtual void execute(T value) const = 0; }; ``` 此段定义了一个名为`Base`的模板类[^1],该类具有纯虚函数`execute()`,接受类型为T的参数并返回void。这使得任何继承自`Base<T>`的具体子类都必须实现自己的版本。 #### 创建具体派生类 下面是一个具体的派生类例子,它实现了上述抽象方法: ```cpp #include <iostream> using namespace std; // 继承自Base<int> 的ConcreteInt 类 class ConcreteInt : public Base<int> { private: string name_; public: explicit ConcreteInt(const string& name):name_(name){} void execute(int value) const override{ cout << "Executing on integer in " << name_ << ": " << value * 2 << endl; } }; // 继承自Base<double> 的ConcreteDouble 类 class ConcreteDouble : public Base<double>{ private: string name_; public: explicit ConcreteDouble(const string& name):name_(name){} void execute(double value)const override{ cout << "Executing on double in " << name_ << ": " << value / 2.0 << endl; } }; ``` 这里展示了两个不同的类分别处理不同类型的数据(`int`, `double`),但是共享相同的接口设计思路。 #### 动态绑定实例化对象 为了展示动态分配以及通过指针调用虚函数的效果,下面是测试代码片段: ```cpp int main(){ // 声明指向Base类型的智能指针数组 vector<unique_ptr<Base<void>>> bases; // 向容器添加不同类型的元素 bases.push_back(make_unique<ConcreteInt>("Integer Processor")); bases.push_back(make_unique<ConcreteDouble>("Floating Point Processor")); // 遍历vector执行操作 for(auto&& basePtr:bases){ if constexpr(is_same_v<decltype(basePtr), unique_ptr<Base<int>>>) { static_cast<Base<int>&>(*basePtr).execute(4); } else if constexpr (is_same_v<decltype(basePtr), unique_ptr<Base<double>>>) { static_cast<Base<double>&>(*basePtr).execute(8.0); } } return 0; } ``` 这段程序首先构建了一组异构的对象集合,并尝试遍历时根据实际存储的内容自动选择合适的方法去调用相应的`execute()`函数。 需要注意的是,在这个特定的例子中,由于C++不允许直接拥有一个`Base<void>*`这样的通用指针(因为无法知道确切的操作),因此需要采用某种形式的类型擦除或变通方案来绕过这个问题;上面给出的方式只是示意性的做法之一。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值