Что такое дескриптор в программировании
Перейти к содержимому

Что такое дескриптор в программировании

  • автор:

Что такое дескриптор.

Обьясните пожалуйста, что такое дескриптор, касаясь функции winmain. Вот я читаю справку из msdn от vc++ на счет синтаксиса этой функции и там написано, что первые два параметра это дескрипторы, что это значит?

#1
13:38, 11 дек 2006

HINSTANCE. Уникальный идентификатор (число), ассоциированный с экземпляром твоего приложения, выполняемого в Windows.

Гы, помню классе в шестом я курил книжку по программированию под винду. Мне потребовалось 5 недель, чтобы осознать, что такое дескриптор окна =)

#2
13:45, 11 дек 2006

Если не трудно, вот само слово дескриптор что значит? Пожалуйста.:)

#3
13:47, 11 дек 2006

>Если не трудно, вот само слово дескриптор что значит? Пожалуйста.:)
Буквально — описатель. Идентификатор, как уже сказал ZET

#4
14:10, 11 дек 2006

Yaricoss
Твой гражданский паспорт, это твой дескриптор. ФСБ, зная серию и номер твоего паспорта, могут получить о тебе информацию и по ней найти тебя. Этот дескриптор уникальный, то есть ФСБ однозначно на тебя выйдут, а не на кого-то другого.

Аналогично, дескриптор может иметь приложение. Используется вместо, например, указателя, поскольку не все языки программирования могут использовать указатели. Также и окно определяется дескриптором. Например у тебя в программе несколько окон, нужно одно открыть, другое закрыть. При помощи дескрипторов окна (HWND) ты определяешь — с каким конкретно окном будешь работать.

#5
14:46, 11 дек 2006

Спасибо. Понятней определения наверно нигде не найдешь! А вот второй(если не лень то третий и четвертый) параметр WinMain можете разъяснить. Буду очень благодарен.

#6
14:52, 11 дек 2006

Напишу несколько статей под названием «Чайник — Чайнику. DirectX 9», чтоб чайники, типа меня не задавали таких вопросов.

#7
16:57, 11 дек 2006

Yaricoss
>Спасибо. Понятней определения наверно нигде не найдешь! А вот второй(если не
>лень то третий и четвертый) параметр WinMain можете разъяснить. Буду очень благодарен.
>Напишу несколько статей под названием «Чайник — Чайнику. DirectX 9», чтоб
>чайники, типа меня не задавали таких вопросов.
Какое отношение WinMain() имеет к DirectX 9?
По поводу аргументов WinMain надо идти сюда: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/… s/winmain.asp

#8
20:32, 11 дек 2006

Ну, незнаю(пока) какое это отношение имеет к Directx, вам виднее. Во всяком случае, winmain я встречал во многих примерах из dx sdk. Ссылка конечно хорошая, но это все и даже большее у меня есть(из msdn от vc++). Хотелось бы увидеть чтонибудь, написанное простым всем понятным языком.

#9
20:58, 11 дек 2006

Yaricoss
Если тебе МСДН не по зубам (хе-хе. ), найди где-нить книжку «Чарльз Калверт. Освой самостоятельно программирование в Windows за 21 день». У меня есть довольно старое издание, времён Windows 95. не знаю, может её переиздавали с учётом развития отрасли. Однако, несмотря на давность, весь материал актуален даже сейчас. Книга просто офигенно на пальцах рассказывает о программировании под винду с нуля. Чисто WinAPI на Си, без дотнетов, даже без MFC. Первые страниц 200 разжёвывают базу, но страницы с 600ой книга становится довольно непростой в освоении для начинающего, и в целом она покрывает очень большой объём информации. Рассматриваются вопросы организации windows-приложения, создание окон, common controls, GDI, ресурсы windows, диалоги, немного извращённых техник типа сабклассинга, механизмы управления памятью, механизмы многозадачности (в том числе методы синхронизации), многооконные приложения.

Если хочется хардкорщины, можно искать в инете E-Book «Джеффри Рихтер. Что-то там типа кодинг под винду и бла бла». На английском есть онлайн нахаляву, во многих местах. Эта книга подразумевает, что вы уже полностью понимаете все основные вещи.

Дескриптор — что это в программировании

vedro-compota's picture

Дескриптор (видимо от англ «describe/description» = «описывать/описание») — это некий набор данных (часто это переменная/объект особого типа), описывающий какую-либо сущность, но при этом им не являющийся.
(что вроде паспорта какой-либо страны, относительно реального человека).

Дескриптор файла

Дескриптор файла — часто это переменная того типа данных, который возвращает в вашем ЯП, например, функция открытия файла, в этом случае дескриптор обычно, помимо прочего, содержит сведения об адресе расположения файла (например, пути к нему в рамках файловой системы).

Руководство к дескрипторам

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

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

Введение и определения

Если говорить в общем, то дескриптор — это атрибут объекта со связанным поведением (англ. binding behavior), т.е. такой, чьё поведение при доступе переопределяется методами протокола дескриптора. Эти методы: __get__ , __set__ и __delete__ . Если хотя бы один из этих методов определён для объекта, то он становится дескриптором.

Стандартное поведение при доступе к атрибутам — это получение, установка и удаление атрибута из словаря объекта. Например, a.x имеет такую цепочку поиска атрибута: a.__dict__[‘x’] , затем в type(a).__dict__[‘x’] , и далее по базовым классам type(a) не включая метаклассы. Если же искомое значение — это объект, в котором есть хотя бы один из методов, определяющих дескриптор, то питон может изменить стандартную цепочку поиска и вызвать один из методов дескриптора. Как и когда это произойдёт зависит от того, какие методы дескриптора определены для объекта. Дескрипторы вызываются только для объектов или классов нового стиля (класс является таким, если наследует от object или type ).

Дескрипторы — это мощный протокол с широкой областью применения. Они являются тем механизмом, который стоит за свойствами, методами, статическими методами, методами класса и вызовом super() . Внутри самого питона с их помощью реализуются классы нового стиля, которые были представлены в версии 2.2. Дескрипторы упрощают понимание нижележащего кода на C, а также представляют гибкий набор новых инструментов для любых программ на питоне.

Протокол дескрипторов
descr.__get__(self, obj, type=None) --> значение descr.__set__(self, obj, value) --> None descr.__delete__(self, obj) --> None 

Собственно это всё. Определите любой из этих методов и объект будет считаться дескриптором, и сможет переопределять стандартное поведение, если его будут искать как атрибут.

Если объект определяет сразу и __get__ , и __set__ , то он считается дескриптором данных (англ. data descriptor). Дескрипторы, которые определили только __get__ называются дескрипторами не данных (англ. non-data descriptors). Их называются так, потому что они используют для методов, но другие способы их применения также возможны.

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

Чтобы создать дескриптор данных только для чтения, определите и __get__ , и __set__ , и сделайте так, чтобы __set__ выбрасывал исключение AttributeError . Определения метода __set__ и выбрасывания исключения достаточно, чтобы этот дескриптор считался дескриптором данных.

Вызов дескрипторов

Дескриптор можно вызвать напрямую через его метод. Например, d.__get__(obj) .

Однако, наиболее частый вариант вызова дескриптора — это автоматический вызов во время доступа к атрибуту. Например, obj.d ищет d в словаре obj . Если d определяет метод __get__ , то будет вызван d.__get__(obj) . Вызов будет сделан согласно правилам, описанным ниже.

Детали вызова различаются от того, чем является obj — объектом или классом. В любом случае, дескрипторы работают только для объектов и классов нового стиля. Класс является классом нового стиля, если он является потомком object .

Для объектов алгоритм реализуется с помощью object.__getattribute__ , который преобразует запись b.x в type(b).__dict__[‘x’].__get__(b, type(b)) . Реализация работает через цепочку предшественников, в которой дескрипторы данных имеют приоритет перед переменными объекта, переменные объекта имеют приоритет перед дескрипторами не данных, и самый низкий приоритет у метода __getattr__ , если он определён. Полную реализацию на языке C можно найти в PyObject_GenericGetAttr() в файле Objects/object.c .

Для классов алгоритм реализуется с помощью type.__getattribute__ , который преобразует запись B.x в B.__dict__[‘x’].__get__(None, B) . На чистом питоне это выглядит так:

def __getattribute__(self, key): "Эмуляция type_getattro() в Objects/typeobject.c" v = object.__getattribute__(self, key) if hasattr(v, '__get__'): return v.__get__(None, self) return v 
  • дескрипторы вызываются с помощью метода __getattribute__
  • переопределение __getattribute__ прекратит автоматический вызов дескрипторов
  • __getattribute__ доступен только внутри классов и объектов нового стиля
  • object.__getattribute__ и type.__getattribute__ делают разные вызовы к __get__
  • дескрипторы данных всегда имеют преимущество перед переменными объекта
  • дескрипторы не данных могут потерять преимущество из-за переменных объекта

Примечание: в питоне 2.2, super(B, obj).m() вызывал __get__ только если m был дескриптором данных. В питоне 2.3, дескрипторы не данных тоже вызываются, за исключением тех случаев, когда используются классы старого стиля. Детали реализации можно найти в super_getattro() в файле Objects/typeobject.c , а эквивалент на чистом питоне можно найти в пособии от Guido.

Детали выше описывают, что алгоритм вызова дескрипторов реализуется с помощью метода __getattribute__() для object , type и super . Классы наследуют этот алгоритм, когда они наследуют от object или если у них есть метакласс, реализующий подобную функциональность. Таким образом, классы могут отключить вызов дескрипторов, если переопределят __getattribute__() .

Пример дескриптора

Следующий код создаёт класс, чьи объекты являются дескрипторам данных и всё, что они делают — это печатают сообщение на каждый вызов get или set . Переопределение __getattribute__ — это альтернативный подход, с помощью которого мы могли бы сделать это для каждого атрибута. Но если мы хотим наблюдать только за отдельными атрибутами, то это проще сделать с помощью дескриптора.

class RevealAccess(object): """Дескриптор данных, который устанавливает и возвращает значения, и печатает сообщение о том, что к атрибуту был доступ. """ def __init__(self, initval=None, name='var'): self.val = initval self.name = name def __get__(self, obj, objtype): print 'Получаю', self.name return self.val def __set__(self, obj, val): print 'Обновляю' , self.name self.val = val >>> class MyClass(object): x = RevealAccess(10, 'var "x"') y = 5 >>> m = MyClass() >>> m.x Получаю var "x" 10 >>> m.x = 20 Обновляю var "x" >>> m.x Получаю var "x" 20 >>> m.y 5 

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

Свойства

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

property(fget=None, fset=None, fdel=None, doc=None) --> атрибут, реализующий свойства 

В документации показано типичное использование property() для создания управляемого атрибута x :

class C(object): def getx(self): return self.__x def setx(self, value): self.__x = value def delx(self): del self.__x x = property(getx, setx, delx, "Я свойство 'x'.") 

Вот эквивалент property на чистом питоне, чтобы было понятно как реализовано property() с помощью протокола дескрипторов:

class Property(object): "Эмуляция PyProperty_Type() в Objects/descrobject.c" def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError, "нечитаемый атрибут" return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError, "не могу установить атрибут" self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError, "не могу удалить атрибут" self.fdel(obj) 

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

Например, класс электронной таблицы может давать доступ к значению ячейки через Cell(‘b10’).value . В результате последующих изменений в программе, понадобилось сделать так, чтобы это значение пересчитывалось при каждом доступе к ячейке, однако программист не хочет менять клиентский код, который обращается к атрибуту напрямую. Эту проблему можно решить, если обернуть атрибут value с помощью дескриптора данных, который будет создан с помощью property() :

class Cell(object): . . . def getvalue(self, obj): "Пересчитываем ячейку прежде чем вернуть значение" self.recalc() return obj._value value = property(getvalue) 
Функции и методы

В питоне все объектно-ориентированные возможности реализованы с помощью функционального подхода. Это сделано совсем незаметно с помощью дескрипторов не данных.

Словари классов хранят методы в виде функций. При определении класса, методы записываются с помощью def и lambda — стандартных инструментов для создания функций. Единственное отличие этих функций от обычных в том, что первый аргумент зарезервирован под экземпляр объекта. Этот аргумент обычно называется self , но может называться this или любым другим словом, которым можно называть переменные.

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

class Function(object): . . . def __get__(self, obj, objtype=None): "Симуляция func_descr_get() в Objects/funcobject.c" return types.MethodType(self, obj, objtype) 

С помощью интерпретатора мы можем увидеть как на самом деле работает дескриптор функции:

>>> class D(object): def f(self, x): return x >>> d = D() >>> D.__dict__['f'] # Внутренне хранится как функция >>> D.f # Доступ через класс возвращает несвязанный метод >>> d.f # Доступ через экземпляр объекта возвращает связанный метод > 

Вывод интерпретатора подсказывает нам, что связанные и несвязанные методы — это два разных типа. Даже если они могли бы быть реализованы таким образом, на самом деле, реализация PyMethod_Type в файле Objects/classobject.c содержит единственный объект с двумя различными отображениями, которые зависят только от того, есть ли в поле im_self значение или там содержится NULL (C эквивалент значения None ).

Таким образом, эффект вызова метода зависит от поля im_self . Если оно установлено (т.е. метод связан), то оригинальная функция (хранится в поле im_func ) вызывается, как мы и ожидаем, с первым аргументом, установленным в значение экземпляра объекта. Если же она не связана, то все аргументы передаются без изменения оригинальной функции. Настоящая C реализация instancemethod_call() чуть более сложная, потому что включает в себя некоторые проверки типов и тому подобное.

Статические методы и методы класса

Дескрипторы не данных предоставляют простой механизм для различных вариантов привязки функций к методам.

Повторим ещё раз. Функции имеют метод __get__ , с помощью которых они становятся методами, во время поиска атрибутов и автоматического вызова дескрипторов. Дескрипторы не данных преобразуют вызов obj.f(*args) в вызов f(obj, *args) , а вызов klass.f(*args) становится f(*args) .

В этой таблице показано связывание и два наиболее популярных варианта:

Преобразование Вызвана через объект Вызвана через класс
Дескриптор функция f(obj, *args) f(*args)
staticmethod f(*args) f(*args)
classmethod f(type(obj), *args) f(klass, *args)

Статические методы возвращают функцию без изменений. Вызовы c.f или C.f эквиваленты вызовам object.__getattribute__(c, «f») или object.__getattribute__(C, «f») . Как результат, функция одинаково доступна как из объекта, так и из класса.

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

Например, пакет для статистики может включать класс для экспериментальных данных. Класс предоставляет обычные методы для расчёта среднего, ожидания, медианы и другой статистики, которая зависит от данных. Однако, там могут быть и другие функции, которые концептуально связаны, но не зависят от данных. Например, erf(x) это простая функция для преобразования, которая нужна в статистике, но не зависит от конкретного набора данных в этом классе. Она может быть вызвана и из объекта, и из класса: s.erf(1.5) —> 0.9332 или Sample.erf(1.5) —> 0.9332 .

Так как staticmethod() возвращает функцию без изменений, то этот пример не удивляет:

>>> class E(object): def f(x): print x f = staticmethod(f) >>> print E.f(3) 3 >>> print E().f(3) 3 

Если использовать протокол дескриптора не данных, то на чистом питоне staticmethod() выглядел бы так:

class StaticMethod(object): "Эмуляция PyStaticMethod_Type() в Objects/funcobject.c" def __init__(self, f): self.f = f def __get__(self, obj, objtype=None): return self.f 

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

>>> class E(object): def f(klass, x): return klass.__name__, x f = classmethod(f) >>> print E.f(3) ('E', 3) >>> print E().f(3) ('E', 3) 

Это поведение удобно, когда нашей функции всегда нужна ссылка на класс и ей не нужны данные. Один из способов использования classmethod() — это создание альтернативных конструкторов класса. В питоне 2.3, метод класса dict.fromkeys() создаёт новый словарь из списка ключей. Эквивалент на чистом питоне будет таким:

class Dict: . . . def fromkeys(klass, iterable, value=None): "Эмуляция dict_fromkeys() в Objects/dictobject.c" d = klass() for key in iterable: d[key] = value return d fromkeys = classmethod(fromkeys) 

Теперь новый словарь уникальных ключей можно создать таким образом:

>>> Dict.fromkeys('abracadabra')

Если использовать протокол дескриптора не данных, то на чистом питоне classmethod() выглядел бы так:

class ClassMethod(object): "Эмуляция PyClassMethod_Type() в Objects/funcobject.c" def __init__(self, f): self.f = f def __get__(self, obj, klass=None): if klass is None: klass = type(obj) def newfunc(*args): return self.f(klass, *args) return newfunc 
  • python
  • descriptor
  • descriptor protocol
  • питон
  • дескриптор
  • дескрипторы
  • протокол дескрипторов

Что такое дескриптор в программировании

На этом шаге мы приведем общие сведения по работе с файлами .

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

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

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

Дескрипторы файлов

Дескриптор — это символическое имя, которое используется в программе Perl для представления файла, устройства, сокета или программного канала. При создании дескриптора он «присоединяется» к соответствующему объекту данных и представляет его в операциях ввода/вывода. Мы дали наиболее полное определение дескриптора, чтобы читатель понимал, что дескриптор позволяет работать не только с данными файлов, но и с данными других специальных программных объектов, реализующих специфические задачи получения и передачи данных. Когда дескриптор присоединен к файлу, мы будем называть его дескриптором файла .

Замечание . При открытии файла в системе UNIX ему также назначается файловый дескриптор, или дескриптор файла, который ничего общего не имеет с файловым дескриптором Perl . В UNIX дескриптор файла является целым числом, тогда как в Perl это символическое имя, по которому мы можем ссылаться на файл. Чтобы получить числовой файловый дескриптор в программе Perl , можно воспользоваться функцией fileno() .

В программе дескриптор файла чаще всего создается при открытии файла функцией open() , которой передаются два параметра — имя дескриптора и строка с именем файла и режимом доступа:

open( LOGFILE, "> /temp/logfile.log");

Этот оператор создает дескриптор с именем LOGFILE и присоединяет его к файлу с указанным именем, который открывается в режиме записи (строка второго параметра начинается с символа «>»). На этом шаге мы не будем касаться вопросов, связанных с режимом открытия файла, а сконцентрируем наше внимание на дескрипторах. В следующих шагах режимы открытия файла будут рассмотрены нами подробнее.

Дескриптор, как указывалось, является символическим именем файла и представляет собой правильный идентификатор, который не может совпадать с зарезервированными словами Perl . В нашем примере создается дескриптор LOGFILE , «замещающий» в операциях ввода/вывода файл, к которому он присоединен ( /temp/logfile.log ). Например, известной нам функцией print() мы можем теперь записать в этот файл значение какой-либо переменной:

print LOGFILE $var;

Любой созданный дескриптор попадает в символьную таблицу имен Perl , в которой находятся также имена всех переменных и функций. Однако дескриптор не является переменной, хотя некоторые авторы и называют его файловой переменной. В имени дескриптора не содержится никакого префикса, присущего переменным Perl ($, @ или %). Поэтому его нельзя непосредственно использовать в операции присваивания и сохранить в переменной или передать в качестве параметра в функцию. Для подобных целей приходится использовать перед его именем префикс *, который дает ссылку на глобальный тип данных. Например, предыдущий оператор печати в файл, определенный дескриптором LOGFILE , можно осуществить с помощью следующих операторов, предварительно сохранив ссылку на дескриптор в переменной $logf :

$logf = *LOGFILE; print $logf $var;

В операции print первая переменная $logf замещает дескриптор файла LOGFILE , в который выводится значение второй переменной $var .

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

В любой программе Perl всегда существуют три предопределенные дескриптора ( STDIN , STDOUT и STDERR ), которые связаны со стандартными устройствами ввода/вывода и используются некоторыми функциями Perl в качестве умалчиваемых дескрипторов файлов ввода или вывода. Как мы уже знаем, дескриптор STDIN связан со стандартным устройством ввода (обычно клавиатура), STDOUT и STDERR — со стандартным устройством вывода (обычно экран монитора). Стандартное устройство ввода используется операцией <>, если в командной строке вызова сценария Perl не задан список файлов. Дескриптор STDOUT по умолчанию используется функциями print() и die , a STDERR — функцией warn . Другие функции также используют предопределенные дескрипторы файлов для вывода своей информации.

При вызове программ в среде Unix и DOS можно перенаправлять стандартный ввод и вывод в другие файлы, задавая в командной строке их имена с префиксами > для файла вывода и < для файла ввода:

perl program.pl out.dat

При выполнении программы program.pl все исходные данные должны быть подготовлены в файле in.dat . Вывод будет сохранен в файле out.dat , а не отображаться на экране монитора.

Перенаправление стандартного ввода и вывода, а также стандартного отображения ошибок, можно осуществлять непосредственно в программе Perl . Для этого следует функцией open() связать соответствующий предопределенный дескриптор с некоторым дисковым файлом:

open(STDIN, "in.dat"); open(STDOUT, ">out.dat"); open(STDERR, ">err.dat");

Теперь весь стандартный ввод/вывод будет осуществляться через указанные в операторах open() файлы. Обратите внимание, что при переопределении стандартных файлов вывода и ошибок перед именами файлов стоит префикс «>», указывающий на то, что файлы открываются в режиме записи.

Замечание . Перенаправление стандартного ввода/вывода в программе можно производить только один раз . Это переназначение действует с момента перенаправления ввода/вывода и до конца программы, причем функцией open() нельзя вернуть первоначальные установки для дескрипторов STDIN, STDOUT и STDERR .

На следующем шаге мы познакомимся с функцией open() .

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

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