Difference in comparing string .c_str() and normal string - c++

I wanted to know what the difference between the two codes is. When I use .c_str() it does not work
std::vector<std::pair<std::string, std::string> >::iterator it
for(;it!=MySet.end();++it)
{
if(std::get<1>(*it).c_str()=="PAUSE") //Why it works only with std::get<1>(*it) and not with std::get<1>(*it).c_str()
{
TempDefaultVan = std::get<0>(*it).c_str();
}
}

So basically what is happening is .c_str() is returning a const char*. This will cause operator == to compare pointers and not the string's content. Since both of these are clearly not pointing to the same memory location (since "PAUSE" is a string literal), this will always be false.

std::get<1>(*it) returns an object of type std::string. This class has overload operator == to compare objects of type std::string with character arrays.
std::get<1>(*it).c_str() returns a character array. Arrays have no the comparison operator. To compare character arrays you should use standard C function std::strcmp
So you could write
if( std::strcmp( std::get<1>(*it).c_str(), "PAUSE" ) == 0 )
If you will write simply as
if(std::get<1>(*it).c_str()=="PAUSE")
then the compiler will compare two pointers because it converts arrays to pointers to their first elements in such expressions. And as the result this expression will be equal always to false if the arrays occupy different areas of memory.

This code:
std::get<1>(*it).c_str()=="PAUSE"
is comparing two const char *, which both point to strings. This is not what you usually want when comparing strings, as it will only evaluate to true when they point to the same place in memory.
This code:
std::get<1>(*it)=="PAUSE"
Will use std::string::operator== to compare the contents of the std::string std::get<1>(*it) with the contents of "PAUSE". If you instead had two char * values, you could use strcmp, but since you have a std::string, this is the best way to make the comparison (and, as you say, "it works").

Related

How to handle POST requests in c++ CGI [duplicate]

This question already has answers here:
How do I properly compare strings in C?
(10 answers)
Closed 1 year ago.
int main (int argc, **argv)
{
if (argv[1] == "-hello")
printf("True\n");
else
printf("False\n");
}
# ./myProg -hello
False
Why? I realize strcmp(argv[1], "-hello") == 0 returns true... but why can't I use the equality operator to compare two C strings?
Because argv[1] (for instance) is actually a pointer to the string. So all you're doing is comparing pointers.
You can't compare strings in C with ==, because the C compiler does not really have a clue about strings beyond a string-literal.
The compiler sees a comparison with a char* on either side, so it does a pointer comparison (which compares the addresses stored in the pointers)
In C because, in most contexts, an array "decays into a pointer to its first element".
So, when you have the array "foobar" and use it in most contexts, it decays into a pointer:
if (name == "foobar") /* ... */; /* comparing name with a pointer */
What you want it to compare the contents of the array with something. You can do that manually
if ('p' == *("foobar")) /* ... */; /* false: 'p' != 'f' */
if ('m' == *("foobar"+1)) /* ... */; /* false: 'm' != 'o' */
if ('g' == *("foobar"+2)) /* ... */; /* false: 'g' != 'o' */
or automatically
if (strcmp(name, "foobar")) /* name is not "foobar" */;
Because there is no such thing as a C string.
In C, a string is usually an array of char, or a pointer to char (which is nearly the same). Comparing a pointer/array to a const array won't give the expected results.
UPDATE: what I meant by 'no C string' is, there is no string in C. What's usually referred to as a 'C string' is language independent (as 'Pascal string' is), it's the representation of strings as a null-terminated linear array of characters.
In C, string values (including string literals) are represented as arrays of char followed by a 0 terminator, and you cannot use the == operator to compare array contents; the language simply doesn't define the operation.
Except when it is the operand of either the sizeof or & operators, or when it is a string literal being used to initialize another array in a declaration, an expression with type "N-element array of T" will have its type implicitly converted (decay) to type "pointer to T", and the value of the expression will be the address of the first element of the array.
So when you write
if (argv[1] == "-hello")
the compiler implicitly converts the expression "-hello" from type "7-element array of char" to "pointer to char" (argv[1] is already a pointer type), and the value of the expression is the address of the character '-'. So what == winds up comparing are two pointer values, which are (most likely) never going to be equal since "-hello" and argv[1] (most likely) occupy different regions in memory.
This is why you have to use library functions like strcmp() to compare string values.
Because C strings dont exist as such. They are char arrays ending in a \0.
The equality operator == will test that the pointer to the first element of the array are the same. It wont compare lexicographically.
On the other hand "-hello" == "-hello" may return non zero, but that doesn't mean that the == operator compares lexicographycally. That's due to other facts.
If you want to compare lexicographycally, you can always
#define STR_EQ(s1,s2) \
strcmp(s1,s2) == 0
Reading harder I see that you tagged as c++. So you could
std::string arg1 ( argv[1] );
if (arg1 == "-hello"){
// yeahh!!!
}
else{
//awwwww
}
Strings are not native types in C. What you are comparing in that example are two pointers. One to your first argument, and the other is a static character array with the contents of "-hello".
You really want to use strncmp or something similar.
When you're using ==, you're comparing pointers. That is, it will return true if the two operands refer to the same string in memory. Therefore, it's unsuitable for use in comparing strings lexicographically.
Because C strings are array of characters. Arrays are simply pointers to the first element in the array, and when you compare two pointers using == it compares the memory address they point to, not the values that they point to.

Calculate length of string object using pointers instead of char arrays

I'm working on an exercise to calculate the length of a string using pointers.
Here's the code I've written below:
int main() {
std::string text = "Hello World";
std::string *string_ptr = &text;
int size = 0;
//Error below: ISO C++ forbids comparison between pointer and integer [-fpermissive]
while (string_ptr != '\0') {
size++;
string_ptr++;
}
std::cout << size;
}
In a lot of examples that I've seen, the string is often a char array which I also understand is a string. However, I want to try calculate it as a string object but I'm getting the error below.
Is it possible to calculate it where the string is an object, or does it need to be a char array?
If you just want the size of the string, well, use std::string::size():
auto size = text.size();
Alternatively, you can use length(), which does the same thing.
But I'm guessing you're trying to reimplement strlen for learning purposes. In that case, there are three problems with your code.
First, you're trying to count the number of characters in the string, and that means you need a pointer to char, not a pointer to std::string. That pointer should also point to constant characters, because you're not trying to modify those characters.
Second, to get a pointer to the string's characters, use its method c_str(). Getting the address of the string just gets you a pointer to the string itself, not its contents. Most importantly, the characters pointed to by c_str() are null terminated, so it is safe to use for your purposes here. Alternatively, use data(), which has been behaving identically to c_str() since C++11.
Finally, counting those characters involves checking if the value pointed to by the pointer is '\0', so you'll need to dereference it in your loop.
Putting all of this together:
const char* string_ptr = text.c_str(); // get the characters
int size = 0;
while (*string_ptr != '\0') { // make sure you dereference the pointer
size++;
string_ptr++;
}
Of course, this assumes the string does not contain what are known as "embedded nulls", which is when there are '\0' characters before the end. std::string can contain such characters and will work correctly. In that case, your function will return a different value from what the string's size() method would, but there's no way around it.
For that reason, you should really just call size().
First things first, the problem is irrelevant. std::string::size() is a O(1) (constant time) operation, as std::string's typically store their size. Even if you need to know the length of a C-style string (aka char*), you can use strlen. (I get that this is an exercise, but I still wanted to warn you.)
Anyway, here you go:
size_t cstrSize(const char* cstr)
{
size_t size(0);
while (*cstr != '\0')
{
++size;
++cstr;
}
return size;
}
You can get the underlying C-style string (which is a pointer to the first character) of a std::string by calling std::string::c_str(). What you did was getting a pointer to the std::string object itself, and dereferencing it would just give you that object back. And yes, you need to dereference it (using the * unary operator). That is why you got an error (which was on the (string_ptr != '\0') btw).
You are totally confused here.
“text” is a std::string, that is an object with a size() method retuning the length of the string.
“string_ptr” is a pointer to a std::string, that is a pointer to an object. Since it is a pointer to an object, you don’t use text.size() to get the length, but string_ptr->size().
So first, no, you can’t compare a pointer with an integer constant, only with NULL or another pointer.
The first time you increase string_ptr it points to the memory after the variable text. At that point using *string_ptr for anything will crash.
Remember: std::string is an object.

ESP32 connecting to Wi-fi using String ssid

I cannot connect to Wi-fi on my ESP32 DEV-KIT; I have a String which contains ssid and password, but the WIFI.begin() function accepts only const char*, and I don't know how to convert String to const char*. I have tried the c_str() function, but the result I am getting is != (not equal) to the actual ssid.
String ssid = "DIR-615A";
char ssidR;
const char* ssidR2 = "DIR-615A";
void setup() {
ssidR = ssid.c_str();
Serial.println(ssidR);
Serial.println(ssidR2);
Serial.println(ssidR == ssidR2); //this returns 0
}
Both ssidR and ssidR2 look identical when printing them, but when I use ssidR to connect to wi-fi, it is not working.
You can use toCharArray() to convert the String object into an array of chars.
Also, you have to use strcmp() to compare two strings, otherwise you're literally just comparing the address in memory where the data is kept.
To use it:
char ssid_char[100]; /* or however long your SSID is going to be */
ssid.toCharArray(ssid_char, ssid.length());
wifi.begin(ssid_char);
The reason is that you need space to store the content of the string somewhere. Your ssidR variable was just a pointer to memory, but there was no memory to store the actual string. By using a static array, the memory is reserved for the toCharArray() call to fill.
I was going to try to put this as a comment, but it's unwieldy there. It's kind of screwed up as an answer, because your question seems to be predicated on an misunderstandings of what to expect from == and const char * and what can be done with a single char, but it does at least address what's been said.
Regarding /*char*/ ssidR = ssid.c_str(); and ssidR == ssidR2
I addressed this part in a comment, but since as though I'm making an answer now I'll pull it into here. I'm not repeating this just to beat a dead horse.
You are attempting to assign a pointer to const char to a single char. Basically you're trying to cram 32bit (in this case) address of something into a character and then you're trying to compare that with const char * again:
Serial.println(ssidR == ssidR2); //this returns 0
Right, it would do. Without splitting hairs about the language standard and undefined behaviour on overflow, etc. what you're doing here is comparing the low byte of the address of the buffer maintained by your String object with the full address of your string literal "DIR-615A" and they're not equal.
begin() and const char *
From your comment on the question
i dont know why the guy who wrote th elibrary decided to make it accept only char pointers.
An entire answer could be written on the decision to just support const char *; There are reasons, though some maybe relevant more historically and may persist more out of habit than anything else. If you want an example of that sort of thing you can look at open() for fstream in C++ prior to C++17; it accepted const char * but not std::string which is standard C++'s counterpart to the Arduino String type.
The main thing I'd impress on you is that it accepting const char * is not actually a problem for you at all. There's absolutely nothing wrong with calling begin with ssid.c_str() and your WiFi connectivity problems don't have anything to do with their decision.
NOT Comparing c-strings
== on pointers compares the pointers themselves and not the things being pointed-to. In the case of const char *, the compiler couldn't generally know what to compare once it followed the pointer; at the time == is evaluated the compiler can't reliable know that these pointers point at null terminated strings and not some other sequence of zero or more chars.
The C and C++ languages allow for string literals to be overlapped in memory, that is to not have distinct addresses, which can lead you to the false sense that you've used == in a way that it's actually comparing the strings. "yes" == "yes" will usually evaluate to true, because typical compilers will reuse the first instance of the literal for the second, and so they're in the same memory location, so == appears to work when it in fact didn't. There's no requirement that the reuse the same memory. And in this case: char no[3] = "no"; if (no == "no") { will be false. Because the no char array is a distinct object, nothing else has its address, including a string literal you might try to compare it to which has the same contents.
Comparing c-strings
What you seem to want to do to convince yourself that .c_str() yields what you want can be done in a pile of ways. The traditional way (from C) to compare null-terminated c-strings is with the strcmp function:
String ssid_via_str_obj = "DIR-615A";
const char *ssid_via_char_ptr = "DIR-615A";
// ...
if (strcmp(ssid_via_str_obj.c_str(), ssid_via_char_ptr) == 0) {
// note strcmp returns zero for equality,
// numbers above and below zero are used to represent greater and lessor.
Serial.println("Equal");
} else {
Serial.println("Unequal");
}
There's also an endless pile of ways you could do this with the Arduino String, and c++ std::string and std::string_view classes you have available to you.

c_str() return value not matching const char*

c_str() returning a const char*, I would assume the following code to print "equal", but it doesn't.
Could someone explain to me where am I wrong ?
string xx = "hello";
const char* same = "hello";
const char* buf = xx.c_str();
if (buf == same)
{
cout << "equal" << endl;
}
I would assume the following code to print "equal"
That's a wrong assumption. std::string copies the literal data to its internal buffer, so the pointers will differ.
In case you wanted to compare the data instead of pointers, don't use c_str() at all and compare the const char* to the string directly - the overloaded comparison operator will do what you expect it to.
operator== for const char* does not do a string comparison, it directly compares the pointers. The buffer used by xx is not the same as the same pointer, so they are not equal.
To do string comparison with a std::string and a const char* you can just use the operator provided by std::string:
if (xx == same)
{
cout << "equal" << endl;
}
Operator == for char* pointers compares the pointers themselves, not the strings they point to.
If you want to second-guess std::string, you need to use comparison routines, like following:
if (strncmp(buf, same, xx.size())
...
buf == same is not comparing the strings but the address of the pointers. Since they are two different variables they will have two different address that will not be equal. If you need the compare char*s or const char*s then you can use std::strcmp
You are comparing the values of two pointers, which can never be equal. same points to read-only memory, and the pointer returned by c_str is the current data buffer of the std::string object with a null terminator guaranteed to be appended.
You could write xx == same: this is because std::string overloads == with the appropriate data types. Alternatively, and perhaps less elegantly, you could use std::strcmp on the two pointers.

Using NULL as a terminator in arrays?

I like "reinventing the wheel" for learning purposes, so I'm working on a container class for strings. Will using the NULL character as an array terminator (i.e., the last value in the array will be NULL) cause interference with the null-terminated strings?
I think it would only be an issue if an empty string is added, but I might be missing something.
EDIT: This is in C++.
"" is the empty string in C and C++, not NULL. Note that "" has exactly one element (instead of zero), meaning it is equivalent to {'\0'} as an array of char.
char const *notastring = NULL;
char const *emptystring = "";
emptystring[0] == '\0'; // true
notastring[0] == '\0'; // crashes
No, it won't, because you won't be storing in an array of char, you'll be storing in an array of char*.
char const* strings[] = {
"WTF"
, "Am"
, "I"
, "Using"
, "Char"
, "Arrays?!"
, 0
};
It depends on what kind of string you're storing.
If you're storing C-style strings, which are basically just pointers to character arrays (char*), there's a difference between a NULL pointer value, and an empty string. The former means the pointer is ‘empty’, the latter means the pointer points to an array that contains a single item with character value 0 ('\0'). So the pointer still has a value, and testing it (if (foo[3])) will work as expected.
If what you're storing are C++ standard library strings of type string, then there is no NULL value. That's because there is no pointer, and the string type is treated as a single value. (Whereas a pointer is technically not, but can be seen as a reference.)
I think you are confused. While C-strings are "null terminated", there is no "NULL" character. NULL is a name for a null pointer. The terminator for a C-string is a null character, i.e. a byte with a value of zero. In ASCII, this byte is (somewhat confusingly) named NUL.
Suppose your class contains an array of char that is used to store the string data. You do not need to "mark the end of the array"; the array has a specific size that is set at compile-time. You do need to know how much of that space is actually being used; the null-terminator on the string data accomplishes that for you - but you can get better performance by actually remembering the length. Also, a "string" class with a statically-sized char buffer is not very useful at all, because that buffer size is an upper limit on the length of strings you can have.
So a better string class would contain a pointer of type char*, which points to a dynamically allocated (via new[]) array of char s. Again, it makes no sense to "mark the end of the array", but you will want to remember both the length of the string (i.e. the amount of space being used) and the size of the allocation (i.e. the amount of space that may be used before you have to re-allocate).
When you are copying from std::string, use the iterators begin(), end() and you don't have to worry about the NULL - in reality, the NULL is only present if you call c_str() (in which case the block of memory this points to will have a NULL to terminate the string.) If you want to memcpy use the data() method.
Why don't you follow the pattern used by vector - store the number of elements within your container class, then you know always how many values there are in it:
vector<string> myVector;
size_t elements(myVector.size());
Instantiating a string with x where const char* x = 0; can be problematic. See this code in Visual C++ STL that gets called when you do this:
_Myt& assign(const _Elem *_Ptr)
{ // assign [_Ptr, <null>)
_DEBUG_POINTER(_Ptr);
return (assign(_Ptr, _Traits::length(_Ptr)));
}
static size_t __CLRCALL_OR_CDECL length(const _Elem *_First)
{ // find length of null-terminated string
return (_CSTD strlen(_First));
}
#include "Maxmp_crafts_fine_wheels.h"
MaxpmContaner maxpm;
maxpm.add("Hello");
maxpm.add(""); // uh oh, adding an empty string; should I worry?
maxpm.add(0);
At this point, as a user of MaxpmContainer who had not read your documentation, I would expect the following:
strcmp(maxpm[0],"Hello") == 0;
*maxpm[1] == 0;
maxpm[2] == 0;
Interference between the zero terminator at position two and the empty string at position one is avoided by means of the "interpret this as a memory address" operator *. Position one will not be zero; it will be an integer, which if you interpret it as a memory address, will turn out to be zero. Position two will be zero, which, if you interpret it as a memory address, will turn out to be an abrupt disorderly exit from your program.