CString::find... iterative use issue? - c++

I'm using a CString to search a text block... here's my code:
// locate file name in dir listing
in = *buf;
i = in.Find("DOWNLD .DAT ");// find start of name, two spaces (0x20) as delim
// size of search text here is 14
if (i == -1) return 0;
j = in.Find(' ',i);// now find next space char *after* file size...
// why don't I have to add to i here? There are spaces in my search string.
if (j == -1) return 0;
fileSize = in.Mid((i+14),j-i);// extract file size string, note indexing past found string
return atoi(fileSize.GetBuffer());
Here's what MSDN has to say about the return value of find:
" Return Value
The zero-based index of the first character in this CString object that matches the requested substring or characters; -1 if the substring or character is not found."
Now the way I read this, I have to index past the string I found before doing another find... but the way it actually works, I use the 'i' returned before as the start position for a new search. I'm using this in other places in my program, and I definitely have to index past it (when using ::mid(), for instance)... I'd like to know why this is happening, if by design or bug. The original string can be large; I've seen it near 300chars... is this the problem?

Your second Find call finds the space after "DOWNLD", not the one after ".DAT ". You want to increment i before the second Find call, so that it refers to the first character past the string your first call searched for.

So I didn't find a bug in CString... my code was in error. Here's the code changed that works:
j = in.Find(' ',i+14);// index past searched string
if (j == -1) return 0;
fileSize = in.Mid((i+14),j-i-14);// note -14 added
return atoi(fileSize.GetBuffer());
It was the missing -14 in the mid that was confusing me... the resulting string was 14 past where it should have been, and was missing the portion of interest. Why my original fix worked? Just a coincidence I guess.
Thanks for helping!

Related

seekg() not working as expected

I have a small program, that is meant to copy a small phrase from a file, but it appears that I am either misinformed as to how seekg() works, or there is a problem in my code preventing the function from working as expected.
The text file contains:
//Intro
previouslyNoted=false
The code is meant to copy the word "false" into a string
std::fstream stats("text.txt", std::ios::out | std::ios::in);
//String that will hold the contents of the file
std::string statsStr = "";
//Integer to hold the index of the phrase we want to extract
int index = 0;
//COPY CONTENTS OF FILE TO STRING
while (!stats.eof())
{
static std::string tempString;
stats >> tempString;
statsStr += tempString + " ";
}
//FIND AND COPY PHRASE
index = statsStr.find("previouslyNoted="); //index is equal to 8
//Place the get pointer where "false" is expected to be
stats.seekg(index + strlen("previouslyNoted=")); //get pointer is placed at 24th index
//Copy phrase
stats >> previouslyNotedStr;
//Output phrase
std::cout << previouslyNotedStr << std::endl;
But for whatever reason, the program outputs:
=false
What I expected to happen:
I believe that I placed the get pointer at the 24th index of the file, which is where the phrase "false" begins. Then the program would've inputted from that index onward until a space character would have been met, or the end of the file would have been met.
What actually happened:
For whatever reason, the get pointer started an index before expected. And I'm not sure as to why. An explanation as to what is going wrong/what I'm doing wrong would be much appreciated.
Also, I do understand that I could simply make previouslyNotedStr a substring of statsStr, starting from where I wish, and I've already tried that with success. I'm really just experimenting here.
The VisualC++ tag means you are on windows. On Windows the end of line takes two characters (\r\n). When you read the file in a string at a time, this end-of-line sequence is treated as a delimiter and you replace it with a single space character.
Therefore after you read the file you statsStr does not match the contents of the file. Every where there is a new line in the file you have replaced two characters with one. Hence when you use seekg to position yourself in the file based on numbers you got from the statsStr string, you end up in the wrong place.
Even if you get the new line handling correct, you will still encounter problems if the file contains two or more consecutive white space characters, because these will be collapsed into a single space character by your read loop.
You are reading the file word by word. There are better methods:
while (getline(stats, statsSTr)
{
// An entire line is read into statsStr.
std::string::size_type posn = statsStr.find("previouslyNoted=");
// ...
}
By reading entire text lines into a string, there is no need to reposition the file.
Also, there is a white-space issue when reading by word. This will affect where you think the text is in the file. For example, white space is skipped, and there is no telling how many spaces, newlines or tabs were skipped.
By the way, don't even think about replacing the text in the same file. Replacement of text only works if the replacement text has the same length as the original text in the file. Write to a new file instead.
Edit 1:
A better method is to declare your key strings as array. This helps with positioning pointers within a string:
static const char key_text[] = "previouslyNoted=";
while (getline(stats, statsStr))
{
std::string::size_type key_position = statsStr.find(key_text);
std::string::size_type value_position = key_position + sizeof(key_text) - 1; // for the nul terminator.
// value_position points to the character after the '='.
// ...
}
You may want to save programming type by making your data file conform to an existing format, such as INI or XML, and using appropriate libraries to parse them.

Check if string contains other string elements

I am trying to check if string contains elements from different string in specific order.
For example:
large string: thisisstring
small string: hssg
it should return true.
I only figured out how to check if string contains whole other string but not parts.
This is the code that I wrote for checking for now:
if ([largestring rangeOfString:smallstring].location != NSNotFound) {
printf("contains");
}
If there are no more characters to search for from the small string, return true.
Starting from the position after the most recently found character in the large string, do a linear search for the first character from the small string that has not yet been searched for.
If the character was not found, return false.
Start back at 1.
There's no easy way to do this, at least, no built in way that I know of. You would have to iterate through each letter of your small string and find the first letter that matches your large string.
Each time you find a matching letter, you loop to the next smallstring letter, but instead only begin searching at the index after you found the previous letter.
EDIT:
some pseudo code, untested, may have syntax errors:
int foundChar = 0;
for (int l = 0; l < strlen(smallstring); l++)
{
bool found = false;
for (; foundChar < strlen(largestring); foundChar++)
{
if (smallstring[l] == largestring[foundChar])
{
// We break here because we found a matching letter.
// Notice that foundChar is still in scope so we preserve
// its value for the next check.
found = true;
foundChar++; // Increment so the next search starts with the next letter.
break;
}
}
// If we get down here, that means we've searched all of the letters
// and found no match, we can result with a failure to find the match.
if (found == false)
{
return false;
}
}
// If we get here, it means every loop resulted in a valid match.
return true;

C : Using substr to parse a text file

I just need a little help with file parsing. We have to parse a file that has 6 string entries per row in the format:
"string1", "string2", "string3", "string4", "string5", "string6"
My instructor recently gave us a little piece of code as a "hint," and I'm supposed to use it. Unfortunately, I can't figure out how to get it to work. Here's my file parsing function.
void parseData(ifstream &myFile, Book bookPtr[])
{
string bookInfo;
int start, end;
string bookData[6];
getline(myFile, bookInfo);
start = -2;
myFile.open("Book List.txt");
for (int j = 0; j < 6; j++)
{
start += 3;
end = bookInfo.find('"', start);
bookData[j] = bookInfo.substr(start, end-start);
start = end;
}
}
So I'm trying to read the 6 strings into an array of strings. Can someone please help walk me through the process?
start = -2;
for (int j = 0; j < 6; j++)
{
start += 3;
end = bookInfo.find('"', start);
bookData[j] = bookInfo.substr(start, end-start);
start = end;
}
So ", " is four characters. The leading closing quote is 3 characters behind the opening closing quote.
At entry to the loop start is pointing to the last closing quote. (On first entry to loop it is faked as -2 to be pointing to the closing quote of the imaginary "-1th" element.)
So we advance from the last closing quote to the following opening quote:
start += 3;
Then we use std::string::find to find the closing quote:
end = bookInfo.find('"', start);
The offset tells it to ignore all characters up to and including that position.
We then have the two quote positions, start..end, so we use substr to extract the string:
bookData[j] = bookInfo.substr(start, end-start);
And we then update start for the next loop to be the last closing quote:
start = end
Please, for your own sake, create a minimal example. This starts with a string like the line you gave as example and ends with the different parts in an array. Leave the loading from a file out for now, getline() seems to work for you, or? Then, do not declare every variable you might want to use at the beginning of a function. This is not ancient C, where you simply had to do that or introduce additional {} blocks. There is another thing odd, and that is the Book bookPtr[]. This is indeed just a Book* bookPtr, i.e. you are not passing an array to a function but just a pointer. Don't fall for this misleading syntax, it's a lie! Anyway, you don't seem to be using that pointer to the object(s) of the unknown type anyway.
Concerning the splitting of a line into strings, one approach is to locate pairs of double quotes. Everything in between is one of the strings, everything without is irrelevant. The string class has a find() function which optionally takes a starting position. Starting position is always one behind the previously found position.
Your code above seems to assume that there is exactly one double quote, a comma, a space and another double quote that separates two strings. This isn't 100% clear, I would also be prepared for handling multiple spaces or no space at all. Also, is the comma guaranteed? Are the double quotes guaranteed? Anyway, keep it simple. Unless you get a better spec on the input, just assume that only the parts between the quotes is what differs.
Then, what exactly works and what doesn't? You need to ask more specific questions and give more detailed information. The code above doesn't look broken per se, although there are a few things a bit off. For example, you don't typically pass ifstreams to a function, but use the istream baseclass. In your case, you read a line from that file and then open another file using the same fstream object, which doesn't make sense to me, since you don't use it after that. If you only needed that stream locally, you would create and open it there (handling errors of course!) and pass in the filename as parameter only.

string.find() doesn't return -1

The code below is simple. As I know, if string::find() didn't find matches it returns -1. But for some reasons the code below doesn't work. Everytime I run this code I get endless loop. Thank you for help!
#include <string>
#include <iostream>
using namespace std;
int main()
{
string text;
text = "asdasd ijk asdasd";
string toReplace = "ijk";
cout<<text<<endl;
int counter = 0;
while ( text.find(toReplace) != -1)
counter++;
cout<<counter<<endl;
system("pause");
}
Aside from the other answers which are completely correct, I just wanted to add that your while loop would have produced an endless loop anyway. For example:
while(text.find(toReplace) != std::string::npos)
counter++;
will be an endless loop because it will keep trying to find the toReplace string in text and it will always find it (that's because find starts from the beginning of the string each time). This is probably not what you intended.
std::string::find returns std::string::npos if the searched substring is not found, not -1. The exact value of npos is implementation-defined, so use npos, as in
while ( text.find(toReplace) != std::string::npos)
Come to think of it, find couldn't return -1 even if it wanted to because the return type of find is specified to be std::size_t which is an unsigned type.
Additionally, find will always search for the first occurrence of the substring, no matter how many times you call it. If you want to iterate through all the occurrences you should use the overload of find which takes a second parameter - the position from which to start searching.
Whoever told you this or wherever you read it, it lied to you.
If std::string::find fails, it returns std::string::npos, which is not -1.
You should check the documentation about such things, when you're not sure.
So, your while will be something like:
while ( std::string::npos != text.find(toReplace) )
Regarding your comment:
UPDATE: I tried to use while ( text.find(toReplace) != string::npos ) but I still get endless loop :( – user2167403 10 secs ago
You should really learn to read the documentation. Use a variable to store the last result of std::string::find (different from std::string::npos) and use std::string::find's second parameter - pos ( by passing value - last_match_position + 1).
Omitting the second parameter, std::string::find always starts from the beginning of the string, which causes the endless loop.
In the code snippet you provided text variable contains substring "ijk" which is stored in the toReplace variable. As long as in while cycle neither text or toReplace variables are changed, find method will always return not a -1 value which is the condition for while cycle to continue.
As already metioned in other comments you should check not for -1 but for std::string::npos.
It does help to read the manual page (string::npos is the answer).
See http://www.cplusplus.com/reference/string/string/find/

How do I return the end of a directory

I have a function that is supposed to find the last bit of a directory in a string. eg:
"C:\Lolcats\pie\ambulance\" should return "ambulance". However it returns some strange characters ive never seen, like the male arrow-point symbol and some other weird stuff.
string App::getlastName(string cakes){
//finds the name of the last folder in a directory
string name;
string temp;//popback all of temp into name to invert it
cakes.pop_back();
char i = cakes[cakes.length()-1];
while (i != '\\'){
temp.push_back(cakes[i]);
cakes.pop_back();
i = cakes[cakes.length()-1];
} //-1?
for (int j = 0; j<temp.length(); ++j){
name.push_back(temp.back());
temp.pop_back();
}
return name;
}
This is probably one of the worst functions i've ever written, but I can't think of how else to wrangle the end off :( Can someone help me please? :D
Note that the function doesnt need to find the name of a file, it'll just be folders.
Two steps:
if it ends with a backslash character remove it:
if (!cakes.empty() && '\\' == *(cakes.end() - 1))
{
cakes.erase(cakes.end() - 1);
}
use std::string::find_last_of() to locate last backslash and std::string::substr() to extract the last part:
std::string last_part;
const size_t slash_idx = cakes.find_last_of("\\");
if (std::string::npos != slash_idx)
{
last_part = cakes.substr(slash_idx + 1);
}
If it is possible that the directory name could contain forward slashes add the additional check for the last character and just add it to the argument to find_last_of("\\/"), as it can search for more than one character.
If you remove the trailing \ off the string you can use a simple combination of rfind and substr to get the data you want.
string substring = cakes.substr(cakes.rfind("\\") + 1);
#Joel Rondeau's comment where he says that the line temp.push_back(cakes[i]) is a problem is correct, but I thought I would elaborate.
The reason is that the variable i is defined as a char not an int, but it is possible for the two to be cast implicitly. So the reason why you are getting strange characters returned is because casting a char to an int has resulted in an index value that probably does not exist in your string.
Reading your code sample it looks like you should be doing temp.push_back(cakes[cakes.length()-1]) instead (or better, store this index in a temp variable so you don't have to keep writing it every time).
Your method depends on the string already being in the correct format with no assertions or error checking--not a good idea.
I would just do something like:
char* directoryName = strrchr(fullPath, '\\') + 1;
after you have trimmed off the trailing '\'.