Converting from C++ to Object PascalDelphi!

    技术2022-05-11  14

    Converting from C++ to Object Pascal/Delphi!==============================

    Topics covered (in order):  Overview Data types Keywords Statement terminators Declaring variables Strings Arrays Assigning and comparing values Declaring constants Functions and procedures The with ... do statement in OP Commenting Controlling program flow Object oriented structures Containers Exception handling Streaming Arrangement of project files How to make the conversion Summary  Stuff not covered

    =================================Overview: The purpose of this article is to help you understand the differences and similarities between C++ and Object Pascal (the language used in Borland's Delphi development tool), and to assist you in converting a project from C++ to Delphi.It was originally prepared as a lecture to the Windows Programming SIG (SpecialInterest Group) of the VPCUS (Vancouver PC User's Society).

     Throughout this article, Object Pascal will be referred to as "OP".

     We recently converted a C++ project (Komodo Market) to Delphi to avoid the frustration and complexity of C++ interface construction. (Komodo Market is a completestock and option trading simulation. It is edutainment, both a game and a realistic simulation where you can learn about stock, stock option, and index option trading.) Delphi was not available when we started this project, and we did not want a multi-file,interpreted language executable requiring a runtime. Since we already knew C++, it made sense at the time to use it.

     I have written a lot of programs in C++, but its usefulness is hampered by the lackof visual development tools. When you look at Komodo Market, think to yourself, "Would this have been fun to code in C++!?". Right.

     This article deals with developing windows programs that have nothing at all to do with Delphi's database development capabilities (which are superb), but the language features I discuss are applicable. (Would you ever consider developing a database application in C++? See a doctor.)

    =================================Data types

     The following chart will helps to map C++ data types to OP data types. When declaring variables, use this chart to cross-reference the conversion.

     C++              OP         Size (bytes)   Range  unsigned char    byte       1              0 to 255 unsigned int     word       2              0 to 65,535 unsigned short   word       2              0 to 65,535 unsigned long    ----       4              0 to 4,294,967,295

     char             ----       1              -128 to 127 ----             char       1              1 ASCII character int              integer    2              -32,768 to 32,767 short            ----       2              -32,768 to 32,767 ----             shortint   1              -128 to 127 long             longint    4              -2,147,483,647 to 2,147,483,647  float            single     4              3.4E-38 TO 3.4E+38 double           double     8              1.7E-308 TO 1.7E+308 long double      extended   10             3.4E-4932 TO 3.4E+4932 ----             comp       8              1.7E-308 TO 1.7E+308 (for 8087/80287) ----             real (for backwards compatibility only -- use double)  void             pointer    8              n/a -- an untyped pointer ----             boolean    1              True or False (C++ may soon have a boolean type) String           ----       a C++ standard object ----             string     an array of up to 255 ASCII characters ----             PChar      pointer to a null-terminated string 

    =================================Keywords

     C++ has 59 keywords, and OP has 60 keywords. This does not include the many vendorspecific extensions to the C++ language or preprocessor directives. OP is specific toBorland, so the notion of cross-platform compatibility is not applicable. Most C++ implementations are ANSI compatible.  C++ is case-sensitive; OP is not.    C++ asm  auto  break  case  catch  cdecl  char  class  const  const_cast  continue default  delete  do  double  dynamic_cast  else  enum  extern  far  float  for friend  goto  huge  if  inline  interrupt  int  near  new  operator  pascal  private protected  public  register  reinterpret_cast  return  short  signed  sizeof static  static_cast  struct  switch  template  this  throw  try  typedef  typeid union  unsigned  virtual  void  volatile  wchar_t  while

     OP and  as  asm  array  begin  case  class  const  constructor  destructor  div  do  downto  else  end  except  exports  file  finally  for  function  goto  if  implementation  in  inherited  inline  initialization  interface  is  label  library  mod  nil  not  object  of  or  packed  procedure  program  property  raise  record  repeat  set  shl shr  string  then  to  try  type  unit  until  uses  var  while  with  xor

    =================================Statement terminators

     C++ Most statements are terminated with a semi-colon ;  There are a couple exceptions... #include <owl/owlpch.h>     // doesn't end with a ; #define MAXNAMELENGTH 35    // also doesn't end with a semi-colon ;  OP All statements end with a semi-colon ;

    =================================Declaring variables

     C++ Maximum identifier length is 32 characters -- can be longer, but only the first 32 are recognized. Variables can be declared anywhere in code, and variables do not all have to be declared at the same location (but must be declared before use, of course)

     // ... anywhere in code ... char sName[10]; int iCount, iLoop, iValToReach; double dMaxLoan, dTotal; float fMaxRate = 123.875; // notice you can assign a value when declaring  OP Maximum identifier length is 63 characters -- can be longer, but only the first 63 are recognized. Variables must be declared in "var" code block at the start of a procedure or function or in an object definition before procedure and function declarations. Values cannot be assigned in the var code block.

     function PayBack_d(var dAmount: double): double; var  iCount, iLoop, iValToReach: integer;  dMaxLoan, dTotal, dMaxRate: double; begin  dMaxRate := 123.875;  {...}  

    =================================Strings

     C++ There is a string object in the standard C++ library now. Unfortunately, it is  not compatible with the more commonly used null-terminated character array. (The  null character is "/0".) Because most strings are character arrays, they can be an unlimited length. This is how character arrays are declared... char sName[26]; // 25 chars plus null char psDemo[] = "Hello, there!";  char * psDemo = new char[26];  The two things you will do most with a string/character array are copy into and concatenate (add to) the string. The only thing you must make sure of is that the array you are copying/concatenating to is large enough to hold the final result. Here is an example of character arrays, copying, and concatenating...

     class Ticker { ... public: // notice colon  char sBuf[10],       sLatestTrade[TRADELENGTHBUF],       saTradeRecords[50] [TRADELENGTHBUF];  ...  void OptnFormat2_v(unsigned long & ulQuantity,                     CompanyC * poC,                     int & iSeries);  ... }; ... void TickerC::OptnFormat2_v(unsigned long & ulQuantity,                             CompanyC * poC,                             int & iSeries) {  ultoa(ulQuantity, sBuf, 10);  strcpy(sLatestTrade, sBuf);  AddMosToString_v(sLatestTrade,        poC->oSOS.oSeries[iSeries].oExpDate.enMonth);  itoa(poC->oSOS.oSeries[iSeries].oExpDate.iDay, sBuf, 10);  strcat(sLatestTrade, sBuf);  strcat(sLatestTrade, poC->oS.sTicker);  double dStrike = poC->oSOS.oSeries[iSeries].dStrike;  gcvt(dStrike, 3, sBuf);  strcat(sLatestTrade, sBuf);  if(poC->oSOS.oSeries[iSeries].enCallPut == Call)   strcat(sLatestTrade, "Calls");  else strcat(sLatestTrade, "Puts"); } // end TickerC::OptnFormat2_v   OP String is a valid data type that can hold up to 255 chars (unlimited in Delphi 2). Strings in OP are 1 byte bigger than the size you declare. That's because strings are still character arrays in OP, and the first element, position [0], contains the size of the array. Also, you delimit strings between SINGLE quotation marks... var  sMyName: string[25]; {only 25 chars in this string}  sYourName: string; {up to 255 because size not specified} begin  sMyName := 'Paul Fulford'; {notice SINGLE quotation marks}   In OP, you can copy to a string variable with the assignment operator (:=), and concatenate with the + plus sign. Here is the same example shown above using OP...

     TickerC = class  ...  public {notice no colon}  sLatestTrade: string[TRADELENGTHBUF];  saTradeRecords: TStringList;  ...  procedure TickerC.OptnFormat2(var lQuantity: longint;                               poC: CompanyC;                               var iSeries: integer);  ... end; ... procedure TickerC.OptnFormat2(var lQuantity: longint;                            poC: CompanyC;                            var iSeries: integer); begin   sLatestTrade := IntToStr(lQuantity);  AddMosToString(sLatestTrade,       poC.oSOS.oSeries[iSeries].oExpDate.enMonth);    sLatestTrade := sLatestTrade +                    IntToStr(poC.oSOS.oSeries[iSeries].oExpDate.iDay) +                  poC.oS.sTicker +                  FloatToStr(poC.oSOS.oSeries[iSeries].dStrike);  if poC.oSOS.oSeries[iSeries].enCallPut = Call then   sLatestTrade := sLatestTrade + 'Calls'  else sLatestTrade := sLatestTrade + 'Puts'; end; {OptnFormat2}   If you take a close look (you will, won't you?), you'll see that the OP way is much easier. The difference between C++ character arrays and OP strings is in the use of element [0]. In OP, it contains the string size. In C++ the array is read until the "/0" null character is reached, and element [0] contains the first character you want.

    =================================Arrays

     Arrays are ordered sequences of one data type (can include objects, too). The methods for declaring an array in C++ and OP are different, but individual array elements are accessed the same way.  C++ Arrays are "zero-based" -- the first element is element [0], the second element is element [1], third is [2], etc. This is always confusing for beginners. // declare arrays... DateC aoCANHolidays[SIZE_HOLIDAYARRAY]; double dAverageLast31Days[31];  // use array... for(int i = 30, j = 29; i > 0; i--,j--)  dAverageLast31Days[i] = dAverageLast31Days[j];  OP Arrays start at element [1] ... well, not always. In Delphi, you will find some components and objects that are still zero-based as in C++. (TList.Items is one of those arrays that is zero-based. It is discussed in the section on containers.) We just have to pay attention to the documentation. Any array that you declare, though, will be one-based. var  aoCANHolidays: array[1..SIZE_HOLIDAYARRAY] of DateC;  dAverageLast31Days: array[1..31] of double;  i,j: integer; begin  j := 30;  for i := 31 downto 2 do  begin   dAverageLast31Days[i] = dAverageLast31Days[j];   Dec(j); { or j := j-1; }  end;     Both languages support multi-dimensional arrays...  C++ double dMatrix[50] [20];  OP var  dMatrix: array[1..50, 1..20] of double;  =================================Assigning and comparing values

     C++ Compare values using == double equal sign. Assign values using = single equal sign. ie: if (dMaxRate == 15.75) { ...  dMaxRate = 12.5; ... }  OP Compare values using = single equal sign. Assign values using := colon equal sign. ie: if dMaxRate = 15.75 then begin ...  dMaxRate := 12.5; ... end;   An exception to the rule in OP is assigning values to a constant and declaring classes. Use = single equal sign.

    =================================Declaring constants

     Constants are values that do not change (no kidding). An identifier can be declared a constant in both C++ and OP. Both C++ and OP constants must be assigned a value when declared.

     C++ Declare constants by preceding the data type with the keyword "const".  ie: const int iMax = 2000; const double dMaxValue = 1234.75;  In C++, you can also declare constants with the #define preprocessor directive like this... #define nMAXAMOUNT 1000 ... but this practice is growing obsolete because type checking cannot be performed (the compiler doesn't know if it's a double or an int in this example).  OP Constants, like variables, must be declared in a "const" code block at the start of a procedure or function definition or in an object declaration. ie: function PayBack_d(var dAmount: double): double; const  iMAX = 2000; {notice value assigned with single equal sign this time}  dMAXVALUE = 1234.75; var  iCount, iLoop, iValToReach: integer;  dMaxLoan, dTotal, dMaxRate: double; begin  dMaxRate := dMAXVALUE;  {...}

    =================================Functions and procedures

     Code blocks that perform a specific task in C++ are called "functions" regardless of whether or not they return a value. In OP, functions must return a value, and procedures do not return a value. In C++, all functions must be prototyped/declaredbefore being defined (so the compiler can compare both to ensure consistancy). In OP, a function or procedure definition can omit (but does not have to) the parameter list and omit the return type. My preference is to exactly copy the declarationstatement to the definition block so I don't have to scroll back and forth if I forget the parameters or return type. C++ function declarations and definitions require braces() regardless of whether or not there are any parameters. OP functions and procedures do not need braces() when declaring or defining them if there are no parameters to pass to the function or procedure.

     C++ ie: double IntSinceLastAddDate_d(double &dAvailCash); // prototype void SetNewIntRate(); // no parameters or return value ... double LoanC::IntSinceLastAddDate_d(double &dAvailCash) {  double dSomething;  ...  return dSomething; }  void LoanC::SetNewIntRate() { ... }   OP Each function and procedure must be identified as such by including the keyword "function" or "procedure" at the start of each. ie: function IntSinceLastAddDate_d(var dAvailCash: double): double; procedure SetNewIntRate; {no parameters or return value} ... function LoanC.IntSinceLastAddDate_d(var dAvailCash: double): double; var  dSomething: double; begin  ...  result := dSomething;   {the global variable "result" is assigned the return value!} end;  procedure LoanC.SetNewIntRate; begin ... end;   Both C++ and OP can pass parameters by value or by reference or pass constants...  C++ pass by value ... double IntSinceLastAddDate_d(double dAvailCash); OP pass by value ... function IntSinceLastAddDate_d(dAvailCash: double): double;

     C++ pass by reference ... double IntSinceLastAddDate_d(double &dAvailCash); OP pass by reference ... function IntSinceLastAddDate_d(var dAvailCash: double): double;

     C++ pass constant ... double IntSinceLastAddDate_d(const double dAvailCash); OP pass constant ... function IntSinceLastAddDate_d(const dAvailCash: double): double;

     Both C++ and OP allow function/procedure overloading as long as their signatures are different.

    =================================The with ... do statement in OP

     Generally, C++ is more compact than OP. But, C++ does not have a with ... do statement. That's unfortunate because it really is a great feature of OP. Consequently, even with copying and pasting, C++ code can be more verbose in spots.

     In C++, when you have to access data members, you end up doing something like this...   poC.oStock.aoTradesLast130Days[0].lVol = 0;  poC.oStock.aoTradesLast130Days[0].dHigh = 0;  poC.oStock.aoTradesLast130Days[0].dLow = 0;  poC.oStock.aoTradesLast130Days[0].dClose = 0;  But in OP, you can make it much more readable by doing this...   with poC.oStock.aoTradesLast130Days[0] do  begin   lVol := 0;   dHigh := 0;   dLow := 0;   dClose := 0;  end;  A little easier to read, don't you think?

    =================================Commenting

     C++ There are two ways to comment in C++ ... // anything after a double slash is a comment to the end of that line /* anything between a slash star and star slash is a comment no matter where it occurs */  OP In Delphi V1, comments are either enclose between {braces} or (*parenthesis star combinations*). This will require careful attention when converting because bracesin C++ are used to delimit code blocks. { this is a comment in OP } (* so is this *) In Delphi V2, you will also be able to use the double slash // just like in C++ now.

    =================================Controlling program flow

     There are five controlling structures in both languages, and they are quite similar. This will take a fair bit of room to review.

    ~~~~~~~~~~~~~~~~~~~~~~~~~1) The if ... else conditional construct:  C++  if(<logical expression>) // notice expression is enclosed in braces { ... } else if(<logical expression>) {... } else {... }  OP  if <logical expression> then  begin  {single expressions do not have to be enclosed in   braces. Also notice the "then" following the expression.} ... end else if <logical expression> then begin .... end else begin ... end; {only the very last "end" in the sequence terminates with a semi-colon.} 

    ~~~~~~~~~~~~~~~~~~~~~~~~~2) The switch/case ... conditional construct:

     C++  switch(<integer constant>) {  case iX: ... break;  case iY: ... break;  default: ... }  OP  case <integer, var or const> of  {no "begin" here}  iX:   begin  ...  end; {semi-colon after each end;}  iY:  begin  ...  end;  else {no colon here}  begin  ...  end; end; {and an "end;" here}  ~~~~~~~~~~~~~~~~~~~~~~~~~3) The for ... loop construct:

     C++  for(iCount = 0; iCount <= 10; iCount++) {   // the increment clause of the for loop, iCount++, can be incremented  // by any amount, not just 1 as shown. You can also decrement the   // the loop counter ...  break; // to escape  continue; // to immediately loop ... }   OP  for iCount := 1 to 10 do begin ... {for loops can only increment by 1. To decrement use the 'downto' keyword.}  break; { to escape }  continue; { to immediately loop } ... end;  There is one difference between the "break" and "continue" implementations in the languages. In C++, break and continue are keywords -- a part of the language; in OP, however, they are library procedures. Same usage as far as we're concerned, though.

    ~~~~~~~~~~~~~~~~~~~~~~~~~4) The while ... loop construct:

     C++  while(<logical expression>)  {  // expression is tested at start of loop, so code may never run if  // expression evaluates to false ...  break; // to escape  continue; // to immediately loop ... }   OP  while <logical expression> do begin  { expression is tested at start of loop, so code may never run if    expression evaluates to false } ...  break; { to escape }  continue; { to immediately loop } ... end;

    ~~~~~~~~~~~~~~~~~~~~~~~~~5) The do/repeat ... loop construct:

     C++  do {  // expression is tested at end of loop, so code always runs at least once ...  break; // to escape  continue; // to immediately loop ... }while(<logical expression>);   OP  repeat  { expression is tested at end of loop, so code always runs at least once.     also notice also there is no begin ... end delimeters in this type of loop } ...  break; { to escape }  continue; { to immediately loop } ... until <logical expression>;

    =================================

    Object oriented structures

     Both languages call their object structures "classes". C++ handles multiple inheritance, but OP only handles single inheritance.

     Let's look at the basic syntax for creating classes...  C++ Classes are declared in a header file... class LoanC // no inheritance shown here {  private: // private by default  ...  protected: // notice colons after keywords  ...  public:  ... }; //semi-colon terminated  ... the syntax for single inheritance in C++ is ... class B: A {...};  ... and for multiple inheritance in C++ ... class D: A, B, C {...};  Those classes that are inherited in C++ can also be identified as public,  protected, or private. The default is private unless otherwise specified... class D: public A, public B, private C {...};  OP Classes are created in a unit/file's interface "type" code block... type LoanC = class(TObject)   {This shows inheritance from TObject. TObject is implicitly inherited --   you don't have to specify it -- but the syntax to inherit from some other   object is the same}  {Notice no "begin" statement. }  {Notice class declared with single equal sign}    private  ...  protected {no colon after keywords}  ...  public  ...  published  ... end;

     Those keywords private, protected, public, and published (OP only) specify the "scope" of each data member and function. Scope tells the compilers what parts of the program and derived classes (if any) can use the data members and functions declared in this class. Generally, private data members and functions can only be used by other functions in this class. Protected data members and functions can be used by this class and derived classes. Public data members and functions can be used anywhere in the program. Published data members are Delphi specific -- these are the attibutes of the components you use to build interfaces.

     The default is private in C++ classes, public in OP classes. In OP, identifiers in the component list after the class header are public.   Every class requires a constructor. A constructor is a class member function whose job it is to initialize the data members of an object just created in memory. You can have many constructors in both languages.

     In C++, constructors have the same name as the class. You can "overload" (have more than one) constructors by declaring the constructors with different parameter lists. ie:  LoanC(); // constructor with no parameters  LoanC(double &dCurrentBal); // one parameter  LoanC(double &dBalOne, double &dBalTwo); // two parameter, etc...   In OP, constructors have different names. You identify the procedure as a constructor by preceding the constructor declaration with the keyword "constructor". ie:  constructor MyLoanOne; {no parameters}  constructor MyLoanTwo(var dCurrentBal: double);  constructor MyLoanThree(var dBalOne, dBalTwo: double);   Each language also supports "destructors" which are functions that are called to free any memory allocated by the constructors and do other chores before the object is freed from memory.

     Destructors in C++, like their constructors, have the same name as their class, and are identified by preceding the class name with a tilde...  ~LoanC(); // C++ destructor   You can have different destructors in OP, but Borland recommends you simply override the inherited TObject's "Destroy" destructor like this...  destructor Destroy; override; {"override" is a directive}... and then in the destructor's definition, call TObject's Destroy after doing your own clean-up...  destructor LoanC.Destroy;  begin   oLoanDate.Free;   ...   inherited Destroy; {"inherited" is a keyword}  end;  To create a new object in C++, just declare a variable of that type...  double dAmount = 1515.75;  LoanC oMyLoan(dAmount); ... this actually allocates memory and creates the object in memory. Or, in C++, using a pointer, do this...  double dAmount = 1515.75;  LoanC * poMyLoan = new LoanC(dAmount); ... which means you'll have to access the new loan class using the poMyLoan-> pointer.

     In OP, it's different. Everything is a pointer, so simply declaring an object in a var block doesn't actually allocate any memory/create the object.   var   dAmount: double;   oMyLoan: LoanC;  begin   {oMyLoan does not yet exist!}   dAmount := 1515.75;   oMyLoan := LoanC.MyLoanTwo(dAmount); {now it does}    In OP, you can also override the default "Create" constructor like this...  type   LoanC = class   ...    constructor Create; {overrides TObject's Create}   ...   end;  ... and call the inherited Create constructor in the LoanC definition...  constructor LoanC.Create;  begin   inherited Create; {calls TObject's Create}   ...  end;   In both C++ and OP, you access an object's data members and functions/procedures using dot notation...  oMyLoan.dPrincipal; In C++, if you are using a pointer, you would access the object using the indirect selector operator ->  poMyLoan->dPrincipal;   In C++, there are three operators used to work with objects and other data types allocated in memory: 1) the & referencing/address-of operator, 2) the * dereferencing operator, 3) the -> indirect selector operator. Every object in OP is a pointer (as explained earlier), but we let the compiler worry about the details. This makes OP much easier, and we can stick to using dot notation.  

    =================================Containers

     Containers are special class objects that are used to store data. Think of containers as database classes and/or classes that are intended to store information that simple arrays cannot. Containers in C++ are implemented through templates. You tell the template what kind of object the container will be storing. In addition to the container itself, you also (are supposed to) implement an iterator template based on the type of object in the container. You also have to overload the == comparison operator, write a default constructor, and if you want the container sorted then overload the < less than operator to facilitate sorting. Sounds easy, huh?! Of course, each vendor has their own library of container templates, so until recently there was no industry-wide standard. That has changed recently with the distribution of the "STL" Standard Template Library which many vendors are finally releasing with their compilers. The STL was originally developed at Hewlett-Packard, but is now a set by the ANSI/ISO C++ Standards Committee. The STL was originally accepted as part of the C++ Standard Library in July, 1994, but its taken a while to get out into the various compilers. Nonetheless, the syntax for implementing a template-based container is downright bizarre. Unlike the OP technique (which you will see), C++ templates are still overly complex. I have not used the STL, but I have seen enough to know it's still complex. Here is an example of a BIDS container implementation...

     typedef TISetAsVector<CompanyC> tdCompanySet; typedef TISetAsVectorIterator<CompanyC> tdCSetIter; ... int OwlMain(int, char*[]) {...  tdCompanySet oCompColl; ... } ... void MainForm::AddNewCompany() {  CompanyC * poC = new CompanyC;  oCompColl.Add(new CompanyC(poC));  ...  // now iterate  tdCSetIter oCIter(oCompColl);  while(oCIter)  {   poC = oCIter++;   // now use poC   ...  } }  Containers in OP are easily implemented. Simply use a TList object to "contain" a list of objects like this... TMainForm = class(TForm) ... public  oCompColl: TList;  ... end; ... procedure TMainForm.AddNewCompany; var  poC: CompanyC;  iCount: integer; begin  poC := CompanyC.Create;  ...  oCompColl.Add(poC);  ...  {now iterate}  for iCount := 0 to oCompColl.Count - 1 do  begin   poC := oCompColl.Items[iCount];   {now use poC}   ...  end;  Since all objects in OP have TObject as a base class, it's easy for Borland to implement a generic container like TList. The idea is both good and bad. It does limit you to containing objects that are inherited from TObject -- that's not much good if you want a bag of doubles. (A "bag" is a special type of vector container.) In this case, you'd have to create some sort of doubles class based on TObject, which is nonsense. (Just create an array of doubles and iterate over the array some custom way.) Furthermore, I can hear the howls already: "But what about sets, and hash tables, binary trees, linked lists, and deques!" Yes, yes, a TObject/TList is pretty simple. And it isn't sorted (but some components in Delphi will sort!). Sure, there will be times when it won't do, but they worked for me in Komodo Market, and with a lot less pain and frustration. Yes, I had to write my own sorting algorithms. Yes, yes, yes, leave me alone. The TList syntax is so much easier, that C++ containers are unnecessary for all but special circumstances. I know, I've used both. =================================Exception handling

     An exception is a runtime error. Error information is passed to your program in an error object. There are different types of error objects depending on the type of exception (makes sense). However, some programs have so many runtime errors, they are the rule, not the exception (har, har ... hem).

     C++ try { ... } catch(<TypeOfException>) { ... } catch(...) // dot dot dot catches any type of exception { ... }  You can also cause an exception in C++ using the "throw" keyword...  throw <TypeOfException>;     OP try  {no begin ... end; statements} ... finally  {release/clean up memory statements -- error handling, not really  'exception handling'. This code always executes regardless of whether  or not an error occurs. C++ has no equivalent.} ... end;  To capture specific runtime errors in OP, the syntax is... try ... except  on <TypeOfException> do ... ;   {this code only executes if there is an exception.} end;  You can also cause an exception in OP using the "raise" keyword...  raise <ExceptionObject>;      =================================Streaming

     In C++, you override the << (put to/insertion) and >> (get from/extraction) operators passing them a stream object. It's easier in OP, but Borland didn't haveany examples in their documentation, so I'll show you how to stream out to a binaryfile (not a text file).

     C++ class CompanyC { ...  friend ofstream & operator <<(ofstream &oS, CompanyC * poC); ... }; ... friend ofstream & operator <<(ofstream &oS, CompanyC * poC) {  oS << poC->dVersion     << poC->dLastDivPerShare     << poC->enIndustry     ...     << poC->sName;  return oS; }

     ... or, in this example, if the CompanyC object had no internal pointer objects, you could shorten the whole thing to this...

     friend ofstream & operator <<(ofstream &oS, CompanyC * poC) {  oS.write( (char*) & (*poC), sizeof(*poC));  return oS; }

     ...easy, huh?! You would do the same with the >> operator.  Now open and use a file like this...  // open a stream ofstream ofS("gamedata.gam"); if(!ofS)   return; ... // save info to file tdCSetIter oCIter(oCompColl); // see container section while(oCIter) {  poC = oCIter;  ofS << poC; } ofS.close();   OP type  CompanyC = class  public   procedure Read(var f:file);   procedure Write(var f:file);   ...  end;  ...  procedure CompanyC.Write(var f:file);  begin   BlockWrite(f, dVersion, sizeof(dVersion));   BlockWrite(f, dLastDivPerShare, sizeof(dLastDivPerShare));   BlockWrite(f, enIndustry, sizeof(enIndustry));   ...   BlockWrite(f, sName, sizeof(sName));  end;   Now open and use a file like this...  procedure TMainForm.FileSaveItemClick(Sender: TObject); var  oGameFile: file;  iCount: integer;  poC: CompanyC;  sNameFile: string[13]; begin  ...  sNameFile := 'gamedata.gam';  AssignFile(oGameFile, sFileName);  Rewrite(oGameFile, 1); {the 1 means 1 byte at a time}  ...  for iCount := 0 to oCompColl.Count - 1 do  begin   poC := oCompColl.Items[iCount];   poC.Write(oGameFile);  end;  CloseFile(oGameFile); end;  

    =================================Arrangement of project files

     In C++, you typically put all your data type defines, constants, and class declarations in a header file with the .h extention. Program implementation  and class function definitions are done in .cpp files. A .cpp file uses the #include directive to find a .h file. In Delphi's implementation of OP, one file is used for both declarations and implementations. Files are called "units", and have a .pas extention. Each unit contains an "interface" section where data types, constants, and global variables (to the unit) are declared, and an "implementation" section where class functions are defined. For one unit in OP to use classes found in another unit, the program adds a "uses" clause to either the interface or the implementation section (sometimes to both). There is nothing in C++ forcing you to use the .h and .cpp method -- that's just common. You could put your declarations at the start of any .cpp file and all would be fine unless you encounter a "circular" reference where one file #include(s) another file which #include(s) the original file. This is why, in OP, you might have a "uses" clause referring to some files in the interface and another "uses" clause referring to other files in the implementation section. In OP, each form has a .pas file for your code and a binary .dfm file that contains form details. (.dfm := 'Delphi form' -- get it?!) 

    =================================How to make the conversion

      You will have more difficulty converting to Delphi if your interface code is mixed in with your non-interface code. In that case, you may just want to create the  Delphi interface forms as shells, and then copy in your C++ non-interface code to appropriate event handlers, and then follow this advice.  Don't do this on the originals of your C++ files! Use copies! Open your C++ files in Delphi, and use the Delphi editor to do the following...

     1) Do a global replace of all C++ {...} code blocks delimiters with OP begin...end; code block keywords. This is easier if your C++ syntax was like this... double CompanyC::NewAnnualReport_v(Economy & oE) { ... // delimiter starts on a new line }  ...versus this... double CompanyC::NewAnnualReport_v(Economy & oE){ // delimiter at end of line ...  }  2) a) Globally replace all C++ || or operators with OP "or" keyword.    b) Globally replace all C++ && and operators with OP "and" keyword.    c) Globally replace all C++ = assignment operators with OP := operator. Make sure you specify "Whole words only" because you don't want to replace C++ == comparison operators with two OP :=:= operators. You shouldn't have any single = operators after this.  Then replace C++ == with OP = operator.    d) Globally replace all C++ /* */ comment delimiters with OP { } comment delimiters.    e) You can do a global replace of all C++ // comment delimiters with the OP { start comment delimiter, but you'll have to manually add the closing OP } comment delimiter yourself. If you're already working in Delphi 2.0, you can leave your // comments.       f) Globally replace double quotation "..." marks used in C++ with single '...' quotation marks in OP.     3) At the end of every "if" C++ statement, you'll have to add the OP keyword "then". Also, multiple conditional expressions in C++ can be separated by the || operator or by the && and operator without braces surrounding each condition. In OP, you have to surround each conditional expression with braces. In C++, one set of braces surround all conditions.

     C++ if(oE.enDirection == Up &&    oE.uNumMosUpYr >= oE.uNumMosDownYr) { ... }     OP if (oE.enDirection = Up) and    (oE.iNumMosUpYr >= oE.iNumMosDownYr) then begin ... end;  4) Go through your code making other controlling structure changes as required.  5) C++ overloaded operators will have to be re-written as class functions (or procedures, depending on how you want to implement the code) in OP.

     6) The C++ scope resolution operator :: can be replaced with the . dot operator at every function or procedure definition. Function and procedure definitions in OP also end with a ; semi-colon.

     procedure TForm1.Memo1Change(Sender: TObject); {see semi-colon here} begin ... end;

     7) Replace all C++ increment ++ and decrement -- operators with the OP inc(iX) and dec(iX) procedures if iX is of an integer type. Otherwise, you'll have to do the more verbose iX := iX + 1.875;  One other thing. The location of the ++ and -- operators has an effect on your code in C++. Those operators can be pre-fixed or post-fixed to increment or decrement the variable either before or after the variable is used. So watch where you put your OP replacement code.

     8) You have to create a "var" code block in OP, and move all your variable declarations to the var block. Replace all the C++ data types with the appropriate OP data types. And since you can't initialize any variables in a var block, you'll have to re-initialize them somewhere in the begin ... end; code block (before being used, of course).

     9) Add the OP keywords "function" or "procedure" before those C++ functions that either return a value or don't return any value.

      10) All those pointers in C++ have got to go. Replace -> with the . dot operator for class member access. Furthermore, simply declaring an object variable in OP does not allocate memory for it. While this... DateC oBirthDate; ... would work in C++, you have to do this... var  oBirthDate: DateC; begin  oBirthDate := DateC.Create; ... in OP because every object identifier is a pointer. But in OP, we use dot notation versus the C++ -> indirect member selector operator.

     11) All the C++ strcpy(...) and strcat(...) functions can be replaced with OP assignment (:=) operator and concatenate (plus sign +) operator respectively. There are other functions that you'll have to convert, too, but overall it's not too hard.  12) What to do about multiple inheritance. I suggest, for example, having one class inherit from the base, then the third class inherit from the second, etc. So class B would inherit from class A, and then class C would inherit from class B. If you have many classes being inherited into another class, it would seem to me that the design is lacking, and you should re-think things.

    =================================Summary

     C++ is more of a shorthand language, and OP is more english-like. Therefore, C++ is more compact, but it is harder to read. You'll have to do more typing in OP (even considering the with ... do statement).

     C++ creates copy constructors automatically and allows multiple inheritance. C++ allows operator overloading and comes with ++ increment and -- decrement operators.

     But all that doesn't matter because the GUI form builder in OP will save you so much time that it puts C++ out of the picture. C++ development tools have all the event handlers built in, and it's not that hard to add code to them, but you can't see your interface as you build it -- and that's the key. In C++, whenever you want to adjust your interface objects to new positions, it's a nightmare. Not so in Delphi.

     Furthermore, trying to read someone else's C++ code is almost impossible. Every C++ programmer comes up with their own shortcuts to prove their programming prowess. Someone else's OP code is much easier to read.

     Another point is the compile and link time between compilers. The difference in compile and link times between C++ and OP is astounding. I can compile and test my OP programs in a fraction of the time my C++ compiler takes. This makes for true RAD development. (May have something to do with the way parameters are parsed.)

     So why not use another development tool such as Visual Basic? VB is not object- oriented, doesn't compile to a standalone executable, and its programs run slower.

    =================================Stuff not covered 1) C++ copy constructors vs OP Assign(...) procedure 2) virtual functions to allow polymorphic behavior 3) pointers, typecasting, and how to allocate global memory (heap memory) 4) C++ structs vs OP record objects 5) using null-terminated strings in OP. Yes, you still need them sometimes. The Windows APIs all require null-terminated strings, so Delphi programmers should become familiar with them. Delphi 2 has an interesting (undocumented) way of handling its newstrings (hint: they are null-terminated, but the compiler handles the details). 6) C++ bitwise operators vs OP subranges, enumerations, and sets. I refer you to John O'Connell's article in the November, 1995 Delphi Informant magazine. John covers Pascal subranges, enumerations, and sets. 7) text streaming: input and output in text files. There are plenty of examples supplied by C++ vendors and by Borland in the Delphi documentation. 8) C++ escape sequences and their OP #<ASCII char> equivalents. In C++, a carriage return is represented by "/n". OP allows you to specify such non-printing characters by concatenating the ASCII value into a string using the pound sign and the ASCII value of the character you need. The carriage return in OP is #13. The bell is #7. There are others. (256 others, from 0 to 255, if you include the printing characters!) 9) OP allows nested local functions/procedures declared in a block after the start of a definition and the 'begin' keyword of the function/procedure itself. This is kind of cool. 10) limitations to multi-dimemsional arrays.  Whew.

    ============END==============

     Yes, the program displaying this article was written in Delphi. It took about 15 minutes.

     This article is copyrighted (c) 1996, 1997 by Paul Fulford. It may be distributed freely as long as I'm given credit as the author, and as long as you don't charge for it. Please let me know what I've missed and/or any mistakes.

     Komodo Software specializes in FoxPro, Paradox, and Delphi (and, okay, C++, but I'mtrying to get away from that). I can be reached at...

      Komodo Software  Box 108, 141-6200 McKay Avenue  Burnaby, B.C.  Canada, V5H 4M9   


    最新回复(0)