How string library works in C++? - c++

According to the text at http://www.cplusplus.com/reference/string/string/, string library in C++ is a class, not just a " mere sequences of characters in a memory array".
I wrote this code to find out more:
string s = "abcd";
cout << &s << endl; // This gives an address
cout << s[0] << endl; // This gives 'a'
cout << &s[0] << endl; // This gives "abcd"
I have some questions:
1. Is string library in C++ still an array of sequence characters?
2. How can I get the address of each character in string? (As in the code, I can retrieve each character, but cannot get its address using & operator)

Much (most) of this really isn't about the string class itself.
std::string does store its contents as a contiguous array of characters.
&s[0] will yield the address of the beginning of that array -- but std::ostream has an overload of operator<< that takes a pointer to char, and prints it as a string.
If you want to see the addresses of the individual characters in a string, you need to take their addresses and then cast each address to pointer to void. std::iostream also has an overload of operator<< that takes a pointer to void, and that overload prints out the address instead of a string that (it assumes) is at that address.
Edit: demo code:
#include <iostream>
#include <string>
int main(){
std::string x("this is a string");
std::cout << &x[0] << "\n";
std::cout << (void *)&x[0] << '\n';
return 0;
}
Result:
this is a string
00481DE0

std::string stores the string as essentially a vector of characters, see basic_string

From Stroutrup's book, chapter 20 on strings, page 579 (2000 edition)
From C, C++ inherited the notion of strings as zero terminated arrays of char..... In C, the name of the array is same as the address of the first character. That's why you get the whole string printed when you pass &s[0] as it same as passing s itself.

Related

Trying to compare a string to a a value with tertiary/conditional operator and it doesn't work [duplicate]

This question already has answers here:
Comparing character arrays and string literals in C++
(4 answers)
Closed 1 year ago.
just learning C++ here.
#include <iostream>
#include <string>
int main()
{
char name[1000];
std::cout << "What is your name?\n";
std::cin.get(name, 50);
name == "Shah Bhuiyan" ? std::cout << "Okay, it's you\n" : std::cout<< "Who is this?\n";
}
So here I wrote a program where I created a variable name[100]. I use the cin iostream object thing to take input for the variable name. If the name is equal to my name (as seen in name == "Shah Bhuiyan" :) then output the first thing or output 'Who are you?'
Instead of outputting 'Oh, it's you' it outputs 'who is this?'
Why doesn't this work?
Your code is using arrays of characters. Any comparisons using == will compare their memory address. Since name and "Shah Bhuiyan" are two distinct arrays of characters, it will always be false.
The obvious solution is to use c++ strings from the standard library:
#include <iostream>
#include <string>
int main()
{
std::string name;
std::cout << "What is your name?\n";
std::getline(std::cin, name);
name == "Shah Bhuiyan" ? std::cout << "Okay, it's you\n" : std::cout<< "Who is this?\n";
}
The std::string type has operators defined that do the right thing here, and will compare the values of each.
Arrays do not have the comparison operator.
In fact in this expression
name == "Shah Bhuiyan"
there are compared two pointers (due to the implicit conversion of array designators to pointers to their first elements): the first one is a pointer to the first character of the character array name and the second one is a pointer to the first character of the string literal.
As the array and the string literal occupy different extents of memory the comparison of the addresses will always evaluate to false.
You need to use the standard C string function strcmp to compare two strings.
#include <cstring>
//...
std::strcmp( name, "Shah Bhuiyan" ) == 0 ? std::cout << "Okay, it's you\n" : std::cout<< "Who is this?\n";
If you want to use the equality operator == then instead of the character array use an object of the type std::string.
You could use std::getline to read into std::string instead of C-style char buffer.

Why is the == operator not yielding the same result as strcmp? [duplicate]

This question already has answers here:
C++ if statements using strings not working as intended
(4 answers)
Closed 3 years ago.
I've created a two dimensional array of character pointers. I'd like to use it to create a dictionary whereby, if the variable ent is part of the dictionary, the corresponding dictionary entry for that word is retrieved if it exists. I'm currently using strcmp, but only because the == operator is giving me a hard time. I'm not sure why the == operator is not leading to the desired results.
I suspect it might have something to do with pointer comparison, as I'm comparing a pointer to a string with another pointer to a string, and not necessarily its contents.
#include <iostream>
#include <cstring>
int main() {
char *dictionary[][2] {
{"First","Monday"},
{"Second","Tuesday"},
{"Third","Wednesday"},
{"Fourth","Thursday"},
{"Fifth","Friday"},
{"Sixth","Saturday"},
{"Seventh","Sunday"},
{"",""}
};
char ent[80] = "Sixth";
for (int i{}; *dictionary[i][0]; i++) {
if (!strcmp(dictionary[i][0], ent)) {
std::cout << "Word found: " << ent
<< " corresponds to: " << dictionary[i][1]
<< std::endl;
return 0;
}
}
std::cout << ent << " not found." << std::endl;
return 1;
}
I would like to replace if (!strcmp(dictionary[i][0], word)) with something like
if (word == dictionary[i][0]) and have it yield Word found: Sixth corresponds to Saturday
If I cannot do this with the == operator, is there a way to do this through a function that uses pointers but doesn't rely on a header?
Thanks!
In the condition of the if statement
if (word == dictionary[i][0])
there are compared addresses of first characters of the strings.
In expressions arrays with rare exceptions as for example using them in the sizeof operator are implicitly converted to pointers to their first elements.
For example if you will write such an if-statement like this
if ( "hello" == "hello" ) { /*...*/ }
then the expression evaluates either to true or false depending on the compiler option that specifies whether equal string literals are stored internally as one string or as separate strings.
You could define the dictionary such a way that the type of elements of which would be std::string. In this case you can use the equality operator ==.
In this case you can compare an object of the type std::string with character arrays containing strings because the character arrays would be implicitly converted to temporary objects of the type std::string.

Exchanging two strings using pointers prints strange output

I wrote a program to swap two strings using pointers.
char *names[]={"Sachin","Kapil","Ajay","Sunil","Anil"};
cout<<"String II is ";cout<<names[1];
cout<<"\nString IV is ";cout<<names[3];
char *t;
t=names[1];
names[1]=names[3];
names[3]=t;
cout<<"\nString II is ";cout<<names[1];
cout<<"\nString IV is ";cout<<names[3];
In the first line I used an array of char pointers to store the address of the 1st characters of the strings.
When I cout names[1] and names[3]:
The entire string was printed,but shouldn't the address of the character pointer alone be printed?
Also,how is the program printing the entire string instead of printing the first character alone?
After I compiled I got a warning from line 1 saying:
warning: deprecated conversion from string constant to 'char*' [-Wwrite-strings]|
The program runs as intended,but I would love to know how it is working.I came across an implementation where they used cout.write(names[i],len) to print the entire string(which makes sense),but to my surprise the above method works too (even without any sort of increase in the pointer value to print the next character).
Any tips and suggestions on the issues listed above would be appreciated.Thank you!
First up - why you don't just get the address output.
When you 'insert' any char* into a stream such as std::cout<<s; where s is of type char* the insertion logic assumes s points to the first character of a 'C' String. A 'C' String is a series of characters ending in a nul ('\0' character).
So the insertion logic outputs that series of characters.
You'll get a different result if you use std::cout<<static_cast<void*>(c); because the insertion of a void* pointer is treated differently.
The << operator on streams is overloaded to do different things depending on what you're inserting.
Now why you get that warning. You're uncovering some of the unhappy legacy of C++ particularly baggage inherited from C.
In the bad old days of early C without the const modifier there was no way of indicating that string literals (e.g. "Sachin") couldn't be modified at run-time and on different platforms doing so had different effects ranging from harmlessy amending the data, throwing an access exception to modifying all literal references to that string value in the code (there may be several).
So when C++ came along and introduced const (soon back adopted by C) it was quite rightly seen fit to make the type of string literals in the code const char[]. However some compilers (when you've got the wrong settings!) still allow you to assign literals to char[] (or char*) for backward compatibility.
Unless you're maintaining ancient code and the senior programmer has instructed you accordingly never leave that option enabled.
The stream library recognises the char* type and treats it as a null terminated c-style string which is simply an array of chars terminated by null.
To get it to print addresses, type cast the string to void*.
i.e.
cout << "String II is "; cout << (void *)names[1];
Will now output:
String II is 0x400a0b
As to why the first character is not printed only, it's because the stream library treats it as null terminated C string. But if you really only wanted the first character printed you could just as easily done this:
cout << "String II is "; cout << *names[1];
or:
cout << "String II is "; cout << names[1][0];
Manipulating pointers like you are is OK, but manipulation what they contain is not when declared like you have them. So you should really declared them const. This also removes the compilation warnings.
So the code would become.
const char *names[]={"Sachin","Kapil","Ajay","Sunil","Anil"};
cout << "\nString II is "; cout << (void*)names[1];
cout << "\nString IV is "; cout << (void*)names[3];
const char *t;
t = names[1];
names[1] = names[3];
names[3] = t;
cout << "\nString II is "; cout <<(void*)names[1];
cout << "\nString IV is "; cout <<(void*)names[3];

Using string array as function argument

I meant to write program which will simply delete single letters from the input given by user, let's say we've got some text like: "monkey eat banana" and we supposed to delete the letter 'a' from the text above.
The final output supposed to look like this:
'monkey et bnn'
I've got the code which works pretty much flawlessly with single strings, but I have to use getline() function to obtain some longer texts, that is why I have to declare array of string, in order to pass it's size in the second argument of getline() function, like so:
string text[256];
getline(text, 256);
I would like to use getline() function without giving a size of an array, but I think it's impossible, therefore I need to stick with string array instead of a string.
The problem I've got is that I don't know how to correctly pass array of string, to use it as function's argument. Here's my code;
#include <iostream>
#include <string>
using namespace std;
void deleteLetter(string &text[], char c)
{
size_t positionL = text.find(c);
if(positionL == string::npos)
cout << "I'm sorry, there is no such letter in text" << endl;
else
text.erase(positionL, positionL);
cout << "After your character removed: " << text << endl;
}
int main()
{
string str1[256];
char a = 'a';
cin.getline(str1, 256);
deleteLetter(str1, a);
}
I know it's elementary stuff, but still I can't figure it out on my own.
Perhpahs I should reach out for your help.
It sounds to me like you don't need an array of strings. Just to read as many characters the user types, into a string. getline should deal fine with this.
int main()
{
std::string str1; // just a string here, not an array.
std::getline (std::cin,str1);
deleteLetter(str1, 'a');
}
Now you should change the signature of DeleteLetter to take a single string as argument.
void deleteLetter(std::string& text, char c);
How your are going to implement deleteLetter is another question. The way you have it, it will delete only the first occurence of 'a'.
To read a string from console input (cin), you can use the getline() function:
std::string line;
std::getline(std::cin, line);
To remove all the occurrences of a given letter from a string, you can use the so called erase-remove idiom, with a combination of the string::erase() method and the std::remove() algorithm.
(Note that this idiom is usually showed applied to std::vector, but don't forget that a std::string can also be viewed as a "container of characters" stored in sequence, similar to vector, so this idiom can be applied to string content as well.)
To pass a std::string to functions/methods, use the usual C++ rules, i.e.:
If the function is observing the string (without modifying it), pass using const reference: const std::string &
If the function does modify the content of the string, you can pass using non-const reference: std::string &
A simple compilable code follows:
#include <algorithm>
#include <iostream>
#include <string>
using namespace std;
//
// NOTE:
// Since the content of 'text' string is changed by the
// removeLetter() function, pass using non-const reference (&).
//
void removeLetter(string& text, char letter)
{
// Use the erase-remove idiom
text.erase(remove(text.begin(), text.end(), letter),
text.end());
}
int main()
{
string line;
getline(cin, line);
cout << "Read string: " << line << endl;
removeLetter(line, 'a');
cout << "After removing: " << line << endl;
}
This is what I got with MSVC:
C:\Temp\CppTests>cl /EHsc /W4 /nologo test.cpp
test.cpp
C:\Temp\CppTests>test.exe
monkey eats banana
Read string: monkey eats banana
After removing: monkey ets bnn
It's not very clear to me from your question if you also want to pass vectors of strings around (probably in other parts of your code)...
Anyway, if you want a vector of strings (i.e. you want to store some strings in a vector container) you can simply combine these STL class templates like this:
std::vector<std::string> strings;
To pass that to functions/methods, use the usual C++ rules, i.e.:
If the function is observing the array of strings (without modifying it), pass using const references (const &): vector<string> &
If the function does modify the content of the vector, you can pass using non-const references (&): vector<string> &

Unexpected behavior on adding '\0' to std::string

Why does the C++ standard allow the following?
#include <iostream>
#include <string>
int main()
{
std::string s(10, '\0'); // s.length() now is 10
std::cout << "string is " << s << ", length is " << s.length() << std::endl;
s.append(5, '\0'); // s.length() now is 15
std::cout << "string is " << s << ", length is " << s.length() << std::endl;
// the same with += char and push_back
// but:
s += "hello"; // s.length() returns 20 string is "hello"
std::cout << "string is " << s << ", length is " << s.length() << std::endl;
return 0;
}
Why does it add 0 and count it?
It looks like broken integrity of string, doesn't it? But I checked standard and it is correct behavior.
Why does standard allows following?
Because the people designing C++ strings decided that such things should be allowed. I'm not sure if anyone that was part of the team that designed C++ strings are on SO... But since you yourself say that the standard allows it, that's the way it is, and I doubt it's about to change.
It's sometimes quite practical to have a string that can contain "anything". I can think of a few instances when I've had to work around the fact that C style strings can't contain zero-bytes. Along with the fact that long C style strings take a long time to find the length of, the main benefit of C++ strings is that they are not restricted to "what you can put in them" - that's a good thing in my book.
Not sure what is problem here.
Adding '\0' in the middle of the std::string changes nothing - null character is treated like any other. The only thing that can change is if you use .c_str() with function that accepts null-terminated strings. But then it's not problem of .c_str(), only with the function that treats '\0' specially.
If you want to know how many characters has this string as if treated like null-terminated string, use
size_t len = strlen(s.c_str());
Note that it's O(n) operation, because that's how strlen works.
If you ask why += operator doesn't add the implicit null character of string literal "hello" to the string, I say the reverse (adding it) is unclear and definitely not what you want 99% of the time. On the other hand, if you want to add '\0' to your string, just append it like a buffer:
char buffer[] = "Hello";
s.append(buffer, sizeof(buffer));
or (even better) drop the char arrays and null-terminated strings altogether and use C++-style replacements like std::string as NTS-replacement, std::vector<char> as contiguous buffer, std::vector as dynamic array with pointers replacement, and std::array (C++11) as standard C array replacement.
Also, (as mentioned by #AdamRosenfield in comments), your string after adding "hello" does have in fact 20 characters, it's probably only that your terminal doesn't print nulls.
NUL char '\0' is the ending character for c style string, not std::strings. However, it supports this character to get values from a const char pointer so that it can find the end of a c-style string. Otherwise, it is treated just like other characters
std::string is more of a container for characters than anything else and \0 is a character. As a real world example, take a look at the CreateProcess function in Windows. The lpEnvironment parameter takes a null-terminated block of null-terminated strings (i.e. A=1\0B=2\0C=3\0\0). If you're building a block it's convenient to use an std::string.