第12章 类和动态内存分配

目录复制

本章内容包括:

 对类成员使用动态内存分配

 隐式和显示复制构造函数

 隐式和显示重载赋值运算符

 在构造函数中使用new所必须完成的工作

 使用静态类成员

 将定位new运算符用于对象

 使用指向对象的指针

 实现队列抽象数据类型(ADT)

12.1 动态内存和类

对于如下代码:
在这里插入图片描述

对于上述代码段“ static int num_strings ”,无论创建多少个StringBad均共享一个num_strings成员变量,num_strings实质上不属于任何特定一个类对象,它是在内存上开辟的一块静态存储空间。
其对应的.cpp文件中进行赋值“ int StringBad::num_strings = 0 ”。
不能在类声明中初始化静态成员变量。声明只描述如何分配内存,但并不分配内存。
对于静态类成员,可在类声明之外使用单独的语句来初始化,因为 静态类成员是单独存储而非对象组成部分。
初始化是在方法文件(.cpp)中,而非类声明文件(.h)中进行,因为类声明位于头文件中,程序可能将头文件包括在其他几个文件中,若在头文件中进行初始化,将出现多个初始化语句副本从而引发错误。
对应的.cpp文件:

在这里插入图片描述
在这里插入图片描述

12.1.2 特殊成员函数

C++自动提供以下成员函数,如果没有定义:
 默认构造函数,;
 默认析构函数;
 复制构造函数;
 赋值运算符(将一个对象赋给另一个对象 );
 地址运算符。

  1. 复制构造函数
    复制构造函数用于将一个对象复制到新创建的对象中,即用于初始化过程中(包括按值传递参数),而非常规赋值过程中。
    类的复制构造函数原型通常为:class_name(const Class_name &);
//StringBad类
StringBad(const StringBad &);  
  1. 何时调用复制构造函数
    新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。每当程序生成了对象副本时编译器都将调用复制构造函数,具体来说当函数按值传递对象或函数返回对象或编译器生成临时对象。

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

使用new时注意事项:

  •  如果在构造函数中使用new来初始化指针成员,则应在析构函数中使用delete
  • newdelete必须相互兼容。new对应deletenew[]对应delete[]
  •  如果有多个构造函数,则必须以相同的方式使用new,要么都带中括号,要么都不带。因为只有一个析构函数,所有的构造函数都必须和它兼容。但是可以在一个构造函数中使用new初始化指针,而在另一个构造函数中将指针初始化为空(0或C++11中的nullptr),因为delete(无论带中括号还是不带)可以用于空指针。
  •  应定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象。类似如下代码:
String::String(const String &st)
{
       num_strings++;
       len = st.len;
       str = new char[len+1];
       std::strcpy(str, st.str);
}

具体来说,复制构造函数应分配足够的空间来存储复制的数据,并复制数据,而非仅仅数据的地址;除此,还应该更新所有受影响的静态类成员(num_strings)。

  •  应当定义一个赋值运算符,通过深度复制将一个对象复制给另一个对象。类似如下代码:
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;
}

具体来说:完成自我赋值的情况,释放成员指针以前指向的内存,复制数据而非仅仅是数据的地址,并返回一个指向调用对象的引用。

NULL、0还是nullptr:C常用NULL,C++常用0,C++11提供了关键字nullptr 此为更好的选择。

12.4 有关返回对象的说明

当函数返回对象时,可以返回指向对象的引用、指向对象的const引用或const对象。

12.4.1 返回指向const对象的引用

使用const引用旨在提高效率。如果函数返回传递给它的对象,可通过返回引用来提高效率。例如返回两个Vector对象中较大的一个。
以下代码一个返回对象,一个返回对象的引用,皆可行:
在这里插入图片描述
第一种,返回对象将调用复制构造函数,而返回引用不会,因此第二个版本效率更高;其次,引用指向的对象应该在调用函数执行时存在(运行结束时引用的对象也应该存在,不能为临时对象的引用);最后,v1v2都被声明为const引用,所以返回类型必须为const

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

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

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

String s1(“Good stuff”);
String s2, s3;
s3 = s2 = s1;

上述代码中,s2.operator=()的返回值赋值给s3。返回String对象或其引用都可行,但通过返回引用可避免该函数调用String的复制构造函数来创建一个新的String对象。返回引用,可以对其进行修改。
operator<<()的返回值用于串接输出:

string s1(“Good stuff”);
cout << s1 << “  is coming!;

上述代码中,operator<<(cout, s1)的返回值称为一个用于显示字符串“ is coming!”的对象。返回类型必须时ostream &,而不能仅仅是ostream。若使用ostream,将要求调用ostream类的复制构造函数,而ostream没有公有的复制构造函数。

12.4.3 返回对象

如果被返回的对象时被调用函数中的局部变量,则不应按引用方式返回它(程序结束,引用指向的对象将不再存在)。通常,被重载的算术运算符(比如operator+())属于这一类。

12.4.4 返回const对象

12.5 指向对象的指针

12.5.2 指针和对象小结

使用对象指针,需注意以下几点:

  •  使用常规表示法来声明指向对象的指针:
String * glamour;
  •  可以将指针初始化为指向已有的对象:
String *first = &saying[0]
  •  可以使用new来初始化指针,这将创建一个新的对象(有关使用new初始化指针细节,参加图12.6):
String *favorite = new String(saying[choice]);
 //上述语句将执行两个操作:1、首先开辟内存空间放类的对象;2、调用相应的构造函数
  • 对类使用new将调用相应的类构造函数来初始化新创建的对象
string *gleep = new String; //调用默认构造函数
string *glop = new String(“my my my”);       //调用String(const char *)构造函数

在这里插入图片描述
第一次开辟内存空间(new)存储类的对象(指针和指针长度),第二次开辟内存空间(构造函数中的new )存储字符串。
何时调用析构函数:
在这里插入图片描述
 如果对象时用new创建的,仅当显示使用delete删除对象时,才会调用其析构函数。

12.5.3 再谈定位new运算符

定位new运算符可以在分配内存时指定内存位置。使用定位new运算符需要加载头文件“#include ”。
在这里插入图片描述
在这里插入图片描述
使用new定位运算符创建对象时,不会自动调用析构函数,需要显示地为其对象调用析构函数。应在上述代码中“delete pc2; delete pc4”后添加:

pc3->~JustTesting();
pc1->JustTesting();
//显示地调用析构函数时必须指定要销毁的对象。

不能直接使用“delete pc3; delete pc1”,因为delete可与常规new运算符配合使用,但不能与定位new运算符配合使用
且对于pc3的new定位运算符应更改为:

pc3 = new(buffer + sizeof(JustTesting));

12.7 队列模拟

12.7.1 队列类

首先需要设计一个Queue类。队列特征:
 队列存储有序的项目序列;
 队列所能容纳的项目数有一定的限制
 应当能够创建空队列
 应当能够检查队列是否为空
 应当能够检查队列是否是满的
 应该能够在队尾添加项目
 应当能够从队首删除项目
 应当能够确定队列中项目数
设计类的时候,需要开发公有接口和私有实现。
queue.h

#ifndef __QUEUE_H__
#define —QUEUE_H__

#include <iostream>

using namespace std;

class Customer
{
         private:
                     long arrive;
                     int processTime;

         public:
                     Customer(){arrive = processTime = 0;}
                     void set(long when);
                     long when() const {return arrive;}
                     int pTime() const {return processTime}
};

//typedef *** Item;
typedef Customer Item;

class Queue
{
          private:
                     enum {Q_SUZE = 10};     //此处也可以用static,定义静态常量
                     struct Node{Item item; struct Node *next;};
                     Node *front;
                     Node *rear;
                     int items; 
                     const int qSize;         //队列最大值,赋值后应为常量
         publicQueue(int qs = Q_SIZE);
~ Queue();
                    bool isEmpty() const;
                    bool isFull() const;
                    int queueCount() const;
                    bool enQueue(const Item &item);
                    bool deQueue(Item &item); 
}

#endif

queue.cpp

# include “queue.h”
# include <cstdlib>
Queue::Queue(int qs)
{
             front = rear = NULL;
             items = 0;
             qSize = qs;  //invalid,qSize可以初始化但不能赋值
         
}

Queue::~Queue()
{
       while(front != NULL)
{
       temp = front;
       front = front->next;
       delete temp;
                 } 
}
bool Queue::isEmpty() const
{
         return items ==0;
}
bool Queue::isFull() const
{
return items == qSize;
}

int Queue::queueCount() const
{
         return items;
}

bool Queue::enQueue(const Item &item)
{
         if(isFull())
                      return false;
         Node *add = new Node;
         add->item = item;
         add->next = NULL;
         items ++;

         if(front == NULL)
                   front = add;
        else
                   rear->next = add;
rear = add;
       
       return true;
}
bool Queue::deQueue(Item &item)
{
         if(front == NULL)
                      return false;
         item = front->item;
         items --;
         
         Node *temp = front;
         front = front->next;   //2号节点变为头指针
         delete temp;       //销毁temp指向的内存空间即最初的front。
         
         if(items == 0)
                      rear = NULL;
         return true;
}

void Customer::set(long when)
{
         arrive = when;
         processTime = rand()%3 + 1;
         arrive = when;
}

bank.cpp

# include <iostream>
# include “queue.h”
int main(void)
{
        return 0;
}

qSize是常量,可以对其进行初始化但不能给它赋值。调用构造函数时,对象将在构造函数中的代码执行之前被创建,因此调用Queue(int qs)构造函数将导致程序首先给4个成员变量分配内存,再使用常规的赋值方式将值存储到内存中。因此,对于const数据成员,必须在执行到构造函数之前,即创建对象时进行初始化。C++提供成员初始化列表的方法来解决上述问题,即:

Queue::Queue(int qs)
{
             front = rear = NULL;
             items = 0;
             qSize = qs;  //invalid
 }
 
//更改为
Queue::Queue(int qs):qSize(qs)
{
            front = rear = NULL;
             items = 0;
             //qSize = qs;
}

注意:
成员初始化列表只能在构造函数中使用。
 必须使用来初始化非静态const数据成员(至少在C++之前如此);(静态成员已有独立的内存空间)
 必须使用来初始化引用数据成员
bank.cpp,简化版

# include <iostream>
# include “queue.h”

using namespace std;

int main(void)
{
         int qs;
         Item temp;
         int i = 0;
         int customers = 0;

cout << “Enter maximum size of queue:;
        cin >> qs;
        
        Queue line(qs);
        while(!line.isFull())
{
       temp.set(i++);
line.enQueue(temp);
customres ++;
}
cout << “Customers :<< customers << endl;

while(line.isEmpty)
{
        line.deQueue(temp);
        cutomer --;
}
cout << “Customers :<< customers << endl;

return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我宿孤栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值