Это потому, что строка (по крайней мере, в C / C ++) не совсем то же самое, что и целое число. Если мы говорим о строках в стиле C, то их массив типа
char* test[3] = { "foo", "bar", "baz" };
что на самом деле происходит под капотом, так это то, что «тест» - это массив указателей, каждый из которых указывает на фактические данные, где находятся символы. Допустим, случайным образом, что «тестовый» массив начинается с адреса памяти 0x10000, и что указатели имеют длину четыре байта, тогда у нас может быть
test[0] (memory location 0x10000) contains 0x10020
test[1] (memory location 0x10004) contains 0x10074
test[2] (memory location 0x10008) contains 0x10320
Тогда мы могли бы взглянуть на области памяти вокруг 0x10020, мы бы нашли фактические данные о символах:
test[0][0] (memory location 0x10020) contains 'f'
test[0][1] (memory location 0x10021) contains 'o'
test[0][2] (memory location 0x10022) contains 'o'
test[0][3] (memory location 0x10023) contains '\0'
и около области памяти 0x10074
test[1][0] (memory location 0x10074) contains 'b'
test[1][1] (memory location 0x10075) contains 'a'
test[1][2] (memory location 0x10076) contains 'r'
test[1][3] (memory location 0x10077) contains '\0'
С объектами C ++ std :: string происходит то же самое: настоящий строковый объект C ++ не «содержит» символы, потому что, как вы говорите, строки имеют переменную длину. На самом деле он содержит указатель на символы. (По крайней мере, в простой реализации std :: string это имеет более сложную структуру, обеспечивающую лучшее использование памяти и производительность).