Какая разница между `write()`, `print()` и `printIn()` при использовании Arduino Ethernet Shield?

Используя библиотеку Ethernet-сервера Arduino, в чем разница между:

server.write(data);, server.print(data); и server.println(data);

Я знаю, что printIn добавляет новую строку, а print — нет. Я не могу найти примеры для server.write();.


person Anonymous Penguin    schedule 26.04.2013    source источник


Ответы (1)


(Длинный ответ, перейдите к TL;DR внизу, если он громоздкий)

Откуда взялись print() и write()

Чтобы узнать, мы можем посмотреть на источник. Server является экземпляром класса EthernetServer, определенного в arduino/libraries/Ethernet/EthernetServer.h (только выбранные строки)

#include "Server.h"

class EthernetClient;

class EthernetServer : 
public Server {
private:
public:
  virtual size_t write(uint8_t);
  virtual size_t write(const uint8_t *buf, size_t size);
  using Print::write;
};

Итак, это Server. Server определено в /usr/share/arduino/hardware/arduino/cores/arduino/Server.h, и в нем очень мало:

class Server : public Print {
public:
  virtual void begin() =0;
};

Это означает, что сервер является подклассом Print, поэтому мы можем искать различия между write() и print() там.

print() и write() параметры

Мы видим, что этот класс (то есть Print) определяет ряд перегруженных print() методов:

size_t print(const __FlashStringHelper *);
size_t print(const String &);
size_t print(const char[]);
size_t print(char);
size_t print(unsigned char, int = DEC);
size_t print(int, int = DEC);
size_t print(unsigned int, int = DEC);
size_t print(long, int = DEC);
size_t print(unsigned long, int = DEC);
size_t print(double, int = 2);
size_t print(const Printable&);

и три перегруженных метода write():

virtual size_t write(uint8_t) = 0;
size_t write(const char *str) { return write((const uint8_t *)str, strlen(str)); }
virtual size_t write(const uint8_t *buffer, size_t size);

Как видите, C-строка write использует блок write (третий метод), а в реализации по умолчанию запись блока использует запись байта (первый метод), который является чисто виртуальным методом: virtual size_t write(uint8_t) = 0;. Его необходимо переопределить в каждом классе, производном от Print. Кроме того, блок write() также может быть переопределен для более эффективной записи многобайтовых данных.

Итак, по параметрам:

  • write(): для байтов (uint8_t), байтовых буферов и указателей массива символов (= обычные строки C)
  • print(): Arduino Strings, ints и longs (в любой базе), floats и любой класс, производный от Printable, в дополнение к строкам chars и C.

Как видите, формально между параметрами write() и print() есть небольшое перекрытие. Например, только write() принимает uint8_t, но только print() может принимать char. Единственная область перекрытия — это строки в стиле C: есть print(const char[]); и write(const char *str);. Однако даже в таких случаях, как char, функция print() просто вызывает write(uint8_t):

size_t Print::print(char c)
{
  return write(c);
}

То же самое верно и для print(char[])

write() в `EthernetServer

Класс EthernetServer представляет метод блочной записи.

size_t EthernetServer::write(const uint8_t *buffer, size_t size) 

а в EthernetServer write(uint8_t) просто подключается к блоку записи:

size_t EthernetServer::write(uint8_t b) 
{
  return write(&b, 1);
}

Поскольку все вызовы print() и не-55_ вызовы write() используют либо write(uint8_t), либо write(uint8_t*, size_t), в классе EthernetServer каждый вызов print/write выполняется с использованием записи блока.

Производительность и выбор между print() и write()

Функции thunking print() (такие как print(char c)), скорее всего, будут встроены компилятором gcc, хотя, если вас это беспокоит, вы можете вызвать write() вместо print().

Одним из случаев, когда вы можете вызвать write() вместо print(), чтобы сэкономить пару тактов, является ситуация, когда вы держите byte/uint8_t и вам нужно его распечатать. Используя print(), ваши данные нужно будет преобразовать в 4-байтовое значение (int), а затем распечатать, используя дополнительный код. В этом случае write() будет немного быстрее.

С другой стороны, согласованность кода, вероятно, тоже чего-то стоит. С этой точки зрения может иметь смысл делать все вызовы print().

Однако в большинстве случаев ваши типы будут диктовать вызов функции print(): запись может принимать только три типа ввода.


TL;DR: Ответ на ваш вопрос заключается в том, что между print() и write() нет большой разницы, за исключением:

  • Методы write() (байтовые или блочные) — это методы, которые выполняют фактическую работу по отправке символов куда-либо в любом случае.
  • write() может принимать байты (uint8_t), байтовые буферы и указатели массива символов (= обычные строки C) в качестве параметров, тогда как print() принимает Arduino Strings, ints и longs (в любой базе), floats и любой класс, производный от Printable, кроме того до chars и до струн. Таким образом, мы можем сказать, что write() имеет более низкий уровень, чем print(), учитывая тот факт, что он принимает только низкоуровневые типы.
  • В большинстве случаев ваши типы вывода будут диктовать, какой из них использовать. Чтобы сделать самый быстрый код, используйте write() для печати типов byte/uint8_t, но print везде делает ваш код немного лучше, ИМХО (в основном потому, что он не вызывает вопросов print() по сравнению с write()).
person angelatlarge    schedule 27.04.2013
comment
Так зачем кому-то использовать write()? Это быстрее, если вы уже используете тип переменной, который он поддерживает? - person Anonymous Penguin; 27.04.2013
comment
Спасибо, что указали на это: это был недостаток в ответе. Я пытался решить эту проблему, но это сделало ответ еще длиннее :( Дайте мне знать, если это не прояснит ситуацию. - person angelatlarge; 27.04.2013