泛型编程入门2

 

5.2 泛型的解决方案

5.2.1 泛型类的编写

为了说明泛型类的优势,让我们来看一个经典的代码:

案例操作020503:编写泛型类

l         代码片段一:交换数据

public class MyClass

{

    public void Swap(ref object obj1, ref object obj2)

    {

        object tmp = obj1;

        obj1 = obj2;

        obj2 = tmp;

    }

}

上面的代码是想作一个通用的,用于交换两个变量的类,由于不可能为每一种数据类型都去重载一次Swap方法,因此,方法中的参数使用了Object类型。但是问题在于如何使用该Swap方法,如果,在一个大规模的整数排序程序中使用该Swap方法,此时又会出现大量装箱及拆箱操作,速度又会受到影响。另外,由于出现了赋值操作,类型转换的安全性也得不到很好的保障。下面我们利用泛型类来对该类进行改进。

l         代码片段二:编写泛型类

public class GenericTest

{

              public void Swap(ref T t1, ref T t2)

              {

              T tmp = t1;

t1 = t2;

t2 = tmp;

}

}

从上面的代码中可以看出一个基本的泛型编写的方式,在类名后面加上“ ” ,这里的T被称为类型参数它是一个已知类,但是直到使用该类之前,还不知道它应该是哪一种具体的类型。在编写该的实例时,我们去指定T是什么类型。

定义完“T”之后,就可以在类中去应用T类型。在Swap方法中,由于不知道使用会交换哪种数据类型,因此,Swap方法中的参数也是T类型的。

下面,我们来看一下如何使用这个泛型类:

示例的窗口如下图所示:

l         代码片段三:使用泛型类

private void button1_Click(object sender, EventArgs e)

{

   int a = int.Parse(this.textBox1 .Text );

    int b = int.Parse(this.textBox2 .Text );

    string s1 = this.textBox3.Text;

    string s2 = this.textBox4.Text;

    //指定类型参数为int

    GenericTest gt1 = new GenericTest< int>();

    gt1.Swap(ref a, ref b);

    this.textBox1.Text = a.ToString();

    this.textBox2.Text = b.ToString();

 

    //指定类型参数为string

    GenericTest gt2 = new GenericTest ();

    gt2.Swap(ref s1,ref s2);

    this.textBox3.Text = s1;

    this.textBox4.Text = s2;

}

在上面的代码片段中,可以看出,使用泛型类是比较简单的,只需要在定义的时候指定类型参数“T”是什么类型即可,如下面两行代码,分别将类型参数T指定为int以及string类型。

    GenericTest gt1 = new GenericTest ();

    GenericTest gt2 = new GenericTest ();

如果指定了类型参数对应的真正类型,那么该泛型类中的所有“T”,都将被替换成对应的真正类型。比如,gt1是将类型参数指定为int,那么GenericTest中的所有“T”都会被替换成为int;而gt2是将类型参数指定为string,那么GenericTest中的所有“T”都会被替换成为string。此时,Swap方法的对应的参数类型也会随着类型参数“T”的不同而发生改变,如下图所示:

通过IDE给我的智能提示,用户们可以发现,虽然利用泛型写的是通用类,但是,由于指定了类型参数,使得在具体调用Swap方法的时候,参数的类型是确定的,这一点和多态是相反,泛型中的通用,追求的在编码的时候确定类型,而多态中的通用,追求的是在运行的时候确定类型。

关于完整的泛型类编写的案例代码,请参考:

案例操作020503:编写泛型类

5.2.2 泛型类的约束

l         类型默认值

在编写泛型类的时候,我们会采用以下形式

public class MyClass

{//do something}

这里的“T”,我们强调过,它是一种已知类型,但是却不能当做任何一种具体的类型来使用。见下面代码:

public class MyClass

{

                  public void MM(T t1)

       {

                            if(t1==null)

            {

               t1=0;

}

}

}

在上面的代码中,判断t1是否为null,以及给t1赋值为0,都是不正确的作法,因为类型参数“T”,目前还不知道是具体的哪一种类,而只有引用类型(Reference Type)可以为null,另外,也只有一部分的值类型(Value Type)如intlong,才可以赋值为0

因此,这里再次强调,泛型类通常用于编写一些泛化的操作,如果想执行更高级且功能更强的操作,请使用泛型+反射技术另外,所有的类者是Object类的派生类,因此,我们在T类型的实例中,唯一能使用的操作就是已经在Object类中定义好的内容,比如,我们可调用t1.ToString()

此外,如果想取得T类型的默认值,请使用default操作符。如下面代码所示:

T t1=default(T);

l         泛型类中方法签名的二义性(Ambiguity

所谓方法签名,就是指类中方法的原型。由于泛型类中方法的参数可以是类型参数,而该类型参数在真正使用的时候可能是任何类型,这样在使用的时候就有可能产生方法调用的二义性(ambiguity)

查看下面的代码片段:

class MyClass

        {

            public string GetStringValue(T t)

            {

                return t.ToString();

            }

            public string GetStringValue(int i)

            {

                return i.ToString();

            }

        }

        private void button1_Click(object sender, EventArgs e)

        {

            MyClass mc = new MyClass ();

            string value = mc.GetStringValue(1);

        }

对于一个泛型类来讲,方法也是可以重载的,比如上面的代码中,就存在着GetStringValue方法的两种形式,第一种形式采用了类型参数“T”作为参数类型,第二种形式,是采用了int类参数类型。在我们使用该类的时候,如果指定了类型参数的真正类型为int,这样就会产生歧义(两个GetStringValue方法中的参数类型都是整型!)。那么当使用者调用了GetStringValue方法,并且传递了一个整型变量给该方法时,到底调用的是哪一个版本的GetValueString呢?是GetStringValue int),还是GetStringValueint)?

针对于上面的问题,只要把握住一个原则即可,即:只要在原型中存在与之对应的参数类型,那么就按存在对应类型参数的方法调用,如果参数的类型不存在,则调用存在泛型参数的方法。多类型参数的泛型方法操作方式也相同。

在上面代码中,实际上调用的是:

public string GetStringValue(int i)

            {

                return i.ToString();

            }

l      类型参数的条件约束

在很多情况下,类型参数的具体类型是有条件的。比如,要利用泛型作一个可以进行排序的链表,如MyList , 在使用该类时T可以被替换成所需类。但既然是一个可排序的链表,那么就要求用于替换T的类型一定是可以比较的类型。这时我们又应该怎么样处理呢?

我们知道在Sql语句中,可以使用Where子句来设置条件,实际上,在我们也可以利用Where语句对类型参数“T”进行约束。

如上面提到的可排序的泛型链表,就可以写成以下形式:

public class MyList where T:IComparable

{

              public int CompareTo(object obj)

{

//.

}

}

这样,所有想利用该链表进行排序的数据,就一定要继承自IComparable接口,否则会出现编译错误!

下表介绍了类型参数“T”可用的约束类型

约束

说明

where T:MyClass

该约束指定,T一定要继承自MyClass类型,MyClass可以是泛型类。

where T:class

该约束指定,T一定是一个类(引用类型)。

where T:MyStruct

该约束指定,T一定要继承自MyStruct结构体。

where T:struct

该约束指定,T一定是一个结构体(值类型)。

where T:IMyInterface

该约束指定,T一定要继承自IMyInterface接口,该接口可以是泛型接口。

where T:new()

该约束指定,T一定要有一个默认构造函数。

where T:U

该约束指定,T一定要继承自另外一个类型参数U

 

5.2.3 泛型类中的静态成员

我们知道,C#中是没有全局变量的,通常情况下,我们可以使用类中的静态字段来实现全局变量的功能,但是这样的一种想法,在泛型中是有所变化的。

泛型类中,静态成员的版本数量并不唯一,其数量与该类所指定的类型参数的真正类型的数量是一样的。

下面的代码中就展示了一个泛型类MyClass ,其中就包括了一个静态成员i

public class MyClass

    {

        public static int i;

    }

泛型类的静态成员使用方式如下:

MyClass .i=100;

MyClass .i=10;

在泛型类中,要想使用静态成员,就一定要先指定类型参数,上面代码中指定了两个类型参数:stringint

此时在内存中,静态成员i就存在了两个版本,所以指定了类型参数Tstring类型的MyClass中共享一个i,同样,所以指定了类型参数Tint类型的MyClass中共享另外一个i

5.2.4 泛型方法

在上一节,泛型类中的静态成员的表现可能会让很多开发人员感觉到失望,因为在泛型类中,静态成员已经失去了全局变量的意义。那么,有没有这样的一种结构,既能够使用泛的功能,还能使用静态成员的全局变量的特性呢?

我们可以降低泛型的使用范围,即:在方法中使用泛型。

l         泛型方法的编写

泛型方法,相对于泛型类来讲,要简单一些,下面代码展示了泛型方法的编写过程:

public class MyClass

    {

        public static int i;

        public void Swap (ref T t1,ref  T t2)

        {

            T tmp = t1;

            t1 = t2;

            t2 = tmp;

        }

    }

该类中的Swap方法就是一个泛型方法,只要在方法名的后面指明类型参数“T”即可。这样方法可以是泛型的,而静态成员又是全局的。

l         泛型方法的使用

泛型方法的编写很简单,那么泛型方法又应该如何使用呢?

泛型方法的使用可以分为两种形式:1.显示声明类型参数的真正类型。2.隐匿声明类型参数的真正类型(由编译器自动推断类型)。

详细使用过程见下面代码:

class MyClass

        {

            public static int i;

            public void Swap (ref T t1, ref  T t2)

            {

                T tmp = t1;

                t1 = t2;

                t2 = tmp;

            }

        }

        private void button1_Click(object sender, EventArgs e)

        {

            MyClass mc = new MyClass();

            string s1 = "hello";

            string s2 = "world";

            int a = 10;

            int b = 20;

            //显示声明类型参数的真正类型

            mc.Swap (ref a,ref b);

            mc.Swap (ref s1, ref s2);

            //由编译器自动推断类型

            mc.Swap(ref a,ref b);

            mc.Swap(ref s1,ref s2);

        }

5.2.5 泛型结构体

结构体,由于其轻便快速的特点,受到编码人员的喜爱,特别是在作一些简单的存储结构时,结构体是一个比较好的选择。在.NET Framework2.0中,开发人员同样可以编写泛型结构体。代码如下所示:

public struct MyStruct

    {

        public T Name;

        public T Age;

      

    }



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目标检测(Object Detection)是计算机视觉领域的一个核心问题,其主要任务是找出图像中所有感兴趣的目标(物体),并确定它们的类别和位置。以下是对目标检测的详细阐述: 一、基本概念 目标检测的任务是解决“在哪里?是什么?”的问题,即定位出图像中目标的位置并识别出目标的类别。由于各类物体具有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具挑战性的任务之一。 二、核心问题 目标检测涉及以下几个核心问题: 分类问题:判断图像中的目标属于哪个类别。 定位问题:确定目标在图像中的具体位置。 大小问题:目标可能具有不同的大小。 形状问题:目标可能具有不同的形状。 三、算法分类 基于深度学习的目标检测算法主要分为两大类: Two-stage算法:先进行区域生成(Region Proposal),生成有可能包含待检物体的预选框(Region Proposal),再通过卷积神经网络进行样本分类。常见的Two-stage算法包括R-CNN、Fast R-CNN、Faster R-CNN等。 One-stage算法:不用生成区域提议,直接在网络中提取特征来预测物体分类和位置。常见的One-stage算法包括YOLO系列(YOLOv1、YOLOv2、YOLOv3、YOLOv4、YOLOv5等)、SSD和RetinaNet等。 四、算法原理 以YOLO系列为例,YOLO将目标检测视为回归问题,将输入图像一次性划分为多个区域,直接在输出层预测边界框和类别概率。YOLO采用卷积网络来提取特征,使用全连接层来得到预测值。其网络结构通常包含多个卷积层和全连接层,通过卷积层提取图像特征,通过全连接层输出预测结果。 五、应用领域 目标检测技术已经广泛应用于各个领域,为人们的生活带来了极大的便利。以下是一些主要的应用领域: 安全监控:在商场、银行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值