Home > Enterprise >  last character of character array is getting excluded
last character of character array is getting excluded

Time:06-21

#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 by s, until any of the following occurs (tested in the order shown):

  1. end of file condition occurs in the input sequence (in which case setstate(eofbit) is executed)
  2. the next available character c is the delimiter, as determined by Traits::eq(c, delim). The delimiter is extracted (unlike basic_istream::get()) and counted towards gcount(), but is not stored.
  3. count-1 characters have been extracted (in which case setstate(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 updates gcount().

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::strings, 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. FILEs 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 and free.
  • 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 of std::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

Cool things you won't need:

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.

  • Related