从C++学C# [5] 类与结构

概述

在C#中的类与C++中编写格式类似,除了多了一点修饰关键字以外,其他是类同的。但提供了很多更便利的方式来编写类,大多属于语法糖功能。

类和结构其实行为上很接近,所以就放在一起。

类与结构中的不同:

名称 支持继承 按引用传递 指定内存布局
T T F
结构 F F T

其声明形式只是将class换为struct关键字,结构和其他基础类型一样,属于值类型,派生于System.ValueType。因此结构可以访问其他方法,甚至可以重写其中的方法。

类的基础

类的声明与构造

在C#中声明一个类的方法和C+是类似的。

//internal是权限修饰符,不写的话对类而言,默认就是internal
//此外还有public protected private static四种,含义和C++一致
//internal则是限于程序集访问,static只类中所有成员、方法、属性等都均为静态的

internal class Point
{
    //对于成员、方法、属性而言,默认的修饰符是private
    public int x { get; set; }
    public int y { get; set; }

    //构造函数写法和C++也是类似的
    public Point()
    {
        //dosth...
    }

    //toString是WriteLine()方法默认调用的方法,实现该方法后可以直接WriteLine(Point)。
    public override string ToString()
    {
        return $"{x},{y}";
    }
}

静态构造函数

这是一个C++没有的特性,静态构造函数,这种构造函数只会执行一次,一般用于初始化静态类成员。它的写法如下:

class MyClass
{
    static MyClass() //该构造函数只会被执行一次
    {
        //....
    }
}

只读成员

C#对于只读成员提供了 readonly 关键字,与const不同的是,const实际上会被编译成常量,而readonly可以也仅可以被构造函数初始化。

public class Document
{
    private readonly DateTime _creationTime;
    public Document(){
        _creationTime = DateTime.Now;
    }
}

只读属性(成员)也可以通过C#自动实现,如果在属性定义时不给出set访问器则是一个只读属性。同理,如果简单的不给出get访问器,就可以创建出只写属性,但一般而言很少情况需要这么写。

自动属性初始化使用属性初始化器来初始化,如:

public string Id {get;} = Guid.NewGuid().ToString();

也可以在构造函数中初始化,如:

public class Person
{
    public Person(string name) {Name = name;}
    public string Name {get;}
}

匿名类

C#中的匿名类和C++中的匿名结构体类似,但在C#中,所有类型都会继承自object,所以需要与new关键字一起使用,如:

//创建了包含三个属性的对象(类)
var captain = new{
    FirstName = "James",
    MiddleName = "T",
    LastName = "Kirk"
}

如果有类型完全一致的对象,他们之间是可以直接互相赋值的,也可以使用已有的属性作为匿名类的内容。

类的继承

抽象类(纯虚基类)(Abstract Class)

行为和C++的纯虚基类一样,主要用于多态,定义抽象类的方法如下:

//在类最前方加入abastract修饰,说明该类是抽象类,抽象类中所有的方法都应该是抽象的。
abstract class AbstractShape
{
    public abstract void GetDraw();
    public abstract void MoveTo(Point newPoint);
}

虚方法(virtual method)

virtual方法和C++完全一致,不同的是,在实现抽象类方法或者重写virtual方法时,需要加关键字override,显式的说明这是一个重写,这个设计有助于减少因为基类方法参数或者方法名改变后导致的错误。在C++中这种情况会变得多出来一个方法。

继承(inherited)

C#继承的写法和C++一致,但不支持权限修饰及多重继承(只有接口可以多重继承)。有一点特殊的是,对于结构体而言,支持接口继承

覆盖(overload)

对于要隐藏或覆盖(overload)基类非virtual方法,需要显式的在最前面加new关键字,否则编译器会提出警告,但是不写也不会对结果造成影响。

封闭(sealed)

sealed修饰方法可以让一个方法不能被重写或是一个类不能被继承,如string就是sealed的类,一般而言如果一个方法要sealed,那么正常情况下它应该是继承自某个类的方法,但不希望再被继承后重写,如果是一个基类的方法不想被继承,那么就不应该把该方法声明为virtual

基类构造

C#调用基类构造函数的方法和目的和C++一致,即如果需要自定义构造函数,则需要使用初始化列表来调用基类函数,不同的是C#统一使用base关键字而非C++中使用基类名称。

public class Ellipse : Shape
{
    public Ellipse() : base() {} //使用base和初始化列表调用基类构造函数
}

基类方法

想在派生类中使用基类方法,和构造是类似的,也是通过base关键字实现

base.Print() //调用基类的Print方法

一颗栗子

class Shape : AbstractShape
{
    public Point Point { get; } = new Point();
    public Size Size { get; } = new Size();

    //继承自抽象类的所有方法都必须实现,而且要加关键字override
    public override void GetDraw()
    {
        Console.WriteLine($"Shape with {Point} and {Size}.");
    }

    //sealed表示该方法不应该再被重写,在形状中,所有形状的移动都应该是一致的
    public sealed override void MoveTo(Point newPoint)
    {
        Point.x = newPoint.x;
        Point.y = newPoint.y;
    }

    //新增加一个抽象类中没有的方法
    //virtual和C++一致,说明该方法可被重写
    //属性和静态函数无法为虚的,因为这个概念只对实例函数有意义
    public virtual void Resize(Size newSize)
    {
        Size.height = newSize.height;
        Size.width = newSize.width;
    }
}

class Rectangle : Shape
{
    //要重写基类的方法,必须显著声明override,这个设计有助于在基类参数表或名改变时使编译器发现并对这个重写报错
    //而如果是C++的话则不会报错并且多出一个方法
    public override void GetDraw()
    {
        Console.WriteLine($"Rectangle with {Point} and {Size}.");
    }

    //被sealed的方法是可以被覆盖的,但不能被重写,这是两个概念!
    //public override void MoveTo(Point newPoint); Error!
    //加new关键字可以隐藏编译器的可能覆盖基类方法警告
    //同时提醒程序员该方法是覆盖了基类方法的
    //但是不加也不会造成任何影响
    new public void MoveTo(Point newPoint)
    {
        Point.x = newPoint.x;
        Point.y = newPoint.y;
    }
}

//sealed封闭一个类可以使该类不能被继承
//对于矩形来说,正方形应该是最后的性质,因此该类不应该再被继承
sealed class Square : Rectangle
{
    public override void GetDraw()
    {
        Console.WriteLine($"Square with {Point} and {Size}.");
    }

    public override void Resize(Size newSize)
    {
        if (newSize.width == newSize.height)
        {
            Size.width = newSize.width;
            Size.height = newSize.width;
        }
    }
}

多态(polymorphism)

多态可以动态的定义调用的方法,与C++中的多态一致,假设Shape是基类,那么任何派生自Shape的类都可以被其管理,并动态地使用Shape中定义的方法。

static void DrawShape(AbstractShape sp)
{
    sp.GetDraw();
}

static void Main(string[] args)
{
    Square s = new Square();
    s.Size.height = 10;
    s.Size.width = 10;
    s.Point.x = 5;
    s.Point.y = 10;

    Rectangle r = new Rectangle();
    r.Size.height = 5;
    r.Size.width = 10;

    Shape sp = new Shape();
    sp.Size.height = 300;
    sp.Size.width = 400;

    Console.WriteLine("Hello World!");

    s.GetDraw();
    r.GetDraw();
    sp.GetDraw();

    Console.WriteLine("Draw with polymorphism.");
    DrawShape(s);
    DrawShape(r);
    DrawShape(sp);
}

这段程序输出的结果是

Hello World!
Square with 5,10 and Size: 10,10.
Rectangle with 0,0 and Size: 10,5.
Shape with 0,0 and Size: 400,300.
Draw with polymorphism.
Square with 5,10 and Size: 10,10.
Rectangle with 0,0 and Size: 10,5.
Shape with 0,0 and Size: 400,300.

修饰符一览

修饰符 应用于 说明
public 所有类型和成员 任何代码均可以访问
protected 类型和内嵌类型的所有成员 只有派生的类型可以访问
internal 所有类型或成员 只能在包含它的程序集中访问该项
private 类型和内嵌类型的所有成员 只能在它所属的类型中访问该项
protected internal 类型和内嵌类型在所有成员 只能在包含它的程序集和派生类型的任何代码中访问该项
new 函数成员 成员用相同签名隐藏继承的成员
static 所有成员 成员不依附于实例,C++的静态方法
virtual 仅函数成员 成员可以被派生类重写,并支持多态使用
abstract 仅函数成员或类 定义为抽象类,无法被实例化
override 仅函数成员 重写了继承的虚拟或抽象成员
sealed 所有类型或成员 禁止类被继承或属性和方法被重写,该修饰如果用于方法必须和override一起使用,否则不符合逻辑。
extern 仅静态[DllImport]方法 成员在外部用另外一种语言实现。

猜你喜欢

转载自blog.csdn.net/GarfieldGCat/article/details/80974127