MENU

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

• March 4, 2021 • Read: 5235 • 学习笔记,C++

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 未授权禁止转载

版权属于:字节星球 / 肥柴之家 (转载请联系作者授权)
原文链接:https://www.bytecho.net/archives/1710.html
本作品采用知识共享署名 - 非商业性使用 - 相同方式共享 4.0 国际许可协议进行许可。

Last Modified: May 23, 2024
Archives QR Code
QR Code for this page
Tipping QR Code
Leave a Comment

6 Comments
  1. u28h9nG u28h9nG IP属地:广东     Windows    Google Chrome

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

    1. Henry Henry     Linux    Google Chrome

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

    2. u28nh9nG u28nh9nG IP属地:广东     Windows    Google Chrome

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

    3. u28nh9nG u28nh9nG IP属地:广东     Windows    Google Chrome

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

    4. Henry Henry     Windows    Google Chrome

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

    5. u28nh9nG u28nh9nG IP属地:广东     Windows    Google Chrome

      @Henry@(OK)