主頁 > 後端開發 > 【STL 原始碼剖析】淺談 STL 迭代器與 traits 編程技法

【STL 原始碼剖析】淺談 STL 迭代器與 traits 編程技法

2020-12-31 07:01:29 後端開發

攝于清華五道口

大家好,我是小賀,

點贊再看,養成習慣

文章每周持續更新,可以微信搜索「herongwei」第一時間閱讀和催更,本文 GitHub : https://github.com/rongweihe/MoreThanCPlusPlus 已經收錄,有一線大廠面試點思維導圖,也整理了很多我的檔案,歡迎 star 和完善,一起加油,變得更好!

前言

上一篇,我們剖析了 STL 空間配置器,這一篇文章,我們來學習下 STL 迭代器以及背后的 traits 編程技法,

在 STL 編程中,容器和演算法是獨立設計的,容器里面存的是資料,而演算法則是提供了對資料的操作,在演算法操作資料的程序中,要用到迭代器,迭代器可以看做是容器和演算法中間的橋梁,

1、迭代器設計模式

為何說迭代器的時候,還談到了設計模式?這個迭代器和設計模式又有什么關系呢?

其實,在《設計模式:可復用面向物件軟體的基礎》(GOF)這本經典書中,談到了 23 種設計模式,其中就有 iterator 迭代模式,且篇幅頗大,

碰巧,筆者在研究 STL 原始碼的時候,同樣的發現有 iterator 迭代器,而且還占據了一章的篇幅,

在設計模式中,關于 iterator 的描述如下:一種能夠順序訪問容器中每個元素的方法,使用該方法不能暴露容器內部的表達方式,而型別萃取技術就是為了要解決和 iterator 有關的問題的,

有了上面這個基礎,我們就知道了迭代器本身也是一種設計模式,其設計思想值得我們仔細體會,

那么 C++ STL 實作 iterator 和 GOF 介紹的迭代器實作方法什么區別呢? 那首先我們需要了解 C++ 中的兩個編程范式的概念,OOP(面向物件編程)和 GP(泛型編程),

在 C++ 語言里面,我們可用以下方式來簡單區分一下 OOP 和 GP :

OOP:將 methods 和 datas 關聯到一起 (通俗點就是方法和成員變數放到一個類中實作),通過繼承的方式,利用虛函式表(virtual)來實作運行時型別的判定,也叫"動態多型",由于運行程序中需根據型別去檢索虛函式表,因此效率相對較低,

GP:泛型編程,也被稱為"靜態多型",多種資料型別在同一種演算法或者結構上皆可操作,其效率與針對某特定資料型別而設計的演算法或者結構相同, 具體資料型別在編譯期確定,編譯器承擔更多,代碼執行效率高,在 STL 中利用 GP 將 methods 和 datas 實作了分而治之,

而 C++ STL 庫的整個實作采用的就是 GP(Generic Programming),而不是 OOP(Object Oriented Programming),而 GOF 設計模式采用的就是繼承關系實作的,因此,相對來講,C++ STL 的實作效率會相對較高,而且也更有利于維護,

在 STL 編程結構里面,迭代器其實也是一種模板 class ,迭代器在 STL 中得到了廣泛的應用,通過迭代器,容器和演算法可以有機的系結在一起,只要對演算法給予不同的迭代器,比如 vector::iterator、list::iterator,std::find() 就能對不同的容器進行查找,而無需針對某個容器來設計多個版本,

這樣看來,迭代器似乎依附在容器之下,那么,有沒有獨立而適用于所有容器的泛化的迭代器呢?這個問題先留著,在后面我們會看到,在 STL 編程結構里面,它是如何把迭代器運用的爐火純青,

2、智能指標

STL 是泛型編程思想的產物,是以泛型編程為指導而產生的,具體來說,STL 中的迭代器將范型演算法 (find, count, find_if) 等應用于某個容器中,給演算法提供一個訪問容器元素的工具,iterator 就扮演著這個重要的角色,

稍微看過 STL 迭代器原始碼的,就明白迭代器其實也是一種智能指標,因此,它也就擁有了一般指標的所有特點—— 能夠對其進行 * -> 操作,

template<typename T>
class ListIterator {//mylist迭代器
public:
    ListIterator(T *p = 0) : m_ptr(p){} //建構式
    T& operator*() const { return *m_ptr;}  //取值,即dereference
    T* operator->() const { return m_ptr;} //成員訪問,即member access
    //...
};

但是在遍歷容器的時候,不可避免的要對遍歷的容器內部有所了解,所以,干脆把迭代器的開發作業交給容器的設計者,如此以來,所有實作細節反而得以封裝起來不被使用者看到,這也正是為什么每一種 STL 容器都提供有專屬迭代器的緣故,

比如筆者自己實作的 list 迭代器在這里使用的好處主要有:

  • (1) 不用擔心記憶體泄漏(類似智能指標,解構式釋放記憶體);
  • (2) 對于 list ,取下一個元素不是通過自增而是通過 next 指標來取,使用智能指標可以對自增進行多載,從而提供統一介面

3、template 引數推導

引數推導能幫我們解決什么問題呢?

在演算法中,你可能會定義一個簡單的中間變數或者設定演算法的回傳變數型別,這時候,你可能會遇到這樣的問題,假如你需要知道迭代器所指元素的型別是什么,進而獲取這個迭代器操作的演算法的回傳型別,但是問題是 C++ 沒有 typeof 這類判斷型別的函式,也無法直接獲取,那該如何是好?

注意是型別,不是迭代器的值,雖然 C++ 提供了一個 typeid() 運算子,這個運算子只能獲得型別的名稱,但不能用來宣告變數,要想獲得迭代器型別,這個時候又該如何是好呢?

function template 的引數推導機制是一個不錯的方法,

例如:

如果 I 是某個指向特定物件的指標,那么在 func 中需要指標所指向物件的型別的時候,怎么辦呢?這個還比較容易,模板的引數推導機制可以完成任務,

template <class I>
inline void func(I iter) {
    func_imp(iter, *iter); // 傳入 iter 和 iter 所指的值,class 自動推導
}

通過模板的推導機制,就能輕而易舉的獲得指標所指向的物件的型別,

template <class I, class T>
void func_imp(I iter, T t) {
        T tmp; // 這里就是迭代器所指物的類別
        // ... 功能實作
}
int main() {
    int i;
    func(&i);//這里傳入的是一個迭代器(原生指標也是一種迭代器)
}


上面的做法呢,通過多層的迭代,很巧妙地匯出了 T ,但是卻很有局限性,比如,我希望 func() 回傳迭代器的 value type 型別回傳值, 函式的 "template 引數推導機制" 推導的只是引數,無法推導函式的回傳值型別,萬一需要推導函式的回傳值,好像就不行了,那么又該如何是好?

這就引出了下面的內嵌型別,

4、宣告內嵌型別

上述所說的 迭代器所指物件的型別,稱之為迭代器的 value type

盡管在 func_impl 中我們可以把 T 作為函式的回傳值,但是問題是用戶需要呼叫的是 func

如果在引數推導機制上加上內嵌型別 (typedef) 呢?為指定的物件型別定義一個別名,然后直接獲取,這樣來看一下實作:

template<typename T>
class MyIter {
public:
    typedef T value_type; //內嵌型別宣告
    MyIter(T *p = 0) : m_ptr(p) {}
    T& operator*() const { return *m_ptr;}
private:
    T *m_ptr;
};

//以迭代器所指物件的型別作為回傳型別
//注意typename是必須的,它告訴編譯器這是一個型別
template<typename MyIter>
typename MyIter::value_type Func(MyIter iter) {
    return *iter;
}

int main(int argc, const  char *argv[]) {
    MyIter<int> iter(new int(666));
    std::cout<<Func(iter)<<std::endl;  //print=> 666
}

上面的解決方案看著可行,但其實呢,實際上還是有問題,這里有一個隱晦的陷阱:實際上并不是所有的迭代器都是 class type ,原生指標也是一種迭代器,由于原生指標不是 class type ,所以沒法為它定義內嵌型別

因為 func 如果是一個泛型演算法,那么它也絕對要接受一個原生指標作為迭代器,下面的代碼編譯沒法通過:

int *p = new int(5);
cout<<Func(p)<<endl; // error

要解決這個問題,Partial specialization (模板偏特化)就出場了,

5、Partial specialization(模板偏特化)

所謂偏特化是指如果一個 class template 擁有一個以上的 template 引數,我們可以針對其中某個(或多個,但不是全部)template 引數進行特化,比如下面這個例子:

template <typename T>
class C {...}; //此泛化版本的 T 可以是任何型別
template <typename T>
class C<T*> {...}; //特化版本,僅僅適用于 T 為“原生指標”的情況,是泛化版本的限制版

所謂特化,就是特殊情況特殊處理,第一個類為泛化版本,T 可以是任意型別,第二個類為特化版本,是第一個類的特殊情況,只針對原生指標,

5.1、原生指標怎么辦?——特性 “萃取” traits

還記得前面說過的引數推導機制+內嵌型別機制獲取型別有什么問題嗎?問題就在于原生指標雖然是迭代器但不是class ,無法定義內嵌型別,而偏特化似乎可以解決這個問題,

有了上面的認識,我們再看看 STL 是如何應用的,STL 定義了下面的類模板,它專門用來“萃取”迭代器的特性,而value type 正是迭代器的特性之一:

traitsbits/stl_iterator_base_types.h 這個檔案中:

template<class _Tp>
struct iterator_traits<_Tp*> {
    typedef ptrdiff_t difference_type;
    typedef typename _Tp::value_type value_type;
    typedef typename _Tp::pointer pointer;
    typedef typename _Tp::reference reference;
    typedef typename _Tp::iterator_category iterator_category;
};
template<typename Iterator>
struct iterator_traits {  //型別萃取機
	typedef typename Iterator::value_type value_type; //value_type 就是 Iterator 的型別型別
}

加入萃取機前后的變化:

template<typename Iterator> //萃取前
typename Iterator::value_type  func(Iterator iter) {
    return *iter;
}

//通過 iterator_traits 作用后的版本
template<typename Iterator>  //萃取后
typename iterator_traits<Iterator>::value_type  func(Iterator iter) { 
    return *iter;
}

看到這里也許你會問了,這個萃取前和萃取后的 typename :iterator_traits::value_typeIterator::value_type 看起來一樣啊,為什么還要增加 iterator_traits 這一層封裝,豈不是多此一舉?

回想萃取之前的版本有什么缺陷:不支持原生指標,而通過萃取機的封裝,我們可以通過類模板的特化來支持原生指標的版本!如此一來,無論是智能指標,還是原生指標,iterator_traits::value_type 都能起作用,這就解決了前面的問題,

//iterator_traits的偏特化版本,針對迭代器是原生指標的情況
template<typename T>
struct iterator_traits<T*> {
    typedef T value_type;
};

看到這里,我們不得不佩服的 STL 的設計者們,真·秒啊!我們用下面這張圖來總結一下前面的流程:

stl-iterator-09.png

5.2 、const 偏特化

通過偏特化添加一層中間轉換的 traits 模板 class,能實作對原生指標和迭代器的支持,有的讀者可能會繼續追問:對于指向常數物件的指標又該怎么處理呢?比如下面的例子:

iterator_traits<const int*>::value_type  // 獲得的 value_type 是 const int,而不是 int

const 變數只能初始化,而不能賦值(這兩個概念必須區分清楚),這將帶來下面的問題:

template<typename Iterator>
typename iterator_traits<Iterator>::value_type  func(Iterator iter) { 
    typename iterator_traits<Iterator>::value_type tmp; 
    tmp = *iter; // 編譯 error
}

int val = 666 ;
const int *p = &val;
func(p); // 這時函式里對 tmp 的賦值都將是不允許的

那該如何是好呢?答案還是偏特化,來看實作:

template<typename T>
struct iterator_traits<const T*> { //特化const指標
    typedef T value_type; //得到T而不是const T
}

6、traits編程技法總結

通過上面幾節的介紹,我們知道,所謂的 traits 編程技法無非 就是增加一層中間的模板 class,以解決獲取迭代器的型別中的原生指標問題,利用一個中間層 iterator_traits 固定了 func 的形式,使得重復的代碼大量減少,唯一要做的就是稍稍特化一下 iterator_tartis 使其支持 pointerconst pointer

#include <iostream>

template <class T>
struct MyIter {
    typedef T value_type; // 內嵌型別宣告
    T* ptr;
    MyIter(T* p = 0) : ptr(p) {}
    T& operator*() const { return *ptr; }
};
// class type
template <class T>
struct my_iterator_traits {
    typedef typename T::value_type value_type;
};
// 偏特化 1
template <class T>
struct my_iterator_traits<T*> {
    typedef T value_type;
};
// 偏特化 2
template <class T>
struct my_iterator_traits<const T*> {
    typedef T value_type;
};

// 首先詢問 iterator_traits<I>::value_type,如果傳遞的 I 為指標,則進入特化版本,iterator_traits 直接回答;如果傳遞進來的 I 為 class type,就去詢問 T::value_type.
template <class I>
typename my_iterator_traits<I>::value_type Func(I ite) {
    std::cout << "normal version" << std::endl;
    return *ite;
}
int main(int argc, const  char *argv[]) {
    MyIter<int> ite(new int(6));
    std::cout << Func(ite)<<std::endl;//print=> 6
    int *p = new int(7);
    std::cout<<Func(p)<<std::endl;//print=> 7
    const int k = 8;
    std::cout<<Func(&k)<<std::endl;//print=> 8
}

上述的程序是首先詢問 iterator_traits::value_type,如果傳遞的 I 為指標,則進入特化版本, iterator_traits 直接回答T;如果傳遞進來的 Iclass type ,就去詢問 T::value_type

通俗的解釋可以參照下圖:

總結:核心知識點在于 模板引數推導機制+內嵌型別定義機制, 為了能處理原生指標這種特殊的迭代器,引入了偏特化機制traits 就像一臺 “特性萃取機”,把迭代器放進去,就能榨取出迭代器的特性,

這種偏特化是針對可呼叫函式 func 的偏特化,想象一種極端情況,假如 func 有幾百萬行代碼,那么如果不這樣做的話,就會造成非常大的代碼污染,同時增加了代碼冗余,

7、迭代器的型別和種類

7.1 迭代器的型別

我們再來看看迭代器的型別,常見迭代器相應型別有 5 種:

  • value_type:迭代器所指物件的型別,原生指標也是一種迭代器,對于原生指標 int*,int 即為指標所指物件的型別,也就是所謂的 value_type ,

  • difference_type: 用來表示兩個迭代器之間的距離,對于原生指標,STL 以 C++ 內建的 ptrdiff_t 作為原生指標的 difference_type,

  • reference_type: 是指迭代器所指物件的型別的參考,reference_type 一般用在迭代器的 * 運算子多載上,如果 value_type 是 T,那么對應的 reference_type 就是 T&;如果 value_type 是 const T,那么對應的reference_type 就是 const T&,

  • pointer_type: 就是相應的指標型別,對于指標來說,最常用的功能就是 operator* 和 operator-> 兩個運算子,

  • iterator_category: 的作用是標識迭代器的移動特性和可以對迭代器執行的操作,從 iterator_category 上,可將迭代器分為 Input Iterator、Output Iterator、Forward Iterator、Bidirectional Iterator、Random Access Iterator 五類,這樣分可以盡可能地提高效率,

    template<typename Category,
             typename T,
             typename Distance = ptrdiff_t,
             typename Pointer = T*,
             typename Reference = T&>
    struct iterator //迭代器的定義
    {
        typedef Category iterator_category;
        typedef T value_type;
        typedef Distance difference_type;
        typedef Pointer pointer;
        typedef Reference reference;
    };
    

iterator class 不包含任何成員變數,只有型別的定義,因此不會增加額外的負擔,由于后面三個型別都有默認值,在繼承它的時候,只需要提供前兩個引數就可以了,這個類主要是用來繼承的,在實作具體的迭代器時,可以繼承上面的類,這樣子就不會漏掉上面的 5 個型別了

對應的迭代器萃取機設計如下:

tempalte<typename I>
struct iterator_traits {//特性萃取機,萃取迭代器特性
    typedef typename I::iterator_category iterator_category;
    typedef typename I::value_type value_type;
    typedef typeanme I:difference_type difference_type;
    typedef typename I::pointer pointer;
    typedef typename I::reference reference;
};

//需要對型別為指標和 const 指標設計特化版本看

7.2、迭代器的分類

最后,我們來看看,迭代器型別 iterator_category 對應的迭代器類別,這個類別會限制迭代器的操作和移動特性,
除了原生指標以外,迭代器被分為五類:

  • Input Iterator: 此迭代器不允許修改所指的物件,是只讀的,支持 ==、!=、++、*、-> 等操作,
  • Output Iterator:允許演算法在這種迭代器所形成的區間上進行只寫操作,支持 ++、* 等操作,
  • Forward Iterator:允許演算法在這種迭代器所形成的區間上進行讀寫操作,但只能單向移動,每次只能移動一步,支持 Input Iterator 和 Output Iterator 的所有操作,
  • Bidirectional Iterator:允許演算法在這種迭代器所形成的區間上進行讀寫操作,可雙向移動,每次只能移動一步,支持 Forward Iterator 的所有操作,并另外支持 – 操作,
  • Random Access Iterator:包含指標的所有操作,可進行隨機訪問,隨意移動指定的步數,支持前面四種 Iterator 的所有操作,并另外支持 [n] 運算子等操作,

那么,這里,小賀想問大家,為什么我們要對迭代器進行分類呢?迭代器在具體的容器里是到底如何運用的呢?這個問題就放到下一節在講,

最最后,我們再來回顧一下六大組件的關系:

這六大組件的互動關系:container(容器) 通過 allocator(配置器) 取得資料儲存空間,algorithm(演算法)通過 iterator(迭代器)存取 container(容器) 內容,functor(仿函式) 可以協助 algorithm(演算法) 完成不同的策略變化,adapter(配接器) 可以修飾或套接 functor(仿函式),

參考文章:

  • 《STL原始碼剖析-侯捷》

  • https://zhuanlan.zhihu.com/p/85809752

  • https://wendeng.github.io/

8、結尾

如果覺得文章對你有幫助,歡迎分享給你的朋友,一鍵三連,謝謝各位,
我是 herongwei ,是男人,就對自己狠一點,祝大家作業愉快,我們下期見,

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

標籤:其他

上一篇:SpringBoot集成Redis

下一篇:SpringBoot 好“吃”的啟動原理

標籤雲
其他(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