目录
假如定义一个类
class Date
{
};
如果这个类中什么成员(成员变量/成员函数)都没有,就称为空类。空类中并不是什么都没有,任何一个类在不写的情况下,都会自动生成上面6个默认成员函数。
1、构造函数
1.1 概念
构造函数:在对象构造时调用的函数,完成初始化工作。构造函数是一个特殊的成员函数,名字与类名相同,对象实例化时自动调用对应的构造函数,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。构造函数是一种特殊的成员函数,其主要任务并不是开空间创建对象,而是初始化对象。
1.2 特性
1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
#include<iostream>
using namespace std;
class Date
{
public:
//这个就是自己写的无参的构造函数
Date()
{
_year = 0;
_month =1;
_day = 1;
}
//带参数的构造函数
Date(int year, int month, int day )
{
_year = year;
_month = month;
_day = day;
cout << "Date()" << this << endl;
}
void print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
//成员变量都要封装成私有
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//会调用无参的那个构造函数
Date d3(2020, 4, 5);//会调用带参数的那个构造函数
d1.print();
d2.print();
system("pause");
return 0;
}
5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户自己显式定义,编译器便不再生成。
编译器自动生成的默认构造函数:1、对内置类型(基本类型),即语法已经定义好的类型如:int /char …不调用 2、对自定义类型(我们使用class/struct/union自己定义的类型)调用其构造函数进行初始化,编译器生成默认的构造函数会对自定类型成员调用的它的默认成员函数。
class Time
{
public:
Time()
{
m_hour = 0;
m_minute = 0;
m_second = 0;
cout << "Time()" << endl;
}
private:
int m_hour;
int m_minute;
int m_second;
};
class date
{
public:
void printf()
{
cout << m_year << "-" << m_month << "-" << m_day << endl;
}
private:
// 基本类型(内置类型)
int m_year;
int m_month;
int m_day;
//自定义类型
Time _t;//会调用他的构造函数
};
int main()
{
date d;//调用默认的无参构造函数
d.printf();
system("pause");
return 0;
}
其结果如下:
6.自己写的无参的构造函数和自己写的全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参的构造函数、全缺省的构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数(不用传参)。
2、析构函数
2.1 概念
析构函数:与构造函数功能相反,析构函数对象生命周期到了就会自动调用.完成对象里面的资源清理工作。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。析构函数体内不是删除对象,而是仅仅做一些对象删除前的相关清理工作。
2.2 特性
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值。
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
class Stack
{
public:
Stack(int n = 10)
{
_a = (int*)malloc(sizeof(int)*n);
_size = 0;
_capacity = n;
cout << "Stack()" << endl;
}
~Stack()
{
free(_a);
_a = nullptr;
_size = _capacity = 0;
cout << "~Stack()" << endl;
}
private:
int* _a;
size_t _size;
size_t _capacity;
};
int main()
{
Stack st;;//析构函数完成资源清理的工作
system("pause");
return 0;
}
5.编译器自动生成的析构函数,对会自定类型成员调用它的析构函数,对内置类型(基本类型)不会处理。和不显式定义时编译器自动生成的默认构造函数一样。
class Time
{
public:
Time()
{
m_hour = 0;
m_minute = 0;
m_second = 0;
cout << "Time()" << endl;
}
~Time()
{
cout << "~Time()" << endl;
}
private:
int m_hour;
int m_minute;
int m_second;
};
class date
{
public:
date(int year = 0, int month = 1, int day = 1)
{
m_year = year;
m_month = month;
m_day = day;
cout << "date()" << this << endl;
}
private:
//内置类型
int m_year;
int m_month;
int m_day;
//自定义类型
Time _t;
};
int main()
{
date d;
system("pause");
return 0;
}
6.析构和构造的顺序不同,先构造的对象后析构,后构造的对象先析构。
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
cout << "Date()" << this << endl;
}
~Date()
{
cout << "~Date()" << this << endl;
}
private:
//成员变量都要封装成私有
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2;
system("pause");
return 0;
}
因为当程序运行到system(“pause”); 时,会输出“请按任意键继续。。。”此时,主函数还未执行完,d1、d2为局部变量,只有等到函数体退出时,才会销毁,可是如果按下任意键进,函数体执行完毕,会输出“this指针的内容”,但由于输出窗口随后就关闭了,所以无法看到调用的析构函数。解决办法:解决办法:1.去system(“pause”);按F10进行调试 2.按图这样写也可以。
3、拷贝构造函数
3.1 概念
拷贝构造函数是构造函数的一个重载形式,也是特殊的成员函数【参数只有一个且必须使用引用传参】,该形参是对本类类型对象的引用(一般常用const修饰),拷贝构造其实就是使用同类型对象初始化创建新对象,由编译器自动调用
3.2 特性
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
cout << "Date()" << endl;
}
//如果是Date(Date d)会存在一个递归拷贝的问题-会无限递归
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
cout << "Date(const)" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2020, 4, 11);
Date d2(d1);//拷贝构造
Date d3 = d1;//这种写法也是拷贝构造
system("pause");
return 0;
}
特别注意:拷贝构造函数要传一个对象的引用而不是传值!传值会构成无穷递归的问题。
3.若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝或者值拷贝。
注意:像日期这种不写拷贝构造函数,编译器会按照浅拷贝的方式来实现,浅拷贝是没有什么问题的。
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
cout << "Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
// 这里d2调用的默认拷贝构造完成拷贝,d2和d1的值也是一样的。
Date d2(d1);
system("pause");
return 0;
}
假如是如下代码,不写拷贝构造函数,这时编译器生成的浅拷贝就会出现问题。浅拷贝会导致拷贝者和被拷贝者是同一个地址,指向同一块空间。
class Stack
{
public:
Stack(int n = 10)
{
_a = (int*)malloc(sizeof(int)*n);
_size = 0;
_capacity = n;
cout << "Stack()" << endl;
}
~Stack()
{
free(_a);
_a = nullptr;
_size = _capacity = 0;
cout << "~Stack()" << endl;
}
private:
int* _a;
size_t _size;
size_t _capacity;
};
int main()
{
Stack d1;
Stack d2(d1);//会出现错误 -》同一块空间被释放了两次
system("pause");
return 0;
}
当类中无需申请动态资源时,浅拷贝构造函数可以很好的工作。当需要申请动态内存时,即类中有指针变量,使用编译器默认生成的拷贝构造函数会出现问题。
4、赋值操作符重载
4.1 运算符重载概念
对于自定义类型来说不能使用运算符,要用就得实现重载函数。C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
4.1.1 特点
1.函数名字:关键字operator后面接需要重载的运算符符号。
2.函数原型:返回值类型 operator操作符(参数列表)
注意:
1)不能通过连接其他符号来创建新的操作符:比如operator@
2)重载运算符必须有一个是类类型或者是枚举类型的操作数。比如不能重载两个整形的加法运算符。
3)用于内置类型【基本类型】的操作符,其含义不能改变。
4)作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的
操作符有一个默认的形参this,限定为第一个形参。
5).* 、:: 、sizeof 、?: 、. 注意这5个运算符不能重载。
class Date
{
public:
//构造函数
Date(int year = 0, 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;
}
//重载==
//d1 ==d2 -> d1.operator(&d1,d2);这里是this指针 &d1取地址
bool operator ==(const Date& d)//bool operator(Date* this, Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
//重载>
bool operator >(const Date& d)
{
if (_year > d._year)
return true;
else if (_year == d._year && _month > d._month)
{
return true;
}
else if (_year == d._year && _month == d._month && _day > d._day)
{
return true;
}
return false;
}
void printf()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2020, 4, 11);
Date d2(d1);//拷贝构造
Date d3 = d1;//这种写法也是拷贝构造
cout << (d1 == d3) << endl;
Date d4(2020, 4, 1);
cout << (d1 > d4) << endl;
cout << (d4 > d2) << endl;
system("pause");
return 0;
}
输出结果如下:
4.2 赋值重载概念
对于类类型的对象我们需要对‘=’重载,以完成类类型对象之间的赋值。需要注意的是赋值重载是对一个已存在的对象进行拷贝赋值。 而拷贝构造是创建一个对象时自动调用进行初始化的工作。
4.2.1 特点
1. 参数类型
2. 返回值:返回*this
3. 需要检测是否自己给自己赋值
class Date
{
public:
//构造函数
Date(int year = 0,int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//赋值操作符的重载 d2 = d1
Date operator=(const Date& d)
{
if (this != &d) //避免自己给自己赋值
{
return _year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
void printf()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2020,1,1);
d1.printf();
Date d2;
d2 = d1;
d2.printf();
return 0;
}
其输出结构如下:
注意:赋值运算符的重载,必须要有返回值,并且返回值是*this。因为:对于内置类型int a,int b,int c,是支持连续等a = b = c的,这块拆分成两个,首先将c赋值给b,然后是存在一个返回值的,再将这个返回值赋值给c。同理,对于类类型来说,也是一样的情况。
4. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝,也叫做浅拷贝。浅拷贝的问题在上面解释过,因此如果一个类中涉及到资源(比如:动态内存开辟空间),一定要自己给出析构函数、拷贝构造函数、赋值运算符函数。
5、const成员函数
5.1 概念
c++中如果一个const修饰一个变量的话,这个变量就成为了一个常量,具有宏的属性,在编译期间会将const所修饰的常量进行替换。如果是const修饰的类成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。const修饰的成员函数里面只能读取数据成员,不能改变数据成员;没有 const 修饰的成员函数,对数据成员则是可读可写的。
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void printf()const //因为this指针是隐含的 ->void printf(const Date* this)
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
const修饰的是*this,所以this指针指向的内容不能改变。
注意:
1)const对象可以调用非const成员函数吗?
不可以。const修饰的对象是不可以修改对象的内容,如果调用非const成员函数,是可能会修改对象内容的,这是权限放大。
2)非const对象可以调用const成员函数吗?
可以。非const修饰的对象是可读可写的。const修饰的成员函数只能读,而不能修改,这是权限缩小。
3)const成员函数内可以调用其它非const的成员函数吗?
不可以。因为this指针的类型不同,这个函数的this指针的类型是
const Date * this ,而非const修饰的成员函数的 this指针为Date* this ,类型不同所以不能调用。
4)非const成员函数内可以调用其它的const成员函数吗?
可以。当前函数是可读可写的,调用const修饰的成员函数是只能读的,这是权限缩小。
//成员函数调用const成员函数
class date
{
public:
date(int year = 0, int month = 1, int day = 1)
{
m_year = year;
m_month = month;
m_day = day;
cout << "date()" << endl;
}
void f1()//void f1(date* this)
{
f2();//可以调用 属于权限缩小
}
void f2()const
{
}
//void f3()
//{
//}
//void f4()const //void f4(const date* this)
//{
// f3();//不可以 ->属于权限放大
//}
private:
int m_year;
int m_month;
int m_day;
};
6、取地址及const修饰的取地址操作符重载
取址操作符重载函数返回值为该类型的指针,无参数。这两个默认的成员函数一般不用重新定义,编译器会重新生成。需要注意的是对象是const的,取地址就是const Date*this。
//对象是const的,取地址就是const Date*this
//取地址的成员函数
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date* operator&()//Date* this
{
cout << "Date* operator&()" << endl;
return this;
}
const Date* operator&()const//const Date* this
{
cout << "const Date* operator&()" << endl;
return this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2;
const Date d3;
cout << &d1 << endl;
cout << &d2 << endl;
cout << &d3 << endl;
system("pause");
return 0;
}