使用指向对象的指针

使用指向对象的指针

使用两个指向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运算符在相邻的内存单元中创建两个对象,并调用了合适的析构函数。
在这里插入图片描述

Guess you like

Origin blog.csdn.net/qq_36314864/article/details/119808496