类和动态内存分配——C++ Prime Plus CH12

①动态内存和类

1.复习示例和静态类成员

使用程序复习new和delete用法。

// badstring.h文件
#include<iostream>
#ifndef STRING_BAD_
#define STRING_BAD_

class Stringbad
{
private:
	char* str;
	int len;
	static int num_strings;          //静态类成员
public:
	Stringbad();
	Stringbad(const char * s);
	~Stringbad();
	friend std::ostream& operator<<(std::ostream & os,const Stringbad& St);
};
#endif

程序说明:

1.静态类成员:特点,无论创建了多少个对象,程序都只将创建一个静态类变量副本。假设创建10个Stringbad 对象,将有10个str成员和10个len成员,但只有一个共享的num_string成员。通常字符串类不需要这样的成员吗,这里只是为了引出问题。

2.变量是 char指针,而不是数组,这意味着并没有为字符串本身分配存储空间,而是在构造函数中使用new来为字符串分配空间。 

//badtring.cpp
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstring>
#include"stringbad.h"

using std::cout;
int Stringbad::num_strings = 0;

//char* str;
//int len;
//static int num_strings;          //静态类成员
//public:
Stringbad::Stringbad()
{
	len = 4;
	str = new char[4];
	std::strcpy(str, "C++");
	num_strings++;
	cout << num_strings << ": \"" << str<< "\" object created\n";       //提醒
}

Stringbad::Stringbad(const char* s)
{
	len = std::strlen(s);
	str = new char[int(len + 1)];
	std::strcpy(str, s);
	num_strings++;
	cout << num_strings << ": \"" << str << "\" object created\n";       //提醒
}
Stringbad::~Stringbad()
{
	cout << ": \"" << str << "\" object deleted,\n";          //提醒
	num_strings--;
	cout << num_strings << "lefted.\n";               //提醒
	delete [] str;

}
std::ostream& operator<<(std::ostream& os, const Stringbad& St)
{
	os << St.str;
	return os;
 }

程序说明:

1.静态类成员不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存。对于静态类成员,可以在类声明之外使用单独的语句来进行初始化,这是因为静态类成员是单独存储的,而不是对象的组成部分,并且初始化中使用了作用域运算符,但没有使用关键字static。

2.初始化是在方法文件中,而不是在头文件中,这是因为类声明位于头文件中,程序很可能将头文件包括在其他几个文件中,这将出现多个初始化语句,引发错误。

3.构造函数中并不是简单的将字符串指针进行赋值:

str = s;

 上述语句只保存了地址,没有创建字符串的副本。所以要采用复制字符串的方式来及进行构造。

3.析构函数中将str分配的内存释放掉。

//use_badstring.cpp
#include<iostream>
#include"stringbad.h"
using std::cout;
using std::endl;
void callme1(Stringbad& rsb);
void callme2(Stringbad rsb);

int main()
{
	
	{
		cout << "Starting an inner block.\n";
		Stringbad headline1("Celery Stalks at midnight");
		Stringbad headline2("Lettuce Prey");
		Stringbad sports("Spinach Leaves bowl for Dollars");
		cout << "headline1: " << headline1 << endl;
		cout << "headline2: " << headline2 << endl;
		cout << "sports: " << sports << endl;


		//调用函数
		callme1(headline1);     //引用传递
		cout << "headline1: " << headline1 << endl;
		callme2(headline2);         //值传递
		cout << "headline2: " << headline2 << endl;

		//创建新变量并初始化
		Stringbad sailor = sports;
		cout << "sailor: " << sailor << endl;

		//赋值未初始化变量
		Stringbad knot;
		knot = headline1;
		cout << "knot: " << knot << endl;
		cout << "Exiting the block.\n";
	}
	cout << "End of the main()\n";
	return 0;
}

void callme1(Stringbad& rsb)
{
	cout << "String passed by reference:\n";
	cout << "\"" << rsb << "\"\n";
}

void callme2(Stringbad rsb)
{
	cout << "String passed by value:\n";
	cout << "\"" << rsb << "\"\n";
}

代码结果:

 结果出现了异常!!!我们一步步分析:

1.我们可以看到,下边的语句运行时正常的。

cout << "Starting an inner block.\n";
		Stringbad headline1("Celery Stalks at midnight");
		Stringbad headline2("Lettuce Prey");
		Stringbad sports("Spinach Leaves bowl for Dollars");
		cout << "headline1: " << headline1 << endl;
		cout << "headline2: " << headline2 << endl;
		cout << "sports: " << sports << endl;

2.callme1()函数运行正常:

		callme1(headline1);     //引用传递
		cout << "headline1: " << headline1 << endl;

3.严重的问题:

callme2(headline2);         //值传递
		cout << "headline2: " << headline2 << endl;

现象:

首先,按值传递防止原始参数被修改,实际上函数已使得原始字符串无法识别。程序出错,导致最后余下-2个对象,并且在删除sports时,最后删除的两个对象(headline1  headline2)已无法识别。

原因:

1.析构函数次数比num_strings递增次数多两次,但结果表明程序使用了第三个构造函数:

Stringbad sailor = sports;

这使用的是哪个构造函数呢?这种初始化函数调用了一个复制构造函数(编译器自动生成的),

上述代码相当于

Stringbad sailor = Stringbad(sports);

这个例子说明的所有问题都是由编译器自动生成的成员函数引起的。 

 2.特殊成员函数:

Stringbad类的问题是由特殊成员函数引起的。这些成员函数是自动定义的,具体的说,C++自动提供了下面这些成员函数:

默认构造函数,默认析构函数,复制构造函数,赋值运算符,地址运算符。

更准确的说,编译器将生成上述最后三个函数的定义。C++11提供了移动构造函数和移动赋值运算符,这将在18章讨论。

隐式地址运算符返回调用对象的地址(this指针的值),在此不详细讨论该成员函数。

1.默认构造函数:

如果没有提供任何构造函数,C++将创建默认构造函数,编译器将提供一个不含任何参数,也不执行任何操作的构造函数,也就是说,它的值在初始化时是未知的。

如果定义了构造函数,C++将不会定义默认构造函数,如果希望在创建对象时可以不显式地对它进行初始化,则必须显式地定义默认构造函数。

带参数地构造函数只要所有参数都有默认值,则这个函数也是默认构造函数,但只能有一个默认构造函数。

2.复制构造函数:

复制构造函数用于将一个对象复制到新创建的对象中,它用于初始化过程中(包括按值传递参数),而不是常规的赋值过程中。类的复制构造函数原型通常如下:

Class_name(const Class_name &);

何时调用复制构造函数:

新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。

	Stringbad motto;
		Stringbad ditto(motto);
		Stringbad metoo = motto;
		Stringbad also = Stringbad(motto);
		Stringbad* pStringbad = new Stringbad(motto);

后边四个都会调用复制构造函数,中间的两个声明可能使用复制构造函数直接创建metto 和 also,也可能使用复制构造函数生成一个临时对象,然后将临时对象的内容赋给metoo also,这取决于具体的实现,最后一个声明使用motto初始化一个匿名对象,毕竟新对象的地址赋给pstring指针。

每当程序生成了对象副本时,编译器都将使用复制构造函数。具体地说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。按值传递意味创建原始变量的一个副本,编译器生成临时对象时,也将使用复制构造函数。合适生成临时对象随编译器而异,但无论哪种编译器,当按值传递和返回对象时,都将调用复制构造函数。因此我们应该按引用来传递对象。

默认的复制构造函数功能:

默认的复制构造函数逐个复制非静态成员(浅复制),复制的是成员的值。静态成员不受影响,一因为它们属于整个类,而不是各个对象。

3.赋值运算符:

 C++允许类对象赋值,这是通过自动为类重载赋值运算符实现的。这种运算符的原型如下:

Class_name & Class_name::operator

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值