C++ 第五周

类和动态内存分配

动态内存和类

       C++ 使用 new 和 delete  运算符来动态内存分配。遗憾的是,在类中使用这些运算符将导致许多新的编程问题。这时,析构函数是必不可少的,有时候还必须重载运算符。

       新的存储类型静态类成员.

       下面是一个示例:

// strngbad.h
#include<iostream>
#ifnedf STRNGBAD_H_
#define STRNGBAD_H_
class StringBad
{
	private:
		char * str;
		int len;
		static int num;
	public:
		StringBad(const char * s);
		StringBad();
		~StringBad();
	// friend function
	    friend std::ostream & operator<<(str:ostream & os,
		                    const StringBad & st);
};
#endif

       静态类成员有一个特点:无论创建了多少对象,程序都只创建一个静态类变量副本。也就是说,类的所有对象共享同一个静态成员,这对于所有类对象都具有相同值的类私有数据是非常方便。

// strngbad.cpp
#include<cstring>
#include "strngbad.h"
using std::cout;

int String::num = 0;

// class methods
StringBad::StringBad(const char * s)
{
	len = std::strlen(s);
	str = new char[len + 1];
	std::strcpy(str,s);
	num++;
	cout << num <<": \"" <<str
	     << "\" object created\n";
}

StringBad::StringBad()
{
	len = 4;
	str = new char[4];
	std::strcpy(str,"C++");
	num++;
	cout << num <<": \"" <<str
	     << "\" default object created\n";
}

StringBad::~StringBad()
{
	cout << "\"" << str << "\" objcet deleted, ";
	--num;
	cout << num << " left\n";
	delete [] str;
}

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

       注意:不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存 . 但如果静态成员是 const 或枚举型,则可以在类声明中初始化.

       另外,在构造函数中使用new时,必须在相应的析构函数中使用delete

特殊成员函数

(1)默认构造函数

       如果没有提供任何构造函数,C++ 将创建默认构造函数。例如,假如定义了一个 Klunk 类,但没有提供任何构造函数,编译器将提供下述默认构造函数:

Klunk::Klunk()
{
}
// 一个不接受参数,不执行操作的构造函数

Klunk lunk; 

       默认构造函数使 lunk 类似于一个常规的自动变量,它的值在初始化时未知的。

       如果定义了构造函数,C++将不会定义默认构造函数。如果希望在创建对象时不显式地对它进行初始化,则必须显式定义默认构造函数。它没有任何参数,但可以用来设定特定的值:

Klunk::Klunk()
{
	Klunk_ct = 0;
	...
}

       带参数的构造函数也可以是默认构造函数,只要所有参数都有默认值。例如,Klunk 类可以包含下述内联构造函数:

Klunk(int n = 0) 
{
    Klunk_ct = n;
}

       但只能有一个默认构造函数。

(2)默认析构函数

(3)复制构造函数

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

Class_name(const Class_name &);

       对于复制构造函数需要知道两点: 何时调用  , 有何功能 .

       何时调用:

       新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。很多情况下都可能发生,最常见的情况是将新对象显式地初始化为现有的对象。以下四种声明都将调用复制构造函数:

StringBad ditto(motto);
StringBad metoo = motto;
StringBad also = StringBad(motto);
StringBad * pStringBad = new StringBad(motto);

       有何功能:

       默认的复制构造函数逐个复制非静态成员(成员复制也被称为浅复制),复制的是成员的值。

下述语句:

StringBad sailor = sports;

// 与下面的代码等效
StringBad sailor;
sailor.str = sports.str;
sailor.len = sports.len;
// 由于私有成员无法访问,因此这三行代码不能通过编译

       如果成员本身就是类对象,则将使用这个类的复制构造函数来复制成员对象。静态成员不受影响,因为他们属于整个类,而不是各个对象。

       注意:如果类中包含了使用 new 初始化的指针成员,应当定义一个复制构造函数,以复制指向的数据,而不是指针,这被称为深度复制。复制的另一种形式(成员复制或浅复制)只是复制指针值。浅复制仅浅浅地复制指针信息,而不会深入挖掘以复制指针引用的结构 

(4)赋值运算符

       赋值运算符只能由类成员函数来重载它.

       对于由于默认赋值运算符不合适而导致的问题,解决方法是提供赋值运算符(进行深度复制)定义。其实现与复制构造函数相似,但也有一些差别。

       1.由于目标对象可能引用了以前分配的数据,所以函数应使用delete[ ] 来释放这些数据.

       2.函数应当避免将对象赋值给自身;否则,给对象重新赋值前,释放内存操作可能删除对象的内容.

       3.函数返回一个指向调用对象的引用.

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;
}

在构造函数中使用 new 时应注意的事项

       1. 如果在构造函数中使用new来初始化指针成员,则应在析构函数中使用delete.

       2. new 和 delete 必须相互兼容,new 对应于delete,new [ ] 对应于 delete [ ] .

       3. 如果有多个构造函数,则必须以相同的方式使用new,要么都带 [ ] , 要么都不带。因为只有一个析构函数,所有的析构函数都必须与他兼容。然而,可以在一个构造函数中使用new 初始化指针,而在另一个构造函数中将指针初始化为空,这是因为delete 可以用于空指针.

有关返回对象的说明

返回指向 const 对象的引用

       如果函数返回(通过调用对象的方法或将对象作为参数)传递给它的对象,可以通过返回引用来提高效率。例如,要编写函数 Max(),它返回两个Vector对象中较大的一个。该函数将以下面的方式被使用:

Vector force1(50,60);
Vector force2(10,70);
Vector max;
max = Max(force1,force2);

以下两种实现都是可行的:

// version1
Vector Max(const Vector & v1, const Vector & v2)
{
	if(v1.magval() > v2.magval)
	{
		return v1;
	}
	else{
		return v2;
	}
}

// version2
const Vector & Max(const Vector & v1, const Vector & v2)
{
	if(v1.magval() > v2.magval)
	{
		return v1;
	}
	else{
		return v2;
	}
}

       首先,返回对象将调用复制构造函数,而返回引用不会。所以第二个实现效率更高。其次,引用指向的对象应该在调用函数指向时存在。

返回指向非 const 对象的引用

       两种常见的返回非 const 对象情形是,重载赋值运算符以及重载与 cout 一起使用的 << 运算符。

返回对象

       如果被返回的对象是被调用函数中的局部对象,则我们就不能够返回指针引用。当控制权毁掉调用函数时,引用已经不存在了。所以在这种情况下,我们应该放回对象而不是引用。

返回 const 对象

假如我们直接返回一个对象,那么我们可能会在程序中遇到一些容易令人误解的语句

net = v1 + v2;
v1 + v2 = net;
cout<<(v1 + v2 = net).getLength();


当我们返回了const类型的对象,我们能够阻止令人生畏的后两句的出现.
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DDsoup

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值