与其告诉你什么时候使用delete,我会试着解释你为什么要使用指针。因此,您可以决定何时使用动态对象、如何使用它们以及何时调用delete(而不是)。
经验法则:
- 尽可能使用静态对象,然后在需要时创建指向该实例的指针。不需要
delete 呼叫。
- 如果创建指向动态对象的指针,请创建清理代码。所以当你写
new 时,也要写delete somehwere at a proper location(并确保它被调用)。
- 对于每个
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++;
}
在此示例中,您将地址传递给一个值,然后 - 在取消引用之后 - 将一个值添加到该值。
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 时,它不指向任何东西,否则它指向它所指向的任何东西。
当您在“主”类中有一个指针,该指针已创建或未创建时,这很有用。
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;
}
我为蹩脚的分数类道歉,它具有越来越蹩脚的扩展功能。但作为一个例子,它可以达到目的。