HOWTO · C++

How to Convert String to Hex in C++

This article introduces how to convert string to hexadecimal value in C++. Learn multiple methods, including using loops, bit manipulation, and standard library functions, to effectively achieve this task. Enhance your C++ programming skills with clear code examples and detailed explanations.

On this page

Converting a std::string to hexadecimal in C++ means producing a text representation of the string’s bytes: each char becomes a two-digit base-16 value, so "Hello" becomes 48656c6c6f. You need this for debugging binary data, producing checksums or fingerprints, and feeding protocols that expect hex-encoded payloads.

This article shows how to print hexadecimal output directly, how to build a reusable hexadecimal std::string, how to keep every byte at two digits, and how the result relates to text encoding, then compares the approaches so you can pick the right one.

Convert a string to hexadecimal bytes

The most direct approach walks the input one byte at a time and writes each value to std::cout in base 16. The stream manipulator std::hex switches integer output to hexadecimal, and std::setw(2) with std::setfill('0') keeps each byte at two digits. Iterating with unsigned char keeps every byte in the range 0–255, so bytes above 0x7f format correctly even on platforms where plain char is signed. Run this example:

#include <iomanip>
#include <iostream>
#include <string>

int main() {
    std::string input = "Hello";

    std::cout << std::hex << std::setfill('0');
    for (unsigned char c : input) {
        std::cout << std::setw(2) << static_cast<int>(c);
    }
    std::cout << '\n';
}
48656c6c6f

std::hex stays in effect until you change it, so set it once before the loop. std::setw(2) applies only to the next inserted value, which is why it sits inside the loop, while std::setfill('0') persists. Printing to std::cout works well for a quick inspection, but the converted text is gone as soon as it scrolls by — the next section keeps it.

Build and store a hexadecimal string

When the converted text is needed later — to return from a function, append to a log message, or write to a file — accumulate it in a std::stringstream instead of std::cout. The same formatting manipulators apply, and str() produces a std::string that holds the result:

#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>

std::string toHex(const std::string& input) {
    std::stringstream ss;
    ss << std::hex << std::setfill('0');
    for (unsigned char c : input) {
        ss << std::setw(2) << static_cast<int>(c);
    }
    return ss.str();
}

int main() {
    std::string text = "C++ Programming";
    std::string hexText = toHex(text);
    std::cout << "string: " << text << '\n';
    std::cout << "hexval: " << hexText << '\n';
}
string: C++ Programming
hexval: 432b2b2050726f6772616d6d696e67

Because toHex returns a std::string, the caller decides what to do with it: print it, store it, or pass it to a file stream. The example only prints the value — writing it to a file is a separate step through std::ofstream, which accepts the same hexadecimal text.

Format each byte with two hexadecimal digits

A byte holds a value from 0 to 255 — 0x00 through 0xff — so two digits always suffice. Without padding, values below 0x10 print as a single digit, which makes the output ambiguous: the byte pair 0x01, 0x0a and the single byte 0x1a would both produce 1a. std::setw(2) sets the minimum field width and std::setfill('0') supplies the padding character:

#include <iomanip>
#include <iostream>
#include <string>

int main() {
    std::string input = "A\nB";

    std::cout << std::hex << std::setfill('0');
    for (unsigned char c : input) {
        std::cout << std::setw(2) << static_cast<int>(c);
    }
    std::cout << '\n';
}
410a42

The middle byte is 0x0a and still prints as two digits. Remove std::setw and std::setfill and the same input prints 41a42, where you can no longer tell where one byte ends and the next begins. Keep std::setw inside the loop — it resets after each formatted value — while std::setfill only needs to be set once.

Understand byte values and text encoding

Every example so far converts bytes, not characters. std::string stores a sequence of char, and the loop formats each one’s numeric value. For ASCII input this distinction is invisible — H is the single byte 0x48 — but it matters for other text. In UTF-8, é (code point U+00E9) is stored as the two bytes 0xc3 0xa9, so the same loop produces c3a9 rather than e9:

#include <iomanip>
#include <iostream>
#include <string>

int main() {
    std::string input = "\xc3\xa9";  // UTF-8 bytes for U+00E9

    std::cout << std::hex << std::setfill('0');
    for (unsigned char c : input) {
        std::cout << std::setw(2) << static_cast<int>(c);
    }
    std::cout << '\n';
}
c3a9

The functions in this article therefore describe the encoded bytes of whatever the string happens to hold. If you need hexadecimal code points instead, decode the text first and format the decoded values. The unsigned-byte handling matters for the same reason: where plain char is signed, a byte such as 0xc3 converts to a value below zero and streams out as ffffffc3. Iterating unsigned char — or casting through it before static_cast<int> — keeps the value at 0xc3.

Choose a conversion approach

Use the direct std::cout loop when you only need to inspect data once. Return a std::string built with std::stringstream when the result must outlive the call — that is the most readable general-purpose option and keeps the std::hex, std::setw, and std::setfill formatting in one place.

A third option splits each byte into its two 4-bit halves (nibbles) and maps each through a lookup table:

#include <iostream>
#include <string>

std::string toHexLookup(const std::string& input) {
    static const char digits[] = "0123456789abcdef";
    std::string result;
    result.reserve(input.size() * 2);
    for (unsigned char c : input) {
        result += digits[c >> 4];
        result += digits[c & 0x0f];
    }
    return result;
}

int main() {
    std::cout << toHexLookup("World") << '\n';
}
576f726c64

The lookup version always emits exactly two digits per byte and performs no stream formatting, which some codebases prefer for consistency. Whether it runs faster depends on your standard library implementation, so measure on your own data if that matters. Whichever approach you choose, the result describes bytes: for ASCII text it reads as one pair per character, and for UTF-8 or binary data it reads as one pair per encoded byte.