C++ Primer Plus学习笔记12-类和动态内存分配

友元函数重载运算符的bug

1. 动态内存和类

C++使用newdelete运算符动态控制内存。在类中使用这些运算符将导致许多新的编程问题。这种情况下,析构函数必不可少。

构造函数必须分配足够的内存来存储数据,然后再将数据复制到内存中。析构函数需要包含delete语句删除成员指针指向的内存。
当使用一个对象初始化另一个对象时,编译器自动生成复制构造函数,这个构造函数不知道需要更新类中自定义的静态变量,因此会将类的设计方案搞乱。

1.1 特殊成员函数

C++自动提供以下成员函数:

  • 默认构造函数
  • 默认析构函数
  • 复制构造函数
  • 赋值运算符
  • 地址运算符

C++11提供了另外连个特殊成员函数:移动构造函数(move constructor)和移动赋值运算符(move assignment operator)。

1.1.1 默认构造函数

创建对象时总会调用构造函数。如果定义了构造函数,C++将不会定义默认构造函数。如果希望在创建对象时不显式地对它进行初始化,则必须显式地定义默认构造函数。

只要所有参数都有默认值,带参数的构造函数也是默认构造函数。但是一个类只能有一个默认构造函数。

1.1.2 复制构造函数

用于将一个对象复制到新构建的对象中。原型通常为:Class_name(const Class_name &);
它接受一个指向类对象的常量引用作为参数。

新建一个对象并将其初始化为同类现有对象时,复制构造函数将被调用。每当程序生成了对象副本,编译器都将使用复制构造函数;即,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。

默认的复制构造函数逐个复制费静态成员(浅复制),复制的是成员的值。

1.1.3 赋值运算符

C++允许类对象赋值,是通过自动为类重载赋值运算符实现。原型为Class_name & Class_name::operator=(const Class_name &);
它接受并返回一个指向类对象的引用。
赋值运算符的隐式实现对成员进行逐个赋复制(浅复制)。

2. 在构造函数汇总使用new应注意的事项

  • 如果在构造函数中使用new初始化指针成员,则应在析构函数中使用delete
  • newdelete必须相互兼容。
  • 如果有多个构造函数,则必须以相同的方式使用new(可以使用指针初始化为空,由于delete可以用于空指针)。因为只有一个析构函数。
  • 应定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象。
  • 应定义一个赋值运算符,通过深度复制将一个对象复制给另一个对象。

以前,空指针可以用0NULL表示。C++传统上更喜欢用0,但是C++11提供关键字nullptr

3. 有关返回对象的说明

3.1 返回指向const对象的引用

使用const引用的常见原因是为了提高效率。
如果函数返回传递给它的对象,可以通过返回引用来提高其效率。
首先,返回对象将调用复制构造函数,而返回引用不会。其次,引用指向的对象应该在调用函数执行时存在。第三,如果函数被声明为const引用,则返回对象的类型必须为const

const Vector & Max(const Vector & v1, const Vector & v2){
  if (v1.magval() > v2.magval())
    return v1;
  else
    return v2;
}

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

3.3 返回对象

如果被返回的对象是被调用函数中的局部变量,则不应该采用引用的方式返回它。通常,被重载的算术运算符属于这一类。
在这种情况下,存在调用复制构造函数来创建被返回的对象的开销,这是无法避免的。

4. 使用指向对象的指针

// sayings2.cpp -- using pointers to objects
// compile with string1.cpp
#include <iostream>
#include <cstdlib> // for rand(), srand()
#include <ctime> // for time()
#include "string1.h"
const int ArSize = 10;
const int MaxLen = 81;
int main(){
  using namespace std;
  String name;
  cout << "Hi, what's your name?\n>> ";
  cin >> name;
  cout << name << ", please enter up to " <<ArSize
       << " short sayings <empty line to quit>:\n";
  String sayings[ArSize];
  char temp[MaxLen];  // temporary string storage
  int i;
  for(i = 0; i < ArSize; i++){
    cout << i + 1 << ": ";
    cin.get(temp, MaxLen);
    while(cin && cin.get()!='\n')
      continue;
    if(!cin || temp[0]=='\0') // empty line?
      break;
    else
      sayings[i] = temp;
  }
  int total = i;
  if (total > 0){
    cout << "Here are your sayings:\n";
    for(i = 0; i < total; i++)
      cout << sayings[i] << endl;
    // using pointers to keep track of shortest, first strings
    String * shortest = &sayings[0];
    String * first = &sayings[0];
    for(i = 1; i < total; i++){
      if (sayings[i].length() < shortest->length())
        shortest = &sayings[i]
      if (sayings[i] < *first)
        first = &sayings[i];
    }
    cout << "Shortest saying:\n" << *shortest << endl;
    cout << "First alphabetically:\n" << *first << endl;
    srand(time(0));
    int choice = rand() % total;
    // use new to create, initialize new String object
    String *favorite = new String(syaings[choice]);
    cout << "My favorite saying:\n" << *favorite << endl;
    delete favorite;
  }
  else
    cout << "Not much to say, eh?\n";
  cout << "Bye.\n";
  return 0;
}

String * favorite = new String(sayings[choice]);使用new为整个对象分配内存,即为保存字符串地址的str指针和·len成员分配内存。创建对象将调用构造函数,构造函数分配用于保存字符串的内存,并将字符串的地址赋给str
当程序不需要该对象时,使用delete删除;这只释放用于保存str指针和len成员的空间,并不释放str指向的内存。释放str指向的内存由析构函数完成。

// placenew1.cpp -- new, placement new, no delete
#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 = "Just Testing", 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];	// get a block of mem
  JustTesting *pc1, *pc2;
  pc1 = new(buffer) JustTestng;			// place object in buffer
  pc2 = new JustTesting("Heap1", 20); 	// place object on heap

  cout << "Memory block address:\n" << "buffer: "
       << (void *) buffer << "\theap: " << pc2 << endl;
  cout << "Memory contents:\n" << pc1 << ": ";
  pc1->Show();
  cout << pc2 << ": ";
  pc2->Show();

  JustTesting *pc3, *pc4;
  pc3 = new(buffer) JustTestng("Bad Idea", 6);	// place object in buffer
  pc4 = new JustTesting("Heap2", 10);		 	// place object on heap

  cout << "Memory contents:\n" << pc3 << ": ";
  pc3->Show();
  cout << pc4 << ": ";
  pc4->Show();

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

发生错误,未知原因

placement1.cpp: error: 'JustTesting' does not name a type
	pc1 = new(buffer) JustTestng;   // place object in buffer
error: expected type-specifier before ‘JustTestng’
	pc3 = new(buffer) JustTestng("Bad Idea", 6); // place object in buffer

以上程序在使用placement new运算符时存在两个问题:

  • 创建*pc2时,placement new运算符使用一个新对象来覆盖用于第一个对象的内存单元。如果类动态地为其成员分配内存,将引发问题
  • delete用于pc2pc4时,自动为二者指向的对象调用析构函数;然而,将delete[]用于buffer时,不会为使用placement new运算符创建的对象调用析构函数。

delete不能与placement new运算符配合使用。解决方案:显式地为使用placement new运算符创建的对象调用析构函数。

pc1->~JustTesting(); // destroy object pointed to by pc1

5. 复习各种技术

5.1 重载<<运算符

以便和cout一起使用以显示对象内容。定义以下友元运算符函数:

ostream & operator<<(ostream & os, const c_name & obj){
  os << obj; // display object contents
  return os;
}

5.2 转换函数

将单个值转换为类类型,创建原型如下的类构造函数:
c_name(type_name value);
要将类转换为其他类型,创建原型如下的类成员函数:
operator type_name();
该函数虽然没有声明返回类型,但是需要返回所需类型的值。

使用转换函数需要小心。可以在声明构造函数时使用关键字explicit,以防止转换函数被用于隐式转换。

5.3 构造函数使用new的类

对于指向的内存是由new分配的所有类成员,都应该在类的析构函数中对其使用delete释放分配的内存。
如果析构函数通过对指针类成员使用delete释放内存,则每个构造函数都应当使用new来初始化指针,或者将其设置为空指针nullptr
应定义一个分配内存的复制构造函数,将类对象初始化为另一个类对象。
应定义一个重载赋值运算符的类成员函数c_name & c_name::operator=(const c_name & cn);

6. 队列模拟

6.1 队列类Queue

class Queue{
  private:
  // class scope definitions
    struct Node { Item item; struct Node * next;}; // 嵌套结构声明
    enum {Q_SIZE = 10};
    // private class members
    Node * front;
    Node * rear;
    int items;
    const int qsize;
    
  public:
    // Queue类的接口
    Queue(int qs=Q_SIZE);
    ~Queue();
    bool isempty() const;
    bool isfull() const;
    int queuecount() const;
    bool enqueue(const Item & item); // add item to end
    bool dequeue(Item & item);       // remove item from front
};

成员初始化列表(member initializer list)由逗号分隔的初始化列表组成,位于参数列表右括号之后、函数体左括号之前。由它完成常量成员的初始化以及被声明为引用的类成员的初始化。
只有构造函数可以使用这种初始化列表语法。

// public methods
Queue::Queue(int qs) : qsize(qs)
{
  front = rear = nullptr;
  items = 0;
  // qsize = qs; // not acceptable
}
// or
Queue::Queue(int qs) : qsize(qs), front(nullptr), rear(nullptr), items(0){}

C++11允许直接初始化:

class Classy{
  int mem1 = 10;
  const int mem2 = 20;
}
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;
  // on failure, new throws std::bad_alloc exception
  add->item = item;
  add->next = nullptr;
  items++;
  if(front == nullptr) front = add;
  else rear->next = add;
  rear = add;
  return true;
}
bool Queue::dequeue(Item & item){
  if(isempty())
    return false;
  item = front->item;
  items--;
  Node * temp = front;
  front = temp->next;
  delete temp;
  if(items==0)
    rear = nullptr;
  return true;
}

Queue::~Queue(){
  Node *temp;
  while(front != nullptr){
    temp = front;
    front = front -> next;
    delete temp;
  }
}

可以如下定义对象不允许被复制的类:

class Queue{
  private:
    Queue(const Queue q) : qsize(0) {} // preemptive definition
    Queue & operator=(const Queue & q) { return *this;}
};

C++11可以使用关键字delete实现上述功能(第18章)。

6.2 Customer

class Customer{
  private:
    long arrive;	// arrival time
    int process_time; // processing time
  public:
    Customer() { arrive = process_time = 0; }
    void set(long when);
    long when() const { return arrive;}
    int ptime() const { return process_time;}
};

void Customer::set(long when){
  process_time = std::rand() % 3 + 1;
  arrive = when;
}
typedef Customer Item;

6.3 ATM模拟

// bank.cpp -- using the Queue
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "queue.h"
const int MIN_PER_HR = 60;

bool newcustomer(double x);  // is there a new customer?

int main(){
  using std::cin;
  using std::cout;
  using std::endl;
  using std::ios_base;
  // setting things up
  std::srand(std::time(0)); // random initializing of rand()
  cout << "Case Study: Bank of Heather Automatic Teller\n";
  cout << "Enter maximum size of queue: ";
  int qs;
  cin >> qs;
  Queue line(qs);

  cout << "Enter the number of simulation hours: ";
  int hours;
  cin >> hours;
  long cyclelimit = MIN_PER_HR * hours;
  cout << "Enter the average number of customers per hour: ";
  double perhour;
  cin >> perhour;
  double min_per_cust;
  min_per_cust = MIN_PER_HR / perhour;

  Item temp;  // new customer data
  long turnaways = 0;  // turned away by full queue
  long customers = 0;  // joined the queue
  long served = 0;     // served during the simulation
  long sum_line = 0;    // cumulative line length
  int wait_time = 0;    // time until autoteller is free
  long line_wait = 0;   // cumulative time in line
  // running the simulation
  for(int cycle = 0; cycle < cyclelimit; cycle++){
    if (newcustomer(min_per_cust)){
      if(line.isfull()) turnaways++;
      else{
        customers++;
        temp.set(cycle);
        line.enqueue(temp);
      }
    }
    if(wait_time <= 0 && !line.isempty()){
      line.dequeue(temp);
      wait_time = temp.ptime();
      line_wait += cycle - temp.when();
      served++;
    }
    if(wait_time > 0)  wait_time--;
    sum_line += line.queuecount();
  }
  // reporting results
  if (customers > 0){
    cout << "customers accepted: " << customers << endl;
    cout << "  customers served: " << served << endl;
    cout << "         turnaways: " << turnaways << endl;
    cout << "average queue size: ";
    cout.precision(2);
    cout.setf(ios_base::fixed, ios_base::floatfield);
    cout << (double) sum_line /cyclelimit << endl;
    cout << " average wait time: " << (double) line_wait/served << "minutes\n";
  }
  else
    cout << "No customers!\n";
  cout << "Done!\n";

  return 0;
}
bool newcustomer(double x){
  return (std::rand() * x / RAND_MAX < 1);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值