一. 类的 6 个默认成员函数
默认成员函数:我们不写,编译器会自己生成

C语言用栈时:1. 有时会忘记初始化、销毁(内存泄漏) 2. 有些地方写起来很繁琐
C++进化到可以自动初始化、销毁
以前的 C++ 栈:
cpp
typedef int DataType;
class Stack
{
public:
void Init(int capacity = 4)
{
_a = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
CheckCapacity();
_a[_size] = data;
_size++;
}
void Pop()
{
if (Empty())
return;
_size--;
}
DataType Top() { return _a[_size - 1]; }
int Empty() { return 0 == _size; }
int Size() { return _size; }
void Destroy()
{
if (_a)
{
free(_a);
_a = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
void CheckCapacity()
{
if (_size == _capacity)
{
int newcapacity = _capacity * 2;
DataType* temp = (DataType*)realloc(_a, newcapacity *
sizeof(DataType));
if (temp == nullptr)
{
perror("realloc申请空间失败!!!");
return;
}
_a = temp;
_capacity = newcapacity;
}
}
private:
DataType* _a;
int _capacity;
int _size;
};
不 Init,不 Destroy
cpp
int main()
{
Stack s;
// s.Init();
s.Push(1);
s.Push(2);
s.Push(3);
s.Push(4);
printf("%d\n", s.Top());
printf("%d\n", s.Size());
s.Pop();
s.Pop();
printf("%d\n", s.Top());
printf("%d\n", s.Size());
// s.Destroy();
return 0;
}

报错,程序异常退出
二. 构造函数
构造函数是特殊的成员函数。不是开空间创建对象,而是初始化对象,在对象整个生命周期内只调用一次
对象不需要某个函数创建。因为对象在栈里面,栈里面的变量是自动创建的(跟着栈帧走的)。函数调用,给局部变量开空间;函数结束,变量随栈帧销毁,空间也就销毁了
特征1. 函数名与类名相同特征2. 无返回值,也不写 void
特征3.对象实例化时 编译器 自动调用对应的构造函数
现在的 C++ 栈:
cpp
class Stack
{
public:
Stack(int capacity = 4) // 构造函数,功能:替代Init
{
cout << "Stack(int capacity = 4)" << endl;
_a = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
/*void Init(int capacity = 4)
{
_a = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}*/
void Push(DataType data)
{ }
void Pop()
{ }
DataType Top() { return _a[_size - 1]; }
int Empty() { return 0 == _size; }
int Size() { return _size; }
void Destroy()
{
if (_a)
{
free(_a);
_a = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
void CheckCapacity()
{ }
private:
DataType* _a;
int _capacity;
int _size;
};
不 Init,不 Destroy
C++祖师爷规定了,对象实例化的时候,自动调用构造函数。从此不需要 Init( )
现在程序还存在内存泄漏,因为我们没有调 Destroy。先看:三. 析构函数
特征 4:构造函数可以重载
为什么构造函数支持重载? 因为有多种初始化方式
eg:一上来有一组数据作为默认初始化
cpp
class Stack
{
public:
Stack(DataType* a, int n)
{
cout << "Stack(DataType* a, int n)" << endl;
_a = (DataType*)malloc(sizeof(DataType) * n);
if (nullptr == _a)
{
perror("malloc申请空间失败!!!");
return;
}
memcpy(_a, a, sizeof(DataType) * n);
_capacity = n;
_size = n;
}
Stack(int capacity = 4) // 构造函数,功能:替代Init
{
cout << "Stack(int capacity = 4)" << endl;
_a = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
......
~Stack()
{
cout << "~Stack()" << endl;
if (_a)
{
free(_a);
_a = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
void CheckCapacity()
{ }
private:
DataType* _a;
int _capacity;
int _size;
};
特征 5:自动生成
如果类中没有显式定义构造函数,则 C++ 编译器会 自动生成****一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
cpp
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 d2;
d1.Print();
return 0;
}

一堆随机值,自动生成的构造函数好像啥也没干(编译器会干,只是我们看不见)。而且我咋知道有没有生成构造函数?
这是祖师爷的失误,这里应该初始化为0 C++标准没有规定要初始化
特征 6:
C++ 把类型分为2类:
1. 内置 / 基本类型: 语言本身定义的基础类型 int / char / double / 指针 ......
2. 自定义类型: struct / class 等类型
我们不写,编译器默认生成的构造函数:
内置类型不做处理(有些编译器会处理,但我们当做不处理)、自定义类型会去调用他们的默认构造
结论:
1. 一般情况,要自己写构造函数
2. 用编译器自动生成的就可以:
a. 内置类型成员都有缺省值,且初始化符合我们的要求
b. 全是自定义类型的构造,且这些类型都定义了默认构造(用栈实现队列,定义2个栈)
2. a. 1
C++ 11发布时,打了补丁:成员声明时,可以给缺省值(主要针对内置类型)
cpp
class Date
{
public:
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
// 内置类型
// C++11支持,这里不是初始化,因为这里只是声明,没有开空间
// 这里给的是默认的缺省值,给编译器生成的默认构造函数用
int _year = 1;
int _month = 2;
int _day = 3;
};
int main()
{
Date d1;
d1.Print();
return 0;
}

cpp
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 = 1;
int _month = 2;
int _day = 3;
};
int main()
{
Date d1(2025, 6, 1); // 在这显式初始化了,不会用缺省值
d1.Print();
return 0;
}

此时必须自己写构造函数,否则:
补充:构造函数的调用问题
构造函数的定义很特殊:同名、无返回值、自动调用 构造函数的调用也很特殊
cpp
class Date
{
public: // 写2个构造函数,构造函数可以重载。有多种初始化方式
Date() // 可以无参
{
_year = 2025;
_month = 6;
_day = 1;
}
Date(int year, int month, int day) // 可以带参
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
// 内置类型
// C++11支持,这里不是初始化,因为这里只是声明,没有开空间
// 这里给的是默认的缺省值,给编译器生成的默认构造函数用
int _year = 1;
int _month = 2;
int _day = 3;
};
cpp
int main()
{
Date d1(2025, 6, 1); // 构造函数的调用1:对象 + 参数列表
Date d2; // 构造函数的调用2:对象不加列表
d1.Print(); // 普通函数的调用:函数名 + 参数列表
d2.Print();
Date d3(); // 报警告
// 原因:与函数声明冲突,编译器不好识别。
// 这么写编译器可以看做构造函数的调用;也可以看做一个函数的声明
// Date d1(2025, 6, 1); 这一看就不是函数声明。函数声明()里不是变量对象,是类型
return 0;
}

cpp
// 有人认为祖师爷脑子不清楚,下面这样写就没上面那么多事:对象调函数,变成正常的函数调用
int main()
{
Date d1; // 对象调函数。对象.函数名
d1.Date(); // 实事是:报错:类型名称"Data"不能出现在类成员访问表达式的右侧
Date d2;
d2.Date(2025, 6, 1); // 实事是:报错
return 0;
}
// 能这么写,为什么不这样写?:
int main()
{
Date d1;
d1.Init(); // 实事是:报错 叫 Init 不是更香吗,为什么还搞 Data出来?
Date d2;
d2.Init(2025, 6, 1); // 实事是:报错
return 0;
} // 回去了。对象实例化时,自动调用怎么办?
2. a. 2
cpp
struct TreeNode
{
TreeNode* _left;
TreeNode* _right;
int _val;
};
class Tree
{
private:
TreeNode* _root; // 定义一棵树,最开始要有根节点
// 能不能不写它的构造函数? 可以!
// 直接不写肯定不行,默认生成的构造函数对内置类型不初始化
};
int main()
{
Tree t1;
return 0;
}

cpp
class Tree
{
private:
TreeNode* _root = nullptr;
};

灵活应变:
cpp
struct TreeNode
{
TreeNode* _left;
TreeNode* _right;
int _val;
TreeNode(int val = 0) // 这里推荐自己写默认构造
{
_left = nullptr;
_right = nullptr;
_val = val;
}
};
class Tree
{
private:
TreeNode* _root = nullptr; // 定义一棵树,最开始要有根节点
};
int main()
{
Tree t1;
TreeNode n0;
TreeNode n1(1);
TreeNode n2(8);
return 0;
}
2. b.
cpp
class Stack
{
public:
Stack(int capacity = 4) // 这些类型(Stack)都定义了默认构造
{
cout << "Stack(int capacity = 4)" << endl;
_a = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == _a)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
~Stack()
{ }
private:
DataType* _a = nullptr;
int _capacity;
int _size = 0;
};
class MyQueue
{
private: // 自定义类型(Stack)成员
Stack _pushst;
Stack _popst;
};
int main()
{
MyQueue q;
return 0;
}

特征 7:默认构造函数
无参的构造函数 和 全缺省的构造函数 都称为默认构造函数,并且默认构造函数 只能有一个**。 注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是** 默认构造函数
总结:不传参就可以调用的就是默认构造函数
构造函数虽可重载,但写全缺省最香:
cpp
class Date
{
public:
Date() // 无参
{
_year = 2025;
_month = 6;
_day = 1;
}
Date(int year = 1, int month = 1, int day = 1) // 全缺省
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
// 内置类型
// C++11支持,这里不是初始化,因为这里只是声明,没有开空间
// 这里给的是默认的缺省值,给编译器生成的默认构造函数用
int _year = 1;
int _month = 2;
int _day = 3;
};
无参、全缺省语法上可同时存在,因为构成函数重载 但无参调用存在歧义,所以现实中不会同时存在
cpp
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
// 内置类型
// C++11支持,这里不是初始化,因为这里只是声明,没有开空间
// 这里给的是默认的缺省值,给编译器生成的默认构造函数用
int _year = 1;
int _month = 2;
int _day = 3;
};
int main()
{
Date d1(2022);
d1.Print();
Date d2(2025, 6);
d2.Print();
return 0;
}

cpp
class Date
{
public:
Date(int year, int month = 1, int day = 1) // 不写成全缺省(没有默认构造)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
// 内置类型
// C++11支持,这里不是初始化,因为这里只是声明,没有开空间
// 这里给的是默认的缺省值,给编译器生成的默认构造函数用
int _year = 1;
int _month = 2;
int _day = 3;
};
int main()
{
Date d1; // 不传参数:报错:"Data": 没有合适的默认构造函数可用
// 可以不传参的是默认构造,这里必须传参数
d1.Print();
return 0;
}
三. 析构函数
析构函数是特殊的成员函数,不是完成对象本身的销毁(不是销空间)。局部对象(在栈帧里,由系统完成)销毁工作是由编译器完成的
特征 1:析构函数名:~类名
特征 2:无参数(析构函数不能重载),无返回值
特征 3:对象生命周期结束(销毁)时,自动调用,完成对象中 资源清理 工作
cpp
class Stack
{
public:
Stack(int capacity = 4) // 构造函数
{ }
~Stack() // 析构函数
{
cout << "~Stack()" << endl;
if (_a)
{
free(_a);
_a = nullptr;
_capacity = 0;
_size = 0;
}
}
/*void Destroy()
{
if (_a)
{
free(_a);
_a = nullptr;
_capacity = 0;
_size = 0;
}
}*/
private:
void CheckCapacity()
{ }
private:
DataType* _a;
int _capacity;
int _size;
};
Stack 有了构造、析构,就不怕忘记写 初始化、清理函数 了,也简化了
特征 4:自动生成
一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数
1、内置类型成员不做处理 2、自定义类型会去调用它的析构函数
所以,上面的代码,不写自己写析构,编译器默认生成的析构,不会释放 _a指向的空间,内存泄漏
cpp
class Stack
{
public:
Stack(int capacity = 4) // 构造函数,功能:替代Init
{ }
private:
DataType _a[100];
int _capacity;
int _size;
};
写的是静态的会不会释放?
析构函数是释放 动态申请(堆)的资源。这种 静态的资源(栈)不用手动释放,出了作用域会自动销毁
只有堆上的要手动释放
总结
1、一般情况下,有动态申请资源,就需要显式写析构函数,来释放资源
2、没有动态申请的资源,不需要写析构
3、需要释放资源的成员都是自定义类型,不需要写析构,前提:类型都定义了析构函数
cpp
// 1、栈是经典的需要写析构
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_capacity = _size = 0;
}
// 2、日期类没有析构可写,没有申请资源
class Data
{
private:
int _year;
int _month;
int _day;
};
// 3、默认生成的析构会自动调用析构
class MyQueue
{
private:
Stack _pushst;
Stack _popst;
};
本篇的分享就到这里了,感谢观看 ,如果对你有帮助,别忘了点赞+收藏+关注 。
小编会以自己学习过程中遇到的问题为素材,持续为您推送文章