The real values \u200b\u200bof the parameters in Delphi are given. Structural data types. An integer data type in Delphi is represented
Non-obvious features of real numbers To take on this article I prompted the questions from time to time questions at a round table caused by the misunderstanding of the internal representation of real numbers. Once a description of the internal representation of such numbers was an integral part of any serious programming book, but now the authors have more interesting items for discussion: COM / DCOM, ActiveX, OLE and much more. On real numbers simply lacking space. And people who have started programming with Delphi and have no experience in older media, often turn out to be completely helpless in front of the incomprehensible behavior of a program containing fractional calculations. I hope my article will overcome the light on these questions and make the behavior of fractions more predictable.
Binary fractions |
Types of Delphi |
|
Infinite fractions |
Example FIRST - "Incorrect value"
So, write this code: VAR R: Single; Begin R: \u003d 0.1; Label1.caption: \u003d Floattostr (R) END; What do we see when you click the button? Of course, not "0.1", otherwise it would not make sense to write this example. We will see "0.100000001490116". That is, the discrepancy in the ninth meaning figure. Well, from the help of Delphi, we know that the accuracy of the type of Single is 7-8 decimal discharges, so that at least no one is deceiving. What is the reason? Just the number 0.1 is not representable in the form of a finite binary fraction, it is 0.0 (0011). And this endless binary fraction will be engaged on the 24th signs; We do not get 0.1, but some approximate number (which is exactly see above). And if we assign a variable R not 0.1, and 0.5? Then we get on the screen 0.5, because 0.5 is represented in the form of a finite binary fraction. Slightly experimenting with different numbers, we note that those numbers that are expressed in the form of M / 2 N, where M, N are some integers (of course, n should not exceed 24, otherwise we do not have enough accuracy of type Single) . As an exercise, I propose to prove that any integer for a record of which is enough of the 24-y binary discharges, can be exactly transmitted by the Single type.
Example Second - Comparison
Now change the code like this: VAR R: Single; Begin R: \u003d 0.1; IF R \u003d 0.1 Then label1.caption: \u003d "equal" else label1.caption: \u003d "Not equal to" END; When you click the button, we will see the inscription "not equal." At first glance, it seems absurd. Indeed, we already know that the variable R gets the value of 0.100000001490116 instead of 0.1. But "0.1" in the right part of equality should also be transformed according to the same laws, because everything is predetermined on the computer. It is time to remember that Intel processors work only with a 10-byte type of extended, therefore the left, and the right side of equality is first converted to this type, and only then a comparison is made. Then the root number, which turned out to be in the variable R instead of 0.1, although it looks scary, but it seems in the form of a finite binary fraction. The information about the fact that it actually should mean "0.1", has not survived anywhere. When converting this number in Extended junior, redundant compared to the Single type of mantissa discharges are simply filled with zeros, and we will again get the same number, just recorded in the extended format. And "0.1" from the right side of equality is converted to extended without intermediate transformation into Single. A 0.1 is infinite in binary representation. Therefore, some of the younger discharges of the Mantissa will contain units. In other words, we will get although not exact representation of the number of 0.1, but still closer to the truth than 0.100000001490116. Because of such cunning transformations, it turns out that we compare two close, but still not equal numbers. From here - a natural result in the form of inscriptions "not equal." This is appropriate an analogy with decimal fractions. Suppose in one case we divide 1 to three with an accuracy of three characters, and we get 0.333. Then we divide 1 to three with an accuracy of four characters, and we get 0.3333. Now we want to compare these two numbers. To do this, give them accuracy of four categories. It turns out that we compare 0.3330 and 0.3333. Obviously, these are different numbers. If you try to replace the number 0.1 at 0.5, then we will get "equal." I think you already know why, but for the completeness of the text I will explain. 0.5 is the ultimate binary fraction. With direct bringing it to the type of extended in junior discharges are zeros. Exactly the same zeros are in these discharges when transforming a number of 0.5 type Single in the Extended type. Therefore, as a result, we compare two numbers. It seems like if we divided 1 to 4 with an accuracy of three to four significant digits. In the first case, they would receive 0.250, in the second - 0.2500. Lowing them both to accuracy in four signs, we obtain a comparison of 0.2500 and 0.2500. Obviously, these numbers are equal.
Example Third - Comparison of different types
A little complicate our example: VAR R1: Single; R2: Double; Begin R1: \u003d 0.1; R2: \u003d 0.1; IF R1 \u003d R2 Then label1.caption: \u003d "equal" else label1.caption: \u003d "not equal to" END; Scientists with bitter experience, you probably expect to see the inscription "not equal." Well, life will not disappoint you, this is what you will see. Double type is more accurate than Single (although its accuracy is also not enough to represent infinite fractions). In R2 we will receive not 0.100000001490116, but another number, with an accuracy of 15-16 decimal signs. I can't call exactly this number because Floattostr perceives it as 0.1, so, replacing the Single in the first example on a double, you will see 0.1 (only do not decease, it's not 0.1, just a function Floattostr has such a feature of work) . The numbers in both variables are given to the type extended, but at the same time they do not change and, as they were not equal, and remain unequal. This reminds the situation when we compare 0.333 and 0.3333, leading them to exactly five characters: numbers 0.33300 and 0.33330 are not equal. I already embarrass you to bother you with such obvious remarks, but still: if in this example, replaced 0.1 at 0.5, we will see "equal."
Example fourth - subtraction in the cycle
Consider another example illustrating the situation that often puzzles the novice programmer VAR R: Single; I: integer; Begin R: \u003d 1; For i: \u003d 1 to 10 DO R: \u003d R-0.1; Label1.caption: \u003d Floattostr (R) END; Of course, if as a result of the execution of this example, you would see zero, I would not spend time at him. But on the screen it will appear -7.3015691270939E-8. I think this turnover does not surprise anyone. We already know about the fact that the number 0.1 cannot be transmitted exactly in any of the real types, and the Single conversion in extended and back. At the same time rounding is constantly occur, and these roundings lead to what we get as a result not zero, but "almost zero".
Example Fifth - Surrupiriz from Microsoft
Change in the previous example, the type of R variable with Single on Double. The value displaced by the program will be 1.44327637948555E-16. It is a logical and predictable result, since the type of double is more accurate than Single and, therefore, all calculations are more accurate, we simply must get a more accurate result. Although, of course, absolute accuracy (that is, zero), for us remains an unattainable ideal. And now - the question is on the backfill. Will the result change if we replace double to more accurate extended? The answer is not so unequivocal, how I would like to see. In principle, after such a replacement, you must get -6.7762635780344e-20. But in some cases, the replacement of Double to the extended result will not change, and you will again get 1.44327637948555E-16. It depends on the operating system. It's all about using "defective" extended. When you start the program, any system sets such a control word of the coprocessor so that Extended has been full. But then the program causes many different Windows API functions. Some kind of (or some) from these numerous functions in incorrectly work with the control word, changing its value and not restoring when exit. Such a problem is found mainly in Windows 95 and old versions of Windows 98. There are also information that the control word can be cleaned and in Windows NT, and the effect is not immediately observed after installing the system, but only after a while, after the installation of others programs. The problem is precisely in the incorrect behavior of system functions; The value of the control word installed by the system when the program starts, is always the same. This problem is known: for example, in the original VCL codes, you can find the storage of the control word of the coprocessor before calling some API functions with its subsequent recovery. Comments report that the function can change the value of the control word, so it is necessary to save and restore it. Thus, we arrive at disappointing conclusion: to those problems with real numbers, which are due to the features of their hardware implementation, more and bugs are added. True, it is pleasing that recently these bugs are extremely rare - apparently, new versions of the system behave more responsibly. However, it is impossible to completely exclude such an opportunity, especially if your program will be used on outdated technique with outdated systems (for example, in educational institutions whose financing leaves much to be desired). In order for our example, it always issued the correct value -6.7762635780344E-20, it is enough to put at the beginning of our SET8087CW (get8087cw or $ 0100), and the program in any system will use the coprocessor in maximum accuracy mode. (If you use old versions of Delphi, this string can be replaced with SET8087CW (default8087cw), unless, of course, the default values \u200b\u200bof other control words are satisfied with you.) Since we started talking about the management word, let's experiment a little experiment with him. Change the first line on Set8087CW (Get8087CW and $ FCFF OR $ 0200). Thus, we translate the coprocessor into the mode of the 53rd gantissy representation accuracy. Now in any system we will see 1.44327637948555E-16, despite the use of extended. If we change the first line on the set8087cw (get8087cw and $ fcff), then we will operate in 24-ydot accuracy mode. Accordingly, there will be a result in any system -7.3015691270939E-8. Note that when booting into a 10-byte register of the approximate number of type extended in the reduced accuracy mode, the "extra" bits are not reset. Only the results of mathematical operations are presented with reduced accuracy. In addition, when comparing two numbers, all bits are also taken into account, regardless of accuracy. Therefore, the VAR R: Double code; // or Single Begin R: \u003d 0.1; IF R \u003d 0.1 Then label1.caption: \u003d "equal" else label1.caption: \u003d "Not equal to" END; When choosing any accuracy, will give "not equal."
Example Sixth - Machine Epsilon
When we are dealing with computing with limited accuracy, such a paradox occurs. Let, for example, we consider up to three significant digits with an accuracy. We add to the number 1.00 number 1.00 * 10 -4. If everything was honest, we would get 1.0001. But we have accuracy limited, so we are forced to round up to three meaningful numbers. The result is 1.00. In other words, we add some number to one, more zero, and as a result, due to limited accuracy, we get a single one again. The smallest positive number, which, when adding it to one, gives a result that is not equal to one, is called machine epsilon. The concept of machine episilon from newcomers is often confused with the concept of the smallest number, which can be recorded in the selected format. It is not right. Machine Epsilon is determined only by the size of the mantissa, and the minimum possible number is significantly less due to the shift of the floating binary point using the exponent. Before you look for a machine epsilon programmatically, try to find it from theoretical considerations. So, the Mantissa type extended contains 64 discharge. To encode a unit, the older bit of the mantissa must be equal to 1 (denormalized entry), the remaining bits are zero. Obviously, with such a record, the smallest of the numbers for which the X\u003e 1 condition is obtained, when the youngest bit of mantissa will also be equal to one, i.e. x \u003d 1.00 ... 001 (in binary representation; between the point and the youngest unit of 62 zero). Thus, the machine epsilon is X-1, i.e. 0.00 ... 001. In a more familiar decimal form of recording, it will be 2 -63, i.e. Approximately 1.084 * 10 -19. Now write a program for finding machine epsilon. VAR R: Extended; Begin R: \u003d 1; While 1 + R / 2\u003e 1 DO R: \u003d R / 2; Label1.caption: \u003d Floattostr (R) END; As a result, the number 1.0842021724855E-19 will appear on the screen in full compliance with theoretical calculations (if your system is present above the bug with the transfer of a processor into a reduced accuracy mode, instead of the number you get 2.22044604925031e-16, i.e. 2 -52 . To this not happened, correct the value of the control word). Now replace the extended type on double. The result will not change. On Single - again will not change. But such behavior only at first glance may seem strange. Let's consider the expression 1 + r / 2\u003e 1. So, all the calculations (including comparison) the coprocessor performs with the data of the type extended. The sequence of actions is as follows: the number R is loaded into the register of the coprocessor, which is converted to the extended type. It is further divided into 2, and then 1 is added to the result, and all this in Extended, no reverse conversion in Single or Double occurs. This number is then compared with unit. Obviously, the result of comparison should not depend on the source type R. In this article, I tried to explain the internal device of real numbers from the point of view of Intel processors and mention some of the problems that are connected with them. In fact, all the problems are reduced to two: first, not any real number can be represented accurately, and, secondly, not any real number, representable in the form of a finite decimal fraction, represents in the form of a finite binary fraction. The second problem, probably, brings more trouble to novice users, as it is less obvious. I consciously pretend to overcome these problems, since the optimal version depends very much on a specific task. The person who solved the causes of the emergence of problems will not be difficult in each particular case to choose the most acceptable solution. In this, in fact, the difference between the programmer and Lamer is: the first one is dealt in the task and finds a solution for it, the second can only throw on the form of ready-made components and eradicate the pieces of someone else's code. And I wrote this article for novice programmers, and not for beginner Lamers, from here and such a style.
Thank you so much by Elena Philippova for help finding information.
Discussion of material [01-07-2019 03:46] 77 posts
Now let's discuss Delphi data types that the programmer uses when writing a program. Any program on Delphi may contain data from many types:
- whole and fractional numbers
- symbols
- strings of characters
- logic values.
The Delphi language library includes 7 entire data types: shortint, smallint, longint, int64, byte, word, longword, whose characteristics are shown in the table below.
In addition, in support of the Delphi language, there are 6 different real-type (Real68, Single, Double, Extended, Comp, Currency, which differ from each other, first of all, by range of permissible values, by the number of significant digits, by the number of bytes, which It is necessary to store some data in the PC memory (characteristics of real types are shown below). Also, the Delphi language library includes the most versatile real type - type Real, equivalent to double.
Symbol type Delphi.
In addition to numeric types, Delphi has two symbolic types:
Type anSichar. - symbols with ANSI encoding, they are set in accordance with the number from 0 to 255;
Type WideChar. - Symbols with Enicode encoding, they are put in touch with the number from 0 to 65 535.
String type Delphi.
The string type in Delphi is indicated by the String ID. In the Delphi language, three string types are presented:
Type shortstring - inherent in the pC statically located in the PC memory, the length of which changes in the range from 0 to 255 characters;
Type longstring - this type is dynamically posted in the PC memory string with a length limited only by the amount of free memory;
Type WideString - Data type used in order to keep the necessary sequence of international characters, similar to whole proposals. Any string character having a type of WideString is a unicode symbol. Unlike the type of shortstring, the WIDESTRING type is a pointer that refers to variables.
Now that we already know the basis of the language and can even write small programs, you can go to a detailed study of the Delphi opportunities provided to work with the most different data types - arrays, recordings, sets, etc. Here we will consider issues of converting data types.
Custom data types
When developing programs, there is not enough of those data types that are presented by the programming language. For example, it is convenient to combine in one variable a number of one-type data at once, or provide storage of data from different types, such as strings and numbers. Fortunately, Object Pascal has the opportunity to create its own data types based on the already existing, combining them, or combining. For example, arrays (arrays) are used to create an ordered list of the same type of data, and to combine multiple types to one - Records.
Another aspect of the application of its own types of data that simplify the programming process and makes it a more understandable person is to use the set - a named set of several possible values.
Creating a particular type of data always begins with a declaration, or descriptions of a new data type. This is done in the header of the program or the module and starts with the keyword type. After a new data type is defined, you can create variables of a new type - in the same way as any simple. Well, the features of working with one or another user type of data are determined by what kind of type it belongs (array, recording, etc.), and in itself its implementation (size and dimension for - arrays, a set of other types - for records and t .P.).
Arrays
An array is an ordered structure consisting of a variety of uniform elements that have a common name. Thus, with just one variable, you can store a whole set of data, and each element of the array has its own index, or indexes in case the multidimensional array. The array is declared in Object Pascal using the ARRAY keyword, optional square brackets indicating the number of array elements, and data type for the elements of the array:
Array [indexes] of;
The number of indexes determines the dimension of the array. So, for a one-dimensional array (in mathematics - vector), only one index is required, and 2 index will be needed for a two-dimensional array (matrix). An arrays can look like this:
Type Myarray1 \u003d Array of Integer; Type Myarray2 \u003d Array of Integer;
In the first case, a one-dimensional array is defined by 100 elements of the type of integer numbers, in the second - a two-dimensional array of 10 to 10 dimension, i.e. Also on 100 elements of the type of integers. After the array is determined, you can create variables of the appropriate type:
VAR A1: MYARRAY1;
Another embodiment of the array is the simultaneous announcement of both the variable and the array descriptions:
VAR A1: Array of Integer;
If the desired size of the array is unknown in advance, then dynamic arrays can be used. They can be determined both with a preliminary ads of type, and without that - it is enough to create a variable and specify an array as its type:
VAR DYNARRAY: Array of Integer;
However, in order to proceed with the use of a dynamic array, it is first necessary to allocate a place in memory for it. This is done using the SetLength procedure, specifying the name of the dynamic array and the required memory size:
SetLength (Dynarray, 10);
The advantage of the dynamic array is that in the course of execution of the program, the amount of memory allocated under an array can be changed.
To appeal to a specific array element, an index (or indexes) of the element in the array is used. Thus, the first element in an array of type MYARRAY1 (Array) has an index equal to 1, and the last - 100. Accordingly, to refer to the first element of the array, let's say in order to assign the value to it, records of this type are used:
A1: \u003d 10;
Here we have appropriated an element of an array A1 with an index 1 Value 10. Data reading is made in the same way:
In this case, the variable x will be assigned the value of the 1st element of the A1 array. It is important that 2 conditions are complied with: first, that element of the array to which the appeal is to have, should exist. Those. If you appeal to the 11th element of an array consisting of 10 element, an error will be error to access memory. Secondly, the type of data assigned must coincide. In other words, if the array is defined as an integer, then only integer values \u200b\u200bcan be assigned. With the opposite situation (data reading), the rules are somewhat softer, since an integer can be assigned to with a variable as an integer and real type.
At the same time, an important (and extremely useful) feature of the arrays is that they can keep in them the data of any type, including both any simple and structural type, including records, objects and other arrays. In fact, a two-dimensional array can be described as a one-dimensional array, each element of which is also a one-dimensional array. From this point of view, it becomes clear why 2 types of ads and access to elements are allowed for two-dimensional arrays, and both are completely interchangeable and similar in meaning:
VAR A1: Array of Integer; VAR A2: Array of Array of Integer; ... A1: \u003d 5; A2: \u003d 5; A1: \u003d 5; A2: \u003d 5;
In this example, both declared arrays (A1 and A2) are completely identical. Similarly, appeals to elements of arrays - in both cases you can use both syntax with individual values \u200b\u200bof indexes and with indexes listed through the comma. Not only static arrays may be multidimensional, but also dynamic, while the 2nd version of the announcement is used. For example, a two-dimensional dynamic array of real numbers is declared as follows:
VAR DYNARRAY: Array Of Array Of Real;
When you allocate memory for such an array, it should also be taken into account its dimension. For example, if an array of 10 per 20 elements is required, you should use the SetLength procedure with the following parameters:
SetLength (Dynarray, 10, 20);
At the end of acquaintance with the arrays, we have left to consider the concentrated example of using cycles to fill the array. Suppose we have a simple array vector for 10 integers:
VAR MYARRAY: Array of Integer;
Thus, if we need to fill it with the values \u200b\u200bfollowing in order (say, numbers from 10 to 100 in step 10), then instead of sequentially assigning its value to each element, you can use the following cycle:
For i: \u003d 1 to 10 Do Myarray [i]: \u003d I * 10;
Here, the variable I, which is a cycle counter, with each iteration it is consistently increased by 1. As a result, each element of the MYARRAY array obtains the value of this variable multiplied by 10, i.e. 10, 20, 30, 40, etc. - What we needed. To make sure that it is at the same time to demonstrate a cycle to read data from the array, turn to the example shown in Listing 5.1 (on the CD, this example is in the Demo \\ Part1 \\ ArrayFor folder):
Listing 5.1. Recording and reading the data of arrays in the cycle
Program ArrayFor; ($ APPTYPE CONSOLE) VAR MYARRAY: Array of Integer; I: integer; Begin for i: \u003d 1 to 10 Do Myarray [i]: \u003d I * 10; // Filling an array for i: \u003d 1 to 10 Do Writeln (MYARRAY [I]); // Conclusion of the READLN array; // Waiting for input to prevent closing the End window.
Regarding arrays, one more note can be made: one data type already known to us - rows (string) can be viewed as an array of characters (CHAR). At the same time, no preliminary transformations are required: if we already have a string, you can always refer to its separate symbol as an element of the array:
VAR S: String; C: CHAR; ... S: \u003d Moscow; C: \u003d S;
In this case, the variable C will receive the letter "M" as its value, i.e. The first line symbol.
Set
Sometimes it is convenient to limit the possible values \u200b\u200bof the variable only part of the values \u200b\u200bfrom a plurality of all values \u200b\u200ballowed by its type. Suppose we need a variable type CHAR, which can only be values \u200b\u200bfrom lowercase latin characters. In this case, we use such a means as a subset, in this case, a subset of characters from a to z. You can determine it like this:
Type smletter \u003d a..z;
Thus, we received a new data type - SMLETTER, which is a "trimmed" type of char. When using this type of data, the variables of the SMLETTER type will not be able to receive values \u200b\u200bthat go beyond the specified range:
VAR A: SMLETTER; ... A: \u003d; // everything is right here, because Small B enters a subset A..z A: \u003d B; // error! Registration B is not included in a subset A..z
Practical candidates for subsets may be variables intended for storing individual dates (say, from 1 to 12 for months and from 1 to 31 - for days of the month), to check the values \u200b\u200bthat the program receives as a result of the user's input from the keyboard, and T .. It should only be borne in mind that subsets, for obvious reasons, can belong only to ordinary data types - whole numbers or symbols.
The development of subsets are sets, or sets (sets). They, like subsets, can be allocated to separate data types, but the allowable ranges of their values \u200b\u200bare somewhat different. First of all, a keyword is used to determine the set:
If you draw an analogy with the previous example, the range of lowercase Latin characters can be defined as follows:
Type Letters \u003d Set of Shar; VAR A: Letters; ... a: \u003d;
Thus, the definition of the set consists of two stages: the type is first determined on the basis of which a subset (Letters) is built, then the variable of this type (a) is declared, and the variable is already applied to the variable. The advantage here is that, firstly, in the course of the program, you can change the permissible ranges of values, and secondly, the ranges themselves are much more flexible. In particular, they may contain in themselves both rows of individual values \u200b\u200band subsets, or their combinations in any sequence. For example, if we need to highlight only some characters, say, up to k, as well as numbers 1, 3, 4, 5 and 9, the definition of the group will be as follows:
Type Letters \u003d Set of Char; VAR A: Letters; ... a: \u003d;
To check whether this or that value is a member of the set, the IN operation is used. For example, to check whether the user-entered symbol refers (we denote it as a variable "C") to the set A, there is enough such an expression:
IF C IN A THEN ...
To demonstrate the work of sets and operations in, we turn to the example shown in Listing 5.2 (Demo \\ Part1 \\ Ranges).
Listing 5.2. In and subset operation
Program Rangeset; ($ APPTYPE CONSOLE) Type Letters \u003d Set of Char; VAR A: Letters; C: CHAR; Begin A: \u003d; // Definition of the READLN group (C); // Reading user entry if c IN A THEN WRITELN (INPUT IS OK!) ELSE WRITELN (INPUT IS WRONG!); readln; // Waiting for input to prevent closing the End window.
Another variety of multiple is the listing. The use of transfer is intended primarily to improve readability (perception) of the program code. Suppose the program needs to repeatedly determine the current keyboard layout, and 3 states are provided - Russian, English and other. Of course, each state can be assigned a digit, say, 1, 2 and 3, respectively, however, in the course of writing the program, every time you have to remember what one or another digit means:
If Keylang \u003d 2 Then ... // After the month, remember that it means 2!
The listed types come to the rescue. They are defined as follows:
= (, ...);
For example, in the embodiment for three keyboard layout values \u200b\u200bwe will get:
Type TKEYLANG \u003d (Klrussian, Klenglish, Klother);
Agree that Klenglish is much clearer than just a number "2". Moreover, on the basis of the list specified in this way, you can create a type-set. For this, after the source type with the range is defined, the type name is determined on its basis:
Type TKEYLANG \u003d (Klrussian, Klenglish, Klother); TKEYLANGS \u003d Set of TKeylang;
ATTENTION
Note that the names of the types begin with the letter "T". Although this is not the requirement of the language, however, start the names of complex types with this letter (from the word type - type) de facto is the standard.
Thus, in the program it will be possible to use no original type in which the composition of the set is determined, and the new type, which is derived:
Var Keylang: TKEYLANGS; ... if Keylang \u003d Klenglish then;
NOTE
Announcement of type-enumerations in 2 stages is the most common practice. At the same time, the original type is determined by the name in the singular (TKEYLANG), and the derivative - in the multiple, with "s" at the end (TKEYLANGS).
Entries
Another useful variety of user data types are records (Records). A record, like an array, can store a whole set of data, but at the same time they should not belong to one type. In other words, if arrays are intended to store any data series, then recording - to combine heterogeneous information under the general name. For example, a record is simply an indispensable data type for storing such information as addresses or person information. And in that and in another case, under the overall name, data should be collected - string, numerical, etc. For example, to address this postal code, the names of the city and the street, as well as the rooms at home and apartments. All this dirty information can be collected under the total sign of one entry.
An entry starts with the keyword Record, followed by the listing of all the elements included in it, called the entry fields, and ends with the key word.
Record :; ...:; end;
With regard to the postal address, the definition of the recording may look like this:
Type Taddress \u003d Record PostIndex: Integer; City: String; Street: String; HOUSENR: Integer; FlatNR: Integer; end;
Appeal to individual entries of records is performed using point notation, i.e. When after the name of the variable, which is a record, put a point, and immediately after the point indicate the name of the field to which you need to appeal. For example, we can create a TADDress type variable and call it, say, MyADDR, after which you fill out someone else's fields using the point to access them:
Var MyAddr: Taddress; ... MyADDR.PostIndex: \u003d 119071; Myaddr.city :\u003d Moscow; In the event that the entry field is another entry, the point is used twice. So, if we have another entry, to store information about the person, then one of its fields will probably have the address, and we already have a suitable data type for this, so you can use it as a field: Type TPerson \u003d Record Name : string; Phone: String; ADDRESS: TADDRESS; end;
Thus, all the TADDRESS fields will also belong to TPerson type, but it is necessary to access them not directly, but through the TPerson recording field of the corresponding type, i.e. Through Address. Such fields are called composite:
Var Anybody: TPerson; ... anybody.name:\u003d Vasya Ivanov; Anybody.address.postIndex: \u003d 119071; Anybody.address.city :\u003d Moscow;
At the same time, you can go otherwise: create a variable of type TADDress, fill it with values, and then assign the type TPerson variable to the corresponding field. In the example shown in Listing 5.3, both methods of working with composite fields are demonstrated, as well as demonstrated that with all fields you can do the same as with separate variables of the same type.
Listing 5.3. Entries
Program Recdemo; ($ APPTYPE CONSOLE) Type Taddress \u003d Record PostIndex: Integer; City: String; Street: String; HOUSENR: Integer; FlatNR: Integer; end; TPerson \u003d Record Name: String; Phone: String; ADDRESS: TADDRESS; end; Var Anybody: TPerson; ADDRESS: TADDRESS; Begin Write (name :); readln (anybody.name); Write (Phone :); readln (anybody.phone); Write (Postal Index :); readln (address.postindex); Write (City :); readln (address.city); Write (Street :); readln (address.street); WRITE (House Number :); readln (address.housenr); WRITE (Flat Number :); readln (address.flatnr); Anybody.address: \u003d Address; Writeln (anybody.name); Writeln (anybody.phone); Writeln (anybody.address.postindex); Writeln (anybody.address.city); Writeln (Anybody.Address.street); Writeln (anybody.address.houseNR); Writeln (anybody.address.flatnr); readln; end.
The program shown in Listing sequentially offers the user to enter properties - first for recording about the person, and then for the address, after which the person's address field is assigned the value of the address address. After that, all fields are sequentially displayed. The source code of the program can be found in the Demo \\ Part1 \\ Records folder (Recdemo.dpr file).
At the end of the topic of entries, we consider another feature characteristic of Object Pascal, namely - the ability to combine in one type of recording with different fields. These records are called variant and are declared as well as the usual, except that they contain an additional part starting with the keyword CASE:
Record [:; ...:;] Case [:] of: (); ...: (); end;
As an example, this case can be created when you need to create a "OPENDER" record type, which requires the name and size of payment. The fact is that these the most employees can be both accepted for a permanent job, and has monthly salary and temporary for which the hourly pay is applied. It is important to note that only one type of salary can be at the same time. Accordingly, we can use a variant record type, as a trait in which the Boolean field "Salaried" will perform ("OKLADE"):
Type Temployee \u003d Record Name: String; Jobtitle: String; Case Salaried: Boolean of True: (Salary: Currency); FALSE: (Hourly: Currency); end;
Here, depending on whether the value of a SALARIED field, a lie or truth, will be the value of the TEMPLOYEE type or truth, it will have either the Salary field or Hourly. You can view an example of using this option to write in the Varrec.dpr file.
Special data types
In addition to the data types, simple and user, in Object Pascal there are a number of other, specialized types. For example, use TDATETIME type for time. In principle, this type of alogure is a real type Double, however, to work with data such as time ranges, it is more convenient to use a special TDATETIME type.
Since this type is, in fact, a real number, then the data is stored in it as follows: The whole part of the number determines the date for which the number of days from December 31, 1899 is taken, and the fractional determines the time in the milliseconds that have passed since the beginning of the current day. The advantage of TDATETIME type is that it provides a whole set of ready-made functions that allow you to work with dates and time. The list is shown in Table 5.1.
Function | Description |
---|---|
Now | Returns the current date and time |
Date. | Returns the current date (whole part of TDATETIME) |
Time. | Returns the current time (fractional part TDATETIME) |
DateTimetostr. | Converts the date and time in a string based on system settings |
DateTimetOc | Copies the date and time to the specified string variable |
DateTostr. | Converts the date in the string |
Timetostr. | Converts time to row |
FormatdateTime. | Converts the date and time to the specified format |
StrtodateTime. | Converts a string containing a proper date and time written in the TDATETIME variable |
StrTodate. | Converts a string at a date in TDATETIME format |
Strtotime. | Converts a string during TDATETIME format |
DayOfweek. | Returns the day number of the week (from 1 to 7) for the specified date. Consider that the 1st day of the week - Sunday |
Decodedate. | Encloses a value of type TDATETIME for 3 integers, which are a year, month and day of the month |
Decodetime. | Encloses a value of type TDATETIME for 4 integers, representing hours, minutes, seconds and milliseconds |
Encodedate. | Unites 3 entire years, month, month and day, in one value of type TDATETIME |
EncodeTime. | Combines 4 integers, representing hours, minutes, seconds and milliseconds? In one value of TDATETIME |
A typical example of using functions to work with dates may look like this:
Var Today, Yesterday: TDATETIME; S: String; ... today: \u003d now (); yesterday: \u003d Today - 1; S: \u003d TateToStr (Yesterday);
Here, the variable S will be assigned a value corresponding to yesterday in the format adopted in the system (for example, "16.07.2005"). You can view a more complete example of working with dates in Demo \\ Part1 \\ Dates.
In addition to dates, consider another data type - files (Files). The files are a certain sequence of the same type of elements placed on the outer medium that is not in the PC RAM. In a typical case, such a carrier is a hard disk. This data type can be described as a one-dimensional array without specifying the size. To produce certain operations on these data, use a special file type variable. Moreover, depending on which data it is necessary to work, you specify one or another type of file. For example, a special type of textFile is used to work with text files, and if the file contains a number of numbers, then determine which type of numbers in it is used:
VAR F1: TextFile; // Text file F2: File of Integer; // File with integers F3: File of Double; // File with real numbers
If the file is inepitized, (for example, binary), then use the type of File without any additions:
VAR F4: File; // binary file or file in advance of unknown type
Working with file variables has a number of features. First of all, simply declare a file type variable is not enough - it is also necessary to link it to any specific file on the disk. After that, the file should be opened, while specifying the file mode - it can be opened for reading or writing, or reading and writing simultaneously.
NOTE
The file itself is called the file descriptor, i.e. In fact, it only indicates a program to a place in memory through which access to the file using the operating system tools.
To work with files, there is also a number of procedures and functions. Among them, you can mark already familiar to us read / readln and write / writeln. So that these procedures work with files, as the first parameter indicate the file variable name (file descriptor):
Writeln (F, text to write to the file);
But before you write to a file, or start reading the data from it, as already mentioned, you need to associate a variable with the file (assign a descriptor) and open it, simply assigning the access mode. Assigning a file descriptor is made using the ASSIGNFILE procedure, for example
ASSIGNFILE (F, C: \\ File.txt);
As for the opening of the file, then things are somewhat more difficult, because the type of file and the access mode should be taken into account. So, in relation to text files, the RESET, REWRITE and APPEND procedures are used, opening the reading file, overwrite and add (entry to the end of the file), respectively.
Finally, it should be noted that after the operations with the file are manufactured, it must be closed. To close the file, use the CloseFile procedure. Thus, the use of all these procedures for reading and writing a file in the general case looks in such a way as shown in Listing 5.4:
Listing 5.4. Record and read in files
PROGRAM READWRITE; ($ APPTYPE CONSOLE) Uses Sysutils; VAR F: TextFile; S: String; Begin AssignFile (F, C: \\ TEST.TXT); // Assign a handle to the Text.txt Rewrite file (F); // Open the Writeln (F, S) entry file; // We write to the CloseFile file (F); // Close the RESET file (F); // Open the reading file readln (F, S); // Read the data from the CloseFile file (F); // Close the END file;
Another example of working with files can be viewed in Demo \\ Part1 \\ Files. At the same time, in practice, file types of data are not often used in modern programming in the Delphi environment, since the VCL offers a number of more convenient and elegant methods for storing data on disk, ranging from individual classes methods and ending with threads and databases.
Compatibility and conversion types
When considering simple types, we have already raised the issue of their compatibility and the transformation into each other. Now it's time to consider this aspect more carefully. In particular, if an integer is reduced to a real one, how to be in case the reverse transformation is required? The output in this situation is to use special type conversion functions. So, for converting a real number to the integer, the functions of the Round and Trunc are used. Their difference is that the first rounded value to the whole, relying on standard mathematical rules, and the second - simply discarded the fractional part of the number. Note that if you just need to drop the fractional part of the number, leaving the data type unchanged, you should use another function - Int. Examples of their use are shown below:
VAR I: Integer; R: REAL; ... R: \u003d 5.75; I: \u003d Round (R); // I will receive a value of 6 i: \u003d trunc (r); // I will receive a value of 5 R: \u003d int (R); // R will receive 5.0
A much larger number of functions is provided for converting numeric types to string and vice versa. With some of them, intended for dates, we are already familiar (see Table 5.1). Others are presented in Table 5.2.
Function | Description |
---|---|
Inttostr. | Converts an integer in the string |
StrToint. | Converts a string into an integer, if the transformation is not possible, it causes an error |
StrtointDef. | Converts a string into an integer, if the transformation is not possible, returns the number specified as the second argument |
Floattostr. | Converts a real number into a string |
Floattostrf. | Converts a real number into a string based on the specified format |
Strtofloat. | Converts a string into a real number, if the transformation is not possible, it causes an error |
Strtofloatdef. | Converts a string into a real number, if the transformation is not possible, returns the number specified as the second argument. |
Currtostr. | Converts the number of type CURRENCY to the string |
Currtostrf. | Converts the number of type CURRENCY to a string based on the specified format |
Strtocurr. | Converts a string to the type of Currency, in case of the impossibility of the conversion causes an error |
Strtocurrdef. | Converts a string to the type of Currency, in case of the impossibility of the conversion, returns the number specified as the second argument |
As a format in Floattostrf functions, one of the predefined formatting options, as well as the number of semicolons and the total number is also implied. For example, in order to leave no more than 2 decimal places, you can use the following expression:
STR: \u003d Floattostrf (X, FFGeneral, 10, 2);
Here FFGeneral is an indication of the output format, 10 defines the maximum possible number of signs in the number at all, and 2 is the maximum permissible number of decimal places. In addition to FFGeneral, which defines the most generalized format of the presentation of numbers, there are other:
- ffexponent - an exhibitor format (for example, 1.45E10);
- fffixed - fixed format (for example, 145000.01);
- fFNUMBER - "capital" format (for example, 1,450,000.0);
- fFCURRENCY - currency format (for example, 145 000.00Р).
Thus, it is easy to give a number to the row of the desired format, it is quite simple - it is important only to decide what you need to get. And another function - CHR - allows you to transform small numbers (up to 255) into symbols, i.e. In fact, from type byte makes Char.
As for the inverse transformations (rows in numbers), then it should be abide by certain caution, since not any string can be a number. First of all, it concerns the conversion of real number, since it is often a comma instead of the point separating the mantissa. In cases where the row transformation is not possible, the program execution error occurs. It can be processed independently (error processing will be told in the second part of the book), or to charge this function of the function - in this case, you should use functions with the DEF suffix (STRTOTIDEF, STRTOFLOATDEF and STRTOCURRDEF). As the second argument, they take the value to be used if the transformation is not possible:
X: \u003d STRTOINTDEF (STR, -1);
In this case, if the string cannot recognize the number, the variable x will be assigned to -1.
With all the variety of data types, in Object Pascal there is a type of another type that does not have type - option. He is called Variant. Variables variant type can take values \u200b\u200bof any simple type, as well as some special. In a typical case, the use of an option type may look like this:
VAR V: Variant; ... v: \u003d 5; v: \u003d string value; V: \u003d 10.54;
This example illustrates how one T the same variable is sequentially assigned data to 3 different types - integer, string and real. In this case, no error occurs. However, the variant data is processed much slower than typed - almost as slowly as programs in Basic. (By the way, in the classic Basic just only the variant data and were). In addition, the use of non-type data at all, and in a strictly typed language - especially, fraught with the unpredictable behavior of the program and other errors. Therefore, we will leave this type for the internal use of Delphi - in the VCL it is used to work with OLE and databases.
Pointers
Pointers (Pointers) is a type of variables that store the address in the memory of the computer, which is located another variable. In fact, the pointer does not contain value, but refers to it.
Pointers can be set in two fundamentally different ways. First, you can use a special type - Pointer. This will create a non-type pointer, under which every time it will be necessary to allocate the memory using the GETMEM function. Another, as a rule, a more preferred method is that the indicator of the desired type is immediately created. This is done using the symbol "^" preceding the name of the type:
VAR P: \u200b\u200b^ Integer;
Here we defined a P index, which is a pointer to an integer-type variable.
After the pointer is created, you can bind it from a variable of a suitable type using the @ operation:
VAR P: \u200b\u200b^ Integer; x: integer; ... p: \u003d @x; Now to the variable x, you can contact both directly and through its pointer. In the case of circulation through the pointer, the symbol "^" is also used: x: \u003d 10; P ^: \u003d 10;
In both cases, the variable X will receive a value of 10.
Another way to use the pointer, in addition to binding to an existing variable, is the allocation of memory for it and further work as reference to a certain abstract variable (in fact, directly to the area in memory allocated for data storage). In this case, the code will look like this:
VAR P: \u200b\u200b^ Integer; ... new (p); // Selecting the memory required for storage of data type Integer P ^: \u003d 10; // Data entertainment in the dedicated DISPOSE memory unit (P); // Release of Memory
When working with pointers, such a method should be used to independently take care of the allocation and cleaning of memory for data.
In general, pointers are usually used to interact with system functions of the operating system (in particular, with Windows API), to interact with third-party programs and dynamically plug-in libraries.
Objects
The most difficult and interesting types of data are objects. In modern versions of Delphi, objects are 3 main types: Objects are actually, as well as classes and interfaces. Object type (OBJECT) Delphi from the predecessor is the Pascal with Objects language, and is currently practically not used. The main type of object data in modern programs is class (Class). As for the interfaces (Interface), they are a type of classes, and are intended to interact with system object function functions.
The subject of objects is quite extensive, since it is the basis for the paradigm of object-oriented programming. OOP in Object Pascal is considered in the second part of this publication.
We continue our training! In Delphi, variables play a very important role. In the process of operation of the program in variables, you can both store and extract information. Variables may have different types. For example, in order to write some text to the variable String. In order to write to the variable number use type Integer.
Here is a list of basic types of variables in Delphi:
- Integer - Whole numbers from the range: -2147483648 .. + 2147483647
- Shortin. - integers from the range: -128 .. + 127
- Byte - integers from the range: 0 .. + 255
- Real - both as well as fractional numbers from the range: 5E-324..1.7E + 308
- Double. - similar to the type REAL
- String - String data type
- Char. - symbol data type
- Bollean - Logical data type. Can take True - Truth or False - Lie
I did everything and I got like this:
Now we need to create an event OnClick. On the button, I hope you remember how to do it.
Variables are announced between keywords procedure. and begin.. Announcement begins with a keyword var., then writes name variable and through colon her a type. Ends everything as always a point with a comma.
Create a variable S. Type String In the procedure OnClick.: procedure TFORM1.Button1Click (Sender: Togject); VAR S: String; Begin End; After that between the keywords begin End. We assign variable value equal to "My first variable". Assignment is written as follows. We write name variable, assignment operator := and value. If we record information type StringThe information is single quotes.
General view: Procedure TFORM1.Button1Click (Sender: Togject); VAR S: String; Begin S: \u003d "My first variable"; end; Now, if you compile the program and click on the button, nothing significant will happen, just a value will be recorded in the variable and that's it. Let's try to output from the variable. It is also done just as written. We will output the value in our Label.
Syntax such: label1.caption: \u003d s; We will analyze this code in detail. First we wrote Label1, then we write a point and in Delphi a huge list with the properties of this component appears. You can of course ride and find there CaptionBut we will be smarter! We, after put the point, write still the letter C. and Delphi sorted all properties and find all that start with the letter C.. The first list is just a property. Caption.
Choose it from the list and click on ENTER. Note that we wrote, but after clicking ENTER, Delphi himself adds the name of the property. Next, again, the appropriation operator and our variable goes.
You will surely ask: "Why is the variable if you could write label1.caption: \u003d" My first variable ";?". The answer is simple. It is necessary then that we study the variables :).
No, in fact, it is also possible to assign so much, but imagine this situation that you wrote a very large, popular program and you, there in the program, fifty The components are assigned the same value, and here you faced the task: "Change this value to a more universal and understandable for the user."
What are you going to do?
- In the first The case of all these components is assigned to all these components and the same variable and to change all these fifty components the value you just need to change the value in the variable.
- In second Case You are sitting 20 minutes and copy everything and copy the value to all fifty components.
And so, continue! In general, it should be like this: procedure TForm1.Button1Click (Sender: Togject); VAR S: String; Begin S: \u003d "My first variable"; Label1.caption: \u003d s; end; Complete our program and click on Button. (Baton / button). Immediately component Label instead Label1 will show My first variable.
I would like to finish it, since I'm already tired of writing a lesson :), but I have not introduced you to the type Integer And how to assign a variable with such a type. You think that you need to assign it just like a type variable StringBut you are mistaken.
The fact is that property Caption In general, all components can only be assigned text values. How will we assign a numeric type if you can only text? Everything is easier nowhere. Between the types of variables, it is possible to switch, that is, it is possible to make textual from a numeric type and assign it to the component Label. This is now we will deal with.
First you need to start first :). Let's declare a variable named I. and type Integerby adding it to the variable S.. Code: Procedure TForm1.Button1Click (Sender: Togject); VAR S: String; I: integer; Begin ... further assign a variable I. value 21 . I: \u003d 21; Note that the numeric value is written without single quotes! Now assign the property Caption Variable value I., for this you need to use the operator INTTOSTR (). It seems to convert a numeric type in the textual. In brackets indicate the variable you want to convert.
General code view: Procedure TFORM1.Button1Click (Sender: Togject); VAR S: String; I: integer; Begin S: \u003d "My first variable"; Label1.caption: \u003d s; I: \u003d 21; Label1.caption: \u003d INTTOSTR (I); end; Compile the program and you will see that Label will display the value of the variable I., i.e 21 .
That's it! Good luck!
See you in the next lesson!