I want to use a std::string for dynamic string handling. The data is append and append to a string, sometime I wan't to set the value for the character at index i. I don't know how many character will be added to string. Something like dynamic collection in .NET.
When I allocate a std::string in C++
std::string s;
and try to set element at index i for it:
s[0] = 'a';
It will through an error related to memory.
A stupid way is init it with exist data and replace them later:
std::string s = generate1000chars();
s[2] = 'c';
Is there a way to init a string that allow manipulate character at index i, like a char array?
You can use std::string::resize() to resize it and fill it in later:
std::string s;
s.resize(1000);
//later..
s[2] = 'c';
Maybe you can try a wrapper function like
std::string& SetChar(std::string& str, char ch, size_t index)
{
if (str.length() <= index)
{
str.resize(index + 1);
}
str[index] = ch;
return str;
}
So that the string can auto extend if needed.
(Modified to a better signature)
Related
I've been reading a book for self study (http://www.amazon.com/gp/product/0321992784) and I'm on chapter 17 doing the exercises. One of them I solved, but I'm not satisfied and would like some help. Thank you in advanced.
The Exercise: Write a program that reads characters from cin into an array that you allocate on the free store. Read indvidual characters until an exclamation mark(!) is entered. Do not use std::string. Do not worry about memory exhaustion.
What I did:
char* append(const char* str, char ch); // Add a character to the string and return a duplicate
char* loadCstr(); // Read characters from cin into an array of characters
int main()
{
char* str{ loadCstr() };
std::cout << str << '\n';
return 0;
}
I made 2 functions, 1 to create a new string with a size 1 larger than the old and add a character at the end.
char* append(const char* str, char ch)
/*
Create a new string with a size 1 greater than the old
insert old string into new
add character into new string
*/
{
char* newstr{ nullptr };
int i{ 0 };
if (str)
newstr = new char [ sizeof(str) + 2 ];
else
newstr = new char [ 2 ];
if(str)
while (str [ i ] != '\0')
newstr [ i ] = str [ i++ ]; // Put character into new string, then increment the index
newstr [ i++ ] = ch; // Add character and increment the index
newstr [ i ] = '\0'; // Trailing 0
return newstr;
}
This is the function for the exercise using the append function I created, It works, but from what I understand each time I call append, there is a memory leak because I create a new character array and didn't delete the old.
char* loadCstr()
/*
get a character from cin, append it to str until !
*/
{
char* str{ nullptr };
for (char ch; std::cin >> ch && ch != '!';)
str = append(str, ch);
return str;
}
I tried adding another pointer to hold the old array and delete it after making a new one, but after about 6 calls in this loop I get a runtime error that I think tells me I'm deleting something I shouldn't? which is where I got confused.
This is the old one that doesn't work beyond 6 characters:
char* loadCstr()
/*
get a character from cin, append it to str until !
*/
{
char* str{ nullptr };
for (char ch; std::cin >> ch && ch != '!';) {
char* temp{ append(str, ch) };
if (str)
delete str;
str = temp;
}
return str;
}
So I want to know how I can fix this function so there are no memory leaks. Thank you again. (Also please note, I do know these functions already exist and using std::string handles all the free store stuff for me, I just want to understand it and this is a learning exercise.)
You have to use standard C function std::strlen instead of the sizeof operator because in case of your function the sizeof operator returns the size of pointer instead of the length of the string.
Also you need to delete already allocated array.
The function can look the following way
char* append(const char* str, char ch)
/*
Create a new string with a size 1 greater than the old
insert old string into new
add character into new string
*/
{
size_t n = 0;
if ( str ) n = std::strlen( str );
char *newstr = new char[ n + 2 ];
for ( size_t i = 0; i < n; i++ ) newstr[i] = str[i];
delete [] str;
newstr[n] = ch;
newstr[n+1] = '\0';
return newstr;
}
And in the function loadCstr it can be called like
str = append( str, ch );
Also instead of the loop to copy the string you could use standard algorithm std::copy
Is the point to learn about memory management, or about how string operations work internally?
For the second (learning about string operations), you should use std::unique_ptr<char[]> which will automatically free the attached array when the pointer dies. You'll still need to calculate string length, copy between strings, append -- all the things you are doing now. But std::unique_ptr<char[]> will handle the deallocation.
For the first case, you're better off writing an RAII class (custom version of std::unique_ptr<T>) and learning how to free memory in a destructor, than scattering delete [] statements all over your code. Writing delete [] everywhere is actually a bad habit, learning it will move your ability to program C++ backwards.
What is the proper c++11 way to extract a set of characters out of a stringstream without using boost?
I want to do it without copying, if possible, because where this is used is in a critical data loop. It seems, though, std::string does not allow direct access to the data.
For example, the code below performs a substring copy out of a stringstream:
inline std::string left(std::stringstream ss, uint32_t count) {
char* buffer = new char[count];
ss.get(buffer, count);
std::string str(buffer); // Second copy performed here
delete buffer;
return str;
}
Should I even be using char *buffer according to c++11?
How do I get around making a second copy?
My understanding is that vectors initialize every character, so I want to avoid that.
Also, this needs to be passed into a function which accepts const char *, so now after this runs I am forced to do a .c_str(). Does this also make a copy?
It would be nice to be able to pass back a const char *, but that seems to go against the "proper" c++11 style.
To understand what I am trying to do, here is "effectively" what I want to use it for:
fprintf( stderr, "Data: [%s]...", left(ststream, 255) );
But the c++11 forces:
fprintf( stderr, "Data: [%s]...", left(str_data, 255).c_str() );
How many copies of that string am I making here?
How can I reduce it to only a single copy out of the stringstream?
You could use something like described in this link: How to create a std::string directly from a char* array without copying?
Basically, create a string, call the resize() method on the string with the size that is passed to your function and then pass the pointer to the first character of the string to the stringstring.get() method. You will end up with only one copy.
inline std::string left(std::stringstream& ss, uint32_t count) {
std::string str;
str.resize(count);
ss.get(&str[0], count);
return str;
}
My suggestion:
Create the std::string to be returned by giving it the size.
Read the characters one by one from the stringstream and set the values in the std::string.
Here's what the function looks like:
inline std::string left(std::stringstream ss, uint32_t count) {
std::string str(count+1, '\0');
for (uint32_t i = 0; i < count; ++i )
{
int c = ss.getc();
if ( c != EOF )
{
str[i] = c;
}
else
{
break;
}
}
return str;
}
R Sahu, this I like! Obvious now that I see it done. ;-)
I do have one mod though (as well as passed a shared_ptr of stream which is what I actually had in my version):
In your initializer, you are filling with nulls. You only need to fill with the last one, so I propose a tweak of this:
inline std::string left(std::shared_ptr<std::stringstream> ss, uint32_t count) {
std::string str;
str.reserve(count + 1);
uint32_t i;
for(i = 0; i < count; ++i) {
int c = ss->get();
if(c != EOF) {
str[i] = c;
} else {
break;
}
}
str[i] = '\0';
return str;
}
Now, only initialized with nulls on a single character.
Thanks R Sahu!
If the purpose of this function is solely for passing to fprintf or another C-style stream, then you could avoid allocation completely by doing the following:
void left(FILE *out, std::stringstream &in, size_t count)
{
in.seekg(0);
char ch;
while ( count-- && in.get(ch) )
fputc(out, static_cast<unsigned char>(ch));
}
Usage:
fprintf( stderr, "Data: [" );
left(stderr, stream, 255);
fprintf( stderr, "] ...\n");
Bear in mind that another seekg will be required if you try to use the stream reading functions on the stringstream later; and it would not surprise me if this is the same speed or slower than the options involving str().
It is possible to delete all characters of a string that is immediately followed by two vowels, without the aid of a char array, using instead exclusively the "string" library ?
For example:
priamo -> iamo
The algorithm should be:
Cycling the string making a for loop from 0 to string.length()-2 to prevent overflow
Compare pairs of characters with a char array containing all the vowels
Using "Erase" function in the string library to delete positions before the vowels
but I have no idea how to implement the second point without the help of an array of characters. Any suggestions?
I'd suggest using std::adjacent_find:
std::string s{"priamo"};
auto is_vowel = [](char c) -> bool {
static const char vowels[] = "aeiou";
return std::any_of(std::begin(vowels), std::prev(std::end(vowels)),
[c](char d) { return c == d; } );
};
auto it = std::adjacent_find(s.crbegin(), s.crend(),
[&](char c, char d) { return is_vowel(c) && is_vowel(d); }).base();
if (it != s.cbegin())
s.erase(s.cbegin(), std::prev(it, 2));
What they meant by saying without the aid of char array probably means that you must not do any kind of buffering. You can use const char vowels[]="aeiou"; of course.
Well, this is probably wrong but should give you the idea and a base for others to correct:
string str="priamo";
const char vowels[]="aeiou";
size_t pos=0;
size_t vowels_piled_up=0;
while((pos=str.find_first_of(&vowels[0], pos+vowels_piled_up))!=string::npos)
{
if((pos+1)+1 >= str.size())//break if it is on the last 2
break;
if((strchr(&vowels[0], str[pos+1]))!=NULL)
{
str.erase(vowels_piled_up, pos-1-vowels_piled_up);
pos=0;
vowels_piled_up+=2;
}
else
++pos;
}
I have an array of chars and I need to extract subsets of this array and store them in std::strings. I am trying to split the array into lines, based on finding the \n character. What is the best way to approach this?
int size = 4096;
char* buffer = new char[size];
// ...Array gets filled
std::string line;
// Find the chars up to the next newline, and store them in "line"
ProcessLine(line);
Probably need some kind of interface like this:
std::string line = GetSubstring(char* src, int begin, int end);
I'd create the std::string as the first step, as splitting the result will be far easier.
int size = 4096;
char* buffer = new char[size];
// ... Array gets filled
// make sure it's null-terminated
std::string lines(buffer);
// Tokenize on '\n' and process individually
std::istringstream split(lines);
for (std::string line; std::getline(split, line, '\n'); ) {
ProcessLine(line);
}
You can use the std::string(const char *s, size_t n) constructor to build a std::string from the substring of a C string. The pointer you pass in can be to the middle of the C string; it doesn't need to be to the very first character.
If you need more than that, please update your question to detail exactly where your stumbling block is.
I didn't realize you only wanted to process each line one at a time, but just in case you need all the lines at once, you can also do this:
std::vector<std::string> lines;
char *s = buffer;
char *head = s;
while (*s) {
if (*s == '\n') { // Line break found
*s = '\0'; // Change it to a null character
lines.push_back(head); // Add this line to our vector
head = ++s;
} else s++; //
}
lines.push_back(head); // Add the last line
std::vector<std::string>::iterator it;
for (it = lines.begin(); it != lines.end(); it++) {
// You can process each line here if you want
ProcessLine(*it);
}
// Or you can process all the lines in a separate function:
ProcessLines(lines);
// Cleanup
lines.erase(lines.begin(), lines.end());
I've modified the buffer in place, and the vector.push_back() method generates std::string objects from each of the resulting C substrings automatically.
your best bet (best meaning easiest) is using strtok and convert the tokens to std::string via the constructor. (just note that pure strtok is not reentrant, for that you need to use the non standard strtok_r).
void ProcessTextBlock(char* str)
{
std::vector<std::string> v;
char* tok = strtok(str,"\n");
while(tok != NULL)
{
ProcessLine(std::string(tok));
tok = strtok(tok,"\n");
}
}
You can turn a substring of char* to std::string with a std::string's constructor:
template< class InputIterator >
basic_string( InputIterator first, InputIterator last, const Allocator& alloc = Allocator() );
Just do something like:
char *cstr = "abcd";
std::string str(cstr + 1, cstr + 3);
In that case str would be "bc".
I am retrieving the environment variables in win32 using GetEnvironmentStrings(). It returns a char*.
I want to search this string(char pointer) for a specific environmental variable (yes I know I can use GetEnvironmentVariable() but I am doing it this way because I also want to print all the environment variables on the console aswell - I am just fiddling around).
So I thought I would convert the char* to an std::string & use find on it (I know I can also use a c_string find function but I am more concerned about trying to copy a char* into a std::string). But the following code seems to not copy all of the char* into the std::string (it makes me think there is a \0 character in the char* but its not actually the end).
char* a = GetEnvironmentStrings();
string b = string(a, sizeof(a));
printf( "%s", b.c_str() ); // prints =::=
Is there a way to copy a char* into a std::string (I know I can use strcpy() to copy a const char* into a string but not a char*).
You do not want to use sizeof() in this context- you can just pass the value into the constructor. char* trivially becomes const char* and you don't want to use strcpy or printf either.
That's for conventional C-strings- however GetEnvironmentStrings() returns a bit of a strange format and you will probably need to insert it manually.
const char* a = GetEnvironmentStrings();
int prev = 0;
std::vector<std::string> env_strings;
for(int i = 0; ; i++) {
if (a[i] == '\0') {
env_strings.push_back(std::string(a + prev, a + i));
prev = i;
if (a[i + 1] == '\0') {
break;
}
}
}
for(int i = 0; i < env_strings.size(); i++) {
std::cout << env_strings[i] << "\n";
}
sizeof(a) in what you have above will return the size of char*, i.e. a pointer (32 or 64bits usually). You were looking for function strlen there. And it's not actually required at all:
std::string b(a);
should be enough to get the first environment variable pair.
The result of GetEnvironmentStrings() points to memory containing all environment strings. Similar to the solution of Puppy it will be put into a vector of string, where each string contains just one environment variable ("key=value")
std::vector<std::string> env_strings;
LPTCH a = GetEnvironmentStrings();
As example we will have 2 environment variables defined:
"A=ABC"
"X=XYZ"
LPTCH a will be:
A=ABC\0X=XYZ\0\0
Each variable is '\0' - terminated and finally the complete environment string (a) will be terminated with an additional '\0'.
strlen will return the size to the first occurrence of the termination character '\0'. The last string will always be empty.
while ((std::size_t len = strlen(a)) > 0)
{
env_strings.push_back(std::string(a, len));
a += len + 1;
}
Multi-byte character
For multi-byte characters it will work as well:
LPTCH a = GetEnvironmentStrings();
std::vector<std::wstring> env_strings;
while ((std::size_t len = wcslen(a)) > 0)
{
env_strings.push_back(std::wstring(a, len));
a += len + 1;
}
FreeEnvironmentStrings(a);
Does the following causes any problems?
char* a = GetEnvironmentStrings();
string b;
b=a;
printf( "%s", b.c_str() );
When you say:
string b = string(a, sizeof(a));
you are getting the size of a, which is a pointer and is probably 4. So you will get the first 4 characters. I'm not sure what you are really trying to do, but you should be able just to say:
string b( a );
char* a = ...;
string str(a);
string b;
b = a;
I assume you mean the Windows API GetEnvironmentStrings function. So, test the result against nullptr and perform simple assignment:
char* env = ::GetEnvironmentStrings();
if (0 != env)
{
std::string senv = env;
// use senv to find variables
}
else
{
// report problem or ignore
}