概述
文章目录
- 访问修饰
- 类修饰符
- 接口
- virtual
- 其他修饰符
前文提要:
????零基础掌握C#开发中最重要的概念 ????面向对象快速上手
????懂了委托,才算真正入门 ????学会泛型,迈向高手之路
????Winform,最友好的GUI ????Winform+OpenGL+MathNet处理Gauss光斑
在快速入门教程中已经疏解了一些面向对象的基本概念,但并不深入,只对类的构造和继承做了简单的介绍。下面将深入理解C#的面向对象,文集中针对12个修饰符以及接口,从内到外对类进行展开讲解。
访问修饰 | private, public, protected, internal |
特殊类 | static, sealed, abstract ; 接口interface |
成员修饰 | static, sealed, abstract, overade, virtual |
其他 | const, readonly, partial |
由于面向对象本来就大肠包小肠,所以本节所有示例均不使用顶级语句。
访问修饰
在声明类成员和方法的时候,往往在类型前面加一个public
或者private
,比如public int add
这种,用以标识外部是否可以访问,此即访问修饰符。
C#一共有6种访问修饰符,这些修饰符当然在类内都是可以访问的,但是在类外,根据是否为派生类、是否在相同程序集,共分为四种特殊情况。
public
和private
是两种极端情况,前者是公共属性,任何时候都可以访问;后者是私有属性,除了类内,其他人都不可以访问。protected
为保护类型,只有派生类可以访问;internal
为内联类型,只有相同的程序集可以访问。private protected
在protected
的基础上缩减一步,只允许相同程序集的派生类访问。protected internal
在protected
的基础上拓展一步,还允许不同程序集的派生类访问。
下面新建一个项目oopTest
,在命名空间oopTest
中新建一个TestClass
,分别用这六种不同的访问修饰符做一个函数。
public class TestClass
{
public void pubPrint(){
Console.WriteLine("public");
}
private void priPrint(){
Console.WriteLine("private");
}
protected void proPrint(){
Console.WriteLine("protected");
}
internal void IntPrint(){
Console.WriteLine("internal");
}
private protected void priProPrint(){
Console.WriteLine("private protected");
}
protected internal void proIntPrint(){
Console.WriteLine("protected internal");
}
}
下面在不同的应用场景调用这六个函数,首先是main
函数中调用,结果如下,其中private, protected, private protected
标红了。
然后新建一个子类,在子类中调用这六个函数,效果如下
接下来右键解决方案,新建一个项目LibTest
,将TestClass
移动到这个项目的命名空间下。然后右键oopTest
的依赖项,在引用管理器中勾选LibTest
,然后在oopTest
中using LibTest
,这样名义上就可以调用LibTest
库中的内容了。
这次,Main
函数和子类的表现如下,不考虑public
,在子类中,protected, protected internal
还依旧坚挺,而在Main
函数中则全员阵亡,此时在Main
函数中调用TestClass
,相当于既不在相同程序集,也不是派生类。
类修饰符
C#除了质朴的class
,还有三种被修饰的类,分别是静态类、密封类以及抽象类,三者分别用修饰符static, sealed, abstract
来修饰,这三者互不兼容,说明如下
static
不能被实例化为对象,且只能从基类派生sealed
密封类不能被继承abstract
抽象类只能被继承,不能被实例化
对于习惯了面向过程或者函数式编程的人来说,使用静态类有种找到家的感觉。静态类不能实例化的同时,支持不开箱即用,静态类就像是一个函数库,可直接调用,像下面这种。
static class StaticClass
{
public static void printStatic()
{
Console.WriteLine("static class");
}
}
class Program
{
static void Main(string[] args)
{
StaticClass.printStatic();
}
}
密封类和抽象类互为相反,前者不能被继承,后者只能被继承。密封类禁止派生,除了能够带来一些安全性上的优势之外,有时也能略微提高一点成员调用速度。
抽象类用于提供一个基类定义,并让多个派生类共享。抽象类中的方法,既可以是抽象的,也可以不是抽象的,通过abstract
修饰的方法,只能声明,不能实现其具体内容。
public abstract class AbstractClass
{
// 用abstract修饰之后,不可实现
public abstract void printSubAbstract();
// 不用abstract修饰,必须写细节
public void printAbstract()
{
Console.WriteLine("Abstract");
}
}
如果一个普通类或者密封类继承了抽象类,那么这个类必须实现抽象类中所有的抽象方法,其中需要用到overide
;而抽象类继承抽象类则不必。
public class SubAbstract : AbstractClass
{
public override void printSubAbstract()
{
Console.WriteLine("Abstract");
}
}
// 如果没有abstract修饰,是会报错的
public abstract class AbstractAbstract : AbstractClass
{ }
接口
抽象类其实已经够抽象的了,但还有更加抽象的类,即接口interface
。接口太特殊了,一般在命名的时候多以I
开头。
public interface ITest
{
public void printI();
}
public class Test : ITest
{
public void printI()
{
Console.WriteLine("interface");
}
}
抽象类和接口都是便于被继承的类,二者的区别大致为,抽象类大而全、接口小而专,关于二者的区别,网上说的很精辟了
飞机会飞,鸟会飞,二者都继承了“飞”这个接口;但是F22属于飞机抽象类,鸽子属于鸟抽象类。
另一方面,如果要去买飞机,你必须说明白要买什么飞机(派生类),如果光说我要买飞机,那没人知道你要买什么飞机(抽象类不能实例化)。
virtual
诸如abstract, static, sealed
除了可以修饰类之外,也可以修饰类中的方法,而且其含义也是高度相似的:
- 密封方法像密封类一样,无法被重写
- 抽象方法只能在抽象类或接口中使用,这在前面已经举例说明了,而已经
abstract
修饰之后,想要重写就需要用到overide
修饰符。 - 静态方法也和静态类一样,可以在其所在类没有实例化的情况下,直接调用,非常便捷。
静态方法和实例方法在调用上存在差异,暗示着二者在编译期间就存在不同的行为。由于静态方法在类未经实例化的时候就已经可以调用了,所以程序一经编译,就已经分配好了静态方法的内存,这块领地既不可更改也不可销毁;而普通的实例方法则在对象实例化之后紧跟着被创建,对象被回收之后,也就紧跟着被销毁。
除了这几个老面孔之外,基类成员还有一个修饰符virtual
,为了理解这个修饰符,先考虑这样一件事,假设B是A的子类,那么我先声明一个A的对象b,然后再将b实例化成B
,那么对于被B
重写的方法,应该执行哪一个?
说起来很绕,具体看代码如下
public class A
{
public void print()
{
Console.WriteLine("A");
}
}
public class B : A
{
public void print()
{
Console.WriteLine("B");
}
}
class Program
{
static void Main(string[] args)
{
A b;
b = new B();
b.print();
}
}
换言之,A b
指明了b
是A
类;但b = new B()
却建了个B
类,那么b.print()
执行谁呢?
结果有些出乎意料,最后输出了B
,看来对于方法重写来说,声明比实例化更重要。
但这里有一个吊诡之处,如果声明一个抽象类,然后对抽象类实例化为其子类,再调用子类中实现的抽象方法,最终会执行谁?
这个问题很好回答,抽象方法没实现,必须执行子类的方法,所以这里看似出现了一个矛盾。
但仔细思量却可以发现,被abstract
修饰的方法,在子类中是不能直接重写的,而必须用到overide
关键字,换言之,方法经overide
修饰后,会导致实现比声明更重要。
但接下来又有一个问题,abstract
不能在常规的类中使用,而且overide
不能重载普通方法。就在这个逻辑崩溃的时刻,虚方法virtual
出现了。通过virtual
修饰的方法,可以被overide
重写。
public class A{
public virtual void print(){
Console.WriteLine("A");
}
}
public class B : A {
public void print(){
Console.WriteLine("B");
}
}
public class C : A{
public override void print(){
Console.WriteLine("C");
}
}
class Program{
static void Main(string[] args){
A b;
A c;
b = new B();
b.print(); // 输出 A
c = new C();
c.print(); // 输出 C
}
}
其他修饰符
一般新建一个WPF程序,VS默认的入口类都这么写
public partial class MainWindow : Window
其中partial
并没有定义一种新的类,相比之下,更像是个语法糖,允许程序员在两个文件中写同一个类。作为窗口程序,其MainWindow
往往特别大,显得partial
非常常见。
const
表示声明一个常量;readonly
表示声明一个只读变量,也叫动态常量。前者在编译时直接被替换为对应常量的值,所以不费内存;后者的值则在运行之后获得,但不可被更改。
const
在使用时有一定的限制,即只能修饰基元类型,比如int, string
之类的,如果有一个List<int>
,则只能通过readonly
修饰。一般如果想把一个泛型结构当作永不更改的常量使用,比较习惯的用法是static readonly
。
C#中的修饰符当然不止这些,但extern
用于调用外部程序,尤以调用dll
时居多;in, out, new
主要应用在泛型方面,volatile, async
则与并发编程更为密切,这些内容都不是三言两语说得清的,所以就不放在对OOP的介绍中了。
最后
以上就是干净羽毛为你收集整理的C#面向对象进阶:十二大修饰符一网打尽的全部内容,希望文章能够帮你解决C#面向对象进阶:十二大修饰符一网打尽所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复