• C++中栈和堆上建立对象的区别


    在C++中类的对象建立分为两种,一种是静态建立,如A a;另一种是动态建立,如A* p=new A(),A*p=(A*)malloc();静态建立一个类对象,是由编译器为对象在栈空间中分配内存,通过直接移动栈顶指针挪出适当的空间,然后在这片内存空间上调用构造函数形成一个栈对象。动态建立类对象,是使用new运算符将对象建立在堆空间中,在栈中只保留了指向该对象的指针。栈是由编译器自动分配释放 ,存放函数的参数值,局部变量的值,对象的引用地址等。其操作方式类似于数据结构中的栈,通常都是被调用时处于存储空间中,调用完毕立即释放。堆中通常保存程序运行时动态创建的对象,C++堆中存放的对象需要由程序员分配释放,它存在程序运行的整个生命期,直到程序结束由OS释放。而在java中通常类的对象都分配在堆中,对象的回收由虚拟机的GC垃圾回收机制决定。

    1.下面的程序来看看静态建立和动态建立对象的区别

     1 #include<iostream>
     2 #include<string>
     3 using namespace std;
     4 class  student
     5 {
     6 public:
     7     string name;
     8     int age;
     9     void sayhello();
    10 };
    11 void student::sayhello()
    12 {
    13     cout<<"my name is: "+this->name+" I am: "<<this->age;
    14     cout<<"\n";
    15 }
    16 student setname(string name)
    17 {
    18     student stu;
    19     stu.age=12;
    20     stu.name=name;
    21     return stu;
    22 }
    23 int main()
    24 {
    25     student stu=setname("jim");
    26     stu.sayhello();
    27     return 0;
    28 }

    程序运行结果:my name is: jim I am: 12;

    程序定义了一个student类,在setname函数中定义一个局部对象作为返回值。程序第18行静态构建了一个student对象stu,它在栈上分配空间,在函数调用结束后就销毁了,函数返回的类对应在内存中的值应该不存在啊?其实原来C++在用类作为函数的返回值时调用了类的拷贝构造函数,而且该拷贝构造函数是在堆上分配存储空间,后面再讨论这个问题。

    在setname函数内的stu在函数调用结束后就销毁了,可以添加一个析构函数来证明:

    在student类中加入析构函数:

    student::~student()
    {
        cout<<this->name<<":gameover"<<endl;
    }

    程序运行结果:

    在sayhello()前,输出jim:gameover,即为setname()里的stu对象执行了析构函数。

    如将setname函数改为:

    student* setname(string name)
    {
        student stu;
        stu.age=12;
        stu.name=name;
        return &stu;
    }

    main函数的调用改为:

    int main()
    {
        student* p=setname("tom");
        p->sayhello();
        return 0;
    }

    显然这里会出现问题,对象指针返回的是栈上的对象,在函数调用结束后已经销毁了,对象指针即为野指针,故程序在编译时会提示:warning C4172: returning address of local variable or temporary。解决这个问题我们自然想到把该对象构建在堆上即可。修改setname函数为下:

     student* setname(string name)
     {
    student* stu= new student();
    stu->age=12;
    stu->name=name;
    return  stu;
     }

    main函数的调用不变;程序正常运行输出:

    上面输出结果并没有调用析构函数,在setname调用后,在main函数结束后也没有调用。在对上的对象需要程序员自己delete释放,将main改为如下:

    int main()
    {
        student* p=setname("tom");
        p->sayhello();
        delete p;
        return 0;
    }

    即加入delete p;运行结果:

    C中用malloc函数来动态申请空间,该内存分配在堆上。这里可以验证,加入#include <malloc.h>,将setname函数改为如下:

    1  student* setname(string name)
    2  {
    3 student* stu=(student*)malloc(sizeof(student));
    4 stu->age=12;
    5 stu->name=name;
    6 return  stu;
    7  }

    为在student中加入构造函数:

    student::student()
    {
        cout<<"constructor"<<endl;
    }

    上面的程序执行到第5行会出错,原因是没有调用构造函数,stu->name根本就没有被初始化(string的构造函数没有被调用),所以不能赋值 。具体的解释是: 因为malloc只是分配堆内存(不会调用构造函数)它并不知道内存里要存的是什么。为此用new即可,将第3行代码改为:student* stu=new student;程序运行结果为下:

    即程序调用了构造函数。若非要用malloc来申请内存可以将setname函数改为如下:

    1  student* setname(string name)
    2  {
    3 student* stu=(student*)malloc(sizeof(student));
    4 new(stu) student;
    5 stu->age=12;
    6 stu->name=name;
    7 return  stu;
    8  }

    即加入了第4行程序正常运行,调用了构造函数。第4行大概可以理解为new了一个student对象,赋值转换为student的指针stu。
    既然这样可以把第3行直接改为:student* stu;即

    1  student* setname(string name)
    2  {
    3 //student*stu= new student;
    4 student* stu;
    5 new(stu) student;
    6 stu->age=12;
    7 stu->name=name;
    8 return  stu;
    9  }

    让说第4,5行的效果应该和第3行相同,编译程序提示:warning C4700: local variable 'stu' used without having been initialized,即stu没有初始化。 new(stu) student;和stu= new student;并不等价。第5行并不是初始化,这里可以看做第5行这种写法(之前还真没见过)是C++为兼容malloc内存申请的用法,一般情况下推荐肯定是用new关键字。
    到此其实这里要说明的主题已经基本说明了,关于malloc的用法当申请的类的成员变量只包含基本的数据类型(数值型int,double等)(string等引用类除外)时是不会出错的。下面的列子可以证明;

     1 #include<iostream>
     2 #include<string>
     3 #include <malloc.h>
     4 using namespace std;
     5 class course
     6 {
     7 public:
     8     int id;
     9     float score;
    10     void printscore()
    11     {
    12     cout<<"id:"<<this->id;
    13         cout<<" score:"<<this->score<<endl;
    14     }
    15 };
    16 
    17  course* setscore(int id,float score)
    18  {
    19      course* co= (course*)malloc(sizeof(course));
    20      co->id=id;
    21      co->score=score;
    22      return co;
    23  }
    24 int main()
    25 {
    26 course* cou=setscore(999,188.9);
    27 cou->printscore();
    28     return 0;
    29 }

    程序运行结果如下:

     程序第19行这样的用法没有问题,而之前的string类却有问题。这样看来,自定义类型作为类的成员时也应该会有问题,来看下面的代码:

     1 #include<iostream>
     2 #include<string>
     3 #include <malloc.h>
     4 using namespace std;
     5 class course;
     6 class  student
     7 {
     8 public:
     9     string  name;
    10     int age;
    11         course cou;
    12     void sayhello();
    13     ~student();
    14     student();
    15 };
    16 student::student()
    17 {
    18     cout<<"constructor"<<endl;
    19 }
    20 student::~student()
    21 {
    22     cout<<this->name<<":gameover"<<endl;
    23 }
    24 void student::sayhello()
    25 {
    26     cout<<"my name is: "+this->name+" I am: "<<this->age;
    27     cout<<"\n";
    28 }
    29 class course
    30 {
    31 public:
    32     int id;
    33     float score;
    34     void printscore()
    35     {
    36     cout<<"id:"<<this->id;
    37         cout<<" score:"<<this->score<<endl;
    38     }
    39 };
    40  course* setscore(int id,float score)
    41  {
    42      course* co= (course*)malloc(sizeof(course));
    43      co->id=id;
    44      co->score=score;
    45      return co;
    46  }
    47  student* setname_score(string name ,course* cou)
    48  {
    49 student* stu= (student*)malloc(sizeof(student));
    50 stu->age=12;
    51 new(stu)student;
    52 stu->cou.id=cou->id;
    53 stu->cou.score=cou->score;
    54 stu->name= name;
    55 return  stu;
    56  }
    57 int main()
    58 {
    59 course* cou=setscore(999,188.9);
    60 student* stu=setname_score("jimm",cou);
    61 stu->cou.printscore();
    62 stu->sayhello();
    63 return 0;
    64 }

    这段代码中把course类对象作为student的类成员。程序编译出错: error C2079: 'cou' uses undefined class 'course'。把course类的定义放在前面则没有错即:

     1 #include<iostream>
     2 #include<string>
     3 #include <malloc.h>
     4 using namespace std;
     5 class course
     6 {
     7 public:
     8     int id;
     9     float score;
    10     void printscore()
    11     {
    12     cout<<"id:"<<this->id;
    13         cout<<" score:"<<this->score<<endl;
    14     }
    15 };
    16 class  student
    17 {
    18 public:
    19     string  name;
    20     int age;
    21         course cou;
    22     void sayhello();
    23     ~student();
    24     student();
    25 };
    26 student::student()
    27 {
    28     cout<<"constructor"<<endl;
    29 }
    30 student::~student()
    31 {
    32     cout<<this->name<<":gameover"<<endl;
    33 }
    34 void student::sayhello()
    35 {
    36     cout<<"my name is: "+this->name+" I am: "<<this->age;
    37     cout<<"\n";
    38 }
    39 course* setscore(int id,float score)
    40  {
    41      course* co= (course*)malloc(sizeof(course));
    42      co->id=id;
    43      co->score=score;
    44      return co;
    45  }
    46 student* setname_score(string name ,course* cou)
    47  {
    48 student* stu= (student*)malloc(sizeof(student));
    49 stu->age=12;
    50 new(stu)student;
    51 stu->cou.id=cou->id;
    52 stu->cou.score=cou->score;
    53 stu->name= name;
    54 return  stu;
    55  }
    56 int main()
    57 {
    58 course* cou=setscore(999,188.9);
    59 student* stu=setname_score("jimm",cou);
    60 stu->cou.printscore();
    61 stu->sayhello();
    62 return 0;
    63 }
    View Code

    运行结果为:

    上面程序setname_score函数中若不用new(stu)student;这种写法,则会出现未初始化的错误。这里完全可以将类的成员改为指针的形式,在初始化时用new在堆上分配存储。改写的代码如下:

     1 #include<iostream>
     2 #include<string>
     3 #include <malloc.h>
     4 using namespace std;
     5 class course;
     6 class  student
     7 {
     8 public:
     9     string*  name;
    10     int age;
    11         course* cou;
    12     void sayhello();
    13     ~student();
    14     student();
    15 };
    16 student::student()
    17 {
    18     cout<<"constructor"<<endl;
    19 }
    20 student::~student()
    21 {
    22     cout<<this->name<<":gameover"<<endl;
    23 }
    24 void student::sayhello()
    25 {
    26     cout<<"my name is: "+*(this->name)+" I am: "<<this->age;
    27     cout<<"\n";
    28 }
    29 class course
    30 {
    31 public:
    32     int id;
    33     float score;
    34     void printscore()
    35     {
    36     cout<<"id:"<<this->id;
    37         cout<<" score:"<<this->score<<endl;
    38     }
    39 };
    40 course* setscore(int id,float score)
    41 {
    42      course* co= (course*)malloc(sizeof(course));
    43      co->id=id;
    44      co->score=score;
    45      return co;
    46 }
    47 student* setname_score(string name ,course* cou)
    48 {
    49 student* stu= (student*)malloc(sizeof(student));//student*stu=new student;也一样,只是一个调用构造函数,一个不调用
    50 stu->age=12;
    51 stu->cou=new course();//这里用new建立对象
    52 stu->cou->id=cou->id;
    53 stu->cou->score=cou->score;
    54 stu->name=new string(name);//new
    55 return  stu;
    56  }
    57 int main()
    58 {
    59 course* cou=setscore(999,188.9);
    60 student* stu=setname_score("jimm",cou);
    61 stu->cou->printscore();
    62 stu->sayhello();
    63 return 0;
    64 
    65 }
    View Code

    上面程序运行结果为:

    综上所述,C++中对象的建立可以在堆和栈上。分别为动态建立和动态建立的方式,构建堆上的对象时一般使用new关键字,而对象的指针在栈上。使用new在堆上构建的对象需要主动的delete销毁。C++对象可以在堆或栈中,函数的传参可以是对象(对象的拷贝),或是对象的指针。而在java中对象一般分配在堆上,对象的传值只有值类型,即对象的引用(地址),这样看来C++要灵活的多。关于c++数组的内存分配还有这里提到的拷贝构造函数,下次再讨论啊。上面的程序在VC++6.0编写通过。

  • 相关阅读:
    JavaScript的数据类型
    伪元素和伪类的区别是什么?
    伪元素::before和::after的详细介绍
    Ksoap2 获取webservice返回值的getResponse() 出现的问题
    解决dropdownlist postback 在 iphone UIwebview 失效的问题
    javascript雪花效果 注释版
    金山词霸每日一句开放平台 .NET demo
    【摘抄】Application.StartupPath和System.Environment.CurrentDirectory的区别
    EditText 监听回车事件 避免2次触发
    【代码】ini 文件读取工具类
  • 原文地址:https://www.cnblogs.com/xiaoxiaoqiang001/p/5557704.html
Copyright © 2020-2023  润新知