working with c_str()

i'm doing an exercise that wants me to use c_str with a vector<char*/>
it seems these two things don't work together, c_str seems to only work with string types but the exercise wants me to use char*. here is the exercise problem:

The program in Exercise •• P7.3 is limited by the fact that it can only handle inputs of 1,000 characters or 100 lines. Remove this limitation as follows. Concatenate the input in one long string object. Use the c_str member function to obtain a char* into the string’s character buffer. Store the beginnings of the lines as a vector<char*>. (Horstmann, 2017-08-12, p. EX7-3)


i will post the P7.3 exercise at the bottom. but here's my code so far (work in progress, not the prettiest looking):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include <iostream>
#include <vector>
#include <cstring>
#include <string>
using namespace std;

int main()
{
	char ch;
	string buffer = "";
	bool run = true;
	vector<char*> lines;
	while (run)
	{
		cin.get(ch);
		if (ch == '\n')
		{
			ch = '\0';
		}
		if (ch == ';')
		{
			run = false;
		}
		else
		{
			buffer = buffer + ch;
		}
	}
	int pos = 0;
	cout << buffer.c_str();
	buffer.c_str();
	lines.push_back(&buffer);
/*	for (int i = 0; i < buffer.length(); i++)
	{
		if (buffer.substr(i, 1) == "\n")
		{
			pos = i + 1;

			cout << pos;
			cout << buffer.c_str() + pos;
			cout << endl;
		}
	}*/



	return 0;

}


Here is Exercise •• P7.3

Write a program that reads lines of text and appends them to a char buffer[1000]. Read one character at a time by calling cin.get(ch), where ch is a variable of type char. Use input redirection (Special Topic 4.3). Stop after reading 1,000 characters. As you read in the text, replace all newline characters '\n' with '\0' terminators. Establish an array char* lines[100], so that the pointers in that array point to the beginnings of the lines in the text. Consider only 100 input lines if the input has more lines. Then display the lines in reverse order, starting with the last input line. (Horstmann, 2017-08-12, p. EX7-3)
it seems these two things don't work together, c_str seems to only work with string types but the exercise wants me to use char*. here is the exercise problem:

c_str provides a char* (I think it is const char*, but that aside).
so you can say
vector<char*> vcp(100);
vcp[0] = somestring.c_str();
and at most you may need a cast to make it happy about the const problem.

c_str() is only available for string, yes. If you need something else converted to a c-string you can use sprintf for 99% of it. I don't know what it would be, but that would do it.

--- this may test your understanding but this assignment is really pfd / messed up thing to ask someone to do.
** end of line is 2 chars on some systems, 10 and 13, on others just 1. reading one char at a time may make this weird so be warned.

you can also take a char * from a string object:
char* foo = &(stringvar[12])
but be warned: if stringvar changes, the pointers is invalidated.
you can also add 0 char to string objects to make char* into them work nicer. They do not have any 0s naturally, but you can inject them. this isnt something anyone sane would do normally, but I believe it would make your work easier for this problem.
Last edited on
i'm doing an exercise that wants me to use c_str with a vector<char*/>
I don't think it's asking you to do that. The text says:
Concatenate the input in one long string object. Use the c_str member ...


it seems these two things don't work together, c_str
Yes, you're correct. std::vector<> doesn't have a .c_str() method, but std::string does.

The text is wrong in that c_str() returns a const char*. In C++, that's quite different from a char*.
Last edited on
it does still want a line by line via char*.
that is the weird part. it should have him make a vector of string.
even if its a backwards compatible thing, to get a vector of char*, if you stored them as a string to a line (vector of string), you can take c_str() (from each vector now) and populate the line pointers, that would be clean. But it appears to be (maybe its just how I am reading it) wanting him to split the line from ONE string into many via pointer offsets, which sounds like something *I* would do but not anything desirable.

Does any of this help or even make sense to you?
Last edited on
so i don't think this is how the book wanted me to do the exercise, but here's how i did it lol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <iostream>
#include <vector>
#include <cstring>
#include <string>
using namespace std;

int main()
{
	char ch;
	string buffer = "";
	bool run = true;
	vector<char*> lines;
	vector<int> positions;
	while (run)
	{
		cin.get(ch);
		if (ch == '\n')
		{
			ch = '\0';
		}
		if (ch == ';')
		{
			run = false;
		}
		else
		{
			buffer = buffer + ch;
		}
	}

	
	bool search = true;
	lines.push_back((char*)buffer.c_str());
	while (search)
	{
		int pos = buffer.find('\0');
		if (pos == -1)
		{
			search = false;
		}
		else
		{
			buffer[pos] = '0';
			positions.push_back(pos);
		}
	}
	cout << endl;
	for (int i = 0; i < positions.size(); i++)
	{
		lines.push_back((char*)buffer.c_str() + positions[i] + 1);

		
	}

	for (int i = 0; i < positions.size(); i++)
	{
		buffer[positions[i]] = '\0';
	}

	for (int i = lines.size() - 1; i >= 0; i--)
	{
		cout << lines[i];
		cout << endl;
	}


	return 0;

}
For the first exercise I read it (from the book itself) that you read the lines in from a text file (indirection blah blah). (Reverse order is no sweat, not done here.)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <fstream>
#include <iomanip>

#include <string>

int main()
{
    // SOURCE TEXT FILE
    std::ifstream infile ("p7_3.txt"); // OUTPUT SEE BELOW
    if (!infile.is_open())
    {
        std::cout << "Unable to open file\n";
        exit(1);
    }
    
    
    size_t buffer_size{1000};
    
    char ch;
    char* buffer = new char[buffer_size];
    char* lines[100];
    
    size_t char_count{0};
    size_t line_count{0};
    
    // READ IN LINES
    lines[line_count] = &buffer[char_count];
    while( char_count < buffer_size and infile >> std::noskipws >> ch )
    {
        if(ch == '\n')
        {
            ch = '\0';
            lines[line_count] = &buffer[char_count + 1];
            line_count++;
        }
        buffer[char_count] = ch;
        char_count++;
    }
    line_count--; // ADJUSTMENT
    
    std::cout << "Line count: " << line_count << '\n';
    
    // DISPLAY ALL LINES
    for(size_t i = 0; i < line_count; i++)
    {
        std::cout
        << "line: "
        << std::setw(2) << std::right << i + 1
        << lines[i] << '\n';
    }
    std::cout << '\n';
    
    return 0;
}



Line count: 13
line:  1 Write a program that reads lines of text and appends them to a
line:  2 char buffer[1000].
line:  3 
line:  4 Stop after reading 1,000 characters.
line:  5 
line:  6 As you read in the text, replace all newline characters '\n' with '\0'
line:  7 terminators.
line:  8 
line:  9 Establish an array char* lines[100], so that the pointers in that array point
line: 10 to the beginnings of the lines in the text. Consider only 100 input lines if
line: 11 the input has more lines.
line: 12 
line: 13 Then display the lines in reverse order, starting with the last input line.
1
2
3
4
5
6
buffer[pos] = '0';
//...
	for (int i = 0; i < positions.size(); i++)
	{
		buffer[positions[i]] = '\0';
	}
¿what's the point of that? ¿why do you set it from '\0' to '0' and then back to '\0'?
apart from that, it's good, although you may do it without the `positions' vector


one thing to notice, if you insert more characters into `buffer' after filling the `lines' vector, then all the pointers may become invalid.


@againtry: char* buffer = new char[buffer_size];
¿why dynamic allocation?
¿why you don't deallocate?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
#include <fstream>
#include <iomanip>

#include <string>
#include <vector>

int main()
{
    // SOURCE TEXT FILE
    std::ifstream infile ("p7_14.txt");
    if (!infile.is_open())
    {
        std::cout << "Unable to open file\n";
        exit(1);
    }
    
    std::string str_continuous;
    std::vector<const char*> lines;
    
    // READ IN LINES
    char ch;
    while( infile >> std::noskipws >> ch )
    {
        if(ch == '\n')
        {
            ch = '\0';
        }
        str_continuous += ch;
    }
    
    const char* b = str_continuous.c_str();
    
    lines.push_back( &b[0] );
    for(int i = 0; i < str_continuous.length(); i++)
    {
        if(b[i] == '\0')
        {
            lines.push_back( &b[i + 1] );
        }
    }
    lines.pop_back(); // ADJUSTMENT
    
    // FORWARD LIST
    size_t line_count{0};
    for (auto i: lines)
    {
        std::cout
        << "line: "
        << std::setw(2) << std::right << line_count
        << i << '\n';
        
        line_count++;
    }
    
    return 0;
}


line:  0 P7.14
line:  1 The program in Exercise P7.13 is limited by the fact that it can only handle
line:  2 inputs of 1,000 characters or 100 lines. Remove this limitation as follows.
line:  3 
line:  4 Concatenate the input in one long string object.
line:  5 
line:  6 Use the c_str member function to obtain a char* into the string’s character
line:  7 buffer.
line:  8 
line:  9 Store the beginnings of the lines as a vector<char*>.
Program ended with exit code: 0
It's very important to understand that a string can move it's data to a new location any time it changes. So pointers into c_str() are only valid until the string changes. Your code handles this, but I wanted to make sure you know that you're relying on that fact.

Looking at your latest code:
Line 21: why stop at a semicolon? Shouldn't you read the whole input?
Line 36: You're always searching from the beginning, so you'll always find the first null character, not the next one.
Line 37: string::npos instead of -1. Also, pos should be type size_t.
Line 43: Buffer[npos] is guaranteed to be '\0' at this point, so no need to set it.
Line 44: You're pushing the address of the null character. You want to push the address of the next character.
Lines 48-53: why not do this inside the previous loop instead. That way there's no need for the positions vector at all.
Lines 55-58: The value s are already '\0' so no need to set them again.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <vector>
#include <cstring>
#include <string>
using namespace std;

int main()
{
	char ch;
	string buffer = "";
	vector<const char*> lines; // because string::c_str() returns const char *

	// Read the data into buffer.
	while (cin.get(ch))
	{
		if (ch == '\n')
		{
			ch = '\0';
		}
		buffer += ch;
	}

	
	// Separate into lines
	size_t pos = 0;
	while (true) {
	    lines.push_back(buffer.c_str()+pos);
	    pos = buffer.find('\0', pos+1);
	    // sometimes it's clearest to break out from the middle of a loop
	    if (pos == string::npos || pos == buffer.size()-1) break; 
	    ++pos;
	}
	

	// Print the lines
	for (int i = lines.size() - 1; i >= 0; i--)
	{
		cout << lines[i];
		cout << endl;
	}


	return 0;
}

Line 37: string::npos instead of -1. Also, pos should be type size_t.
I have seen a fair number of online examples doing this. Its works on most compilers, but not all; I had some trouble with it and stopped doing it.
@againtry: char* buffer = new char[buffer_size];
¿why dynamic allocation?

Just for fun. No special reason at all.

¿why you don't deallocate?

Should have but forgot. Probably because I was having so much fun :)
@jonnin
(I think it is const char*, but that aside)

Just saw your comment - correct weight - doesn't 'work' otherwise
Topic archived. No new replies allowed.