Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't understand the output of C++ string with special characters

I'm using c++ string with special characters for console output. Most of the results can be predicted, but one of them fell out of my expectation. I could not find answers anywhere.

Platform: Windows 7 Enterprise Version 6.1 (Build 7601: Service Pack 1) Compiler: g++ (GCC) 8.2.0, c++17

#include <iostream>

int main(){
    using namespace std;
    char numString[12] = "0123456789\n";

    //This is group 1
    numString[3] = '\t';
    numString[4] = '\b';
    cout << "Group 1 output:\n" << numString << endl;

    //This is group 2
    numString[3] = '\b';
    numString[4] = '\t';
    cout << "Group 2 output:\n" << numString << endl;

    //This is group 3
    numString[3] = '\n';
    numString[4] = '\b';
    cout << "Group 3 output:\n" << numString << endl;

    //This is group 4
    numString[3] = '\b';
    numString[4] = '\n';
    cout << "Group 4 output:\n" << numString << endl;

    //This is group 5
    numString[2] = '\b';
    numString[3] = '\b';
    numString[4] = '\n';
    cout << "Group 5 output:\n" << numString << endl;

    return 0;
}

The output in console:

Group 1 output:
01256789

Group 2 output:
01      56789

Group 3 output:
012
56789

Group 4 output:
012
56789

Group 5 output:
01
56789

The 4th group output is expected as,

Group 4 output:
01
56789

while the output actually is,

Group 4 output:
012
56789

What I can't understand is why the character '2' is still there.

Anyone could please help me understand the problem? Thank you.


After seeing the answers below, especially zar's, I believe I have understood the problem, and would like to summarize a bit here.

  1. Windows cmd console is in non-destructive mode when no physical keystrokes detected.
  2. Any new output starts overwriting the existing ones from the current cursor. Sounds redundant but necessary. If there is any new character output, it will overwrite the existing one until new characters are used up. If there are still more existing characters left, they will continue existing over there and may look like "output" behind the new characters.
  3. '\b' only moves the cursor one character back. It does not delete anything.
  4. '\n' only moves the cursor to the next line. It does not move any character behind it to the next line.
  5. '\r' only moves the cursor to the beginning of the current line.

Please pay attention to move the cursor.

I'd like to paste all of the code here:

//strwithspecialchar.cpp -- Understand special characters in C++ string
#include <iostream>

int main(){
    using namespace std;
    char numString[12] = "0123456789\n";

    //This is group 1
    numString[3] = '\t';
    numString[4] = '\b';
    cout << "Group 1 output:\n" << numString << endl;

    //This is group 2
    numString[3] = '\b';
    numString[4] = '\t';
    cout << "Group 2 output:\n" << numString << endl;

    //This is group 3
    numString[3] = '\n';
    numString[4] = '\b';
    cout << "Group 3 output:\n" << numString << endl;

    //This is group 4
    numString[3] = '\b';
    numString[4] = '\n';
    cout << "Group 4 output:\n" << numString << endl;

    //This is group 5
    numString[2] = '\b';
    numString[3] = '\b';
    numString[4] = '\n';
    cout << "Group 5 output:\n" << numString << endl;

    //This is group 6
    numString[2] = '\b';
    numString[3] = '3';
    numString[4] = '\n';
    cout << "Group 6 output:\n" << numString << endl;

    //This is group 7
    numString[2] = '2';
    numString[3] = '\b';
    numString[4] = '\a';
    cout << "Group 7 output:\n" << numString << endl;

    //This is group 8
    numString[3] = '\b';
    numString[4] = '\r';
    cout << "Group 8 output:\n" << numString << endl;

    //This is group 9
    numString[3] = '\b';
    numString[4] = '\n';
    numString[8] = '\r';
    cout << "Group 9 output:\n" << numString << endl;

    return 0;
}

And the output below for better understanding these special characters:

Group 1 output:
01256789

Group 2 output:
01      56789

Group 3 output:
012
56789

Group 4 output:
012
56789

Group 5 output:
01
56789

Group 6 output:
03
56789

Group 7 output:
0156789

Group 8 output:
56789

Group 9 output:
012
967
like image 598
Shaun Yu Avatar asked Jun 12 '19 13:06

Shaun Yu


People also ask

What does a backslash do in C++?

The backslash character ( \ ) is a line-continuation character when it's placed at the end of a line. If you want a backslash character to appear as a character literal, you must type two backslashes in a row ( \\ ). For more information about the line continuation character, see Phases of Translation.

How to write backslash in Cpp?

C++ assigns special meaning to the backslash within a string literal and requires it to be escaped to be read as an actual backslash: To represent a single backslash, it's necessary to place double backslashes (\\) in the source code. (Exception: Raw literals, supported by C++11, remove the need to escape characters.)


2 Answers

This comes down to your terminal. We can see easily from a platform that doesn't render the control character '\b' in any special way that it's present in the string at the expected location:

Screenshot of some "raw"-ish output via Coliru

So, why doesn't it "erase" the 2?

If we open up cmd.exe and type in A, B, Ctrl+H then we see the B is immediately erased. This would seem to disprove the notion that cmd.exe handles backspace "non-destructively" as many consoles do.

But it doesn't disprove it! This seems to be a special handling for keystrokes, presumably tied into how the actual backspace character works. After all, you want the backspace character to actually erase things, rather than just moving the cursor.

cmd.exe treats the control character differently when found in output not generated by the keyboard: in a non-destructive fashion. So it moves the cursor backwards then the next character "overwrites" the would-be erased character.

But in group 4, you have a newline, so the next character goes on the next line and is not in the right place to erase anything.

We can reproduce this without C++, by constructing a special file then instructing cmd.exe to print it:

"Working"

Screenshot of non-reproducing file contents Screenshot of non-reproduced problem

"Not working"

Screenshot of reproducing file contents Screenshot of reproduced problem

(You can insert the special character ASCII 08 in Notepad++, using the "Edit"/"Character Panel" menu item.)

My conclusion is not to rely on control codes for such "tricks": if you want to remove a character from a string, actually do so; if you want to create a GUI, either actually do so, or simulate one with a clever library like ncurses.

like image 84
Lightness Races in Orbit Avatar answered Nov 15 '22 00:11

Lightness Races in Orbit


What the console is showing is the correct output, that is

Group 4 output:
012
56789

You are mistaken to expect

Group 4 output:
01
56789

What \b character does is move the cursor one character back, it does not delete it. So what has happened is the cursor is moved back to 2 but the character is still there.

012
  ^

The next character \n is not a printable character but a control character and it just moves the cursor to the next line so it doesn't overwrite that character which was already printed.

If you do this instead:

//This is group 4
numString[3] = '\b';
numString[4] = 'X';
cout << "Group 4 output:\n" << numString << endl;

Now \b moves to 2 but the next character 'X' immediately overwrites it yielding the following result as expected.

Group 4 output:
01X56789

Another demonstration is even if you add another backspace:

numString[3] = '\b';
numString[4] = '\b';
numString[5] = '\n';

The cursor is now at 1

012
 ^

Now it encounters the \n (new line) as next character and it simply moves the cursor to the next line so 1 and 2 are never overwritten as they were already printed and remains now in the previous line.

The output hence now is, as expected:

Group 4 output:
012
6789

See also this and that

like image 21
zar Avatar answered Nov 15 '22 00:11

zar