首先来看一个例子:
class Date
{public:
void Show()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year;
int _month;
int _day;
};
我的问题是:Show函数没有传参,为什么能够访问得到成员变量呢?
原因就在于this指针。
一个对象的this指针并不是对象本身的一部分,this作用域是在类内部,只能在成员函数中使用。当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。
下图说明:
可以看到,this指针是被隐含传递了,并且默认为成员函数的第一个参数。
那么我们可以自己使用this指针传递吗?
不可以。隐含传递this指针是编译器的任务,我们不能越俎代庖。但是我们可以使用该隐含的this指针。
比如下面的代码为例:
还有几个容易被忽略的问题:
this指针存放在何处?其实很容易想到,this指针t在成员函数的开始执行前构造,在成员的执行结束后清除。那么也就是说,在函数调用期间,this指针才会存在,所以this指针一定存在于栈上。
this指针会因编译器不同而有不同的放置位置。可能是栈,也可能是寄存器,甚至全局变量。在汇编级别里面,一个值只会以3种形式出现:立即数、
寄存器值和内存变量值。不是存放在寄存器就是存放在内存中,它们并不是和高级语言变量对应的。
默认成员函数
构造函数
成员变量是私有的,要对它们进行初始化,必须用一个公有函数来进行。同时这个函数应该有且仅在对应对象时自动执行一次,这时调用的函数称为构造函数。
class Date
{
public:
//无参的构造函数
Date()
{}
//带参的构造函数
Date(int year,int month,int day)
{
_year=year;
_month=month;
_day=day;
}
private:
int _year;
int _month;
int _day;
};
class Date
{
public:
//缺省参数的构造函数
Date(int year=1900,int month=1,int day=1)
{
_year=year;
_month=month;
_day=day;
}
//半缺省参数的构造函数
Date(int year,int month=1)
{
_year=year;
_month=month;
_day=1;
}
private:
int _year;
int _month;
int _day;
};
特点:
- 函数名与类名相同
- 无返回值
- 对象实例化系统自动调用对应的构造函数。
- 构造函数可以重载
- 构造函数可以在类中定义,也可以在类外定义。
- 如果类定义中没有给出构造函数,编译器会自动产生一个缺省的构造函数,但是只要我们定义了一个构造函数,那么系统就不会自动生成一个缺省的构造函数。
- 无参的构造函数和全缺省的构造函数都认为是缺省构造函数,并且只能有一个缺省的构造函数。
析构函数
当一个对象的生命周期结束时,c++编译系统会自动调用一个成员函数,这个特殊的成员函数即析构函数。
特点:
析构函数在类名前加上~。
析构函数无参数无返回值。
一个类有且只有一个析构函数,若未显示定义,系统会自动生成缺省的析构函数。
对象生命周期结束时,系统会自动调用析构函数。
析构函数并不是删除对象,而是做一些清理工作。清理工作,并不是删除,删除是由操作系统完成的。
当需要free,close的可以交给析构函数来管理。
class Array
{
public:
Array(int size)
{
_ptr=(int*)malloc(size*sizeof(int));
}
//析构函数
~Array()
{
free(_ptr);
_ptr=NULL;
}
private:
int* _ptr;
};
拷贝构造
创建对象时使用同类对象来进行初始化,这时所用的构造函数称为拷贝构造函数,
特征:
- 拷贝构造函数是一个构造函数的重载
- 拷贝构造函数的传参必须使用引用传参,使用传值方式会引发无穷递归调用。
- 若未显式定义,系统会默认缺省的拷贝构造函数。缺省的拷贝构造函数会依次拷贝类成员进行初始化。
为什么会引发无穷递归调用?原因是:
如果使用值传递时,会调用自己的拷贝构造进行初始化赋值时,又会调用自己的拷贝构造,仍然是值传递,那么就会一直调用自己的拷贝构造。
见下例:
当我们不写拷贝构造函数时,系统会自动默认生成一个拷贝构造函数。那么不写可不可以,会不会出现什么问题。在下面这种场景下,如果不写拷贝构造就会出现问题。
class Date
{
public:
//缺省参数的构造函数
Date(int year=1900,int month=1,int day=1)
{
_year=year;
_month=month;
_day=day;
}
//析构函数
~Date()
{}
//拷贝构造函数
Date(const Date& d)
{
_year=d._year;
_month=d._month;
_day=d._day;
}
void Show()
{
cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(1988,8,2);
d1.Show();
//调用拷贝构造函数,两种方式等价
Date d2(d1);
d2.Show();
Date d3=d1;
d3.Show();
return 0;
}
运算符重载
运算符重载的意义:为了增强的代码的可读性。
运算符重载的特征:
- operator加合法的运算符 构成函数名
- 重载运算符之后,不能改变运算符的优先级/结合性/操作数个数
5个不能重载的运算符:
- ‘.’ 成员访问
- .* 成员指针访问
- :: 域运算符
- sizeof 长度运算符
- ?: 条件运算符
赋值运算符重载
当我们使用单纯的运算符的功能不能够帮助我们实现一些功能时,c++的运算符重载就能够帮助我们解决这个问题。
//赋值运算符的重载
Date& operator= (const Date& d)//为什么返回的是引用
{
if(this!=&d)//该条件判断了?
{
this->_year=d._year;
this->_month=d._month;
this->_day=d._day;
}
return *this;
}
在这段代码中我做了两个标注的问题:
1.为什么返回的是引用类型:考虑的是以下场景,在使用赋值运算符时,a=b=c;可以理解为先将c赋给b,b通过隐含的this指针传递给成员函数,再进行成员变量的一一赋值,然后将该结果作为引用返回,那么就可以将刚才的结果作为下一个赋值的一个操作数,继而进行赋值。
2.if判断语句判断了什么条件?由于左操作数是通过this指针隐含传递的,所以需要判断一次是否是将自己赋给自己。
3.返回*this:在类的非静态成员函数中返回类对象本身的时候,直接使用 return *this;
成员变量的初始化
成员变量的两种初始化方式:
- 初始化列表
- 构造函数内进行赋值
初始化列表:以一个冒号开始,接着一个逗号分隔数据列表,每个数据成员都放在一个括号中进行初始化,尽量使用初始化列表进行初始化,因为比较高效。
为什么高效?
如果这个类的大小非常大,当使用构造函数初始化时,需要先调用一次默认构造函数,在进行成员变量的赋值,在这中间的开销是非常大的。当使用初始化列表时,只需要调用一次拷贝构造函数即可。效率的差别不言而喻。
那些成员变量必须放在初始化列表中?
- 常量成员变量。
- 引用类型成员变量。
- 没有缺省构造函数的类成员变量。
成员变量按声明顺序执行,而不按照出现顺序执行。声明的顺序与定义的顺序保持一致。