36

As the title said, I'm curious if there is a way to read a C++ string with scanf.

I know that I can read each char and insert it in the deserved string, but I'd want something like:

string a;
scanf("%SOMETHING", &a);

gets() also doesn't work.

Thanks in advance!

Daniel Trugman
  • 8,186
  • 20
  • 41
Vlad Tarniceru
  • 711
  • 1
  • 8
  • 15

7 Answers7

39

this can work

char tmp[101];
scanf("%100s", tmp);
string a = tmp;
Patato
  • 1,464
  • 10
  • 12
  • 5
    I didn't know that I can initialize a C++ string with a char[] .. thanks a lot! :) – Vlad Tarniceru Nov 23 '13 at 18:12
  • 5
    It can work. It can also have undefined behavior because the contents of `tmp` may be uninitialized here. Never use `scanf` without checking its return value. – melpomene May 08 '19 at 13:11
  • But it is chopping off the first character of string in my case. I have done something like this :- ``` string x; scanf("%s", &x);``` – 0xB00B Feb 22 '21 at 16:40
25

There is no situation under which gets() is to be used! It is always wrong to use gets() and it is removed from C11 and being removed from C++14.

scanf() doens't support any C++ classes. However, you can store the result from scanf() into a std::string:

Editor's note: The following code is wrong, as explained in the comments. See the answers by Patato, tom, and Daniel Trugman for correct approaches.

std::string str(100, ' ');
if (1 == scanf("%*s", &str[0], str.size())) {
    // ...
}

I'm not entirely sure about the way to specify that buffer length in scanf() and in which order the parameters go (there is a chance that the parameters &str[0] and str.size() need to be reversed and I may be missing a . in the format string). Note that the resulting std::string will contain a terminating null character and it won't have changed its size.

Of course, I would just use if (std::cin >> str) { ... } but that's a different question.

tom
  • 21,844
  • 6
  • 43
  • 36
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Are you sure your code is compiling? I get compilation error using both C++98 and C++11 – Vlad Tarniceru Nov 23 '13 at 18:19
  • @Vlad: I'd think the code should compile assuming you included `` and `` and sits within a function, of course. What error messages do you get? – Dietmar Kühl Nov 23 '13 at 18:21
  • @Vlad: OK, I see: seems `std::string` doesn't declare a constructor taking just a size. Using `std::string str(100, ' ');` fixes this problem. – Dietmar Kühl Nov 23 '13 at 18:26
  • At compile time one cannot safely specify 100, or any other finite number, to limit the length of what scanf will write into `%*s`. +Patato's answer is safer. Google "buffer overflow". – Camille Goudeseune Dec 23 '14 at 03:35
  • 2
    It should be `scanf("%99s", &str[0]))`, as `scanf()` does not allow to specify the field width during run-time using the `*`, as `printf()` does. Also `scanf()` suffixes what had been read in with the `0`-terminator, so one needs to read in one few than `str`'s size. – alk Oct 14 '15 at 15:53
  • 6
    For `scanf`, the `*` *supresses* assignment, it does not mean to take the next argument as the field width. – dreamlax May 04 '16 at 06:10
  • 4
    "*I'm not entirely sure about the way to specify that buffer lenght in scanf() and in which order the parameters go*" - Easy: You can't do it at all. `scanf` does not support length parameters. This code does not work. – melpomene May 08 '19 at 13:09
5

Problem explained:

You CAN populate the underlying buffer of an std::string using scanf, but(!) the managed std::string object will NOT be aware of the change.

const char *line="Daniel 1337"; // The line we're gonna parse

std::string token;
token.reserve(64); // You should always make sure the buffer is big enough

sscanf(line, "%s %*u", token.data());
std::cout << "Managed string: '" << token
          << " (size = " << token.size() << ")" << std::endl;
std::cout << "Underlying buffer: " << token.data()
          << " (size = " << strlen(token.data()) << ")" << std::endl;

Outputs:

Managed string:  (size = 0)
Underlying buffer: Daniel (size = 6)

So, what happened here? The object std::string is not aware of changes not performed through the exported, official, API.

When we write to the object through the underlying buffer, the data changes, but the string object is not aware of that.

If we were to replace the original call: token.reseve(64) with token.resize(64), a call that changes the size of the managed string, the results would've been different:

const char *line="Daniel 1337"; // The line we're gonna parse

std::string token;
token.resize(64); // You should always make sure the buffer is big enough

sscanf(line, "%s %*u", token.data());
std::cout << "Managed string: " << token
          << " (size = " << token.size() << ")" << std::endl;
std::cout << "Underlying buffer: " << token.data()
          << " (size = " << strlen(token.data()) << ")" << std::endl;

Outputs:

Managed string: Daniel (size = 64)
Underlying buffer: Daniel (size = 6)

Once again, the result is sub-optimal. The output is correct, but the size isn't.

Solution:

If you really want to make do this, follow these steps:

  1. Call resize to make sure your buffer is big enough. Use a #define for the maximal length (see step 2 to understand why):
std::string buffer;
buffer.resize(MAX_TOKEN_LENGTH);
  1. Use scanf while limiting the size of the scanned string using "width modifiers" and check the return value (return value is the number of tokens scanned):
#define XSTR(__x) STR(__x)
#define STR(__x) #x
...
int rv = scanf("%" XSTR(MAX_TOKEN_LENGTH) "s", &buffer[0]);
  1. Reset the managed string size to the actual size in a safe manner:
buffer.resize(strnlen(buffer.data(), MAX_TOKEN_LENGTH));
Daniel Trugman
  • 8,186
  • 20
  • 41
  • Good catch, I've fixed this bug in my answer. Some small issues in your answer: concatenation of side-by-side string literals doesn't work with integers, so `"%" MAX_TOKEN_LENGTH "s"` is a syntax error. This can be fixed using [stringification](https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html): write `#define STR0(x) #x` and `#define STR(x) STR0(x)` then use `"%" STR(MAX_TOKEN_LENGTH) "s"`. Also, the return type of string::data() is `const char*` which shouldn't be modified, so use `&buffer[0]` instead. – tom Oct 17 '20 at 11:02
3

The below snippet works

string s(100, '\0');
scanf("%s", s.c_str());
1

You can construct an std::string of an appropriate size and read into its underlying character storage:

std::string str(100, ' ');
scanf("%100s", &str[0]);
str.resize(strlen(str.c_str()));

The call to str.resize() is critical, otherwise the length of the std::string object will not be updated. Thanks to Daniel Trugman for pointing this out.

(There is no off-by-one error with the size reserved for the string versus the width passed to scanf, because since C++11 it is guaranteed that the character data of std::string is followed by a null terminator so there is room for size+1 characters.)

tom
  • 21,844
  • 6
  • 43
  • 36
1

Here a version without limit of length (in case of the length of the input is unknown).

std::string read_string() {
  std::string s; unsigned int uc; int c;
  // ASCII code of space is 32, and all code less or equal than 32 are invisible.
  // For EOF, a negative, will be large than 32 after unsigned conversion
  while ((uc = (unsigned int)getchar()) <= 32u);
  if (uc < 256u) s.push_back((char)uc);
  while ((c = getchar()) > 32) s.push_back((char)c);
  return s;
}

For performance consideration, getchar is definitely faster than scanf, and std::string::reserve could pre-allocate buffers to prevent frequent reallocation.

XQY
  • 552
  • 4
  • 17
0
int n=15; // you are going to scan no more than n symbols
std::string str(n+1); //you can't scan more than string contains minus 1
scanf("%s",str.begin()); // scanf only changes content of string like it's array
str=str.c_str() //make string normal, you'll have lots of problems without this string