使用指向对象的指针
使用两个指向String的指针指向这些类别的开始位置。shortest指针指向数组中的第一个对象。每当程序找到比指向的字符串更短的对象时,就把shortest重新设置为指向该对象。同样,first指针跟踪按字母顺序排在最前面的字符串。这两个指针并不创建新的对象,而只是指向已有的对象。因此,这些指针并不要求使用new来分配内存。
main.cpp
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "string1.h"
const int ArSize = 10;
const int MaxLen = 81;
int main()
{
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]; //存储临时字符串
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; //i值没有增加
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] << "\n";
//使用指针指向shortest, first字符串
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; //随机选择索引
//利用new来创建并初始化新的String对象
String* favorite = new String(sayings[choice]);
cout << "My favorite saying:\n" << *favorite << endl;
delete favorite;
}
else
cout << "No much to say, eh?\n";
cout << "Bye.\n";
return 0;
}
指针favorite指向new创建的未被命名对象。这种特殊语法意味着使用对象saying[choice]来初始化新的String对象,这将调用复制构造函数,因为复制构造函数(const String &)的参数类型与初始化值(saying[choice])匹配。程序使用srand()、rand()、time()随机选择一个值。
String* favorite = new String(sayings[choice]);
使用new初始化对象
如果Class_name是类,value的类型为Type_name,则:
Class_name * pClass = new Class_name(value);
将调用如下构造函数:
Class_name(Type_name);
这里可能还有一些琐碎的转换:
Class_name(const Type_name &);
如果不存在二义性,则将发生由原型匹配导致的转换(从int到double)。下面初始化方式将调用默认构造函数:
Class_name * ptr = new Class_name;
由于随机选择用户输入的格言,即使输入相同,显示的结果也可能不同。
再谈new和delete
使用new为创建的每一个对象的名称字符串分配存储空间,这是在构造函数中进行,因此析构函数使用delete来释放这些内存。因为字符串是一个字符数组,所以析构函数使用的是带中括号的delete。当对象被释放时,用于存储字符串内容的内存将被自动释放。
使用new为整个对象分配内存:
String* favorite = new String(sayings[choice]);
这不是为要存储的字符串分配内存,而是为对象分配内存;为保存字符串地址的str指针和len成员分配内存(程序并没有num_string成员分配内存,因为num_string成员是静态成员,它独立于对象被保存)。创建对象将调用构造函数,后者分配用于保存字符串的内存,并将字符串的地址赋给str。
然后,当程序不再需要该对象时,使用delete删除它。对象是单个的,因此,程序使用不带中括号的delete。这将只释放用于保存str指针和len成员的空间,并不释放str指向的内存,而该任务将由析构函数来完成。
● 如果对象是动态变量,则当执行完定义该对象的程序块时,将调用该对象的析构函数。因此,执行完main()时,将调用headline[0]和headline[1]的析构函数;执行完callme1()时,将调用grub的析构函数。
● 如果对象是静态变量(外部、静态、静态外部、来自名称空间),则在程序结束时将调用对象的析构函数。
● 如果对象是用new创建的,则仅当显式使用delete删除对象时,其析构函数才会调用。
指针和对象小结
使用对象指针,注意几点:
● 使用常规表示法来声明指向对象的指针:
String *glamour;
● 可以将指针初始化为指向已有的对象:
String *first = &sayings[0];
● 可以使用new来初始化指针,这将创建一个新的对象:
String *favorite = new String(sayings[choice]);
● 对类使用new将调用相应的类构造函数来初始化新创建的对象:
//调用默认构造函数
String *gleep = new String;
//调用String(const char*)构造函数
String *glop = new String("my my my");
//调用String(const String &)构造函数
String *favorite = new String(sayings[choice]);
● 可以使用->运算符通过指针访问类方法:
if(sayings[i].length() < shortest->length())
● 可以对对象指针应用解除引用运算符(*)来获得对象:
if(sayings[i] < *first) //比较对象的值
first = &sayings[i]; //分配对象地址
再谈定位new运算符
定义new运算符在分配内存时能够指定内存位置。定义new运算符和常规new运算符给对象分配内存,其中定义的类的构造函数和析构函数都会显示一些信息,让用户能够了解对象的历史。
#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 << ", " << number << endl;
}
void Show() const
{
cout << words << ", " << number << endl;
}
};
int main()
{
char* buffer = new char[BUF]; //获得一块内存
JustTesting *pc1, *pc2;
pc1 = new (buffer) JustTesting; //把对象放入buffer
pc2 = new JustTesting("Heap1", 20); //把对象放在heap上
cout << "Memory block addresses:\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; //释放Heap1
delete pc4; //释放Heap2
delete[] buffer; //释放buffer
cout << "Done\n";
return 0;
}
使用new运算符创建了一个512字节的内存缓存区,然后使用new运算符在堆中创建两个JustTesting对象,并试图使用定位new运算符在内存缓存区中创建两个JustTesting对象。最后,它使用delete来释放使用new分配内存。
内存地址的格式和值将随系统而异。
使用new运算符时存在两个问题。
1)在创建第二个对象时,定位new运算符使用一个新对象来覆盖用于第一个对象的内存单元。如果类动态地为其成员分配内存,将引发问题。
2)将delete用于pc2和pc4时,将自动调用为pc2和pc4指向的对象调用析构函数;将delete[]用于buffer时,不会为使用定位new运算符创建的对象调用析构函数。
程序员必须负责管用定位new运算符用从中使用的缓存区内存单元。要使用不同的内存单元,程序员需要提供两个位于缓冲区的不同地址,并确保这两个内存单元不重叠。
pc1 = new(buffer) JustTesting;
pc3 = new(buffer + sizeof(JustTesting)) JustTesting("Better Idea", 6);
其中指针pc3相对于pc1的偏移量为JustTesting对象的大小。
如果使用定位new运算符来为对象分配内存,必须确保其析构函数被调用。对于在堆中创建的对象,可以这样做:
delete pc2; //删除指向pc2的对象
但不能像下面这样做:
delete pc1; //不能这样做
delete pc3;//不能这样做
原因在于delete可与常规new运算符配合使用,但不能与定位new运算符配合使用。
指针pc3没有收到new运算符返回的地址,因此delete pc3将导致运行阶段错误。指针pc1指向的地址与buffer相同,但buffer是使用new[]初始化的,因此必须使用delete[]而不是delete来释放。即使buffer是使用new而不是new[]初始化的,delete pc1也将释放buffer,而不是pc1。因为new/delete系统知道已分配的512字节块buffer,但对定义new运算符对该内存块做了何种处理一无所知。
确实释放了buffer。
delete[] buffer; //释放buffer
delete[] buffer;释放使用常规new运算符分配的整个内存块,但它没有为定位new运算符在该内存块中创建的对象调用析构函数。因此程序使用了一个显示信息的析构函数,宣布了"Heap1"和"Heap2"的死亡,但没有宣布"Just Testing"和"Bad Idea"的死亡。
解决方法:显式地为使用定位new运算符创建的对象调用析构函数。正常情况下将自动调用析构函数,这是需要显示调用析构函数的少数几种情形之一。显式地调用析构函数时,必须指定要销毁的对象。由于有指向对象的指针,因此可以使用这些指针:
pc3->~JustTesting(); //销毁指向pc3的对象
pc1->~JustTesting(); //销毁指向pc1的对象
对定义new运算符使用的内存单元进行管理,加入到合适的delete和显式析构函数调用。需要注意的一点是正确的删除顺序。对于使用定位new运算符创建的对象,应以与创建顺序相反的顺序进行删除。因为晚创建的对象可能依赖于早创建的对象。仅当所有对象都被销毁后,才能释放用于存储这些对象的缓冲区。
plancenew2.cpp
#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 << ", " << number << endl;
}
void Show() const
{
cout << words << ", " << number << endl;
}
};
int main()
{
char* buffer = new char[BUF]; //获得一块内存
JustTesting *pc1, *pc2;
pc1 = new (buffer) JustTesting; //把对象放入buffer
pc2 = new JustTesting("Heap1", 20); //把对象放在heap上
cout << "Memory block addresses:\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; //释放Heap1
delete pc4; //释放Heap2
//显式的销毁新放置的对象
pc3->~JustTesting(); //销毁指向pc3的对象
pc1->~JustTesting(); //销毁指向pc1的对象
delete[] buffer; //释放buffer
cout << "Done\n";
return 0;
}
使用定位new运算符在相邻的内存单元中创建两个对象,并调用了合适的析构函数。