十二、泛型
Covers:泛型
概念
考虑在算法一节接触过的“交换变量内容”,我们可以这样写:
static void Swap(ref int a, ref int b)
{
int tmp = a;
a = b;
b = tmp;
}
Swap 函数通过引用传参,可以操作调用方传入的变量,将两个整型变量的内容互换。现在问题来了,我们又想写一个函数来交换两个浮点型变量的内容:
static void Swap(ref double a, ref double b)
{
double tmp = a;
a = b;
b = tmp;
}
我们重载了 Swap,做了一个接受两个浮点型的版本。现在我们还想交换 decimal、bool 等等数据类型的内容……我们真的要重载那么多 Swap 吗??
泛型 了解一下:
static void Swap<T>(ref T a, ref T b)
{
T tmp = a;
a = b;
b = tmp;
}
这个方法看起来有点奇怪?在函数名右面增加了一个 <T>
,这就是 C# 中引入泛型的语法,它表示在 Swap 这个方法的作用域(花括号)内,我们用 类型参数 T(也可以用别的名称,T 只是约定俗成)来指代“任何类型”。上面这段代码与更上面的两段非常相似,只有类型用 T 代替了;如果用任何实际类型替换 T,含泛型的代码就可以直接变换为适应于那种类型的等效代码,换言之,泛型代码就像一个代码的“模板”,它规划了如何用实际类型替换其中的 T,从而生成实际代码,谁替我们完成这个替换工作?编译器。
泛型编程基础
泛型是 静态多态 的实现手段之一(一个泛类型 T,当赋以不同实际类型时,表现出不同的功能和性质),只有静态类型(强类型)语言才有泛型编程。它允许我们超越类型的概念,让类型变得通透,最大限度地重用代码,并更多地从智能 IDE/编译器中获得辅助。
C++ 的泛型是通过模板实现的,在漫长的 C++ 编程史中,才华横溢的程序员们发展出了灿烂的、精巧的、极尽复杂的、巴洛克式的泛型技巧,包括但不限于偏特化、萃取、模板元等,模板及其艺术般的应用是 C++ 继指针之后实至名归的另一瑰宝,原生的 C++ 标准模板库更是将泛型技术发挥到了极致,一切均用泛型编写。
咳咳,言归正传,我们的目的是简单认识泛型,并且通过了解泛型为后续学习 .NET 标准容器提供基础知识。
类、接口、方法都可以用泛型定义;泛型具有作用域的覆盖性,即 如果一个类具有泛型,那么其所有成员就都具有泛型(不需要也不允许再次声明 T)。
下面是一个泛型类的例子:
using System;
namespace Learning
{
class MyArray<T>
{
T[] _array;
public MyArray(int capacity = 0xffff)
{
_array = new T[capacity];
}
public T Get(int pos)
{
return _array[pos];
}
public void Set(int pos, T data)
{
_array[pos] = data;
}
}
}
这里我们直接泛型定义了 class MyArray,类型参数 T,那么在整个 class 的作用域内,所有成员就都可以使用 T,而不需要再次定义 T。
泛型代码在编译时必须完成类型推导才有实际意义,因此我们需要为类型参数绑定实际类型,有两种方式:
- 显式绑定,在调用处,直接在类名或方法名后面加上 用尖括号括起来的实际类型
<TYPE>
,就将实际类型 TYPE 绑定到了类型参数 T 上; - 隐式绑定,在调用处不写实际类型,就像使用普通方法一样。只有泛型方法才能隐式绑定实际类型,因为 实际类型可以从填入的参数推断,这称为 (静态)类型推导。
我们为上面的 MyArray 和更前面的泛型 Swap 写一些测试代码:
class Program
{
static void Main()
{
var array = new MyArray<double>();
array.Set(0, 1.414);
array.Set(1, 0.618);
double a = array.Get(0), b = array.Get(1);
Swap(ref a, ref b);
Console.WriteLine("{0}, {1}", a, b);
}
}
这里我们显式为类 MyArray 绑定了实际类型 double;调用 Swap 时,由于已经能从 a 和 b 的类型推断出 Swap 的实际类型,故不需要显式给出。
T.B.C.