本章内容包括:
- 对类成员使用动态内存分配
- 隐式和显式复制构造函数
- 隐式和显式重载赋值运算符
- 在构造函数中使用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;
}