Boost原始碼剖析之:型別分類器——type_traits
1. 分派
下面有一個模板函式,假設一個動物收容組織提供了它,他們接受所有無家可歸的可憐的小動物,於是他們向外界提供了一個函式接受註冊。函式看起來像這樣:
AcceptAnimals(T animal) { ... };但是,如果他們想將貓和狗分開處理(畢竟飼養一隻貓和飼養一隻狗並不相同。他們可能會為狗買一根鏈子,而溫順的貓則可能不需要)。一個可行的方法是分別提供兩個函式:AcceptDog和AcceptCat,然而這種解決辦法並不優雅(想想看,註冊者可能既有一隻貓又有一隻狗,這樣他不得不呼叫不同的函式來註冊,而且,如果種類還在增多呢,那樣會導致向外提供的介面的增多,註冊者因此而不得不記住那些煩瑣的名字,而這顯然沒有隻需記住AccpetAnimal這一個名字簡單)。如果想保持這個模板函式,並將它作為向外界提供的唯一介面,則我們需要某種方式來獲取型別T的特徵(trait),並按照不同的特徵來採用不同的策略。這裡我們有第二個解決辦法:
約定所有的動物類(如class Cat,class Dog)都必須在內部typedef一個表明自己身份的型別,作為標識的型別如下:
cat_tag{}; dog_tag{};於是,所有狗類都必須像這樣:
Dog { : dog_tag type; ... }然後,動物收容組織可以在內部提供對貓狗分開處理的函式,像這樣:
Accept(T dog,dog_tag) {...} Accpet(T cat,cat_tag) {...}於是先前的Accept函式可以改寫如下:
Accept(T animal) { 1 Accept(animal, T::type()); }
AnimalTraits { T::type type; }; %ENDOCDE% 於是1處的程式碼可以寫成Accept(animal, AnimalTraits::type()); 2. 效率 通常為了提高效率,為某種情況採取特殊的措施是必要的,例如STL裡面的copy,原型像這樣: %CODE{""}% IterOut copy(IterIn first,IterIn last,IterOut dest){ copy_opt(first,last,dest,ptr_category(first,dest)); }
IterOut copy(IterIn first,IterIn last,IterOut dest,scalar_ptr){ ...} copy( first,IterIn last,IterOut dest,non_scalar_ptr){ ...}其實通常為了提高效率,還是需要分派。
3. 使某些程式碼能通過編譯
這或許令人費解,原來不能通過編譯的程式碼,經過traits的作用就能編譯了嗎?是的,考慮std::pair的程式碼(為使程式碼簡潔,忽略大部分):
pair { T1 first; T2 second; 2 pair( T1 & nfirst, T2 & nsecond) :first(nfirst), second(nsecond) { } ... };這裡可以使用一個traits(boost庫裡面的名字為add_reference)來避免這樣的錯誤。這個traits內含一個typedef,如果add_reference的T為引用,則typedef T type;如果不是引用,則typedef T& type;這樣,2處的程式碼可寫成:
pair(add_reference::type nfirst,add_reference::type nsecond)。這對所有的型別都能通過編譯。
Boost庫中的Traits
Boost中的Traits十分完善,可分為幾大類:1. Primary Type Categorisation(初級型別分類) 2. Secondary Type Categorisation(次級型別分類) 3. Type Properties(型別屬性) 4. Relationships Between Types(型別間關係) 5. Transformations Between Types(型別間轉換) 6. Synthesizing Types(型別合成) 7. Function Traits(函式traits)
由於其中一些traits只是簡單的模板偏特化,故不作介紹,本文僅介紹一些技術性較強的traits。由於traits的定義往往重複程式碼較多,所以必要時本文僅剖析其底層機制。所有原始碼均摘自相應標頭檔案中,為使原始碼簡潔,所有的巨集均已展開。由於traits技巧與編譯平臺息息相關,某些平臺可能不支援模板偏特化。這裡我們假設編譯器是符合C++標準的。在我的VC7.0上,以下程式碼均通過編譯並正常工作。
1. 初級型別分類
is_array<>(boost/type_traits/is_array.hpp)定義
is_array{ value=;}; is_array{ value=;};註解
C++標準允許整型作為模板引數,上面的N就是這樣。這也說明出現在模板偏特化版本中的模板引數(在本例中為typename T,size_t N兩個)個數不一定要跟預設的(本例中為typename T一個)相同,但是出現在類名稱後面的引數個數卻要跟預設的個數相同(is_array,T[N]為一個引數,與預設的相同)。
is_array::value is_array::value is_class<>(.../is_class.hpp)定義
is_class_impl { ::boost::type_traits::yes_type is_class_tester((U::*)()); ::boost::type_traits::no_type is_class_tester(...); value = ::boost::type_traits::ice_and< 3 (is_class_tester(0)) == (::boost::type_traits::yes_type), ::boost::type_traits::ice_not< ::boost::is_union::value >::value >::value }; is_class { value=is_class_impl::value; };註解
::boost::type_traits::yes_type為一個typedef: typedef char yes_type; 所以sizeof(yes_type)為1.
::boost::type_traits::no_type為一個struct: struct no_type{char padding[8]; };sizeof(no_type)為8。他們
一般被用作過載函式的返回值型別,這樣通過檢查返回值型別的大小就知道到底呼叫了哪個函式,他
們被定義在boost/type_traits/detail/yes_no_type.hpp中。
is_class_impl中有兩個static函式,第一個函式僅當模板引數U是類時才能夠被具現化,因為它的引數型別是void(U::*)(void),即指向成員函式的指標。第二個函式具有不定量任意引數列表,C++標準說只有當其它所有的過載版本都不能匹配時,具有任意引數列表的過載版本才會被匹配。所以,如果T為類,則void (T::*)(void)這種型別就存在,所以對is_class_tester(0)的過載決議將是呼叫第一個函式,因為將0賦給任意型別的指標都是合法的。而如果T不是類,則就不存在void(T::*)(void)這種指標型別,所以第一個函式就不能具現化,這樣,對is_class_tester(0)的過載決議結果只能呼叫第二個函式。
現在注意3處的表示式sizeof(is_class_tester(0))==sizeof(boost::type_traits::yes_type)。按照以上的推論,如果T為類,is_class_tester(0)實際呼叫第一個過載版本,返回yes_type,則表示式評估為true。如果T不是類,則is_class_tester(0)呼叫第二個過載版本,返回no_type,則表示式評估為false。這正是我們想要的。一個值得注意的地方是:在sizeof的世界裡,沒有表示式被真正求值,編譯器只推匯出表示式的結果的型別,然後給出該型別的大小。對於sizeof(is_class_tester(0))編譯器實際並不呼叫函式的程式碼來求值,而只關心函式的返回值型別。所以宣告該函式就夠了。另一個值得注意之處是is_class_tester的兩個過載版本都用了模板函式的形式。第一個版本用模板形式的原因是如果不那樣做,而是這樣static yes_type is_class_tester(void(T::*)(void));則當T不是類時,該traits將不能通過編譯,原因很簡單,當T不是類時void (T::*)(void)根本不存在。然而,使用模板時,當T不是類時該過載版本會因不能具現化而根本不編譯,C++標準允許不被使用的模板不編譯(具現化)。這樣編譯器就只能使用第二個版本,這正合我們的意思。而第二個版本為模板是因為第一個版本是模板,因為在3處對is_class_tester的呼叫是這樣的:is_class_tester(0),如果第二版本不是模板則這樣程式碼只能解析為對is_class_tester模板函式(即第一個版本)的呼叫,過載解析也就不復存在。
“等等!”你意識到了一些問題:“模板函式的呼叫可以不用顯式指定模板引數!”好吧,也就是說你試圖這樣寫:
template static ::boost::type_traits::yes_type is_class_tester(void(U::*)(void)); //模板
static ::boost::type_traits::no_type is_class_tester(...); //非模板
然後在3標記的那一行這樣呼叫:is_class_tester(0)(原來是is_class_tester(0)),是的,我得承認,這的確構成了函式過載的條件,也的確令人欣喜的通過了編譯,然而結果肯定不是你想要的!你會發現對所有型別is_class::value現在都是0。也就是說,編譯器總是呼叫is_class_tester(..);這是因為,當呼叫的函式的所有過載版本中有一個或多個為模板時,編譯器首先要嘗試進行模板函式具現化而非過載決議,而在嘗試具現化的過程中,編譯器會進行模板引數推導,0的型別被編譯器推導為int(0雖然可以賦給指標,但0的型別不可能被推導為指標型別,因為指標型別可能有無數種,而事實上C++是強型別語言,物件只能屬於某一種型別),而第一個函式的引數型別void (U::*)(void)根本無法與int匹配(因為如果匹配了,那麼模板引數U被推導為什麼呢?)。所以第一個版本具現化失敗後編譯器只能採用非模板的第二個版本。結果如你所見,是令人懊惱的。然而如果你寫的是is_class_tester(0)你其實是顯式具現化了is_class_tester每一個模板函式(除了那些不能以T為模板引數具現化的),而它們都被列入接受過載決議的侯選單,然後編譯器要做的就只剩下過載決議了。(關於編譯器在含有模板函式的過載版本時是如何進行過載決議的,可參見C++ Primer的Function Templates一節,裡面有極其詳細的介紹)。
以上所將的利用函式過載來達到某些目的的技術在type_traits甚至整個Boost庫裡多處用到。
初級型別分類還有:is_void<>,is_integral<>,is_float<>,is_pointer,is_reference<>,is_union<>,is_enum<>,
is_function<>。請參見Boost提供的文件。
2.次級型別分類
is_member_function_pointer<>(.../is_member_function_pointer.hpp)
定義(.../detail/is_mem_fun_pointer_impl.hpp)
template
struct is_mem_fun_pointer_impl //預設版本
{
static const bool value = false;
};
template //偏特化版本,匹配無引數的成員函式
struct is_mem_fun_pointer_impl { static const bool value = true; };
template //匹配一個引數的成員函式
struct is_mem_fun_pointer_impl { static const bool value = true; };
.etc. ... //其它版本只是匹配不同引數個數的成員函式的偏特化而已,參見原始檔。
template
struct is_mem_function_pointer
{ static const bool value=is_mem_fun_pointer_impl::value; };
註解
假設你有一個類X,你這樣判斷:is_mem_function_pointer::value;則編譯器先將
is_mem_function_pointer的模板引數class T推導為int (X::*)(int),然後將其傳給is_mem_fun_pointer_impl
,隨後編譯器尋找後者的偏特化版本中最佳匹配項為is_mem_fun_pointer_impl其中R=int,
T=X,T0=int。該偏特化版本的value=true;
次級型別分類還有:is_arithmetic<>,is_fundamental<>,is_object<>,is_scalar<>,is_compound<>。請參見
Boost提供的文件。
3.型別屬性
is_empty<>(.../is_empty.hpp)
定義
template
struct empty_helper_t1 : public T //如果T是空類,那麼派生類的大小就是派生部分的大小
{ //即sizeof(int)*256
empty_helper_t1();
int i[256];
}; //
struct empty_helper_t2 { int i[256]; }; //大小為sizeof(int)*256
//通過比較以上兩個類的大小可以判斷T是否為空類,如果它們大小相等則T為空類。反之則不為空
//這裡一個值得注意的地方是:若定義一個空類E,則sizeof(E)為1(這一個位元組是用於在記憶體中唯一
//標識該類的不同物件。如果sizeof(E)為0,則意味著不同的物件在記憶體中的位置沒有區別,這顯然有
//違直觀)。然而如果有另一個非空類繼承自E,那麼這一個位元組的記憶體就不需要。也就是說派生類的
//大小等於派生部分的大小而非多出一個位元組。
template
struct empty_helper //這個輔助類的作用是:如果T不是類則使用該預設版本
{ //如果T是類則使用下面的偏特化版本。而判斷T是否為類的工作則由上面講過
static const bool value = false; //的is_class<>traits來做。
};
template
struct empty_helper
{
5 static const bool value = (sizeof(empty_helper_t1) == sizeof(empty_helper_t2));
};
template
struct is_empty_impl
{
typedef typename remove_cv::type cvt; //remove_cv將T的const volatile屬性去掉,這是因為在
//public 後面不能跟const volatile T。
static const bool value = (::boost::type_traits::ice_or< //ice_or<>相當於邏輯或
4 ::boost::detail::empty_helper::value>::value //cvt作為基類,不能有cv修飾符
, BOOST_IS_EMPTY(cvt) //該巨集被簡單地定義為false,但對結果無影響
>::value);
};
註解
在標記4處,如果is_class::value為true(即T為類)則empty_helper::value>::value實際決議為empty_helper,這將採用偏特化版本,轉到5,則結論出現。否則T不是類,則採用預設版本,結果value為false。
is_polymorphic<>(.../is_polymorphic.hpp)
is_plymorphic<>基於一個基本事實:一個多型的類裡面會有一個虛擬函式表指標(一般稱為vptr),它指向一個虛擬函式表(一般稱為vtbl)。後者儲存著一系列指向虛擬函式的函式指標以及執行時型別識別資訊。一個虛擬函式表指標通常佔用4個位元組(32定址環境下的所有指標都佔用4個位元組)。反之,如果該類不是多型,則沒有這個指標的開銷。基於這個原理,我們可以斷定:如果類X不是多型類(沒有vtbl及vptr),則如果從它派生一個類Y,Y中僅含有一個虛擬函式,這會導致sizeof(Y)>sizeof(X)(這是因為虛擬函式的首次出現導致編譯器必須在Y中加入vptr的緣故)。反之,如果X原本就是多型類,則sizeof(Y)==sizeof(X)(因為這種情況下,Y中其實已經有了從X繼承而來的vtbl及vptr,編譯器所要做的只是將新增的虛擬函式納入到vtbl中去)。
定義
template
struct is_polymorphic_imp1 //當T為類時使用這個版本
{
typedef typename remove_cv::type ncvT;
struct d1 : public ncvT //ncvT是將T的const volatile修飾符去掉後的型別,因為public後不能跟這樣的
{ //修飾符
d1(); //該類裡沒有虛擬函式
~d1()throw();
char padding[256];
}; //d1中沒有虛擬函式
struct d2 : public ncvT //在d2中加入一個虛擬函式
{
d2();
virtual ~d2()throw(); //加入一個虛擬函式,如果ncvT為非多型則會導致vptr的加入從而多佔用4位元組
char padding[256];
};
static const bool value = (sizeof(d2) == sizeof(d1)); //如果T為多型類則valu為true
};
template
struct is_polymorphic_imp2 //當T並非類時採用這個版本
{static const bool value = false;}; //既然T不是類,那麼就不存在多型,所以總是false
template
struct is_polymorphic_selector //這個selector根據is_class的真假來選擇判斷的方式
{
template
struct rebind //如果is_class為false則由is_polymorphic_imp2<>來判斷
{ //這將導致結果總是false
typedef is_polymorphic_imp2 type; //使用_imp2
};
};
template <>
7 struct is_polymorphic_selector //當is_class為true時使用該特化版本
{
template
struct rebind //如果is_class為true,則由is_polymorphic_imp1<>來作判斷
{
typedef is_polymorphic_imp1 type; //使用_imp1
};
};
template
struct is_polymorphic_imp // is_polymorphic<>完全由它實現
{
6 typedef is_polymorphic_selector< ::boost::is_class::value> selector; //選擇selector
8 typedef typename selector::template rebind binder; //
9 typedef typename binder::type imp_type;
static const bool value = imp_type::value;
};
註解
6處,如果T為類,則is_class::value為true,則那一行實際上就是:
typedef is_polymorphic_selector selector;
這將決議為is_polymorphic_selector<>的第二個過載版本7,其中的template rebind將判斷的任務交給is_polymorphic_imp1<>,所以8行的binder其實就是is_polymorphic_selector::rebind。而9行的imp_type其實就是is_polymorphic_imp1,結果正如預期。如果T不是類,按照類似的推導過程,最終會推導至is_polymorphic_imp2::value,這正是false。
“嗨!這太煩瑣了!”你抱怨道:“可以簡化!”。我知道,你可能會想到使用boost::ct_if(ct_if是?:三元操作符的編譯期版本,像這樣使用typedef ct_if::value result,則當CompileTimeBool為true時result為TypeIfTrue,否則result為TypeIfFalse。ct_if<>的實現很簡單,模板偏特化而已)。於是你這樣寫:
typedef typename boost::ct_if<
boost::is_class::value,
is_polymorphic_imp1, //is_class::value為true時的型別
is_polymorphic_imp2, // is_class::value為false時的型別
>::type imp_type;
static const bool value = imp_type::value;
這在我的VC7.0環境下的確編譯通過並正常工作,但是有一個小問題:假如T不是class,比如,T是一個int,則編譯器的型別推導將is_polymorphic_imp1賦給ct_if<>的第二個模板引數,在這個過程中編譯器會不會具現化is_polymorphic_imp1(或者,換句話說,編譯器會不會去檢視它的定義),如果具現化了,那麼其內部的struct d1 : public ncvT會不會也跟著具現化為struct d1:public int,如果是這樣,那麼將會有編譯期錯誤,因為C++標準不允許有public int這樣的東西出現。事實上我的編譯器沒有報錯,即是說它並沒有去檢視is_polymorphic_imp1的定義。
但我不知道C++標準關於這點怎麼說。然而Boost庫所用的方法是標準所保證的。
型別屬性traits還有:alignment_of<>, is_const<>, is_volatile<>, is_pod<>, has_trivial_constructor<>等。
4. 型別間關係
is_base_and_derived<>( boost/type_traits/ is_base_and_derived.hpp)
template
struct bd_helper
{
template
static type_traits::yes_type check(D const volatile *, T); //兩個過載函式
static type_traits::no_type check(B const volatile *, int);
};
template
struct is_base_and_derived_impl2
{
struct Host
{
operator B const volatile *() const; //該轉換操作符當物件為const物件時才起作用
operator D const volatile *();
};
- static const bool value
sizeof(bd_helper::check(Host(), 0)) =
sizeof(type_traits::yes_type);
};
以上就是is_base_and_derived<>的底層機制。下面我就為你講解它所仰賴的機制,假設有這樣的類繼承體系:
struct B {};
struct B1 : B {};
struct B2 : B {};
struct D : private B1, private B2 {}; //將D*轉換為B1*會導致訪問違規,私有基類部分無法訪問
首先是一些術語: //但是後面解釋了這為什麼不會發生
SC - Standard Conversion
UDC - User-Defined Conversion
一個user-defined轉換序列由一個SC後跟一個UDC後再跟一個SC組成。其中頭尾兩個SC都可以為到自身的轉換(如:D->D), 10處將一個預設構造的Host()交給bd_helper::check函式。
對於static no_type check(B const volatile *, int),我們有如下可行的隱式轉換序列:
C -> C const (SC - Qualification Adjustment) -> B const volatile* (UDC) //C表示Host()
C -> D const volatile* (UDC) -> B1 const volatile* / B2 const volatile* ->
B const volatile* (SC - Conversion)
對於static yes_type check(D const volatile *, T),我們有如下轉換序列:
C -> D const volatile* (UDC)
C++標準說,在過載決議中選擇最佳匹配函式時,只考慮標準轉換(SC)序列,而這個序列直到遇到一個UDC為止,對於第一個函式,將C->C const與C->C比較,顯然選擇後者。因為後者是前者的一個真子集。因此,去掉第一個轉換序列我們得到:
C -> D const volatile* (UDC) -> B1 const volatile* / B2 const volatile* ->
B const volatile* (SC - Conversion)
C -> D const volatile* (UDC)
這裡採用選擇最短序列的原則,選擇後者,這表明編譯器甚至根本不需要去考慮向B轉換的多重路徑,或者訪問限制,所以轉換二義性和訪問違規也就不會發生。結論是如果D繼承自B,則選擇yes_type check()。
如果D不是繼承自B,則對於static no_type check(B const volatile *, int)編譯器的給出的轉換為:
C -> C const -> B const volatile*(UDC)
對於static yes_type check(D const volatile *, T)編譯器給出:
C -> D const volatile* (UDC)
這兩個都不錯(都需要一個UDC),然而由於static no_type check(B const volatile *, int)為非模板函式,所以被編譯器選用。結論是如果D並非繼承自B,則選擇no_type check()。
另外,在我的VC7.0環境下,如果將Host的operator B const volatile *() const的const拿掉,則結果將總是false。
可惜這樣的理解並不屬於我,它們來自Boost原始碼中的註釋。
is_convertible<>(boost/type_traits/is_convertible.hpp)
定義
template< typename From >
struct does_conversion_exist
{
template< typename To > struct result_
{
static no_type _m_check(...); //當不存在從From到To的任何轉型時呼叫它
static yes_type _m_check(To); //只要轉型存在就呼叫它
static From _m_from; //這只是個宣告,所以並不佔用空間,且沒有開銷。
enum { value = sizeof( _m_check(_m_from) ) == sizeof(yes_type) };
};
};
template<> struct does_conversion_exist{ //這是個為void準備的特化版本,因為不能宣告
template< typename To > struct result_{ //void _m_from
enum { value = ::boost::is_void::value }; //只有void可以向void“轉換”
};
};
//is_convertible<>完全使用does_conversion_exist<>作底層機制,所以略去。
註解
does_conversion_exist<>也使用了與is_class_impl<>一樣的技術。所以註解從略。該技術最初由Andrei Alexandrescu發明,請參見Modern Design C++(<>--侯捷、於春景譯)。
型別間關係traits還有:is_same<>,它只是簡單的模板偏特化。
5. Transformations Between Types(型別間轉換) 6. Synthesizing Types(型別合成) 7. Function Traits(函式traits)的機制較為單純,請參見Boost提供的文件或標頭檔案。
Traits是泛型世界中的精靈:小巧,精緻。Traits也是泛型程式設計中最精微的東西,它們往往仰賴於一些編譯期決議的規則,C++標準,和神奇的模板偏特化。這也導致了它們在不同的平臺上可能有不同表現,更常見的是,在某些平臺上根本無法工作。然而,由於它們的依據是C++標準,而編譯器會越來越符合標準,所以這些問題只是暫時的。Traits也是構建泛型世界的基本元件之一,它們往往能使設計變得優雅,精緻,甚至完美。
相關推薦
Boost原始碼剖析之:型別分類器——type_traits
1. 分派 下面有一個模板函式,假設一個動物收容組織提供了它,他們接受所有無家可歸的可憐的小動物,於是他們向外界提供了一個函式接受註冊。函式看起來像這樣: AcceptAnimals(T animal) { ... }; 但是,如果他們想將
SpringMVC原始碼剖析5:訊息轉換器HttpMessageConverter與@ResponseBody註解
轉自 SpringMVC關於json、xml自動轉換的原理研究[附帶原始碼分析] 本系列文章首發於我的個人部落格:https://h2pl.github.io/ 歡迎閱覽我的CSDN專欄:Spring原始碼解析 https://blog.csdn.net/column/details/21851.html 部
Dream team: Stacking for combining classifiers夢之隊:組合分類器
由於 into it is appear nds lin eas 行操作 blank sklearn實戰-乳腺癌細胞數據挖掘 https://study.163.com/course/introduction.htm?courseId=1005269003&u
scikit-learn /sklearn : 整合學習 之 隨機森林分類器(Forests of Randomized Tree)官方檔案翻譯
整合學習 之 隨機森林分類器 整合學習的定義和分類。 隨機森林法的定義和分類。 隨機森林sklearn.ensemble.RandomForestClassifier()引數分類和含義。 附註:Bias和Variance的含義和關係。 一、整合學習 (Ensemble
文件服務器之:SAMBA 服務器,穩定可靠,沒有連接數限制
目錄 AI drive 用戶密碼 HA CP dump 代碼 samba配置 SAMBA使用的是NetBIOS通訊協議,NetBIOS是無法跨路由的透過NetBIOS over TCP/IP的技術,可以跨路由使用SAMBA服務器所提供的功能 SAMBA聯機模式:1、peer
Spring原始碼窺探之:Spring IOC之BeanPostProcessor
Spring的Bean後置處理器 1. 實體類 /** * @author 70KG * @Title: Train * @Description: * @date 2018/7/23下午11:31 * @From www.nmyswls.com */ public cla
模式識別: 線性分類器
一、實驗目的和要求 目的: 瞭解線性分類器,對分類器的引數做一定的瞭解,理解引數設定對演算法的影響。 要求: 1. 產生兩類樣本 2. 採用線性分類器生成出兩類樣本的分類面 3. 對比線性分類器的效能,對比引數設定的結果 二、實驗環境、
Spring原始碼窺探之:Spring IOC之FactoryBean
1. 定義Fish實體類 /** * @author 70KG * @Title: Fish * @Description: * @date 2018/7/22下午5:00 * @From www.nmyswls.com */ @Data public class Fish
機器學習筆記(六):KNN分類器
1 KNN演算法 1.1 KNN演算法簡介 KNN(K-Nearest Neighbor)工作原理:存在一個樣本資料集合,也稱為訓練樣本集,並且樣本集中每個資料都存在標籤,即我們知道樣本集中每一資料與所屬分類對應的關係。輸入沒有標籤的資料後,將新資料中的每個特徵與樣本集中資料對應的特
spring boot 專案重新搭建----------mvc配置:型別轉換器
實現WebMvcConfigurer介面: 1.configurePathMatch路徑配置: setUseSuffixPatternMatch : 設定是否是字尾模式匹配,如“/user”是否匹配/user.*,預設為true setUseTrailingSlash
STL原始碼剖析之map和set
之前分析二叉搜尋樹和平衡二叉樹時,真心感覺樹的實現真是難,特別是平衡二叉樹,不平衡之後需要調整,還要考慮各種情況,累感不愛.今天看到這個紅黑樹,發現比平衡二叉樹還難,但是紅黑樹比平衡二叉樹使用的場景更多,所以平常使用時,我們需要了解紅黑樹的實現原理,如果有能力,可以自己實現,但是如果實在做不出來,也
STL原始碼分析之__type_traits型別
前言 上一篇探討的是traits是為了將迭代器沒能完善的原生指標, traits用特化和偏特化程式設計來完善. 這一篇準備探討__type_traits, 為了將我們在空間配置器裡面的提過的__true_type和false_type進行解答. 而type_traits型別對我們ST
STL原始碼分析之第二級配置器
前言 第一級是直接呼叫malloc分配空間, 呼叫free釋放空間, 第二級三就是建立一個記憶體池, 小於128位元組的申請都直接在記憶體池申請, 不直接呼叫malloc和free. 本節分析第二級空間配置器, STL將第二級配置器設定為預設的配置器, 所以只要一次申請的空間不超過1
STL原始碼分析之第一級配置器
前言 上一節我們分析了空間配置器對new的配置, 而STL將空間配置器分為了兩級, 第一級是直接呼叫malloc分配空間, 呼叫free釋放空間, 第二級三就是建立一個記憶體池, 小於128位元組的申請都直接在記憶體池申請, 不直接呼叫malloc和free. 本節我們就先分析第
STL原始碼分析之multimap配接器
前言 前面我們分析了map, 知道map是不允許插入相同的鍵值的, 也不會儲存第二次的資料, 而本節分析的multimap與map不同, 它允許多個重複的鍵值插入. mutimap操作 int main() { multimap<string, int> mul
STL原始碼分析之multiset配接器
前言 前面也分析過set, 並且set不能插入相同的鍵, 本節分析的multiset與set不同之處就是他允許插入相同的鍵. multiset操作 int main() { multiset<string> multi; // 允許重複插入鍵 mult
STL原始碼分析之map配接器
前言 上一節分析了pair結構, 正是為map分析做鋪墊, map本身實現也不難, 其資料儲存是pair, 儲存結構是RB-tree, 即map也並不能說是關聯容器, 而應該是配接器. map操作 map的insert必須是以pair為儲存結構, 當然也可以直接使用make_
STL原始碼分析之set配接器
前言 上面兩節我們分析了RB-tree, 同時我們也知道了rb-tree的插入操作還分為可重複插入和不可重複插入(insert_unique). 本節分析set, 嚴格意義說set就是修改了底層容器介面的, 所以應該是配置器. set就是將RB-tree作為底層容器, 以insert
STL原始碼分析之queue配接器
前言 上一節分析了stack實現, stack是修改了deque的介面而實現的一個功能簡單的結構, 本節分析的queue也是用deque為底層容器封裝. queue資料都是在頭部進行操作的, 之允許進行push和pop操作. queue分析 queue結構 #ifnd
STL原始碼分析之stack配接器
前言 上面幾節我們分析了deque是一個雙向開口, 並且每部分資料也是存放在連續空間中, 而stack是棧, 只允許在尾進行操作, 而且stack的功能deque都已經實現了, 只需要對deque的功能進行部分封裝就行了, 也就提取出pop_back, push_back就行了.