#include<iostream>
using namespace std;
int main()
{
int n;
cin>>n;
cin.ignore();
char arr[n 1];
cin.getline(arr,n);
cin.ignore();
cout<<arr;
return 0;
}
Input:
11
of the year
Output:
of the yea
I'm already providing n 1 for the null character. Then why is the last character getting excluded?
CodePudding user response:
You allocated n 1
characters for your array, but then you told getline
that there were only n
characters available. It should be like this:
int n;
cin>>n;
cin.ignore();
char arr[n 1];
cin.getline(arr,n 1); // change here
cin.ignore();
cout<<arr;
CodePudding user response:
Per cppreference.com:
https://en.cppreference.com/w/cpp/io/basic_istream/getline
Behaves as UnformattedInputFunction. After constructing and checking the sentry object, extracts characters from
*this
and stores them in successive locations of the array whose first element is pointed to bys
, until any of the following occurs (tested in the order shown):
- end of file condition occurs in the input sequence (in which case
setstate(eofbit)
is executed)- the next available character
c
is the delimiter, as determined byTraits::eq(c, delim)
. The delimiter is extracted (unlikebasic_istream::get()
) and counted towardsgcount()
, but is not stored.count-1
characters have been extracted (in which casesetstate(failbit)
is executed).If the function extracts no characters (e.g. if
count < 1)
,setstate(failbit)
is executed.In any case, if
count > 0
, it then stores a null character CharT() into the next successive location of the array and updatesgcount()
.
In your case, n=11
. You are allocating n 1
(12) chars, but telling getline()
that only n
(11) chars are available, so it reads only n-1
(10) chars into the array and then terminates the array with '\0'
in the 11th char. That is why you are missing the last character.
of the year
^
10th char, stops here
You need to 1
when calling getline()
, to match your actual array size:
cin.getline(arr,n 1);
CodePudding user response:
john's answer should fix your issue. Variable-length arrays (your char arr[n 1]
) are not part of the C standard, for justified reasons. Yet I've taken a few hours of my time to go way out of question's scope and create the...
Student's guide to C I/O
...and I/O in general, with an emphasis on the I part. Fear not, do it the C way! The following snippets should be compiled with a standard-conforming C compiler.
C I/O & standard library
Textual input
This is the recommended way of reading UTF-8 encoded strings in C , the most widespread text encoding. We will use std::string
for storage, which is the de-facto way for holding UTF-8 encoded strings, and std::getline
for the reading itself.
#include <iostream> // std::cin, std::cout, std::ws
#include <string> // std::string, std::getline
int main() {
int size;
// std::ws ignores all whitespace in the stream,
// until the first non-whitespace character.
// it's prettier and handles cases a simple .ignore() does not.
std::cin >> size >> std::ws;
std::string input;
std::getline(std::cin, input);
// This condition will most certainly be true (output will be 1).
std::cout << (size == input.size()) << '\n';
}
std::string
is dynamically allocated, or, as you may hear, on the heap. This is a broad subject, so feel free to venture on your own, from this given starting point! How does this help us? We can store strings of sizes unknown ahead of time on the heap, because we can always reallocate a bigger buffer! std::getline
allocates and reallocates as it reads the input until newline is reached, so you can read without knowing a size
beforehand. Your size
variable will most probably be equal with the size of the string under the assumption that this is a school exercise where the input length is provided as you're probably not taught of dynamic memory. For good reason, though - it's complex and would needlessly distract from the actual subject (algorithms, data structures etc.). Good to keep in mind: std::string
s, unlike C-style strings, are not null terminated, but you can get a null-terminated C-style string from an std::string
by calling the .c_str()
method.
Binary data
What's binary data? Everything that's not text: images, videos, music, 2003 MS Word documents (the .doc
ones, wait 'til you see what .docx
is) and many others. It's customary to store binary as raw bytes, which is a fancy way to say numbers. unsigned char
is the C/C type used to represent these raw bytes (C 17 introduces std::byte
for this purpose. To work with data from binary input we need to store it somewhere in memory - either on the stack, or on the heap. We could store the whole input at once, but binary files are considered too large for this (and, really, are - think about the size of a movie!), so we usually read it in chunks - that means, we read only a finite part of it at a time (say 256 characters, that's our buffer), and we keep reading until we reach the end of the input (usually called end-of-file or, short, EOF). As a rule of thumb, when a buffer is small and static (doesn't need to be resized, as our string above), we can store it on the stack. If any of those conditions is not met, it goes on the heap. We should note that the notions of small and large are quite context dependent - compiler, OS, hardware, runtime environment (see this thread on stack size limits and embedded systems). The buffer size you'll choose is also task-specific, so there's no rule here, too. Let's see some code now!
#include <array> // std::array
#include <fstream> // std::ifstream, std::ofstream
int main() {
// We open this file in binary mode.
// The default mode may modify the input.
std::ifstream input{"some_image.jpg", std::ios::binary};
// 256 is our buffer size, unsigned char is the array type.
// This is the C way of `unsigned char buffer[256]`.
std::array<unsigned char, 256> buffer;
while (input.read(buffer.data(), buffer.size())) {
// Buffer is filled, do something with it
}
// At this point, either EOF is reached or an error occurred.
if (input.eof()) {
// Less characters than the buffer's size have been read.
// .gcount() returns the number of characters read by
// the last operation.
const std::streamsize chunk_size = input.gcount();
// Do something with these characters, as in the loop.
// Valid range to access in the buffer is [0, chunk_size).
// chunk_size can be 0, too. In that case, there is no more data
// to handle.
} else {
// Some other failure, handle error.
}
}
This snippet is reading through a file using a small, stack-allocated buffer of 256 bytes. std::array
makes usage convenient and safe with its methods - read the linked docs! If we want to use a large buffer (say, 16MB), we replace the std::array
with an std::vector
:
std::vector buffer(1 << 24); // 1 << 24 gives 16MB in bytes
Rest is the same. You could also use std::string
here, too, as std::string
does not imply/force UTF-8 encoding of input. It's useful to have a convention that easily differentiates between binary and text data, in code.
Something to note is that reading in smaller chunks uses less space, but takes more time - taking bytes from a file involves making OS system calls and moving disks or electrons, when reading from a hard drive or an SSD, respectively. C 's fstream
objects already do buffering for you to speed up reads, which is usually a much-needed optimization. You'll know if this affects you.
Another thing to note is the EOF
and error handling, using the .eof()
method. We have omitted error handling in the textual input retrieval, but here we are forced into doing it, if we don't want to lose data. When EOF
is reached, usually less bytes than the buffer size have been read, so we need a way to know how much of the buffer was filled with data. This is what .gcount()
tells us. Depending on the program you're making, you may deem the EOF
error as "unexpected" if the buffer is partially filled (.gcount()
returns a non-0 value) - for example, the data read is incomplete, according to the rules it was assumed to be created after, or in other words, the end of file was reached before the data was supposed to end. Other than that, EOF
is a condition that all files are in after being fully read.
C-style I/O
This may look closer to what's taught in school. As we've explained the general concepts above, this section will be richer in coding and explanations of code. We still use C as a language, so the C version of the C headers and the std
namespace will be used - to have the code that follows work in a C compiler, replace the <csomething>
headers with <something.h>
and remove the std::
namespace prefix from types and functions. Let's dive into it!
Textual input
The equivalent of a C stream (std::cin, std::fstream etc.) in C is the std::FILE
. FILE
s are buffered by default, as are C streams. We'll use std::fscanf
for reading the size of the input, which is just scanf
but it takes as parameter the stream you read from, and std::fgets
for reading the text line.
#include <cstdio> // std::FILE, std::fscanf, std::fgets, stdin
#include <cstring> // std::strcspn
// discard_whitespace does what std::ws did above.
// It consumes all whitespace before a non-whitespace
// character from stream f.
void discard_whitespace(std::FILE* f) {
char discard;
// The leading space in the format string
// tells fscanf to consume all whitespace.
std::fscanf(f, " %c", &discard);
}
int main() {
int size;
// stdin is a macro, doesn't have a namespace,
// hence no std:: prefix.
std::fscanf(stdin, "%d", &size);
// fscanf, like std::cin, doesn't consume whitespace
discard_whitespace(stdin);
// Your school exercise will probably have a size limit for the input.
// We consider it to be 256.
const int SIZE_UPPER_BOUND = 256;
// We add some extra bytes so the maximum length input can be accomodated.
// 1 is added for the null terminator of C-style strings.
// The other 2 is because `fgets` will also read the newline,
// which can be \n or \r\n, depending on OS. See explanation after code.
char input[SIZE_UPPER_BOUND 3];
// The actual read - sizeof gets the size of our input buffer,
// we don't have to write it twice.
std::fgets(input, sizeof input, stdin);
// fgets also reads the newline, unlike `std::getline` or
// `std::cin.getline` - we have to remove it ourselves.
input[std::strcspn(input, "\r\n")] = '\0';
// This condition will be true, as in the C example.
std::fprintf(stdin, "%d\n", std::strlen(input) == size);
}
Let's unpack that newline removal. std::strcspn
finds the first position of any of the given characters in the input. We provide both \r
and \n
, to support UNIX (\n
) and Windows (\r\n
) newline terminators - yeah, they're different, see Wikipedia, on "Newline". By adding the null terminator, '\0', we move the ending of the string where the newline was, basically "removing" the newline. If this is a school assignment, we can assume input is correct, so we could have used size 1
instead of std::strcspn
to remove the newline:
input[size 1] = '\0';
This doesn't work when we don't know the input size or the input may be invalid.
As an optimization trick, observe that std::strcspn
returns the line length, in this case. When you don't know the size, but you need it for later, you can save the result of std::strcspn
in a variable before, and then use it instead of std::strlen
:
// std::size_t is an unsigned integral type, used to represent
// array sizes and indexes in C/C
const std::size_t input_size = std::strcspn(input, "\r\n");
input[input_size] = '\0';
You'll see some people use 0
or NULL
for the terminator. I recommend against it - unlike the \0
literal, that is of char
type, the other two variants are implicitly casted to char
. If you read the linked documentation, you'll realize NULL
is even incorrect, according to the spec, as it's meant to be used in contexts that require pointers only.
An alternative method to fgets
is fscanf
, again. Thread carefully, though - while a simple %s
may do it, it makes your code vulnerable to buffer overflow exploits. See this StackOverflow thread on disadvantages of scanf, too. Let's see the (safe) code:
std::fscanf(stdin, "%6[^\r\n]s", input);
That number limits the input size to our SIZE_UPPER_BOUND
, and the [^\r\n]
tells fscanf
to read all characters up to \r
or \n
. With this method you can remove the discard_whitespace
call, as fscanf
with the %s
verb consumes leading whitespace. A downside to fscanf
is that you have to keep the size limit in the input string and the buffer size in sync - you have no way to specify the input size dynamically other than building the format string dynamically, which is overkill for a school assignment). This is a problem in more sizable codebases, but for a one-file, one-time school assignment it's not a big deal, so you may prefer fscanf
over fgets
, as it's less work. fscanf
doesn't read the newline in the buffer, too.
Binary data
The equivalent of C 's std::cin.read
in C world is std::fread
. Code will resemble its C counterpart:
#include <cstdio>
int main() {
// The second parameter is the file access mode.
// In this case, it is read (r) binary (b).
std::FILE* f = std::fopen("some_image.jpg", "rb");
unsigned char buffer[256];
std::size_t chunk_size;
while (chunk_size = std::fread(buffer, sizeof buffer[0], sizeof buffer, f)) {
// chunk_size == sizeof buffer, do something with the buffer
}
if (std::feof(f)) {
// chunk_size != sizeof buffer, do something with buffer
// or handle as error
} else {
// an error occurred, handle it
}
// We need to close the file, unlike in C , where it is closed automatically.
std::fclose(f);
}
The arguments to std::fread
are hairy: read the documentation. Everything else looks very similar to the C way, from the loop to the error handling. Why? Because it's literally the same thing - we're just using different (standard) libraries. Another similarity is that C I/O is also buffered by default, just like C 's. What's different is the line at the end - the call to std::fclose
. We're not doing anything similar in the C code, right? No. Remember that C classes have constructors and destructors, functions that are automatically called at the beginning, respectively at the end of a variable's lifetime. These two allow us to implement the RAII technique, which will do the resource management automatically (opening the file in the constructor, closing it in the destructor). RAII is used inside std::string
and std::vector
(and other containers, smart pointers & others). In other words, the destructor of std::ifstream
closes the file at the end of main()
, just as we are doing here, manually.
Hybrid approach (??)
Would you ever want to combine the two? So it seems. Let's talk drawbacks:
- The C I/O library, due to the way it's built, takes more care to use in a performant manner compared to C's (
virtual
function calls and extra function calls in general, especially when using<<
and>>
operators & stream manipulators, as each of these is a function call, compared to a single plain function call/operation with the C library). See this StackOverflow thread on i/ostream speed, too. The C library is also more verbose, especially in the case of outputting (ever heard of the "chevrone hell"?) - The C I/O library is easy to use improperly/unsafely, the terse, shorthand namings make code difficult to follow, and output cannot be extended to support custom types (this is a problem when using C-style I/O in C ). It also takes great care to handle dynamic buffers correctly, given that the only way of managing heap memory in C is
malloc
andfree
. - Some schools may crucify you if any trace of
std::string
is left in sight (or so I've heard) - Using C-style types (
char[N]
instead ofstd::array<char, N>
, for example), is easier - no headers to include, as the types are builtin primitives and less to type. May be preferred in short, throwaway programs like algorithmic exercises at school.
With these in mind, we can take a look at how to conveniently combine the two when reading text and binary!
Textual input
We will take advantage of the terseness of C-style types and the ease of use of C 's I/O library:
#include <iostream>
int main() {
int size;
std::cin >> size >> std::ws;
const int SIZE_UPPER_BOUND = 256;
char input[SIZE_UPPER_BOUND 1];
std::cin.getline(input, sizeof input);
// Input done, solve the problem.
}
Teachers don't have to scratch their head at the presence of std::string
and std::getline
and all the standard library shenanigans you start using after diving in this rabbit hole. You, the programmer, don't have to clean up newline endings and memorize arcane format specifiers just to read a string and an int. Focus on code and solve problems without having to debug the input reading logic, ever - it just works!
Binary data
The convoluted hierarchical tree of C 's I/O library types scares you, the clean assembly output enjoyer, just like Linus Torvalds. You're still somehow afraid to manually manage memory, so you choose this solution:
#include <cstdio>
#include <vector>
int main() {
// The second parameter is the file access mode.
// In this case, it is read (r) binary (b).
std::FILE* f = std::fopen("some_image.jpg", "rb");
std::vector<unsigned char> buffer(1 << 24);
std::size_t chunk_size;
while (chunk_size = std::fread(buffer.data(), sizeof buffer[0], buffer.size(), f)) {
// use the buffer
}
if (std::feof(f)) {
// handle EOF
} else {
// handle error
}
std::fclose(f);
}
Weird choice, given that you still manage the file's lifetime manually. While this may not be the best example, using C RAII containers together with C libraries is not uncommon - memory safety is crucial.
Trivia
- as usual, weigh your decision of
using namespace std;
Cool things you won't need:
- speed up C I/O using a single line at the beginning of the program (but be careful)
- disable C I/O buffering
- disable C I/O buffering
Conclusion
I/O is the crowded junction of fundamental CS concepts, hardware & software inner workings and C 's features and quirks. Take in what you can at a time & focus on what matters, and make sure you're building on sturdy fundamentals.