目录
一、类的六个默认成员函数
如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?实际并非如此,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数。
二、构造函数
1、定义
#include <iostream>
using namespace std;
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();
d2.SetDate(2018, 7, 1);
d2.Display();
return 0;
}
对于上述Date类,可以通过setDate共有方法对对象设置内容,但是如果每次创建对象都调用该方法设置信息,可能显得有些麻烦,那么有可能在创建对象时,就将信息设置进去呢?
构造函数就很好的解决了这个问题。
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只能调用一次。
2、特性
构造函数的主要任务不是开辟空间创建对象,而是初始化对象。
其主要特性如下:
1)函数名与类名相同
2)无返回值
3)对象实例化时编译器自动调用对应的构造函数
4)构造函数可以重载
5)如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参数的默认构造函数,一旦用户显式定义编译器将不再自动生成。
#include <iostream>
using namespace std;
class Date
{
public:
//1.无参构造函数
Date(){}
//2.有参构造函数
Date(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;//调用无参构造函数
d1.Display();
Date d2(2018, 5, 1);//调用有参构造
d2.Display();
return 0;
}
【注意】
如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数说明。
Date d3();
//实际是声明了一个d3函数,该函数没有参数,返回一个日期类型的对象。
6)无参构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
【注意】无参构造函数,全缺省构造函数、编译器默认自动生成的构造函数,都可以认为是默认成员函数。
#include <iostream>
using namespace std;
class Date
{
public:
//1.全缺省的构造函数
Date()
{
_year = 1999;
_month = 1;
_day = 13;
}
//2.有参构造函数
Date(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;//调用全缺省的构造函数
d1.Display();
return 0;
}
运行结果:
三、析构函数
1、定义
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁工作,局部对象销毁工作由编译器完成。对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
2、特性
1)析构函数名是在类名前加~。
2)无参数无返回值。
3)一个类中有且只有一个析构函数。若没有显式定义,系统会自动生成默认的析构函数。
4)对象生命周期结束时,C++编译器会自动调用析构函数。
5)编译器自动生成的析构函数,会对自定义类型成员调用它的析构函数。
#include <iostream>
using namespace std;
#pragma warning(disable:4996)
class String
{
public:
String(const char* str = "jack")
{
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String()
{
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
class Person
{
private:
String _name;
int _age;
};
int main()
{
Person p;
return 0;
}
运行结果:
四、拷贝构造函数
1、定义
拷贝构造函数:只有单个形参,该形参为对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
2、特征
1)拷贝构造函数是构造函数的一个重载形式
2)拷贝构造函数参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
#include <iostream>
using namespace std;
#pragma warning(disable:4996)
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)若没有显式定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象内存存储按字节序完成拷贝,这种拷贝称为“浅拷贝”(值拷贝)。
#include <iostream>
using namespace std;
class Date
{
public:
Date()
{
_year = 1999;
_month = 1;
_day = 13;
}
Date(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;
d1.Display();
Date d2(d1);
d2.Display();
return 0;
}
运行结果:
结果分析:
d1通过调用缺省的构造函数,完成初始化,d2通过调用默认拷贝构造函数,进行浅拷贝完成初始化,所以d2和d1的值相同。
五、赋值运算符的重载
1、运算符重载
C++为了增强代码的可读性引入运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回类型,函数名及参数列表,其返回值类型与参数列表与普通函数类似。
函数名:关键字operator后面接需要重载的运算符。
函数原型:返回值类型 operator运算符(参数列表)
【注意】
- 不能通过连接其他符号来创建新的操作符,例如:operator@
- 重载运算符必须有一个类类型或者枚举类型的操作数
- 用于内置类型的操作符,其含义不能变
- 作为类成员的重载函数时,其形参看起来比操作数数目少一个成员函数的操作符有一个默认的形参this,限定为第一个形参
- *、::、sizeof、?:、. 这五个运算符不能重载
#include <iostream>
using namespace std;
#pragma warning(disable:4996)
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// bool operator==(Date* this, const Date& d2)
// 这里需要注意的是,左操作数是this指向的调用函数的对象
bool operator==(const Date& d2)
{
return (_year == d2._year && _month == d2._month && _day == d2._day);
}
private:
int _year;
int _month;
int _day;
};
void Test()
{
Date d1(2018, 9, 27);
Date d2(2018, 9, 27);
cout << (d1 == d2) << endl;
}
int main() {
Test();
}
2、赋值运算符重载
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;
}
return *this;
}
private:
int _year ;
int _month ;
int _day ;
};
赋值运算符重载函数
1)参数类型
2)返回值
3)检测是否自己赋值给自己
4)返回*this
5)一个类如果没有显式定义赋值运算符重载,编译器会自动生成一个完成对象按字节序的拷贝。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2018,10,1);
// 这里d1调用的编译器生成operator=完成拷贝,d2和d1的值也是一样的。
d1 = d2;
return 0;
}
六、取址运算符&重载和const &运算符重载
1、const 函数
在成员函数后面加const,实际const修饰就是this指针所指向的对象,也就是保证调用这个const成员函数的对象在函数内不会被改变
class Test
{
private:
int i;
public:
void setvalue(int num) {
i = num;
}
void display() const
{
i++;//编译失败,因为不能对const成员函数内的对象进行修改
cout << i << endl;
}
};
如果必须要修改类成员变量,可以加上multable关键字
class Test
{
private:
mutable int i;
public:
void setvalue(int num) {
i = num;
}
void display() const
{
i++;//编译通过,i的const属性已经更改
cout << i << endl;
}
};
2、辨析
1)const对象不可以调用非const函数
2)非const对象可以调用非const成员函数和const函数
3)const成员函数不可以调用其他的非const成员函数
4)非const成员函数内可以调用其他的const成员函数和非const成员函数
class Test
{
private:
int i;
public:
Test() = default;
Test(int ii) :i(ii) {}
int getValue()const
{
return i;
}
void print() const
{
//setValue(3);//error,const成员函数不能调用非const成员函数
cout << getValue() << endl;
}
void setValue(int ii)
{
i = ii;
//print();//OK,非const成员函数可以调用const成员函数
}
};
int main()
{
Test t;
t.setValue(1);//非const对象可以调用非const函数
t.print();//非const对象可以调用const函数
const Test t2(2);
//t2.setValue(3); //编译失败,const对象不能调用非const函数
}
3、取址运算符重载和const取址运算符重载
class Test
{
private:
int i;
public:
Test() = default;
Test(int ii) :i(ii) {}
Test* operator&() //&取地址运算符重载
{
return this;
}
const Test* operator&()const //const &取地址运算符重载
{
return this;
}
};
这两个运算符不需要重载,使用编译器生成的默认取址重载函数即可,只有特殊情况,才需要重载,例如:想让别人获取指定的内容。