空类:一个类中什么成员都没有
空类并不是什么都没有,任何类在什么都不写时,编译器会自动生成6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数。
图片来自博主:柠檬叶子C
Date类可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
构造函数是一个特殊的成员函数,名字与类名相同,创建类 类型 对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
下面这个代码:自己编写初始化函数
class Date
{
public:
void Init(int year, int month, int day)
{
year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Init(2022, 7, 5);
d1.Print();
Date d2;
d2.Init(2022, 7, 6);
d2.Print();
return 0;
}
构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任
务并不是开空间创建对象,而是初始化对象。
其特征如下:
不给参数调用无参构造函数 给参数调用带参构造函数
class Date
{
public:
// 1.无参构造函数
Date()
{
_year = 0;
_month = 1;
_day = 1;
}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
printf("%d-%d-%d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
};
int main()
{
通过无参构造函数创建对象时,对象后面不用跟括号,
否则就成了函数声明
Date d1; // 调用无参构造函数
d1.Print(); 0-1-1
Date d2(2023, 5, 4); // 调用带参构造函数
d2.Print(); 2023-5-4
// 声明了d3无参函数,返回一个日期类型的对象
Date d3();//warning C4930: “Date d3(void)”:
未调用原型函数
}
class Date
{
public:
/*
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
*/
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;这里没有传参数 所以要调用一个无参构造函数
将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
return 0;
}
1.观察下述代码 思考输出结果
class Time
{
public:
Time()//无参构造函数
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year;
int _month;
int _day;
// 自定义类型
Time _t;
};
int main()
{
Date d;//输出Time()
return 0;
2.对自定义类型处理,会调用默认构造函数(不用参数就可以调的函数)
#include
using namespace std;
class A
{
public:
A() //无参构造函数
{
cout << " A() " << endl;
_a = 0;
}
private:
int _a;
};
class Date
{
private:
int _year;
int _month;
int _day;
A _aa;
};
int main(void)
{
Date d1;
//输出A()
return 0;
}
}
3.故意写个带参的默认构造函数,让编译器不默认生成
#include
using namespace std;
class A
{
public:
A(int a)
{
cout << " A() " << endl;
_a = 0;
}
private:
int _a;
};
class Date
{
private:
int _year;
int _month;
int _day;
A _aa;
};
int main(void)
{
Date d1;
无法引用"Date"的默认构造函数--它是已删除的函数
return 0;
}
4.不写,让编译器默认生成
#include
using namespace std;
class A
{
private:
int _a;
};
class Date
{
private:
int _year;
int _month;
int _day;
A _aa;
};
int main(void)
{
Date d1;
Date定义了一个类d1 此时编译器自动调用Date的构造函数去初始化他的成员
但是_year\_month\_day都是内置类型 编译器对他们不做处理
_aa是自定义类型所以会对它进行初始化 此时就去调用类A的构造函数
而用户并没有自己编写 所以编译器会默认生成一个构造函数对其成员进行初始化
同样的 _a是内置类型 编译器对其不做处理
但是类A中没有自定义类型 所以有一下调试结果
return 0;
}
C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值(注意:此处是声明 给了一个缺省值 并非是初始化)。
class Time
{
public:
Time()
{
cout << "Time()" << endl;
_hour = 0;
_minute = 0;
_second = 0;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
Time _t;// 自定义类型
};
int main()
{
Date d;
return 0;
}
调试结果
运行结果:Time()
由此可以看到 因为这个补丁 此时想要初始化的已经初始化了
综上所述,我们可以这么去认识构造函数:
class A
{
public:
A()
{
cout << "A()" << endl;
X = 0;
Y = 0;
Z = 0;
}
private:
int X;
int Y;
int Z;
};
class B
{
private:
int x = 1;
int y = 1;
int z = 1;
A _a;
};
int main()
{
B b;
return 0;
}
调试结果
运行结果
理解代码
析构函数:与构造函数功能相反,析构函数不是完成对象本身的销毁,局部对象销毁工作是由编译器完成的。对象在销毁时会自动调用析构函数,完成对象中资源的清理作。
析构函数是特殊的成员函数。
class Time
{
public:
~Time()
{
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d;
return 0;
}
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象 创建新对象时 由编译器自动调用。
拷贝构造函数也是特殊的成员函数,其特征如下:
特性2代码演示
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d) // 正确写法
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
class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
_second = 1;
}
Time(const Time& t)
{
_hour = t._hour;
_minute = t._minute;
_second = t._second;
cout << "Time::Time(const Time&)" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
1.用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
2.Date类没有显式定义拷贝构造函数,编译器会给Date类生成一个默认的拷贝构造函数
特性4
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申请空间失败");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType *_array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2(s1);
return 0;
}
特性5
class Date
{
public:
Date(int year, int minute, int day)
{
cout << "Date(int,int,int):" << this << endl;
}
Date(const Date& d)
{
cout << "Date(const Date& d):" << this << endl;
}
~Date()
{
cout << "~Date():" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
Date Test(Date d)
{
Date temp(d);
return temp;
}
int main()
{
Date d1(2022,1,13);
Test(d1);
return 0;
}
vs2022运行结果把Test函数中temp返回时使用的拷贝构造函数(这个函数是用来构造临时变量的)以及销毁这个临时变量的析构函数给优化了 所以运行结果只有6行代码
#include
using namespace std;
class A
{
public:
A(int a = 0)
{
_a = a;
cout << "A(int a = 0)->" << _a << endl;
}
~A()
{
cout << "~A()->" << _a << endl;
}
private:
int _a;
};
int main()
{
A aa1(1);
A aa2(2);
return 0;
}
先调用的后析构
后调用的先析构
#include
using namespace std;
class A
{
public:
A(int a = 0)
{
_a = a;
cout << "A(int a = 0)->" << _a << endl;
}
~A()
{
cout << "~A()->" << _a << endl;
}
private:
int _a;
};
A aa0(0);
int main()
{
static A aa3(3);
A aa1(1);
A aa2(2);
static A aa4(4);
return 0;
}
初始化顺序(即构造函数的调用顺序):
全局变量aa0(0)
局部变量aa3(3) aa1(1) aa2(2) aa4(4)
析构顺序:
1.栈帧先销毁
2.main函数结束后
3.静态局部变量随之
4.全局变量最后
局部变量aa2(2) aa1(1)
静态局部变量aa4(4) aa3(3)
全局变量aa0(0)
#include
using namespace std;
class A
{
public:
A(int a = 0)
{
_a = a;
cout << "A(int a = 0)->" << _a << endl;
}
~A()
{
cout << "~A()->" << _a << endl;
}
private:
int _a;
};
A aa0(0);
void f()
{
static A aa3(3);
A aa1(1);
A aa2(2);
static A aa4(4);
}
int main()
{
f();
f();
return 0;
}
1.程序执行—aa0创建(调用构造)–main函数建立栈帧–调用f()–f函数建立栈帧
2.aa3、aa1、aa2、aa4创建–f函数调用结束–f函数栈帧销毁
3.aa2析构–aa1析构
4.二次调用f()函数–f函数建立栈帧aa1、aa2创建–f函数调用结束–f函数栈帧销毁
5.main函数调用结束–main函数栈帧销毁
6. 局部静态变量aa4、aa3析构–全局变量aa0析构–静态变量、全局变量销毁–程序结束
7.如果再定义一个全局静态变量二者谁在程序前谁先构造,销毁相反。
#include
using namespace std;
class A
{
public:
A(int a = 0)
{
_a = a;
cout << "A(int a = 0)->" << _a << endl;
}
A(const A& aa)
{
_a = aa._a;
cout << "A(const A& aa)->" << _a << endl;
}
~A()
{
cout << "~A()->" << _a << endl;
}
private:
int _a;
};
void func1(A aa)
{
}
void func2(A& aa)
{
}
int main()
{
A aa1(1);
func1(aa1);
func2(aa1);
return 0;
}
func2函数调不调用运行结果一样
说明传引用时没有调用拷贝构造函数
这里不明白的再看一下拷贝构造函数的讲解
#include
using namespace std;
class A
{
public:
A(int a = 0)
{
_a = a;
cout << "A(int a = 0)->" << _a << endl;
}
A(const A& aa)
{
_a = aa._a;
cout << "A(const A& aa)->" << _a << endl;
}
~A()
{
cout << "~A()->" << _a << endl;
}
private:
int _a;
};
A func3()
{
static A aa;
return aa;
}
A& func4()
{
static A aa;
return aa;
}
int main()
{
func3();
cout << endl << endl;
func4();
return 0;
}
稍微修改一下函数
修改函数后的运行结果
有的同学蒙圈了,到底是怎么回事呢?好烦…呜呜呜…
一起来看看吧!!!
只调用func3:
进入func3函数—aa创建—调用构造函数—返回aa是值返回----要创建一个临时变量aa‘—调用拷贝构造函数创建aa’—func3函数调用结束—调用析构函数清理临时变量aa’–main函数调用结束—调用析构函数清理静态变量aa
进入func4函数—aa创建—调用构造函数—返回的是引用—不调用拷贝构造函数—main函数结束—调用析构函数清理静态变量aa
此时,func3和func4函数的调用就不难理解了吧!嘿嘿…
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数。
1.具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名:关键字operator 运算符符号
函数原型:返回值类型 operator操作符(参数列表)
2.不能通过连接其他符号来创建新的操作符:operator@
3.重载操作符必须有一个类类型参数
4.用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
5.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
6. .* :: sizeof ?: .
注意以上5个运算符不能重载。
判断相等
#include
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//bool operator==(Date x1, Date x2)
//bool operator==(const Date& x1, const Date& x2)
bool operator==(const Date& x)
{
return _year == x._year
&& _month == x._month
&& _day == x._day;
}
//通过在类外调用此函数可以访问私有成员
//int GetYear()
//{
// return _year;
//}
private:
int _year;
int _month;
int _day;
};
//内置类型直接使用运算符运算 编译器知道如何运算
//自定义类型无法直接使用运算法 编译器不知道如何运算
//需要自己实现运算符重载
int main()
{
Date d1(2023, 5, 30);
Date d2(2023, 2, 23);
cout << d1.operator==(d2) << endl; // <==> d1.operator==(&d1, d2)
cout << (d1 == d2) << endl;
return 0;
}
+= 和 +
class Date
{
public:
//判断闰年
int IsLeapYear(int year)
{
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
return 1;
}
//获得每月天数
int GetMonthDay(int year, int month)
{
static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (month == 2 && (IsLeapYear(year)))
return 29;
else
return days[month];
}
//构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//==重载
bool operator==(const Date& x)
{
return _year == x._year
&& _month == x._month
&& _day == x._day;
}
//+=重载
Date& operator+=(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
_month = 1;
_year++;
}
}
return *this;
}
// +重载
Date operator+(int day)
{
Date ret(*this);//用d1拷贝构造ret
ret._day += day;
while (ret._day > GetMonthDay(ret._year, ret._month))
{
ret._day -= GetMonthDay(ret._year, ret._month);
++ret._month;
if (ret._month == 13)
{
ret._month = 1;
ret._year++;
}
}
return ret;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022, 7, 23);
Date d2(2022, 7, 24);
//==
d1 == d2;
//+=
d1 += 50;
// +
Date ret = d1 + 50;//Date ret(d1 + 50); ==>拷贝构造
return 0;
}
/*bool operator<(const Date& x);
bool operator>(const Date& x);
bool operator>=(const Date& x);
bool operator<=(const Date& x);
bool operator!=(const Date& x);*/
观察参数类型、返回值类型
Date.h
#pragma once
#include
using namespace std;
class Date
{
public:
//构造函数频繁调用 放在类里作为inline
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month =d._month;
_day = d._day;
}
//连续赋值 i = j = k
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;
};
Test.cpp
#define _CRT_SECURE_NO_WARNINGS
#include"Date.h"
void TestDate1()
{
Date d1(2022, 7, 24);
Date d2(d1);
Date d3(2022, 8, 24);
d2 = d1 = d3;//==>d.operator=(&d1,d3);
d2 = d2;
}
int main()
{
TestDate1();
return 0;
}
赋值运算符只能重载成类的成员函数不能重载成全局函数
用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实现吗?
以下代码将崩溃
对与下面这个类,默认生成的函数就十分方便
点击 Cpp日期类最新版 即可跳转码云获取代码。(有兴趣的小伙伴也可以关注一下俺的码云呦~
+运算里有拷贝构造,+=去复用+时拷贝构造次数变多,性能下降。相比之下,+=里面没有拷贝构造,+去复用+=时,没有多余的拷贝构造,性能提升。
了解了这一部分,可以进一步修改日期类的函数,当然前提是可以加。
将const修饰的“成员函数”称之为const成员函数
const修饰类成员函数,实际修饰该成员函数
隐含的this指针
表明在该成员函数中不能对类的任何成员进行修改。
#define _CRT_SECURE_NO_WARNINGS
#include
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << "Print()" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
void Print() const
{
cout << "Print()const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
void Test()
{
Date d1(2022, 1, 13);
d1.Print();
const Date d2(2022, 1, 13);
d2.Print();
}
int main()
{
Test();
return 0;
}
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如 return nullptr;
不让别人获取你哒地址。
class Date
{
public :
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Date* operator&()
{
return this ;
}
const Date* operator&() const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
int main(void)
{
Date d1(2022, 2, 2);
cout << &d1 << endl; // 取出d1的地址
const Date d2(2022, 1, 1);
cout << &d2 << endl; // 取出d2的地址
return 0;
}