Pascal/Delphi dynamic array as argument - arrays

I'd like to do something like this:
procedure show(a : Array of Integer);
var
i : integer;
begin
for i in a do
writeln(i);
end;
begin
show((1, 2));
show((3, 2, 5));
end.
but this is the closest I got
Program arrayParameter(output);
type
TMyArray = Array[0..2] of Integer;
var
arr : TMyArray = (1, 2, 3);
procedure show(a : TMyArray);
var
i : integer;
begin
for i in a do
writeln(i);
end;
begin
show(arr);
end.
So do I have to declare a different array for each time I want to call the function? Please provide a working example.

If you do
procedure show(a: array of Integer);
var
i: Integer;
begin
for i in a do
Writeln(i);
end;
then you may write
show([1, 2, 3, 4]);
This kind of array parameter is called an open array parameter. If a function has an open array parameter, you can give it both dynamic and static arrays, in addition to these "literal arrays". So, given our show procedure, we may also do
var
DynArr: TArray<Integer>; // = array of Integer
StaticArr: array[0..2] of Integer;
begin
show(DynArr);
show(StaticArr);
end;
Just for comparison: If you instead do
procedure show(a: TArray<Integer>);
or has a
type
TDynIntArray = array of Integer;
and do
procedure show(a: TDynIntArray);
then show will only accept such dynamic arrays.

Related

Delphi: How to reference an array from within a class

I work with arrays and have tested the functionality within the context of a class, like:
Ttest = class
values : array of integer;
procedure doStuff;
end;
The methods, like doStuff all operate on the values array without the need to pass the array as a parameter. This suits me and it is fast.
Now I want to use this class to work with an external array, like Ttest.create(myValues) In the constructor I could copy myValues to the internal values but that would be quite a waste and moreover, at the end the copy would have to be reversed to pass the updated values back.
My question is how can I extend this class so that it can efficiently work with an external array. In pseudo code like this:
constructor create(var myValues : array of integer);
begin
address of values := address of myValues;
doSTuff;
end;
Lesson 1
In Delphi, dynamic arrays are reference types. A variable of dynamic array type contains only a pointer to the actual dynamic array heap object, and in an assignment,
A := B
where A and B are dynamic arrays of the same type, the dynamic array heap object isn't copied. The only thing that happens is that A and B will point to the same dynamic array heap object (that is, the 32- or 64-bit B pointer is copied to A) and that the reference count of the heap object is increased by 1.
Lesson 2
When you write
constructor Create(var AValues: array of Integer);
you need to realise that this, despite the appearance, isn't a dynamic array parameter, but an open array parameter.
If you explicitly want a dynamic array parameter, you need to use such a type explicitly:
constructor Create(AValues: TArray<Integer>);
By definition, TArray<Integer> = array of Integer is a dynamic array of Integers.
Please note that the language only has two types of arrays – static and dynamic; the open array concept is only about function parameters.
If you want to work with dynamic arrays, taking advantage of their nature as reference types, I would suggest you use dynamic array parameters. Then the only thing that is passed to the function (the constructor in this case) is the pointer to the heap object. And the heap object's reference count is increased, of course.
Lesson 3 – An example
var
Arr: TArray<Integer>;
procedure Test(A: TArray<Integer>);
var
i: Integer;
begin
for i := Low(A) to High(A) do
A[i] := 2*A[i];
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
SetLength(Arr, 10);
for i := 0 to High(Arr) do
Arr[i] := i;
Test(Arr);
for i := 0 to High(Arr) do
ShowMessage(Arr[i].ToString);
end;
After SetLength, Arr points to a dynamic array heap object of reference count 1. You can see it in your RAM (press Ctrl+Alt+E, then Ctrl+G and goto Arr[0]). When you enter Test, the reference count is increased to 2 because both Arr and A refer to it. When you leave Test, the reference count is reduced back to 1 because A goes out of scope: now again only Arr refers to it.
Lesson 4
Now, a “gotcha”: if you change the number of elements of a dynamic array, it needs to be reallocated (*). Hence, a new dynamic array heap object is created with a reference count of 1, and the old object has its reference count decreased by 1 (and removed if it becomes zero).
Thus, while the previous example works as expected, the following will not:
var
Arr: TArray<Integer>;
procedure Test(A: TArray<Integer>);
var
i: Integer;
begin
SetLength(A, 5);
for i := Low(A) to High(A) do
A[i] := 2*A[i];
end;
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
SetLength(Arr, 10);
for i := 0 to High(Arr) do
Arr[i] := i;
Test(Arr);
for i := 0 to High(Arr) do
ShowMessage(Arr[i].ToString);
end;
The SetLength will create a new dynamic array heap object with refcount 1 and copy half of the old array into this, put the new address in the local A parameter, and Test will transform this new array, not touching the old one which the global Arr variable points to. The reference count of the original heap object is decreased by one.
However, if you use a var parameter,
procedure Test(var A: TArray<Integer>);
var
i: Integer;
begin
SetLength(A, 5);
for i := Low(A) to High(A) do
A[i] := 2*A[i];
end;
it will work as before. This effectively works with Arr. If we had increased the number of elements, the array would probably have been reallocated and the global Arr variable would have been updated with the new address.
Conclusion
As long as you don't need to reallocate the memory (change the number of elements), Delphi already gives you what you want, because dynamic arrays are reference types. If you do need to reallocate, at least now you know enough of the technical details in order to reason about it.
Update: Hence, the suggestion is to do
type
TTest = class
FData: TArray<Integer>;
constructor Create(AData: TArray<Integer>);
procedure Enlarge;
procedure Shrink;
procedure ShowSum;
end;
{ TTest }
constructor TTest.Create(AData: TArray<Integer>);
begin
FData := AData; // will NOT copy the array, since dynamic arrays are reference types
end;
procedure TTest.Enlarge;
var
i: Integer;
begin
for i := 0 to High(FData) do
FData[i] := 2*FData[i];
end;
procedure TTest.ShowSum;
var
s: Integer;
i: Integer;
begin
s := 0;
for i := 0 to High(FData) do
Inc(s, FData[i]);
ShowMessage(s.ToString);
end;
procedure TTest.Shrink;
var
i: Integer;
begin
for i := 0 to High(FData) do
FData[i] := FData[i] div 2;
end;
To test it:
procedure TForm1.FormCreate(Sender: TObject);
var
MyArray: TArray<Integer>;
t: TTest;
begin
MyArray := [1, 2, 3, 4, 5];
t := TTest.Create(MyArray);
try
t.ShowSum;
t.Enlarge;
t.ShowSum;
t.Shrink;
t.ShowSum;
finally
t.Free;
end;
end;
Footnotes
If you (1) decrease the number of elements and (2) the reference count is 1, typically the data isn't moved in memory. If the reference count is > 1, the data is always moved, because SetLength guarantees that the reference count of its argument is 1 when it returns.

How can I pass array of string a parameter to function in delphi

I have a problem with Delphi.
I wrote a function like this:
function MyFunction(arr: array of AnsiString): Boolean;
begin
//code here
end;
And now, when I pass an array of AnsiString directly into function, like this, everything works perfectly:
MyFunction(['one', 'two', 'three']);
But, when I try to store this array like this:
var arr: array of AnsiString;
procedure MyProcedure;
begin
arr[0] := ['one', 'two', 'three'];
MyFunction(arr[0]);
end;
There is a mismatch error.
I'm a beginner with Delphi, but this is really confusing.
Your second example is not functionally identical to the fist example.
The first example is fine. The function takes an open array as an input parameter, and you are constructing a fixed array of strings directly in that parameter, which is perfectly fine. Any array type can be passed to an open array parameter.
In the second example, you are declaring a dynamic array of strings, but you are not allocating any memory for the array, and you are trying to assign its first element (which is a single string) to point at a fixed array of strings. And then you are trying to pass that element (again, a single string) where an array is expected. That is why the code fails to compile.
The correct way to write your procedure would like more like this:
procedure MyProcedure;
var
arr: array of AnsiString;
begin
SetLength(arr, 3);
arr[0] := 'one';
arr[1] := 'two';
arr[2] := 'three';
MyFunction(arr);
end;
Alternatively:
procedure MyProcedure;
var
arr: array of AnsiString;
begin
arr := ['one', 'two', 'three'];
MyFunction(arr);
end;
Alternatively:
type
TAnsiStringArray = array of AnsiString;
procedure MyProcedure;
var
arr: TAnsiStringArray;
begin
arr := TAnsiStringArray.Create('one', 'two', 'three');
MyFunction(arr);
end;

How to change the values of a boolean array in delphi

I'm making a small Delphi program using Delphi XE5. In my code there is a dynamic boolean array and I'm no able to change the value of some of the arrays elements. I tried to initialize the array after setting it's length but it didn't help. Here is part of the code:
procedure DoSomething(names: array of string);
var startWithA: array of Boolean;
i: integer;
begin
SetLength(startWithA, Length(names)); // each element is false by default
for i := 0 to Length(names) - 1 do begin
if (names[i].indexOf('A') = 0) then begin
startWithA[i] := true; // the value is not changed after executing this line
end;
end;
end;
Your code works absolutely fine. Here is the proof:
{$APPTYPE CONSOLE}
uses
System.SysUtils;
function StartsWithAIndices(const Names: array of string): TArray<Boolean>;
var
i: Integer;
begin
SetLength(Result, Length(Names));
for i := 0 to high(Result) do begin
if (Names[i].IndexOf('A') = 0) then begin
Result[i] := true;
end;
end;
end;
var
Indices: TArray<Boolean>;
b: Boolean;
begin
Indices := StartsWithAIndices(['Bob', 'Aaron', 'Aardvark', 'Jim']);
for b in Indices do begin
Writeln(BoolToStr(b, True));
end;
Readln;
end.
Output
False
True
True
False
Perhaps your confusion stems from the fact that you assign to an array that is a local variable and whose values are never read. How can you say that the array values are not modified if you never read from them? Or perhaps you have optimizations enabled and the compiler decided to optimize away the local variable whose values are written to but never read.
As an aside, your function could be written more simply like this:
function StartsWithAIndices(const Names: array of string): TArray<Boolean>;
var
i: Integer;
begin
SetLength(Result, Length(Names));
for i := 0 to high(Result) do begin
Result[i] := Names[i].StartsWith('A');
end;
end;

Declaring Arrays of Variable Length in Delphi [duplicate]

This question already has answers here:
How do I declare an array when I don't know the length until run time?
(2 answers)
Closed 9 years ago.
This post was edited and submitted for review 4 months ago and failed to reopen the post:
Original close reason(s) were not resolved
I have a procedure in Delphi which currently looks like this:
Procedure Time.TimeDB(algorithm: string; Encode, Decode: InputFunction; N, R: Int);
VAR
i : LongInt;
Errors : Array[N] of LongInt;
BEGIN
for i := 0 to N-1 do
Errors[i] := 0;
END;
I'm given the error that N, as passed to the definition of Errors, is an undeclared identifier, despite declaring it in the procedure definition. N is recognized in the BEGIN-END section, though. Any ideas what's causing this and how I can otherwise declare a variable-length array in the VAR section?
You write array of Int to declare a dynamic array of Ints:
procedure Time.TimeDB(algorithm: string; Encode, Decode: InputFunction; N, R: Int);
var
i: int;
errors: array of Int;
begin
SetLength(errors, N);
for i := 0 to N - 1 do
Errors[i] := 0;
end;
Also notice that if an array has N elements, then they are indexed 0, 1, ..., N - 1. There is no element indexed N.
(Also, are you sure you don't mean integer when you write Int?)
The construct array[M..N] of Int is called a static array. In this case, M and N must be constants, like array[0..15] of TColor. You also got the static array declaration array[TMyType] of TMySecondType where the index will be of type TMyType, as in array[byte] of TColor or array[TFontStyle] of cardinal.
In your code your initializing your Errors Array to zero...Note with SetLength you don't need to do this...just set the Array to 0 and then set it to the length you want, and then just assign the values you need.
procedure WorkArrays(var aWorking: array of integer);
begin
if High(aWorking) >= 0 then
aWorking[0] := 1;
if High(aWorking) >= 3 then
aWorking[3] := 5;
end;
procedure WorkArrays2(var aWorking: array of integer);
begin
if High(aWorking) >= 1 then
aWorking[1] := 4;
if High(aWorking) >= 9 then
aWorking[9] := 7;
end;
procedure WorkArrays3(var aWorking: TIntArray);
begin
SetLength(aWorking, 4);
aWorking[0] := 1;
aWorking[3] := 5;
end;
procedure WorkArrays4(var aWorking: TIntArray);
begin
SetLength(aWorking, 10);
aWorking[1] := 4;
aWorking[9] := 7;
end;
procedure TForm58.ShowArrays(aWorking: array of integer);
var
a_Index: integer;
begin
for a_Index := Low(aWorking) to High(aWorking) do
Memo1.Lines.Add(IntToStr(aWorking[a_Index]));
end;
procedure TForm58.ShowArrays2(aWorking: TIntArray);
var
a_Index: integer;
begin
for a_Index := Low(aWorking) to High(aWorking) do
Memo1.Lines.Add(IntToStr(aWorking[a_Index]));
end;
procedure TForm58.Button1Click(Sender: TObject);
var
a_MyArray: array of integer;
a_MyArray1: TIntArray;
begin
SetLength(a_MyArray, 3);//note this is a Zero based Array...0 to 2
WorkArrays(a_MyArray);//note aWorking[3] will not show...because High is 2...
ShowArrays(a_MyArray);
SetLength(a_MyArray, 0);
SetLength(a_MyArray, 10);//note this is a Zero based Array...0 to 9
WorkArrays2(a_MyArray);
ShowArrays(a_MyArray);
WorkArrays3(a_MyArray1);
ShowArrays2(a_MyArray1);
WorkArrays4(a_MyArray1);
ShowArrays2(a_MyArray1);
end;
end.

Pass multidimensional array as argument in Delphi [duplicate]

This question already has answers here:
Closed 12 years ago.
Possible Duplicate:
Pass a multidimensional array as a parameter in Delphi
Hi all,
same question as here:
Pass a multidimensional array as a parameter in Delphi
but need for another answer.
type
MultiArray = array of array of Integer;
procedure Foo(a : MultiArray);
begin
end;
procedure Bar(a : array of Integer);
var i : Integer;
begin
for i in a do WriteLn(IntToStr(i));
end;
const
a : array[0..2] of Integer = (1, 2, 3);
ma : array[0..1] of array[0..1] of Integer = ((1,2),(3,4));
begin
Bar(a);
Bar([1,2,3]);
//Foo(ma);
end.
I want to pass arrays of different sizes to Foo. If its not possible this way, any 'workarounds'?
I guess that the question is about multidimensional open arrays.
There are no multidimensional open arrays in Delphi. But you can create a single-dimensional open array with dynamic arrays as elements. Here is an example:
type
TIntArray = array of Integer;
procedure Test(arr: array of TIntArray);
begin
ShowMessage(IntToStr(arr[1,1]));
end;
procedure TForm9.Button5Click(Sender: TObject);
begin
Test([TIntArray.Create(1,2), TIntArray.Create(2,3)]);
end;
I don't get what you want to do. You write
I want to pass arrays of different
sizes to Foo.
Well, why don't you do that, then?
If
type
TMultiArray = array of array of integer;
and
procedure Foo(a: TMultiArray);
then you can do
var
ShortMultiArray, LongMultiArray, HugeMultiArray: TMultiArray;
begin
SetLength(ShortMultiArray, 10, 10);
SetLength(LongMultiArray, 100, 100);
SetLength(HugeMultiArray, 1000, 1000);
Foo(ShortMultiArray);
Foo(LongMultiArray);
Foo(HugeMultiArray);
end;

Resources