I need to create and initialize an object from a subtype B, with supertype A in PL/SQL:
create or replace
TYPE "A" as object{
school_category varchar(10);
}
create or replace
TYPE "B" UNDER A {
school_name varchar(10);
school_ranking INTEGER;
}
Now, I when I run the below code:
Declare
i_B B;
BEGIN
i_B := B('name_sample', 12, A('elementary'));
END;
I get below error:
PLS-00306: wrong number or types of arguments in call to 'B'
I really appreciate your help on this. Thanks a lot.
TRY THIS CODE:-
Create Or Replace Type Atest As Object
(
School_Category Varchar2(10)
);
create or replace Type Btest Under Atest (
school_name varchar2(10),
School_Ranking number
);
DECLARE
var2 Btest;
BEGIN
var2 := Btest('Good','MySchool',2);
Dbms_Output.Put_Line(var2. School_Category);
Dbms_Output.Put_Line(var2. school_name);
Dbms_Output.Put_Line(var2. School_Ranking);
End;
Or
Create Or Replace Type Atest As Object
(
School_Category Varchar2(10)
);
create or replace Type Btest as Object (
school_name varchar2(10),
School_Ranking number,
School_Categ Atest
);
Declare
iobj Btest;
Begin
iobj:=Btest(school_name=>'MYSCHOOL',School_Ranking=>2,School_Categ=>Atest(School_Category=>'GOOD'));
Dbms_Output.Put_Line(Iobj.School_Name);
Dbms_Output.Put_Line(Iobj.School_Ranking);
end;
Related
I have the following function in my Inno setup:
function GetSerialNumber(ADelimiter: Char): string;
var
I: Integer;
begin
Result := '';
for I := 0 to GetArrayLength(SerialEdits) - 1 do
Result := Result + SerialEdits[I].Text + ADelimiter;
if GetArrayLength(SerialEdits) > 1 then
begin
Delete(Result, Length(Result), 1);
end
end;
Within another function below, I have a variable named Serial: string, but when I do
Serial := GetSerialNumber('');
I get a type mismatch error, does anyone know what I'm doing wrong? Thanks!
The problem is not the return value, but the argument.
The '' is not a valid char literal. A char literal must be exactly one character long. The '' is a string.
If you want to allow an empty delimiter, change the argument type to the string.
Problem
There are multiple ways to store string reference, so how would you do it in the example code? Currently the problem is with storing access to string because it is causing non-local pointer cannot point to local object. Is storing 'First and 'Last to reference a string a preferable way?
String reference storage
This record stores reference to a string. The First and Last is supposed to point to a string. The Name should be able to the same I think, but that will cause non-local pointer cannot point to local object when a local string is assigned to that. So the current work around solution is to use First and Last.
type Segment is record
First : Positive;
Last : Positive;
Length : Natural := 0;
Name : access String;
end record;
Assigning sub string reference
The commented line is causing non-local pointer cannot point to local object. This is because Item is local. Source is not local and that is the string I want sub string references from.
procedure Find (Source : aliased String; Separator : Character; Last : out Natural; Item_Array : out Segment_Array) is
P : Positive := Source'First;
begin
for I in Item_Array'Range loop
declare
Item : aliased String := Separated_String_Next (Source, Separator, P);
begin
exit when Item'Length = 0;
Item_Array (I).Length := Item'Length;
Item_Array (I).First := Item'First;
Item_Array (I).Last := Item'Last;
--Item_Array (I).Name := Item'Access;
Last := I;
end;
end loop;
end;
Example
with Ada.Text_IO;
with Ada.Integer_Text_IO;
procedure Main is
use Ada.Text_IO;
use Ada.Integer_Text_IO;
function Separated_String_Next (Source : String; Separator : Character; P : in out Positive) return String is
A : Positive := P;
B : Positive;
begin
while A <= Source'Last and then Source(A) = Separator loop
A := A + 1;
end loop;
P := A;
while P <= Source'Last and then Source(P) /= Separator loop
P := P + 1;
end loop;
B := P - 1;
while P <= Source'Last and then Source(P) = Separator loop
P := P + 1;
end loop;
return Source (A .. B);
end;
type Segment is record
First : Positive;
Last : Positive;
Length : Natural := 0;
Name : access String;
end record;
type Segment_Array is array (Integer range <>) of Segment;
procedure Find (Source : String; Separator : Character; Last : out Natural; Item_Array : out Segment_Array) is
P : Positive := Source'First;
begin
for I in Item_Array'Range loop
declare
Item : aliased String := Separated_String_Next (Source, Separator, P);
begin
exit when Item'Length = 0;
Item_Array (I).Length := Item'Length;
Item_Array (I).First := Item'First;
Item_Array (I).Last := Item'Last;
--Item_Array (I).Name := Item'Access;
Last := I;
end;
end loop;
end;
Source : String := ",,Item1,,,Item2,,Item3,,,,,,";
Item_Array : Segment_Array (1 .. 100);
Last : Natural;
begin
Find (Source, ',', Last, Item_Array);
Put_Line (Source);
Put_Line ("Index First Last Name");
for I in Item_Array (Item_Array'First .. Last)'Range loop
Put (I, 5);
Put (Item_Array (I).First, 6);
Put (Item_Array (I).Last, 5);
Put (" ");
Put (Source (Item_Array (I).First .. Item_Array (I).Last));
New_Line;
end loop;
end;
Output
,,Item1,,,Item2,,Item3,,,,,,
Index First Last Name
1 3 7 Item1
2 11 15 Item2
3 18 22 Item3
The error message tells you exactly what is wrong : Item is a string declared locally, i.e. on the stack, and you are assigning its address to an access type (pointer). I hope I don't need to explain why that won't work.
The immediate answer - which isn't wrong but isn't best practice either, is to allocate space for a new string - in a storage pool or on the heap - which is done with new.
Item : access String := new String'(Separated_String_Next (Source, Separator, P));
...
Item_Array (I).Name := Item;
Note that some other record members, at least, Length all appear to be completely redundant since it is merely a copy of its eponymous attributes, so should probably be eliminated (unless there's a part of the picture I can't see).
There are better answers. Sometimes you need to use access types, and handle their object lifetimes and all the ways they can go wrong. But more often their appearance is a hint that something in the design can be improved : for example:
the Unbounded_String may manage your strings more simply
You could use the length as a discriminant on the Segment record, and store the actual string (not an Access) in the record itself
Ada.Containers are a standard library of containers to abstract over handling the storage yourself (much as the STL is used in C++).
If you DO decide you need access types, it's better to use a named access type type Str_Access is access String; - then you can create a storage pool specific to Str_Acc types, and release the entire pool in one operation, to simplify object lifetime management and eliminate memory leaks.
Note the above essentially "deep copies" the slices of the Source string. If there is a specific need to "shallow copy" it - i.e. refer to the specific substrings in place - AND you can guarantee its object lifetime, this answer is not what you want. If so, please clarify the intent of the question.
For a "shallow copy" the approach in the question essentially fails because Item is already a deep copy ... on the stack.
The closest approach I can see is to make the source string aliassed ... you MUST do as you want each Segment to refer to it ... and pass its access to the Find procedure.
Then each Segment becomes a tuple of First, Last, (redundant Length) and access to the entire string (rather than a substring).
procedure Find (Source : access String; Separator : Character;
Last : out Natural; Item_Array : out Segment_Array) is
P : Positive := Source'First;
begin
for I in Item_Array'Range loop
declare
Item : String := Separated_String_Next (Source.all, Separator, P);
begin
exit when Item'Length = 0;
...
Item_Array (I).Name := Source;
Last := I;
end;
end loop;
end;
Source : aliased String := ",,Item1,,,Item2,,Item3,,,,,,";
...
Find (Source'access, ',', Last, Item_Array);
for I in Item_Array (Item_Array'First .. Last)'Range loop
...
Put (Item_Array (I).Name(Item_Array (I).First .. Item_Array (I).Last));
New_Line;
end loop;
A helper to extract a string from a Segment would probably be useful:
function get(S : Segment) return String is
begin
return S.Name(S.First .. S.Last);
end get;
...
Put (get(Item_Array (I));
The only rationale I can see for such a design is where the set of strings to be parsed or dissected will barely fit in memory so duplication must be avoided. Perhaps also embedded programming or some such discipline where dynamic (heap) allocation is discouraged or even illegal.
I see no solution involving address arithmetic within a string, since an array is not merely its contents - if you point within it, you lose the attributes. You can make the same criticism of the equivalent C design : you can identify the start of a substring with a pointer, but you can't just stick a null terminator at the end of the substring without breaking the original string.
Given the bigger picture ... what you need, rather than the low level details of how you want to achieve it, there are probably better solutions.
I need to cheate array of events with win32 help. Language: Ada.
What I do:
p:integer := 4;
Type EvArr1 is array (1..p) of HANDLE;
procedure Start (Tid : in integer) is
Task T1;
task body T1 is
Bl:bool;
temp:Dword;
...
begin
...
Bl:=(EvArr1(Tid));
temp:=WaitForMultipleObjects(EvArr1, infinite);
...
end T1;
end start;
BEGIN
...
for i in 1..p loop
EvArr1(i) := CreateEvent(null, 1, 0, null); -- error
EvArr2(i) := CreateEvent(null, 1, 0, null); -- error
start(i);
end loop;
Error:
The types are not convertible; the operand type must be an array type, Continuing
As declared, EvArr1 is a type, not an object.
Try
EvArr1 : array (1 .. p) of HANDLE;
or
type Event_Array is array (1 .. p) of HANDLE;
EvArr1 : Event_Array;
The second is good if you want to have subprograms/entries with parameters of the type. Even better,
type Event_Array is array (Positive range <>) of HANDLE;
EvArr1 : Event_Array (1 .. p);
I was wondering if there's a way to define a type of string or similar in delphi 7 which is intended to be in a particular format, or matching certain specifications? For example, I'd like to define a TSizeString type which accepts values such as 4x6 or 9x12 or maybe even 2.5x10.75. It should require the x as the only deliminator between two numbers. So there should never be anything like x9 or 65 or 2-4 or 4-6x6-2 and not even 4 x 6.
Just INTEGER + 'x' + INTEGER or SINGLE + 'x' + SINGLE.
Similar I guess to how like a TFilename works, standard filenames may look like C:\MyPath\MyFile.txt or \\Storage\SomeDir\SomeFile.doc
In newer versions of Delphi, advanced records and operator overloading are very handy in this case:
type
TSizeString = record
x, y: single;
public
class operator Implicit(const S: string): TSizeString;
class operator Implicit(const S: TSizeString): string;
end;
implementation
class operator TSizeString.Implicit(const S: string): TSizeString;
var
DelimPos: integer;
begin
DelimPos := Pos('x', S);
if (DelimPos = 0) or (not TryStrToFloat(Copy(S, 1, DelimPos-1), result.X)) or
(not TryStrToFloat(Copy(S, DelimPos + 1), result.y)) then
raise Exception.CreateFmt('Invalid format of size string "%s".', [S]);
end;
class operator TSizeString.Implicit(const S: TSizeString): string;
begin
result := FloatToStr(S.x) + 'x' + FloatToStr(S.y);
end;
Now you can do
procedure TForm1.Button1Click(Sender: TObject);
var
S: TSizeString;
begin
S := '20x30'; // works
ShowMessage(S);
S := 'Hello World!'; // exception raised
ShowMessage(S);
end;
In older versions of Delphi, you simply have to write a class, or create a basic record to hold your size (and then, of course, you can create functions that convert between such records and formatted strings).
Special types, like TFileName and TCaption are nothing special, like Andreas mentioned, but they can be used to register a specific property editor in the IDE. This will help entering such values through the object inspector.
To really enforce such a value, if your string is a property of an object, you can write a setter for it.
Otherwise, I should make a TSize class that has properties for the two integers, and an AsString property that combines its properties to a string.
type
TSize = class
private
FLeftInt, FRightInt: Integer;
function GetString: string;
procedure SetString(Value: string);
public
property LeftInt: Integer read FLeftInt write FLeftInt;
property RightInt: Integer read FRightInt write FRightInt;
property AsString: string read GetString write SetString;
end;
function TSize.GetString: string;
begin
Result := Format('%dx%d', [FLeftInt, FRightInt]);
end;
function TSize.SetString(Value: string);
begin
// Validate and parse Value. Set LeftInt and RightInt.
end;
Simplest way is just to use a function, and always use it when defining your strings...
function MyString(numA, numB: single) : string;
begin
Result := FloatToStr(numA) + 'x' + FloatToStr(numB)
end;
If you want to get fancier, you can do it as a class which allows a direct string assignment as a property, but which parses the string for compliance.
I'd like to be able to access sections of a short string as part of a record
Something like
TMyRecord = record
case Boolean of
True:
(
EntireString: String[20];
);
False
(
StringStart: String[8];
StringMiddle: String[4];
StringEnd: String[8];
);
end;
Is this possible or would I have to declare each char individually
TMyRecord = record
private
Chars: Array[1..20] of Char;
Function GetStringStart:String;
Procedure SetStringStart(Value: String);
public
Property StringStart: String read GetStringStart write SetStringStart; // Can I have properties on a record?
end;
Function GetStringStart: String;
begin
Result := Chars[1] + Char[2]....;
end;
Procedure SetStringStart(Value: String);
begin
for i := 1 to 8 do
begin
Chars[i] := Value[i];
end;
end;
Is this possible / worth the effort?
A Delphi short string contains more than just the string contents. The initial byte in the data structure contains the length of the string. This is why short strings are limited to 255 characters.
So, you can't use short strings in your variant array the way you propose.
What you could do is adapt your second approach based on getter and setter methods to be a bit more readable.
For example:
function TMyRecord.GetStringStart: string;
begin
SetString(Result, #Chars[1], 8);
end;
You might consider using a string rather than a char array, but it's a little hard to be 100% sure of that advice without knowing exactly what your underlying problem is.
As a final thought, why not turn the problem around? Store 3 strings: StartString, MiddleString and EndString. Then have a property backed with a getter and setter called EntireString. When you read EntireString it pieces it together from the 3 individual parts, and when you write to it it pulls the individual parts out. I suspect it would be easier that way around.
Your first sample doesn't consider the length byte. The memory layout looks like this:
case True:
L12345678901234567890
^....................
case False:
L12345678L1234L12345678
^........^....^........
(L = length byte).
Depending on your requirements (e.g.: Are the partial strings always 8, 4 and 8 Chars?) I'd try storing the partial strings and make EntireString the property, using System.Copy, StrUtils.LeftStr etc.
ShortString has an implied length, so your first example will map the length parts of the substrings on top of the main string.
Your second sample is the way to start, with these notes:
properties on records are possible
you should think of the length of each sub-string (or is it always a fixed array of 20 characters?)
Edit
It totally depend on the reason you want this, and mixing character arrays and strings will get you into trouble because strings can be shorter than the array length.
Small example:
program VariantRecordsWithCharactersAndStrings;
{$APPTYPE CONSOLE}
uses
SysUtils,
Math;
const
Size20 = 20;
Size8 = 8;
Size4 = 4;
type
TChar20 = array[0..Size20-1] of Char;
TChar8 = array[0..Size8-1] of Char;
TChar4 = array[0..Size4-1] of Char;
TMyRecord = record
class var FillCharValue: Byte;
function GetEntireString: string;
function GetStringStart: string;
function GetStringMiddle: string;
function GetStringEnd: string;
procedure SetEntireString(const Value: string);
procedure SetStringStart(const Value: string);
procedure SetStringMiddle(const Value: string);
procedure SetStringEnd(const Value: string);
property EntireString: string read GetEntireString write SetEntireString;
property StringStart: string read GetStringStart write SetStringStart;
property StringMiddle: string read GetStringMiddle write SetStringMiddle;
property StringEnd: string read GetStringEnd write SetStringEnd;
procedure SetCharArray(const CharArrayPointer: PChar; const CharArraySize: Integer; const Value: string);
case Boolean of
True:
(
CharFull: TChar20;
);
False:
(
CharStart: TChar8;
CharMiddle: TChar4;
CharEnd: TChar8;
);
end;
function TMyRecord.GetEntireString: string;
begin
Result := CharFull;
end;
function TMyRecord.GetStringStart: string;
begin
Result := CharStart;
end;
function TMyRecord.GetStringMiddle: string;
begin
Result := CharMiddle;
end;
function TMyRecord.GetStringEnd: string;
begin
Result := CharEnd;
end;
procedure TMyRecord.SetEntireString(const Value: string);
begin
SetCharArray(CharFull, SizeOf(CharFull), Value);
end;
procedure TMyRecord.SetCharArray(const CharArrayPointer: PChar; const CharArraySize: Integer; const Value: string);
begin
FillChar(CharArrayPointer^, CharArraySize, FillCharValue);
Move(Value[1], CharArrayPointer^, Min(CharArraySize, SizeOf(Char)*Length(Value)));
end;
procedure TMyRecord.SetStringStart(const Value: string);
begin
SetCharArray(CharStart, SizeOf(CharStart), Value);
end;
procedure TMyRecord.SetStringMiddle(const Value: string);
begin
SetCharArray(CharMiddle, SizeOf(CharMiddle), Value);
end;
procedure TMyRecord.SetStringEnd(const Value: string);
begin
SetCharArray(CharEnd, SizeOf(CharEnd), Value);
end;
var
MyRecord: TMyRecord;
procedure Dump();
begin
Writeln(MyRecord.EntireString);
Writeln(MyRecord.StringStart);
Writeln(MyRecord.StringMiddle);
Writeln(MyRecord.StringEnd);
end;
procedure TestWithFillCharValue(const FillCharValue: Byte);
begin
Writeln('Testing with FillCharValue ', FillCharValue);
TMyRecord.FillCharValue := FillCharValue;
MyRecord.EntireString := '123456789001234567890';
Dump();
MyRecord.StringStart := 'AAA';
MyRecord.StringMiddle := 'BBB';
MyRecord.StringEnd := 'CCC';
Dump();
end;
begin
try
TestWithFillCharValue(0); // this will truncated all the sub arrays when you pass strings that are too short
TestWithFillCharValue(20); // when using Unicode, this fails even more horribly
Write('Press <Enter>');
Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
This class does more or less what you want:
it has overlapping data structures
when you assign the arrays: no problem
when you assign the strings: be aware when strings get to short
As other stated, it won't work, because the variant-sized record will add some lengths for StringStart/StringMiddle/StringEnd in the middle of the EntireString type.
You are confusing the *char type of C with the pascal shortstring type. There is an hidden character at position [0] which is the shortstring length.
You could use regular string type, then split in on purpose:
procedure StringSplit(const EntireString: string; out StringStart, StringMiddle, StringEnd: string);
begin
if length(EntireString)<>20 then
exit;
StringStart := copy(EntireString,1,8);
StringMiddle := copy(EntireString,9,4);
StringEnd := copy(EntireString,13,8);
end;
Note that the out parameter type will set all output String* variables into '' before calling the function.
This version will expect entering entire string of 20 chars long.
You could use shortstrings, but with custom types of the exact length, if you want to avoid hidden copies from/to string[255] (which occur when you use a shortstring type and work with string[n] with n<255):
type
String20 = string[20];
String4 = string[4];
String8 = string[8];
procedure StringSplit(const EntireString: String20; out StringStart: String8;
out StringMiddle: String4; out StringEnd: String8);
begin
if length(EntireString)<>20 then
exit;
StringStart := copy(EntireString,1,8);
StringMiddle := copy(EntireString,9,4);
StringEnd := copy(EntireString,13,8);
end;