What is the best strategy is for returning a modified copy of a string from a function? From my experience in programming in C, I know that it is generally preferable to return a pointer to a large object (array or struct) instead of a copy.
I have the following example, where I pass a string type to a C++ function and return a char *. I was wondering how I should go about returning a string object instead. If I were to return a string, would it be advisable to return a copy or dynamically allocate it and return the pointer?
#include <iostream>
using namespace std;
char *removeSpaces(string sentence)
{
char *newSentence = new char[sentence.size()]; // At most as long as original
int j = 0;
for (int i = 0; i < sentence.size(); i++) {
if (sentence[i] == ' ') {
newSentence[j++] = ' ';
while (sentence[++i] == ' ');
}
newSentence[j++] = sentence[i];
}
return newSentence;
}
int main(void)
{
string sentence;
cout << "Enter a sentence: ";
getline(cin, sentence);
char *sentenceSpacesRemoved = removeSpaces(sentence);
cout << "After removing spaces: ";
cout << sentenceSpacesRemoved << endl;
delete[] sentenceSpacesRemoved;
return 0;
}
Thanks to copy elision, you can just return a stack-allocated string object from a function without any overhead:
std::string getString()
{
std::string str;
return str;
}
Moreover, you can get "guaranteed" elision (since C++17) if you construct the string right in the return expression:
std::string getString()
{
return std::string{};
}
If you don't need a copy at all, however, for your scenario you can just pass the string and remove whitespaces in-place:
void removeSpaces(std::string& sentence)
{
for (auto it = std::cbegin(sentence); it != std::cend(sentence); ++it) {
if (*it == ' ') {
it = sentence.erase(it);
}
}
}
Related
I have prototype - int replace_char(string &, char);
I can't use library from string and ctype.h, I should write my own function.
So the task is to find in the text caharacter, which should I should replace with "*" .
example: In This is my text .
replace all t to * . Result will be - *his is my *ex*.
#include <iostream>
using namespace std;
int replace_char(string &, char);
int main ()
{
cout << ""Please insert text:"
cin >> str;
}
int replace_char(string str, char c1)
{
for (int i = 0 ; i < str.length(); i++)
{
if(str[i]==c1)
str[i]='*';
}
return str;
}
There were several errors in the code:
The function signature mismatches, the prototype is defined as std::string& but in the function definition, std::string only was used.
The program never converted the capital letter T or anything which is capital in order to convert them before comparing each letter with a single char.
The function is never used in the code.
cin >> str won't take longer texts followed by next whitespace character.
The function wants to return an integer, but actually returned type was a std::string, which is totally a misunderstanding.
The code redefined:
#include <iostream>
// taking a reference of std::string and a char
int replaceText(std::string&, char);
int main(void) {
std::string s;
int rep;
std::cout << "Enter a string: ";
std::getline(std::cin, s); // getline() to accept whitespaces
// since we're using a reference, the original variable is manipulated
int rep = replaceText(s, 't');
std::cout << "Output: " << s << std::endl;
std::cout << "Replaced number of chars: " << rep << std::endl;
return 0;
}
int replaceText(std::string& str, char c) {
size_t len = str.length();
static int count;
// changing each letter into lowercase without using any built-in functions
for (size_t i = 0; i < len; i++)
if (str[i] >= 'A' && str[i] <= 'Z')
str[i] = str[i] + 32;
// replacing the character, the main work
for (size_t i = 0; i < len; i++)
if (str[i] == c) {
str[i] = '*';
count++; // count a static variable
}
return count; // returning the overall counts
}
The program firstly takes an input from the user of type std::string and uses reference to the variable str. Now the program enters to the function code.
In the beginning, the function converts each letter to lowercase without using any library or a built-in function. Afterwards, it tries to compare each letter of the string carefully and as soon the given character matches a value containing in the string passed to the function, it replaces and counts a static variable which keeps the value save for the entire program life.
Thereafter, it simply displays the manipulated string.
It outputs something like:
Enter a string: This is a text
Output: *his is a *ex*
Replaced chars: 3
You seem to have a good start.
You need to declare str before reading input into it. Try string str;
Then you need to use your function in main. Either store its output into another string like string replaced = replace_char(str, 't');
Or put it into the output directly like cout << replace_char(str, 't') << endl;
Probably this is what you need
#include <iostream>
using namespace std;
int replace_char(string &, char);
int main ()
{
string str;
cout << "Please insert text:"
std::getline(cin, str);
int rlen = replace_text(str, 't')
cout << str << endl;
cout << "Number of replaced : " << rlen << endl;
return 0;
}
int replace_char(string str, char c1)
{
int rlen = 0;
for (int i = 0 ; i < str.length(); i++)
{
if(str[i]==c1) {
str[i]='*';
rlen++;
}
}
return rlen;
}
Given the prototype of the function, I'm guessing you need to return the number of chars replaced. This implementation should work:
#include <string>
#include <iostream>
using namespace std;
int replace_char(string &, char);
int main ()
{
cout << "Please insert text:";
string str;
getline(cin, str);
int nCharsReplaced = replace_char(str, 't');
}
int replace_char(string& str, char c1)
{
int count = 0;
for (int i = 0 ; i < str.length(); i++)
{
if(str[i]==c1)
{
str[i]='*';
count++;
}
}
return count;
}
Keep in mind there's no need to return the string, as you're passing it by reference, so the argument itself is modified.
Also, if you want the example you provided to work the replace_char functions cannot be case sensitive, since you replaced the capital 'T' with '*' too. In order to achieve that, you could implement a function that turns every char to lowercase (ideally, you would use tolower from ctype):
char to_lower_case(char c)
{
return c - ('Z' - 'z');
}
And replace the if condition with:
if (to_lower_case(str[i]) == c1)
If you don't understand how this work, take a look at how ASCII works.
Sorry for the noob question, I'm a newbie programmer and transitioning from C to C++.
I could easily write a program to reverse a string in C the same way with minor changes but writing this in C++, why does this not print anything:
#include <iostream>
#include <string>
using namespace std;
int main(){
string s,p;
getline(cin,s);
int j=0,i = 0;
while(s[i]!='\0'){
i++;
}
i--;
while(i!=-1){
p[j] = s[i];
j++;
i--;
}
cout << p << endl;
return 0;
}
if i replace the p with say p[2], it correctly prints out the reverse 3rd character of the original string, but i cant find a way to print the whole string.
std::string str{"reverse me"};
std::string rev{str.rbegin(), str.rend()};
//or when you are not interested in the orignal string
std::reverse(str.begin(), str.end());
Giving the constructur of the reverse string the reversed iterators of your input string gives you the string in reversed order.
To fix your string reverse code you just have to resize the string object p:
int main(){
std::string s = "hello",
p;
p.resize(s.size()); // this was causing your problems, p thought it was size 0
for (int i = s.size() - 1, j = 0; i >= 0; i--, j++)
{
p[j] = s[i];
}
std::cout << p << std::endl;
return 0;
}
In addition to this, there is no need to find \0 in the string, while it will be there, you can just ask std::string what its size() is.
On a side note, while std::string probably allocates some memory by default, just assuming it has enough to store whatever you input is going to be undefined behaviour.
While there are ways to iterate over a std::string and fill the contents of another, the overload of std::basic_string::operator= will replace the content of p (if any) with the content of s in a simple assignment. See std::basic_string::operator=
For example:
#include <iostream>
#include <string>
int main (void) {
std::string s {}, p {};
std::cout << "enter string: ";
if (getline (std::cin, s)) {
p = s; /* simple assignment replaced content of p with content of s */
std::cout << "string in p : " << p << '\n';
}
}
Example Use/Output
$ ./bin/stringps
enter string: the string s
string in p : the string s
string p; doesn't have enough allocated space for directly accessing by something like p[j]
You can change to initialize p from copying s like below, your code will work.
string s;
getline(cin,s);
string p(s); // p will be allocated and be the same as s
#include<iostream>
#include<string>
using namespace std;
int STRLEN(char* s){
cout<<"\n1.";
int i=0;
while(s[i] != '\0'){
cout<<"\n2.";
i++;
}
return i;
}
int main(){
int i,j;
char* s1;
char* s2;
cout<<"\nEnter string : ";
cin.getline(s1,50);
cout<<s1;
cout<<"\nEnter string : ";
cin.getline(s2,50);
cout<<s2;
int L1=STRLEN(s1);
int L2=STRLEN(s2);
cout<<"\nL1 = "<<L1;
cout<<"\nL2 = "<<L2;
/*
for*(i=L1,j=0; i<L1+L2; i++,j++)
{
s1[i] = s2[j];
j++;
}
cout<<s1;*/
return 0;
}
the above code is giving me segmentation fault at line int L1=STRLEN(s1);
Please provide a solution , i want my string to be dynamically manipulated, so that i can extend the given string, also append new string to existing string without using inbuilt methods.
Also without using string data type
Actually, your function STRLEN looks norm (except couts inside and lack of const for s)
int STRLEN(const char* s)
{
int i=0;
while(s[i] != '\0')
{
i++;
}
return i;
}
The problem in memory allocation :getline does not allocate memory for you - you must allocate memory for strings
char* s1;
char* s2;
E.g. like:
char* s1 = malloc(100);
char* s2 = malloc(100);
Actually for your case with cin.getline(s2,50); 50 bytes will be enough:
char* s2 = (char*)malloc(50);
Here (char*) is explicit cast of a pointer type (see also static_cast for C++, and be informed that for C implicit cast is working in that case)
UPDATE:
Just to give you more examples and provoke more questions... the following is my modification of your program with comments for each section:
#include<iostream>
#include<string>
using namespace std;
int STRLEN(const char* s)
{
int i=0;
while(s[i] != '\0')
{
i++;
}
return i;
}
int main(void)
{
int i; // one counter will be enough
char* s1;
char* s2;
// allocation the memory
s1 = static_cast<char*>(malloc(50));
s2 = static_cast<char*>(malloc(50));
// check results of memory allocation
if(!s1 || !s2)
{
cerr << "Something went wrong!" << endl;
return 1;
}
// getting strings
cout<<"\nEnter the first string : ";
cin.getline(s1,50);
cout<< "S1 : [" << s1 << "]" << endl;
// clean input buffer before next string input
cin.clear(); // reset state of cin
cin.ignore(INT_MAX, '\n'); // clean the input buffer
// continue input
cout<<"\nEnter the second string : ";
cin.getline(s2,50);
cout<< "S2 : [" << s2 << "]" << endl;
// count sizes (like strlen)
int L1=STRLEN(s1);
int L2=STRLEN(s2);
// reallocate memory for resulting string in s1
if( !(s1 = static_cast<char*>(realloc(s1, L1+L2+1))) )
{
cerr << "Something went wrong while reallocating memory!" << endl;
return 1;
}
// manipulations with strings (like strcat)
for(i=0; i <= L2; i++) // <= to copy '\0' also
{
s1[i + L1] = s2[i];
}
cout<< "Result : [" << s1 << "]" << endl;
// deallocate memory
free(s1);
free(s2);
return 0;
}
And as molbdnilo rightly noted in the comments, in C++ it is better to use new and delete for memory allocation and deallocation, so after you figure out with my example try to get rid of C functions: malloc, realloc and free.
After that, like making your program even more C++ solution, consider changing the type of strings from char * to std::string this will definitely save you from memory allocation problem and make all other parts of program simpler (e.g. s1 += s2 operation will be possible). When you get to that read about getline for string
How can I get tis to work by passing a string to a Boolean function? I need to have the user input a series of strings, and after each entry, the program should give feedback depending upon whether or not the string fit the given criteria. The string should contain the substring of "1101", without any letters. Thanks for all your help
#include <iostream>
#include <cstring> // for strstr
#include <string>
#include <cctype>
using namespace std;
bool stringCompare(char*y);
string str2;
int main ()
{
string str1, str2;
str1= "1101";
do
{
cout << "Please enter your string: " << endl;
cin >> str2;
while((stringCompare(str2)) == true)
{
if(strstr(str2.c_str(),str1.c_str())) // Primary string search function
{
cout << "ACCEPTED " << endl;
}
else
cout << "NOT ACCEPTED " << endl;
}
} while (2 > 1);
return 0;
}
bool stringCompare(char*y)
{
for(int a = 0; a < strlen(str2); a++)
{
if (!isdigit(str2[a]))
return false;
}
return true;
}
stringCompare takes a parameter of type char*, but you're trying to pass a std::string. That won't work.
You can either use the c_str method of std::string, to get a const char* pointing to the std::string's internal char array. This means that you'll have to chance the parameter to const char*.
Or, much better, you can replace stringCompare to take a reference to a std::string:
bool stringCompare(string& y)
and change strlen(str2) to str2.length(). (Or even better, replace the whole loop to simply:
for(char& ch : str2) // range-based for-loop loops over the entire str2
{
if (!isdigit(ch))
return false;
}
)
Also, you don't need to compare the return value == true. Just do:
while(stringCompare(str2))
This version is working. I've made // comments throughout my code to better illustrate what I am having trouble with. The program is dependent on reading a text file. In the format of paragraphs with punctuation included.
One could copy this and the above into a text file and run the program.
// Word.cpp
#define _CRT_SECURE_NO_WARNINGS // disable warnings for strcpy
#define ARRY_SZ 100
#include <iostream>
#include <fstream>
#include "Word.h"
using namespace std;
Word::Word( const char* word )
{
ptr_ = new char[ strlen( word ) + 1 ];
strcpy( ptr_, word );
len_ = strlen( ptr_ );
}
Word::Word( const Word* theObject )
{
ptr_ = theObject->ptr_;
len_ = theObject->len_;
}
Word::~Word()
{
delete [] ptr_;
ptr_ = NULL;
}
char Word::GetFirstLetterLower()
{
// I want to use ptr_ and len_ here
// but the information is gone!
char c = 0;
return c;
}
char* Word::GetWord()
{
for (int x = 0; x < strlen( (char*)ptr_ ); x++)
ptr_[x]; // Results in a crash.
return ptr_;
}
// Word.h
const int FILE_PATH_SZ = 512;
class Word
{
private:
char* ptr_;
int len_;
public:
Word( const Word* ); // an appropriate default constructor
Word( const char* );
~Word( );
char GetFirstLetterLower( );
char* GetWord( );
static char fileEntry[ FILE_PATH_SZ ];
};
// main.cpp
#ifdef _DEBUG
#define _CRTDBG_MAP_ALLOC
#include <iostream>
#include <fstream>
#include <string>
#endif
#include "Word.h"
using namespace std;
const int WORD_SZ = 100;
Word** g_wordArray;
int g_arrSz;
static char filePath[ FILE_PATH_SZ ] = {};
void FreeWordArray();
int main( const int argc, const char **argv )
{
int wrdCount = 0;
char usrMenuOption = 0,
getFirstLetter = 0,
tmpArray[WORD_SZ] = {},
*getWord = 0;
string str,
str2;
ifstream inFile,
inFile2;
do
{
cout << "Please make a selection: \n\
a) Read a text file\n\
b) Remove words starting with letter\n\
c) Print words to console\n\
d) Quit\n";
cin >> usrMenuOption;
switch( usrMenuOption )
{
case'A':
case'a':
cout << "Enter a file name: ";
cin.sync();
cin >> filePath;
inFile.open( filePath );
if ( !inFile ) return -1;
inFile >> str; // prime the eof flag
while ( !inFile.eof() )
{
inFile >> str;
wrdCount++;
g_wordArray = new Word *[wrdCount];
}
inFile.close();
inFile2.open( filePath );
while( !inFile2.eof() )
{
inFile2 >> str2;
for ( unsigned x = 0; x < str2.length(); x++ )
g_wordArray[x] = new Word( str2.c_str() );
}
cout << wrdCount << " Words read from the file " << endl;
inFile2.close();
break;
case'B':
case'b':
getFirstLetter = g_wordArray[wrdCount]->GetFirstLetterLower();
//getWord = g_wordArray[wrdCount]->GetWord();
cout << getWord << endl;
break;
case'C':
case'c':
break;
case'D':
case'd':
cout << "Quit Requested. " << endl;
break;
default:
cout << '"' << usrMenuOption << '"' << " Not Defined! " << endl;
}
} while (usrMenuOption != 'D' && usrMenuOption != 'd');
#ifdef _DEBUG
_CrtDumpMemoryLeaks();
#endif
cin.ignore();
return 0;
}
void FreeWordArray()
{
// free the memory that is allocated
return;
}
EDIT: I've put this edit at the top since it directly answers your question of why Word is broken. Your copy constructor is wrong:
Word::Word( const Word* theObject )
{
ptr_ = theObject->ptr_;
len_ = theObject->len_;
}
this doesn't make a copy of what theObject->ptr_ points to, just the pointer. So you effectively have two Word objects pointing to the same internal string. This gets very bad when a Word object gets deleted. A proper implementation (using the techniques you did, I recomend against them) would be like this:
Word::Word( const Word* theObject )
{
ptr_ = new char[theObject->len_ + 1 ];
strcpy( ptr_, theObject->ptr_ );
len_ = theObject->len_;
}
EDIT: Earwicker noted the following as well:
... although that "copy constructor"
is not a copy constructor. So the
compiler-generated one will still
exist, and does the same memberwise
copy, and therefore the same problem
still exists.
To remedy that, you'll need to make a proper copy constructor which should have the prototype:
Word::Word(const Word &theObject);
Also this code here:
while ( !inFile.eof() )
{
inFile >> str;
wrdCount++;
g_wordArray = new Word *[wrdCount];
}
leaks like sieve! You reallocate g_wordArray after every word is read and entirely forget to delete the previous one. Once again I'll show a sane implementation using the techniques you are attempting to use.
while (inFile >> str)
{
inFile >> str;
wrdCount++;
}
g_wordArray = new Word *[wrdCount];
Notice how it counts the words, then allocates room once, after it knows how much to allocate. Now g_wordArray is ready to be used for up to wrdCount word objects.
ORIGINAL ANSWER:
why don't you just replace the Word class with std::string? It would make the code much smaller and easier to work with.
If it makes it easier for you, just do this:
typedef std::string Word;
then you can do like this:
Word word("hello");
char first_char = word[0];
plus it has the added bonus of removing the need for you to use the .c_str() member to get the c-style string.
EDIT:
I would also change your g_wordArray to just be a std::vector<Word>. That way you can simple do this:
g_wordArray.push_back(Word(str));
no more dynamic allocation! it's all done for you. Size of the word array will be limited only by the amount of RAM you have, because std::vector grows as needed when you use push_back().
Also, if you do this...guess what to get the word count you simply do this:
g_wordArray.size();
no need to manually keep track of the number of them!
EDIT:
Also, this code is broken:
while( !inFile2.eof() )
{
inFile2 >> str2;
...
}
because eof isn't set till after you attempt a read, you are better off using this pattern:
while(inFile2 >> str2)
{
...
}
which will correctly stop on EOF.
Bottom line, if you do this right, there should be very little actual code you need to write.
EDIT:
Here's a sample straight forward implementation of what I think you want. From the menu items it seems that the intention is for the user to choose option 'a' first, then 'b' zero or more times to filter out some words, then finally c to print the results (one word per line). Also, option 'D' isn't really needed since hitting Ctrl+D sends an EOF to the program and makes the "while(std::cin >> option)" test fail. Thus ending the program. (At least in my OS, it might be Ctrl+Z` for windows).
Also it makes no effort (neither did yours) to deal with punctuation, but here it is:
#include <string>
#include <vector>
#include <fstream>
#include <iostream>
#include <algorithm>
#include <functional>
#include <iterator>
struct starts_with : public std::binary_function<std::string, char, bool> {
bool operator()(const std::string &s, char ch) const {
return s[0] == ch;
}
};
void print_prompt() {
std::cout << "Please make a selection: \na) Read a text file\nb) Remove words starting with letter\nc) Print words to console" << std::endl;
}
int main( const int argc, const char **argv) {
std::vector<std::string> file_words;
char option;
print_prompt();
while(std::cin >> option) {
switch(option) {
case 'a':
case 'A':
std::cout << "Enter a file name: ";
// scope so we can have locals declared
{
std::string filename;
std::string word;
std::cin >> filename;
int word_count = 0;
std::ifstream file(filename.c_str());
while(file >> word) {
file_words.push_back(word);
}
std::cout << file_words.size() << " Words read from the file " << std::endl;
}
break;
case 'b':
case 'B':
// scope so we can have locals declared
{
std::cout << "Enter letter to filter: ";
char letter;
std::cin >> letter;
// remove all words starting with a certain char
file_words.erase(std::remove_if(file_words.begin(), file_words.end(), std::bind2nd(starts_with(), letter)), file_words.end());
}
break;
case 'c':
case 'C':
// output each word to std::cout separated by newlines
std::copy(file_words.begin(), file_words.end(), std::ostream_iterator<std::string>(std::cout, "\n"));
break;
}
print_prompt();
}
return 0;
}
Without slogging through the entirety of your main.cpp, I did notice a problem here in your constructor/destructor set:
Word::Word( const Word* theObject )
{
ptr_ = theObject->ptr_;
len_ = theObject->len_;
}
Word::~Word()
{
delete [] ptr_;
ptr_ = NULL;
}
Notice that in your copy constructor, you simply assign your internal pointers to match the object you're copying from. But in your destructor, you delete data at your internal pointers.
Here's where that can get ulgy:
Word w1 = Word("hello");
Word w2 = Word(w1);
delete w2;
Now, what does your first word contain? The pointer will exist, but the data it references was deleted in w2's destructor.
They call it a "copy constructor" because that's what you're supposed to do: copy the thing.
Is g_wordArray[wrdCount] a valid Word object? My C++ is rusty, but it looks to me like this is beyond the end of the array. Also, you appear to be repeatedly trampling over the contents of g_wordArray in the inFile2 loop: for each string you read into str2, you're going to reinitialise the first str2.length() entries in g_wordArray. That would explain the apparent clobbering of the member variables. Finally, I'm not sure about this, but is the pointer returned by str2.c_str() still valid after you've read a new value into str2? So your GetWord() loop may be getting a garbage value from strlen and trundling off into never never land.