C++_primer_plus学习笔记 第12章 类和动态内存分配

本章内容包括:

  • 对类成员使用动态内存分配
  • 隐式和显式复制构造函数
  • 隐式和显式重载赋值运算符
  • 在构造函数中使用new所必须完成的工作
  • 使用静态类成员
  • 将定位new运算符用于对象
  • 使用指向对象的指针
  • 实现队列抽象数据类型

本章介绍对类使用 new和delete以及如何处理动态内存引起的一些微秒问题。

12.1 动态内存和类

12.1.1 复习示例和静态类成员

//程序清单 12.1
#pragma once
#ifndef STRNGBAD_H_
#define STRNGBAD_H_

#include <iostream>

using namespace std;

class StringBad
{
public:
	StringBad();
	~StringBad();
	StringBad(const char* s);

	friend ostream& operator<<(ostream& os, const StringBad& st);
private:
	char* str;		//char* 没有为字符串分配内存, 在构造函数中用new分配
	int len;
	static int num_strings;		//类声明中声明,类方法中初始化,不属于任何类对象,共用
};

#endif // !STRNGBAD_H_

 使用char指针(而不是char数组)来表示姓名。意味着类声明没有为字符串本身分配存储空间,而是在构造函数中使用new来为字符串分配空间。

//程序清单 12.2
#include "strngbad.h"
#include <cstring>
#pragma warning(disable:4996)        //vs中使用strcpy、strlen

int StringBad::num_strings = 0;      //放外面,单独赋值,初始化,使用::,没有static

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 delete, " << --num_strings << " left\n";
	delete[] str;		//构造中的new开辟内存,析构中delete释放
}
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";
}

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

 不能在类声明中初始化静态成员变量,声明只描述如何分配内存,但并不分配内存。

静态类成员可以在类声明之外使用单独语句进行初始化,因为静态类成员是单独存储的,不是对象的组成部分。

初始化是在方法文件中,类声明位于头文件,程序可能将头文件包括在其他几个文件。如果在头文件中初始化,将出现多个初始化语句副本。

静态数据成员在类声明中声明,在类方法中初始化。

如果静态成员是整型或枚举型const,可以在类声明中初始化。

字符串并不保存在对象中。字符串单独保存在堆,对象仅指出字符串的地址。

//程序清单 12.3
#include <iostream>
#include "strngbad.h"

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

int main(void)
{
	StringBad headline1("Celery Stalks at Midnight");
	StringBad headline2("Lettuce Prey");
	StringBad sports("Spinach Leaves Bowl for Dollars");
	cout << endl;

	cout << "headline1: " << headline1 << endl;
	cout << "headline2: " << headline2 << endl;
	cout << "sports: " << sports << endl;
	cout << endl;

	callme1(headline1);
	cout << "headline1: " << headline1 << endl;
	callme2(headline2);
	cout << "headline2: " << headline2 << endl;

	cout << "Initialize one object to another:\n";
	StringBad sailor = sports;		
	//StringBad sailor = StringBad(sports);
	//原型:StringBad(const StringBad& st);
	//使用对象初始化另一个对象时,自动生成复制构造函数,
	//没有new,也没有num_strings,但也会调用析构
	cout << "sailor: " << sailor << endl;
	cout << "Assign one object to another:\n";
	StringBad knot;
	knot = headline1;
	cout << "knot: " << knot << endl;

	return 0;
}

void callme1(StringBad& rsb)	//按引用传递
{
	cout << "String passed by reference:\n";
	cout << " \"" << rsb << "\"\n";
}

void callme2(StringBad sb)		//按值传递
{
	cout << "String passed by value:\n";
	cout << " \"" << sb << "\"\n";
}

StringBad sailor = sports; 

等效:StringBad sailor = StringBad(sports);
原型:StringBad(const StringBad& st);
使用对象初始化另一个对象时,自动生成复制构造函数,
没有new,也没有num_strings,但也会调用析构

自动生成的赋值构造函数不知道需要更新静态变量num_string,因此将计数搞乱。

3次new,4次delete,所以出问题。

12.1.2 特殊成员函数

C++自动提供的成员函数:

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

StringBad类中的问题是隐式复制构造函数和隐式赋值运算符引起的。

复制构造函数用于将一个对象复制到新创建的对象中。

复制构造函数用于初始化过程中,而不是赋值过程中。

下面4种声明都将调用复制构造函数:StringBad(const StringBad &)

StringBad ditto(motto);

StringBad metoo = motto;

StringBad also = StringBad(motto);

StringBad * pStringBad = new StringBad(motto);

 每当程序生成对象副本,编译器都将使用复制构造函数。

具体说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。

由于按值传递对象将调用复制构造函数,应该按引用传递对象,节省时空,避免使用复制构造函数

12.1.3 回到Stringbad:复制构造函数的哪里出了问题

定义一个显式复制构造函数解决问题

StringBad::StringBad(const StrngBad & st)
{
	len = st.len;
	str = new char[len + 1];
	strcpy(str, st.str);
	num_strings++;
	cout << num_strings << ": \"" << str << "\" object created\n";
}

12.1.4 Stringbad的其他问题:赋值运算符

提供赋值运算符(深度复制)定义:

  • 由于目标对象可能引用了以前分配的数据,所以函数应使用delete[]释放
  • 函数应避免将 对象赋给自身,否则给对象重新赋值前,释放内存可能把自己内容delete
  • 函数返回指向对象的引用,连续赋值

StringBad & StringBad::operator=(const StringBad & st)

{
        if (this == &st)

                return *this;

        delete[] str;

        len = st.len;

        str = new char[len + 1];

        std::strcpy(str, st,str);

        return *this;
}

12.2 改进后的新String类

12.2.1 修订后的默认构造函数

String::String()
{
    len = 0;
    str = new char[1];
    str[0] = '\0';
    //str = 0;
    //str = nullptr;
}

12.2.2 比较成员函数

bool operator>(const String& s1, const String& s2)
{
    if (strcmp(s1.str, s2.str) > 0)
	    return true;
    else
        return false;
}
//简化为:
bool operator>(const String& s1, const String& s2)
{
	return (strcmp(s1.str, s2.str) > 0);
}
//同样:
bool operator<(const String& s1, const String& s2)
{
	return (s2 > s1);		//使用刚重载的>
}

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

 if (operator == (String("love"), answer)

 if (operator == ("love", answer)

 if ("love" == answer)

12.2.3 使用中括号表示访问字符

char& String::operator[](int i)        //注意返回char& 不是char*
{
    return str[i];
}

有了上述定义:

cout << opera.operator[4];

简化为:

cout << opera[4];

12.2.4 静态类成员函数

  • 不能通过对象调用静态成员函数
  • 静态成员函数不能使用this指针
  • 使用类名和作用域解析运算符调用它
  • 静态成员函数不与特定对象相关联,只能使用静态数据成员。

static int HowMany () { return num_strings; }

调用:

int count = String::HowMany();

12.2.5 进一步重载赋值运算符

//程序清单 12.4
#pragma once
#ifndef STRING1_H_
#define STRING1_H_

#include <iostream>

using namespace std;

class String
{
public:
	//构造函数及其他
	String();
	~String();
	String(const char* s);
	String(const String& st);
	int length() const { return len; }

	//运算符重载
	String& operator=(const String& st);
	String& operator=(const char* s);
	char& operator[](int i);
	const char& operator[](int i) const;

	//友元函数
	friend bool operator>(const String& s1, const String& s2);
	friend bool operator<(const String& s1, const String& s2);
	friend bool operator==(const String& s1, const String& s2);
	friend ostream& operator<<(ostream& os, const String& st);
	friend istream& operator>>(istream& is, String& st);

	//静态函数
	static int Howmany();
private:
	char* str;		//char* 没有为字符串分配内存, 在构造函数中用new分配
	int len;
	static int num_strings;		//类声明中声明,类方法中初始化
	static const int CINLIM = 80;
};

#endif // !STRING1_H_

 
//程序清单 12.5
#include "string1.h"
#include <cstring>
#pragma warning(disable:4996)

int String::num_strings = 0;

//构造函数及其他
String::String()
{
	len = 0;
	str = new char[1];
	str[0] = '\0';		//str = 0;或str = nullptr;
	num_strings++;
}
String::~String()
{
	--num_strings;
	delete[] str;
}
String::String(const char* s)
{
	len = strlen(s);
	str = new char[len + 1];
	strcpy(str, s);
	num_strings++;
}
String::String(const String& st)	//显式定义复制构造函数
{
	len = st.len;		//调用成员变量
	str = new char[len + 1];
	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];
	strcpy(str, st.str);
	return *this;		//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)		//注意返回char& 不是char*
{
	return str[i];
}
const char& String::operator[](int i) const
{
	return str[i];
}

//友元函数
bool operator>(const String& s1, const String& s2)
{
	return (strcmp(s1.str, s2.str) > 0);
}
bool operator<(const String& s1, const String& s2)
{
	return (s2 > s1);		//使用刚重载的>
}
bool operator==(const String& s1, const String& s2)
{
	return (strcmp(s1.str, s2.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;		//String& String::operator=(const char* s);
	while (is && is.get() != '\n')		//超过80
		continue;
	return is;
}

//静态函数
int String::Howmany()
{
	return num_strings;
}
//程序清单 12.6
#include <iostream>
#include "string1.h"

const int ArSize = 10;
const int MaxLen = 81;

int main(void)
{
	String name;
	cout << "What is your name? ";
	cin >> name;
	cout << name << ", please enter up to " << ArSize 
        << " short sayings <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: ";
		for (int i = 0; i < total; i++)
			cout << sayings[i][0] << ": " << sayings[i] << endl;
		int shortest = 0;
		int first = 0;
		for (int 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 << "The program used " << String::Howmany() << " string objects.\n";
	}
	else
		cout << "No input! Bye.\n";

	return 0;
}

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

  • 构造函数中使用new来初始化指针成员,析构函数中应使用delete
  • new对应delete,new [] 对应delete []
  • 多个构造函数,必须以相同的函数使用new
  • 定义(深度)复制构造函数和(深度)赋值运算符

12.4 有关返回对象的说明

12.4.1 返回指向const对象的引用

例如,假设要编写函数Max(),返回两个Vector对象中较大的一个

Vector force1(50, 60);

Vector force2(10, 70);

Vector max;

max = Max(force1, force2);

下面两种实现都可行:

//version 1

Vector Max(const Vector & v1, const Vector & v2)

{

        if (v1.magval() > v2.magval())

                return v1;

        else

                return v2;

}

//version 2

const Vector& Max(const Vector & v1, const Vector & v2)

{

        if (v1.magval() > v2.magval())

                return v1;

        else

                return v2;

}

  • 返回对象调用复制构造函数,返回引用不会
  • 引用指向的对象应在调用函数时存在,不能返回临时对象,引无可引
  • v1和v2都为const引用,返回也必须为const引用

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

常见为:重载赋值运算符和重载<<运算符

12.4.3 返回对象

被重载的算术运算符

Vector Vector::opreator+(const Vector & b) const

{

        return Vector(x + b.x, y + b.y);

}

12.4.4 返回const对象

避免 force1 + force2 = net和if (orce1 + force2 = net),这样的错误输入

将返回类型声明为const Vector

12.5 使用指向对象的指针

//程序清单 12.7
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "string1.h"

const int ArSize = 10;
const int MaxLen = 81;

int main(void)
{
	String name;
	cout << "What is your name? ";
	cin >> name;
	cout << name << ", please enter up to " << ArSize << " short sayings <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: ";
		for (int i = 0; i < total; i++)
			cout << sayings[i][0] << ": " << sayings[i] << endl;

		//int shortest = 0;
		//int first = 0;
		//将对象指针初始化为指向已有对象
		String* shortest = &sayings[0];
		String* first = &sayings[0];

		for (int i = 1; i < total; i++)
		{
			//if (sayings[i].length() < saying[shortest].length())
			if (sayings[i].length() < shortest->length())
				//shortest = i;
				shortest = &sayings[i];
			//if (sayings[i] < saying[shortest])
			if (sayings[i] < *first)
				//first = i;
				first = &sayings[i];
		}

		cout << "Shortest saying:\n" << *shortest << endl;
		cout << "First alphabetically:\n" << *first << endl;
		srand(time(0));
		int choice = rand() % total;
		//使用new来初始化指针对象,new将调用相应的构造函数
		String* favorite = new String(sayings[choice]);
		cout << "My favorite saying:\n" << *favorite << endl;
		delete favorite;
	}
	else
		cout << "No input! Bye.\n";

	return 0;
}

12.5.1 再谈new和delete

下属情况将调用析构函数:

  • 对象是动态变量,执行完定义该对象的程序块,将调用该对象的析构函数
  • 对象是静态变量,程序结束时将调用对象的析构函数
  • 对象是new创建的,仅当显式使用delete删除对象时,其析构函数被调用

12.5.2 指针和对象小结

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

  • 常规表示法声明指向对象的指针:

String * glamour;

  • 指针初始化为指向已有的对象:

String * first = &sayings[0];        //对象数组第一个元素地址

  • 使用new初始化指针,创建新对象

String * favorite = new String(sayings[chioce]);        //new开辟内存,调用相应构造函数

  • 对类使用new将调用相应类构造函数来初始化新建的对象

//调用默认构造函数

String * gleep = new String;

//调用String(const char *)

String * glop = new String("my my my");

//调用String(const String &)

String * favorite = new String(sayings[choice]);

 

12.5.3 再谈定位new运算符

//程序清单 12.8
#include <iostream>
#include <string>
#include <new>

using namespace std;
const int BUF = 512;

class JustTesting
{
public:
	JustTesting(const string& s = "Just Testing", int n = 0)
	{
		words = s;
		numbers = n;
		cout << words << " constructed.\n";
	}
	~JustTesting() { cout << words << " destroyed.\n"; }
	void show() const { cout << words << ", " << numbers << endl; }
private:
	string words;
	int numbers;
};

int main(void)
{
	char* buffer = new char[BUF];
	JustTesting* pc1, * pc2;

	pc1 = new(buffer) JustTesting;
    //pc1没有开辟新内存,只是定位,pc1和buffer是同一地址
	pc2 = new JustTesting("Heap1", 20);

	cout << "\nMemory block address:\n";
	cout << "buffer: " << (void*)buffer << "\theap: " << pc2 << endl;
    //                    类型转换
	cout << "\nMemory contents:\n";
	cout << pc1 << ": ";
	pc1->show();
	cout << pc2 << ": ";
	pc2->show();

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

	cout << "\nMemory contents:\n";
	cout << pc3 << ": ";
	pc3->show();
	cout << pc4 << ": ";
	pc4->show();

	delete pc2;
	delete pc4;
	//显式地为new定位运算符调用析构函数
	//按创建顺序相反的顺序删除
	pc3->~JustTesting();
	pc1->~JustTesting();
	delete[]buffer;

	cout << "Done.\n";

	return 0;
}

 可以:

    delete pc2;
    delete pc4;

但不可以:

    delete pc1;
    delete pc3;

pc1和pc3没有使用new开辟内存,而是指向已开辟的内存

显式地为定位new运算符创建的对象调用析构函数:

    pc3->~JustTesting();
    pc1->~JustTesting();
    delete[]buffer;

应与创建顺序相反的顺序删除

12.7 队列模拟

//程序清单 12.10 queue.h
#pragma once
#ifndef QUEUE_H_
#define QUEUE_H_

#include <iostream>

using namespace std;

class Customer
{
public:
	Customer() { arrive = processtime = 0; }
	
	void set(long when);
	long when() const { return arrive; }
	int ptime() const { return processtime; }

private:
	long arrive;
	int processtime;
};

typedef Customer Item;

class Queue
{
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);

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

#endif // !QUEUE_H_
//程序清单 12.11
#include "queue.h"
#include <cstdlib>

Queue::Queue(int qs) :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 = rand() % 3 + 1;
	arrive = when;
}
//程序清单 12.12
#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);
		customers++;
	}
	cout << "Customers: " << customers << endl;

	while (!line.isempty())
	{
		line.dequeue(temp);
		customers--;
	}
	cout << "Now customers: " << customers << endl;

	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值