12

Pascal には 2 種類の型宣言があります。

  • 型エイリアス: type NewName = OldType
  • タイプの作成:タイプ NewType =タイプOldType

前者は、C のtypedefのように、便利な省略形を作成しているだけです。エイリアスは、互いに互換性があり、元の型と互換性があります。作成された型は意図的に互換性がなく、明示的でない限り混合できず、定義により安全ではありません。

var
  nn: NewName; nt: NewType; ot: OldType;
...
  nn := ot; // should work
  nt := ot; // should break with type safety violation error.

  nt := NewType(ot); // Disabling type safety. Should work even if 
  // it has no sense semantically and types really ARE incompatible.

私が理解しているように、これらはパスカルの基本です。

ここで、特定の型 1 つとそのエイリアス 2 つを見てみましょう。

  • System.Types.TStringDynArray =文字列の配列
  • System.TArray<T> = Tの配列
    • 特に、 TArray<string> =文字列の配列; 定義により。

ここで、前者の型エイリアスを返す関数を取り、その結果を後者を期待する関数にフィードしましょう。

uses Classes, IOUtils;

 TStringList.Create.AddStrings(
    TDirectory.GetFiles('c:\', '*.dll') );

 TStringList.Create.AddStrings(
     TArray<string>( // this is required by compiler - but why ???
         TDirectory.GetFiles('c:\', '*.dll') ) );

1 番目のスニペットは型違反のためコンパイルされません。2 番目のものは問題なくコンパイルおよび動作しますが、将来の型の変更に対して脆弱であり、冗長です。

QC は、コンパイラが正しく、RTL 設計が間違っていることを示します。 http://qc.embarcadero.com/wc/qcmain.aspx?d=106246

なぜコンパイラがここにあるのですか? これらのエイリアスが互換性がないのはなぜですか? RTL が設計されたまさにその方法でさえ、それらが互換性があると見なされたことを示唆しています!

PS。David は、TArray<T> を使用せずに、さらに単純な例を提案しました

 type T1 = array of string; T2 = array of string;

 procedure TForm1.FormCreate(Sender: TObject);
  function Generator: T1;
    begin Result := T1.Create('xxx', 'yyy', 'zzz'); end;
  procedure Consumer (const data: T2);
    begin
      with TStringList.Create do 
      try
        AddStrings(data);
        Self.Caption := CommaText;
      finally
        Free;
      end;
    end;
  begin
    Consumer(Generator);
  end;

説明なしで同じ落とし穴...

PPS。現在、多数のドキュメント参照があります。1 つのことを強調したいと思います。この制限は 1949 年のパスカル レポートから間接的に継承されている可能性がありますが、今日は 2012 年であり、Delphi は半世紀前の学校の研究室とは大きく異なって使用されています。この制限を守ることの悪い影響をいくつか挙げましたが、良い影響は見当たりませんでした。

皮肉なことに、Pascal の規則を破ることなくこの制限を解除することができます。Pascal には、Open Array や Dynamic Array のような非厳密な獣はありません。したがって、これらの元の固定配列は好きなように制限できますが、オープン配列と動的配列は Pascal 市民ではなく、そのコードブックによって制限される義務はありません!

QC またはここでも Emba に連絡してください。ただし、意見を表明せずに通り過ぎるだけでは、何も変わりません。

4

3 に答える 3

11

この問題を理解するための鍵は、言語ガイドの「タイプの互換性とID」トピックです。そのトピックをよく読んでおくことをお勧めします。

例を単純化することも役立ちます。例にジェネリックスを含めることは、主に問題を複雑にし、混乱させるのに役立ちます。

program TypeCompatibilityAndIdentity;
{$APPTYPE CONSOLE}

type
  TInteger1 = Integer;
  TInteger2 = Integer;
  TArray1 = array of Integer;
  TArray2 = array of Integer;
  TArray3 = TArray1;

var
  Integer1: TInteger1;
  Integer2: TInteger2;
  Array1: TArray1;
  Array2: TArray2;
  Array3: TArray3;

begin
  Integer1 := Integer2; // no error here
  Array1 := Array2; // E2010 Incompatible types: 'TArray1' and 'TArray2'
  Array1 := Array3; // no error here
end.

ドキュメントから:

あるタイプ識別子が、修飾なしで別のタイプ識別子を使用して宣言されている場合、それらは同じタイプを示します。

これは、TInteger1TInteger2同じタイプであり、実際にと同じタイプであることを意味しIntegerます。

ドキュメントのもう少し先はこれです:

タイプ名として機能する言語構造は、発生するたびに異なるタイプを示します。

の宣言とはTArray1このTArray2カテゴリに分類されます。つまり、これら2つの識別子は異なるタイプを示します。

次に、互換性について説明しているセクションを確認する必要があります。これにより、2つのタイプが互換性があるか、または割り当て互換性があるかどうかを判断するために従うべき一連のルールが提供されます。実際、別のヘルプトピックである構造化型、配列型、および割り当てを参照することで、その議論を短縮できます。

配列は、同じタイプの場合にのみ割り当て互換です。

これにより、割り当てArray1 := Array2によってコンパイラエラーが発生する理由が明確になります。

あなたのコードはパラメータの受け渡しに注目しましたが、私のコードは割り当てに焦点を合わせていました。呼び出し手順と関数のヘルプトピックで説明されているように、問題は同じです。

ルーチンを呼び出すときは、次の点に注意してください。

  • 型付きのconstおよびvalueパラメーターを渡すために使用される式は、対応する仮パラメーターと代入互換でなければなりません。
  • .....。
于 2012-06-14T14:19:34.737 に答える
7

Delphi は厳密に型指定された言語です。つまり、同一の (この場合、それらの定義がまったく同じに見えることを意味します) 型は代入に互換性がありません。

書くときarray of <type>は、エイリアスではなくタイプを定義しています。Davidがコメントですでに言ったように、2つの同一のタイプは次のようになります

type 
  T1 = array of string; 
  T2 = array of string;

代入互換性がありません。

同じことが言えます

type
  TStringDynArray = array of string;
  TArray<T> = array of string;

多くの場合、人々は同一の型の非互換性を忘れていますが、私の推測では、たとえば IOUtils を導入したときに忘れてしまったのではないでしょうか。理論的には TStringDynArray の定義は に変更されているはずTStringDynArray = TArray<string>ですが、他の問題が発生した可能性があると思います (ジェネリックのバグとは言っていません...)。

于 2012-06-14T12:14:17.040 に答える
2

Delphi でも同じ問題があり、同じ配列から別の配列に値を渡したいと考えていました。2 つの同様の配列割り当てで「非互換性」の問題が発生しただけでなく、「Copy()」プロシージャも使用できませんでした。この問題を回避するために、代わりに文字列配列の型配列へのポインターを使用できることがわかりました。

例えば:

type RecArry = array of array of string
     end;
var TArryPtr : ^RecArry;

これで、互換性や機能の問題なしに、任意の固定配列から別の同一の配列に値を渡すことができます。例えば:

TArryPtr := @RecArry.LstArray //This works!
TArryPtr := @LstArray         //This also works!

この作成された配列割り当てテンプレートを使用して、すべての 2 次元配列を問題なく操作できるようになりました。ただし、このタイプの文字列配列ポインターにアクセスすると、追加の要素が作成されるため、このタイプの配列 2D 配列が次のように予想される場合に、次のようになることを理解する必要があります。

Two_Dimensional_Fixed_Array[10][0]

次に示すように、追加の要素調整済み配列を取得します。

New_Two_Dimensional_Fixed_Array[10][1]    

これは、Two_Dimensional_Fixed_Array[10][0] に設定されたすべての要素が下に移動し、New_Two_Dimensional_Fixed_Array[10][1 のように 1 だけオフセットされるため、ポインター配列にアクセスするために少しトリッキーなコードを使用する必要があることを意味します。 ]。

したがって、通常はTwo_Dimensional_Fixed_Array [1][0]で値'X'が見つかる場所が、ここでは TArryPtr [0][1]で見つかります。

それは私たち全員が一緒に暮らさなければならないトレードオフです!

留意すべきもう 1 つの重要な注意事項は、宣言時のポインター配列の定義です。ポインタ配列が型宣言されている場合、Borland コンパイラは、ポインタ配列がそれが指している配列と同じ要素サイズを持つことを許可しません。たとえば、配列が次のように宣言されている場合:

Orig_Arry : array [1..50,1] of string;

それを指すポインター配列は、次のように宣言されます。

Type Pntr_Arry : array [1..50,2] of string;

余分な要素に気づきましたか?Borland コンパイラは、ポインター アドレスを許可するために配列ポインターを拡張する必要があると推測しています。

于 2013-02-18T10:35:08.973 に答える