2.3深入探讨函数:
2.3.1构造函数、默认构造函数、 缺省构造函数
对于上面的实例,它已经能完成绝大部分工作了,但它还是不完善的,还有许许多多的细节等到我们去完善!也许有的同学已经注意到了,当我创建完“jingwei”这个对象时,这个对象的所有的属性都是空的,也就是说:这个对象的姓名是未定的、年龄是未定的、性别是未定的、薪水是未定的、午餐也是未定的。而我们想把这些属性都添加上去,就还要用对象调用相应的方法,去一个个修改!天啊,这简直是太麻烦了!有没有什么好方法能够在我们创建对象的同时就完成了对属性赋值的操作呢?哦不,应该说是对属性的初始化呢?当然没问题了,这就需要所谓的构造函数!
构造函数是类中最特殊的函数,它与析构函数的功能正好相反!
从特征上来说:1.它是编程语言中唯一没有返回值类型的函数。
2.它的名称与类的名称必须要完全相同。
3.它必须被声明为公共(public)的类型
4,可以对构造函数进行重载。
5.它在创建对象是自动被调用。
从功能上来说:1.它是对类中的属性进行初始化。
其实,对于上面的程序来说我们没有自己定义构造函数。但是,在这种情况下,系统会自动为我们定义一个“默认构造函数”。他会把数值变量自动赋值为0,把布尔行变量赋值为false等等(但在C++中,默认构造函数不初始化其成员)。如果程序员定义了构造函数,那么系统就不会再为你的程序添加一个缺默认造函数了。(在这里,我们提倡的是自己定义构造函数,而不是用系统的默认构造函数)
还是看个实例吧!这样比较清楚一些!
//employee.java
public class employee{
private String name; //员工姓名
private int age; //员工年龄
private char sex; //员工性别
private float emolument; //员工薪水
private boolean lunch; //员工午餐
//……等等
public employee(){ //这个就是“默认”构造函数
name = “jw”; //设置员工姓名
age = 20; //设置员工年龄
sex = “M”; //设置员工性别
emolument = 100; //设置员工薪水
lunch = false; //设置员工午餐
}
public void heater(){ //这个方法是用来加工员工的午餐
lunch = true;
}
//……等等
};
这样,在我们创建“jingwei”这个对象的同时,它的所有的属性也被初始化了!显然,这大大的提高了工作效率,但是,它还是不符合要求。想想看,如果我们现在创建这个类型的第二个对象的时候会发生什么事情?告诉你,除了对象的“名”(这个名称不在是对象属性中的名称,而是对象本身的名称)不一样外,其所有的“属性值”都一样!比如:现在我们创建第二个对象flashmagic,然而我会发现这个对象的所有的属性和jingwei这个对象的所有的属性完全相同。而我们只能在用对象的方法去改变着写属性了!很显然,这种方法不大好!我们需要一种方法在创建对象的时候为对象的属性赋予“我们想要的值”。
相信你也看到了,默认构造函数就显得无能为力了。我们需要的是带参数的构造函数,在创建对象时,我们把参数传给构造函数,这样就能完成了上述的功能!口说无凭,还是来看个实例吧:
//employee.java
public class employee{
private String name; //员工姓名
private int age; //员工年龄
private char sex; //员工性别
private float emolument; //员工薪水
private boolean lunch; //员工午餐
//……等等
public employee(String n,int a,char s,float e,boolean l){ //看这个构造函数
name = n; //设置员工姓名
age = a; //设置员工年龄
sex = s; //设置员工性别
emolument = e; //设置员工薪水
lunch =l; //设置员工午餐
}
public void heater(){ //这个方法是用来加工员工的午餐
lunch = true;
}
//……等等
};
这样一来,在创建对象的同时我们就可以给他赋予我们想要的值,很显然,这可就方便多了。哦,对了!还没有告诉你怎么创建呢!哈哈,往前翻几页你会看到这句话:
jingwei = new employee();这是创建一个对象,而我们把它改成
jingwei = new employee("jingwei",20,'M',100,false);这样一来,所有的工作都完成了,呵呵!(在创建对象的同时赋予了我们想要的“初值”)
2.3.2重载构造函数:
我还是先把概念给你吧,让你有个认识,随后我们在进行论述。
在JAVA中:
1. 函数重载是一个类中声明了多个同名的方法,但有不同的参数个数和参数类型。
2. 函数重构是指在子类中声明与父类同名的方法,从而覆盖了父类的方法。重构解决了子类与父类的差异问题。(在讨论到继承时我会详细说明)
在C++中:
1. 数重载的概念一样。
2. 重构的概念可就不一样了,C++中功能更为庞大的虚函数。更详细内容这里就不过多介绍了!
其实关于重载的概念你并不陌生,在编程中相信你也接触过。呵呵!让我们来举个操作符重载的例子你就会明白了,(JAVA中不支持这个功能)我们定义三个整数变量:
int i1=2, i2=3,i3=0;
i3 = i1 + i2;
此时i3=5;加号实现了两个数相加的运算功能。然而我们现在要定义三个字符串变量:
String str1=”jing”, str2=”wei”,str3=””;
str3 = str1 + str2;
此时str3 = “jingwei”;加号实现了两个字符串相加的运算功能。同样是加号,既可以把两个整型的变量加在一起,也可以把两个字符串类型的变量加在一起。同一个操作符实现了不同的功能------这就是所谓的操作符重载(嘿嘿,我说你一定见过吧:)!不就好像是汉语中的一词多意一样!我需要说明一下的是,C++中的操作符重载可没有这么简单。比如,我们可以对两个自定义类型的对象进行相加的运算,进行赋值的运算。这样书写简洁明了,而且非常实用。当然,关于操作符重载的话题太多了,有兴趣再看看书吧!
我们把操作符的话题在转到函数上来,我们一直强调的是“对象调方法”------对象其实调的是方法的“名称”。而我们现在要对方法进想重载,也就是定义多个相同名称的函数,这样计算机在调用的时候不会混淆嘛?我想应该不会的,呵呵,因为仅仅是函数名称相同,而我们在调用函数时会把参数传递给他的。既是没有参数也是一种参数传递参数的信息(信息为无参数)!然而由于参数类型、参数数量、返回值类型不同我们就可以对相同名称的函数进行区分了!目的只有一个,用简便的方法实现更多的功能。还是举个例子吧,重载构造函数!
public class employee{
public employee(String n,int a,char s,float e,boolean l){ //看这个构造函数
name = n; //设置员工姓名
age = a; //设置员工年龄
sex = s; //设置员工性别
emolument = e; //设置员工薪水
lunch =l; //设置员工午餐
}
public employee(){ //请注意这个函数没有参数
name = “jw”;
age = 20;
sex = ’W’;
emolument = 99;
lunch = true
}
//……等等
};
看,在一个类中有两个名称相同的函数,可我们在使用的时候系统如何知道我们调用的是那个版本的函数呢?呵呵,我刚刚说过了,可以通过函数的参数类型、参数数量、返回值类型来确定。现在我们接着试验,我们创建两个对象其中的一个调用带参数的构造函数,第二个对象调用缺省值的构造函数。我们来看看结果:
jingwei = new employee("jingwei",20,'M',100,false);/*创建这个对象的时候调用的是带参数的构造函数*/
flashmagic = new employee();//创建这个对象是调用的是却省值的构造函数
而得到的结果呢?让我们一起来看一看!
Jingwei这个对象中: flashmagic这个对象中:
name jingwei name jw
age 20 age 20
sex M sex W
emolument 100 emolument 99
lunch false lunch true
看,虽然是两个名称完全相同的函数,但完成了不同的工作内容。呵呵!关于函数重载我们就料到这里吧,我相信你已经有个大印象了,而更详细的内容仍需要你的努力!
重载普通的函数与重载构造函数大同小异,不过他多了一个this指针!this一般是对当前对象的引用。这么说吧,如果涉及到两个以上的对象时就会使用this指针。每个成员函数都有一个this指针,它是一个隐藏的参数,this指针只向调用它的对象!我说过方法只有一份,而对象都有自己的属性,当对象调用方法来先是属性的时候,他怎么来判断调用的时不是自己的属性呢?这就需要this指针来大显神威了。
关于拷贝构造函数、内联函数、虚函数、模版等欧就不做过多的讨论了,因为JAVA中好像没有这些了。不过我需要提醒你一下的是,在C++中,类内定义的函数自动转换为内联函数,而这好像与我前面提到的思想有冲突。因为内联函数的目的是减少函数调用的开销!呵呵!我也没绕出来呢!还请哪为大虾指点一二!谢!
2.3.3 初始化与赋值
这里我却要提醒你一下的是,初始化与赋值是完全不同的两个概念。创建一个类的时候会调用这个类的构造函数对对象的属性进行初始化。而如果以后再把这个对象赋给其他同类型的对象时可就没那么简单了。在JAVA中直接赋值就行了,因为JAVA中取消了指针,不存在指针的深拷贝与前拷贝问题。而在C++中就需要拷贝构造函数以及操作符重载了。因为JAVA中不牵扯这些东西,所以偶就不做过多介绍了。详情请参阅相关书籍吧!
2.3.4析构函数:
JAVA中不再支持指针了,所以你感觉不到它的重要性,因为系统会自动为你释放内存。而在C++中一切都是手动的。在构造函数中new了一个指针,在析够函数中就要delete这个指针。
2.3.5静态:
现在我们再来看一看“静态”是咋一回事儿!
把一个变量或函数声明为静态的需要“static”这个关键字。声明静态的目的是“为某个类的所有对象的某个属性或方法分配单一的存储空间”。静态的数据是属于类的,不属于任何的对象。静态的数据在声明的时候系统就为他分配了内存空间,而不用等到创建对象时。举个例子来帮你更好的理解它吧。
还是接着上面的例子。还记得刚刚我说过的员工能用微波炉热饭的事情吧。现在我们要找一个手套,毕竟想把热好的饭从微波炉里拿出来直接下手是不行的。我把手套定义成一个布尔型的变量,它有干净和脏两种状态。想想看手套是属于谁的?所有对象?不对!因为只有方法才能属于所有的对象。它是属于类的,它像微波炉那个方法一样,在内存中只有一份,所有的对象通过方法都能够修改它。而下一次修改是基于上一次修改的基础之上的!我的意思是:一个员工把手套弄脏了,下一个员工在使用的时候它还是脏的。而这个员工把手套洗干净之后,别人再用的时候它就是干净的了!就这么点事儿,明白了吧!
关于静态函数我想就没什么可多说的了。给我的感觉就是,它也是属于类的,在定义的时候就分配的内存。调用是可以使用类名直接调用。其他的和普通成员函数没什么不同的了不过这里需要说明的一点是:在JAVA中,静态的成员函数只能修改静态的属性,而静态的属性可以被所有的成员函数修改。不过在C++中就没这么多事儿了!
2.4继承
继承很好理解,它的最大好处就是“代码重用”,大大提高了工作效率。举个例子你就明白了。世界上先有的黑白电视机,它有自己的工作原理。然而人们在他的基础之上开发出了彩色电视机。彩色电视机继承了黑白电视机的所有的特性与方法!因为它既能显示彩色图像也能显示黑白图像。然而它与黑白电视机又有许多区别,在工作原理上。彩色电视及多了矩阵色电路,把彩色信号分离出三种颜色(RGB),他就能显示彩色的图像了。而黑白电视机没有这块电路,即使它收到了彩色信号也显示不了彩色图像。彩色电视机是从黑白电视机中派生出来的。所以,黑白电视机是父类,彩色电视既是子类,彩色电视继承了黑白电视机所有的特性与方法。看看再计算机中它是什么样子的吧:
//BWtv.java 父类的定义
public class BWtv{
private int a;
public BWtv(){
a=1;
}
public changeBWtv(int i){
a=i;
}
}
//Ctv.java 子类的定义
class Ctv exntends BWtv{ //注意关键字“extends”
private int b;
public Ctv(){
b=2;
}
public changetCv(int x){
b = x;
}
}
有了上面的定义,我们来看看他们都有什么数据。
BWtv的数据包括 Ctv的数据包括
private int a private int a
private int b
public changeBWtv(); public changeBWtv()
public changeCtv();
你看,子类拥有父类的所有的方法及属性。注意关键字”extends”,它的意思是继承。在C++中使用的是“:”操作符。意思是一样的。但是这里有许多问题,首先是访问权限的问题,子类的对象拥有父类的所有的属性和方法这句话。对嘛?肯定是对的!(不过JAVA的书中可不是这么说的,他说只继承非private类型的属性及方法,我觉得它这句话有错误!)可是,子类的对象不能直接访问父类的私有属性或方法,它只能通过父类的公有成员函数来访问。而此时,如果你修改了父类的属性的值。那就真的修改了。我的意思是:父类的私有属性的值会随着子类对象调用父类的公有方法进行对相应属性的修改而发生变化!(这里面存在一个域的问题,所有的修改都是在子类中进行的,修改的是子类继承的父类的属性(在子类这个域中,此时父类以拷贝到子类中了。)。而程序中定义的父类的属性不会发生任何变化(在父类的域中),)
其次是构造函数,在创建一个子类对象时首先要调用的是父类的构造函数,然后再调用子类的构造函数,毕竟,子类的构造函数不包括父类的属性的初始化功能!(从这一点来说我的观点又是正确的“子类的对象拥有父类的所有的属性和方法”)当然了,析够函数的调用顺序正好相反!
现在让我们来谈谈protected这个关键字吧,它的意思是:对对象来说,声明为protected的变量是私有的,而对子类父类来说,声明为protected的变量是公共的。
现在又出现了这样的一个问题,如果我们在子类中也定义了一个int 类型的变量a,那我们在创建子类的对象的时候调用的是子类定义的还是父类定义的呢?这就涉及到数据的隐藏的问题了,我可以告诉你肯定是调用的子类的变量a。因为,子类把父类的这个同名变量给隐藏了。而如果是方法呢?这就涉及到重构的问题了,在上面我提到过“函数重构是指在子类中声明与父类同名的方法,从而覆盖了父类的方法。重构解决了子类与父类的差异问题。”这里必须要声明一下的是,在JAVA中,子类出现了对父类属性的隐藏和父类方法的覆盖后,在子类中,子类对象仅能调用子类本身的属性和方法。要调用父类的属性和方法必须要实用super这个关键子。而在C++中就不这样了。因为它有虚函数
虚拟函数在C++中非常好玩的事。我们可以把需要改写的函数声明为虚函数,用virtual这个关键字来声明。这样。假如如果我们CwinApp这么一个基类,它里面定义了一个成员(虚)函数为InitInstance()和另一个为(虚)函数InitApplication()。如果我从CWinApp派生一个子类为CMyWinApp并修改了InitInstance()这个成员函数。我们并没有修改InitApplication()这个成员函数。现在我们创建CMyWinApp这个类的函数theApp,我们并创建一个指针*pApp指向这个对象theApp。此时:
pApp->InitInstance() //指针调用的是子类CMyWinApp的虚方法
pApp->InitApplication() //指针调用的时父类CwinApp的虚方法
因为子类并没有修改父类的方法,所以调用的是父类的虚方法。这就牵扯到虚你表的问题。碍与本篇文章的定位,这里就不讨论了!
关于父类与子类的对象的类型转换问题是这样的,子类对象转换为父类对象时,不会出现错误。因为子类包含父类的所有的属性及方法,而父类向子类转换时就难说了,呵呵。这还会牵扯到虚拟表的问题,也不讨论了!
JAVA中不再支持多重继承,也就是一个类从两个以上的类中继承而来,但它却多了接口的概念“interface”。这里就不做过多介绍了!
关于抽象基类也没什么难的!他的一个大概念就是:做为许多类的父类,不定义对象,只做派生用!
我能做得也只有这些了,如果你能明白以上的六七成,那就是对我最大的回报了,呵呵!就像刚刚开始我说的,我只是给你一个大概的思想,至于内部的实现细节,仍需要你的继续努力。关于编程语言的内容还有许多许多,实属小生个人能力有限而不能全盘照顾到。不过作为一个初学者的你来说,这些东西都是基本的。需要我提醒你一点的是,不要指望在第一、二遍的时候看懂什么!加油:)
04.4.29 韩景维
愿意和大家保持联络,如果大家能够对本篇文章提出宝贵的意见和建议,我将不胜感激。我的电子邮件地址是:
onegenius@126.com
或在QQ里给我留言(86228551---乱码游魂.h)