Область видимости класса?

Grumpy
Offline
Зарегистрирован: 05.07.2022

Добрый день! В проекте столкнулся с ошибками на этапе компиляции, в приведенном ниже коде сообщение об ошибке в процедуре PrintAt() "'Font' was not declared in this scope". Если класс вызывать напрямую в функции, без передачи в параметрах, то все работает, но мне как раз и нужно передать имя класса в процедуру PrintAt(). enum кидал в отдельный заголовок, не помогает. Ради интереса эту же ситуацию прогонял в Microsoft Visual Studio обыкновенным компилятором C++, все работает идеально. Начитавшись за два дня форумов, для себя сделал вывод, что возможно все дело в Arduino IDE ванильный C++ урезается, дабы несмышленый кодер не накодил чего лихого... Основной код и вызываемый класс:

enum SystemFonts {
    SetTimeMenu_Font,
    ASCII_Font
};

void PrintAt(char *ch, uint8_t x, uint8_t y, SystemFonts SF);

void PrintAt(char *ch, uint8_t x, uint8_t y, SystemFonts SF) {
  if (SF == SetTimeMenu_Font) {
    FONT_SetTimeMenu Font;
  }
  else if (SF == ASCII_Font) {
    FONT_ASCII Font;
  }
  Serial.println(Font.FONT_WIDTH);
}

void TimeSettings(){
  PrintAt("00:00", 0, 0, SetTimeMenu_Font);
}
class FONT_SetTimeMenu {
public:
  const int FONT_ARRAY[11][20] =
  {
    {0x00, 0x00, 0xFC...
    {0x00, 0x00, 0x00...
    {0x00, 0x00, 0x0C...
    .
    .
    .
  };
  const int FONT_WIDTH = 20;
  const int FONT_HEIGHT = 11;
  const int FONT_START_CHAR = 0x30;
  const int FONT_CHAR_WIDTH = 10;
  const int FONT_CHAR_HEIGHT = 16;
};

Заранее благодарен за помощь!!!

rkit
Offline
Зарегистрирован: 23.11.2016

В твоем коде Font не определен. У тебя две совершенно разные переменные в разных контекстах. Так работать не будет. И объявлять огромную переменную на стеке функции глубоко в дереве вызовов - дурацкая затея.

b707
Offline
Зарегистрирован: 26.05.2017

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

Поэтому область действия переменной Font, обьявленной в строке 10, заканчивается у скобки в строке 11. Закономерно что в строке 15 вы получаете ошибку.

Grumpy
Offline
Зарегистрирован: 05.07.2022

rkit пишет:

В твоем коде Font не определен. У тебя две совершенно разные переменные в разных контекстах. Так работать не будет. И объявлять огромную переменную на стеке функции глубоко в дереве вызовов - дурацкая затея.

Как же правильно сделать в этом случае? У меня несколько классов со шрифтами и одна процедура для их вызова.

b707
Offline
Зарегистрирован: 26.05.2017

Grumpy пишет:

Как же правильно сделать в этом случае?

вариантов дофига

Например переместить строчку 15 внутрь блоков. где обьявлен Font или использовать перегрузку функций... или шаблоны под каждый тип фонта...

 

В любом случае, если классы фонтов существенно разные и не связаны наследованием. написать одну функцию, работающую с разными фонтами - не получится.

Grumpy
Offline
Зарегистрирован: 05.07.2022

b707 пишет:

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

Поэтому область действия переменной Font, обьявленной в строке 10, заканчивается у скобки в строке 11. Закономерно что в строке 15 вы получаете ошибку.

 

Да, перепроверил, я и в MVS неправильно смоделировал ситуацию, и пошел не в том направлении.

rkit
Offline
Зарегистрирован: 23.11.2016

Grumpy пишет:

Как же правильно сделать в этом случае? У меня несколько классов со шрифтами и одна процедура для их вызова.

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

Потом определить нормальные глобальные константы в progmem, и обернуть работу с ними в класс с легким экземпляром содержащим только адрес константы, если уж так хочется.

Grumpy
Offline
Зарегистрирован: 05.07.2022

b707 пишет:

вариантов дофига

Например переместить строчку 15 внутрь блоков. где обьявлен Font или использовать перегрузку функций... или шаблоны под каждый тип фонта...

 

В любом случае, если классы фонтов существенно разные и не связаны наследованием. написать одну функцию, работающую с разными фонтами - не получится.

Пытался посмотреть как это реализовано в адафрут, шрифты в заголовочных файлах в виде двух массивов, две структуры с описанием, а вот дальше не понял, указатели на указателях и указателями погоняют)

По вопросу, универсальный алгоритм уже написал, работает, вместо него в примере заглушка Serial.print... для наглядности и простоты примера.

Grumpy
Offline
Зарегистрирован: 05.07.2022

rkit пишет:

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

Потом определить нормальные глобальные константы в progmem, и обернуть работу с ними в класс с легким экземпляром содержащим только адрес константы, если уж так хочется.

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

b707
Offline
Зарегистрирован: 26.05.2017

Grumpy пишет:

Тем более progmem не работает с классами в ардуино, ну или работает, но с костылями...

фигня.

работает ровно так же, как и без классов...

Классы - не более чем абстракция для удобства программиста.

Grumpy
Offline
Зарегистрирован: 05.07.2022

b707 пишет:

фигня.

работает ровно так же, как и без классов...

Классы - не более чем абстракция для удобства программиста.

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

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Да, кстати, а если сделать константный класс, поля которого не изменяюся во время выполнения, можно ли его в PROGMEM запхать, как обычную строку?  Если у него виртуальных методов не будет, то компилятор и VMT создавать не будет, наерна.  Или класс на 146% состоящий из одних static constexpr. 

Всё, вопрос дебильный, конструктор всё равно должен вызваться, чтоб инициализировать поля.  

b707
Offline
Зарегистрирован: 26.05.2017

Grumpy пишет:

Откройте секрет, на форумах пишут, что классы и конструкты не поддерживаются прогмемом.

а как же фонты от адафруит? Фонты это именно структуры и их экземпляры отлично кладутся в прогмем.

Что касается классов - то поясните, что вы подразумеваете под идеей "положить класс в прогмем". Класс - это описание данных и методов работы с ними. Положить класс в прогмем нельзя точно так же, как нельзя сохранить в прогмеме цикл или функцию. А вот данные экземпляра класса положить в прогмем можно. если вы, конечно, дружите с такими понятиями как бинарное представление данных и указатели...

b707
Offline
Зарегистрирован: 26.05.2017

DetSimen пишет:

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

вопрос тот же, что и ТС - ты что в прогмем класть собрался - класс или экземпляр?

Grumpy
Offline
Зарегистрирован: 05.07.2022

b707 пишет:

а как же фонты от адафруит? Фонты это именно структуры и их экземпляры отлично кладутся в прогмем.

Что касается классов - то поясните, что вы подразумеваете под идеей "положить класс в прогмем". Класс - это описание данных и методов работы с ними. Положить класс в прогмем нельзя точно так же, как нельзя сохранить в прогмеме цикл или функцию. А вот данные экземпляра класса положить в прогмем можно. если вы, конечно, дружите с такими понятиями как бинарное представление данных и указатели...

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

const uint8_t FreeMono9pt7bBitmaps[] PROGMEM = {
    0xAA, 0xA8, 0x0C, 0xED, 0x24, 0x92, 0x48, 0x24, 0x48, 0x91, 0x2F, 0xE4,
    0x89, 0x7F, 0x28, 0x51, 0x22, 0x40, 0x08, 0x3E, 0x62, 0x40, 0x30, 0x0E,
    0x01, 0x81, 0xC3, 0xBE, 0x08, 0x08, 0x71, 0x12, 0x23, 0x80, 0x23, 0xB8,
    0x0E, 0x22, 0x44, 0x70, 0x38, 0x81, 0x02, 0x06, 0x1A, 0x65, 0x46, 0xC8,
    0xEC, 0xE9, 0x24, 0x5A, 0xAA, 0xA9, 0x40, 0xA9, 0x55, 0x5A, 0x80, 0x10,
    .
    .
    0x2C, 0x61, 0x24, 0x30};

const GFXglyph FreeMono9pt7bGlyphs[] PROGMEM = {
    {0, 0, 0, 11, 0, 1},      // 0x20 ' '
    {0, 2, 11, 11, 4, -10},   // 0x21 '!'
    {3, 6, 5, 11, 2, -10},    // 0x22 '"'
    {7, 7, 12, 11, 2, -10},   // 0x23 '#'
    {18, 8, 12, 11, 1, -10},  // 0x24 '$'
    {30, 7, 11, 11, 2, -10},  // 0x25 '%'
    {40, 7, 10, 11, 2, -9},   // 0x26 '&'
    .
    .
    .
    {841, 7, 3, 11, 2, -6}};  // 0x7E '~'

Вторая таблица это описание шрифтов, т.к. насколько понял, они для экономии памяти еще и рисуют их нетривиально.

Grumpy
Offline
Зарегистрирован: 05.07.2022

b707 пишет:

а как же фонты от адафруит? Фонты это именно структуры и их экземпляры отлично кладутся в прогмем.

Что касается классов - то поясните, что вы подразумеваете под идеей "положить класс в прогмем". Класс - это описание данных и методов работы с ними. Положить класс в прогмем нельзя точно так же, как нельзя сохранить в прогмеме цикл или функцию. А вот данные экземпляра класса положить в прогмем можно. если вы, конечно, дружите с такими понятиями как бинарное представление данных и указатели...

Да, мне как раз данные экземпляра класса и нужно хранить, это константы.

Grumpy
Offline
Зарегистрирован: 05.07.2022

b707 пишет:

а как же фонты от адафруит? Фонты это именно структуры и их экземпляры отлично кладутся в прогмем.

Что касается классов - то поясните, что вы подразумеваете под идеей "положить класс в прогмем". Класс - это описание данных и методов работы с ними. Положить класс в прогмем нельзя точно так же, как нельзя сохранить в прогмеме цикл или функцию. А вот данные экземпляра класса положить в прогмем можно. если вы, конечно, дружите с такими понятиями как бинарное представление данных и указатели...

Так. Я не доглядел ниже, в заголовочном файле с массивами шрифтов у Адафрут есть в самом низу инициализация структуры

const GFXfont FreeMono9pt7b PROGMEM = {(uint8_t *)FreeMono9pt7bBitmaps,
                                       (GFXglyph *)FreeMono9pt7bGlyphs, 0x20,
                                       0x7E, 18};

 

И в заголовочном файле со структурами само определение структуры

/// Data stored for FONT AS A WHOLE
typedef struct {
  uint8_t *bitmap;  ///< Glyph bitmaps, concatenated
  GFXglyph *glyph;  ///< Glyph array
  uint16_t first;   ///< ASCII extents (first char)
  uint16_t last;    ///< ASCII extents (last char)
  uint8_t yAdvance; ///< Newline distance (y axis)
} GFXfont;

Теперь немного яснее становится)

b707
Offline
Зарегистрирован: 26.05.2017

Grumpy пишет:

Вторая таблица это описание шрифтов

это структура, которую, как вы пишете, нельзя сохранить в прогмеме...:)

описание этой структуры находится в файле glcfont.h

b707
Offline
Зарегистрирован: 26.05.2017

Grumpy пишет:

И в заголовочном файле со структурами само определение структуры

/// Data stored for FONT AS A WHOLE
typedef struct {
  uint8_t *bitmap;  ///< Glyph bitmaps, concatenated
  GFXglyph *glyph;  ///< Glyph array
  uint16_t first;   ///< ASCII extents (first char)
  uint16_t last;    ///< ASCII extents (last char)
  uint8_t yAdvance; ///< Newline distance (y axis)
} GFXfont;

Теперь немного яснее становится)

как вы можете видеть, описание структуры во флеш не пишется(нет спецификатора PROGMEM). А вот данные конкретного экземпляра (фонта) - в прогмеме (спецификатор есть)

Grumpy
Offline
Зарегистрирован: 05.07.2022

b707 пишет:

как вы можете видеть, описание структуры во флеш не пишется(нет спецификатора PROGMEM). А вот данные конкретного экземпляра (фонта) - в прогмеме (спецификатор есть)

О да, нужно быть внимательней, спасибо!:)

Grumpy
Offline
Зарегистрирован: 05.07.2022

b707 пишет:

как вы можете видеть, описание структуры во флеш не пишется(нет спецификатора PROGMEM). А вот данные конкретного экземпляра (фонта) - в прогмеме (спецификатор есть)

Кстати, я использую буфер памяти для дисплея в виде двумерного массива, хотя, как погляжу, многие используют одномерный. Соответственно я и массив шрифтов использую тоже двумерный, просто потому что мне так проще показалось, работать с двумя координатами, а не пересчитывать из одномерного в две координаты при выводе на дисплей. Есть ли на ваш взгляд принципиальная причина применения именно одномерного буфера?

b707
Offline
Зарегистрирован: 26.05.2017

Grumpy пишет:

Есть ли на ваш взгляд принципиальная причина применения именно одномерного буфера?

многомерные массивы в си - это всего лишь внешнее представление для программиста. Массивы a[2][2] и b[4] в памяти представлены одинаково. Поэтому что в данном случае удобнее. то и используйте

 

Grumpy
Offline
Зарегистрирован: 05.07.2022

Понял, спасибо!