C++值元編程

——永遠不要在OJ上使用值元編程,過於簡單的沒有優勢,能有優勢的編譯錯誤。

背景

2019年10月,我在學習算法。有一道作業題,輸入規模很小,可以用打表法解決。具體方案有以下三種:

  1. 運行時預處理,生成所需的表格,根據輸入直接找到對應項,稍加處理后輸出;

  2. 一個程序生成表格,作為提交程序的一部分,後續與方法1相同,這樣就省去了運行時計算的步驟;

  3. 以上兩種方法結合,編譯期計算表格,運行時直接查詢,即元編程(metaprogramming)。

做題當然是用方法1或2,但是元編程已經埋下了種子。時隔大半年,我來補上這個坑。

題目

北京大學OpenJudge 百練4119 複雜的整數劃分問題

描述

將正整數 \(n\) 表示成一系列正整數之和,\(n = n_1 + n_2 + … + n_k\),其中 \(n_1 \geq n_2 \geq … \geq n_k \geq 1\)\(k \geq 1\)。正整數 \(n\) 的這種表示稱為正整數 \(n\) 的劃分。

輸入

標準的輸入包含若干組測試數據。每組測試數據是一行輸入數據,包括兩個整數 \(N\)\(K\)。( \(0 \le N \leq 50\)\(0 \le K \leq N\)

輸出

對於每組測試數據,輸出以下三行數據:

第一行: \(N\) 劃分成 \(K\) 個正整數之和的劃分數目

第二行: \(N\) 劃分成若干個不同正整數之和的劃分數目

第三行: \(N\) 劃分成若干個奇正整數之和的劃分數目

樣例輸入

5 2

樣例輸出

2
3
3

提示

第一行: 4+1,3+2

第二行: 5,4+1,3+2

第三行: 5,1+1+3,1+1+1+1+1+1

解答

標準的動態規劃題。用dp[c][i][j]表示把i分成c個正整數之和的方法數,其中每個數都不超過j

第一行。初始化:由 \(i \leq j\) 是否成立決定dp[1][i][j]的值,當 \(i \leq j\) 時為1,劃分為 \(i = i\),否則無法劃分,值為0

遞推:為了求dp[c][i][j],對 \(i = i_1 + i_2 + … + i_c\)\(i_1 \geq i_2 \geq … \geq i_c\) 中的最大數 \(i_1\) 分類討論,最小為 \(1\),最大不超過 \(i – 1\),因為 \(c \geq 2\),同時不超過 \(j\),因為定義。最大數為 \(n\) 時,對於把 \(i – n\) 分成 \(c – 1\) 個數,每個數不超過 \(n\) 的劃分,追加上 \(n\) 可得 \(i\) 的一個劃分。\(n\) 只有這些取值,沒有漏;對於不同的 \(n\),由於最大數不一樣,兩個劃分也不一樣,沒有多。故遞推式為:

\[dp[c][i][j] = \sum_{n=1}^{min\{i-1,j\}}dp[c-1][i-n][n] \]

dp[K][N][N]即為所求ans1[K][N]

第二行。可以把遞推式中的dp[c - 1][i - n][n]修改為dp[c - 1][i - n][n - 1]后重新計算。由於只需一個與c無關的結果,可以省去c這一維度,相應地改變遞推順序,每輪累加。

另一種方法是利用已經計算好的ans1數組。設 \(i = i_1 + i_2 + … + + i_{c-1} + i_c\),其中 \(i_1 \ge i_2 \ge … \ge i_{c+1} \ge i_c \ge 0\),則 \(i_1 – \left( c-1 \right) \geq i_2 – \left( c-2 \right) \geq … \geq i_{c-1} – 1 \geq i_c \ge 0\),且 \(\left( i_1 – \left( c-1 \right) \right) + \left( i_2 – \left( c-2 \right) \right) + … + \left( i_{c-1} – 1 \right) + \left( i_c \right) = i – \frac {c \left( c-1 \right)} {2}\),故把i劃分成c個不同正整數之和的劃分數目等於ans[c][i - c * (c - 1) / 2],遍歷c累加即得結果。

第三行。想法與第二行相似,也是找一個對應,此處從略。另外,數學上可以證明,第二行和第三行的結果一定是一樣的。

#include <iostream>
#include <algorithm>

constexpr int max = 50;
int dp[max + 1][max + 1][max + 1] = { 0 };
int ans1[max + 1][max + 1] = { 0 };
int ans2[max + 1] = { 0 };
int ans3[max + 1] = { 0 };

int main()
{
    int num, k;
    for (int i = 1; i <= max; ++i)
        for (int j = 1; j <= max; ++j)
            dp[1][i][j] = i <= j;
    for (int cnt = 2; cnt <= max; ++cnt)
        for (int i = 1; i <= max; ++i)
            for (int j = 1; j <= max; ++j)
            {
                auto min = std::min(i - 1, j);
                for (int n = 1; n <= min; ++n)
                    dp[cnt][i][j] += dp[cnt - 1][i - n][n];
            }
    for (int cnt = 1; cnt <= max; ++cnt)
        for (int i = 1; i <= max; ++i)
            ans1[cnt][i] = dp[cnt][i][i];
    for (int i = 1; i <= max; ++i)
        for (int cnt = 1; cnt <= i; ++cnt)
        {
            int j = i - cnt * (cnt - 1) / 2;
            if (j <= 0)
                break;
            ans2[i] += ans1[cnt][j];
        }
    for (int i = 1; i <= max; ++i)
        for (int cnt = 1; cnt <= i; ++cnt)
        {
            int j = i + cnt;
            if (j % 2)
                continue;
            j /= 2;
            ans3[i] += ans1[cnt][j];
        }
    
    while (std::cin >> num)
    {
        std::cin >> k;
        std::cout << ans1[k][num] << std::endl;
        std::cout << ans2[num] << std::endl;
        std::cout << ans3[num] << std::endl;
    }
}

值元編程基礎

元編程是指計算機程序能把其他程序作為它們的數據的編程技術。在目前的C++中,元編程體現為用代碼生成代碼,包括宏與模板。當我們使用了std::vector<int>中的任何一個名字時,std::vector類模板就用模板參數int, std::allocator<int>實例化為std::vector<int, std::allocator<int>>模板類,這是一種元編程,不過我們通常不這麼講。

狹義的C++模板元編程(template metaprogramming,TMP)包括值元編程、類型元編程,以及兩者的相交。本文討論的是值元編程,即為編譯期值編程。

在C++中有兩套工具可用於值元編程:模板和constexpr。C++模板是圖靈完全的,這是模板被引入C++以後才被發現的,並不是C++模板的初衷,因此用模板做計算在C++中算不上一等用法,導致其語法比較冗長複雜。constexpr的初衷是提供純正的編譯期常量,後來才取消對計算的限制,但不能保證計算一定在編譯期完成。總之,這兩套工具都不完美,所以本文都會涉及。

嚴格來說,constexpr不符合上述對元編程的定義,但它確實可以提供運行時程序需要的數據,所以也歸入元編程的類別。

constexpr式值元編程

constexpr開始講,是因為它與我們在C++中慣用的編程範式——過程式範式是一致的。

constexpr關鍵字在C++11中被引入。當時,constexpr函數中只能包含一條求值語句,就是return語句,返回值可以用於初始化constexpr變量,作模板參數等用途。如果需要分支語句,用三目運算符?:;如果需要循環語句,用函數遞歸實現。比如,計算階乘:

constexpr int factorial(int n)
{
    return n <= 1 ? 1 : (n * factorial(n - 1));
}

對於編譯期常量ifactorial(i)產生編譯期常量;對於運行時值jfactorial(j)產生運行時值,也就是說,constexpr可以視為對既有函數的附加修飾。

然而,多數函數不止有一句return語句,constexpr對函數體的限制使它很難用於中等複雜的計算任務,為此C++14放寬了限制,允許定義局部變量,允許if-elseswitch-casewhilefor等控制流。factorial函數可以改寫為:

constexpr int factorial(int n)
{
    int result = 1;
    for (; n > 1; --n)
        result *= n;
    return result;
}

也許你會覺得factorial函數的遞歸版本比循環版本易懂,那是因為你學習遞歸時接觸的第一個例子就是它。對於C++開發者來說,大多數情況下首選的還是循環。

計算單個constexpr值用C++14就足夠了,但是傳遞數組需要C++17,因為std::arrayoperator[]從C++17開始才是constexpr的。

整數劃分問題的constexpr元編程實現需要C++17標準:

#include <iostream>
#include <utility>
#include <array>

constexpr int MAX = 50;

constexpr auto calculate_ans1()
{
    std::array<std::array<std::array<int, MAX + 1>, MAX + 1>, MAX + 1> dp{};
    std::array<std::array<int, MAX + 1>, MAX + 1> ans1{};
    constexpr int max = MAX;
    for (int i = 1; i <= max; ++i)
        for (int j = 1; j <= max; ++j)
            dp[1][i][j] = i <= j;
    for (int cnt = 2; cnt <= max; ++cnt)
        for (int i = 1; i <= max; ++i)
            for (int j = 1; j <= max; ++j)
            {
                auto min = std::min(i - 1, j);
                for (int n = 1; n <= min; ++n)
                    dp[cnt][i][j] += dp[cnt - 1][i - n][n];
            }
    for (int cnt = 1; cnt <= max; ++cnt)
        for (int i = 1; i <= max; ++i)
            ans1[cnt][i] = dp[cnt][i][i];
    return ans1;
}

constexpr auto calculate_ans2()
{
    constexpr auto ans1 = calculate_ans1();
    std::array<int, MAX + 1> ans2{};
    constexpr int max = MAX;
    for (int i = 1; i <= max; ++i)
        for (int cnt = 1; cnt <= i; ++cnt)
        {
            int j = i - cnt * (cnt - 1) / 2;
            if (j <= 0)
                break;
            ans2[i] += ans1[cnt][j];
        }
    return ans2;
}

int main()
{
    constexpr auto ans1 = calculate_ans1();
    constexpr auto ans2 = calculate_ans2();

    for (int cnt = 1; cnt <= 10; ++cnt)
    {
        for (int i = 1; i <= 10; ++i)
            std::cout << ans1[cnt][i] << ' ';+
        std::cout << std::endl;
    }
    std::cout << std::endl;
    for (int i = 1; i <= 50; ++i)
        std::cout << ans2[i] << ' ';
    std::cout << std::endl;

    int num, k;
    while (std::cin >> num)
    {
        std::cin >> k;
        std::cout << ans1[k][num] << std::endl;
        std::cout << ans2[num] << std::endl;
        std::cout << ans2[num] << std::endl;
    }
}

模板式值元編程

模板式與C++11中的constexpr式類似,必須把循環化為遞歸。事實上C++模板是一門函數式編程語言,對值元編程和類型元編程都是如此。

程序控制流有三種基本結構:順序、分支與循環。

順序

在函數式編程中,數據都是不可變的,函數總是接受若干參數,返回若干結果,參數和結果是不同的變量;修改原來的變量是不允許的。對於C++模板這門語言,函數是類模板,也稱“元函數”(metafunction);參數是模板參數;運算結果是模板類中定義的靜態編譯期常量(在C++11以前,常用enum來定義;C++11開始用constexpr)。

比如,對於參數 \(x\),計算 \(x + 1\)\(x ^ 2\) 的元函數:

template<int X>
struct PlusOne
{
    static constexpr int value = X + 1;
};

template<int X>
struct Square
{
    static constexpr int value = X * X;
};

這裏假定運算數的類型為int。從C++17開始,可以用auto聲明非類型模板參數。

順序結構,是對數據依次進行多個操作,可以用函數嵌套來實現:

std::cout << PlusOne<1>::value << std::endl;
std::cout << Square<2>::value << std::endl;
std::cout << Square<PlusOne<3>::value>::value << std::endl;
std::cout << PlusOne<Square<4>::value>::value << std::endl;

或者藉助constexpr函數,回歸熟悉的過程式範式:

template<int X>
struct SquareAndIncrease
{
    static constexpr int calculate()
    {
        int x = X;
        x = x * x;
        x = x + 1;
        return x;
    }
    static constexpr int value = calculate();
};

void f()
{
    std::cout << SquareAndIncrease<5>::value << std::endl;
}

過程式方法同樣可以用於分支和循環結構,以下省略;函數式方法可以相似地用於值元編程與類型元編程,所以我更青睞(主要還是逼格更高)。

分支

C++模板元編程實現分支的方式是模板特化與模板參數匹配,用一個額外的帶默認值的bool類型模板參數作匹配規則,特化falsetrue的情形,另一種情形留給主模板。

比如,計算 \(x\) 的絕對值:

template<int X, bool Pos = (X > 0)>
struct AbsoluteHelper
{
    static constexpr int value = X;
};

template<int X>
struct AbsoluteHelper<X, false>
{
    static constexpr int value = -X;
};

如果你怕用戶瞎寫模板參數,可以再包裝一層:

template<int X>
struct Absolute : AbsoluteHelper<X> { };

void g()
{
    std::cout << Absolute<6>::value << std::endl;
    std::cout << Absolute<-7>::value << std::endl;
}

標準庫提供了std::conditional及其輔助類型std::conditional_t用於模板分支:

template<bool B, class T, class F>
struct conditional;

定義了成員類型type,當B == true時為T,否則為F

模板匹配實際上是在處理switch-case的分支,bool只是其中一種簡單情況。對於對應關係不太規則的分支語句,可以用一個constexpr函數把參數映射到一個整數或枚舉上:

enum class Port_t
{
    PortB, PortC, PortD, PortError,
};

constexpr Port_t portMap(int pin)
{
    Port_t result = Port_t::PortError;
    if (pin < 0)
        ;
    else if (pin < 8)
        result = Port_t::PortD;
    else if (pin < 14)
        result = Port_t::PortB;
    else if (pin < 20)
        result = Port_t::PortC;
    return result;
}

template<int Pin, Port_t Port = portMap(Pin)>
struct PinOperation;

template<int Pin>
struct PinOperation<Pin, Port_t::PortB> { /* ... */ };

template<int Pin>
struct PinOperation<Pin, Port_t::PortC> { /* ... */ };

template<int Pin>
struct PinOperation<Pin, Port_t::PortD> { /* ... */ };

如果同一個模板有兩個參數分別處理兩種分支(這已經從分支上升到模式匹配了),或同時處理分支和循環的特化,總之有兩個或以上維度的特化,需要注意兩個維度的特化是否會同時滿足,如果有這樣的情形但沒有提供兩參數都特化的模板特化,編譯會出錯。見problem2::Accumulator,它不需要提供兩個參數同時特化的版本。

循環

如前所述,循環要化為遞歸,循環的開始與結束是遞歸的起始與終點或兩者對調,遞歸終點的模板需要特化。比如,還是計算階乘:

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

template<>
struct Factorial<0>
{
    static constexpr int value = 1;
};

或許階乘的遞歸定義很大程度上來源於數學,那就再看一個平方和的例子:

template<int N>
struct SquareSum
{
    static constexpr int value = SquareSum<N - 1>::value + N * N;
};

template<>
struct SquareSum<0>
{
    static constexpr int value = 0;
};

\(1^2 + 2^2 + \cdots + n^2 = \frac {n \left( n + 1 \right) \left( 2n + 1\right)} {6}\)

好吧,還是挺數學的,去下面看實例感覺一下吧,那裡還有break——哦不,被我放到思考題中去了。

加群是交換群,求和順序不影響結果,上面這樣的順序寫起來方便。有些運算符不滿足交換律,需要逆轉順序。還以平方和為例:

template<int N, int Cur = 0>
struct SquareSumR
{
    static constexpr int value = Cur * Cur + SquareSumR<N, Cur + 1>::value;
};

template<int N>
struct SquareSumR<N, N>
{
    static constexpr int value = N * N;
};

遞歸

遞歸在過程式中是一種高級的結構,它可以直接轉化為函數式的遞歸,後面會提到兩者的異同。

比如,計算平方根,這個例子來源於C++ Templates: The Complete Guide 2e:

// primary template for main recursive step
template<int N, int LO = 1, int HI = N>
struct Sqrt {
    // compute the midpoint, rounded up
    static constexpr auto mid = (LO + HI + 1) / 2;
    // search a not too large value in a halved interval
    using SubT = std::conditional_t<(N < mid * mid),
                                   Sqrt<N, LO, mid - 1>,
                                   Sqrt<N, mid, HI>>;
    static constexpr auto value = SubT::value;
};
// partial specialization for end of recursion criterion
template<int N, int S>
struct Sqrt<N, S, S> {
    static constexpr auto value = S;
};

這個遞歸很容易化為循環,有助於你對循環化遞歸的理解。

存儲

實際應用中我們可能不需要把所有計算出來的值存儲起來,但在打表的題目中需要。存儲一系列數據需要用循環,循環的實現方式依然是遞歸。比如,存儲階乘(Factorial類模板見上):

template<int N>
inline void storeFactorial(int* dst)
{
    storeFactorial<N - 1>(dst);
    dst[N] = Factorial<N>::value;
}

template<>
inline void storeFactorial<-1>(int* dst)
{
    ;
}

void h()
{
    constexpr int MAX = 10;
    int factorial[MAX + 1];
    storeFactorial<MAX>(factorial);
    for (int i = 0; i <= MAX; ++i)
        std::cout << factorial[i] << ' ';
    std::cout << std::endl;
}

多維數組同理,例子見下方。注意,函數模板不能偏特化,但有靜態方法的類模板可以,這個靜態方法就充當原來的模板函數。

雖然我們是對數組中的元素挨個賦值的,但編譯器的生成代碼不會這麼做,即使不能優化成所有數據一起用memcpy,至少能做到一段一段拷貝。

類內定義的函數隱式成為inline,手動寫上inline沒有語法上的意義,但是對於一些編譯器,寫上以後函數被內聯的可能性更高,所以寫inline是一個好習慣。

解答

#include <iostream>
#include <algorithm>

constexpr int MAX = 50;

namespace problem1
{

template<int Count, int Num, int Max>
struct Partition;

template<int Count, int Num, int Loop>
struct Accumulator
{
    static constexpr int value = Accumulator<Count, Num, Loop - 1>::value + Partition<Count, Num - Loop, Loop>::value;
};

template<int Count, int Num>
struct Accumulator<Count, Num, 0>
{
    static constexpr int value = 0;
};

template<int Count, int Num, int Max = Num>
struct Partition
{
    static constexpr int value = Accumulator<Count - 1, Num, std::min(Num - 1, Max)>::value;
};

template<int Num, int Max>
struct Partition<1, Num, Max>
{
    static constexpr int value = Num <= Max;
};

template<int Count, int Num>
struct Store
{
    static inline void store(int* dst)
    {
        Store<Count, Num - 1>::store(dst);
        dst[Num] = Partition<Count, Num>::value;
    }
};

template<int Count>
struct Store<Count, 0>
{
    static inline void store(int* dst)
    {
        ;
    }
};

template<int Count>
inline void store(int (*dst)[MAX + 1])
{
    store<Count - 1>(dst);
    Store<Count, MAX>::store(dst[Count]);
}

template<>
inline void store<0>(int (*dst)[MAX + 1])
{
    ;
}

inline void store(int(*dst)[MAX + 1])
{
    store<MAX>(dst);
}

}

namespace problem2
{

template<int Num, int Count = Num, int Helper = Num - Count * (Count - 1) / 2, bool Valid = (Helper > 0)>
struct Accumulator
{
    static constexpr int value = Accumulator<Num, Count - 1>::value + problem1::Partition<Count, Helper>::value;
};

template<int Num, int Count, int Helper>
struct Accumulator<Num, Count, Helper, false>
{
    static constexpr int value = Accumulator<Num, Count - 1>::value;
};

template<int Num, int Helper, bool Valid>
struct Accumulator<Num, 0, Helper, Valid>
{
    static constexpr int value = 0;
};

template<int Num>
inline void store(int* dst)
{
    store<Num - 1>(dst);
    dst[Num] = Accumulator<Num>::value;
}

template<>
inline void store<0>(int* dst)
{
    ;
}

inline void store(int* dst)
{
    store<MAX>(dst);
}

}

int ans1[MAX + 1][MAX + 1];
int ans2[MAX + 1];

int main()
{
    problem1::store(ans1);
    problem2::store(ans2);
    int num, k;
    while (std::cin >> num)
    {
        std::cin >> k;
        std::cout << ans1[k][num] << std::endl;
        std::cout << ans2[num] << std::endl;
        std::cout << ans2[num] << std::endl;
    }
}

請對照運行時版本自行理解。

討論

constexpr

constexpr不保證計算在編譯期完成,大部分編譯器在Debug模式下把所有可以推遲的constexpr計算都推遲到運行時完成。但constexpr可以作為一個強有力的優化提示,原本在最高優化等級都不會編譯期計算的代碼,在有了constexpr后編譯器會儘力幫你計算。如果編譯器實在做不到,根據你是否強制編譯期求值,編譯器會給出錯誤或推遲到運行時計算。在不同的編譯器中,這類行為的表現是不同的——眾所周知MSVC對constexpr的支持不好。

目前(C++17)沒有任何方法可以檢查一個表達式是否是編譯期求值的,但是有方法可以讓編譯器對於非編譯期求值表達式給出一個錯誤,把期望constexpr的表達式放入模板參數或static_assert表達式都是可行的:如果編譯期求值,則編譯通過;否則編譯錯誤。

(C++20:constevalis_constant_evaluated

模板

如果我們把Sqrt中的遞歸替換為如下語句:

static constexpr auto value = (N < mid * mid) ? Sqrt<N, LO, mid - 1>::value
                                              : Sqrt<N, mid, HI>::value;

顯然計算結果是相同的,看上去還更簡潔。但是問題在於,編譯器會把Sqrt<N, LO, mid - 1>Sqrt<N, mid, HI>兩個類都實例化出來,儘管只有一個模板類的value會被使用到。這些類模板實例繼續導致其他實例產生,最終將產生 \(O \left( n \log n \right)\) 個實例。相比之下,把兩個類型名字傳給std::conditional並不會導致類模板被實例化,std::conditional只是定義一個類型別名,對該類型求::value才會實例化它,一共產生 \(O \left( \log n \right)\) 個實例。

還有一個很常見的工具是變參模板,我沒有介紹是因為暫時沒有用到,而且我怕寫出非多項式複雜度的元程序。如果我還有機會寫一篇類型元編程的話,肯定會包含在其中的。

函數式

循環的一次迭代往往需要上一次迭代的結果,對應地在遞歸中就是函數對一個參數的結果依賴於對其他 \(n\) 個參數的結果。有些問題用遞歸解決比較直觀,但是如果 \(n \geq 2\),計算過程就會指數爆炸,比如:

int fibonacci(int n)
{
    if (n <= 2)
        return 1;
    else
        return fibonacci(n - 2) + fibonacci(n - 1);
}

計算fibonacci(30)已經需要一點點時間了,而計算fibonacci(46)(4字節帶符號整型能容納的最大斐波那契數)就很慢了。把這種遞歸轉化為循環,就是設計一個動態規劃算法的過程。然而函數式中的遞歸與過程式中的循環可能有相同的漸近複雜度:

template<int N>
struct Fibonacci
{
    static constexpr int value = Fibonacci<N - 2>::value + Fibonacci<N - 1>::value;
};

template<>
struct Fibonacci<1>
{
    static constexpr int value = 1;
};

template<>
struct Fibonacci<2>
{
    static constexpr int value = 1;
};

因為只有Fibonacci<1>Fibonacci<46>這46個類模板被實例化,是 \(O \left( n \right)\) 複雜度的。

在題目中,由於表中的所有數據都有可能用到,並且運行時不能執行計算,所以要把所有數據都計算出來。實際問題中可能只需要其中一個值,比如我現在就想知道不同整數的劃分問題對 \(50\) 的答案是多少,就寫:

std::cout << problem2::Accumulator<50>::value << std::endl;

那麼problem1::PartitionCount參數就不會超過10,不信的話你可以加一句static_assert。實例化的模板數量一共只有2000多個,而在完整的問題中這個數量要翻100倍不止。這種性質稱為惰性求值,即用到了才求值。惰性求值是必需的,總不能窮盡模板參數的所有可能組合一一實例化出來吧?

函數式編程語言可以在運行時實現這些特性。

性能

我愧對這個小標題,因為C++值元編程根本沒有性能,時間和空間都是。類型元編程也許是必需,至於值元編程,emm,做點簡單的計算就可以了,這整篇文章都是反面教材。

思考題2用GCC編譯,大概需要10分鐘;用MSVC編譯,出現我聞所未聞的錯誤:

因為編譯器是32位的,4GB內存用完了就爆了。

停機問題

一個很有趣的問題是編譯器對於死循環的行為。根據圖靈停機問題,編譯器無法判斷它要編譯的元程序是否包含死循環,那麼它在遇到死循環時會怎樣表現呢?當然不能跟着元程序一起死循環,constexpr的循環次數與模板的嵌套深度都是有限制的。在GCC中,可以用-fconstexpr-depth-fconstexpr-loop-limit-ftemplate-depth等命令行參數來控制。

思考題

  1. problem2::AccumulatorCount == 0Count == Num都要實例化,但其實只需實例化到 \(O \left( \sqrt{n} \right)\) 就可以了,試改寫之。

  2. 洛谷 NOIp2016提高組D2T1 組合數問題,用元編程實現。

    • 只需完成 \(n \leq 100, m \leq 100\) 的任務點;

    • 使用64位編譯器(指編譯器本身而非目標代碼),給編譯器億點點時間;

    • 不要去網站上提交,我已經試過了,編譯錯誤。

    • 測試數據下載。

題目描述

組合數 \(\binom {n} {m}\) 表示的是從 \(n\) 個物品中選出 \(m\) 個物品的方法數。舉個例子,從 \(\left( 1, 2, 3 \right)\) 三個物品中選擇兩個物品可以有 \(\left( 1, 2 \right), \left( 1, 3 \right), \left( 2, 3 \right)\) 這三種選擇方法。根據組合數的定義,我們可以給出計算組合數 \(\binom {n} {m}\) 的一般公式

\[\binom {n} {m} = \frac {n!} {m! \left( n-m \right) !} \,, \]

其中 \(n! = 1 \times 2 \times \cdots \times n\);特別地,定義 \(0! = 1\)

小蔥想知道如果給定 \(n\)\(m\)\(k\),對於所有的 \(0 \leq i \leq n, 0 \leq j \leq \min \left( i, m \right)\) 有多少對 \(\left( i, j \right)\) 滿足 \(k \mid \binom {i} {j}\)

輸入格式

第一行有個兩個整數 \(t, k\),其中 \(t\) 代表該測試點總共有多少組測試數據,\(k\) 的意義見問題描述。

接下來 \(t\) 行每行兩個整數 \(n, m\),其中 \(n, m\) 的意義見問題描述。

輸出格式

\(t\) 行,每行一個整數代表所有的 \(0 \leq i \leq n, 0 \leq j \leq \min \left( i, m \right)\) 有多少對 \(\left( i, j \right)\) 滿足 \(k \mid \binom {i} {j}\)

輸入輸出樣例

【輸入#1】

1 2
3 3

【輸出#1】

1

【輸入#2】

2 5
4 5
6 7

【輸出#2】

0 7

說明/提示

【樣例1說明】

在所有可能的情況中,只有 \(\binom {2} {1} = 2\) 一種情況是 \(2\) 的倍數。

【子任務】

測試點 \(n\) \(m\) \(k\) \(t\)
1 \(\leq 3\) $ \leq 3$ \(= 2\) $ = 1$
2 \(= 3\) \(\leq 10^4\)
3 \(\leq 7\) $ \leq 7$ \(= 4\) $ = 1$
4 \(= 5\) \(\leq 10^4\)
5 \(\leq 10\) $ \leq 10$ \(= 6\) $ = 1$
6 \(= 7\) \(\leq 10^4\)
7 \(\leq 20\) $ \leq 100$ \(= 8\) $ = 1$
8 \(= 9\) \(\leq 10^4\)
9 \(\leq 25\) $ \leq 2000$ \(=10\) $ = 1$
10 \(=11\) \(\leq 10^4\)
11 \(\leq 60\) $ \leq 20$ \(=12\) $ = 1$
12 \(=13\) \(\leq 10^4\)
13 \(\leq 100\) $ \leq 25$ \(=14\) $ = 1$
14 \(=15\) \(\leq 10^4\)
15 $ \leq 60$ \(=16\) $ = 1$
16 \(=17\) \(\leq 10^4\)
17 \(\leq 2000\) $ \leq 100$ \(=18\) $ = 1$
18 \(=19\) \(\leq 10^4\)
19 $ \leq 2000$ \(=20\) $ = 1$
20 \(=21\) \(\leq 10^4\)
  • 對於全部的測試點,保證 \(0 \leq n, m \leq 2 \times 10^3, 1 \leq t \leq 10^4\)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

HotCorner:讓Windows 10擁有macOS的觸發角特性!

目錄

  • 簡介
  • 軟件功能
  • 下載
  • 安裝
  • 卸載
  • 使用
  • License
  • 作者
  • FAQ

簡介

macOS上有一個很方便的功能:“觸發角”。通過這個功能可以設置當鼠標移動到屏幕的四個角時的觸發事件,例如觸發啟動屏幕保護程序等,显示桌面等功能。和我們習慣的熱鍵相對應,macOS將其稱之為“Hot Corners(熱角)”。筆者接下來要介紹的軟件“HotCorner“就是用於讓Windows系統擁有像macOS那樣的觸發角,實現下面動圖展示的效果:

當鼠標移動到屏幕的左上角時,自動打開Windows的時間軸試圖,實現快捷切換任務。

這個程序來源於一個國外大神(Google的信息安全工程師)Tavis Ormandy 的一個小項目 hotcorner,他創作這個項目是因為習慣於一款Linux操作系統桌面:GNOME 3,這款桌面可以在鼠標移動到左上角時觸發任務視圖。他發現每當自己使用Windows 10時,總是會忘記Windows中並沒有這個功能,四處尋找替代軟件都無法令他滿意,因此自己用C語言手擼了一個小程序來實現這個功能。但這個小程序只有一個功能:屏幕左上角觸發Windows時間軸視圖。並且軟件的安裝,卸載都需要通過命令行或者手動實現,十分不方便。

筆者在原先的項目基礎上做出了如下改動:

  1. 用屏幕的左下角來觸發開始菜單
  2. 將軟件打包成安裝引導程序(安裝包)
  3. 給軟件添加圖標
  4. 安裝時可選擇軟件開機啟動
  5. 編寫中文文檔

下面一張動圖演示了筆者添加的左下角觸發開始菜單的功能

軟件功能

  • 當鼠標移動至屏幕左上角時显示Windows 10時間軸視圖
  • 當鼠標移動至屏幕右下角時显示Windows 開始菜單

下載

Github地址:下載地址

碼雲地址:下載地址

如果你不打算參与本軟件開發,只需要下載HotcornerInstaller.exe這個安裝程序即可
國內推薦使用碼雲地址進行下載,速度比較快,但如果你需要提交issue,請前往Github地址。

安裝

從上述下載地址將HotcornerInstaller.exe下載下來之後,雙擊打開即可開始安裝。

卸載

找到軟件的安裝位置(默認是C:\Program Files (x86)\HotCorner),雙擊該文件夾下的unins000.exe即可完成卸載。在卸載之前請先停止軟件運行(同時按下Ctrl+Alt+C)。

使用

軟件安裝完成之後會自動添加到開始菜單的應用列表中,在其中找到HotCorner,單擊之後軟件即可後台運行。如果你使用了如圖所示的屏幕縮放,並且縮放比例不是100%時,則需要進行下面的配置

正常情況下,軟件可以自動獲取屏幕的高度,但是在系統使用屏幕縮放時,會導致軟件獲取到的不是屏幕的真實高度,因此你需要編輯軟件安裝路徑(默認是C:\Program Files (x86)\HotCorner)下的config.txt文件,在這個文件中寫入屏幕的真實高度,例如圖中的屏幕真實高度為1080(無單位),然後重啟軟件。(config.txt中的默認值是0,表示自動獲取屏幕高度。)

在軟件運行過程中同時按下Ctrl+Alt+C可以關閉程序

License

代碼使用GPL3協議進行開源,如需使用代碼請遵循CPL3協議相關規定。

作者

  • Tavis Ormandy @taviso – Original Author
  • Ahmed Samy @asamy – HotKey support
  • Yuchao Huang @misterchaos – Application Package

FAQ

  • Q: 屏幕左上角可以觸發時間軸視圖,但是屏幕右下角沒有反應?

  • A: 你可能使用了屏幕縮放,查看配置說明

  • Q: 我想修改屏幕角觸發的事件,怎麼辦?

  • A: 目前只能自己下載源代碼進行修改,然後重新編譯運行。

  • Q: 軟件運行之後怎麼關閉?

  • A: 在軟件運行過程中同時按下Ctrl+Alt+C可以關閉程序

  • Q: 怎麼讓軟件在開機時運行?

  • A: 在安裝過程中可以選擇開機啟動,如果安裝時沒有選擇,可以手動實現(方法自己百度即可)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

比思域更快的大眾家轎才14.59萬起步,買車還不用排隊

這樣的加速放眼這個價位,可以說是數一數二了。當你大腳油門跺下去時,馬牌的MC5輪胎雖然捉地力不錯,但還是響着胎竄出去了,確實有那麼點性能車的味道。儘管動力確實不俗,但是代號DQ380的7速濕式雙離合變速箱還是小小地拖了一下後腿。

大眾在中國深耕多年,絕大部分車系都賣得相當不錯,今天要給大家講講的是奢適寬體轎跑——凌渡。這款車在今年1至3月累計賣出了超過4萬台,實力不用小覷。

動力可以說是凌渡一個小小的賣點,TSI+DSG這套黃金組合併沒有讓我們失望。即便是凌渡的1.4T低功版車型,在實測時,也能有9.1s左右的零百成績。不過在起步時不能憋轉速,這點對於那些玩家來說,還是有點小遺憾。

1.8T車型的零百成績為7.9s,表現比1.4T車型好不少。雖然都是7擋雙離合,但1.8T車型採用的是7擋濕式雙離合。這套雙離合較好地改善了凌渡低速蠕行時的頓挫問題,1/2擋之間的切換不再顯得猶豫。

不過這套7擋雙離合也是有一點點問題,那就是為了平順性,犧牲了一點換擋速度。急加速時的降擋會稍微慢了那麼一點,但也在一個合理的範圍內。如果不是一個對駕駛有很高要求的人,這樣的動力響應已然不錯。

2.0T GTS車型可以說是凌渡的精華所在,220馬力推動僅1475kg的車身,出來的便是6.9s的零百加速。這樣的加速放眼這個價位,可以說是數一數二了。當你大腳油門跺下去時,馬牌的MC5輪胎雖然捉地力不錯,但還是響着胎竄出去了,確實有那麼點性能車的味道。

儘管動力確實不俗,但是代號DQ380的7速濕式雙離合變速箱還是小小地拖了一下後腿。這套變速箱的表現與1.8T車型上的那副相近,都是平順為先,急加速時的降擋還是稍顯拖沓了一些。

除了轎跑這個賣點外,奢適也是凌渡的一大優點。前懸架採用的是麥弗遜式獨立懸架,后懸架則為多連桿獨立懸架,這種結構在同級車中極為常見,但凌渡的調校在操控與舒適之間拿捏得恰到好處。

在過濾路面的震動時,凌渡表現出了足夠的厚實感。即便是在遇到一些大坑窪時,車身的拋跳也不會很明顯。走高速遇到一些接縫位置時,凌渡的底盤貼服性相當不錯。

既然說是奢適寬體轎跑,空間自然不得不提。175cm的體驗者坐在前排時,能獲得3指左右的頭部空間,同時前排的包裹性和舒適性都做得不錯,只是中央扶手的位置稍微低了一些。

二排來看,由於車身偏低矮,所以175cm的體驗者坐在裏面直接就頂頭了,還好腿部依然有兩拳的空間。凌渡的二排中央地板凸起較為明顯,對於中間的乘客不太友好,不過勝在帶有中央頭枕。同時,整個二排的橫向空間表現不錯,坐滿三人時也不會覺得過於擁擠,這點還是值得表揚。

總結

可以看到凌渡這款車表現得頗為全面,無愧於奢適寬體轎跑的名號。動力水平高,儘管換擋稍稍慢了一些,但整體平順性確實好;底盤調校得有高級感,容易討好乘客;最大問題恐怕還是二排頭部空間稍微小了一些。目前凌渡的終端優惠能有5萬塊左右,喜歡的人可以果斷出手了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

花25.88萬買漢蘭達的人看見榮威RX8會相當後悔?

通過尺寸對比表,我們可以看出榮威RX8的三圍比漢蘭達要更大一些,軸距也比漢蘭達長60mm,營造出充足的7座空間。而且榮威RX8的第三排的膝部/頭部空間比漢蘭達大一拳,全幅靠背的採用舒適性更好。230mm同級稱冠的第二排座椅可調行程以及特有的Easy–Entry一鍵放倒功能,進出第三排的便利性更高。

談起7座的SUV,大家都會聯想到充沛的乘坐空間、良好的通過性等標籤,它們擁有着極高的實用性,在生活方式日益多樣化的今天已成為汽車消費升級的首選,漢蘭達可以說是其中最熱門的車型之一,但是7座SUV車型之間的競爭越來越激烈,全新打造的車型越來越多,漢蘭達不得不面臨一位強勁對手發起的挑戰!

它就是來自上汽榮威的“全領域大7座豪華SUV”車型榮威RX8!它是一款基於上汽新一代IS(智能化SUV)平台打造,具備“ALL-DRIVE”智能全領域駕駛系統,也擁有寬敞7座空間的重點車型!而這兩款車型在價格上有所重疊,到底榮威RX8能不能戰勝目前7座SUV界最火車型呢?虎哥就來和大家一探究竟!

不得不和大家先嘮嗑一下車型的價格,因為榮威RX8已經在北京車展正式上市,虎哥認為其售價富有誠意,吸引力絕對不低,給予消費者選擇的空間也更多。

咱們選取售價為22.88萬的榮威RX8 2018款 30T 智聯網四驅至尊版車型和指導價為25.88萬的豐田漢蘭達2.0T四驅精英版進行直接對比。

先來對比外觀造型,榮威RX8的前臉採用了“律動”設計語言,造型富有力量感。全LED騰雲大燈科技感強,視覺也相當舒展,4顆透鏡式LED燈源組成的獵戶座星雲霧燈,也顯得炯炯有神。

(注:拍攝視頻車型為2017款漢蘭達)

榮威RX8的車身還具備黃金分割設計短前懸長后懸的豪華四驅車身比例,看上去具備動感,側面也刻畫出強而有力的輪包曲面。尾部設計橫向舒展,它具備內切燕尾、梯形的尾門的設計特點,比較有意思的是定音錘尾燈造型帶有“一錘定音”的寓意,細節處理值得仔細欣賞!

新款漢蘭達的外觀設計在原有基礎上有所升級,它進氣格柵的面積得到擴大,但是車身輪廓仍舊屬於中庸的設計風格。

視覺上略顯沉悶,而大燈造型的變化不大,它融入了LED日間行車燈,相比榮威RX8的全LED燈組顯得精緻感不足。車尾的造型敦實,造型飽滿但精緻感略顯不足。

榮威RX8品牌家族化設計相當舒展、座艙採用環繞式設計富有層次感、體現出精緻豪華也兼具優雅的設計風格。用料相當考究,大面積皮質軟包與淡雅木紋飾板帶來良好觸感,10.1英寸中控大屏也富有科技感,細節處理也進一步提升了精緻感!

而漢蘭達的內飾質感則顯得比較遜色,它中控布局簡潔明了,採用的軟質材料不少,看上去比較大氣。但是軟包面積不及榮威RX8,6.1英寸中控屏多媒體娛樂系統也顯得有些小氣。

在大家相當關心的配置方面,榮威RX8拉開了明顯的優勢!值得一提的是在用戶舒適性營造方面它十分到位,無線充電、真皮座椅、主駕駛電動調節、前排座椅加熱+通風、第二排座椅加熱、大面積採光的全景天窗、獨立式空氣凈化器它均有配備!

而且科技配備也已相當齊全,它配備有車機手機雙向WIFI、AI人工智能語音系統、主動導航系統、智能操作系統、虛擬儀錶、loT手機遠程車控系統等潮流前端的科技配備,配置方面的優勢十分明顯!

通過尺寸對比表,我們可以看出榮威RX8的三圍比漢蘭達要更大一些,軸距也比漢蘭達長60mm,營造出充足的7座空間。而且榮威RX8的第三排的膝部/頭部空間比漢蘭達大一拳,全幅靠背的採用舒適性更好!230mm同級稱冠的第二排座椅可調行程以及特有的Easy–Entry一鍵放倒功能,進出第三排的便利性更高。漢蘭達第二排僅有150mm的移動距離,便利性有待提高。

榮威RX8採用了BBA豪華品牌供應商德國Bader的頂級頭層小公牛皮座椅,而且還應用階梯式劇院座椅布局,每排乘客能享受舒適坐姿、視野開闊,並且NVH靜音水準較高,還配備大採光面積全景天窗。

舒適配置全面,榮威RX8的前排擁有電動調節、主駕迎賓記憶、遠程座椅加熱、三檔加熱和通風等功能,第二排乘客也能享受座椅加熱功能,這功能在冬天相當實用!而漢蘭達則採用織物座椅,缺乏座椅加熱、通風功能,前排座椅沒有沒有電動調節,實用性不及榮威RX8。

榮威RX8搭載縱置布局的“藍芯”2.0TGI缸內直噴渦輪增壓發動機,它具備全鋁缸體、雙可變氣門正時技術、渦輪增壓等高效科技,輸出224馬力最大功率、360牛米最大扭矩,動力輸出優於漢蘭達。

與之匹配的是一款縱置愛信6速手自一體變速箱,動力輸出具備平順性、高效率的特點,這讓榮威RX8具備9.1秒的百公里加速和9.1L/100km綜合油耗的出眾表現!

此外榮威RX8全系標配雙活塞浮動式制動卡鉗和陶瓷剎車片,這讓它的100-0km/h制動距離僅38米,要知道這是一款大尺寸的7座SUV車型,日常駕駛富有底氣!而某主流汽車媒體實測的漢蘭達剎車距離已超過41米,與榮威RX8有着不少差距。

咱們先來看看硬件配備,榮威RX8採用高強度非承載式車身、雙叉臂獨立前懸架+五連桿后懸架,車身剛性高、抗扭能力強。它還具備較大后傾角設定,比起採用承載式車身設計的漢蘭達更能適應各種道路情況!

而且榮威RX8公路駕駛時擁有良好的操控性、舒適性,它還搭載全時四驅系統和專業級中央、後橋差速鎖,可實現2驅和4驅鎖止!自動机械後橋限滑差速器在左右車輪速差超過120轉/分鐘時會自動鎖死,在複雜路面脫困能力高。

漢蘭達則屬於承載式車身的城市SUV,採用的是基於前橫置的適時四驅系統,而且它僅有中央差速器鎖止功能,通過性不強,更適合市區路段。

而且在四驅系統標定這一重要的軟實力方面,榮威RX8 無疑更加全面,下面咱們通過一個表格來詳細對比:

榮威RX8帶有六大地形駕駛模式,在各種路況上適應能力強不少,而且雪地、越野、低速這三個駕駛模式能針對複雜的越野路面,實用性比漢蘭達高不少。

榮威RX8搭載全新一代互聯網汽車智能系統-,即基於AliOS的斑馬智行解決方案,各項功能符合時下潮流,實用性強!在硬件上榮威RX8搭載10.1英寸高清中控屏和7英寸虛擬儀錶,显示清晰,操控更加便利,提升駕駛安全性。同時也提供了車機手機雙向WiFi和基礎服務、基本流量終身免費。

而在這一價位中的漢蘭達只有6.1英寸多媒體娛樂系統,導航、有手機映射、聯網功能欠奉,功能性上落後於時代。

寫在最後:經過全面的對比,咱們可以發現榮威RX8的外觀設計、內飾豪華度、科技感、配置豐富程度、乘坐空間、複雜路面適應性能、四驅系統全面性上都具備優勢,其實這也在意料之中,現在車型迭代迅速,作為全新車型的榮威RX8明顯具有後來居上之優勢,也極有潛力成為7座SUV中最有競爭力的車型!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

Jmeter(十) – 從入門到精通 – JMeter邏輯控制器 – 中篇(詳解教程)

1.簡介

Jmeter官網對邏輯控制器的解釋是:“Logic Controllers determine the order in which Samplers are processed.”。

意思是說,邏輯控制器可以控制採樣器(samplers)的執行順序。由此可知,控制器需要和採樣器一起使用,否則控制器就沒有什麼意義了。放在控制器下面的所有的採樣器都會當做一個整體,執行時也會一起被執行。

JMeter邏輯控制器可以對元件的執行邏輯進行控制,除僅一次控制器外,其他可以嵌套別的種類的邏輯控制器。

2.邏輯控制器分類

JMeter中的Logic Controller分為兩類:
(1)控制測試計劃執行過程中節點的邏輯執行順序,如:Loop Controller、If Controller等;
(2)對測試計劃中的腳本進行分組、方便JMeter統計執行結果以及進行腳本的運行時控制等,如:Throughput Controller、Transaction Controller。

3.預覽邏輯控制器 

首先我們來看一下JMeter的邏輯控制器,路徑:線程組(用戶)->添加->邏輯控制器(Logic Controller);我們可以清楚地看到JMeter5中共有17個邏輯控制器,如下圖所示:

如果上圖您看得不是很清楚的話,宏哥總結了一個思維導圖,關於JMeter5的邏輯控制器類型,如下圖所示: 

 通過以上的了解,我們對邏輯控制器有了一個大致的了解和認識。下面宏哥就給小夥伴或則童鞋們分享講解一些通常在工作中會用到的邏輯控制器。 

4.常用邏輯控制器詳解

  這一小節,宏哥就由上而下地詳細地講解一下常用的邏輯控制器。

4.1Critical Section Controller

我們先來看一下,官方原汁原味的解釋:The Critical Section Controller ensures that its children elements (samplers/controllers, etc.) will be executed by only one thread as a named lock will be taken before executing children of controller.

宏哥這個二把刀的翻譯,給你們翻譯一下這段鳥語看看到底是什麼意思,大致意思是: Critical Section Controller(臨界區控制器),確保它的子元素(samplers /控制器等)在執行控制器的子程序之前只執行一個線程作為指定的鎖。呵呵!看到這句話是不是覺得一頭霧水啊,摸一摸自己所剩無幾的頭髮陷入沉思中…..沒有關係的,不要糾結了,宏哥後邊會用具體實例講解一下,看完實例后,再回過頭來閱讀這句話,你就會恍然大悟了。

 1、我們先來看看這個Critical Section Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > 臨界部分控制器,如下圖所示:

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空;

Lock name:鎖名稱,這裏可以填入其子節點下執行的線程的名稱,這個線程作為一個全局鎖存在

4.1.1實例講解 

這部分主要是通過配合實例我們來理解一下開始那句話到底什麼意思。

1、宏哥這裏先說一個訪問宏哥博客園的JMeter系列文章的測試場景:我們第一步首先要訪問博客園的首頁,第二步找到宏哥的訪問宏哥博客園的首頁,第三步點擊JMeter類別。按順序完成三步,才能完成這個測試場景。那麼我們根據這個場景用JMeter來添加多個請求的取樣器。如下圖所示:

2、腳本調試是通了,運行JMeter,查看結果樹,如下圖所示:

3、從上個圖,查看結果樹中显示請求結果數據不是按照順序請求,不符合預期,這個時候增加一個critical section controller(臨界部分控制器),增加一個鎖,就能控制執行順序。如下圖所示:

4、腳本調試是通了,運行JMeter,查看結果樹,可以清楚地看出來是按預期的順序執行請求的。但是這樣響應時間會過長,這個後邊再做講解。如下圖所示: 

 4.1.2鎖名分類

(1)鎖名為空,認為每個鎖為不同的鎖

1、宏哥通過具體實例,來看一下,創建鎖名為空的臨界部分控制器,如下圖所示:

2、運行JMeter,查看結果樹,如下圖所示:

(2)鎖名相同,多個鎖認為是同一個鎖,同一個時間點只能存在一個運行中

1、宏哥通過具體實例,來看一下,創建鎖名相同的臨界部分控制器,如下圖所示:

2、運行JMeter,查看結果樹,如下圖所示:

(3)鎖名為變量,根據變量值來判斷是不是屬於同一個鎖,變量值為相同時,則認為是同一個鎖

1、宏哥通過具體實例,來看一下,創建鎖名為變量的臨界部分控制器,如下圖所示:

2、運行JMeter,查看結果樹,如下圖所示:

4.2ForEach Controller

ForEach 控制器:一般搭配用戶變量使用。依次調用用戶定義的變量,直到最後一個,結束循環。為了滿足ForEach Controller提取數據,變量命名的格式一般為“變量名_数字”,其中数字從1開始。
1、即遍歷循環控制器,顧名思義是定義一個循環規則。
2、用來遍歷當前元素的所有可執行場景。
3、在用戶自定義變量中讀取一系列相關的變量,該控制器下的採樣器或控制器都會被執行一次或多次,每次讀取不同的變量值。
4、這個控制器一般配合配置元件 → 正則表達式提取器來一起使用,可對頁面上的某些元素進行重複處理。

1、我們先來看看這個ForEach Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > ForEach控制器,如下圖所示: 

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空;

Input variable prefix:輸入變量前綴,可以在“用戶自定義變量”中定義一組變量,循環控制器可以從中獲取到變量對應的值,然後作為循環控制器的循環條件,還可以輸出變量作為取樣器的參數。

Start index for loop:循環變量下標起點。循環指數開始(唯一)→ 遍歷查詢的變量範圍,開始的值(這裏如果不填寫,默認從 1 開始,如果沒有 1 開始的變量,執行時會報錯)

End index for loop:循環變量下標終點。循環指數結束(包含)→ 遍歷查詢的變量範圍,結束的值

Output variable name:輸出變量名稱,循環控制器生成的變量名稱。後續可通過${}引用

Add “_” before number ?: 變量前綴后是否加“_”作為分隔符。如果定義的變量名中有下劃線的話就要勾選此項,否則找不到;反之,沒有的話不要勾選,否則同樣找不到變量

4.2.1實例講解

1、首先在自定義5個前綴為 北京宏哥 的變量,值分別為 a b c d e。並且 北京宏哥 後面的数字是連續的,如果不連續,則不會被循環到,如下圖所示:

2、配置ForEach控制器,如下圖所示:

3、添加請求 訪問博客園首頁 + 輸出值:${宏哥},輸出值是在控制器的輸出變量 宏哥,通過 ${宏哥} 取到輸出變量的值,如下圖所示:

4、添加查看結果樹,運行JMeter,查看結果樹,如下圖所示: 

注意:敲黑板,敲腦袋!!!

1、輸入變量的後綴數值一定要連續,比如 北京宏哥_1, 北京宏哥_2, 北京宏哥_3 … 這樣,如果中間有不連續的,循環會中斷

2、循環開始的設定:如果變量為北京宏哥_1, 北京宏哥_2, 北京宏哥_3,而設定的開始為 1,則會從北京宏哥_2 開始循環

3、循環結束的設定:如果變量有3個 北京宏哥_1, 北京宏哥_2, 北京宏哥_3,而設定的結束為5,則只會循環 北京宏哥_1, 北京宏哥_2, 北京宏哥_3 ,如果設定的結束為2,則會循環  北京宏哥_1, 北京宏哥_2 。

4.3Include Controller

Include控制器用來導入外部的測試片段(非完整的測試計劃),在執行時會執行導入的測試計劃,但是被導入的測試計劃有特殊要求,它不能有線程組,只能包含簡單的控制器及控制器下的元件。換句話說就是相當於加了一個執行單元,一個封裝了的業務操作單元,類似我們程序開發中的函數(方法)一樣。例如一個查詢學生信息的業務操作我們用取樣器來模擬,然後放到簡單控制器中作為一個執行單元,別的地方也要用到時,我們可以不用重複造輪子直接引用過來。

一般來說,Include控制器和測試片段(Test Fragment)配合使用的比較常見。

1、我們先來看看這個include Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > Include控制器,如下圖所示:

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空;

Filename:文件名,必輸字段,如果沒有,就會報錯。通過Filename的路徑和文件名引用外部的jmx文件。

宏哥推薦小夥伴或者童鞋們可以將 Include控制器 Module控制器(傳送門)一起對比着學習,Include控制器 是從外部文件引用,只能引用整個測試片段的內容,Module控制器 是從內部文件中引用,引用上相對比較靈活,可以只引用部分測試片段或模塊內容。這樣一內一外不僅容易理解也容易記憶和學習。

4.3.1實例

(1)當Filename路徑的值為空,程序執行報錯,腳本執行中止,不會繼續執行下面的腳本內容。

1、首先創建一個Filename路徑為空的測試計劃,如下圖所示:

2、運行JMeter,查看結果樹(程序執行報錯,腳本執行中止,不會繼續執行下面 訪問博客園首頁 的取樣器),如下圖所示: 

(2)當Filename路徑中的文件不存在,程序直接彈窗報錯並停止執行。

1、首先創建一個Filename路徑中的文件不存在的測試計劃,點擊“保存”按鈕的時候,就會直接彈窗報錯。如下圖所示:

(3)當Filename路徑的文件中不包含測試片段,跳過控制器,繼續向下執行。

1、首先創建一個外部引用沒有測試片段的測試計劃,如下圖所示:

2、創建一個Filename路徑的文件中不包含測試片段的測試計劃,將上邊的外部引用-無測試片段文件添加到Include控制器中,如下圖所示:

3、運行JMeter,查看結果樹(跳過控制器,繼續向下執行 訪問博客園首頁 的取樣器),如下圖所示: 

(3)當Filename路徑的文件中包含測試片段,執行完控制器,再繼續向下執行。

1、首先創建一個外部引用有測試片段的測試計劃,如下圖所示:

2、創建一個Filename路徑的文件中包含測試片段的測試計劃,將上邊的外部引用-有測試片段文件添加到Include控制器中,如下圖所示:

3、運行JMeter,查看結果樹(執行完控制器里的測試片段,再繼續向下執行 訪問博客園首頁 的取樣器),如下圖所示: 

到這裏,大家應該理解了  Include Controller 和  Test Fragment 了吧。宏哥的理解就是,Test Fragment 相當於一個獨立的部分,可以被其他測試計劃引用,實現 樣例的片段化,模塊化,遇到重複需要的,比如登錄、註冊之類的,就可以用Test FragmentInclude Controller 了。這樣可以避免重複造輪子,做許多無用功。

5.小結

   好了,今天關於邏輯控制器的上篇就講解到這裏,這一篇主要介紹了 Critical Section ControllerForEach ControllerInclude控制器

 

您的肯定就是我進步的動力。如果你感覺還不錯,就請鼓勵一下吧!記得隨手點波  推薦  不要忘記哦!!!

別忘了點 推薦 留下您來過的痕迹

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

Spring系列.AOP使用

AOP簡介

利用面向對象的方法可以很好的組織代碼,也可以繼承的方式實現代碼重用。但是項目中總是會出現一些重複的代碼,並且不太方便使用繼承的方式把他們重用管理起來,比如說通用日誌打印,事務處理和安全檢查等。我們可以將這些代碼封裝起來,做成通用模塊,但是還是需要在代碼中每處需要的地方進行显示調用,使用起來不方便。這是時候就是利用AOP的時候。

AOP是一種編程範式,用來解決特定的問題,不能解決所有問題,可以看做是OOP的補充,常見的編程範式還有:

  • 面向過程編程;
  • 面向對象編程;
  • 面向函數編程(函數式編程);
  • 事件驅動編程(GUI開發中比較常見);
  • 面向切面編程

AOP的常見使用場景

  • 性能監控,在方法調用前後記錄調用時間,方法執行太長或超時報警;
  • 緩存代理,緩存某方法的返回值,下次執行該方法時,直接從緩存里獲取;
  • 軟件破解,使用AOP修改軟件的驗證類的判斷邏輯;
  • 記錄日誌,在方法執行前後記錄系統日誌;
  • 工作流系統,工作流系統需要將業務代碼和流程引擎代碼混合在一起執行,那麼我們可以使用AOP將其分離,並動態掛接業務;
  • 權限驗證,方法執行前驗證是否有權限執行當前方法,沒有則拋出沒有權限執行異常,由業務代碼捕捉;
  • 事務處理 。

Spring AOP相關概念

  • AOP:這種在運行時(或者編譯時或者加載時),動態地將某些公共代碼切入到類的指定方法、指定位置上的編程思想就是面向切面的編程;
  • 切面(Aspect):A modularization of a concern that cuts across multiple classes。在Spring中切面就是一個標註@AspectJ的類,不要想得太複雜;
  • 連接點(Joinpoint):方法執行過程中的某個點,是在應用執行過程中能夠插入切面的一個點。這個點可以是調用方法時、拋出異常時、甚至修改一個字段時。切面代碼可以利用這些點插入到應用的正常流程之中,並添加新的行為;
  • 通知(advice):描述切面要完成什麼工作,以及在什麼時間點進行工作;
  • Pointcut:用來匹配一組連接點,並且pointcut會關聯advice,在pointcut匹配的連接點執行的時候,advice代碼會被執行;
  • Introduction
  • Target object:被織入切面的對象;
  • AOP proxy : 包裝了切面代碼和target代碼的對象,Spring中支持JDK動態代理和
    CGLIB,默認使用JDK動態代理,但是如果被代理的類沒有實現接口,或者用戶強制使用CGLIB,那麼Spring會使用CGLIB代理;
  • Weaving:將切面代碼添加到目標代碼的過程,織入的類型有編譯時織入,加載時織入和運行時織入(Spring是運行時織入)

SpringAOP可以應用5種類型的通知:

  • 前置通知(Before):在目標方法被調用之前調用通知功能。
  • 後置通知(After):在目標方法完成之後調用通知,此時不會關心方法的輸出是什麼。(不管執行是否成功都執行都執行)
  • 返回通知(After-returning):在目標方法成功執行之後調用通知。
  • 異常通知(After-throwing):在目標方法拋出異常后調用通知。
  • 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之後執行自定義的行為。

Spring AOP相關

開啟Aop


//自動選擇合適的AOP代理
//傳統xml這樣配置:<aop:aspectj-autoproxy/>

//exposeProxy = true屬性設置成true,意思是將動態生成的代理類expose到AopContext的ThreadLocal線程
//可以通過AopContext.currentProxy();獲取到生成的動態代理類。

//proxyTargetClass屬性設置動態代理使用JDK動態代理還是使用CGlib代理,設置成true是使用CGlib代理,false的話是使用JDK動態代理

//注意:如果使用Spring Boot的話,下面的配置可以不需要。AopAutoConfiguration這個自動配置類中已經自動開啟了AOP
//默認使用CGLIB動態代理,Spring Boot配置的優先級高於下面的配置

@Configuration
@EnableAspectJAutoProxy(exposeProxy = true,proxyTargetClass = false)
public class AopConfig {

}


如果使用傳統的配置方式的話,可按如下配置開啟AOP功能。

<aop:aspectj-autoproxy/>

定義一個Aspect

Aspects (classes annotated with @Aspect) can have methods and fields, the same as any other class. They can also contain pointcut, advice, and introduction (inter-type) declarations.

可以使用普通Bean的定義方式,或者加@Aspect註解的方式定義。一旦一個類被標註成切面類,它就不會成為其他切面的代理對象。

定義一個PointCut

切面表達式可以由指示器,通配符和運算符組成

  1. 指示器(Designators)
  • 匹配方法 execution() (重點掌握…)
  • 匹配註解 @target() @args() @within() @annotation()
  • 匹配包/類型 within()
  • 匹配對象 this() bean() target()
  • 匹配參數 args()
  1. Wildcards(通配符)
  • *匹配任意數量的字符
  • +匹配指定類及其子類
  • .. 一般用於匹配任意參數的子包或參數
  1. Operators(運算符)
  • && 與操作符
  • || 或操作符
  • ! 非操作符

下面給出一個定義PointCut的例子

package com.csx.demo.spring.boot.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class MyAspect {

    //PointCut匹配的方法必須是Spring中bean的方法
    //Pointcut可以有下列方式來定義或者通過&& || 和!的方式進行組合.
    //下面定義的這些切入點就可以通過&& ||組合

    private static Logger logger = LoggerFactory.getLogger(MyAspect.class);

    //*:代表方法的返回值可以是任何類型
    //整個表達式匹配controller包下面任何的的echo方法,方法入參樂意是任意
    @Pointcut("execution(* com.csx.demo.spring.boot.controller.*.echo(..))")
    public void pointCut1(){}

    //代表echo方法必須有一個參數 參數的類型可以是任意類型
    @Pointcut("execution(* com.csx.demo.spring.boot.controller.*.echo(*))")
    public  void pointCut2(){}

    //代表echo方法必須有兩個參數,第一個類型任意,第二個類型必須是String
    @Pointcut("execution(* com.csx.demo.spring.boot.controller.*.echo(*,String))")
    public void pointCut3(){}

    //contrller包及其子包下面的任意類的任意方法
    //需要注意的是with和@with都是正對包級別的
    @Pointcut("within(com.csx.demo.spring.boot.controller..*)")
    public void pointCut4(){}

    //使用RestController這個註解標註任意類的任意方法
    @Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
    public void pointCut5(){}

    //用法和@Within類似
    @Pointcut("@target(org.springframework.web.bind.annotation.RestController)")
    public void pointCut10(){}

    //MyService這個接口實現類的任何方法
    //如果MyService是一個類的話,那匹配這個類內部的所有方法
    @Pointcut("this(com.csx.demo.spring.boot.service.MyService)")
    public void pointCut6(){}

    @Pointcut("this(com.csx.demo.spring.boot.service.MyServiceImpl)")
    public void pointCut7(){}

    //某個bean內部的所有方法
    @Pointcut("bean(myServiceImpl)")
    public void pointCut8(){}

    //@within和@target針對類的註解,@annotation是針對方法的註解
    //匹配任何標註GetMaping註解的方法
    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)")
    public void pointCut9(){}

    //匹配只有一個參數,參數類型是String的方法
    @Pointcut("args(String)")
    public void pointCut11(){}


    @Before("pointCut1()")
    public void befor(){
        logger.info("前置通知vvvv...");
        logger.info("我要做些事情...");
    }

    @After("pointCut1()")
    public void after(){
        logger.info("後置通知");
    }

    @AfterReturning("pointCut1()")
    public void afterReturn(){
       logger.info("後置返回");
    }

     //目標方法拋出相關異常后通知
    @AfterThrowing("pointCut1()")
    public void afterThrowing(){
        logger.info("後置異常");
    }

    @Around("pointCut1()")
    public void around(ProceedingJoinPoint point) throws Throwable {
        logger.info("環繞通知...");
        logger.info("我要做些事情...");
        point.proceed();
        logger.info("結束環繞通知");
    }

}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

亞洲碳中和浪潮下 澳洲總理仍裹足不前捍衛煤炭

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

研究:全球氣溫升高 孕婦早產與死產風險俱增

摘錄自2020年11月4日中央社報導

研究人員今(3日)表示,暴露於高溫和熱浪的孕婦,有較高的早產或死產風險。

法新社報導,「英國醫學期刊」(BMJ)的研究報告指出,這項結果與貧窮有密切關聯,尤其是熱帶地區。隨著全球暖化問題加劇,特別是熱浪頻繁且強烈的期間,這個問題會更加嚴重。

世界衛生組織統計,每年有1500萬名嬰兒早產,這是導致5歲以下幼兒死亡的主因,而死亡率主要集中在發展中國家,特別是非洲國家。

為量化氣溫升高對懷孕結果的影響,南非約翰尼斯堡金山大學生殖健康暨愛滋病毒研究所(Wits Reproductive Health and HIV Institute)研究人員契爾斯奇(Matthew Chersich)領導的國際團隊,檢視70篇經同儕審視的相關研究,內容涵蓋27個富裕、貧窮與中間所得國家。

研究發現,在47篇關於早產的研究中有40篇表示,溫度升高後早產的現象更為常見。

根據這項新發現,平均而言,氣溫每上升攝氏1度,早產風險就會增加5%;而在熱浪侵襲期間,早產風險更是增加16%。

氣候變遷
國際新聞
升溫
孕婦
早產

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

同級最帥的4款轎車,50萬以內沒對手,年輕人就該買這些車

68-68。38萬1、3、5、7系,大家肯定都知道,那4系是什麼鬼。基本上來說,把它“當”成3系的雙門版本准沒錯。這一代寶馬4系其實與3系關聯性非常大,應該說除了外形上的差異,動力系統與机械結構基本一致的。這時候肯定有人會說,“貴這麼多還要少兩扇門這是玩我呢。

基本上每個男生或者喜愛汽車的女性朋友都有着一個跑車夢,開着敞篷跑車在海邊公路上馳騁,或者傍晚獨自在山路享受日落的時間。但現實總是殘酷的,年輕的時候可能礙於經濟實力的原因,基本沒可能會選擇跑車,到了成家立業以後,手頭是寬裕了,可是為了家人買一台敞篷跑車似乎也不是太適合,當然如果是自己的第二第三台車的話就無所謂。那有沒有能作為第一台車,既能滿足家用,也有着想到高的顏值和動感的駕駛體驗呢?答案是有,而且還有4部給閣下選擇。

梅賽德斯-奔馳(進口)- C級

指導價:41.68萬-48.98萬

每次提起奔馳,用優雅這個形容詞最為合適,好像就是它與生俱來的氣質融入到每一款車中,而加上Coupe這個詞之後,這份優雅更能發揮到極致。性感的外形總能給路人留下深刻的印象,經過徹底的革新之後,完全不同於三廂版C級。如果閣下看到這樣一台陌生的屁股,絕對就是Coupe。

C級Coupe不管是300還是200,對應的都是個性化雙門轎跑風格的中型車市場,它們都擁有凌厲而運動的外觀設計,毫不保留的展現出自己的個性,時刻讓你記住一句話,你開的可是奔馳。

英菲尼迪(進口)- Q60

指導價38.98-40.98萬

也許是品牌關係,大家一說到英菲尼迪第一時間想到的就是Q50,然後,就沒有然後了。那現在大家不妨認識一下Q50的Coupe版,Q60,一台詮釋了什麼叫“曲線美”雙門轎跑。各種流暢似水的線條勾勒出絕美的車身輪廓,一副攻擊性十足的前臉相信能瞬間俘獲閣下的心,不說了,先去擦一下口水。

外觀融合了Q50 EauRouge和概念車Q80 Inspiration的設計新元素,使車頭更有衝擊力。內飾相比動感的外觀則溫柔很多,甚至略帶平淡。動力方面目前在售的版本均匹配2.0T發動機搭配7速手自一體變速箱,整體動態跟Q50L相似。

寶馬(進口)- 4系

指導價:41.68-68.38萬

1、3、5、7系,大家肯定都知道,那4系是什麼鬼?基本上來說,把它“當”成3系的雙門版本准沒錯。這一代寶馬4系其實與3系關聯性非常大,應該說除了外形上的差異,動力系統與机械結構基本一致的。這時候肯定有人會說,“貴這麼多還要少兩扇門這是玩我呢?”,這個問題一開始也是這麼想的,直到開上去的那瞬間,那操控手感,那底盤素質。值,非常值。

總結起來4系的特點就是:外觀比3系更寬更低,視覺效果也更震撼,內飾就是一台“寶馬”,沒有驚喜,沒有意外。要說4系最吸引的,還是跟3系的外觀差異,以及紮實的底盤功底。

雷克薩斯-RC

指導價:48.80-58.60萬

雷克薩斯這車在今天講的4部車裡算是比較特別的存在,很多人以為它是IS的雙門版本,但實際上RC可以完全看作單獨專門開發的跑車,而不是衍生車型。

日本紳士最厲害是什麼?當然就是那個固執起來又挺能跟上潮流的性格,RC霸氣的外觀設計會很容易讓人誤以為它有着強大的性能,事實上它對舒適性的妥協較多,很舒服的座椅,很舒服的車內氛圍,在高速上會開到困也不奇怪。性能也是今天講的4部車裡最不“跑車”的,但如果閣下就喜歡跑車造型設計和超級舒適的內在,那麼RC也許就是最適合閣下的那台愛車

結尾

50萬可以買一部思域然後改得“飛天遁地”,也可以買坐上去就舒服到馬上睡着的行政車,但能在外形和內部豪華感上達到平衡且高水準的,目前還是比較少見的。雖然4部都是2.0T汽油發動機,跟“性能”都相去甚遠,但日常在道路找找激情撒撒野還是沒任何問題的。如果閣下是“絕對顏控”,Q60肯定是第一選擇;如果說雙門跑車一定要德國車的話,C級Coupe和4系都不會讓閣下失望,甚至能有點小興奮;至於RC,它除了能給閣下“我很快”的感覺之外,雷克薩斯的匠人品質以及豪華感還能讓圍觀的路人都覺得“你好棒”。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

教授試了一款不到10萬的智能SUV?

1英寸的中控大屏清晰度和靈敏度都表現不錯,並且還加入了車聯網功能,通過語音可以控制車輛大多數的功能,例如開關天窗,開關音樂等,方便車主日常用車生活,同時增加行車安全。整車的配置豐富,前排座椅加熱,車道偏離預警,盲區監測,全景攝像頭,ESp車身穩定系統應有盡有,這也是自主品牌的優勢之一。

五月份的青島是夏至未至的時分,海風輕拂過臉頰,聞着少許帶有魚腥味的海風,來到青島試駕2018款凱翼X3,雖然說凱翼是新興品牌,但並不代表車輛的競爭力低,到底細節如何?產品力怎麼樣?為你一一解讀。

外觀上,2018款凱翼X3與2017款區別不大,U型鍍鉻條圍繞着進去格柵,並與前大燈相結合,配合上半圓形行車燈圍繞的透鏡大燈,雖說是鹵素光源,但透鏡的加入讓整車的精神氣更上一層樓。

雙色的輪圈時尚感很強,215/60 R17的輪胎尺寸,有足夠厚的胎壁來面對各種複雜路況,符合它SUV的定位,高配車型為18寸輪圈,視覺效果更好,但濾震效果不及17寸輪胎。

車尾的造型是凱翼X3的亮點之一,小書包造型的外掛備胎,讓整輛車的野性十足,側開的開門方式也個性十足;尾燈的造型與頭燈呼應,同是半圓形,點亮后視覺效果滿分。

內飾有兩種配色:全黑/黑棕,中控台總體以對稱設計為主,主要突出年輕,簡約化,中控台上方使用的是軟質材料,銀色的飾條讓整个中控台的層次感增加了不少,圓形的空調出風口加上周圍的銀色裝飾,增添了內飾的一絲活力。10.1英寸的中控大屏清晰度和靈敏度都表現不錯,並且還加入了車聯網功能,通過語音可以控制車輛大多數的功能,例如開關天窗,開關音樂等,方便車主日常用車生活,同時增加行車安全。

整車的配置豐富,前排座椅加熱,車道偏離預警,盲區監測,全景攝像頭,ESp車身穩定系統應有盡有,這也是自主品牌的優勢之一。

主駕的座椅支持6向手動調節,座椅皮質的手感雖然不算特別好,比上不足比下有餘。第二排座椅支持4/6放倒,不過放倒以後上下地台差別大,不利於大物件裝載,側開門也帶來了一個缺點,往右開的側開門方式不太適合中國國情使用,因為我國道路是靠右行駛,一般情況下是靠右停車,向右開門的話會阻礙物品的裝載以及影響安全。

2018款凱翼X3全系標配1.6L自然吸氣發動機,智享版以上配備CVT變速箱(智享版一下為手動),發動機最大馬力126ps,最大扭矩160N*m,從數據上看,整體的功率不算大,由於CVT變速箱的特性,整體動力輸出以平順為主調,2000rpm一下車輛表現慵懶,2000rpm以上稍有起色,如果想要在高速上快速超車,需要增加多一點提前量。3000rpm以上發動機的噪音又會相對明顯,總體來說動力總成在夠用的範圍。

方向盤轉向力度有三擋可調,轉向有三種重量,日常使用個人比較喜歡最輕模式,減輕勞動強度。

凱翼X3使用前麥弗遜后雙連桿結構,整體調校毫無意外地偏舒適,在車輛經過減速帶等快速的凸起時,會比較积極地過濾震動,但遇到波浪型或較大拋跳的路面時,懸挂就顯得有點無所適從,且懸挂偏向支撐,所以傳到車內的震動會相對大一點,給人一絲運動感。

總結

凱翼X3的外形和內飾都是為了迎接年輕人圍目的,2018款凱翼X3將該理念貫徹得更加徹底,並且增加了車聯網系統,進入如今時髦的互聯網汽車行列,即使將價格控制在10萬以下,ESp、盲區監測、車道偏離預警系統等實用的配置一樣不落,動力和操控不是凱翼的長處,但好在空間濾震都不錯,總體中規中矩,沒有什麼短板,是年輕人第一輛車,又需要一定實用性不錯的選擇。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?