何时使用新建和删除
我正在重读一段时间以前关于C++的代码(我现在正在学校学习Java),我有点困惑什么时候必须使用delete
。
例如: 声明两个对象时:
Fraction* f1;
Fraction* f2;
和创建f1
和f2
如下:
f1 = new Fraction(user_input1, user_input2);
f2 = new Fraction(user_input3, user_input4);
下次要使用new
运算符创建新对象时,是否必须先使用delete
?我感到困惑是因为我习惯了让Java中的垃圾收集器来处理对象及其删除。再次使用new
之前必须delete
吗?
if (f1) delete f1;
if (f2) delete f2;
// initialize again...
解决方案
我不会告诉您何时使用delete
,而是尝试解释为什么您无论如何都要使用指针。因此您可以决定何时使用动态对象、如何使用它们以及何时调用delete
(以及不调用)。
经验法则:
- 尽可能使用静态对象,然后在需要时创建指向该实例的指针。不需要
delete
调用。 - 如果创建指向动态对象的指针,请创建清理代码。因此,当您编写
new
时,也要将delete
写入到合适位置(并确保该位置被调用)。 - 对于每个
new
关键字,需要是delete
关键字。否则,您将占用机器拥有的所有资源,从而导致应用程序崩溃或停止。它还会使系统变慢。
静态创建对象:
Fraction f1;
- 不需要删除任何内容,退出创建它的独家新闻时会处理这些内容。
动态创建对象:
Fraction* f1;
现在您有了指向堆上内存块的地址。它是无效的,因为您没有为其分配任何内容。根据声明位置的不同,最佳做法是为其分配NULL
(Windows)或0
(跨平台)。
Fraction* f1 = 0;
何时使用delete
一旦您创建了动态对象,从而调用了new
运算符,您就需要在某个地方调用delete
。
int main()
{
Fraction* f1 = 0; // Good practise to avoid invalid pointers
// An invalid pointer - if( f1 ){ Access violation }
f1 = new Fraction(); // Could have done this at the previous line
/* do whatever you need */
if( f1 )
{
delete f1;
f1 = 0; // not needed since we are leaving the application
}
return 0;
}
在某些情况下,拥有一个Fraction
数组或指向它的指针可能很有用。为简单起见,此处使用int
,与跳过错误处理相同:
int arr[ 10 ];
int cur = -1;
int* Add( int fraction )
{
arr[++cur] = fraction;
return &arr[cur];
}
// Usage:
Add( 1 );
Add( 4 );
这里发生了一件事,没有通过动态对象分配给任何内存。它们会自动释放。该函数返回的指针是指向静态内存块的指针。
将arr
作为指向int
的指针时:
int* arr[ 10 ];
int cur = -1;
int* Add( int* fraction )
{
arr[++cur] = fraction;
return arr[cur];
}
// Usage:
int* test;
test = Add( new int( 1 ) );
test = Add( new int( 4 ) );
现在您必须修复由于没有清理代码而泄漏的内存块。
当您在每个Add(...)
delete test
之后调用时,您清理了内存,但丢失了int* arr[ 10 ]
中存储的值,因为它们指向保存该值的内存。
您可以创建另一个函数,并在处理完这些值后调用此函数:
void CleanUp()
{
for( int a = 0; a < 10; ++a )
delete arr[ a ];
}
小用法示例:
int* test;
int test2;
test = Add( new int( 1 ) );
test2 = *Add( new int( 4 ) ); // dereference the returned value
/* do whatever you need */
CleanUp();
为什么要使用指针:
int Add( int val )
{
return val; // indeed very lame
}
调用需要参数(类型)的函数时,不是传入实例,而是实例的副本。在上面的函数中,您将返回该副本的副本。这将相当于大量复制所有涉及的内存,并使您的应用程序变得非常慢。
考虑一下:
class Test
{
int t;
char str[ 256 ];
}
如果函数需要类型Test
,则需要复制int
和256个字符。因此,使该函数只需要指向Test
的指针。则使用指针所指向的内存,不需要复制。
int Add( int val )
{
val++;
return val;
}
在最后一个示例中,我们向val
的副本添加1,然后返回该副本的副本。
int i = Add( 1 );
结果:i=2;
void Add( int* val )
{
// mind the return type
*val++;
}
在此示例中,您将地址传递给一个值,然后在取消引用后将1添加到该值。
int i = 1;
Add( &i );
结果:i=2;
现在您已将地址传递给i
,而不是复制它。在该函数中,您可以直接将该内存块的值加1。由于您更改了内存本身,因此不返回任何内容。
为空/测试有效指针
有时您会遇到这样的例子:
if( p != 0 ) // or if( p )
{
/* do something with p */
}
这只是为了检查指针p
是否有效。然而,无效的地址--因此不指向您已保留的内存(访问冲突)--也将通过。对于您的代码,无效指针是有效地址。
因此,要使用此类检查,您必须NULL
(或0
)指针。
Fraction* f1 = 0;
当f1 == 0
时,它不指向任何内容,否则它指向它指向的任何内容。
当您在已创建或未创建的"main"类中有指针时,此功能非常有用。
class Fraction
{
public:
int* basicFeature;
int* ExtendedFeature = 0; // NULL this pointer since we don't know if it
// will be used
Fraction( int fraction )
{
// Create a pointer owned by this class
basicFeature = new int( fraction );
}
Fraction( int fraction, int extended ) // mind the static
: Fraction( fraction )
{
// Create a pointer owned by this class
ExtendedFeature = new int( extended );
}
~Fraction()
{
delete basicFeature;
if( ExtendedFeature )
// It is assigned, so delete it
delete ExtendedFeature;
}
}
通过构造函数,我们创建了两个指针,因此在析构函数中,我们清理了这些指针。仅检查ExtendedFeature
,因为可能会创建也可能不会创建。basicFeature
始终创建。
您可以通过调用一个新函数来替换if
语句,包括其在析构函数中的作用域:removeExtendedFeature()
,其中该函数实现将是:
Fraction::removeExtendedFeature()
{
if( ExtendedFeature )
{
// It is assigned, so delete it
delete ExtendedFeature;
// Now it is important to NULL the pointer again since you would
// get an access violation on the clean up of the instance of
// the class Fraction
ExtendedFeature = 0;
}
}
和新的析构函数:
Fraction::~Fraction()
{
delete basicFeature;
removeExtendedFeature();
}
清空的另一个功能可能是:
int Fraction::getValue()
{
int result = *basicFeature;
if( ExtendedFeature )
result += *ExtendedFeature;
return result;
}
我为这个糟糕的类片段道歉,因为它有一个更糟糕的扩展功能。但作为一个例子,它将达到目的。
相关文章