一. const 和constexpr的區別ios
(一)修飾變量時,const爲「運行期常量」,即運行期數據是隻讀的。而constexpr爲「編譯期」常量,這是const沒法保證的。二者都是對象和函數接口的組成部分。編程
(二)修飾函數時,與const關鍵字相比,constexpr關鍵字不只能夠修飾變量和指針,還能夠修飾函數(含構造函數)。注意constexpr用於定義自定義類的對象時,要求該類具備常量構造函數,而使用const定義類對象時,則無此要求。函數
(三)二者在修飾指針時,行爲有所差別。const放在*號前,表示指針指向的內容不能被修改.const放在*號後,表示指針不能被修改;而constexpr關鍵字只能放在*號前面,而且表示指針所指的內容不能被修改。spa
2、常量表達式函數指針
(一)構成constexpr函數的條件code
1. 函數體只有單一的return語句(能夠經過「:」或遞歸來擴展)。在C++14中這條己經再也不是限制。orm
2. 函數體必須有返回值(C++11中要求不能是void函數,但C++14己再也不限制)對象
三、constexpr函數內部不能調用很是量表達式的函數,會形成編譯失敗。blog
4. return返回語句表達式中不能使用很是量表達式的函數、全局數據,且必須是一個常量表達式。遞歸
(二)注意事項
1. 在使用常量表達式函數前,必須先被定義。不能先聲明,而後在函數調用後再定義。
2. constexpr函數在調用時若傳入的實參均爲編譯期己知的,則返回編譯期常量。只要任何一個實參在編譯期未知,則它的運做與普通函數無異,將產生運行期結果。
3. constexpr函數的構成條件不知足時,就會變成一個普通的函數。同時constexpr函數能夠同時應用於編譯期語境或運行期語境(編譯期語境如constexpr int a = func_constexpr(x, y)。運行期語境如int a = func_constexpr(x, y))。
【編程實驗】const和constexpr的差別
#include <iostream> #include <array> using namespace std; int g_count = 0; int normalfunc(int x) { return x; } //constexpr函數(便可當編譯期常量表達式函數,也能夠看成普通函數使用) constexpr int func1(int x) { return x + 2; } constexpr void func2(){} //普通函數,constexpr必須有非void的返回值類型 constexpr int func3(); //注意這裏只聲明,定義在main()函數以後 //constexpr int func4() //編譯失敗 //{ // int x = normalfunc(2); //調用了非constexpr函數! // return x; //} //冪函數:用於計算base的exp次冪 constexpr int pow(int base, int exp) { //版本1:遞歸(一條語句)----> C++11要求有且只有一條語句(即return語句)。 //return (exp == 0) ? 1 : base * pow(base, exp - 1); //版本2:非遞歸(多條語句) --->C++14以上 auto ret = 1; for (int i = 0; i < exp; ++i) { ret *= base; } return ret; } int main() { //1.1 修飾變量時 int x = 0; //非constexpr變量 const int y = 0; constexpr int a = 2; //a爲編譯期常量 constexpr int b = a + 4; //b爲編譯期常量 constexpr int c = y + 1; //c爲編譯期常量,y直接取自符號表,而不是來自內存值。 //constexpr auto m = x; //error,由於x不是常量 const auto n = x; //ok, d爲x的副本 constexpr auto N = 10; //std::array<int, n> arr1; //error, n不是編譯期常量 std::array<int, N> arr2; //ok, N爲編譯期常量 std::array<int, c> arr3; //ok, c爲編譯期常量 constexpr auto exp = 5; std::array<int, pow(3, exp)> arr4; //ok! pow是constexpr函數 //std::array<int, pow(3, x)> arr5; //error! x爲運行期變量,從而致使pow返回值爲運行期的值 //1.2 constexpr修飾指針時,表示指針自己不可修改!!!(constexpr只能放在*的左側) constexpr int* ptr2 = &g_count; //ok,全局變量,編譯期地址可肯定 //ptr2 = nullptr; //注意,與const不一樣,這裏表示ptr2是常量。 *ptr2 = 100; //constexpr int* ptr1 = &x; //error,由於x在棧上分配,在編譯期不能肯定其地址。 //1.3 修飾函數時 int var1 = func3(); //ok! func3先聲明,使用後定義。 //constexpr int var2 = func3(); //error,在調用函數前,func3必須定義好了(不能只看到聲明)。 constexpr int c1 = func1(N); //constexpr語境: 返回值賦值constexpr變量,是constexpr函數(實參也爲constexpr)。 int c2 = func1(x); //非constexpr語境:func1用於返回值賦值給普通變量時,看成普通函數使用! int c3 = func1(N); //非constexpr語境:func1用於返回值賦值給普通變量時,看成普通函數使用! return 0; } constexpr int func3() //聲明放在main以前,這裏是定義! { return 0; }
3、constexpr的應用
(一)定義自定義類的constexpr對象。
1. 自定義類的構造函數須爲constexpr函數。
2. constexpr不能用於修飾virtual函數。由於virtual是運行時的行爲,與constexpr的意義衝突。
3. C++11中被聲明爲constexpr成員函數會自動設爲const函數,但C++14中再也不自動設爲const函數。
(二)函數模板: 實例化後的函數是否爲constexpr函數,在編譯期是不肯定的,取決於傳入的實參是否爲constexpr類型。
(三)constexpr類型的函數遞歸
1. 利用constexpr進行編譯期運算的編程方式稱爲constexpr元編程
2 .基於模板編譯期運算的編程方式,叫模板元編程。
【編程實驗】constexpr的應用
#include <iostream> using namespace std; //1. constexpr構造函數和成員函數 class Point { double x, y; public: //常量表達式構造函數 constexpr Point(double x=0, double y=0) noexcept : x(x),y(y){} //成員函數(注意:constexpr不容許用於修飾virtual函數) constexpr double getX() const noexcept { return x;} //constexpr函數,在C++11中默認爲const函數。但C++14中取消了,這裏加const constexpr double getY() const noexcept { return y;} //如下兩個函數在C++11中沒法聲明爲constexpr(C++14能夠!),緣由以下: //1.C++11中被聲明爲constexpr成員函數會自動設爲const函數,而這兩個函數須要更改爲員變量的值,這與const成員函數 //卻不容許需改爲員的值,會產生編譯錯誤。C++14中constexpr函數再也不默認爲const函數,所以能夠修改爲員變量。 //2. C++11中constexpr函數不能返回void型,但C++14中再也不限制。 constexpr void setX(double newX) noexcept { x = newX; } //C++11中須去掉constexpr; constexpr void setY(double newY) noexcept { y = newY; } }; //計算中點座標 constexpr Point midpoint(const Point& p1, const Point& p2) noexcept { //p1和p2均爲const對象,會調用const版本的getX()和getY() return { (p1.getX() + p2.getX()) / 2, (p1.getY() + p2.getY()) / 2 }; } //返回p相對於原點的中心對稱點(C++14) constexpr Point reflection(const Point& p) noexcept { Point ret; ret.setX(-p.getX()); ret.setY(-p.getY()); return ret; } //2. 函數模板:(constexpr元編程) //函數是否爲constexpr函數,編譯期未知。取決於傳入的實參t是否爲constexpr變量/對象 template<typename T> constexpr T func(T t) { return t; } struct NotLiteral { int i; NotLiteral():i(5){} //注意:構造函數不是constexpr類型! }; //3.constexpr類型的遞歸函數 //3.1 求斐波那契數 constexpr int Fib(int n) { return (n == 1) ? 1 : ((n == 2) ? 1 : Fib(n-1) + Fib(n -2)); } //3.2 模板遞歸(模板元編程) template<long N> struct Fibonacci { static const long val = Fibonacci<N - 1>::val + Fibonacci<N - 2>::val; }; //特化 template<> struct Fibonacci<2> { static const long val = 1; }; template<> struct Fibonacci<1> { static const long val = 1; }; template<> struct Fibonacci<0> { static const long val = 0; }; void printArray(int a[], int len) { cout << "Fibonacci: "; for (int i=0; i<len; ++i) { cout << a[i] << " "; } cout << endl; } int main() { //1. 用constexpr定義自定義類的對象(要求該類具備constexpr構造函數) constexpr Point p1(9.4, 27.7); //ok, p1爲編譯期常量 constexpr Point p2{ 28.8, 5.3 }; //ok,同上 //1.1求中點 constexpr auto mid = midpoint(p1, p2); //mid爲編譯期常量,mid.getX()也是編譯期常量!!! //1.2求關於原點的對稱點 constexpr auto reflectedMid = reflection(mid); //2. constexpr型的函數模板 NotLiteral n1; //非constexpr對象 NotLiteral n2 = func(n1); //傳入實參爲非constexpr對象,func成爲普通函數! //constexpr NotLiteral n3 = func(n1); //error,因爲NotLiteral的構造函數不是constexpr類型, //不能用constexpr定義該類型的constexpr的對象!!! constexpr int a = func(1); //ok,1爲constexpr對象 //3. constexpr類型遞歸函數 int fib1[]{ Fib(11), Fib(12), Fib(13), Fib(14) }; printArray(fib1, 4); int fib2[] = { Fibonacci<11>::val, Fibonacci<12>::val, Fibonacci<13>::val, Fibonacci<14>::val }; printArray(fib1, 4); return 0; } /*輸出結果 Fibonacci: 89 144 233 377 Fibonacci: 89 144 233 377 */