纯净、安全、绿色的下载网站

首页|软件分类|下载排行|最新软件|IT学院

当前位置:首页IT学院IT技术

C++中的const和constexpr 详解C++中的const和constexpr

yytarget   2021-12-03 我要评论
想了解详解C++中的const和constexpr的相关内容吗yytarget在本文为您仔细讲解C++中的const和constexpr的相关知识和一些Code实例欢迎阅读和指正我们先划重点:C++const,C++constexpr下面大家一起来学习吧

一.const常量与#define比较

  • define只是简单的替换没有类型const可以做到防窜改与类型安全
  • 而且#define会在内存中可能(有几次替换就有几次拷贝)有多份拷贝对于字面值常量加不加const都一样例如:const char* arr = “123”;储存在常量区只有一份拷贝;对于局部对象常量存放在栈区例如:void add(){const char crr[] = “123”;},这里“123”本应储存在栈上但编译器可能会做某些优化将其放入常量区;对于全局对象常量存放在全局/静态存储区;用const会比#define使用更少的空间效率更高
  • 这里有一个小例子:char* brr = "123"; char drr[] = "123";前者字符串123存在常量区不能通过brr去修改"123"的值;后者"123"保存在栈区可以通过drr去修改
  • 现在C++除了一些特定用法推荐用const,inline,enum等替换宏——来自《Effective C++》条款02

二.const修饰

1.修饰普通变量必须初始化

const int a = 10; 表示int对象a是一个常量不可以改变值从编译器生成二进制角度看生成的a存放在.rodata段也就是只读(readonly)区域不过并不绝对有的时间统计优化等级开的高也不取地址可能会优化成立即数在.text段中

2.修饰类变量和成员变量

class cAAA{
public:
    cAAA(int a) : m_iV(a){}
    const int GetValue() const {return m_iV;}
    void AddValueOneTime(){m_iChangeV++;}
private:
    const int m_iV;
public:
    mutable int m_iChangeV;
    static const int m_iStaticV;
};
static const int m_iStaticV = 1000;

const cAAA aa(100);
aa.GetValue();
aa.m_iChangeV++;
  • cAAA类成员m_iV是const变量必须放到初始化列表中进行初始化不能进行赋值
  • 对于静态常成员与普通静态成员类似推荐放到类外.cpp中初始化
  • aa只能调用const函数如aa.GetValue(),不能调用非常成员函数aa.AddValueOneTime()
  • 对于这种const对象又想修改成员可以在成员声明加上mutable这样const对象aa也可以修改m_iChangeV,这种用法比较少

3.修饰成员函数

  • 表示这个函数可以被const对象调用也可以被普通对象调用不会改变对象的成员也就是说更像一种只读不写型的逻辑运算所以有些人推荐类成员函数可以都加上const有一个小技巧当const和non-const成员函数有着实质等价的实现时令non-const版本调用const版本可避免代码重复;但反过来不行const函数内部也必须只能调用const函数—— 《Effective C++》条款03
  • 有一点要注意编译器强制实施bitwase constness但编写程序时应该使用conceptual constness解决编译器的bitwase constness属性就用到了上述的mutable关于介绍bitwase constness的具体表现可以参考《Effective C++》条款03

4.修饰指针

const char* p1;
char const *p2;
char* const p3;
const char* const p4;

对于初学者来说这大概是很难理解的一个知识点怎么区分这四个呢?记住秘诀直接从右向左读就一招制敌了

  • p1是一个指针指向char字符常量表示p1所指对象内容不可以改所指地址可以改
  • p2同p1写法不同两者等价
  • p3是一个常量且是个指针指向char字符表示p3所指对象内容可以改所指地址不可以改
  • p4是一个常量且是个指针指向char字符常量表示p4所指对象内容不可以改且所指地址也不可以改
  • 相对来说p1,p2是最常用传参或者返回值的手段

5.修饰引用

  • 修饰引用和对象差不多对象内容不可以改变作为函数参数传参数不存在copy开销这是比较推荐的写法例如:拷贝构造函数赋值构造STL里用于比较的函数或者仿函数详情请参阅《Effective C++》条款20bool Less(const cAAA& left, const cAAA& right);
float dValue = 1.05f;
const int& a = dValue;

const int iTemp = dValue;
const int& a = iTemp;
  • 因为常引用不能改变这种情况下编译器会创建一个临时变量来处理隐式转换我们实际是对临时变量进行了常引用

三.const转换

  • 一般来说从T*转换到const T*是比较简单的且编译器支持的隐式转换也可以显示的用模板处理例如我们简单写一下RemoveConst模板最后用using化名一下但从const T*到T*就麻烦一些推荐使用const_cast
template <typename T>
struct RemoveConst{
    typedef T Type;
};

template <typename T>
struct RemoveConst<const T>{
    typedef T Type;
};

template <typename T>
using RCType = typename RemoveConst<T>::Type;

四.顶层const与底层const

  • 简单来说const修饰的对象本身不能改变就是顶层const但如果是指针或者引用的对象不能改变则称为底层const
  • const int cV = 10; cV是顶层const本身不能改变
  • char const *p2; p2是底层constp2本身值可以改变但所指内容不可以改变
  • char* const p3; p3是顶层constp3的本身值不可以改变
  • const char* const p4; p4既是顶层const又是底层const
  • 注:对于上述模板RCType是无法移除p2这种底层const如果要移除请用const_cast<T*>移除但这种操作可能引起Crash或者未知风险
const char* pA = "sss";
char* pB = const_cast<char*>(pA);
auto pC = RCType<decltype(pA)>(pA);
std::cout << "type is the same: " << std::is_same<decltype(pB), decltype(pC)>::value << std::endl;
std::cout << "pB Type Name: " << typeid(pB).name() << "pc Type Name: " << typeid(pC).name() << std::endl;
//pB[0] = 'A';//error, Segmentation fault

五.C++11新引入的constexpr

  • 这个关键字表示这是一个常量表达式是一个编译期就可以确认的值最常用于模板中例如模板递归求值
  • 它可不只是变量例如:
const int iSize1 = sizeof(int);
const int iSize2 = GetSize();

iSize1是个常量编译期的但iSize2就不一定它虽然不能改变但要到GetSize()执行结束才能知道具体值这与常量一般在编译期就知道的思想不符解决这个问题的方法就是改为:constexpr int iSize2 = GetSize(); 这样要求GetSize()一定要能在编译期就算出值下面几个例子中GetSizeError()就会编译失败GetFibo函数编译期就已经递归计算出值

constexpr int GetSize(){
  return sizeof(int) + sizeof(double);
}

constexpr int GetSizeError(){
  return random();
}

constexpr int GetCalc(int N){
  return N <= 1 ? 1 : N * GetCalc(N - 1);
}

const int iSize1 = sizeof(int);
constexpr int iSize2 = GetSize();
//constexpr int iSize3() = GetSizeError();
constexpr int iSize4 = GetCalc(10);
std::cout << iSize1 << " " << iSize2 << " " << iSize4 <<std::endl;

当然我们可以用模板写GetCalc函数:

template <int N>
struct TCalc{
  static constexpr int iValue = N * TCalc<N-1>::iValue;
};

template <>
struct TCalc<1>{
  static constexpr int iValue = 1;
};
std::cout << TCalc<10>::iValue << std::endl;

总结

本篇文章就到这里了希望能够给你带来帮助也希望您能够多多关注的更多内容!


相关文章

猜您喜欢

  • docker安装ros2 docker安装ros2详细步骤介绍

    想了解docker安装ros2详细步骤介绍的相关内容吗鱼香ROShi在本文为您仔细讲解docker安装ros2的相关知识和一些Code实例欢迎阅读和指正我们先划重点:docker安装ros2,linux下安装ros2下面大家一起来学习吧..
  • BeyondCompare4使用 BeyondCompare4下载安装使用超详细步骤

    想了解BeyondCompare4下载安装使用超详细步骤的相关内容吗Hal白夜在本文为您仔细讲解BeyondCompare4使用的相关知识和一些Code实例欢迎阅读和指正我们先划重点:BeyondCompare4使用,BeyondCompare4安装使用下面大家一起来学习吧..

网友评论

Copyright 2020 www.eleasoftware.com 【绿软下载站】 版权所有 软件发布

声明:所有软件和文章来自软件开发商或者作者 如有异议 请与本站联系 点此查看联系方式