电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
适合具备 C 语言基础的 C++ 入门教程(二)
分 享
扫描二维码分享
适合具备 C 语言基础的 C++ 入门教程(二)
C++
wenzi 嵌入式软件
关注
发布时间: 2021-02-09
丨
阅读: 444
# 前言 在上一则教程中,通过与 C 语言相比较引出了 C++ 的相关特性,其中就包括函数重载,引用,this 指针,以及在脱离 IDE 编写 C++ 程序时,所要用到的 `Makefile`的相关语法。本节所要叙述的是 `C++`的另外两个重要的特性,也就是构造函数和析构函数的相关内容,这两部分内容也是有别于 `c`语言而存在的,也是 `c++`的一个重要特性。 ## 构造函数 类的构造函数是类的一种特殊的成员函数,它会在每次创建新的对象的时候执行,构造函数的名称和类的名称是完全相同的,并不会返回任何的类型,也不会返回 void。构造函数可以用于为某些成员变量设置初始值。 比方说,我们现在有如下所示的一段代码: ```cpp #include
using namespace std; class Person{ private: char *name; int age; char *work; public: Person() {cout << "Person()" << endl;} }; int main(int argc, char **argv) { Person per; return 0; } ``` 在主函数中,定义 Person per 的同时,就会自动地调用 Person() 函数,那么不难猜出,执行 test 文件地时候,输出结果如下: ![image-20210113124209248](https://gitee.com/wenzi_D/images4mk/raw/master/20210113124210.png) 上述地构造函数并没有参数,实际上在构造函数是可以具有参数的,具体的看如下所示的代码: ```cpp #include
using namespace std; class Person { private: char *name; int age; public: Person(char *name, int age) { cout << "Person(char *,int)" << endl; this->name = name; this->age = age; } Person(){cout << "Person()" << endl;} }; int main(int argc, char **argv) { Person per; Person per2("zhangsan",18); return 0; } ``` 上述代码中,定义第一个 Person 实例的时候,就会自动地调用无形参地构造函数,当实例化第二个 Person 类地时候,就会自动地调用有形参地构造函数。 这个时候,运行函数地输出结果如下所示: ![image-20210113125016221](https://gitee.com/wenzi_D/images4mk/raw/master/20210113125016.png) 可以看到调用构造函数的顺序是和实例化对象的顺序是一致的。 构造函数除了可以有形参,也可以有默认的形参,比如说下面这段代码: ```cpp #include
using namespace std; class Person { private: char *name; int age; public: Person(char *name, int age, char *work = "none") { cout << "Person(char *,int)" << endl; this->name = name; this->age = age; this->work = work; } Person(){cout << "Person()" << endl;} void printInfo(void) { cout << "name=" << name << ",age="<< age << ",work="<< work << endl; } }; int main(int argc, char **argv) { Person per; Person per2("zhangsan",18); Person per3(); per2.printInfo(); return 0; } ``` 上述代码中,第一条代码和第二条代码创建了两个 Person 实例,在创建时依次调用构造函数,这里需要注意的是,第三条语句,这条语句看起来像是实例化了一个 per3 对象,但是 per3 括号里并没有实参,这其实是定义了一个函数,函数的形参为`void`,返回值为 Person ,并非是一个对象。这里还需要注意的一点是 per2 对象,它在调用构造函数时,形参有一个默认值,所以最终,程序输出的结果如下所示: ![image-20210113131653000](https://gitee.com/wenzi_D/images4mk/raw/master/20210113131653.png) 在实例化对象的时候,我们也可以通过定义指针的形式实现,下面代码是上述代码的一个改进,并且以指针的形式实例化了对象,代码如下所示: ```cpp #include
#include
using namespace std; class Person { private: char *name; int age; char *work; public: Person(){cout << "person()" << endl;} Person(char *name,int age, char *work) { cout << "Person(char *,int, char *)" << endl; this->name = new char[strlen(name) + 1]; strcpy(this->name,name); this->age = age; this->work = new char[strlen(work) + 1]; strcpy(this->work,work); } void printInfo(void) { cout << "name is:" << name << ",age is:" << age << ",work is:" << work << endl; } }; int main(int argc,char *argv) { Person per("zhangsan",18,"teacher"); Person per2; Person *per4 = new Person; Person *per5 = new Person(); /@@* 这两种方式定义的效果是一样的 */ Person *per6 = new Person[2]; Person *per7 = new Person("lisi", 18,"doctor"); per.printInfo(); per7.printInfo(); delete per4; delete per5; delete []per6; delete per7; } ``` 上述代码中,使用了new 来分配给对象空间,再分配完之后,系统会自动的进行释放,或者说是使用手动的方式进行释放内存,在手动释放内存的时候,我们采用 `delete` 的方式来进行释放,当创建了两个指针数组的时候,在手动释放的时候,要在指针变量前面加上 `[]`,在实例化指针对象的时候,也可以带上参数或者说是不带参数。下面是上述代码的运行结果: ![image-20210114125841211](https://gitee.com/wenzi_D/images4mk/raw/master/20210114125847.png) ## 析构函数 ### 析构函数的引出 上述我们知道,在函数运行完之后,用 new 分配到的空间才会被释放掉,那么如果是在函数调用里用 new 获取到的空间会随着函数调用的结束而释放么,我们现在来做这样一个实验,把上述中的代码中的主函数写成 `test()`函数,然后在 `main()` 函数里调用。 代码如下所示: ```cpp #include
#include
#include
using namespace std; class Person { private: char *name; int age; char *work; public: Person(){cout << "person()" << endl;} Person(char *name,int age, char *work) { cout << "Person(char *,int, char *)" << endl; this->name = new char[strlen(name) + 1]; strcpy(this->name,name); this->age = age; this->work = new char[strlen(work) + 1]; strcpy(this->work,work); } void printInfo(void) { //cout << "name is:" << name << ",age is:" << age << ",work is:" << work << endl; } }; void test(void) { Person per("zhangsan",18,"teacher"); Person per2; Person *per4 = new Person; Person *per5 = new Person(); /@@* 这两种方式定义的效果是一样的 */ Person *per6 = new Person[2]; Person *per7 = new Person("lisi", 18,"doctor"); per.printInfo(); per7->printInfo(); delete per4; delete per5; delete []per6; delete per7; } int main(int argc, char **argv) { for (int i = 0; i < 1000000; i++) test(); cout << "run test end" << endl; sl
eep(10); return 0; } ``` 这是运行前的空闲内存的大小: ![image-20210114133025365](https://gitee.com/wenzi_D/images4mk/raw/master/20210114133025.png) 紧接着是函数运行完 100 0000 次的 `test` 函数之后的空闲内存大小: ![image-20210114133140216](https://gitee.com/wenzi_D/images4mk/raw/master/20210114133140.png) 然后,是主函数运行完之后,推出主函数之后,空闲的内存剩余量: ![image-20210114133241325](https://gitee.com/wenzi_D/images4mk/raw/master/20210114133241.png) 总结以下就是,在子函数里用 new 分配给局部变量的空间,具体来说在上述代码中的体现就是用 `new`给 `this->name`分配的空间。也就是在主函数没有运行完是不会被释放掉的,也就是说只有在主函数运行完之后,子函数里用 new 分配的空间才会被释放掉,因此,如果想要在子函数调用完之后就释放掉用 new 分配的空间,就需要编写代码来实现。而这个操作, C++ 提供了析构函数来完成,下面是使用析构函数来进行释放内存的代码: ```cpp #include
#include
#include
using namespace std; class Person { private: char *name; int age; char *work; public: Person(){cout << "person()" << endl;} Person(char *name,int age, char *work) { cout << "Person(char *,int, char *)" << endl; this->name = new char[strlen(name) + 1]; strcpy(this->name,name); this->age = age; this->work = new char[strlen(work) + 1]; strcpy(this->work,work); } ~Person() { if (this->name) delete this->name; if (this->work) delete this->work; } void printInfo(void) { //cout << "name is:" << name << ",age is:" << age << ",work is:" << work << endl; } }; void test(void) { Person per("zhangsan",18,"teacher"); Person per2; Person *per4 = new Person; Person *per5 = new Person(); /@@* 这两种方式定义的效果是一样的 */ Person *per6 = new Person[2]; Person *per7 = new Person("lisi", 18,"doctor"); per.printInfo(); per7->printInfo(); delete per4; delete per5; delete []per6; delete per7; } int main(int argc, char **argv) { for (int i = 0; i < 1000000; i++) test(); cout << "run test end" << endl; sl
eep(10); return 0; } ``` 下述就是代码运行之前,和主函数在休眠的时候的剩余内存的容量,可以看出,剩余内存的容量是一样的,换句话说,也就是在 `test()`函数运行完成之后,用 new 分配的空间就已经被释放掉了,就算执行了 1000000 次也没有造成内存泄漏。这也说明了我们的析构函数是有作用的。 ![image-20210115130212394](https://gitee.com/wenzi_D/images4mk/raw/master/20210115130214.png) ### 析构函数在什么地方被调用 上述析构函数的存在避免了内存泄漏,那么析构函数是在什么时候被调用的呢,用一句话描述就是:**在实例化对象被销毁的前一瞬间被调用的**,另外还要注意的是构造函数可以有很多个,有参的,无参的构造函数,但是对于析构函数来讲,它只有一个,并且它是无参的。具体的来看如下所示的代码,在刚才那段代码的基础上,我们添加一些打印信息,从而推断我们析构函数调用的位置: ```cpp #include
#include
#include
using namespace std; class Person { private: char *name; int age; char *work; public: Person() { name = NULL; work = NULL; } Person(char *name,int age, char *work) { this->name = new char[strlen(name) + 1]; strcpy(this->name,name); this->age = age; this->work = new char[strlen(work) + 1]; strcpy(this->work,work); } ~Person() { cout << "~Person()" << endl; if (this->name) { delete this->name; cout << "The name is:" << name << endl; } if (this->work) { delete this->work; cout << "The work is:" << work << endl; } } void printInfo(void) { //cout << "name is:" << name << ",age is:" << age << ",work is:" << work << endl; } }; void test(void) { Person per("zhangsan",18,"teacher"); Person *per7 = new Person("lisi", 18,"doctor"); delete per7; } int main(int argc, char **argv) { test(); return 0; } ``` 我们来看输出的结果: ![image-20210115132418481](https://gitee.com/wenzi_D/images4mk/raw/master/20210115132418.png) 通过上面的输出结果可以知道,先输出的是`lisi`,后输出的是 `zhangsan`,而在实例化对象的时候,是先创建的 per 对象,并初始化为 `zhangsan`,后创建的 per7 对象,并初始化为 `lisi`,再调用析构函数的时候顺序却是颠倒过来的。因此,总结以下就是: per 这个实例化对象是在 `test()`函数执行完之后,再调用的析构函数,而对于 `per7`对象来说,是在执行 `delete per7`这条语句之后调用的析构函数,所以也就有了上述的输出结果。 另外,引出一点,如果我们在上述的代码中把`delete per7`这条语句给注释掉,那么会怎么样呢,下图是去掉该语句之后的结果: ![image-20210115133215468](https://gitee.com/wenzi_D/images4mk/raw/master/20210115133215.png) 我们看到,上述就只执行了 `zhangsan`的析构函数,并没有执行` lisi `的析构函数,这也告诉我们,**在使用 new 创建的实例化对象,必须使用 delete 将其释放掉,如果没有使用 delete 来将其释放,那么在系统退出之后,会自动地释放掉它地内存,但是这个时候是不会调用它地析构函数的**。 最后,关于构造函数和析构函数,如果类里没有实现任何构造函数和析构函数,那么其系统本身会调用一个默认的构造函数和析构函数。那么,除了默认的构造函数和默认的析构函数,还存在一个默认的拷贝构造函数,接下来,来叙述这个拷贝构造函数。 ## 拷贝构造函数 ### 默认拷贝构造函数 我们直接来看这样一段代码: ```cpp #include
#include
#include
using namespace std; class Person { private: char *name; int age; char *work; public: Person() {//cout <<"Pserson()"<
name = new char[strlen(name) + 1]; strcpy(this->name, name); this->work = NULL; } Person(char *name, int age, char *work = "none") { //cout <<"Pserson(char*, int)"<
age = age; this->name = new char[strlen(name) + 1]; strcpy(this->name, name); this->work = new char[strlen(work) + 1]; strcpy(this->work, work); } ~Person() { cout << "~Person()"<
name) { cout << "name="<
name; } if (this->work) { cout << "work="<
work; } } void printInfo(void) { //printf("name=%s, age=%d, work=%s\n", name, age, work); cout<<"name="<
#include
#include
using namespace std; class Person { private: char *name; int age; char *work; public: Person() {//cout <<"Pserson()"<
name = new char[strlen(name) + 1]; strcpy(this->name, name); this->work = NULL; } Person(char *name, int age, char *work = "none") { cout <<"Pserson(char*, int)"<
age = age; this->name = new char[strlen(name) + 1]; strcpy(this->name, name); this->work = new char[strlen(work) + 1]; strcpy(this->work, work); } Person(Person &per) { cout <<"Pserson(Person &per)"<
age = per.age; this->name = new char[strlen(per.name) + 1]; strcpy(this->name, per.name); this->work = new char[strlen(per.work) + 1]; strcpy(this->work, per.work); } ~Person() { cout << "~Person()"<
name) { cout << "name="<
name; } if (this->work) { cout << "work="<
work; } } void printInfo(void) { //printf("name=%s, age=%d, work=%s\n", name, age, work); cout<<"name="<
本节所涉及的代码可以通过百度云链接的方式获取到,链接:https://pan.baidu.com/s/1v4EI2_RCt0aCfdShyaUKgg > 提取码:dcnq
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
0
)
wenzi 嵌入式软件
关注
评论
(0)
登录后可评论,请
登录
或
注册
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交