详解C++中的const和constexpr
程序员文章站
2022-06-26 10:48:14
目录一.const常量与#define比较二.const修饰1.修饰普通变量,必须初始化2.修饰类变量和成员变量3.修饰成员函数4.修饰指针5.修饰引用三.const转换四.顶层const与底层con...
一.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++》条款20。bool 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是底层const,p2本身值可以改变,但所指内容不可以改变
- char* const p3; p3是顶层const,p3的本身值不可以改变
- 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;
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注的更多内容!