类的6个默认成员函数(我们不写,编译器自动生成):构造函数,析构函数,拷贝构造,赋值重载,普通对象和const对象取地址。
一、构造函数主要完成初始化工作,是特殊的成员函数
特点:
1、函数名和类名相同
2、无返回值(不需要写void)
3、对象实例化时编译器自动调用对应的构造函数。
4、构造函数可以重载
5、 如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义,编译器将不再生成
6、无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数,都可以认为是默认构造函数。
二、析构函数主要完成清理工作,是特殊的成员函数
特点
1、析构函数名是在类名前加上字符~
2、无参数返回值类型
3、一个类只能有一个析构函数。若未显示定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。
4、对象生命周期结束时自动调用
三、内置类型与自定义类型
· 内置类型/基本类型、语言本身定义的基础类型int/char/double/指针等等
· 自定义、用struct/class等等定义的类型
1、再谈构造函数
我们不写,编译器默认生成的构造函数,内置类型不做处理(有些编译器也会处理),自定义类型会去调用他的默认构造。
#include<stdlib.h>
#include<iostream>
using namespace std;
class Stack
{
public:
//构造函数
Stack(int capacity = 4)
{
cout << " Stack(int capacity = 4) " << endl;
_array = (int*)malloc(sizeof(int) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败");
return;
}
_capacity = capacity;
_size = 0;
}
private:
int* _array;
int _capacity;
int _size;
};
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day; //C++规定内置类型不做处理(有些编译器可能处理,但那是个性化行为)
Stack _st;//自定义类型调用它的构造函数
};
int main()
{
Date s1;
s1.Print();
}
· 一般条件下,有内置类型成员,就需要自己写构造函数,不能用编译器自己生成的。
· 可以考虑让编译器自己生成的情况:
a.内置类型成员都有缺省值且初始化,符合我们的要求
b.全是自定义类型的构造,且这些类型都定义默认构造。
(C++11标准发布的时候打了个补丁,在成员声明的时候可以给缺省值)
#include<iostream>
using namespace std;
class Date
{
public:
/*Date()
{
int _year = 2023;
int _month = 7;
int _day = 21;
}*/
Date(int year=2023, int month=7, int day=21)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 2023;
int _month = 7;
int _day = 21;
};
//缺省参数
//没有传参时,使用参数的默认值传参时,使用指定的实参
//传参是从左向右依次,缺省值是从右向左依次,栈帧调用压栈顺序由编译器决定(不能跳着传/指定传参)
//声明和定义不能同时给缺省值
//声明给缺省值,定义不给缺省值
int main()
{
Date d1;
d1.Print();
//Date d1();//不可以这样写,会和函数声明冲突,编译器不好识别
Date d2(2022,8,22);
d2.Print();
}
· 构造函数的调用跟普通函数也不一样:对象(参数),不可以空传,会跟函数声明有冲突,编译器不好识别
· 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个,所以无参构造函数、全缺省构造函数,我们没写编译器默认生成的构造函数都可以认为是默认构造函数,不传参就可以调用的,就是默认构造函数
· 无参和全缺省构成函数重载,但会产生调用歧义
2、初始化列表
创建对象调用构造函数虽然能给函数体赋初值,但算不上对其进行初始化。若要对其初始化,则需要使用初始化列表。
初始化列表:对象成员定义的位置(一个冒号开始接着是一个逗号分隔的成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式数据)
//构造函数体赋值
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
//初始化列表
class Date
{
public:
Date(int year,int month,int day)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
注意:
1.每个成员变量在初始化列表中最多只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
☐ 引用成员变量
☐ const成员变量
☐ 自定义类型成员(在该类没有默认构造函数时)
特征:必须在定义的时候初始化
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后顺序无关
(声明顺序和定义顺序尽量保持一致)
class A
{
public:
A(int a)//没有默认构造,只有带参构造
:_a(a)
{
cout << "A(int a)" << endl;
}
private:
int _a;
};
class B
{
public:
B(int a, int& ref)
:_ref(ref)
, _n(0)
, _x(0)
,aobj(a)//显式初始化
{
//_n = 0;
//_ref = ref;
}
private:
int& _ref;//引用
const int _n = 1;//const
int _x = 1;//缺省值给初始化列表
A _aobj;//若没有默认构造函数
};
3、再谈析构函数
内置类型成员不做处理。自定义类型会去调用它的析构函数
· 一般情况下,有动态申请资源,就需要显示析构函数释放资源
· 没有动态申请的资源,不需要写析构,需要释放资源的成员,不需要写析构
· 需要释放资源的成员都是自定义类型,不需要写析构
四、拷贝构造函数
拷贝构造函数是构造函数的一个重载形式
拷贝构造函数的参数只有一个,且必须是类类型对象的引用,使用传值方式,编译器直接报错,因为会引发无穷递归调用
无穷递归原因:C++规定:内置类型直接拷贝,自定义类型必须调用拷贝构造(建议加const引用)。可以用指针和引用解决问题
如下列代码:d1是d的别名,d传给了this
#include<iostream>
using namespace std;
class Date
{
public:
//构造函数
Date(int year = 2023, int month = 7, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year = 2023;
int _month = 7;
int _day = 21;
};
int main()
{
Date d1(2022, 8, 22);
d1.Print();
Date d2(d1);
d2.Print();
}
内置类型成员完成值拷贝浅拷贝
自定义类型成员会调用它的拷贝构造
Date默认生成的拷贝构造可以用
但是stack默认生成的拷贝构造不可以用,因为同一块空间析构了两次(一个修改会影响另一个)(后定义的先析构),所以必须自己实现深拷贝(后续章节会详细解析深拷贝相关内容)
内置类型可以直接比较自定义类型,不可以直接比较(运算符重载相关内容将在下一节详细论述)
以下代码作为对前几章节的应用,一并附上:
#include<iostream>
using namespace std;
typedef int Datatype;
class Stack
{
public:
//手动初始化
Stack(Datatype* a, int n)
{
cout << "Stack(Datatype * a, int n)" << endl;
_array = (Datatype*)malloc(sizeof(Datatype) * n);
if (NULL == _array)
{
perror("malloc申请空间失败");
return;
}
memcpy(_array, a, sizeof(Datatype) * n);
_capacity = n;
_size = 0;
}
//构造函数
Stack(int capacity = 4)
{
cout << " Stack(int capacity = 4) " << endl;
_array = (Datatype*)malloc(sizeof(Datatype) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败");
return;
}
_capacity = capacity;
_size = 0;
}
//析构函数
~Stack()
{
cout << " ~Stack() " << endl;
free(_array);
_array = nullptr;
_capacity = _size = 0;
}
//尾插
void Push(Datatype data)
{
checkcapacity();
_array[_size] = data;
_size++;
}
//尾删
void Pop()
{
if (Empty())
{
return;
}
_size--;
}
Datatype Top() { return _array[_size - 1]; }//取栈顶元素
int Empty() { return 0 == _size; }//判空
int Size() { return _size; }//栈大小
private:
//检查容量
void checkcapacity()
{
if (_size == _capacity)
{
int newcapacity = _capacity * 2;
Datatype* temp = (Datatype*)realloc(_array, newcapacity * sizeof(Datatype));
if (NULL == _array)
{
perror("realloc申请空间失败");
return;
}
_array = temp;
_capacity = newcapacity;
}
}
private:
Datatype* _array;
int _capacity;
int _size;
};
int main()
{
Stack s1;
}