Because there are so many possible solutions, let us just show some of them.
The basic difference is:
- If we know the dimensions of the array at compile time, so, if there are compile time constants, then we can still use a C-Style array, or better, a
std::array
.
- If we do not know the dimensions of the source data array, then we need a dynamic container that can grow, for example a
std::vector
.
In all cases, we can use the index operator [] with a standard for loop or a range based for loop with references. There is no difference.
Examples:
C-Style array with standard for loops and index based access
#include <iostream>
#include <fstream>
constexpr int NumberOfRows = 6;
constexpr int NumberOfColumns = 6;
int main() {
// Open the sourcefile
std::ifstream sourceFileStream{ "test.txt" };
// And check, if it could be opened
if (sourceFileStream) {
// Define 2D array to hold all data and initialize it with all 0
char array2D[NumberOfRows][NumberOfColumns]{};
// Read the rows and columns from the source file
for (int row = 0; row < NumberOfRows; ++row)
for (int col = 0; col < NumberOfColumns; ++col)
sourceFileStream >> array2D[row][col];
// Debug output
for (int row = 0; row < NumberOfRows; ++row) {
for (int col = 0; col < NumberOfColumns; ++col) std::cout << array2D[row][col] << ' ';
std::cout << '\n';
}
}
else std::cerr << "\nError: Could not open source file\n\n";
}
C-Style array with range based for loop and reference access
#include <iostream>
#include <fstream>
constexpr int NumberOfRows = 6;
constexpr int NumberOfColumns = 6;
int main() {
// Open the sourcefile
std::ifstream sourceFileStream{ "test.txt" };
// And check, if it could be opened
if (sourceFileStream) {
// Define 2D array to hold all data and initialize it with all 0
char array2D[NumberOfRows][NumberOfColumns]{};
// Read the rows and columns from the source file
for (auto& row : array2D)
for (auto& col : row)
sourceFileStream >> col;
// Debug output
for (const auto& row : array2D) {
for (const auto& col : row) std::cout << col << ' ';
std::cout << '\n';
}
}
else std::cerr << "\nError: Could not open source file\n\n";
}
C++ std::array
with range based for loop
#include <iostream>
#include <fstream>
#include <array>
constexpr int NumberOfRows = 6;
constexpr int NumberOfColumns = 6;
int main() {
// Open the sourcefile
std::ifstream sourceFileStream{ "test.txt" };
// And check, if it could be opened
if (sourceFileStream) {
// Define 2D array toholdall data and initialize it with all 0
std::array<std::array<char, NumberOfColumns>, NumberOfRows> array2D{};
// Read the rows and columns from the source file
for (auto& row : array2D)
for (auto& col : row)
sourceFileStream >> col;
// Debug output
for (const auto& row : array2D) {
for (const auto& col : row) std::cout << col << ' ';
std::cout << '\n';
}
}
else std::cerr << "\nError: Could not open source file\n\n";
}
Dynamic solution, with a std::vector
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
int main() {
// Open the sourcefile
std::ifstream sourceFileStream{ "test.txt" };
// And check, if it could be opened
if (sourceFileStream) {
// Define 2D array to hold all data and initialize it with all 0
std::vector<std::vector<char>> array2D{};
// Read the rows and columns from the source file
std::string line{};
while (std::getline(sourceFileStream, line)) {
// Add a new row to our matrix
array2D.push_back(std::vector<char>{});
// Read all column data
char c{};
for (std::istringstream iss(line); iss >> c; array2D.back().push_back(c))
;
}
// Debug output
for (const auto& row : array2D) {
for (const auto& col : row) std::cout << col << ' ';
std::cout << '\n';
}
}
else std::cerr << "\nError: Could not open source file\n\n";
}
More modern and compact C++ solution
#include <vector>
#include <string>
#include <iterator>
int main() {
// Open the sourcefile and check, if it could be opened
if (std::ifstream sourceFileStream{ "test.txt"}; sourceFileStream) {
// Define 2D array to hold all data and initialize it with all 0
std::vector<std::vector<char>> array2D{};
// Read the rows and columns from the source file
for (std::string line{}; std::getline(sourceFileStream, line);) {
std::istringstream iss(line);
array2D.push_back({ std::istream_iterator<char>(iss), {} });
}
// Debug output
for (const auto& row : array2D) {
for (const auto& col : row) std::cout << col << ' ';
std::cout << '\n';
}
}
else std::cerr << "\nError: Could not open source file\n\n";
}
And now, we imagine that we do not have any vector
or even a string
.
For that, we build a small class "DynamicArray" with some functions and an iterator. This can easily be extended.
And the result will be that in main, only one small statement, sourceFileStream >> dada;
will read all the data into a 2d array.
Please note. We are only using stream functions for stream io, nothing more.
Cool . . .
#include <iostream>
#include <sstream>
#include <fstream>
// The Dynamic Array has an initial capacity.
// If more elements will be added, there will be a reallocation with doublecapacity
constexpr unsigned int InitialCapacity{ 4 };
// Definition of simple dynamic array class
template <typename T>
class DynamicArray {
protected:
// Internal data ------------------------------------------------------------------------------
T* data{}; // Dynamic Storage for Data
unsigned int numberOfElements{}; // Number oe elements currently in the container
unsigned int capacity{ InitialCapacity }; // Current maximum capacity of the container
public:
// Construction and Destruction ---------------------------------------------------------------
DynamicArray() { data = new T[capacity]; } // Default constructor. Allocate new memory
DynamicArray(const DynamicArray& other) { // Copy constructor. Make a deep copy
capacity = numberOfElements = other.numberOfElements;
data = new T[capacity]; // Get memory, same size as other container
for (size_t k = 0; k < other.numberOfElements; ++k)
data[k] = other.data[k]; // Copy data
}
~DynamicArray() { delete[] data; } // Destructor: Release previously allocated memory
bool empty() { return numberOfElements == 0; }
void clear() { numberOfElements = 0; }; // Clear will not delete anything. Just set element count to 0
void push_back(const T& d) { // Add a new element at the end
if (numberOfElements >= capacity) { // Check, if capacity of this dynamic array is big enough
capacity *= 2; // Obviously not, we will double the capacity
T* temp = new T[capacity]; // Allocate new and more memory
for (unsigned int k = 0; k < numberOfElements; ++k)
temp[k] = data[k]; // Copy data from old memory to new memory
delete[] data; // Release old memory
data = temp; // And assign newly allocated memory to old pointer
}
data[numberOfElements++] = d; // And finally, stor the given fata at the end of the container
}
// Add iterator properties to class ---------------------------------------------------------------
// Local class for iterator
class iterator{
T* iter{}; // This will be the iterator
public: // Define alias names necessary for the iterator functionality
using iterator_category = std::input_iterator_tag;
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
explicit iterator(T* i) : iter(i) {}; // Default constructor for the iterator
T operator *() const { return *iter; } // Dereferencing
iterator& operator ++() { ++iter; return *this; } // Pre-Increment
bool operator != (const iterator& other) { return iter != other.iter; } // Comparison
};
// Begin and end function to initiliaze an iterator
iterator begin() const { return iterator(data); }
iterator end() const { return iterator (data + numberOfElements); }
// Operators for class------------------------ ---------------------------------------------------------------
T& operator[] (const size_t i) { return data[i]; } // Index operator, get data at given index. No boundary chek
DynamicArray& operator=(const DynamicArray& other) { // Assignment operator. Make a deep copy
if (this != &other) { // Prevent self-assignment
delete[] data; // Release any previosly existing memory
capacity = numberOfElements = other.numberOfElements;// Take over capacity and number of elements from other container
data = new int[capacity]; // Get new memory, depending on size of other
for (unsigned int k = 0; k < numberOfElements; ++k) // Copy other data
data[k] = other.data[k];
}
return *this;
}
// Extractor and Inserter ------------------------ ---------------------------------------------------------------
friend std::istream& operator >> (std::istream& is, DynamicArray& d) {
std::stringstream ss{};
for (char c{}; (is.get(c) and c != '\n'); ss << c); // Read one line until newline into a stringstream
for (T x{}; ss >> x; d.push_back(x)); // Now extract the data from there
return is;
}
friend std::ostream& operator << (std::ostream& os, const DynamicArray& d) {
for (unsigned int k = 0; k < d.numberOfElements; ++k) // Ultra simple output
os << d.data[k] << ' ';
return os;
}
};
// -----------------------------------------------------------------------------------------------------------
// Very simple 2d array. Derived from standard dynamic array and just defining differen input and output
template <typename T>
class Dynamic2dArray : public DynamicArray<DynamicArray<T>> {
public:
friend std::istream& operator >> (std::istream& is, Dynamic2dArray& d) {
for (DynamicArray<T> temp{}; is >> temp; d.push_back(temp), temp.clear());
return is;
}
friend std::ostream& operator << (std::ostream& os, const Dynamic2dArray& d) {
for (unsigned int k = 0; k < d.numberOfElements; ++k)
os << d.data[k] << '\n';
return os;
}
};
// -----------------------------------------------------------------------------------------------------------
int main() {
// Open the sourcefile and check, if it could be opened
if (std::ifstream sourceFileStream{ "test.txt" }; sourceFileStream) {
// Define 2D array to hold all data and initialize it with all 0
Dynamic2dArray<int> dada;
// Read complete matrix from file
sourceFileStream >> dada;
// Debug output. Show complete Matrix
std::cout << dada;
}
else std::cerr << "\n\nError. Could not open source file\n\n";
}
Basically, everything is the same, somehow . . .