C# Notizen 11 泛型

object 變量可指向任何類的實(shí)例,這讓你能夠創(chuàng)建可對(duì)任何數(shù)據(jù)類型進(jìn)程處理的類。然而,這種方法存在幾個(gè)嚴(yán)重的問(wèn)題。使用object時(shí),類無(wú)法將輸入限定為特定類型;要對(duì)數(shù)據(jù)執(zhí)行有意義的操作,必須將其從object轉(zhuǎn)換為更具體的類型。這不但增加了復(fù)雜性,還在編譯階段犧牲了類型安全。
C#泛型避免了轉(zhuǎn)換(即裝箱和取消裝箱),讓通用化在編譯階段是類型安全的,從而解決了上述問(wèn)題。泛型提供了非泛型類無(wú)法實(shí)現(xiàn)的類型安全、可重用性和效率。泛型通常與集合一起使用,但也可使用泛型來(lái)創(chuàng)建自定義泛型類型和泛型方法。

一、為什么應(yīng)使用泛型
由于數(shù)組每個(gè)元素的數(shù)據(jù)類型都被顯式地聲明為int,編譯器將確保只能將int值賦給每個(gè)元素。還使用為int值定義的方法和運(yùn)算符對(duì)元素進(jìn)行了操作。
如下所示的代碼可用于找出任何int數(shù)組中的最小值。

public int Min(int[] values)
{    
    int min = values[0];    
    foreach (int value in values)    
    {        
        if (value.CompareTo(min) < 0)        
        {            
             min = value;        
        }    
    }    
    return min;
}

如果希望這些代碼可用于任何數(shù)值數(shù)組,該怎么辦呢?如果不使用泛型,就需要為每種數(shù)值類型編寫(xiě)一個(gè)版本的Min,這些版本只是數(shù)據(jù)類型不同。這雖然可行,但是代碼很復(fù)雜,并且很多代碼是重復(fù)的。

如果知道IComparable接口定義了一個(gè)CompareTo方法,就可使用object編寫(xiě)更通用的代碼。這只需編寫(xiě)代碼一次,而這些代碼可用于任何數(shù)值類型的數(shù)組,如下代碼所示:

public int Min(object[] values)
{    
    IComparable min = (IComparable)values[0];    
    foreach (object value in values)    
    {        
        if (((IComparable)value).CompareTo(min) < 0)        
        {            
            min = (IComparable)value;        
        }    
    }    
    return min;
}

不幸的是,雖然只需編寫(xiě)代碼一次帶來(lái)了一定的好處,但是類型安全也喪失殆盡。另外, int數(shù)組不能轉(zhuǎn)換為object數(shù)組。鑒于這個(gè)方法適用于object,如果給它傳遞一個(gè)類似于下面這樣的數(shù)組,結(jié)果將如何呢?

object[] array = { 5, 3, "a", "hello" };

這是合法的,因?yàn)閿?shù)組存儲(chǔ)的是object元素,因此任何值都將隱式地轉(zhuǎn)換為object。

不僅類型安全喪失殆盡,還執(zhí)行了n+1次轉(zhuǎn)換操作,其中n是數(shù)組包含的元素?cái)?shù)。數(shù)組越大,這個(gè)方法的開(kāi)銷越高。
借助于泛型,這個(gè)問(wèn)題將變得很簡(jiǎn)單。只需編寫(xiě)代碼一次,而不會(huì)喪失類型安全或執(zhí)行多次轉(zhuǎn)換操作。如下代碼顯示了使用泛型定義的Min方法

public T Min<T>(T[] values) where T : IComparable<T>
{    
    T min = values[0];    
    foreach (T value in values)    
    {        
        if (value.CompareTo(min) < 0)        
        {            
            min = value;        
        }    
    }    
    return min;
}

ps:where T : IComparable<T>

這個(gè)約束可能有點(diǎn)令人迷惑,因?yàn)榭雌饋?lái)好像存在循環(huán)依存關(guān)系。事實(shí)上,它很簡(jiǎn)單,意味著T必須是可比較的類型。

最大的不同在于,泛型版本使用了泛型類型參數(shù) T,這是在方法名后面使用語(yǔ)法<T>指定的。類型參數(shù)充當(dāng)編譯階段提供的實(shí)際類型的占位符。在這個(gè)例子中,用于替換T的實(shí)際類型必須實(shí)現(xiàn)了接口IComparable<T>,其中T是泛型方法的類型參數(shù)。這要求類型參數(shù)只能是實(shí)現(xiàn)了該接口的類型。

ps:C#泛型、C++模板和Java泛型
雖然C#泛型、C++模板和Java泛型都支持參數(shù)化類型,但是它們之間有幾項(xiàng)重要的差別。C#泛型的語(yǔ)法與Java泛型類似,但比C++模板簡(jiǎn)單。
C#泛型的所有類型替換都是在運(yùn)行階段進(jìn)行的,從而保留了對(duì)象的泛型類型信息。在Java中,泛型是一種語(yǔ)言結(jié)構(gòu),只在編譯器中以類型擦除(type erasure)的方式實(shí)現(xiàn)。因此,在運(yùn)行階段無(wú)法獲悉對(duì)象的泛型類型信息。這不同于C++模板,C++在編譯階段展開(kāi),為每種模板類型生成額外的代碼。
在有些情況下,C#泛型的靈活性沒(méi)有C++模板和Java泛型高。例如, C#泛型不像Java泛型那樣支持類型參數(shù)通配符;也不能像在C++模板中那樣,可調(diào)用類型參數(shù)的算術(shù)運(yùn)算符。

1.1 泛型類型參數(shù)
方法有參數(shù),而在運(yùn)行階段,這些形參的值為實(shí)參。同樣,泛型類型和泛型方法也有類型參數(shù)和類型實(shí)參,其中類型參數(shù)充當(dāng)編譯階段提供的類型實(shí)參的占位符。
這不僅僅是簡(jiǎn)單的文本替換——使用提供的類型提供類型參數(shù)。泛型類型或泛型方法被編譯后,生成的 CIL 包含元數(shù)據(jù),指出它有類型參數(shù)。在運(yùn)行階段,JIT 用提供的類型參數(shù)進(jìn)行替換,創(chuàng)建出構(gòu)造類型(constructed type)。
泛型類型和泛型方法可以有多個(gè)用逗號(hào)(,)分隔的類型參數(shù)。有多個(gè)泛型集合類使用了多個(gè)類型參數(shù),如Dictionary<TKey, TValue>和KeyValuePair<TKey,TValue>。Tuple類也是泛型,最多可以有8個(gè)類型參數(shù)。

約束
約束讓您能夠指定哪些類型可在編譯階段用作類型實(shí)參。這些限制是使用關(guān)鍵字 where指定的。通過(guò)約束類型參數(shù),便可使用約束類型及其繼承鏈中所有類型都支持的操作和方法。
約束共有6種,如下所示

約束 描述
where T : struct 類型實(shí)參必須是值類型,但可以為null的值類型除外
where T : class 類型實(shí)參必須是引用類型,這適用于任何類、接口、委托和數(shù)據(jù)類型
where T : new() 類型實(shí)參必須有不接受任何參數(shù)的共有構(gòu)造函數(shù),且為具體類型。與其他約束一起使用時(shí),new()約束必須位于最后面
where T : <base class name> 類型實(shí)參必須是指定的基類或其派生類
where T : <interface name> 類型實(shí)參必須能夠隱式地轉(zhuǎn)換為指定的接口。約束接口可以使泛型的,還可指定多個(gè)接口約束
where T : U 類型實(shí)參必須是U(另一個(gè)泛型類型參數(shù))指定的類型或從它派生而來(lái)的

約束告訴編譯器,類型參數(shù)支持哪些運(yùn)算符和方法。沒(méi)有約束的類型參數(shù)為無(wú)約束類型參數(shù),只支持簡(jiǎn)單賦值以及System.Object支持的方法。對(duì)于無(wú)約束類型參數(shù),不能將運(yùn)算符!=和==用于它們,因?yàn)榫幾g器不知道它們是否能獲得支持。
單個(gè)類型參數(shù)可以有多個(gè)約束,也可以給多個(gè)類型參數(shù)指定約束:

CustomDictionary<TKey, TValue>
where TKey : IComparable
where TValue : class, new()

ps:泛型的值相等性檢測(cè)
即使指定了約束where T : class,也不應(yīng)將運(yùn)算符==和!=用于類型參數(shù)。這些運(yùn)算符檢測(cè)引用是否相同,而不是值是否相等。
即使在類型中重載了這些運(yùn)算符,情況也是如此。因?yàn)榫幾g器只知道T是引用類型。因此,它只能使用 System.Object 定義的可用于所有引用類型的默認(rèn)運(yùn)算符。
要進(jìn)行值相等性檢測(cè),推薦使用約束where T : IComparable<T>,并確保將用于構(gòu)造泛型類的所有類都實(shí)現(xiàn)了該接口。通過(guò)指定該約束,可使用方法CompareTo進(jìn)行值相等性測(cè)試
類型參數(shù)約束(type parameter constrain)是一個(gè)這樣的泛型類型參數(shù),即用于約束另一個(gè)類型參數(shù)。類型參數(shù)約束最常用于這樣的情形:泛型方法需要將其類型參數(shù)約束為其所屬類型的類型參數(shù),如下代碼所示。
在這個(gè)示例中,T是方法Add的一個(gè)類型參數(shù)約束,該方法接受一個(gè)List<U>,其中U必須是T或從T派生而來(lái)的。

public class List<T>
{    
    public void Add<U>(List<U> items) where U : T    
    {            

    }
}

類型參數(shù)約束還可用于泛型類,以指定兩個(gè)類型參數(shù)之間的關(guān)系,如下代碼所示。在這個(gè)例子中,Example有3個(gè)類型參數(shù)(T、U和V),其中T必須是V或從V派生而來(lái),而U和V之間沒(méi)有約束

public class Example<T, U, V> where T : V
{

}

1.2 泛型類型的默認(rèn)值

C#是一種強(qiáng)類型語(yǔ)言,要求使用變量前給它賦值。為方便滿足這種要求,每種類型都有默認(rèn)值。顯然,對(duì)于泛型類型,無(wú)法預(yù)先知道默認(rèn)值應(yīng)為 null、0 還是用零初始化的結(jié)構(gòu),那么如何為泛型類型指定適合任何類型的默認(rèn)值呢?
C#提供了關(guān)鍵字default,它表示適合類型參數(shù)的默認(rèn)值,其具體值隨指定的實(shí)際類型而異。這意味著對(duì)于參數(shù)類型,它返回 null;對(duì)于所有數(shù)值類型,它返回 0;如果類型實(shí)參是結(jié)構(gòu),則根據(jù)其每個(gè)成員的數(shù)據(jù)類型,將它們初始化為null或零;對(duì)于可以為null的值類型,則返回null。

二、泛型方法
泛型方法與非泛型方法類似,但使用一組泛型類型參數(shù)而不是具體類型定義。泛型方法是在運(yùn)行階段用于生成方法的設(shè)計(jì)圖。

ps:非泛型類中的泛型方法
并非只有泛型類才能有泛型方法,非泛型類也可包含泛型方法,這完全合法,也很常見(jiàn)。
另外,泛型類也可包含非泛型方法,且后者可訪問(wèn)前者的類型參數(shù)。
通過(guò)給泛型方法指定約束,可使用約束保證可用的具體操作。泛型方法和非泛型方法都可使用泛型類定義的類型參數(shù),因此如果泛型方法定義了與其所屬的類相同的類型參數(shù),給泛型方法提供的實(shí)參T將隱藏給類提供的實(shí)參T,而編譯器將發(fā)出警告。如果希望方法使用的類型實(shí)參與實(shí)例化類時(shí)提供的類型實(shí)參不同,應(yīng)給類型參數(shù)指定不同的標(biāo)識(shí)符,如下代碼所示:

class GenericClass<T>
{    
    void GenerateWarning<T>()    
    {            

    }    
    void NoWarning<U>()    
    {            

    }
}

調(diào)用泛型方法時(shí),必須給它定義的類型參數(shù)提供實(shí)際的數(shù)據(jù)類型。如下代碼所示演示了圖和調(diào)用方法Min<T>:

public static class Program
{    
    static void Main()    
    {        
        int[] array = { 3, 5, 7, 0, 2, 4, 6 };        
        Console.WriteLine(Min<int>(array));    
    }
}

雖然這是可以接受的,但是在大多數(shù)情況下是不必要的,這要?dú)w功于類型推斷(type inference)。如果省略了類型實(shí)參,編譯器將根據(jù)方法實(shí)參推斷出類型。如下代碼利用類型推斷進(jìn)行了相同的調(diào)用:

public static class Program
{    
    static void Main()    
    {        
        int[] array = { 3, 5, 7, 0, 2, 4, 6 };        
        Console.WriteLine(Min(array));    
    }
}

由于類型推斷依賴于方法實(shí)參,因此它無(wú)法僅根據(jù)約束或返回類型推斷出類型。這意味著不能將其用于沒(méi)有參數(shù)的方法。

對(duì)泛型方法來(lái)說(shuō),類型參數(shù)是方法簽名的一部分??梢赃@樣重載泛型方法:聲明多個(gè)泛型方法,它們的形參列表相同,但類型參數(shù)不同。

ps
:類型推斷和重載解析
類型推斷發(fā)生在編譯階段,并在編譯器試圖解析重載的方法簽名之前進(jìn)行。進(jìn)行類型替換后,非泛型方法和泛型方法的簽名可能相同。在這種情況下,將使用最具體的方法(總是為非泛型方法)。

三、創(chuàng)建泛型類

泛型類最常用于集合,因?yàn)闊o(wú)論存儲(chǔ)的數(shù)據(jù)類型是什么,集合的行為都相同。泛型方法是運(yùn)行階段用于生成方法的設(shè)計(jì)圖,同樣,泛型類也是運(yùn)行階段用于構(gòu)造類的設(shè)計(jì)圖。
除使用.NET Framework提供的泛型類外,您還可以創(chuàng)建自定義泛型類。這與創(chuàng)建非泛型類沒(méi)有什么不同,只是您需要提供類型參數(shù)而不是實(shí)際數(shù)據(jù)類型。

創(chuàng)建自定義泛型類時(shí),請(qǐng)牢記下面幾個(gè)重要問(wèn)題:
哪些類型應(yīng)為類型參數(shù)?一般而言,參數(shù)化的類型越多,泛型類就越靈活。然而,對(duì)實(shí)際的類型參數(shù)數(shù)量存在一定的限制,因?yàn)轭愋蛥?shù)越多,代碼的可讀性越差。
應(yīng)指定什么樣的約束?確定這一點(diǎn)的方式有多種。一種方式是,確保能夠使用希望的類型的情況下,指定盡可能多的約束;另一種方式是,指定盡可能少的約束,以最大限度地提高泛型類的靈活性。這兩種方式都可行,但是也可采取更實(shí)用的方式,即根據(jù)泛型類要達(dá)到的目的,指定必要的約束。例如,如果知道泛型類應(yīng)只用于引用類型,就應(yīng)指定where T : class約束。這樣既可禁止泛型類用于值類型,又能使用as運(yùn)算符并進(jìn)行null檢查。
行為應(yīng)在基類還是子類中提供?泛型類可用作基類,就像非泛型類一樣。因此,適用于非泛型類的設(shè)計(jì)選項(xiàng)也可用于泛型類。
應(yīng)實(shí)現(xiàn)泛型接口嗎?您可能需要實(shí)現(xiàn)甚至創(chuàng)建一個(gè)或多個(gè)泛型接口,這取決于設(shè)計(jì)的泛型類是什么樣的。自定義泛型類的用法也決定了它要實(shí)現(xiàn)哪些接口。

非泛型類可繼承具體的非泛型類,也可繼承抽象的非泛型類;同樣,泛型類也可繼承非泛型具體類或抽象類,但泛型類還可繼承其他泛型類。

ps:泛型結(jié)構(gòu)和泛型接口
結(jié)構(gòu)也可以是泛型的,泛型結(jié)構(gòu)使用的語(yǔ)法和類型約束與泛型類相同。泛型結(jié)構(gòu)和泛型類之間的差別與非泛型結(jié)構(gòu)和非泛型類之間的差別相同。
泛型接口使用的類型參數(shù)語(yǔ)法和約束與泛型類相同,其聲明規(guī)則與非泛型接口相同。一種明顯的差別是,泛型類型實(shí)現(xiàn)的接口對(duì)所有可能的構(gòu)造類型來(lái)說(shuō)都必須是唯一的,這意味著如果替換類型參數(shù)后,同一個(gè)泛型類實(shí)現(xiàn)的兩個(gè)泛型接口相同,那么該泛型類的聲明將是非法的。
雖然泛型類可以繼承非泛型接口,但是最好不要這樣做,而是繼承泛型接口。
為理解泛型類的繼承,需要明白開(kāi)放類型(open type)和封閉類型(closed type)之間的差別。開(kāi)放類型是包含類型參數(shù)的類型,具體地說(shuō),它是這樣的泛型類型,即沒(méi)有給其類型參數(shù)提供類型實(shí)參。封閉類型也叫構(gòu)造類型,是不開(kāi)放的泛型類型,即給它的所有類型參數(shù)都提供了類型實(shí)參。
泛型類可繼承開(kāi)放類型,也可繼承封閉類型。派生類可給基類的所有類型參數(shù)都提供類型實(shí)參,在這種情況下,派生類為構(gòu)造類型;如果派生類沒(méi)有給基類提供任何類型實(shí)參,它將是開(kāi)放類型。雖然泛型類可繼承封閉類型和開(kāi)放類型,但非泛型類只能繼承封閉類型,否則編譯器將無(wú)法知道應(yīng)使用什么樣的類型實(shí)參。

如下代碼提供了一些繼承開(kāi)放類型和封閉類型的示例

abstract class Element {  }

class Element<T> : Element {  }

class BasicElement<T> : Element<T> {  }

class Int32Element : BasicElement<int> {  }

在這個(gè)示例中, Element<T>繼承了 Element ,是開(kāi)放類型;BasicElement<T>繼承了Element<T>,是開(kāi)放類型;而Int32Element是構(gòu)造類型,因?yàn)樗^承了構(gòu)造類型BasicElement<int>。
然而,派生類可給基類的部分類型參數(shù)提供類型實(shí)參,在這種情況下,派生類為開(kāi)放構(gòu)造類型(open constructed type)??烧J(rèn)為開(kāi)放構(gòu)造類型位于開(kāi)放類型和封閉類型之間,即它至少給一個(gè)類型參數(shù)提供了實(shí)參,但要成為構(gòu)造類型,還至少有一個(gè)類型參數(shù)需要提供實(shí)參。
如下代碼擴(kuò)展了上述示例,它創(chuàng)建了一個(gè)有兩個(gè)類型參數(shù)(T和K)的開(kāi)放類型(Element),還創(chuàng)建了各種可能的開(kāi)放構(gòu)造類型。

class Element<T, K> {   }

class Element1<T> : Element<T, int> {   }

class Element2<K> : Element<string, K> {   }

如果是開(kāi)放類型指定的約束,那么其派生類提供的類型實(shí)參必須滿足這些約束,可以指定約束來(lái)實(shí)現(xiàn)。子類的約束可以與基類相同,也可以是基類約束的超集。
如下代碼演示了如何繼承帶約束的開(kāi)放類型:

class ConstainedElement<T>    
    where T : IComparable<T>,new()

class ConstainedElement1<T> : ConstainedElement<T>    
    where T : IComparable<T>,new()

最后,如果泛型類實(shí)現(xiàn)了一個(gè)接口,那么其所有實(shí)例都可轉(zhuǎn)換為該接口。

四、結(jié)合使用泛型和數(shù)組(泛型數(shù)組)

所有一維數(shù)組的最小索引都為零,且自動(dòng)實(shí)現(xiàn)了IList<T>。因此,可以創(chuàng)建一個(gè)對(duì) IList<T>的內(nèi)容進(jìn)行遍歷泛型方法,而該方法可用于所有集合類型(因?yàn)樗鼈兌紝?shí)現(xiàn)了IList<T>)和所有一維數(shù)組。
如下示例演示了使用泛型方法顯示集合的元素

public static class Program
{    
    public static void PrintCollection<T>(IList<T> collection)    
    {        
        StringBuilder builder = new StringBuilder();        
        foreach (var item in collection)        
        {            
            builder.AppendFormat("{0} ", item);        
        }        
        Console.WriteLine(builder.ToString());    
    }    
    public static void Main()    
    {        
        int[] array = { 0, 2, 4, 6, 8 };        
        List<int> list = new List<int>() { 1, 3, 5, 7, 9 };        
        PrintCollection(array);        
        PrintCollection(list);        
        string[] array2 = { "hello", "world" };        
        list<string> list2 = new List<string>() { "now", "is", "the", "time" };        
        PrintCollection(array2);        
        PrintCollection(list2);    
    }
}

泛型接口的可變性
類型可變性(variance)指的是可使用不同于指定類型的類型。協(xié)變(covariance)能夠使用派生程度比指定類型更高的類型,而逆變(contravariance)能夠使用派生程度更低的類型。C#對(duì)返回類型支持協(xié)變,對(duì)參數(shù)支持逆變。
C#泛型集合是不可變的(invariant),這意味著必須使用指定的類型。因此,在需要派生程度低的類型的集合時(shí),不能使用派生程度高的類型的集合。

ps:實(shí)現(xiàn)了可變(variant)泛型接口的類
實(shí)現(xiàn)了可變(variant)泛型接口的類總是不變的(invariant)。
這里的真正問(wèn)題是集合是可修改的。如果可對(duì)集合進(jìn)行限制,使其只支持只讀行為,就可將其聲明為協(xié)變的。
在C#中,如果接口的類型參數(shù)被聲明為協(xié)變或逆變的,接口就是可變的。僅當(dāng)兩種類型之間能夠進(jìn)行引用轉(zhuǎn)換時(shí),才能使用協(xié)變和逆變。這意味著可變性不能用于值類型,也不能用于ref和out參數(shù)。
有多個(gè)泛型集合接口支持可變性,如下所示

接口 可變性
IEnumerable<T> T是協(xié)變的
IEnumerator<T> T是協(xié)變的
IQueryable<T> T是協(xié)變的
IGrouping<TKey, TElement> TKey和TElement是協(xié)變的
IComparer<T> T是逆變的
IEqualityComparer<T> T是逆變的
IComparable<T> T是逆變的

擴(kuò)展可變的泛型接口
編譯器不會(huì)根據(jù)被繼承的接口推斷可變性,因此必須顯式地指定派生接口是否支持可變性,如下所示:

interface ICovariant<out T>{  }

interface IInvariant<T> : ICovariant<T>{  }

interface IExtendCovariant<out T> : ICovariant<T>{  }

雖然接口 IInvariant<T>和 IExtendedCovariant<out T>擴(kuò)展的是同一個(gè)協(xié)變接口,但只有IExtendedCovariant<out T>也是協(xié)變的??梢砸酝瑯拥姆绞綌U(kuò)展逆變接口。
還可在同一個(gè)接口中同時(shí)擴(kuò)展協(xié)變接口和逆變接口,條件是派生接口是不可變的,如下所示同時(shí)擴(kuò)展協(xié)變接口和逆變接口:

interface ICovariant<out T>{  }

interface IInvariant<in T>{  }

interface IInvariant<T> : IContravariant<T>, ICovariant<T>{  }

然而,不能使用協(xié)變接口擴(kuò)展逆變接口,反之亦然,如下代碼所示:

//    Generates a compiler error.
interface IInvalidVariance<in T> : ICovariant<T>{  }

ps:創(chuàng)建自定義的可變泛型接口
可以創(chuàng)建自定義的泛型接口,同樣,可創(chuàng)建自定義的可變泛型接口,方法是給泛型類型參數(shù)指定關(guān)鍵字in和out。
關(guān)鍵字out將泛型類型參數(shù)聲明為協(xié)變的,而關(guān)鍵字in將泛型類型參數(shù)聲明為逆變的。同一個(gè)接口可同時(shí)包含協(xié)變類型參數(shù)和逆變類型參數(shù)。關(guān)鍵字ref和out在聲明和調(diào)用方法時(shí)都需指定,而關(guān)鍵字in和out只需在接口聲明中指定。

五、元組

元組是一種數(shù)據(jù)結(jié)構(gòu),它包含特定個(gè)數(shù)值,而這些值按特定順序排列。元組常用于以下用途:
表示一組數(shù)據(jù);
提供一種訪問(wèn)數(shù)據(jù)集的簡(jiǎn)單方法;
輕松地從方法返回多個(gè)值

雖然元組最常用于函數(shù)編程語(yǔ)言,如F#、Ruby和Python,但是.NET Framework也提供了多個(gè)Tuple類,分別用于表示包含1~7個(gè)值的元組。還有一個(gè)表示n元組的類,其中n是大于或等于8的任何整數(shù)。表示n元組的類與表示1~7元組的類稍有不同,其第8個(gè)分量也是一個(gè)Tuple對(duì)象,定義了其他的分量。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容