C# 类型转换(向上转型,虚函数)

本文介绍了C#中的类型转换,重点讲解了向上转型的内存模型和合理性,强调了向下转型的错误以及条件。讨论了虚函数的概念,展示了如何实现多态,并解释了虚函数表的工作原理。最后,提到了as和is操作符在继承链中的应用。

1, C# 构造函数

  • 当重写构造函数,则默认无参构造函数会失效,如下代码:
    class Program
    {
        private static void Main(string[] args)
        {
            A a = new A();//会报错!!!
        }
    }

    class A
    {
        public A(string name)
        {
            _name= name;
        }
        
        private string _name;
    }

  • 如需要无参的构造函数存在,需重载一个无参数构造函数,如下代码:
    class Program
    {
        private static void Main(string[] args)
        {
            A a1 = new A();
            A a2 = new A("hello");
        }
    }

    class A
    {
        public A()
        {

        }

        public A(string name)
        {
            _name= name;
        }

        private string _name;
    }

  • A1继承中构造过程,如下代码:
    1,将name通过base传给基类A的构造函数,进行基类A创建
    2,调用A1的构造函数,进行派生类A1创建

当不写base时,派生类会找基类的无参构造函数,找不到会报错

    class A
    {
        public A(string name)
        {
            _name = name;
        }

        private string _name;
    }

    class A1 : A
    {
        public A1(string name, int score)
            : base(name)
        {
            _score = score;
        }

        private int _score;
    }

2, C# 类型转换

  • 从内存模型角度,派生类引用转换成基类引用是合理的,因为派生类由基类成员和自己的成员组成,在内存中是可以指向其内存中的基类成员信息(向上转型),如下代码:
           static void Main(string[] args)
        {
            
            A a = new A("A");//a指向内存中的(实例a)

            A1 a1 = new A1("A1",12);//a1指向内存中的(实例a1)

            a = a1;//a放弃指向(实例a),转而指向(实例a1)中的基类部分

            a.Afunc();//a可以指向(实例a1)中的基类部分中的成员函数

            Console.WriteLine(a._name); //a可以指向(实例a1)中的基类部分中的成员变量

            Console.ReadKey();
        }
    }
    class A
    {
        public A(string name)
        {
            _name = name;
        }

        public void Afunc()
        { 

        }

        public string _name;
    }
    class A1 : A
    {
        public A1(string name, int score)
            : base(name)
        {
            _score = score;
        }

        public void A1func()
        {

        }

        public int _score;
    }
  1. a = a1之后,a可以指向的信息如下图:
    在这里插入图片描述
  2. 代码运行结果如下:Afunc没写内容所以内输出,a._name得到的(实例a1)中的_name值A1,如下图:
    在这里插入图片描述

  • 但是反过来,基类引用转换成派生类引用不合理,因为基类内存是无法额外产生组成派生类成员内存,这是错误的。(向下转型),如下图:
  1. (编译期报错)
    在这里插入图片描述
  2. (运行期报错)
    在这里插入图片描述

上面代码中,原本指向(实例a1)a1引用现在需要指向(实例a),但是a1引用A1派生类型,它是需指向派生类的成员比如:_scoreA1func,但是现在指向的(实例a)中无法额外生成派生类的成员,所以转型错误

  • 针对上一点,如果这个基类引用本身就是派生类转换过来的,它是可以继续回头再指向原来的派生类信息,如下代码:
    在这里插入图片描述

3, 虚函数

示例代码:

       class Program
    {
        private static void Main(string[] args)
        {
            A a = new A();
            a.Show();

            a = new A1();
            a.Show();

            a = new A2();
            a.Show();

            Console.ReadKey();
        }
    }

    class A
    {
        public virtual void Show()
        {
            Console.WriteLine("this is showfunc of classA");
        }

        public int _a;
    }

    class A1 : A
    {
        public override void Show()
        {
            Console.WriteLine("this is showfunc of classA1");
        }
        public int _a1;
    }

    class A2 : A
    {
        public override void Show()
        {
            Console.WriteLine("this is showfunc of classA2");
        }

        public int _a2;
    }
}

运行结果如下 :
在这里插入图片描述
如上代码基类引用可以指向其派生类的成员函数了,形成多态。

不知道c#的虚函数多态机制是如何实现的,C++是利用虚函数表,所以下面就以c++的虚函数表机制解释下多态的形成

按照上文,我们知道基类的引用是无法找派生类的成员的,这里之所以可以找到派生类成员,是因为虚函数表的存在

示例代码如下:

class Program
    {
        private static void Main(string[] args)
        {
            A a = new A();
            a.Show();
            a.Call();

            a = new A1();
            a.Show();
            a.Call();

            a = new A2();
            a.Show();
            a.Call();

            Console.ReadKey();
        }
    }

    class A
    {
        public virtual void Call()
        {
            Console.WriteLine("this is Callfunc of classA");
        }

        public virtual void Show()
        {
            Console.WriteLine("this is showfunc of classA");
        }

        public int _a;
    }

    class A1 : A
    {
        public override void Show()
        {
            Console.WriteLine("this is showfunc of classA1");
        }

        public int _a1;
    }

    class A2 : A
    {
        public override void Call()
        {
            Console.WriteLine("this is Callfunc of classA2");
        }

        public override void Show()
        {
            Console.WriteLine("this is showfunc of classA2");
        }

        public int _a2;
    }

运行结果如下:
在这里插入图片描述

  1. 当基类中存有虚函数时,在基类和派生类中对应实例内存中会多一个隐藏信息:虚函数表指针它指向虚函数表
  2. 虚函数表其实就存放虚函数地址的数组
  3. 当调用虚函数时,首先去虚函数表地址里找到对应的虚函数列表,在虚函数表里再找到需要执行的虚函数

上述代码对应的内存模型概览如下:
在这里插入图片描述

a = new A1();
a.Show();
a.Call();

a.Show();调用过程如下:
1,获悉A1实例内存中虚函数地址2096
2,前往2096处的表
3,获悉表中第2个函数的地址6820
4,前往地址6820,并执行这里的函数(传入this指针)

a.Call();调用过程如下:
1,获悉A1实例内存中虚函数地址2096
2,前往2096处的表
3,获悉表中第1个函数的地址4064
4,前往地址4064,并执行这里的函数(传入this指针)

不过虚函数表示面对程序员的隐藏的,代码书写只需定义虚函数行为,编译器去实现


4, as 和 is

  • is
    在这里插入图片描述
  • as
    如下代码:test1转换成指向(a2实例)中基类部分的引用,test2编译器报错,as 和 is 用于继承链中的转换和判断
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值