Как сделать так, чтобы переменная зависела от других переменных внутри класса?

Что не так с переменной international_standard_book_number? Как я могу сделать так, чтобы он менялся, когдаisbn_field_i изменения?

#include <iostream>
#include <string>

class ISBN
{
private:
  unsigned int isbn_field_1 = 0;
  unsigned int isbn_field_2 = 0;
  unsigned int isbn_field_3 = 0;
  char digit_or_letter = 'a';
  std::string international_standard_book_number =
    std::to_string(isbn_field_1) + "-" + std::to_string(isbn_field_2) + "-" +
    std::to_string(isbn_field_3) + "-" + digit_or_letter;

public:
  ISBN()
  {
    isbn_field_1 = 0, isbn_field_2 = 0, isbn_field_3 = 0, digit_or_letter = 'a';
  }
  ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
  {
    isbn_field_1 = a, isbn_field_2 = b, isbn_field_3 = c, digit_or_letter = d;
  }
  friend std::ostream& operator<<(std::ostream& os, ISBN const& i)
  {
    return os << i.international_standard_book_number;
  }
};

int
main()
{
  ISBN test(1, 2, 3, 'b');
  std::cout << test << "\n";
  return 0;
}

Выход:

0-0-0-a

Желаемый результат:

1-2-3-b

Изменить: этот вопрос нацелен на что-то другое (почему, а не как), чем мой, и его ответы не помогают мне так сильно, как ответы из этой темы.

6 ответов

Решение

Что не так с переменной international_standard_book_number? Как я могу сделать так, чтобы он менялся, когдаisbn_field_i изменения?

В общем: вы должны переназначать его каждый раз, когда меняется один компонент.

В вашем конкретном случае: измените конструктор, используя список инициализации.

Я имею в виду... вместо этого

ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
 {isbn_field_1=a, isbn_field_2=b, isbn_field_3=c, digit_or_letter=d;};

записывать

ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
 : isbn_field_1{a}, isbn_field_2{b}, isbn_field_3{c}, digit_or_letter{d}
{}

Теперь ваш пример кода напишите

1-2-3-b

Что меняется?

С участием

ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
 {isbn_field_1=a, isbn_field_2=b, isbn_field_3=c, digit_or_letter=d;};

сначала ваши поля инициализируются по умолчанию, поэтому

isbn_field_1    = 0;
isbn_field_2    = 0;
isbn_field_3    = 0;
digit_or_letter = 'a';

international_standard_book_number="0"+"-"+"0"+"-"+"0"+"-"+'a';

затем выполняется тело конструктора

isbn_field_1    = 1;
isbn_field_2    = 2;
isbn_field_3    = 3;
digit_or_letter = 'b';

но international_standard_book_number оставаться без изменений.

С участием

ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
 : isbn_field_1{a}, isbn_field_2{b}, isbn_field_3{c}, digit_or_letter{d}
{}

список инициализации инициализирует поля (и заменяет инициализацию по умолчанию)

isbn_field_1    = 1;
isbn_field_2    = 2;
isbn_field_3    = 3;
digit_or_letter = 'b';

а затем выполняется инициализация по умолчанию international_standard_book_number но используя новые значения, поэтому

international_standard_book_number="1"+"-"+"2"+"-"+"3"+"-"+'b';

Используйте функцию-член.

#include <iostream>
#include <string>

class ISBN
{
private:
    unsigned int isbn_field_1=0;
    unsigned int isbn_field_2=0;
    unsigned int isbn_field_3=0;
    char digit_or_letter='a';
    std::string international_standard_book_number() const {
        return std::to_string(isbn_field_1)+"-"+std::to_string(isbn_field_2)+"-"+std::to_string(isbn_field_3)+"-"+digit_or_letter;
    }
public:
    ISBN(){isbn_field_1=0, isbn_field_2=0, isbn_field_3=0, digit_or_letter='a';}
    ISBN(unsigned int a, unsigned int b, unsigned int c, char d){isbn_field_1=a, isbn_field_2=b, isbn_field_3=c, digit_or_letter=d;};
    friend std::ostream &operator<<(std::ostream &os, ISBN const &i) 
    { 
        return os << i.international_standard_book_number();
    }
};


int main()
{
    ISBN test(1,2,3,'b');
    std::cout << test << "\n";
    return 0;
}

Переменные в C++ используют сематику значений. Когда ты делаешь

std::string international_standard_book_number=
std::to_string(isbn_field_1)+"-"+std::to_string(isbn_field_2)+"-"+std::to_string(isbn_field_3)+"-"+digit_or_letter;

он присвоит значение international_standard_book_number на основе ценностей, которые isbn_field_nимеет прямо сейчас. Он не создает какую-то автоматическую связь между этими переменными, чтобы гарантировать их синхронизацию.

Если вы хотите такого поведения, вам нужно будет обновить international_standard_book_number каждый раз обновляются одно другое поля.

Поддерживать инварианты класса (зависит от переменных) - это то, что вам нужно кодировать вручную. Это одна из причин, по которой нам нужны занятия. В классе вы можете запретить прямое изменение членов (сделать их закрытыми), но когда они изменяются, например, с помощью специальных методов набора, соответственно обновляются инварианты.

Например

void set_field_1(int field) {
  isbn_field_1 = field;
  international_standard_book_number = std::to_string(isbn_field_1)+"-"+std::to_string(isbn_field_2)+"-"+std::to_string(isbn_field_3)+"-"+digit_or_letter; 
}

Этот код в Visual Studio 2019 по крайней мере работает:

#include <iostream>
#include <string>

class ISBN
{
private:
    unsigned int isbn_field_1 = 0;
    unsigned int isbn_field_2 = 0;
    unsigned int isbn_field_3 = 0;
    char digit_or_letter = 'a';
    std::string international_standard_book_number =
        std::to_string(isbn_field_1) + "-" + std::to_string(isbn_field_2) + "-" +
        std::to_string(isbn_field_3) + "-" + digit_or_letter;

public:
    ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
        :isbn_field_1(a), isbn_field_2(b), isbn_field_3(c), digit_or_letter(d), international_standard_book_number(std::to_string(isbn_field_1) + "-" + std::to_string(isbn_field_2) + "-" +
            std::to_string(isbn_field_3) + "-" + digit_or_letter)
    {
    }
    friend std::ostream& operator<<(std::ostream& os, ISBN const& i)
    {
        return os << i.international_standard_book_number;
    }
};

int
main()
{
    ISBN test(1, 2, 3, 'b');
    std::cout << test << "\n";
    test = {2, 3, 4, 'c'};
    std::cout << test << "\n";
    return 0;
}

Кроме того, почему пустой конструктор?

Я хочу добавить к ответу @max66.

Обычно у вас есть иерархия конструкторов, вызывающих друг друга, и один последний финальный "главный" конструктор, который принимает все аргументы и инициализирует переменные. Это позволяет избежать дублирования кода и значительно упрощает, какие конструкторы что инициализируют. Вы можете увидеть, что я имею в виду, в приведенном ниже примере. Кроме того, для правильного форматирования строк в удобочитаемой форме используйте библиотеку {fmt}:

#include <fmt/format.h>
#include <string>

class ISBN
{
private:
        unsigned int isbn_field_1;
        unsigned int isbn_field_2;
        unsigned int isbn_field_3;
        char digit_or_letter;
        std::string international_standard_book_number;

public:
        ISBN()
                : ISBN{ 0, 0, 0, 'a' }
        {}
        ISBN(unsigned int a, unsigned int b, unsigned int c, char d)
                : isbn_field_1{ a }
                , isbn_field_2{ b }
                , isbn_field_3{ c }
                , digit_or_letter{ d }
                , international_standard_book_number{ fmt::format("{}-{}-{}-{}",
                                                                  isbn_field_1,
                                                                  isbn_field_2,
                                                                  isbn_field_3,
                                                                  digit_or_letter) }
        {}
};

Если вам нужно установить значение только один раз (например, другие значения не меняются после создания объекта), вы можете использовать список инициализаторов:

ISBN(unsigned int a, unsigned int b, unsigned int c, char d) 
    : isbn_field_1(a), 
      isbn_field_2(b),
      isbn_field_3(c),
      digit_or_letter(d),
      international_standard_book_number(
          std::to_string(isbn_field_1) + "-" + 
          std::to_string(isbn_field_2) + "-" + 
          std::to_string(isbn_field_3) + "-" + 
          digit_or_letter)
{};

Но имейте в виду, что члены по-прежнему инициализируются в том порядке, в котором они объявлены, а не в порядке списка инициализаторов.

Технически вам не нужно инициализировать international_standard_book_number в списке инициализаторов, как показывает ответ max66, это вопрос личных предпочтений.

Другие вопросы по тегам