const char * const порівняно const char *?


110

Я переглядаю кілька прикладних програм для ознайомлення з C ++, і я натрапив на наступне питання. По-перше, ось приклад коду:

void print_string(const char * the_string)
{
    cout << the_string << endl;
}

int main () {
    print_string("What's up?");
}

У наведеному вище коді може бути замість цього параметр print_string const char * const the_string. Що було б правильніше для цього?

Я розумію, що різниця полягає в тому, що один - вказівник на постійний символ, а інший - постійний вказівник на постійний символ. Але чому обидва ці роботи? Коли це було б актуально?

Відповіді:


244

Останнє заважає вам змінюватись the_stringвсередині print_string. Тут насправді було б доречно, але, можливо, багатослів’я відклали розробника.

char* the_string: Я можу змінити, charна які the_stringточки, і я можу змінити точки, charна які він вказує.

const char* the_string: Я можу змінити charна які the_stringточки, але не можу змінити точки, charна які він вказує.

char* const the_string: Я не можу змінити, charна які the_stringточки, але я можу змінити точки, charна які він вказує.

const char* const the_string: Я не можу змінити, charна які the_stringточки, а також не можу змінити точки, charна які він вказує.


11
+1 за останнє речення. Коректність коректності є багатослівною, але вона того варта.
— mskfisher

6
@Xeo: ваша форма є ще більш заплутаною, оскільки це є одним транспозицією від зміни цілого значення. const char *набагато краще, тому що constзнаходиться на протилежній стороні.
— R .. GitHub СТОП ДОПОМОГАТИ

7
@R ..: Ну, принаймні для мене це не так. Читаючи справа наліво, я отримую "вказівник на const char". Для мене просто так почувається краще.
— Xeo

6
Ну, ти обманюєш себе, бо типи С читаються зсередини, а не зліва направо. :-)
— R .. GitHub ЗАСТАНОВИЙ ДІЙ

11
Мене трохи бентежить, я, мабуть, єдиний, хто цього не розуміє ... але яка різниця між "знаком, на який він вказує", і "символом, на який він вказує"?
— Відсутність

138
  1. Змінюваний вказівник на змінний символ

    char *p;
  2. Змінна вказівник на постійний символ

    const char *p;
  3. Постійний вказівник на змінний символ

    char * const p; 
  4. Постійний вказівник на постійний символ

    const char * const p;

Чи не повинно бути це так: const char* p; --> constant pointer to mutable characterі char *const p; --> mutable pointer to constant character
— PnotNP

2
@ NulledPointer №. C ++ декларації формуються праворуч ліворуч. Таким чином, ви можете прочитати const char * pяк: "p - вказівник на констант символів" або змінений вказівник на постійний символ, як правильно стверджує Джеймс. те ж саме з другим :).
— Самідамару

28

const char * constозначає покажчик, а також дані , покажчик вказував на, є як сопзЬ!

const char *означає лише дані, на які вказував вказівник, є const. вказівник сам по собі не const.

Приклад.

const char *p = "Nawaz";
p[2] = 'S'; //error, changing the const data!
p="Sarfaraz"; //okay, changing the non-const pointer. 

const char * const p = "Nawaz";
p[2] = 'S'; //error, changing the const data!
p="Sarfaraz"; //error, changing the const pointer. 

20

(Я знаю, що це старе, але я хотів поділитися все-таки.)

Просто хотів детальніше розкрити відповідь Томаса Меттьюса. Праве ліве правило декларацій типу C в значній мірі говорить: коли читаєте декларацію типу C, починайте з ідентифікатора і рухайтеся праворуч, коли можна, а ліворуч, коли не можете.

Це найкраще пояснити на кількох прикладах:

Приклад 1

  • Почніть з ідентифікатора, ми не можемо піти направо, тому ми підемо вліво

    const char* const foo
                ^^^^^

    foo - це постійна ...

  • Продовжуйте ліворуч

    const char* const foo
              ^

    foo - це постійний вказівник на ...

  • Продовжуйте ліворуч

    const char* const foo
          ^^^^

    foo - це постійний вказівник на char ...

  • Продовжуйте ліворуч

    const char* const foo
    ^^^^^

    foo - це постійний вказівник на char константа (Повна!)

Приклад 2

  • Почніть з ідентифікатора, ми не можемо піти направо, тому ми підемо вліво

    char* const foo
          ^^^^^

    foo - це постійна ...

  • Продовжуйте ліворуч

    char* const foo
        ^

    foo - це постійний вказівник на ...

  • Продовжуйте ліворуч

    char* const foo
    ^^^^

    foo - це постійний вказівник на char (Повне!)

Приклад 1337

  • Почніть з ідентифікатора, але тепер ми можемо піти правильно!

    const char* const* (*foo[8])()
                            ^^^

    foo - це масив з 8 ...

  • Натисніть на дужки, тому більше не можна йти направо, ідіть ліворуч

    const char* const* (*foo[8])()
                        ^

    foo - це масив з 8 вказівників на ...

  • Готові всередині дужок тепер можуть вийти правильно

    const char* const* (*foo[8])()
                                ^^

    foo - це масив з 8 вказівників на функцію, яка повертається ...

  • Більше нічого вправо, ідіть ліворуч

    const char* const* (*foo[8])()
                     ^

    foo - це масив з 8 вказівників на функцію, який повертає вказівник на ...

  • Продовжуйте ліворуч

    const char* const* (*foo[8])()
                ^^^^^

    foo - це масив з 8 вказівників на функції, що повертає вказівник на постійну ...

  • Продовжуйте ліворуч

    const char* const* (*foo[8])()
              ^

    foo - це масив з 8 вказівників на функції, що повертає вказівник на постійний вказівник на ...

  • Продовжуйте ліворуч

    const char* const* (*foo[8])()
          ^^^^

    foo - це масив з 8 вказівників на функції, що повертає вказівник на постійний вказівник на char ...

  • Продовжуйте ліворуч

    const char* const* (*foo[8])()
    ^^^^^

    foo - це масив з 8 вказівників на функції, який повертає покажчик на постійний вказівник до константи char (Повна!)

Подальше пояснення: http://www.unixwiz.net/techtips/reading-cdecl.html


CMIIW, const char * const foo має бути еквівалентним char const * const foo?
— luochenhuan

@luochenhuan Так, справді вони є.
— Гаррет

12

Багато людей пропонують прочитати специфікатор типу справа наліво.

const char * // Pointer to a `char` that is constant, it can't be changed.
const char * const // A const pointer to const data.

В обох формах вказівник вказує на постійні дані чи дані лише для читання.

У другій формі вказівник неможливо змінити; вказівник завжди буде вказувати на одне і те ж місце.


3

Різниця полягає в тому, що без зайвого constпрограміст міг би змінитися всередині методу, де вказівник вказує на; наприклад:

 void print_string(const char * the_string)
 {
    cout << the_string << endl;
    //....
    the_string = another_string();
    //....

 }

Це було б натомість незаконно, якби підпис був void print_string(const char * const the_string)

Багато програмістів відчувають занадто багатослівне (у більшості сценаріїв) додаткове constключове слово і його опускають, навіть якщо це було б семантично правильним.


2

В останньому ви гарантуєте не змінювати і вказівник, і символ, у першому ви гарантуєте лише, що вміст не зміниться, але ви можете переміщати вказівник навколо


Так, без остаточного const, я міг би фактично встановити вказівник, щоб він вказував на зовсім інший рядок?
— малюнок

Так, без цього остаточного const ви можете використовувати вказівник параметра, щоб зробити деяку ітерацію за арифметикою вказівника, де, якби він був, ви повинні створити власний покажчик, який є копією цього параметра.
— Ісус Рамос

2

Немає жодної причини, що б і не працювала. Все, що print_string()робити - це надрукувати значення. Він не намагається змінити його.

Це гарна ідея зробити функцію, яка не змінює аргументи знаків як const. Перевага полягає в тому, що змінні, які неможливо змінити (або ви не хочете змінювати), можуть бути передані цим функціям без помилок.

Що стосується точного синтаксису, ви хочете вказати, який тип аргументів є "безпечним" для передачі функції.


2

Я думаю, що це дуже рідко актуально, тому що ваша функція не викликається аргументами на зразок & * the_string або ** the_string. Сам вказівник є аргументом типу значення, тому навіть якщо ви його модифікуєте, ви не збираєтесь змінювати копію, яка використовувалася для виклику вашої функції. Версія, яку ви показуєте, гарантує, що рядок не зміниться, і я думаю, що цього в цьому достатньо.


2

const char *означає, що ви не можете використовувати вказівник для зміни того, на що вказано. Ви можете змінити вказівник, щоб вказати на щось інше.

Поміркуйте:

const char * promptTextWithDefault(const char * text)
{
    if ((text == NULL) || (*text == '\0'))
        text = "C>";
    return text;
}

Параметр - неконст-покажчик const char, тому його можна змінити на інше const char *значення (наприклад, постійний рядок). Якщо ж ми помилково написали, *text = '\0'то ми отримаємо помилку компіляції.

Можливо, якщо ви не збираєтесь змінювати те, на що вказує параметр, ви можете зробити параметр const char * const text, але це не звичайно. Зазвичай ми дозволяємо функціям змінювати передані значення на параметри (оскільки ми передаємо параметри за значенням, будь-яка зміна не впливає на абонента).

До речі: цього слід уникати, char const *оскільки це часто неправильне прочитання - це означає те саме, що const char *, але занадто багато людей читають це як значення char * const.


Оце Так! Я намагався з'ясувати різницю між моїм const char *і підписом char const *- як ви написали свій текст BTW, справді допомогли!
— шавлія

1

Майже всі інші відповіді правильні, але вони пропускають один аспект цього: Коли ви використовуєте додатковий constпараметр у оголошенні функції, компілятор, по суті, ігнорує його. На мить давайте ігноруємо складність того, що ваш приклад є вказівником, і просто скористаємося int.

void foo(const int x);

оголошує ту ж функцію, що і

void foo(int x);

Тільки у визначенні функції є додаткове constзначення:

void foo(const int x) {
    // do something with x here, but you cannot change it
}

Це визначення сумісне з будь-якою з декларацій вище. Абонента не хвилює, що xце const- це детальна інформація про реалізацію, яка не є актуальною на сайті виклику.

Якщо у вас є constвказівник на constдані, застосовуються ті самі правила:

// these declarations are equivalent
void print_string(const char * const the_string);
void print_string(const char * the_string);

// In this definition, you cannot change the value of the pointer within the
// body of the function.  It's essentially a const local variable.
void print_string(const char * const the_string) {
    cout << the_string << endl;
    the_string = nullptr;  // COMPILER ERROR HERE
}

// In this definition, you can change the value of the pointer (but you 
// still can't change the data it's pointed to).  And even if you change
// the_string, that has no effect outside this function.
void print_string(const char * the_string) {
    cout << the_string << endl;
    the_string = nullptr;  // OK, but not observable outside this func
}

Небагато програмістів на C ++ constнамагаються робити параметри , навіть коли вони можуть бути, незалежно від того, чи є ці параметри вказівниками.


"компілятор, по суті, ігнорує це" не завжди відповідає дійсності, Visual C ++ 2015 видасть попередження, якщо ви додасте додаткові constдо параметра функції у визначенні, а не в декларації.
— raymai97

@ raymai97: Я думаю, що це помилка в компіляторі 2015 року, але я не маю зручності для тестування 2015 року. Я працюю так, як я описав у 2017 році, що, за моїми розмовами з деякими експертами зі стандартів, - це очікувана поведінка.
— Адріан Маккарті

-1

Різниця між ними полягає в тому, що char * може вказувати на будь-який довільний покажчик. Const char *, навпаки, вказує на константи, визначені в розділі DATA виконуваного файлу. І, таким чином, ви не можете змінювати значення символів рядка const char *.


Я не прошу різниці між char * і const char *. Я запитую між const char * та const char * const
— малюнок

Ця відповідь невірна. А const char*може вказувати куди завгодно.
— Кубік
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.