C++ split string with \0 into list - c++

I want to List the logical drives with:
const size_t BUFSIZE = 100;
char buffer[ BUFSIZE ];
memset(buffer,0,BUFSIZE);
//get available drives
DWORD drives = GetLogicalDriveStringsA(BUFSIZE,static_cast<LPSTR>(buffer));
The buffer then contains: 'C',':','\','0'
Now I want to have a List filled with "C:\","D:\" and so on. Therefore I tried something like this:
std::string tmp(buffer,BUFSIZE);//to split this string then
QStringList drivesList = QString::fromStdString(tmp).split("\0");
But it didn't worked. Is it even possible to split with the delimiter \0? Or is there a way to split by length?

The problem with String::fromStdString(tmp) is that it will create a string only from the first zero-terminated "entry" in your buffer, because that's how standard strings works. It is certainly possible, but you have to do it yourself manually instead.
You can do it by finding the first zero, extract the substring, then in a loop until you find two consecutive zeroes, do just the same.
Pseudoish-code:
current_position = buffer;
while (*current_position != '\0')
{
end_position = current_position + strlen(current_position);
// The text between current_position and end_position is the sub-string
// Extract it and add to list
current_position = end_position + 1;
}

Related

Use C++, How to change the above multiple \n into only one \n?

CString str = _T("111\n\n\n222");
How to change the above multiple \n into only one \n?
Cannot use Replace directly, because the number of \n is not fixed
while (str.Replace("\n\n", "\n") > 0)
;
You can use CString::GetBuffer to obtain a buffer that you can modify. The corresponding CString::ReleaseBuffer allows you to specify a new length for the string.
If you want to remove consecutive characters, you can do this easily by simply walking through the string and rewriting its characters. Any time you see a character that you wish to remove, simply don't write it and don't update the end-position of the string.
Here's a general-purpose function to remove some number of consecutive characters from a CString:
void LimitConsecutiveCharacters(CString& str, TCHAR ch, int maxConsecutive = 1)
{
LPTSTR *begin = str.GetBuffer(0);
LPTSTR *end = begin;
int consecutive = 0;
for (LPTSTR *pos = begin; *pos != _T('\0'); ++pos)
{
if (*pos == ch)
{
if (consecutive >= maxConsecutive)
continue;
++consecutive;
}
else
{
consecutive = 0;
}
*end++ = *pos;
}
int newLength = end - begin;
str.ReleaseBuffer(newLength);
}
As you can see above, it keeps a count of how many consecutive values it has seen for the target character. If the maximum number of consecutive characters is reached, then it simply moves to the next loop iteration. Any time it sees some other character, the "consecutive" count resets.
The end tracks the position that is being written to, which might even be the same position you're reading from, if you've not removed any characters. At the end, some simple pointer arithmetic calculates the new string length and calls CString::ReleaseBuffer.
An example invocation would be:
CString str = _T("111\n\n\n222");
LimitConsecutiveCharacters(str, _T('\n'));
You can convert your CString into a std::wstring, use regex_replace and then convert back to CString.
The patterns for the regular expression would be something like:
find what: L"\n+"
replace by: L"\n"

How to pad char array with empty spaces on left and right hand side of the text

I am fairly new with C++ so for some people the answer to the quesiton I have might seem quite obvious.
What I want to achieve is to create a method which would return the given char array fill with empty spaces before and after it in order to meet certain length. So the effect at the end would be as if the given char array would be in the middle of the other, bigger char array.
Lets say we have a char array with HelloWorld!
I want the method to return me a new char array with the length specified beforehand and the given char array "positioned" in the middle of returning char array.
char ch[] = "HelloWorld";
char ret[20]; // lets say we want to have the resulting char array the length of 20 chars
char ret[20] = " HelloWorld "; // this is the result to be expected as return of the method
In case of odd number of given char array would like for it to be in offset of one space on the left of the middle.
I would also like to avoid any memory consuming strings or any other methods that are not in standard library - keep it as plain as possible.
What would be the best way to tackle this issue? Thanks!
There are mainly two ways of doing this: either using char literals (aka char arrays), like you would do in C language or using built-in std::string type (or similar types), which is the usual choice if you're programming in C++, despite there are exceptions.
I'm providing you one example for each.
First, using arrays, you will need to include cstring header to use built-in string literals manipulation functions. Keep in mind that, as part of the length of it, a char array always terminates with the null terminator character '\0' (ASCII code is 0), therefore for a DIM-dimensioned string you will be able to store your characters in DIM - 1 positions. Here is the code with comments.
constexpr int DIM = 20;
char ch[] = "HelloWorld";
char ret[DIM] = "";
auto len_ch = std::strlen(ch); // length of ch without '\0' char
auto n_blanks = DIM - len_ch - 1; // number of blank chars needed
auto half_n_blanks = n_blanks / 2; // half of that
// fill in from begin and end of ret with blanks
for (auto i = 0u; i < half_n_blanks; i++)
ret[i] = ret[DIM - i - 2] = ' ';
// copy ch content into ret starting from half_n_blanks position
memcpy_s(
ret + half_n_blanks, // start inserting from here
DIM - half_n_blanks, // length from our position to the end of ret
ch, // string we need to copy
len_ch); // length of ch
// if odd, after ch copied chars
// there will be a space left to insert a blank in
if (n_blanks % 2 == 1)
*(ret + half_n_blanks + len_ch) = ' ';
I chose first to insert blank spaces both to the begin and to the end of the string and then to copy the content of ch.
The second approach is far easier (to code and to understand). The max characters size a std::string (defined in header string) can contain is std::npos, which is the max number you can have for the type std::size_t (usually a typedef for unsigned int). Basically, you don't have to worry about a std::string max length.
std::string ch = "HelloWorld", ret;
auto ret_max_length = 20;
auto n_blanks = ret_max_length - ch.size();
// insert blanks at the beginning
ret.append(n_blanks / 2, ' ');
// append ch
ret += ch;
// insert blanks after ch
// if odd, simply add 1 to the number of blanks
ret.append(n_blanks / 2 + n_blanks % 2, ' ');
The approach I took here is different, as you can see.
Notice that, because of '\0', the result of these two methods are NOT the same. If you want to obtain the same behaviour, you may either add 1 to DIM or subtract 1 from ret_max_length.
Assuming that we know the size, s, of the array, ret and knowing that the last character of any char array is '\0', we find the length, l, of the input char array, ch.
int l = 0;
int i;
for(i=0; ch[i]!='\0'; i++){
l++;
}
Then we compute how many spaces we need on either side. If total_space is even, then there are equal spaces on either side. Otherwise, we can choose which side will have the extra space, in this case, the left side.
int total_spaces = size-l-1; // subtract by 1 to adjust for '\0' character
int spaces_right = 0, spaces_left = 0;
if((total_spaces%2) == 0){
spaces_left = total_spaces/2;
spaces_right = total_spaces/2;
}
else{
spaces_left = total_spaces/2;
spaces_right = (total_spaces/2)+1;
}
Then first add the left_spaces, then the input array, ch, and then the right_spaces to ret.
i=0;
while(spaces_left > 0){
ret[i] = ' ';
spaces_left--;
i++;
} // add spaces
ret[i] = '\0';
strcat(ret, ch); // concatenate ch to ret
while(spaces_right){
ret[i] = ' ';
spaces_right--;
i++;
}
ret[i] = '\0';
Make sure to include <cstring> to use strcat().

Parsing a character array with several null terminated characters into different strings - C++

I asked this question before but with less information than I have now.
What I essentially have is a data block of type char. That block contains filenames that I need to format and put into a vector. I initially thought the formation of this char block had three spaces between each filename. Now, I realize they are '/0' null terminated characters. So the solution that was provided was fantastic for the example I gave when I thought that there were spaces rather than null chars.
Here is what the structure looks like. Also, I should point out I DO have the size of the character data block.
filename1.bmp/0/0/0brick.bmp/0/0/0toothpaste.gif/0/0/0
The way the best solution did it was this:
// The stringstream will do the dirty work and deal with the spaces.
std::istringstream iss(s);
// Your filenames will be put into this vector.
std::vector<std::string> v;
// Copy every filename to a vector.
std::copy(std::istream_iterator<std::string>(iss),
std::istream_iterator<std::string>(),
std::back_inserter(v));
// They are now in the vector, print them or do whatever you want with them!
for(int i = 0; i < v.size(); ++i)
std::cout << v[i] << "\n";
This works fantastic for my original question but not with the fact they are null chars instead of spaces. Is there any way to make the above example work. I tried replacing null chars in the array with spaces but that didn't work.
Any ideas on the best way to format this char block into a vector of strings?
Thanks.
If you know your filenames don't have embedded "\0" characters in them, then this should work. (untested)
const char * buffer = "filename1.bmp/0/0/0brick.bmp/0/0/0toothpaste.gif/0/0/0";
int size_of_buffer = 1234; //Or whatever the real value is
const char * end_of_buffer = buffer + size_of_buffer;
std::vector<std::string> v;
while( buffer!=end_of_buffer)
{
v.push_back( std::string(buffer) );
buffer = buffer+filename1.size()+3;
}
If they do have embedded null characters in the filename you'll need to be a little cleverer.
Something like this should work. (untested)
char * start_of_filename = buffer;
while( start_of_filename != end_of_buffer )
{
//Create a cursor at the current spot and move cursor until we hit three nulls
char * scan_cursor = buffer;
while( scan_cursor[0]!='\0' && scan_cursor[1]!='\0' && scan_cursor[2]!='\0' )
{
++scan_cursor;
}
//From our start to the cursor is our word.
v.push_back( std::string(start_of_filename,scan_cursor) );
//Move on to the next word
start_of_filename = scan_cursor+3;
}
If spaces would be a suitable separator, you could just replace the null characters by spaces:
std::replace(std::begin(), std::end(), 0, ' ');
... and go from there. However, I'd suspect that you really need to use the null characters as separators as file names typically can include spaces. In this case, you could either use std::getline() with '\0' as the end of line or use the find() and substr() members of the string itself. The latter would look something like this:
std::vector<std::string> v;
std::string const null(1, '\0');
for (std::string::size_type pos(0); (pos = s.find_first_not_of(null, pos)) != s.npos; )
{
end = s.find(null, pos);
v.push_back(s.substr(0, end - pos));
pos = end;
}

Double null-terminated string

I need to format a string to be double null-terminated string in order to use SHFileOperation.
Interesting part is i found one of the following working, but not both:
// Example 1
CString szDir(_T("D:\\Test"));
szDir = szDir + _T('\0') + _T('\0');
// Example 2
CString szDir(_T("D:\\Test"));
szDir = szDir + _T("\0\0");
//Delete folder
SHFILEOPSTRUCT fileop;
fileop.hwnd = NULL; // no status display
fileop.wFunc = FO_DELETE; // delete operation
fileop.pFrom = szDir; // source file name as double null terminated string
fileop.pTo = NULL; // no destination needed
fileop.fFlags = FOF_NOCONFIRMATION|FOF_SILENT; // do not prompt the user
fileop.fAnyOperationsAborted = FALSE;
fileop.lpszProgressTitle = NULL;
fileop.hNameMappings = NULL;
int ret = SHFileOperation(&fileop);
Does anyone has idea on this?
Is there other way to append double-terminated string?
The CString class itself has no problem with a string containing a null character. The problem comes with putting null characters into the string in the first place. The first example works because it is appending a single character, not a string - it accepts the character as is without checking to see if it's null. The second example tries appending a typical C string, which by definition ends at the first null character - you're effectively appending an empty string.
You cannot use CString for this purpose. You will need to use your own char[] buffer:
char buf[100]; // or large enough
strcpy(buf, "string to use");
memcpy(buf + strlen(buf), "\0\0", 2);
Although you could do this by only copying one more NUL byte after the existing NUL terminator, I would prefer to copy two so that the source code more accurately reflects the intent of the programmer.

Splitting the string at Enter key

I'm getting the text from editbox and I'd want to get each name separated by enter key like the character string below with NULL characters.
char *names = "Name1\0Name2\0Name3\0Name4\0Name5";
while(*names)
{
names += strlen(names)+1;
}
how would you do the same for enter key (i.e separated by /r/n) ? can you do that without using the std::string class?
Use strstr:
while (*names)
{
char *next = strstr(names, "\r\n");
if (next != NULL)
{
// If you want to use the key, the length is
size_t len = next - names;
// do something with a string here. The string is not 0 terminated
// so you need to use only 'len' bytes. How you do this depends on
// your need.
// Have names point to the first character after the \r\n
names = next + 2;
}
else
{
// do something with name here. This version is 0 terminated
// so it's easy to use
// Have names point to the terminating \0
names += strlen(names);
}
}
One thing to note is that this code also fixes an error in your code. Your string is terminated by a single \0, so the last iteration will have names point to the first byte after your string. To fix your existing code, you need to change the value of names to:
// The algorithm needs two \0's at the end (one so the final
// strlen will work and the second so that the while loop will
// terminate). Add one explicitly and allow the compiler to
// add a second one.
char *names = "Name1\0Name2\0Name3\0Name4\0Name5\0";
If you want to start and finish with a C string, it's not really C++.
This is a job for strsep.
#include <stdlib.h>
void split_string( char *multiline ) {
do strsep( &multiline, "\r\n" );
while ( multiline );
}
Each call to strsep zeroes out either a \r or a \n. Since only the string \r\n appears, every other call will return an argument. If you wanted, you could build an array of char*s by recording multiline as it advances or the return value of strsep.
void split_string( char *multiline ) {
vector< char* > args;
do {
char *arg = strsep( &multiline, "\r\n" );
if ( * arg != 0 ) {
args.push_back( arg );
}
} while ( multiline );
}
This second example is at least not specific to Windows.
Here's a pure pointer solution
char * names = "name1\r\nName2\r\nName3";
char * plast = names;
while (*names)
{
if (names[0] == '\r' && names[1] == '\n')
{
if (plast < names)
{
size_t cch = names - plast;
// plast points to a name of length cch, not null terminated.
// to extract the name use
// strncpy(pout, plast, cch);
// pout[cch] = '\0';
}
plast = names+2;
}
++names;
}
// plast now points to the start of the last name, it is null terminated.
// extract it with
// strcpy(pout, plast);
Since this has the C++ tag, the easiest would probably using the C++ standard library, especially strings and string streams. Why do you want to avoid std::string when you're doing C++?
std::istringstream iss(names);
std::string line;
while( std::getline(iss,line) )
process(line); // do process(line.c_str()) instead if you need to