C++primeplus P368-P391

string

  • 1.定位new运算符
    • (1)介绍最基本的定位new
    • (2)定位new运算符用于对象
  • 2.重载<<运算符,转换函数,构造函数使用new
    • (1)重载<<运算符
    • (2)转换函数
    • (3)其构造函数使用new类
  • 3.类的补充

1.定位new运算符

(1)介绍最基本的定位new

通常,new负责在堆中找到一个足以满足要求的内存块。new运算符还有另一种变体,被称为定位new运算符。

功能:可以指定要在内存中的位置存放数据。

该内容简单,直接用一个小程序来说明即可:

#include
#include
using namespace std;

char buffer[5000];



int main()
{
	double* p1, * p2;   //先定义两个double类型的指针
	p1 = new double[100];    //开辟一个内存空间:(1)大小为100个double  (2)p1指向这个空间的起始位置
	p2 = new(buffer)double[100];    //开辟一个内存空间: (1)大小为100个double   (2)p2指向这个空间的起始位置   (3)这个内存空间从buffer的
	//内存空间占用,且占用起始位置为buffer数组的首地址。
	//可以输出他们的地址来求证
	for (int i = 0; i < 100; i++)   //先初始化赋值
	{
		p1[i] = p2[i] = i + 1;
	}

	cout << " buffer数组的首地址是:" << (void*)buffer << endl;
	cout << "数组p1的首地址是:" << p1 << endl;
	cout << "数组p2的首地址是:" << p2 << endl;
	//结果说明buffer的地址和p2的地址是一样的。
	//那么这就要注意了,虽然说p2数组是使用new运算符的,但是不能使用delete删除,因为本质上,p2是使用静态数组的空间,而不是从堆空间开辟的
	delete[]p1;


	return 0;
}

上面的程序说明了3点:

  1. 使用new运算符从静态数组(buffer)里“开辟”空间本质上没有开辟空间,只是利用了原本定义了的数组的内存,这里的数组应该是在栈空间,属于自动变量,所以不能使用delete来释放空间。
  2. 使用new运算符开辟空间,其初始位置和被占用空间的数组的头地址一样(上面程序里p2的值和buffer一样)
  3. 注意使用(void*)输出buffer,因为我们要得到的是一个地址,否则会输出一个字符串。

再加深一点
如果想要在buffer数组的中间位置新建一个数组,而不是在buffer的头位置新建数组,该怎么搞?
很简单,看下面的程序:

#include
#include
using namespace std;

char buffer[5000];



int main()
{
	double* p1, * p2;   //先定义两个double类型的指针
	p1 = new double[100];    //开辟一个内存空间:(1)大小为100个double  (2)p1指向这个空间的起始位置
	p2 = new(buffer)double[100];    //开辟一个内存空间: (1)大小为100个double   (2)p2指向这个空间的起始位置   (3)这个内存空间从buffer的
	//内存空间占用,且占用起始位置为buffer数组的首地址。
	//可以输出他们的地址来求证
	for (int i = 0; i < 100; i++)   //先初始化赋值
	{
		p1[i] = p2[i] = i + 1;
	}

	cout << " buffer数组的首地址是:" << (void*)buffer << endl;
	cout << "数组p1的首地址是:" << p1 << endl;
	cout << "数组p2的首地址是:" << p2 << endl;
	//结果说明buffer的地址和p2的地址是一样的。
	//那么这就要注意了,虽然说p2数组是使用new运算符的,但是不能使用delete删除,因为本质上,p2是使用静态数组的空间,而不是从堆空间开辟的
	delete[]p1;
	//delete[]p2  //error


	//第二阶段;增加一个偏移量,从而灵活使用buffer空间

	p2 = new(buffer + 100 * sizeof(double))double[100];   //可以这样理解:从buffer的头地址开始,依次移动100*8个字节,也就是buffer[800].那么
	//也就是说,从buffer[800]开始,一直到buffer[800+100*8]的位置都是归p2所有(即使这里面有数据)
	cout << endl << endl << "p2的地址是:" << p2 << endl;
	cout << "buffer[800]的地址是:" << (void*)&buffer[800] << endl << "buffer的地址是:" << (void*)buffer << endl;
	cout << " 会发现,p2的地址和buffer[800]是一样的,800这个偏移量没有算错,可能会发现地址只差了320,不是800,但是注意,地址是16进制!!\n";
		

	return 0;
}

(2)定位new运算符用于对象

定位new运算符用于对象有一点问题。具体是什么;看一下下面的程序,有详细说明:

#include
#include
#include

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 << " 构建\n";
	}

	~JustTesting()  //析构函数,里面加一个追踪功能
	{
		cout <<"对象--"<< words << " 析构\n";
	}

	void Show()const
	{
		cout << words << ", " << number << endl;
	}
};




int main()
{
	char* buffer = new char[BUF];   //使用常规new开辟对空间,buffer指向这个空间的首地址。等价于buffer[BUF],但是在堆空间中

	JustTesting* pc1, * pc2;    //两个对象指针

	pc1 = new (buffer)JustTesting;    //使用new的重载,定位new运算符
	pc2 = new JustTesting("Heap1", 20);   //常规new,开辟空间的同时调用构造函数初始化对象,然后让pc2指向这个对象


	cout << " 内存空间地址:\n" << "buffer: " << (void*)buffer << endl << "  pc2: " << pc2 << endl<<"pc1: "<<pc1<<endl;
	cout << "  内存里的数据:\n";
	cout << " pc1:";
	pc1->Show();
	cout << "pc2:";
	pc2->Show();


	cout << "上面的测试说明pc1不需要析构,也就是说,pc1的空间在buffer数组里面,还是自动变量,静态数组变量,不会调用析构函数。";
	delete pc2;
	return 0;

}

相信到这里,鉴于之前的重载复制函数和赋值运算符(上一篇博客C++P356-386),想到一个问题。如果说,使用定位new运算符导致对象不能析构,那么如果对象里面有字符串指针,那么岂不是不能释放空间了?

#include
#include
#include
#include

using namespace std;

const int BUF = 512;

class JustTesting
{
private:
	
	int number;
public:
	string words;
	JustTesting(const string& s = "Just Testing", int n = 0)   //默认构造函数(只能有一个)
	{
		words = s;
		number = n;
		cout << "对象--"<<words << " 构建\n";
	}

	~JustTesting()  //析构函数,里面加一个追踪功能
	{
		cout <<"对象--"<< words << " 析构\n";
	}

	void Show()const
	{
		cout << words << ", " << number << endl;
	}
};




int main()
{
	char* buffer = new char[BUF];   //使用常规new开辟对空间,buffer指向这个空间的首地址。等价于buffer[BUF],但是在堆空间中

	JustTesting* pc1, * pc2;    //两个对象指针

	pc1 = new (buffer)JustTesting;    //使用new的重载,定位new运算符
	pc2 = new JustTesting("Heap1", 20);   //常规new,开辟空间的同时调用构造函数初始化对象,然后让pc2指向这个对象


	cout << " 内存空间地址:\n" << "buffer: " << (void*)buffer << endl << "  pc2: " << pc2 << endl<<"pc1: "<<pc1<<endl;
	cout << "  内存里的数据:\n";
	cout << " pc1:";
	pc1->Show();
	cout << "pc2:";
	pc2->Show();


	cout << "上面的测试说明pc1不需要析构,也就是说,pc1的空间在buffer数组里面,还是自动变量,静态数组变量,不会调用析构函数。\n\n\n";

	JustTesting* pc3, * pc4;
	pc3 = new(buffer)JustTesting("Bad idea", 6);
	pc4 = new JustTesting("Heap2", 10);

	cout << "  内存里的数据:\n";
	cout << " pc3:";
	pc3->Show();
	cout << "pc4:";
	pc4->Show();
	delete pc2;
	delete pc4;
	cout << pc3->words<<endl;
	pc3->~JustTesting();     //显示使用析构函数即可
	delete[]buffer;
	return 0;

}

总结:使用定位new运算符创建对象,那么要显示使用析构函数,否则对象不会被析构。这有什么坏处呢?暂时也不知道.好像释放buffer空间就相当于析构了对象了,毕竟对象是在buffer里面创建的。

2.重载<<运算符,转换函数,构造函数使用new

(1)重载<<运算符

要重新定义<<运算符,以便将它和cout一起用来显示对象的内容,要像下面这样:

ostream &operator<<(ostream &os,const c_name &obj)  //c_name是类名
{
	os<<...;
	return os;
}

(1)如果该类提供了能够返回所需内容的公有方法,则可以在运算符函数中使用这种方法,这样就不需要设置为友元函数了。
(2)返回类型必须是引用

(2)转换函数

1.如果要将单个值转换为类类型,需要创建原型如下所示的类构造函数:
c_name(type_name value);
其中,c_name是类名,type_name是要转换的类型的名称。
2.如果要将类类型转换为其它类型,则需要创建原型如下的类成员函数
operator type_name();
即使该函数没有类型,但是仍然需要返回所需类型的值.
如果不想被隐式转换,应该使用关键字explicit;

(3)其构造函数使用new类

如果类使用new运算符来分配类成员指向的内存,在设计时应采取一些预防措施

  • 对于指向的内存是由new分配的所有类成员,都应在类的析构函数中对其使用delete。
  • 如果析构函数通过对指针类成员使用delete来释放内存,则每个构造函数都应当使用new来初始化指针,或将它设置为空指针
  • 构造函数中要么使用new[],要么使用new,不能混合使用。如果构造函数使用new,则析构函数应该使用delete,如果构造函数使用new[],则相应的析构函数应该使用delete[];
  • 应定义一个分配内存的复制构造函数,这样程序将能够将类对象初始化为另一个对象。这种构造函数的原型通常如下:
    className(const className&);
  • 应定义一个重载赋值运算符的类成员函数。
    c_name & c_name::operator=(const c_name& cn)
    {
    if(this==&cn)
    return *this;
    delete [] c_pointer;
    c_pointer=new type_name[size];

    return *this;
    }

3.类的补充

(1)嵌套结构和类
在类声明中声明的结构,类,或枚举被称为是被嵌套在类中,其作用域为整个类。这种声明不会创建数据对象(当然,声明只是声明),只是说明了这个类型可以在类中使用。
如果在类的私有部分声明,则只能在该类中使用被声明的类型;
如果在类的公有部分声明,则可以从类的外部通过作用域解析运算符使用被声明的类型。

(2)类的常量成员初始化
如果在类中又const 的成员,则只能初始化,而不能赋值。所以对于const类型数据(和引用类型数据),必须在执行到构造函数体之前,即创建对象时初始化。C++提供了一种特殊的初始化方式,叫初始化列表(只有构造函数可以使用)。成员初始化列表由逗号分隔的初始化列表组成(前面带冒号)。它位于参数列表的右括号之后,函数体左括号之前。如果数据成员的名称为mdata,需要初始化为val,则初始化mdata(val)。

一个例子来说明初始化列表的使用和语法:

//class是一个类,a,b,c都是类的数据成员。则应该这样使用初始化列表
class ::class(int n,int m) :a(n), b(0), c(n*m)
{
...
}

上面的代码将a初始化为n,b初始化为0,c初始化为n*m;从概念上来说,这些工作都是创建对象的时候完成的,因为此时没有执行大括号里面的代码块。
注意几点:

  1. 初始化列表只能用于构造函数
  2. 必须使用这种格式来初始化非静态的const数据成员(C++11之前)
  3. 必须使用这样格式来初始化引用数据成员

你可能感兴趣的:(c++prime,plus,c++,算法,开发语言)