this指针
1.当类中有成员函数时,在上一篇文章我们说到过成员函数在非动态时不会存储函数指针也就是函数的地址,会存在一片公共的区域中,也就是说函数体中没有关于不同对象的区分,那当我们实例化出类的多个对象时,它是如何区分是哪个对象呢。C++给了一个隐含的this指针解决这个问题。
2.具体的说法,也就是编译器编译后,类的成员函数(非静态的)默认都会在形参的第一个位置增加一个当前类类型的指针,叫做this指针,其实也可以把它理解为函数的形参。
代码示例:
编译器编译后Init函数void Init(int year,int month,int day)将会转化为void Init(Date* const this,int year,int month,int day) ,在函数调用时它会把你要访问的实例化对象地址传过去,这个时候就可以区分你所定义的多个对象。
可以看成这样理解:
3.在类的成员函数中访问成员变量,本质都是通过this指针访问的,如此一来就解决了我们1中所说的的问题.
4.C++规定不能在形参和实参的位置显示的写this指针,但可以在函数体内显示的写this指针。
eg:
函数体内使用this我们也是很好理解的,在前面说过this可以理解为函数的形参,而且这个形参是被const修饰了,修饰的是指针本身,所以在函数体内的运用的写法即为注释后的样子。
温馨知识:
被const修饰的变量,const在*之前修饰的是指向对象,意思是即指针指向的内容不可更改,但可以更改这个指针所指向的的地址,也就是说原本它是指向a的地址,但可以改变为指向b的地址;在*之后修饰的是指针本身,意味着指针的地址不可修改,但是它解引用修改可以,也就是说可以修改指向对象的数据内容。
接下来说一个综合的例子来更好的理解:
我们可以看到在控制台中没有显示0,而是显示的是-1073741819,这个是说明代码逻辑有问题,但是语法没有错误。
接下来对它进行解释说明:
我们定义了一个Date类型的指针,且这个指针是空,在前面几篇文章说过对象没有存储函数的地址,因此d->Print();这个语句可以执行因为没有对空指针进行解引用,我们可以看到控制台中出现了第一个cout的打印也佐证了它是没问题的,但第二个cout << year;为什么没有打印出来,我们刚刚说过成员函数的使用会把对象的地址传过去也就是会有一个默认的this指针来接受这个对象的地址,此时this指针就相当于空指针,那么将cout << year理解为cout << this->year;意味着对空指针进行了解引用,所以最终代码显示错误。
类的默认成员函数
默认成员函数就是用户没有定义也就是没有写出来的函数,编译器会自动生成的成员函数称为默认成员函数。一个类,我们不写的情况下编译器会默认生成以下6个默认成员函数。
如图所示:
值得注意的是C++11之后又增加了两个默认成员函数,移动构造和移动赋值。
构造函数
构造函数时特殊的成员函数,值得注意的是,构造函数虽然名字叫做构造,但是构造函数的主要任务并不是开空间创建对象(我们平常使用的局部对象是栈帧创建时,空间就已经开辟好了),而是对象实例化时初始化对象。但是我们也需要知道自动生成的构造函数也不一定都符合我们的需求。形象的说法:构造函数的本质是要替代我们上面Date类中写的Init函数的功能,构造函数自动调用的特点就完美的替代了Init函数。
构造函数的特点:
1.函数名与类名相同。
2.无返回值。(返回值什么都不需要,void也不需要写,C++规定这样)
3.对象实例化时系统会自动调用对应的构造函数。
4.构造函数可以重载。
对这几个进行实例说明(可以对比一下上面写的Init函数):
这两个Date函数构造也是函数重载,在前面的篇章中有讲到过。
注意:如果通过无参构造函数创建对象时,对象后面不用加()否则编译器无法区分究竟是函数声明还是实例化对象。
即这种代码是错误的:
我们可以看到编译都未通过。你会发现它完全可以理解为函数声明Date是返回值类型,A是函数名,导致编译器无法区分,所以最后未通过。
5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
6.无参构造函数,全缺省构造函数,我们不写构造时编译器默认生成的构造函数,都叫做默认构造函数。但是这三个函数只能有一个存在,不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载,但是调用时会存在歧义,前面的一篇文章有说过。还有值得注意的是无参构造函数和全缺省构造函数也是默认构造,这个需要记清楚,总之,就是不传实参就可以调用的构造就叫默认构造。
7.我们没有显示的写出来的,编译器默认生成的构造,对内置类型成员变量的初始化没有要求,也就是说是否初始化是不确定的,看编译器。而对于自定义类型的成员变量,要求调用这个成员变量的默认构造函数初始化。如果这个成员变量没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要用初始化列表才能解决。
举个例子说明(编译器默认生成的构造函数):
但是要注意的是大多数默认构造函数都是我们自己写的,因为编译器初不初始化是不确定的。