文章目录

  • 1. const
  • 2.constexpr
  • 3.常量表达式函数

1. const

在 C++11 之前只有 const 关键字,从功能上来说这个关键字有双重语义:变量只读,修饰常量

  • eg:
void func(const int num)
{const int count = 24;int array[num];            // error,num是一个只读变量,不是常量int array1[count];         // ok,count是一个常量int a1 = 520;int a2 = 250;const int& b = a1;b = a2;                         // errora1 = 1314;cout << "b: " << b << endl;     // 输出结果为1314
}
  • 函数 void func(const int num) 的参数 num 表示这个变量是只读的,但不是常量,因此使用 int array[num]; 这种方式定义一个数组,编译器是会报错的,提示 num不可用作为常量来使用。
  • const int count = 24; 中的 count 却是一个常量,因此可以使用这个常量来定义一个静态数组。

另外,变量只读并不等价于常量,二者是两个概念不能混为一谈,分析一下这句测试代码 const int& b = a1;

  • b 是一个常量的引用,所以 b 引用的变量是不能被修改的,也就是说 b = a2; 这句代码语法是错误的。

  • 但是const 对于变量 a1 是没有任何约束的,a1 的值变了 b 的值也就变了

  • 引用 b 是只读的,但是并不能保证它的值是不可改变的,也就是说它不是常量。

2.constexpr

在 C++11 中添加了一个新的关键字 constexpr,这个关键字是用来修饰常量表达式的。

  • 所谓常量表达式,指的就是由多个(≥1)常量(值不会改变)组成并且在编译过程中就得到计算结果的表达式。(编译阶段会进行替换)

  • 常量表达式和非常量表达式的计算时机不同,非常量表达式只能在程序运行阶段计算出结果,但是常量表达式的计算往往发生在程序的编译阶段,这可以极大提高程序的执行效率,因为表达式只需要在编译阶段计算一次,节省了每次程序运行时都需要计算一次的时间。

那么问题来了,编译器如何识别表达式是不是常量表达式呢?

  • 在 C++11 中添加了 constexpr 关键字之后就可以在程序中使用它来修改常量表达式,用来提高程序的执行效率。
  • 在使用中建议将 const 和 constexpr 的功能区分开,即凡是表达“只读”语义的场景都使用 const,表达“常量”语义的场景都使用 constexpr。
  • eg:
const int m = f();  // 不是常量表达式,m的值只有在运行时才会获取。
const int i=520;    // 是一个常量表达式
const int j=i+1;    // 是一个常量表达式constexpr int i=520;    // 是一个常量表达式
constexpr int j=i+1;    // 是一个常量表达式
  • 对于 C++ 内置类型的数据,可以直接用 constexpr 修饰,但如果是自定义的数据类型(用 struct 或者 class 实现),直接用 constexpr 修饰是不行的。
// 此处的constexpr修饰是无效的
constexpr struct Test
{int id;int num;
};
  • 如果要定义一个结构体 / 类常量对象,可以这样写:
    在第 13 行的代码中 t.num += 100; 的操作是错误的,对象 t 是一个常量,因此它的成员也是常量,常量是不能被修改的。
struct Test
{int id;int num;
};int main()
{constexpr Test t{ 1, 2 };constexpr int id = t.id;constexpr int num = t.num;// error,不能修改常量t.num += 100;cout << "id: " << id << ", num: " << num << endl;return 0;
}

3.常量表达式函数

为了提高 C++ 程序的执行效率,我们可以将程序中值不需要发生变化的变量定义为常量,也可以使用 constexpr 修饰函数的返回值,这种函数被称作常量表达式函数,这些函数主要包括以下几种:普通函数/类成员函数、类的构造函数、模板函数。

修饰函数

  • constexpr 并不能修改任意函数的返回值,这些函数称为常量表达式函数,必须要满足以下几个条件:
    三条规则不仅对应普通函数适用,对应类的成员函数也是适用的:
(1)函数必须要有返回值,并且 return 返回的表达式必须是常量表达式。
// error,不是常量表达式函数
constexpr void func1()
{int a = 100;cout << "a: " << a << endl;
}// error,不是常量表达式函数
constexpr int func1()
{int a = 100;return a;
}
函数 func1() 没有返回值,不满足常量表达式函数要求
函数 func2() 返回值不是常量表达式,不满足常量表达式函数要求(2)函数在使用之前,必须有对应的定义语句。
#include <iostream>
using namespace std;constexpr int func1();
int main()
{constexpr int num = func1();  // errorreturn 0;
}constexpr int func1()
{constexpr int a = 100;return a;
}
在测试程序 constexpr int num = func1(); 中,还没有定义 func1() 就直接调用了,应该将 func1() 函数的定义放到 main() 函数的上边。(3)整个函数的函数体中,不能出现非常量表达式之外的语句(using 指令、typedef 语句以及 static_assert 断言、return 语句除外)。
// error
constexpr int func1()
{constexpr int a = 100;constexpr int b = 10;for (int i = 0; i < b; ++i){cout << "i: " << i << endl;}return a + b;
}// ok
constexpr int func2()
{using mytype = int;constexpr mytype a = 100;constexpr mytype b = 10;constexpr mytype c = a * b;return c - (a + b);
}
因为 func1() 是一个常量表达式函数,在函数体内部是不允许出现非常量表达式以外的操作,因此函数体内部的 for 循环是一个非法操作。
  • 类成员函数
class Test
{public:constexpr int func(){constexpr int var = 100;return 5 * var;}
};int main()
{Test t;constexpr int num = t.func();cout << "num: " << num << endl;return 0;
}

修饰模板函数

  • C++11 语法中,constexpr 可以修饰函数模板,但由于模板中类型的不确定性,因此函数模板实例化后的模板函数是否符合常量表达式函数的要求也是不确定的。
  • 如果 constexpr 修饰的模板函数实例化结果不满足常量表达式函数的要求,则 constexpr 会被自动忽略,即该函数就等同于一个普通函数。
#include <iostream>
using namespace std;struct Person {const char* name;int age;
};// 定义函数模板
template<typename T>
constexpr T dispaly(T t) {return t;
}int main()
{struct Person p { "luffy", 19 };//普通函数struct Person ret = dispaly(p);cout << "luffy's name: " << ret.name << ", age: " << ret.age << endl;//常量表达式函数constexpr int ret1 = dispaly(250);cout << ret1 << endl;constexpr struct Person p1 { "luffy", 19 };constexpr struct Person p2 = dispaly(p1);cout << "luffy's name: " << p2.name << ", age: " << p2.age << endl;return 0;
}

在上面示例程序中定义了一个函数模板 display(),但由于其返回值类型未定,因此在实例化之前无法判断其是否符合常量表达式函数的要求:

  • struct Person ret = dispaly§; 由于参数 p 是变量,所以实例化后的函数不是常量表达式函数,此时 constexpr 是无效的
  • constexpr int ret1 = dispaly(250); 参数是常量,符合常量表达式函数的要求,此时 constexpr 是有效的
  • constexpr struct Person p2 = dispaly(p1); 参数是常量,符合常量表达式函数的要求,此时 constexpr 是有效的

修饰构造函数

  • 如果想用直接得到一个常量对象,也可以使用 constexpr 修饰一个构造函数,这样就可以得到一个常量构造函数了。
  • 常量构造函数有一个要求:构造函数的函数体必须为空,并且必须采用初始化列表的方式为各个成员赋值。
  • eg:
#include <iostream>
using namespace std;struct Person {constexpr Person(const char* p, int age) :name(p), age(age){}const char* name;int age;
};int main()
{constexpr struct Person p1("luffy", 19);cout << "luffy's name: " << p1.name << ", age: " << p1.age << endl;return 0;
}
  • 链接:constexpr

(P3-P4)constexpr修饰常量表达式和常量表达式函数相关推荐

  1. c语言 常量表达式,C++11 constexpr:验证是否为常量表达式(长篇神文)

    constexpr 是 C++ 11 标准新引入的关键字,不过在讲解其具体用法和功能之前,读者需要先搞清楚 C++ 常量表达式的含义. 所谓常量表达式,指的就是由多个(≥1)常量组成的表达式.换句话说 ...

  2. C++语言编程概念:常量、常量表达式和常量初始化

    常量 常量是固定值,在程序执行期间不会改变.这些固定的值,又叫做字面值.常量可以是任何的基本数据类型,可分为整型数字.浮点数字.字符.字符串和布尔值.常量就像是常规的变量,只不过常量的值在定义后不能进 ...

  3. 第 2 章 常量、变量和表达式

    <一>继续Hello World 深入了解C语言的注释,注释可以跨行,也可以穿插在程序中.注释的定界符/*和*/,注释需要注意两点:注释不能够嵌套使用,如:/*test1 /* test2 ...

  4. final修饰的变量就是常量?

    概念 什么是常量? 对于这个问题,可能很多人都可以脱口而出 : 用final修饰的变量是常量 ,或者是在编译时期定义好的字符串.(字符串常量) 但是这种说法是不严谨的,因为准确来说 : 常量是用fin ...

  5. final修饰的变量就是常量?final修饰局部变量在栈还是堆还是常量池中?

    概念 常量池 常量池的好处 Class类文件中的常量池 常量池 运行时常量池 包装类常量池对象池 Java中装箱和拆箱 赋值时 方法调用时 方法运算时 参考 概念 什么是常量? 对于这个问题,可能很多 ...

  6. 常引用、常量指针、指针常量、指向常量的常指针、空指针与野指针解释

    1.一.基础知识 引用并非对象 引用必须初始化 引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起 类型要严格匹配 int &a = 10; //错误:引用类型的初始值必须是 ...

  7. [转载] java常量池-字符串常量池、class常量池和运行时常量池

    参考链接: 如何在Java中初始化和比较字符串 原文链接:http://tangxman.github.io/2015/07/27/the-difference-of-java-string-pool ...

  8. c语言常量定义规则,c语言常量(c语言常量定义规则)

    帮帮忙吧 ! 还有 知不知道在C语言中形式参数和实际参数之间的联系是什么. C语言定义常量常用的方法有以下2种:第一种:宏定义#define N 3 // 定义了一个常量为3的宏N,在程序中N就代表3 ...

  9. c语言---字符串为什么不能修改内容???字符串常量存放在常量区吗???

    疑惑: 1. const修饰的变量都存放在常量区吗??? 2. 字符串常量存放在那???为什么字符型指针(char*)指向字符串不能修改其内容??? 字符串的演变: char* p="123 ...

最新文章

  1. LwIP 之一 源码目录文件详解及移植说明
  2. 【经典回放】多种语言系列数据结构算法:希尔排序(C/C#版)
  3. 深入浅出InfoPath——让管理员来部署InfoPath表单
  4. java嵌入groovy脚本,java-如何捕获传递给Groovy脚本的参数?
  5. 盘点数据处理工具,手把手教你做数据清洗和转换
  6. linux查看磁盘io的几种方法
  7. pytest学习(2)
  8. 【渝粤教育】国家开放大学2018年秋季 0169-22T工程制图基础 参考试题
  9. android模拟器 分辨率,Android模拟器各个皮肤的分辨率
  10. Redisson的看门狗机制
  11. 光流的基本概念和原理-Lucas–Kanade光流算法
  12. 一文读懂随机森林的解释和实现
  13. Arcgis地图切片专题(关于tpk的制作以及迁移切片包至服务器的相关流程)
  14. Linux系统load average异常值处理的trick
  15. F5 GTM DNS 知识点和实验 3 -加速dns解析
  16. 树莓派python蓝牙_用树莓派玩转蓝牙
  17. IDea 工具debug模式详细说明
  18. 泰勒公式--泰勒多项展开以及应用
  19. 【MyBatis】关联查询
  20. 关于 MySQLTransactionRollbackException 异常的排查经历

热门文章

  1. 《普通高等学校招生全国统一考试 · 程序员卷》
  2. 夸克浏览器有没有linux,我手机上唯一的浏览器——夸克浏览器
  3. 看完你就懂的PID算法
  4. 苹果cms解析播放的常见问题
  5. 实现在echart饼状图上显示百分比,数据
  6. 有人提议扣程序员80%的税分给穷人,多人点赞。
  7. elasticsearch重要但容易被忽略的几个参数设置
  8. VSCode绘制UML类图
  9. javascript中浏览器控制台console.log 输出图片,彩色字体,文字
  10. 网页设计师需要学习什么