Marco Cantù's
Essential Pascal Chapter 6
Procedures and Functions
第6章
手続きと関数
Another important idea emphasized by Pascal is the concept of the routine, basically a series of statements with a unique name, which can be activated many times by using their name. This way you avoid repeating the same statements over and over, and having a single version of the code you can easily modify it all over the program. From this point of view, you can think of routines as the basic code encapsulation mechanism. I'll get back to this topic with an example after I introduce the Pascal routines syntax.
Pascal が採用している考え方で、もうひとつ重要なのが、ルーチンだ。 これは、独自の名前を付けた文の集まりで、この名前を呼び出すだけで実行されるものだ。 こうすることで、同じ文の集まりを何度も何度も繰り返す必要がなく、また、そのコードをひとつにできるので、このコードを一回修正するだけで、プログラム全体で使える。 つまり、ルーチンは、コードのカプセル化と言える。 このことについては、Pascal ルーチンの構文を説明した後で、例を示してみよう。
Pascal Procedures and Functions
Pascal の手続きと関数
In Pascal, a routine can assume two forms: a procedure and a function. In theory, a procedure is an operation you ask the computer to perform, a function is a computation returning a value. This difference is emphasized by the fact that a function has a result, a return value, while a procedure doesn't. Both types of routines can have multiple parameters, of given data types.
Pascal には、ルーチンがふたつある。 それが手続きと関数だ。 理論的には、手続きはコンピュータに実行を指示するものであり、関数は値を返すものだ。 この差をもっと明確にすると、関数は result という戻り値を持ち、手続きは持たない、ということだ。 どちらのルーチンも、特定のデータ型の複数のパラメータを取ることができる。
In practice, however, the difference between functions and procedures is very limited: you can call a function to perform some work and then skip the result (which might be an optional error code or something like that) or you can call a procedure which passes a result within its parameters (more on reference parameters later in this chapter).
ただ、実際には関数と手続きの差は、限定的なものだ。 関数を呼び出しておいて、何か動作をさせ、result ( 異常時のエラーコードなんかを仕込んでおいてもいい ) をスキップしたり、手続きを呼び出しておいて、パラメータの形で戻り値を変えさせてもいい。 この参照パラメータについては、後の方で触れる。
Here are the definitions of a procedure and two versions of the same function, using a slightly different syntax:
少し構文を変えて、手続きと関数の例を挙げてみよう。
procedure Hello;
begin
ShowMessage ('Hello world!');
end;
function Double (Value: Integer) : Integer;
begin
Double := Value * 2;
end;
// or, as an alternative この関数の別バージョン
function Double2 (Value: Integer) : Integer;
begin
Result := Value * 2;
end;
The use of Result instead of the function name to assign the return value of a function is becoming quite popular, and tends to make the code more readable, in my opinion.
近頃では、関数名を戻り値として返すより、Result を使う方が人気があり、僕もこの方がコードの可読性が上がると思う。
Once these routines have been defined, you can call them one or more times. You call the procedure to make it perform its task, and call a function to compute the value:
一度ルーチンを定義したら、何回でも呼び出すことができる。 手続きを呼び出して、何か仕事をさせ、関数を呼び出して値を計算させる。
procedure TForm1.Button1Click (Sender: TObject);
begin
Hello;
end;
procedure TForm1.Button2Click (Sender: TObject);
var
X, Y: Integer;
begin
X := Double (StrToInt (Edit1.Text));
Y := Double (X);
ShowMessage (IntToStr (Y));
end;
Note: For the moment don't care about the syntax of the two procedures above, which are actually methods. Simply place two buttons on a Delphi form, click on them at design time, and the Delphi IDE will generate the proper support code: Now you simply have to fill in the lines between begin and end. To compile the code above you need to add also an Edit control to the form.
注 : 上の手続きは、実際にはメソッドだけど、構文すべてを気にする必要はない。 Delphi フォームにボタンをふたつ置き、設計時にクリックすると Delphi IDE が必要なコードを補完してくれる。 君がするのは、begin と end の間にコードを書くだけだ。 上記のコードを完成させるには、フォームにエディット・コントロールを置く。
Now we can get back to the encapsulation code concept I've introduced before. When you call the Double function, you don't need to know the algorithm used to implement it. If you later find out a better way to double numbers, you can easily change the code of the function, but the calling code will remain unchanged (although executing it will be faster!). The same principle can be applied to the Hello procedure: We can modify the program output by changing the code of this procedure, and the Button2Click method will automatically change its effect. Here is how we can change the code:
さて、既に話した、コードのカプセル化に戻ろう。 Double という関数を呼びだしたとき、どういう実装がされているか、知らなくてもいい。 後から、数値を倍増する、もっといいやり方を思いつけば、この関数自体のコードを変えるだけでよく、Double 関数を呼び出している部分のコードは、変える必要が無い。 上の場合は、呼び出し部分を変えた方が、ずっと簡単だけど... これは、Hello 手続きでも同じことだ。 この手続きのコードを変えることで、プログラムの実行結果を変えることができる。 Button2Click というメソッドの実行結果を変えてみよう。
procedure Hello;
begin
MessageDlg ('Hello world!', mtInformation, [mbOK]);
end;
Tip: When you call an existing Delphi function or procedure, or any VCL method, you should remember the number and type of the parameters. Delphi editor helps you by suggesting the parameters list of a function or procedure with a fly-by hint as soon as you type its name and the open parenthesis. This feature is called Code Parameters and is part of the Code Insight technology.
ヒント : 既存の Delphi 関数や手続き、VCL メソッドを呼び出すときには、パラメータの数と型に注意すること。 手続きか関数の名前と括弧を入力すると、Delphi エディタはその手続きや関数のパラメータ・リストを、吹き出しヒントで示してくれる。 これは、コード・パラメータと呼ばれ、コード補完技術の一部だ。
Reference Parameters
参照パラメータ
Pascal routines allow parameter passing by value and by reference. Passing parameters by value is the default: the value is copied on the stack and the routine uses and manipulates the copy, not the original value.
Pascal では、パラメータに値を渡すことも、参照を渡すことも可能だ。 値渡しがデフォルトになっていて、値はスタックにコピーされ、ルーチンはこのコピーを扱う。 オリジナルの値には関係しない。
Passing a parameter by reference means that its value is not copied onto the stack in the formal parameter of the routine (avoiding a copy often means that the program executes faster). Instead, the program refers to the original value, also in the code of the routine. This allows the procedure or function to change the value of the parameter. Parameter passing by reference is expressed by the var keyword.
パラメータに参照を渡す、というのは、デフォルトのようには値をスタックにコピーしないということだ。 コピーしないので、それだけプログラムの実行速度が上がる。 コピーする代わりに、プログラムやルーチンでは、オリジナルの値を参照する。 つまり、手続きや関数が、パラメータの値を変えられる、ということだ。 参照渡しのパラメータは、var キーワードを付けて書く。
This technique is available in most programming languages. It isn't present in C, but has been introduced in C++, where you use the & (pass by reference) symbol. In Visual Basic every parameter not specified as ByVal is passed by reference.
この参照渡し、というのは、他のプログラミング言語でもある。 C には無いが、C++ では導入され、& シンボルを付けて参照渡しを表す。 Visual Basic では、ByVal が付いていないパラメータは、すべて参照渡しだ。
Here is an example of passing a parameter by reference using the var keyword:
参照渡しの書き方を示す。
procedure DoubleTheValue (var Value: Integer);
begin
Value := Value * 2;
end;
In this case, the parameter is used both to pass a value to the procedure and to return a new value to the calling code. When you write:
この場合は、パラメータは値を手続きに渡す場合も使われ、また新しい値を、この手続きを呼びだしたコードにも返す。 こう書くと、
var
X: Integer;
begin
X := 10;
DoubleTheValue (X);
the value of the X variable becomes 20, because the function uses a reference to the original memory location of X, affecting its initial value.
変数 X は、20 になる。 これは、この関数が、X の値が格納されているオリジナルのメモリ・ロケーションを参照し、元の値を変更してしまうからだ。
Passing parameters by reference makes sense for ordinal types, for old-fashioned strings, and for large records. Delphi objects, in fact, are invariably passed by value, because they are references themselves. For this reason passing an object by reference makes little sense (apart from very special cases), because it corresponds to passing a "reference to a reference."
参照渡しは、順序型や、古い形式の文字列、大きなレコードを扱うのに適している。 Delphi のオブジェクトは、それ自体が参照なので、すべて値渡しをしている。 そのため、オブジェクトを参照で渡すのは、非常に特殊な場合を除いては意味がない。 というのも、こうすれば、「参照を参照する」という事態になってしまうからだ。
Delphi long strings have a slightly different behavior: they behave as references, but if you change one of the string variables referring to the same string in memory, this is copied before updating it. A long string passed as a value parameter behaves as a reference only in terms of memory usage and speed of the operation. But if you modify the value of the string, the original value is not affected. On the contrary, if you pass the long string by reference, you can alter the original value.
Delphi の長い文字列は、少し違った動きをする。 これは参照として働くが、メモリにある同じ文字列の参照をしている複数の文字列のどれかを変更すると、その文字列が更新される前にコピーされる。 値パラメータとして渡される長い文字列は、参照渡しのように動くが、これはメモリ管理を向上させ、動作のスピードアップを図るために採用されている。 反対に、長い文字列を参照として渡した場合は、オリジナルの文字列の値を変更することができる。
Delphi 3 introduced a new kind of parameter, out. An out parameter has no initial value and is used only to return a value. These parameters should be used only for COM procedures and functions; in general, it is better to stick with the more efficient var parameters. Except for not having an initial value, out parameters behave like var parameters.
Delphi 3 では、新しいパラメータ、out が導入された。 out パラメータは、初期値を持たず、ただ値を返す場合にのみ使用される。 このパラメータは、COM 手続きと関数でのみ使われる。 一般的には、var パラメータを使った方が、ずっと効率的だ。 初期値を持たない以外は、out パラメータは、var パラメータと同様の動きをする。
Constant Parameters
定数パラメータ
As an alternative to reference parameters, you can use a const parameter. Since you cannot assign a new value to a constant parameter inside the routine, the compiler can optimize parameter passing. The compiler can choose an approach similar to reference parameters (or a const reference in C++ terms), but the behavior will remain similar to value parameters, because the original value won't be affected by the routine.
参照パラメータの代わりに、定数パラメータも使える。 ルーチンの内部では、定数パラメータには新しい値を割り当てることができないので、コンパイラが、最適なパラメータが渡されるよう、最適化を行う。 コンパイラは、参照パラメータと同じようなアプローチ ( または、C++ で言うところの、const 参照 ) を取るが、値パラメータと同様の動きをする。 というのも、ルーチン内部でオリジナルの値が変更されることはないからだ。
In fact, if you try to compile the following (silly) code, Delphi will issue an error:
実際、下記のような ( 馬鹿げた ) コードを書くと、Delphi はエラーを出してくる。
function DoubleTheValue (const Value: Integer): Integer;
begin
Value := Value * 2; // compiler error コンパイル・エラー
Result := Value;
end;
Open Array Parameters
オープン配列パラメータ
Unlike C, a Pascal function or procedure always has a fixed number of parameters. However, there is a way to pass a varying number of parameters to a routine using an open array.
C とは違い、Pascal の関数や手続きで使うパラメータの個数は、必ず決まっている。 しかし、オープン配列を使うと、パラメータの個数を変えることができる。
The basic definition of an open array parameter is that of a typed open array. This means you indicate the type of the parameter but do not know how many elements of that type the array is going to have. Here is an example of such a definition:
オープン配列とは、型付きのオープン配列ということだ。 つまり、パラメータの型は明示するけれど、いくつパラメータを使うのか、ということは、はっきりしていない、ということだ。 例を挙げよう。
function Sum (const A: array of Integer): Integer;
var
I: Integer;
begin
Result := 0;
for I := Low(A) to High(A) do
Result := Result + A[I];
end;
Using High(A) we can get the size of the array. Notice also the use of the return value of the function, Result, to store temporary values. You can call this function by passing to it an array of Integer expressions:
High(A) を使うことで、配列のサイズが定まる。 関数の返り値 Result に一時的な値を収納していることに注意。 この関数を呼ぶときには、Integer 型の値か式を配列として渡す。
X := Sum ([10, Y, 27*I]);
Given an array of Integers, of any size, you can pass it directly to a routine requiring an open array parameter or, instead, you can call the Slice function to pass only a portion of the array (as indicated by its second parameter). Here is an example, where the complete array is passed as parameter:
どういう値の範囲でもいいが、与えられた Integer 配列を、オープン配列を要求するルーチンに直接渡したり、Slice 関数の2番目のパラメータに個数を指定すれば、配列の一部をルーチンに渡すことができる。 配列すべてを渡すサンプルを示す。
var
List: array [1..10] of Integer;
X, I: Integer;
begin
// initialize the array 配列を初期化
for I := Low (List) to High (List) do
List [I] := I * 2;
// call
X := Sum (List);
If you want to pass only a portion of the array to the Slice function, simply call it this way:
配列の一部を渡したいときは、Slice 関数を使い、こうする。
X := Sum (Slice (List, 5));
You can find all the code fragments presented in this section in the OpenArr example (see Figure 6.1, later on, for the form).
参考として、図 6.1 の OpenArr プログラムを見てほしい。
Figure 6.1: The OpenArr example when the Partial Slice button is pressed
図 6.1 : Partial Slice ボタンを押した場合の、OpenArr の例
Typed open arrays in Delphi 4 are fully compatible with dynamic arrays (introduced in Delphi 4 and covered in Chapter 8). Dynamic arrays use the same syntax as open arrays, with the difference that you can use a notation such as array of Integer to declare a variable, not just to pass a parameter.
Delphi 4 での型付きのオープン配列は、動的配列と完全に互換性がある。 これについては、8章でも扱っている。 動的配列は、オープン配列とおなじ構文を使っていて、違うのは、パラメータを渡すだけでなく、変数宣言に Integer 配列などを使えることだ。
Type-Variant Open Array Parameters
バリアント型オープン配列パラメータ
Besides these typed open arrays, Delphi allows you to define type-variant or untyped open arrays. This special kind of array has an undefined number of values, which can be handy for passing parameters.
この型付きのオープン配列に加え、Delphi ではバリアント型、または型無しのオープン配列が使える。 この特殊な配列は、パラメータに不特定の値を渡すときに便利だ。
Technically, the construct array of const allows you to pass an array with an undefined number of elements of different types to a routine at once. For example, here is the definition of the Format function (we'll see how to use this function in Chapter 7, covering strings):
技術的には、定数の配列を使えば、型の違った、しかも個数の定まらない配列を、一度にルーチンに渡すことができる。 例として、Format 関数 ( 第7章で、string を扱うときに、この関数について取り上げる ) の定義を挙げよう。
function Format (const Format: string;
const Args: array of const): string;
The second parameter is an open array, which gets an undefined number of values. In fact, you can call this function in the following ways:
第2のパラメータはオープン配列で、不特定の個数の値を取る。 実際には、こう使う。
N := 20;
S := 'Total:';
Label1.Caption := Format ('Total: %d', [N]);
Label2.Caption := Format ('Int: %d, Float: %f', [N, 12.4]);
Label3.Caption := Format ('%s %d', [S, N * 2]);
Notice that you can pass a parameter as either a constant value, the value of a variable, or an expression. Declaring a function of this kind is simple, but how do you code it? How do you know the types of the parameters? The values of a type-variant open array parameter are compatible with the TVarRec type elements.
定数値でも、バリアントでも、または式でも、パラメータとして渡せることに注意。 この手の関数を宣言するは簡単だけど、コードを書くのは難しい。 パラメータの型を知るときにも、難しい。 バリアント型のオープン配列パラメータは、TVarRec 型の要素と、互換性がある。
Note: Do not confuse the TVarRec record with the TVarData record used by the Variant type itself. These two structures have a different aim and are not compatible. Even the list of possible types is different, because TVarRec can hold Delphi data types, while TVarData can hold OLE data types.
注 : バリアント型自身が使っている TVarData と、この TVarRec を混同しないこと。 このふたつは、それぞれ目的があり、しかも互いに互換性が無い。 TVarRec は Delphi データ型、一方 TVarData は OLE 型、という具合に、保持する型も違う。
The TVarRec record has the following structure:
TVarRec レコードは、こういう構造になっている。
type
TVarRec = record
case Byte of
vtInteger: (VInteger: Integer; VType: Byte);
vtBoolean: (VBoolean: Boolean);
vtChar: (VChar: Char);
vtExtended: (VExtended: PExtended);
vtString: (VString: PShortString);
vtPointer: (VPointer: Pointer);
vtPChar: (VPChar: PChar);
vtObject: (VObject: TObject);
vtClass: (VClass: TClass);
vtWideChar: (VWideChar: WideChar);
vtPWideChar: (VPWideChar: PWideChar);
vtAnsiString: (VAnsiString: Pointer);
vtCurrency: (VCurrency: PCurrency);
vtVariant: (VVariant: PVariant);
vtInterface: (VInterface: Pointer);
end;
Each possible record has the VType field, although this is not easy to see at first because it is declared only once, along with the actual Integer-size data (generally a reference or a pointer).
使える型すべてに VType フィールドがあるが、整数サイズのデータ表示と一緒に、一度切りしか宣言されていないので、ちょっと見ただけではわかりにくい。
Using this information we can actually write a function capable of operating on different data types. In the SumAll function example, I want to be able to sum values of different types, transforming strings to integers, characters to the corresponding order value, and adding 1 for True Boolean values. The code is based on a case statement, and is quite simple, although we have to dereference pointers quite often:
これで、別のデータ型を操作できる関数を書くことができる。 たとえば、SumAll 関数という例では、string を integer に、character をそれに合致する順序数に、と言う具合に変換して、データ型の違う値の合計を取り、ブールで True の場合は、それに 1 を加える。 このコードでは、ポインタを頻繁に逆参照しているけれど、case 文を使っていて、非常に簡単に見える。
function SumAll (const Args: array of const): Extended;
var
I: Integer;
begin
Result := 0;
for I := Low(Args) to High (Args) do
case Args [I].VType of
vtInteger: Result :=
Result + Args [I].VInteger;
vtBoolean:
if Args [I].VBoolean then
Result := Result + 1;
vtChar:
Result := Result + Ord (Args [I].VChar);
vtExtended:
Result := Result + Args [I].VExtended^;
vtString, vtAnsiString:
Result := Result + StrToIntDef ((Args [I].VString^), 0);
vtWideChar:
Result := Result + Ord (Args [I].VWideChar);
vtCurrency:
Result := Result + Args [I].VCurrency^;
end; // case
end;
I've added this code to the OpenArr example, which calls the SumAll function when a given button is pressed:
このコードを、サンプルの OpenArr に加え、ボタンを押したら、SumAll 関数が呼び出されるようにしてみた。
procedure TForm1.Button4Click(Sender: TObject);
var
X: Extended;
Y: Integer;
begin
Y := 10;
X := SumAll ([Y * Y, 'k', True, 10.34, '99999']);
ShowMessage (Format (
'SumAll ([Y*Y, ''k'', True, 10.34, ''99999'']) => %n', [X]));
end;
You can see the output of this call, and the form of the OpenArr example, in Figure 6.2.
図 6.2. で、この呼び出しの結果と、OpenArr サンプルのフォームを確認できる。
Figure 6.2: The form of the OpenArr example, with the message box displayed when the Untyped button is pressed.
図 6.2 : OpenArr のフォームと、Untyped ボタンが押されたときのメッセージ・ボックス
Delphi Calling Conventions
Delphi の呼び出し規約
The 32-bit version of Delphi has introduced a new approach to passing parameters, known as fastcall: Whenever possible, up to three parameters can be passed in CPU registers, making the function call much faster. The fast calling convention (used by default in Delphi 3) is indicated by the register keyword.
32 ビット・バージョンの Delphi では、パラメータを渡すのに新しいやり方、fastcall と呼ばれる方法が導入された。 可能な場合はいつでも、3 個までのパラメータを CPU レジスタに格納でき、関数呼び出しをより早くできる。 この呼び出しを使う規約は、Delphi 3 でのデフォルトでは、register キーワードを付けることだ。
The problem is that this is the default convention, and functions using it are not compatible with Windows: the functions of the Win32 API must be declared using the stdcall calling convention, a mixture of the original Pascal calling convention of the Win16 API and the cdecl calling convention of the C language.
ただ問題は、このデフォルトの規約は Windows と互換性が無いことだ。 つまり、Win32 API の関数を使うときには、stdcall 呼び出し規約、Win16 API 用のオリジナルの Pascal 呼び出し規約、それにC 言語の cdecl 規約をすべて宣言する必要がある。
There is generally no reason not to use the new fast calling convention, unless you are making external Windows calls or defining Windows callback functions. We'll see an example using the stdcall convention before the end of this chapter. You can find a summary of Delphi calling conventions in the Calling conventions topic under Delphi help.
external Windows 呼び出しや、Windows callback 関数を使わないので有れば、このfast call はいつでも使っていい。 stdcall 規約を使ったサンプルは、この章の最後に示す。 Delphi ヘルプの呼び出し規約の項を読めば、Delphi 呼び出し規約の概略が分かる。
What Is a Method?
メソッドとは ?
If you have already worked with Delphi or read the manuals, you have probably heard about the term "method". A method is a special kind of function or procedure that is related to a data type, a class. In Delphi, every time we handle an event, we need to define a method, generally a procedure. In general, however, the term method is used to indicate both functions and procedures related to a class.
もう Delphi を使っていたり、マニュアルを読んだのなら、「メソッド」という言葉を聞いたことがあるだろう。 メソッドというのは、関数や手続きの一種で、特定のデータ型、クラスに関係している。 Delphi ではイベントを扱うたびに、メソッド、一般的には手続き、を定義する必要がある。 ただし、Delphi では、メソッドという言葉は、クラスに関係している関数や手続きを表すのに用いられる。
We have already seen a number of methods in the examples in this and the previous chapters. Here is an empty method automatically added by Delphi to the source code of a form:
この章でも、この章以前でも、様々なメソッドを見てきた。 Delphi が自動的にフォームのソースに追加する、空のメソッドを挙げておこう。
procedure TForm1.Button1Click(Sender: TObject);
begin
{here goes your code} ここに君のコードを書く
end;
Forward Declarations
フォワード宣言
When you need to use an identifier (of any kind), the compiler must have already seen some sort of declaration to know what the identifier refers to. For this reason, you usually provide a full declaration before using any routine. However, there are cases in which this is not possible. If procedure A calls procedure B, and procedure B calls procedure A, when you start writing the code, you will need to call a routine for which the compiler still hasn't seen a declaration.
どのようなものでもいいが、何か識別子を使う必要があるとする。 この場合、コンパイラはその識別子が何なのか、知っている必要がある。 だから、何かルーチンを使う前に、完璧に宣言しておく必要がある。 しかし、場合によっては、事前に宣言しておくことが不可能の時がある。 もし手続き A が手続き B を呼び、手続き B がまた手続き A を呼び出すとする。 この場合、コードを書き始めた段階では、コンパイラに、まだ宣言が知らされていないルーチンを、呼び出すことになる。
If you want to declare the existence of a procedure or function with a certain name and given parameters, without providing its actual code, you can write the procedure or function followed by the forward keyword:
実際のコードはまだできあがってなく、しかし特定の名前やパラメータの付いた手続きや関数を宣言しよう、と言うときには、forward キーワードを付けて、手続きや関数を書けばいい。
procedure Hello; forward;
Later on, the code should provide a full definition of the procedure, but this can be called even before it is fully defined. Here is a silly example, just to give you the idea:
もちろん後から、その手続きの完全な定義をする必要はあるが、定義前でも、この手続きを呼び出すことができる。 まあ、この例は馬鹿げたものだが、雰囲気を感じ取ってほしい。
procedure DoubleHello; forward;
procedure Hello;
begin
if MessageDlg ('Do you want a double message?', 二重にメッセージを出すか ?
mtConfirmation, [mbYes, mbNo], 0) = mrYes then
DoubleHello
else
ShowMessage ('Hello');
end;
procedure DoubleHello;
begin
Hello;
Hello;
end;
This approach allows you to write mutual recursion: DoubleHello calls Hello, but Hello might call DoubleHello, too. Of course there must be a condition to terminate the recursion, to avoid a stack overflow. You can find this code, with some slight changes, in the DoubleH example.
こうすると、相互循環、DoubleHello は Hello を呼び、その Hello はまた DoubleHello を呼ぶ、を書くことができる。 もちろん、スタックオーバーフローになってしまうので、この循環を停止する条件を与えておくことが必要だ。 DoubleH サンプルでは、このコードを少し変えてある。
Although a forward procedure declaration is not very common in Delphi, there is a similar case that is much more frequent. When you declare a procedure or function in the interface portion of a unit (more on units in the next chapter), it is considered a forward declaration, even if the forward keyword is not present. Actually you cannot write the body of a routine in the interface portion of a unit. At the same time, you must provide in the same unit the actual implementation of each routine you have declared.
Delphi では、フォワード手続き宣言というのは、あまり一般的ではないけれど、これに似たもので、もっと良く使われているものがある。 ユニットのインターフェイス部分に手続きや関数を宣言すれば、forward キーワードが無くとも、それはフォワード宣言と見なされる。 実際、ユニットのインターフェイス部分に、ルーチンの本体を書くわけには行かない。 もちろん、そのユニットに、宣言した各ルーチンの実装を書く必要はある。
The same holds for the declaration of a method inside a class type that was automatically generated by Delphi (as you added an event to a form or its components). The event handlers declared inside a TForm class are forward declarations: the code will be provided in the implementation portion of the unit. Here is an excerpt of the source code of an earlier example, with the declaration of the Button1Click method:
このことは、フォームやコンポーネントのイベントを処理するとき、Delphi が自動的に補完する、クラス内部のメソッド宣言についても同じだ。 TForm クラスの内部で宣言されたイベントハンドラは、将来その本体がそのユニットの実装部に書かれるだろう、というフォワード宣言だ。 前のソースコードから抜粋して、ButtonClick メソッドを宣言した例を示そう。
type
TForm1 = class(TForm)
ListBox1: TListBox;
Button1: TButton;
procedure Button1Click(Sender: TObject);
end;
Procedural Types
手続き型
Another unique feature of Object Pascal is the presence of procedural types. These are really an advanced language topic, which only a few Delphi programmers will use regularly. However, since we will discuss related topics in later chapters (specifically, method pointers, a technique heavily used by Delphi), it's worth a quick look at them here. If you are a novice programmer, you can skip this section for now, and come back to it when you feel ready.
Object Pascal のもう一つの特徴が、手続き型だ。 これは本当に高度な話題で、ごくわずかの Delphi プログラマしか使っていないだろう。 ただ、後の章で、これに関連した話題、たとえば Delphi が多用しているメソッドポインタなど、を取り上げるので、ちょっとだけ見てみよう。 もし君が本当の初心者なら、ここは読み飛ばして、いつか、必要になったら戻ってくればいい。
In Pascal, there is the concept of procedural type (which is similar to the C language concept of function pointer). The declaration of a procedural type indicates the list of parameters and, in the case of a function, the return type. For example, you can declare a new procedural type, with an Integer parameter passed by reference, with this code:
Pascal には、手続き型がある。 これは、C 言語の関数ポインタに似たものだ。 手続き型の宣言では、パラメータのリストを書き、関数なら、戻り値を書く。 たとえば、参照渡しで整数型のパラメータのある手続き型なら、
type
IntProc = procedure (var Num: Integer);
This procedural type is compatible with any routine having exactly the same parameters (or the same function signature, to use C jargon). Here is an example of a compatible routine:
この手続き型は、まったく同じパラメータを持つ、( C なら同じ関数シグネチャという ) どのようなルーチンとも互換性がある。 互換性のあるルーチンを挙げれば、
procedure DoubleTheValue (var Value: Integer);
begin
Value := Value * 2;
end;
Note: In the 16-bit version of Delphi, routines must be declared using the far directive in order to be used as actual values of a procedural type.
注 : 16 ビットの Delphi では、手続き型の実値として扱うためには、far 指令を付ける必要がある。
Procedural types can be used for two different purposes: you can declare variables of a procedural type or pass a procedural type (that is, a function pointer) as parameter to another routine. Given the preceding type and procedure declarations, you can write this code:
手続き型の用途は、ふたつある。 手続き型の変数を宣言できること、それに他のルーチンにパラメータとして、手続き型 ( すなわち関数ポインタ ) を渡せることだ。 上記の例から、こういう具合にコードが書ける。
var
IP: IntProc;
X: Integer;
begin
IP := DoubleTheValue;
X := 5;
IP (X);
end;
This code has the same effect as the following shorter version:
このコードは、こうすると、もっと短いコードで書ける。
var
X: Integer;
begin
X := 5;
DoubleTheValue (X);
end;
The first version is clearly more complex, so why should we use it? In some cases, being able to decide which function to call and actually calling it later on can be useful. It is possible to build a complex example showing this approach. However, I prefer to let you explore a fairly simple one, named ProcType. This example is more complex than those we have seen so far, to make the situation a little more realistic.
前のコードの方が、明らかに複雑で、なぜこれを使うのか、分からないだろう。 ある場合には、複数の関数を選べる状態にしておいて、必要に応じて関数を選べれば、都合の良い時がある。 こういう場合に備えて、複雑なアプローチも示した。 ただ、ProcType と命名した、明快なサンプルを見てもらう方が良いと思う。 このサンプルは、上記よりずっと複雑だが、現実的なものになっている。
Simply create a blank project and place two radio buttons and a push button, as shown in Figure 6.3. This example is based on two procedures. One procedure is used to double the value of the parameter. This procedure is similar to the version I've already shown in this section. A second procedure is used to triple the value of the parameter, and therefore is named TripleTheValue:
新しいプロジェクトを開始し、ラジオボタンをふたつ、ただのボタンをひとつ置く。 手続きはふたつだ。 ひとつは、パラメータの値を二倍する。 これは、上記の例と同じだ。 ふたつ目は、パラメータの値を三倍し、TripleTheValue という名前になっている。
Figure 6.3: The form of the ProcType example.
図 6.3 : ProcType のフォーム
procedure TripleTheValue (var Value: Integer);
begin
Value := Value * 3;
ShowMessage ('Value tripled: ' + IntToStr (Value)); // 三倍
end;
Both procedures display what is going on, to let us know that they have been called. This is a simple debugging feature you can use to test whether or when a certain portion of code is executed, instead of adding a breakpoint in it.
呼ばれた手続きが何をしているのか、表示するようになっている。 こうすると、ブレークポイントを設定しなくとも、今どの部分が実行されているか分かるので、デバッグがし易い。
Each time a user presses the Apply button, one of the two procedures is executed, depending on the status of the radio buttons. In fact, when you have two radio buttons in a form, only one of them can be selected at a time. This code could have been implemented by testing the value of the radio buttons inside the code for the OnClick event of the Apply button. To demonstrate the use of procedural types, I've instead used a longer but interesting approach. Each time a user clicks on one of the two radio buttons, one of the procedures is stored in a variable:
ユーザーが Apply ボタンを押すたびに、ラジオボタンでどちらが選択されているかによって、手続きのどちらかが実行される。 ラジオボタンは、どちらか一方だけが押せるようになっている。 元々このコードは、Apply ボタンの OnClick イベントハンドラで、ラジオボタンの値をテストするために書いたものだ。 手続き型の使い方を示すために、長くはなるが、ずっと面白いアプローチを取ってみた。 ユーザーがどちらかのラジオボタンを押すたびに、手続きが変数に納められる。
procedure TForm1.DoubleRadioButtonClick(Sender: TObject);
begin
IP := DoubleTheValue;
end;
When the user clicks on the push button, the procedure we have stored is executed:
ユーザーがボタンを押すと、納められた手続きが実行される。
procedure TForm1.ApplyButtonClick(Sender: TObject);
begin
IP (X);
end;
To allow three different functions to access the IP and X variables, we need to make them visible to the whole form; they cannot be declared locally (inside one of the methods). A solution to this problem is to place these variables inside the form declaration:
三つの異なった関数が、IP と X 変数にアクセスできるよう、変数をフォーム全体から見えるようにする必要がある。 どれかのメソッド内部に置いてしまうと、他からは見えなくなってしまう。 こうするには、フォームの宣言部で、変数をこう書く。
type
TForm1 = class(TForm)
...
private
{ Private declarations }
IP: IntProc;
X: Integer;
end;
We will see exactly what this means in the next chapter, but for the moment, you need to modify the code generated by Delphi for the class type as indicated above, and add the definition of the procedural type I've shown before. To initialize these two variables with suitable values, we can handle the OnCreate event of the form (select this event in the Object Inspector after you have activated the form, or simply double-click on the form). I suggest you refer to the listing to study the details of the source code of this example.
なぜこうようにするのか、次の章で説明するので、今は Delphi の生成した上記のコードを変えて、既に説明したように手続き型の定義をしておいてほしい。 この変数を初期化するには、フォームの OnCreate イベントハンドラを使う。 フォームを選択するか、フォームをダブルクリックして、オブジェクトインスペクタのイベントを選べば良い。 ソースコードの内容を、しっかりと勉強してほしい。
You can see a practical example of the use of procedural types in Chapter 9, in the section A Windows Callback Function.
手続き型の実践的な例は、第9章、Windows コールバック関数の部分で取り上げる。
Function Overloading
関数オーバーロード
The idea of overloading is simple: The compiler allows you to define two functions or procedures using the same name, provided that the parameters are different. By checking the parameters, in fact, the compiler can determine which of the versions of the routine you want to call.
オーバーロードの考え方は、明快なものだ。 同じ名前で、パラメータが違うふたつの関数、または手続きを使える。 コンパイラは、パラメータをチェックして、どちらのルーチンを使いたいのか、判断してくれる。
Consider this series of functions extracted from the Math unit of the VCL:
VCL の Math ユニットから抽出した関数を、例に挙げよう。
function Min (A,B: Integer): Integer; overload;
function Min (A,B: Int64): Int64; overload;
function Min (A,B: Single): Single; overload;
function Min (A,B: Double): Double; overload;
function Min (A,B: Extended): Extended; overload;
When you call Min (10, 20), the compiler easily determines that you're calling the first function of the group, so the return value will be an Integer.
Min ( 10, 20 ) と呼ぶと、コンパイラは、これは最初の関数を呼んでいるんだ、と判断し、 Integer 値を返してくる。
The basic rules are two:
基本ルールはふたつだ。
Each version of the routine must be followed by the overload keyword.
それぞれのルーチンには、必ず overload キーワードを付ける。
The differences must be in the number or type of the parameters, or both. The return type, instead, cannot be used to distinguish among two routines.
ルーチン間の差は、パラメータの数、または型、もしくは数と型の両方。 戻り値では、どのルーチンかは、判断できない。
Here are three overloaded versions of a ShowMsg procedure I've added to the OverDef example (an application demonstrating overloading and default parameters):
OverDef サンプルに、3 種類の ShowMsg 手続きを加えてみた。 このサンプルは、overload とデフォルト・パラメータを説明するものだ。
procedure ShowMsg (str: string); overload;
begin
MessageDlg (str, mtInformation, [mbOK], 0);
end;
procedure ShowMsg (FormatStr: string;
Params: array of const); overload;
begin
MessageDlg (Format (FormatStr, Params),
mtInformation, [mbOK], 0);
end;
procedure ShowMsg (I: Integer; Str: string); overload;
begin
ShowMsg (IntToStr (I) + ' ' + Str);
end;
The three functions show a message box with a string, after optionally formatting the string in different ways. Here are the three calls of the program:
この三つの関数は、メッセージボックスに string を表示するものだが、呼ばれる関数によって、文字列の書式が違う。 こういう具合だ。
ShowMsg ('Hello');
ShowMsg ('Total = %d.', [100]);
ShowMsg (10, 'MBytes');
What surprised me in a positive way is that Delphi's Code Parameters technology works very nicely with overloaded procedures and functions. As you type the open parenthesis after the routine name, all the available alternatives are listed. As you enter the parameters, Delphi uses their type to determine which of the alternatives are still available. In Figure 6.4 you can see that after starting to type a constant string Delphi shows only the compatible versions (omitting the version of the ShowMsg procedure that has an integer as first parameter).
いつも驚くんだが、Delphi のコード・パラメータ技術というのは、実にうまく overload 付きの手続きや関数を扱う。 ルーチン名の後、左括弧を入力すると、そこに入力できる候補者をリスト表示してくれる。 パラメータを入力すると、それ以外の候補リストを表示する。 図 6.4 に、定数 string を入力した後、Delphi が互換性のあるもののみ抽出しているところを示してある。 ShowMsg 手続きのリストから、最初のパラメータが Integer であるものを除外している。
Figure 6.4: The multiple alternatives offered by Code Parameters for overloaded routines are filtered according to the parameters already available.
図 6.4 : コード・パラメータで、overload ルーチンに該当するパラメータのみ抽出しているところ。
The fact that each version of an overloaded routine must be properly marked implies that you cannot overload an existing routine of the same unit that is not marked with the overload keyword. (The error message you get when you try is: "Previous declaration of '<name>' was not marked with the 'overload' directive.") However, you can overload a routine that was originally declared in a different unit. This is for compatibility with previous versions of Delphi, which allowed different units to reuse the same routine name. Notice, anyway, that this special case is not an extra feature of overloading, but an indication of the problems you can face.
重要なのは、overload キーワードの付いていない既存の、しかも同じユニットに存在するルーチンを overload してはいけない、ということだ。 こうしようとすると、Delphi は、「 < 名称 > の既定義には、overload 指令が付けられていません。」というエラーを表示する。 また、元々別のユニットに宣言されているルーチンも、オーバーロードできる。 これは、同じ名前のルーチンを別のユニットで再使用できた、という前のバージョンの Delphi との互換性を保つためだ。 ただし、この特殊なケースは、overload のもうひとつの特徴と言えるものではなく、問題を抱える元になることに、注意すること。
For example, you can add to a unit the following code:
たとえば、ユニットにこのようなコードを書くとする。
procedure MessageDlg (str: string); overload;
begin
Dialogs.MessageDlg (str, mtInformation, [mbOK], 0);
end;
This code doesn't really overload the original MessageDlg routine. In fact if you write:
このコードは、オリジナルの MessageDlg ルーチンをオーバーロードしたものではない。 つまり、こう書くと、
MessageDlg ('Hello');
you'll get a nice error message indicating that some of the parameters are missing. The only way to call the local version instead of the one of the VCL is to refer explicitly to the local unit, something that defeats the idea of overloading:
パラメータのいくつかが無くなっている、という「ステキな」エラーメッセージがでる。 VCL で定義されているオリジナルではなく、ローカルで再定義されているそのルーチンを呼び出すには、オーバーロードの考え方を、まったく無駄にするようにコードを書かなくてはならない。
OverDefF.MessageDlg ('Hello');
Default Parameters
デフォルト・パラメータ
A related new feature of Delphi 4 is that you can give a default value for the parameter of a function, and you can call the function with or without the parameter. Let me show an example. We can define the following encapsulation of the MessageBox method of the Application global object, which uses PChar instead of strings, providing two default parameters:
Delphi 4 の新しい機能のひとつが、関数のパラメータにデフォルト・パラメータを与えることができ、パラメータ付き、もしくはパラメータ無しで関数を呼び出せるようになったことだ。 例を挙げよう。 String の代わりに PChar を渡せるよう、デフォルト・パラメータをふたつ持った、アプリケーションのグローバル・オブジェクト用の MessageBox メソッドをカプセル化してみよう。
procedure MessBox (Msg: string;
Caption: string = 'Warning';
Flags: LongInt = mb_OK or mb_IconHand);
begin
Application.MessageBox (PChar (Msg),
PChar (Caption), Flags);
end;
With this definition, we can call the procedure in each of the following ways:
この定義で、手続きを下記のように呼び出すことができる。
MessBox ('Something wrong here!');
MessBox ('Something wrong here!', 'Attention');
MessBox ('Hello', 'Message', mb_OK);
In Figure 6.5 you can see that Delphi's Code Parameters properly use a different style to indicate the parameters that have a default value, so you can easily determine which parameters can be omitted.
図 6.5 では、Delphi のコード・パラメータが、デフォルト値を持つパラメータ一覧を表示しているところ。 どのパラメータを除外するべきか、すぐに分かる。
Figure 6.5: Delphi's Code Parameters mark out with square brackets the parameters that have default values; you can omit these in the call.
図 6.5 : Delphi のコード・パラメータが、デフォルト値を持つパラメータを角括弧付きで表示しているところ。 呼び出すときには、この一覧のパラメータを除外する。
Notice that Delphi doesn't generate any special code to support default parameters; nor does it create multiple copies of the routines. The missing parameters are simply added by the compiler to the calling code.
Delphi が、デフォルト・パラメータについては、なんら特殊なコードを生成しないことに注意。 ルーチンの複数コピーも行わない。 足りないパラメータは、コンパイラがコードを呼び出すときに追加するだけだ。
There is one important restriction affecting the use of default parameters: You cannot "skip" parameters. For example, you can't pass the third parameter to the function after omitting the second one:
デフォルト・パラメータの利用には、ひとつ重要な制限がある。 パラメータをスキップできない、ということだ。 たとえば、2番目のパラメータを除外した後、3番目のパラメータを関数に渡すことはできない。
MessBox ('Hello', mb_OK); // error エラー
This is the main rule for default parameters: In a call, you can only omit parameters starting from the last one. In other words, if you omit a parameter you must omit also the following ones.
デフォルト・パラメータの主要なルールというのは、呼び出し時に後ろ側のパラメータからなら順に除外できる、ということだ。 言い換えれば、どれかパラメータを除外するときは、その後ろのパラメータも除外しなければならない。
There are a few other rules for default parameters as well:
その他のデフォルト・パラメータのルールと言えば、
Parameters with default values must be at the end of the parameters list.
デフォルト値を持つパラメータは、パラメータのリストの一番最後に置く。
Default values must be constants. Obviously, this limits the types you can use with default parameters. For example, a dynamic array or an interface type cannot have a default parameter other than nil; records cannot be used at all.
デフォルト値は、定数でないといけない。 だから、デフォルト・パラメータでは、利用できる型が制限される。 たとえば、動的配列やインターフェイス型は、nil 以外のデフォルト・パラメータを持てない。 レコードは nil もだめだ。
Default parameters must be passed by value or as const. A reference (var) parameter cannot have a default value.
デフォルト・パラメータは、値渡し、または定数でないといけない。 参照 ( var ) パラメータは、デフォルト値にはなれない。
Using default parameters and overloading at the same time can cause quite a few problems, as the two features might conflict. For example, if I add to the previous example the following new version of the ShowMsg procedure:
デフォルト・パラメータとオーバーロードを同時に使うのは、問題を引き起こすことがある。 両方の特徴が、ぶつかり合ってしまう。 たとえば、前の例で、新しい ShowMsg 手続きを加えたとする。
procedure ShowMsg (Str: string; I: Integer = 0); overload;
begin
MessageDlg (Str + ': ' + IntToStr (I),
mtInformation, [mbOK], 0);
end;
then the compiler won't complain-this is a legal definition. However, the call:
すると、コンパイラは、不正だと文句は言わないが、この呼び出しでは、
ShowMsg ('Hello');
is flagged by the compiler as Ambiguous overloaded call to 'ShowMsg'. Notice that this error shows up in a line of code that compiled correctly before the new overloaded definition. In practice, we have no way to call the ShowMsg procedure with one string parameter, as the compiler doesn't know whether we want to call the version with only the string parameter or the one with the string parameter and the integer parameter with a default value. When it has a similar doubt, the compiler stops and asks the programmer to state his or her intentions more clearly.
コンパイラは、ShowMsg へのあやふやなオーバーロード呼び出しだ、と判断する。 このエラーが、新しくオーバーロード定義をするまでは、問題なくコンパイルされるコードで起こることに注意。 実際、string パラメータをひとつ持つ ShowMsg 手続きを呼び出す方法はもう無い。 コンパイラは、string パラメータがひとつだけのものか、string パラメータとデフォルト値がある integer パラメータを持つ手続きなのか、判断できない。 どちらか決められなければ、コンパイラは、もっとはっきり指示してくれ、とプログラマに要求するだけだ。
Conclusion
結論
Writing procedure and functions is a key element of programming, although in Delphi you'll tend to write methods -- procedures and functions connected with classes and objects.
手続きと関数を書く、というのは、プログラミングの核心だ。 もちろん Delphi では、クラスやオブジェクトにむすびつけられた手続きや関数、すなわちメソッドを書くことになる。
Instead of moving on to object-oriented features, however, the next few chapters give you some details on other Pascal programming elements, starting with strings.
オブジェクト指向に向かう前に、次のいくつかの章では、string など、Pascal プログラミングの要素について紹介しよう。
Next Chapter: Handling Strings
次章 : String を扱う
Marco Cantù's
Essential Pascal Chapter 7
Handling Strings
第7章
String を扱う
String handling in Delphi is quite simple, but behind the scenes the situation is quite complex. Pascal has a traditional way of handling strings, Windows has its own way, borrowed from the C language, and 32-bit versions of Delphi include a powerful long string data type, which is the default string type in Delphi.
Delphi で String を扱うのは簡単だが、その裏側では非常に複雑な操作がされている。 Pascal は、自分の String 操作を持っており、Windows も C 言語から借りてきた独自のやり方を持っている。 32 ビット Delphi では、Delphi のデフォルトである、強力な長い文字列型を持っている。
Types of Strings
String の種類
In Borland's Turbo Pascal and in 16-bit Delphi, the typical string type is a sequence of characters with a length byte at the beginning, indicating the current size of the string. Because the length is expressed by a single byte, it cannot exceed 255 characters, a very low value that creates many problems for string manipulation. Each string is defined with a fixed size (which by default is the maximum, 255), although you can declare shorter strings to save memory space.
Borland の TurboPascal と 16 ビットの Delphi では、典型的な文字列型というのは、現在の文字列のサイズを示す長さバイトを先頭にくっつけた、一連の文字になっている。 長さはシングル・バイトで表されるので、文字列長は 255 以上にはなれず、この制限が文字列操作の難問になっていた。 ただ文字列の長さが最大 255 と、制限はあるが、短い文字列を使うと、メモリの消費を押さえることができる。
A string type is similar to an array type. In fact, a string is almost an array of characters. This is demonstrated by the fact that you can access a specific string character using the [] notation.
文字列型は、配列と似ている。 実のところ、文字列は、文字の配列とほぼ同じだ。 [] の中に数値をいれると、その箇所の文字を取り出せるのを見ても、分かる。
To overcome the limits of traditional Pascal strings, the 32-bit versions of Delphi support long strings. There are actually three string types:
伝統的な Pascal 文字列の制限を克服するため、32 ビット Delphi では、長い文字列もサポートするようになった。 実際には、3種類の文字列を用意している。
The ShortString type corresponds to the typical Pascal strings, as described before. These strings have a limit of 255 characters and correspond to the strings in the 16-bit version of Delphi. Each element of a short string is of type ANSIChar (the standard character type).
ShortString 型は、伝統的な Pascal 文字列と同じだ。 この型は、文字長が 255 まで、という制限があり、16 ビット Delphi は、この型を使っている。 この型の文字要素は、すべて ANSIChar ( 標準文字型 ) になっている。
The ANSIString type corresponds to the new variable-length long strings. These strings are allocated dynamically, are reference counted, and use a copy-on-write technique. The size of these strings is almost unlimited (they can store up to two billion characters!). They are also based on the ANSIChar type.
ANSIString 型は、新しい可変長の長い文字列と同じだ。 これらの文字列は、メモリに動的に割り当てられ、参照カウントされ、書き込み時コピーという技術が使われている。 文字列長の制限も、ほぼ無い。 20億文字まで格納できる。 また、ANSIChar をベースにしている。
The WideString type is similar to the ANSIString type but is based on the WideChar type-it stores Unicode characters.
WideString 型は、ANSIString 型と似ているが、Unicode 文字を格納する、WideChar 型をベースにしている。
Using Long Strings
長い文字列を使う
If you simply use the string data type, you get either short strings or ANSI strings, depending on the value of the $H compiler directive. $H+ (the default) stands for long strings (the ANSIString type), which is what is used by the components of the Delphi library.
ただ単に文字列データ型を使うのなら、短い文字列か ANSI 文字列を使うといい。 これは、コンパイラ指令の $H で指定する。 $H+ ( これがデフォルトだ ) は、長い文字列 (ANSIString 型 ) を示し、Delphi ライブラリのコンポーネントは、これを使っている。
Delphi long strings are based on a reference-counting mechanism, which keeps track of how many string variables are referring to the same string in memory. This reference-counting is used also to free the memory when a string isn't used anymore-that is, when the reference count reaches zero.
Delphi の長い文字列は、参照カウントを採用している。 これは、何回、文字列変数がメモリに格納された同じ文字列を参照しているか、を見ているものだ。 参照カウントは、メモリの解放にも使われている。 メモリに格納されている文字列を変数がまったく参照しなくなったら、つまり参照カウントがゼロになったら、その文字列をメモリから消去する。
If you want to increase the size of a string in memory but there is something else in the adjacent memory, then the string cannot grow in the same memory location, and a full copy of the string must therefore be made in another location. When this situation occurs, Delphi's run-time support reallocates the string for you in a completely transparent way. You simply set the maximum size of the string with the SetLength procedure, effectively allocating the required amount of memory:
メモリにある文字列のサイズを増やしたいときに、すぐ隣のメモリが詰まっている場合、その同じメモリ位置ではサイズを増やせない。 つまり、どこか別のメモリ位置を探さないといけない。 こういう場合、Delphi は実行時に、この操作を意識させないで、メモリへの再配置をやってくれる。 プログラマがやることは、ただ SetLength 手続きを使って文字列の最大サイズを指定することだけだ。
SetLength (String1, 200);
The SetLength procedure performs a memory request, not an actual memory allocation. It reserves the required memory space for future use, without actually using the memory. This technique is based on a feature of the Windows operating systems and is used by Delphi for all dynamic memory allocations. For example, when you request a very large array, its memory is reserved but not allocated.
SetLength 手続きは、メモリに対する要求をするだけで、実際のメモリへの配置は行わない。 将来使えるようにメモリ空間を予約するだけで、実際にメモリを使うのではない。 この技法は、Windows オペレーティングシステムの機能を利用しており、Delphi がこの機能を使って動的なメモリ配置を行う。 つまり、非常に大きなメモリ空間を要求しても、その空間は予約されるだけで、使われるのではない。
Setting the length of a string is seldom necessary. The only case in which you must allocate memory for the long string using SetLength is when you have to pass the string as a parameter to an API function (after the proper typecast), as I'll show you shortly.
文字列の長さを指定するのは、ほとんど必要ない。 唯一の場合が、API 関数に ( 正しい型キャストの後で ) パラメータとして文字列を渡す時だけだ。 これについては、後から実例を紹介する。
Looking at Strings in Memory
メモリ内での文字列を見てみよう
To help you better understand the details of memory management for strings, I've written the simple StrRef example. In this program I declare two global strings: Str1 and Str2. When the first of the two buttons is pressed, the program assigns a constant string to the first of the two variables and then assigns the second variable to the first:
文字列がどのようにメモリ内部で操作されるのか、良く分かってもらうために、StrRef サンプルを書いてみた。 このプログラムでは、グローバル文字列をふたつ、Str1 と Str2 を使う。 ふたつのボタンの最初のボタンを押したとき、プログラムは、ふたつの変数の最初のものに、定数になっている文字列を渡す。 その後、2番目の変数に最初のものを割り当てる。
Str1 := 'Hello';
Str2 := Str1;
Besides working on the strings, the program shows their internal status in a list box, using the following StringStatus function:
文字列を操作するだけでなく、プログラムは、リストボックスに、下記の StringStatus 関数を使って状態を示す。
function StringStatus (const Str: string): string;
begin
Result := 'Address: ' + IntToStr (Integer (Str)) + // アドレス
', Length: ' + IntToStr (Length (Str)) + // 文字列長
', References: ' + IntToStr (PInteger (Integer (Str) - 8)^) + // 参照カウント
', Value: ' + Str; // 値
end;
It is vital in the StringStatus function to pass the string parameter as a const parameter. Passing this parameter by copying will cause the side effect of having one extra reference to the string while the function is being executed. By contrast, passing the parameter via a reference (var) or constant (const) parameter doesn't imply a further reference to the string. In this case I've used a const parameter, as the function is not supposed to modify the string.
この StringStatus 関数では、文字列パラメータを定数パラメータとして渡しているのが、ポイントだ。 コピーしてパラメータを渡す、ということで、プログラムの実行時に副次的に文字列への参照カウントをインクリメントしている。 その反対に、パラメータを参照 (var) や定数 (const) として渡してしまうと、文字列への参照は行われなくなる。 上記では、文字列の内容を変える必要がないので、定数パラメータを使っている。
To obtain the memory address of the string (useful to determine its actual identity and to see when two different strings refer to the same memory area), I've simply made a hard-coded typecast from the string type to the Integer type. Strings are references-in practice, they're pointers: Their value holds the actual memory location of the string.
文字列のメモリ・アドレスを取得する ( 実際にどのようにメモリ配置しているのか見えるし、また同じメモリ領域を、ふたつの変数が参照している様子が見える ) のに、文字列型から整数型に変換する、ハードコードされた型キャストを使った。 文字列は、実際には参照で、ポインタだ。 保持している値は、文字列が実際に格納されているメモリ領域を指している。
To extract the reference count, I've based the code on the little-known fact that the length and reference count are actually stored in the string, before the actual text and before the position the string variable points to. The (negative) offset is -4 for the length of the string (a value you can extract more easily using the Length function) and -8 for the reference count.
参照カウントを取得するのに、ほとんど知られていない事実、文字長と参照カウントは、文字列に含められていて、実テキストの前の、文字列変数が指している場所の、そのまた前に格納されている。 ( 負数 ) オフセットの−4に、文字列長 ( Length 関数を使えば、もっと簡単に取り出せる ) があり、−8の位置に参照カウントが納められている。
Keep in mind that this internal information about offsets might change in future versions of Delphi; there is also no guarantee that similar undocumented features will be maintained in the future.
ただし、この内部情報を表すオフセットは、将来の Delphi では、変更される可能性がある。 同様に、公開されていない機能が、将来もそのまま、という保証は無いね。
By running this example, you should get two strings with the same content, the same memory location, and a reference count of 2, as shown in the upper part of the list box of Figure 2.1. Now if you change the value of one of the two strings (it doesn't matter which one), the memory location of the updated string will change. This is the effect of the copy-on-write technique.
このサンプルを動かしてみると、図 2.1 のリストボックスの上半分にあるように、同じ内容、同じメモリ領域で、参照カウントが2の文字列を取得できる。 このふたつの文字列の内、どちらかを変えると、アップデートされた文字列のメモリ領域が変わる。 これが「書き込み時コピー」という技術だ。
Figure 7.1: The StrRef example shows the internal status of two strings, including the current reference count.
図 7.1 : ふたつの文字列の内部状態と、現在の参照カウントを示す StrRef サンプル
We can actually produce this effect, shown in the second part of the list box of Figure 7.1, by writing the following code for the OnClick event handler of the second button:
2番目のボタンの OnClick イベントハンドラに、下記のコードを書けば、図 7.1 のリストボックスの2番目に示した結果を得ることができる。
procedure TFormStrRef.BtnChangeClick(Sender: TObject);
begin
Str1 [2] := 'a';
ListBox1.Items.Add ('Str1 [2] := ''a''');
ListBox1.Items.Add ('Str1 - ' + StringStatus (Str1));
ListBox1.Items.Add ('Str2 - ' + StringStatus (Str2));
end;
Notice that the code of the BtnChangeClick method can be executed only after the BtnAssignClick method. To enforce this, the program starts with the second button disabled (its Enabled property is set to False); it enables the button at the end of the first method. You can freely extend this example and use the StringStatus function to explore the behavior of long strings in many other circumstances.
BtnChangeClick メソッドは、BtnAssignClick メソッドの後でしか実行できないことに注意! この動作を確実にするため、起動時には第2ボタンは Enabled := False として、操作できないようになっている。 最初のメソッドの実行後、このボタンが操作できるようになる。 このサンプルを自由に拡張して、いろいろな状況で長い文字列がどのようになるのか、StringStatus 関数を使ってみるといい。
Delphi Strings and Windows PChars
Delphi 文字列と Windows PChar
Another important point in favor of using long strings is that they are null-terminated. This means that they are fully compatible with the C language null-terminated strings used by Windows. A null-terminated string is a sequence of characters followed by a byte that is set to zero (or null). This can be expressed in Delphi using a zero-based array of characters, the data type typically used to implement strings in the C language. This is the reason null-terminated character arrays are so common in the Windows API functions (which are based on the C language). Since Pascal's long strings are fully compatible with C null-terminated strings, you can simply use long strings and cast them to PChar when you need to pass a string to a Windows API function.
長い文字列については、ヌルで終わる、というのもポイントのひとつだ。 つまり、Windows の使っている、C 言語のヌル文字列と完全に互換性がある、ということだ。 ヌル文字列というのは、一連の文字の終端バイトが、ゼロ ( またはヌル ) になっているものだ。 これは Delphi では、文字のゼロ・ベースの配列、という形で表せる、これが C 言語の文字列に使われているデータ型と合致する。 C 言語で書かれた Windows API 関数では、このヌル文字列が一般的になっている。 Pascal の長い文字列は、C のヌル文字列とまったく互換するので、Windows API 関数に文字列を渡す時には、長い文字列を使い、それを PChar キャストしてやるだけでいい。
For example, to copy the caption of a form into a PChar string (using the API function GetWindowText) and then copy it into the Caption of the button, you can write the following code:
たとえば API 関数の GetWindwText を使って、フォームの Caption を PChar 文字列にコピーし、ボタンの Caption にコピーするのなら、
procedure TForm1.Button1Click (Sender: TObject);
var
S1: String;
begin
SetLength (S1, 100);
GetWindowText (Handle, PChar (S1), Length (S1));
Button1.Caption := S1;
end;
You can find this code in the LongStr example. Note that if you write this code but fail to allocate the memory for the string with SetLength, the program will probably crash. If you are using a PChar to pass a value (and not to receive one as in the code above), the code is even simpler, because there is no need to define a temporary string and initialize it. The following line of code passes the Caption property of a label as a parameter to an API function, simply by typecasting it to PChar:
このコードは、LongStr サンプルにある。 ただし、このコードを実行したときに、SetLength 部分で文字列を格納するメモリ確保に失敗したとき、プログラムはクラッシュする可能性がある。 もしも、PChar を使って、上記のように値を受け取るのでなく、値を渡すだけであれば、コードはもっと簡単にできる。 というのも、一時的な文字列を定義し、それを初期化する必要が無いからだ。 次のコードは、ラベルの Caption を API 関数に渡すもので、PChar に型キャストするだけだ。
SetWindowText (Handle, PChar (Label1.Caption));
When you need to cast a WideString to a Windows-compatible type, you have to use PWideChar instead of PChar for the conversion. Wide strings are often used for OLE and COM programs.
WideString を Windows 互換の型にキャストするときは、PChar の代わりに、PWideChar を使えばいい。 Wide 文字列は、OLE やCOM プログラミングでよく使われている。
Having presented the nice picture, now I want to focus on the pitfalls. There are some problems that might arise when you convert a long string into a PChar. Essentially, the underlying problem is that after this conversion, you become responsible for the string and its contents, and Delphi won't help you anymore. Consider the following limited change to the first program code fragment above, Button1Click:
長所についての紹介は終わったので、さて、欠点についても取り上げよう。 長い文字列を PChar に変換すると、問題が発生する場合がある。 はっきり言ってしまえば、変換したら、その文字列については、君が責任を負わないといけない。 Delphi は、一切助けてくれない。 次の例では、Button1Click を一部変更してある。
procedure TForm1.Button2Click(Sender: TObject);
var
S1: String;
begin
SetLength (S1, 100);
GetWindowText (Handle, PChar (S1), Length (S1));
S1 := S1 + ' is the title'; // this won't work これは無効になる
Button1.Caption := S1;
end;
This program compiles, but when you run it, you are in for a surprise: The Caption of the button will have the original text of the window title, without the text of the constant string you have added to it. The problem is that when Windows writes to the string (within the GetWindowText API call), it doesn't set the length of the long Pascal string properly. Delphi still can use this string for output and can figure out when it ends by looking for the null terminator, but if you append further characters after the null terminator, they will be skipped altogether.
このプログラムはコンパイルできるが、起動させると、ボタンの Caption は、元のウィンドウのタイトルだけで、付け加えた文字列は反映されない。 つまり、Windows が GetWindowTex API 関数で文字列に書き込む際、長い Pascal 文字列の正しい長さを無視する、ということだ。 Delphi は、この文字列をそのまま扱ってくれるし、またヌル終端文字がどこにあるのか、判断してくれるが、ヌル終端文字以降に付け加えた文字は、無視してしまうのだ。
How can we fix this problem? The solution is to tell the system to convert the string returned by the GetWindowText API call back to a Pascal string. However, if you write the following code:
どうしたらいいんだろう? 解決法としては、システムに GetWindowText API で返された文字列を、Pascal 文字列に変換するように、教えてやることだ。 しかし、こう書くと、
S1 := String (S1);
the system will ignore it, because converting a data type back into itself is a useless operation. To obtain the proper long Pascal string, you need to recast the string to a PChar and let Delphi convert it back again properly to a string:
あるデータ型を、元のデータ型に変換するのは意味がないので、システムは無視してしまう。 正しい Pascal 文字列を得るには、文字列を PChar 変換し、Delphi に正しい文字列への再変換をさせることだ。
S1 := String (PChar (S1));
Actually, you can skip the string conversion, because PChar-to-string conversions are automatic in Delphi. Here is the final code:
実際には、この文字列変換はスキップできる。 Delphi では、PChar から文字列への変換は自動的に行われるからだ。 上記のコードは、正しくは、
procedure TForm1.Button3Click(Sender: TObject);
var
S1: String;
begin
SetLength (S1, 100);
GetWindowText (Handle, PChar (S1), Length (S1));
S1 := String (PChar (S1));
S1 := S1 + ' is the title';
Button3.Caption := S1;
end;
An alternative is to reset the length of the Delphi string, using the length of the PChar string, by writing:
または、PChar 文字列の長さを利用して、Delphi 文字列の文字列長をリセットして、
SetLength (S1, StrLen (PChar (S1)));
You can find three versions of this code in the LongStr example, which has three buttons to execute them. However, if you just need to access the title of a form, you can simply use the Caption property of the form object itself. There is no need to write all this confusing code, which was intended only to demonstrate the string conversion problems. There are practical cases when you need to call Windows API functions, and then you have to consider this complex situation.
LongStr サンプルでは、この3種のバージョン用にボタンを用意してあり、それぞれ試すことができる。 しかし、フォームのタイトルにアクセスするだけなら、フォーム自体の Caption プロパティを使った方がいい。 そうすれば、こんな複雑なコードを書かなくていい。 このコードは、文字列変換の問題を示すためだけに作ったものだ。 ただ、Windows API 関数を使う必要が有るときは、時にはこうした複雑な操作も必要になる。
Formatting Strings
書式付き文字列
Using the plus (+) operator and some of the conversion functions (such as IntToStr) you can indeed build complex strings out of existing values. However, there is a different approach to formatting numbers, currency values, and other strings into a final string. You can use the powerful Format function or one of its companion functions.
プラス (+) オペレータと変換関数 ( IntToStr など ) を使えば、既存の値から複雑な文字列を作ることができる。 また、数値の書式化、金額値やその他の文字列を、目指す文字列に変換するには、別の方法がある。 書式関数、またはコンポーネント関数が、それだ。
The Format function requires as parameters a string with the basic text and some placeholders (usually marked by the % symbol) and an array of values, one for each placeholder. For example, to format two numbers into a string you can write:
書式関数は、基本文字と指定子 ( 通常は % シンボルで表示 ) からなる文字列と、その指定子に対応する値の配列をパラメータに取る。 たとえば、ふたつの数値を文字列に変換するには、
Format ('First %d, Second %d', [n1, n2]);
where n1 and n2 are two Integer values. The first placeholder is replaced by the first value, the second matches the second, and so on. If the output type of the placeholder (indicated by the letter after the % symbol) doesn't match the type of the corresponding parameter, a runtime error occurs. Having no compile-time type checking is actually the biggest drawback of using the Format function.
ここでは、n1 と n2 は、それぞれ整数値だ。 最初の指定子が最初の数値に適用され、2番目のものが2番目の数値に適用される。 使われている指定子 ( % シンボルの後ろの文字 ) とパラメータが合わないときは、実行時エラーになる。 コンパイル時にはエラーにならないのが、この Format 関数の一番の欠点だ。
The Format function uses an open-array parameter (a parameter that can have an arbitrary number of values), something I'll discuss toward the end of this chapter. For the moment, though, notice only the array-like syntax of the list of values passed as the second parameter.
Format 関数はオープン配列パラメータ ( 任意の数の値を持てるパラメータ ) を使っており、これについてはこの章の終わり部分で、少し取り上げる。 今は、第2パラメータには、配列に似た構文のリストが渡されることに注意するだけでいい。
Besides using %d, you can use one of many other placeholders defined by this function and briefly listed in Table 7.1. These placeholders provide a default output for the given data type. However, you can use further format specifiers to alter the default output. A width specifier, for example, determines a fixed number of characters in the output, while a precision specifier indicates the number of decimal digits. For example,
%d 以外にも、この関数には、たくさんの指定子が使える。 これを表 7.1. に示す。 この指定子は、与えられたデータ型のデフォルト出力を示している。 もちろん、他の指定子を使えば、デフォルト出力を変えることができる。 たとえば、桁指定子は、文字の桁数を示し、精度指定子は、小数点以下の値を示す。 たとえば、
Format ('%8d', [n1]);
converts the number n1 into an eight-character string, right-aligning the text (use the minus (-) symbol to specify left-justification) filling it with white spaces.
これは、n1 を8桁の右詰め文字列 ( (-) シンボルは左詰め ) で、文字の無い桁は空白が埋められる。
Table 7.1: Type Specifiers for the Format Function
表 7.1. : Format 関数用型指定子
TYPE SPECIFIER DESCRIPTION
型指定子 説明
d (decimal) The corresponding integer value is converted to a string of decimal digits.
d ( 十進法 ) 整数値を、指定桁数の文字列に変換。
x (hexadecimal) The corresponding integer value is converted to a string of hexadecimal digits.
x ( 十六進法 ) 十進整数を、十六進文字列に変換。
p (pointer) The corresponding pointer value is converted to a string expressed with hexadecimal digits.
p ( ポインタ ) ポインタ値を、十六進数表現の文字列に変換。
s (string) The corresponding string, character, or PChar value is copied to the output string.
s ( 文字列 ) 文字列、文字、PChar 値を、出力文字列にコピー。
e (exponential) The corresponding floating-point value is converted to a string based on exponential notation.
e ( 指数 ) 小数点値を、指数表現の文字列に変換。
f (floating point) The corresponding floating-point value is converted to a string based on floating point notation.
f ( 小数点 ) 小数点値を、小数点表現の文字列に変換。
g (general) The corresponding floating-point value is converted to the shortest possible decimal string using either floating-point or exponential notation.
g ( 一般 ) 小数点値を、小数点表現、または指数表現で、小数点以下を最小にして、文字列に変換。
n (number) The corresponding floating-point value is converted to a floating-point string but also uses thousands separators.
n ( 数値 ) 小数点値を、小数点表現の文字列にし、しかも千ごとに区切り文字を追加した文字列に変換。
m (money) The corresponding floating-point value is converted to a string representing a currency amount. The conversion is based on regional settings-see the Delphi Help file under Currency and date/time formatting variables.
m ( 金額 ) 小数点値を、金額を表す文字列に変換。 この変換は、それぞれの地域の設定に従う。 Delphi の、金額、日付 / 時間フォーマット変数のヘルプを参照のこと。
The best way to see examples of these conversions is to experiment with format strings yourself. To make this easier I've written the FmtTest program, which allows a user to provide formatting strings for integer and floating-point numbers. As you can see in Figure 7.2, this program displays a form divided into two parts. The left part is for Integer numbers, the right part for floating-point numbers.
この変換を理解しようと思ったら、自分でいろんなフォーマットを試してみるのが一番いい。 FmtTest サンプルを書いてみた。 これを使うと、整数や小数点値の変換が簡単にできる。 図 7.2 にあるように、このプログラムでは、フォームがふたつに分かれている。 左側は整数、右側が小数点値だ。
Each part has a first edit box with the numeric value you want to format to a string. Below the first edit box there is a button to perform the formatting operation and show the result in a message box. Then comes another edit box, where you can type a format string. As an alternative you can simply click on one of the lines of the ListBox component, below, to select a predefined formatting string. Every time you type a new formatting string, it is added to the corresponding list box (note that by closing the program you lose these new items).
どちらの部分にも、自由に数値を入れられる Edit ボックスを用意してある。 この Edit ボックスの下には、変換を行い、メッセージボックスにその結果を表示させるボタンがある。 別の Edit ボックスには、フォーマット文字列を入力する。 下の ListBox には、フォーマット文字列をあらかじめ入れてあるので、そこから選んでもいい。 新しいフォーマット文字列を入力すると、リストボックスに追加される。 ただし、プログラムを閉じると、新しく入力したフォーマット文字列はすべて失われる。
Figure 7.2: The output of a floating-point value from the FmtTest program
図 7.2 : FmtTest プログラムでの変換
The code of this example simply uses the text of the various controls to produce its output. This is one of the three methods connected with the Show buttons:
このプログラムでは、様々なコントロールのテキストを、変換入力に使っている。 Show ボタンに付いているメソッドのひとつを見れば、
procedure TFormFmtTest.BtnIntClick(Sender: TObject);
begin
ShowMessage (Format (EditFmtInt.Text,
[StrToInt (EditInt.Text)]));
// if the item is not there, add it
// もしその項目が無ければ追加
if ListBoxInt.Items.IndexOf (EditFmtInt.Text) < 0 then
ListBoxInt.Items.Add (EditFmtInt.Text);
end;
The code basically does the formatting operation using the text of the EditFmtInt edit box and the value of the EditInt control. If the format string is not already in the list box, it is then added to it. If the user instead clicks on an item in the list box, the code moves that value to the edit box:
このコードでは、基本的に EditFmtInt 名の Edit ボックスと、EditInt コントロールのテキストを使って、フォーマットする。 もしもフォーマット文字列がリストボックスに無ければ、追加する。 もしユーザーがリストボックスの項目をクリックしたら、その項目を Edit ボックスに表示する。
procedure TFormFmtTest.ListBoxIntClick(Sender: TObject);
begin
EditFmtInt.Text := ListBoxInt.Items [
ListBoxInt.ItemIndex];
end;
Conclusion
結論
Strings a certainly a very common data type. Although you can safely use them in most cases without understanding how they work, this chapter should have made clear the exact behavior of strings, making it possible for you to use all the power of this data type.
文字列は、非常に一般的なデータ型だ。 どのように処理されているのか、分からなくても安全に文字列を使うことはできるが、この章では、文字列の内部処理について明らかにし、このデータ型の持っているパワーすべてを、使いこなせるようにしたつもりだ。
Strings are handled in memory in a special dynamic way, as happens with dynamic arrays. This is the topic of the next chapter.
文字列は、動的配列のように、特殊な動的手法を使って、メモリ操作される。 このメモリ操作が、次章の話題になる。
Next Chapter: Memory
次章 : メモリ
© Copyright Marco Cantù, Wintech Italia Srl 1995-2000