当前位置:首页 > 公众号精选 > wenzi嵌入式软件
[导读]

前言

在上一则教程中,通过与 C 语言相比较引出了 C 的相关特性,其中就包括函数重载,引用,this 指针,以及在脱离 IDE 编写 C 程序时,所要用到的 Makefile的相关语法。本节所要叙述的是 C 的另外两个重要的特性,也就是构造函数和析构函数的相关内容,这两部分内容也是有别于 c语言而存在的,也是 c 的一个重要特性。

构造函数

类的构造函数是类的一种特殊的成员函数,它会在每次创建新的对象的时候执行,构造函数的名称和类的名称是完全相同的,并不会返回任何的类型,也不会返回 void。构造函数可以用于为某些成员变量设置初始值。

比方说,我们现在有如下所示的一段代码:

#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
上述构造函数并没有参数,实际上在构造函数是可以具有参数的,具体的看如下所示的代码:

#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
可以看到调用构造函数的顺序是和实例化对象的顺序是一致的。

构造函数除了可以有形参,也可以有默认的形参,比如说下面这段代码:

#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
在实例化对象的时候,我们也可以通过定义指针的形式实现,下面代码是上述代码的一个改进,并且以指针的形式实例化了对象,代码如下所示:

#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

析构函数

析构函数的引出

上述我们知道,在函数运行完之后,用 new 分配到的空间才会被释放掉,那么如果是在函数调用里用 new 获取到的空间会随着函数调用的结束而释放么,我们现在来做这样一个实验,把上述中的代码中的主函数写成 test()函数,然后在 main() 函数里调用。

代码如下所示:

#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;
    sleep(10);
    return 0;
}
这是运行前的空闲内存的大小:

image-20210114133025365
紧接着是函数运行完 100 0000 次的 test 函数之后的空闲内存大小:

image-20210114133140216
然后,是主函数运行完之后,推出主函数之后,空闲的内存剩余量:

image-20210114133241325
总结下就是,在子函数里用 new 分配给局部变量的空间,具体来说在上述代码中的体现就是用 new给 this->name分配的空间。也就是在主函数没有运行完是不会被释放掉的,也就是说只有在主函数运行完之后,子函数里用 new 分配的空间才会被释放掉,因此,如果想要在子函数调用完之后就释放掉用 new 分配的空间,就需要编写代码来实现。而这个操作, C 提供了析构函数来完成,下面是使用析构函数来进行释放内存的代码:

#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;
    sleep(10);
    return 0;
}
下述就是代码运行之前,和主函数在休眠的时候的剩余内存的容量,可以看出,剩余内存的容量是一样的,换句话说,也就是在 test()函数运行完成之后,用 new 分配的空间就已经被释放掉了,就算执行了 1000000 次也没有造成内存泄漏。这也说明了我们的析构函数是有作用的。

image-20210115130212394

析构函数在什么地方被调用

上述析构函数的存在避免了内存泄漏,那么析构函数是在什么时候被调用的呢,用一句话描述就是:在实例化对象被销毁的前一瞬间被调用的,另外还要注意的是构造函数可以有很多个,有参的,无参的构造函数,但是对于析构函数来讲,它只有一个,并且它是无参的。具体的来看如下所示的代码,在刚才那段代码的基础上,我们添加一些打印信息,从而推断我们析构函数调用的位置:

#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
通过上面的输出结果可以知道,先输出的是lisi,后输出的是 zhangsan,而在实例化对象的时候,是先创建的 per 对象,并初始化为 zhangsan,后创建的 per7 对象,并初始化为 lisi,再调用析构函数的时候顺序却是颠倒过来的。因此,总结下就是:

per 这个实例化对象是在 test()函数执行完之后,再调用的析构函数,而对于 per7对象来说,是在执行 delete per7这条语句之后调用的析构函数,所以也就有了上述的输出结果。

另外,引出一点,如果我们在上述的代码中把delete per7这条语句给注释掉,那么会怎么样呢,下图是去掉该语句之后的结果:

image-20210115133215468
我们看到,上述就只执行了 zhangsan的析构函数,并没有执行lisi的析构函数,这也告诉我们,在使用 new 创建的实例化对象,必须使用 delete 将其释放掉,如果没有使用 delete 来将其释放,那么在系统退出之后,会自动地释放掉它地内存,但是这个时候是不会调用它地析构函数的

最后,关于构造函数和析构函数,如果类里没有实现任何构造函数和析构函数,那么其系统本身会调用一个默认的构造函数和析构函数。那么,除了默认的构造函数和默认的析构函数,还存在一个默认的拷贝构造函数,接下来,来叙述这个拷贝构造函数。

拷贝构造函数

默认拷贝构造函数

我们直接来看这样一段代码:

#include 
#include 
#include 

using namespace std;

class Person {
private:
    char *name;
    int age;
    char *work;

public:

    Person() {//cout <<"Pserson()"<
        name = NULL;
        work = NULL;
    }
    Person(char *name) 
    {
        //cout <<"Pserson(char *)"<
        this->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)"<
        this->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()"<<endl;
        if (this->name) {
            cout << "name = "<endl
;
            delete this->name;
        }
        if (this->work) {
            cout << "work = "<endl
;
            delete this->work;
        }
    }

    void printInfo(void)
    
{
        //printf("name = %s, age = %d, work = %s\n", name, age, work); 
        cout<<"name = "<", age = "
<", work = "<endl;
    }
};

int main(int argc, char **argv)
{
    Person per("zhangsan"18);
    Person per2(per);

    per2.printInfo();

    return 0;
}
在主函数的第二行代码中,我们可以看到我们创建了一个实例,并且传入的参数是 per,但是我们看类里面的代码实现,并没有发现有一个构造函数的形参为 Person ,那这个时候,会发生什么函数调用呢,实际上是会调用一个系统的默认构造函数,这个默认的构造函数会进行值拷贝,会将 per中的内容拷贝到 per2中去,下图是这个过程的一个示意图:

image-20210117015212259.png
通过上图可以看到,在执行默认的拷贝构造函数的时候,执行的是值拷贝,那么相应的,per 的 name 也就指向了 address1,per2 的 name 同样也指向了 adress,从而完成了值拷贝的过程,下面是代码运行的结果:

image-20210117015527675
可以看到,在输出 per2 的内容的时候,输出的是 per 的初始化内容,在主函数运行完之后,就要执行析构函数来释放使用 new 分配的空间,首先是释放 per 的内容,然后紧接着是释放 per2的内容,但是在刚刚的叙述中,使用默认构造函数进行拷贝的时候,使用的是值拷贝,从而造成的效果是 per2 的 name 和 work 指向的地址是 per 中的同一块地址,这样,在执行析构函数的时候,同一块内存空间就会被释放两次,从而导致错误。因此,使用默认的拷贝构造函数存在一定的问题,也就需要我们自己来定义拷贝构造函数,下面介绍自定义的拷贝构造函数。

自定义拷贝构造函数

我们根据在上述代码的基础上,修改得到我们自定义的拷贝构造函数如下:

#include 
#include 
#include 

using namespace std;

class Person {
private:
    char *name;
    int age;
    char *work;

public:

    Person() {//cout <<"Pserson()"<
        name = NULL;
        work = NULL;
    }
    Person(char *name) 
    {
        //cout <<"Pserson(char *)"<
        this->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)"<<endl;
        this->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
本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

2023年10月18日,中国在第三届“一带一路”国际合作高峰论坛期间发布《全球人工智能治理倡议》,围绕人工智能发展、安全、治理三方面系统阐述了人工智能治理中国方案。

关键字: 人工智能 大模型 代码

学好电子技术基础知识,如电路基础、模拟电路、数字电路和微机原理。这几门课程都是弱电类专业的必修课程,学会这些后能保证你看懂单片机电路、知道电路的设计思路和工作原理;

关键字: 单片机 编程 电路设计

单片机编程需要使用专门的软件工具,这些工具能够帮助程序员编写、调试和烧录程序到单片机中。以下是一些常用的单片机编程软件:

关键字: 单片机 编程 软件工具

我们看到这么多的安全问题,部分原因在于我们对待安全的方式:安全性通常被认为是事后考虑的问题,是在开发结束时才添加到设备上的东西。然而,复杂的系统,尤其是嵌入式系统,有一个很大的攻击面,这让攻击者有机可乘,能够在“盔甲”上...

关键字: 代码 嵌入式系统 软件漏洞

Java语言和C语言是两种不同的编程语言,它们在语法、特性和应用领域上有许多差别。下面将详细介绍Java语言和C语言之间的差异以及它们各自的技术特点。

关键字: Java语言 C语言 编程

嵌入式系统是一种专门设计用于特定应用领域的计算机系统,它通常由硬件和软件组成,并且被嵌入到其他设备或系统中,以实现特定的功能。在嵌入式系统的开发过程中,选择适合的编程语言是至关重要的。C语言是一种被广泛应用于嵌入式系统开...

关键字: 嵌入式 计算机 C语言

C语言是一种广泛应用于软件开发领域的编程语言。它是由贝尔实验室的Dennis Ritchie在20世纪70年代初创建的,旨在为UNIX操作系统的开发提供一种高级编程语言。C语言具有简洁、高效、可移植性强等特点,因此成为了...

关键字: C语言 操作系统 应用程序

嵌入式系统是现代生活中无处不在的一部分。它们包括了我们的家电、汽车、智能手机、医疗设备等等。这些系统的工作必须高效、可靠,因为它们往往控制着生活中的关键方面。而C语言作为一种广泛用于嵌入式系统开发的编程语言,其质量和稳定...

关键字: 嵌入式系统 C语言 编程

在嵌入式系统开发领域中,C语言是使用最广泛的编程语言之一。它具有高效、灵活和可移植的特点,成为嵌入式系统设计师的首选语言。本文将介绍C语言编程的基本概念、特点以及在嵌入式系统开发中的应用。

关键字: 嵌入式系统 C语言 编程

C语言编译器是一种用于将C语言源代码转换为可执行程序的软件工具。它的主要功能是将C语言代码翻译成机器语言,以便计算机能够理解和执行。C语言编译器通常包括预处理器、编译器、汇编器和链接器等多个组件,它们协同工作以完成编译过...

关键字: C语言 编译器 Microsoft Visual C++
关闭
关闭