詳解μC/OS-II如何檢測任務堆疊實際使用情況——即如何設定ucosii任務堆疊大小
不少屌絲同學都有類似經歷吧,在使用ucosii建立任務時,關於任務堆疊大小設為多大合適搞的不清不楚,鬱悶之下就隨便整個數,比如就1024吧,呵呵,反正也沒見得出問題,那就不多想了。
我想大多數同學都是這樣做的吧。這樣只是因為在一般情況下,1024確實已經足夠大了,堆疊溢位的可能性很小而已。那麼,如果你任務實際使用率只有很小的百分之幾,一旦被你知道了,你會痛心不?我想你不痛心,μC/OS-II也會痛心的,它會覺得這個coder真是浪費啊,哈哈,這其實還好,至少只是太大導致浪費而已,萬一小了那可就堆疊溢位——慘啦!順便提醒下大家,堆和棧是完全不同的兩個概念,出於國內習慣,還是稱之為堆疊罷了!
下面,我就來告訴大家怎麼知道執行中任務的堆疊實際使用情況,然後就知道應該分配多少堆疊大小合適了!開始正題。
1、首先需要知道,μC/OS-II中建立任務的函式有兩個: OSTaskCreate()和OSTaskCreateExt()
(1)OSTaskCreate() //建立普通任務
由於重點在下面的建立擴充套件任務函式,故本函式就不多說了!確實,要想實現檢測目標任務棧實際使用情況的功能,是不能使用這個函式來建立目標任務的,必須使用OSTaskCreateExt() 。
(2)OSTaskCreateExt() //建立擴充套件任務
函式介面原型為:
#if OS_TASK_CREATE_EXT_EN > 0
INT8U OSTaskCreateExt
(
void (*task)(void *pd), //建立擴充套件任務(任務程式碼指標
void *pdata, //傳遞引數指標
OS_STK *ptos, //分配任務堆疊棧頂指標
INT8U prio, //分配任務優先順序
INT16U id, //(未來的)優先順序標識(與優先順序相同)
OS_STK *pbos, //分配任務堆疊棧底指標
INT32U stk_size, //指定堆疊的容量(檢驗用)
void *pext, //指向使用者附加的資料域的指標
INT16U opt //建立任務設定選項
)
#endif
2、其次需要知道μC/OS-II中有這麼個函式:OSTaskStkChk()
不錯,檢測任務堆疊實際使用情況正是用的這個函式,下面來本函式的介面原型:
INT8U OSTaskStkChk
(
INT8U prio, //待測任務的優先順序
OS_STK_DATA *pdata //指向一個型別為OS_STK_DATA的結構體
)
3、再次需要知道一個結構體:
#if OS_TASK_CREATE_EXT_EN > 0
typedef struct
{
INT32U OSFree; //堆疊中未使用的位元組數
INT32U OSUsed; //堆疊中已使用的位元組數
} OS_STK_DATA;
#endif
引數: prio 為指定要獲取堆疊資訊的任務優先順序,也可以指定引數OS_PRIO_SELF,獲取呼叫任務本身的
資訊。
pdata 指向一個型別為OS_STK_DATA的資料結構,其中包含如下資訊:
INT32U OSFree; // 堆疊中未使用的位元組數
INT32U OSUsed; // 堆疊中已使用的位元組數
4、有了上述三個知識點後就可以啦,具體方法為:
(1)將函式的最後一個引數opt 設定為:OS_TASK_OPT_STK_CHK | OS_TASK_OPT_STK_CLR
(2)定義一個變數:OS_STK_DATA StackBytes;
(3)呼叫函式OSTaskStkChk(TestTaskPRIO, &StackBytes)
(4)StackBytes.OSFree的值即為被測任務堆疊未使用的位元組數,
StackBytes.OSUsed的值即為被測任務堆疊已使用的位元組數。
5、需要設定巨集:OS_TASK_OPT_STK_CLR為1
6、最後一點建議:
(1)將被測任務經歷最壞的堆疊使用狀態,測出來的使用率才可靠
(2)堆疊使用率最好在%50~%80之間,太小浪費空間,太大不安全
(3)最好在工程中單獨建立一個優先順序較低延時較長的任務來測試其它任務的堆疊使用情況,不用時可以掛起該任務
有時候決定任務實際所需的堆疊空間大小是很有必要的。因為這樣使用者就可以避免為任務分配過多的堆疊空間,從而減少自己的應用程式程式碼所需的RAM(記憶體)數量。?C/OS-Ⅱ提供的OSTaskStkChk()函式可以為使用者提供這種有價值的資訊。
在圖4.2中,筆者假定堆疊是從上往下遞減的(即OS_STK_GROWTH被置為1),但以下的討論也同樣適用於從下往上長的堆疊[F4.2(1)]。?C/OS-Ⅱ是通過檢視堆疊本身的內容來決定堆疊的方向的。只有核心或是任務發出堆疊檢驗的命令時,堆疊檢驗才會被執行,它不會自動地去不斷檢驗任務的堆疊使用情況。在堆疊檢驗時,?C/OS-Ⅱ要求在任務建立的時候堆疊中儲存的必須是0值(即堆疊被清零)[F4.2(2)]。另外,?C/OS-Ⅱ還需要知道堆疊棧底(BOS)的位置和分配給任務的堆疊的大小[F4.2(2)]。在任務建立的時候,BOS的位置及堆疊的這兩個值儲存在任務的OS_TCB中。
為了使用?C/OS-Ⅱ的堆疊檢驗功能,使用者必須要做以下幾件事情:
l 在OS_CFG.H檔案中設OS_TASK_CREATE_EXT為1。
l 用OSTaskCreateExt()建立任務,並給予任務比實際需要更多的記憶體空間。
l 在OSTaskCreateExt()中,將引數opt設定為OS_TASK_OPT_STK_CHK+OS_TASK_OPT_STK_
CLR。注意如果使用者的程式啟動程式碼清除了所有的RAM,並且從未刪除過已建立了的任務,那麼使用者就不必設定選項OS_TASK_OPT_STK_CLR了。這樣就會減少OSTaskCreateExt()的執行時間。
l 將使用者想檢驗的任務的優先順序作為OSTaskStkChk()的引數並呼叫之。
圖 4.2 堆疊檢驗
OSTaskStkChk()順著堆疊的棧底開始計算空閒的堆疊空間大小,具體實現方法是統計儲存值為0的連續堆疊入口的數目,直到發現儲存值不為0的堆疊入口[F4.2(5)]。注意堆疊入口的儲存值在進行檢驗時使用的是堆疊的資料型別(參看OS_CPU.H中的OS_STK)。換句話說,如果堆疊的入口有32位寬,對0值的比較也是按32位完成的。所用的堆疊的空間大小是指從使用者在OSTaskCreateExt()中定義的堆疊大小中減去了儲存值為0的連續堆疊入口以後的大小。OSTaskStkChk()實際上把空閒堆疊的位元組數和已用堆疊的位元組數放置在0S_STK_DATA資料結構中(參看?COS_Ⅱ.H)。注意在某個給定的時間,被檢驗的任務的堆疊指標可能會指向最初的堆疊棧頂(TOS)與堆疊最深處之間的任何位置[F4.2(7)]。每次在呼叫OSTaskStkChk()的時候,使用者也可能會因為任務還沒觸及堆疊的最深處而得到不同的堆疊的空閒空間數。
使用者應該使自己的應用程式執行足夠長的時間,並且經歷最壞的堆疊使用情況,這樣才能得到正確的數。一旦OSTaskStkChk()提供給使用者最壞情況下堆疊的需求,使用者就可以重新設定堆疊的最後容量了。為了適應系統以後的升級和擴充套件,使用者應該多分配10%-100%的堆疊空間。在堆疊檢驗中,使用者所得到的只是一個大致的堆疊使用情況,並不能說明堆疊使用的全部實際情況。
OSTaskStkChk()函式的程式碼如程式清單 L4.10所示。0S_STK_DATA(參看?COS_Ⅱ.H)資料結構用來儲存有關任務堆疊的資訊。筆者打算用一個數據結構來達到兩個目的。第一,把OSTaskStkChk()當作是查詢型別的函式,並且使所有的查詢函式用同樣的方法返回,即返回查詢資料到某個資料結構中。第二,在資料結構中傳遞資料使得筆者可以在不改變OSTaskStkChk()的API(應用程式程式設計介面)的條件下為該資料結構增加其它域,從而擴充套件OSTaskStkChk()的功能。現在,0S_STK_DATA只包含兩個域:OSFree和OSUsed。從程式碼中使用者可看到,通過指定執行堆疊檢驗的任務的優先順序可以呼叫OSTaskStkChk()。如果使用者指定0S_PRIO_SELF[L4.10(1)],那麼就表明使用者想知道當前任務的堆疊資訊。當然,前提是任務已經存在[L4.10(2)]。要執行堆疊檢驗,使用者必須已用OSTaskCreateExt()建立了任務並且已經傳遞了選項OS_TASK_OPT_CHK[L4.10(3)]。如果所有的條件都滿足了,OSTaskStkChk()就會象前面描述的那樣從堆疊棧底開始統計堆疊的空閒空間[L4.10(4)]。最後,儲存在0S_STK_DATA中的資訊就被確定下來了[L4.10(5)]。注意函式所確定的是堆疊的實際空閒位元組數和已被佔用的位元組數,而不是堆疊的總位元組數。當然,堆疊的實際大小(用位元組表示)就是該兩項之和。
程式清單 L 4.10 堆疊檢驗函式 |
INT8U OSTaskStkChk (INT8U prio, OS_STK_DATA *pdata) |
{ |
OS_TCB *ptcb; |
OS_STK *pchk; |
INT32U free; |
INT32U size; |
pdata->OSFree = 0; |
pdata->OSUsed = 0; |
if (prio > OS_LOWEST_PRIO && prio != OS_PRIO_SELF) { |
return (OS_PRIO_INVALID); |
} |
OS_ENTER_CRITICAL(); |
if (prio == OS_PRIO_SELF) { (1) |
prio = OSTCBCur->OSTCBPrio; |
} |
ptcb = OSTCBPrioTbl[prio]; |
if (ptcb == (OS_TCB *)0) { (2) |
OS_EXIT_CRITICAL(); |
return (OS_TASK_NOT_EXIST); |
} |
if ((ptcb->OSTCBOpt & OS_TASK_OPT_STK_CHK) == 0) { (3) |
OS_EXIT_CRITICAL(); |
return (OS_TASK_OPT_ERR); |
} |
free = 0; (4) |
size = ptcb->OSTCBStkSize; |
pchk = ptcb->OSTCBStkBottom; |
OS_EXIT_CRITICAL(); |
#if OS_STK_GROWTH == 1 |
while (*pchk++ == 0) { |
free++; |
} |
#else |
while (*pchk-- == 0) { |
free++; |
} |
#endif |
pdata->OSFree = free * sizeof(OS_STK); (5) |
pdata->OSUsed = (size - free) * sizeof(OS_STK); |
return (OS_NO_ERR); |
} |
uCOS-III任務堆疊溢位檢測及統計任務堆疊使用量的方法
1. 在作業系統任務設計的時候,通常會遇到一個比較麻煩的問題,也就是任務 堆 棧大小設定的問題,為此我們我需要知道一些問題 :
1.1. 任務堆疊一但溢位,意味著系統的崩潰,在有MMU或者MPU的系統中,對堆疊溢位的檢測十分簡單,因為這是MMU和MPU必備的功能之一。(uCOS-II/uCOS-III中均有針對沒有MMU和MPU的處理器對堆疊溢位檢測的策略)
1.2. 堆疊的大小取決於該任務的需求。設定堆疊大小時,你就需要考慮:所有可能被堆疊呼叫的函式及其函式的巢狀層數,相關區域性變數的大小,中斷服務程式所需要的空間。另外,堆疊還需存入CPU暫存器,如果處理器有浮點數單元FPU暫存器的話還需存入FPU暫存器。(PS:出於這點,所以在嵌入式系統中有個潛規則,避免寫遞迴函式)
1.3. 雖然任務堆疊大小可以通過人工計算出來,但是要考慮的太多,而且不能十分精確的計算。比如逐級巢狀被呼叫的函式的引數使用,上下文切換時候的CPU暫存器空間的儲存,中斷時CPU暫存器空間的儲存和中斷處理函式的堆疊空間等等,未免太過麻煩。特別的,當任務中使用了printf()之類引數可變的函式,那麼統計起來就更困難了。所以這種方式怎麼看怎麼不現實。囧 。
1.4. 建議在不是很精確的確定任務堆疊使用大小(stk_size)的情況下,還是採取stk_size乘以1.5或者2.0的做法,以保證任務的正常執行。
2. uCOS-III任務堆疊溢位檢測原理
每個任務都有自己的TCB(Task Control Block 任務控制塊), TCB結構定義在uCOS-III原始碼(我使用的是V3.03.00版本)中的os.h中。 TCB中有個 StkLimitPtr成員。
假設在切換到任務S前,程式碼會檢測將要被載入CPU堆疊指標的值是否超出該任務S的TCB中StkLimitPtr限制。因為軟體不能在溢位時就迅速地做出反應,所以應該設定StkLimitPtr的值儘可能遠離&MyTaskStk[0],保證有足夠的溢位緩衝。如下圖。軟體檢測不會像硬體檢測那樣有效,但也可以防止堆疊溢位。 當uC/OS-III從一個任務切換到另一個任務的時候,它會呼叫一個 hook函式OSTaskSwHook(),它允許使用者擴充套件上下文切換時的功能。 所以,如果處理器沒有硬體支援溢位檢測功能,就可以在該hook函 數中新增程式碼軟體模擬該能。
不過我個人的做法是,通常設定StkLimitPtr指向任務棧大小的90%處,然後獲取任務堆疊使用量,如果棧使用率大於90%時就必須做出警告了!下面就來介紹任務棧使用量的獲取。
圖1
3. uCOS-III任務堆疊使用量統計的原理和方法
3.1 原理
原理其實很簡單,就是統計連續為0的區域的大小就可以知道空閒棧free的大小,而任務在建立時任務棧總量TaskStkSize是確定的,那麼使用了的棧大小used = TaskStkSize - free。見圖2。
圖2
首先,當任務建立時其堆疊被清零。然後,通過一個低優先順序任務,計算該任務整個堆疊中值為0的記憶體大小。如果發現都不為0,那麼就需要擴充套件堆疊的大小。然後,調整堆疊為的相應大小。這是一種非常有效的方法。注意的是,程式需用執行很長的時間以讓堆疊達到其需要的最大值。
3.2 方法
uC/OS-III提供了一個函式OSTaskStkChk()用於實現這個計算功能。那麼就來看看具體怎麼做吧。
3.2.1 首先建立一個任務來執行任務堆疊統計工作,值得注意的是,這個任務的優先順序建議設定為系統所有任務中最低的一個!
#define SystemDatasBroadcast_PRIO 12 // 統計任務優先順序最低,我這裡是12,已經低於其他任務的優先順序了
#define SystemDatasBroadcast_STK_SIZE 100 // 任務的堆疊大小,做統計一般夠了,統計結果出來後不夠再加..
OS_TCB SystemDatasBroadcast_TCB; // 定義統計任務的TCB
CPU_STK SystemDatasBroadcast_STK [SystemDatasBroadcast_STK_SIZE];// 開闢陣列作為任務棧給任務使用
static void AppTaskCreate(void)
{
// .....
// 這是系統建立任務的函式,還有其他任務建立的程式碼,這裡就不貼出了
// .....
OSTaskCreate( (OS_TCB *)&SystemDatasBroadcast_TCB,
(CPU_CHAR *)"SystemDatasBroadcast",
(OS_TASK_PTR ) SystemDatasBroadcast,
(void *) 0,
(OS_PRIO ) SystemDatasBroadcast_PRIO,
(CPU_STK *)&SystemDatasBroadcast_STK[0],
(CPU_STK_SIZE) SystemDatasBroadcast_STK_SIZE/10,/*棧溢位臨界值我設定在棧大小的90%處*/
(CPU_STK_SIZE) SystemDatasBroadcast_STK_SIZE,
(OS_MSG_QTY ) 0,
(OS_TICK ) 0,
(void *) 0,
(OS_OPT )(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),
(OS_ERR *) &err);
}
3.2.2 然後在任務函式SystemDatasBroadcast()中開始統計各個任務 (其他任務的程式碼就不貼了, 跟
SystemDatasBroadcast 建立程式碼的方法是一模一樣的,大家根據自己的需求照葫蘆畫瓢即可 )的棧使用,程式碼如下:
void SystemDatasBroadcast (void *p_arg)
{
OS_ERR err;
CPU_STK_SIZE free,used;
(void)p_arg;
while(DEF_TRUE)
{
OSTaskStkChk (&SystemDatasBroadcast_TCB,&free,&used,&err);
printf("SystemDatasBroadcast used/free:%d/%d usage:%%%d\r\n",used,free,(used*100)/(used+free));
OSTaskStkChk (&Core_Page_TCB,&free,&used,&err);
printf("Core_Page used/free:%d/%d usage:%%%d\r\n",used,free,(used*100)/(used+free));
OSTaskStkChk (&GUIActive_TCB,&free,&used,&err);
printf("GUIActive used/free:%d/%d usage:%%%d\r\n",used,free,(used*100)/(used+free));
OSTaskStkChk (&KeyCheck_Process_TCB,&free,&used,&err);
printf("KeyCheck used/free:%d/%d usage:%%%d\r\n",used,free,(used*100)/(used+free));
OSTaskStkChk (&Light_Adjust_TCB,&free,&used,&err);
printf("Light_Adjust used/free:%d/%d usage:%%%d\r\n",used,free,(used*100)/(used+free));
OSTaskStkChk (&Calibrate_Process_TCB,&free,&used,&err);
printf("Calibrate used/free:%d/%d usage:%%%d\r\n",used,free,(used*100)/(used+free));
OSTaskStkChk (&Data_Process_TCB,&free,&used,&err);
printf("Data_Process used/free:%d/%d usage:%%%d\r\n",used,free,(used*100)/(used+free));
printf("\r\n\r\n\r\n");
OSTimeDlyHMSM(0,0,5,0,(OS_OPT)OS_OPT_TIME_DLY,(OS_ERR*)&err);
}
}
3.2.3實驗結果 上述程式碼的實驗結果如下圖所示,可以清楚的看到每個任務的堆疊的使用狀況。 但是請遵循一個原則:必須讓系統執行足夠久,比如儘量讓系統處於不同的執行狀態下,然後觀察任務堆疊使用的變化,找到堆疊的最高使用率,然後根據上文所說的原則按需重新分配新的任務堆疊大小。
圖3
相關推薦
詳解μC/OS-II如何檢測任務堆疊實際使用情況——即如何設定ucosii任務堆疊大小
不少屌絲同學都有類似經歷吧,在使用ucosii建立任務時,關於任務堆疊大小設為多大合適搞的不清不楚,鬱悶之下就隨便整個數,比如就1024吧,呵呵,反正也沒見得出問題,那就不多想了。 我想大多數同學都是這樣做的吧。這樣只是因為在一般情況下,1024確實已經足夠
μC/OS-II與RT-Thread對比——任務排程
RT-Thread中提供的執行緒排程器是基於優先順序的全搶佔式排程:在系統中除了中斷處理函式、排程器上鎖部分的程式碼和禁止中斷的程式碼是不可搶佔的之外,系統的其他部分都是可以搶佔的,包括執行緒排程器自身。系統總共支援256個優先順序(0 ~ 255,數值越小的優先順序越高,0為最高優先順序,2
μC/OS-II 的任務排程演算法分析
/* ********************************************************************************************************* * PRIORI
µC/OS-II核心任務排程模組的擴充套件
摘 要:µC/OS-II是一個實時作業系統核心,支援64種不同優先順序的任務。以簡單實用為原則,借用核心中的兩個優先順序任務,充當時鐘源和輪詢引擎,讓同級任務在最低優先順序任務下輪流執行。這在不失實時性的前提下,讓核心支援多達192個同級任務,極大地擴充套件了µC/OS
詳解C# 網絡編程系列:實現類似QQ的即時通信程序
並且 會話 hat chat .sh odin unicode 情況 plist 引言: 前面專題中介紹了UDP、TCP和P2P編程,並且通過一些小的示例來讓大家更好的理解它們的工作原理以及怎樣.Net類庫去實現它們的。為了讓大家更好的理解我們平常中常見的軟件QQ的工作原理
詳解C# Tuple VS ValueTuple(元組類 VS 值元組)
edit 成員 擴展 ati art info ets 簡單 ole C# 7.0已經出來一段時間了,大家都知道新特性裏面有個對元組的優化,並且網上也有大量的介紹,這裏利用詳盡的例子詳解Tuple VS ValueTuple(元組類VS值元組),10分鐘讓你更了解Value
(轉)詳解C#中的反射
typeof ref enc sin setvalue abs class 方法測試 strac (轉)http://www.cnblogs.com/Stephenchao/p/4481995.html 反射的用途: (1)使用Assembly定義和加載程序集,加載在
函數中{}輸出格式詳解(C#)
with content c-s sort 命名方式 發現 1-1 原始的 des Console.WriteLine()函數中{}輸出格式詳解(C#) Console.WriteLine()函數的格式一直沒怎麽註意。今天同事問起Console.WriteLine({0
詳解C#7.0新特性
numeric base rdquo 字母 and throw cal odin png 1. out 變量(out variables) 以前我們使用out變量必須在使用前進行聲明,C# 7.0 給我們提供了一種更簡潔的語法 “使用時進行內聯聲明&r
詳解C# 叠代器【轉】
mov 添加 除了 叠代 RF 發生 time 初始 奇怪 叠代器模式是設計模式中行為模式(behavioral pattern)的一個例子,他是一種簡化對象間通訊的模式,也是一種非常容易理解和使用的模式。簡單來說,叠代器模式使得你能夠獲取到序列中的所有元素而不
Adaboost算法詳解(haar人臉檢測)
nim -s current center one features 重疊 asc his Adaboost是一種叠代算法,其核心思想是針對同一個訓練集訓練不同的分類器(弱分類器),然後把這些弱分類器集合起來,構成一個更強的最終分類器(強分類器)。Adaboost算法本身是
詳解C#特性和反射(三)
typeinfo ref 都是 system.in 全局 color com 依然 程序 類型信息(Type Information)用來表示類型聲明的信息,通過抽象基類System.Type的實例存儲這些信息,當使用反射時,CLR獲取指定類型的Type對象,通過這個對
詳解C#泛型(一)
安全 情況 重用 模板 信息 普通 cast 綁定 封閉式 一、C#中的泛型引入了類型參數的概念,類似於C++中的模板,類型參數可以使類型或方法中的一個或多個類型的指定推遲到實例化或調用時,使用泛型可以更大程度的重用代碼、保護類型安全性並提高性能;可以創建自定義的泛型類
詳解C# 網路程式設計系列:實現類似QQ的即時通訊程式
https://www.jb51.net/article/101289.htm 引言: 前面專題中介紹了UDP、TCP和P2P程式設計,並且通過一些小的示例來讓大家更好的理解它們的工作原理以及怎樣.Net類庫去實現它們的。為了讓大家更好的理解我們平常中常見的軟體QQ的工作原理,所以在本專題
詳解C語言在字串的指定位置插入字元
問題分析 在字串S的所有數字字元前加一個$字元,可以有兩種實現方法。方法一:用串S拷貝出另一個串T,對串T從頭至尾掃描,對非數字字元原樣寫入串S,對於數字字元先寫一個$符號再寫該數字字元,最後,在S串尾加結束標誌。使用此方法是犧牲空間,贏得時間。方法二:對串S從頭至尾掃描,當遇到數字字元時,從該字元至串
各種排序演算法詳解C++實現
1.氣泡排序 時間複雜度 O ( n
詳解C++中基類與派生類的轉換以及虛基類
原文來源:https://www.jb51.net/article/72586.htm# C++基類與派生類的轉換 在公用繼承、私有繼承和保護繼承中,只有公用繼承能較好地保留基類的特徵,它保留了除建構函式和解構函式以外的基類所有成員,基類的公用或保護成員的訪問許可權在派生類中全部都按原樣保留下來
詳解C#委託和事件(二)
一、當我們使用關鍵字delegate宣告一個自定義委託型別時,實際上是聲明瞭一個該名稱的類型別,繼承自抽象類System.MulticastDelegate,還包含例項方法Invoke、BeginInvoke、EndInvoke: public delegate void MyDelegate
詳解C程式的編譯過程
環境 在C語言的任何一種實現中,存在兩種不同的環境,分別是翻譯環境和執行環境。這兩種環境並不一定必須同時位於一臺機器上。 翻譯環境 翻譯環境即是為C語言程式碼從程式碼轉變為機器可以執行的二進位制檔案過程中所要依賴的環境。 執行環境 執行環境即用於實際執行程式碼。
詳解C#泛型(三) 詳解C#泛型(一) 詳解C#泛型(二)
一、前面兩篇文章分別介紹了定義泛型型別、泛型委託、泛型介面以及宣告泛型方法: 詳解C#泛型(一) 詳解C#泛型(二) 首先回顧下如何構建泛型類: public class MyClass<T> { public void MyFunc() {