if exp1 and exp2 and exp3 then
//something
In Delphi is the order of evaluation exp1, exp2 and exp3 (of course they are all Boolean) is defined or random?
The order of evaluation is only defined when Boolean Short-Circuit Evaluation is enabled. Then, as the documentation in Complete Versus Short-Circuit Boolean Evaluation explains, evaluation is left to right.
If Boolean Short-Circuit Evaluation is not enabled, the order is undefined. The following code demonstrates this:
{$APPTYPE CONSOLE}
function A: boolean;
begin
Result := True;
Write('A ');
end;
function B: string;
begin
Result := '';
Write('B ');
end;
function C: Integer;
begin
Result := 0;
Write('C ');
end;
begin
{$O+}
Writeln('short circuit on');
{$B-}
if A and (B = '') and (C = 0) then Writeln;
Writeln('short circuit off');
{$B+}
if A and (B = '') and (C = 0) then Writeln;
end.
For the first one, you get printed A B C, and for the second one you get B A C.
And that was compiled for Win32 - to make this spicy, and bring home the point of this being Undefined, lets run it on Win64 where we get A B C in both cases.
You might say: "Ok, but maybe just B was called first but the evaluation of the boolean expression B = '' is evaluated in the correct order." Let's take a look at the assembler code that gets executed:
if A and (B = '') and (C = 0) then Writeln;
0040B17C 8D45E8 lea eax,[ebp-$18]
0040B17F E864EAFFFF call B
0040B184 837DE800 cmp dword ptr [ebp-$18],$00
0040B188 0F94C3 setz bl
0040B18B E81CEAFFFF call A
0040B190 22D8 and bl,al
0040B192 E891EAFFFF call C
0040B197 85C0 test eax,eax
0040B199 0F94C0 setz al
0040B19C 22D8 and bl,al
Nope:
call B, compare to ''
call A, and it with the result of the string comparison
call C, compare with 0, and it with the result of the previous and
Which translates to (written in left to right order):
((B = '') and A) and (C = 0)
Addendum: Because I saw this mentioned in another answer and discussed in the comments. Parentheses are no solution to force order of evaluation of operands. You can only group operations but the compiler might still decide to evaluate the right operand first.
In the code above there is no way to put any parentheses to get A B C simply because the compiler flipped the first two operands. It then however executed the operators from left to right and that is something to easily confuse - the question was not whether the first or the second and was executed first but the order of the boolean expressions - the operands.
The documentation will guide you of course.
I have found that in more complex if statements the compiler does not do what even experienced programmers expected when they wrote the code, often because they are including logical nots and mixing boolean ands and ors. Remembering the precedence and association rules is not always easy.
My tip is to be explicit about what you want and put brackets around the terms. The compiler won't mind and it makes it clear what you intended when you or someone else reviews the code.
With compound if statements laying out the logical parts together can aso help humans to understand what was meant.
For example:
if( ((pObjectRef<>nil) And (pObjecRef.Property=SomeValue)) Or
((pAlternateObject<>nil) And
((pAlternatObject.Property=AnotherValue) Or
(pAlternatObject.Property=YetAnotherValue))) ) then
begin
...
end
Related
Short Version
Given:
const
Whitespace = [$0009, $000A, $000C, $0020];
Works: if ch in Whitespace then...
Fails: case ch of Whitespace: ...
Long Version
Normally, if you were to have a case statement, you can include various values for each case, eg:
var
ch: UCS4Char; // unsigned 32-bit
case ch of
48..57: {asciiDigit}; //i.e. '0'..'9'
65..70: {asciiUpperHexDigit}; //i.e. 'A'..'F'
97..102: {asciiLowerHexDigit}; //i.e. 'a'..'f'
end;
That works, but I would like these values to be constants:
const
//https://infra.spec.whatwg.org/#code-points
asciiDigit = [Ord('0')..Ord('9')]; //https://infra.spec.whatwg.org/#ascii-digit
asciiUpperHexDigit = [Ord('A')..Ord('F')]; //https://infra.spec.whatwg.org/#ascii-upper-hex-digit
asciiLowerHexDigit = [Ord('a')..Ord('f')]; //https://infra.spec.whatwg.org/#ascii-lower-hex-digit
asciiHexDigit = asciiUpperHexDigit + asciiLowerHexDigit; //https://infra.spec.whatwg.org/#ascii-hex-digit
asciiUpperAlpha = [Ord('A')..Ord('Z')]; //https://infra.spec.whatwg.org/#ascii-upper-alpha
asciiLowerAlpha = [Ord('a')..Ord('z')]; //https://infra.spec.whatwg.org/#ascii-lower-alpha
asciiAlpha = asciiUpperAlpha + asciiLowerAlpha; //https://infra.spec.whatwg.org/#ascii-alpha
asciiAlphaNumeric = asciiDigit + asciiAlpha; //https://infra.spec.whatwg.org/#ascii-alphanumeric
Is there any arrangement of any syntax that will allow:
caseing a Cardinal
against a "set of Cardinals"?
Or am I permanently stuck with the following?
var
ch: UCS4Char; //unsigned 32-bit
case ch of
Ord('!'): FState := MarkupDeclarationOpenState;
Ord('/'): FState := EndTagOpenState;
Ord('?'): AddParseError('unexpected-question-mark-instead-of-tag-name');
UEOF: AddParseError('eof-before-tag-name parse error');
Whitespace: FState := SharkTankContosoGrobber;
else
if ch in asciiDigit then
begin
FReconsume := True;
FState := tsTagNameState;
end
else
AddParseError('invalid-first-character-of-tag-name parse error');
end;
Obviously, using the conceptual case matches the logic being performed; having to do if-elseif is...lesser.
Note: I don't need it to actually be a Delphi "set", that is a specific term with a specific meaning. I just want:
case ch of Whitespace: ...
to work the same way:
if ch in Whitespace then...
already does work.
And we know the compiler already is OK with comparing a Cardinal to a "set", because the following already works:
case ch of
$000A, $000D, $0009, $0032: ...
end;
It's comparing a Cardinal to a "set of numbers".
Bonus Reading
Delphi case statement for integer ranges
Efficiently compare an integer against a static list of integers in Delphi?
any way to compare an integer variable to a list of integers in if statement
What Delphi type for 'set of integer'?
No, this is not supported.
According to the official documentation:
A case statement has the form:
case selectorExpression of
caseList1: statement1;
...
caseListn: statementn;
end
where [...] each caseList is one of the following:
A numeral, declared constant, or other expression that the compiler can evaluate without executing your program. It must be of an ordinal type compatible with selectorExpression. [...]
A subrange having the form First..Last, where First and Last both satisfy the criterion above and First is less than or equal to Last.
A list having the form item1, ..., itemn, where each item satisfies one of the criteria above.
Hence, this only allows single values, explicit ranges, and lists of such values, as part of the case syntax.
Although the Delphi documentation is good, it isn't perfect and you cannot rely on it to 100%. However, I'm sure all experienced Delphi developers will agree that a caseList cannot be a predeclared single-identifier "collection" of ordinal values compatible with selectorExpression.
You may file a feature request at Embarcadero's Jira.
But you can use the range syntax and a previously declared subrange type (not set constant) to achieve something partly similar:
type
TAsciiDigit = '0'..'9';
TAsciiLatinCapitalLetter = 'A'..'Z';
TAsciiLatinSmallLetter = 'a'..'z';
procedure TForm1.FormCreate(Sender: TObject);
begin
var c := 'R';
case c of
Low(TAsciiDigit) .. High(TAsciiDigit):
ShowMessage('Digit!');
Low(TAsciiLatinCapitalLetter) .. High(TAsciiLatinCapitalLetter):
ShowMessage('Capital!');
Low(TAsciiLatinSmallLetter) .. High(TAsciiLatinSmallLetter):
ShowMessage('Small letter!');
else
ShowMessage('Something else.');
end;
end;
Bonus remark: In fact, the non-100% accuracy of the documentation can be seen in the section quoted above:
selectorExpression is any expression of an ordinal type smaller than 32 bits
That's nonsense. selectorExpression certainly can be a 32-bit integer.
The intuitive answer would be that the loop is never entered. And this seems to be case in all tests I could come up with. I'm still anxious and always test it before entering the loop.
Is this necessary?
No, it is not necessary.
The documentation clearly states :
for counter := initialValue to finalValue do statement
or:
for counter := initialValue downto finalValue do statement
...
If initialValue is equal to finalValue, statement is executed exactly once. If initialValue is greater than finalValue in a for...to statement, or less than finalValue in a for...downto statement, then statement is never executed.
There is no need for anxiety.
If we want to examine further what happens, let's make a few examples. Consider first :
program Project1;
{$APPTYPE CONSOLE}
var
i : integer;
begin
for i := 2 to 1 do WriteLn(i);
end.
This produces a compiler hint:
[dcc32 Hint] Project1.dpr(6): H2135 FOR or WHILE loop executes zero times - deleted
So the compiler will simply throw away a loop with constants that produce no loop iterations. It does this even with optimizations turned off - no code is produced for the loop at all.
Now let's be a bit more clever :
program Project1;
{$APPTYPE CONSOLE}
var
i, j, k : integer;
begin
j := 2;
k := 1;
for i := j to k do WriteLn(i);
end.
This actually compiles the loop. The output is as below:
Project1.dpr.8: for i := j to k do WriteLn(i);
004060E8 A1A4AB4000 mov eax,[$0040aba4] {$0040aba4 -> j = 2}
004060ED 8B15A8AB4000 mov edx,[$0040aba8] {$0040aba8 -> k = 1}
004060F3 2BD0 sub edx,eax {edx = k - j = -1}
004060F5 7C2E jl $00406125 {was k-j < 0? if yes, jmp to end.}
004060F7 42 inc edx {set up loop}
004060F8 8955EC mov [ebp-$14],edx
004060FB A3A0AB4000 mov [$0040aba0],eax
00406100 A118784000 mov eax,[$00407818] {actual looped section}
00406105 8B15A0AB4000 mov edx,[$0040aba0]
0040610B E8E8D6FFFF call #Write0Long
00406110 E8C3D9FFFF call #WriteLn
00406115 E8EECCFFFF call #_IOTest
0040611A FF05A0AB4000 inc dword ptr [$0040aba0] {update loop var}
00406120 FF4DEC dec dword ptr [ebp-$14]
00406123 75DB jnz $00406100 {loop ^ if not complete}
Project1.dpr.9: end.
00406125 E88EE1FFFF call #Halt0
So, the very first thing a loop does is to check whether it needs to execute at all. If the initial is greater than the final (for a for..to loop) then it skips straight past it entirely. It doesn't even waste the cycles to initialize the loop counter.
There are some edge-cases in which you may be surprised to discover that the code does unexpectedly enter the loop. And still other cases where you may be tempted to pre-check whether to call the loop. But before I get into those details, I want to try impress on you the importance of not pre-checking your loop with an if condition.
Every line of code, no matter how easy to understand draws attention. It's more to read and more to confirm is correct. So if it's not important, or if it's technically redundant: it's best left out.
A for loop is conceptually translated as follows:
Initialise loop index to starting value.
If iteration constraint is valid (e.g. Index <= EndValue in case of forward loop):
Perform iteration (code within loop block/statement)
Perform loop control operations (increment loop index)
Repeat 2
Otherwise continue at first instruction after loop.
The way in which Step 2 is checked, makes an extra if condition before the loop completely redundant.
So if you (or another developer) is later maintaining code with a redundant if condition, they're left to wonder:
Is the line correct?
It seems redundant; is there a special condition it's trying to handle?
If it currently serves no purpose, perhaps it was intended to prevent calling the loop on a different condition?
In simple case, redundant lines of code can create some confusion. In more complex cases, they can result in whole new sections of irrelevant code being developed; that tries to cater for irrelevant scenarios implied by legacy redundant code.
Recommendation: Stamp out redundant code as much as possible. Including redundant pre-checks for "should the loop execute at all".
The most important benefit of stamping out redundant code is that: it correctly draws attention to peculiar cases whenever special handling actually is required.
There are 2 potential pitfalls, and the first is the more dangerous one as it deals with implicit type conversion. So it may not always be easy to detect. The following code was tested on rextester using fpc, but I have verified the same issue on Delphi 2007 / 2009 in the past.
//fpc 2.6.2
program UnexpectedForIteration;
{$MODE DELPHI}
{ Ensure range-checking is off. If it's on, a run-time error
prevents potentially bad side-effects of invalid iterations.}
{$R-,H+,W+}
var
IntStart, IntEnd, IntIndex: Integer;
UIntStart, UIntEnd, UIntIndex: Cardinal;
IterCount: Integer;
begin
Writeln('Case 1');
IntStart := High(Integer) - 1;
IntEnd := -IntStart;
UIntStart := Cardinal(IntStart);
UIntEnd := Cardinal(IntEnd);
{This gives a clue why the problem occurs.}
Writeln('From: ', IntStart, ' To: ', IntEnd);
Writeln('From: ', UIntStart, ' To: ', UIntEnd, ' (unsigned)');
Writeln('Loop 1');
IterCount := 0;
for IntIndex := IntStart to IntEnd do Inc(IterCount);
Writeln(IterCount);
Writeln('Loop 2');
IterCount := 0;
{ The loop index variable is a different type to the initial &
final values. So implicit conversion takes place and:
IntEnd **as** unsigned is unexpectedly bigger than IntStart }
for UIntIndex := IntStart to IntEnd do Inc(IterCount);
Writeln(IterCount, ' {Houston we have a problem}');
Writeln();
Writeln('Case 2');
UIntStart := High(Cardinal) - 2;
UIntEnd := 2;
IntStart := Integer(UIntStart);
IntEnd := Integer(UIntEnd);
{This gives a clue why the problem occurs.}
Writeln('From: ', UIntStart, ' To: ', UIntEnd);
Writeln('From: ', IntStart, ' To: ', IntEnd, ' (signed)');
Writeln('Loop 3');
IterCount := 0;
for UIntIndex := UIntStart to UIntEnd do Inc(IterCount);
Writeln(IterCount);
Writeln('Loop 4');
IterCount := 0;
{ The loop index variable is a different type to the initial &
final values. So implicit conversion takes place and:
UIntStart **as** signed is unexpectedly less than UIntEnd }
for IntIndex := UIntStart to UIntEnd do Inc(IterCount);
Writeln(IterCount, ' {Houston we have a problem}');
end.
The output is as follows:
Case 1
From: 2147483646 To: -2147483646
From: 2147483646 To: 2147483650 (unsigned)
Loop 1
0
Loop 2
5 {Houston we have a problem}
Case 2
From: 4294967293 To: 2
From: -3 To: 2 (signed)
Loop 3
0
Loop 4
6 {Houston we have a problem}
In many cases the problem is resolved by ensuring the same types are used for loopIndex, initialValue and finalValue. As this means there won't be an implicit type conversion, and the loop will reliably iterate as the initialValue and finalValue would suggest.
It would be easier if the compiler emits appropriate warnings for implicit type conversion in for loops. Unfortunately fpc didn't; I don't recall whether Delphi 2007/2009 does; and have no idea whether any recent versions do.
However, the preferred approach would be to favour container iteration syntax (pushing responsibility for 'correct' iteration on the enumerators). E.g.: for <element> in <container> do ...;. This should not iterate empty containers provided the enumerator's methods are implemented correctly.
The only time I'd say a pre-check is worth considering is:
when for in is not feasible for some reason
and the loop index needs to be zero-based
and support large unsigned integers (High(Integer) < index < High(Cardinal))
because this leaves no space for a reliable sentinel less than all possible initial values.
Even in this case, consider using an Int64 loop index instead of if (initialValue <= finalValue) then for ....
The second pitfall involves what I would in any case consider to be a design flaw. So the problem can be avoided entirely by rather being aware of this design consideration. It is demonstrated in code that looks as follows:
if Assigned(AnObject) then
for LIndex := 0 to AnObject.Count - 1 do ...;
In this case the if condition may in fact be necessary as a result of dubious design. Certainly, if AnObject hasn't been created, you do not want to access its Count property/method. But the dubious aspect of the design is the fact that you're uncertain whether AnObject exists. Yes, you may have employed a lazy-initialisation pattern. But it doesn't change the fact that in the above code, there's no way to differentiate between: "zero iterations" because AnObject doesn't exist or because AnObject.Count = 0.
I'd like to point out that when code has many redundant if Assigned(AnObject) then (or similar) lines, it leads to one of the problems I described in section 1. Local code caters for 2 possibilities. And by extension, client code also caters for 2 possibilities. And by induction, this problem eventually leaks throughout the code-base.
The solution is to first and foremost limit the cases where existence of AnObject is uncertain.
It's much easier to ensure an empty object with Count = 0 is guaranteed to be created (typically only affecting a small number of places in code).
It's far more work to deal with the ripple effects of a large number of places where the object might not exist yet; yielding 2 possible states and code paths.
If lazy-initialisation is required, try to ensure the code surface where existence is optional is kept as small as possible.
In C, you can assign two variables in one line with
b = a = sqrt(10);
In Delphi
b := a := Sqrt(10);
is not allowed.
With IfThen, there is an "alternative" for the ternary operator ?: as discussed in Delphi - Equivalent to C#'s ternary operator? In summary, IfThen doesn't seem absolutely necessary.
So maybe there is also something like this:
function AssignAndReturn(var LHS: Integer; RHS: Integer): Integer;
begin
LHS := RHS;
Result := RHS;
end;
(...)
var
a, b: Integer;
begin
b := AssignAndReturn(a, Round(Sqrt(10)));
I'm not trying to "make everything look like C". I just noticed, that sometimes it would be nice to "reuse" the right hand side of an assignment in the same line again. (See Lazarus/Free Pascal: How to improve coding style (to avoid using endless loop) of a while loop whose boolean expression is reassigned every pass for example.)
An assignment statement is not an expression. It does not, and will never, yield a value. The only way to assign to multiple variables in one statement is via a function.
Based on this question and your previous question I would say that you are mistaken in trying to fight against the language. Code into the language you are using. In C you might write
b = a = sqrt(10);
But in Pascal the language wants you to write it like this
a := sqrt(10);
b := a;
Do it this way. Don't create obscure functions just so that you can cram it all onto a single line. The two lines above could not be clearer to read. Stop trying to force other languages into Pascal. They don't fit.
The short answer is:
No. What you want is not possible. And arguably would be of little if any benefit in Delphi.
First, let's discuss a more practical example. To be frank, a = b = f(x); is a rather poor example.
It's rare that you arbitrarily want the same value assigned to 2 variables.
Order of operation above has to break the left to right convention otherwise it would first resolve a = b then ? = f(x).
So there's little justification to favour that over b = f(x); a = b;.
Basically it's good for byte count (code golf) not readability.
So instead, let's consider the following, more practical scenario:
//Option 1
if ((b = f(x)) > 42)
//use b
There's no Delphi equivalent to the above, but it's at least as readable/maintainable to write:
//Option 2
b := f(x);
if (b > 42) then
//use b
There is another option to consider; especially if Option 1 is sitting in the middle of a larger function. Remember small functions are more maintainable and easier for a compiler to optimise. So consider:
//Option 3
...
ProcessRule42(f(x));
...
//Where Rule42 is is implemented as:
procedure ProcessRule42(b: Integer);
begin
if (b > 42) then
//use b
end;
Delphi doesn't really seem to be suffering due to not being able to write: if (b := f(x)) > 42 then //use b.
Is there any real benefit to Option 1?
If the Option 2 is at least as good, why would one ever bother with Option 1. The benefit is seen when you consider scoping rules.
//b does not exist
if ((var b = f(x)) > 42) {
//use b
}
//b does not exist
// Using the Delphi approach, this becomes:
//b does not exist
var b = f(x);
if (b > 42) {
//use b
}
//b still exists
Delphi simply does not have the same scoping concerns. Delphi variables are all declared at the beginning of the method, and available for the whole method.
Conclusion
This brings us back to what others have been trying to explain. Delphi is a different language with slightly different approaches to some problems. While I applaud you for examining other options and considering what concepts can be borrowed from other languages: be careful of trying to force-fit some concepts where they don't belong.
If you break too many Delphi paradigms, your code may become unnecessarily difficult for Delphi programmers to maintain.
I have to make a program where I have to convert numbers from the decimal system into another one (base 2 to 9), like in my first question.
But this time I have to make it using a recursive function. So I came with:
function cambiarBase(n,b: integer): string;
Begin
if n < b then
cambiarBase := inttostr(n)
else
cambiarBase := inttostr(n mod b) + cambiarBase(n div b, b);
End;
It takes 2 integer variables, 'n' being the decimal number and 'b' being the base, and returns a string with the converted number. At the button procedure I show the number in a TMemo
memo1.Lines.Add(cambiarBase(n,b));
The problem I have is: with the function the way it is gives the digits in the reverse order
(e.g. 301 in base 9 is 364, but its shown as 463). But if I use a ReverseString function after the if statement then the number its shown in a different order (in the example, the number would be now 634).
But if I apply the ReverseString function at the memo1.Lines.Add (outside of the function) then it shows the correct convertion.
What I want is to know how can I make it return the correct order of the digits by the function itself.
The program compiles it without any errors.
Again, thanks for reading.
LeoAM
You just need to reverse the order of concatenation. Instead of:
cambiarBase := inttostr(n mod b) + cambiarBase(n div b, b);
you write
cambiarBase := cambiarBase(n div b, b) + inttostr(n mod b);
If you think about this it should be obvious that the concatenation should be this way round. In that expression, the inttostr(n mod b) term is the less significant term and so appears on the right.
For what it is worth, I think that this code reads much better if you use Result rather than the function name. Especially for a recursive function it can be hard visually to distinguish between the result variable and the recursive call. I'd write it like this:
function cambiarBase(n, b: Integer): string;
begin
if n < b then
// termination
Result := IntToStr(n)
else
// recursive step
Result := cambiarBase(n div b, b) + IntToStr(n mod b);
end;
Let's work through a simple example for the sake of illustration. Let's say 27 base 4 which we know to be equal to 123 (16 + 2*4 + 3).
cambiarBase(27, 4) = cambiarBase(6, 4) + inttostr(3)
Next we need to evaluate
cambiarBase(6, 4) = cambiarBase(1, 4) + inttostr(2)
And finally the termination
cambiarBase(1, 4) = inttostr(1)
Plug it all together and you have
cambiarBase(27, 4) = inttostr(1) + inttostr(2) + inttostr(3)
The difference between Chr and Char when used in converting types is that one is a function and the other is cast
So: Char(66) = Chr(66)
I don't think there is any performance difference (at least I've never noticed any, one probably calls the other).... I'm fairly sure someone will correct me on this!
EDIT Thanks to Ulrich for the test proving they are in fact identical.
EDIT 2 Can anyone think of a case where they might not be identical, e.g. you are pushed towards using one over the other due to the context?
Which do you use in your code and why?
I did a small test in D2007:
program CharChr;
{$APPTYPE CONSOLE}
uses
Windows;
function GetSomeByte: Byte;
begin
Result := Random(26) + 65;
end;
procedure DoTests;
var
b: Byte;
c: Char;
begin
b := GetSomeByte;
IsCharAlpha(Chr(b));
b := GetSomeByte;
IsCharAlpha(Char(b));
b := GetSomeByte;
c := Chr(b);
b := GetSomeByte;
c := Char(b);
end;
begin
Randomize;
DoTests;
end.
Both calls produce the same assembly code:
CharChr.dpr.19: IsCharAlpha(Chr(b));
00403AE0 8A45FF mov al,[ebp-$01]
00403AE3 50 push eax
00403AE4 E86FFFFFFF call IsCharAlpha
CharChr.dpr.21: IsCharAlpha(Char(b));
00403AF1 8A45FF mov al,[ebp-$01]
00403AF4 50 push eax
00403AF5 E85EFFFFFF call IsCharAlpha
CharChr.dpr.24: c := Chr(b);
00403B02 8A45FF mov al,[ebp-$01]
00403B05 8845FE mov [ebp-$02],al
CharChr.dpr.26: c := Char(b);
00403B10 8A45FF mov al,[ebp-$01]
00403B13 8845FE mov [ebp-$02],al
Edit: Modified sample to mitigate Nick's concerns.
Edit 2: Nick's wish is my command. ;-)
The help says: Chr returns the character with the ordinal value (ASCII value) of the byte-type expression, X. *
So, how is a character represented in a computer's memory? Guess what, as a byte*. Actually the Chr and Ord functions are only there for Pascal being a strictly typed language prohibiting the use of bytes* where characters are requested. For the computer the resulting char is still represented as byte* - to what shall it convert then? Actually there is no code emitted for this function call, just as there is no code omitted for a type cast. Ergo: no difference.
You may prefer chr just to avoid a type cast.
Note: type casts shall not be confused with explicit type conversions! In Delphi 2010 writing something like Char(a) while a is an AnsiChar, will actually do something.
**For Unicode please replace byte with integer*
Edit:
Just an example to make it clear (assuming non-Unicode):
var
a: Byte;
c: char;
b: Byte;
begin
a := 60;
c := Chr(60);
c := Chr(a);
b := a;
end;
produces similar code
ftest.pas.46: a := 60;
0045836D C645FB3C mov byte ptr [ebp-$05],$3c
ftest.pas.47: c := Chr(60);
00458371 C645FA3C mov byte ptr [ebp-$06],$3c
ftest.pas.48: c := Chr(a);
00458375 8A45FB mov al,[ebp-$05]
00458378 8845FA mov [ebp-$06],al
ftest.pas.49: b := a;
0045837B 8A45FB mov al,[ebp-$05]
0045837E 8845F9 mov [ebp-$07],al
Assigning byte to byte is actually the same as assigning byte to char via CHR().
chr is a function, thus it returns a new value of type char.
char(x) is a cast, that means the actual x object is used but as a different type.
Many system functions, like inc, dec, chr, ord, are inlined.
Both char and chr are fast. Use the one that is most appropriate each time,
and reflects better what you want to do.
Chr is function call, it is a bit (tiny-tiny) more expensive then type cast. But i think Chr is inlined by compiler.
They are identical, but they don't have to be identical. There's no requirement that the internal representation of characters map 1-to-1 with their ordinal values. Nothing says that a Char variable holding the value 'A' must hold the numeric value 65. The requirement is that when you call Ord on that variable, the result must be 65 because that's the code point designated for the letter A in your program's character encoding.
Of course, the easiest implementation of that requirement is for the variable to hold the numeric value 65 as well. Because of this, the function calls and the type-casts are always identical.
If the implementation were different, then when you called Chr(65), the compiler would go look up what character is at code point 65 and use it as the result. When you write Char(65), the compiler wouldn't worry about what character it really represents, as long as the numeric result stored in memory was 65.
Is this splitting hairs? Yes, absolutely, because in all current implementations, they're identical. I liken this to the issue of whether the null pointer is necessarily zero. It's not, but under all implementations, it ends up that way anyway.
chr is typesafe, char isn't: Try to code chr(256) and you'll get a compiler error. Try to code char(256) and you will either get the character with the ordinal value 0 or 1, depending on your computers internal representation of integers.
I'll suffix the above by saying that that applies to pre-unicode Delphi. I don't know if chr and char have been updated to take unicode into account.