Introduction
Ok, intro right... I haven't written an article for a long time now, so don't know about my writing skills still present, anyways...
This little paper is about multi-dimensional string
arrays. String
s only, as there are already tons of tips about integers arrays, but not much info about this article's subject, and, completely no ready to use code, which you can just copy and paste to your project, so here we go. So, yeah, I am not going to write a long technical story about (!VERY IMPORTANT!) memory management and other things - I'll just give you a class, which you can just use and that's it. However, I am not stating that it is memory leaks proof, there maybe there will be bugs and stuff, but that is why you got the source to play with.
All I'll say about memory management in C is that: if you allocate memory - free it. Everywhere. Else memory leaks are guaranteed. So, I've told you that I will give you a class. Well, I call the whole thing a class, but this class actually is just a wrapper class for regular C functions. And, there is additional functionality, provided by my class - user can supply his own pointers. What for? Well, you can push your pointer from different languages for example.
Consider such a situation:
wchar_t *test()
{
static wchar_t *a = (wchar_t *)malloc(MAX_PATH);
wcscpy(a, L"something");
return a;
}
and this:
void test(wchar_t *&pointer)
{
pointer = (wchar_t *)malloc(MAX_PATH);
wcscpy(pointer, L"something");
}
So, you can push your pointer, make class manage it, and then you can, on your own, do anything you like with it. That above example is kinda silly, but I am talking here about 3D arrays, where all this may become a little bit tricky.
Another thing, you may ask, why have I used good old C when there is C++ with all its benefits. First of all, because this class (ArrayWorker
) works with native types: char
and wchar_t
, so once again, any output you'll get from it - you can easily share between different languages, like C and C# for example. So when I took a look at C++ and how many conversions I would need to make, I decided to focus on C.
And the last thing. ArrayWorker
is arranged in a way that you are not limited to just 3D, you can make it 100D - it's all about adding one more level of indirection.
Background
So, what is string
array anyway? You probably know that. What is multi-dimensional array? You probably know that too.
wchar_t *String = L"some string";
wchar_t *Array1D[2] = {L"some", L"string"};
wchar_t *Array2D[2][2] =
{
{L"some", L"String"},
{L"some other", L"string"}
}
wchar_t *Array3D[2][2][2] =
{
{
{L"some", L"string"},
{L"nice", L"string"}
},
{
{L"another", L"String"},
{L"just cut", L"this already"}
}
}
And so on. These are static
arrays - pretty much easy stuff. 1D Array is just like a string
s container, 2D array is much like a table and 3D array is almost like a database. And now, all we need to do, is to create such arrays, but dynamic arrays, with dynamic memory allocation, reallocation, deallocation, removing string
s, replacing string
s, adding string
s and so on. One can easily get lost in bytes and give up using StringBuilder
in C#, but not us, comrades, we are proceeding forward.
Using the Code
Here is what we got for now.
public:
_1D(IN BOOL UserArray = FALSE); ~_1D();
VOID InitUserArray(IN wchar_t **&uArray);
BOOL Append(IN std::string Temp);
BOOL Append(IN std::string Temp, IN OUT wchar_t **&uArray);
BOOL Append(IN std::wstring Temp);
BOOL Append(IN std::wstring Temp, IN OUT wchar_t **&uArray);
BOOL Append(IN char *String);
BOOL Append(IN char *String, IN OUT wchar_t **&uArray);
BOOL Append(__in wchar_t *String);
BOOL Append(__in wchar_t *String, IN OUT wchar_t **&uArray);
unsigned int Length(VOID); unsigned int Length(IN wchar_t **uArray, IN int count = -1); void ToArray(IN char *String, IN char *Delimiter);
void ToArray(IN char *String, IN char *Delimiter, IN OUT wchar_t **&uArray);
void ToArray(IN wchar_t *String, IN wchar_t *Delimiter);
void ToArray(IN wchar_t *String, IN wchar_t *Delimiter, IN OUT wchar_t **&uArray);
wchar_t * AStringToW(IN char * pstr);
char * WStringToA(IN wchar_t * pstr);
BOOL NullOrEmpty(IN wchar_t *String); BOOL NullOrEmpty(IN char *String);
char * ToString(IN char *Delimiter); char * ToString(IN char *Delimiter, IN wchar_t **uArray, IN int Elems = -1);
wchar_t * ToString(IN wchar_t *Delimiter);
wchar_t * ToString(IN wchar_t *Delimiter, IN wchar_t **uArray, IN int Elems = -1);
BOOL Replace(IN char *Search, IN char *Replace,
IN BOOL isSearchPartial = FALSE,
IN BOOL replaceAll = FALSE,
IN int replAtIndex = -1);
BOOL Replace(IN char *Search, IN char *Replace,
IN OUT wchar_t **&uArray,
IN int Elems = -1,
IN BOOL isSearchPartial = FALSE,
IN BOOL replaceAll = FALSE,
IN int replAtIndex = -1);
BOOL Replace(IN wchar_t *Search,IN wchar_t *Replace,
IN BOOL isSearchPartial = FALSE,
IN BOOL replaceAll = FALSE,
IN int replAtIndex = -1);
BOOL Replace(IN wchar_t *Search,IN wchar_t *Replace,
IN OUT wchar_t **&uArray,
IN int Elems = -1,
IN BOOL isSearchPartial = FALSE,
IN BOOL replaceAll = FALSE,
IN int replAtIndex = -1);
wchar_t * GetAtIndex(IN int Index);
wchar_t * GetAtIndex(IN int Index, IN wchar_t **uArray);
char * GetAtIndexA(IN int Index); char * GetAtIndexA(IN int Index, IN wchar_t **uArray);
BOOL Remove(IN wchar_t *Item = NULL,
IN int Index = -1,
IN BOOL isItemPartial = FALSE,
IN BOOL removeAll = FALSE);
BOOL Remove(IN wchar_t **&uArray,
IN int Elems = -1,
IN wchar_t *Item = NULL,
IN int Index = -1,
IN BOOL isItemPartial = FALSE,
IN BOOL removeAll = FALSE);
wchar_t **GetArrayW();
int Count(VOID); int Count(IN wchar_t **uArray);
void Clear(VOID); void Clear(IN OUT wchar_t **&uArray, IN OUT int Elems = -1);
void Destroy(VOID); void Destroy(IN OUT wchar_t **&uArray, IN OUT int Elems = -1);
That's for 1D array. Here we go with 2D and 3D arrays.
_2D(IN BOOL UserTable = FALSE);
~_2D();
void InitUserTable(IN OUT wchar_t ***&uTable); void AppendRow(IN wchar_t **Row); void AppendRow(IN wchar_t **Row, IN OUT wchar_t ***&uTable);
void ReplaceRow(IN wchar_t **NewRow, IN int Index);
void ReplaceRow(IN wchar_t **NewRow, IN int Index, IN OUT wchar_t ***&uTable);
void RemoveRow(IN int Index);
void RemoveRow(IN int Index, IN OUT wchar_t ***&uTable);
unsigned int Size();
unsigned int Size(IN wchar_t ***uTable);
wchar_t ***GetTable();
int CountRows();
int CountRows(IN wchar_t ***uTable);
void Clear(VOID);
void Clear(IN OUT wchar_t ***&uTable, IN int Elems = -1);
void Destroy(VOID);
void Destroy(IN OUT wchar_t ***&uTable, IN int Elems = -1);
_3D(IN BOOL UserDb = FALSE);
~_3D();
void InitUserDb(IN OUT wchar_t ****&uDb); void AppendTable(IN wchar_t ***Tab);
void AppendTable(IN wchar_t ***Tab, IN OUT wchar_t ****&uDb);
void ReplaceTable(IN wchar_t ***NewTable, IN int Index);
void ReplaceTable(IN wchar_t ***NewTable, IN int Index, IN OUT wchar_t ****&uDb);
void RemoveTable(IN int Index);
void RemoveTable(IN int Index, IN OUT wchar_t ****&uDb);
wchar_t ****GetDataBase(); unsigned int Size();
unsigned int Size(IN wchar_t ****uDb);
int CountTables();
int CountTables(IN wchar_t ****uDb);
void Clear(VOID);
void Clear(IN OUT wchar_t ****&uDb, IN int Elems = -1);
void Destroy(VOID);
void Destroy(IN OUT wchar_t ****&uDb, IN int Elems = -1);
That's it. As you can see, there are overloads, like for Clear
method. So, this "uArray
", "uTable
", and "uDb
" are your pointers. Another thing, sometimes, there are params like: IN int Elems = -1 - here is an explanation. Basically, while adding elements, class counts them, but for example, if you are supplying your own array and you have made own counts, so you can supply them too. Almost in 100% this param is unused.
Alright, let's test it.
void Array1DTest(int loops) {
ArrayWorker::_1D *row1 = new ArrayWorker::_1D();
std::string stdtest = "somestd";
std::wstring stdwstr = L"unicode";
for(int i = 0; i <= loops; i++)
{
wprintf(L"Lopp nr: %d\n", i);
row1->Append(stdtest);
row1->Append(stdwstr);
row1->Append("char");
row1->Append(L"wchar");
int len = row1->Length();
char *tempstr = row1->ToString(",");
row1->ToArray(tempstr, ",");
wchar_t *wtempstr = row1->ToString(L",");
row1->ToArray(wtempstr, L",");
row1->Replace("char",
"newchar1111111111111111111111111111111111111111x");
row1->Replace(L"wchar",
L"newwchar2222222222222222222222222222222222222222222222z");
row1->Remove(NULL, 0);
row1->Remove(NULL, 0);
free(tempstr);
free(wtempstr);
if(i < loops)row1->Clear();
}
wchar_t **myrow = row1->GetArrayW();
wprintf(L"\r\n%s | %s\r\n", myrow[0], myrow[1]); delete row1;
}
void Array2DTest(int loops) {
ArrayWorker::_2D *Table = new ArrayWorker::_2D();
wchar_t *testrow[] = {L"some", L"row", L"here", L"man"};
wchar_t *somerow[] = { L"well", L"done", L"my", L"friend"};
wchar_t *nicerow[] = { L"looks", L"like", L"its", L"done" };
for(int i = 0; i <= loops; i++)
{
wprintf(L"Loop nr: %d\n", i);
Table->AppendRow(testrow);
Table->AppendRow(somerow);
Table->RemoveRow(0);
Table->ReplaceRow(nicerow, 0);
if(i < loops)Table->Clear();
}
wchar_t ***tab = Table->GetTable();
wprintf(L"row %d: %s %s %s %s\r\n", loops,
tab[0][0], tab[0][1], tab[0][2], tab[0][3]);
delete Table;
}
Ok here too.
void Array3DTest(int loops) {
while(loops >= 0)
{
ArrayWorker::_1D *row1 = new ArrayWorker::_1D();
ArrayWorker::_1D *row2 = new ArrayWorker::_1D();
ArrayWorker::_1D *row3 = new ArrayWorker::_1D();
ArrayWorker::_1D *row4 = new ArrayWorker::_1D();
row1->Append("nice");
row1->Append("stuff");
row2->Append("some");
row2->Append("thing");
row3->Append("another");
row3->Append("thing");
row4->Append("whatever");
row4->Append("boy");
ArrayWorker::_2D *table1 = new ArrayWorker::_2D();
ArrayWorker::_2D *table2 = new ArrayWorker::_2D();
table1->AppendRow(row1->GetArrayW());
table1->AppendRow(row2->GetArrayW());
table2->AppendRow(row3->GetArrayW());
table2->AppendRow(row4->GetArrayW());
ArrayWorker::_3D *db = new ArrayWorker::_3D();
db->AppendTable(table1->GetTable());
db->AppendTable(table2->GetTable());
wchar_t ****dat = db->GetDataBase();
wprintf(L"Table1: \n%s %s \n%s %s\n\nTable2: \n%s %s\n%s %s\n\n",
dat[0][0][0], dat[0][0][1], dat[0][1][0], dat[0][1][1],
dat[1][0][0], dat[1][0][1], dat[1][1][0], dat[1][1][1]);
delete db;
delete table2;
delete table1;
delete row4;
delete row3;
delete row2;
delete row1;
loops--;
wprintf(L"Loop left: %d\n", loops);
}
wprintf(L"DONE!\n");
}
Looks like it's all ok, no memory leaks and stuff. Anyway, there were no tests done with supplied user arrays. The complete ArrayWorker
class source code is attached to this article, however, it does not include testing functions. Cheers, csrss.
Points of Interest
Writing this class, I have learnt memory management probably in 100%. Also I would like to thank a CodeProject user, Andrew Brock, who clarified a few things to me, while answering my questions in CP forums.
History
- 11th February, 2011: Initial version