天天看点

C++Primer Plus笔记——第十二章 类和动态内存分配总结及程序清单本章小结程序清单

目录

本章小结

程序清单

改进前的string类(在构造函数和析构函数中使用了动态内存分配)

改进后的String类(添加了复制构造函数及赋值运算符)

再谈定位new运算符

复习:模拟队列

本章小结

       本章介绍了定义和使用类的许多重要方面。其中的一些方面是非常微妙甚至很难理解的概念。如果其中的某些概念对于您来说过于复杂,也不用害怕——这些问题对于大多数C++的初学者来说都是很难的。 通常,对于诸如复制构造函数等概念,都是在由于忽略它们时遇到了麻烦后逐步理解的。本章介绍的一些内容乍看起来非常难以理解,但是随着经验越来越丰富,对其理解也将越透彻。

       在类构造函数中,可以使用new为数据分配内存,然后将内存地址赋给类成员。这样,类便可以处理长度不同的字符串,而不用在类设计时提前固定数组的长度。在类构造函数中使用new,也可能在对象过期时引发问题。如果对象包含成员指针,同时它指向的内存是由new分配的.则释放用于保存对象的内存并不会自动释放对象成员指针指向的内存。因此在类构造函数中使用new类來分配内存时,应在类析构函数中使用delete来释放分配的内存。这样,当对象过期时,将自动释放其指针成员指向的内存。

       如果对象包含指向new分配的内存的指针成员,则将一个对象初始化为另—个对象,或将一个对象赋给另一个对象时,也会出现问题。在默认情况下,C++逐个对成员进行初始化和赋值,这意味着被初始化或被赋值的对象的成员将与原始对象完全相同。如果原始对象的成员指向一个数据块,则副本成员将指向同一个数据块。当程序最终删除这两个对象时,类的析构函数将试图删除同一个内存数据块两次,这将出错。解决方法是:定义一个特殊的复制构造函数来重新定义初始化,并重载赋值运择符。在上述任何一种 情况下,新的定义都将创建指向数据的副本,并使新对象指向这些副本。这样,旧对象和新对象都将引用独立的、相同的数据而不会重叠。由于同样的原因,必须定义赋值运算符。对于每一种情况,最终目的都是执行深度复制,也就是说,复制实际的数裾,:而不仅仅是复制指向数据的指针。

       对象的存储持续性为自动或外部时,在它不再存在时将自动调用其析构函数。如使用new运算符为对象分配内存,并将其地址赋赋给一个指针,则当您将delete用于该指针时将自动为对象调用析构函数。然而,如果使用定位new运算符(而不是常规new运算符) 为类对象分配内存,则必须负责显式地为该对象调用析构函数,方法是使用指向该对象的指针调用析构函数方法。C++允许在类中包含结构、类和枚举定义。这些嵌套类型的作用域是整个类,这意味着它们被局限于类中,不会与其他地方定义的同名结构、类和枚举发生冲突。

       C++为类构造函数提供了一种可用来初始化数据成员的特殊语法。这种语法包括冒号和由逗号分隔的初始化列表,被放在构造函数参数的右括号后,函数体的左括号前。每一个初始化器都由被初始化的成员的名称和包含初值的括号组成。从概念上说,这些初始化操作是在对象创建时进行的,此时函数体中的语句还没有执行。语法如下;

       queue(int qs) :qsize(qs),items(0),front(NULL),rear(NULL) {}

       如果数据成员是非静态const成员或引用,则必须采用这种格式,但可将C++11新增类内初始化用于非静态const成员。

       C++允许类内初始化,即在类定义中进行初始化:

class Queue
{
private:
...
    Node * front = NULL;
    enum {Q_SIZE = 10};
    int items = 0;
    const int qsize = Q_SIZE;
...
}
           

       这与使用初始化列表等价。然而,使用成员初始化列表的构造函数将覆盖相应的类内初始化。

程序清单

改进前的string类(在构造函数和析构函数中使用了动态内存分配)

12.1 strngbad.h 

//strngbad.h -- flawed string class definition

#include <iostream>

#ifndef STRNGBAD_H_
#define STRNGBAD_H_

class StringBad
{
private:
	char * str;
	int len;
	static int num_strings;
public:
	StringBad(const char *s);
	StringBad();
	~StringBad();
	friend std::ostream & operator << (std::ostream & os, const StringBad & st);
};

#endif // !STRNFBAD_H_
           

12.2 strngbad.cpp

//strngbad.cpp -- StringBad calss methods
#include <cstring>
#include "strngbad.h"

using namespace std;

int StringBad::num_strings = 0;

StringBad::StringBad(const char *s)
{
	len = strlen(s);
	str = new char[len + 1];
	strcpy(str, s);
	num_strings++;
	cout << num_strings << ": \"" << str << "\" object created\n";
}

StringBad::StringBad()
{
	len = 4;
	str = new char[4];
	strcpy(str, "C++");
	num_strings++;
	cout << num_strings << ": \"" << str << "\" default object created\n";
}

StringBad::~StringBad()
{
	cout << "\"" << str << "\" object deleted\n";
	--num_strings;
	cout << num_strings << " left\n";
	delete[]str;
}

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

12.3 vegnews.cpp

//vegnews.cpp -- using new and delete with classes 
//compile with strngbad.cpp
#include <iostream>
#include "strngbad.h"

using namespace std;

void callme1(StringBad &);
void callme2(StringBad);

int main()
{
	cout << "Startings an inner block.\n";
	StringBad headline1("Celery Stalks at Midnight");
	StringBad headline2("Lettuce Prey");
	StringBad sports("Spinach Leaves Bowl for Dollars");

	cout << "headline1: " << headline1 << endl;
	cout << "headline2: " << headline2 << endl;
	cout << "sports: " << sports << endl;
	callme1(headline1);
	cout << "headline1: " << headline1 << endl;
	callme2(headline2);
	cout << "headline1: " << headline1 << endl;
	cout << "Initialize one object to another:\n";
	StringBad sailor = sports;
	cout << "sailor: " << sailor << endl;
	cout << "Assign one object to another:\n";
	StringBad knot;
	knot = headline1;
	cout << "knot: " << knot << endl;
	cout << "Exiting the block.\n";
	cout << "End of main()\n";

	return 0;
}

void callme1(StringBad & rsb)
{
	cout << "String passed by reference:\n";
	cout << "    \"" << rsb << "\"\n";
}

void callme1(StringBad & sb)
{
	cout << "String passed by value:\n";
	cout << "    \"" << sb << "\"\n";
}
           

改进后的String类(添加了复制构造函数及赋值运算符)

12.4 string1.h

//string1.h -- fixed and augmented string class definition
#ifndef STRING1_H_
#define STRING1_H_

#include <iostream>

using namespace std;

class String
{
private:
	char * str;
	int len;
	static int num_strings;
	static const int CINLIM = 80;
public:
	String(const char * s);
	String();
	String(const String &);
	~String();
	int length() const { return len };
	//overloaded operator methods
	String & operator=(const String &);
	String & operator=(const char *);
	char & operator[](int i);
	const char & operator[](int i) const;
	//overloaded operator friends
	friend bool operator<(const String &st, const String &st2);
	friend bool operator>(const String &st, 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 function
	static int HowMany();
};


#endif //STRING1_H_
           

12.5 string1.cpp

//string1.cpp -- String class methods
#include <cstring>
#include "string1.h"

using namespace std;

int String::num_strings = 0;

int String::HowMany()
{
	return num_strings;
}

String::String(const char * s)
{
	len = strlen(s);
	str = new char[len + 1];
	strcpy(str, s);
	num_strings++;
}

String::String()
{
	len = 4;
	str = new char[1];
	str[0] = '\0';
	num_strings++;
}

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

String::~String()
{
	--num_strings;
	delete[]str;
}

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

String & String::operator=(const char * s)
{
	delete[]str;
	len = strlen(s);
	str = new char[len + 1];
	strcpy(str, s);
	return *this;
}

char & String::operator[](int i)
{
	return str[i];
}

const char & String::operator[](int i) const
{
	return str[i];
}

bool operator<(const String &st1, const String &st2)
{
	return (strcmp(st1.str, st2.str) < 0);
}

bool operator>(const String &st1, const String &st2)
{
	return st2 < st1;
}

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

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

istream & operator>>(istream & is, String & st)
{
	char temp[String::CINLIM];
	is.get(temp, String::CINLIM);
	if (is)
		st = temp;
	while (is && is.get() != '\n')
		continue;
	return is;
}
           

12.6 sayings1.cpp

//sayings1.cpp -- using expanded String class
//compile with string1.cpp

#include <iostream>
#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 saying <empty line to quit>:\n";
	String sayings[ArSize];
	char temp[MaxLen];
	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')
			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][0] << ": " << sayings[i] << endl;

		int shortest = 0;
		int first = 0;
		for (i = 1; i < total; i++)
		{
			if (sayings[i].length() < sayings[shortest].length())
				shortest = i;
			if (sayings[i] < sayings[first])
				first = i;
		}
		cout << "Shortest saying:\n" << sayings[shortest] << endl;
		cout << "First alphabetically:\n" << sayings[first] << endl;
		cout << "This program used " << String::HowMany() << " String objects. Bye.\n";
	}
	else
		cout << "No input! Bye.\n";
	return 0;
}
           

12.7 sayings2.cpp  使用指针追踪新对象

//sayings2.cpp -- using pointers to objects
//compile with string1.cpp
#include <iostream>
#include <cstdlib>
#include <ctime>
#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 saying <empty line to quit>:\n";
	String sayings[ArSize];
	char temp[MaxLen];
	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')
			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;

		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;
		String * favorite = new String(sayings[choice]);
		cout << "My favorite saying:\n" << *favorite << endl;
		delete favorite;
	}
	else
		cout << "Not much to say, eh?\n";
	cout << "Bye.\n";
	return 0;
}
           

再谈定位new运算符

12.8 placenew1.cpp 定位new运算符

//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];

	JustTesting *pc1, *pc2;

	pc1 = new (buffer) JustTesting;
	pc2 = new JustTesting("Heap1", 20);

	cout << "Memory block address:\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;
	delete pc4;
	delete[] buffer;
	cout << "Done\n";
	return 0;
}
           

12.9 placenew2.cpp 加入了合适的delete和显式析构函数

//placenew2.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];

	JustTesting *pc1, *pc2;

	pc1 = new (buffer) JustTesting;
	pc2 = new JustTesting("Heap1", 20);

	cout << "Memory block address:\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;
	delete pc4;
	pc3->~JustTesting();
	pc1->~JustTesting();
	delete[] buffer;
	cout << "Done\n";
	return 0;
}
           

复习:模拟队列

12.10 queue.h

//queue.h -- interface for a queue
#ifndef QUEUE_H_
#define QUEUE_H_

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 Customer Item;

class Queue
{
private:
	struct Node { Item item; struct Node * next; };
	enum { Q_SIZE = 10 };
	Node * front;
	Node * rear;
	int items;
	const int qsize;
	Queue(const Queue & q) : qsize(0) {}
	Queue & operator=(const Queue & q) { return *this; }
public:
	Queue(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_H_

           

12.11 queue.cpp

//queue.cpp -- Queue and Customer methods
#include "queue.h"
#include <cstdlib>

Queue::Queue(int qs /* = Q_SIZE */) :qsize(qs)
{
	front = rear = NULL;
	items = 0;
}

Queue::~Queue()
{
	Node * temp;
	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;
	delete temp;
	if (items == 0)
		rear = NULL;
	return true;
}

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

12.12  bank.cpp

//bank.cpp -- using the Queue interface
//compile with queue.cpp
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "queue.h"

const int MIN_PER_HR = 60;

bool newcustomer(double x);

int main()
{
	using namespace std;
	srand(time(0));

	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;
	long turnaways = 0;
	long customers = 0;
	long served = 0;
	long sum_line = 0;
	int wait_time = 0;
	long line_wait = 0;

	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();
	}

	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 << "averahe wait time: " << (double)line_wait / served << " minutes\n";
	}
	else
		cout << "No customers!\n";
	cout << "Done!\n";
	return 0;
}


bool newcustomer(double x)
{
	return(rand()*x / RAND_MAX < 1);
}