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;
}
a = a1
之后,a可以指向的信息如下图:
- 代码运行结果如下:Afunc没写内容所以内输出,
a._name
得到的(实例a1)
中的_name
值A1,如下图:
- 但是反过来,基类引用转换成派生类引用不合理,因为基类内存是无法额外产生组成派生类成员内存,这是错误的。(向下转型),如下图:
- (编译期报错)
- (运行期报错)
上面代码中,原本指向(实例a1)
的a1引用
现在需要指向(实例a)
,但是a1引用
是A1派生类型
,它是需指向派生类的成员比如:_score
和A1func
,但是现在指向的(实例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;
}
运行结果如下:
- 当基类中存有虚函数时,在基类和派生类中对应实例内存中会多一个隐藏信息:
虚函数表指针
,它指向虚函数表 - 虚函数表其实就存放虚函数地址的数组
- 当调用虚函数时,首先去虚函数表地址里找到对应的虚函数列表,在虚函数表里再找到需要执行的虚函数
上述代码对应的内存模型概览如下:
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 用于继承链中的转换和判断