I am trying to create a Complex Number Calculator using Delphi Pascal. The first part is to extract a string entry, separate the real & imaginary part by inserting a delimiter on 'i'. For Example: A number entry can be: 7+2i or any other combination. This was my approach:
TForm2.btnExtractClick(Sender: TObject);
var sCode, pic: string;
sConst: integer;
im,re: integer;
iConst: string;
j,k, delimiterPos: integer;
begin
memDisplay.Clear;
sCode := Edit1.Text;
sConst := Pos (sCode, 'i');
im := StrToInt(Copy(sCode, sConst - 1));
Delete(sCode, sConstant - 1, im);
re := StrToInt(sCode);
But I am getting the following error:
The substring which you search for should be the first argument to Pos.
Replace
Pos(sCode, 'i')
with
Pos('i', sCode)
This function is documented here: http://docwiki.embarcadero.com/Libraries/en/System.Pos
There are many further issues with your code, but this answers the primary question posed here, namely why Pos is returning 0. I won't attempt to debug the rest of your code, not least because this isn't your real code because it contains compile errors.
To resolve the subsequent issues in your code you must learn to use the debugger. Step through the code and inspect the value of each of the local variables after each line of code has executed. Compare the values you see under the debugger with the values that you expect to see based on your paper based static analysis.
In older versions of Delphi, like D7, you could do like ord(s[i]) where s was a string, but trying this with an AnsiString results in an exception (access violation).
P.S. I was w/delphi 7 for a long time.
Here are the steps to reproduce the error:
Create a new project and through a memo on the form (let it be memo1) than add the following code to the form create event handler:
procedure TForm1.FormCreate(Sender: TObject);
var u: ansistring;
begin
u := 'stringtest';
memo1.Lines.Add(inttostr(ord(u[2])));
end;
For me this code produces an AV.
It does work with an ansistring, but you cannot read past the end of it and you must make sure the string is initialized.
function CharCode(const S: ansistring; pos: integer): byte;
begin
if pos <= 0 then result:= 0
//else if s='' then Result:= 0 //unassigned string;
else if Length(s) < Pos then Result:= 0 //cannot read past the end.
else Result:= Ord(s[pos]);
end;
Note that if s='' is the same as asking if pointer(s) = nil. An empty string is really a nil pointer.
This is probably why you where getting an access violation.
If you want to force the ansistring to be a certain length you can use SetLength(MyAnsistring, NewLength);
The length of the (ansi)string is variable. That means it grows and shrinks as needed. If you read past the end of the string you may get an access violation.
Note that you don't have to get an AV, the RTL leaves a bit of slack in its allocation; it usually allocates a slightly bigger buffer than requested, this is due to performance and architectural reasons.
The other reason why you may not get an AV if reading past the end of a string is that your program may own both the string buffer and whatever happens to be right next to it.
For this reason it is a good idea to enable range checking in debug mode {$R+} it adds extra checks to protect against reading past the end of structures.
The difference between shortstring and (ansi)string
A short string has a fixed length and it lives on the stack.
A long string (ansi or wide) is a pointer to a record that gets allocated on the heap; it looks like this:
type
TStringRecord = record
CodePage: word;
ElementSize: word; //(1, 2 or 4)
ReferenceCount: integer;
Length: Integer;
StringData: array[1..length(s)] of char;
NullChar: char;
end;
The compiler hides all these details from you.
see: http://docwiki.embarcadero.com/RADStudio/Seattle/en/Internal_Data_Formats
Continuing from the topic here, being unfamiliar with pointers,
how to correctly call the function bellow, given the fact that the pointers in it are the data to be retrieved back, and how to actually get to that data after calling it?
function RetrieveDSOData(whatchannels: Byte; DSOCH1, DSOCH2: PDouble; LADATA: PWord; Nth_Sample: Byte): longint; cdecl; external 'E_l80.dll';
(Needed DSOCH1, DSOCH2 and LADATA data...)
If it's in any help / important at all -> the documentation states that these are "pointers to an array".
In case this is a duplicate and I just didn't look by correct keywords, vote for closing this.
Thank you.
Edit:
It is to be assumed the size can be received by the return of the function. Documentation states:
Return: The number of samples in the DSO and LA arrays. This number may not
be (total blocks x 1024) as some samples at the beginning and end are thrown away for
various purposes.
So, max size might be (1024 x 32samples x 2 (2x8 digital channels, 2 bytes?)) for LADATA array...
You have to call the function with addresses of arrays, and it will fill these arrays. Example:
var
DS1: array[0..31] of Double;
RetrieveDSOData(whatchannels, #DS[0], ...
I've done some research here regarding the problem given above and come up with the following code:
VarStr = array of WideChar;
function ArrayToString(const a: VarStr): UnicodeString;
begin
if Length(a) > 0 then
begin
ShowMessage ('Länge des übergebenen Strings: ' + IntToStr(Length(a)));
SetString(Result, PWideChar(#a[0]), Length(a) div 2)
end
else
Result := '';
end;
ShowMessage displays the correct number of characters in a given array, but the result of the function is always an empty string.
Your ideas please?
You are passing the wrong length value. You only ask for half of the characters. Fix your code like this:
function ArrayToString(const a: VarStr): string;
begin
SetString(Result, PWideChar(a), Length(a));
end;
However, you also report that your function returns an empty string. The most likely cause for that is that you are passing invalid input to the function. Consider this program:
{$APPTYPE CONSOLE}
type
VarStr = array of WideChar;
function ArrayToStringBroken(const a: VarStr): UnicodeString;
begin
SetString(Result, PWideChar(#a[0]), Length(a) div 2);
end;
function ArrayToStringSetString(const a: VarStr): UnicodeString;
begin
SetString(Result, PWideChar(a), Length(a));
end;
var
a: VarStr;
begin
a := VarStr.Create('a', 'b', 'c', 'd');
Writeln(ArrayToStringBroken(a));
Writeln(ArrayToStringSetString(a));
end.
The output is:
ab
abcd
So as well as the problem with the code in your question, you would seem to have problems with the code that is not in your question.
Perhaps when you said:
The result of the function is always an empty string.
You actually meant that no text is displayed when you pass the returned value to ShowMessage. That's a completely different thing altogether. As #bummi points out in comments, ShowMessage will truncate its input at the first null-terminator that is encountered. Use proper debugging tools to inspect the contents of variables.
Result:= Trim(string(a));
UPDATE: As colleagues graciously pointed in comments, this is a wrong answer! It works only because internal string and dynamic array implementation are pretty similar and there is no guarantee that such code would work in the future compilator versions. The correct way to DynArray->String conversion is described in the David answer. I would not delete my answer to preserve comments, in my opinion their worth is much greater..
Question One
I have
var example : array[0..15] of char;
I want to assign the value from an input to that variable
example := inputbox('Enter Name', 'Name', '');
In the highscores unit I have record and array
type
points = record
var
_MemoryName : array[0..15] of char;
_MemoryScore : integer;
end;
var
rank : array[1..3] of points;
var s: string;
a: packed array[0..15] of char;
highscoresdata.position[1]._MemoryName := StrPLCopy(a, s, Length(a)) ;
returns -> (186): E2010 Incompatible types: 'array[0..15] of Char' and 'PWideChar'
var s: string;
a: packed array[0..15] of char;
s := InputBox('caption', 'Caption', 'Caption');
FillChar(a[0], length(a) * sizeof(char), #0);
Move(s[1], a[0], length(a) * sizeof(char));
scores.rank[1]._MemoryName := <<tried both s and a>> ;
returns (189): E2008 Incompatible types
Question One
There are many ways. One is:
procedure TForm1.FormCreate(Sender: TObject);
var
s: string;
a: packed array[0..15] of char;
begin
s := InputBox(Caption, Caption, Caption);
assert(length(s) <= 16);
FillChar(a[0], length(a) * sizeof(char), #0);
Move(s[1], a[0], length(s) * sizeof(char));
end;
But there might be a more elegant solution to your original problem, I suspect.
Question Two
Every time you wish a function/procedure didn't have a particular argument, you should realize that there might be a problem with the design of the project. Nevertheless, it isn't uncommon that Sender parameters are superfluous, because they are almost omnipresent because of the design of the VCL (in particular, the TNotifyEvent). If you know that the receiving procedure doesn't care about the Sender parameter, simply give it anything, like Self or nil.
Question Three
Consider this code:
procedure TForm4.FormCreate(Sender: TObject);
var
a: packed array[0..15] of char;
b: packed array[0..15] of char;
begin
a := b;
end;
This doesn't work. You cannot treat arrays like strings; in particular, you cannot assign static arrays like this (a := b).
Instead, you have to do something like...
Move(b[0], a[0], length(a) * sizeof(char));
...or simply loop and copy one value at a time. But the above simple assignment (a := b) does work if you declare a static array type:
type
TChrArr = packed array[0..15] of char;
procedure TForm4.FormCreate(Sender: TObject);
var
a: TChrArr;
b: TChrArr;
begin
b := a;
end;
Andreas has you covered for question 1.
Question 2
I would arrange that your event handler called another method:
procedure TForm5.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
RespondToEditControlKeyPress;
end;
That way you can just call RespondToEditControlKeyPress directly.
I'd guess that you want to call it with no parameters because you want code to run when the edit control's text is modified. You could perhaps use the OnChange event instead. And it may be that OnChange is more appropriate because pressing a key is not the only way to get text into an edit control.
By the way, it's better to ask one question at a time here on Stack Overflow.
For a quick way to copy string-type values into array-of-character type values. I suggest a small helper function like this:
procedure StrToCharArray( inputStr:String; var output; maxlen:Integer);
type
ArrayChar = Array[0..1] of Char;
begin
StrLCopy( PChar(#ArrayChar(output)[0]),PChar(inputStr),maxlen);
end;
Each time you call it, pass in the maximum length to be copied. Remember that if the buffer length is 15, you should pass in 14 as the maxlen, so that you leave room for the terminating nul character, if you intend to always terminate your strings:
StrToCharArray( UserInputStr, MyRecord.MyField, 14 );
This function will ensure that the data you copy into the record is null terminated, assuming that's what you wanted. Remember that in a fixed length character array it's up to you to decide what the rules are. Null terminated? Fully padded with spaces or null characters.... Strings and arrays-of-characters are so different, that there exist multiple possible ways of converting between the two.
If you don't intend to terminate your strings with nul, then you should use the FillChar+Move combination shown in someone else's answer.
The obvious answer is of course.
Don't use a packed array of char.
Use a string instead.
If you use ansistring, 1 char will always take 1 byte.
If you use shortstring ditto.
Ansistring is compatible with Pchar which is a pointer to a packed array of char.
So you can write
function inputbox(a,b,c: ansistring): pchar;
begin
Result:= a+b+c;
end;
var s: ansistring;
begin
s:= inputbox('a','b','c');
end;
Some advice
It looks like your are translating code from c to Delphi.
a packed array of char is exactly the same as the old (1995) shortstring minus the length byte at the beginning of shortstring.
The only reason I can think of to use packed array of char is when you are reading data to and from disk, and you have legacy code that you don't want to change.
I would keep the legacy code to read and write from disk and then transfer the data into an ansistring and from there on only use ansistring.
It's soooooooo much easier, Delphi does everything for you.
And... ansistring is much faster, gets automatically created and destroyed, can have any length (up to 2GB), uses less memory --because identical strings only get stored once (which means stringa:= stringb where a string is 20 chars is at least 5x faster using ansistrings than array's of char).
And of course best of all, buffer overflow errors are impossible with ansistring.
What about unicodestring?
Unicodestring is fine to use, but sometimes translation of chars happens when converting between packed array of char and unicodestring, therefore I recommend using ansistring in this context.
What you try to do is impossible, indeed:
highscoresdata.position[1]._MemoryName := StrPLCopy(a, s, Length(a));
That tries to assign a pointer (the result of StrPLCopy, a PWideChar in the last few versions of Delphi) to an array, which is indeed impossible. You can't copy an array like that. I would do:
StrLCopy(highscoresdata.position[1]._MemoryName, PChar(s),
Length(highscoresdata.position[1]._MemoryName));
That should work, and is IMO the simplest solution to copy a string to an array of characters. There is no need to use a as some kind of intermediate, and using Move is, IMO, rather low level and therefore a little tricky (it is easy to forget to multiply by the size of a character, it is unchecked, it does not add a #0, etc.), especially if you don't know what exactly you are doing.
This solution should even work for versions of Delphi before Delphi 2009, as it does not rely on the size of the character.
FWIW, I would not use packed arrays. Packed doesn't have a meaning in current Delphi, but could confuse the compiler and make the types incompatible.