C++ Primer Plus学习笔记-第十二章:类和动态内存分配

前言:

本章主要阐述对类成员使用动态内存分配的技术细节,并复习前面提到的复制构造函数,赋值运算符,静态类成员,定位new运算符,并动手实现string类;

下面这个文件提供了一个不太好的string类实现方式:

#include <iostream>
#ifndef STRINGBAD_H_
#define STRINGBAD_H_
class StringBad
{
private:
	char * str;//指向字符串的指针
	int len;//字符串的长度
	static int num_strings;//string类对象的个数,但这个变量对类功能的实现来说不是必须的
public:
	StringBad(const char * s);//使用字符串初始化对象的构造函数
	StringBad();//不使用参数的构造函数
	~StringBad();//析构函数
	friend std::ostream & operator<<(std::ostream & os, const StringBad & st);//友元函数,作用是重载<<运算符
};
#endif

注意:无论创建了多少对象,程序都只创建一个静态的类变量副本!

下面这个文件为上面的头文件提供了类函数的定义:

#include<cstring>
#include "stringbad.h"
using std::cout;

StringBad::StringBad(const char * s)
{
	len = std::strlen(s);
	str = new char[len + 1];
	std::strcpy(str,s);
	num_strings++;
}

StringBad()
{
	len = 4;
	str = new char[4];
	std::strcpy(str,"C++");
	num_strings++;
}

~StringBad()
{
	--num_strings;
	delete [] str;
}

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

注意:不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存;

另外,初始化类中的静态变量时不使用static关键字:

int StringBad::num_strings = 0;
//关键字static只在变量声明中出现

例外情况是:静态数据成员为整型或枚举类型,可以在类定义中提供声明;

当定义一个类时,C++自动提供这些类方法:

  • 默认构造函数,如果没有定义构造函数
  • 默认析构函数,如果没有定义
  • 复制构造函数,如果没有定义
  • 赋值运算符,如果没有定义(执行浅复制)
  • 地址运算符,如果没有定义
    更准确的说,编译器会生成后面三个函数定义;

复制函数用于将一个对象复制到新创建的对象中;函数原型是这样的:

Class_name(const Class_name &);
//新建一个类对象并初始化为同类现有对象时,复制构造函数会被使用
//每当程序生成了对象副本时,编译器都将使用复制构造函数

默认的复制构造函数逐个复制非静态成员,但只进行浅复制;如果类成员本身也是一个类对象,程序会智能调用相应的复制函数,默认也执行浅复制;

要进行深复制,唯一的方法是提供一个显式复制构造函数:

StringBad::StringBad(const String & s)
{
	num_strings++;
	...//进行显示复制,申请内存空间并复制内容
}

**必须进行深层复制的构造函数的原因是:**一些类成员是使用new初始化的,指向数据的指针,而不是数据本身;**当使用delete释放原来的对象时,会导致被赋值对象的值发生设计之外的变化;

除了复制运算符外,还有赋值运算符,函数原型是:

Class_name & Class_name::operator=(const StringBad &);

在自定义赋值构造函数时应当遵守这些规则:

  • 由于目标对象可能引用了以前的数据,所以构造函数应使用delete[]来释放这些数据
  • 函数应当避免将对象赋值给自身;否则,在给对象重新赋值前,释放内存操作可能删除对象的内容
  • 函数返回一个指向调用对象的引用

下面的代码说明了如何为StringBad类编写赋值运算符:

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

现在我们可以丰富StringBad这个类了,向其中添加以下方法:

int length() const {return len}
friend bool operator<(const String &st, const String &st2);
friend bool operator>(const String &st, const String &st2);
friend bool operator==(const String &st, const String &st2);
friend operator>>(istream & is, String & st);
char & operator[](int i);
const char & operator[](int i) const;
static int HowMany();

这是修订后的默认构造函数:

String::String()
{
	len = 0;
	str = new char[1];
	str[0] = '\0';
}
//注意到new使用了中括号,这是为了和使用delete []的析构函数兼容
//delete[]对使用new []的构造函数创建的内存空间和空指针都兼容,但对new不兼容

**表示空指针的三种方法:NULL,0,nullptr(这是C++11独有的特性,也是最建议使用的表示方法);

可以将类成员函数声明为静态的,但这样做不能通过对象调用静态成员函数,而是使用类的作用域解析运算符调用;并且由于静态函数本身不和特定的类对象相关联, 因此只能使用静态数据成员;

现在让我们完成String类的设计。

下面是类声明:

#ifndef STRING1_H_
#define STRING1_H_
#include <iostream>

using std::ostream;
using std::istream;

class String
{
private:
	char * str;
	int len;
	static int num_strings;
	static const int CINLIM = 80;
public:
	String(const char * s);
	String();
	String(const String &);
	~String();
	int length() const {return len;}
	String & operator=(const String &);
	String & operator=(const * char);
	char & operator[](int i);
	const char & operator[](int i);
	friend bool operator<(const String & st, const   String & st2);
	friend bool operator>(const String & st, const String & st2);
	friend bool operator==(const String & st, const String & st2);
	friend ostream & operator<<(ostream & os, const String & st);
	friend istream & operator>>(istream & is,String & st);
	static int HowMany();
}

上面的文件给出了类定义,下面文件给出了类方法的定义:

#include<cstring>
#include "string1.h"
using std::cin;
using std::cout;

int String::num_strings = 0;

int String::HowMany()//返回String对象的总数
{
	return num_strings;
}

String::String(const char * s)//构造函数
{
	len = std::strlen(s);
	str = new char[len + 1];
	std::strcpy(str,s);
	num_strings++;
}

String::String()//构造函数
{
	len = 4;
	str = new char[1];
	str[0] = '\0';
	num_strings++;
}

String::String(const String & st)//构造函数
{
	num_strings++;
	len = st.len;
	str = new char[len + 1];
	std::strcpy(str,st.str);
}

String::~String()
{
	--num_strings;
	delete [] str;
}

String & String::operator=(const String & st)
{
	if (this == &st)
		return *this;
	delete [] str;
	len = st.len;
	str = new char[len + 1];
	std::strcpy(str,st.str);
	return *this;
}

String & String::operator(const char * s)
{
	delete [] str;
	len = std::strlen(s);
	str = new char[len + 1];
	std::strcpy(str,s);
	return *this;
}

char & String::operator[](int i)
{
	return str[i];
}

const char & String::operator[](int i) const
{
	return str[i];
}

bool operator<(const String & st1, const String & st2)
{
	return (std::strcmp(st1.str, st2.str) == 0);
}

bool operator>(const String & st1, const String & st2)
{
	return st2 < st1;//使用了上面开发的方法
}

bool operator==(const String & st1, const String & st2)
{
	return (std::strcmp(st1.str, st2.str) == 0);
}

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

istream & operator>>(istream & is, String & st)
{
	chat temp[String::CINLIM];
	is.get(temp,String::CINLIM);
	if (is)
		st = temp;
	while (is && is.get() != '\n')
		continue;
	return is;
}

下面是在构造函数中使用new的注意事项:

  • 如果在构造函数中使用new来初始化指针成员,则应在析构函数中使用delete
  • new和delete必须相互兼容,new对应delete而new []对应delete []
  • 如果有多个构造函数,则必须以相同的方式使用new,因为只有一个析构函数,所有构造函数都要和它兼容,不过可以在一个构造函数中使用new初始化指针,而在另一个构造函数中将指针初始化为空,因为delete无论带不带中括号都可以用于空指针
  • 应定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象
  • 应定义一个赋值运算符,通过深度复制将一个对象赋值给另一个对象

默认的逐成员复制和赋值有一定的智能,会在合适的场景调用合适的复制或赋值函数;

定位new运算符

当在一段已经创建了一个内容的内存空间中,再创建第二个内容会导致对象被覆盖;程序员可以手动管理内存:

pc1 = new (buffer) JustTesting;
pc3 = new (buffer + sizeof (JustTesting)) JustTesting("Better Idea", 6);

注意:delete可以和new搭配使用,但是不能和定位new搭配使用!要释放内存,只能通过释放内存段本身的方式实现;另一种解决办法是:显式的为使用定位new运算符创建的对象调用析构函数:

pc3->~JustTesting();
pc1->~JustTesting();
//注意删除顺序应当和创建顺序相反,因为后创建的对象可能是依赖于早创建的对象
//仅当所有对象都被销毁后,才能释放用于存储这些对象的缓冲区
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值