一、隐含的this指针
1、性质:每个成员函数都有一个指针形参,它的名字是固定的,称为this指针,this指针是隐式的(构造函数除外)。成员函数隐含指针形参,是编译器自己处理的,不能在成员函数的形参中添加this指针的参数定义,也不能在调用时显示传递对象的地址给this指针。
2、传递方式:对象地址作为实参传递给成员函数的第一个形参this指针。
3、存放位置:this指针在对象调用成员函数前创建,在成员函数调用结束后销毁,所以this指针存放在栈上。
二、类的默认成员函数
类的六个默认成员函数:构造函数、拷贝构造函数、析构函数、赋值操作符重载、取地址操作符重载、const修饰的取地址操作符重载。
1、构造函数
(1)概念:
被初始化的私有成员变量和一个公有成员函数,有且仅在对象定义对象时自动执行一次的函数,就叫做构造函数。
2)特征:
A、函数名与类名相同;
B、无返回值;
C、对象构造(对象实例化)时系统自动调用对应的构造函数;
D、构造函数可以重载;
E、构造函数可以在类中定义,也可以在类外定义。
F、如果类定义中没有给出构造函数,则C++编译器自动产生一个缺省的构造函数,但只要我们定义了一个构造函数,系统就不会自动生成缺省的构造函数。
G、无参的构造函数和全缺省值的构造函数都认为是缺省的构造函数,并且缺省的构造函数只能有一个。
【例】有参和无参的构造函数
class Date
{
private:
int _year;
int _month;
int _day;
public:
//无参构造函数
Date()
{}
//带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
public:
void Display()
{
cout << _year << _month << _day << endl;
}
};
void Test()
{
Date d1;//调用无参构造函数
Date d2(2015, 1, 1);//调用带参构造函数
Date d3();//注意这里没有调用d3的构造函数定义出d3 ???????
Date p;
p.Display();
}
【例】带参省的构造函数
class Date
{
private:
int _year;
int _month;
int _day;
public:
void Display()
{
cout << _year << _month << _day << endl;
}
public:
//缺省参数的构造函数
Date(int year = 2000, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//半缺省参数的构造函数
Date(int year, int month = 1)
{
_year = year;
_month = month;
_day = 1;
}
};
void Test()
{
Date d1;//调用缺省构造函数
Date d2(2015, 1, 9);
Date d;
d.Display();
}
注:如果缺省参数声明和定义分离,那么可以在声明或定义中给默认参数。
(3)构造函数的调用顺序总是如下:
1)基类构造函数。如果有多个基类,则构造函数的调用顺序是某类在类派生表中出现的顺序,而不是它们在成员初始化表中的顺序。
2)成员类对象构造函数。如果有多个成员类对象则构造函数的调用顺序是对象在类中被声明的顺序,而不是它们出现在成员初始化表中的顺序。
3)派生类构造函数。
2、拷贝构造函数
(1)概念:
创建对象时使用同类对象来进行初始化,这时使用的构造函数称为拷贝构造函数,拷贝构造函数是特殊的构造函数。
(2)特征:
A、拷贝构造函数其实是一种构造函数的重载。
B、拷贝构造函数必须使用引用传参,使用传值方式会引发无穷递归调用。为什么?
答:如果拷贝构造函数中的参数不是一个引用,那么就相当于采用了传值的方式(pass-by-value),而传值的方式会调用该类的拷贝构造函数,从而造成无穷递归地调用拷贝构造函数,因此拷贝构造函数的参数必须是一个引用。如下图所示:
C、若未显示定义,系统会默认缺省的拷贝构造函数。缺省的拷贝构造函数会依次拷贝类成员进行初始化。
【例】
class Date
{
public:
//无参的构造函数
Date()
{}
//拷贝构造函数
Date(const Date& d)//引用传参
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
void Test()
{
Date d1;
//以下两种方法是等价的
Date d2(d1);
Date d3 = d1;
}
3、析构函数
(1)概念:
当一个对象的生命周期结束时,C++编译系统自动调用一个成员函数,这个特殊的成员函数即是析构函数。
(2)特征:
A、析构函数在类名前加上字符“~”。
B、析构函数无参数,无返回值。
C、一个类有且只有一个析构函数。若未显示定义,系统会自动生成缺省的析构函数。
D、对象生命周期结束时,C++编译系统会自动调用析构函数。
E、注意析构函数体内并不是删除的对象,而是做一些清理工作。
(3)析构函数的调用顺序
首先调用派生类的析构函数;其次再调用成员类对象的析构函数;最后调用基类的析构函数。
(4)析构函数在下边3种情况时被调用:
1)对象生命周期结束,被销毁时(一般类成员的指针变量与引用都不自动调用析构函数);
2)delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类虚构函数是虚函数时;
3)对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。
【例】
class Date
{
public:
~Date()//析构函数
{}
private:
int _year;
int _month;
int _day;
};
class Array
{
public:
Array(int size)
{
_ptr = (int *)malloc(size*sizeof(int));
}
//这里的析构函数需要完成清理工作
~Array()
{
if (_ptr)
{
free(_ptr);
_ptr = 0;
}
}
private:
int *_ptr;
};
4、运算符重载
(1)概念:
运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。
(1)特征:
A、operator + 合法的运算符构成函数名(重载 < 运算符函数名: operate <)。
B、重载运算符以后,不能改变运算符的优先级/结合性/操作数的个数。
【例】
#include<iostream>
#include<stdlib.h>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
//构造函数
Date(int year, int day, int month)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造函数
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//“==”操作符重载
bool operator ==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
//打印函数
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
};
void Test()
{
//调用构造函数
Date date1(2018, 8, 8);
date1.Print();
//调用拷贝构造函数
Date date2 = date1;
date2.Print();
//调用“==”操作符重载
date2 == date1;
}
int main()
{
Test();
system("pause");
return 0;
}
运行结果:
【笔试题考点】5个C++不能重载的运算符:“.”、“.*”、“::”、“?:”、“sizeof”。
5、赋值运算符重载
拷贝构造函数是创建新的的对象,使用一个已有对象来初始化这个准备创建的对象。赋值运算符的重载是对一个已存在的对象进行拷贝赋值。
重载运算符的调用及编译器的处理,如图2:
图2 运算符重载和编译器处理图
【例】
#include<iostream>
#include<stdlib.h>
using namespace std;
//赋值运算符重载
class Date
{
private:
int _year;
int _month;
int _day;
public:
Date()
{}
//拷贝构造函数
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{}
//赋值操作符重载
//(1)思考为什么operate=赋值函数需要一个Date&的返回值,使用void做返回值,可以吗?
Date& operator = (const Date& d)
{
//(2)这里的if条件判断实在检查什么?
if (this != &d)
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
return *this;
}
};
void Test()
{
Date d1;
Date d2 = d1;//调用拷贝构造函数
Date d3;
d3 = d1;//调用赋值运算符的重载
}
int main()
{
Test();
system("pause");
return 0;
}
(1)答:如果返回void类型,回造成类型不匹配的问题。
1)本意为“返回左值的引用”(左值:赋值号左面的变量而非其值)
2)并非必须返回引用,返回引用的好处是:既可以以赋值的原始语义已知,又可以避免拷贝构造函数和析构函数的调用。
【例】为了进行连续赋值,即 x = y = z
A、赋值返回引用
x = y = z 先执行y = z,返回y的引用,执行x = y
B、赋值不返回引用
x = y = z 先执行y = z,返回用y初始化的临时对象(注意临时对象都是常对象),再执行x = y的临时对象(要求operator=(const X&) ),返回用x初始化的临时对象(此处要求拷贝构造函数必须为X(const X&) )。
(2)答:避免类对象自己给自己赋值。
6、类的const成员函数
在成员函数后面加const,修饰this指针指向的对象,保证调用这个const成员函数的对象不会被改变。如下图所示:
【例】
#include<iostream>
#include<stdlib.h>
using namespace std;
class Date
{
private:
int _year;
int _month;
int _day;
public:
//构造函数
Date(int year = 2019, int month = 8, int day = 18)
{
_year = year;
_month = month;
_day = day;
}
//普通的成员函数
void display()
{
cout << "display()" << endl;
cout << "year:" << _year << endl << endl;
}
//const成员函数
void display() const
{
cout << "display() const" << endl;
cout << "month:" << _month << endl;
}
};
void Test()
{
Date date1;
date1.display();//调用普通的成员函数
const Date date2;
date2.display();
}
int main()
{
Test();
system("pause");
return 0;
}
运行结果:
注意事项:
(1)const对象可以调用其它的const函数;
(2)非const对象可以调用非const成员函数和const成员函数;
(3)const成员函数内可以调用其它的const成员函数;
(4)非const成员函数内可以调用其它的const成员函数和非const成员函数。
7、类的取地址操作符重载和const修饰的取地址操作符重载
这两个默认成员函数一般不用重新定义,编译器默认会生成。
class Date
{
public:
//取地址操作符重载
Date* operator &()
{
return this;
}
//const修饰的取地址操作符重载
const Date* operator&() const
{
return this;
}
private:
int _year;
int _month;
int _day;
};
只有一种情况下需要重载这两个操作符,那就是想获取指定内容时。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/18207.html