Бага в классе String в IDE версии 1.6.7

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Приветствую!

Так получилось, что сижу под 1.6.7 и переползать на свежую - пока не планирую. Заметил, что ВНЕЗАПНО перестал работать отсыл СМС. Подумал, поковырял, почесял репу - вроде код валидный, но - не работает. Откатил изменения - работает. А теперь - розыгрыш призов с выдачей свекольного ордена разработчикам класса String!

Вот так: работает:

String out; out.reserve(2);

char ch1, ch2;

out = String(ch1); out += String(ch2);

А вот так - не работает:

String out; out.reserve(2);

char ch1, ch2;

out[0] = ch1;
out[1] = ch2;

Обратите внимание на вызов reserve - это чтобы не дёргать постоянно память. Начал разбираться, почему при прямом доступе по индексу символов - не работает, нашёл вот такую любопытную шляпу:

char & String::operator[](unsigned int index)
{
    static char dummy_writable_char;
    if (index >= len || !buffer) {
        dummy_writable_char = 0;
        return dummy_writable_char;
    }
    return buffer[index];

Обратите внимание на условие index >= len. Буфер-то выделен вызовом reserve, длина строки при этом - 0 (len == 0). И тут я ВНЕЗАПНО хочу записать в нулевую ячейку какой-то байт. И - обломинго, бро, иди кури бамбук.

Налицо - архитектурный косяк. Мне плевать, что там лежит в len, главное чтобы переданный индекс вмещался в capacity - 1. Да, при таком подходе код этого оператора сильно усложняется, но оно меня парит вообще? Есть буфер, есть индекс - чего бы не записать туда символ-то? 

Как считаете - косяк или нет? Я - за косячище, и оправдания типа "если записать один символ по центру пустого буфера - чего тогда будет?" - не катят: это не дело класса String, строить предположения, чего там будет - на крайняк - всегда есть нуль-терминатор в буфере, а кто запихал по центру мусора один символ и потом получает хз что - ССЗБ.

Собственно, мелочь, а крови попила :)

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

оптимизирую твою личную проблему:

DIYMan пишет:

Так получилось, что сижу под 1.6.7 и переползать на свежую - пока не планирую.

Собственно, мелочь, а крови попила :)

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Клапауций 232 пишет:

оптимизирую твою личную проблему:

DIYMan пишет:

Так получилось, что сижу под 1.6.7 и переползать на свежую - пока не планирую.

Собственно, мелочь, а крови попила :)

Спасибо, но думаю, лучше со старыми граблями, чем ловить россыпь новых - там их, судя по всему, не убавилось, а только прибавилось :)

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

DIYMan пишет:

Спасибо, но думаю, лучше со старыми граблями, чем ловить россыпь новых - там их, судя по всему, не убавилось, а только прибавилось :)

за что "спасибо" - проблема то не решена.

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

если отсутствует, но ты не желаешь новых проблем, то:

сравни файлы WString.cpp, WString.h со своего актуального ядра и с последнего актуального релиза - посмотри, чем отличаются, замени, протестируй.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

DIYMan пишет:

Так получилось, что сижу под 1.6.7 

В 1.6.8 и в 1.6.9 ровно та же хрень.

DIYMan пишет:

Как считаете - косяк или нет? 

Как говорят наши американские партнёры, "it depends". Если писал я, то это фича, а если кто другой, то, конечно же баг :)

 

 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

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

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

andriano пишет:

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

Вообще-то, у класса String определён ссылочный оператор доступа по индексу, его код я и привёл выше. Если определили оператор - то будьте добры обеспечить его нормальную работу. То, что я привёл - это бага.

З.Ы. Если не очень разбираетесь в ООП - так и скажите, вместо того, чтобы писать, как бы помягче это сказать, недостоверные домыслы.

Datak
Offline
Зарегистрирован: 09.10.2014

Если хотите моё мнение - да, String это объект, и его основное свойство - это длина, len.

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

А вот свойство capacity, скорее, является чем-то дополнительным и неестественным. По логике вещей, программисту для работы со строкой оно не нужно - всё что можно сделать со строкой, легко делается без capacity.

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

Так что, извините - мой голос за "не баг". :)

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Datak пишет:

Так что, извините - мой голос за "не баг". :)

Исходники класса String смотрели хоть? Они же кривые все, как моя жизнь. Не баг, говорите? Ок, по вашему, не баг. Допустим, у нас в строке уже будет что-то записано, например, "Hello". Какая длина строки, сиречь, len? 5 (возможно, мультибайтовых, заметьте) символов.

И вот я беру, и пишу helloString[2] = '\0';

Какая длина строки-то теперь? Что там прилеплено сбоку - capacity или такие len? Там криво всё, как курятник бабушки Нюры.

Datak
Offline
Зарегистрирован: 09.10.2014

DIYMan пишет:
Исходники класса String смотрели хоть? Они же кривые все, как моя жизнь.

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

Насчёт helloString[2] = '\0'; смотреть не полезу, но насколько я понимаю, на len это никак не повлияет?
Если так, то я с этим тоже вполне согласен, как ни странно. Длина String'а, по моим представлениям, определяется именно значением len, а не содержимым строки. А символ '\0', по-моему, ничем не лучше и не хуже любого другого символа.

-------

Насчёт мультибайтовых символов озадачился...
Неужели в ардуиновском String'е действительно предусмотрена работа с мультибайтами? Не верится, если честно - но проверять не полезу, лениво. Соответственно, утверждать тоже ничего не буду. Лучше ещё раз извинюсь. :)

 

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Datak пишет:

Насчёт мультибайтовых символов озадачился...
Неужели в ардуиновском String'е действительно предусмотрена работа с мультибайтами? Не верится, если честно - но проверять не полезу, лениво. Соответственно, утверждать тоже ничего не буду. Лучше ещё раз извинюсь. :)

Именно так. Если в настройках стоит substitute_unicode=true, то все строки кодируются в UTF-8, т.е. String вполне себе их переваривает. Представляете, какой секас будет в этом случае с оператором доступа по индексу? Именно поэтому я утверждаю, что класс писан очень криво.

По поводу символа '\0', смотрите, какая шляпа: делать предположение о длине хранящейся в буфере строки только на основании переменной len - в корне, именно в корне - неправильно. Давайте для простоты разберём вот такой случай, считая, что строка - в однобайтовой кодировке:

String str = "Hello";

Serial.println(str.length());

str[2] = 0;

Serial.println(str.length());

А теперь представьте, что на основании информации, возвращаемой методом length() мы пишем в пакет заголовок "длина строки - такая-то", потом - данные строки, а на принимающей стороне ловим эти байты. И тут, внезапно - по центру строку нуль-терминатор, по итогу - строка не та, которая ожидалась. Я уже не говорю про мультибайтовые кодировки ;)

Т.е. в классе String крайне неудачно пытались скрестить ужа с ежом, а именно - байтовый буфер и собственно класс - держатель строк в различных кодировках. Вы же согласитесь с тем, что нуль-терминатор в строке - он и в Африке нуль-терминатор? Даже в случае однобайтовой кодировки реализация класса String - ущербна, вот я о чём.

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Кому он нужен этот String. Пусть и дальше не работает, обойдемся без него.

 

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

kisoft пишет:

Кому он нужен этот String. Пусть и дальше не работает, обойдемся без него.

Ну я бы не был так категоричен, конечно. Он вполне себе удобен как менеджер динамического буфера, например. Скажем, я его вполне себе юзаю для работы с UTF-8 и СМС на русском языке, да и во многих других местах, где его применение оправдано и удобно. Просто обратил внимание общественности на несколько неоднозначную реализацию одного оператора класса, вот и всё.

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

DIYMan пишет:

И тут, внезапно - по центру строку нуль-терминатор

предлагаю не мелочиться, а довести ситуацию до полного абсурда - строить строку исключительно из символов '/0' и утверждать, что у нас нихрена не работает класс стринг.

ведь, не работает же!

kisoft пишет:

Кому он нужен этот String. Пусть и дальше не работает, обойдемся без него.

верно - член стеклянный не выдавать.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Клапауций 232 пишет:

 

предлагаю не мелочиться, а довести ситуацию до полного абсурда - строить строку исключительно из символов '/0' и утверждать, что у нас нихрена не работает класс стринг.

ведь, не работает же!

Ну вот опять ты передёргиваешь :) Ты прекрасно понял, о чём я, уверен на 100%. И кривая реализация char& operator[] у класса String - твоё передёргивание не поддерживает ;)

Ок, скажу по-другому: длина строки должна считаться исключительно по нуль-терминатору, и если нам дают записать его по центру строки - длина должна пересчитываться, как минимум.

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

DIYMan пишет:

Ок, скажу по-другому: длина строки должна считаться исключительно по нуль-терминатору, и если нам дают записать его по центру строки - длина должна пересчитываться, как минимум.

длина какого из двух кусков хакнутой на две части строки?

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

я не сильно разбираюсь, но что увидел в глючном коде ТС - ровно то же, на чём вначале споткнулся сам.

имеем:

  • string - массив символов
  • String - объект класс

думаю, неправильно объявлять объект класс и работать с ним как с массивом символов.

если уж заюзали String, то что-то делаем с ним этим

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Клапауций 232 пишет:

длина какого из двух кусков хакнутой на две части строки?

Очевидно, что от начала буфера, куда ссылается внутренний указатель.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Клапауций 232 пишет:

думаю, неправильно объявлять объект класс и работать с ним как с массивом символов.

Ты наступаешь на неправильные грабли ;) Смотри: оператор класса - это, по сути, та же самая функция-метод класса, список которых ты так любезно привёл. И если у класса определён ссылочный оператор доступа по индексу (char& operator[]) - то я, как программист, пользующийся этим классом, считаю, что поведение этого оператора будет адекватным, при этом не зная, как всё это реализовано внутри (инкапсуляция, паанимаишь).

И вот смотри, я начинаю манипулировать со строкой:

String str = "Hello, world"; // тут всё ясно

str[2] = 0; // а вот после этого вызова - неясно.

Я ожидаю, что методы будут работать адекватно, а не как бог на дущу положит. Продолжая пример: у класса String есть замечательный метод c_str() который выдаёт нам константный указатель на внутренний буфер. Для ясности кусочек кода:

String str = "Hello, world";
const char* strPtr = str.c_str();

Serial.println(str.length());
Serial.println(strlen(strPtr));

str[2] = 0;
Serial.println(str.length());
Serial.println(strlen(strPtr));

Помедитируй над этим примером и пойми, что сочетание того, что наколбашено в классе String - дичь дикая. По сравнению с теми имплементациями класса String, которые я последние лет 15 юзаю, начиная от MFC и заканчивая C#.

Впрочем, разговор уже повернул в идеологическую сторону. Для себя я сделал только один вывод: не пользоваться криво написанным ссылочным оператором доступа по индексу у класса String, вот и всё. Ибо там реально - кривота.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Кстати, вдогонку: емнип именно по причинам неоднозначности в STL нетути

reference& operator[]

Для класса std::string. Щас пойду гляну импрементацию, на всякий.

З.Ы. Поглядел имплементацию SGI - точно, нету, и неспроста нету.

З.З.Ы. Думаю, тему можно считать закрытой, мне уже чего-то неинтересно :)

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

DIYMan пишет:

Клапауций 232 пишет:

думаю, неправильно объявлять объект класс и работать с ним как с массивом символов.

И вот смотри, я начинаю манипулировать со строкой:

String str = "Hello, world"; // тут всё ясно

str[2] = 0; // а вот после этого вызова - неясно.

снова за рыбу деньги 

или String - объект класс, тогда 

String str = "Hello, world";
str.setCharAt(2, 0);

или string - массив символов, тогда

string str[] = "Hello, world";
str[2] = 0;

в случае

String str = "Hello, world";
str[2] = 0;

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

vde69
Offline
Зарегистрирован: 10.01.2016

при длинне буфера 7 символов что по вашему должен вернуть оператор [111] ???

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

с другой стороны это свойство введено для поддержки Print и прочего связаного с выводом в поток.

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

надеюсь я объяснил почему именно так сделано?

зы

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

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Клапауцию: запись str[2] не означает, что я обращаюсь к классу как к массиву символов, это ключевой момент. Пока ты это не поймёшь - мы говорим на разных языках.

vde69: очевидно, по стандарту должен бросать исключение при доступе за пределами выделенного буфера. Но, поскольку мы на МК - вполне достаточно вернуть ссылку на статический символ, который можно хоть до усёру изменять (что, собственно, и сделано в классе String).

Примеры кривости класса String я привёл, считаю, что это именно кривость, и решение разработчиков обеспечить поддержку

char& operator[]

в классе String - криворуким. Уж лучше бы они не делали этот метод, как и поступили гораздо более умные ребята из SGI, выпуская свою версию STL.

Всё, для себя тему свернул, всем спасибо за участие.

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

DIYMan пишет:

Клапауцию: запись str[2] не означает, что я обращаюсь к классу как к массиву символов, это ключевой момент. Пока ты это не поймёшь - мы говорим на разных языках.

на настоящую реальность никак не влияет, что я считаю.

ты так и не ответил, корректно ли у тебя работает

String str = "Hello, world";
str.setCharAt(2, 0);

если "да", то зачем ты используешь нерабочую конструкцию?

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Клапауций 232 пишет:

ты так и не ответил, корректно ли у тебя работает

String str = "Hello, world";
str.setCharAt(2, 0);

если "да", то зачем ты используешь нерабочую конструкцию?

Вот кусок кода из исходников:

void String::setCharAt(unsigned int loc, char c) 
{
	if (loc < len) buffer[loc] = c;
}

Я ответил на твой вопрос? Если нет, то поясню - работает некорректно и в этом случае.

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

DIYMan пишет:

ты так и не ответил, корректно ли у тебя работает

String str = "Hello, world";
str.setCharAt(2, 0);

Я ответил на твой вопрос? Если нет, то поясню - работает некорректно и в этом случае.

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

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

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

fix

String str = "Hello, world";
str.setCharAt(2, '0');

 

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

народ, колитесь - где я пробегаю между грабель.


#include <LiquidCrystalFast.h>
LiquidCrystalFast lcd( 0,  15, 1, 10, 11, 12, 13);

#include <class_BUTTON.h>
BUTTON BUTTON_01(3);

String str = "Hello, world";

void setup () {
lcd.begin(16, 2);

lcd.setCursor(0, 0); lcd.print(str);

}

void loop() {

BUTTON_01.read();

if (BUTTON_01.click_down) {str.setCharAt(2, '0'); lcd.setCursor(0, 0); lcd.print(str);} // "He0lo, world"
if (BUTTON_01.click_up)   {str.setCharAt(2, '1'); lcd.setCursor(0, 0); lcd.print(str);} // "He1lo, world"

}

 

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Клапауций 232 пишет:

fix

String str = "Hello, world";
str.setCharAt(2, '0');

 

Мастер по ремонту бытовой техники понимает разницу между символом '\0' и символом '0'? Если нет - тогда и говорить не о чем.

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

DIYMan пишет:

Мастер по ремонту бытовой техники понимает разницу между символом '\0' и символом '0'? 

понимаю.

DIYMan пишет:

Если нет - тогда и говорить не о чем.

тогда есть, что говорить программисту - нафига программист хакает строку и возумащается, что ему за хак не оторвало руки?

кароче - зачем ты используешь символ, предназначенный для обозначения конца строки не по назначению?

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Ок, последняя итерация: если какой-то метод разрещает устанавливать ЛЮБОЙ символ, включая нуль-терминатор, в произвольную позицию, то я, по крайней мере, жду, что в случае передачи нуль-терминатора длина результирующей строки пересчитается (это ведь очень просто, на самом-то деле). Если этого не делается - это баг.

Давай уже закончим: я уже понял, что тебе нужен спор ради спора, не более. Я открыл топик не для спора, а для того лишь, чтобы читающие могли ознакомится с недоработками встроенного класса String. Я не призываю хакать строки, как ты выразился, я писал лишь об архитектурной неполноте класса. Мне, как программисту, эта неполнота очевидна в полной мере. Как другим - это уже не моё дело.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

DIYMan пишет:

Ок, последняя итерация: если какой-то метод разрещает устанавливать ЛЮБОЙ символ, включая нуль-терминатор, в произвольную позицию, то я, по крайней мере, жду, что в случае передачи нуль-терминатора длина результирующей строки пересчитается (это ведь очень просто, на самом-то деле). Если этого не делается - это баг.

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

Нуль-терминатор тут вообзе не при делах. Он имеет смысл терминатора в последовательности символов, чтобы определить конец (длину строки). В классе String он не несёт нагрузки терминатора. Длина там определяется по-другому и экземпляр класса String может состоять хоть из ста нулей - имеет право.

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

DIYMan пишет:

Давай уже закончим:

нет - не закончим: я хочу воспроизвести, описанный тобой баг методами String, а не string

смотри - у меня не получается:


#include <LiquidCrystalFast.h>
LiquidCrystalFast lcd( 0,  15, 1, 10, 11, 12, 13);

#include <class_BUTTON.h>
BUTTON BUTTON_01(3);

String str = "Hello, world";

void setup () {
lcd.begin(16, 2);

lcd.setCursor(0, 0); lcd.print(str);

str.setCharAt(0, 0); // хакаем начало строки символом NUL

}

void loop() {

BUTTON_01.read();

if (BUTTON_01.click_down) {str.setCharAt(3, 35); lcd.setCursor(0, 0); lcd.print(str);} // " el#o, world"
if (BUTTON_01.click_up)   {str.setCharAt(3, 42); lcd.setCursor(0, 0); lcd.print(str);} // " el*o, world"

}

 

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

ЕвгенийП пишет:

Нуль-терминатор тут вообзе не при делах. Он имеет смысл терминатора в последовательности символов, чтобы определить конец (длину строки). В классе String он не несёт нагрузки терминатора. Длина там определяется по-другому и экземпляр класса String может состоять хоть из ста нулей - имеет право.

Согласен и с этим. Но! Это ещё раз подтверждает курсы кройки и шитья, с помощью которых свёрстан класс String. Длина там лежит в переменной len, ок. Тогда объясните мне, как можно было так придумать, что вот это - не работает по-любому:

String str;
str.reserve(100);

str.setCharAt(20,'A');

Право имею? Буфер зарезервировал, оно есть где-то внутри там, может состоять, как вы выразились, хоть из ста нулей. len при этом - 0, и вызов setCharAt (чтобы не ломать картину мироздания Клапауция - я уж по простому, без операторов доступа по индексу) - обламывается.

Где написано, что я обязан что-то записать в строку до того, как обращаться по этому индексу? Если мы оперируем понятиями буфера? В таком случае получается, что имплементация reserve выполнена косячно, раз String может держать в себе совершенно любые символы. Или, что вернее - что-то пришито сбоку бантиком: начали с нормальной имплементации простого строкового класса, а затем сбоку припиздячили полочку, которая то и дело норовит отвалится. 

Особенно умиляет, при таком раскладе, наличие методов типа getBytes - типа, байты с внутреннего буфера я получить могу любые, а вот зарезервировать память, потом записать туда любой байт - обломись, вражина. Так, что ли?

Вы не находите нигде противоречия?

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Клапауций 232 пишет:

DIYMan пишет:

Давай уже закончим:

нет - не закончим: я хочу воспроизвести, описанный тобой баг методами String, а не string

смотри - у меня не получается:


#include <LiquidCrystalFast.h>
LiquidCrystalFast lcd( 0,  15, 1, 10, 11, 12, 13);

#include <class_BUTTON.h>
BUTTON BUTTON_01(3);

String str = "Hello, world";

void setup () {
lcd.begin(16, 2);

lcd.setCursor(0, 0); lcd.print(str);

str.setCharAt(0, 0); // хакаем начало строки символом NUL

}

void loop() {

BUTTON_01.read();

if (BUTTON_01.click_down) {str.setCharAt(3, 0); lcd.setCursor(0, 0); lcd.print(str);} // " el#o, world"
if (BUTTON_01.click_up)   {str.setCharAt(3, 0); lcd.setCursor(0, 0); lcd.print(str);} // " el*o, world"

}

 

Поправил твой код, скомпилируй и насладись.

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

DIYMan пишет:

Поправил твой код, скомпилируй и насладись.

а, в чём суть? - два непечатных символа NUL в строке

что бы было интерактивно кнопкой, то так:

#include <LiquidCrystalFast.h>
LiquidCrystalFast lcd( 0,  15, 1, 10, 11, 12, 13);
#include <class_BUTTON.h>
BUTTON BUTTON_01(3);
String str = "Hello, world";

void setup () {
lcd.begin(16, 2);
lcd.setCursor(0, 0); lcd.print(str);
str.setCharAt(0, 0); // хакаем начало строки символом NUL
}

void loop() {
BUTTON_01.read();
if (BUTTON_01.click_down) {str.setCharAt(3, 35); lcd.setCursor(0, 0); lcd.print(str);} // " el#o, world"
if (BUTTON_01.click_up)   {str.setCharAt(3,  0); lcd.setCursor(0, 0); lcd.print(str);} // " el o, world"
}

 

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

А теперь попробуй так, для полного фаршу:

#include <LiquidCrystalFast.h>
LiquidCrystalFast lcd( 0,  15, 1, 10, 11, 12, 13);
#include <class_BUTTON.h>
BUTTON BUTTON_01(3);
String str = "Hello, world";
const char* strPtr = str.c_str();

void setup () {
lcd.begin(16, 2);
lcd.setCursor(0, 0); lcd.print(strPtr);
str.setCharAt(0, 0); // хакаем начало строки символом NUL
}

void loop() {
BUTTON_01.read();
if (BUTTON_01.click_down) {str.setCharAt(3, 35); lcd.setCursor(0, 0); lcd.print(strPtr);} // " el#o, world"
if (BUTTON_01.click_up)   {str.setCharAt(3,  0); lcd.setCursor(0, 0); lcd.print(strPtr);} // " el o, world"
}

А потом представь, что потребности в классе String не ограничиваются только его методами, а есть случаи, когда нужно работать с указателем на строку (метод такой нам класс String предоставляет? Предоставляет.). И передать вовне длину строки, а затем - саму строку:

String str = "Hello";
const char* strPtr = str.c_str();
str[2] = 0;

Serial.println(str.length()); // 5
while(*strPtr)
Serial.print(*strPtr++); // будет напечатано 2 символа

Али прикажешь в таком случае не надеяться на метод length(), а тупо юзать strlen()? Знаю, выход, выход. Знаю, нельзя так изгаляться над бедным классом String - не мной писан, а я, гад, на святое руку поднял. Всё знаю. 

Ты прав - в классе String нет багов, признаю это. Каюсь, посыпаю голову пеплом, позорно умолкаю и бреду в угол, вставать на чечевицу.

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

DIYMan пишет:

Ты прав - в классе String нет багов, признаю это. Каюсь, посыпаю голову пеплом, позорно умолкаю и бреду в угол, вставать на чечевицу.

да, шож такое - это какой-то BDSM, а не конструктивный диалог.

я сломал игрушку - игрушка плохая, вы все бяки. ко-ко-ко.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Клапауций 232 пишет:

да, шож такое - это какой-то BDSM, а не конструктивный диалог.

я сломал игрушку - игрушка плохая, вы все бяки. ко-ко-ко.

Ахахах, спасибо, поржал :) Да просто мне поднадоело сухой теорией развлекаться, вот и всё. Понятное дело, что на практике я не сказочный долбоёб, который только и делает, что нули пихает куда ни попадя. Однако - в жопе свербит от вселенской несправедливости, вот и кручусь как могу :)

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

DIYMan пишет:

Ахахах, спасибо, поржал :) Да просто мне поднадоело сухой теорией развлекаться, вот и всё. Понятное дело, что на практике я не сказочный долбоёб, который только и делает, что нули пихает куда ни попадя. Однако - в жопе свербит от вселенской несправедливости, вот и кручусь как могу :)

ну, и ладно.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

DIYMan пишет:

Вы не находите нигде противоречия?

Ну, на эту тему я уж почти неделю назад высказался.

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

Попкрон вечен

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

kisoft пишет:

Попкрон вечен

Хрен там! Кончается, сцуко, в самый неподходящий момент.