目录
1. this指针
什么是this关键字呢?
我们先来看一段代码:
之前我们得知创建对象时,函数成员是在公共代码区的,那d1和d2同时调用Display函数,怎么能分辨出呢?
C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
因此当我们调用Display函数时,系统会将其转换成:
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
void Display(Date* this) {
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
1.1 this的特征
1. this指针的类型:类类型* const
void Display(Date* const this) {
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
严格来说,系统会将每个调用的函数转变成这种形式,加上const的目的是为了保证this的指向不被改变。
2. 只能在“成员函数”的内部使用
3. this指针本质上其实是一个对象的形参,是对象调用成员函数时,将对象地址作为实参传递给this 形参。所以对象中不存储this指针。
从这里我们可以更好地证明this其实是所在对象地址的拷贝。
因为this是一个形参,所以一般情况下this存储在栈中,但有时候为了效率,this也有可能存储在寄存器中。
4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
因为this是一个对象地址的拷贝,所以当一个对象的地址的nullptr时,那么this也是nullptr
这里我们可以看见this确实可以为空。
2. 构造函数
class Date
{
public:
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Display()
{
cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1,d2;
d1.SetDate(2018,5,1);
d1.Display();
Date d2;
d2.SetDate(2018,7,1);
d2.Display();
return 0;
}
这里我们每创建一个对象都要对其调用一个初始化函数,那有没有在创建对象期间就对其初始化的呢?
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,并且在其生命周期内只调用一次。
2.1 特征
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数并不是开辟空间的,而是初始化对象。
构造函数有几大特征:
1. 函数名与类名相同
2. 无返回值
3. 对象实例化时编译器自动调用对应的构造函数
4. 构造函数可以重载5.
class Date
{
public :
// 1.无参构造函数
Date ()
{}
// 2.带参构造函数
Date (int year, int month , int day )
{
_year = year ;
_month = month ;
_day = day ;
}
private :
int _year ;
int _month ;
int _day ;
};
void TestDate()
{
Date d1; // 调用无参构造函数
Date d2 (2015, 1, 1); // 调用带参的构造函数
Date d3();
}
如果构造函数重载了带参和不带参的,那么在创建对象的时候可以通过是否传入参数来选择调用哪个构造函数。
5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义了构造函数编译器将不再生成。
一旦显式定义了构造函数,在创建对象时就必须按照函数你创建函数的方式传参进行初始化,否则就会产生错误。
这里是因为在创建对象时没有引入参数,得出没有默认的构造参数,那我们可以得出当构造函数不需要传参时就被成为默认构造函数。
6. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、编译器默认生成的构造函数,都可以认为是默认成员函数。
我们需要注意的是默认构造函数有且仅有一个。
如果出现多个默认构造函数,在创建对象时会产生歧义,因为不知道到底使用哪一个构造函数来初始化对象。
7. 编译器自动生成的默认构造函数是将我们在对象中的成员变量都初始化吗?
我们发现当使用默认的构造函数时,对象中的变量没有进行初始化,那编译器默认生成的构造函数就没有一点作用吗?
这里我们要思考一下,在C++中把类型分成了内置类型/基本类型和自定义类型,而编译器默认构造函数的功能其实是将自定义函数来进行初始化。
我们发现自定义的类型地区被初始化了,而我们还可以发现当初始化自定义类型时,会调用自定义类型中的构造函数。
8. 变量名的命名规范
这里我们也可以使用this来分辨变量名相同的变量。
但是一般情况下,我们尽量避免写出产生歧义的代码,从源头上根治,而不是发生问题后进行更改。
3. 析构函数
前面通过构造函数的学习,我们知道一个对象是如何初始化的,那一个对象又是怎么没呢的? 析构函数:与构造函数功能相反,但析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而 对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
这里的清理工作一般来说是将开辟的空间或者打开的文件指针等进行回收。
3.1 特征
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值。
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
我们发现当离开对象所在的作用域时,则进行析构函数的调用。
5. 当对象里的成员变量存在自定义类型时,对调用自定义类型中的析构函数。
当生命周期结束时,对调用自定义类型中的析构函数。
4. 拷贝构造函数
在生活中我们可能见过一模一样的东西,那么在写代码的时候,能否创建出两个一模一样的对象呢?
这时我们就可以调用拷贝构造函数来帮助我们实现。
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
4.1 特征
1. 拷贝构造函数是构造函数的一个重载形式。
只不过拷贝构造的格式很唯一,只能有单个形参。
2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用
当创建d1时,会调用第一个构造函数,而调用d2时则调用第二个构造函数。
那么为什么要引用传参呢?
我们知道,如果函数在传参的过程中使用的是传值调用,那么在函数中会形成临时拷贝来接受传入的值,因为传出的是一个对象,那在接收的过程中又将创建一个对象来接受这个对象,这样就无限的调用拷贝构造函数,形成无限递归。
3. 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
这里我们发现d2的值最终和d1的值一样。
4. 那么编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?
我们发现当使用默认的拷贝构造函数时,会引发一些问题,当s2调用拷贝构造函数时,会将s2的_str也指向了s1的_str开辟的空间,那么当对象的生命周期结束时,会将此空间free两次,导致出现问题,因此默认的拷贝构造函数也不是万能的。