Pascal Programming/Records: Difference between revisions

From testwiki
Jump to navigation Jump to search
imported>R. Henrik Nilsson
m whereever > wherever
 
(No difference)

Latest revision as of 12:41, 29 December 2023

Template:Epigraph After you have learned to use an [[../Array|array]], this chapter introduces you to another data type structure concept called record. Like an array, the use of records primarily serves the purposes of allowing you to write clean, structured programs. It is otherwise optional.

Concept

You briefly saw a record in the [[../Beginning#Terminology|first chapter]]. While an array is a homogenous aggregation of data, that means all members have to have the same base data type, a record is potentially, but not necessarily an aggregation of data having various different data types.[1]

Declaration

A record data type declaration looks pretty much like a collection of variable declarations:

program recordDemo;
type
	(* a standard line on a text console *)
	line = string(80);
	(* 1st grade through 12th grade *)
	grade = 1..12;
	
	(* encapsulate all administrative data in one structure *)
	student = record
			firstname: line;
			lastname: line;
			level: grade;
		end;

The declaration begins with the word record and ends with end. Inbetween you declare fields, or members, member elements of the entire record. Template:Technote All record members have to bear distinct names within the record declaration itself. For instance in the example above, declaring two “variables”, member elements of the name level will be rejected.

There is no requirement on how many fields you have to declare. An “empty” record is also possible:[fn 1]

type
	emptyRecord = record
		end;

Many fields of the same data type

Similar to the [[../Variables and Constants#More variables|declaration of variables]] you can define multiple fields of the same data type at once by separating identifiers with a comma. The previous declaration of sphere could also be written as:

type
	sphere = record
			radius, volume, surface: real;
		end;

Most Pascal veterans and style guides, however, discourage the use of this shorthand notation (both for variable as well as record declarations, but also in formal parameter lists). It is only reasonable if all declared identifiers absolutely always have same data type; it is virtually guaranteed you will never want to change the data type of just one field in the comma-separated list. If in doubt, use the longhand. In programming, convenience plays a tangential role.

Use

By declaring a record variable you immediately have the entire set of “sub”‑variables at your hand. Accessing them is done by specifying the record variable’s name, plus a dot (.), followed by the record field’s name:

var
	posterStudent: student;
begin
	posterStudent.firstname := 'Holden';
	posterStudent.lastname := 'Caulfield';
	posterStudent.level := 10;
end.

You already saw the dot notation in the previous chapter on [[../Strings|strings]], where appending [[../Strings#Inquiry|.capacity]] on a name of a string() variable refers to the respective variable’s character capacity. This is not a coincidence. Template:Code:Basic

Advantages

But why and when do we want to use a record? At first glance and in the given examples so far it may seem like a troublesome way to declare and use multiple variables. Yet the fact that a record is handled as one unit entails one big advantage:

  • You can copy entire record values via a simple assignment (:=).
  • This means you can pass much data at once: A record can be a parameter of routines, and in Template:Abbr functions can return them as well.[fn 2]

Evidently you want to group data together that always appear together. It does not make sense to group unrelated data, just because we can. Another quite useful advantage is presented below in the section on variant records.

Routing override

As you saw earlier, referring to members of a record can get a little tedious, because we are repeating the variable name over and over again. Fortunately, Pascal allows us abbreviate things a bit.

With-clause

The with-clause allows us to eliminate repeating a common prefix, specifically the name of a record variable.[2]

begin
	with posterStudent do
	begin
		firstname := 'Holden';
		lastname := 'Caulfield';
		level := 10;
	end;
end.

All identifiers that identify values are first looked for in the record scope of posterStudent. If there is no match, all variable identifiers outside of the given record are considered too.

Of course it is still possible to denote a record member by its full name. E. g. in the source code above it would be perfectly legal to still write posterStudent.level within the with-clause. Concededly, this would defeat the purpose of the with-clause, but sometimes it may still be beneficial to emphasize the specific record variable just for documentation. It is nevertheless important to understand that the FQI, the fully-qualified identifier, the one with a dot in it, does not lose its “validity”.

In principle, all components of structured values “containing dots” can be abbreviated with with. This is also true for the [[../Strings|data type string]] you have learned in the previous chapter.

program withDemo(input, output);
type
	{ Deutsche Post „Maxi-Telegramm“ }
	telegram = string(480);
var
	post: telegram;
begin
	with post do
	begin
		writeLn('Enter your telegram. ',
			'Maximum length = ',
			capacity, ' characters.');
		readLn(post);
		{ … }
	end;
end.

Here, within the with-clause capacity, and for that matter post.capacity, refer to post.capacity.

Multiple levels

If multiple with-clauses ought to be nested, there is the short notation:

	with snakeOil, sharpTools do
	begin
		
	end;

which is equivalent to:

	with snakeOil do
	begin
		with sharpTools do
		begin
			
		end;
	end;

It is important to bear in mind, first identifiers in sharpTools are searched, and if there is no match, secondly, identifiers in snakeOil are considered.

Variant records

In Pascal a record is the only data type structure concept that allows you to, so to speak, alter its structure during run-time, while a program is running. This super practical property of record permits us to write versatile code covering many cases.

Declaration

Let’s take a look at an example:

type
	centimeter = 10..199;
	
	// order of female, male has been chosen, so `ord(sex)`
	// returns the [minimum] number of non-defective Y chromosomes
	sex = (female, male)
	
	// measurements according EN 13402 size designation of clothes [incomplete]
	clothingSize = record
			shoulderWidth: centimeter;
			armLength: centimeter;
			bustGirth: centimeter;
			waistSize: centimeter;
			hipMeasurement: centimeter;
			case body: sex of
				female: (
					underbustMeasure: centimeter;
				);
				male: (
				);
		end;

The variant part of a record starts with the keyword case, which you already know from [[../Enumerations#Selections|selections]]. After that follows a record member declaration, the variant selector, but instead of a semicolon you put the keyword of thereafter. Below that follow all possible variants. Each variant is marked by a value out of the variant selector’s domain, here female and male. Separated by a colon (:) follows a variant denoter surrounded by parentheses. Here you can list additional record members that are only available if a certain variant is “active”. Note that all identifiers across all alternatives must be unique. The individual variants are separated by a semicolons, and there can be at most one variant part which has to appear at the end. Because you will need to be able to list all possible variants, the variant selector has to be an ordinal data type.

Use

Using variant records requires you to first select a variant. Variants are “activated” by assigning a value to the variant selector. Note, variants are not “created”; they all already exist at program startup. You merely need to make a choice.

	boobarella.body := female;
	boobarella.underbustMeasure := 69;

Only after assigning a value to the variant selector and as long as this value remains unchanged, you are allowed to access any fields of the respective variant. It is illegal to reverse the previous two lines of code and attempt accessing the underbustMeasure field even though body is not defined yet and, more importantly, does not bear the value female.

It is certainly permissible to change the variant selector later in your program and then use a different variant, but all previously stored values in the variant part relinquish their validity and you cannot restore them. If you switch back the variant to a previous, original value, you will need to assign all values in that variant anew.

Application

This concept opens up new horizons: You can design your programs more interactively in a neat fashion. You can now choose a variant based on run-time data (data that is read while the program is running). Because at any time (after the first assignment of a value to the variant selector) only one variant is “active”, your program will crash if it attempts reading/writing values of an “inactive” variant. This is a desirable behavior, because that is the whole idea of having distinct variants. It guarantees your programs overall integrity.

Anonymous variants

Pascal also permits having anonymous variant selectors, that is selectors not bearing any name. The implications are

  • you cannot explicitly select (nor query) any variant, so
  • in turn all variants are considered “active” at the same time.

“But wasn’t this the object of the exercise?” you might ask. Yes, indeed, since there is no named selector your program cannot keep track which variant is supposed to work and which one is “defective”. You are responsible to determine which variant you can sensibly read/write at present.

Template:Code:Output

This concept exists in many other programming languages too. In the programming language C, for instance, it is called a union.

Conditional loops

So far we have been exclusively using counting [[../Sets#Loops|loops]]. This is great if you can predict in advance the number of iterations, how many times the loop’s body needs to be executed. Yet every so often it is not possible to formulate a proper expression determining the number of iterations in advance.

Conditional loops allow you to make the execution of the next iteration dependent on a Boolean expression. They come in two flavors:

  • Head-controlled loop, and
  • tail-controlled loop.

The difference is, the loop’s body of a tail-controlled loop is executed at least once in any case, whereas a head-controlled loop might never execute the loop body at all. In either case, a condition is evaluated over and over again and must uphold for the loop to continue.

Head-controlled loop

A head-controlled loop is frequently called while-loop because of its syntax. Template:Code:Output EOF is shorthand for EOF(input). This standard function returns true if there is no further data available to read, commonly called end of file. It is illegal, and will horribly fail, to read from a file if the respective EOF function call returns true.

Unlike a counting loop, you are allowed to modify data the conditional loop’s condition depends on.

const
	(* instead of a hard-coded length `64` *)
	(* you can write `sizeOf(integer) * 8` in Delphi, FPC, GPC *)
	wordWidth = 64;
type
	integerNonNegative = 0..maxInt;
	wordStringIndex = 1..wordWidth;
	wordString = array[wordStringIndex] of char;

function binaryString(n: integerNonNegative): wordString;
var
	(* temporary result *)
	binary: wordString;
	i: wordStringIndex;
begin
	(* initialize `binary` with blanks *)
	for i := 1 to wordWidth do
	begin
		binary[i] := ' ';
	end;
	(* if n _is_ zero, the loop's body won't be executed *)
	binary[i] := '0';
	
	(* reverse Horner's scheme *)
	while n >= 1 do
	begin
		binary[i] := chr(ord('0') + n mod 2);
		n := n div 2;
		i := i - 1;
	end;
	
	binaryString := binary;
end;

The n the loop’s condition depends on will be repeatedly divided by two. Because the division operator is an integer division (div), at some point the value 1 will be divided by two and the arithmetically correct result 0.5 is truncated (trunc) toward zero. Yet the value 0 does not satisfy the loop’s condition anymore, thus there will not be any subsequent iterations.

Tail-controlled loop

In a tail-controlled loop the condition appears below the loop’s body, at the foot. The loop’s body is always run once before even the condition is evaluated at all.

program repeatDemo(input, output);
var
	i: integer;
begin
	repeat
	begin
		write('Enter a positive number: ');
		readLn(i);
	end
	until i > 0;
	
	writeLn('Wow! ', i:1, ' is a quite positive number.');
end.

The loop’s body is encapsulated by the keywords repeat and until.[fn 3] After until follows a Boolean expression. In contrast to a while loop, the tail-controlled loop always continues, always keeps going, until the specified condition becomes true. A true condition marks the end. In the above example the user will be prompted again and again until he eventually complies and enters a positive number.

Date and time

This section introduces you to features of Extended Pascal as defined in the Template:Abbr standard 10206. You will need an Template:Abbr‑compliant compiler to use those features.

Time stamp

In Template:Abbr there is a standard data type called timeStamp. It is declared as follows:[fn 4]

type
	timeStamp = record
			dateValid: Boolean;
			timeValid: Boolean;
			year: integer;
			month: 1..12;
			day: 1..31;
			hour: 0..23;
			minute: 0..59;
			second: 0..59;
		end;

As you can see from the declaration, timeStamp also contains data fields for a calendar date, not just the time as indicated by a standard clock. Template:XNote

Getting a time stamp

Template:Abbr also defines a unary procedure that populates a timeStamp variable with values. GetTimeStamp assigns values to all members of a timeStamp record passed in the first (and only) parameter. These values represent the “current date” and “current time” as at the invocation of this procedure. However, in the 1980’s not all (personal/home) computers did have a built-in “real time” clock. Therefore, the Template:Abbr standard 10206 devised prior 21st century stated that the word “current” was “implementation-defined”. The dateValid and timeValid fields were specifically inserted to address the issue that some computers simply do not know the current date and/or time. When reading values from a timeStamp variable, it is still advisable to check their validity first after having getTimeStamp fill them out.

If getTimeStamp was unable to obtain a “valid” value, it will set

  • day, month and year to a value representing Template:Nowrap, but also dateValid to false.
  • In the case of time, hour, minute and second become all 0, a value representing midnight. The timeValid field becomes false.

Both are independent from each other, so it may certainly be the case that just the time could be determined, but the date is invalid.

Note that the Gregorian calendar was introduced during the year 1582 Template:Abbr, so the timeStamp data type is generally useless for any dates before 1583 Template:Abbr.

Printable dates and times

Having obtained a timeStamp, Template:Abbr furthermore supplies two unary functions:

  • date returns a human-readable string representation of day, month and year, and
  • time returns a human-readable string representation of hour, minute and second.

Both functions will fail and terminate the program if dateValid or timeValid indicate an invalid datum respectively. Note, the exact format of string representation is not defined by the Template:Abbr standard 10206. Template:Code:Output

Summary on loops

This is a good time to take inventory and reiterate all kinds of loops.

Conditional loops

Conditional loops are the tools of choice if you cannot predict the total number of iterations.

head-controlled loop tail-controlled loop
while condition do
begin
	…
end;
 
repeat
begin
	…
end
until condition;
condition must evaluate to true for any (including subsequent) iterations to occur. condition must be false for any subsequent iteration to occur.
comparison of conditional loops in Pascal

It is possible to formulate either loop as the other one, but usually one of them is more suitable. A tail-controlled loop is particularly suitable if you do not have any data yet to make a judgment, to evaluate a proper condition prior the first iteration.

Counting loops

Counting loops are good if you can predict the total number of iterations before entering the loop.

counting up loop counting down loop
for controlVariable := initialValue to finalValue do
begin
	…
end;
for controlVariable := initialValue downto finalValue do
begin
	…
end;
After each non-final iteration controlVariable becomes succ(controlVariable). controlVariable must be less than or equal to finalValue for another iteration to occur. After each non-final iteration controlVariable becomes pred(controlVariable). controlVariable must be greater than or equal to finalValue for another iteration to occur.
comparison of counting loop directions in Pascal

Template:XNote

Inside counting loops’ bodies you cannot modify the counting variable, only read it. This prevents you from any accidental manipulations and ensures the calculated predicted total number of iterations will indeed occur.

Template:XNote

Loops on aggregations

If you are using an Template:Abbr-compliant compiler, you furthermore have the option to use a Template:Nowrap on [[../Sets|sets]].

program forInDemo(output);
type
	characters = set of char;
var
	c: char;
	parties: characters;
begin
	parties := ['R', 'D'];
	for c in parties do
	begin
		write(c:2);
	end;
	writeLn;
end.

Tasks

You have made it this far, and it is quite impressive how much you already know. Since this chapter’s concept of a record should not be too difficult to grasp, the following exercises mainly focus on training. A professional computer programmer spends most of his time on thinking what kind of implementation, using which tools (e. g. array “vs.” set), is the most useful/reasonable. You are encouraged to think first, before you even start typing anything. Nonetheless, sometimes (esp. due to your lack of experience) you need to just try things out, which is fine if it is intentional. Aimlessly finding a solution does not discern an actual programmer.

Template:Question-answer Template:- Template:Question-answer Template:- Template:Question-answer Template:- Template:Question-answer Template:- Template:Question-answer Template:- Template:Question-answer Template:- Template:Question-answer Template:- Template:Question-answer

Template:Newpage


Sources:

Cite error: <ref> tag with name "module" defined in <references> is not used in prior text.
Cite error: <ref> tag with name "pumar" defined in <references> is not used in prior text. Notes:

  1. This kind of record will not be able to store anything. In the [[../Pointers|next chapter]] you will learn a (and the only) instance it could be useful.
  2. In Standard (“unextended”) Pascal, Template:Abbr standard 7185, a function can only return “simple data type” and “[[../Pointers|pointer]] data type” values.
  3. Actually the shown Template:Nowrap is redundant since Template:Nowrap constitute a frame in their own right. For pedagogical reasons we teach you to always use Template:Nowrap nevertheless wherever a sequence of statements usually appears. Otherwise you might change your Template:Nowrap to a Template:Nowrap forgetting to surround the loop’s body statements with a proper Template:Nowrap frame.
  4. The packed designation has been omitted for simplicity.

Template:Auto navigation