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

У меня есть класс с 12 экземплярами кнопки для создания клавиатуры( это означает, что это единственный случай, когда я использую 12 экземпляров). Но этот класс может быть просто массивом кнопок с любым другим числом от 1 до 12. Я ищу способ минимизировать накладные расходы класса из 12 экземпляров, когда я большую часть времени использую гораздо меньше.

файл '.h':

class buttonArrayTFT
{
public:
  buttonArrayTFT(XPT2046_Touchscreen &_ts = ts, Adafruit_ILI9341 &_tft = tft);
  void create_array(uint8_t R, uint8_t C, char *but_txt[]);
  uint8_t checkPress(TS_Point &p);

public:
  
protected:
  ButtonTFT _button0;
  ButtonTFT *_buttons[12] = {&_button0, &_button1, &_button2, &_button3,
                             &_button4, &_button5, &_button6, &_button7,
                             &_button8, &_button9, &_button10, &_button11};
private:
  ButtonTFT _button1;
  ButtonTFT _button2;
  ButtonTFT _button3;
  ButtonTFT _button4;
  ButtonTFT _button5;
  ButtonTFT _button6;
  ButtonTFT _button7;
  ButtonTFT _button8;
  ButtonTFT _button9;
  ButtonTFT _button10;
  ButtonTFT _button11;
  
private:
  uint8_t _num_items = 0;
};

, 👍1

Обсуждение

http://www.cplusplus.com/doc/oldtutorial/templates/, @Majenko

@Majenko Не могли бы вы указать, пожалуйста?, @Guy . D


4 ответа


Лучший ответ:

4

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

Шаблоны позволяют писать очень гибкий код. Я не буду объяснять почему; это то, что выходит немного за рамки самого вопроса. Однако ниже показано, что вы можете создать шаблон, который означает, что вы можете создать класс buttonArrayTFT с N количеством элементов buttonTFT, если N известно во время компиляции. Ссылка, приведенная в качестве комментария, описывает различные аспекты шаблонов, но не обязательно объясняет, как связать все это вместе.

В разделе Параметры, не относящиеся к типу для шаблонов, вы увидите, что вы можете указать целое число в качестве параметра для создания N элементов переменной-члена массива.

Но как насчет параметра типа, T? Вы можете просто опустить его. На самом деле вам не нужно создавать шаблон для типа.

В вашем файле .h:

template <uint8_t ARRAY_SIZE>
class buttonArrayTFT
{
public:
// One thing to remember about templates: you have to pack the definition
// into the header file with the declaration to avoid all manner of headaches.

    buttonArrayTFT(XPT2046_Touchscreen &_ts = ts, Adafruit_ILI9341 &_tft = tft)
    {
    // Perhaps this is a good opportunity to check that the specified
    // size of the array is sensible, i.e. not zero and no more than 12.
        if ((ARRAY_SIZE == 0) || (ARRAY_SIZE > 12))
        {
            // decide how you want to handle this problem
        }
            // Add the rest of your constructor here
    }

    void create_array(uint8_t R, uint8_t C, char *but_txt[]);
    uint8_t checkPress(TS_Point &p);
        
    // This little piece of magic allows you to access buttonTFT 'x' of the
    // private _buttonArray as if a buttonArrayTFT class was an array
    buttonTFT &operator[](uint8_t index)
    {
    // Check that 'index' is a valid element in the array
        if (index > _num_items)
        {
            // decide how you want to handle this problem
        }
        return _buttonArray[index];
    }
private: 
    buttonTFT _buttonArray [ARRAY_SIZE];
    // I recommend making this constant
    const uint8_t _num_items = ARRAY_SIZE;
};

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

Вы также заметите, что массив, как и раньше, является закрытым, но теперь нет массива указателей на кнопки. Это происходит потому, что в этом нет необходимости; волшебная кнопка и оператор[](индекс int) позволяют вам напрямую обращаться к элементам массива.

В вашем файле .ino или, в зависимости от обстоятельств, вы используете следующий код для создания и последующего использования класса:

buttonArrayTFT<4> myFourButtonArray(/* constructor arguments as required*/);
buttonArrayTFT<12> myTwelveButtonArray(/* constructor arguments as required*/);

Это дает вам два экземпляра класса с соответствующим количеством элементов buttonTFT! Теперь вы можете получить доступ к определенным кнопкам, используя следующий код:

myFourButtonArray[0]; // Возвращает ссылку на первую кнопку
myTwelveButtonArray[10]; // Возвращает ссылку на одиннадцатую кнопку
myFourButtonArray[n] // Возвращает ссылку на "n+1" - ю кнопку

Исходя из предположения, что класс buttonTFT имеет какую-то функцию "чтение", которая возвращает значение TRUE или FALSE для статуса кнопки, вы можете написать:

if (myFourButtonArray[0].read())
{
    // выполняется, если кнопка включена
}
else
{
    // выполняется, если кнопка выключена
}

ПРИМЕЧАНИЕ О ПОСТОЯНСТВЕ:

Перегрузка оператора[], хотя и удивительно волшебная, открыта для злоупотреблений. Однако это ничем не отличается от вашего исходного кода. Массив элементов buttonTFT является закрытым, но есть открытый доступ ко всем кнопкам, как если бы массив не был закрытым. В случае вашего исходного кода можно было бы написать:

myButtonArray._buttons[0] = myButtonArray._buttons[1];

Теперь _buttons[0] указывает на ту же кнопку, что и _buttons[1].

Ой. Теперь myButtonArray._buttons[0]->читать()> буквально то же самое, что и myButtonArray._buttons[1]->читать()>.

Тем не менее, мой код не делает его намного лучше; эквивалентный код:

myFourButtonArray[0] = myFourButtonArray[1];

Просто скопируйте правую сторону на левую сторону. У вас все еще есть два отдельных экземпляра buttonTFT, вероятно, без утечек памяти, но я предполагаю, что класс buttonTFT может содержать ссылку на входной пин, поэтому pin _button[0] теперь будет любым пином _button[1], потому что переменная-член, в которой хранится ссылка на pin, будет перезаписана.

Чтобы устранить эту слабость, просто добавьте классификатор const в начало перегрузки оператора []:

const buttonTFT &operator[](uint8_t index)
{ // };

Теперь элемент buttonTFT не может быть изменен. Это, конечно, вызывает новую головную боль, что все функции, которые полагаются на myArray[n], также должны быть постоянными. Вам решать, как следует интерпретировать постоянную корректность и стоит ли это хлопот.

,

Ну что ж - так много информации предстоит узнать :)) во - первых-большое вам спасибо за лаконичный ответ. Необходимы некоторые разъяснения (и, возможно, за ними последуют еще некоторые ). 1) Я не знал, что файл .h может получать операторы "если", а не только заголовки. 2) вы написали " вернуть массив[индекс]", какой параметр является массивом ? 3) использование оператора & - не могли бы вы объяснить, пожалуйста, как это работает ? 4) можете ли вы знать, как будет выглядеть файл .cpp, @Guy . D

theArray, вероятно, должен был относиться к члену "_buttonArray" (а не к параметру)., @timemage

Я написал пример кода со своими именами участников и пропустил одно из них, когда менял их, чтобы они соответствовали вашим. Ответ был отредактирован., @CharlieHanson

@Guy.D (1) Да, вы можете написать все, что захотите, в файле .h, конечно, при условии, что это подлинный код C++. Когда дело доходит до шаблонов, вы не можете легко разделить определение и объявление на отдельные файлы из-за того, как работает компиляция. В Google есть множество статей на эту тему. (2) Мои извинения, это осталось с тех пор, как я написал пример и не смог вспомнить, как вы называли своих участников., @CharlieHanson

@Guy.D (3) " & " на самом деле принадлежит спецификатору типа buttonTFT, но соглашение требует, чтобы он был написан таким образом. Символ означает, что функция возвращает ссылку. Концепция *Операторов* может привести к путанице, поэтому прочтите о них [в этой статье](https://www.cplusplus.com/doc/tutorial/operators/) (4) Я не могу показать вам, как выглядит файл .cpp, так как я не знаю, что в нем находится! Я могу сказать вам, что любой код, относящийся к шаблонному классу, принадлежит файлу .h, все остальное содержится в файле .cpp., @CharlieHanson

int x = 7; int& y = x, z = x; " y "- это ссылка на "int", которая была привязана к "x"; по сути, новое имя для " x. z - это просто " int "(*не* ссылка) и просто инициализируется копией со значениемx". Так что на самом деле " & " соответствует заявленной вещи. То же самое с указателями "int* u, v"; " u "- это указатель на "int"; " v "- это просто " int`. По какой-то причине большинство пользователей C++ (не я) хотят написать декларатор рядом с базовым типом, даже если это не то, с чем он связан синтаксически. Я думаю, они просто хотят притвориться, что язык работает иначе, чем на самом деле., @timemage

@CharlieHanson Я понимаю, к чему относится"&", и это не было фактическим вопросом (возможно, из-за отсутствия у меня навыков английского языка), каково фактическое использование члена "оператора". Итак, я написал свой класс, как вы и майенко объяснили, но я получил ошибки, когда закодировал его обратно в библиотеку (.h и .cpp). Не могли бы вы, пожалуйста, указать на это ?, @Guy . D

@Парень.D "Оператор" - это то, что выполняет операции над чем-то другим. Подумайте о простом коде int x = 1 + 2. "+"- это _оператор_, и компилятор знает, что "int + int" - это простая математическая сумма. Но как насчет "MyType t = &foo + &bar"? Компилятор не знает, как "добавить" один тип MyType в другой, поэтому вам нужно _ перезагрузить оператор _ и написать код, который должен быть выполнен. Чтобы устранить путаницу, " + "упоминается как" оператор+". Все _операторы_, как-, '&&, (), и т.д. аналогично называются " оператор-, оператор&&, оператор()`., @CharlieHanson

@CharlieHanson и таким образом при использовании [] он возвращает указатель на экземпляр кнопки?, @Guy . D

@Парень.D Он вернет любой тип, который вы объявите возвращаемой функцией оператора. В этом случае вы хотите, чтобы "оператор []" возвращал __ссылку__ на "buttonTFT", а не ссылку на "buttonArrayTFT", потому что это не имеет никакого смысла. Вы также не хотите, чтобы он возвращал указатель, потому что "_buttonArray" является массивом "buttonTFT", а не " buttonTFT*". Кроме того, как я объяснил в своем ответе, "*(buttonTFT[x]) = (*buttonTFT[y]) " оставит вас с указателем левой руки, указывающим на указатель правой руки, но только на время этой строки кода, поэтому теоретически на самом деле ничего не произойдет., @CharlieHanson


4

Вы можете использовать класс шаблона для предоставления параметров времени компиляции вашему классу для его настройки. Это чаще всего используется в контексте предоставления типа для работы с классом. Однако этим дело не ограничивается. Он также часто используется для предоставления классу размера хранилища или другой подобной константы.

Они работают как макросы препроцессора. Вы указываете маркер в классе, а затем присваиваете этому маркеру значение при создании экземпляра объекта. Затем этот токен можно будет использовать повсюду в вашем классе.

В вашем случае вы можете указать количество элементов в массиве кнопок в качестве переменной шаблона, например (примечание: непроверено):

template <int N> class buttonArrayTFT {
    protected:
        ButtonTFT _buttons[N];
    public:
        void doSomething();
};

template <int N> void buttonArrayTFT::doSomething() {
    for (int i = 0; i < N; i++) {
        _buttons[i].doSomethingOnAButton();
    }
}

Конечно, вы должны относиться к своим объектам немного по-другому, так как вы больше не можете создавать массив указателей на предопределенные объекты. Вместо этого должно быть достаточно простого массива объектов.

Затем вы создаете экземпляр своего класса, передавая необходимое количество элементов:

// Массив из 12 кнопок
buttonArrayTFT<12> myButtons;
// и сделать что-нибудь
myButtons.doSomething();
,

Заменяет ли " ButtonTFT _buttons[N]" все определение? в своем коде я использую массив указателей на N экземпляров, поэтому, похоже, чего-то не хватает (или моего понимания), @Guy . D

Я написал класс не в библиотеке, и все было хорошо. НО когда я записываю это в свой файл lib, я получаю ошибки. Как это следует отметить таким образом ?, @Guy . D

@Парень.D Какие ошибки вы получаете?, @Majenko

Пожалуйста, смотрите мой ответ ниже, @Guy . D

@Парень.D Поскольку шаблон создается во время компиляции, вы не можете создать отдельный TU с ним - он фактически должен быть на 100% встроенным. Он будет работать только в заголовочном файле., @Majenko


0

Я хочу поделиться своей последней обновленной библиотекой (благодаря ответам Майенко и Чарли), возможно, это поможет другим.

файл .h

template <uint8_t N>
class buttonArray_TFT
{
public:
  int8_t dx = 5;         /* define spacing between buttons */
  int8_t dy = 5;         /* define spacing between buttons */
  uint8_t scale_f = 100; /* change the cale of array. 100% take entire screen */
  uint8_t shift_y = 255; /* Shifts in y director*/
  uint8_t shift_x = 255; /* Shifts in x director*/
  int shrink_shift = 0;  /* shrink array in pixels, and shifts up/ down (+/-) */

  uint8_t &a = butarray[0].a;
  uint8_t &b = butarray[0].b;
  uint8_t &txt_size = butarray[0].txt_size;
  uint16_t &txt_color = butarray[0].txt_color;
  uint16_t &border_color = butarray[0].border_color;
  uint16_t &face_color = butarray[0].face_color;
  bool &roundRect = butarray[0].roundRect;

protected:
  ButtonTFT butarray[N];

public:
  buttonArray_TFT(XPT2046_Touchscreen &_ts = ts, Adafruit_ILI9341 &_tft = tft);
  void create_array(uint8_t R, uint8_t C, char *but_txt[]);
  uint8_t checkPress(TS_Point &p);
};

и многое другое в : файл .h :

template <uint8_t N>
buttonArray_TFT<N>::buttonArray_TFT(XPT2046_Touchscreen &_ts, Adafruit_ILI9341 &_tft)
{
  for (int i = 0; i < N; i++)
  {
    butarray[i].TS[0] = &ts;
    butarray[i].TFT[0] = &tft;
  }
}

template <uint8_t N>
void buttonArray_TFT<N>::create_array(uint8_t R, uint8_t C, char *but_txt[])
{
\\ Code 
}

template <uint8_t N>
uint8_t buttonArray_TFT<N>::checkPress(TS_Point &p)
{
  for (uint8_t i = 0; i < N; i++)
  {
    if (butarray[i].checkPress(p))
    {
      return i;
    }
  }
  return 99;
}
,

Вы не можете разделить код на два файла. ВСЕ для класса buttonTFT должно быть в файле _.h_, как мы с Майенко уже говорили. Я знаю, что это противоречит всем правилам, которым вас учили в противном случае, но шаблоны-исключение из правила., @CharlieHanson

@CharlieHanson - Спасибо. Я исправлю свой ответ. Ценю пояснения по поводу " оператора`, @Guy . D


2

Есть еще несколько вариантов решения вашего вопроса.


Использовать шаблон, как рекомендуется в других ответах, - это необычный вариант, но если вы используете шаблон несколько раз, он создает реальный класс для каждого значения шаблона, и вы увидите его в используемом размере флэш-памяти.


На мой взгляд, лучший вариант - предоставить массив классу через конструктор. Создайте глобальный массив кнопок и передайте его объекту buttonArrayTFT через конструктор.

const uint8_t BUTTON_COUNT = 12;
ButtonTFT buttons[BUTTON_COUNT];
ButtonArrayTFT buttonArrayTFT(buttons, BUTTON_COUNT);

Класс ButtonArrayTFT (упрощенный) будет выглядеть следующим образом:

class ButtonArrayTFT {
private:
  ButtonTFT *buttons;
  uint8_t count;
public:
  ButtonArrayTFT(ButtonTFT *_buttons, uint8_t _count) {
    buttons = _buttons;
    count = _count;
  }

Другой вариант - использовать динамическое распределение в куче. Если бы вы создали объект ButtonArrayTFT только один раз при запуске и никогда не удаляли его, это не привело бы к фрагментации кучи.

class ButtonArrayTFT {
private:
  ButtonTFT *buttons;
  uint8_t count;
public:
  ButtonArrayTFT(uint8_t _count) {
    count = _count;
    buttons = new ButtonTFT[count];
  }
,

Я пытаюсь "переварить" все новое в одном вопросе (полиморфизм,встроенный,&оператор,шаблон ...аааааааа). Я постараюсь понять, как вы предложили через некоторое время :) TNX!, @Guy . D

Глобальный подход-хороший вариант, если вы не возражаете против прямого доступа к кнопкам из любого места кода. Сокрытие их в классе напоминает "будущему мне" и любому другому, кто унаследует код, что они скрыты по какой-то причине. Понятия не имею, в чем причина, но это предотвращает нежелательную возню. Динамическое распределение выиграло бы от отслеживания памяти; "статическая" переменная, которая увеличивается на "count * sizeof(кнопка)" в конструкторе, плюс, возможно, "static_assert", предотвратит попадание в стену памяти., @CharlieHanson

И +1 для строки о создании одного полного класса для каждого отдельного количества кнопок. Я не учел, что если один и тот же метод будет использоваться на многочисленных элементах графического интерфейса, пространство программы может вскоре испариться., @CharlieHanson