Wie drucke ich ein vorzeichenloses Zeichen als Hex in C ++ mit ostream?

73

Ich möchte mit vorzeichenlosen 8-Bit-Variablen in C ++ arbeiten. Entweder unsigned charoder uint8_tmache den Trick, was die Arithmetik betrifft (was erwartet wird, da AFAIK uint8_tnur ein Alias ​​für ist unsigned char, oder so präsentiert es der Debugger.

Das Problem ist, dass wenn ich die Variablen mit ostream in C ++ drucke, sie als Zeichen behandelt werden. Wenn ich habe:

unsigned char a = 0;
unsigned char b = 0xff;
cout << "a is " << hex << a <<"; b is " << hex << b << endl;

dann ist die Ausgabe:

a is ^@; b is 377

anstatt

a is 0; b is ff

Ich habe es versucht uint8_t, aber wie ich bereits erwähnt habe, ist das typisiert unsigned char, also macht es dasselbe. Wie kann ich meine Variablen richtig drucken?

Bearbeiten: Ich mache dies an vielen Stellen in meinem Code. Gibt es eine Möglichkeit, dies zu tun, ohneint jedes Mal zu gießen, wenn ich drucken möchte?

Nathan Fellman
quelle
2
Ich denke, MartinStettners Antwort ist ziemlich verwirrend. Ich denke nicht, dass es sich lohnt, eine zusätzliche Struktur und einen zusätzlichen Stream-Operator zu implementieren. Die Lösung von anon ist unkompliziert und funktioniert gut genug für mich.
tdihp

Antworten:

56

Ich würde die folgende Technik vorschlagen:

struct HexCharStruct
{
  unsigned char c;
  HexCharStruct(unsigned char _c) : c(_c) { }
};

inline std::ostream& operator<<(std::ostream& o, const HexCharStruct& hs)
{
  return (o << std::hex << (int)hs.c);
}

inline HexCharStruct hex(unsigned char _c)
{
  return HexCharStruct(_c);
}

int main()
{
  char a = 131;
  std::cout << hex(a) << std::endl;
}

Es ist kurz zu schreiben, hat die gleiche Effizienz wie die ursprüngliche Lösung und ermöglicht es Ihnen, die "ursprüngliche" Zeichenausgabe zu verwenden. Und es ist typsicher (ohne "böse" Makros :-))

MartinStettner
quelle
11
Solange wir Makros meiden: Um vollständiger C ++ zu sein, sollten Sie nicht (int)hs.cals schreiben static_cast<int>(hs.c)? : P
Seth Johnson
14
Es gibt einen kleinen Fehler in Ihrem Code. Wenn Sie im Code ein negatives Zeichen eingeben, wird der Hex-Wert zu 4 Byte anstelle von 2. Durch Umschalten auf wird unsigned chardas Problem behoben.
Ted
12
Ein weiterer Fehler (?) Ist, dass das oben operator<<Gesagte den Modus des angegebenen Streams auf hexadezimal ändert: cout << hex(a) << 100Sie werden überrascht sein. Sie sollten den Status des Streams speichern, bevor Sie ihn ändern, und ihn später wiederherstellen.
Musiphil
5
Ich bin wirklich erstaunt, wie schlecht C ++ bei der Konvertierung charin Hex ist.
Craig Ringer
50

Verwenden:

cout << "a is " << hex << (int) a <<"; b is " << hex << (int) b << endl;

Und wenn Sie mit führenden Nullen auffüllen möchten, dann:

#include <iomanip>
...
cout << "a is " << setw(2) << setfill('0') << hex << (int) a ; 

Da wir Casts im C-Stil verwenden, sollten Sie das ganze Problem mit der C ++ - Schlechtigkeit des Terminals lösen und ein Makro verwenden!

#define HEX( x )
   setw(2) << setfill('0') << hex << (int)( x )

du kannst dann sagen

cout << "a is " << HEX( a );

Edit: Trotzdem ist die Lösung von MartinStettner viel schöner!


quelle
4
Kann ich Sie überzeugen, die bösen C-Casts ein für alle Mal zu meiden? stackoverflow.com/questions/527999/…
Konrad Rudolph
1
Nicht in diesem Fall - es ist ungefähr der einzige Ort, an dem sie meiner Meinung nach gerechtfertigt sind.
Ich würde es genauso machen, außer um die Besetzung zu vermeiden, würde ich cout << hex << int (a) verwenden; Es bedeutet dasselbe wie eine Besetzung ohne die Besetzung. :)
Brian Neal
36

Weitere Informationen hierzu finden Sie unter http://cpp.indi.frih.net/blog/2014/09/tippet-printing-numeric-values-for-chars-and-uint8_t/ und http: //cpp.indi. frih.net/blog/2014/08/code-critique-stack-overflow-posters-cant-print-the-numeric-value-of-a-char/ . Ich poste dies nur, weil klar geworden ist, dass der Autor der oben genannten Artikel dies nicht beabsichtigt.

Die einfachste und korrekteste Technik, um ein Zeichen als Hex zu drucken, ist

unsigned char a = 0;
unsigned char b = 0xff;
auto flags = cout.flags(); //I only include resetting the ioflags because so
                           //many answers on this page call functions where
                           //flags are changed and leave no way to  
                           //return them to the state they were in before 
                           //the function call
cout << "a is " << hex << +a <<"; b is " << +b << endl;
cout.flags(flags);

Die Digest-Version des Lesers zeigt, dass der Operator unary + eine Konvertierung vom Typ no op in ein int mit der richtigen Signatur erzwingt. Ein vorzeichenloses Zeichen wird in vorzeichenloses int konvertiert, ein vorzeichenbehaftetes Zeichen wird in int konvertiert und ein Zeichen wird entweder in vorzeichenloses int oder int konvertiert, je nachdem, ob char auf Ihrer Plattform signiert oder nicht signiert ist (für viele ist es ein Schock, dass char etwas Besonderes ist und nicht als signiert oder nicht signiert angegeben).

Das einzig Negative dieser Technik ist, dass es möglicherweise nicht offensichtlich ist, was mit jemandem passiert, der mit ihr nicht vertraut ist. Ich denke jedoch, dass es besser ist, die richtige Technik zu verwenden und andere darüber zu unterrichten, als etwas zu tun, das falsch, aber sofort klarer ist.

ltc
quelle
3
Warum ist das nicht die beste Antwort? Es ist kurz und effektiv.
James Pack
2
Diese Technik erweitert Typen mit Vorzeichen , die kleiner als unsigned int/ intsind. Dies bedeutet, dass für Zeichen mit negativem Vorzeichen (oder beispielsweise int16_t) 8 Halbbytes ausgegeben werden, von denen die ersten 6 sind F.
Brian McFarland
5
@BrianMcFarland Ich habe dieses Problem auch bei nicht signiertem Zeichen (wenn es größer als 127 ist) bemerkt und es mit einer Bitmaske gelöst : (+c & 0xFF).
Bis zum
@To 마 SE, das Unäre +ist unnötig, die Binärdatei &konvertiert bereits in ein int.
Adrian
18

Nun, das funktioniert bei mir:

std::cout << std::hex << (0xFF & a) << std::endl;

Wenn Sie nur (int)wie vorgeschlagen aumwandeln , wird möglicherweise 1s links von 1 hinzugefügt, wenn das höchstwertige Bit 1 ist. Wenn Sie also diese binäre UND-Operation ausführen, wird die Ausgabe garantiert, dass die linken Bits mit 0s gefüllt sind, und konvertiert sie auch in vorzeichenloses int, wodurch cout zum Drucken gezwungen wird es als hex.

Ich hoffe das hilft.

VinGarcia
quelle
3
Dies ist die einfachste und sauberste Methode der Reihe.
Adrian
Diese Antwort gefällt mir auch sehr gut! Ich habe diese Lösung verwendet.
sep
6

Hm, anscheinend habe ich das Rad gestern neu erfunden ... Aber hey, zumindest ist es diesmal ein generisches Rad :) chars werden mit zwei hexadezimalen Ziffern gedruckt, shorts mit 4 hexadezimalen Ziffern und so weiter.

template<typename T>
struct hex_t
{
    T x;
};

template<typename T>
hex_t<T> hex(T x)
{
    hex_t<T> h = {x};
    return h;
}

template<typename T>
std::ostream& operator<<(std::ostream& os, hex_t<T> h)
{
    char buffer[2 * sizeof(T)];
    for (auto i = sizeof buffer; i--; )
    {
        buffer[i] = "0123456789ABCDEF"[h.x & 15];
        h.x >>= 4;
    }
    os.write(buffer, sizeof buffer);
    return os;
}
Fredoverflow
quelle
2
Ich mag das. Prägnant. Keine redundanten Informationen erforderlich. Ich würde gerne einen Bool verwenden, um anzuzeigen, ob Groß- oder Kleinbuchstaben gewünscht werden. Oder ehre die std::uppercaseund std::nouppercaseManipulatoren (die die std::ios_base::flags::uppercaseiosflag manipulieren )
sehe
4

Ich würde es wie MartinStettner machen, aber einen zusätzlichen Parameter für die Anzahl der Ziffern hinzufügen:

inline HexStruct hex(long n, int w=2)
{
  return HexStruct(n, w);
}
// Rest of implementation is left as an exercise for the reader

Sie haben also standardmäßig zwei Ziffern, können aber vier, acht oder was auch immer einstellen, wenn Sie möchten.

z.B.

int main()
{
  short a = 3142;
  std:cout << hex(a,4) << std::endl;
}

Es mag übertrieben erscheinen, aber wie Bjarne sagte: "Bibliotheken sollten einfach zu bedienen und nicht einfach zu schreiben sein".

Jimmy J.
quelle
4

Ich denke, die Antwort von TrungTN und anon ist in Ordnung, aber MartinStettners Methode zur Implementierung der hex () - Funktion ist nicht wirklich einfach und zu dunkel, wenn man bedenkt, dass hex << (int) mychar bereits eine Problemumgehung ist.

Hier ist meine Lösung, um den Operator "<<" zu vereinfachen:

#include <sstream>
#include <iomanip>

string uchar2hex(unsigned char inchar)
{
  ostringstream oss (ostringstream::out);
  oss << setw(2) << setfill('0') << hex << (int)(inchar);
  return oss.str();
}

int main()
{
  unsigned char a = 131;
  std::cout << uchar2hex(a) << std::endl;
}

Es lohnt sich einfach nicht, einen Stream-Operator zu implementieren :-)

tdihp
quelle
3

Ich benutze folgendes unter win32 / linux (32/64 bit):

#include <iostream>
#include <iomanip>

template <typename T>
std::string HexToString(T uval)
{
    std::stringstream ss;
    ss << "0x" << std::setw(sizeof(uval) * 2) << std::setfill('0') << std::hex << +uval;
    return ss.str();
}
Drus
quelle
2

Sie können den folgenden Code ausprobieren:

unsigned char a = 0;
unsigned char b = 0xff;
cout << hex << "a is " << int(a) << "; b is " << int(b) << endl;
cout << hex
     <<   "a is " << setfill('0') << setw(2) << int(a)
     << "; b is " << setfill('0') << setw(2) << int(b)
     << endl;
cout << hex << uppercase
     <<   "a is " << setfill('0') << setw(2) << int(a)
     << "; b is " << setfill('0') << setw(2) << int(b)
     << endl;

Ausgabe:

a is 0; b is ff

a is 00; b is ff

a is 00; b is FF

TrungTN
quelle
Dies löst zwar das Problem, aber eine meiner Anforderungen bestand darin, nicht zu intjedem Zeitpunkt, zu dem ich dies tun möchte, umwandeln zu müssen, da dies im Code zu oft vorkommt.
Nathan Fellman
1

Ich möchte meine neu erfundene Version basierend auf @ FredOverflow's veröffentlichen. Ich habe die folgenden Änderungen vorgenommen.

Fix:

  • Rhs von operator<<sollte vom constReferenztyp sein. Im Code von @ FredOverflow wird die h.x >>= 4Ausgabe geändert h, die überraschenderweise nicht mit der Standardbibliothek kompatibel ist, und der Typ muss Tkopierkonstruierbar sein.
  • Angenommen, nur CHAR_BITSein Vielfaches von 4. @ FredOverflows Code geht von char8 Bit aus, was bei einigen Implementierungen auf DSPs nicht immer der Fall ist. Insbesondere ist es nicht ungewöhnlich, dass chares sich um 16 Bit, 24 Bit, 32 Bit usw. Handelt .

verbessern:

  • Unterstützt alle anderen Standardbibliotheksmanipulatoren, die für integrale Typen verfügbar sind, z std::uppercase. Da die Formatausgabe in verwendet wird _print_byte, sind weiterhin Standardbibliotheksmanipulatoren verfügbar.
  • Hinzufügen hex_sep, um separate Bytes zu drucken (beachten Sie, dass in C / C ++ ein 'Byte' per Definition eine Speichereinheit mit der Größe von ist char). Fügen Sie einen Vorlagenparameter hinzu Sepund instanziieren Sie _Hex<T, false>bzw. _Hex<T, true>in hexund hex_sep.
  • Vermeiden Sie das Aufblähen von Binärcode. Die Funktion _print_bytewird operator<<mit einem Funktionsparameter aus extrahiert size, um eine Instanziierung für verschiedene zu vermeiden Size.

Mehr zum Aufblähen von Binärcode:

Wie in Verbesserung 3 erwähnt, werden unabhängig vom Umfang hexund der hex_sepVerwendung nur zwei Kopien der (fast) duplizierten Funktion im Binärcode beendet: _print_byte<true>und _print_byte<false>. Und Sie haben vielleicht festgestellt, dass diese Duplizierung auch mit genau demselben Ansatz beseitigt werden kann: Fügen Sie einen Funktionsparameter hinzu sep. Ja, aber wenn dies if(sep)erforderlich ist , wird eine Laufzeit benötigt. Ich möchte ein allgemeines Bibliotheksdienstprogramm, das häufig im Programm verwendet wird. Daher habe ich bei der Duplizierung Kompromisse eingegangen und nicht den Laufzeitaufwand. Ich habe dies durch die Verwendung der Kompilierungszeit erreicht if: C ++ 11 std::conditional, der Overhead des Funktionsaufrufs kann hoffentlich durch optimiert werden inline.

hex_print.h:

namespace Hex
{
typedef unsigned char Byte;

template <typename T, bool Sep> struct _Hex
{
    _Hex(const T& t) : val(t)
    {}
    const T& val;
};

template <typename T, bool Sep>
std::ostream& operator<<(std::ostream& os, const _Hex<T, Sep>& h);
}

template <typename T>  Hex::_Hex<T, false> hex(const T& x)
{ return Hex::_Hex<T, false>(x); }

template <typename T>  Hex::_Hex<T, true> hex_sep(const T& x)
{ return Hex::_Hex<T, true>(x); }

#include "misc.tcc"

hex_print.tcc:

namespace Hex
{

struct Put_space {
    static inline void run(std::ostream& os) { os << ' '; }
};
struct No_op {
    static inline void run(std::ostream& os) {}
};

#if (CHAR_BIT & 3) // can use C++11 static_assert, but no real advantage here
#error "hex print utility need CHAR_BIT to be a multiple of 4"
#endif
static const size_t width = CHAR_BIT >> 2;

template <bool Sep>
std::ostream& _print_byte(std::ostream& os, const void* ptr, const size_t size)
{
    using namespace std;

    auto pbyte = reinterpret_cast<const Byte*>(ptr);

    os << hex << setfill('0');
    for (int i = size; --i >= 0; )
    {
        os << setw(width) << static_cast<short>(pbyte[i]);
        conditional<Sep, Put_space, No_op>::type::run(os);
    }
    return os << setfill(' ') << dec;
}

template <typename T, bool Sep>
inline std::ostream& operator<<(std::ostream& os, const _Hex<T, Sep>& h)
{
    return _print_byte<Sep>(os, &h.val, sizeof(T));
}

}

Prüfung:

struct { int x; } output = {0xdeadbeef};
cout << hex_sep(output) << std::uppercase << hex(output) << endl;

Ausgabe:

de ad be ef DEADBEEF

wpzdm
quelle
1

Mir ist klar, dass dies eine alte Frage ist, aber es ist auch ein Top-Ergebnis von Google bei der Suche nach einer Lösung für ein sehr ähnliches Problem, das ich habe, nämlich den Wunsch, willkürliche Konvertierungen von Ganzzahlen in Hex-Zeichenfolgen innerhalb einer Vorlagenklasse zu implementieren. Mein Endziel war eigentlich eine Gtk::EntryUnterklassenvorlage, mit der verschiedene ganzzahlige Breiten in hexadezimaler Form bearbeitet werden können, aber das ist nebensächlich.

Dies kombiniert den unären operator+Trick mit std::make_unsignedvon <type_traits>, um das Problem des vorzeichenverlängernden Negativs int8_toder signed charder in dieser Antwort auftretenden Werte zu vermeiden

Jedenfalls glaube ich, dass dies prägnanter ist als jede andere generische Lösung. Es sollte für alle vorzeichenbehafteten oder vorzeichenlosen Ganzzahltypen funktionieren und einen Fehler beim Kompilieren auslösen, wenn Sie versuchen, die Funktion mit Nicht-Ganzzahltypen zu instanziieren.

template < 
  typename T,
  typename = typename std::enable_if<std::is_integral<T>::value, T>::type
>
std::string toHexString(const T v)
{ 
  std::ostringstream oss;
  oss << std::hex << +((typename std::make_unsigned<T>::type)v);
  return oss.str();
}

Einige Anwendungsbeispiele:

int main(int argc, char**argv)
{
  int16_t val;
  // Prints 'ff' instead of "ffffffff". Unlike the other answer using the '+'
  // operator to extend sizeof(char) int types to int/unsigned int
  std::cout << toHexString(int8_t(-1)) << std::endl;

  // Works with any integer type
  std::cout << toHexString(int16_t(0xCAFE)) << std::endl;

  // You can use setw and setfill with strings too -OR- 
  // the toHexString could easily have parameters added to do that.
  std::cout << std::setw(8) << std::setfill('0') << 
    toHexString(int(100)) << std::endl;
  return 0;
}

Update: Wenn Ihnen die Idee der ostringstreamVerwendung nicht gefällt, können Sie alternativ den Vorlagen- und den unären Operator-Trick mit der strukturbasierten Lösung der akzeptierten Antwort für Folgendes kombinieren. Beachten Sie, dass ich hier die Vorlage geändert habe, indem ich die Prüfung auf Ganzzahltypen entfernt habe. Die make_unsignedVerwendung kann ausreichen, um Sicherheitsgarantien für den Kompilierungszeittyp zu erstellen.

template <typename T>
struct HexValue 
{
  T value;
  HexValue(T _v) : value(_v) { }
};

template <typename T>
inline std::ostream& operator<<(std::ostream& o, const HexValue<T>& hs)
{
  return o << std::hex << +((typename std::make_unsigned<T>::type) hs.value);
}

template <typename T>
const HexValue<T> toHex(const T val)
{
  return HexValue<T>(val);
}

// Usage:
std::cout << toHex(int8_t(-1)) << std::endl;
Brian McFarland
quelle
1

Ich denke, wir vermissen eine Erklärung, wie diese Typkonvertierungen funktionieren.

charist plattformabhängig signedoder unsigned. In x86 charentspricht signed char.

Wenn ein integraler Typ ( char, short, int, long) zu einem größeren Kapazitätstyp umgewandelt wird, wird die Umwandlung hergestellt von Nullen links im Fall der Zugabe unsignedfür Typen und durch Vorzeichenerweiterung signeddiejenigen. Die Vorzeichenerweiterung besteht darin, das höchstwertige (ganz links stehende) Bit der ursprünglichen Zahl nach links zu replizieren, bis die Bitgröße des Zieltyps erreicht ist.

Wenn ich mich also in einem signed charStandardsystem befinde und dies tue:

char a = 0xF0; // Equivalent to the binary: 11110000
std::cout << std::hex << static_cast<int>(a);

Wir würden erhalten, F...F0da das führende 1Bit erweitert wurde.

Wenn wir sicherstellen möchten, dass wir nur F0in einem beliebigen System drucken , müssen wir einen zusätzlichen Zwischentyp in a umwandeln, unsigned charsodass stattdessen Nullen hinzugefügt werden und, da sie für eine Ganzzahl mit nur 8 Bit nicht von Bedeutung sind, nicht gedruckt werden:

char a = 0xF0; // Equivalent to the binary: 11110000
std::cout << std::hex << static_cast<int>(static_cast<unsigned char>(a));

Dies erzeugt F0

Marco A.
quelle
0

Wenn Sie vorgefüllte und signierte Zeichen verwenden, achten Sie darauf, keine unerwünschten Fs anzuhängen

char out_character = 0xBE; cout << setfill('0') << setw(2) << hex << unsigned short(out_character);

Drucke: ffbe

Verwenden von int anstelle von kurzen Ergebnissen in ffffffbe

Um unerwünschte Fs zu vermeiden, können Sie sie leicht ausblenden.

char out_character = 0xBE; cout << setfill('0') << setw(2) << hex << unsigned short(out_character) & 0xFF;

anonymer Affe
quelle
-1

Dies wird auch funktionieren:

std::ostream& operator<< (std::ostream& o, unsigned char c)
{
    return o<<(int)c;
}

int main()
{
    unsigned char a = 06;
    unsigned char b = 0xff;
    std::cout << "a is " << std::hex << a <<"; b is " << std::hex << b << std::endl;
    return 0;
}
Naveen
quelle
Aber dann muss er Casts verwenden, wenn er sie tatsächlich als Zeichen ausgeben möchte, was ein wenig unnatürlich erscheint!
-2

Ich habe auf diese Weise verwendet.

    char strInput[] = "yourchardata";
char chHex[2] = "";

int nLength = strlen(strInput);
char* chResut = new char[(nLength*2) + 1];
memset(chResut, 0, (nLength*2) + 1);



for (int i = 0; i < nLength; i++)
{
    sprintf(chHex, "%02X", strInput[i]& 0x00FF);    
    memcpy(&(chResut[i*2]), chHex, 2);
}

printf("\n%s",chResut);
delete chResut;
chResut = NULL;
Mazharul Islam
quelle
1
Dies ist eine Frage zu C ++ und std :: ostream, nicht zu C und printf. :)
Daminetreg