C++ 类中的二维数组动态分配问题
本帖最后由 yy198698 于 2013-06-20 11:25:35 编辑 这是目前学习测试的一段小代码,麻烦高手看看问题:
class Neuralnet
{
private:
//单元网络的基本数目
int m_nInput; //输入层单元数目
int m_nHidden; //隐藏层单元数目
int m_nCase; //打包的数据个数
double **Input; //输入层参数
double *yy;
public:
Neuralnet(int nInput, int nHidden, int nCase);
~Neuralnet();
void CreateNetwork(); //建立网络,为各层单元分配空间
void InitializeNetwork();
void shanchu();
};
主要是两个函数的问题:
void Neuralnet::CreateNetwork()
{
int i;
//初始化神经网络基本参数
Input = new double *[m_nCase];
yy = new double [m_nCase];
for (i=0;i<m_nCase;i++)
{
Input[i] = new double [m_nInput];
}
}
void Neuralnet::shanchu()
{
int i;
//删除参数空间
if(Input != NULL)
{
for (i=0; i<m_nCase;i++)
{
cout<<endl;
cout<<i;
delete[] Input[i];
Input[i]=NULL;
}
delete[] Input;
Input=NULL;
}
delete []yy;
}
下面是主函数
#include "test.h"
using namespace std;
int main()
{
int i,j;
Neuralnet test(4,5,3);
int row = 3;
int col = 4;
double ** array2D = new double*[row];
for(i = 0; i < row; i++)
array2D[i] = new double[col];
for(i = 0; i < row; i++)
for(j = 0; j < col; j++)
array2D[i][j] = i+j;
test.Calculate(array2D);
test.display();
test.shanchu();
for(int i = 0; i < row; i++){
delete[] array2D[i];
}
delete []array2D;
return 0;
}
我按照网上给定的方法进行二维数组的动态分配和删除
动态分配的时候没有问题,但是在删除的时候,二维数组的里层 delete[] Input[i];没问题,执行到delete[] Input;的时候运行的时候报错,无法执行~~~,同样的代码在主函数中运行就没有这样的问题!
为了解决问题,我拿一维数组测试,就不存在类似的问题。。。。无解啊
求教大神们,在编写的时候是不是有些问题。
二维数组 C++ 类
[解决办法]
崩溃的时候在弹出的对话框按相应按钮进入调试,按Alt+7键查看Call Stack里面从上到下列出的对应从里层到外层的函数调用历史。双击某一行可将光标定位到此次调用的源代码或汇编指令处。
判断是否越界访问,可以在数组的最后一个元素之后对应的地址处设置数据读写断点。如果该地址对应其它变量干扰判断,可将数组多声明一个元素,并设置数据读写断点在该多出元素对应的地址上。
[解决办法]
单看这两段代码没有什么问题,应该是调用chanchu之前有过越界写访问,或者n_Case被改变了(变大),从而导致前面的释放代码越界,所以才导致delete失败。也有可能是某段代码改变了Input指针的值。
delete失败常见原因有:
1:被释放的内存块发生过越界写访问,破坏了内存块信息。
2:其它操作破坏了堆信息
3:内存块地址被改变了。
4:被释放的对象的析构函数中发生上述问题。本例中对象为double,不存在这种问题。
此外
delete[]yy;
实现“二维数组”的更好的方法是使用线性的存储空间:
double * Input;
new double[m_nInput*m_nCase];
访问时:
Input[ row * m_nInput + col ];
释放时:
delete []Input;
当然如果你就是这种需要每行长度都可能不同的“数组的数组”那就只能循环为每行分配内存了。
建议把二维数组用一个类来实现,这样有几个好处:
1:可以实现“资源分配即初始化”,简化内存管理。
2:数组的数据指针和长度信息封装在数组对象中,不受其它代码影响,减少bug。
3:可以定义成员函数来访问数据,简化访问代码。比如用线性空间实现的版本,就可以把计算索引的代码放在成员函数或操作符中。
4:可以在访问代码中加入越界查检,便于查错。
5:便于重定义或禁止拷贝操作,以避免重复释放
我这里有一个线性空间的二维数组,我改了改,去掉了std::vector,改成了自己分配释放内存,你可以参考一下。
class double_array_2d
{
public:
//缺省构造函数,空数组
double_array_2d()
: data(0)
, row_count(0)
, col_count(0)
{
}
//构造函数,创建一个crow行ccol列的二维数组
double_array_2d( int crow, int ccol )
: data( new double[ crow * ccol ] )
, row_count( crow )
, col_count( ccol )
{
}
//拷贝构造函数,分配空间并复制数据
double_array_2d ( double_array_2d const & src )
: data( new src.row_count * src.col_count )
, row_count( src.row_count )
, col_count( src.col_count )
{
for( int i = 0; i < row_count * col_count; ++ i )
{
data[i] = src.data[i];
}
}
//析构时释放空间。
~double_array_2d()
{
if( data )
{
delete []data;
}
}
//普通的访问方法,调用者需要保证不越界,调试版本用断言检查
double & at( unsigned int row, unsigned int col )
{
assert( row < row_count && col < col_count );
return data[ row * col_count + col ];
}
//普通的访问方法,常量版本,调用者需要保证不越界,调试版本用断言检查
double at( unsigned int row, unsigned int col )
{
assert( row < row_count && col < col_count );
return data[ row * col_count + col ];
}
//下面实现operator[ ],也可以不实现,只用at进行访问。
//这个类是辅助operator[ ]用的,因为operator[ ]只接收一个参数。
class row_accessor
{
public:
row_accessor( double * data, unsigned int length )
: my_data(data)
, my_length(length)
{
}
double & operator[] ( unsigned int col ) throw()
{
assert( col < my_length );
return data[ col ];
}
double operator[] ( unsigned int col ) const throw()
{
assert( col < my_length );
return data[ col ];
}
};
row_accessor operator[]( int row ) throw()
{
return row_accessor( data[ row ] );
}
const row_accessor operator[]( int row ) const throw()
{
return row_accessor( row );
}
//交换两个数组的数据
//拷贝构造函数和redim通过调用这个方法实现。
//这是除了构造与析构以外,唯一直接改变data、row_count与col_count的成员函数
//其改变也是在两个构造完成的array之间进行交换,并且是长度与指针信息一同交换,
//从而保证内存分配与释放的安全性
void swap( double_array_2d & other ) throw()
{
unsigned int crow = row_count;
unsigned int ccol = col_count;
double * my_data = data;
data = other.data;
row_count = other.row_count;
col_count = other.col_count;
other.data = my_data;
other.row_count = crow;
other.col_count = ccol;
}
//重新分配,可以增加代码以保持数组中的数据,用构造函数+swap实现
void redim( unsigned int crow, unsigned ccol )
{
double_array_2d temp( crow, ccol );
//可以添加代码把data中的数据或一部分数据拷贝到temp中。
//拷贝时要用二重循环,用at或operator[]访问数据
temp.swap( * this );
}
//拷贝赋值操作符,用拷贝构造函数和swap实现
double_array_2d & operator = ( double_array_2d const & other )
{
double_array_2d temp( other );
temp.swap( * this );
}
private:
unsigned int row_count;
unsigned int col_count;
double * data;
private:
//如果不想实现拷贝构造和拷贝赋值,可以用下面的代码禁止拷贝操作
//这时前面的拷贝构造函数和拷贝赋值操作符的定义就不需要了
//double_array_2d ( double_array_2d const & src );
//double_array_2d & operator = ( double_array_2d const & other );
};
//拷贝构造函数,分配空间并复制数据
double_array_2d ( double_array_2d const & src )
: data( new double[ src.row_count * src.col_count ] )
, row_count( src.row_count )
, col_count( src.col_count )
{
for( int i = 0; i < row_count * col_count; ++ i )
{
data[i] = src.data[i];
}
}
row_accessor operator[]( int row ) throw()
{
return row_accessor( data[ row ] );
}
const row_accessor operator[]( int row ) const throw()
{
return row_accessor( row );
}
73 - 82行:
row_accessor operator[]( int row ) throw()
{
return row_accessor( data[ row ], col_count );
}
const row_accessor operator[]( int row ) const throw()
{
return row_accessor( data[ row ], col_count );
}