- 对类成员使用动态内存分配
- 隐式和显式复制构造函数
- 隐式和显式重载赋值运算符
- 在构造函数中使用new所必须完成的工作
- 使用静态类成员
- 将定位new运算符用于对象
- 使用指向对象的指针
- 实现队列抽象数据类型
本章介绍对类使用 new和delete以及如何处理动态内存引起的一些微秒问题。
12.1 动态内存和类 12.1.1 复习示例和静态类成员//程序清单 12.1 #pragma once #ifndef STRNGBAD_H_ #define STRNGBAD_H_ #includeusing 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#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 createdn"; } StringBad::~StringBad() { cout << """ << str << "" object delete, " << --num_strings << " leftn"; 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 createdn"; } ostream& operator<<(ostream& os, const StringBad& st) { os << st.str; return os; }
不能在类声明中初始化静态成员变量,声明只描述如何分配内存,但并不分配内存。
静态类成员可以在类声明之外使用单独语句进行初始化,因为静态类成员是单独存储的,不是对象的组成部分。
初始化是在方法文件中,类声明位于头文件,程序可能将头文件包括在其他几个文件。如果在头文件中初始化,将出现多个初始化语句副本。
静态数据成员在类声明中声明,在类方法中初始化。
如果静态成员是整型或枚举型const,可以在类声明中初始化。
字符串并不保存在对象中。字符串单独保存在堆,对象仅指出字符串的地址。
//程序清单 12.3 #include#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"; }
12.1.2 特殊成员函数StringBad sailor = sports;
等效:StringBad sailor = StringBad(sports);
原型:StringBad(const StringBad& st);
使用对象初始化另一个对象时,自动生成复制构造函数,
没有new,也没有num_strings,但也会调用析构自动生成的赋值构造函数不知道需要更新静态变量num_string,因此将计数搞乱。
3次new,4次delete,所以出问题。
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 createdn";
}
12.1.4 Stringbad的其他问题:赋值运算符
提供赋值运算符(深度复制)定义:
- 由于目标对象可能引用了以前分配的数据,所以函数应使用delete[]释放
- 函数应避免将 对象赋给自身,否则给对象重新赋值前,释放内存可能把自己内容delete
- 函数返回指向对象的引用,连续赋值
12.2 改进后的新String类 12.2.1 修订后的默认构造函数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;
}
String::String()
{
len = 0;
str = new char[1];
str[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);
}
12.2.3 使用中括号表示访问字符if (operator == (String("love"), answer)
if (operator == ("love", answer)
if ("love" == answer)
12.2.4 静态类成员函数char& String::operator[](int i) //注意返回char& 不是char*
{
return str[i];
}有了上述定义:
cout << opera.operator[4];
简化为:
cout << opera[4];
- 不能通过对象调用静态成员函数
- 静态成员函数不能使用this指针
- 使用类名和作用域解析运算符调用它
- 静态成员函数不与特定对象相关联,只能使用静态数据成员。
12.2.5 进一步重载赋值运算符static int HowMany () { return num_strings; }
调用:
int count = String::HowMany();
//程序清单 12.4 #pragma once #ifndef STRING1_H_ #define STRING1_H_ #includeusing 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#pragma warning(disable:4996) int String::num_strings = 0; //构造函数及其他 String::String() { len = 0; str = new char[1]; str[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 #include12.3 在构造函数中使用new时应注意的事项#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 :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] == ' ') 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; }
- 构造函数中使用new来初始化指针成员,析构函数中应使用delete
- new对应delete,new [] 对应delete []
- 多个构造函数,必须以相同的函数使用new
- 定义(深度)复制构造函数和(深度)赋值运算符
例如,假设要编写函数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.3 返回对象被重载的算术运算符
12.4.4 返回const对象Vector Vector::opreator+(const Vector & b) const
{
return Vector(x + b.x, y + b.y);
}
避免 force1 + force2 = net和if (orce1 + force2 = net),这样的错误输入
将返回类型声明为const Vector
12.5 使用指向对象的指针//程序清单 12.7 #include12.5.1 再谈new和delete#include #include #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 :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] == ' ') 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; }
下属情况将调用析构函数:
- 对象是动态变量,执行完定义该对象的程序块,将调用该对象的析构函数
- 对象是静态变量,程序结束时将调用对象的析构函数
- 对象是new创建的,仅当显式使用delete删除对象时,其析构函数被调用
使用对象指针,需要注意几点:
- 常规表示法声明指向对象的指针:
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#include #include 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; }
12.7 队列模拟可以:
delete pc2;
delete pc4;但不可以:
delete pc1;
delete pc3;pc1和pc3没有使用new开辟内存,而是指向已开辟的内存
显式地为定位new运算符创建的对象调用析构函数:
pc3->~JustTesting();
pc1->~JustTesting();
delete[]buffer;应与创建顺序相反的顺序删除
//程序清单 12.10 queue.h #pragma once #ifndef QUEUE_H_ #define QUEUE_H_ #includeusing 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" #includeQueue::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暑期编程PK赛 得CSDN机械键盘等精美礼品!#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; }



