Почему не нужно разыменовывать указатель на символьный массив когда её выводить в c++?

можно делать так:

char *string = "Строка";
std::cout << sring;

string образует указатель на массив "Строка", который находится в таблице строк. Это понятно.

Но почему не нужно разыменовывать string когда её выводишь? Ведь по сути cout << string; должен дать нам адрес памяти, а не саму строку. Не понимаю

Такое чувство, как будто cout << хранит в себе функцию, которая позволяет через цикл брать каждый адрес символа строки, разыменовывать и выводить...


Ответы (2 шт):

Автор решения: VladimirS

Всё верно, если судить "по аналогии" то std::cout << string должен был бы вывести адрес. Но это именно если судить по аналогии.

На практике, указатель на последовательность char-ов - рассматривается особым образом: не как конкретный адрес (как все остальные указатели), а как адрес первого символа последовательности строки в стиле C. Т.е. функция вывода подразумевает, что если ей дают указатель на char (именно, на char - символьный тип, а не какой другой), то ей дают указатель на начало строки в стиле C - начало массива символов, последний из которых заканчивается терминирующим нулём '\0'.

То же поведение (изначально printf-овское) продублировано и на оператор вывода в поток.

Такое чувство, как будто cout << хранит в себе функцию, которая позволяет через цикл брать каждый адрес символа строки, разыменовывать и выводить...

Интуиция не обманывает: примерно так оно и работает (реализовано внутри), всё верно.

С другой стороны, в языке C, из которого изначально и вырос C++, нет понятия "строка". Там есть либо массив символов (указатель на его начало), либо просто указатель на символ, либо просто символ. Соответственно, чтобы обозначить строку, обычно использовалось (и используется) соглашение, что передаётся указатель на 1й символ символ, а последний символ - всегда '\0'.

Т.е. работает вывод как-то так (правда, реализован вывод обычно сервисами операционной системы, нередко на ассемблере, а в C и C++ предоставляется в виде printf-подобных функций):

void print(char* string)
{
    for ( char* c = string; c++ != '\0'; )
        вывести( *c );
}

Аналогичный принцип работы, перекочевал из C в C++.

Другое дело, если всё равно требуется вывести именно адрес соответствующего символа. Если такое желание есть, то нужно указатель преобразовать из char* в void*.

Т.е. сделать как-то так:

    std::cout << static_cast<void*>(string);

В общем, указатель на char следует рассматривать как исключение, связанное с историческим происхождением языка и историческим же соглашением ещё в языке Си, обусловленным в т.ч. производительностью.

→ Ссылка
Автор решения: maestro

Совершенно верно - в вашем примере string - это адрес памяти, а не сама строка. Оператор << знает, что если вы передаёте ему указатель на char, то нужно вывести строку до первого встретившегося нулевого байта.

Но давайте подумаем, что получится, если этот указатель разыменовать?

std::cout << *string;

Результат разыменовывания указателя на char - это просто char. То есть вы даёте оператору << один символ. И оператор поступит совершенно адекватно - выведет этот символ - первый символ в вашей строке.

Такое чувство, как будто cout << хранит в себе функцию, которая позволяет через цикл брать каждый адрес символа строки, разыменовывать и выводить...

И тут вы попали в точку. Операторы в C++ - это те же функции, которые могут перегружаться с различными параметрами. В частности, оператор << для типа char* может выглядеть следующим образом:

ostream& operator<<(ostream& out, const char* string)
{
    char *c = string;
    do
    {
        out << *c; // Здесь с указывает на текущий символ, и этот единичный символ отправляем в поток ввода
    } while (*(++c)); //Выходим, как только достигнем нулевого байта
    return out;
}

Таким образом можно определять операторы для своих произвольных типов данных, и выводить их с помощью вывода тех типов данных, реализация которых уже имеется в библиотеке языка.

→ Ссылка