// Sequential_Sort.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <Windows.h>
#include <WinBase.h>
/**
* Class: Array
*
* Custom Array class used to read input from a file
* and store the contents to specific members of Node.
* 1 Node is equivalent to 1 person.
*
*/
class Array {
private:
class Node {
private:
friend class Array;
int m_SSid;
int m_age;
int m_commulativeContribution;
int m_yearStarted;
public:
};
Node *m_headPtr;
int m_ArraySize;
std::fstream m_dataFile;
public:
Array( int size, std::string fileName );
~Array();
void populateArray();
void displayArray();
void displayElement( int index );
int sequentialSearch( int SSid );
};
/**
* Constructor: Array()
*
* The constructor must accept the size of the list to be used
* as well as the name of the file which contains the data of
* each Node/Person. If the file fails to Open for input then
* throw an exception which is caught and the error is stored in
* a log file. If successfull then allocate space for our custom
* array depending on the size specified by the user/programmer.
* If unable to allocate memory on the heap for the custom array close
* the file and then throw an exception which is caught by the caller
* and stored into a log file.
*
* @access public
*/
Array::Array( int size, std::string fileName )
: m_ArraySize( size )
{
m_dataFile.open( fileName.c_str(), std::ios::in );
if( m_dataFile.fail() )
throw "Unable To Open The Specified File";
m_headPtr = new Node[size]; // allocate memory for Array
if( m_headPtr == NULL ) {
m_dataFile.close();
throw "Unable To Allocate Sufficient Memory On The Heap";
}
}
Array::~Array()
{
m_dataFile.close();
delete [] m_headPtr;
}
/**
* Function: populateArray()
*
* Simply progresses through the custom array and populates it
* with data from the specified input file. Must clear eof/status
* flags for the ofstream and then reset file cursor to the beginning
* of the file in order to make this function call to be successfull
* multiple times
*
* @return void
* @access private
*/
void Array::populateArray()
{
m_dataFile.clear();
m_dataFile.seekp( 0L, std::ios::beg ); // (offset, base(where to start))
for( int i = 0; i < m_ArraySize; i++ ) {
m_dataFile >> m_headPtr[i].m_SSid;
m_dataFile >> m_headPtr[i].m_age;
m_dataFile >> m_headPtr[i].m_commulativeContribution;
m_dataFile >> m_headPtr[i].m_yearStarted;
}
}
/**
* Function: displayArray()
*
* Simply Progresses through the whole list
* and displays the datamembers of each Node/Person
* element in the list.
*
* @return void
* @access private
*/
void Array::displayArray()
{
for( int i = 0; i < m_ArraySize; i++ ) {
std::cout << m_headPtr[i].m_SSid << std::endl;
std::cout << m_headPtr[i].m_age << std::endl;
std::cout << m_headPtr[i].m_commulativeContribution << std::endl;
std::cout << m_headPtr[i].m_yearStarted << std::endl << std::endl;
}
}
/**
* Function: displayElement()
*
* Recieves an Index to where the located Person/Node
* object is located in the list, and then dereferences
* the object's data members there and displays them.
*
* @return void
* @access private
*/
void Array::displayElement( int i )
{
std::cout << m_headPtr[i].m_SSid << std::endl;
std::cout << m_headPtr[i].m_age << std::endl;
std::cout << m_headPtr[i].m_commulativeContribution << std::endl;
std::cout << m_headPtr[i].m_yearStarted << std::endl;
}
/**
* Function: sequentialSearch()
*
* Simply Progresses through the whole list
* and when a match for the specified passed in SSid
* is found, then return the index to which that
* SSid number was found. Returns -1 if no match was found
*
* @return bool
* @access private
*/
int Array::sequentialSearch( int SSid )
{
for( int i = 0; i < m_ArraySize; i++ )
if( m_headPtr[i].m_SSid == SSid )
return i;
return -1;
}
/**
* Function: add_log()
*
* Whenever an exception is thrown and caught
* this function is called to write to a text
* file what exception was thrown, and when it
* happened.
*
* @return void
* Free Function
*/
void add_log(char buffer[100])
{
std::ofstream outFile;
outFile.open("Log.txt", std::ios::app);
SYSTEMTIME st;
GetLocalTime(&st);
outFile << "===================================" << std::endl;
outFile << "Date: " << st.wMonth << "/" << st.wDay << "/" << st.wYear << std::endl;
outFile << "Time: " << st.wHour << ":" << st.wMinute << std::endl;
outFile << "===================================" << std::endl;
outFile << "[ERROR] - " << buffer << std::endl;
outFile.close();
}
/**
* Function: holdScreen()
*
* This function is pretty self explanatory.
* All it does is hold the console screen open.
* It does this by ignoring any input left in the
* input stream, and then asking the user to type
* a character which is pretty much used to exit
* the program in this case
*
* @return void
*/
inline void holdScreen()
{
std::cin.ignore();
std::getchar();
}
int _tmain(int argc, _TCHAR* argv[])
{
try {
Array Array1( 2, "datafile.txt" );
Array1.populateArray();
Array1.displayArray();
int index = Array1.sequentialSearch( 645 );
if( index == -1 )
std::cout << "The SSid that was searched for was not found in the Array" << std::endl;
else
std::cout << "The SSid was found at index " << index << std::endl;
}
catch( char *string ) {
add_log( string );
}
holdScreen();
return 0;
}
Custom Array Class**looking advice**
#1 Members - Reputation: 119
Posted 24 February 2011 - 10:42 PM
#2 Moderators - Reputation: 13606
Posted 24 February 2011 - 11:04 PM
1) Functions like displayElement, which do not modify members, should be const
2) Complex types, such as std::string should be passed by const-reference, instead of passed by value.
3) Array should definitely be a template class, with Node as the template parameter.
4) The check of m_headPtr against NULL is useless -- the basic new operator never returns NULL (it throws a std::bad_alloc on failure).
5) You're not following the rule of three, which means your class is dangerous and will cause crashes if an instance is ever copied/assigned.
Design problems:
This design constantly violates the law of demeter. You've tied together:
A ) a particular application-specific data structure (Node)
B ) functionality for deserialising 'Nodes' from a file stream (which also requires the user to guess how many items are in the file...)
C ) functionality for displaying 'Nodes' to a hard-coded output stream (this logic is even duplicated in displayArray/displayElement)
D ) a linear search function (std::find)
E ) array resource management (std::vector)
That's 5 different areas of functionality, all tightly coupled together. There is no need for any of those areas to be directly connected to each other like that.
#3 Moderators - Reputation: 13606
Posted 26 February 2011 - 10:44 AM
I'll reply to your PM here.Could you plz explain how/what i can do to make my member functions loosely coupled? Also how can i stop from duplicating logic like you say?
First, to decouple the array class, we can strip the it back so it only deals with array memory management - nothing else.
template<class T> class Array
{
private:
typedef T Node;
Node* m_headPtr;
size_t m_ArraySize;
public:
Array() : m_ArraySize(), m_headPtr() {}
Array( size_t size ) : m_ArraySize( size ), m_headPtr( new Node[size] ) {}
Array( const Array& obj ) : m_ArraySize(), m_headPtr() { *this = obj; }
~Array() { delete [] m_headPtr; }
Array& operator=( const Array& obj );
void resize( size_t );
size_t size() const { return m_ArraySize; }
Node& operator[]( size_t i ) { return m_headPtr[i]; }
const Node& operator[]( size_t i ) const { return m_headPtr[i]; }
};
template<class T> Array<T>& Array<T>::operator=( const Array<T>& obj )
{
if( this != &obj )//skip over this code if performing self assignment (e.g. "a = a;")
{
delete [] m_headPtr;
m_ArraySize = obj.m_ArraySize;
m_headPtr = new Node[obj.m_ArraySize];
for( int i = 0; i < m_ArraySize; i++ )
m_headPtr[i] = obj.m_headPtr[i];
}
return *this;
}
template<class T> void Array<T>::resize( size_t size )
{
m_ArraySize = size;
Node* pNew = new Node[size];
for( int i = 0; i < m_ArraySize; i++ )
pNew[i] = m_headPtr[i];
delete [] m_headPtr;
m_headPtr = pNew;
}Some of the logic that was in populate/display can be moved into overloaded operators for your node structure:
struct Node {
int m_SSid;
int m_age;
int m_commulativeContribution;
int m_yearStarted;
};
std::istream& operator>>( std::fstream& in, Node& out )
{
in >> out.m_SSid;
in >> out.m_age;
in >> out.m_commulativeContribution;
in >> out.m_yearStarted;
return in;
}
std::ostream& operator<<( std::ostream& out, const Node& in )
{
out << in.m_SSid << std::endl;
out << in.m_age << std::endl;
out << in.m_commulativeContribution << std::endl;
out << in.m_yearStarted << std::endl;
return out;
}...Which makes the populate/display functions a bit more reusable (they work for any kind of array/node now). They also don't have to be member functions, because they don't need to access any private variables.//print out an array's values
template<class Array>void displayArray( const Array& in )
{
for( size_t i = 0; i != in.size(); ++i )
std::cout << in[i];
}
//read array values from a file
template<class Array> void populateArray( Array& out, std::fstream& in, size_t size )
{
in.clear();
in.seekp( 0L, std::ios::beg ); // (offset, base(where to start))
out.resize( size );
for( int i = 0; i < size; i++ )
in >> out[i];
}
//helper overload takes a file-name instead of a file
template<class Array> void populateArray( Array& out, const std::string& fileName, size_t size )
{
std::fstream dataFile;
dataFile.open( fileName.c_str(), std::ios::in );
if( dataFile.fail() )
throw "Unable To Open The Specified File";
populateArray( out, dataFile, size );
dataFile.close();
}The search function used to be hard-coded to only search for the "m_SSid" field. If you turn it into a template then you can reuse this logic for many different kinds of searchestemplate<class Array, class T> int sequentialSearch( const Array& data, const T& find )
{
for( int i = 0; i < data.size(); i++ )
if( data[i] == find )
return i;
return -1;
}//pass a 'FindSsid' object to sequentialSearch in order to search nodes based on m_SSid
struct FindSsid {
int ssid;
FindSsid(int i) : ssid(i) {}
};
bool operator==( const Node& lhs, const FindSsid& rhs ) {
return lhs.m_SSid == rhs.ssid;
}
//putting it all together
int main( int argc, char** argv )
{
try {
Array<Record> array1;
populateArray( array1, "datafile.txt", 2 );
displayArray( array1 );
int index = sequentialSearch( array1, FindSsid(645) );
if( index == -1 )
std::cout << "The SSid that was searched for was not found in the Array" << std::endl;
else
std::cout << "The SSid was found at index " << index << std::endl;
}
catch( const char *string ) {
add_log( string );
}
catch( const std::bad_alloc& exception ) {
add_log( "Unable to Allocate the Specified memory" );
}
catch( ... ) {
add_log( "Unknown error" );
}
holdScreen();
return 0;}
#4 Members - Reputation: 119
Posted 26 February 2011 - 12:23 PM
typedef T Node; <--- why instantiate a Node object on creation of an array? is this a mistake?
#5 Members - Reputation: 1349
Posted 26 February 2011 - 02:11 PM
That doesn't instantiate anything. A typedef creates a type alias so that Node can be used in place of T.thanks a bunch mate! am currently at work, but when i get home i shall study this and understand how it's differs from my code! and make according changes!
typedef T Node; <--- why instantiate a Node object on creation of an array? is this a mistake?
C++: A Dialog | C++0x Features: Part1 (lambdas, auto, static_assert) , Part 2 (rvalue references) , Part 3 (decltype) | Write Games | Fix Your Timestep!
#6 Moderators - Reputation: 13606
Posted 26 February 2011 - 10:53 PM
Sorry, that was unnecessarily confusing.<--- why instantiate a Node object on creation of an array? is this a mistake?
I did that because the Array class was already written to use 'Node' everywhere, but I wanted it to use 'T' instead. That typedef says that wherever you see the word Node used (inside the array class and it's functions ONLY), then it's actually a 'T' (NOT the Node from your first post).
e.g. given a Array<int>, then Array<int>::Node would be a private typedef for 'int'.
template<class T> class Array
{
private:
typedef T Node; //Inside the scope of "Array", pretend that the word "Node" is actually "T".
....
....
template<class T> void Array<T>::resize( size_t size )
{//We're inside the scope of "Array::" here
...
//this doesn't actually make one of your 'Node' structs, this makes a 'T' (whatever that is)
Node* pNew = new Node[size]; //i.e. T* pNew = new T[size];
...I could've done a find&replace with 'Node' and 'T' instead :/






