主頁 > 後端開發 > 泛化之美 —— C++11 可變引數模板的妙用

泛化之美 —— C++11 可變引數模板的妙用

2022-10-28 06:42:26 後端開發

image

概述

首先這篇文章出自博客園作者:[ ?? qicosmos ],我對本文的實體代碼進行了學習、思考和整理糾正,理清了文章的全部細節,覺得這是一篇讓我受益匪淺的文章,之所以會接觸「可變引數模板」這部分的內容,是因為我當下剛好在學C++11 function機制,其內部實作需要接收不定長度的引數,因此需要用到「可變引數模板」相關的知識,本文有很多的C++模板元編程「黑魔法」是我之前從來沒接觸過的,比如模板遞回展開、型別萃取type_traits中的基石integral_constant等等,C++的學習之路任重而道遠呀,那廢話不多說,我們來說說今天的主題,C++11的可變引數模板,

C++11的新特性「可變引數模板(variadic templates)」是C++11新增的「最強大」的特性之一,它對引數進行了高度泛化,它能表示0到任意個數、任意型別的引數,相比C++98/03,類模板和函式模板中只能含固定數量的模板引數,可變模板引數無疑是一個巨大的改進,然而由于可變模板引數比較抽象,使用起來需要一定的技巧,所以它也是C++11中最難理解和掌握的特性之一,雖然掌握可變引數模板有一定難度,但是它卻是C++11 中最有意思的一個特性,本文希望帶領讀者由淺入深的認識和掌握這一特性,同時也會通過一些實體來展示可變引數模板的一些用法,

可變模板的引數展開

可變引數模板和普通模板的語意是一樣的,只是寫法上稍有區別,宣告可變引數模板時需要在typenameclass后面帶上省略號「...」,比如我們常常這樣宣告一個可變模板引數:template<typename...>或者template<class...>,一個典型的可變模板引數的定義是這樣的:

template <typename... T>
void f(T... args);

上面的可變模板引數的定義當中,省略號的作用有兩個:

  • 宣告一個引數包T... args,這個引數包中可以包含0到任意個模板引數;
  • 在模板定義的右邊,可以將引數包展開成一個一個獨立的引數,

上面的引數args「前面」有省略號,所以它就是一個可變模板引數,我們把帶省略號的引數稱為“引數包”,它里面包含了0N(N>=0)個模板引數,我們無法直接獲取引數包args中的每個引數的,只能通過「展開引數包」的方式來獲取引數包中的每個引數,這是使用可變模板引數的一個「主要特點」,也是「最大的難點」,即如何展開可變模板引數,

可變模板引數和普通的模板引數語意是一致的,所以可以應用于函式和類,即「可變引數模板函式」和「可變引數模板類」,然而,模板函式不支持偏特化,所以可變引數模板函式和可變引數模板類展開可變引數的方法還不盡相同,下面我們來分別看看他們展開可變引數的方法,

可變引數函式模板

一個簡單的可變引數函式模板:

#include <iostream>
using namespace std;

template<typename... T>
void f(T... args) {
    cout << sizeof...(args) << endl;
}

int main() {
    f();        // 0
    f(1, 2);    // 2
    f(1, 2, "");// 3
    return 0;
}

上面的例子中,f()沒有傳入引數,所以引數包為空,輸出的size為0,后面兩次呼叫分別傳入兩個和三個引數,故輸出的size分別為2和3,由于可變模板引數的型別和個數是不固定的,所以我們可以傳任意型別和個數的引數給函式f,這個例子只是簡單的將可變引數模板的個數列印出來,如果我們需要將引數包中的每個引數列印出來的話就需要通過一些方法了,展開可變模板引數函式的方法一般有兩種:

  • 一種是通過「遞回函式」來展開引數包
  • 另外一種是通過「逗號運算式」來展開引數包

下面來看看如何用這兩種方法來展開引數包,

遞回函式方式展開引數包

通過遞回函式展開引數包,需要提供一個「引數包展開的函式」和一個「遞回終止函式」,遞回終止函式正是用來終止遞回的,來看看下面的例子,

#include <iostream>
using namespace std;

// @note 遞回終止函式
void print() {
    cout << "empty" << endl;
}

// @note 展開函式
template<typename T, typename... Args>
void print(T head, Args... rest) {
    cout << "parameter " << head << endl;
    print(rest...);
}

int main() {
    print(1, 2, 3, 4);
    return 0;
}

上例會輸出每一個引數,直到為空時輸出empty,展開引數包的函式有兩個,一個是遞回函式,另外一個是遞回終止函式,引數包Args...在展開的程序中遞回呼叫自己,每呼叫一次引數包中的引數就會少一個,直到所有的引數都展開為止,當沒有引數時,則呼叫非模板函式print終止遞回程序,

遞回呼叫的程序是這樣的:

print(1,2,3,4);
print(2,3,4);
print(3,4);
print(4);
print();

上面的遞回終止函式還可以寫成這樣:

template<typename T>
void print(T t) {
    cout << t << endl;
}

修改遞回終止函式后,上例中的呼叫程序是這樣的:

print(1, 2, 3, 4);
print(2, 3, 4);
print(3, 4);
print(4);

當引數包展開到最后一個引數時遞回終止,

再看一個通過可變模板引數求和的例子:

#include <iostream>
using namespace std;

template<typename T>
T sum(T t) {
    return t;
}

template<typename T, typename... Types>
T sum(T first, Types... rest) {
    return first + sum<T>(rest...);
}

int main() {
    cout << sum(1, 2, 3, 4) << endl;    // 10
    return 0;
}

sum在展開引數包的程序中將各個引數相加求和,引數的展開方式和前面的列印引數包的方式是一樣的,

逗號運算式展開引數包

遞回函式展開引數包是一種標準做法,也比較好理解,但也有一個缺點,就是「必須」要一個多載的遞回終止函式,即「必須」要有一個同名的終止函式來終止遞回,這樣可能會感覺稍有不便,有沒有一種更簡單的方式呢?其實還有一種方法可以不通過遞回方式來展開引數包,這種方式需要借助「逗號運算式」和「初始化串列」,比如前面print的例子可以改成這樣:

#include <iostream>
using namespace std;

template<typename T>
void printArg(T t) {
    cout << t << endl;
}

template<typename... Args>
void expand(Args... args) {
    int arr[] = {(printArg(args), 0)...};
}

int main() {
    expand(1, 2, 3, 4);
    return 0;
}

這個例子將分別列印出1、2、3、4四個數字,這種展開引數包的方式,「不需要」通過遞回終止函式,是直接在expand函式體中展開的,printArg不是一個遞回終止函式,只是一個處理引數包中每一個引數的函式,這種就地展開引數包的方式實作的關鍵是逗號運算式,我們知道逗號運算式會按順序執行逗號前面的運算式,比如:

d = (a = b, c);

這個運算式會按順序執行:b會先賦值給a,接著括號中的逗號運算式回傳c的值,因此d將等于c,

expand 函式中的逗號運算式:

(printArg(args), 0)

也是按照這個執行順序,先執行printArg(args),再得到逗號運算式的結果0,同時還用到了C++11的另外一個特性:「初始化串列」,通過初始化串列來初始化一個「變長陣列」:

{(printArg(args), 0)...}

將會展開成

{((printArg(arg1), 0), (printArg(arg2), 0), (printArg(arg3), 0),  etc...)}

最侄訓創建一個元素值都為0的陣列int arr[sizeof...(Args)],由于是逗號運算式,在創建陣列的程序中會先執行逗號運算式前面的部分printArg(args)列印出引數,也就是說在構造int陣列的程序中就將引數包展開了,這個陣列的目的純粹是為了在陣列構造的程序展開引數包,我們可以把上面的例子再進一步改進一下,將函式作為引數,就可以支持lambda運算式了,從而可以少寫一個遞回終止函式了,具體代碼如下:

#include <iostream>
#include <functional>
using namespace std;

template<typename T, typename ...Args>
void expand(const T &func, Args&&... args) {
    // 這里用到了完美轉發
    int arr[] = { (func(std::forward<Args>(args)), 0)... };
    // initializer_list<int>{ (func(std::forward<Args>(args)), 0)... };
}

int main() {
    expand([](int i)->void{cout << i << endl;}, 1, 2, 3);
    return 0;
}

其實上面這里的T型別就是function<void int>型別,引數可以寫成const function<void(int)> &func,也可寫成function<void(int)> func,這都無妨,我們只需要知道這是參考、或者是使用了function機制即可,

上面的例子將列印出每個引數,這里如果再使用「C++14的新特性」泛型lambda運算式的話,可以寫更泛化的lambda運算式了(把引數改為auto):

expand([](auto i)->void{cout << i << endl;}, 1, 2.2, "hello");

可變引數類模板

可變引數模板類是一個帶可變引數的模板類,比如C++11中的元組std::tuple就是一個可變模板類,它的定義如下:

template<typename... Types>
class tuple;

這個可變引數模板類可以攜帶任意型別任意個數的模板引數:

std::tuple<int> tp1 = std::make_tuple(1);
std::tuple<int, double> tp2 = std::make_tuple(1, 2.5);
std::tuple<int, double, string> tp3 = std::make_tuple(1, 2.5, "");
//std::tuple<int, double, string> tp3 = {1, 2.5, ""};
//std::tuple<int, double, string> tp3(1, 2.5, "");

可變引數模板類的引數個數可以為0個,所以下面的定義也是也是合法的:

std::tuple<> tp;

可變引數模板類的引數包展開的方式和可變引數模板函式的展開方式不同,可變引數模板類的引數包展開需要通過「模板特化」和「繼承方式」去展開,展開方式比可變引數模板函式要復雜,下面我們來看一下展開可變引數模板類中的引數包的方法,

模板偏特化和遞回方式來展開引數包

可變引數模板類的展開一般需要定義兩到三個類,包括「類宣告」和「偏特化」的模板類,如下方式定義了一個基本的可變引數模板類:

#include <iostream>
using namespace std;

// 前向宣告
template<typename... Args>
struct Sum;

// 基本定義
template<typename First, typename... Rest>
struct Sum<First, Rest...> {
    enum {
        value = https://www.cnblogs.com/S1mpleBug/archive/2022/10/27/Sum::value + Sum::value
    };
};

// 遞回終止
template
struct Sum {
    enum {
        value = sizeof(Last)
    };
};

int main() {
    cout << Sum::value << endl; // 14
    // Sum s;
    // cout << s.value << endl;                     // 4+8+2=14
    return 0;
}

這個Sum類的作用是在編譯期計算出引數包中引數型別的size之和,通過Sum<int, double, short>::value就可以獲取這3個型別的size之和為14,這是一個簡單的通過可變引數模板類計算的例子,可以看到一個基本的可變引數模板應用類由三部分組成:

「第一部分」是:

template<typename... Args>
struct Sum;

它是前向宣告,宣告這個Sum類是一個可變引數模板類;

「第二部分」是類的定義:

template<typename First, typename... Rest>
struct Sum<First, Rest...> {
    enum {
        value = https://www.cnblogs.com/S1mpleBug/archive/2022/10/27/Sum::value + Sum::value
    };
};

它定義了一個部分展開的可變引數模板類,告訴編譯器如何遞回展開引數包,

「第三部分」是特化的遞回終止類:

template<typename Last>
struct Sum<Last> {
    enum {
        value = https://www.cnblogs.com/S1mpleBug/archive/2022/10/27/sizeof(Last)
    };
};

通過這個特化的類來終止遞回,

template<typename First, typename... Args>
struct Sum<First, Rest...> {
    ...
};

這個前向宣告要求Sum的模板引數至少有一個,因為可變引數模板中的模板引數可以有0個,有時候0個模板引數沒有意義,就可以通過上面的宣告方式來限定模板引數不能為0個,上面的這種三段式的定義也可以改為兩段式的,可以將前向宣告去掉,這樣定義:

#include <iostream>
using namespace std;

// 基本模板類定義
template<typename First, typename... Rest>
struct Sum {
    enum {
        value = https://www.cnblogs.com/S1mpleBug/archive/2022/10/27/Sum::value + Sum::value
    };
};

// 特化的終止函式
template
struct Sum {
    enum {
        value = sizeof(Last)
    };
};

int main() {
    cout << Sum::value << endl; // 14
    // Sum s;
    // cout << s.value << endl;                     // 4+8+2=14
    return 0;
}

上面的方式「只要」一個基本的「模板類定義」和一個「特化的終止函式」就行了,而且限定了模板引數至少有一個,

遞回終止模板類可以有「多種寫法」,比如上例的遞回終止模板類還可以這樣寫:

#include <iostream>
using namespace std;

template<typename... Args>
struct Sum;

// 基本模板類定義
template<typename First, typename... Rest>
struct Sum<First, Rest...> {
    enum {
        value = https://www.cnblogs.com/S1mpleBug/archive/2022/10/27/sizeof(First) + Sum::value
    };
};

// 遞回終止模板類
template
struct Sum {
    enum {
        value = sizeof(First) + sizeof(Last)
    };
};

int main() {
    // cout << Sum<>::value << endl;                   // error
    // cout << Sum::value << endl;                // error
    cout << Sum::value << endl;        // 12
    cout << Sum::value << endl; // 14
    return 0;
}

在展開到最后兩個引數時終止,

還可以在展開到0個引數時終止:

#include <iostream>
using namespace std;

template<typename... Args>
struct Sum;

// 基本模板類定義
template<typename First, typename... Rest>
struct Sum<First, Rest...> {
    enum {
        value = https://www.cnblogs.com/S1mpleBug/archive/2022/10/27/sizeof(First) + Sum::value
    };
};

// 遞回終止模板類
template<>
struct Sum<> {
    enum {
        value = 0
    };
};

int main() {
    cout << Sum<>::value << endl;                   // 0
    cout << Sum::value << endl;                // 4
    cout << Sum::value << endl;        // 12
    cout << Sum::value << endl; // 14
    return 0;
}

??注:我一開始對遞回終止條件那里的「展開到2個引數」和「展開到0個引數」的代碼改來改去就是跑不通,發現是「基本模板類定義」那里出了問題,將一開始的Sum<First>::value改為sizeof(First)即可,對「展開到2個引數」的代碼而言,若不進行「基本模板類定義」這里的修改,那只能保證傳入的引數個數是>=2的偶數個,而當引數個數為奇數個時,就會報錯,這里可以仔細想想為什么,那么對「展開到0個引數」的情況,同理,就不再贅述,

那么說到這里,想必大家都有個疑惑,可以看到不論是模板函式還是模板類的遞回程式,都用到了enum做遞回的數值計算,在模板元編程中,enum是一項重要手段,其主要解決的問題是:

  1. enum的「值由編譯器在編譯期間計算」
  2. 利用遞回演算法和模板特化,可以讓編譯器在計算enum值時「遞回產生一系列class

下面直接羅列一個求N的階乘的代碼,可以體會一下如何在模板中借助遞回演算法和模板特化來使用enum實作N的階乘:

#include <iostream>
using namespace std;

template<int N>
class F {
public:
    enum {
        res = N * F<N-1>::res
    };
};

//遞回終止條件
template<>
class F<1> {
public:
    enum {
        res = 1
    };
};

int main() {
    cout << F<4>::res << endl; // 24 = 1*2*3*4
    return 0;
}

C++模板元編程中有個重要的類叫做std::integral_constant,用來定義型別的常量,其實可以使用std::integral_constant來消除列舉定義,利用std::integral_constant也可獲得「編譯期常量」的特性,原始碼之下了無秘密,std::intergral_constant的原始碼如下:

  template<typename _Tp, _Tp __v>
    struct integral_constant
    {
      static constexpr _Tp                  value = https://www.cnblogs.com/S1mpleBug/archive/2022/10/27/__v;
      typedef _Tp                           value_type;
      typedef integral_constant<_Tp, __v>   type;
      constexpr operator value_type() const noexcept { return value; }
#if __cplusplus > 201103L

#define __cpp_lib_integral_constant_callable 201304

      constexpr value_type operator()() const noexcept { return value; } //C++14起
#endif
    };

本文對該模板類不做說明,我直接貼出相關文章:[ ?? C++11中type_traits中的基石 - integral_constant ],等后面有機會我再單獨搞一下這里,

因此,可以將前面的Sum例子改為這樣:

#include <iostream>
using namespace std;

//前向宣告
template <typename... Args>
struct Sum;

//基本定義
template <typename First, typename... Rest>
struct Sum<First, Rest...> : std::integral_constant<int, Sum<First>::value + Sum<Rest...>::value>
{};

//遞回終止
template<typename Last>
struct Sum<Last> : std::integral_constant<int, sizeof(Last)>
{};

int main() {
    cout << Sum<int, double, short>::value << endl; // 14 = 4+8+2
    return 0;
}

繼承方式展開引數包

還可以通過繼承方式來展開引數包,比如下面的例子就是通過繼承的方式去展開引數包:

//整型序列的定義
template<int...>
struct IndexSeq {};

//繼承方式,開始展開引數包
template<int N, int... Indexes>
struct MakeIndexes : MakeIndexes<N-1, N-1, Indexes...> {};

// 模板特化,終止展開引數包的條件
template<int... Indexes>
struct MakeIndexes<0, Indexes...> {
    using type = IndexSeq<Indexes...>;
};

int main() {
    using T = MakeIndexes<3>::type;
    cout << typeid(T).name() << endl; // IndexSeq<0, 1, 2>
    return 0;
}

其中MakeIndexes的作用是為了生成一個可變引數模板類的整數序列,「最終輸出」的型別是:struct IndexSeq<0, 1, 2>

MakeIndexes繼承于自身的一個「特化的」模板類,這個特化的模板類同時也在展開引數包,這個展開程序是通過繼承發起的,直到遇到特化的終止條件展開程序才結束,MakeIndexes<3>::type的展開程序是這樣的:

MakeIndexes<3> : MakeIndexes<2, 2> {}
MakeIndexes<2, 2> : MakeIndexes<1, 1, 2> {}
MakeIndexes<1, 1, 2> : MakeIndexes<0, 0, 1, 2> {
    using type = IndexSeq<0, 1, 2>;
}

通過不斷的繼承遞回呼叫,最終得到整型序列IndexSeq<0, 1, 2>

如果不希望通過繼承方式去生成整型序列,則可以通過下面的方式生成:

#include <iostream>
using namespace std;

//整型序列的定義
template <int...>
struct IndexSeq {};

// 非繼承方式 開始展開引數包
template<int N, int... Indexes>
struct MakeIndexes {
    using type = typename MakeIndexes<N-1, N-1, Indexes...>::type;
};

// 模板特化 終止展開引數包的條件
template<int... Indexes>
struct MakeIndexes<0, Indexes...> {
    using type = IndexSeq<Indexes...>;
};

int main() {
    using T = MakeIndexes<3>::type;
    cout << typeid(T).name() << endl;
    return 0;
}

我們看到了如何利用「遞回」以及「偏特化」等方法來展開「可變模板引數」,那么實際當中我們會怎么去使用它呢?我們可以用可變模板引數來消除一些重復的代碼以及實作一些高級功能,下面我們來看看可變模板引數的一些應用,

可變引數模板消除重復代碼

C++11 之前如果要寫一個泛化的工廠函式,這個工廠函式能接受任意型別的入參,并且引數個數要能滿足大部分的應用需求的話,我們不得不定義很多重復的模板定義,比如下面的代碼:

template<typename T>
T *Instance() {
    return new T();
}

template<typename T, typename T0>
T *Instance(T0 arg0) {
    return new T(arg0);
}

template<typename T, typename T0, typename T1>
T *Instance(T0 arg0, T1 arg1) {
    return new T(arg0, arg1);
}

template<typename T, typename T0, typename T1, typename T2>
T *Instance(T0 arg0, T1 arg1, T2 arg2) {
    return new T(arg0, arg1, arg2);
}

template<typename T, typename T0, typename T1, typename T2, typename T3>
T *Instance(T0 arg0, T1 arg1, T2 arg2, T3 arg3) {
    return new T(arg0, arg1, arg2, arg3);
}

template<typename T, typename T0, typename T1, typename T2, typename T3, typename T4>
T *Instance(T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) {
    return new T(arg0, arg1, arg2, arg3, arg4);
}

struct A {
    A(int) {}
};
struct B {
    B(int, double) {}
};

A *pa = Instance<A>(1);
B *pb = Instance<B>(1, 2);

可以看到這個泛型工廠函式存在大量的重復的模板定義,并且限定了模板引數,用可變模板引數可以消除重復,同時去掉引數個數的限制,代碼很簡潔,通過可變引數模板優化后的工廠函式如下:

template<typename T, typename... Args>
T *Instance(Args... args) {
    return new T(args...);
}

在上面的實作代碼T *Instance(Args... args)中,Args是值拷貝的,存在性能損耗,可以通過完美轉發來消除損耗,代碼如下:

template<typename T, typename... Args>
T *Instance(Args&&... args) {
    return new T(std::forward<Args>(args)...);
}

struct A {
    A(int) {}
};
struct B {
    B(int, double) {}
};

A *pa = Instance<A>(1);
B *pb = Instance<B>(1, 2);

可變引數模板實作泛化的delegate

C++ 中沒有類似C#的委托,我們可以借助可變模板引數來實作一個,C#中的委托的基本用法是這樣的:

delegate int AggregateDelegate(int x, int y); // 宣告委托型別

int Add(int x, int y) { return x + y; }
int Sub(int x, int y) { return x - y; }

AggregateDelegate add = Add;
add(1, 2); // 呼叫委托物件求和
AggregateDelegate sub = Sub;
sub(2, 1); // 呼叫委托物件相減

C#中的委托的使用需要先定義一個委托型別,這個委托型別不能泛化,即委托型別一旦宣告之后就不能再用來接受其它型別的函式了,比如這樣用:

int Fun(int x, int y, int z) { return x + y + z; }
int Fun1(string s, string r) { return s.Length + r.Length; }
AggregateDelegate fun = Fun;   //編譯報錯,只能賦值相同型別的函式
AggregateDelegate fun1 = Fun1; //編譯報錯,引數型別不匹配

這里不能泛化的原因是宣告委托型別的時候就限定了「引數型別」和「個數」,在C++11里不存在這個問題了,因為有了可變模板引數,它就代表了任意型別和個數的引數了,下面讓我們來看一下如何實作一個功能更加泛化的C++版本的委托(這里為了簡單起見只處理成員函式的情況,并且忽略const、volatile和const volatile成員函式的處理),

#include <iostream>
using namespace std;

template<typename T, typename R, typename... Args>
class MyDelegate {
public:
    MyDelegate(T *t, R (T::*f)(Args...)) : m_t(t), m_f(f) {}

    R operator()(Args&&... args) {
        return (m_t->*m_f)(std::forward<Args>(args)...);
    }

private:
    T *m_t;
    R (T::*m_f)(Args...);   // 函式指標
};

template<typename T, typename R, typename... Args>
MyDelegate<T, R, Args...> CreateDelegate(T *t, R (T::*f)(Args...)) {
    return MyDelegate<T, R, Args...>(t, f);
}

struct A {
    void Fun(int i ) { cout << i << endl; }
    void Fun1(int i, double j) { cout << i+j << endl; }
};

int main() {
    A a;
    auto d = CreateDelegate(&a, &A::Fun);   // 創建委托
    d(1);                                   // 呼叫委托 將輸出1
    auto d1 = CreateDelegate(&a, &A::Fun1); // 創建委托
    d1(1, 2.5);                             // 呼叫委托 將輸出3.5
    return 0;
}

MyDelegate實作的「關鍵」是內部定義了一個能接受任意型別和引數個數的「萬能函式」:R  (T::*m_f)(Args...),正是由于「可變模板引數」的特性,所以我們才能夠讓這個m_f接受任意引數,

總結

使用可變模板引數的這些技巧相信讀者看了會有耳目一新之感,使用可變模板引數的關鍵是如何展開引數包,展開引數包的程序是很精妙的,體現了泛化之美、遞回之美,正是因為它具有神奇的「魔力」,所以我們可以更泛化的去處理問題,比如用它來消除重復的模板定義,用它來定義一個能接受任意引數的「萬能函式」等,其實,可變模板引數的作用遠不止文中列舉的那些作用,它還可以和其它C++11特性結合起來,比如type_traitsstd::tuple等特性,發揮更加強大的威力,

我想后面我得系統學習一下C++模板元編程了,這破爛玩意兒太裝逼了,我好喜歡!

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/521820.html

標籤:其他

上一篇:JAVA常見基礎知識點

下一篇:設計模式---模板方法模式

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more