学会总结,最有效的学习方式

C#中Class和Struct的区别

  • class(类)是面向对象编程的基本概念,是一种自定义数据结构类型,通常包含字段、属性、方法、属性、构造函数、索引器、操作符等。在.NET中,所有的类都最终继承自System.Object类,因此是一种引用类型,也就是说,new一个类的实例时,在堆栈(stack)上存放该实例在托管堆(managed heap)中的地址,而实例的值保存在托管堆(managed heap)中。
  • struct(结构)是一种值类型,用于将一组相关的变量组织为一个单一的变量实体 。所有的结构都继承自System.ValueType类,因此是一种值类型,也就是说,struct实例在创建时分配在线程的堆栈(stack)上,它本身存储了值。所以在使用struct时,我们可以将其当作int、char这样的基本类型类对待。
  • Class是引用类型,可以赋值为null,struct是值类型,不能赋值为null。
  • 对class的传递实际上是引用传递,既将类实例的引用传递给对方。struct传递的是值。
  • class可以有无参构造器,不需要初始化全部字段,而struct只能使用有参构造器,必须初始化该结构体全部字段。
  • Class字段可以初始化,而struct不能初始化。
  • class实例化时必须使用new关键字(静态类除外),Struct不需要。
  • Class支持继承和多态,Struct不支持,但是Struct支持实现接口,struct内部函数不能声明为abstract和virtual,但是可以使用override关键字覆盖基类System.ValueType中的方法。
  • class默认是public。struct默认是private。
  • Struct总是隐式密封的,因此在定义时不能使用sealed和abstract关键字。
  • Class有面向对象的拓展优势,struct有性能优势。

Static在C#中的作用

  • 用于修饰变量,每次重新使用该变量所在方法、类时,被static修饰的变量的值为程序运行中最后一次为该变量赋值时的值。
  • 用于修饰方法,表示此方法属于该类,而不属于该类的所有实例。
  • 用于Class前,说明此类无法创建实例。不能在静态类中声明非静态成员。

Static在C++中的作用

  • 隐藏。所有未加static前缀的全局变量和函数都具有全局可见性。对于函数来讲,Static的作用仅限于隐藏。
  • 保持变量内容的持久。全局变量和静态变量存储在静态数据区,会在程序刚开始运行时就完成初始化,也是唯一一次初始化。如果在函数内部定义static变量,生存周期为整个源程序,但是其作用域仅限于该函数内部,在函数外部不能使用该变量。
  • 默认初始化为0。因为静态变量存储在静态数据区,静态数据区所有字节默认为0x00。
  • C++中的类成员声明static

     在类中声明static变量或者函数时,初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员,这样就出现以下作用:

    (1)类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致 了它仅能访问类的静态数据和静态成员函数。      

    (2)不能将静态成员函数定义为虚函数。      

    (3)由于静态成员声明于类中,操作于其外,所以对其取地址操作,就多少有些特殊 ,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember函数指针”。

    (4)由于静态成员函数没有this指针,所以就差不多等同于nonmember函数,结果就 产生了一个意想不到的好处:成为一个callback函数,使得我们得以将C++和C-based X W indow系统结合,同时也成功的应用于线程函数身上。 (这条没遇见过)  

    (5)static并没有增加程序的时空开销,相反她还缩短了子类对父类静态成员的访问 时间,节省了子类的内存空间。      

    (6)静态数据成员在<定义或说明>时前面加关键字static。      

    (7)静态数据成员是静态存储的,所以必须对它进行初始化。 (程序员手动初始化,否则编译时一般不会报错,但是在Link时会报错误) 

    (8)静态成员初始化与一般数据成员初始化不同:

    初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆;
    初始化时不加该成员的访问权限控制符private,public等;        
    初始化时使用作用域运算符来标明它所属类;
               所以我们得出静态数据成员初始化的格式:
    <数据类型><类名>::<静态数据成员名>=<值>

    (9)为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有重复定义了静态成员,这会不会引起错误呢?不会,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志。

泛型约束

  • 首先,我们需要知道反省究竟是什么?

泛型是C#语言和公共语言运行库(CLR)中的一个新功能,它将类型参数的概念引入.NET Framework。类型参数使得设计某些类和方法成为可能,例如,通过使用泛型类型参数T,可以大大简化类型之间的强制转换或装箱操作的过程(下一篇将说明如何解决装箱、拆箱问题)。说白了,泛型就是通过参数化类型来实现在同一份代码上操作多种数据类型,利用“参数化类型”将类型抽象化,从而实现灵活的复用。

使用泛型给代码带来的5点好处:1、可以做大限度的重用代码、保护类型的安全以及提高性能。

                  2、可以创建集合类。

                  3、可以创建自己的泛型接口、泛型方法、泛型类、泛型事件和泛型委托。

                  4、可以对泛型类进行约束,以访问特定数据类型的方法。

                  5、关于泛型数据类型中使用的类型的信息,可在运行时通过反射获取。

六种类型的约束:

T:结构                       类型参数必须是值类型。可以指定除Nullable以外的任何值类型。

T:类                           类型参数必须是引用类型,包括任何类、接口、委托或数据类型。

T:new()                  类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new()约束必须最后制定。

T:<基类名>                 类型参数必须是指定的基类或派生自指定的基类。

T:<接口名称>              类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。

T:U                              为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。

例子:

1.接口约束。

例如,可以声明一个泛型类 MyGenericClass,这样,类型参数 T 就可以实现 IComparable<T> 接口:

public class MyGenericClass<T> where T:IComparable { } 

  2.基类约束。

  指出某个类型必须将指定的类作为基类(或者就是该类本身),才能用作该泛型类型的类型参数。这样的约束一经使用,就必须出现在该类型参数的所有其他约束之前。

class MyClassy<T, U>
where T : class
where U : struct
{

}

  3.构造函数约束。

  以使用 new 运算符创建类型参数的实例;但类型参数为此必须受构造函数约束 new() 的约束。new() 约束可以让编译器知道:提供的任何类型参数都必须具有可访问的无参数(或默认)构造函数。new() 约束出现在 where 子句的最后。

public class MyGenericClass <T> where T: IComparable, new()
{
         T item = new T();
}

  4.对于多个类型参数,每个类型参数都使用一个 where 子句。

interface MyI { }
class Dictionary<TKey,TVal>
where TKey: IComparable, IEnumerable
where TVal: MyI
{
    public void Add(TKey key, TVal val)
    {

    }
}

  5.还可以将约束附加到泛型方法的类型参数。

public bool MyMethod<T>(T t) where T : IMyInterface { }  

  6. 裸类型约束

  用作约束的泛型类型参数称为裸类型约束。当具有自己的类型参数的成员函数需要将该参数约束为包含类型的类型参数时,裸类型约束很有用。

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

  为什么要有约束呢?

  当一个泛型参数没有任何约束时,它可以进行的操作和运算是非常有限的,因为不能对实参做任何类型上的保证,这时候就需要用到泛型的约束。泛型的主要约束和次要约束都是指泛型的实参必须满足一定的规范。C#编译器在编译的过程中可以根据约束来检查所有泛型类型的实参并确保其满足约束条件。

  一个泛型参数可以至多拥有一个主要约束,主要约束可以是一个引用类型、class或者struct。如果指定一个引用类型,则实参必须是该类型或者该类型派生类型。class规定实参必须是一个引用类型。struct规定了参数必须是一个之类新。以下代码是泛型参数主要约束的示例。

using System;

namespace Test
{
    class GenericPrimaryConstraint
    {
        static void Main()
        {
            Console.Read();
        }
    }
    //主要约束限定T继承自Exception类型
    public class ClassT1<T> where T : Exception
    {
        private T myException;
        public ClassT1(T t)
        {
            myException = t;
        }
        public override string ToString()
        {
            //主要约束保证了myException拥有Source成员
            return myException.Source;
        }
    }
    //主要约束限定T是引用类型
    public class ClassT2<T> where T : class
    {
        private T myT;
        public void Clear()
        { 
            //T是引用类型,可以置null
            myT = null;
        }
    }
    //主要约束限定T是值类型
    public class ClassT3<T> where T : struct
    {
        private T myT;
        public override string ToString()
        {
            //T是值类型,不会发生NullReferenceException异常
            return myT.ToString();
        }
    }
}

  以上代码,泛型参数具备了主要约束后,就能够在类型中对其进行一定的操作,否则任何算法就只能基于一个System.Object类型的成员。

  可以说,主要约束是实参类型的限定,而相对的次要约束,则是指实参实现的接口的限定。对于一个泛型类型,可以有0至无限的次要约束,次要约束规定了参数必须实现所有次要约束中规定的接口。次要约束的语法和主要约束基本一致,区别仅在于提供的不是一个引用类型而是一个或多个接口。

  ps:同时拥有主要约束和次要约束的泛型参数,表示实参必须同时满足主要约束和次要约束。

这篇文章是自己在面试过程中发现自己的不足,从其他大神哪里Copy过来,以备自己学习并希望能给有和我一样的小白一点帮助。如果对原作者有冒犯,希望告知。

发布了13 篇原创文章 · 获赞 9 · 访问量 8259

猜你喜欢

转载自blog.csdn.net/qq_39025293/article/details/83511177
今日推荐