第8課 常量表達式(constexpr)

一. 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
*/
相關文章
相關標籤/搜索