MENU

C++:堆与拷贝构造函数及补充this指针

• March 4, 2021 • Read: 472 • 高级语言,学习笔记

C++ 内存区

C++ 内存通常分为 4 个区域:

  1. 全局数据区(data area);
  2. 代码区(code area);
  3. 堆区(自由存储区)(heap area)
  4. 栈区(stack area);

在 C 语言中,我们通过 malloc 或者 calloc 申请的空间即为堆区的空间,使用完成后用 free 归还申请的内存;而在 C++ 中我们用 new 申请堆区内存,delete 释放内存。操作堆内存时,有借有还,分配了堆内存就要记得对其进行回收,当然,这在 C++ 中是一件很麻烦的事情。

new 和 delete

C++ 是面向对象编程语言,类和对象变得尤为重要,也是 C++ 与 C 语言的主要区分标志,在 C++ 中我们不能再依赖 C 语言中的 malloc ()等函数申请内存,其中一个原因是,它不能在分配空间时调用类中的构造函数,然而类对象的建立正是由构造函数来完成的。

而 free()函数也不会调用类中的析构函数,关于构造函数及析构函数详见文章

通过 malloc 申请到的对象空间无非就是一个含有随机数据的类对象空间而已,值不确定,毫无意义,要使得对象有意义我们还需要在后续自行调用构造函数,十分不便,故 C++ 用 new 代替 malloc ()那是必然的。

使用 new 分配堆对象

C++ 的 new 和 deleta 机制简单易懂,以下程序片段演示了堆对象空间的申请

class student  //student类
{
    public:
        //..
    private:
        //..
};
void func(){
    student* p;
    p=new student;//申请堆对象,p指向该对象地址,此时C++自动调用构造函数student()
    //..
    delete p;//释放堆对象的空间,此时C++自动调用析构函数~student()
}

如果需要调用有参构造函数,参考以下程序片段

class Tdate{
    public:
        Tdate(int m,int d,int y);//有参构造函数
        Tdate(){};//无参构造函数
    protected:
        int month;
        int day;
        int year;
};
Tdate::Tdate(int m,int d,int y)
{
    //..
}
void fun(){
    Tdate* p;
    //p=new Tdate();调用无参构造函数的方法
    p=new Tdate(1,1,2021);//调用有参构造函数
    //..
    delete p;
}

delete 与 delete[]的使用

delete 的用法如以上内容,在其后跟上指向需要释放的空间的指针即可;而 delete[]我们通过后面的“[]”就知道这是用于释放数组空间的,如果我们申请的是如下的堆空间

void fun(){
    Tdate* p;
    p=new Tdate[5];//分配5个对象数组空间,此时只能调用默认的无参构造函数
    //..
    delete[] p;//释放这5个对象数组空间
}

delete[]即是告诉 C++ 该指针指向的是一个数组,[]不需要写上数组长度,如果有,C++ 编译器也会将其忽略,但绝不能忘记写[]。

拷贝构造函数

拷贝构造函数,顾名思义,用于拷贝一个对象然后去构造另一个对象的函数,即用一个对象的值去初始化一个新构造的对象,如以下代码片段

student s1("Henry");
student s2=s1;//拷贝对象s1至s2,此时会调用拷贝构造函数

将对象作为函数参数传递时,也涉及对象的拷贝,因为函数调用涉及实参到形参的传递,也就是将实参对象拷贝到形参对象,对象的类型多种多样,很多对象中的数据并不像基本的 int、double 等能够简单的赋值拷贝,比如对象里可能存在用 new 申请的堆空间?那么显然就不能进行简单的赋值拷贝,这也就引出了拷贝构造函数的意义。

以下程序片段演示了如何编写我们需要的拷贝构造函数

class student{
    private:
        string name;
        int id;
    public:
        student(string nName="null",int nId=0)//构造函数
        {
            this->name=nName;
            this->id=nId;
            cout<<"Constructing new student "<<nName<<endl;
}
        student(student& s){//拷贝构造函数,student&为student类对象的引用,引用的内容参考前一篇文章
            cout<<"Constructing copy of "<<s.name<<endl;
            this->name="copy of "+s.name;
            this->id=s.id;
}
        ~student(){
            cout<<"delete "<<this->name<<endl;
}
};
void fun(student s){
    cout<<"In function fun()"<<endl;
}
int main()
{
    student henry("Henry",21);
    cout<<"Calling fun()"<<endl;
    fun(henry);
    cout<<"Returned from fun()"<<endl;
    return 0;
}

运行结果

Constructing new student Henry
Calling fun()
Constructing copy of Henry
In function fun()
delete copy of Henry
Returned from fun()
delete Henry

默认拷贝构造函数

与构造函数类似,当开发者没有定义自己的拷贝构造函数时,C++ 将提供一个默认拷贝构造函数。

其工作方法为:完成一个成员一个成员的简单拷贝。

浅拷贝与深拷贝

浅拷贝即是像默认拷贝构造函数那样对数据成员进行简单的复制,那么如果对象中存在分配的资源(如堆内存)我们就不能在进行简单的浅拷贝,那样会使多个对象拥有同一块内存资源,如果其中一个对象遭到释放,那么其他对象将面临严重的内存堆栈错误,并且,在对象进行析构时,也会多次释放同一块资源,程序崩溃。

以下程序片段演示了深拷贝

class student{
    public:
        student(char* nName);
        student(student& s);
        ~student();
    protected:
        char* name;
};
student::student(cahr* nName){//构造函数
    this->name=new char[strlen(nName)+1];//申请新内存
    if(nName)
        strcpy(this->name,nName);
}
student::student(student& s){//深拷贝构造函数
    this->name=new char[strlen(s.name)+1];//申请新内存
    if(s.name)
        strcpy(this->name,s.name);
}
student::~student(){
    name[0]="\0";
    delete name;
}

无名对象

仅简述无名对象的三种用法

//此程序片段中student为一个类
student& refs=student("Henry");//初始化引用
student s=student("Henry");//初始化对象
fun(student("Henry"));//作为函数参数

【补充】this 指针

this 指针,其实我们看名字可以知道,这个指针肯定是指向与自己相关的,或正在处理的内存空间。

的确如此,一个类中所有对象调用的成员函数都处于同一个代码段,成员函数为了区分数据成员属于哪一个对象,故出现了 this 指针。

如在对象 s 调用成员函数 set()时:s.set(1,1,2021),成员函数 set 除了接受了 3 个实参外,还接受了一个对象的地址(对象 s 的地址),这个地址被隐含的形参 this 指针所获取即 this 相当于&s,所有都数据成员的访问都隐含地被加上了 this->,在本文前面的代码片段中,我特意加上了 this->,方便读者理解。

//以下三种数据成员访问方法等价
month=m;
this->month=m;
s.month=m;

我们在一个成员函数需要返回当前处理的对象或对象的地址时,this 指针就成为了必要,如以下程序片段

student student::fun(){
    //..
    return *this;//返回当前对象
    //return this;//返回指向当前对象的指针
}

编辑:Henry 2021-03-04 未授权禁止转载


Archives QR Code
QR Code for this page
Tipping QR Code
Leave a Comment

6 Comments
  1. u28h9nG u28h9nG     Windows 10 /   Google Chrome

    【浅拷贝与深拷贝】里,为什么说:那么如果对象中存在分配的资源(如堆内存)我们就不能在进行简单的浅拷贝,那样会使多个对象拥有同一块内存资源。不太理解为什么会拥有同一块内存。

    1. Henry Henry     Linux /   Google Chrome

      @u28h9nG你好,因为浅拷贝对于字符数组等仅会复制其内存地址,也就使得你复制出来的新变量依然指向你复制的那个变量的地址。这也是为什么字符数组需要用strcpy复制的原因。其他需要深拷贝的案例也可以这样理解。

    2. u28nh9nG u28nh9nG     Windows 10 /   Google Chrome

      @Henry我知道了,谢谢博主OωO

    3. u28nh9nG u28nh9nG     Windows 10 /   Google Chrome

      @Henry欸,那是不是类的成员变量里没有指针类型的变量,使用浅拷贝就不会发生指向同一块内存的问题了?

    4. Henry Henry     Windows 10 /   Google Chrome

      @u28nh9nG其实,只要理解“拷贝”究竟拷贝的是什么就可以了。

    5. u28nh9nG u28nh9nG     Windows 10 /   Google Chrome

      @Henry@(OK)