类和柏拉图的理念世界
我们知道面向对象编程中到处是一个个的类,但类只是个概念性的东西,不是个实体,不占内存,你没实例化之前也不能用它.只有把类实例化成一个对象后,它才是一个真正存在的实体.占有内存,能被我们使用.类就有点像柏拉图所说的理念世界一样,柏拉图认为存在着两个世界,一个是我们生活于其中的现实世界,与之对立的是理念世界,理念世界有些啥东东呢?那是个永恒不变的世界,由一堆堆的理念组成,比如人,马,鸟....理念的人只是个概念,不是任何实际的人.而我们现实世界是根据理念世界模拟出来实际的一个个的人,一匹匹的马.有些人可能模拟的比较漂亮,于是就有帅哥美女嘛,有些人就是失败品,于是啥恐龙青蛙的也数量众多.至于模拟的动作谁做的呢?柏拉图没说.有人就认为就是上帝在干这活.
那我们实例化一个类时就有点像上帝干的活,从理念世界拿个空的理念来,然后用个啥构造函数实例化成一个个的对象.
C#类初始化
我们实例化类时一般是用个new关键字比如MyClass arwen = new MyClass().这和C++中的蛮类似.C++这可以这样实例化类表示在heap中开辟一块内存来保存一个对象.用完了之后自己去释放内存.但也可以直接这样MyClass weiwen; 表示是在stack中保存对象,但stack的分配和收回由系统控制. C#则只能用new在heap中开辟内存这一个选择,而且heap中的内存由CRL去管理,用完了它给你去释放,所以此处的heap叫托管堆.
实例化类时我们只是简单的用了个new,但实际上后台还做了其他很多操作.具体做了些啥呢?按顺序做了如下事情
1.初始化类中静态变量
2.调用类中静态构造函数
3.初始化类中非静态变量
4.初始化父类中静态变量
5.调用父类静态构造函数
6.初始化父类非静态变量
7.调用父类构造函数
8.调用自己的构造函数
当然如果没有继承某个父类就不用管父类的事了.如果父类还继承了某个父类,那父类继承重复上面的操作.举个简单的例子看下吧.假如有父类Father,子类Son
class Father
{
public static int age; //4.赋值为0
public string name; //6.赋值为null
public Father() //7.调用此构造函数
{
Console.WriteLine("I am a lazy father construtor,don't do anything.");
}
public Father(string myName)
{
Console.WriteLine("I am a diligent father constructor.");
name = myName;
}
static Father() //5.赋值age为110
{
age = 110;
Console.WriteLine("Father staticconstrutorwill never do anyting after setting age with a value 110.");
}
}
class Son : Father
{
public static int age; //1.初始化age为0
public string name; //3.初始化为null
public Son()
{
Console.WriteLine("I am a lazy son construtor,don't do anything.");
}
public Son(string myName) //8.调用此构造函数
{
Console.WriteLine("I am a diligent son construtor.");
name = myName;
}
static Son() //2.赋值age为11
{
age = 11;
Console.WriteLine("Son staticconstrutorwill never do anyting after setting age with a value 11.");
}
}
我们实例化类Son.
Son arwen = new Son("arwen");
输出的结果为
Son static construtor will never do anyting after setting age with a value 11 //类静态构造函数
Father static construtor will never do anyting after setting age with a value 110. //父类静态构造函数
I am a lazy father construtor,don't do anything.//父类构造函数
I am a diligent son construtor //类构造函数
执行顺序都是按那8步来的.不过里面有些要注意的地方.
1.)静态构造的定义只能是static 加类名,不能有参数,不能有public等任何修饰符.在里面只能给静态变量赋值,不能给一般字段赋值,静态构造函数只在类第一次实例化时调用一次.以后再实例化类时不调用.它只能是被默认调用,不能显示调用.除了类第一次实例化时会被调用,第一次使用类中的静态变量前也会被调用.比如你不实例化类Son,而是直接调用
Console.WriteLine(Son.age); 打印结果为
Son static construtor will never do anyting after setting age with a value 11
11
另外虽然const类型的字段也默认是static的.但如果你调用const字段时静态构造函数还是不会执行.
2.)如果类中有很多个构造函数,不管你用哪一个来实例化类,调用父类的构造函数时只会调用无参数的那个.所以如果你没有无参的构造函数,而且有带参的构造函数,而你又作为某个类的父类.那编译时会出错的.如果你啥构造函数都没的话反而没问题.因为如果你任何构造函数都没有,系统会为你默认生成一个.但你如果自己写了任何构造函数带参的或不带参的,系统就不为你默认生成了.
3.)如果想调用父类的有参构造函数必须指定.用关键字base.比如在Son中调用有参构造函数时还要同时调用父类的有参构造函数就像下面这样
public Son(string myName):base(myName)
{
Console.WriteLine("I am a diligent son construtor.");
name = myName;
}
这样的话就不会调用父类的无参构造函数了.而有调用有参的那个.不过尽管你这会没调用无参构造函数.但你也不能省了它.无参构造函数必须得写好放那.
4.)我们知道如果子类中有和父类同名的函数的话就会隐藏父类的函数.其实有同名的字段也一样会隐藏.像我举的例子中就有.编译时会有警告,会提醒你加个new关键字.
new public static int age;
new public string name;
改成上面这样就不会有警告.不过看着挺怪的啊.实际上不加new效果也一样的.默认是加了new,只不过显式的再加下new好点吧.
C++类初始化
C++中没有静态构造函数了.调用构造函数的顺序和方式跟C#一样的.只有调用父类的构造函数时有一点点写法上的不一样.把C#中的base换成父类的类名.
另外C++的static变量成员和C#的用法是不一样的.实例化类时也不会默认给初始化.必须自己在类外面去初始化.在C++中的成员变量除了const static类型的可以在声明时同时赋值,其他的都不行.
class Father
{
public:
static int age;
string name;
Father()
{
cout<<"I am a lazy father constructor,don't do anything."<<endl;
}
Father(string myName)
{
cout<<"I am a diligent father constructor."<<endl;
name = myName;
}
};
class Son :public Father
{
public:
static int age;
string name;
Son()
{
cout<<"I am a lazy son constructor,don't do anything."<<endl;
}
Son(string myName):Father(myName)
{
cout<<"I am a diligent son constructor."<<endl;
name = myName;
}
};
int Son::age = 110; //只能在类外面这样去初始化static变量.前面还得加个int,不过不用加static,也不能再加static.
这样初始化完了你在其他地方就可以Son:age这样去引用了,并且可以直接改变它的值.比如Son::age = 911;
C#与C++初始化对比
从上面我们可以看出,两者构造函数的用法基本上差不多.只不过C++没有静态构造函数.另外调用父类有参构造函时不用base关键字而直接用父类名.
两者的主要区别是在初始化类中成员上.
1.)C++初始化时只要负责根据类中的类型来分配多少内存,没有对成员变量做任何初始化赋值.而且static变量所在占的内存不算在类里面.static变量是保存在静态内存区.而且实例化类时实际还没有给static变量分配内存.只有你在类外面初始化时才会分配内存.
2.)另外你如果在类中定义了一个普通public成员变量,你没有给赋值时用cout打印会发现有一个值.不过那值不确定,而且可能是个蛮大的值.咋回事呢?
实际上我们申请来一块内存,它不是空白的.里面有内容的.比如你用new申请一块内存,然后delete,不是说把内存清空,里面啥都没有了.实际上只是告诉系统这块内存我不要了.然后其他谁申请到这块内存,里面还有你的内容在.只有在它重新赋值后才能擦除掉你的内容.
没初始化成员变量前就使用它自然很危险.所以一定要初始化,C++中这重要的工作就留给构造函数了啊,而C#由于默认给初始化,构造函数就没显得像C++那么重要.
补充
static变量在C#,C++中含义差不多.不过const变量就差远了.C#中const变量默认是static.而C++中const变量不是static.它其实有点像C#中的readonly. const成员变量只能在构造函数中赋值.而且只能在初始化参数列表中赋值.假如类Son中有有成员变量const int no.则只能这样赋值
Son() : no(120)
{
}