相关文章推荐
活泼的泡面  ·  C# ...·  1 年前    · 
不拘小节的吐司  ·  OpenCV系列 | ...·  1 年前    · 
博学的绿茶  ·  python的importlib模块 - ...·  1 年前    · 

C#内容与知识点整理(3)

本次我们来说一下关于C#的面向对象的基础知识的第一部分,需要的朋友可以关注后续文章

引路:

二.面向对象基础

一.面向对象程序设计概述

首先,是面向对象的一些基本概念:

1.类和对象

2.属性,方法和事件

3.封装

4.继承

5.重载与重写

面向对象的优点:
1.维护简单。
2.可扩充性。
3.代码重用。
4.多态性。
具体内容见书p88

二.类

1.类的声明:
类的声明语法格式如下:
[类的修饰符] class 类名 [:基类名]
              //类的成员;
类似于下面这种:
public class Student
   private int num;
   public int number;
   string name;//默认私有
下面的表格说明了不同的类的修饰符之间的区别:
(注意,默认的类的修饰符是internal)

Microsoft官方文档对于类的访问修饰符的介绍如下:

可使用访问修饰符指定以下六个可访问性级别:

public:访问不受限制。

protected:访问限于包含类或派生自包含类的类型。

internal:访问限于当前程序集。

(protected internal:访问限于当前程序集或派生自包含类的类型。)

private:访问限于包含类。

(private protected:访问限于包含类或当前程序集中派生自包含类的类型。)

一个完整声明的Person类:
public class Person  
    public int pno;         //编号  
    string pname;       //姓名:私有的  
   public void setdata(int no,string name)  
      pno=no; pname=name;  
    public void dispdata()  
       Console.WriteLine("{0} {1}", pno, pname);  

2.类的成员

类是一种活动的数据结构。程序的数据和功能被组织为逻辑上相关的数据和函数的封装集合,这就是类。

类的成员可以分为两大类:数据成员和函数成员。

(1)类有哪些类别的成员:

注意,类的公有成员提供了类的外部接口,至于保护的成员, 只有在类继承时能体现出与私有成员不同 ,后续将展开介绍

接下来是关于类成员的修饰符:

(2)字段
字段是隶属于类的变量。它可以是任何类型,和所有变量一样, 字段用来保存数据,并具有两个特征:可以被写入和可以被读取
(i)定义字段
定义一个字段的格式如下:
访问修饰符 类型 字段名;
与其他类成员一样,字段的默认访问修饰符为private。
与C++语言相比,C#语言的字段有如下两个重要的差别:
C#中的字段可以赋初值 ,例如,在前面Person类声明中可以将pno字段定义改为:
public int pno = 101;
这样Person类的每个对象的pno字段都有默认值101。
C#在类的外面不能定义全局变量(也就是变量或字段),所有的字段都属于类,而且必须在类内部定义。

(ii)常量字段
可以在字段定义时使用const 关键字来定义常量字段(也就是类中的符号常量),并且要在同一语句中给常量字段赋初值。常量字段不是变量并且不能修改。

!!不允许在常量字段定义中使用 static 修饰符。
例如:以下类中定义了两个常量字段:

class MyClass
{
public const int N = 10; //共有常量字段
const double M = 3.14; //私有常量字段(默认private)
}

(iii)只读字
可以在字段定义时 使用readonly 关键字来定义只读字段。
在定义只读字段时, 可以在同一语句中给只读字段赋初值,或者在该类的构造函数给只读字段赋值,其他地方不能更改只读字段的值。
下面是一个正确的类的声明:
class MyClass
{
public readonly int f1=10; //允许只读字段初始化
readonly double f2; //只读字段
MyClass() //构造函数
{ f2 = 2.58; } //允许在构造函数给只读字段赋值
}
关于只读字段与常量字段:
readonly比const灵活得多 ,允许把一个字段设置为常量,但还需要执行一些计算,以确定其初始值.其规则是可以在构造函数中给只读字段赋值,但不能在其他地方赋值. 只读字段还可以是一个实例字段而不是静态字段.类的每个实例都可以有不同的值
另外,const字段为编译时常数,而readonly字段可用作运行时常数

例如,以下一段代码体现出了readonly关键字的优势:

internal class Class1
        public readonly int rd;
        public int n;
        public Class1()//如果是默认构造函数,那么rd=3;
            rd = 3;
        public Class1(int n)//如果是带参数的构造函数,那么rd=5
            rd = 5;
            this.n = n;
        public void showdata()
            Console.WriteLine("rd={0}", rd);


3.分部类
分部类可以将类(结构或接口等)的声明拆分到两个或多个源文件中。
若要拆分类的代码, 被拆分类的每一部分的定义前边都要用partial关键字修饰。 分部类的 每一部分都可以存放在不同的文件中 ,编译时会自动将所有部分组合起来构成一个完整的类声明。
4.类和结构类型的差异(具体差别见C#高级编程p86-87)

三.对象

1.定义类的对象
  一旦声明了一个类,就可以用它作为数据类型来定义类对象(简称为对象)。定义类的对象分以下两步:
1)定义对象引用
 其语法格式如下:
 类名 对象名;
 例如,以下语句定义Person类的对象引用p:
 Person p; 
2)创建类的实例
 其语法格式如下:
 对象名=new 类名( );
 例如,以下语句创建Person类的对象实例:
 p=new Person();
以上两步也可以合并成一步(比较常见)。其语法格式如下:
类名 对象名=new 类名();
Person p=new Person();
2.访问对象的字段
  访问对象字段的语法格式如下:
          对象名.字段名
  其中,“.”是一个运算符,该运算符的功能是表示对象的成员。
  例如,前面定义的p对象的成员变量表示为:
          p.pno
    !!注意:只能通过对象访问public成员
3.调用对象的方法
    调用对象的方法的语法格式如下:
    对象名.方法名(参数表)
例如,调用前面定义的p对象的成员方法setdata为:
    p.setdata(101,"Mary");
4.访问对象成员的限制
    对于类的私有成员,只能从声明它的类的内部访问,其他类不能看见或访问它们,也不能从类的外部即!!通过该类的对象访问它们!!。例如,对于前面定义并实例化的Person类对象p,使用p.pname是错误的,因为Person类中的pname字段是私有字段。
    对于类的公有成员,可以从类的外部即通过该类的对象访问它们。所以类的公有成员提供了类的外部接口。
    下面的图展示了Person类及其成员的表示:
设计一个控制台应用程序,说明调用对象方法的过程
using System;
namespace proj5_1
     public class TPoint    //声明类TPoint
     {    int x,y;     //类的私有字段
          public void setpoint(int x1,int y1)
          {     x=x1;y=y1;  }
          public void dispoint()
          {   Console.WriteLine("({0},{1})",x,y);  }
     class Program
     {   static void Main(string[] args)
                 TPoint p1 = new TPoint();  // 定义对象p1
                 p1.setpoint(2,6);
                 Console.Write("第一个点=>");
                 p1.dispoint();
                 TPoint p2 = new TPoint();  // 定义对象p2
                 p2.setpoint(8,3);
                 Console.Write("第二个点=>");
                 p2.dispoint();


:可以在对象浏览器中查看项目中类的信息
5.类对象的内存空间分配方式
    对象引用变量是一个引用类型的变量,和值类型变量一样,对象引用变量的空间也是在栈空间中分配的。
    而对象实例的空间是在堆空间中分配的,对象引用变量中可以存放对象实例的地址,这样通过对象引用变量来操作对象实例。
    !!务必了解对象引用变量和对象实例之间的差异。
    例如,对于前面声明的Person类,执行如下语句:
Person p1 = new Person();
Person p2 = new Person();
p1.setdata(1,"Mary");       //p1.pno=1,p1.pname="Mary"
p2.setdata(2,"Smith");      //p2.pno=2,p2.pname="Smith"
    其流程图如下图所示:
实际上,两个或多个对象引用变量可以引用同一个对象实例,例如:
        Person p1 =  new Person();
        Person p2 = p1;
流程图如下图所示:

这样, p1和p2都指向Person类的同一个实例 ,可以通过p1和p2对该实例进行操作.所有的类都是从object类派生的,它的静态方法ReferenceEquals用于判断两个对象引用变量所指的实例是否相同,相同输出true
当引用变量指向了新的实例,原来所指的实例就会丢失,此时其堆空间由CLR收回.

四.命名空间

1.命名空间概述:
在.NET中,类是通过命名空间(namespace)来组织的。 命名空间提供了可以将类分成逻辑组的方法,将系统中的大量类库有序地组织起来,使得类更容易使用和管理。

可以将命名空间想像成文件夹 ,类的文件夹就是命名空间,不同的命名空间内,可以定义许多类。在每个命名空间下,所有的类都是“独立”且“唯一”的。

2.使用命名空间
在C#中,使用命名空间有两种方式,一种是明确指出命名空间的位置,另一种是通过using关键字引用命名空间。
(1)直接定位在应用程序中,任何一个命名空间都可以在代码中直接使用 。例如:
System.Console.WriteLine("ABC");
这个语句是调用了System命名空间中Console类的WriteLine方法。
(2.1)使用using关键字
在应用程序中要使用一个命名空间, 还可以采取引用命名空间的方法, 在引用后,应用程序中就可使用该命名空间内的任一个类了。 引用命名空间的方法是利用using关键字,其使用格式如下:
using [别名=] 命名空间

using [别名=] 命名空间.成员

(比如,using myns=System.Math)(注意,System.Math指的是一个类),这样的话在使用的时候就可以将该别名作为前缀
(!!注意,不能使用using System.Math,这是因为System.Math是一个类而不是命名空间)
(2.2)自定义命名空间
在C#中,除了使用系统的命名空间外,还可以在应用程序中自已声明命名空间。其使用语法格式如下:
namespace 命名空间名称
{
命名空间定义体
}
其中,“命名空间名称”指出命名空间的唯一名称,必须是有效的C#标识符。例如,在应用程序中自定义Ns1命名空间:
namespace Ns1
{
class A {…}
class B {…}
}

在C#中开发项目时,每个项目都会自动附加一个默认的命名空间。如果在应用程序中没有自定义的命名空间,那么应用程序中所定义的所有的类都属于一个默认的命名空间,其名称就是项目的名称,这个命名空间称为根命名空间。

可以通过选择“项目”菜单下的“项目属性”对话框来查看或修改此命名空间。

如果有两个相同名称的类,可以放到不同的命名空间下面,见书p98
同样, 命名空间可以嵌套 ,在使用的时候要写例如Ns1.Ns2.类
注明:namespace语句只能出现在文件级或者命名空间级当中

五.构造函数和析构函数

构造函数可以被重载,析构函数不能(因为析构函数没有参数)
两者都不能被继承

1.构造函数
(1)什么是构造函数
构造函数是在创建给定类型的对象时执行的类方法。

构造函数具有如下性质:

(1) 构造函数的名称与类的名称相同。
(2)构造函数 尽管是一个函数,但没有任何类型, 即它既不属于返回值函数也不属于void函数。
(3)一个类可以有多个构造函数,但所有构造函数的名称都必须相同,它们的参数各不相同,即 构造函数可以重载。
(4)当类对象创建时, 构造函数会自动地执行 ;由于它们没有返回类型,因此不能像其他函数那样进行调用。
当类对象声明时,调用哪一个构造函数取决于传递给它的参数类型。
构造函数不能被继承

(2)调用构造函数
当定义类对象时,构造函数会自动执行。

2.1)调用默认构造函数
不带参数的构造函数称为默认构造函数。 无论何时,只要使用new运算符实例化对象,并且不为new提供任何参数,就会调用默认构造函数。假设一个类包含有默认构造函数,调用默认构造函数的语法如下:
类名 对象名=new 类名();
如果没有为对象提供构造函数,则默认情况下 C#将创建一个构造函数,该构造函数实例化对象,并将所有成员变量设置为相应的默认值。
2.2)调用带参数的构造函数
假设一个类中包含有带参数的构造函数,调用这种带参数的构造函数的语法如下:
类名 对象名=new 类名(参数表);

注意: 如果提供了带参数的构造函数,编译器就不会自动提供默认的构造函数,只有在没有别的构造函数的时候才调用默认的
可以把构造函数定义为private或protected,这样不相关的类也不能访问他们
注意this关键字的使用:this关键字可以用来处理同名变量


补充知识:例如下面这个类
class Testclass
{
private int pnum;
private string pname;
private Testclass(int number)
{
this.pnum = number;
}
}
这个类没有提供任何共有的或受保护的构造函数,因此不能用new在外部代码中实例化,这在下面两种情况下是有意义的:
(1)类仅用作某些静态成员或属性的容器,因此永远不会对他实例化
(2)希望类仅通过调用某个静态成员函数来实例化(这就是所谓对象实例化的类工厂方法)


关于静态构造函数见C#高级编程p80

设计一个控制台应用程序,说明调用构造函数的过程
namespace proj5_3
{ class Program
{ public class TPoint1 //声明类TPoint1
{ int x, y;  //类的私有变量
public TPoint1() { } //默认的构造函数
public TPoint1(int x1, int y1) //带参数的构造函数
{ x = x1; y = y1; }
public void dispoint()
{ Console.WriteLine("({0},{1})", x, y); }
}
static void Main(string[] args)
{ TPoint1 p1 = new TPoint1(); //调用默认的构造函数
Console.Write("第一个点=>");
p1.dispoint();//不传参数则第一个点默认是(0,0)
TPoint1 p2 = new TPoint1(8, 3);//调用带参数的构造函数
Console.Write("第二个点=>");
p2.dispoint();
}
}}
可以给字段设置为private,在不适用属性的情况下使用构造函数给私有字段传参

2.析构函数
(1)什么是析构函数
在对象不再需要时,希望确保它所占的存储空间能被收回。C#中提供了析构函数用于专门释放被占用的系统资源。析构函数具有如下性质:

析构函数在类对象销毁时自动执行。
一个类只能有一个析构函数,而且析构函数没有参数,即析构函数不能重载。

析构函数的名称是“~”加上类的名称(中间没有空格)。
与构造函数一样,析构函数也没有返回类型。
析构函数不能被继承。

(2)调用析构函数
当一个对象被系统销毁时自动调用类的析构函数。
设计一个控制台应用程序,说明调用析构函数的过程

using System;
namespace proj5_4
{ class Program
{ public class TPoint2 //声明类TPoint2
{ int x, y;
public TPoint2(int x1, int y1) //带参数的构造函数
{ x = x1; y = y1; }
~TPoint2()//实际开发的时候应该是用不到这个
{ Console.WriteLine("点=>({0},{1})", x, y); }
}
static void Main(string[] args)
{
TPoint2 p1 = new TPoint2(2,6);
TPoint2 p2 = new TPoint2(8, 3);
}
}
}
最终输出结果:
点=>(8,3)
点=>(2,6)
这说明,析构函数在对象被系统销毁时自动执行(在自己的visual studio上并未输出相关信息)

六.静态成员

1.静态字段

静态字段是类中所有对象共享的成员,而不是某个对象的成员,也就是说 静态字段的存储空间不是放在每个对象中,而是和方法一样放在类公共区中。

对静态字段的操作和一般字段一样,定义为私有的静态字段不能被外界访问。静态字段的使用方法如下:

(1)静态字段的定义与一般字段相似,但前面要加上static关键词。

(2)在访问静态字段时采用如下格式:

类名.静态字段名

下面一图说明了静态字段和非静态字段的存储方式:

说明: 静态成员是属于整个类的,不针对该类的某个对象,称为类成员,所以静态方法是通过类名来调用的 静态字段所有实例共享,所以修改静态字段之后所有实例的静态字段数值都会改变,但是非静态字段每个实例都会有自己的副本;

如图,静态字段的存储空间放在类公共区中,在创建实例时可以在实例中对类公共区进行访问

2.静态方法
    静态方法与静态字段类似,也是从属于类,都是类的静态成员。只要类存在,静态方法就可以使用,静态方法的定义是在一般方法定义前加上static关键字。调用静态方法的格式如下:
        类名.静态方法名(参数表);
        书上写的是static public void 这种,那也参考这种
     !!注意:静态方法只能访问静态字段、其他静态方法和类以外的函数及数据,
       不能访问类中的非静态成员(因为非静态成员只有对象存在时才有意义)。
       但静态字段和静态方法可由任意访问权限许可的成员访问。
关于静态成员和静态方法的其他知识,见书p101-p105
常量和索引器不能声明为静态成员
关于静态构造函数:(静态构造函数不允许出现访问修饰符,只能写一个static Student之类的)
1.在一个类中最多有一个静态构造函数,且不含有参数;
2.静态构造函数不会被继承
3.在所有的构造函数中静态的最先被执行;
4.在类的任何实例被创建之前或任意静态成员被引用之前自动调用静态构造函数

七.属性

1.什么是属性?
    属性描述了对象的具体特性,它提供了对类或对象成员的访问。
    C#中的属性更充分地体现了对象的封装性,属性不直接操作类的字段,而是通过访问器进行访问。
    属性的概念是:它是一个方法或一对方法,在客户端代码看来,是一个字段;
    比如Windows窗体的Height属性,使用mainform.Height=20;
2.属性声明
    属性在类模块里是采用下面的方式进行声明的,即指定变量的访问级别、属性的类型、属性的名称,然后是get访问器或者set访问器代码块。其语法格式如下:
        修饰符 数据类型  属性名称
             get访问器
             set访问器
      其中,修饰符有newpublicprotectedinternalprivatestaticvirtualoverride和abstract
(当会隐藏访问成员的时候可以用new,但是暂时不需要了解太多这方面的知识)
设计一个控制台应用程序,说明属性的使用。
using System;
namespace proj5_8
{   public class TPoint3    //声明类TPoint3
    {    int x,y;
          public int px
                get     //get访问器
                {   return x; }
                set     //set访问器
                {   x = value; }
         public int py 
                get     //get访问器
                {  return y; }
                set     //set访问器
                {  y = value; }
     class Program
     {  static void Main(string[] args)
             TPoint3 p = new TPoint3();
             p.px = 3; p.py = 8;    //属性写操作
             Console.WriteLine("点=>({0},{1})", p.px, p.py);//属性读操作
属性是特殊的方法成员,因此存在一些特定的局限:
不能使用set访问器来初始化一个struct或者class的属性
在一个属性中,最多只能包含一个get访问器和一个set访问器。属性不能包含其他方法、字段或属性。
get和set访问器不能获取任何参数,所赋的值会使用value变量,自动传给set访问器
不能声明const或者readonly属性
get访问器不带任何参数,且必须返回属性声明的类型,get访问器必须最后执行一条return语句
,返回一个和实行类型相同的值.也不应为set访问器指定任何显式参数,
但编译器假定它带一个参数,其类型也和属性相同,并表示为value
get和set访问器可以以任何顺序声明,除此之外,属性不允许有其他方法

关于get访问器和set访问器的额外知识,参阅microsoft的官方文档

使用属性 - C# 编程指南 | Microsoft Docs

C#允许给属性的get和set访问器设置不同的访问修饰符,所以可以有public的get访问器和private的set访问器这种,
如果两个都不具备属性的访问级别,就会报错
另外,如果直接写public int Age{get;set;}
这种情况下不需要再写private int age,因为编译器会自动创建它,见下面一条
3.自动实现的属性(此时只声明属性即可,不需要定义后备字段)
    所谓自动实现的属性是指只声明属性而不定义其后备字段,编译器会创建隐藏的后备字段,
并自动挂接到 get  set 访问器上。
    class MyClass
        public int f    //自动实现的属性
            { set; get; }   
    class Program
        static void Main()
              MyClass s=new MyClass();
              s.f = 100;