C++ 学习系列 -- 运行时常量 const 与 编译期常量 constexpr 介绍

本文详细解析了C++中的const关键字,说明了其用于常量变量和指针的特性,以及const修饰函数和类成员的作用。同时介绍了constexpr关键字,强调了它作为编译期间常量的特点及其在函数和构造函数中的应用。
摘要由CSDN通过智能技术生成

一    const 关键字 

     C++ 中引入了 运行时常量 const ,被 const 关键字修饰的变量其值在程序运行期间是不能修改的,是只读的,只初始化一次。

1  const 变量的初始化与存储

1. const 变量初始化时就需要赋初值,否则无法编译通过

2  const 与指针

  • 指针常量:

    指针所指向的值不可改变,但是指针本身是可以改变的

  • 常量指针:

    指针本身是不可以改变的,但是所指向的值是可以改变的

例子如下:

int main()
{
    char   p[] = "wo shi hao ren!";
    char*  p1 = "wo shi hao ren!";
    std::cout << "p: "<<p << std::endl;
    std::cout << "p1: "<<p1 << std::endl;

    const int a = 66;
    // const int b; // 编译不过,需要赋初值

    // 常量指针
    const char* p2;// 编译通过,值为空,但是值不可以变化
    p2 = p1; // 编译通过
    // *p1 = "666"; // 编译不过, 因为值不可以变化
    
    // 指针常量
    char* const p3 = p1; // 指针不可以变,其指向的值可变
    // p3 = p2; // 编译不过,因为指针不可以变
    *p3 = 'b'; // 修改 p3 所指向的字符,因为指针常量指向的值是可变
    
    return 0;
}

3  const 与函数

  • const 修饰函数参数
    • 对于函数的入参,不管是什么数据类型,也不管是 指针传递,还是 引用传递,只要加了 const 修饰,就可以防止函数内意外修改该参数,起到保护作用
void  func1(const int a, const int* b){
    a = 66; // 错误, 不可修改
    *b = 6; // 错误, 不可修改
    int aaa = 888;
    b = &aaa;
}
  • const 修饰函数返回参数

     返回值为const值,或者const指针,const引用时该如何考虑呢?

       1. 返回值为 const 值传递

     若是返回值是值传递,因为会进行拷贝,获取到的是副本,此时加 const 关键字没有什么用处

      2. 返回值为 const 指针传递

   若是返回值为指针,此时加 const 则意味着返回的指针锁指向的内容不可以被修改

      3. 返回值为const 引用传递

若是返回值为引用,此时加 const 则意味着返回的变量不可修改

      4. const 修饰的类成员函数

类成员函数加上 const 后,意味着该函数不可以修改类的成员变量,也不可以调用其他非 const 修饰的函数(因为无法预料非const 修饰的函数是否会修改类的成员变量)

    对象调用 const 成员函数与非 const 成员函数的关系列表如下:

const 对象non-const 对象
const 成员函数可调用可调用
non-const 成员函数不可调用可调用

         当类有两个相同名称的成员函数时,即函数重载有两个版本一个由 const 关键字修饰,一个无 const 关键字修饰,此时,const 对象与 non-const 对象分别调用成员函数时,会出现

const 对象 调用 const 版本的成员函数,non-const 对象调用 non-const 版本的成员函数。

// aaa.h
#include<string>
#include<iostream>

class BBB
{
public:
    BBB(std::string name):m_name(name)
    {
    }
    ~BBB()
    {
    }

public:
    std::string m_name;
};

class AAA
{
public:
    AAA(BBB* ptr):m_ptr(ptr)
    {

    }
    ~AAA(){
        if(m_ptr)
            delete m_ptr;
    }
    // 1.返回值为 const 值传递
    const BBB getValue1(){
        return *this->m_ptr;
    }
    // 2.返回值为 const 指针传递
    const BBB* getValue2(){
        return this->m_ptr;
    }
    // 3.返回值为const 引用传递
    const BBB& getValue3()
    {
        return *this->m_ptr;
    }
    // 4.const 修饰的类成员函数
    BBB& getValue4() const
    {
       // getValue1(); // 编译不通过
        return *this->m_ptr;
    }


    void printValNonConst()
    {
        std::cout << "non-const printValNonConst" << std::endl;
    }

    void printValConst() const
    {
        std::cout << "const printValConst" << std::endl;
    }

    void printVal()
    {
        // do nothing
        std::cout << "non-const printVal" << std::endl;
    }

    void printVal() const
    {
        // do nothing
        std::cout << "const printVal" << std::endl;
    }

private:
    BBB* m_ptr;
};

// main.cpp
#include"aaa.h"

int main()
{
   BBB* bbb = new BBB("bbb666");
   cout << bbb->m_name << endl;
   AAA aaa(bbb);

   // 1.返回值为 const 值传递
   BBB b1 = aaa.getValue1();
   // 2.返回值为 const 指针传递
   const BBB* b2 = aaa.getValue2();
   b2 = new BBB("b2"); // 可以编译通过
   // *b2 = *bbb; // 无法编译通过

   // 3.返回值为const 引用传递
   const BBB& b3 = aaa.getValue3();
   // b3 = b1;  // 无法编译通过
   // b3.m_name = "aaa"; // 无法编译通过

   // 4.const 修饰的类成员函数
   BBB& b4 = aaa.getValue4();
   b4.m_name = "888888"; // 可以改变 bbb 对象的成员变量值
   cout << bbb->m_name << endl;

   delete bbb;

   // non-const 对象
   AAA aa1(bbb);
   aa1.printValConst(); // non-const 对象允许调用 non-const 成员函数
   aa1.printValNonConst(); // // non-const 对象天然可以调用 non-const 成员函数

   // const 对象
   const AAA aa2(bbb);
   // aa2.printValNonConst(); // 无法编译通过, const 对象不允许调用 non-const 成员函数
   aa2.printValConst(); // const 对象天然可以调用 const 成员函数

   // 面对相同签名的成员函数 printVal, 同时重载 const and non-const version,结果如下
   aa1.printVal(); // non-const 对象调用 non-const 成员函数 printVal
   aa2.printVal(); // const 对象调用 const 成员函数 printVal
   return 0;
}

输出:

二  constexpr 关键字

       constexpr 是 c++11 引入的关键字,与前面的 const 关键字的区别在于,其所修饰的变量是编译期间的常量,故而也叫做编译期常量。

       引入 constexpr  关键字的目的是将一些常量的运算提前到编译期间运行,提高程序运行时的效率,缺点是因为将计算逻辑提前,可能会导致编译时间增加。

        constexpr关键字可以用于修饰普通变量、函数(包括模板函数)以及类的构造函数。

注意:被 constexpr 关键字修饰的表达式不一定会在编译期间执行,具体计算的时机,还是视编译器而定。

  1   constexpr 修饰普通变量

       使用 constexpr 修饰变量时,变量必须定义时就要初始化,且初始值要是一个常量表达式。

 constexpr int num = 1 + 2 + 3;
 int url[num] = {1,2,3,4,5,6};

  2   constexpr 修饰函数

     constexpr 可以用于修饰函数的返回值,这样的函数也被称为 “常量表达式函数”。

    常量表达式函数需要满足以下 4 哥条件:

   1. 整个函数的函数体中,除了可以包含 using 指令、typedef 语句以及 static_assert 断言外,只能包含一条 return 返回语句。

  2. 该函数必须有返回值,即函数的返回值类型不能是 void

  3. 函数在使用之前,必须有对应的定义语句。我们知道,函数的使用分为“声明”和“定义”两部分,普通的函数调用只需要提前写好该函数的声明部分即可(函数的定义部分可以放在调用位置之后甚至其它文件中),但常量表达式函数在使用前,必须要有该函数的定义。

 4. return 返回的表达式必须是常量表达式

      

int num = 3;
constexpr int display1(int x){ 
    return num + x;  // 非常量表达式,编译出错
}

constexpr int display2(int x){ 
    return  1 + 6 + x;  // 常量表达式,编译通过
}

int main()
{
   int res1 = display1(33);
   int res2 = display2(33);   
 
   return 0;
}


  3   constexpr 修饰类构造函数

    对于 C++ 内置类型的数据,可以直接用 constexpr 修饰,但如果是自定义的数据类型(用 struct 或者 class 实现),直接用 constexpr 修饰是不行的。

    如果我们想自定义一个可产生 constexpr 常量的类型时,正确的做法是在该类型的内部添加一个常量构造函数

   针对上述三种情况,下面写了几个测试代码:

   

// c.h
class C
{
public:
    C()
    {
    }
};

// d.h
class D
{
public:
    constexpr D(char* name):m_name(name)
    {
    }
public:
   const char* m_name;
};

// main.cpp
#include"c.h"
#include"d.h"

int main()
{
    
      // 1. constexpr 修饰变量
    constexpr int num = 1 + 2 + 3;
    // num = 22; // 与 const 关键字相同,初始化后的变量不允许修改

    int url[num] = {1,2,3,4,5, 6};
    std::cout<< url[1] << std::endl;
    int num1 = 1 + 2 + 3;
    // int url1[num1] = {1,2,3,4,5, 6}; // 编译不过,数组长度需要是一个常量
    // std::cout<< url1[1] << std::endl;
    // constexpr int aaa; // 编译不过,constexpr 变量声明时就需要初始化

    // 2. constexpr 修饰函数
    auto begin = std::chrono::high_resolution_clock::now();
    std::cout << fab(10000) << std::endl;
    auto end = std::chrono::high_resolution_clock::now();
    auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin);
    std::cout << "constexpr function time spend: " << elapsed.count() << std::endl;

    auto begin1 = std::chrono::high_resolution_clock::now();
    std::cout << fab2(10000) << std::endl;
    auto end1 = std::chrono::high_resolution_clock::now();
    auto elapsed1 = std::chrono::duration_cast<std::chrono::nanoseconds>(end1 - begin1);
    std::cout << "constexpr function time spend: " << elapsed1.count() << std::endl;

    // 3. constexpr 修饰类构造函数
    const C c1;
   // constexpr C c2; // 编译不过, 对于 C++ 内置类型的数据,可以直接用 constexpr 修饰,但如果是自定义的数据类型(用 struct 或者 class 实现),直接用 constexpr 修饰是不行的。
    constexpr D d1("aaaaaaa"); // 可以编译过
    std::cout << d1.m_name << std::endl;

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值