第12章、类和动态内存分配

12.1.2 特殊成员函数

C++自动提供了下面的这些成员函数:
1、默认构造函数,如果没有定义构造函数
带参数的构造函数也可以是默认构造函数,只要所有参数都有默认构造函数(只能有一个默认 构造函数,也就是说不能有无参数默认构造函数与 有默认参数值的构造函数同时存在)

2、默认析构函数,如果没有定义
3、复制构造函数,如果没有定义
4、赋值运算符,如果没有定义、
5、地址运算符,如果没有定义

还有移动构造函数,移动赋值运算符

以下实现一个String类

说明这些函数的使用
sting.h

#ifndef STRING_H
#define STRING_H
#include<iostream>
using std::ostream;
using std::istream;

class String
{
private:
    char *str;//指向string 的指针
    int len;//string 的长度
    static int num_strings;//对象的个数
    static const int CINLIM =80//cin 输入的极限
public:
            //构造函数和其他成员函数
    String();//默认构造函数
    String(const char *s);//构造函数
    String(const String &);//复制构造函数
    //何时调用复制构造函数
    /*//每当程序生成了副本,编译器都将使用复制构造函数
     * 1、String ditto(motto);
     * 2、String metoo =motto;
     * 3、String also=String (motto);
     * 4、String *p=new String(motto);
    */
    
    ~String();//析构函数
    int length() const {return len;}
    //重载操作符

    String & operator=(const String &);//赋值运算符
    /*
     * 何时使用赋值运算符
     * 将已有的对象赋值给另一个对象时,将使用重载的赋值运算符:
     * 例如:
     * String head("hey man");
     * String boy;
     * boy=head;
*/
    String & operator=(const char *);
    char &operator[](int i);
    const char &operator[] (int i) const;

    //重载操作符 友元
    friend bool operator<(const String &st1,const String &st2);
    friend bool operator>(const String &st1,const String &st2);
    friend bool operator==(const String &st,const String &st2);

    friend ostream & operator<<(ostream & os,const String &st);
    friend istream & operator>>(istream & is, String &st);

   //静态方法
    static int HowMany();
};

#endif // STRING_H

sting.cpp

#include "string.h"
#include<cstring>
using std::cin;
using std::cout;

//初始化静态类成员
int String::num_strings=0;



String::String()//默认构造函数
{
    len=4;
    str=new char[1];
    str[0]='\0';//默认string
    num_strings++;
}
//类方法 从 C string 构造 String
String::String(const char *s)
{
    len =std::strlen(s);//设置大小
    str=new char[len+1];//申请内存
    std::strcpy(str,s);//初始化指针
    num_strings++;//设置对象计数
}

String::String(const String &st)
{
    num_strings++;//设置对象计数
    len=st.len;
    str= new char[len+1];//以下两行属于深拷贝,防止this对象和st对象的str成员指向同一个地址空间
    std::strcpy(str,st.str);//复制字符串到新的地址

}

String::~String()//必要的析构函数
{
    --num_strings;
    delete [] str;
}
//重载操作符 方法
//赋值一个 String 到一个String
String &String::operator=(const String &st)
{
    if(this==&st)//避免给自身赋值
        return *this;
    delete [] str;//释放以前分配的数据
    len =st.len;
    str=new char[len+1];
    std::strcpy(str,st.str);
    return *this;//函数返回一个指向对象的引用
}
//赋值一个C String 到 String
String &String::operator=(const char *s)
{

    delete [] str;//释放以前分配的数据
    len=std::strlen(s);
    str=new char[len+1];
    std::strcpy(str,s);
    return *this;
}

//通过非常量String 读写char
char &String::operator[](int i)
{
    return str[i];
}

//通过常量String 只读char
const char &String::operator[](int i) const
{
    return str[i];
}

istream &operator>>(std::istream &is, String &st)
{
    char temp[String::CIMLIM];
    is.get(temp,String::CINLIM);
    if(is)
        st=temp;
    while(is&&is.get()!='\n')
        continue;
    return is;
}
//重载操作符友元
bool operator<(const String &st1, const String &st2)
{
    return (std::strcmp(st1.str,str2.str)<0);//strcmp:依照字母排序,如果第一个参数位于第二个参数之前,
    //返回负数;相同,返回0;第一个位于第二个之后,返回正值

}

bool operator>(const String &st1, const String &st2)
{
    return  st2 <st1 ;//内置的< 、>比较运算符替代strcmp函数 

}

bool operator==(const String &st1, const String &st2)
{
    return (std::strcmp(st1.str,str2.str)==0);

}
//简单的String 输出
ostream &operator<<(std::ostream &os, const String &st)
{
    os<<st.str;
    return os;
}

//静态方法
int String::HowMany()
{
    return num_strings;
}

12.4.1 返回指向const对象的引用

例如:


Vector f1(50,60);
Vector f2(10,70);
Vector max;
max=Max(f1,f2);
//下面两种实现方式都是可行的
1、
Vector Max(const Vector &v1,const Vector &v2){
	if(v1.val()>v2.val())
		return v1;
	else
	    return v2;	
}
2const Vector &Max(const Vector &v1,const Vector &v2){
	if(v1.val()>v2.val())
		return v1;
	else
	    return v2;	
}

对上面的代码进行说明,
1、返回对象将调用复制构造函数,而返回引用不会。因此第二版本做的工作更少,效率更高。
2、引用指向的对象应该在调用函数调用执行时存在。
3、v1和v2都被声明为const引用,因此返回类型必须为const,这样才匹配

12.4.2 返回指向非const对象的引用

两种常用的返回非const对象情形是,重载赋值运算符以及重载与cout一起使用的<<运算符。前者这样做旨在提高效率,而后者必须这样做。

1、operator=()的返回值用于连续赋值:

String s1("Good stuff");
String s2,s3;
s3=s2=s1;

12.4.3 返回对象

如果被返回的对象是被调用函数中的局部变量,则不应该按引用方式返回它,因为在被调用函数执行完毕时,局部对象将调用其析构函数。

例如:

Vector f1(50,60);
Vector f2(10,70);
Vector net;
net=f1+f2;
Vector Vector::operator +(const Vector &b) const 
{
	return Vector(x+b.x,y+b.y);
}


12.4.4 返回const对象

返回对象,允许以下的操作;
net=f1+f2;
也允许以下操作
f1+f2=net;
f1+f2结果为一个临时变量,如果执行f1+f2=net;,net的内容将会覆盖它们的结果。

如果想要写

if(f1+f2==net)代码的时候,写成了if(f1+f2=net)。这可能导致错误。因此返回const对象可以避免这种错误的出现。

12.5 使用指向对象的指针

在下述情况,析构函数将会被调用
1、如果对象是动态变量(临时对象),则执行完定义该对象的程序块时,将调用该对象的析构函数。
2、如果对象是静态变量(外部、静态、静态外部或者来自名称空间),则在程序结束时将调用对象的析构函数。
3、如果对象是用New创建的,则仅在您显式使用delete删除对象时,析构函数 才会被调用

在这里插入图片描述
图 1. 指针和对象

在这里插入图片描述
图 2 使用new创建对象

12.5.3再谈定位new对象

定位new运算符让你能够在分配内存时能够指定内存位置。
下面例子使用了定位new运算符和常规new运算符给对象分配内存,其中定义的类和构造函数和析构函数都会显示一些信息,让用户能够了解对象的历史。

#include <iostream>
#include <string>
#include <new>

using namespace std;
const int BUF =512;
class JustTesting{
private:
    string words;
    int number;
public:
    JustTesting(const string &s ="JustTesting",int n=0)
    {words=s;number=n;cout<<words<<" constructed\n";}
    ~JustTesting(){cout<<words<<" destroyed\n";}
    void Show() const {cout<< words<<" , "<<number<<endl;}
};

int main()
{
    char * buffer =new char[BUF];//获得一个内存块

    JustTesting *pc1,*pc2;

    pc1=new (buffer)JustTesting;// 在buffer(缓存区)创建对象
    pc2 =new JustTesting("Heap1",20); //在堆创建对象

    cout<< "Memory block addresses:\n"<<"buffer:"
        <<(void*)buffer <<" heap:"<<pc2<<endl;

    cout<<"Memory contents:\n";

    cout<<pc1<<":";
    pc1->Show();
    cout<<pc2<<":";
    pc2->Show();


    JustTesting *pc3,*pc4;

    pc3=new (buffer)JustTesting("Bad Idea",6);
    pc4 =new JustTesting("Heap2",10);

    cout<<"Memory contents:\n";

    cout<<pc3<<":";
    pc3->Show();
    cout<<pc4<<":";
    pc4->Show();

    delete pc2;//释放Heap1
    delete pc4;//释放Heap2
    delete [] buffer;//释放 buffer
    cout << "Done\n";
    return 0;
}

该程序使用New运算符创建了一个512字节的内存缓冲区,然后使用new运算符在堆中创建两个JustTesting,最后,它使用delete来释放使用new分配的内存。下面是程序的输出:

JustTesting constructed
Heap1 constructed
Memory block addresses:
buffer:000001D80C892940 heap:000001D80C891BF0
Memory contents:
000001D80C892940:JustTesting , 0
000001D80C891BF0:Heap1 , 20
Bad Idea constructed
Heap2 constructed
Memory contents:
000001D80C892940:Bad Idea , 6
000001D80C892BB0:Heap2 , 10
Heap1 destroyed
Heap2 destroyed
Done

和往常一样,内存地址的格式和值将随系统而异。
例子在使用定位new运算符时存在两个问题。首先,创建第二个对象时,定位new运算符使用一个新的对象来覆盖第一个对象的内存单元。显然,如果类动态地为其成员分配内存,这将引发问题。

其次,将delete用于pc2和pc4时,将自动调用pc2和pc4指向的对象调用析构函数,然而,将delete[]用于buffer时,不会为使用定位new运算符创建的对象调用析构函数。

因此:程序员必须负责管用定位New运算符用从中使用的缓冲区内存单元。要使用不同的内存单元,程序员需要提供两个位于缓冲区的不同地址,并确保这两个内存单元不重叠。

pc1=new (buffer)JustTesting;// 在buffer(缓存区)创建对象
pc3=new (buffer+sizeof(JustTesting))JustTesting("Better Idea",6);

其中,指针pc3相对于pc1的偏移量为JustTesting对象的大小。

第二个教训是,如果使用定位new运算符来为对象分配内存,必须确保其析构函数被调用。但如何确保呢?对于在堆中创建的对象,可以这样做:

delete pc2;//删除pc2对象

但不可以如下方式:

delete pc1;//删除pc1对象 不可以
delete pc3;//删除pc3对象 不可以

原因在于delete可以与常规New运算符配合使用,但是不可以与定位new运算符配合使用。。例如,指针pc3没有收到new运算符返回的地址,因此delete pc3将导致运行阶段错误。在另外一方面,指针pc1指向的地址与buffer相同,但buffer是使用new[] 初始化的,因此必须使用delete[] 而不是delete来释放。即使buffer是使用new 而不是new[]初始化的,delete pc1也将释放buffer,而不是pc1。这是因为New/delete系统知道已分配的512字节块buffer,但对定位New运算符对该内存块做了何种处理一无所知。
该程序确实释放了buffer:

delete [] buffer;//释放 buffer

正如上述注释指出的,delete[] buffer;释放使用常规new运算符分配的整个内存块,但它没有为定位new运算符在该内存块中创建的对象调用析构函数。因为程序中使用了一个显示信息的析构函数,该析构函数宣布了“Heap1”和“Heap2”的死亡,但却没有宣布”JustTesting“和"Bad Idea “的死亡。
这种问题的解决方案是,显式地为使用定位new运算符创建的对象调用析构函数。正常情况下将自动调用析构函数,这是需要显式地调用析构函数的少数几种情形之一。显示地调用析构函数时,必须指定要销毁的对象。由于有指向对象的指针,因此可以使用这些指针:

     pc3->~JustTesting();//销毁指向pc3的对象
     pc1->~JustTesting();

下面例子对定位new运算符使用的内存单元进行管理,加入到合适的delete和显式析构函数调用,从而修复了上面例子的问题,需要注意一点是正确的删除顺序,对于使用定位new运算符创建的对象。应该以与创建顺序相反的顺序进行删除。原因在于,晚创建的对象可能依赖于早创建的对象。另外,仅当所有对象都被销毁后,才能释放用于存储这些对象的缓冲区。

#include <iostream>
#include <string>
#include <new>

using namespace std;
const int BUF =512;
class JustTesting{
private:
    string words;
    int number;
public:
    JustTesting(const string &s ="JustTesting",int n=0)
    {words=s;number=n;cout<<words<<" constructed\n";}
    ~JustTesting(){cout<<words<<" destroyed\n";}
    void Show() const {cout<< words<<" , "<<number<<endl;}
};

int main()
{
    char * buffer =new char[BUF];//获得一个内存块

    JustTesting *pc1,*pc2;

    pc1=new (buffer)JustTesting;// 在buffer(缓存区)创建对象
    pc2 =new JustTesting("Heap1",20); //在堆创建对象

    cout<< "Memory block addresses:\n"<<"buffer:"
        <<(void*)buffer <<" heap:"<<pc2<<endl;

    cout<<"Memory contents:\n";

    cout<<pc1<<":";
    pc1->Show();
    cout<<pc2<<":";
    pc2->Show();


    JustTesting *pc3,*pc4;

    pc3=new (buffer+sizeof(JustTesting))JustTesting("Better Idea",6);
    pc4 =new JustTesting("Heap2",10);

    cout<<"Memory contents:\n";

    cout<<pc3<<":";
    pc3->Show();
    cout<<pc4<<":";
    pc4->Show();

    delete pc2;//释放Heap1
    delete pc4;//释放Heap2

     pc3->~JustTesting();//销毁指向pc3的对象
     pc1->~JustTesting();

    delete [] buffer;//释放 buffer
    cout << "Done\n";
    return 0;
}

以下为输出结果:

JustTesting constructed
Heap1 constructed
Memory block addresses:
buffer:000001B7D04F2A10 heap:000001B7D04E7040
Memory contents:
000001B7D04F2A10:JustTesting , 0
000001B7D04E7040:Heap1 , 20
Better Idea constructed
Heap2 constructed
Memory contents:
000001B7D04F2A40:Better Idea , 6
000001B7D04F2C80:Heap2 , 10
Heap1 destroyed
Heap2 destroyed
Better Idea destroyed
JustTesting destroyed
Done

来源:C++ Primer plus
仅供学习,侵删

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值