类和动态内存分配

一、动态内存和类

C++让程序在运行时决定内存分配,而不是在初始编译时就决定。这样可以根据程序的需要,而不是根据一系列严格的存储类型规则来使用内存。
C++使用new和delete运算符来动态控制内存,但是需要注意使用的方法和编程问题。

//示例类StringBad的头文件 stringbad.h

#include <ostream>
#ifndef STRINGBAD_H_
#define STRINGBAD_H_

class StringBad
{
private:
	char * str;       //指向string类型的指针
	int len;				//string类型长度
	static int num_strings;		//类对象的个数

public:
	StringBad (const char * s); //构造函数
	StringBad(); //默认构造函数
	~StringBad();    //析构函数
	friend std::ostream & operator<<(std::operator & os, const StringBad & st);
};
#endif



//示例类StringBad的方法实现文件
#include <cstring>
#include "stringBad.h"
using std::count;

int num_strings = 0;      //静态成员变量初始化。

StringBad::StringBad(const char * s)
{
	len = std::strlen(s);    //设置大小
	str = new char[len + 1];	//分配内存
	std::strcpy(str,s);        //初始化指针
	num_strings++;
	cout<<num_string<<": "<<str<<" object created\n"
}

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

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

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

1.1、静态成员的声明与初始化

对于上述类,要注意不能在类声明中初始化静态成员,因为类声明描述了如何分配内存,但是没有实际分配内存,可以在类声明以外创建对象,从而分类和初始化内存,注意初始化的时候不需要再有static关键字。对于静态成员,在类声明以外使用单独的语句初始化是因为静态成员变量是单独存储的,不是对象的组成部分。
特例:静态数据成员是整形或枚举型const,则可以在类声明中进行初始化。

1.2、构造函数分配内存并赋值新内存地址

类成员str是一个指向string的指针,构造函数必须提供内存来存储字符串,初始化对象时,可以给构造函数传递一个字符串指针,如下例:

StingBad boston("Boston");

构造函数必须分配足够的内存来存储字符串(len+1),然后将字符串复制到内存中(strcpy()),再将新内存的地址赋给str成员。所以可以理解字符串并不存储在对象中,字符串单独保留在堆内存中,对象仅保存了指出到哪里去查找字符串的信息。
这里len要加一的原因是,strlen()函数返回字符串内字符的长度,但是不包括末尾的空字符,因此new的时候要加一,确保分配的内存足够,能够存储包含空字符的字符串。

1.3、析构函数释放new分配的内存

str成员指向new分配的内存,当StringBad对象过期的时候,类成员str指针也将过期,但是str指向的内存地址还处于被分配状态,这个时候需要使用delete将其释放。
删除一个对象可以释放对象本身占用的内存,但并不能自动释放属于对象成员的指针所指向的内存,因此需要使用析构函数,在析构函数内使用delete语句可以确保在类对象过期时,释放由构造函数内的new运算符分配的内存。
特别的,delete对应了new,delete []对应了new []释放内存。

1.4、特殊成员函数

在类的使用过程中,会有一些成员函数被自动定义出来,有些情况下这些自动定义的成员函数将与类本身的行为相违背。
如果程序使用对象的方式要求这么做,编译器将自动生成对应的定义。包括:
***默认构造函数:如果没有定义构造函数
***默认析构函数:如果没有定义析构函数
***拷贝构造函数:如果没有定义
***赋值运算符:如果没有定义
***地址运算符:返回调用对象的地址。

1.4.1、默认构造函数

如果没有提供构造函数,则C++自动创建默认构造函数,即编译器将会提供一个不接受任何参数,也不执行任何操作的构造函数,它的值在初始化时是未知的。
如果定义了构造函数,C++将不会在定义默认构造函数,此时需要程序员自己定义默认构造函数,包括不带参数或带全部参数且有默认值两种中的一种。

1.4.2、拷贝构造函数

拷贝构造函数用于将一个对象复制到新创建的对象中。它接受一个指向类对象的常量引用作为参数。形如

StringBad (const StringBad &;

在新建一个类对象将其初始化为同类现有对象时,拷贝构造函数将被调用。,比如:
StringBad a(motto);
StingBad b= motto;
StringBad c = StringBad (motto);
StringBad *d = new StringBad(motto); //使用motto初始化了一个匿名的对象,并将该对象的地址赋给了d。

a、当程序中创建了对象副本,比如函数按值传递对象或直接返回对象时,都将会调用拷贝构造函数。
b、因为按值传递会创建对象副本,所以尽量按引用传递类对象,节省调用构造函数的时间以及存储新对象的空间。
c、拷贝构造函数将逐个赋值类内非静态成员,复制的是成员的值。如果成员本身就是类对象,则使用这个类的拷贝构造函数复制成员对象。静态成员不参与复制,因为静态成员不属于对象,而是属于整个类。

NULL 表示空指针的C语言宏,C++11中使用关键字nullptr,用于表示空指针,这种表示方法更好。

1.5、静态成员函数

函数声明中包括staic,将成员函数声明为静态的。

1.5.1、静态成员函数的调用

因为声明为静态后,不再属于某个类对象,所以不能通过类对象调用,也不能使用this指针。
但是如果被声明为public,则可以使用类名和作用域解析运算符进行调用,比如:
StringBad::howMuch();

1.5.2、静态成员函数使用成员

由于静态成员函数不与特定的类对象关联,所以无法使用常规类数据成员,只能使用静态数据成员,比如只能访问StringBad类内的num_strings,但是不能访问len或str。

二、在构造函数中使用new

2.1、注意事项

a、如果在构造函数中使用new初始化对象,则要在析构函数中使用delete释放分配的内存。
b、new和delete运算符要互相配套使用,new对应delete,new[]对应delete[]。
c、如果有多个构造函数,必须以相同的方式使用new,要么都带中括号,要么都不带。因为只有一个析构函数,所有的构造函数要与它匹配。
d、应主动定义一个拷贝构造函数,而不是编译器隐式完成,深度复制将一个对象初始化为另一个对象。此处要求复制拷贝函数应分配足够的内存存储复制的数据,并复制数据源,而不只是获取数据的地址,还需要更新所有受影响的静态成员。形式如下例:

String :: String (const String & st)
{
	num_strings++;
	len = st.len;//设置相同大小
	str = new[len +1];//分配内存
	std::strcpy(str, st.str);   //复制内容
} 

e、定义一个赋值运算符。深度复制将一个对象复制给另一个对象。形式如下例:

String & String operator= (const String & st)
{
	if(this == &st)
	{
		return &this;
	}
	delete [] str;
	len = st.len;//设置相同大小
	str = new[len +1];//分配内存
	std::strcpy(str, st.str);   //复制内容
	return *this;
} 
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值