Ada 程式設計/庫/Ada.Streams
此語言特性從 Ada 95 開始提供。
Ada.Streams 是 預定義語言環境 自 Ada 95 以來的一部分。
Ada **流** 是一種強大的 I/O 機制,允許將任何型別的物件讀寫到任何型別的“*介質*”(例如,網路連線、磁碟上的檔案、磁帶、記憶體緩衝區)。對於初學者來說,流可能有點難以理解,這是因為“雙重通用性”:關於要寫入/讀取的物件的通用性,以及關於所涉及介質的通用性。本節的目的是對 Ada 流進行直觀的介紹,省略一些細節。有關 Ada 流更精確和詳細的描述,請參閱 Ada 參考手冊,特別是 13.13: 流 [帶註釋的]。
語言設計者將透過介質寫入物件的難題分解為兩個子問題
- 將物件轉換為位元序列
- 透過流寫入位元
請注意,第一步僅取決於要傳送的物件,而不取決於實際介質。另一方面,第二步的細節僅取決於所使用的介質,而不取決於物件型別。
類似地,為了從網路連線“讀取”物件,必須
- 從流中讀取位元塊
- 解析讀取的塊並將其轉換為物件
再次注意,第一步僅取決於介質,而第二步僅取決於物件型別。
Ada 流的抽象模型基本上是一系列 *原始資料* (Stream_Element),可以以塊的形式讀寫。這種抽象檢視在 Stream 包定義中被形式化(來自 RM 13.13.1: 包 Streams. [帶註釋的] 添加了一些省略和註釋)
packageAda.StreamsistypeRoot_Stream_Typeisabstracttaggedlimitedprivate; -- Elementary piece of data. A Stream is a sequence of Stream_ElementtypeStream_Elementismodimplementation defined;typeStream_Element_Offsetisrangeimplementation defined; -- A block of datatypeStream_Element_Arrayisarray(Stream_Element_Offsetrange<>)ofaliasedStream_Element; -- Abstract procedure that reads a block of dataprocedureRead ( Stream :inoutRoot_Stream_Type; Item :outStream_Element_Array; Last :outStream_Element_Offset)isabstract; -- Abstract procedure that writes a block of dataprocedureWrite ( Stream :inoutRoot_Stream_Type; Item :inStream_Element_Array)isabstract;privateimplementation defined...endAda.Streams;
由於型別 Root_Stream_Type 是抽象,無法建立 Root_Stream_Type 型別的物件,而必須首先從 Root_Stream_Type 派生一個新型別。Ada.Streams 僅指定了流必須提供的 *最小* 介面:使用流,我們必須能夠
- **讀取** 一塊資料(使用過程 Read)以及
- **寫入** 一塊資料(使用 Write)。
通常,對於每種新的介質(例如,網路連線、磁碟檔案、記憶體緩衝區),都將派生一個專門用於讀寫該介質的新型別。請注意,Read 和 Write 都是抽象的,因此任何非-抽象 型別必須必然用處理從特定介質讀寫細節的新過程覆蓋它們。
請注意,Ada.Streams 的最小介面不包括例如用於開啟或關閉流的函式,也不包括用於檢查例如 End-Of-Stream 條件的函式。這是合理的,因為這些函式介面的細節取決於特定介質:用於開啟與檔案關聯的流的函式將期望檔名作為引數,用於開啟網路流的函式可能期望網路地址,而用於開啟與記憶體緩衝區關聯的流的函式可能需要緩衝區的地址和大小。派生自 Root_Stream_Type 的包將負責定義這些“輔助”函式。
Ada 流系統中的第二個成分是我們稱為 *序列化函式* 的東西,即負責將 Ada 物件轉換為 Stream_Element 序列以及反之亦然的函式。實際上,我們將在下一刻看到,序列化函式不會透過來回傳遞 Stream_Element 陣列來與呼叫者互動,而是直接與流互動。
與給定型別關聯的序列化函式定義為型別屬性。對於型別 T 的每個子型別 S,Ada 定義了以下與流相關函式和過程關聯的屬性
| 型別 | 輸入 | 輸出 |
|---|---|---|
| 簡單 | S'Read | S'Write |
| 簡單,類範圍 | S'Class'Read | S'Class'Write |
| 複合 | S'Input | S'Output |
| 複合,類範圍 | S'Class'Input | S'Class'Output |
我們將首先描述 S'Read 和 S'Write,因為它們是最簡單的,從某種意義上說,是最“原始”的。
過程 S'Write 在 13.13.2: 面向流的屬性 (3) [帶註釋的] 中定義如下(請記住 S 是型別 T 的子型別)
procedureS'Write( Stream :notnullaccessAda.Streams.Root_Stream_Type'Class; Item :inT);
S'Write 的職責是將 Item 轉換為 Stream_Element 序列,並將結果寫入 Stream。請注意,Stream 是對類範圍型別 Root_Stream_Type'Class 的訪問,因此程式設計師可以使用 S'Write 與從 Root_Stream_Type 派生的任何流型別一起使用。
根據 13.13.2: 面向流的屬性 (9) [帶註釋的],Ada 為 S'Write 定義了預設實現,如下所示
- 對於基本型別(例如,整數、字元、浮點數),預設實現將 Item 的適當表示寫入 Stream。該表示是 *實現相關的*,但大多數情況下,這僅對應於記憶體中的表示。
- 對於複合型別(例如,記錄和陣列),預設實現使用相應的 S'Write 過程寫入每個元件(陣列條目或記錄元件)。請注意,沒有寫入其他資訊。例如,如果 Item 是一個數組,則不會寫入陣列維度;如果 Item 有一個 *沒有預設值* 的判別式,則不會寫入判別式。從某種意義上說,S'Write 將 Item 的非常“原始”的表示寫入 Stream。
顯然,預設實現依賴於機器和編譯器,只有當資料由使用相同編譯器編譯的程式寫入和讀取時,它才會有用。例如,如果資料要透過網路傳送並由用另一種語言編寫的程式讀取,該程式執行在未知的架構上,那麼程式設計師控制透過網路傳送的資料格式非常重要。由於這種需要,Ada 允許程式設計師使用屬性定義子句 (RM 13.3 [Annotated]) 覆蓋 S'Write(以及以下描述的其他與流相關的函式)。
forS'Writeuseuser_defined_subprogram;
例如,假設網路協議要求以以下文字長度-型別-值格式格式化資料
- 整數值格式化為 "<len> i <value>",其中 <len> 是用於表示整數的數字位數,<value> 是以十進位制表示的整數。(例如,整數 42 將表示為 "2i42")
以下程式碼為整數情況定義了一個合適的 S'Write 過程(注意:為了簡單起見,以下程式碼假設每個 Stream_Element 長度為 8 位)
packageExampleistypeIntisnewInteger;typeInt_Arrayisarray(Intrange<>) of Int;procedurePrint ( Stream :notnullaccessAda.Streams.Root_Stream_Type'Class; Item :inInt);forInt'WriteUsePrint;endExample;
packagebodyExampleisprocedurePrint ( Stream :notnullaccessAda.Streams.Root_Stream_Type'Class; Item :inInt)is-- Convert Item to String (with no trailing space) Value : String := Trim(Int'Image(Item), Left); -- Convert Value'Length to String (with no trailing space) Len : String := Trim(Integer'Image(Value'Length), Left); Descr : String := Len & 'i' & Value; Buffer : Stream_Element_Array (1 .. Stream_Element_Offset (Descr'Length));begin-- Copy Descr to BufferforIinBuffer'RangeloopBuffer (I) := Stream_Element (Character'Pos (Descr (Integer (I))));endloop; -- Write the result to Stream Stream.Write(Buffer);endPrint;endExample;
請注意 Print 的結構:首先,Item 在 Stream_Element 序列(包含在 Buffer 中)中被“序列化”,然後透過呼叫 Write 方法(它將負責 Stream 上寫入的詳細資訊)將此序列寫入 Stream。現在假設有人想將 42 的描述列印到標準輸出。可以使用以下程式碼
withAda.Text_IO.Text_Streams;useAda.Text_IO; -- defines Current_Output. See RM A.10.1 [Annotated] -- Text_Streams.Stream (Current_Output) returns a stream access -- associated with the file given as parameter Int'Write (Text_Streams.Stream (Current_Output), 42);
結果將是 "2i42" 列印到標準輸出。請注意,以下程式碼
Int_Array'Write (Text_Streams.Stream (Current_Output), (1=>42, 2=>128, 3=>6));
將寫入標準輸出字串 "2i42_3i128_1i6"("_" 實際上不存在;它們已新增用於可讀性),對應於按順序對 42、128 和 6 呼叫 Int'Write。請注意,陣列維度不會被寫入。
如果有人想透過 TCP 連線傳送相同的描述,可以使用以下程式碼(使用 GNAT)
withGNAT.Sockets;useGNAT; ... Sock : Sockets.Socket_Type; Server : Sockets.Sock_Addr_Type := server address; ... Sockets.Create_Socket (Sock); Sockets.Connect_Socket (Sock, Server); -- Here Sock is connected to the remote server -- Use Sockets.Stream to convert Sock to a stream -- First send the integer 42 Int'Write (Sockets.Stream (Sock), 42); -- Now send the array Int_Array'Write (Sockets.Stream (Sock), (1=>42, 2=>128, 3=>6));
讀取屬性
[edit | edit source]過程 S'Read 在 13.13.2: Stream-Oriented Attributes (6) [Annotated] 中定義如下
procedureS'Read( Stream :notnullaccessAda.Streams.Root_Stream_Type'Class; Item :outT);
它的行為與 S'Write 明顯對稱:S'Read 從 Stream 中讀取一個或多個 Stream_Element,並將其“解析”以構造 Item。與 S'Write 的情況類似,Ada 為 S'Read 定義了預設實現,程式設計師可以使用屬性定義子句來覆蓋這些實現
forS'Readuse...
例如,以下過程可以分配給型別 Int,其中for Int'Read use Parse;.
procedureParse ( Stream :notnullaccessRoot_Stream_Type'Class; Item :outInt)isLen : Integer := 0; Buffer : Stream_Element_Array (1 .. 1); Last : Stream_Element_Offset; Zero : Stream_Element := Stream_Element (Character'Pos ('0')); Nine : Stream_Element := Stream_Element (Character'Pos ('9'));begin-- Extract the length from the streamloop-- Read one element from the stream Stream.Read (Buffer, Last);exitwhennot(Buffer (1)inZero .. Nine); Len := Len * 10 + Integer (Buffer (1) - Zero);endloop; -- Check for the correct delimiterifCharacter'Val (Integer (Buffer (1))) /= 'i'thenraiseData_Error;endif; -- Now convert the following Len characters Item := 0;forIin1 .. LenloopStream.Read (Buffer, Last); Item := 10 * Item + Int (Buffer (1) - Zero);endloop;endParse;
輸出屬性
[edit | edit source]過程 S'Output 在 13.13.2: Stream-Oriented Attributes (19) [Annotated] 中定義如下
procedureS'Output( Stream :notnullaccessAda.Streams.Root_Stream_Type'Class; Item :inT);
S'Output 與 S'Write 的區別在於它的預設實現
- 首先,它寫入陣列邊界(如果 S 是陣列)和判別式(如果 S 是記錄)。
- 然後,它呼叫 S'Write 來寫入 Item 本身
請注意,邊界或判別式是透過呼叫相應的 S'Write 過程寫入的。因此,由於 Int_Array 在上面被定義為由 Int 索引的 Int 陣列,因此以下行
Int_Array'Output (Text_Streams.Stream (Current_Output), (1 => 42, 2 => 128, 3 => 6));
將產生("_" 新增用於可讀性,實際上不在輸出中存在)
1i1_1i3_2i42_3i128_1i6
請注意行開頭處的陣列邊界 "1i1" 和 "1i3"。
輸入屬性
[edit | edit source]函式 S'Input 在 13.13.2: Stream-Oriented Attributes [Annotated] 中定義如下
functionS'Input( Stream :notnullaccessAda.Streams.Root_Stream_Type'Class)returnT;
S'Input 對於 S'Read 的作用相當於 S'Output 對於 S'Write,因為 S'Read
請注意,S'Input 是一個函式,而 S'Read 是一個過程。這與呼叫 S'Read 時,任何邊界和/或判別式都必須已知,以便呼叫者可以建立物件並將其傳遞給 S'Read 一致。另一方面,對於 S'Input,邊界/判別式是未知的,而是從流中讀取的;因此,建立物件的責任在 S'Input 上。
類範圍的讀取和寫入
[edit | edit source]請注意,S'Read 和 S'Write 不是 S 的原始子程式,即使 S 是一個帶標記的型別,它們也不能動態排程。為了允許 S'Read 和 S'Write 方法的動態排程,13.13.2: Stream-Oriented Attributes [Annotated] 定義了過程
procedureS'Class'Write( Stream :notnullaccessAda.Streams.Root_Stream_Type'Class; Item :inT'Class);procedureS'Class'Read( Stream :notnullaccessAda.Streams.Root_Stream_Type'Class; Item :outT'Class);
請注意,在這兩種情況下,Item 的型別都是 T'Class,因此 Item 可以是任何從 T 派生的型別。這些過程的行為是排程到由 Item 的標記標識的實際 S'Write 或 S'Read。有關類範圍流屬性 S'Class'Read 和 S'Class'Write 用法的示例,請參閱Ada Programming/Input Output/Stream Tutorial/Example。
類範圍的輸入和輸出
[edit | edit source]類似於 S'Read 和 S'Write,13.13.2: 面向流的屬性 [註釋] 定義了 S'Output 和 S'Input 的類級版本。
procedureS'Class'Output( Stream :notnullaccessAda.Streams.Root_Stream_Type'Class; Item :inT'Class)functionS'Class'Input( Stream :notnullaccessAda.Streams.Root_Stream_Type'Class)returnT'Class;
當我們記住帶標記的型別實際上可以被視為具有“隱藏的判別式”的記錄時,它們預設的行為幾乎是顯而易見的。
- S'Class'Output 首先將標籤轉換為字串,然後在結果上呼叫 String'Output,從而將標籤寫入流。接著,S'Class'Output 分派到由標籤標識的特定型別的子程式 S'Output。
- S'Class'Input 首先透過呼叫 String'Input 並將結果轉換為標籤來從流中讀取標籤。接著,S'Class'Input 分派到由標籤標識的特定型別的子程式 S'Input。
有關更詳細和精確的解釋,請參閱 13.13.2: 面向流的屬性 [註釋]。有關類級流屬性用法示例,請參閱 Ada Programming/Libraries/Ada.Streams/Example。
規範
[edit | edit source]-- Standard Ada library specification -- Copyright (c) 2003-2018 Maxim Reznik <reznikmm@gmail.com> -- Copyright (c) 2004-2016 AXE Consultants -- Copyright (c) 2004, 2005, 2006 Ada-Europe -- Copyright (c) 2000 The MITRE Corporation, Inc. -- Copyright (c) 1992, 1993, 1994, 1995 Intermetrics, Inc. -- SPDX-License-Identifier: BSD-3-Clause and LicenseRef-AdaReferenceManual -- -------------------------------------------------------------------------packageAda.StreamsispragmaPure (Streams);typeRoot_Stream_Typeisabstracttaggedlimitedprivate;pragmaPreelaborable_Initialization (Root_Stream_Type);typeStream_Elementismodimplementation_defined;typeStream_Element_Offsetisrangeimplementation_defined .. implementation_defined;subtypeStream_Element_CountisStream_Element_Offsetrange0..Stream_Element_Offset'Last;typeStream_Element_Arrayisarray(Stream_Element_Offsetrange<>)ofaliasedStream_Element;procedureRead (Stream :inoutRoot_Stream_Type; Item :outStream_Element_Array; Last :outStream_Element_Offset)isabstract;procedureWrite (Stream :inoutRoot_Stream_Type; Item :inStream_Element_Array)isabstract;privatepragmaImport (Ada, Root_Stream_Type);endAda.Streams;
另請參閱
[edit | edit source]華夏公益教科書
[edit | edit source]外部示例
[edit source]- 在以下網站搜尋 示例:Rosetta Code,GitHub (gists),任何 Alire 包 或 這本華夏公益教科書。
- 在以下網站搜尋 帖子:Stack Overflow,comp.lang.ada 或 任何與 Ada 相關的頁面。
Ada 參考手冊
[edit | edit source]Ada 95
[edit | edit source]Ada 2005
[edit | edit source]Ada 2012
[edit | edit source]開源實現
[edit | edit source]FSF GNAT
- 規範:a-stream.ads
- 主體:a-stream.adb
drake
- 規範:a-stream.ads
