C++类和动态内存分配

动态内存和类

如果有这样的strbad类:

#include<iosream>
#ifdef STRBAD_H_
#define SREBAD_H_
class strbad{
  private:
    char * str;
    int len;
    static int num;
  public:
    strbad(const char *s);
    strbad();
    ~strbad();
    friend std::ostream & operator<<(std::ostream &os,const strbad &st);
  }
#endif
#include<cstring>
#include"stringbad.h"
using std::cout;

int strbad::num=0;//静态成员变量不能在声明中初始化
//除非静态变量是const或者枚举型

strbad::strbad(const char *s){
  len=std::strlen(s);
  str=new char[len+1];//str是指针,因此必须提供内存
  std::strcopy(str,s);//不能用str=s,这样的话没有创建副本,而只是复制了指针
  num++;
}

strbad::strbad(){
  len=4;
  str=new char[4];
  std::strcopy(str,"C++");
  num++;
}

strbad::~strbad(){
  num--;
  delete[] str;
  //new对应delete,new[]对应delete[]
}


std::ostream & operator<<(std::ostream &os,const strbad &st){
  os<<st.str;
  return os;
}

看似设计没有什么问题,但下面的语句证明它其实大有问题:

{
strbad a=srebad("a");
call(a);
cout<<a;//事实证明在这里a已经被析构了
strbad b=a;
}
cout<<strbad::num;//发现num为负数

void call(strbad s);//这里是值传递

所有的原因在于忽略了类的复制构造函数。

strbad(const strbad &);

编译器自动生成的复制构造函数不知道如何处理num,导致num被弄乱了。

下面一些新建对象的情况会导致调用复制构造函数:

strbad ditto(motto);
strbad metto=motto;
strbad also=strbad(motto);
strbad * p=new strbad(motto);

当程序生成了对象副本时,会调用复制构造函数。具体比如:当函数按值传递对象(call方法)或函数返回对象时,都将使用复制构造函数。因此我们应该尽可能按引用传递对象。

默认复制构造函数逐个复制非静态成员,复制的是成员的值。

因此我们可以发现,在调用call方法时,s被a赋值,但由于类中存储的值为指针类型,因此使用在调用s的析构函数时会把s的指针(同样是a的指针)指向的动态内存释放,因此我们会发现a的动态内存已经被delete了。

我们可以定义一个显式复制构造函数:

strbad:strbad(const strbad & st){
  num++;
  len=st.len;
  str=new char[len+1];
  std::strcpy(str,st.str);
}

有些类成员是使用new初始化的、指向数据的指针,而不是数据本身,因此我们需要深度复制(改写默认复制构造函数)。

当然我们也要重载赋值运算符。

strbad b;
b=a;//不会触发复制构造函数,需要重载=

有以下注意点:
(1)目标对象可能引用了以前分配的数据,函数应使用delete来释放它们。
(2)函数应当避免将对象赋给自身,否则可能会导致在赋值前删除了自身的内容。
(3)函数返回一个指向调用对象的引用。

strbad & strbad::operator=(const strbad& st){
  if (this==&st)  return *this;
  delete[] str;
  len=st.len;
  str=new char[len+1];
  std::strcpy(str,st.str);
  return *this;
}

如果赋值为str=0,这说明str为空指针,此时使用delete[] str是可行的。

可以将成员函数声明为静态的,这样就不能通过对象调用静态成员函数,甚至不能用this,静态成员函数也不与特定的对象相关联,因此只能使用静态数据成员。

构造函数中new的注意事项

有如下注意点:
(1)如果在构造函数中用new,应该在析构函数中用delete。
(2)new和delete必须兼容,new对应delete,new[]对应delete[]
(3)如果有多个构造函数,则必须用相同的方法new,要么都带中括号,要么都不带,因为只有一个析构函数。delete(无论有没有中括号)都可以用于空指针(0,NULL)
(4)应定义一个复制构造函数。
(5)应重载一个赋值运算符。

对于定位new运算符,有如下注意点:

pc1=new(buffer) strbad;
pc2=new(buffer) strbad;//no
pc2=new(buffer+sizeof(strbad)) strbad("aaa");//yes

再执行buffer的delete操作前,需要显式地调用析构函数:

pc1->~strbad();
pc2->~strbad();
delete[] buffer;//这样才算完全销毁

有关返回对象的说明

1.返回指向const对象的引用

如果函数返回传递给它的对象,可以通过返回引用来提高效率,因为返回引用不会调用复制构造函数。而且,引用指向的对象应该在调用函数执行时存在。除此以外,返回的类型必须为const才能匹配。

2.返回指向非const对象的引用

两种常见的情形是,重载赋值运算符和cout一起使用的<<。前者是提高效率,后者是必须那么做。

3.返回对象

如果被返回的对象是被调用函数中的局部变量,则不应按引用方式返回它。此时将使用复制构造函数来生成返回的对象。

4.返回const对象

net=force1+force2;
force1+force2=net;//返回const对象将不允许那么做

使用const能够防止那样奇怪的错误,当然返回的对象将会是const的。

猜你喜欢

转载自blog.csdn.net/dxy18861848756/article/details/113771505