跳轉到內容

Ada 程式設計/庫/Ada.Streams

來自 Wikibooks,開放世界中的開放書籍

Ada. Time-tested, safe and secure.
Ada. 久經考驗,安全可靠。

此語言特性從 Ada 95 開始提供。

Ada.Streams預定義語言環境 自 Ada 95 以來的一部分。

Ada **流** 是一種強大的 I/O 機制,允許將任何型別的物件讀寫到任何型別的“*介質*”(例如,網路連線、磁碟上的檔案、磁帶、記憶體緩衝區)。對於初學者來說,流可能有點難以理解,這是因為“雙重通用性”:關於要寫入/讀取的物件的通用性,以及關於所涉及介質的通用性。本節的目的是對 Ada 流進行直觀的介紹,省略一些細節。有關 Ada 流更精確和詳細的描述,請參閱 Ada 參考手冊,特別是 13.13: 流 [帶註釋的]

語言設計者將透過介質寫入物件的難題分解為兩個子問題

  1. 將物件轉換為位元序列
  2. 透過流寫入位元

請注意,第一步僅取決於要傳送的物件,而不取決於實際介質。另一方面,第二步的細節僅取決於所使用的介質,而不取決於物件型別。

類似地,為了從網路連線“讀取”物件,必須

  1. 從流中讀取位元塊
  2. 解析讀取的塊並將其轉換為物件

再次注意,第一步僅取決於介質,而第二步僅取決於物件型別。


抽象流

[編輯 | 編輯原始碼]

Ada 流的抽象模型基本上是一系列 *原始資料* (Stream_Element),可以以塊的形式讀寫。這種抽象檢視在 Stream 包定義中被形式化(來自 RM 13.13.1: 包 Streams. [帶註釋的] 添加了一些省略和註釋)

  package Ada.Streams is
  
     type Root_Stream_Type is abstract tagged limited private;
  
     -- Elementary piece of data.  A Stream is a sequence of Stream_Element
     type Stream_Element is mod implementation defined;
   
     type Stream_Element_Offset is range implementation defined;
   
     -- A block of data
     type Stream_Element_Array is 
         array (Stream_Element_Offset range <>) of aliased Stream_Element;
    
     -- Abstract procedure that reads a block of data
     procedure Read (
          Stream : in out Root_Stream_Type;
          Item   : out Stream_Element_Array;
          Last   : out Stream_Element_Offset) is abstract;
  
     -- Abstract procedure that writes a block of data
     procedure Write (
          Stream : in out Root_Stream_Type;
          Item   : in Stream_Element_Array) is abstract;
  
  private
         implementation defined...
  end Ada.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,因為它們是最簡單的,從某種意義上說,是最“原始”的。

Write 屬性

[編輯 | 編輯原始碼]

過程 S'Write 在 13.13.2: 面向流的屬性 (3) [帶註釋的] 中定義如下(請記住 S 是型別 T 的子型別)

  procedure S'Write(
        Stream : not null access Ada.Streams.Root_Stream_Type'Class;
        Item   : in  T);

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(以及以下描述的其他與流相關的函式)。

    for S'Write use user_defined_subprogram;

例如,假設網路協議要求以以下文字長度-型別-值格式格式化資料

  • 整數值格式化為 "<len> i <value>",其中 <len> 是用於表示整數的數字位數,<value> 是以十進位制表示的整數。(例如,整數 42 將表示為 "2i42")

以下程式碼為整數情況定義了一個合適的 S'Write 過程(注意:為了簡單起見,以下程式碼假設每個 Stream_Element 長度為 8 位)

   package Example is
       type Int is new Integer;
       type Int_Array is array (Int range <>) of Int;
      
       procedure Print (
            Stream : not null access Ada.Streams.Root_Stream_Type'Class;
            Item   : in  Int);
       
       for Int'Write Use Print;
   end Example;
   package body Example is
     procedure Print (
          Stream : not null access Ada.Streams.Root_Stream_Type'Class;
          Item   : in  Int) 
     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 Buffer
          for I in Buffer'Range loop
              Buffer (I) := Stream_Element (Character'Pos (Descr (Integer (I))));
          end loop; 
       
          -- Write the result to Stream
          Stream.Write(Buffer);
     end Print;
   end Example;

請注意 Print 的結構:首先,Item 在 Stream_Element 序列(包含在 Buffer 中)中被“序列化”,然後透過呼叫 Write 方法(它將負責 Stream 上寫入的詳細資訊)將此序列寫入 Stream。現在假設有人想將 42 的描述列印到標準輸出。可以使用以下程式碼

   with Ada.Text_IO.Text_Streams;
   use  Ada.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)

    with GNAT.Sockets;
    use  GNAT;
    ...
    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] 中定義如下

  procedure S'Read(
        Stream : not null access Ada.Streams.Root_Stream_Type'Class;
        Item   : out  T);

它的行為與 S'Write 明顯對稱:S'Read 從 Stream 中讀取一個或多個 Stream_Element,並將其“解析”以構造 Item。與 S'Write 的情況類似,Ada 為 S'Read 定義了預設實現,程式設計師可以使用屬性定義子句來覆蓋這些實現

  for S'Read use ...

例如,以下過程可以分配給型別 Int,其中for Int'Read use Parse;.

  procedure Parse (
                   Stream : not null access Root_Stream_Type'Class;
                   Item   : out Int)
  is
     Len    : 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 stream
     loop
        -- Read one element from the stream
        Stream.Read (Buffer, Last);
      
        exit when not (Buffer (1) in Zero .. Nine);
        Len := Len * 10 + Integer (Buffer (1) - Zero);
     end loop;
     
     -- Check for the correct delimiter
     if Character'Val (Integer (Buffer (1))) /= 'i' then
        raise Data_Error;
     end if;
  
     -- Now convert the following Len characters
     Item := 0;
     for I in 1 .. Len loop
        Stream.Read (Buffer, Last);
        Item := 10 * Item + Int (Buffer (1) - Zero);
     end loop;
  end Parse;

輸出屬性

[edit | edit source]

過程 S'Output13.13.2: Stream-Oriented Attributes (19) [Annotated] 中定義如下

  procedure S'Output(
        Stream : not null access Ada.Streams.Root_Stream_Type'Class;
        Item   : in  T);

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'Input13.13.2: Stream-Oriented Attributes [Annotated] 中定義如下

  function S'Input(
        Stream : not null access Ada.Streams.Root_Stream_Type'Class)
        return  T;

S'Input 對於 S'Read 的作用相當於 S'Output 對於 S'Write,因為 S'Read

  • 首先,它讀取邊界或判別式(使用相應的 S'Read
  • 它使用讀取的值來建立要返回的物件
  • 它呼叫相應的 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] 定義了過程

  procedure S'Class'Write(
     Stream : not null access Ada.Streams.Root_Stream_Type'Class;
     Item   : in T'Class);
  
  procedure S'Class'Read(
     Stream : not null access Ada.Streams.Root_Stream_Type'Class;
     Item   : out T'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'Write13.13.2: 面向流的屬性 [註釋] 定義了 S'Output 和 S'Input 的類級版本。

  procedure S'Class'Output(
     Stream : not null access Ada.Streams.Root_Stream_Type'Class;
     Item   : in T'Class)
  
  function S'Class'Input(
     Stream : not null access Ada.Streams.Root_Stream_Type'Class)
          return T'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
-- -------------------------------------------------------------------------

package Ada.Streams is
   pragma Pure (Streams);

   type Root_Stream_Type is abstract tagged limited private;
   pragma Preelaborable_Initialization (Root_Stream_Type);

   type Stream_Element is mod implementation_defined;
   type Stream_Element_Offset is range
     implementation_defined .. implementation_defined;

   subtype Stream_Element_Count is
     Stream_Element_Offset range 0..Stream_Element_Offset'Last;

   type Stream_Element_Array is
     array (Stream_Element_Offset range <>) of aliased Stream_Element;

   procedure Read (Stream : in out Root_Stream_Type;
                   Item   : out Stream_Element_Array;
                   Last   : out Stream_Element_Offset) is abstract;

   procedure Write (Stream : in out Root_Stream_Type;
                    Item   : in Stream_Element_Array) is abstract;

private

   pragma Import (Ada, Root_Stream_Type);

end Ada.Streams;



另請參閱

[edit | edit source]

華夏公益教科書

[edit | edit source]

外部示例

[edit source]


Ada 參考手冊

[edit | edit source]

Ada 95

[edit | edit source]

Ada 2005

[edit | edit source]

Ada 2012

[edit | edit source]

開源實現

[edit | edit source]

FSF GNAT

drake

華夏公益教科書