十几年前,dBASE、FoxBase和FoxPro数据库盛极一时,C/C++程序员使用C/C++直接操作DBF数据文件是理所当然的事,下面是我在1994年写的一个DBFile类代码。
DBFIle类的头文件:
#ifndef __DBFIO_HPP
#define __DBFIO_HPP
#include < stdlib.h >
#include < fstream.h >
#include " marray.hpp "
const int
DB_FieNameSize = 11 ;
class DBField // DBF 文件的字段类
{
public :
char name[DB_FieNameSize];
char type;
int off;
int nul;
unsigned char width;
unsigned char dec;
DBField()
{
memset( this , 0 , sizeof ( DBField ) );
}
int operator == ( const DBField & );
int operator < ( const DBField & );
DBField & operator = ( const DBField & );
// 设置字段.参数: 名称, 类型, 宽度, 小数位
void SetValue( char * , int , int , int = 0 );
};
inline int DBField:: operator == ( const DBField & d )
{
return ! strcmp( name, d.name );
}
inline int DBField:: operator < ( const DBField & d )
{
return strcmp( name, d.name ) < 0 ? 1 : 0 ;
}
inline DBField & DBField:: operator = ( const DBField & d )
{
memmove( this , & d, sizeof ( DBField ) );
return * this ;
}
// 定义排序字段数组类
typedef MSArray < DBField > DBFieldArray;
class DBFile : public fstream // DBF 文件类
{
typedef struct
{
unsigned char dbfflag;
unsigned char date_n;
unsigned char date_y;
unsigned char date_r;
long records;
unsigned ldb, lrd;
char _nul[ 20 ];
}DBFSTRUCT;
DBFSTRUCT str;
char * buf;
DBField TmpField;
int fields;
long oldrecords;
int openerror;
DBFieldArray dArray;
void SetBuf();
void Init();
void SetFields( int );
void WriteDelFlag( long , int = ' * ' );
public :
DBFile();
// 调用 Use( char* )
DBFile( const char * );
// 调用 Use( char*, DBField*, int )
DBFile( const char * , DBField * , int );
~ DBFile();
// 打开一个已存在文件;参数: 文件名
void Use( const char * );
// 用一字段数组建立新文件;参数:文件名,字段数组,字段数
void Use( const char * , DBField * , int );
// 关闭文件
void Use();
void Close();
// 返回记录数
long Records();
// 返回记录长度
int RecSize();
// 返回文件头结构长度
int TopSize();
// 返回字段数
int Fields();
// 返回打开文件时的错误代码, 错误码:
// 0 无错误
// 1 文件不存在
// 2 建立新文件失败
// 3 建立文件时未设置字段
// 4 读文件头出错或非 DBF 文件
// 5 写文件头出错
// 6 内存不够
int OpenError();
// 把当前记录内容读到缓冲区
void Read();
// 将缓冲区内容写到当前记录
void Write();
// 取一字段内容到字符串中;参数:字段名,字符串
char * Get( char * , char * );
// 取一字段内容到字符串中;参数:字段序号(按字段名排过序),字符串
char * Get( unsigned, char * );
// 将字符串内容输出到字段中;参数:字段名,字符串
void Put( char * , const char * );
// 将字符串内容输出到字段中;参数:字段序号(按字段名排过序),字符串
void Put( unsigned, const char * );
// 将一浮点数输出到字段中;参数:,字段名,浮点数
void Put( char * , double );
// 将一浮点数输出到字段中;参数:字段序号(按字段名排过序),浮点数
void Put( unsigned, double );
// 将缓冲区内容追加到文件尾;参数:追加标记( 0 空记录 )
void Append( int = 0 );
// 将一字段内容转换为浮点数返回(未检查字段类型);参数: 字段名
double operator [] ( char * );
// 功能同上;参数:字段序号(按字段名排过序)
double operator [] ( unsigned );
// 移动文件记录 put 指针;参数: 记录号
void Seekp( long );
// 移动文件记录 get 指针;参数: 记录号
void Seekg( long );
// 将缓冲区内容输出到文件;参数: 记录号
DBFile & operator << ( long );
// 从文件中输入内容到缓冲区中;参数: 记录号
DBFile & operator >> ( long );
// 返回缓冲区指针
char * Buf();
// 在字段排序数组中查找字段,返回序号,未找到返回 0X7FFF;参数: 字段名
unsigned FindField( char * );
// 返回字段排序数组
DBFieldArray & FieldArray();
// 给记录打上删除标记
void Delete( long );
// 取消记录的删除标记
void UnDelete( long );
// 如记录号在文件记录范围内返回 TRUE, 否则返回 FALSE
int InRecords( long );
};
inline DBFile::DBFile() : dArray( 0 , 1 )
{
Init();
}
inline long DBFile::Records()
{
return str.records;
}
inline int DBFile::RecSize()
{
return str.lrd;
}
inline int DBFile::TopSize()
{
return str.ldb;
}
inline int DBFile::Fields()
{
return fields;
}
inline char * DBFile::Buf()
{
return buf;
}
inline DBFieldArray & DBFile::FieldArray()
{
return dArray;
}
inline void DBFile::Use()
{
Close();
}
inline DBFile:: ~ DBFile()
{
Close();
}
inline int DBFile::OpenError()
{
return openerror;
}
inline unsigned DBFile::FindField( char * name )
{
strcpy( TmpField.name, strupr( name ) );
return dArray.Find( TmpField );
}
inline void DBFile::Read()
{
read( buf, str.lrd );
}
inline void DBFile::Write()
{
* buf = 32 ;
write( buf, str.lrd );
}
inline char * DBFile::Get( char * name, char * s )
{
return Get( FindField( name ), s );
}
inline void DBFile::Put( char * name, const char * s )
{
Put( FindField( name ), s );
}
inline void DBFile::Put( char * name, double s )
{
Put( FindField( name ), s );
}
inline double DBFile:: operator [] ( char * name )
{
return atof( Get( name, str._nul ) );
}
inline double DBFile:: operator [] ( unsigned i )
{
return atof( Get( i, str._nul ) );
}
inline void DBFile::Seekp( long recnum )
{
seekp( recnum * str.lrd + str.ldb );
}
inline void DBFile::Seekg( long recnum )
{
seekg( recnum * str.lrd + str.ldb );
}
inline void DBFile::Delete( long recnum )
{
WriteDelFlag( recnum );
}
inline void DBFile::UnDelete( long recnum )
{
WriteDelFlag( recnum, 32 );
}
inline int DBFile::InRecords( long recnum )
{
return ( recnum >= 0 && recnum < str.records );
}
#endif
DEFile类的CPP文件:
#include " dbfio.hpp "
#include < ctype.h >
void DBField::SetValue( char * n, int t, int w, int d )
{
strcpy( name, strupr( n ) );
type = toupper( t );
width = w;
dec = d;
}
void DBFile::Init()
{
fields = 0 ;
buf = 0 ;
}
void DBFile::SetFields( int n )
{
fields = n;
if ( n )
{
dArray.SetLimit( n );
buf = new char [str.lrd + 1 ];
buf[ 0 ] = 32 ;
buf[str.lrd] = 0x1a ;
if ( ! buf || ! dArray.Items() )
openerror = 6 ;
}
}
void DBFile::Close()
{
if ( oldrecords != str.records )
{
seekp( 4 );
write( ( char * ) & str.records, sizeof ( unsigned long ) );
oldrecords = str.records;
}
close();
if ( buf )
delete[] buf;
dArray.RemoveAll();
Init();
if ( openerror )
setstate( ios::badbit );
}
char * DBFile::Get( unsigned i, char * s )
{
if ( i < dArray.Count() )
{
memcpy( s, & buf[dArray[i].off], dArray[i].width );
s[ dArray[i].width ] = 0 ;
}
else
* s = 0 ;
return s;
}
DBFile & DBFile:: operator >> ( long recnum )
{
if ( InRecords( recnum ) )
{
Seekg( recnum );
Read();
}
else
memset( buf, 32 , str.lrd );
return * this ;
}
void DBFile::WriteDelFlag( long recnum, int Flag )
{
Seekp( recnum );
write( ( char * ) & Flag, 1 );
}
// DBFPUT.CPP
#include " dbfio.hpp "
#include < strstrea.h >
#include < iomanip.h >
#include < bcd.h >
void DBFile::Put( unsigned i, const char * s )
{
if ( i < dArray.Count() )
{
int flag = ios::left;
ostrstream os( buf, str.lrd );
os.seekp( dArray[i].off );
if ( dArray[i].type == ' N ' || dArray[i].type == ' F ' )
flag = ios::right;
os << setw( dArray[i].width ) << setiosflags( flag ) << s;
}
}
void DBFile::Put( unsigned i, double val )
{
if ( i < dArray.Count() )
{
ostrstream os( buf, str.lrd );
bcd a( val, dArray[i].dec );
os.seekp( dArray[i].off );
os << setw( dArray[i].width ) << setiosflags( ios::right | ios:: fixed ) << a;
}
}
void DBFile::Append( int flag )
{
if ( ! flag )
memset( buf, 32 , str.lrd );
Seekp( str.records );
Write();
str.records ++ ;
}
DBFile & DBFile:: operator << ( long recnum )
{
if ( InRecords( recnum ) )
{
Seekp( recnum );
Write();
}
else
Append( 1 );
return * this ;
}
// DBFUSE.CPP
#include " dbfio.hpp "
DBFile::DBFile( const char * name ) : dArray( 0 , 1 )
{
Init();
Use( name );
}
void DBFile::Use( const char * name )
{
if ( fields )
Close();
open( name, ios:: in | ios:: out | ios::binary | ios::nocreate );
if ( bad() )
{
openerror = 1 ;
return ;
}
openerror = 0 ;
read( ( char * ) & str, sizeof ( DBFSTRUCT ) );
oldrecords = str.records;
if ( fail() || str.dbfflag != 3 )
openerror = 4 ;
else
SetFields( str.ldb / 32 - 1 );
if ( ! openerror )
{
DBField Field;
for ( int i = 0 ; i < fields; i ++ )
{
read( ( char * ) & Field, sizeof ( DBField ) );
dArray.Add( Field );
seekg( 32 - sizeof ( DBField ), ios::cur );
}
seekg( 1 , ios::cur );
if ( fail() )
openerror = 4 ;
}
if ( openerror )
Close();
}
DBFile::DBFile( const char * name, DBField * fie, int n ) : dArray( 0 , 1 )
{
Init();
Use( name, fie, n );
}
void DBFile::Use( const char * name, DBField * fie, int n )
{
if ( ! fie || ! n )
{
openerror = 3 ;
return ;
}
if ( fields )
Close();
openerror = 0 ;
str.lrd = 1 ;
for ( int i = 0 ; i < n; str.lrd += fie[i].width, i ++ )
fie[i].off = str.lrd;
str.dbfflag = 3 ;
str.date_n = 96 ;
str.date_y = str.date_r = 1 ;
str.ldb = n * 32 + 33 ;
memset( str._nul, 0 , 20 );
open( name, ios:: in | ios:: out | ios::binary | ios::trunc );
if ( bad() )
openerror = 2 ;
else
{
str.records = oldrecords = 0 ;
write( ( char * ) & str, sizeof ( DBFSTRUCT ) );
SetFields( n );
if ( ! openerror )
{
for ( int i = 0 ; i < fields; i ++ )
{
write( ( char * ) & fie[i], sizeof ( DBField ) );
write( str._nul, 32 - sizeof ( DBField ) );
dArray.Add( fie[i] );
}
i = 0x0d ;
write( ( char * ) & i, 1 );
if ( fail() )
openerror = 5 ;
}
}
if ( openerror )
Close();
}
#ifndef __MARRAY_HPP
#define __MARRAY_HPP
#include < string .h >
#include < iostream.h >
// 无序直接数组类模板.用户类中应定义默认构造函数和运算符 ==.
template < class T > class MArray
{
protected :
char * items; // 动态数组指针
int count; // 数组中对象个数
int limit; // 数组容量
int delta; // 数组增量
int typesize;
MArray(){}
void Init( int , int , int );
virtual T & Item( int index )
{
return * ( T * ) & items[ index * typesize ];
}
virtual void Let( int index, const T * t )
{
memmove( & items[index * typesize], t, typesize );
}
public :
// 构造函数.参数: 数组容量(个);增加量
MArray( int , int = 0 );
~ MArray();
void SetLimit( int );
// 移去一个对象,其后对象前移.参数: 数组下标
void Remove( int );
// 移去指定位置及其后的所有对象; 参数:数组下标
void RemoveAll( int = 0 );
// 增加对象到指定位置,其后对象后移,返回实际下标,出错返回 0x7fff.
// 参数: 数组下标;对象
int AddAt( int , const T & );
// 增加对象到数组尾部,返回实际的数组下标,出错返回 0x7fff.参数: 对象
int Add( const T & );
// 返回动态数组指针
char * Items();
// 查找对象,返回对象下标,未找到返回 0X7FFF.参数: 对象
int Find( const T & );
// 返回数组中的对象个数
int Count();
// 返回数组容量大小
int ArraySize();
// 返回对象的内存长度
int ObjectSize();
// 返回数组下标所指的对象,下标超范围时返回值不定
T & operator [] ( int );
};
template < class T > inline MArray < T > ::MArray( int mLimit, int mDelta )
{
Init( mLimit, mDelta, sizeof ( T ) );
}
template < class T > inline void MArray < T > ::RemoveAll( int index )
{
if ( index >= 0 && index < count )
count = index;
}
template < class T > inline char * MArray < T > ::Items()
{
return items;
}
template < class T > inline int MArray < T > ::Add( const T & t )
{
return AddAt( count, t );
}
template < class T > inline int MArray < T > ::Count()
{
return count;
}
template < class T > inline int MArray < T > ::ArraySize()
{
return limit;
}
template < class T > inline int MArray < T > ::ObjectSize()
{
return typesize;
}
template < class T > inline T & MArray < T > :: operator [] ( int index )
{
return Item( index );
}
#pragma option -Jgx
template < class T >
void MArray < T > ::Init( int mLimit, int mDelta, int size )
{
items = 0 ;
count = 0 ;
limit = 0 ;
delta = mDelta;
typesize = size;
SetLimit( mLimit );
}
template < class T > MArray < T > :: ~ MArray()
{
if ( items )
delete[] items;
}
template < class T > void MArray < T > ::SetLimit( int aLimit )
{
if ( aLimit <= limit )
return ;
if ( aLimit > 0xfff0 / typesize )
aLimit = 0xfff0 / typesize;
char * aItems = new char [aLimit * typesize];
if ( aItems )
{
if ( count )
memmove( aItems, items, count * typesize );
if ( items )
delete[] items;
items = aItems;
limit = aLimit;
}
}
template < class T > void MArray < T > ::Remove( int index )
{
if ( index < 0 || index >= count )
return ;
count -- ;
if ( count )
memmove( & items[index * typesize],
& items[( index + 1 ) * typesize], ( count - index ) * typesize );
}
template < class T > int MArray < T > ::AddAt( int index, const T & t )
{
if ( index < 0 )
return 0x7fff ;
if ( count == limit )
SetLimit( count + delta );
if ( count == limit )
return 0x7fff ;
if ( index < count )
memmove( & items[( index + 1 ) * typesize],
& items[index * typesize],
( count - index ) * typesize
);
else
index = count;
Let( index, & t );
count ++ ;
return index;
}
template < class T > int MArray < T > ::Find( const T & t )
{
for ( int i = 0 ; i < count; i ++ )
if ( Item( i ) == t )
return i;
return 0x7fff ;
}
#pragma option -Jg
// 有序直接数组类模板.用户类中应定义默认构造函数和运算符 == 和 <.
template < class T > class MSArray : public virtual MArray < T >
{
protected :
int index; // 搜索时保存的数组下标值
int res; // 搜索成功标记. 0 未找到匹配对象
int duplicates; // 允许重复插入标记
MSArray(){}
int Search( const T & key );
public :
// 构造函数.参数: 数组容量(个);增量;允许重复插入标记( 0 不允许重复 )
MSArray( int l, int d = 0 , int u = 0 );
// 将对象 t 按索引方式插到数组中,返回下标.如 t 属重复对象,
// 重复插入标记 != 0, t 被插入到相同对象的尾部,否则返回 0x7fff.
int Add( const T & t );
// 用二分法查找第一个匹配对象, 返回数组下标, 无匹配对象返回 0x7fff.
int Find( const T & t );
// 在Find()基础上,查找下一匹配对象,返回数组下标,无匹配对象返回 0x7fff.
int FindNext();
// 返回使用 Find() 或 FindNext() 后 >= 关键对象的下标值
int IndexNum()
{
return index;
}
};
#pragma option -Jgx
template < class T > int MSArray < T > ::FindNext()
{
if ( res && duplicates && ++ index < count
&& Item( index - 1 ) == Item( index ) )
return index;
res = 0 ;
return 0x7fff ;
}
template < class T > int MSArray < T > ::Search( const T & key )
{
int l = 0 , i;
int h = count - 1 ;
res = 0 ;
while ( l <= h )
{
i = ( l + h ) >> 1 ;
if ( Item( i ) < key )
l = i + 1 ;
else
{
h = i - 1 ;
if ( Item( i ) == key )
{
res = 1 ;
if ( ! duplicates )
l = i;
}
}
}
index = l;
return res;
}
template < class T > int MSArray < T > ::Add( const T & t )
{
int i = Find( t );
if ( i == 0x7fff )
i = index;
else
{
if ( ! duplicates )
return 0x7fff ;
for ( i ++ ; Item( i ) == t && i < count; i ++ );
}
return AddAt( i, t );
}
template < class T > int MSArray < T > ::Find( const T & t )
{
if ( Search( t ) )
return index;
return 0x7fff ;
}
template < class T > MSArray < T > ::MSArray( int l, int d, int u ) :
MArray < T > ( l, d ),
duplicates( u ),
res( 0 )
{}
#pragma option -Jg
// 无序间接数组类模板.退出时不删除对象本身,其余同 MArray<T> 类
template < class T > class IMArray : public virtual MArray < T >
{
protected :
IMArray(){}
virtual void Let( int i, const T * t )
{
unsigned long p = ( unsigned long )t;
memmove( & items[i * typesize], & p, typesize );
}
virtual T & Item( int i )
{
return * ( T * )( * ( long * ) & items[i * typesize]);
}
public :
IMArray( int Limit, int Delta = 0 )
{
Init( Limit, Delta, sizeof ( T * ) );
}
};
// 有序间接数组类模板.退出时不删除对象本身.其余同 MSArray<T> 类
template < class T > class IMSArray : public MSArray < T > , public IMArray < T >
{
protected :
IMSArray(){}
public :
IMSArray( int Limit, int Delta = 0 , int u = 0 ) :
IMArray < T > ( Limit, Delta )
{
duplicates = u;
res = 0 ;
}
};
#endif
对以上代码作些简单的说明:
1、DBFile类代码使用的Borland C++ 3.1编译器,其它C++编译器可能要作些修改。
2、DBFile中打开和关闭文件使用的是Use函数,没有使用Open、Create这些熟悉的函数名,是按照dBASE命令习惯命名的。
3、DEFile类没有提供DBF文件排序方法,是因为我还有个通用的 B- 树排序类,实际操作中,需要排序时,2个类结合使用的,那个类有个纯虚方法,需要继承才能使用,因为没有找到以前排序类测试代码例子,所以我正在考虑是否把那个排序类贴上来,如果贴上来,还得写一个例子,而我不用C++的时间太长,机器上又没有安装BC3.1,不知能否写好这个例子。
4、DBFile类使用了动态数组,而当时是没有STL的(即使有,我可能也不会使用,因为DOS下的资源相当紧张,而且BC3.1自身也带有数组类,和现在的STL类似,但是我嫌使用它麻烦),所以自己写了个模板类,该模板类在其它C++版本编译必须做编译选项的修改。
声明,文章的代码是1995年前的东西,只能供初学者们借鉴参考。有错误或建议,请来信:maozefa@hotmail.com
更新:今天找了个以前DBFile类的测试代码,更新在此。另外补充说明一点:这个DBFile类用今天的眼光看,是比较简陋的,其中的方法也较低阶,一般数据文件操作方法如First、Last、Next、Bof、Eof等都没有。原因是当时使用C/C++的人,一般都不大喜欢dBASE,但是为了能读取当时大量的dbf文件的数据,写一个能读dbf文件,转换成自己的文件格式的代码就不错了,我这个类在当时已经是很完善了,不仅能读写,还能创建dbf文件。通过以前这个例子,不难看出,我们当时需要的只是文件转换,或者数字字段计算功能(对数字字段使用了[]重载,可以直接以字段名或字段序号操作)[2007-9-17 13:43P]。
#include " DBFio.hpp "
void CreateDbf( char * fileName)
{
DBField fields[ 3 ];
fields[ 0 ].SetValue( " Code " , ' C ' , 4 );
fields[ 1 ].SetValue( " Name " , ' C ' , 8 );
fields[ 2 ].SetValue( " Value " , ' N ' , 10 , 2 );
DBFile Dbf;
Dbf.Use(fileName, fields, 3 );
Dbf.Put( " Code " , " 0001 " );
Dbf.Put( " Name " , " Maozefa " );
Dbf.Put( " Value " , 12345.67 );
Dbf.Append( 1 );
Dbf.Put( " Code " , " 0002 " );
Dbf.Put( " Name " , " Maojun " );
Dbf.Put( " Value " , 78543.21 );
Dbf.Append( 1 );
Dbf.Use();
}
void main()
{
CreateDbf( " Test.dbf " );
DBFile dbf( " Test.dbf " );
char code[ 5 ], name[ 9 ];
for ( long i = 0 ; i < dbf.Records(); i ++ )
{
dbf.Read();
dbf.Get( " Code " , code);
dbf.Get( " Name " , name);
cout << code << ' ' << name << ' ' << dbf[ " Value " ] << endl;
}
system( " pause " );
}
声明,文章的代码是1995年前的东西,只能供初学者们借鉴参考。有错误或建议,请来信:maozefa@hotmail.com