主頁 > 後端開發 > c++11-模板元實戰

c++11-模板元實戰

2021-06-09 15:33:49 後端開發

前言

黑魔法,應用場景 :
1.實作宿生語言
2.實作一些常規手段做不到的東西,比如 c++11::share_prt::enable_share_from_this
3.作為實作各種庫的基本組件 :Stl,Boost,標準庫都大量運用了模板元技術

很多人比較排斥這個東西...個人覺得實用就行,工具終究是為人服務的
just enjoy it ??

推薦的資料

《C++ Templates》
《C++模板元編程》
C++11模板元入門 https://www.cnblogs.com/qicosmos/p/4480460.html
C++高質量社區 http://purecpp.org/

目錄

Part 1 實作一個基于C++的宿生Lisp方言

Part 2 實作一些小玩意

Part 1 實作一個基于C++的宿生Lisp方言

基本約定

建議先看看江南的博客入個門 :C++11模板元入門 https://www.cnblogs.com/qicosmos/p/4480460.html
這是一個型別的世界,首先定義三種基本型別
1.integral_constant :與值關聯的型別
2.元型別
3.元函式

1.integral_constant

c++11中的標準庫元型別,先看源代碼

template <typename T,T v>
struct integral_constant
  {
	using value_type = T ;
	using type = integral_constant<T,v>;
	static constexpr T value = https://www.cnblogs.com/XDU-mzb/p/v;
	constexpr operator value_type() 
	  {
	  	return value;
	  }
  };

看幾個栗子

using true_type = integral_constant<bool, true>;
using ten = integral_constant<int,10>;
ten::value_type val = 10;
cout << ten::type::type::type::value << endl;
cout << ten() << endl;

typedef和uisng指令等效,using指令更清晰
可以觀察到 integral_constant可以生成一個新型別,新型別系結一個值和該值的型別
并且特別的是 :integral_constant::type指向型別本身

型別2.元型別

特征十分明顯 :結構體為空 (建議元編程的時候全部采用struct,struct與class沒什么區別,僅僅默認訪問權限、繼承全部為public

template<typename a,typename b>
struct sum : integral_constant<typename a::value_type,a::value + b::value>
  {};
int main()
  {
  	using ten = integral_constant<int,10>;
  	using five = integral_constant<int,5>;
  	cout << sum<ten,five>::value << endl;
	return 0;
  }

繼續觀察樣例,可以看到sum完成了兩個數的加法
觀察點1 : 第二行 typename a::value_type,為什么要加奇怪的typename?因為c++模板的特殊性質,編譯器在看到a::value_type的時候并不能分析出來a::value_type是成員變數還是型別,所以需要顯式說明
觀察點2 : sum的結構體為空,所以sum是元型別,但是,sum卻完成一個元函式行為 :計算兩數和,既然元型別可以進行元計算,為什么我們還需要劃分出元函式?
答案是元型別可以進行元計算,但是完成不了復雜的元計算,因為在元型別做元計算的時候,宣告不了新型別,導致只能實作一句代碼的元計算,做不了復雜計算

型別3.元函式

特征也十分明顯:沒有繼承行為,結構體內部充滿一些using指令,用來做元計算

template<typename a,typename b>
struct sum
  {
  	using result = integral_constant<typename a::value_type,a::value + b::value>;
  };
int main()
  {
  	using ten = integral_constant<int,10>;
  	using five = integral_constant<int,5>;
  	cout << sum<ten,five>::result() << endl;
	return 0;
  }

類似于c/c++中的函式,元函式也有"函式名" : sum,"函式引數" :typename a,typename b,"回傳值" result
觀察點1.我們約定,元函式的"回傳值"全部定義為result
觀察點2.cout << sum<ten,five>::result() << endl; result是型別,我們可以通過類似 cout << int() << endl;的形式來列印元資訊,多載一下流輸出即可,后面還會涉及
觀察點3.我們約定,元函式不繼承任何型別

lisp方言基本型別與操作

1.基本型別,如整形,字串
2.null型別
3.pair型別
4.元函式car,cdr
有一個非常龐大的證明和實踐體系證明上述簡單型別/操作能實作非常復雜的計算,是圖靈完備的

1.基本型別

整形

template<int val>
struct number : integral_constant<int,val> {};

字串 : 因為c++11不滋滋模板字串,所以不能直接定義(因為 "string"的型別是const cahr* const 兩個相同的字串常量擁有不同的常量地址,模板不能識別這種
因為本次模板元實戰主要在模板,我采用了一種取巧的辦法 :把字串壓進unsigned long long int(簡記為ull,可以滋滋長度在8及以下的任何字串

  	constexpr ull stoull(const char *p,ull now = 0)
  	  {
  	  	return *p == 0 ? now : stoull(p + 1,now * 255 + *p); 
		}
	template<ull val>
	struct string : public integral_constant<ull,val> {};
#define str(x) string<stoull(x)>

這樣就可以很方便的用str("string")來產生字串型別
浮點 : 因為c++的歷史原因,模板也不滋滋浮點常量,取巧的辦法是定義一個pair<int,int> 即"int.int",與主題關系不大,故不再贅述

2.null型別

為什么要把null單獨說明呢...因為空型別相當重要,設計良好的空型別可以很好的簡化很多設計

struct null : str("null") {};

null也可以定義成別的樣子,這樣方便一些

3.pair型別

和c++中的pair類似,只不過first -> car,second -> cdr(僅僅是遵循lisp方言慣例,沒什么特別含義

template<typename T,typename U>
struct pair
  {
  	using car = T;
  	using cdr = U;
  };
4.元函式car,cdr
#define car(x) typename Car<x>::result
	template<typename T>
	struct Car
	  {
	  	using result = typename T::car;
	  };
#define cdr(x) typename Cdr<x>::result
	template<typename T>
	struct Cdr
	  {
	  	using result = typename T::cdr;
	  };
#define equal(typea,typeb) typename std::is_same<typea,typeb>::type
#define if_else(a,b,c) typename std::conditional<a,b,c>::type

順便包裝一下std::is_same std::conditional

元函式約定 :

1.每一個元函式都會搭配一個宏
2.統一用result作為元函式回傳值
3.元函式不繼承任何型別

實作一個異類字典

先看效果

using table = list<pair<str("a"),str("value_a")>,
             	   pair<str("b"),integral_constant<bool,0>>,
		   pair<number<10>,str("x")>
		   >;
using resulta = table_find(table,str("a"));
using resultb = table_find(table,number<10>);
using resultc = table_find(table,str("c"));
cout << resulta() << endl; //value_a
cout << resultb() << endl; //x
cout << resultc() << endl; //null

看起來很復雜?我們分三步實作
1.實作list
2.實作table_find
3.實作輸出

1.實作list
template<typename T,typename ...arg>
struct list : pair<T,list<arg...>> {};
template<typename T>
struct list<T> : pair<T,null> {};

不會模板引數包的建議百度
概念上list<a,b,c> = pair<a,pair<b,pair<c,null>>>;
利用引數包 + pair不斷展開即可

template<typename T,typename ...arg>
struct list<T,arg...> : pair<T,list<arg...>> {};
template<typename T>
struct list<T> : pair<T,null> {};

因為list對型別沒有啥要求,所以直接實作了一個異類字典

2.實作table_find
#define table_find(table,value) typename Table_find<table,value>::result	
	template<typename list,typename key>
	struct Table_find
	  {
	  	using if_found = cdr(car(list));
	  	using if_not_found = table_find(cdr(list),key);
	  	using check_equal = equal(car(car(list)),key);
	  	using result = if_else(check_equal::value,if_found,if_not_found);
	  };
	template<typename key>
	struct Table_find<null,key>
	  {
	  	using result = null;
	  };

首先定義宏,得益我們良好的約定,我們可以寫出非常陽間的代碼
遞回終止的條件十分簡單:找不到回傳null即可,注意偏特化的寫法
概念上,table_find里面核心就是一個if_else邏輯
代碼寫的十分清楚,不停的遞回list結構,層層尋找即可
注意using check_equal = equal(car(car(list)),key);因為我們的宏中有typename
所以equal(car(car(list)),key)::value;的代碼是錯誤的,因為value是個靜態成員,解決辦法是加個間接型別

實作輸出

下面結尾c++輸出風格,如果你喜歡printf()這樣的,原理一樣

cout << integral_constant<int,10>() << endl;            // 10
cout << table_find<table,str("12")>::result() << endl;  // null

首先integral_constant里面多載了型別轉換運算子,編譯器會自動轉化,我們只需要寫自己型別的輸出格式即可

template<ull val>
ostream& operator << (ostream& os,string<val>)
  {
  	std::string ret;
  	ull now = val;
  	while (now)
  	  ret.push_back(now % 255),
  	  now /= 255;
  	reverse(ret.begin(),ret.end());
  	return os << ret;
  }
ostream& operator << (ostream& os,null)
  {
  	return os << "null";
  }
template<typename T,typename U>
ostream& operator << (ostream& os,pair<T,U>)
  {
  	return os << "(" << T() << "," << U() <<")";
  }

注意ostream& operator << (ostream& os,string);第二個沒引數,只有型別
因為資訊都在型別里面,引數寫不寫無所謂,你寫的話,編譯器自動會優化掉
呼叫的時候func(type()),編譯器就能演繹型別了,func(type)這樣是不行滴,因為違背了c\c++引數傳遞規則
就是函式多載 + 模板特化,大多數輸出就這三種特化方式...都在這了...比較簡單,照貓畫虎即可
list<number<10>,str("22")> : [10,22]
list的輸出比較麻煩一點,因為要控制輸出格式 + 引數包,代碼里有,原理沒區別,不贅述了

小結

至此,我們實作了一個字典...難者不會,會者不難...寫熟練之后還是挺簡單的
小練習,實作一個length的元函式,計算list,字典的元素個數
多動手,練習是必須滴

#define length(type) typename Length<type>::result    
	template<typename T>
	struct Length
	  {
	  	using len = length(cdr(T));
	  	using result = number<1 + len::value>;
	  };
	template<>
	struct Length<null>
	  {
	  	using result = number<0>;
	  };

實作一個歸并排序

1.實作merge : 按順序合并兩個list得到新的list
2.實作split : 切割一個list為長度差不超過1的兩個list
3.實作sort_merge : 合并兩個有序list為一個有序list
4.實作sort : 排序一個list

1.實作merge : 按順序合并兩個list得到新的list

這里需要一個技巧 : 模板的模板引數,不會的百度一下
模板的模板引數,在這里的作用是拿到list內部的型別,常規手段拿不到
其實模板的模板引數類似于函式的實參演繹,只不過挪到了編譯期,作用依舊是推導型別

#define merge(typea,typeb) typename Merge<typea,typeb>::result	  
	template<typename list1,typename list2>
	struct Merge {};
	template<typename ...args1,template<typename ...args1> class list1,
	         typename ...args2,template<typename ...args2> class list2
			 >
	struct Merge<list1<args1...>,list2<args2...>>
	  {
	  	using result = list<args1...,args2...>;
	  };
	template<typename ...args1,template<typename ...args1> class list1>
	struct Merge<list1<args1...>,list<null>>
	  {
	  	using result = list<args1...>;
	  };
	template<typename ...args2,template<typename ...args2> class list2>
	struct Merge<list2<null>,list2<args2...>>
	  {
	  	using result = list<args2...>;
	  };

結構就是一個宣告 + 4個偏特化...后面三個偏特化很簡單,重點看第一個偏特化

template<typename ...args1,template<typename ...args1> class list1,
         typename ...args2,template<typename ...args2> class list2 
         >
struct Merge<list1<args1...>,list2<args2...>>
  {
  	using result = list<args1...,args2...>;
  };

這里的merge其實可以通過lisp形式完成...(偷個懶順便練習一下模板的模板引數...后面還會用lisp的風格寫元函式的
元函式體里面很直觀,就是拿到兩個list的引數,合并到一個新的list就完了
重點在于模板的模板引數,因為是偏特化,所以template中的順序無所謂,編譯器會推匯出正確的型別
template<typename ...args1> class list1因為模板類才有模板的模板引數,c++要求在這里寫一個class...(比較憨,但是你得寫...以后的標準可能改進這一點...
merge就沒了...(偷懶成功,大霧

2.實作split : 切割一個list為長度差不超過1的兩個list

split的實作就很lisp了,先康代碼

#define split(type) typename Split<type>::result
#define _split(type,typeb,typec) typename Split<type,typeb,typec>::result
	template<typename T,typename first = list<null>,typename second = list<null>>
	struct Split
	  {
	  	using new_first  = merge(first,list<car(T)>);
	  	using new_second = merge(second,list<car(T)>);
	  	using len_first = length(new_first);
	  	using len_second = length(new_second);
	  	using result = if_else(len_first::value < len_second::value,
	                               _split(cdr(T),new_first,second),
			               _split(cdr(T),first,new_second));			    
	  }; 
	template<typename first,typename second>
	struct Split<null,first,second>
	  {
	  	using result = pair<first,second>;
	  };

老樣子,先看簡單的第二個特化...
split概念上是一個帶引數的dfs(T,first,second),運算結果存在引數里面,T為空的時候運算結束,回傳引數作為計算結果
再看

#define split(type) typename Split<type>::result
#define _split(type,typeb,typec) typename Split<type,typeb,typec>::result
	template<typename T,typename first = list<null>,typename second = list<null>>
	struct Split
	  {
	  	using new_first  = merge(first,list<car(T)>);
	  	using new_second = merge(second,list<car(T)>);
	  	using len_first = length(new_first);
	  	using len_second = length(new_second);
	  	using result = if_else(len_first::value < len_second::value,
	                               _split(cdr(T),new_first,second),
			               _split(cdr(T),first,new_second));			    
	  }; 

注意偏特化不能有默認引數
思路很直觀,比較一下兩個引數new_first,new_second的長短,給短的添加當前元素
這里有一個陰間坑 : 如果你split實作的不對,比如 [1,2] -> [1,2],[] 也就是沒有正確切割...split會編譯通過,但是在某些計算中模板會報一大推錯...
老樣子,if_else實作一下,給短的添加一個元素即可...注意我們不能銷毀、改寫一個已經存在的型別...必須生成新的型別,代碼很直觀...
其實這樣子的元函式已經和普通函式差別不大了...只不過把值的運算變成了型別的運算

3.實作sort_merge : 合并兩個有序list為一個有序list
#define sort_merge(typea,typeb) typename Sort_merge<typea,typeb>::result
#define _sort_merge(typea,typeb,typec) typename Sort_merge<typea,typeb,typec>::result	
	template<typename list1,typename list2,typename ret = list<null>>
	struct Sort_merge
	  {
	  	using car1 = car(list1);
	  	using car2 = car(list2);
	  	using cdr1 = cdr(list1);
	  	using cdr2 = cdr(list2);
	  	using ret1 = merge(ret,list<car1>);
	  	using ret2 = merge(ret,list<car2>);
	  	using result = if_else(car1::value <= car2::value,
	                               _sort_merge(cdr1,list2,ret1),
				       _sort_merge(list1,cdr2,ret2));
	  };
	template<typename list1,typename ret>
	struct Sort_merge<list1,null,ret>
	  {
	  	using result = merge(ret,list1);
	  };
	template<typename list2,typename ret>
	struct Sort_merge<null,list2,ret>
	  {
	  	using result = merge(ret,list2);
	  };

老樣子,先看簡單的偏特化:)
和slpit類似,計算結果帶在引數ret里面
只有一個list的時候,直接和ret合并回傳,注意合并順序

#define sort_merge(typea,typeb) typename Sort_merge<typea,typeb>::result
#define _sort_merge(typea,typeb,typec) typename Sort_merge<typea,typeb,typec>::result	
	template<typename list1,typename list2,typename ret = list<null>>
	struct Sort_merge
	  {
	  	using car1 = car(list1);
	  	using car2 = car(list2);
	  	using cdr1 = cdr(list1);
	  	using cdr2 = cdr(list2);
	  	using ret1 = merge(ret,list<car1>);
	  	using ret2 = merge(ret,list<car2>);
	  	using result = if_else(car1::value <= car2::value,
	                               _sort_merge(cdr1,list2,ret1),
				       _sort_merge(list1,cdr2,ret2));
	  };

很直觀了...這里再強調一下,為什么,元型別也可以計算,但是我們不用
元型別通過繼承做元計算的時候,沒辦法宣告新型別
這導致了它的計算能力大大下降...直接寫元函式,簡單直觀,沒必要用一些詭異的技巧折磨自己
sort_merge就沒了

4.實作sort : 排序一個list

萬事俱備...輕松愉快

#define sort(type) typename Sort<type>::result	  
	template<typename T>
	struct Sort
	  {
	  	using left = car(split(T));
	  	using right = cdr(split(T));
	  	using result = sort_merge(sort(left),sort(right));
	  };
	template<typename T>
	struct Sort<list<T>>
	  {
	  	using result = list<T>;
	  };

是8是很直觀...某種意義上編譯期排序并不比普通排序難...來個栗子

因為函式式編程語言在語法簡潔性上有巨大優勢

編譯期計算8皇后

#include <bits/stdc++.h>
using namespace std;
int dfs(int n,int now = 0,int zuo = 0,int you = 0,int has = 0)
  {
  	if ((1 << n) - 1 == has)
  	  return 1;
  	int ret = 0;
  	int state = zuo | you | has;
  	state = ((1 << n) - 1) & (~state);
  	for (int i = 0;i < 32;i++)
  	  if ((1 << i) & (state))
  	    {
  	  	    int pos = (1 << i);
  	    	ret += dfs(n,now + 1,zuo + pos << 1,you + pos >> 1,has + pos);
		}
	return ret;
  }
int main()
{
    int n;
    cin >> n;
    cout << dfs(n);
    return 0;
}

我們把這個良好的實作挪到編譯期就行了...八皇后的實作還是比較復雜了...包含了一部分元編程難點...有興趣的自己看看代碼
https://www.cnblogs.com/XDU-mzb/p/14832089.html
完整實作過一遍模板元實作lisp,對編程能力有一個巨大的鍛煉

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

標籤:C++

上一篇:PAT乙級 1014 福爾摩斯的約會 (20 分)

下一篇:基礎知識:DFRduino UNO R3最全資料詳解

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