隐含的this指针
问题导入
当我初学C++时,我遇到了这样一个问题,当时我写了一个简单的代码,如下:
class Date{
public:
void Print(){
cout<<_year<<"-"<<_month<<"-"<<day<<endl;
}
private:
int _year;
int _month;
int _day;
};
很容易理解,我写了一个简单的日期类,当我实例化两个对象时d1、d2时,赋予不同的数值,打印出来结果是不同的,但是在类中的成员函数体内,我并没有对不同对象作区分,那么该函数是如何知道要打印的是d1还是d2对象呢? 我当时也是很迷惑,想不出来为什么,但是我却知道它肯定通过某种方式对不同的对象作区分了,最后,查阅资料我才知道,原来在这里还有一个隐含的this指针。C++通过this指针来解决该问题。
this指针是什么?
C++编译器给每个成员函数增加了一个隐含的this指针,该指针指向当前对象(调用该成员函数的对象),在函数体内都是通过this指针去访问,只不过所有操作对于用户都是隐藏的,不需要用户自己去操作,编译器自动完成。
this指针特性
- this指针的类型是 类类型*const
- 只能在成员函数内部使用
- this指针的本质其实就是一个成员函数的形参,是对象调用成员函数时将该对象的地址作为实参传递给this指针。对象中不存储this指针。
- this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
this指针存储在哪里?
- 其实编译器在生成程序时,为了获取对象的首地址,就会将对象的首地址存储在寄存器ECX中,不同的编译器也可能存储位置不同,也就是说成员函数的其它参数都存储在栈中,而this指针存储在寄存器中。类的静态成员函数因为没有this指针,所以类的静态成员函数也就无法访问类的非静态的成员变量。
this指针可以为空吗?
- this指针是作为成员函数的形参存在,那它可以为空吗?其实很容易想明白,this指针它也是指针,指针当然可以为空,所以this指针也可以为空。很重要的一点,那就是不能对空指针解引用。所以在这里我们要注意,如果this指针为空,就不能进行解引用。(在成员函数里不能使用this指针去操作)
类的六个默认成员函数
类的六个默认成员函数
1. 构造函数
概念:构造函数虽然叫做构造函数,但其实它并不是去构造对象,而是进行初始化对象的工作。
特点:
- 构造函数函数名与类名相同
- 构造函数没有返回值
- 构造函数可以重载
- 对象实例化后自动调用构造函数
- 如果用户没有显示定义构造函数,则编译器会默认生成一个无参的构造函数,一旦用户显示定义,编译器将不会再生成。
- 默认的构造函数一共有三种,一种是我们没写,编译器自动生成的无参构造,另一种是我们写的无参构造,还有一种就是我们写的全缺省的构造函数。
class Date{
public:
Date(){ //无参构造函数
}
Date(int year = 1900, int month = 1,int day = 1){
//全缺省的默认构造
}
Date(int year,int month,int day){ //构造函数重载,进行初始化。
_year = year;
_month = month;
_day = day;
}
void Print(){
cout<<_year<<"-"<<_month<<"-"<<day<<endl;
}
private:
int _year;
int _month;
int _day;
};
作用
- 也许有人会问默认的构造函数有什么用?在这里我需要解释一下,默认的构造函数其实并不是没用有,在C++中,数据类型可以分为两类,一类是内置类型,例如 int , char , double 等等。另一种就是自定义类型,例如我们自己写的Date类,对内置类型不做处理,但是对我们自定义类型的对象,会自动的去调用它的构造函数。
2. 析构函数
概念:析构函数和构造函数正好相反,析构函数也不是进行对象销毁的,局部对象销毁由系统完成的,只不过对象在销毁时,会自动的调用析构函数完成对象的清理工作。例如我们在堆上开辟了一块内存,这就是需要我们去手动完成的。我们会将代码写在析构函数中,对象销毁时,会自动调用析构函数,防止我们未释放资源而导致内存泄漏。
特点:
- 析构函数的函数名是在类名前加上~
- 析构函数无参数无返回值
- 一个类有且只有一个析构函数,若未显式定义,则编译器会自动生成一个默认的析构函数。
- 对象的生命周期结束时,会自动的调用析构函数,完成清理工作。
作用
- 析构函数同构造函数一样,对内置类型不做处理,但对于自定义类型会自动的去调用它的析构函数
class SeqList{
public:
SeqList(int cap = 10){
pdata = (int*) malloc(sizeof(int)*cap);
assert(pdata);
_size = 0;
_capacity = cap;
}
~SeqList(){
if(pdata){
free(pdata);
pdata = NULL;
_size = 0;
_capacity = 0;
}
}
private:
int* _pdata;
int _size;
int _capacity;
};
3. 拷贝构造函数
概念:用同类对象初始化创建对象
特点:
- 拷贝构造是构造函数的一个重载函数
- 拷贝构造的参数只有一个且必须使用引用方式传参,使用传值方式会引发无穷递归。
- 若未显示定义拷贝构造,系统会默认生成拷贝构造。默认生成的拷贝构造函数是按照字节进行一个字节一个字节进行拷贝的,类似于memcpy()函数,这种方式叫做浅拷贝,也叫做值拷贝,这种方式是不安全的,所以有时候我们得进行深拷贝。(具体深浅拷贝问题我会在后面跟大家分享)
class Date{
public:
Date(const Date& d){ //拷贝构造函数
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print(){
cout<<_year<<"-"<<_month<<"-"<<day<<endl;
}
private:
int _year;
int _month;
int _day;
};
赋值运算符重载
运算符重载:C++为了增强代码的可读性,引入了运算符重载这个概念,具体看看什么是运算符重载。
- 函数名字为 operator加上你需要重载的运算符符号
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型或者枚举类型的操作数
- 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
- 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的
操作符有一个默认的形参this,限定为第一个形参 - .* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。
4. 赋值运算符重载
特点:
- 函数名 operator=
- 返回值为类类型,返回 *this
- 一个类如果没有显式定义一个赋值运算符重载函数,则系统会默认生成一个,并按照字节序完成拷贝。
class SeqList{
public:
Date& operator=(const Date& d){
if(*this != d){
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int* _pdata;
int _size;
int _capacity;
};
const 成员函数
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
5. 取地址操作符重载
class Date{
public:
Date* operator&(){
return this;
}
private:
int _year;
int _month;
int _day;
};
6. const取地址操作符重载
class Date{
public:
const Date* operator&() const {
return this;
}
private:
int _year;
int _month;
int _day;
};
这两个默认成员函数一般不用重写,编译器会默认生成。