Урок 15. Указатели в C для Ардуино. Преобразование разных типов данных в байты.

 

Указатели в C++ для Ардуино

В уроке узнаем, что такое указатели, и как они позволяют оптимизировать код программы, научимся преобразовывать сложные типы данных (int, long, float…) в последовательность байтов.

Предыдущий урок     Список уроков     Следующий урок

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

 

Тема указателей в языке C++ важная и обширная. Сейчас я очень коротко расскажу об указателях. Подробно примеры их использования будут рассмотрены в следующих уроках по мере необходимости.

Указатели в C для Ардуино.

При разработке программы мы работаем с переменными разных типов, массивами, объектами, функциями…

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

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

  • Указатели могут быть использованы для передачи данных по ссылке. Это намного ускоряет обработку данных, т.к. нет необходимости в копировании данных, как это делается при передаче с использованием имени переменной.
  • Указатели используются для динамического распределения памяти, например, для массива неограниченного размера.
  • Указатели удобно использовать для преобразования различных типов данных в байтовые потоки.

 

Косвенная адресация.

Узнать адрес конкретной переменно в C++ можно операцией получения адреса &. Она выдает адрес переменной, перед которой написан символ &.

А для обращения к переменной по адресу (указателю) есть операция косвенной адресации *. Она выдает значение ячейки памяти по адресу, на который ссылается указатель.

cod = 15;                    // переменная cod = 15
ptr
Cod= &cod;         // переменая ptrCod = адрес переменной cod
vl = * ptrCod;  // переменная vl = значению по адресу из ptrCod, т.е. vl = cod = 15

Надо понимать, что &cod это число, конкретный адрес. А ptrCod это переменная типа указатель, т.е. переменная для адреса.

Виды указателей.

Бывают указатели:

  • на основные типы;
  • на массивы;
  • на составные объекты (описываемые классами);
  • на функции;
  • на указатели;
  • на void.

Указатели на основные типы.

Как и любая другая переменная, указатель должен быть объявлен перед использованием. При объявлении указателя перед его именем ставится *.

int *ptrdt;  // указатель на переменную типа int
float *ptrx, *ptry, *ptrz;  // указатели на переменные типа float

Чтобы отличать указатели от обычных переменных принято добавлять к имени символы ptr. Но это условие необязательно и многие его не придерживаются.

При объявлении указателей выделяется необходимое число байтов памяти, в зависимости от типа данных. Например, для dt (int) компилятор выделит 2 байта, а для x (float) будет зарезервировано 4 байта.

Указатели на массивы.

Очень эффективно использование указателей при работе с массивами. Имя массива уже представляет собой скрытую форму применения указателей.

Имя массива это указатель на его первый элемент.

int weights[10];  // массив weights
weights == &weights[0];  // имя это адрес первого элемента массива

Удобно использовать указатели на массивы, т.е. имена массивов, как аргументы функций.

int weights[10];  // массив weights
calculateAll(weights);  // функция использует в качестве аргумента имя массива

Указатель на функцию.

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

  • для вызова функции через обращение к переменной с ее адресом, а не через имя;
  • для передачи имени функции в качестве аргумента в другую функцию.

В роке 10 мы использовали функцию MsTimer2::set(). В качестве второго аргумента мы задали имя другой функции (timerInterupt)- обработчика прерывания.

MsTimer2::set(2, timerInterupt); // задаем период прерывания и имя обработчика прерывания

Объявляется указатель на функцию так:

тип (*имя)(аргументы).

По сравнению с объявлением функции добавились скобки и *.

int (*ptrCalc)(int, float);  // объявление указателя на функцию с аргументами int и float
ptrCalc = calculate;  // присвоение указателю ptrCalc  адреса функции calculate
Serial.printf(ptrCalc(x, 2.345)  );  // вызов функции через указатель

int calculate(int, float) {
// тело функции calculate
}

Указатели на void.

Указатели на void используются для ссылки на объект, тип которого не определен. Например, для хранения различных типов данных в одних и тех же ячейках памяти.

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

 

Динамические переменные.

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

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

Выделение памяти для динамической переменной осуществляется с помощью оператора new:

тип_данных *имя_указателя = new тип_данных;

Например:

long *dt = new long;
Выделяется память, достаточная для типа long (4 байта). Адрес начала записывается в указатель dt.

int *weights = new int[50];
Выделяется память для 50 значений типа int (100 байтов). Адрес начала записывается в указатель weights, который может использоваться как имя массива.

Можно при объявлении выполнить инициализацию значения по адресу указателя:

long *dt = new long(102345);  // значение памяти по адресу dt = 102345

Освободить память, выделенную оператором new, можно с помощью оператора delete.

// выделение памяти
long *dt = new long;
int *weights = new int[50];

// код

// освобождение памяти
delete [] weights;
delete  dt;

Заметьте, что для освобождения памяти массивов надо использовать оператор delete []. Если забыть про скобки, то будет удален только первый элемент массива, а остальные будут недоступны.

 

Указатели на составные объекты (описываемые классами).

Для создания динамических объектов также используется оператор new.

Button *buttonPlus = new Button;  // выделение памяти под объект buttonPlus типа Button

При создании статического объекта, доступ к его свойствам и методам происходит операцией прямого обращения – ”.”.

buttonPlus.scanState();
buttonPlus.flagClick = false;

Для работы с динамическим объектом через указатель, для доступа к его свойствам и методам используется оператор косвенного обращения ”->”.

buttonPlus->scanState();
buttonPlus->flagClick = false;

Операции с указателями.

С указателями можно производить простые операции:

  • косвенное обращение (разадресация);
  • присваивание;
  • сложение с константой;
  • вычитание;
  • инкремент;
  • декремент;
  • сравнение;
  • явное приведение типов.

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

Арифметические операции с указателями автоматически учитывают размер типа переменной. Т.е. для типа данных char инкремент указателя увеличит адрес памяти на 1 байт, а для типа long прибавление 1 к указателю прибавит к реальному адресу памяти 4 байта. Арифметические операции применяют в основном при работе с данными, размещенными в памяти последовательно, например, с массивами.

 

Преобразование разных типов данных в байты.

В прошлом уроке мы сохраняли данные в EEPROM платы Ардуино. Сохраняли мы данные типа byte. EEPROM хранит данные в байтах и наши данные в байтах. Все просто. Но, допустим, нам надо сохранить в EEPROM переменную типа int . Она занимает в памяти 2 байта и для записи в энергонезависимую память ее надо преобразовать в отдельные байты. Можно сделать так:

int dt = 0x1234;  // переменная типа int, которую надо преобразовать в байты
byte byteEeprom1 =     (byte)(dt & 0xff);  // младший байт = 0x34
byte byteEeprom2 =     (byte)(dt >> 8);     // старший байт = 0x12

Мы получили два байта, но пришлось выполнить несколько операций, в том числе сдвиг на 8 разрядов. Для обработки переменной типа long сдвигов и других операций будет значительно больше. А вот что делать с переменной типа float я вообще не представляю. Преобразовать ее в число с фиксированной запятой? Наверное, что-то можно придумать, но это будет сложное решение и потребует значительных ресурсов от микроконтроллера. А ведь все переменные хранятся в памяти, разбитой на байты. Надо и взять их из памяти в виде байтов. Сделать это можно с помощью указателя.

Преобразуем переменную типа int в байты таким способом.

int dt = 0x1234;  // переменная типа int, которую надо преобразовать в байты
byte byteEeprom1 =  * ((byte *)(& dt));    // младший байт = 0x34
byte byteEeprom2 =  * ((byte *)(& dt) + 1);     // старший байт = 0x12

Для чтения байта мы:

  • получили адрес переменной: & dt;
  • явно преобразовали адрес к указателю на тип byte: (byte *);
  • применили операцию косвенной адресации: *;
  • для чтения второго байта прибавили 1 к указателю.

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

Пример преобразования переменной типа float в байты:

float dt = 2.58901;  // переменная типа float, которую надо преобразовать в байты
byte* ptrdt;  // указатель на тип byte
ptrdt =  (byte*)(& dt);  // получаем адрес переменной dt

byte byteEeprom1 = * ptrdt;    // считываем байты
byte byteEeprom2 = * (ptrdt+1);
byte byteEeprom3 = * (ptrdt+2);
byte byteEeprom4 = * (ptrdt+3);

Осталось записать байты в EEPROM. Конечно, это можно сделать без промежуточных переменных.

// записываем  байты в EEPROM
EEPROM.write(0, * ptrdt);
EEPROM.write(1, *(ptrdt+1));
EEPROM.write(2, *(ptrdt+2));
EEPROM.write(3, *(ptrdt+3));

Для чтения данных типа float из EEPROM - считываем байты и записываем их последовательно в область памяти по указателю ptrdt:

// чтение байтов из EEPROM и запись в область памяти для dt
* ptrdt = EEPROM.read(0);
* (ptrdt+1) = EEPROM.read(1);
* (ptrdt+2) = EEPROM.read(2);
* (ptrdt+3) = EEPROM.read(3);

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

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

 

В следующем уроке поговорим о надежности программ для Ардуино, узнаем, как использовать сторожевой таймер.

 

Предыдущий урок     Список уроков     Следующий урок

4

Автор публикации

не в сети 3 часа

Эдуард

280
Комментарии: 1936Публикации: 197Регистрация: 13-12-2015

42 комментария на «Урок 15. Указатели в C для Ардуино. Преобразование разных типов данных в байты.»

  1. Очень здорово написанная статья! Спасибо огромное! Пересылал с платы на плату переменные long по последовательному порту — проблем не было, при использовании операторов сдвига. Как появились переменные float ,так начались проблемы. Специально искал внятную инфу по применению указателей в таких случаях, и нашёл! Более внятно написать наверное невозможно 🙂

    0
  2. Если позволите, оставлю здесь шпаргалку про передачу массива как аргумент по ссылке.

    const int in = 3;//число пинов входа
    byte KlaIn[in]={6, 7, 8}; // пины входа

    void setup() {
    Serial.begin(9600); // открываем Serial порт
    for (int i = 0, x=in-1; i <= x; i++){ // выставляем входы
    pinMode (KlaIn[i], INPUT);
    }
    }

    void loop() {
    Serial.print(" STR = ");
    Serial.println( read_pins( &KlaIn[0], in ) , BIN);
    delay(10);
    }

    int read_pins(byte *pins, int kol_vo) {
    int str = 0; // "строка строк" общий сигнал со строк кнопок
    int stepen = 1; // 2 в степени, чтоб не возиться со степенями 10
    for (int i = 0, x = kol_vo -1 ; i <= x; i++){ // пробегаем по входным пинам
    if ( digitalRead( pins[i]) ){ // если сигнал = 1 (5В)
    str = str + stepen; // запоминаем 100/010/001 в STR
    }
    stepen = stepen << 1; // возводить 10 в степень — дорого
    } // (for)
    return str;
    }

    0
    • при вызове функции read_pins. В качестве первого аргумента IMHO можно просто указать имя массива т.к. это и есть адрес первого элемента массива

      Serial.println(read_pins(KlaIn,in),BIN)

      0
  3. Эдуард, здравствуйте. Помогите. Мой проект упёрся в передачу массива из функции в основную программу. Написал упрощенную конструкцию для Вас и она заработает, а вот в настоящей программе выдает 13 беспорядочных символа перед заданными. Посмотрите, пожалуйста, может я и тут что-то не верно написал?

    void setup() {
    Serial.begin(9600);

    char data[13];
    char ptrName = func();
    data[0] == &ptrName;
    Serial.println(data);
    }

    void loop() {}

    char func() {
    char dataF[13] = «abcdefghigkl»;
    Serial.println(dataF);
    char *ptrDataF = &dataF[0];
    return ptrDataF;
    }

    0
  4. В общем-то вопрос вот в чем: как правильно передать массив типа char из функции в программу?

    0
    • Здравствуйте!
      Не понятно:
      data[0] == &ptrName;
      Функция возвращает char, а вы передаете указатель;
      указателем на первый элемент массива является его имя …

      void setup() {
      Serial.begin(9600);
      Serial.println(func());
      }

      void loop() {}

      char * func() {
      char dataF[] = «abcdefghigkl»;
      Serial.println(dataF);
      return dataF;
      }

      0
      • Да, Эдуард, Ваша конструкция заработала и в упрощенном виде и в моей основной программе. Но есть вопросы! Я же не могу из функции передать целый массив данных, а лишь только одно значение, не так ли? Поэтому я пытался передать из функции как раз указатель на место, куда функция сохранила массив, а из основной программы уже присвоить новому массиву значения по указателю. Как раз в «строчке data[0] == &ptrName;» я сделал попытку сделать это. Вот как я понимаю свой код. (поправьте меня, пожалуйста). В функции, в массив я сохранил значения (char dataF[13] = «abcdefghigkl»;). Адрес массива я сохранил в указатель (char *ptrDataF = &dataF[0];), указатель я вернул в программу (return ptrDataF;), а значением адреса, которая вернула функция я указал на новый массив data[0] == &ptrName;), чтобы сохранить данные туда, и дальше с ними дальше работать. В общем, задача перенести значение массива dataF в массив data, но через функцию.

        0
      • Эдуард, не могли бы Вы объяснить для чего функция Serial.println(); в вашем примере используется два раза. Первый раз в void setup, второй раз в описании самой функции?

        0
      • Я попробовал вот такой вариант

        void setup() {
        Serial.begin(9600);
        func();/вызов функции по указателю
        }

        void loop() {}

        char * func() {
        char dataF[] = «acdefghigkl»;
        Serial.println(dataF);
        return dataF;
        }

        он дает точно такой же результат. В чем разница?

        0
    • char dataF[13] = «abcdefghigkl»; вы создали локальную динамическую переменную, которая при выходе из функции может быть уничтожена. Чтобы не уничтожалось, надо использовать глобальную переменную или static char dataF[13] = «abcdefghigkl»;

      char *ptrDataF = &dataF[0]; имя массива является указателем на первый элемент char *ptrDataF = dataF;

      return ptrDataF; Ваша функция возвращает тип char, а вы возвращаете указатель.

      data[0] == &ptrName; операция сравнения.

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

      0
      • Тут я все понял. Но я не понимаю как раз самого главного — как по указателю из функции сохранить массив в новый массив. Как??? ))). Значение из функции Вы вывели в COM-порт (Serial.println(func());), а как сохранить это значение в новый массив, чтобы не утерять данные при выходе из функции?

        0
          • Немного не то, что я себе представлял, но в любом случае, Эдуард, Вы мне очень помогли и кое что прояснили с указателями. Буду думать. Спасибо за все и особенно за терпение!

            0
  5. [quote] При объявлении указателей выделяется необходимое число байтов памяти, в зависимости от типа данных. Например, для dt (int) компилятор выделит 2 байта, а для x (float) будет зарезервировано 4 байта. [/quote]
    Почему-то всегда считал что указатель в памяти занимает одинаковое количество байт вне зависимости от его типа. Ведь указатель — это просто адрес расположения данных.

    0
  6. Эдуард! Спасибо Вам за информацию про сдвиги и указатели! Я раньше думал, что умею программировать на ардуино (как я ошибался))) Недавно стала задача прочитать данные с шины CAN. Я подключил шилду MCP2515 и нашел в интернете скейч на нее. Но она не стала работать… Нашел другой скейч и тот же результат. Решил просмотреть досконально все и понять как оно должно работать и ужаснулся всем этим непонятным символам &, *, -> ))))))) теперь понимаю зачем разработчики их использовали)) Спасибо еще раз Вам за Вашу работу!
    А у Вас есть форум на котором можно общаться на темы ардуино и не только?))

    0
  7. Здравствуйте Эдуард. Можно ли оператором delite удалить указатель на составной объект описываемый классом, и освободиться ли память занимаемая кодом объекта в таком случае?

    0
  8. Большущее спасибо за материал! так наглядной информации по Преобразованию разных данных в байты и не расчитывал даже найти!Применил метод успешно, но по i2c почему-то абра-кадабра.
    Мастер
    #include // Лобавляем необходимую библиотеку
    #include
    #include
    iarduino_I2C_connect I2C2;
    LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // (RS, E, DB4, DB5, DB6, DB7)

    short value=0; //Переменная для сборки из байтов
    byte* pot; // указатель на тип byte

    void setup () {
    Wire.begin();

    lcd.begin(16, 2); // Задаем размерность экрана
    lcd.setCursor(0, 0); // Устанавливаем курсор в начало 1 строки
    lcd.print(«hello»); // Выводим смайлик (символ под номером 1) — «\1»
    delay(200);
    lcd.clear();
    }

    void loop() {
    pot = (byte*)(& value); // получаем адрес переменной value
    * pot = I2C2.readByte(0x01,0); //собираем байт 1
    * (pot+1) = I2C2.readByte(0x01,1);

    delay(200);
    lcd.setCursor(0, 0); // Устанавливаем курсор в начало 1 строки
    lcd.print(value);
    delay(200);
    lcd.clear();
    }

    Слейв
    #include // Лобавляем необходимую библиотеку
    #include
    #include
    iarduino_I2C_connect I2C2;
    LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // (RS, E, DB4, DB5, DB6, DB7)
    short dt= 1256; //Переменная для разбивки на байты
    byte* ptrdt; // указатель на тип byte

    short value=0; //Переменная для сборки из байтов
    byte* pot; // указатель на тип byte

    byte myArray[2]; // Массив буферный для сборки/разбрки делаем доступным с помощью библиотеки iarduino_I2C_connect.

    void setup () {
    Wire.begin(0x01);
    I2C2.begin(myArray);

    lcd.begin(16, 2); // Задаем размерность экрана
    lcd.setCursor(0, 0); // Устанавливаем курсор в начало 1 строки
    lcd.print(«hello»); // Выводим смайлик (символ под номером 1) — «\1»
    delay(200);
    lcd.clear();
    }

    void loop() {
    ptrdt = (byte*)(& dt); // получаем адрес переменной dt
    myArray[0] = * ptrdt; // считываем байт 1
    myArray[1] = * (ptrdt+1); // считываем байт 2

    pot = (byte*)(& value); // получаем адрес переменной value
    * pot = myArray[0]; //собираем байт 1
    * (pot+1) = myArray[1]; // собираем байт 1

    lcd.setCursor(0, 0); // Устанавливаем курсор в начало 1 строки
    lcd.print(value);
    delay(200);
    dt++;
    lcd.clear();
    }

    не поскажете?

    0
    • Дружище, только без обид. Как-то не хочется использовать свой мозг в качестве отладчика чужих проектов, когда «системного времени» не хватает на свои разработки. Другими словами, в таких больших портянках ковырятся не комильфо. Давай конкретный вопрос, больше шансов получить конкретный ответ.

      0
  9. Пытался преобразовать float в byte как у вас. Типа
    float dt = 2.58901; // переменная типа float, которую надо преобразовать в байты
    byte* ptrdt; // указатель на тип byte
    ptrdt = (byte*)(& dt); // получаем адрес переменной dt
    byte byteEeprom1 = * ptrdt; // считываем байты
    byte byteEeprom2 = * (ptrdt+1);
    byte byteEeprom3 = * (ptrdt+2);
    byte byteEeprom4 = * (ptrdt+3);
    Но не получилось. Ardfuino компилятор ругается на
    byte* ptrdt; // указатель на тип byte
    ptrdt = (byte*)(& dt)

    0
      • Вот скетч, который компилируется без ошибок:

        float dt = 2.58901; // переменная типа float, которую надо преобразовать в байты
        byte* ptrdt; // указатель на тип byte

        void setup() {
        ptrdt = (byte*)(& dt); // получаем адрес переменной dt
        byte byteEeprom1 = * ptrdt; // считываем байты
        byte byteEeprom2 = * (ptrdt+1);
        byte byteEeprom3 = * (ptrdt+2);
        byte byteEeprom4 = * (ptrdt+3);
        }

        0
  10. Эдуард, подскажите пожалуйста, сколько байт в памяти занимает сам указатель?
    Вы пишите: «в зависимости от типа данных. Например, для dt (int) компилятор выделит 2 байта, а для x (float) будет зарезервировано 4 байта.» А для указателя на строку из 20 символов будет что выделено 20 байт? А для указателей на класс?
    Разве указатель не занимает железно 4 байта всегда?

    0
    • Здравствуйте!
      Указатель занимает 2 байта. Это столько, сколько необходимо для адресации в памяти микроконтроллера ATmega328.
      Но указатель содержит только информацию в каком месте в памяти располагаются данные. А вот для данных резервируется память в зависимости от их типов. Если, например, вы объявили указатель на float, то следующий указатель можно задавать только через 4 байта. Иначе работа с ним испортит предыдущие данные float.

      0
      • Ошибся, да не 4, а 2 байта, конечно. Вопрос был сколько занимает именно сам указатель. Понял всегда 2 байта. Спасибо

        0
  11. Здравствуй те Эдуард!
    Недавно прочёл книгу «Как писать программы без ошибок» автор В. Тимофеев. И там, на стр.11 (раздел 3.1.3) есть такой пример с пояснением:

    Побайтовое обращение к многобайтовой переменной.
    Пример неправильного обращения:

    unsigned char lo, hi;
    unsigned int ui;

    lo = *((unsigned char*)&ui + 0);
    hi = *((unsigned char*)&ui + 1);

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

    Правильное обращение:
    lo = ui & 0xFF;
    hi = ui >> 8;

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

    0
    • Здравствуйте!
      Я использую оба способа. Обращение к байтам через указатели выполняется быстрее, программа получается компактнее. На счет порядка байтов могу сказать, что я применяю обращение через указатели на разных версиях Си. Все работает. Как без указателей разбить формат float на байты, я не представляю.

      0
  12. Эдуард, доброго дня. Подскажите, что почитать, чтобы понять :

    int dt = 0x1234; // переменная типа int, которую надо преобразовать в байты
    byte byteEeprom1 = (byte)(dt & 0xff); // младший байт = 0x34
    byte byteEeprom2 = (byte)(dt >> 8); // старший байт = 0x12

    именно каким происходит преобразование? что за число и в какой системе 0x1234 исчисления. Хочу в этом разобраться, если можно на пальцах. или ссылку на информацию, Спасибо!

    0
    • Здравствуйте!
      Не знаю, что посоветовать. Я такие книжки очень давно читал. Задавайте конкретные вопросы. Если их много, то откройте тему на форуме. Вопросы для новичков или что-нибудь подобное.
      int dt = 0x1234; означает следующее.
      Объявлена переменная с именем x.
      Тип переменной int, т.е. для нее отведены 2 байта (16 разрядов), старший разряд знаковый. Переменная предназначена для хранения чисел в целочисленном знаковом формате.
      При объявлении переменной присвоено значение 0x1234. 0x означает, что значение указано в шестнадцатеричной системе исчисления. Соответствует двоичному значению 0b0001001000110100.

      0
      • Да спасибо, теперь более ли менее понятно
        тогда: вторая строка это (0001001000110100 & 00000000 11111111 ) = 00000000 00110100 то есть эквивалентно 0х34
        третья строчка ( 0001001000110100 после смещения на 8 разрядов — 00000000 00010010) тобишь в шестнадцатеричной 12 …

        0
    • ну например если обычное число в нашем мире десятичных чисел то мы всё умножаем на 10 и так далее . то есть 7234= начиная с самого меньшего=4+3х10 потом+2х10х10 и потом+ 7х10х10х10 =4+3х10+2х100+7х1000=7234 . если шестнадцетиричное число или двоичное делаем то же самое но уже вместо 10 везде пишем 16 или 2 . то есть если в шестнадцетиричной системе число равно 1234 то чтобы понять чему оно равно в нашем привычном нам мире десятичных обычных чисел проводим операцию как и для десятичного обычного числа только вместо 10 пишем 16 , 1234=4+3х16+2х16х16+1х16х16х16=
      4+48+512+4096=4657 можно его перевести в двоичный путем последовательного деления на 2 и обратным собиранием остатков . но проще это сделать для нашего шестнадцетиричного числа 1234 расписав каждый элемент в двоичном виде и приписав нули слева чтобы двоичный вид был из четырёх энаков .
      1=0001(приписали три нуля слева чтобы был стройным четырёхзначным) далее 2=0010 (для стройности слева приписали два нуля все равно они значения не меняют но приводят к стройному 4значному виду) далее 3=0011( приписали два нуля слева) и 4=0100( нарисовали один ноль слева . всё пищем эти четыре четырехзнаных числа и получаем наш 4657 в двоичном виде 0001001000110100

      0
  13. Здравствуйте, Эдуард. Должен сказать что для новичка изучающего с нуля программирование и МК по вашим урокам начинаются сложности примерно с 13 урока. Все потому что, отсутствует огромный кусок теории по системам счисления, двоичной системе, битовым операциям и т.п.. Отчего разобраться что такое 0x1234 и как работает конструкция
    byte byteEeprom2 = (byte)(dt >> 8); // старший байт = 0x12 ..просто невозможно. Я понимаю что это основы и теория которую можно найти в тысяче источников и вам нет смысла её пересказывать. В тоже время считаю что нужно хотя бы дать понятный источник для новичков (например https://alexgyver.ru/lessons/bitmath/ ). После которого уже можно вернутся к вашим урокам, а то складывается впечатление что ты что то упустил в предыдущих уроках, там все так последовательно и доходчиво объясняется…
    Но ваши уроки мне прежде всего нравятся профессиональным подходом, сразу все делается по уму, как в серьёзной технике. Вот это действительно очень ценно на фоне обилия любительских обучалок в интернете, после которых можно только нелепые самоделки для дома собирать.
    Спасибо вам за труд и успехов!

    0
    • Здравствуйте!
      Спрашивайте. Давайте на форуме откроем раздел на эту тему. Буду подробно отвечать на вопросы. Может справочник по элементарным вопросам и соберется.
      Спасибо за оценку моей работы.

      0
  14. ptrdt = (byte*)(& dt); // получаем адрес переменной dt

    вернее будет сказать , получили адрес операцией & dt а затем этот адрес адрес привели к байтовой адресации командой (byte*) где каждое изменение на 1 дает сдвиг на один байт . если будет команда (int*) то там каждое увеличение на 1 единицу даст переход уже на размер числа int то есть уже на 2 байта так как размер чисел int два байта .

    0

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Нажимая кнопку "Отправить" Вы даёте свое согласие на обработку введенной персональной информации в соответствии с Федеральным Законом №152-ФЗ от 27.07.2006 "О персональных данных".