Pour projeter des nombres en format hexadémical sur un flux en C++, la stratégie traditionnelle est d'utiliser les flux standards avec des manipulateurs de <iomanip> tels que std::hex (hexadécimal) et std::dec (décimal) (il y a aussi std::oct, pour octal). Le changement de mode persiste jusqu'à un prochain changement, alors si notre souhait est de ne projeter qu'une seule valeur en format hexadécimal sur un flux, il est sage de remettre le mode décimal (qui est le mode par défaut) en place par la suite :
#include <iostream>
#include <iomanip>
int main() {
using namespace std;
cout << 255 << ' ' << hex << 255 << ' ' << dec << 255 << '\n';
}
Sortie (https://wandbox.org/permlink/KME2RudGiGT77qJ3) :
255 ff 255
Les affichages en mode hexadécimal ont typiquement une largeur choisie, car ils servent souvent à représenter des adresses de manière à faciliter la visibilité des bits qui sont allumés ou pas. Ainsi, il est possible de coupler l'affichage aux manipulateurs std::setw (Set Width)...
#include <iostream>
#include <iomanip>
int main() {
using namespace std;
cout << 255 << ' ' << hex << setw(8) << 255 << ' ' << dec << 255 << '\n';
}
Sortie (https://wandbox.org/permlink/5CGbVqyv4N1NDQ4C) :
255 ff 255
... et std::setfill (fixer le caractère de remplissage, qui est ' ' par défaut) :
#include <iostream>
#include <iomanip>
int main() {
using namespace std;
cout << 255 << ' ' << hex << setw(8) << setfill('0') << 255 << ' ' << dec << 255 << '\n';
}
Sortie (https://wandbox.org/permlink/eGfeKjWzxuURhnKc) :
255 000000ff 255
Notez que contrairement à std::hex et std::dec, les manipulateurs std::setw et std::setfill n'ont un impact que sur le prochain affichage :
#include <iostream>
#include <iomanip>
int main() {
using namespace std;
cout << 255 << ' ' << hex << setw(8) << setfill('0') << 255 << ' ' << 255 << ' ' << dec << 255 << '\n';
}
Sortie (https://wandbox.org/permlink/MkCLE4C5uhKqd8t1) :
255 000000ff ff 255
Prenons un exemple concret, soit celui d'afficher des valeurs RGB de pixels encodées sur 24 bits en format hexadécimal de largeur 6 (donc trois octets). La petite danse par laquelle nous basculons les modes à chaque affichage peut vite devenir lassante, alors écrivons une fonction print_hext<N>(val) où N sera la largeur de l'affichage et val sera la valeur à afficher (nous contraindrons les valeurs à afficher à des entiers non signés, peu importe leur type).
Une implémentation possible serait :
#include <iostream>
#include <iomanip>
#include <vector>
#include <concepts>
#include <cstdint>
#include <cassert>
using namespace std; // lazy, I know
template <int N>
void print_hex(ostream &os, unsigned_integral auto n) {
os << hex << setw(N) << setfill('0') << n << dec;
}
struct pixel {
uint32_t rgba;
pixel(unsigned char r, unsigned char g, unsigned char b)
: rgba( ((r << 0) | (g << 8) | (b << 16)) ) {
}
constexpr bool operator==(const pixel &other) const = default;
constexpr operator uint32_t() const {
return rgba;
}
};
int main() {
vector<pixel> pix;
for (int i = 0; i != 10; ++i)
pix.emplace_back(0, 255, 0); // green
for (int i = 0; i != 4; ++i)
pix.emplace_back(0, 0, 255); // blue
for (int i = 0; i != 6; ++i)
pix.emplace_back(255, 0, 0); // red
for (const auto &p : pix) {
print_hex<6>(cout, static_cast<unsigned int>(p));
cout << ' ';
}
cout << '\n';
}
Sortie (https://wandbox.org/permlink/5pYz2pfZ1Vq1jrlZ) :
00ff00 00ff00 00ff00 00ff00 00ff00 00ff00 00ff00 00ff00 00ff00 00ff00 ff0000 ff0000 ff0000 ff0000 0000ff 0000ff 0000ff 0000ff 0000ff 0000ff
Une autre écriture élégante serait d'utiliser un type as_hex<N> qui enroberait un entier non-signé et se projetterait sur un flux en format hexadécimal avec une largeur de N. Une implémentation possible serait :
#include <iostream>
#include <iomanip>
#include <vector>
#include <concepts>
#include <cstdint>
#include <cassert>
using namespace std; // lazy, I know
template <int N>
void print_hex(ostream &os, unsigned_integral auto n) {
os << hex << setw(N) << setfill('0') << n << dec;
}
template <int N, unsigned_integral T>
struct as_hex_ {
T val;
friend ostream& operator<<(ostream &os, const as_hex_<N, T> &h) {
print_hex<N>(os, h.val);
return os;
}
};
template <int N, unsigned_integral T>
constexpr as_hex_<N, T> as_hex(T val) {
return { val };
}
struct pixel {
uint32_t rgba;
pixel(unsigned char r, unsigned char g, unsigned char b)
: rgba( ((r << 0) | (g << 8) | (b << 16)) ) {
}
constexpr bool operator==(const pixel &other) const = default;
constexpr operator uint32_t() const {
return rgba;
}
};
int main() {
vector<pixel> pix;
for (int i = 0; i != 10; ++i)
pix.emplace_back(0, 255, 0); // green
for (int i = 0; i != 4; ++i)
pix.emplace_back(0, 0, 255); // blue
for (int i = 0; i != 6; ++i)
pix.emplace_back(255, 0, 0); // red
for (const auto &p : pix)
cout << as_hex<6>(static_cast<unsigned int>(p)) << ' ';
cout << '\n';
}
Sortie (https://wandbox.org/permlink/Ih8kelKrmYNte1De) :
00ff00 00ff00 00ff00 00ff00 00ff00 00ff00 00ff00 00ff00 00ff00 00ff00 ff0000 ff0000 ff0000 ff0000 0000ff 0000ff 0000ff 0000ff 0000ff 0000ff
Les entrées / sorties se modernisent avec l'arrivée de std::format() en C++ 20 et celle de std::print() en C++ 23. Notez que pour les exemples qui suivent, le support d'un compilateur à l'autre varie beaucoup au moment d'écrire ces lignes. Dans tous les cas ci-dessous, il vous faudra inclure <format>.
Pour un affichage décimal de base :
template <int N>
void print_hex(std::ostream &os, std::unsigned_integral auto n) {
using namespace std;
os << format("{}", n);
}
Pour un affichage décimal avec largeur fixe (largeur de 6) et avec des '0' à gauche :
template <int N>
void print_hex(std::ostream &os, std::unsigned_integral auto n) {
using namespace std;
os << format("{:06}", n);
}
Pour un affichage hexadécimal avec largeur fixe :
template <int N>
void print_hex(std::ostream &os, std::unsigned_integral auto n) {
using namespace std;
os << format("{:06x}", n);
}
Pour un affichage hexadécimal avec largeur dynamique (en théorie, du moins, car ça ne semble pas encore implémenté par les principaux compilateurs) :
template <int N>
void print_hex(std::ostream &os, std::unsigned_integral auto n) {
using namespace std;
os << format("{:0{1}x}", n, N);
}
Quelques liens pour enrichir le propos.