一、构造函数
1.1概念
构造函数是一个特殊的成员函数,名字与类相同,创建类类型对象时由编译器自动调用,保证每个数据成员都由一个合适的初始值。在对象的生命周期内只调用一次。
不使用构造函数
#include<iostream>
using namespace std;
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);//每次都要调用Init函数
d1.Print();
Date d2;
d2.Init(2022, 7, 6);
d2.Print();
return 0;
}
使用构造函数
#include<iostream>
using namespace std;
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(2022, 7, 5);//不需要调用函数直接可以初始化
d1.Print();
Date d2(2022,7,6);
d2.Print();
return 0;
}
上面两幅图在main函数中,图1未使用构造函数,每次定义时都要调用Init函数初始化。
图2 使用了构造函数,直接定义就可以,编译器会自动调用构造函数初始化,方便了用户,也防止了用户忘记调用Init函数的情况。
1.2特性
1.构造函数是特殊的成员函数,虽然名字叫构造,但作用其实是初始化对象。
2.构造函数的函数名与类的名字相同。
3.在对象实例化时会自动调用对应的构造函数。
4.构造函数没有返回值。
5.构造函数可以重载。
class Date
{
public:
// 1.无参构造函数
Date()
{}
// 2.带参构造函数
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void TestDate()
{
Date d1; // 调用无参构造函数
Date d2(2015, 1, 1); // 调用带参的构造函数
// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
Date d3();
}
所以Date d3()是错误的用法。
6.如果用户没有定义构造函数,编译器会自动生成一个无参的默认构造函数。如果用户定义了构造函数就不会生成构造函数。
class Date
{
public:
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; // 自动生成无参的构造函数
return 0;
}
7.如果编译器自动生成构造函数,对象的初始化由两种情况分为内置类型(基本类型)和自定义类型。对内置类型不做处理,初始化成随机值。对自定义类型会调用其的默认成员函数
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的默认成员函数
Time _t;
};
int main()
{
Date d;
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;
}
8.无参的构造函数和全缺省的构造函数都称为默认构造函数,且默认构造函数只能存在一个。
class Date
{
public:
Date()//无参的构造函数
{
_year = 1900;
_month = 1;
_day = 1;
}
Date(int year = 1900, int month = 1, int day = 1)//全缺省的函数构造
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
// 以下测试函数能通过编译吗?
void Test()
{
Date d1;//编译报错
}
二、析构函数
2.1概念
在对象销毁时自动调用析构函数完成自身的销毁。析构函数的功能与构造函数的相反,但析构函数不是完成对对象本身的销毁,局部对象的销毁工作是由编译器完成的。
2.2特性
1.析构函数是特殊的成员函数,析构函数是在类名前加上字符~。
2.析构函数无返回值和类型。
3.一个类只能有一个析构函数,若未定义,编译器会默认生成析构函数。
4.析构函数不能重载。
5.对象的生命周期结束时,C++编译系统会自动调用析构函数。
typedef int DataType;
class Stack
{
public:
Stack(DataType capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 其他方法...
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main()
{
Stack s;//自动调用Stack构造
s.Push(1);
s.Push(2);//自动调用~Stack销毁
return 0;
}
6.析构函数对于内置类型成员不做资源清理,系统最后会将其内存回收。对于自定义类型会调用
其自身的析构函数。
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;
}
// 程序运行结束后输出:~Time()
// 在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
// 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month,
// _day三个是
// 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;
// 而_t是Time类对
//象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类
// 的析构函数。
// 但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,
// 所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date
// 类生成一个默认的析构函数,
// 目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个
// 自定义对象都可以正确销毁
// main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的
// 默认析构函数
// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的
// 析构函数
7.如果类中没有资源申请(如Date),则可以不写析构函数;如果有资源申请(如Stack),则必须写析构函数。