【C++要笑着学】类的默认成员函数详解 | 构造函数 | 析构函数 | 构造拷贝函数

article/2025/9/14 4:10:35

 🔥 🔥 🔥 🔥 🔥  火速猛戳订阅 👉  《C++要笑着学》   👈 趣味教学博客 🔥 🔥 🔥 🔥 🔥

   [ 本篇博客热榜最高排名:7 ]

写在前面

 朋友们好啊,今天终于更新了。我是柠檬叶子C,本章将继续讲解C++中的面向对象的知识点,本篇主要讲解默认成员函数中的构造函数、析构函数和拷贝构造函数。还是和以前一样,我们将由浅入深地去讲解,以 "初学者" 的角度去探索式地学习。会一步步地推进讲解,而不是直接把枯燥的知识点倒出来,应该会有不错的阅读体验。如果觉得不错,可以 "一键三连" 支持一下博主!你们的关注就是我更新的最大动力!Thanks ♪ (・ω・)ノ


Ⅰ.  默认成员函数

如果一个类中什么成员都没有,我们称之为 "空类" 。

❓ 但是空类中真的什么都没有吗?

 并不是的……

类有六个默认成员函数,特殊的点非常多,后面我们会壹壹学习。

 对于默认成员函数,如果我们不主动实现,编译器会自己生成一份。

❓ 他们有什么用呢?举个例子:

比如我们在上一章里举过的一个 Stack 的例子,

如果需要初始化和清理,"构造函数" 和 "析构函数" 就可以帮助我们完成。

构造函数就类似于 Init,而析构函数就类似于 Destroy

还是和以前一样,我们将先由浅入深地进行学习,我们先从 "构造函数" 开始讲起。

Ⅱ. 构造函数

0x00 引入

打开宇宙第一编辑器,一起敲一敲看看 ~

📚 为了能够更好地讲解,我们来写一个简单的日期类,通过日期类来讲解。

💬 Date.cpp

#include <iostream>class Date {
public:void SetDate(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(void)
{Date d1;d1.SetDate(2022, 3, 8);d1.Print();Date d2;d2.SetDate(2022, 3, 12);d2.Print();return 0;
}

💡 对于 Date 类,我们可以通过我们写的成员函数 SetDate 给对象设置内容。

 但是每次创建对象都要调用这个 SetDate ,是不是太鸡儿烦了?

❓ 那有没有什么办法能在创建对象时,自动将我们要传递的内容放置进去呢?

 有!下面我们来隆重介绍一下 构造函数

0x01 构造函数的概念

📚 构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用。

能够保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。

 构造函数的意义:能够保证对象被初始化。

构造函数是特殊的成员函数,主要任务是初始化,而不是开空间。(虽然构造函数的名字叫构造)

0x02 构造函数的特性

📚 构造函数是特殊的成员函数,主要特征如下:

① 构造函数的函数名和类名是相同的

② 构造函数无返回值

③ 构造函数可以重载

③ 会在对象实例化时自动调用对象定义出来。

比如下面的代码只要  就会自动调用,保证了对象一定是被初始化过的。

 我们直接来看看它是怎么用的! 

💬 构造函数的用法:

#include <iostream>class Date {
public:/* 无参构造函数 */Date() {_year = 0;_month = 1;_day = 1;}/* 带参构造函数 */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(void)
{Date d1;   // 对象实例化,此时触发构造,调用无参构造函数d1.Print();Date d2(2022, 3, 9);   // 对象实例化,此时触发构造,调用带参构造函数d2.Print();return 0;
}

🚩 运行结果如下:

 🔑 解读:不给参数时就会调用无参构造函数,给参数则会调用带参构造函数。

📌 注意事项:

① 构造函数是特殊的,不是常规的成员函数,不能直接调  。

#include <iostream>class Date {
public:Date(int year = 1, int month = 0, int day = 0) {_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};int main(void)
{Date d1;d1.Date(); // 不能这么去调,构造函数是特殊的,不是常规的成员函数!return 0;
}

🚩 运行结果:(报错)

② 如果通过无参构造函数创建对象,对象后面不用跟括号,否则就成了函数声明。

#include <iostream>class Date {
public:Date(int year = 1, int month = 0, int day = 0) {_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(void)
{//带参这么调:加括号(),在括号中加参数列表Date d2(2022, 3, 9);Date d3();   // 这样可以吗? // 既然代参的调用加括号,在括号中加参数列表。// 那我不带参,可不可以加括号呢?❌ 仍然不可以。// 这个对象实际上没有被定义出来,这里会报错。 // 编译器不会识别,所以不传参数就老老实实地  // Date d3; 不要 Date d3();   // 主要是编译器没法识别,所以这里记住不能这么干就行了。return 0;
}

③ 这里如果调用带参构造函数,我们需要传递三个参数(这里我们没设缺省) 。

④ 如果你没有自己定义构造函数(类中未显式定义),C++ 编译器会自动生成一个无参的默认构造函数。当然,如果你自己定义了,编译器就不会帮你生成了。

#include <iostream>class Date {
public:/* 如果用户显式定义了构造函数,编译器将不再生成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(void)
{Date d1;  // 这里调用的是默认生成的无参的构造函数d1.Print();return 0;
}

🚩 运行结果如下:

🔑 没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器默认生成的构造函数。

0x03 默认构造函数

 无参构造函数、全缺省构造函数都被称为默认构造函数。

并且默认构造函数只能有一个!

class Date {
public:/* 全缺省构造函数 - 默认构造函数 */Date(int year = 1970, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};

📌 注意事项:

① 无参构造函数、全缺省构造函数、我们没写编译器默认生成的无参构造函数,都可以认为是默认成员函数。

② 语法上无参和全缺省可以同时存在,但如果同时存在会引发二义性:

#include <iostream>class Date {
public:Date() {_year = 1970;_month = 1;_day = 1;}Date(int year = 1970, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};int main(void)
{Date d1; ❌return 0;
}

🚩 运行结果如下:(报错)

🔑 解读:无参的构造函数和全缺省的构造函数都成为默认构造函数,并且默认构造参数只能有一个,语法上他们两个可以同时存在,但是如果有对象定义去调用就会报错。

  推荐实现全缺省或者半缺省,因为真的很好用:

#include <iostream>class Date {public:/* 全缺省 */Date(int year = 0, int month = 1, int day = 1) {_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(void)
{Date d1; // 如果不传,就是缺省值Date d2(2022, 1, 15);Date d3(2009);Date d4(2012, 4);d1.Print();  // 0-1-1d2.Print();  // 2022-1-15d3.Print();  // 2009-1-1d4.Print();  // 2012-4-1return 0;
}

🚩 运行结果如下:

0x04 构造函数的特性的测试

📚 任何一个类的默认构造函数,只有三种:

① 无参的构造函数

② 全缺省的构造函数

③ 我们不写,编译器自己生成的构造函数

💬 如果你没有自己定义构造函数(类中未显式定义),

C++ 编译器会自动生成一个无参的默认构造函数。

当然,如果你自己定义了,编译器就不会帮你生成了。

#include <iostream>class Date {
public:// 让编译器自己生成一个void Print() {printf("%d-%d-%d\n", _year, _month, _day);}private:int _year;int _month;int _day;
};int main(void)
{Date d1;  // 这里调用的是默认生成的无参的构造函数d1.Print();return 0;
}

🚩 运行结果如下:

  好了,我们来好好探讨探讨这个问题!

在我们不是先构造函数的情况下,编译器生成的默认构造函数。

似乎这看起来没有什么鸟用啊,这不就是一堆随机值嘛……

d1 对象调用了编译器生成的默认函数,但 d1 对象 year / month / day 依旧是随机值,

也就是说这里编译器生成的默认构造函数好像并没有什么卵用。

🔑 解答:C++ 把类型分成内置类型(基本类型)和自定义类型。

内置类型就是语法已经定义好的类型:如 int / char...,

自定义类型就是我们使用 class / struct / union / 自己定义的类型

💬 看看下面的程序,就会发现。编译器生成默认的构造函数会对自定类型成员 aa 调用的它的默认成员函数:

#include <iostream>
using namespace std;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;return 0;
}

🚩 运行结果如下: 

💬 测试:对自定义类型处理,会调用默认构造函数(不用参数就可以调的函数)

#include<iostream>
using namespace std;class A {
public:// 默认构造函数(不用参数就可以调的)A() {cout << " A() " << endl;_a = 0;}private:int _a;
};class Date {
public:
private:int _year;int _month;int _day;A _aa;   // 对自定义类型处理,此时会调用默认构造函数 A() {...}
};int main(void)
{Date d1;return 0;
}

🚩 运行结果如下:

C++ 里面把类型分为两类:内置类型(基本类型)和 自定义类型

C++ 规定:我们不写编译器默认生成构造函数,对于内置类型的成员变量,不做初始化处理。

但是对于自定义类型的成员变量会去调用它的默认构造函数(不用参数就可以调的)初始化。

如果没有默认构造函数(不用参数就可以调用的构造函数)就会报错!

💬 为了验证,这里我们故意写个带参的默认构造函数,让编译器不默认生成:

#include <iostream>
using namespace std;class A {
public:// 如果没有默认的构造函数,会报错。A(int a) {    // 故意给个参cout << " A() " << endl;_a = 0;}
private:int _a;
};class Date {
public:
private:// 如果没有默认构造函数就会报错int _year;int _month;int _day;A _aa;
};int main(void)
{Date d1;return 0;
}

🚩 运行结果如下(报错)

💬 我们不写,让编译器默认生成一个:

#include<iostream>
using namespace std;class A {
public:// 让编译器默认生成
private:int _a;
};class Date {
public:
private:int _year;int _month;int _day;A _aa;
};int main(void)
{Date d1;return 0;
}

 是随机值没错,但是这是一种对自定义类型的 "处理" 。

 这里说个题外话,个人认为 C++里,我们不写构造函数编译器会默认生成的这个特性设计得不好(狗头保命)……因为没有对内置类型和自定义类型统一处理,不处理内置类型成员变量,只处理自定义类型成员变量。

Ⅲ. 析构函数

0x00 引入

 通过前面构造函数的学习,我们知道了一个对象是怎么来的了,

❓ 那一个对象又是怎么没的呢?既然构造函数的本质是初始化,那清理的工作交给谁来干呢?

💡 交给专门擦屁股的 —— 析构函数

以前我们玩数据结构的时候经常忘记调用 destroy 函数,但是现在我们有析构函数了!!!

多么振奋人心啊!话不多说让我们开始讲解!!! 

0x01 析构函数的概念

 析构函数与构造函数的功能相反。

构造函数是特殊的成员函数,主要任务是初始化,而不是开空间;

析构函数也一样,主要任务是清理,而不是做对象销毁的工作。

(局部对象销毁工作是由编译器完成的)

📚 概念:对象在销毁时会自动调用析构函数,完成对象的一些资源清理工作。

0x02 析构函数的特性

 构造函数是特殊的成员函数,主要特征如下:

① 析构函数名是在类名前面加上字符 ~

② 析构函数既没有参数也没有返回值(因为没有参数,所以也不会构成重载问题)

③ 一个类的析构函数有且仅有一个(如果不写系统会默认生成一个析构函数)

④ 析构函数在对象生命周期结束后,会自动调用。

(和构造函数是对应的构造函数是在对象实例化的时候自动调用)

💬 为了演示自动调用,我们来让析构函数被调用时 "吱" 一声:

#include <iostream>
using namespace std;class Date {
public:Date(int year = 1, int month = 0, int day = 0) {_year = year;_month = month;_day = day;}void Print() {printf("%d-%d-%d\n", _year, _month, _day);}~Date() {// Date 类没有资源需要清理,所以Date不实现析构函都是可以的cout << "~Date() 吱~ " << endl;  // 测试一下,让他吱一声}private:int _year;int _month;int _day;
};int main(void)
{Date d1;Date d2(2022, 3, 9);return 0;
}

🚩 运行结果:

额,之前举得日期类的例子没法很好地展示析构函数的 "魅力" ……

就像本段开头说情景,我们拿 Stack 来举个例子,这就很贴切了。

我们知道,栈是需要 destroy 清理开辟的内存空间的。

 这里我们让析构函数来干这个活,简直美滋滋!

💬 析构函数的用法:

#include<iostream>
#include<stdlib.h>
using namespace std;typedef int StackDataType;
class Stack {
public:/* 构造函数 - StackInit */Stack(int capacity = 4) {  // 这里只需要一个capacity就够了,默认给4(利用缺省参数)_array = (StackDataType*)malloc(sizeof(StackDateType) * capacity);if (_array == NULL) {cout << "Malloc Failed!" << endl;exit(-1);}_top = 0;_capacity = capacity;}/* 析构函数 - StackDestroy */~Stack() {   // 这里就用的上析构函数了,我们需要清理开辟的内存空间(防止内存泄漏)free(_array);_array = nullptr;_top = _capacity = 0;}private:int* _array;size_t _top;size_t _capacity;
};int main(void)
{Stack s1;Stack s2(20); // s2 栈 初始capacity给的是20(可以理解为"客制化")return 0;
}

🔑 解读:我们在设置栈的构造函数时,定义容量 capacity 时利用缺省参数默认给个4的容量,这样用的时候默认就是4,如果不想要4可以自己传。

如此一来,就可以保证了栈被定义出来就一定被初始化,用完后会自动销毁。以后就不会有忘记调用 destroy 而导致内存泄露的惨案了,这里的析构函数就可以充当销毁的作用。

❓ 问一个比较有意思的问题,这里是先析构 s1 还是先析构 s2

 既然都这样问了,应该是先析构 s2 了 ~

析构的顺序在局部的栈中是相反的,栈帧销毁清理资源时 s2 先清理,然后再清理 s1

(不信的话可以去监视一下 this 观察下成员变量)

0x03 析构函数的特性的测试

又到了测试环节,上号!

我们知道了,如果没写析构函数编译器会自动生成一个。

那默认生成的析构函数会做什么事情呢?它会帮我们 destroy 嘛?

 hhh,哪有这种好事,不能什么都帮你做啊!

我们刚才讲了(我们回顾下,串联一下知识点):

📌 如果不自己写构造函数,让编译器自动生成,那么这个自动生成的 默认构造函数

  • 对于 "内置类型" 的成员变量:不会做初始化处理。
  • 对于 "自定义类型" 的成员变量:会调用它的默认构造函数(不用参数就可以调的)初始化,如果没有默认构造函数(不用参数就可以调用的构造函数)就会报错!

 而我们的析构函数也是这样的,一个德行!

📌 如果我们不自己写析构函数,让编译器自动生成,那么这个 默认析构函数

  • 对于 "内置类型" 的成员变量:不作处理 (不会帮你清理的.)
  • 对于 "自定义类型" 的成员变量:会调用它对应的析构函数 (已经仁至义尽了)

" 编译器:哈哈哈,给你默认生成个用用就不错了,你都懒得写了,不要挑三拣四滴!"

💬 代码演示:

#include<iostream>
#include<stdlib.h>
using namespace std;
typedef int StackDataType;class Stack {
public:Stack(int capacity = 4) {_array = (StackDataType*)malloc(sizeof(int*) * capacity);if (_array == NULL) {cout << "Malloc Failed!" << endl;exit(-1);}_top = 0;_capacity = capacity;}// ~Stack() {//     free(_array);//     _array = nullptr;//     _top = _capacity = 0;// }private:int* _array;size_t _top;size_t _capacity;
};int main(void)
{Stack s1;Stack s2(20);return 0;
}

难道就不能帮我把这些事都干了吗?帮我都销毁掉不就好了?

不不不,举个最简单的例子,迭代器,析构的时候是不释放的,因为不需要他来管,

所以默认不对内置类型处理是正常的,万一误杀了怎么办,对吧。 

有人可能又要说了,这么一来默认生成的析构函数不就没有用了吗?

有用!他对内置类型的成员类型不作处理,会在一些情况下非常的有用!

比如说:   两个栈实现一个队列(LeetCode232) ,用C++可以非常的爽。

💬 自定义类型的成员变量调用它的析构函数:

#include <iostream>
using namespace std;class String {
public:String(const char* str = "jack") {_str = (char*)malloc(strlen(str) + 1);strcpy(_str, str);}~String() {cout << "~String()" << endl;free(_str);}
private:char* _str;
};class Person {
private:String _name;int _age;
};int main()
{Person p;return 0;
}

🚩 运行结果如下:

Ⅳ.  拷贝构造函数

0x00 引入

我们在创建对象的时候,能不能创建一个与某一个对象一模一样的新对象呢?

Date d1(2022, 3, 9);    
d1.Print();Date d2(d1);    // 照着d1的模子做一个d2
d2.Print();

当然可以,这时我们就可以用拷贝构造函数。

0x01 拷贝构造函数的概念

📚 拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用 const 修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

0x02 拷贝构造函数的特性

它也是一个特殊的成员函数,所以他符合构造函数的一些特性:

① 拷贝构造函数是构造函数的一个重载形式。函数名和类名相同,没有返回值。

② 拷贝构造函数的参数只有一个,并且必须要使用引用传参!

      使用传值方式会引发无穷递归调用!

💬 拷贝构造函数的用法:

#include <iostream>class Date {
public:Date(int year = 0, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}/* Date d2(d1); */Date(Date& d) {         // 这里要用引用,否则就会无穷递归下去_year = d._year;_month = d._month;_day = d._day;}void Print() {printf("%d-%d-%d\n", _year, _month, _day);}private:int _year;int _month;int _day;
};int main(void)
{Date d1(2022, 3, 9);Date d2(d1);          // 拷贝复制// 看看拷贝成功没d1.Print();d2.Print();return 0;
}

🚩 运行结果如下:

❓ 为什么必须使用引用传参呢?

调用拷贝构造,需要先穿参数,传值传参又是一个拷贝构造。

调用拷贝构造,需要先穿参数,传值传参又是一个拷贝构造。

调用拷贝构造,需要先穿参数,传值传参又是一个拷贝构造。

……

一直在传参这里出不去了,所以这个递归是一个无穷无尽的。

💬 我们来验证一下:

error: invalid constructor; you probably meant 'Date (const Date&)'

这里不是加不加 const 的问题,而是没有用引用导致的问题。

不用引用,他就会在传参那无线套娃递归。至于为什么我们继续往下看。

💬 拷贝构造函数加 const

如果函数内不需要改变,建议把 const 也给它加上

class Date {
public:Date(int year = 0, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}/* Date d2(d1); */Date(const Date& d) {    // 如果内部不需要改变,建议加上const_year = d._year;_month = d._month;_day = d._day;}void Print() {printf("%d-%d-%d\n", _year, _month, _day);}private:int _year;int _month;int _day;
};

第一个原因:怕出错,万一你一不小心写反了怎么办?

/* Date d2(d1); */
Date(Date& d) {d._year = _year;d._month = _month;d._day = _day;
}

这样会产生一个很诡异的问题,这一个可以被编译出来的 BUG ,结果会变为随机值。

所以,这里加一个 const 就安全多了,这些错误就会被检查出来了。

第二个原因:以后再讲,因为涉及一些临时对象的概念。

🔺 反正,不想深究的话就记住:如果函数体内不需要改变,建议把 const 加上就完事了。

0x03 关于默认生成的拷贝构造

 这里比较特殊,我们单独领出来讲。 

📚 默认生成拷贝构造:

内置类型的成员,会完成按字节序的拷贝(把每个字节依次拷贝过去)。

② 自定义类型成员,会再调用它的拷贝构造。

💬 拷贝构造我们不写生成的默认拷贝构造函数,对于内置类型和自定义类型都会拷贝处理。但是处理的细节是不一样的,这个跟构造和析构是不一样的!

#include<iostream>
using namespace std;class Date {public:Date(int year = 0, int month = 1, int day = 1) {_year = year;_month = month;_day = day;}// Date(Date& d) {//     _year = d._year;//     _month = d._month;//     _day = d._day;// }void Print() {printf("%d-%d-%d\n", _year, _month, _day);} private:int _year;int _month;int _day;
};int main(void)
{Date d1(2002, 4, 8);// 拷贝复制Date d2(d1);// 没有写拷贝构造,但是也拷贝成功了d1.Print();d2.Print();return 0;
}

🚩 运行结果如下:

🔑 他这和之前几个不同了,这个他还真给我解决了。

所以为什么要写拷贝构造?写他有什么意义?没有什么意义。

 默认生成的一般就够用了!

 当然,这并不意味着我们都不用写了,有些情况还是不可避免要写的

比如实现栈的时候,栈的结构问题,导致这里如果用默认的 拷贝构造,会翻车。

按字节把所有东西都拷过来会产生问题,如果 Stack st1 拷贝出另一个 Stack st2(st1

会导致他们都指向那块开辟的内存空间,导致他们指向的空间被析构两次,导致程序崩溃

然而问题不止这些……

 其实这里的字节序拷贝是浅拷贝,下面几章我会详细讲一下深浅拷贝,这里的深拷贝和浅拷贝先做一个大概的了解。

🔺 总结:对于常见的类,比如日期类,默认生成的拷贝构造能用。但是对于栈这样的类,默认生成的拷贝构造不能用。

Ⅴ.  总结

默认成员函数有六只,本篇只介绍了三只,剩下的我们后面讲。

类和对象部分知识很重要,所以我们来做一个简单的总结 ~

0x00 构造函数 

初始化,在对象实例化时候自动调用,保证实例化对象一定被初始化。

构造函数是默认成员函数,我们不写编译器会自己生成一份,我们写了编译器就不会生成。

我们不写内置类型成员变量不处理。

对于内置类型成员变量不处理。

对于自定义类型的成员变量会调用它的默认构造函数。

// 我们需要自己实现构造函数
class Date {int _year;int _month;int _day;
};// 我们不需要自己实现构造函数,默认生成的就可以
class MyQueue {Stack _pushST;Stack _popST;
};

0x01 析构函数

 完成对象中自愿的清理。如果类对象需要资源清理,才需要自己实现析构函数。

析构函数在对象生命周期到了以后自动调用,如果你正确实现了析构函数,保证了类对象中的资源被清理。

什么时候生命周期到了?如果是局部变量,出了作用域。全局和静态变量,整个程序结束。

我们不写编译器会默认生成析构函数,我们实现了,编译器就不会实现了。

对于内置类型成员变量不处理。

对于自定义类型的成员变量会调用它的析构函数。

// 没有资源需要清理,不徐需要自己实现析构函数
class Date {int _year;int _month;int _day;
};// 需要自己实现析构函数,清理资源。
class Stack {int* _a;int  _top;int  _capacity;
};

0x02 拷贝构造

使用同类型的对象去初始化实例对象。

参数必须是引用!不然会导致无穷递归。

如果我们不实现,编译器会默认生成一份默认的拷贝构造函数。

默认生成的拷贝构造:

① 内置类型完成按子继续的值拷贝。 —— 浅拷贝

② 自定义类型的成员变量,会去调用它的拷贝构造。

// 不需要自己实现,默认生成的拷贝构造,完成浅拷贝就能满足需求
class Date {int _year;int _month;int _day;
};// 需要自己实现,因为默认生成的浅拷贝不能满足需求。
// 我们需要自己实现深拷贝的拷贝构造,深拷贝我们后面会用专门的章节去讲解。        
class Stack {int* _a;int  _top;int  _capacity;
};

#include <iostream>
using namespace std;class Date {
public:Date(int year = 1, int month = 0, int day = 0) {_year = year;_month = month;_day = day;}void Print() {printf("%d-%d-%d\n", _year, _month, _day);}~Date() {cout << "&Date()" << endl;}private:int _year;int _month;int _day;
};int main(void)
{Date d1;d1.Print();Date d2(2002);d2.Print();Date d3(2022, 3);d3.Print();Date d4(2022, 3, 9);d4.Print();return 0;
}

 🚩 运行结果如下:


参考资料:

Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .

百度百科[EB/OL]. []. https://baike.baidu.com/.

比特科技. C++[EB/OL]. 2021[2021.8.31]. .

📌 笔者:王亦优

📃 更新: 2022.3.15

❌ 勘误:Star丶北辰:拿栈举例时malloc空间 sizeof 有误【已修正】

📜 声明: 由于作者水平有限,本文有错误和不准确之处在所难免,本人也很想知道这些错误,恳望读者批评指正!

本章完。


http://chatgpt.dhexx.cn/article/aVxaPxS4.shtml

相关文章

JSF 文档参考

转自&#xff1a;http://blog.csdn.net/ontheway20/article/details/38532241 A4J 用户指南 目录 1. 介绍 2. 开始使用Ajax4jsf 环境需求 下载Ajax4jsf 安装 简单的 AJAX Echo 项目 JSP 页面 数据 Bean faces-config.xml Web.xml 部署 3. Ajax4jsf 框架的基本概念 介绍 结构概…

IDEA 2020.2 部署JSF项目

目录 一、用Glassfish部署JSF项目 1、下载glassfish 2、配置glassfish环境变量 3、修改jdk环境变量 4、测试glassfish是否可以正常启动 5、在IDEA中创建一个JSF项目 6.问题&#xff1a;部分标签元素无法显示 二、用tomcat部署JSF项目 1、新建项目或者模块&#xff0c;…

JSF教程(1)——简介 + HelloWorld

在写第一个HelloWorld之前先来宏观的了解一下JSF&#xff0c;也许你之前使用过Struts&#xff08;1或者2&#xff09;&#xff0c;SpringMVC&#xff0c;甚至于直接采用JSPServelet开发过web层。JSF与这些最大的不同是JSF是基于一种以组件为中心的用户界面&#xff08;UI&#…

JSF程式

概述&#xff1a; jsf使用spring的依赖注入的思想使得页面和业务逻辑更好的分离开来&#xff0c;页面与页面的跳转&#xff0c;逻辑关系&#xff0c;页面与后台不同的beans的对应和操作都是通过faces-config.xml文件来说明和配置。对程序员的要求不高&#xff0c;页面程序员可…

谈谈京东的服务框架JSF

谈谈京东的服务框架 最近由于在实习期间接触到了京东的自研服务框架JSF&#xff0c;简称“杰夫”&#xff0c;目前我写的一些新功能里面调用的下游接口就是杰夫提供的。现有有很多高效的服务框架&#xff0c;如阿里巴巴的Dubbo配合Apache的ZooKeeper&#xff0c;那么为什么京东…

JSF 转换与验证

在本文中&#xff0c;我们将介绍 JSF 转换和验证框架的概念&#xff0c;它比您所想的要容易使用得多&#xff0c;也灵活得多。 首先我们将介绍应用于 JSF 生命周期的转换和验证过程&#xff0c;然后展示一个简单的 JSF 应用程序中的默认转换和验证过程。接着将展示如何创建和插…

JSF----------基础知识初解

初次学习JSF,对其基础进行了一些学习与整理。 JSF(JavaServer Faces)它是一个基于服务器端组件的用户界面框架。 它用于开发Web应用程序。 它提供了一个定义良好的编程模型&#xff0c;由丰富的API和标签库组成。最新版本JSF 2使用Facelets作为其默认模板系统。 它是用Java编写…

JSF详解

1&#xff0e; 结构&#xff1a; a) 结构图&#xff1a; b) 说明&#xff1a;JSF以MVC模式为基础&#xff0c;与Struts不同&#xff0c;JSF的目标是希望以一个与Swing相类似的方式来开发网页&#xff0c;因此&#xff0c;从JSF的结构图当中&#xff0c;他的核心…

JSF框架整理(一)

一、框架简介 JavaServer Faces (JSF) 是一种用于构建Java Web 应用程序的标准框架&#xff0c;它提供了一种以组件为中心的用户界面&#xff08;UI&#xff09;构建方法&#xff0c;从而简化了Java服务器端应用程序的开发。 典型的JSF应用程序包含下列部分&#xff1a; 一组J…

JSF简介

JSF简介 一、 什么是 JSF &#xff1a; JavaServer Faces (JSF) 是一种用于构建 Web 应用程序的新标准 Java 框架。它提供了一种以组件为中心来开发 Java Web 用户界面的方法&#xff0c;从而简化了开发。 JavaServer Faces于2004年三月1.0版正式提出&#xff0c;清楚的将Web应…

JSF概述

1. JSF简洁 JSF是一种以组件为中心&#xff0c;遵循MVC设计模式的一种框架。 Web引用程序开发人员划分&#xff1a;网页设计人员应用程序设计人员UI组件设计人员 所有与应用程序都由一个前端控制器(FacesServlet)来处理 2. JSF声明周期 FacesServlet充当用户和JSF应用程序之间的…

ztree项目

思路&#xff1a; 创建一个登陆 登陆上去 就是树 每个是的根节点有他所要展示的内容 表 可以有无数个 主要说的是创建树的表 这个是树的一个表 id 是 节点 name 名字 pid 根节点 url 路径 树的页面 后台通过登陆转的页面 转页面 在前台打印出你想要的数据 前台页面 退出 /*…

ztree使用

官方文档地址 http://www.treejs.cn/v3/main.php#_zTreeInfo 各种参数 http://www.treejs.cn/v3/api.php 简单静态调用 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title><link href"https://cdn…

zTree 简介

zTree 是一个依靠 jQuery 实现的多功能 “树插件”。优异的性能、灵活的配置、多种功能的组合是 zTree 最大优点。 zTree 是开源免费的软件&#xff08;MIT 许可证&#xff09;。如果您对 zTree 感兴趣或者愿意资助 zTree 继续发展下去&#xff0c;可以进行捐助。 zTree v3.0 …

ztree 详解

官网:http://www.treejs.cn/v3/demo.php#_101 里面有例子和demo,齐全。 zTree是一个基于jQuery的树形列表生成控件。 切换语言,点击下载,里面有各种demo: 里面各种demo,比博客写的好。 <!DOCTYPE html> <html lang="en"><head><meta ch…

zTree的简单使用1.0

2018/10/10 北京朝阳.冠城大厦17楼 这里是引用 zTree 简介 zTree 是一个依靠 jQuery 实现的多功能 “树插件”。优异的性能、灵活的配置、多种功能的组合是 zTree 最大优点。 zTree 是开源免费的软件&#xff08;MIT 许可证&#xff09;。如果您对 zTree 感兴趣或者愿意资助 zT…

ZTree基本使用及本人详解

文章目录 ZTree树简介简介ZTree的特点练习ztree之前的小建议ZTree文件介绍ZTree的配置介绍 ZTree使用案例需求1&#xff1a;前端初始化数据&#xff08;标准json数据&#xff09;前端代码 需求2&#xff1a;后端查询ztree数据&#xff08;简单JSON数据&#xff09;需求3&#x…

ztree 使用教程

zTree 是一个依靠 jQuery 实现的多功能 “树插件”。被广泛应用在系统的权限管理中&#xff0c;本文讲解zTree的一般应用 zTree 官网 http://www.treejs.cn/v3/main.php#_zTreeInfo 1、zTree 官网下载 ztree 下载好后放到项目相关目录下 2、编写相关代码 引入相关js 、 css …

@PersistenceContext和@Autowired在EntityManager上应用的不同

首先PersistenceContext是jpa专有的注解&#xff0c;而Autowired是spring自带的注释 上方图片的意思就是EntityManager不是线程安全的&#xff0c;当多个请求进来的时候&#xff0c;spring会创建多个线程&#xff0c;而PersistenceContext就是用来为每个线程创建一个EntityMana…

java:spring:注解:@PersistenceContext和@Resource

PersistenceContext private EntityManager em; 注入的是实体管理器&#xff0c;执行持久化操作的&#xff0c;需要配置文件persistence.xml。 注入一堆保存实体类状态的数据结构&#xff0c;针对实体类的不同状态(四种,managedh或detached等)可以做出不同的反应(merge,persis…