类的六个默认成员函数
1.构造函数
1.1概念
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。(初始化对象)
class Data
{
public:
void Setdata(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Printdata()
{
cout << _year << "." << _month << "." << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;
Data d2;
d1.Setdata(2019, 8, 1);
d2.Setdata(2018, 7, 1);
d1.Printdata();
d2.Printdata();
system("pause");
return 0;
}
对于Data类,每次都可以通过调用Setdata函数给对象设置内容。但每次都要创建对象很麻烦。可以通过构造函数,在创建对象的同时将内容设置。
1.2特性
构造函数是特殊的成员函数,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
构造函数特征如下:
- 函数名与类名相同。
- 无返回值。
- 对象实例化时编译器自动调用对应的构造函数。
- 构造函数可以重载。
class Data
{
public:
Data()//无参构造函数
{}
Data(int year, int month, int day)//带参构造函数
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;//调用无参构造函数(通过无参构造函数定义对象,后面不需加括号,否则成为函数声明)
Data d2(2019, 8, 1)//调用带参构造函数
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
Date d3();
}
5.如果类中没有显式定义一个构造函数,编译器会自动生成一个无参的默认构造函数。
class Data
{
public:
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;//没有定义构造函数,对象也能创建成功,系统自动调用默认构造函数
}
6.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
#include<iostream>
using namespace std;
class Date {
int m_year;
size_t m_month;
size_t m_day;
public:
Date() //无参构造函数
{
m_year = 1900;
m_month = 01;
m_day = 15;
}
//只允许存在一个默认构造函数!
Date(int y = 2019, size_t m = 05, size_t d = 01) //全缺省构造函数
{
m_year = y;
m_month = m;
m_day = d;
}
};
int main()
{
Date a;
Date b(2019, 01, 01);
return 0;
}
1.3初始化列表
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称作为类对象成员的初始化,构造函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
1.3.1概念
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个**"成员变量"后面跟一个放在括号中的初始值或表达式。**
Date(int year, int month, int day)
: _year(year),
_month(month),
_day(day)
类中包含以下成员,必须放在初始化列表位置进行初始化:
(1)类类型成员 (该类没有默认构造函数)
因为Test带参数的构造函数,那么他是无法依靠编译器生成无参构造函数的。所以没有三个int型数据,就无法创建Test的对象。Test类对象是MyTest的成员,想要初始化这个对象test,那就只能用成员初始化列表。
//类 类型成员
class Test
{
public:
Test (int, int, int){
cout <<"Test" << endl;
};
private:
int x;
int y;
int z;
};
class Mytest
{
public:
Mytest():test(1,2,3){ //初始化
cout << "Mytest" << endl;
};
private:
Test test;
};
(2)const成员变量&引用类型成员变量
因为这两种对象要在声明后马上初始化,而在构造函数中,做的是对他们的赋值,这样是不被允许的
//const变量和引用
class Test
{
const int a; //const成员声明
public:
Test():a(10){} //初始化
};
class Test
{
int &a; //引用声明
public:
Test(int a):a(a){} //初始化
}
(3)尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。
class Time
{
public:
Time(int hour = 0):_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int day)
{}
private:
int _day;
Time _t;
};
int main()
{
Date d(1);
}
(4)成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
class Array
{
public:
Array(int size):
_size(size),
_array((int*)malloc(sizeof(int)*_size))
{}
private:
int* _array;
int _size;
};
2.析构函数
与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
typedef int DataType;
class SeqList
{
public:
SeqList(int capacity = 10)
{
_pData = (DataType*)malloc(capacity * sizeof(DataType));
assert(_pData);
_size = 0;
_capacity = capacity;
}
~SeqList()
{
if (_pData)
{
free(_pData); // 释放堆上的空间
_pData = NULL; // 将指针置为空
_capacity = 0;
_size = 0;
}
}
private:
int* _pData;
size_t _size;
size_t _capacity;
};
析构函数是特殊的成员函数
2.2特征
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值。
- 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
3.拷贝构造函数
拷贝构造函数: 特殊的构造函数(构造函数的重载) , 只有单个形参(不算隐藏的this指针),该形参是对本类类型对象的引用(一般常用const修饰),在建立一个新对象时, 使用一个已经存在的对象去初始化这个新对象。
Date d2(d1);
Date d3=d1;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)//拷贝构造 使用引用传参
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
3.2特征
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
因为当形参是对象时, 我们知道当一个(有参)函数被调用会发生实参给形参传值的情况。拷贝构造函数也一样, 形参是对象 , 实参也是对象 ,一传参就又要调用拷贝构造函数 , 再次调用又会出现这种情况, 无穷无尽递归调用 .我们传引用就可以很好地解决这个问题, 既让拷贝构造函数得到了已存在对象的数据给新对象赋值 , 又不会出现自递归。
3.3浅拷贝(简单的赋值拷贝操作)
若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
class Date
{
public:
Date(int year = 1900, int month = 01, int day = 01)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);//d2调用默认拷贝构造函数,d2的值和d1一样。
//d2=d1=1900 1 1
return 0;
}
3.5深拷贝(在堆区重新申请空间进行拷贝)
#include<iostream>
using namespace std;
class Person {
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(int age,int height)
{
m_height=new int(height);
m_age = age;
cout << "Person的有参构造函数调用" << endl;
}
int m_age;
int* m_height;
~Person()
{
if (m_height != NULL)
{
delete m_height;
m_height = NULL;
}
cout << "Person的析构函数调用" << endl;
}
};
void test01()
{
Person p1(18,160);
cout << "p1的年龄为:" << p1.m_age <<"p1的身高为" <<p1.m_height<<endl;
Person p2(p1);
cout << "p2的年龄为:" << p2.m_age <<"p2的身高为" << p2.m_height<<endl;
}
int main() {
test01();
return 0;
}
程序编译会出现错误,因为p2浅拷贝p1, 两个对象都指向了p1开辟的内存空间(p2没有开辟其自身的内存空间),但在析构函数执行后,p2释放掉了内存空间,当p1再次执行析构函数时,其内存空间已经被释放掉,再次释放则会出错!(浅拷贝带来的问题:堆区内存重复释放)这时就需要深拷贝来解决问题。
#include<iostream>
using namespace std;
class Person {
public:
Person()
{
cout << "Person的默认构造函数调用" << endl;
}
Person(int age,int height)
{
m_height=new int(height);
m_age = age;
cout << "Person的有参构造函数调用" << endl;
}
Person(const Person& p)
{
cout << "Person的拷贝构造函数调用" << endl;
m_age = p.m_age;
//深拷贝操作
m_height = new int(*p.m_height);//开辟新的内存空间来储存p2的信息
}
int m_age;
int* m_height;
~Person()
{
if (m_height != NULL)
{
delete m_height;
m_height = NULL;
}
cout << "Person的析构函数调用" << endl;
}
};
void test01()
{
Person p1(18,160);
cout << "p1的年龄为:" << p1.m_age <<"p1的身高为" <<*p1.m_height<<endl;
Person p2(p1);
cout << "p2的年龄为:" << p2.m_age <<"p2的身高为" <<*p2.m_height<<endl;
}
int main() {
test01();
return 0;
}
4.运算符重载
运算符重载:如果不做特殊处理,C++ 的 +、-、*、/ 等运算符只能用于对基本类型的常量或变量进行运算,不能用于对象之间的运算。利用运算符重载机制,赋予运算符新的机制。
运算符重载的实质时编写以运算符为名称的函数。
返回值类型 operator 运算符(参数列表)
注意:
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个类类型或者枚举类型的操作数
3.用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
4.作为类成员的重载函数时,其形参看起来比操作数数目少1
5.操作符有一个默认的形参this,限定为第一个形参
6." .* " 、" :: " 、" sizeof " 、" ?: " 、" . " 以上5个运算符不能重载。
包含被重载的运算符的表达式会被编译成对运算符函数的调用,运算符的操作数成为函数调用时的实参,运算的结果就是函数的返回值。运算符可以被多次重载。
#include<iostream>
using namespace std;
class complex
{
public:
int real;
int imag;
complex(int r = 0, int i = 0) :real(r), imag(i){}
complex operator - (const complex& c);//-重载为一个成员函数 参数个数等于运算符操作数-1
};
complex operator + (const complex& a, const complex& b)//+重载为一个全局函数 参数个数等于运算符操作数
{
return complex(a.real + b.real, a.imag + b.imag);
}
complex complex::operator-(const complex& c)
{
return complex(real - c.real, imag - c.imag);//生成一个临时的complex对象作为return语句的返回值
}
int main()
{
complex a(4,4), b(1,1), c;
c = a + b;//等价于 c=operator+(a,b);
cout << c.real << "," << c.imag << endl;
cout << (a - b).real << "," << (a - b).imag << endl;//等价于 a.operator-(b);
system("pause");
return 0;
}
5.赋值运算符重载
class Date
{
public :
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date (const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
}
private:
int _year ;
int _month ;
int _day ;
};
特征 :
- 参数类型(自定义类型或者自定义类型的引用)
- 返回值(*this, 返回本对象的引用)
- 检测是否自己给自己赋值
- 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。(浅拷贝)
6.const类型
6.1const成员修饰类的成员函数
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
#include<iostream>
using namespace std;
class Date
{
public:
void display()const
{
cout << _year << endl;
cout << _month << endl;
cout << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
编译器的实际处理:
#include<iostream>
using namespace std;
class Date
{
public:
//const修饰类成员函数,**实际修饰该成员函数隐含的this指针
void display(const Date* this)
{
cout <<this-> _year << endl;
cout <<this-> _month << endl;
cout <<this-> _day << endl;
}
private:
int _year;
int _month;
int _day;
};
非const对象可以调用const成员函数,非const成员函数内可以调用其它的const成员函数。 反之则不可以。
#include<iostream>
using namespace std;
class Date {
int m_year;
size_t m_month;
size_t m_day;
public:
Date(int y, size_t m, size_t d) {
m_year = y;
m_month = m;
m_day = d;
}
void PrintDate();//非const成员函数
void PrintDate()const;//const成员函数
};
void Date::PrintDate() {
cout << "调用PrintDate()\n";
cout << m_year << "年" << m_month << "月" << m_day << "日\n";
}
void Date::PrintDate() const{
cout << "调用PrintDate()const\n";
cout << m_year << "年" << m_month << "月" << m_day << "日\n";
}
int main() {
Date a(2019, 10, 1);
const Date b(2019, 10, 2);
a.PrintDate();
b.PrintDate();
system("pause");
return 0;
}
- const对象可以调用非const成员函数吗?
不可以, 因为const 类型对象的指针为 const类类型* 强转不成非const类型的的 类类型*
- 非const对象可以调用const成员函数吗?
可以, 因为非const 类型对象的指针为 类类型* 可以转换成 const 类类型*
- const成员函数内可以调用其它的非const成员函数吗?
不可以, 同样,因为const 类型对象的指针为 const类类型* 强转不成非const类型的的 类类型*
- 非const成员函数内可以调用其它的const成员函数吗?
可以, 因为非const 类型对象的指针为 类类型* 可以转换成 const 类类型*
6.2取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成
#include<iostream>
using namespace std;
class Date {
int m_year;
size_t m_month;
size_t m_day;
public:
Date() {
}
Date* operator& () {
cout << "Date* operator& ()被调用\n";
return this;
}
const Date* operator& () const {
cout << "const Date* operator& () const 被调用\n";
return this;
}
};
int main() {
Date a;
&a;
const Date b;
&b;
system("pause");
return 0;
}