Массив тоже unsigned char, как и указатель, но тем не менее компилятор ругается: a value of type "const unsigned char (*)[5]" cannot be assigned to an entity of type "unsigned char *"
WiseLord писал(а):
Массив-то двумерный, а указатель - на одномерный. Кстати, странно, что на саму инициализацию массива не ругается.
В источнике этих символов массив представлялся как {{5 байт},{5 байт},{5 байт}, и т.д.}, я по какой-то причине (это было несколько месяцев назад) внутренние {} убрал. Получился одномерный массив, но адресуемый как двухмерный. Я, кстати, такое и раньше делал, ошибок в работе это не вызывало.
_________________ Каждый имеет право на свое личное ошибочное мнение.
У меня было тяжелое детство - я до 14 лет смотрел черно-белый телевизор.
Карма: 90
Рейтинг сообщений: 1289
Зарегистрирован: Чт мар 18, 2010 23:09:57 Сообщений: 4510 Откуда: Планета Земля
Рейтинг сообщения:2 Медали: 1
Zhuk72 писал(а):
Массив тоже unsigned char, как и указатель, но тем не менее компилятор ругается: a value of type "const unsigned char (*)[5]" cannot be assigned to an entity of type "unsigned char *"
А, ну так правильно. Массив то двумерный. Вот так надо :
Код:
ptr = lcd_font[0];
если указываем на одну строку. Или так :
Код:
ptr = &lcd_font[0][0];
если хотим указать на конкретную ячейку.
Добавлено after 6 minutes 42 seconds: Т.е., в данном случае, мы можем сделать так :
Код:
ptr = lcd_font[ch]; for (i = 0; i < 5 ; i++) { lcd_send(*(ptr + i), DTA); // или lcd_send(ptr[i]), DTA); }
или так :
Код:
for (i = 0; i < 5 ; i++) { ptr = &lcd_font[ch][i]; lcd_send(*ptr,DTA); }
Спасибо большое, ptr = lcd_font[0]; подошло. Я так пробовал и раньше, но тогда, видать, мешала ошибка, связанная с константой. Завтра с отсылкой еще буду разбираться, а пока у меня что-то функция дилей не работает. Собрал все на макетке, прогнал дебаггером, оказалось,что дисплей инициализацию не проходит из-за зацикленной задержки. Что-то я там с SysTick'ом не то сварганил
_________________ Каждый имеет право на свое личное ошибочное мнение.
У меня было тяжелое детство - я до 14 лет смотрел черно-белый телевизор.
Обязательным условием долгой и стабильной работы Li-FePO4-аккумуляторов, в том числе и производства EVE Energy, является применение специализированных BMS-микросхем. Литий-железофосфатные АКБ отличаются такими характеристиками, как высокая многократность циклов заряда-разряда, безопасность, возможность быстрой зарядки, устойчивость к буферному режиму работы и приемлемая стоимость. Но для этих АКБ очень важен контроль процесса заряда и разряда для избегания воздействия внешнего зарядного напряжения после достижения 100% заряда. Инженеры КОМПЭЛ подготовили список таких решений от разных производителей.
ов массив представлялся как {{5 байт},{5 байт},{5 байт}, и т.д.}, ... внутренние {} убрал. Получился одномерный массив, но адресуемый как двухмерный. Я, кстати, такое и раньше делал, ошибок в работе это не вызывало.
В принципе, размерность массива явно указывается при его объявлении количеством скобок []. Внутри него вы можете получать доступ к элементам как угодно, но его размерность от этого не изменится. В регистрах памяти массив, независимо от его размерности, будет располагаться линейно, элемент за элементом, строка за строкой. В двухмерном массиве array[][] при его инициализации строки можно разделять внутренними скобками {{ , , },{ , , },{ , , },{ , , }}. В одномерном массиве такая запись приведет (может привести) к предупреждению "скобки вокруг скалярной инициализации".
Наверное люди делятся на две категории: одни понимают указатели, другие нет
Эт веееерно Вообще-то, тут по большей части теоретический вопрос. В умных книшшках много раз описывались указатели, но мало прочесть, надо опробовать самому на деле. И автору (Zhuk72) желательно было бы потренироваться "на кошках", т.е. на простом абстрактном примере, а не влезать сразу к шрифтам и дисплею. Указатель подобен курсору в текстовом редакторе, или прямоугольной рамке размером в один символ (равносильно char *ptr), и эта рамка может перемещаться по строке текста. Позиция указателя будет отсчитываться от начала строки (т.е. значение переменной ptr, хранящей некоторый адрес из адресного пространства). Рамку можем перемещать в любую позицию. Например, перемещаем на первую букву в выбранном слове (char *ptr = word1 ) и в рамке видим эту букву. Рамку можем двигать на какое-то число позиций вправо или влево по слову (ptr += 5; ptr--) и наблюдать в рамке буквы ( a = *ptr). Номер позиции рамки от начала всей строки мы записываем в свободное место той же строки (переменная ptr сама находится в том же адресном пространтве), а так же в свободном месте строки можем записывать буквы, которые видим в рамке ( переменная, в которую читается по указателю, тоже находится в том же адресном пространстве). Причем, мы можем перескочить за границы интересующего нас слова или вообще попасть туда, где нет текста, но есть место под буквы (перемещаясь по массиву, мы можем запросто выйти за его границы и ошибок компиляции не будет). Причем, буквы могут быть разной ширины, и чтобы увидеть в рамке именно то, что интересует, нужно выбирать рамку строго по ширине конкретной буквы (т.е. указатель и переменная должны быть одного типа (кол-ва байт)). К тому же, буквы могут не быть в составе осмысленных слов (указатели могут указывать на отдельные переменные).
Просто так взяв рамку и бросив ее на текст, мы можем попасть на любую букву. Это к слову об использовании неинициализованных указателей, которые могут указывать на любое случайное место. Поэтому, взяв рамку в руки, обязательно определим, какое место в строке нас интересует. То есть, объявив указатель, скажите ему, на что он указывает. В крайнем случае, напишите char *pa = (void*)0, сделав его "нулевым указателем". (чисто технически он указывает на адрес 0 в адресном пространстве). Вместо 0 вы можете записать любой другой адрес, либо получить адрес какой-то переменно с помощью символа &.
Встречается ситуация, когда указатель может указывать на переменную не своего типа. Тогда выдается предупреждение о несовместимости типов переменной и указателя на нее. Образно выражаясь, ширина рамки не совпадает с шириной символа. И если это сделано сознательно, а не в результате ошибки, то можно выполнить приведение типа (подогнать ширину рамки вручную по месту). Вот в таком примере мы сможем четырехбайтную переменную var разложить на отдельные байты в отдельных переменных byte3 - byte0. (обратная последовательность связана с тем, что младший байт имеет больший адрес, чем старший)
Код:
int var = 0x12345678; char *ptr = (char*)&var; // указатель будет указывать только на 1 байт переменной var
byte3 = *ptr++; // чтение переменной по указателю и последующий инкремент указателя, передвигая его на следующий байт byte2 = *ptr++; byte1 = *ptr++; byte0 = *ptr;
Используя возможности приведения типа, можно создать указатель на void, то есть указатель на неопределенный тип переменной, и использовать этот указатель как универсальный, для указания на любой тип. Объясняется это просто - тип void имеет размер в 1 байт (по соглашению в новых редакциях языка)
Код:
void *pv = (void*)0; // "нулевой" указатель на void
int a = 0x12345, x; char b = 'A', y;
pv = &a; // получает адрес переменной любого типа x = *((int*)pv); // чтение по указателю с приведением указателя к типу читаемой переменной. x = 0x12345
pv = &b; y = *((char*)pv); // y = 'A' /* можно пойти еще дальше и объявить указатель непосредственно на char, который будет выражаться через указатель на void с приведением типа */ char *pc; pc = (char*)pv; /* и теперь использовать уже его */ char symb; symb = *pc; // фактически, symb = b непосредственно *pc = 'B'; // фактически, b = 'B' непосредственно
без приведения типа указателя компилятор будет выдавать предупреждение о несовместимости типов. А void* - это в большей мере условность, наглядно показывающая универсальный указатель. Этот механизм используется для передачи через параметры функции данных произвольного размера, в том числе и структуры. Параметр функции используется как "универсальный передатчик". Метод используется во FreeRTOS при передаче произвольных параметров в создаваемую задачу.
Вот вкратце и есть суть указателей. Как видим, они используются не только для работы в массивах, но и для создания универсальных взаимосвязей между переменными в разных частях кода. Поэтому освоить указатели - очень даже важно для продвинутого написания кода. "уровень - эдвансд юзер", так сказать
------ Что касается доступа к элементам двухмерного массива через индексацию и через указатели, тут вот какая штука. Допустим, у нас есть двухмерный массив array[3][5] = {{0,1,2,3,4},{5,6,7,8,9},{10,11,12,13,14}}; с одинаковой длиной строк:
Код:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
Доступ к произвольному элементу при помощи индексации осуществляется по типу X-Y (не забываем, что индексация происходит с 0, как по горизонтали, так и по вертикали!)
Код:
char a; int x, y; x = 2; y = 1; a = array[y][x]; a = array[y+1][x-2];
таким способом удобно получать доступ к произвольному элементу по его координатам. А вот доступ по порядковому номеру (по смещению от начала) удобнее выполняется через указатель.
Код:
char *ptr = &array[0][0]; // создается указатель на элементы массива и ему сразу присваивается адрес начала массива ptr += 8; // смещение указателя на 8 позиций от начала массива a = *ptr; // получение элемента 8-го массива ptr++; // указатель перемещается на следующий элемент массива a = *ptr; // получение следующего элемента массива ptr -= 3; // указатель перемещается на 3 позиции назад от текущей a = *ptr; a = *ptr++; // получение текущего элемента с последующим смещением указателя на след.элемент ptr += (1 * 5); // перемещение указателя на следующую строку ниже. 1 - число строк вниз, 5 - ширина массива (X) a = *ptr; // получение элемента строкой ниже (12-й)
однако, как видим, произвольный доступ по координатам X-Y через указатель записывается гораздо хуже, потому что нужно знать именно ширину строки, т.е. количество элементов в строке. Таким образом, указатели хорошо подходят для последовательного считывания в одномерном или двухмерном массиве. Но указатели усложняют жизнь, когда нужно прочитать некоторую ограниченную область в двухмерном массиве, либо при произвольном доступе в координатах X-Y. С таким простым шрифтом, как у Zhuk72, можно работать хоть как - по указателю или по индексации, разницы большой нет.
Компания EVE выпустила новый аккумулятор серии PLM, сочетающий в себе высокую безопасность, длительный срок службы, широкий температурный диапазон и высокую токоотдачу даже при отрицательной температуре.
Эти аккумуляторы поддерживают заряд при температуре от -40/-20°С (сниженным значением тока), безопасны (не воспламеняются и не взрываются) при механическом повреждении (протыкание и сдавливание), устойчивы к вибрации. Они могут применяться как для автотранспорта (трекеры, маячки, сигнализация), так и для промышленных устройств мониторинга, IoT-устройств.
Спасибо большое за такую серьезную работу, это действительно впечатляет! По указателям я несколько раз читал КиР, смотрел одно обширное видео на ютубе, потому смысл сего чуда представляю. Но вот когда доходит до практической реализации, у меня в особо сложных случаях шарики за ролики заходят.
Почитаю ваше объяснение, может просветлею в этот раз
_________________ Каждый имеет право на свое личное ошибочное мнение.
У меня было тяжелое детство - я до 14 лет смотрел черно-белый телевизор.
Да нет, тут не в практической реализации дело. Судя по вашему первому вопросу, уж извините, дело в теоретических пробелах. Если в описании функции в параметрах запрашивается указатель, то при вызове функции должен быть передан адрес какой-либо переменной. Почему-то никто явно этого не заметил, а написали, что "ошибка не в указателях" и дальше пошли в какие-то далекие дебри про двухмерные указатели, только лишь мельком зацепившись за несоответствие параметра функции. А раз так, то разберем детально:
Код:
void lcd_out (unsigned char *text) // параметр функции в виде указателя { unsigned char ch; // переменная, в которую будет читаться то, что принято по указателю ch = *text; // собственно, это и есть чтение в переменную по принятому указателю }
void main(void) { unsigned char symbol = 'A'; // переменная с одиночным символом unsigned char message[] = "whattafaka"; // массив переменных в виде строки, инициализированной последовательностю символов lcd_out(&symbol); // вызов функции и передача в нее параметра в виде адреса одиночной переменной
lcd_out(message); // вызов функции и передача в нее параметра в виде адреса массива символов. // Без знака &, поскольку имя одномерного массива - это и есть его адрес, //адрес первого его элемента }
Для той функции, которая была изначально задана, будет работать именно приведенный вариант вызова. А вот попытка сделать вызов вот так (как поначалу начали советовать):
lcd_out('A'); приведет к предупреждению (если оно включено в настройках) о несовместимости типов. Можно плюнуть на это, а можно прикинуться "типа умным" и вручную привести к запрашиваемому функцией типу указателя, используя наработки из ранее написанного мною.
lcd_out((unsigned char*)'A'); Предупреждений нету, но функция делает совсем не то, что ожидалось. А теперь вспомним, что запрашивает параметр? Он запрашивает какой-то адрес. А что есть 'A' ? Это ANSI-представление числа 0х41. То есть, мы явным образом передаем адрес памяти 0х41. Для функции это будет указатель (ссылка) на адрес 0х41. При дальнейшей работе функции идет чтение по принятому в параметре адресу 0х41. Что там физически находится - хрен его знает. Да, работает. Но результат не тот, что ожидался.
Но чтобы раз и навсегда разобраться с вариантами, теперь перепишем немного нашу функцию и попробуем вызвать ее так, как было в вашем первом вопросе:
/--- void main(void) { lcd_out("Whattafaka"); // строка символов в качестве параметра }
И у нас уже замечательнейше работает и выдает то, что и ожидали (однако, предыдущий вариант с адресом массива уже работать не будет). Именно на этом построены всякие printf, в том числе и самописные. Так вот видите, где собака зарыта то? А вы говорите "не в указателях дело", да "кавычки не те". В них дело, в них самых.
Кстати, можно не извращаться, а для такого случая использовать готовую встроенную sprintf или ее облегченный вариант siprintf из набора tiny_printf. Но об этом - позже.
Самая большая проблема с указателями, с моей точки зрения, это процесс разыменования и приращения. При разыменовании надо всегда помнить приоритет этой операции, что частенько порождает кучу скобок, вносящих путаницу. А при приращении - надо держать в голове размер данных разыменованного указателя, что тоже портит настроение, если в программе много указателей разных типов.
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
Да нет, тут не в практической реализации дело. Судя по вашему первому вопросу, уж извините, дело в теоретических пробелах.
Для меня реализация в программе - это именно практика. Смысл указателя, получение содержимого по указателю (*) и получение адреса переменной, на который он указывает (&), мне понятны.
Я же писал выше, что у меня получилось избавиться от ошибок. После исправления у меня получилось так:
lcd_setxy(row,col*6); while (*text) { ch = *text; if ((ch >= 32) && (ch <= '~')) ch -= 32; else if ((ch >= 192) && (ch <= 255)) ch -= 97; // 'А' <= ch <= 'я' else ch = 255; for (i = 0; i < 5 ; i++) { lcd_send(*(lcd_font[0]+ch * 5 + i),DTA); } lcd_send(0,DTA); // Additional space text++; } }
Я изначально и вообще всегда работал с массивами через индексы, так мне понятнее, но вот литература говорит, что обращаться к массиву через указатель правильнее. Отсюда и пошло вот это: lcd_send(*(lcd_font[0]+ch * 5 + i),DTA).
Вот на на эту строку я и получаю предупреждение в виде восклицательного знака с подсказкой passing 'char [5]' to parameter of type 'unsigned char *' converts between pointers to integer types with different sign, которое тем не менее отсутствует в окне Build output.
Добавлено after 5 minutes 44 seconds: Забыл добавить, что вышеуказанное на практике пока не проверил, т.к. дисплей сразу не завелся, пока не понял причину.
_________________ Каждый имеет право на свое личное ошибочное мнение.
У меня было тяжелое детство - я до 14 лет смотрел черно-белый телевизор.
Ну так в фунции параметр типа "unsigned char *", а посылается в неё "char *". Вот и говорит оно о неявном преобразовании указателей на разные типы - знаковый и беззнаковый.
Я изначально и вообще всегда работал с массивами через индексы, так мне понятнее, но вот литература говорит, что обращаться к массиву через указатель правильнее.
Пишите как вам понятнее. Компилятор соптимизирует, а глаза меньше болеть будут от расшифровки. Особенно когда вернётесь править этот код через несколько недель.
Текст (массив символов) по умолчанию считается signed?
"Знаковость" типа char в случае отсутствия явного указания signed/unsigned трактуется как правило в зависимости от настроек компилятора/проекта. Прошерстите в этих свойствах.
_________________ Одновременным нажатием LIGHT и POWER, РП Sangean ATS-909X (ver 1.29) превращается в ATS-909XR!
Язык Си - фантастическая солянка противоречий. Например, типы данных в Си - это просто чудо! Единственный тип, о котором хоть что-то наперед КОНКРЕТНО известно, это int - но известно о нем только то, что это тип со знаком и его размер определяется платформой. А все прочие типы опираются на int и так или иначе (снова по-разному) кратны его размеру.
казалось бы, что может быть проще char-а? это ведь просто байт! но нет: char на самом деле платформозависимый тип и единственное, что про него известно гарантированно, что его размера должно хватать для обозначения кода символа (по-моему, английского алфавита). про знак char-а не сказано ничего конкретного. поэтому формально вполне может быть что char != unsigned char или (зависит от компилятора) char != signed char. соответственно указатель на char может быть как указатель на число со знаком, так и без. существуют платформы, где char занимает больше 8 бит.
именно поэтому в стандарте С99 появились типы гарантированной разрядности int8_t, int16_t, uint16_t и т.д. - но опять-таки формально они не эквивалентны соответствующим стандартным типам аналогичной разрядности. правда, степень этой формальности разная, например, в AVR-GCC все эти intXX_t являются алиасами стандартных... что еще больше запутывает
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
Нашел картинку (просто пример, на выделение не обращайте внимания): Спойлер Опция Plain char as signed в любом виде на предупреждение никак не влияет, С99 тоже. Ради интереса тикнул вариант Strict ANSI C; в результате при попытке скомпилировать получил 150 ошибок и 10 предупреждений
_________________ Каждый имеет право на свое личное ошибочное мнение.
У меня было тяжелое детство - я до 14 лет смотрел черно-белый телевизор.
что вы хотели этим сказать? например, как sizeof поможет мне разобраться, на сколько изменится значение указателя по записи *ptr++? sizeof тут не липнет - только просмотр исходника до места описания указателя, чтобы вспомнить, что он long int *ptr и поэтому увеличится на 4.
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
это к "держать в голове" честно говоря, могу предположить не так много вариантов, когда из данных, на которые у нас есть указатель, нужно получать значения другого типа и знать для этого их размер. Разве что разгребать какой-нибудь сырой кусок данных в виде uint8_t*, содержащий некие известные структуры.
А при приращении - надо держать в голове размер данных разыменованного указателя, что тоже портит настроение, если в программе много указателей разных типов.
Я бы сказал - немножко иначе, нужно быть аккуратным, чтобы тип указателя при приращении был правильным - а компилятор сам нужное количество байтов отсчитает. Ведь операция проводится над структурированными данными, а количество байтов мало кого из шерифов волнует, как правило. Ну а для именования переменных - проклятая венгерская нотация не совсем уж и бесполезна.
_________________ Одновременным нажатием LIGHT и POWER, РП Sangean ATS-909X (ver 1.29) превращается в ATS-909XR!
использование указателей, с моей точки зрения, примерно в половине случаев как раз и требуется дя ковыряния в сырых данных для "логичного" доступа квадратные скобки с индексом понятнее гораздо, да и классическое разыменование тут уместно. выручают так же соглашения об именах переменных. Microsoft, например, страшно любит "префиксы" к переменным, аж скулы сводит от всяких lsptrData во: пока писал, сообщили про венгерскую нотацию - она самая, в яблочко!
_________________ если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе при взгляде на многих сверху ничего не меняется...
Вообще-то, если подходить с правильного направления, то та рассматриваемая функция изначально предназначена для работы совместно с одной из "печатающих" функций типа форматной sprint или неформатной "копирующей" strcpy . Это стандартные встроенные функции, поставляемые с компилятором. Правда, встроенная sprintf довольно тяжела по объему и скорости, и потому у нее есть облегченный вариант siprintf с урезанным функционалом, входящий в комплект tiny printf. Суть этого - получение универсального способа вывода, причем не только на физический дисплей, но и куда угодно, хоть в UART-терминалку, хоть в азбуку Морзе. Выглядеть это может вот так, на примере вывода двух строк в классический двухстрочник 1602 (на самом деле, эти функции не привязаны к конкретному дисплею)
Код:
#define LINE_LENGTH (16+1) // длина строки, плюс символ конца строки char txtbuf[LINE_LENGTH * 2] = {0}; // текстовый буфер длиной в 2 строки по (16+1) элементов int length; // длина строки int voltage = 0, current = 0; // переменные для значений напряжения и тока
voltage = 7; current = 210;
length = sprintf(txtbuf, "Voltage: %2i V", voltage); // первая строка (возвращается число скопированных символов) length = sprintf(txtbuf + LINE_LENGTH, "Current: %4i mA", current); // вторая строка
/* либо немного иначе, но функция вывода должна распознавать символ переноса строки \n */ length = sprintf(txtbuf, "Voltage: %2i V\nCurrent: %4i mA", voltage, current);
write_to_LCD(txtbuf, LINE_LENGTH * 2 ); // выводятся в дисплей две строки сразу
а сама функция просмотра строки для вывода на дисплей будет вот такой:
Код:
int write_to_LCD(char *buf, int lehgth) { /* это заглушка функции */ }
благодаря такой конструкции мы можем выводить в дисплей любые данные - текстовые сообщения, текст с переменной, просто переменную в нужной нам позиции на дисплее
Код:
sprintf(txtbuf + 5, "%i", voltage); // вывод только значения переменной в опред.позиции sprintf(txtbuf + LINE_LENGTH + 3, "Hello world"); // вывод текста в определенной позиции strcpy(txtbuf, " "); // затирается строка
const char message[] = {"Main messaga"}; // есть некий массив, инициализованный как строка strcpy(txtbuf+1, message); // копируем ее в текстовый буфер для вывода strcpy(txtbuf + LINE_LENGTH, "Waka-Waka-shet"); // или пишем напрямую на второй строчке
Таким образом, мы видим, что мы простым способом можем вывести в дисплей любую инфу в любом месте дисплея и нам не требуется изобретать ничего в функции вывода на дисплей. Выбор способа идет на уровне подготовки массива текстового буфера.
Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 28
Вы не можете начинать темы Вы не можете отвечать на сообщения Вы не можете редактировать свои сообщения Вы не можете удалять свои сообщения Вы не можете добавлять вложения