茄子在线看片免费人成视频,午夜福利精品a在线观看,国产高清自产拍在线观看,久久综合久久狠狠综合

    <s id="ddbnn"></s>
  • <sub id="ddbnn"><ol id="ddbnn"></ol></sub>

  • <legend id="ddbnn"></legend><s id="ddbnn"></s>

    C++泛型用法
    來源:易賢網(wǎng) 閱讀:2152 次 日期:2014-09-05 15:49:24
    溫馨提示:易賢網(wǎng)小編為您整理了“C++泛型用法”,方便廣大網(wǎng)友查閱!

    我們先來看一個最為常見的泛型類型List<T>的定義

    (真正的定義比這個要復雜的多,我這里刪掉了很多東西)

    [Serializable]

    public class List<T> : IList<T>, ICollection<T>, IEnumerable<T>

    {

    public T this[int index] { get; set; }

    public void Add(T item);

    public void Clear();

    public bool Contains(T item);

    public int IndexOf(T item);

    public bool Remove(T item);

    public void Sort();

    public T[] ToArray();

    }

    List后面緊跟著一個<T>表示它操作的是一個未指定的數(shù)據(jù)類型(T代表著一個未指定的數(shù)據(jù)類型)

    可以把T看作一個變量名,T代表著一個類型,在List<T>的源代碼中任何地方都能使用T。

    T被用作方法的參數(shù)和返回值。

    Add方法接收T類型的參數(shù),ToArray方法返回一個T類型的數(shù)組

    注意:

    泛型參數(shù)必須以T開頭,要么就叫T,要么就叫TKey或者TValue;

    這跟接口要以I開頭是一樣的,這是約定。

    下面來看一段使用泛型類型的代碼

    var a = new List<int>();

    a.Add(1);

    a.Add(2);

    //這是錯誤的,因為你已經(jīng)指定了泛型類型為int,就不能在這個容器中放入其他的值

    //這是編譯器錯誤,更提升了排錯效率,如果是運行期錯誤,不知道要多么煩人

    a.Add("3");

    var item = a[2];

    請注意上面代碼里的注釋

    二、泛型的作用(1):

    作為程序員,寫代碼時刻不忘代碼重用。

    代碼重用可以分成很多類,其中算法重用就是非常重要的一類,假設(shè)你要為一組整型數(shù)據(jù)寫一個排序算法,又要為一組浮點型數(shù)據(jù)寫一個排序算法,如果沒有泛型類型,你會怎么做呢?

    你可能想到了方法的重載。

    寫兩個同名方法,一個方法接收整型數(shù)組,另一個方法接收浮點型的數(shù)組。

    但有了泛型,你就完全不必這么做,只要設(shè)計一個方法就夠用了,你甚至可以用這個方法為一組字符串數(shù)據(jù)排序。

    三、泛型的作用(2):

    假設(shè)你是一個方法的設(shè)計者,這個方法需要有一個輸入?yún)?shù),但你并能確定這個輸入?yún)?shù)的類型,那么你會怎么做呢?

    有一部分人可能會馬上反駁:“不可能有這種時候!”

    那么我會跟你說,編程是一門經(jīng)驗型的工作,你的經(jīng)驗還不夠,還沒有碰到過類似的地方。

    另一部分人可能考慮把這個參數(shù)的類型設(shè)置成Object的,這確實是一種可行的方案,但會造成下面兩個問題,如果我給這個方法傳遞整形的數(shù)據(jù)(值類型的數(shù)據(jù)都一樣),就會產(chǎn)生額外的裝箱、拆箱操作,造成性能損耗。

    如果你這個方法里的處理邏輯不適用于字符串的參數(shù),而使用者又傳了一個字符串進來,編譯器是不會報錯的,只有在運行期才會報錯。

    (如果質(zhì)管部門沒有測出這個運行期BUG,那么不知道要造成多大的損失呢)

    這就是我們常說的:類型不安全。

    四、泛型的示例:

    像List<T>和Dictionary<TKey,TValue>之類的泛型類型我們經(jīng)常用到,下面我介紹幾個不常用到的泛型類型。

    ObservableCollection<T>

    當這個集合發(fā)生改變后會有相應的事件得到通知。

    請看如下代碼:

    static void Main(string[] args)

    {

    var a = new ObservableCollection<int>();

    a.CollectionChanged += a_CollectionChanged;

    }

    static void a_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)

    {

    //可以通過Action來判斷是什么操作觸發(fā)了事件

    //e.Action == NotifyCollectionChangedAction.Add

    //可以根據(jù)以下兩個屬性來得到更改前和更改后的內(nèi)容

    //e.NewItems;

    //e.OldItems;

    }

    使用這個集合需要引用如下兩個名稱空間

    using System.Collections.ObjectModel;

    using System.Collections.Specialized;

    BlockingCollection<int>是線程安全的集合

    來看看下面這段代碼

    var bcollec = new BlockingCollection<int>(2);

    //試圖添加1-50

    Task.Run(() =>

    {

    //并行循環(huán)

    Parallel.For(1, 51, i =>

    {

    bcollec.Add(i);

    Console.WriteLine("加入:" + i);

    });

    });

    Thread.Sleep(1000);

    Console.WriteLine("調(diào)用一次Take");

    bcollec.Take();

    //等待無限長時間

    Thread.Sleep(Timeout.Infinite);

    輸出結(jié)果為:

    加入:1

    加入:37

    調(diào)用一次Take

    加入:13

    BlockingCollection<int>還可以設(shè)置CompleteAdding和IsCompleted屬性來拒絕加入新元素。

    .NET類庫還提供了很多的泛型類型,在這里就不一一例舉了。

    五、泛型的繼承:

    在.net中一切都繼承字Object,泛型也不例外,泛型類型可以繼承自其他類型。

    來看一下如下代碼

    public class MyType

    {

    public virtual string getOneStr()

    {

    return "base object Str";

    }

    }

    public class MyOtherType<T> : MyType

    {

    public override string getOneStr()

    {

    return typeof(T).ToString();

    }

    }

    class Program

    {

    static void Main(string[] args)

    {

    MyType target = new MyOtherType<int>();

    Console.WriteLine(target.getOneStr());

    Console.ReadKey();

    }

    }

    泛型類型MyOtherType<T>成功的重寫了非泛型類型MyType的方法。

    如果我試圖按如下方式從MyOtherType<T>類型派生子類型就會導致編譯器錯誤。

    //編譯期錯誤

    public class MyThirdType : MyOtherType<T>

    {

    }

    但是如果寫成這種方式,就不會出錯

    public class MyThirdType : MyOtherType<int>

    {

    public override string getOneStr()

    {

    return "MyThirdType";

    }

    }

    注意:

    如果按照如上寫法,會造成類型不統(tǒng)一的問題,

    如果一個方法接收MyThirdType類型的參數(shù),

    那么不能將一個MyOtherType<int>的實例傳遞給這個方法, 

    然而一個方法如果接收MyOtherType<int>類型的參數(shù),

    卻可以把MyThirdType類型的實例傳遞給這個方法,

    這是CLR內(nèi)部實現(xiàn)機制造成的,

    這看起來確實很怪異!

    寫成如下方式也不會出錯:

    public class MyThirdType<T> : MyOtherType<T>

    {

    public override string getOneStr()

    {

    return typeof(T).ToString() + " from MyThirdType";

    }

    }

    此中訣竅,只可意會,不可言傳。

    六、泛型接口

    .NET類庫里有很多泛型的接口,比如:IEnumerator<T>、IList<T>等,這里不對這些接口做詳細描述了,值說說為什么要有泛型接口。

    其實泛型接口出現(xiàn)的原因和泛型出現(xiàn)的原因類似,拿IComparable這個接口來說,此接口只描述了一個方法:

    int CompareTo(object obj);

    大家看到,如果是值類型的參數(shù),勢必會導致裝箱和拆箱操作。

    同時,也不是強類型的,不能在編譯期確定參數(shù)的類型,有了IComparable<T>就解決掉這個問題了:

    int CompareTo(T other);

    七、泛型委托

    委托描述方法,泛型委托的由來和泛型接口類似。

    定義一個泛型委托也比較簡單:

    public delegate void MyAction<T>(T obj);

    這個委托描述一類方法,這類方法接收T類型的參數(shù),沒有返回值。

    來看看使用這個委托的方法:

    public delegate void MyAction<T>(T obj);

    static void Main(string[] args)

    {

    var method = new MyAction<int>(printInt);

    method(3);

    Console.ReadKey();

    }

    static void printInt(int i)

    {

    Console.WriteLine(i);

    }

    由于定義委托比較繁瑣,.NET類庫在System名稱空間,下定義了三種比較常用的泛型委托。

    Predicate<T>委托:

    public delegate bool Predicate<T>(T obj);

    這個委托描述的方法為接收一個T類型的參數(shù),返回一個BOOL類型的值,一般用于比較方法。

    Action<T>委托

    public delegate void Action<T>(T obj);

    public delegate void Action<T1, T2>(T1 arg1, T2 arg2);

    這個委托描述的方法,接收一個或多個T類型的參數(shù)(最多16個,我這里只寫了兩種類型的定義方式),沒有返回值。

    Func<T>委托

    public delegate TResult Func<TResult>();

    public delegate TResult Func<T, TResult>(T arg);

    這個委托描述的方法,接收零個或多個T類型的參數(shù)(最多16個,我這里只寫了兩種類型的定義方式),與Action委托不同的是,它有一個返回值,返回值的類型為TResult類型的。

    關(guān)于委托的描述,您還可以看我這篇文章。

    八、泛型方法

    泛型類型中的T可以用在這個類型的任何地方,然而有些時候,我們不希望在使用類型的時候就指定T的類型,我們希望在使用這個類型的方法時,再指定T的類型。

    來看看如下代碼:

    public class MyClass

    {

    public TParam CompareTo<TParam>(TParam other)

    {

    Console.WriteLine(other.ToString());

    return other;

    }

    }

    上面的代碼中MyClass并不是一個泛型類型,但這個類型中的CompareTo<TParam>()卻是一個泛型方法,TParam可以用在這個方法中的任何地方。

    使用泛型方法一般用如下代碼就可以了:

    obj.CompareTo<int>(4);

    obj.CompareTo<string>("ddd");

    然而,你可以寫的更簡單一些,寫成如下的方式:

    obj.CompareTo(2);

    obj.CompareTo("123");

    有人會問:“這不可能,沒有指定CompareTo方法的TParam類型,肯定會編譯出錯的”

    我告訴你:不會的,編譯器可以幫你完成類型推斷的工作。

    注意:

    如果你為一個方法指定了兩個泛型參數(shù),而且這兩個參數(shù)的類型都是T,那么如果你想使用類型推斷,你必須傳遞兩個相同類型的參數(shù)給這個方法,不能一個參數(shù)用string類型,另一個用object類型,這會導致編譯錯誤。

    九、泛型約束

    我們設(shè)計了一個泛型類型,很多時候,我們不希望使用者傳入任意類型的參數(shù),也就是說,我們希望“約束”一下T的類型。

    來看看如下代碼:

    public class MyClass<T> where T : IComparable<T>

    {

    public int CompareTo(T other)

    {

    return 0;

    }

    }

    上面的代碼要求T類型必須實現(xiàn)了IComparable<T>接口。

    如你所見:泛型的約束通過關(guān)鍵字where來實現(xiàn)。

    泛型方法當然也可以通過類似的方式對泛型參數(shù)進行約束。

    請看如下代碼:

    public class MyClass

    {

    public TParam CompareTo<TParam>(TParam other) where TParam:class

    {

    Console.WriteLine(other.ToString());

    return other;

    }

    }

    上面代碼中用了class關(guān)鍵字約束泛型參數(shù)TParam;具體稍后解釋。

    注意1:

    如果我有一個類型也定義為MyClass<T>但沒有做約束,那么這個時候,做過約束的MyClass<T>將與沒做約束的MyClass<T>沖突,編譯無法通過。

    注意2:

    當你重寫一個泛型方法時,如果這個方法指定了約束,在重寫這個方法時,不能再指定約束了。

    注意3:

    雖然我上面的例子寫的是接口約束,但你完全可以寫一個類型,比如說BaseClass。而且,只要是繼承自BaseClass的類型都可以當作T類型使用,你不要試圖約束T為Object類型,編譯不會通過的。(傻子才這么干)

    注意4:

    有兩個特殊的約束:class和struct。

    where T : class 約束T類型必須為引用類型

    where T : struct 約束T類型必須為值類型

    注意5:

    如果你沒有對T進行class約束,

    那么你不能寫這樣的代碼:T obj = null; 這無法通過編譯,因為T有可能是值類型的。

    如果你沒有對T進行struct約束,也沒有對T進行new約束。

    那么你不能寫這樣的代碼:T obj = new T(); 這無法通過編譯,因為值類型肯定有無參數(shù)構(gòu)造器,而引用類型就不一定了。

    如果你對T進行了new約束:where T : new(); 那么new T()就是正確的,因為new約束要求T類型有一個公共無參構(gòu)造器。

    注意6:

    就算沒有對T進行任何約束,也有一個辦法來處理值類型和引用類型的問題。

    T temp = default(T);

    如果T為引用類型,那么temp就是null;如果T為值類型,那么temp就是0;

    注意7:

    試圖對T類型的變量進行強制轉(zhuǎn)化,一般情況下會報編譯期錯誤。

    但你可以先把T轉(zhuǎn)化成object再把object轉(zhuǎn)化成你要的類型(一般不推薦這么做,你應該考慮把T轉(zhuǎn)化成一個約束兼容的類型)。

    你也可以考慮用as操作符進行類型轉(zhuǎn)化,這一般不會報錯,但只能轉(zhuǎn)化成引用類型。

    關(guān)于泛型約束的內(nèi)容,我在這篇文章里也有提到。

    十、逆變和協(xié)變

    一般情況下,我們使用泛型時,由T標記的泛型類型是不能更改的。

    也就是說,如下兩種寫法都是錯誤的:

    var a = new List<object>();

    List<string> b = a;

    var c = new List<string>();

    List<object> d = c;

    注意:這里沒有寫強制轉(zhuǎn)換,即使寫了強制轉(zhuǎn)換也是錯誤的,編譯就無法通過,然而泛型提供了逆變和協(xié)變的特性,有了這兩種特性,這種轉(zhuǎn)換就成為了可能。

    逆變:

    泛型類型T可以從基類型更改為該類的派生類型,用in關(guān)鍵字標記逆變形式的類型參數(shù),而且這個參數(shù)一般作輸入?yún)?shù)。

    協(xié)變:

    泛型類型T可以從派生類型更改為它的基類型,用out關(guān)鍵字來標記協(xié)變形式的類型參數(shù),而且這個參數(shù)一般作為返回值。

    如果我們定義了一個這樣的委托:

    public delegate TResult MyAction<in T,out TResult>(T obj);

    那么,就可以讓如下代碼通過編譯(不用強制轉(zhuǎn)換)

    var a = new MyAction<object, ArgumentException>(o => new ArgumentException(o.ToString()));

    MyAction<string, Exception> b = a;

    這就是逆變和協(xié)變的威力。

    更多信息請查看IT技術(shù)專欄

    更多信息請查看網(wǎng)絡(luò)編程
    易賢網(wǎng)手機網(wǎng)站地址:C++泛型用法

    2026上岸·考公考編培訓報班

    • 報班類型
    • 姓名
    • 手機號
    • 驗證碼
    關(guān)于我們 | 聯(lián)系我們 | 人才招聘 | 網(wǎng)站聲明 | 網(wǎng)站幫助 | 非正式的簡要咨詢 | 簡要咨詢須知 | 新媒體/短視頻平臺 | 手機站點 | 投訴建議
    工業(yè)和信息化部備案號:滇ICP備2023014141號-1 云南省教育廳備案號:云教ICP備0901021 滇公網(wǎng)安備53010202001879號 人力資源服務(wù)許可證:(云)人服證字(2023)第0102001523號
    聯(lián)系電話:0871-65099533/13759567129 獲取招聘考試信息及咨詢關(guān)注公眾號:hfpxwx
    咨詢QQ:1093837350(9:00—18:00)版權(quán)所有:易賢網(wǎng)