【C#】C#入门学习笔记

前言


        友友们好,本文为是 C# 入门的学习知识,包括笔者C#学习过程中的认识和心得。

        这篇笔记整理花费一周时间,可以收藏拿来当作实际开发的 “划重点” 。由于内容中并不包含传统C语言本身的基础特性和基本语法,因此本文更适合有 C/C++ 基础的同学食用。或是有 C# 经验的小伙伴进行自查。

        如果你对 C/C++ 感兴趣,或者在学习 C# 前需要想要了解大概的学习路线,可以参考我的另一篇博文:《C、C++、C# 我全都要!无痛学习经验分享》

初学难点提要


以下部分内容属于笔者初学时期略微难啃一点的内容,帮你挑出来了。建议同学们重点照顾:

        5、命令行输入输出Write相关内容

        6、数据格式转换:Parse法,把字符串转化为对应的类型(比较容易忘)

        9、二维数组(C#不同于 C/C++ 的表示方法)

        11、值类型和引用类型

                “Xiaochou x1” 不会调用构造,“Xiaochou x1 = new Xiaochou()” 才会

        14、可变长参数 params 关键字函数

        21、“只外部获取,不外部修改” 的成员变量——成员属性(成员变量的加强版)

                这个章节要注意名为 name 和 Name 成员变量的区别,文章正文会体现

                name:受保护的普通成员变量

                Name:对外可访问的成员属性“接口”

        23、索引器(属于高级语法)

                 public Person this [int index]

        27、扩展方法

    static class Add {
        public static int IntAddOne(this int thisval) {
            return thisval + 1;
        }
    }

               其中,对 Add 类有 static 要求,对 IntAddOne 有 public static 的要求。

               其中的 this int, int 是扩充的那个类,任意变量名取什么都行。

        29、分部类:partial,把一个类分成几部分声明 partial class Person

        36、密封类 / 抽象类

     


环境搭建

一、新建项目

        笔者使用的是VS2019,实测2015以上版本也可以兼容本篇内容。

        进入修改,安装.NET桌面应用 → .NET SDK,之后再打开VS2019

【C#】C#入门学习笔记_第1张图片

二、新建解决方案下的项目

        注意不是新建项=.=

        是新建项目!项目!每个解决方案下可以新建多个项目

【C#】C#入门学习笔记_第2张图片

【C#】C#入门学习笔记_第3张图片

         进入到如下示例程序,运行显示HelloWorld,则环境配置完毕。

【C#】C#入门学习笔记_第4张图片

笔记正文


1、学会两个最基本的快捷键——快捷注释

        ctrl K+C         注释

        ctrl K+U         去注释

        F12                跳转到 函数/类/命名空间 的声明

2、认识代码块、函数块和命名空间

        这个代码用来输出Hello World

【C#】C#入门学习笔记_第5张图片

        引用了一个工具包 System,其中 System 是通过:

namespace System{ ... }

        这种方式定义的一个巨大的工具包,内部是各种系统自带的函数的声明。

3、查看函数或类的定义(F12)

        当我们想查看一个函数或类的定义,可以选择它,按 F12 进行跳转。

【C#】C#入门学习笔记_第6张图片

4、region(代码块的折叠)

        使用 region 指令,可以将一段代码折叠成一个独立的区域,这个区域可以包含多个代码块,例如类定义、属性、方法等。在 Visual Studio 中,可以通过单击左侧的减号来折叠或展开区域,也可以使用快捷键 Ctrl+M+M 来切换区域的折叠状态。

【C#】C#入门学习笔记_第7张图片

5、输入输出

    Console.WriteLine("Hello World!");
    string sin = Console.ReadLine();       // 等效于 cin  <<
    Console.WriteLine("OK");               // 等效于 cout <<
    Console.ReadKey();        // 监听一个按键消息。如果有任何按键立刻释放阻塞
    Console.WriteLine("   Detected.");

6、数据类型转换

        数据类型转换的本质不仅仅是将相同长度的数据放在不同的容器中,而且是根据数据类型的范围和精度要求进行的。在C#中,数据类型分为值类型和引用类型。

        通常情况下,值类型的数据(例如 int、double、struct 等)可以直接进行类型转换。而引用类型的数据(例如class、interface、delegate)不建议用强制类型转换,因为转换的结果可能引发空指针。标准的转换方式是通过容器 as 成其他类型,这点在后续进阶过程中会学到。

        具体的转换方法:

        隐式转换:由编译器自动实现的默认转换。

        显示转换:

                1)强转,同C语言强转方法(在C#中较少使用)

                2)Parse法,把字符串转化为对应的类型

                语法: vartype.Parse("str") , eg: int.Parse("123");

            string inp = Console.ReadLine();      // 等于 cin
            Console.WriteLine(inp);
            Console.WriteLine("result is:" + (int.Parse(inp)+1));
            //输入123字符串,Parse成int,返回这个输入数+1的结果

                3)Convert 方法,普适方法,可以更精确乱转,还可以四舍五入

                语法: Convert.ToXXX(data)

                string和其他类型的互相转换:

            string i = "6";
            int a = Convert.ToInt32(i);
            Console.WriteLine("result is:" + a);

            float f = 1.233f;   //注意C#要求必须写f作为小数后缀
            string ret = Convert.ToString(f);
            Console.WriteLine("result is:" + ret);

                4)其他变量转字符串: .ToString() 大法

            float f = 1.233f;   
            Console.WriteLine("result :" + f.ToString());

                甚至于你可以写:

                        1.ToString()

                这是因为 C#提供了一种简化的语法来调用 ToString()方法

7、异常捕获

        最基本的:

        以下代码中,当 try 中的代码执行异常时,会跳出 try 代码,转而执行 catch 的代码。

        在没有使用 throw() 的情况下,try中的异常一般不会导致程序异常关闭。

try{
   这里写我们要执行的
}
catch{
   这里写如果异常,那么捕获异常的代码
}

        进一步的,我们可以使用进阶版的异常捕获代码。当发生异常时,exception e 可以被捕捉下来,编程者可以决定是打印输出或者存留到日志里。

try{
   这里写我们要执行的
}
catch(exception e (用来捕获异常内容)) {
   这里写如果异常,那么捕获异常的代码
   throw;      // 加上这句的效果是异常发生的时候抛出。
}
finally{
    这句对不对都会执行,初步使用可以不用 finally
}

8、字符串拼接

1)自动转换规则:如果一个string+int出现时,这个int 会自动 Tostring

        str += 1;         //不报错,结果是 str 的后面添1

2)字符串拼接规则

        str += " " + 1 + 2 + 3; //结果是 123

        str += 1 + 2 + 3 + " "; //结果是 6

        str += 1 + 2 + " " + 3; //结果是 3 3

原因是 先计算运算符右边的,算完的部分会进行类型合并。X+string就是合并成string类型

3)Format拼接

        string str = string.Format("我是{0},我今年{1}岁", a, b);

9、一维数组,二维数组的定义

        这部分定义的格式较为灵活,推荐大伙儿只掌握五种中的一种方法。

        一维数组:

    int[] arr1 = new int[] { 1, 2, 3 };
    int[] arr2 = { 1, 2, 3, 4 };
    //这两种都可以,感觉比较自由,注意不用C风格就好了

        二维数组:(注意只能用new进行预分配空间)

    int[,] arr2 = new int[2,2];
    arr2[0,0] = 1;

        C#在一维和二维数组的定义上采用了与C和C++不同的形式,主要是为了提供更好的类型安全性和可读性,并简化语法。

10、获取数组长度

    // 获取数组的个数
    int[] arr = new int[] { 1, 2, 3 };
    int len = arr.Length;
    // 获取数组某个维度的个数,入参dimension,一般用于高维数组的kkjk
    int[] arr = new int[] { 1, 2, 3 };
    int len = arr.GetLength(0);

11、变量的值类型和引用类型

        值类型:基本数据类型+结构体,值类型保存在栈空间里,在内存中的角色相当于“房间”

        引用类型:类+数组。保存在堆空间,在内存中的角色相当于 “门牌号”

                引用类型的变量在 arr2=arr1 之后,由于操作的是“地址引用的”

                在改arr2[x]的时候,再去读arr1会发现改变了。

                这是因为操作的具体内存是一个地址。    

【C#】C#入门学习笔记_第8张图片

        特殊的引用类型:string

        其实在使用感受上,string认为是值类型其实也可以。

        在申明string类型时候,系统会自动用临时变量分配一个string容器,不用担心门牌号问题。

12、VS调试和断点核心技能        

        遇到问题了怎么办?可以利用调试功能逐步看内存。

        调试的功能比较庞大,初学我们只学习使用最简单的办法

        调试四部曲:1)设置断点 2)运行 3)关注左下角自动/监视窗口 4)F10逐步

【C#】C#入门学习笔记_第9张图片


 

13、引用型变量的函数操作

        下面这个函数在C#里,由于数组是引用规则,作为入参其结果居然会变(这点直观上与C/C++很大不同,但其实内因是一样的)

// Change函数
static void change(int[] arr)
{
    arr[0] = 233;
    return;
}
...
int[] arr1 = { 1, 2, 3 };
change(arr1);
//这个操作的结果是会把 arr1 的首项值 = 233;
      

        本质原理:

【C#】C#入门学习笔记_第10张图片

 图-引用类型的传参过程

        笔者初学时,一时tm难以接受!经验证,值传递型不会有这个问题。

        以下是值传递的验证代码:

static void change(int[] arr) {
  arr = new int[] { 233, 233, 233 };  //不直接修改,而是new了一个!
  return;
}
...
int[] arr1 = { 1, 2, 3 };
change(arr1);

        由于在函数内部重新对arr分配了一个新的 int[ ] 空间,导致原有的关系被破坏

        这个和 int *p = malloc() 有所区别,但其内部设计思想类似。

【C#】C#入门学习笔记_第11张图片

13、ref (out 和 ref,二者使用方法几乎是一模一样的)

        ref 等效于C++中函数参数的引用,相当于&,需要注意的是函数定义和使用都要加ref。eg:

static void change(ref int value)

{

value = 233;

return;

}

...

int origin = 0;

change(ref origin);

        ref 和 out的区别:out的传入变量必须在内部赋值,就是说函数 默认会对out参数修改值

        ps:你要用out,就一定要改他; ref 的变量则是必须要经过初始化。

14、 可变长参数 params 关键字函数

        必须是数组类型注意

        int func(int a, char b, params int[] c)

        然后params只能有一个,且放在最后

15、重载和返回值类型无关,只和参数有关

        如果入参一样,尽管返回值不同仍然会有错误

        注意一点,ref int 和 int 算是两种不同的入参类型,但是 ref int 和 out int 算是一种。

        

16、结构体在C#里不可以嵌套,也不准套娃

        需要注意的几个点:

    1. C#的结构体是可以写构造和析构函数的,但是必须含参,因为无参构造默认生成了。
    2. C#的结构体是可以用 private 等修饰的 (学名访问控制符,默认为private)
    3. 结构体可以使用 this 关键字来引用当前对象。

        结构体:

                MyStruct stu1 = new MyStruct { "qingxiu", 23 };  // 这种初始化方法不可用

                MyStruct stu1 = new MyStruct {age=23}; // 这种对劲儿,需要赋值时说明属性

                MyStruct stu1 = new MyStruct();  // 这种也对劲,注意有一个(),初学者很容易忘

【C#】C#入门学习笔记_第12张图片

17、面向对象优点

        清晰的逻辑关系,更高的效率、复用性和开发效率。

18、面向对象的三大特性(封装、继承、多态)和七大原则(偏理论)

        这部分与编程的语言学习关系不大,有兴趣的同学自行检索即可。

        这部分知识多归纳于 软件工程设计模式 这两门课程,分布于国内大学的本科和研究生阶段。感兴趣的同学们可以从 MOOC 上补充这些知识。

19、有参构造函数的内容继承

        + :this() 继承无参构造函数的代码,以完成复用

        同样的,可以是:有参构造函数 + :this(age+1)  //继承有参构造函数的代码,且入参自定

        eg:

    class Person
    {
        private string name = "Default";

        public Person() {
            Console.WriteLine(this.name + " Created.");
        }

        public Person(string Name):this(){
            this.name = Name;
            Console.WriteLine(this.name + " Created.Append");
        }
    }

        最下方的 Person(string Name) 构造函数会继承 Person() 中的内容。

        调用方法如下:

                Person p1; // 注意,这只是申明变量,并没有new操作,所以不会调用构造

                Person p1 = new Person(); // 调用无参构造

                Person p1 = new Person("blabla"); // 调用无参构造+有参构造, 因为上文追加了:this()

20、自动垃圾回收机制GC

        垃圾的定义:没有任何被引用的内容,需要被回收和释放

        垃圾回收的过程:遍历每代堆上分配的所有对象。

        从1代开始存,如果当前第n代存满就会触发GC,回收&重排前1~n代。

        具体机制:

【C#】C#入门学习笔记_第13张图片

        实际上是多级优先反馈队列的一个应用。GC运用了局部性原理。最终经常被引用的变量会升代,而大内存一开始就会在2代上。

        手动触发GC:GC.Collect();

21、成员属性

        “只外部获取,不外部修改” 的成员变量(属于高级语法)

        背景:3P(private、protected、public)方式不便在外面定义一种约束。

                private: 仅内访问

                protected: 仅内、内子类访问

                public: 内外访问

                然后并没有那种 “仅外访问” 的,为了便于灵活的权限管理,产生了成员属性的需求。

        成员属性的几个tips:

        1)关键特征:对私有成员变量的专用 get{} 和 set{}。 这两个东西叫做“访问器”。

        2)value关键字:代表入参

        3)get和set可以只实现一个,二者也可以声明访问权限。

        4)get和set可以被设计成 加密、控制访问权限等应用。

        基本操作:

    class Person
    {
        private string name = "Default";

        public string Name{
            get {
                return name;
            }
            set {
                name = value;    // value关键字:简化代表为入参
           }
        }
        ....
调用:
       p1.Name = "Yooo!";  
       Console.WriteLine(p1.Name); 

        扩展操作:存储加密

get{
    return name - asdafs;    // 这样在内存里就找不到存入的值了
}
set{
    name = value + asdafs;    
}

        扩展操作:把变量的访问权限设置为【单纯只想让外部访问,而不可修改】

public string Name{
    get{
        return name;
    }
    private set { }
}
或者简写为:
public string Name
{
    get;
    private set;
}

        效果:

【C#】C#入门学习笔记_第14张图片

22、如何表示 “一个Person 的 Person朋友们” ?

可以用以下代码表示:

class Person{

        private string name;

        private Person[] friends;

}

23、索引器(属于高级语法)

        让类对象可以像数组一样访问其中元素,观念上类似于重载了 "[ ]"

        注意这里说是观念上。实际上C#不支持中括号的重载,并不能像C++那样广泛的重载。

    class Person
    {
        public string name = "Default";
        private Person[] friends;

        public Person this[int index]
        {
            get {
                return friends[index];          // p[0]:定义为第一个朋友
            }
            set {
                if (friends == null) {
                    friends = new Person[10];
                    friends[0] = value;         // value:入参
                }
            }
        }
   }
   
   调用方法:
            p2.name = "Amy";
            p1[0] = p2;           // 将p1的第0个朋友设置为 Name="Amy"的人

tips:

        1) public Person this [int index] // 这个格式是固定搭配:public 返回类型 this [params]

        2) public Person this [int index, int age] // 索引器可以重载

24、静态成员 static

        C#中的关键字 static,较为本质的理解可以说是 “将对应的成员放进静态区,从而在程序启动的开始进行加载”

        如果读者恰好学习过操作系统,也可以把 static 的成员想成是 “程序运行之初就已经分配好内存” 的成员。之所以可以这么理解,是因为static成员在被读取加载之后便没有办法动态操作,也不可以通过GC进行回收。

        实际上,实例化的对象并不拥有静态成员,静态成员的所有者是他所在的类。我们通常把这部分变量直接用类名点出来使用,而不需要实例化对象!

        static 变量会在程序运行时,由程序直接分配内存空间,二者同生共死,全生命周期

        static 变量特性:1)全局性   2)直接 “.” 访问   3)程序运行时始终在特定内存段。

        eg : 在 class TestClass 中,可以有如下变量:

                1)

                静态成员变量: static public float PI=3.1415926f; 可以 TestClass.PI

                普通成员变量: public float PI=3.1415926f;

                2)

                静态成员方法: static public void TestFunc(); 可以 TestClass.TestFunc()

                普通成员方法: public void TestFunc();

        注意:static Func() 之中,不能使用 普通成员变量(非常重要,要懂原理)。因为 static Func() 在程序运行时就拥有了自己的内存,而普通成员变量必须进行实例化后才被分配内存。所以,是生命周期的问题导致了这个特点,实在要用就先实例化new一个。

        同样,static 变量不会被 GC!静态分配会一直占着内存,分配大块内存要慎用static。

        static 变量作用:1)常用于唯一量 2)想绕过实例化给别人用

        static 方法的典型例子:1)不会被改变,且定义唯一的数学公式等

25、const 是特殊的 static

        具有很多相同的性质

26、静态类:

        1)不可实例化

        2)具有唯一性

        3)静态构造函数:第一次使用时调用,之后不再调用

        eg:Console

27、扩展方法(有用!Must be public & static)

        可以将一个 非static 的类中已有的方法进行外部拓展

        比如可以给 Int32 类加一个你自己的方法。

        特点:一定是个static类中的static函数,并且第一个参数是this修饰

class Person  // 其中Person类没有任何除构造以外的成员方法   
 {}
 
// 定义扩展方法
static class AddFunc
{
                            // 意在对 Person 进行扩展
    public static void Func(this Person obj, int val)
    {  // obj:名字可以乱起,调用时是自动传入的,代表实例化的对象
        Console.WriteLine("AddFunc:" + val);
    }
}

// 调用
Person p1 = new Person();
p1.Func(233);

        在C#中,扩展方法必须声明为 static 才能正常工作。这是因为,扩展方法是一种静态方法,它不依赖于类实例,而是在整个应用程序中都可以使用的方法。

        简言之,这个方法可以在不创建类的实例的情况下直接使用,如:1.ShowIntValue();

        这里的1不需要我们进行 new int 操作,拿来就可以用。而且是在整个exe中使用。

28、运算符重载(Must be public & static)

        注意,operator重载必须要将重载方法设置为类内static,且有返回值。

        以下代码示例将二位数 (x,y) 添加相加规则:

class Point //
{
    public int x;
    public int y;

    public static Point operator +(Point p1, Point p2)  //记住格式!
    {
        Point temp = new Point(0, 0);
        temp.x = p1.x + p2.x;
       temp.y = p1.y + p2.y;
       return temp;
    }
    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

//调用如下:
 Point po1 = new Point(1, 1);
 Point po2 = new Point(3, 4);
 Point po3 = po1 + po2;

 Console.WriteLine("{0}, {1}", po3.x, po3.y);

        C# 不能重载的:[ ] && || . ?: () =

        在 C# 中,可以为一个类实现多个运算符的重载,包括新的运算符。但是,为了使这些运算符重载生效,必须确保它们被声明为 public 和 static。

        之所以用 public,是为了确保这些运算符重载被其他类访问和调用。public 访问修饰符告诉编译器,这些运算符可以从任何地方访问,而不仅仅是从类的内部访问。如果运算符不是 public,那么只能从类的内部访问,这会导致编译错误。

       之所以用 static,是为了确保这些运算符重载生效。static 访问修饰符告诉编译器,这些运算符不是实例化的,而是类级别的运算符,可以在整个应用程序中使用。如果运算符不是 static,那么它们只能在类的内部使用,这会导致编译错误。

        怎么理解呢?其实很简单,我们用的是 p1+p2,而不是类似 p1.+() 以及 p1.+ 这种错误形式。

        很显然p1+p2这种写法,并不属于通常用点的方式进行链接式调用。

        这种 p1+p2 的可行的前提,肯定是在程序运行之初就要支持的。

29、内部类和分部类

        内部类:

        调用:Person.Body.ShowBody();

        ps:就是类中类,使得两个类的从属关系显示的更为亲密

class Person
{
    public string name = "Default";
    private Person[] friends;

    public class Body{
        public static void ShowBody(){
            Console.WriteLine("ShowBody.");
        }
    }
}

        分部类:

               关键词是 partial,把一个类分成几部分声明

               属于不同的模块化子系统,然后不得不这样。

               总之弹幕有人说这个很有用,先mark一下。

    partial class Person{
        public string name;
        public int age;
    }

    partial class Person {
        public string nation;
    }

30、继承

class PersonMan : Person {

        public string nation;

}

        C#的继承:只能有唯一父类;C++的继承则可以有多个父亲;

        所以有个梗,在C++中,可以有:class 吕布 : 丁原,董卓

31、里氏替换原则

        任何父类出现的地方,子类都可以替代。优秀的设计应该是:用父类容器 装载子类对象。

        eg:

        Person p1 = new Student();

        Person p2 = new Teacher();

        eg:

GameObject[] objects = new GameObject[] { new Boss(), new Monster() ,\
                                          new Player() };   
遍历这个数组,可以用is和as来进行不同类型的对象的处理。 

        is和as:对象的类型检查与转换(服务于里氏替换原则)

        is:(返回值是bool型)

                eg: if (p is Player) ...

        as: (返回值:转换成功时返回指定类型对象,转换失败时返回null)

                eg: Player p = object as Player

        常规操作——二者配合使用:

// 前置定义 
    class PersonMan : Person{
        public int len;
    }
    class PersonWoman : Person{
        public int dep;
    }
 
Person p1 = new PersonMan() { name = "A", age = 18, len = 18 };
Person p2 = new PersonWoman();

if (p1 is PersonMan) {   // 判断p1是否属于PersonMan类,如果是,则...
    PersonWoman p3 = p2 as PersonWoman;    // 令p2转换成PersonWoman类型
    Console.WriteLine("Show:" + p3.name + " " + p3.dep);  
}

        如果转换后的类型与目标类型相同,则转换成功。当Person有子类PersonMan时,一个PersonMan既是PersonMan,又是Person

32、继承中的构造函数

        先调用父类构造,再调用子类构造

        由于默认调用是父类的无参构造,约定父类的无参构造必须存在!(因为默认找无参构造)

        如果想默认调用有参构造,需要用 base 关键字,这个时候允许没有声明父类无参构造

        eg 伪代码:

        class {

                func() : this(可传参) // 去找对应参数的构造函数

                func() : base(可传参) // 去找对应参数的父类构造函数

        }

【C#】C#入门学习笔记_第15张图片

// 申明类和构造函数
        class Father
    {
        public Father() {    // 默认调用的无参父类构造函数
            Console.WriteLine("Father NULL");
        }
        public Father(int i) {
            Console.WriteLine("Father:" + i);
        }
    }

    class Son : Father
    {
        public Son() { }
        public Son(int i) : base(i)  //base是Father(),base(i)是Father(i)
        {
            Console.WriteLine("Son: i");
        }
        public Son(int n, int m) : this(n)  // this(..) 都是指自己的构造
        {
            Console.WriteLine("Son: n, m");
        }
    }
// 调用:
        Son s = new Son(2, 3);  
        
--控制台打印结果:
Father:2
Son: i
Son: n, m     -解析:new Son(int, int) 的操作:1、先构造父类
                                              2、再构造 this(int)
                                              3、最后构造自己

33、继承的构造调用关系

        简言之,就是从老祖宗开始,逐步往各后代去调用

34、最基本的基类:object(引用类型)。

        object对象由于是引用类型,他被设计成可以装任何种类对象的容器。

        注意:object不能随便强转或者as,如:object w = (Son)f; 这种语法正确,但会出问题。

    object val = 1.23f;       // 装箱
    Console.WriteLine(val);   
    float v = (float)val;     // 拆箱

        引用类型:建议永远用as去转换,而不是强转:

                object NewArray = new int[10];

                int[] ar = NewArray; // 这句代码会报错,因为不能将object型隐式转换成int[]型

        应该这么做:

                object NewArray = new int[10];

                int[] ar = NewArray as int[ ]

        这里一个自己的小tip:

        new和as的区别:new一定是用来分配内存的,而as是用来转换类型的

35、装箱拆箱(object操作的意义在哪?)

        值类型用引用类型存储,保存地点可以由栈迁移到堆

        好处:不确定类型时,可以方便参数先行存储和转换

        缺点:内存迁移带来性能损耗

        eg:object [ ] 可以在不知道成员是什么类型时暂时 “吃下一切”,可以用来装任何类型。

【C#】C#入门学习笔记_第16张图片

        but,还是尽量少用,因为性能损耗和设计规范都有点问题。

36、密封类/密封函数,关键字:sealed

        sealed类:让类无法再被继承(断子绝孙类,可以保证安全类和规范性)

        sealed函数:该函数不能被override(不可被重写)

37、多态经典方案:

        背景:我们试图解决一个问题:

    class Father{
        public void Speak() {
            Console.WriteLine("Father");
        }
    }
    class Son : Father{
        public new void Speak()    
        {
            Console.WriteLine("Son");
        }
    }

        当进行以下语句时,会有不同的表现:

Father s = new Son();
    s.Speak();           // 执行Father的Speak函数,因为使用的是Father容器
    (s as Son).Speak();  // 执行Son的Speak函数

        这对继承产生了一定的困扰,为了避免继承合逻辑,我们之后引入了 virtual, overide

        virtual 虚函数:用来给子类重写的。

        override:声明重写,然后补全这个函数的时候可以自动补全成带有一个base.Speak() 的子类重写函数。

    class Father{
        virtual public void Speak() {
            Console.WriteLine("Father");
        }
    }
    class Son : Father{
        public override void Speak(){    
            base.Speak();   // base代表父类,可以用base保留父类的行为
        }
    }

调用:

        当我们再进行

        s.Speak();

        这个操作的时候,虽然s还在使用Father的容器,但是其Speak函数已经被override,所以会执行Son.speak() 方法

38、多态的另一种实现方法——抽象类:abstract修饰

一类class的统称,如:abstract class Things.

现实世界中不存在一类东西叫做:Things,但是我们可以把他们以后通过不同类继承出来。

这么做类似“蓝图”:

        1、抽象类就只负责声明应该出现的具有共性的属性和方法,但不做具体实现

        2、抽象类不可以被实例化

eg:

    //抽象类
      abstract class Father{
          //抽象方法(只能写在抽象类里,且必须是public的,因为以后必须override)
        public abstract void Speak();   // 不用写函数体,类似纯虚函数
    }

39、命名空间 namespace

        namespace 具有以下特点:

        1、像是工具包;相比之下 ,class像工具

        2、可以分开写诶,像是之前那个分部类,可以写成好几段。

        3、命名空间必须通过using引用或者点出使用,不然即使在同一个文件里一样会找不到。

        4、可以嵌套:

                namespace 工具包{

                        namespace 螺丝 { ... }

                        namespace 螺母 { ... }

                }

                引用时可以是:using 工具包.螺母;using Game.UI

        5、namespace的类默认是internal(只能在程序集中使用),而不是private,要注意!


笔记还有部分适读性上的小毛病,目前赶着上新,待我有空一定来修!

友友们一起加油 owo

你可能感兴趣的:(C#,开发语言,c#)