Bootstrap

C++ primer Plus 第十二章:类和动态内存分配

12.1动态内存和类

12.1.1复习实例和静态类成员

12.1.2特殊成员函数

首先看这样一个例子

#include <iostream>
#include <cstring>
using namespace std;
class string1
{
private:
    char *str;
    int len;
public:
    string1(const char*);
    string1();
    ~string1();
    void display()
    {
        cout<<str<<endl;
    }
};
string1::string1()
{
   len=0;
   str=new char [1];//这里是为了,和析构函数里的 delete [] 匹配
   str[0]='\0';
}
string1::string1(const char*s)
{
    len=strlen(s);
    str = new char[len+1];
    strcpy(str,s);
}
string1::~string1()
{
    delete [] str;
    cout<<"析构函数被调用"<<endl;
}
string1 fun()
{
    string1 a("111");
    return a;
}
int main()
{
    string1 a=fun();
    a.display();
    return 0;
}

这个例子是错误的,对于类中不带指针类型成员变量的类来说可以使用这种函数返回赋值的方法,但是上面这个例子就不行了,因为在fun函数里,定义了一个局部对象,在离开函数时候,这个局部对象会被析构,所以函数将创建一个临时无名对象,这个临时无名对象会调用一个叫复制构造函数的构造函数,逐个赋值局部对象的非静态成员,它复制的是成员的值。

如果没有定义复制构造函数,程序会调用自己生成的默认复制构造函数

12.1.3使用程序提供的默认复制构造函数存在的问题

存在什么问题呢

这个临时无名对象里的东西跟局部对象一模一样,就连指针也一样,它们指向了同一部分内存,但是函数结束后,析构函数会释放这部分内存,这时候在主函数里访问这部分内存就会出现问题。

如何解决这个问题

很明显我们可以把局部对象里指针指向的那部分内存,赋给另一部分内存,并让临时无名对象的指针指向它,这样函数结束程序调用析构函数时,临时无名对象不会被修改

如何实现这个操作呢

复制构造函数,顾名思义,复制时候使用的构造函数,他只有一个参数,即对象的引用。

string a("123");
string b;
b=a;//将调用复制构造函数

如果没有定义复制构造函数,系统将自动生成一个默认的复制构造函数,它的作用是逐个复制非静态成员,复制的是成员的值,这种拷贝称为浅层拷贝,在本例中它长这个样子:

string1::string1(string1 &p)
{
   str=p.str;
}

可以看出它只是将指针指向的地址给复制了,所以就出现了上面这个问题。

要解决上面这个问题,我们需要手动定义一个复制构造函数

string1::string1(const string1 &p)
{
    len=p.len;
    str=new char[len+1];
    strcpy(str,p.str);
    cout<<"复制构造函数被调用"<<endl;
}

输出结果是

这种拷贝称为深层拷贝

何时调用复制构造函数

1.新建一个对象并将其初始化为同类现有对象时。

如果是赋值则不会调用

2.函数按值传递和返回对象时

3.作为当对象作为函数参数时

 

12.1.4赋值运算符

 

上述调用复制构造函数的时候不包括非初始化赋值的时候,也就是说当我们使用下述代码时,还是会发生浅层拷贝。

string1 a("123");
string1 b;
b = a;

可以通过重载=来实现深层拷贝

string1& string1::operator=(const string1 &p)
{
    if(this==&p)如果=两边对象地址相同,说明程序正进行自己给自己赋值这种操作,直接返回对象本身即可
    {
        return *this;
    }
    delete[] str;//要释放对象内指针本来指向的内存
    len=p.len;
    str = new char[len+1];
    strcpy(str,p.str);
    cout<<"=重载成功"<<endl;
    return *this;
}

 

返回一个对象的引用的作用是,帮助我们实现a=b=c这种操作。

12.2实现string类的其他功能

这里直接给出代码

#include <iostream>
#include <cstring>
using namespace std;
class string1
{
private:
    char *str;
    int len;
    static const int MAX=100;//最大100个字符
public:
    //构造函数
    string1();//默认构造函数
    string1(const char*);//构造函数
    string1(const string1 &p);//复制构造函数
    //重载运算符
    string1& operator =(const string1 &p);//重载=
    string1& operator =(const char *p);
    char& operator[](int i);
    //重载比较运算符
    friend bool operator <(const string1 &a,const string1 &b);
    friend bool operator >(const string1 &a,const string1 &b);
    friend bool operator ==(const string1 &a,const string1 &b);
    friend ostream& operator <<(ostream &os,const string1 &a);
    friend istream& operator >>(istream &is,string1 &a);
    //析构函数
    ~string1();//析构函数
    //其他功能函数
    int length();
    void display();
};
//构造函数
string1::string1()
{
    len=0;
    str=new char[1];
    str='\0';
}
string1::string1(const char*s)
{
    len=strlen(s);
    str = new char[len+1];
    strcpy(str,s);
}
string1::string1(const string1 &p)
{
    len=p.len;
    str=new char[len+1];
    strcpy(str,p.str);
    cout<<"复制构造函数被调用"<<endl;
}
string1& string1::operator=(const string1 &p)
{
    if(this==&p)
    {
        return *this;
    }
    delete[] str;
    len=p.len;
    str = new char[len+1];
    strcpy(str,p.str);
    cout<<"=重载成功"<<endl;
    return *this;
}
string1& string1::operator=(const char *p)
{
    delete []str;
    len=strlen(p);
    str=new char[len+1];
    strcpy(str,p);
    return *this;
}
char & string1::operator[](int i)
{
    return str[i];
}
bool operator<(const string1 &a,const string1 &b)
{
    return strcmp(a.str,b.str);
}
bool operator>(const string1 &a,const string1 &b)
{
    return b<a;//直接使用重载后的小于号
}
bool operator==(const string1 &a,const string1 &b)
{
    if(strcmp(a.str,b.str)==0)
        return true;
    return false;
}
ostream& operator <<(ostream &os,const string1 &a)
{
    os<<a.str<<endl;
    return os;
}

istream& operator >>(istream &is,string1 &a)
{
    char temp[string1::MAX];
    is.get(temp,string1::MAX);
    if(is)
    {
        a=temp;
    }
    while(is&&is.get()!='\n')
    {
        continue;
    }
    return is;
}
int string1::length()
{
    return len;
}
void string1::display()
{
    cout<<str<<endl;
}
//析构函数
string1::~string1()
{
    delete [] str;
    cout<<"析构函数被调用"<<endl;
}
int main()
{
    string1 b;
    cin>>b;
    cout<<b;
    return 0;
}

12.3在构造函数中使用new时应注意的事项

1.如果在构造函数中使用new来初始化指针成员,则应在析构函数中使用delete

2.new 和delete必须相互兼容,new对应于delete,而new[]对应于delete[]

3.如果有多个构造函数,则应该以相同的方式使用new,要么都带括号,要么都不带,因为只有一个析构函数与它兼容

4.应定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象

5.应定义一个赋值运算符,通过深度复制将一个对象复制给另一个对象

12.4有关返回对象的说明

使用const引用是为了提高效率,但对于何时采用还有一些限制。如果函数返回传递给它的对象,可以通过返回引用来提高效率

因为返回对象会调用复制构造函数,而返回引用不会。

不能返回一个局部对象的引用,因为函数结束后,局部对象的内存会被释放,所以引用将引用一个不确定的内存。

12.5定位new运算符

 

用将定位new运算符来创建新的类对象后,当该对象消亡是时,程序并不会自动地调用其析构函数,所以必须显示地调用析构函数。这个少数的需要显示调用析构函数的情况之一。

需要注意的是,对于使用定位new运算符创建的对象,应以与创建顺序相反的顺序进行删除。原因在于,晚创建的对象可能依赖于早创建的对象。另外,仅当所有对象都被销毁后,才能释放用于储存这些对象的缓冲区。
 

 

 

;