0

The following code is working but I'm using a string instead of a char array. It is a homework question I received and I'm struggling to implement a 2D char array with pointers. Help would be much appreciated!

#include <iostream>
#include <iomanip>
#include <fstream>
    
using namespace std;

int readFromFile(string* P);
void displayData(string* P, int S);

int main()
{
    const int Size = 30;
    string arrFriends[Size];
    string* pName = arrFriends;

    int count = readFromFile(pName);

    displayData(pName, count);


    return 0;
}


int readFromFile(string* P)
{
    ifstream infile;
    infile.open("friends.txt");
    int count = 0;

    cout << "Reading from the file.";
    if(infile.fail())
    {
        cout << "\nError opening file!";
    }
    else
    {
        while(!(infile.eof()))
        {
            getline(infile, *(P + count));
            count++;
        }
        infile.close();
    }
    cout << "\nDone!\n";
    return count;
}


void displayData(string* P, int S)
{
    cout << "\nContent of the array:\n";
    for(int i = 0; i < S; i++)
    {
        cout << *(P + i) << endl;
    }
}

2.1 Declare a char array called arrFriends that will be able to hold 30 elements. Declare a pointer for the array.

2.2 Write a method called readFromFile that will receive a pointer to the address of the first element of the array as a parameter. Read the names of a few friends from the text file called friends.txt into an array using the pointer. Return the number of elements saved in the array.

2.3 Write a function called displayData that will receive the pointer of the first element of the array and the number of elements stored in the array as parameters. Display a heading and a list of names.

2.4 In the main function, call the methods to read the name from the file and display the names from the array.

enter image description here

WD de Wet
  • 25
  • 4
  • Two things: First of all why don't you use `std::getline` and `std::vector` to read all lines? Secondly [always consider a loop like `while (!infile.eof())` as wrong](https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-i-e-while-stream-eof-cons). – Some programmer dude Oct 21 '22 at 16:18
  • Regarding my first point in the comment above, think about a case where there are more than 30 lines in the file. – Some programmer dude Oct 21 '22 at 16:19
  • 'std::getline' I can use but we haven't learned 'std::vector'. The while loop I copied from an example out of our slides. – WD de Wet Oct 21 '22 at 16:22
  • @Someprogrammerdude I know that the code is not perfect but it is to test our knowledge for a very specific case which is why we have to follow the instructions exactly. – WD de Wet Oct 21 '22 at 16:25
  • 2
    Then please read the *second* part of my first comment. Your reading-loop just isn't correct. Whomever taught you that should be forced to read that link. – Some programmer dude Oct 21 '22 at 16:29
  • 1
    Generalizing the `while (!eof)` problem, when reading you need 1) read the data, 2) test that you read the data, and 3) use the data or handle the error depending on the results of 2). If you use any other order, you have a bug. In the ``while (!eof)``, the test for a successful data read is performed BEFORE the read where it is almost completely worthless. – user4581301 Oct 21 '22 at 16:34
  • I recommend clarifying, with an edit to the question, exactly where you have run into trouble. When you clearly describe the problem, including differentiating the desired behaviour from the observed behaviour, this often shakes loose where the problem is and how tro fix it without having to post the question. A good question writing strategy doesn't just lead to a good question, more often than not it leads to NO question. – user4581301 Oct 21 '22 at 16:39

2 Answers2

1

Evolution.

Implementation of your homework with pointer to first character of 2d array

#include <iostream>
#include <fstream>

#define MAX_NUMBER_FRIENDS 30
#define MAX_NAME_LENGTH 50

int readFromFile(char *dataPointer)
{
    int count = 0;

    std::ifstream infile;
    infile.open("r:\\friends.txt");

    if (infile.fail())
    {
        std::cout << "\nError opening file!\n";
    }
    else
    {
        std::cout << "Reading from the file.";
        do 
        {
            infile.getline(dataPointer, MAX_NAME_LENGTH);
            if (not infile.fail()) {
                count++;
                dataPointer += MAX_NAME_LENGTH;
            }
        } while ((not infile.fail()) and (count < MAX_NUMBER_FRIENDS));
        infile.close();
        std::cout << "\nDone!\n";
    }
    return count;
}
void displayData(char* dataPointer, int count) {
    std::cout << "\nContent of the array\n";
    for (int i = 0; i < count; ++i) {
        std::cout << dataPointer << '\n';
        dataPointer += MAX_NAME_LENGTH;
    }
    std::cout << '\n';
}

int main() {

    char arrFriends[MAX_NUMBER_FRIENDS][MAX_NAME_LENGTH];
    char* arrFriendsPointer = &arrFriends[0][0];

    int numberOfNames = readFromFile(arrFriendsPointer);
    displayData(arrFriendsPointer, numberOfNames);
}

Next, with comments. Looks immediately better:

#include <iostream>
#include <fstream>

// Compile Time Specification: Numbers are not magic, but given by requirement
// This will define the number of the elements in the friends array
#define MAX_NUMBER_FRIENDS 30
// This is the maximum length of the name
#define MAX_NAME_LENGTH 50

// Read a a list of names from a file and store it in a given array
int readFromFile(char* dataPointer) {
    
    // We will count the number of names in the file and return this info to the caller of this function
    int nameCount = 0;

    // Open the input text file with names
    std::ifstream nameFileStream;
    nameFileStream.open("r:\\friends.txt");

    // Check, if the file could be opened and that there is no failure
    if (nameFileStream.fail()) {
        // Error. There was a failure. File could not be opened
        std::cout << "\nError opening file!\n";
    }
    else
    {
        // File could be opened. Give status message
        std::cout << "Reading from the file.";
        do
        {
            // Read one name from the file. Protect from out of bound error
            nameFileStream.getline(dataPointer, MAX_NAME_LENGTH);

            // Check, if the name could be read, or, if there was a failure
            if (not nameFileStream.fail()) {

                // Name could be successfully read. Increase name counter
                nameCount++;
                // And point to the next row in the 2d array
                dataPointer += MAX_NAME_LENGTH;
            }
            // Loop end condition will check for stream failure or too many names
        } while ((not nameFileStream.fail()) and (nameCount < MAX_NUMBER_FRIENDS));

        // Close the file at the end
        nameFileStream.close();

        // Final status message
        std::cout << "\nDone!\n";
    }
    return nameCount;
}

// Display the data
void displayData(char* dataPointer, int count) {

    // Give user information
    std::cout << "\nContent of the array\n";

    // In a loop, show all names
    for (int i = 0; i < count; ++i) {

        // Output name
        std::cout << dataPointer << '\n';

        // Set pointer to next row in 2d array
        dataPointer += MAX_NAME_LENGTH;
    }
    std::cout << '\n';
}

int main() {

    // Define a 2 dimensional array to hold a number of name strings
    char arrFriends[MAX_NUMBER_FRIENDS][MAX_NAME_LENGTH];

    // This is a pointer to the first character in the 2d array
    char* arrFriendsPointer = &arrFriends[0][0];

    // Read and show data
    int numberOfNames = readFromFile(arrFriendsPointer);
    displayData(arrFriendsPointer, numberOfNames);
}

Now, with a pointer to the first row of the 2d array. And some further improvements

#include <iostream>
#include <fstream>

// Compile Time Specification: Numbers are not magic, but given by requirement
// This will define the number of the elements in the friends array
constexpr unsigned int MaxNumberFriends = 30;
// This is the maximum length of the name
constexpr unsigned int MaxNameLength = 50;


// Read a a list of names from a file and store it in a given array
unsigned int readFromFile(char (*dataPointer)[MaxNameLength]) {

    // We will count the number of names in the file and return this info to the caller of this function
    unsigned int nameCount{};

    // Open the input text file with names
    std::ifstream nameFileStream{ "r:\\friends.txt"};

    // Check, if the file could be opened and that there is no failure
    if (nameFileStream.fail()) {
        // Error. There was a failure. File could not be opened
        std::cout << "\nError opening file!\n";
    }
    else {
        // File could be opened. Give status message
        std::cout << "Reading from the file.";
        do {
            // Read one name from the file. Protect from out of bound error
            nameFileStream.getline(dataPointer[nameCount], MaxNameLength);

            // Check, if the name could be read, or, if there was a failure
            if (not nameFileStream.fail()) {

                // Name could be successfully read. Increase name counter
                nameCount++;
            }
            // Loop end condition will check for stream failure or too many names
        } while ((not nameFileStream.fail()) and (nameCount < MaxNumberFriends));

        // Final status message
        std::cout << "\nDone!\n";
    }
    return nameCount;
}

// Display the data
void displayData(char(*dataPointer)[MaxNameLength], int count) {

    // Give user information
    std::cout << "\nContent of the array\n";

    // In a loop, show all names
    for (int i = 0; i < count; ++i) {

        // Output name
        std::cout << dataPointer[i] << '\n';
    }
    std::cout << '\n';
}

int main() {

    // Define a 2 dimensional array to hold a number of name strings
    char arrFriends[MaxNumberFriends][MaxNameLength]{};

    // This is a pointer to the first row in the 2d array
    char(*arrFriendsPointer)[MaxNameLength] {&arrFriends[0]};

    // Read and show data
    unsigned int numberOfNames = readFromFile(arrFriendsPointer);
    displayData(arrFriendsPointer, numberOfNames);
}

And now with a pointer to the complete 2d array. And some further improvements . . .

#include <iostream>
#include <fstream>

// Compile Time Specification: Numbers are not magic, but given by requirement
// This will define the number of the elements in the friends array
constexpr unsigned int MaxNumberFriends = 30;
// This is the maximum length of the name
constexpr unsigned int MaxNameLength = 50;


// Read a a list of names from a file and store it in a given array
unsigned int readFromFile(char (*dataPointer)[MaxNumberFriends][MaxNameLength]) {

    // We will count the number of names in the file and return this info to the caller of this function
    unsigned int nameCount{};

    // Open the input text file with names
    std::ifstream nameFileStream{ "r:\\friends.txt" };

    // Check, if the file could be opened and that there is no failure
    if (nameFileStream.fail()) {
        // Error. There was a failure. File could not be opened
        std::cout << "\nError opening file!\n";
    }
    else {
        // File could be opened. Give status message
        std::cout << "Reading from the file.";
        do {
            // Read one name from the file. Protect from out of bound error
            nameFileStream.getline((*dataPointer)[nameCount], MaxNameLength);

            // Check, if the name could be read, or, if there was a failure
            if (not nameFileStream.fail()) {

                // Name could be successfully read. Increase name counter
                nameCount++;
            }
            // Loop end condition will check for stream failure or too many names
        } while ((not nameFileStream.fail()) and (nameCount < MaxNumberFriends));

        // Final status message
        std::cout << "\nDone!\n";
    }
    return nameCount;
}

// Display the data
void displayData(char(*dataPointer)[MaxNumberFriends][MaxNameLength], int count) {

    // Give user information
    std::cout << "\nContent of the array\n";

    // In a loop, show all names
    for (int i = 0; i < count; ++i) {

        // Output name
        std::cout << (*dataPointer)[i] << '\n';
    }
    std::cout << '\n';
}

int main() {

    // Define a 2 dimensional array to hold a number of name strings
    char arrFriends[MaxNumberFriends][MaxNameLength]{};

    // This is a pointer to the 2d array
    char (* arrFriendsPointer)[MaxNumberFriends][MaxNameLength] = &arrFriends;

    //Read and show data
    unsigned int numberOfNames = readFromFile(arrFriendsPointer);
    displayData(arrFriendsPointer, numberOfNames);
}

A little bit more C++

#include <iostream>
#include <fstream>

// Compile Time Specification: Numbers are not magic, but given by requirement
// This will define the number of the elements in the friends array
constexpr unsigned int MaxNumberFriends{ 30u };
// This is the maximum length of the name
constexpr unsigned int MaxNameLength{ 50u };

// Defining types
using Array2d = char[MaxNumberFriends][MaxNameLength];
using Array2dPtr = Array2d*;

// Read a a list of names from a file and store it in a given array
size_t readFromFile(Array2dPtr dataPointer) {

    // We will count the number of names in the file and return this info to the caller of this function
    size_t nameCount{};

    // Open the input text file with names and check, if it could be opened
    if (std::ifstream nameFileStream{ "r:\\friends.txt" }; nameFileStream) {

        // File could be opened. Give status message
        std::cout << "Reading from the file.";
        do {
            // Read one name from the file. Protect from out of bound error
            nameFileStream.getline((*dataPointer)[nameCount], MaxNameLength);

            // Check, if the name could be read, or, if there was a failure
            if (not nameFileStream.fail()) ++nameCount;
            
            // Loop end condition will check for stream failure or too many names
        } while ((not nameFileStream.fail()) and (nameCount < MaxNumberFriends));

        // Final status message
        std::cout << "\nDone!\n";
    }
    else std::cerr << "\n*** Error: File could not be opened.\n";

    return nameCount;
}

// Display the data
void displayData(Array2dPtr dataPointer, size_t count) {

    // Give user information
    std::cout << "\nContent of the array\n";

    // In a loop, show all names
    for (size_t i{}; i < count; ++i)
        std::cout << (*dataPointer)[i] << '\n';
    std::cout << '\n';
}

int main() {

    // Define a 2 dimensional array to hold a number of name strings
    Array2d arrFriends{};

    // This is a pointer to the 2d array
    Array2dPtr arrFriendsPointer{ &arrFriends};

    //Read and show data
    size_t numberOfNames = readFromFile(arrFriendsPointer);
    displayData(arrFriendsPointer, numberOfNames);
}

And, at the end, one of many full blown C++ solutions:

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <iterator>

struct Names {
    std::vector<std::string> names{};

    size_t count() const { return names.size(); }
    // Define simple extractor
    friend std::istream& operator >> (std::istream& is, Names& n) {
        n.names.clear();
        for (std::string line{}; std::getline(is, line); n.names.push_back(line));
        return is;
    }
    // Define simple inserter
    friend std::ostream& operator << (std::ostream& os, const Names& n) {
        std::copy(n.names.begin(), n.names.end(), std::ostream_iterator< std::string>(os, "\n"));
        return os;
    }
};
size_t readFromFile(Names& names) {

    // Open the input text file with names and check, if it could be opened
    if (std::ifstream nameFileStream{ "r:\\friends.txt" }; nameFileStream) 

        // Rad everything with one simple statement
        nameFileStream >> names;

    else std::cerr << "\n*** Error: File could not be opened.\n";

    // Return number of elements
    return names.count();
}

int main() {

    Names names{};

    size_t numberOfNames = readFromFile(names);

    std::cout << "\nNumber of names read: " << numberOfNames << "\n\n" << names << '\n';
}


Or, using a line proxy . . .

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
#include <algorithm>

class CompleteLine {    // Proxy for the input Iterator
public:
    // Overload extractor. Read a complete line
    friend std::istream& operator>>(std::istream& is, CompleteLine& cl) { std::getline(is, cl.completeLine); return is; }
    // Cast the type 'CompleteLine' to std::string
    operator std::string() const { return completeLine; }
protected:
    // Temporary to hold the read string
    std::string completeLine{};
};


// Solution
int main() {

    // Open the input text file with names and check, if it could be opened
    if (std::ifstream nameFileStream{ "r:\\friends.txt" }; nameFileStream) {

        // Read the complete file
        std::vector names{ std::istream_iterator<CompleteLine>(nameFileStream),{} };

        // Show user output and list up the names
        if (not names.empty()) std::cout << "\nReading Data from file\nDone\n\n";
        std::copy(names.begin(), names.end(), std::ostream_iterator<std::string>(std::cout, "\n"));
    }
    else std::cerr << "\n*** Error: File could not be opened.\n";
}

Have fun . . .

A M
  • 14,694
  • 5
  • 19
  • 44
0

If you know a fixed upper bound for the length of a name, you could simply create an array char friends[30][MaxNameLength + 1].

Using a custom allocator and making sure no reallocations happen allows you to use code that's pretty similar to the code you've written.

constexpr size_t MaxFriends = 30;
constexpr size_t MaxNameLength = 255;

using Friend = char[MaxNameLength + 1];

// allocator returning the storage for one friend name for use by std::basic_string
template<class T>
struct Allocator
{
    Friend* m_friend;

    Allocator(Friend* fr)
        : m_friend(fr)
    {}

    template<class U>
    Allocator(Allocator<U> const& other)
        : m_friend(other.m_friend)
    {
    }

    template<class U>
    Allocator& operator=(Allocator<U> const& other)
    {
        m_friend = other.m_friend;
        return *this;
    }
    using value_type = T;
    using size_type = size_t;

    template<class U>
    struct rebind
    {
        using other = Allocator<U>;
    };

    T* allocate(size_t n)
    {
        return reinterpret_cast<T*>(*m_friend);
    }

    void deallocate(T*, size_t)
    {
    }

    bool operator==(Allocator const& other) const
    {
        return false;
    }

    bool operator!=(Allocator const& other) const
    {
        return true;
    }

    constexpr size_t max_size() const
    {
        return sizeof(Friend) / sizeof(T);
    }

};

using String = std::basic_string<char, std::char_traits<char>, Allocator<char>>;

static_assert(sizeof(String) <= MaxNameLength, "name length may not be sufficient to pervent short string optimization");

int readFromFile(Friend* frnd)
{
    std::ifstream infile("friends.txt");
    int count = 0;

    std::cout << "Reading from the file.";
    if (infile.fail())
    {
        std::cout << "\nError opening file!";
    }
    else
    {
        while (count < MaxFriends)
        {

            String str{ Allocator<char>{ frnd + count } };
            str.reserve(MaxNameLength); // the allocator is used only once
            if (std::getline(infile, str))
            {
                ++count;
            }
            else
            {
                break;
            }
        }

        infile.close();
    }
    std::cout << "\nDone!\n";
    return count;
}


void displayData(Friend* P, int S)
{
    std::cout << "\nContent of the array:\n";
    for (int i = 0; i < S; i++)
    {
        std::cout << P[i] << std::endl;
    }
}

int main() {
    Friend friends[MaxFriends]{};
    auto const friendCount = readFromFile(friends);

    displayData(friends, friendCount);
}
fabian
  • 80,457
  • 12
  • 86
  • 114