现在的位置: 首页 > 综合 > 正文

[C++基础]C++内存分配那些事

2012年10月11日 ⁄ 综合 ⁄ 共 5681字 ⁄ 字号 评论关闭
文章目录

动静态内存分配

静态内存分配:编译器在处理程序源代码时(即编译时)分配。

动态内存分配:程序执行时调用运行时刻库函数来分配。

静态与动态内存分配的两个主要区别:

  • 静态对象是有名字的变量,我们直接对其进行操作。而动态对象是没有名字的变量,我们通过指针间接地对它进行操作。 
  • 静态对象的分配与释放由编译器自动处理。程序员需要理解这一点,但不需要做任何事情。相反,动态对象地分配与释放,必须由程序员显示地管理,相对来说比较容易出错 。

new/delete

C语言中进行内存的动态分配与释放,我们使用malloc()free()函数。C++中不再使用C语言中的malloc()free()函数进行内存的动态分配与释放。因为,malloc()函数在运行时从自由内存中分配存储单元。C++中创建对象时会发生两件事情:(1)为对象分配内存;(2)调用构造函数来初始化那个内存。而构造函数不允许向它传递内存地址来进行初始化。

C++中使用newdelete来进行内存的动态分配与释放。new会触发类对象的构造函数,delete会触发类对象的析构函数。

动态申请内存操作符 new

语法格式:类型名T *指针变量名P = new  类型名T(初值列表);

功能:在程序执行期间,申请用于存放T类型对象的连续的、未命名的内存空间,并依初值列表赋以初值。

结果值:成功:T类型的指针,指向新分配的内存。失败:0NULL

new运算符是一个一元运算符。它隐含地生成一个函数来调用函数operator new(),从这个函数返回被分配内存对象的指针。 

new表达式中,可以在类型名后使用空圆括号:new 数据类型T()功能:将初始化为数据类型T的默认值。 若new 数据类型T,没有在T后加(),则表明没有初始化数据类型T的默认值

new 数据类型T;new 数据类型T();的区别

eg:int *ptrInt1 = new int;   // Value is unknown

     int *ptrInt1 = new int(); // Value is zero

说明:第一种情况下,在空闲存储区中创建了一个int类型对象,但其值未知。在第二种情况下,在空闲存储区中创建了一个int类型对象并初始化为int在全局作用域中的值(默认值)。而所有的全局基本类型在默认情况下的 初始值为0,故第二种情况在创建了对象后将其值初始化为0

释放内存操作符delete

语法格式:delete 指针变量名Pdelete [ ]  指针变量名P

功能:释放指针P所指向的内存。P必须是new操作的返回值。delete表达式首先调用析构函数,然后释放内存(经常调用free( ))。若正在删除的指针是0,则将不发生任何事情,是安全的。故开发人员经常在删除指针后立即把指针赋值为0以免对它删除两次(对一个对象删除两次可能会产生某些问题)。

eg:delete pi;注意这时释放了pi所指的目标的内存空间,也就是撤销了该目标,称动态内存释放(dynamic memory
deallocation),但指针pi本身并没有撤销,该指针所占内存空间并未释放。 删除一个指针p(delete p;)实际意思是删除了p所指的目标(变量或对象等),释放了它所占的自由存储区空间,而不是删除p本身,p成了空悬指针。空悬指针是程序错误的一个根源)。建议这时将p置空(NULL)。

如何释放单个对象的空闲空间

语法格式:delete 指针变量名P指针变量名P = NULL;

功能:释放指针P所指向的内存。P必须是new操作的返回值。

删除基于0的指针(空指针)总是一种安全的操作。 

如何为对象数组分配空闲空间

语法格式:数据类型T *指针变量名P = new 数据类型T[元素个数];

功能:在空闲空间分配对象数组,指针P指向数组的第一个元素。如果在方括号中的表达式的值为0,则就会分配没有元素的数组,new表达式返回的指针是非0值,并且与其他指向任何对象的指针都不同。如果这个值在运行期间被确定是无效的,则结果不可预知。

保持平衡

涉及到new以及delete的主要规则相当简单,使用delete来平衡每一个new如果在new语句中使用了方括号(即分配了对象数组),必须在delete后使用方括号。无论何时使用new,一定要确保使用正确的delete格式。

浅拷贝与深拷贝

浅拷贝:实现对象间数据元素的一一对应复制。

深拷贝:当被复制的对象数据成员是指针类型时,不是复制该指针成员本身,而是将指针所指的对象进行复制。

深拷贝实例

// test.cpp : 定义控制台应用程序的入口点。 
#include "stdafx.h"
#include <iostream>
using namespace std;
// string类,深度复制实例
class student
{
	char *pName; //为了演示深复制,不用string类
public:
	student(); //默认构造函数
	student(char *pname); //带参数构造函数
	student(student &s); //复制(拷贝)构造函数
	~student(); //析构函数
	student & operator=(student &s); //复制赋值操作符
}; 
student::student()
{
	cout<<"Constructor";
	pName=NULL;
	cout<<"默认"<<endl;
}
student::student(char *pname)
{
	cout<<"Constructor";
	if(pName=new char[strlen(pname)+1])
		strcpy(pName,pname);
	cout<<pName<<endl;
}
student::~student()
{
	cout<<"Destructor"<<pName<<endl;
// 	if(pName) 
// 		pName[0]='\0';//===>>*pName='\0';//间接的把不是指针的变量置为空('')。当然,此处没必要也不需要判断来间接设置其指向的变量为空
	delete[] pName;//释放字符串
} 
student::student(student &s)
{
	cout<<"Copy Constructor";
	if(s.pName)
	{
		if(pName=new char[strlen(s.pName)+1])
			strcpy(pName,s.pName);
	}
	else 
		pName=NULL;
	cout<<pName<<endl;
}
student & student::operator=(student &s)
{
	cout<<"Copy Assign operator";
	delete[] pName;//如原来已分配,应先撤销,再重分配。先删除自己分配的空间
	if(s.pName)//否则会发生内存泄漏
	{ 
		if(pName=new char[strlen(s.pName)+1])
			strcpy(pName,s.pName);}
	else 
		pName=NULL;
	cout<<pName<<endl;
	return *this;//this是一个指针,它的作用域在类的内部。而函数返回的是对象,所以 return *this;
}
int main(void)
{
	student s1("范英明"),s2("沈俊"),s3;
	student s4=s1;
	s3=s2;
	return 0;//加断点调试,你回发现堆变量删除释放(由系统自动释放)的顺序与其创建的顺序相同。
}
void test()
{
	int val=66;
	int *pval = &val;
	int **ppval = &pval;
	cout<<"val="<<val<<'\n'<<"**ppval="<<**ppval<<'\n';//输出: 66 66
	**ppval=18;
	cout<<"val="<<val<<'\n'<<"**ppval="<<**ppval<<endl; //输出: 18 18
}

对象成员的另一种访问方式

指 针,声明形式:类名  *对象指针名;eg:

Point A(5, 10);

Piont *ptr = NULL;

ptr = &A;

通过指针访问对象成员,对象指针名->成员名,ptr->getx()  相当于  (*ptr).getx();

指针与引用的优劣

  1. 指针是个变量,可以把它再赋值成其它的地址(水性杨花) 。然而建立引用时必须进行初始化并且决不会再指向其他不同的变量(从一而终) 。
  2. 有空指针,无空引用。
  3. 由于指针也是变量,所以可以有指针变量的引用。egint * a = NULL;  int * & p = a;// (表示 int * 的引用初始化为 a)  int   b = 5;  p = &b ; // p 的一个别名,是一个指针
  4. 引用本身不是一种数据类型,所以没有引用的引用,也没有引用的指针。eg:int  a;  int   & r = a;   // (表示  int  * 的引用初始化为 a)  int  & * p = & r ;   //  错, 企图定义一个引用的指针由于指针也是变量,所以可以有指针变量的引用 
  5. 使用引用和指针都可以使函数返回多个值。
  6. 在参数传递方面:引用比指针直观,可读性强,尽量使用引用代替指针,以下两条除外。a,如果必须传递数组,使用指针加元素个数。b,如果参数可能无效(null),使用指针,并判断(p == null)。如果输入参数以值传递的方式传递对象,则宜改用“const &”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率。
  7. 返回值方面:如果返回动态分配的对象或内存,必须使用指针,引用可能引起内存泄漏。 operator = 的返回值必须是 Cxxx &
  8. 可以使用指针或引用的时候,除了传递参数和返回值,其它地方都应该是指针更好一些。
  9. 必须使用引用的地方:operator = 的返回值必须是 Cxxx &;运算符重载的时候:operator + (const Cxxx& lhs, const Cxxx & rhs); 不能用指针。如果想重载[ ]并且想让他和数组用法完全一致,那么返回的类型必须是引用类型,此时对于类或结构体应注意对象是否需要深度拷贝。   

实例

#include "stdafx.h"
#include <iostream>
using namespace std;
void GetMemory1(char* p,int num)
{
	p=new char[num];
}
//如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”
void GetMemory2(char** p,int num)
{
	*p=new char[num];
}
//或者改用“指向指针的引用”
void GetMemory3(char* &p,int num)
{
	p=new char[num]; 
}
//或者改用函数返回值类传递动态内存
char* GetMemory4(int num)
{
	char *p=new char[num]; 
	return p;//必须放回的是堆内存存储区,静态全局存储区,或者返回只读的常量存储区
}
char* GetMemory5()
{
	char p[]="hello world";//数组p是栈内存,没有用new,"hello world"给数组p赋值,数组p是被分配连续的内存空间 
	return p;//return语句返回不能是指向"栈内存"的指针,因为该内存在函数结束时自动消亡
}
char* GetMemory6()
{
	char* p="hello world";//"hello world"是字符串常量,位于常量存储区,它在程序生命期内恒定不变。无论什么时候调用GetMemory6,它返回的始终是同一个“只读”的内存块。
	return p;
}
void test()
{
// 	char* str=NULL;
// 	GetMemory1(str,100);//str仍然为NULL
// 	strcpy(str,"hello");//运行有误
	// 	cout<<str<<endl;
// 	delete [] str; 
// 	str=NULL;
	
// 	char* str=NULL;
// 	GetMemory2(&str,100);
// 	strcpy(str,"hello");
	// 	cout<<str<<endl;
// 	delete [] str; 
// 	str=NULL;

// 	char* str=NULL;
// 	GetMemory3(str,100);
// 	cout<<str<<endl;
// 	strcpy(str,"hello");
// 	cout<<str<<endl;
// 	delete [] str; 
// 	str=NULL;

// 	char* str=NULL;
// 	str=GetMemory4(100);
// 	strcpy(str,"hello");
// 	cout<<str<<endl;
// 	delete [] str; 
// 	str=NULL;

// 	char* str=NULL;
// 	str=GetMemory5();//GetMemory5()返回后str不再是NULL指针,但str的内容不是"hello world",而是垃圾信息,可以是乱码
// 	strcpy(str,"hello");
// 	cout<<str<<endl; 

	char* str=NULL;
	str=GetMemory6();//GetMemory6()返回的是一个"只读"的内存块
	cout<<str<<endl; //输出"hello world"
	strcpy(str,"hello");
	cout<<str<<endl; 
}
int _tmain(int argc, _TCHAR* argv[])
{
	test();
	return 0;
}

那些总结

  1. 指针p指向常量字符串“world”(位于静态存储区,内容为world\0),常量字符串的内容是不可以被修改的。
  2. return语句不可返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

抱歉!评论已关闭.