构造与析构函数

        面向对象编程的整体工作分为2大块,一个是建模(将任务抽象成类)和编写类库(类的方法实体编写),另一个是使用类库来编写主程序完成业务。有些人只负责建模和编写类库,这些都是高手,还有些人是调用现成的类库来编写自己的主任务程序。

c++的四重境界

  • 第一重:语法层面,对语法比较熟悉,会使用c++的语法来建模、编程。
  • 第二重:基于第一重,能使用c++来解决实际问题。我们工作大部分时候在这一重。
  • 第三重:编写库给别人用,出了问题能快速解决,这种人基础比较强,且有一定的框架思维。
  • 第四重:理解c++的语法设计的背后的原因。有自己的独立思考能力,能思考如果让他来设计,也会像c++作者这样来设计,这种已经把c++上升到哲学的境界了,非常恐怖。

构造与析构函数

        构造函数constructor,是用来初始化对象的函数,当我们在栈上定义一个对象或在堆中new一个对象的时候就会自动调用构造函数。构造函数一般用于初始化class的属性、分配class内部需要的动态内存。

        析构函数destructor,是用来销毁对象的工作痕迹,在程序结束时,被自动调用,析构函数一般用来回收构造函数中申请的动态内存,避免造成内存丢失。

        构造和析构函数是c++面向对象编程的一大语言特性,如果我们没有提供构造函数和析构函数,会使用c++默认的构造与析构函数。当然我们也可以显示提供构造函数和析构函数。

构造与析构函数的应用

        默认的构造函数不需要返回值,可以带参也可以不带参,析构函数是不带参数的,构造的默认函数名和类名相同,而析构函数的函数名是类名前面多一个“~”取反符号,示例如下:

class person   //类名
person( )       //默认构造函数,无需写返回值void和形参void
~person( )  //默认的析构函数,无需写返回值void和形参void

        按上示例,将构造和析构函数声明直接写在类中,函数实体可以像其他函数一样写在外面。析构函数不需要重载,而构造函数可以重载(有多个函数名相同的函数,只是形参不同),如下示例:

person();   // person类默认构造函数,显式提供。
person(int a); // person类默认带参构造函数
person  *p = new person ;  //新建对象,自动匹配无参构造函数person();
person  *p = new person(5); //新建对象,自动匹配有参构造函数person(int a);

        通常我们在写代码的时候会建立一个专门建模(创建类)的头文件.hpp,再建一个.cpp文件,用来编写类中的构造、析构函数、及类中的方法实体。就像本文开始提到的,有的人是专门建模和编写类库。那么建模者把这些东西独立成文件,是顺利成章的。

构造与析构之动态内存

析构函数的使用

        析构函数在对象被销毁时自动被调用。一般有两种情况,一个是我们的对象在栈上,当函数执行完成,系统自动回收栈内存时调用析构函数,另一个是在堆上new了一个对象,程序结束时使用delete释放堆内存时调用析构函数。在一般情况下,析构函数是空的,因为啥也不用做。只有我们在类中申请了动态内存,函数结束时需要释放,那么就可以放在析构函数中去执行。要注意的是:

  • 释放单个类型对象可以直接使用delete xxx  (xxx代表指针变量)
  • 释放一个数组使用delete[ ] xxx (xxx代表指针变量)

        实际上我们工作中很少使用上面两种方式来申请动态内存,更多的时候是使用vector,后面再来讨论。

构造函数的细节

        构造函数的一大功能就是初始化成员变量,然而在初始时有以下细节要注意。

  • 默认的构造函数是无参的,所以无法初始化。
  • 一个对象,若无自定义的带参构造函数,那么默认的构造函数可以省略,c++会为我们免费提供。
  • 如果我们写了带参函数,那么c++将不会再为我们提供不带参的构造函数,此时如果去调用默认的不带参的构造函数,编译器将报错。
  • 当我们把对象定义栈上,定义对象时,如果不带参数,后面不能有括号( ),而要带参初始化则需要括号。而如果new在堆上,则都可以有括号,这一点确实恶心,如下示例:
class person dai;  //定义一个类(简写)
person dai(); //错误语法,栈上无参,不能有括号
person *hdz = new person(); //new时可以带括号
person *hdz = new person; //new也可以不带括号

valgrind工具查看内存泄漏

        之所以c++难,其实主要是我们要对内存操心,当我们程序太大后内存申请后容易忘记释放,导致程序吃内存,所以检测程序是否有内存泄漏比较重要,在此介绍一个工具来做内存检测valgrind。

valgrind工具介绍

        Memcheck是valgrind应用最广泛的内存检查器,能够发现开发中绝大多数内存错误使用情况,除了内存检查还包了以下功能:

  • Callgrind—用于检查程序中函数调用过程中出现的问题。
  • Cachegrind—用于检查程序中缓存使用出现的问题。
  • Helgrind—用于检查多线程程序中出现的竞争问题。
  • Massif—用于检查程序中堆栈使用中出现的问题。

安装valgrind

命令:sudo apt-get install valgrind

memcheck使用

  • 编译:重新编译要检查的源码,编译时添加-g生成dbug版本目标文件。
g++ person.cpp main.cpp -g -o apptest
  • 检查内存:实际上该工具就是对代码运行过程的内存申请和释放统计。./后面跟被检查的可执行文件,
valgrind --tool=memcheck --leak-check=full --show-reachable=yes --trace-children=yes ./app

成员初始化列表

        当我们要给对象内的变量赋初值时可以在构造函数中处理,如下:

person::person(string n,int a,bool s)
{
this->name = name;
this->age = a;
this->sex = s;
}
person p1(“hu”,32,1);//定义一个person类型的变量p1,并传参给构造函数。

这种方式也可以,但c++为我们提供了更简单的方法“成员初始化列表”,示例如下:

person::person(string n,int a,bool s):name(n),age(a),sex(s)  //实体函数带参数列表

上例中,在函数后面添加“:”来表达后面的是参数列表,多个参数用“,”隔开,而定义在类中的函数声明不能有参数列表。

构造函数默认值

        在class定义中函数声明可以给默认值,当没有传参时编译器就会使用默认值,但是函数实体不能有默认值,示例如下:

class person
    {
    public:  
    person(string n="lilei",int a=6,bool s=1); //构造函数声明时给默认值
    };

        有部分默认值时,默认值不能写在前面,必须写后面,否则默认值没有意义,如示例一,因为构造函数person形参a没有默认值,所以在调用时a必须传参,而a在后面,要想给a传参,必须传2个参数,前一个默认值则被覆盖,这样默认值就失去了意义。正确的写法如示例二,在调用时传1个参数就能对a赋值,而n使用默认值,传2个参数,都覆盖。

示例一:person(string n="lilei",int a); //错误

示例二:person(int a , string n="lilei" ); //正确

调用歧义

        函数声明带默认值时,容易出现调用歧义,需要注意,如示例一, person的声明1片顶3片,如示例二,无论是我们怎么调用,都可以重载调用到示例一。所以当我们同时出现示例二种的任意2个调用,都会相互冲突,编译不知道该与谁匹配,就会报错。所以很多时候为了避免歧义,干脆都不带默认值。

示例一:person(string n="lilei",int a=6,bool s=1);//默认全带参构造函数

示例二:person( );
        person(“han” );
        person(“han”,10 );
        person(“han”,10,flas ),

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值