Hi everybody:

I'm trying to do the practices on the top of forum. I started from

beginner's level. This time I will show you my solution to implement

a simple database in C++. If you could give some advice on the

following aspects or other, it would be so nice of you:)

1. base datastructure. I used a 2D array to store a number of

pointers to string data. However, it's not easy to resize. I find it's not

easy to add a column.

2. design pattern. My solution really looks like a hardcoded solution

only to this question. How to find a better a design pattern than could

be reused easily?

3.algothirms.

4. multithreads and process protection

5....

I hope my post is not too long to make you boring.

my codes:

#include "stdafx.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "io.h"
const char *filename = "d:/database.txt";
const int ROWS = 10;
const int COLOUMS = 3;
class Data
{
public:
    Data() :
        m_nTotalColumns(COLOUMS),
        m_nTotalRows(ROWS),
        m_nCurCol(0),
        m_nCurRow(0),
        m_ppcData(NULL),
        m_fp(NULL)
    {
    }
 
    virtual ~Data() {}
 
 //create a dynamic 2 dimension char * array which points to a single data string
    bool Init() {
  m_ppcData = (char **)new char *[ROWS][COLOUMS];
  if(NULL == m_ppcData) {
      return false;
  }
        memset((void *)m_ppcData, NULL, ROWS * COLOUMS * sizeof(char *));
        return ReadFile();
    }
 
    //insert a record with all values of fields
    void Insert(const char *name, const char *Gender, const char *old) {
        if (NULL == name || NULL == Gender || NULL == old) {
            return;
        }
        //array is full
        if(++m_nCurRow >= m_nTotalRows) {
            return;
        }
        StrCpy(m_ppcData[m_nCurRow * m_nTotalColumns], name);
        StrCpy(m_ppcData[m_nCurRow * m_nTotalColumns + 1], Gender);
        StrCpy(m_ppcData[m_nCurRow * m_nTotalColumns + 2], old);
    }
 
    //updata a special data
    void Update(const unsigned int row, const unsigned int column, const char *data) {
        if(row > m_nCurRow || column >= m_nTotalColumns || NULL == data) {
            return;
        }
        StrCpy(m_ppcData[row * m_nTotalColumns + column], data);
    }
 
    //select a special data
    char *Select(const unsigned int row, const unsigned int column) {
        if(row > m_nCurRow || column >= m_nTotalColumns) {
            return NULL;
        }
        return m_ppcData[row * m_nTotalColumns + column];
    }
 
    //delete a row
    void DeleteRecord(const unsigned int row) {
        if(row > m_nCurRow) {
            return;
        }
        unsigned int i;
        unsigned int j;
        for(i = 0; i < m_nTotalColumns; i++) {
            delete [] m_ppcData[row * m_nTotalColumns + i];
            m_ppcData[row * m_nTotalColumns + i] = NULL;
        }
        //if the deleted row is not the last, i will fflush the data to the top
        if(row < m_nCurRow) {
            for(i = row; i < m_nCurRow; i++) {
                for(j = 0; j < m_nTotalColumns; j++) {
                    m_ppcData[i * m_nTotalColumns + j] = m_ppcData[(i + 1) * m_nTotalColumns + j];
                }
            }
        }
        memset((void *)(m_ppcData + m_nCurRow * m_nTotalColumns), NULL, COLOUMS * sizeof(char *));
        m_nCurRow--;
    }
 
    void display() {
        printf("**************\n");
        for(int i = 0; i < ROWS * COLOUMS; ++i) {
            if (NULL != m_ppcData && NULL != m_ppcData[i]) {
                printf("%s\n ", m_ppcData[i]);
            }
        }
    }
 
    void Save() {
        WriteFile();
    }
protected:
    const char* StrCpy(char *&dest, const char *src) {
        if(NULL == src) {
            return NULL;
        }
        //avoid copy self
        if(src == dest) {
            return dest;
        }
        //release original memory
        if(NULL != dest) {
            delete [ ]dest;
            dest = NULL;
        }
        int len = strlen(src);
        dest = new char[len + 1];
        if(NULL == dest) {
            return NULL;
        }
        return strcpy(dest, src);
    }
    //write data into file
    bool WriteFile() {
  m_fp = fopen(filename, "w+");
  if(NULL == m_fp) {
      return false;
  }
        for(int i = 0; i < ROWS * COLOUMS; ++i) {
            if (NULL != m_ppcData && NULL != m_ppcData[i]) {
                fputs(m_ppcData[i], m_fp);
                fputs(" ", m_fp);
            }
        }
        fclose(m_fp);
     return true;
    }
    //Read data From file
    //load all the data from the disk, do I need to do so?
    bool ReadFile() {
  m_fp = fopen(filename, "r+");
  if(NULL == m_fp) {
      return false;
  }
        long length = _filelength(_fileno(m_fp)) + 1;
        char *tmp = new char[length];
        if(NULL == tmp) {
      return false;
  }
        fgets(tmp, length, m_fp);
        int i = 0;
        char *pre = tmp;
        char *t = tmp;
        unsigned int pos;
        bool bIsEOF = false;

        for(i = 0, pos = 0; (i < length) && (pos < m_nTotalRows * m_nTotalColumns); ++i) {
            if(*t == '\0' || *t == ' ') {
                //if we come to the end of the data, break
                if(*t == '\0') {
                    StrCpy(m_ppcData[pos], pre);
                    break;
                }
                else {
                    *t = '\0';
                    StrCpy(m_ppcData[pos], pre);
                    //deal with multi spaces
                    while (*++t == ' ') {
                    }
                    pre = t;
                    pos++;
                }
            }
&nref="http://bbs.fdc.com.cn/boardlist.asp?boardid=376"                   target=_blank>԰                    ԰                                               </DIV>              <DIV class=tabcontent id=tcontent5>                                                    p;  delete [] tmp;
        fclose(m_fp);
     return true;
    }
 
private:
    const unsigned int m_nTotalColumns;
    const unsigned int m_nTotalRows;
    unsigned int m_nCurCol;
    unsigned int m_nCurRow;
    FILE  *m_fp;
    char   **m_ppcData;
};
 
int main(int argc, char* argv[])
{
    Data d;
    if (d.Init()) {
        d.Update(2, 2, "100");
        d.Insert("Rock", "male", "24");
        d.display();
        char * p = d.Select(3, 2);
        printf("p = %s\n", p);
        d.DeleteRecord(1);
        d.display();
        d.Save();
    }
 return 0;
}

You have to create a file called "d:/database.txt" on your disk, the contens look like this"name Gender old Rock male 100".

Thankyou very much for watching.

don't use c style file i/o and strings in c++ programs. Yes its valid to do that but c++ file i/o and c++ string class are generally easier to use and makes your program less buggy.

c++ vectors and lists are the containers that will make it almost trivel to resize. You can create a 2d vector (rows and columns) by using a vector of vectors, something like this.

typedef vector<string> COLUMNS;
vector<COLUMS> rows;

A few comments:
1. Given it's C++ problem, try using C++ containers (instead of array) e.g. vector/list/map.
2. You don't really need to force use of a design. Not yet at least.
3. Comment on the design: Class Data should not be responsible for insertion/deletion/.. Look at it purely from english pov, Data itself shouldn't have this functionality. If you look at your problem stmt, you'll see that you're missing a major class (named Database). Database should be the one who has this functionality.

Thanks for all of you.

I tried to give a more c++ solution in the following. Acturally, it's the

first time I use some STL and exception mechanism in my codes.

So it would so nice of you to give me some advice on my codes:)

#include "stdafx.h"
#pragma warning(disable:4786)
#include <vector>
#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <iterator>
#define  RECORDMAXLENGstringH 100
using namespace std;
//template <class string>
class Database
{
public:
    Database() {
    }
    virtual ~Database() {
    }
    Database(const Database &d) {
        m_vRow = d.m_vRow;
    }
    const Database& operator=(const Database& d) {
        if (this != &d) {
            this->m_vRow = d.m_vRow;
        }
        return *this;
    }
    const string& Select(const int r, const int c) {
        if(r >= m_vRow.size()) {
            throw r;
        }
        if(c >= m_vRow[0].size()) throw c;
        cout<<m_vRow[r][c]<<"\n";
        return m_vRow[r][c];
    }
    bool Update(const int r, const int c, const string & Data) {
        if(r >= m_vRow.size()) return false;
        if(c >= m_vRow[0].size()) return false;
        m_vRow[r][c] = Data;
    }
    bool DeleteRecord(const int r) {
        if(r >= m_vRow.size()) throw r;
        int i = 0;
        ROW::iterator itr = m_vRow.begin();
        while (i++ < r) {
            itr++;
        }
        m_vRow.erase(itr);
        return true;
    }
    void AddRecord(const string & name, const string& gender, const string &old) {
        COLUMN column;
        column.push_back(name);
        column.push_back(gender);
        column.push_back(old);
        m_vRow.push_back(column);
    }
    bool Init() {
        fstream fs("d:/database.txt", ios::in);
        string line;
        COLUMN column;
        //get a line from file and analyse the words in it
        while (getline(fs, line, '\n')) {
            string::size_type pos = 0;
            string::size_type pre = 0;
            string record;
            //when i find a space, it means I find a whole word
            while ((pos = line.find(' ', pos)) != string::npos) {
                record = line.substr(pre, pos - pre);
                pre = ++pos;
                //ignore multispace
                if (record != "") {
                    column.push_back(record);
                }
            }
            //deal with the last word end with '\0'
            record = line.substr(pre, string::npos);
            column.push_back(record);
            m_vRow.push_back(column);
            column.clear();
        }
        fs.close();
        return false;
    }
    bool Save() {
        fstream fs("d:/database.txt", ios::trunc | ios::out);
        COLUMN::iterator itc;
        ROW::iterator itr;
        for(itr = m_vRow.begin(); itr != m_vRow.end(); itr++) {
            for(itc = (*itr).begin(); itc != (*itr).end(); itc++) {
                fs<<(*itc)<<' ';
            }
            fs<<'\n';
        }
        fs.close();
        return false;
    }
    void display() {
        COLUMN::iterator itc;
        ROW::iterator itr;
        for(itr = m_vRow.begin(); itr != m_vRow.end(); itr++) {
            for(itc = (*itr).begin(); itc != (*itr).end(); itc++) {
                cout<<*itc<<' ';
            }
            cout<<'\n';
        }
    }
private:
    typedef vector<string> COLUMN;
    typedef vector<COLUMN> ROW;
    ROW m_vRow;
    //const static char* m_sFilename;
};
//template <class string>
//string
//const char* Database::m_sFilename = "d:/database.txt";
int main(int argc, char* argv[])
{
    Database database;
    try {
        database.Init();
        database.display();
        database.AddRecord("Chirly", "female", "18");
        database.display();
        database.Select(3, 1);
        database.DeleteRecord(5);
        database.display();
        database.Save();
    }
    catch (int i) {
        cout<<i<<"too large\n";
    }
    catch(bad_alloc &) {
        cout<<"bad alloc\n";
    }

 return 0;
}

Ps: the definitions of list, vector.. in headers LIST, VECTOR...

are really difficult to understand. Why does that coder write that

complicated codes? To avoid the codes being understood by others?

:)

>So it would so nice of you to give me some advice on my codes
Granted, all I did was skim over it, but your use of the standard library and exceptions looks okay.

>Why does that coder write that complicated codes?
Well, it looks like you're using Visual Studio, and the standard library code for Visual Studio is notoriously obtuse. But there's always going to be some measure of complexity in code that does so much with templates.

:)

const string& Select(const int r, const int c) {
        if(r >= m_vRow.size()) {
            throw r;
        }
        if(c >= m_vRow[0].size()) throw c;
        cout<<m_vRow[r][c]<<"\n";
        return m_vRow[r][c];
    }

In fact, in this function, I tried to return something instead of

throw something. But it's not easy to return something, I have to

return a string object if r >= m_vRow.size() is true. What to return?

I have no idea.:)

>What to return?
An exception is the better option in this case. But if you want to return something, you need to pick a string that couldn't possibly be in the database. That's difficult to do, so you might do well to add a level of indirection by using an object for each cell rather than a string. Then you can easily give it a null value and any other cellish information that isn't easily derived from the value:

class Cell {
  bool is_null;
  std::string value;
public:
  Cell ( const std::string& init = "" )
    : is_null ( init.size() == 0 ), value ( init )
  {}
public:
  const std::string& GetValue() const
  {
    return value;
  }

  void SetValue ( const std::string& s )
  {
    value = s;
    is_null = false;
  }

  bool IsNull() const
  {
    return is_null;
  }
};

Ps: the definitions of list, vector.. in headers LIST, VECTOR...

are really difficult to understand. Why does that coder write that

complicated codes? To avoid the codes being understood by others?

:)

I agree -- and I don't bother any more trying to read those header files. You can find better explanations for the stl classes with google. And you can find some example programs with google too. Also check out the c++ code snippets here at DaniWeb.

>What to return?
An exception is the better option in this case. But if you want to return something, you need to pick a string that couldn't possibly be in the database. That's difficult to do, so you might do well to add a level of indirection by using an object for each cell rather than a string. Then you can easily give it a null value and any other cellish information that isn't easily derived from the value:

class Cell {
  bool is_null;
  std::string value;
public:
  Cell ( const std::string& init = "" )
    : is_null ( init.size() == 0 ), value ( init )
  {}
public:
  const std::string& GetValue() const
  {
    return value;
  }
 
  void SetValue ( const std::string& s )
  {
    value = s;
    is_null = false;
  }
 
  bool IsNull() const
  {
    return is_null;
  }
};

Thank u very much.

But I still have a question, if I write something like this

const Cell & Test() {
 
       return NULL; //I just want to tell something is wrong
}

Of course, I defined a copy constructor in Class Cell like this

Cell(const int i) {
     if(0 == i) is_null =true;
}

But it doesn't work well, there is a waring "returning address of local

variable or temporary". I know that, a temp object is generated in

Test and the reference of that object is returned.

In fact, i think Test() returns a reference of something, it must be

a long life object(i mean not temp or local). But at that time, i don't

have such a object, how to return?

Thank you:)

>const Cell & Test() {
> return NULL; //I just want to tell something is wrong
There's no such thing as a null reference in C++. If all you want to do is return a single error state, you can follow the same pattern as the iostream library:

operator void*() const
{
  return ( is_null ) ? 0 : this;
}

This returns a null pointer if the cell is null, or a pointer to the object if it's not. If you aren't comfortable working with overloaded type conversions, you can do the same thing with Test:

void *Test() const
{
  return ( is_null ) ? 0 : this;
}
Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.