虽然项目中一直在使用类、结构体等类型,仔细琢磨,还真没法系统的说出个因此然。记录一下类、结构体、类和结构体区别html
1、类程序员
对于类,你们都特别熟悉。简单的介绍一下类的结构,而后记录一下Class须要注意的地方。考虑到静态类和非静态类的区别。下面介绍的都是主要以非静态类为前提介绍。 数组
一、类的成员网络
类的数据和函数都属于类的成员 数据结构
(1)类的成员能够分为两类:首先是类自己所声明的。而后是从基类中继承来的。若是在类声明中没有指定基类,则该类将继承System.Object类的全部成员。less
(2)C#中的类成员能够是任意类型,包括数组和集合。ide
(3)C#类成员修饰符。函数
private声明私有成员,只有该类中的成员能够访问,若是在声明中没有设置访问修饰符,则默认是private。 性能
public 声明公有成员,访问不受限制,容许从外部访问。ui
protected声明受保护成员,包含类和包含类派生的类能够访问 ,对外界是隐藏的。
internal声明内部成员,只能当前程序集能够访问。
protected internal声明受保护的内部成员,只能访问当前程序集和包含类派生的类。


public class Teacher { private int _age; private string _name; public Teacher(int age, string name) { this.Age = age; this.Name = name ?? throw new ArgumentNullException(nameof(name)); } public int Age { get { return this._age; } set { this._age = value;} } public string Name { get { return this._name; } set { this._name = value; } } public string GetName() { return this.Name; } }
二、类的构造函数
概念:构造函数是类的一种特殊方法,每次建立类的实例都会调用它。在建立一个类的实例时,构造函数就像一个方法同样被调用,但不返回值。
分类:实例构造函数,静态构造函数,私有构造函数。
(1)实例构造函数的特征
a、构造函数的名字与类名相同。
b、使用 new 表达式建立类的对象或者结构(例如int)时,会调用其构造函数。而且一般初始化新对象的数据成员。
c、除非类是静态的,不然会为没有构造函数的类,自动生成一个默认构造函数,并使用默认值来初始化对象字段。
d、构造函数能够有参数,能够以重载的形式存在多个构造函数。


class Program { static void Main(string[] args) { Car car = new Car(); car.Print(); Car car1 = new Car(200000,"奥迪"); car1.Print(); Console.ReadKey(); } } public class Car { private int _price; private string _carName; public Car() { this.Price = 600000; this.CarName = "奔驰"; } public Car(int price,string carName) { this.Price = price; this.CarName = carName; } public int Price { get { return this._price; } set { this._price = value; } } public string CarName { get { return this._carName; } set { this._carName = value; } } public void Print() { Console.WriteLine("{0}价格是{1}元!",this.CarName,this.Price); } }
(2)静态构造函数的特征
a、静态构造函数不使用访问修饰符或不能有参数。
b、不能直接调用静态构造函数。
c、若是静态构造函数引起异常,运行时将不会再次调用该函数,而且类型在程序运行所在的应用程序域的生存期内将保持未初始化。
d、用户没法控制在程序中执行静态构造函数的时间。


class Program { private static int _price=1000; public Program() { _price = 10; } static Program() { _price += 1; } static void Main(string[] args) { Console.WriteLine("房子价格{0}",_price);//程序启动,静态类就会占用内存,会先执行静态构造函数 _price=1001 Program program = new Program(); Console.WriteLine("房子价格{0}",_price);//实例化类,会执行实例构造函数 _price=10 Console.ReadKey(); } }
在调用某类的静态函数时真正的执行顺序:
一、静态变量 > 静态构造函数 > 静态函数
二、静态变量 > 静态构造函数 > 构造函数


public class A { public static readonly int x; static A() { //第二步,调用B.y,此处B.y = 0,由于int类型在初始化阶段,会给赋默认值,默认值为0。最后x = 0 + 1(返回给第一步) x = B.y + 1; } } public class B { //第一步,调用A.x,而后执行类A的静态构造函数,等待返回(第二步返回的A.x = 1,全部y = 1 + 1) public static int y = A.x + 1; public static void Main(string[] args) { //第三步,A.x = 1,y = 2。 Console.WriteLine("x:{0},y:{1}。", A.x, y); Console.ReadLine(); } }
(3)私有构造函数
私有构造函数是一种特殊的实例构造函数。 它一般用于只包含静态成员的类中。 若是类具备一个或多个私有构造函数而没有公共构造函数,则其余类(除嵌套类外)没法建立该类的实例。声明空构造函数可阻止自动生成默认构造函数。 请注意,若是不对构造函数使用访问修饰符,则在默认状况下它仍为私有构造函数。 可是,一般会显式地使用 private 修饰符来清楚地代表该类不能被实例化。 其中单例模式就用到了私有构造函数的特性来保证类不会被实例化。


class Program { private Program() { // House house = new House();//注释打开会报错,错误信息:不可访问,由于它受保护级别限制。由于私有构造函数没法在类的外面实例化。 } public class House { private int price; private House() { price = 1000; } static void Main(string[] args) { Program program = new Program();//嵌套类容许实例化 House house = new House(); Console.WriteLine("price{0}",house.price);//输出1000 Console.ReadKey(); } } }
三、类的析构函数
(1)概念:析构函数(destructor) 与构造函数相反,当对象脱离其做用域时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数每每用来作“清理善后” 的工做(例如在创建对象时用new开辟了一片内存空间,应在退出前在析构函数中用delete释放)。
(2)特征
一个类只能有一个析构函数。
不能在结构中定义析构函数。 只能对类使用析构函数。
没法继承或重载析构函数。
没法调用析构函数。 它们是被自动调用的。
析构函数既没有修饰符,也没有参数。
(3)析构函数的做用
程序员没法控制什么时候调用析构函数,由于这是由垃圾回收器决定的。 垃圾回收器检查是否存在应用程序再也不使用的对象。 若是垃圾回收器认为某个对象符合析构,则调用析构函数(若是有)并回收用来存储此对象的内存。 程序退出时也会调用析构函数。
一般,与运行时不进行垃圾回收的开发语言相比,C# 无需太多的内存管理。 这是由于 .NET Framework 垃圾回收器会隐式地管理对象的内存分配和释放。 可是,当应用程序封装窗口、文件和网络链接这类非托管资源时,应当使用析构函数释放这些资源。 当对象符合析构时,垃圾回收器将运行对象的 Finalize 方法
(4)实例


class Program { static void Main(string[] args) { RedApple redApple = new RedApple(); } } class Fruits //基类Fruits { ~Fruits()//析构函数 { Console.WriteLine("~Fruits()的析构函数"); } public Fruits() { Console.WriteLine("Fruits()的构造函数"); } } class Apple:Fruits { ~Apple() { Console.WriteLine("~Apple()的析构函数"); } public Apple() { Console.WriteLine("Apple()的构造函数"); } } class RedApple:Apple { ~RedApple() { Console.WriteLine("~RedApple()的析构函数"); } public RedApple() { Console.WriteLine("RedApple()的构造函数"); } }
运行结果
总结:
1>程序运行时,这三个类的析构函数将自动被调用,调用顺序是按照从派生程度最大的(~C())到派生程度最小的(~A())次序调用的,和构造函数的调用顺序正好相反。则能够得出,当用户建立类的对象是调用构造函数,而当该对象已经调用完毕时,使用析构函数
2>析构函数(destructor) 与构造函数相反,当对象脱离其做用域时(例如对象所在的函数已调用完毕),系统自动执行析构函数。
3>析构函数每每用来作“清理善后” 的工做(例如在创建对象时用new开辟了一片内存空间,应在退出前在析构函数中用delete释放).
4>析构函数名也应与类名相同,只是在函数名前面加一个波浪符~,例如~A( ),以区别于构造函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。
5>若是用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数,它也不进行任何操做。因此许多简单的类中没有用显式的析构函数。
四、类的继承
(1)类的注意事项
1>注意静态类和非静态类的特征以及使用方式的不一样。能够参见C#基础知识之静态和非静态
2>派生类只能从一个类中继承,从多个基类中派生一个类这每每会带来许多问题,从而抵消了这种灵活性带来的优点。
3>C#中,派生类从它的直接基类中继承成员:方法、域、属性、事件、索引指示器。除了构造函数和析构函数,派生类隐式地继承了直接基类的全部成员。
(2)类的重写 override和virtual
1>重写父类的方法要用到override关键字(具备override关键字修饰的方法是对父类中同名方法的新实现)。
2>要重写父类的方法,前提是父类中该要被重写的方法必须声明为virtual或者是abstract类型。给父类中要被重写的方法添加virtual关键字表示能够在子类中重写它的实现。
3>virtual关键字用于将方法定义为支持多态,有virtual关键字修饰的方法称为“虚拟方法”。
4>virtual:不是必须被子类重写的方法,父类必须给出实现,子类能够重写(使用override,new,或无特殊标志的普通方法),也能够不重写该方法。
5>new:重写父类方法时,父类可使用virtual,override,new之一声明,也能够是没有关键字的普通方法,运行时会根据引用类型选择调用父类仍是子类方法,重写父类方法时,使用new关键字与使用没有关键字的普通方法的等效的,可是后者会给出编译警告。


class Program { static void Main(string[] args) { Dog dog = new Dog("狗"); dog.ShowName(); Console.ReadKey(); } } class Animal { private string _name; public string Name { get { return _name; } set { _name = value; } } public virtual void ShowName() { Console.WriteLine("动物列表:"); } } class Dog : Animal { public Dog(string name) { base.Name = name; } public override void ShowName() { base.ShowName(); Console.WriteLine("动物的名称:{0}",base.Name); } }


class Program { static void Main(string[] args) { Dog dog = new Dog("狗"); dog.ShowName(); Console.ReadKey(); } } abstract class Animal { private string _name; public string Name { get { return _name; } set { _name = value; } } public abstract void ShowName(); } class Dog : Animal { public Dog(string name) { base.Name = name; } public override void ShowName() { Console.WriteLine("动物的名称:{0}",base.Name); } }
(3)类的重载
方法名相同,可是参数不一样,参数的个数不一样或者类型不一样,知足一个就能够(和返回值无关,和参数的类型和个数有关)。注意:返回值能够相同也能够不一样。当参数个数相同而参数类型不一样的时候,能够考虑使用泛型(C#基础知识之泛型),提升代码的复用性。


class Dog { public void Show() { } public void Show(string name) { } public void Show(string name,int weight) { } public void Show(int weight,int age) { } }
(4)抽象类
修饰类名为抽象类,修饰方法为抽象方法。若是一个类为抽象类,则这个类只能是其余某个类的基类。抽象方法在抽象类中没有函数体。抽象类中的抽象方法是没有方法体的,继承其的子类必须实现抽象类的抽象方法。
1>抽象类的特征
u 抽象类不能实例化。
u 抽象类的派生类必须实现全部抽象方法。
u 抽象类的抽象方法是没有方法体的,继承抽象类的子类必须得实现全部抽象方法。
2>抽象方法的特征
u 抽象方法是隐式的虚方法。
u 只容许在抽象类中声明抽象方法。
u 抽象方法在抽象类中没有方法体。
u 在抽象方法声明中,不能使用static和virtual修饰符。
(5)密封类
密封类不能被继承,因此sealed和abstract不能共用。
1>密封类


class Program { static void Main(string[] args) { B b = new B(); b.x = 10; b.y = 20; Console.WriteLine("b.x {0}, b.y {1}",b.x,b.y); Console.ReadKey(); } } public sealed class B { public int x; public int y; }
2>密封方法
对类中的方法可使用sealed修饰符,咱们称该方法为密封方法。不是类的每一个成员方法均可以做为密封方法,密封方法必须对基类的虚方法进行重载,提供具体的实现方法。因此,在方法的声明中,sealed修饰符老是和override修饰符同时使用,sealed修饰符位于override修饰符前面。


class Program { static void Main(string[] args) { A a1 = new A();//实例化A类的对象,调用A类中的方法 a1.Fun1(); a1.Fun2(); A a2 = new B();//实例化B类的对象,调用B类中的方法,注意前边 new做为修饰符的用法(很重要!) a2.Fun1(); a2.Fun2(); A a3 = new C();//实例化C类的对象,调用C类中的方法,由于FUN1()在B类中使用密封修饰符,因此C类中没法进行重写,因此调用B.Fun1() a3.Fun1(); a3.Fun2(); B b1 = new B();//实例化B类的对象,调用B类中的方法 b1.Fun1(); b1.Fun2(); B b2 = new C();//实例化C类的对象,由于FUN1()在B类中使用密封修饰符,因此C类中没法进行重写,因此调用B.Fun1() b2.Fun1(); b2.Fun2(); C c1 = new C();//由于FUN1()在B类中使用密封修饰符,因此C类中没法进行重写,因此调用B.Fun1() c1.Fun1(); c1.Fun2(); Console.ReadKey(); } } public class A { public virtual void Fun1() { Console.WriteLine("base.Fun1();"); } public virtual void Fun2() { Console.WriteLine("base.Fun2();"); } } public class B:A { public sealed override void Fun1() { Console.WriteLine("B.Fun1();"); } public override void Fun2() { Console.WriteLine("B.Fun2();"); } } public class C : B { public override void Fun2() { Console.WriteLine("C.Fun2()"); } }
(6)New new的关键词有三个功能
u 做为运算符: 用于建立对象和调用构造函数。 Class obj = new Class();
u 做为修饰符 : 在用做修饰符时,new 关键字能够显式隐藏从基类继承的成员。具体地说,new声明的方法,当使用子类的类型来调用的时候,它会运行子类的函数,而若是类型是基类的话,被隐藏的基类函数会被调用。而子类中函数使用override的时候,则当使用子类的类型来调用的是,它会运行子类的函数,类型是基类的时候,仍会调用子类函数。


class Program { static void Main(string[] args) { A a1 = new A();//若是类型是基类的话,被隐藏的基类函数会被调用 a1.ShowInfo(); System.Console.WriteLine("----------"); A a2 = new B(); a2.ShowInfo();//若是类型是基类的话,被隐藏的基类函数会被调用 System.Console.WriteLine("----------"); B a3 = new B(); a3.ShowInfo();//若是类型是子类的话,它会运行子类的函数 System.Console.WriteLine("----------"); A a4 = new C();//子类中函数使用override的时候,类型是基类的时候,仍会调用子类函数。 a4.ShowInfo(); System.Console.WriteLine("----------"); C a5 = new C(); a5.ShowInfo();//子类中函数使用override的时候,则当使用子类的类型来调用的是,它会运行子类的函数 System.Console.WriteLine("----------"); Console.ReadKey(); } class A { public virtual void ShowInfo() { System.Console.WriteLine("Four wheels and an engine."); } } class B : A { public new void ShowInfo() { System.Console.WriteLine("A roof that opens up."); } } class C : A { public override void ShowInfo() { System.Console.WriteLine("Carries seven people."); } } }
u 做为约束: 用于在泛型声明中约束可能用做类型参数的参数的类型。new约束指定泛型类声明中的任何类型参数都必须具备公共的无参数构造函数


class Program { static void Main(string[] args) { AFactory<A> aFactory = new AFactory<A>(); //此处编译器会检查Employee是否具备公有的无参构造函数。 //若没有则会有The Employee must have a public parameterless constructor 错误。 Console.WriteLine("{0}'ID is {1}.", aFactory.GetName().Name, aFactory.GetName().ID); Console.ReadKey(); } } public class A { private string name; private int id; public A() { name = "dachong"; id = 100; } public A(string s, int i) { name = s; id = i; } public string Name { get { return name; } set { name = value; } } public int ID { get { return id; } set { id = value; } } } class AFactory<T> where T : new() { public T GetName() { return new T(); } }
2、结构体
一、概念:C# 中,结构体是值类型数据结构。它使得一个单一变量能够存储各类数据类型的相关数据。struct 关键字用于建立结构体。结构体是用来表明一个记录。
二、特色:C#中结构体的特色
(1)结构可带有方法、字段、索引、属性、运算符方法和事件。
(2)结构可定义构造函数,但不能定义析构函数。可是,您不能为结构定义默认的构造函数。默认的构造函数是自动定义的,且不能被改变。
(3)与类不一样,结构不能继承其余的结构或类。
(4)结构可实现一个或多个接口。
(5)结构成员不能指定为 abstract、virtual 或 protected。
(6)当您使用 New 操做符建立一个结构对象时,会调用适当的构造函数来建立结构。与类不一样,结构能够不使用 New 操做符便可被实例化。若是不使用 New 操做符,只有在全部的字段都被初始化以后,字段才被赋值,对象才被使用。


struct Books { private string title; private string author; private string subject; private int book_id; public void getValues(string t, string a, string s, int id) { title = t; author = a; subject = s; book_id =id; } public void display() { Console.WriteLine("Title : {0}", title); Console.WriteLine("Author : {0}", author); Console.WriteLine("Subject : {0}", subject); Console.WriteLine("Book_id :{0}", book_id); } }; class Program { Books Book1 = new Books(); Book1.getValues("C Programming", "Nuha Ali", "C Programming Tutorial",6495407); Book1.display(); Console.ReadKey(); }
3、类和结构体区别
一、结构是值类型,它在栈中分配空间;而类是引用类型,它在堆中分配空间,栈中保存的只是引用。
二、C# 中的简单类型,如int、double、bool等都是结构类型。若是须要的话,甚至可使用结构类型结合运算符运算重载,再为 C# 语言建立出一种新的值类型来。因为结构是值类型,而且直接存储数据,所以在一个对象的主要成员为数据且数据量不大的状况下,使用结构会带来更好的性能。由于结构是值类型,所以在为结构分配内存,或者当结构超出了做用域被删除时,性能会很是好,由于他们将内联或者保存在堆栈中。当把一个结构类型的变量赋值给另外一个结构时,对性能的影响取决于结构的大小,若是结构的数据成员很是多并且复杂,就会形成损失。
三、在结构体中能够声明字段,可是声明字段的时候是不能给初始值的。
四、类中若是咱们没有为类写任意的构造函数,那么C#编译器在编译的时候会自动的为这个类生成1个无参数的构造函数.咱们将这个构造函数称之为隐式构造函数 可是一旦咱们为这个类写了任意的1个构造函数的时候,这个隐式的构造函数就不会自动生成了。隐式的无参数的构造函数在结构中不管如何都是存在的,因此程序员不能手动的为结构添加1个无参数的构造函数,不然会报错。
五、C#语法规定在结构体的构造函数中,必需要为结构体的全部字段赋值。注意在结构体的构造函数中咱们为属性赋值,不认为是在对字段赋值,因此咱们在构造函数中要直接为字段赋值.
4、类和结构体的使用场所
一、当堆栈的空间颇有限,且有大量的逻辑对象时,建立类要比建立结构好一些;
二、对于点、矩形和颜色这样的轻量对象,假如要声明一个含有许多个颜色对象的数组,则CLR须要为每一个对象分配内存,在这种状况下,使用结构的成本较低;
三、在表现抽象和多级别的对象层次时,类是最好的选择,由于结构不支持继承。