I have set up a dynamic 2d array, ... if I were to add the 4 and 5,
would strtok or something token related work?
Short answer "Yes". Before looking at an answer, let's look at what you are doing.
While there is nothing wrong with using basic types and dynamic allocation with new
, you are missing all of the benefit of automatic memory management provided by C++ container types such as vector
and string
(as well as missing out on all the nice member functions they include) It would be far easier to declare your storage for your strings as std::vector<std::string>
which would give you the ability to fill each sting and simply .push_back(string)
to add it to your vector
of string.
Presuming that this is an educational task and that you really do want to use char**
to allow allocating a given number of pointers (determined by the number input by the user) and you then want to allocate storage to hold each string entered, you can use an approach similar to what you have tried. However, if you are dynamically allocating storage for each string, then it makes little sense to allocate a fixed number of characters (e.g. cppStrings[i] = new char[100];
) If that were the case, you may as well just declare a 2D array of char
.
Instead, you want to read each string (c_string) entered by the user and then determine the length of the string entered. (e.g. using strlen()
) and then allocate storage for length + 1
characters (the +1
to provide storage for the nul-terminating character that terminates each c_string). Then it is just a matter of copying the string read to the new block of memory allocated and assigning the starting address for that block of memory to you next available pointer in turn.
Understand, in approaching your allocation and storage in this way, you are effectively writing your code in C, with the exception of using iostream
for input/output and new/delete
instead of malloc/free
. Basically how C++ was used 30 years ago. As mentioned before, it's not wrong, it just loses the benefit of the past 30 years of progress in C++. Ironically, unless you create a stringstream
from your buffer and tokenize using getline
with a delimiter, then you will be using the C function strtok
to split your string on commas. Since you have included <cstring>
you may as well make use of strtok
.
The approach is not far from what you have attempted. If you have determined that an array of 100 characters is sufficient for your anticipated input, then declare a fixed buffer of 100-chars to serve as your temporary read buffer to take input from the user. This allows you to take input, determine the length of the input in your buffer before allocating and copying the input to your dynamically allocated storage. With this approach you are able to size your allocated memory to exactly fit the user input.
std::strtok modifies the original string during tokenization, so make a copy of the string you will tokenize if you need to preserve the original. Also, since you have a fixed buffer available from the read of input, you can simply use that buffer to tokenize from.
Note, that there is actually no need to storage the original strings read from the user in dynamically allocated storage, you can simply read the input into the fixed buffer and tokenize from there. However, since you have started with the dynamic storage of the input, we will use that approach in an example and simply copy back to our fixed buffer to tokenize.
What you do with the tokens is up to you. You mention concatenation. If that is your goal, you can simply use a second fixed buffer of your maximum length to concatenate all c_strings with strcat
(not you must either strcpy
the first string or make your second buffer the empty-string before calling strcat
as it requires a nul-terminated buffer to concatenate to).
Putting all the pieces together you could do something like the following:
#include <iostream>
#include <iomanip>
#include <cstring>
#define MAXS 100 /* if you need constants, #define one (or more) */
#define DELIM ",\n"
using namespace std;
int main (void) {
int n = 0,
nstr = 0; /* counter for c_strings read/allocated */
char **strings = nullptr, /* pointer to pointer to char */
buf[MAXS]; /* temporary buffer to hold c_string */
cout << "enter max number of strings: ";
if (!(cin >> n)) { /* validate every user input */
cerr << "error: invalid entry\n";
return 1;
}
cin.getline (buf, MAXS); /* read/discard trailing '\n' */
strings = new char*[n]; /* allocate n pointers to char */
/* protect pointers limit / validate read of line */
while (nstr < n && cin.getline (buf, MAXS)) {
size_t len = strlen (buf); /* get length of string */
strings[nstr] = new char[len+1]; /* allocate +1 for '\0' */
strcpy (strings[nstr++], buf); /* copy buf, increment nstr */
}
for (int i = 0; i < nstr; i++) { /* ouput strings / tokenize */
char *p = buf; /* ptr to buf, tokenize copy
* (strtok modifies string) */
strcpy (buf, strings[i]); /* copy string[i] to buf p */
cout << "\nstring[" << setw(2) << i << "]: " << p << '\n';
/* example tokenizing string in p with strtok */
for (p = strtok (p, DELIM); p; p = strtok (NULL, DELIM))
cout << " " << p << '\n';
}
for (int i = 0; i < nstr; i++) /* free all allocated memory */
delete[] strings[i]; /* free allocated c_strings */
delete[] strings; /* free pointers */
}
(note: don't forget to free the memory you allocate with new
by calling delete
when you are done with it)
Example Use/Output
$ ./bin/newptr2array2
enter max number of strings: 2
shipment1,20180208,4
shipment2,20180319,5
string[ 0]: shipment1,20180208,4
shipment1
20180208
4
string[ 1]: shipment2,20180319,5
shipment2
20180319
5
Memory Use/Error Check
In any code you write that dynamically allocates memory, you have 2 responsibilities regarding any block of memory allocated: (1) always preserve a pointer to the starting address for the block of memory so, (2) it can be freed when it is no longer needed.
It is imperative that you use a memory error checking program to insure you do not attempt to access memory or write beyond/outside the bounds of your allocated block, attempt to read or base a conditional jump on an uninitialized value, and finally, to confirm that you free all the memory you have allocated.
For Linux valgrind
is the normal choice. There are similar memory checkers for every platform. They are all simple to use, just run your program through it.
$ valgrind ./bin/newptr2array2
==4344== Memcheck, a memory error detector
==4344== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==4344== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==4344== Command: ./bin/newptr2array2
==4344==
enter max number of strings: 2
shipment1,20180208,4
shipment2,20180319,5
string[ 0]: shipment1,20180208,4
shipment1
20180208
4
string[ 1]: shipment2,20180319,5
shipment2
20180319
5
==4344==
==4344== HEAP SUMMARY:
==4344== in use at exit: 0 bytes in 0 blocks
==4344== total heap usage: 4 allocs, 4 frees, 72,762 bytes allocated
==4344==
==4344== All heap blocks were freed -- no leaks are possible
==4344==
==4344== For counts of detected and suppressed errors, rerun with: -v
==4344== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Always confirm that you have freed all memory you have allocated and that there are no memory errors.
(This is where the automatic memory management of vector
and the other containers make like a lot easier. They handle freeing the memory when the final reference to it goes out of scope freeing you from that responsibility.)
Look things over, there is good educational value in understanding how to properly use new/delete
as there is still a substantial codebase from the past few decades that makes use of that approach. Going forward, you will want to use the container classes that C++ provides. If you have any further questions, just drop a comment and I'm happy to help further.