下面呢我们来说说这个构造函数。 构造函数可是面向对象的程序员居家必备的好东西啊。它呢是成员函数的一种, 它的特点是类名字和类名一样,然后它当然可以有参数, 但是它不能有返回值。啊,返回值哪怕你写void也是不行的。因为构造函数啊,它的作用就是对对象进行初始化。 比方说给成员变量做一些初值,那么初学者从constructor这个构造函数这个名字上 往往会误会,就觉得这个对象所占用的存储空间是不是也是构造函数分配的。 啊,其实不是啊,构造函数只是在对象已经占用了存储空间以后,在对象的存储空间里面去做一些初始化的工作, 如果你把对象比作房子的话,这构造函数呢它不盖房子,它只是等房子盖完了以后, 进去装修。啊,那类一定是有构造函数的,那我们前面看到的类并没有写构造函数啊,那怎么回事呢? 啊就是说我们定义一个类的时候,如果你没有写构造函数的话,编译器就会生成一个默认的没有参数的构造函数。 但这个默认的没有参数的构造函数呢,它实际上什么都不干, 那如果我们定义了 构造函数,编译器它就不会再去生成这个默认的没有参数的废柴一样的这个构造函数了。 那构造函数是在对象生成的时候会被自动调用的。 大家牢记一点。只要有对象生成,不管是以什么形式生成的对象, 这个对象生成的时候都一定会调用构造函数,来对它进行初始化。 而且这个对象一旦生成了,那它上面当然就不会再执行构造函数了, 因为已经初始化过了。然后呢,一个类才可以有多个构造函数。 那多个构造函数具体一个对象生成的时候到底由哪个构造函数初始化呢? 啊,这就要看生成这个对象的语句是不是提供了一些构造函数的参数了。这个我们后面会讲。 那我们为什么会需要构造函数呢? 因为这个构造函数它能够 执行必要的初始化工作,那有了构造函数以后啊,我们就能够把这些初始化的代码写在构造函数里面, 那我们就不需要专门再写一个专门的初始化函数了,对吧? 然后因为这个对象生成的时候它就会自动调用构造函数初始化,所以我们也就不必去担心这个对象我们 忘了去初始化了。我们要记住,一个对象呢,它应该被初始化以后,然后再使用它, 这才是正常的。比方说你写了一个学生类,然后定义了一个学生对象,那这个学生对象它代表了学生,对吧, 既然它代表一个学生,它按理说就应该有一些属性,比方说它应该有,有姓名,有年龄,有性别等等, 那你不能说这是一个光秃秃的学生对象它什么属性都没有,那什么属性都没有就相当于你 你说一个人,你应该知道他的长相,然后说这个人没有长相,那看上去就像一个无脸鬼, 好恐怖啊。那总之嘛,一个对象,它一定要初始化以后再使用,否则呢,就会 很容易导致程序出错,然后可以举一个例子。 那比方说,你可能写了学生管理程序, 你需要根据学生的年龄去查,他100米跑了多少秒 算及格,那这样的话一般来说你会把这个呃,不同年龄所对应的100秒的及格成绩放在一个数组里面, 然后呢以年龄作为下标去查这个数组,就能够得到一个年龄对应的及格成绩了对吧? 那架设你现在有一个学生对象,你没有对这个学生对象进行初始化, 那这个学生对象里面的年龄,那它的值就是随机的,搞不好就是一个负数, 然后你又拿这个负数的年龄作为下标去查那个那个百米及格成绩的 这个数组,那它最后当然就数组越界了,对吧,你的程序就出错了。 哎,所以,你写一个类的时候一定要想好,啊,你要写一个类,写一个构造函数, 那好多年以前啊,我用刚刚用面向对象的方法开发一个软件的时候, 然后我一天写了好几个类,结果我偷懒没写构造函数,结果当天晚上我在床上就翻来覆去就觉得很不放心啊, 最后,还是半夜两三点爬起来把这个构造函数给补上,然后我才觉得安心,踏实的去睡觉了。 啊,一定要写构造函数啊。下面呢,我们就来看看这个构造函数具体 起作用的这个例子。那我们这里写了一个complex类,啊,代表复数,它有实部和虚部两个成员变量。 没有构造函数,对吧?那这时候编译器就会为它自动生成一个默认的无参的一个构造函数。 我们看这里complex c1定义了一个c1对象, 那这个对象就必须要用构造函数初始化,啊,只要有对象生成,就一定要用构造函数初始化, 那这个c1是用哪一个构造函数初始化的呢?当然就是要用编译器 自动生成的默认的无参的构造函数初始化的。 那下面这个,new出来的complex对象,动态分配一个对象。 那也是生成了一个对象啊,那当然也要用构造函数初始化,也是用默认的无参构造函数初始化的。 下面这个complex类型呢,我们自己编写了一个构造函数,啊,它的代码在这, 啊,我们用两个参数对, 这个实部和虚部进行了初始化,啊,那这个时候编译器就不会生成 那个默认的无参的构造函数了啊,你自己写了构造函数,编译器就不再生成了。 那么在这种情况下,如果你来个complex1,哎,编译就会报错, 它为什么会报错呢?因为这个c1必须要用构造函数初始化。 那么这个类里面呢现在只有这样一个构造函数, 可是这个构造函数呢又是需要参数的, 那你定义c1的时候没有给出这个构造函数所需要的参数, 编译器当然就没有办法对这个c1进行初始化了。 所以就会报错。下面这条语句,同样也是错的,啊,你动态生成了一个complex对象, 这个对象需要用构造函数初始化,可是我们这唯一的构造函数呢,是需要参数的,然后你又没给参数,当然就报错了。 再下面这条语句就没问题了,啊,这个c1呢,我们给了一个参数2, 那我们这边这个构造函数不是需要两个参数的嘛?对吧?它需要两个参数,但是我们注意到了,第二个参数是可以缺省掉的, 所以说这边的这个c1就是用这个构造函数初始化的, 然后这个参数r等于2, i等于0,所以,这个c1生成以后,它的实部是 2,虚部是0,对吧。 那同理下面这个下面这条语句的c1,c2 是怎么初始化的就很好解释了,都是用这个构造函数来初始化的。 再来看啊,我们new一个complex的对象,啊,这是动态生成的一个对象, 它是如何初始化的呢?也很简单,同样也是用这个构造函数来初始化的。 那一个类呢它可以有多个构造函数的啊, 只要这多个构造函数他们呢,参数个数或者参数类型不同, 这多个构造函数就形成了这个重载的关系。就是说这个complex类 里面有这三个构造函数。他们之间是重载的这个这个关系, 那这个三个构造函数分别在什么情况下起作用呢?就得看你定义 对象的时候,啊,或者生成对象的时候给了什么样的参数了。 我们看, 这里有一个c1对象,那么定义c1对象的时候给了一个参数啊,这个参数是一个整型的, 那哪一个构造函数能够跟这样一个整型参数匹配呢? 那显然就是这一个,啊,虽然它的参数是double类型的,但我们知道整型可以被自动转换成double类型, 所以说,这个构造函数是 可以用来初始化c1的。 其他的都不行,那c1生成以后它的实部的值是多少呢?就是这个r也就是这个3,虚部呢,当然就是0了, 那我们再看c2,啊,c2它的两个参数呢都是整数, 有哪一个构造函数它的两个参数能跟两个整数匹配呢? 很显然就是这个。啊,两个整型参数可以被自动转换成 这个double类型的参数嘛。所以c2就是用刚才的双double参数的构造函数初始化的。 再看这个c3,啊, c3我们看到它的两个参数呢都是complex对象,那毫无疑问,只有这个构造函数能跟他匹配。 所以,c3是由这个构造函数初始化,初始化的结果是什么呢?那我们看到了,啊,在这里面, c3的实部被初始化成c1和c2的实部之和, 这个c3的,c3的这个虚部呢,被初始化成c1和c2的虚部之和, 啊,所以c3初始化完以后它的实部是4啊,虚部是0, 那下面我们再来看呢 这个构造函数在对象数组里面是怎么 起作用的。我们注意这个c3,它有两个构造函数,一个没有参数,一个有一个参数。 没有参数的构造函数输出constructor 1 called,有参数的构造函数输出constructor 2 called, 看这个main函数。 呃,这第一条语句,就定义了一个 对象数组array 1,这个数组里面有两个元素。 每个元素都是CSample对象,那这两个CSample对象肯定要初始化啊,到底用哪一个构造函数初始化呢? 我们看到,对这两个对象初始化的时候所用的参数我们没有做任何交代, 对吧,你对这个初始化的时候你的参数没有做交代,编译器就认为 这个对象应该是用无参的构造函数初始化的。 因此说,这个array 1这个数组里面的两个对象都是用无参构造函数初始化, 也就是说那个无参构造函数会被调用两次,那自然就会输出两行, Constructor 1 Called,对吧?啊,接下来输出step1, 我们再看这个array 2, array 2这个数组里面还同样是有两个对象,那这两个对象是如何初始化的呢?我们在这里给出了 参数的信息,就是下边为0的那个对象,用一个构造函数初始化,然后这个构造函数的参数是4,下面呢我们用 啊,下边唯一那个的元素呢,也要用构造函数初始化,这个构造函数的参数是5 那,于是,这两个元素当然都是用有参构造函数初始化了,所以就可以输出 两行,constructor2 called,再接下来呢, 输出step 2,然后我们再看这个,啊,array 3,那array3 里面也有两个对象喽,!!!,然后我们只给出了 第一个对象的那个初始化的参数,那第二个对象的初始化的那个构造函数的参数没给,没给就等于是用 无参构造函数初始化,啊,那所以,!!!,很明显,这个这个array3[0]就是用有参构造函数初始化,array3[1]呢, 就是用无参构造函数初始化,所以就输出了,啊,constructor 2 called constructor 1 called,再接下来, step3 输出,啊,再下来我们看这一行,啊,这行呢,我们就动态分配了一个数组,对吧,这个数组里边有两个 对象,这两个对象如何初始化呢,我们对参数没有做任何交代,那这么两个对象都是用无参的构造函数 初始化的,所以就接着输出了,两行的constructor 1 called 啊,那最后,我们要注意,我们new出来的对象啊,我们new出来的这个东西啊,我们要用delete 把它,!!!,这个空间给它收回,对吧,在程序结束之前,要给它收回 再看一个构造函数跟数组的这个关系的这个,这个 例子,啊,先喝点水, ! ! ! 好,啊,这个开始的呢,它有,3个构造函数,啊,编号为 1,2,3,分别有 1个参数,2个参数,没参数,好了,那这个array1里面它有,它有3个对象,对吧 好,我们给出的前两个对象的初始化的这个参数,分别是用1个参数和2个参数对它进行初始化,然后,第3个 对象呢,!!!,没有交代它的这个构造函数参数的事情,那么,这个第3个对象当然就是用无参构造函数初始化 的,所以,这个array1里面的3个元素分别就是用1、2、3这3个构造函数来 初始化,好,再看这个array2,! ! ! array2,!!!,里面有3个对象,这3个对象,如何初始化,我们全部都给出来了,啊 将,它们分别是用 !!!,前两个都是用2号构造函数初始化,最后一个呢,是用1号构造函数 初始化,好了,那我们再看,!!!,这个稍微 有一点难的例子了,啊,就这个pArray, 啊,哦,我们一定要注意到,这块是有一个星号,啊,那有一个星号,就说明,这个pArray它是一个指针 数组,对吧,它不是一个对象数组,啊,那如果我们没有后面的这些东西的话,大家想想看 啊,这个,我们定义了这样的一个pArray数组,会不会导致, 对象生成,会不会引发test的构造函数 被调用,那当然,当然,是不会,因为这是一个指针数组,它里面的每一个元素都是一个指针,对吧 是一个指针,又不是一个对象,这个指针你可以不初始化的,那它就,!!!,它就是指针好了,不会引发任何对象的生成 那好了,在这里,我们对这个指针数组进行了初始化,而且对它的前两个元素进行 了初始化,那我们初始化的方法呢,就是,我们new出来两个 对象,大家一定要注意这个,这个new这个表达式的返回值是什么呀? 是指针,对吧,那么这个表达式的返回值就是test *,!!!,的这种类型的这个指针, 我们用new出来的这个对象,它的地址去初始化, 这个数组里面的元素,啊,那在这个数组里面的前两个元素,都被我们初始化了, 啊,也就是说,这前两个,前两个元素,分别各指向一个new出来的这个对象,那至于这个new出来的对象呢 第一个new出来的对象,啊,是用1号构造函数初始化的,第二个new出来的对象,是用2号构造函数初始化的 那我们就分析完了,看,这条语句一共生成了几个对象, 啊?注意,是生成了2个对象,而不是3个,为什么呀?因为这个, pArray2,也就说,最后这个元素, 我们没有初始化,没有初始化它的话呢,它只不过是一个指针,而且也不知道 指在,指向哪,那这个pArray2 并不会导,!!!,pArray2这个元素生成并不会导致任何对象的生成,对吧,所以这条语句, 只是生成, 了两个对象,这两个对象呢,分别用1,2进行初始化,!!!,这个 pArray2呢这个元素,它就是一个未经初始化的指针。