
Programming Language Textbook D. Part 2
- Transfer
- Tutorial
The second part of the translation of D Programming Language Tutorial by Ali Çehreli . Most of the material is focused on beginners, but since most of the audience already has basic programming knowledge, this material has been removed for habrakat. This section discusses fundamental types, type properties, the basics of compilation and imperative programming.
Other parts:
Earlier, we found out that the CPU is the brain of the computer. Most program tasks are performed on the CPU, and the rest are distributed across other parts of the computer.
The smallest unit of data in a computer is called a bit , which can take values 0 or 1.
Since a data type that can contain only values 0 or 1 would have very limited use, the CPU determines larger data types that are combinations of more than one bit. For example, a byte contains 8 bits. The most productive data type determines the CPU bit: 32-bit CPU, 64-bit CPU, etc.
The types defined by the CPU are still not enough: they cannot describe high-level concepts like the name of a student or a playing card. D provides many useful data types, but even these types are not enough to describe many high-level concepts. Such concepts should be defined by the programmer as structures (struct) or classes (class), which we will see in the following chapters.
The fundamental types of D are very similar to the fundamental types of many other languages, as shown in the following table. The terms appearing in this table are explained below:
In addition to the above, the void keyword describes entities that are not of type . The keywords cent and ucent are reserved for future use to represent signed and unsigned 128-bit values.
You can use int for most values, as long as there is no particular reason not to. Consider a double to represent concepts that have a fractional part.
In D, types have properties . To get the value of a property, you need to write the name of the property after the type through the dot. For example, the sizeof int property is invoked like this: int.sizeof . This chapter discusses only the following four attributes:
You will also encounter the size_t type , whose name stands for “size type”. It is not an independent type, but an alias of the unsigned type, which is enough to represent all possible addresses in memory. Therefore, this type depends on the system: uint on 32- bit systems , ulong for 64-bit systems, etc.
You can use the .stringof property to see what type size_t refers to on your system:
Other parts:
Base material
We saw earlier that the most commonly used tools in D are a text editor and compiler . Programs in D are written in text editors (approx. Your KO).
When using compiled languages such as D, you need to understand the concept of compilation and the function of the compiler.
The brain of a computer is a microprocessor (or CPU, short for central processing unit ). Coding is an indication of what the CPU should do, and the instructions that are used are called machine codes .
Most CPU architectures use machine-specific codes specific to them. These machine instructions are determined by hardware limitations during architecture design. At the lowest level, these instructions are implemented as electrical signals. Since the simplicity of programming at this level is not the main goal, writing programs directly in the computer code of the CPU is a very difficult task.
These machine instructions are special numbers that represent the various operations supported by a particular CPU. For example, for an imaginary 8-bit CPU, the number 4 can represent the load operation, the number 5 is the save operation, and the number 6 is the operation of increasing the value by one. Assuming that the first 3 bits on the left are the operation number and 5 subsequent bits are the value that is used in this operation, the example program in machine codes of this CPU may look like this:
Being so close to the hardware, machine codes are not suitable for representing high-level concepts such as a playing card or a student record .
Programming languages are designed to be an effective way of CPU programming with the ability to present high-level concepts. Programming languages do not have to deal with hardware limitations; their main task is ease of use and expressiveness. Programming languages are easier to understand by people, they are closer to natural languages:
However, programming languages adhere to much more strict and formal rules than any language that is spoken.
In some programming languages, instructions must be compiled before becoming an executable program. Such languages create fast-executing programs, but the development process involves two main steps: writing a program and compiling it.
In general, compiled languages help in detecting errors even before the program starts to run.
D is a compiled language.
Some programming languages do not require compilation. Such languages are called interpreted . The program can be launched directly from freshly printed source code. Some examples of interpreted languages: Python, Ruby, and Perl. Since there is no compilation stage, program development may be easier for such languages. On the other hand, since program instructions must be parsed for interpretation each time the program is launched, programs in such languages are slower than their equivalents written in compiled languages.
In the general case for interpreted languages, many types of errors in the program cannot be detected until the start of execution.
The purpose of the compiler is translation: it translates programs written in a programming language into machine code. This is a translation from the programmer language to the CPU language. This translation is called compilation . Each compiler understands a specific programming language and is described as a compiler for this language, for example, “compiler for D”.
Since the compiler compiles the program according to the rules of the language, it stops compilation as soon as it reaches incorrect instructions. Incorrect instructions are those that get out of the language specifications. Problems, such as: inappropriate brackets, missing semicolon, misspelled keyword, etc. - all cause compilation errors.
The compiler also generates compilation warnings when it sees a suspicious piece of code that may be troubling, but not necessarily an error. However, warnings almost always indicate a real mistake or bad style, so the most common practice is to treat most or all warnings as errors.
Compiler
We saw earlier that the most commonly used tools in D are a text editor and compiler . Programs in D are written in text editors (approx. Your KO).
When using compiled languages such as D, you need to understand the concept of compilation and the function of the compiler.
Machine codes
The brain of a computer is a microprocessor (or CPU, short for central processing unit ). Coding is an indication of what the CPU should do, and the instructions that are used are called machine codes .
Most CPU architectures use machine-specific codes specific to them. These machine instructions are determined by hardware limitations during architecture design. At the lowest level, these instructions are implemented as electrical signals. Since the simplicity of programming at this level is not the main goal, writing programs directly in the computer code of the CPU is a very difficult task.
These machine instructions are special numbers that represent the various operations supported by a particular CPU. For example, for an imaginary 8-bit CPU, the number 4 can represent the load operation, the number 5 is the save operation, and the number 6 is the operation of increasing the value by one. Assuming that the first 3 bits on the left are the operation number and 5 subsequent bits are the value that is used in this operation, the example program in machine codes of this CPU may look like this:
Operation Value Meaning
100 11110 LOAD 11110
101 10100 STORE 10100
110 10100 INCREMENT 10100
000 00000 PAUSE
Being so close to the hardware, machine codes are not suitable for representing high-level concepts such as a playing card or a student record .
Programming languages
Programming languages are designed to be an effective way of CPU programming with the ability to present high-level concepts. Programming languages do not have to deal with hardware limitations; their main task is ease of use and expressiveness. Programming languages are easier to understand by people, they are closer to natural languages:
if (a_card_has_been_played()) {
display_the_card();
}
However, programming languages adhere to much more strict and formal rules than any language that is spoken.
Compiled languages
In some programming languages, instructions must be compiled before becoming an executable program. Such languages create fast-executing programs, but the development process involves two main steps: writing a program and compiling it.
In general, compiled languages help in detecting errors even before the program starts to run.
D is a compiled language.
Interpreted Languages
Some programming languages do not require compilation. Such languages are called interpreted . The program can be launched directly from freshly printed source code. Some examples of interpreted languages: Python, Ruby, and Perl. Since there is no compilation stage, program development may be easier for such languages. On the other hand, since program instructions must be parsed for interpretation each time the program is launched, programs in such languages are slower than their equivalents written in compiled languages.
In the general case for interpreted languages, many types of errors in the program cannot be detected until the start of execution.
Compiler
The purpose of the compiler is translation: it translates programs written in a programming language into machine code. This is a translation from the programmer language to the CPU language. This translation is called compilation . Each compiler understands a specific programming language and is described as a compiler for this language, for example, “compiler for D”.
Compilation errors
Since the compiler compiles the program according to the rules of the language, it stops compilation as soon as it reaches incorrect instructions. Incorrect instructions are those that get out of the language specifications. Problems, such as: inappropriate brackets, missing semicolon, misspelled keyword, etc. - all cause compilation errors.
The compiler also generates compilation warnings when it sees a suspicious piece of code that may be troubling, but not necessarily an error. However, warnings almost always indicate a real mistake or bad style, so the most common practice is to treat most or all warnings as errors.
Fundamental types
Earlier, we found out that the CPU is the brain of the computer. Most program tasks are performed on the CPU, and the rest are distributed across other parts of the computer.
The smallest unit of data in a computer is called a bit , which can take values 0 or 1.
Since a data type that can contain only values 0 or 1 would have very limited use, the CPU determines larger data types that are combinations of more than one bit. For example, a byte contains 8 bits. The most productive data type determines the CPU bit: 32-bit CPU, 64-bit CPU, etc.
The types defined by the CPU are still not enough: they cannot describe high-level concepts like the name of a student or a playing card. D provides many useful data types, but even these types are not enough to describe many high-level concepts. Such concepts should be defined by the programmer as structures (struct) or classes (class), which we will see in the following chapters.
The fundamental types of D are very similar to the fundamental types of many other languages, as shown in the following table. The terms appearing in this table are explained below:
A type | Definition | Initial value |
---|---|---|
bool | Boolean True / False | false |
byte | 8 bit signed number | 0 |
ubyte | 8 bit unsigned number | 0 |
short | 16 bit signed number | 0 |
ushort | 16 bit unsigned number | 0 |
int | 32 bit number cos sign | 0 |
uint | 32 bit unsigned number | 0 |
long | 64 bit signed number | 0 |
ulong | 64 bit unsigned number | 0 |
float | 32 bit floating point real number | float.nan |
double | 64 bit floating point real number | double.nan |
real | highest floating point number supported by hardware | real.nan |
ifloat | imaginary part of a complex number for float | float.nan * 1.0i |
idouble | imaginary part of a complex number for double | double.nan * 1.0i |
ireal | the imaginary part of the complex number for real | real.nan * 1.0i |
cfloat | complex option float | float.nan + float.nan * 1.0i |
cdouble | complex option double | double.nan + double.nan * 1.0i |
creal | complex option real | real.nan + real.nan * 1.0i |
char | UTF-8 character (code point) | 0xFF |
wchar | UTF-16 character (code point) | 0xFFFF |
dchar | UTF-32 character (code point) | 0x0000FFFF |
In addition to the above, the void keyword describes entities that are not of type . The keywords cent and ucent are reserved for future use to represent signed and unsigned 128-bit values.
You can use int for most values, as long as there is no particular reason not to. Consider a double to represent concepts that have a fractional part.
Explanations of the concepts presented in the table
Boolean : A type of boolean expression that is true for true and false for false .
Signed Type: A type that can take negative and positive values. For example, byte can have values from 0 to 255. The letter u at the beginning of the name of these types is taken from the word unsigned .
Floating-point number: The type can represent decimal-fractional values, as in 1.25. The accuracy of floating point calculations directly depends on the number of bits in the type: the more the number of bits, we get more accurate results.
Only floating point numbers can represent decimal fractions; integer types (e.g. int) can hold only values like 1 and 2.
Complex types of numbers: These types can represent complex numbers from mathematics.
Imaginary types of numbers: These types can represent only the imaginary part of complex numbers. The letter i , which is in the column of initial values, in mathematics is the square root of -1.
nan: Acronym for "not a number", representing an invalid floating point value .
Signed Type: A type that can take negative and positive values. For example, byte can have values from 0 to 255. The letter u at the beginning of the name of these types is taken from the word unsigned .
Floating-point number: The type can represent decimal-fractional values, as in 1.25. The accuracy of floating point calculations directly depends on the number of bits in the type: the more the number of bits, we get more accurate results.
Only floating point numbers can represent decimal fractions; integer types (e.g. int) can hold only values like 1 and 2.
Complex types of numbers: These types can represent complex numbers from mathematics.
Imaginary types of numbers: These types can represent only the imaginary part of complex numbers. The letter i , which is in the column of initial values, in mathematics is the square root of -1.
nan: Acronym for "not a number", representing an invalid floating point value .
Translator Comments
It may seem strange that for floating-point numbers, NaN values are used as initializers. An official explanation from the developers (although I do not agree with this position):
NaNs have the interesting property that no matter what operation they are used in, the result is NaN again. Therefore, NaN will be distributed and will be the result of any calculations where it was used. This means that a sudden NaN is an unambiguous indicator that an uninitialized variable has been used.
If 0.0 were used as the initial value, its consequences would not have been noticeable on the output, so if the initialization was unintentional by default, the bug could go unnoticed.
It is not meant that the default initializer should be a useful value, its purpose is to open bugs. NaN is well suited for this role.
But, of course, can the compiler detect and generate an error about variables that have not been initialized? In most cases, it can, but not always, and what it can depends on the complexity of the internal analysis of data flows. Therefore, the hope for this mechanism is an intolerable and unreliable solution.
Due to the nature of the CPU implementation, NaN is absent for integers, so D instead uses 0. A value of zero does not have such advantages for detecting errors as NaN has, but at least errors from unintentional initialization will be repeatable and therefore more debugged.
NaNs have the interesting property that no matter what operation they are used in, the result is NaN again. Therefore, NaN will be distributed and will be the result of any calculations where it was used. This means that a sudden NaN is an unambiguous indicator that an uninitialized variable has been used.
If 0.0 were used as the initial value, its consequences would not have been noticeable on the output, so if the initialization was unintentional by default, the bug could go unnoticed.
It is not meant that the default initializer should be a useful value, its purpose is to open bugs. NaN is well suited for this role.
But, of course, can the compiler detect and generate an error about variables that have not been initialized? In most cases, it can, but not always, and what it can depends on the complexity of the internal analysis of data flows. Therefore, the hope for this mechanism is an intolerable and unreliable solution.
Due to the nature of the CPU implementation, NaN is absent for integers, so D instead uses 0. A value of zero does not have such advantages for detecting errors as NaN has, but at least errors from unintentional initialization will be repeatable and therefore more debugged.
Type properties
In D, types have properties . To get the value of a property, you need to write the name of the property after the type through the dot. For example, the sizeof int property is invoked like this: int.sizeof . This chapter discusses only the following four attributes:
- stringof: type name
- sizeof: type size in bytes (for the number of bits, multiply by 8)
- min: the minimum value that the type can take
- max: the maximum value that the type can take
A program that prints these properties for int
import std.stdio;
void main ()
{
writeln ("Type:", int.stringof);
writeln ("Length in bytes:", int.sizeof);
writeln ("Minimum value:", int.min);
writeln ("Maximum value:", int.max);
}
Translator Notes
Some types have other properties, for example, float and double do not have the min property , instead -float.max and - double.max are used , more in detail .
size_t
You will also encounter the size_t type , whose name stands for “size type”. It is not an independent type, but an alias of the unsigned type, which is enough to represent all possible addresses in memory. Therefore, this type depends on the system: uint on 32- bit systems , ulong for 64-bit systems, etc.
You can use the .stringof property to see what type size_t refers to on your system:
The code
Output on my system:
import std.stdio;
void main ()
{
writeln (size_t.stringof);
}
Output on my system:
ulong
Exercises
Print properties of other types.
Note : You cannot use the reserved types cent and ucent in any program; as an exception, void has no properties .min and .max .
Note : You cannot use the reserved types cent and ucent in any program; as an exception, void has no properties .min and .max .
Decision
import std.stdio;
void main ()
{
writeln ("Type:", short.stringof);
writeln ("Size in bytes:", short.sizeof);
writeln ("Minimum value:", short.min);
writeln ("Maximum value:", short.max);
writeln ();
writeln ("Type:", ulong.stringof);
writeln ("Size in bytes:", ulong.sizeof);
writeln ("Minimum value:", ulong.min);
writeln ("Maximum value:", ulong.max);
}
Base material
The first two stumbling blocks that await students of programming are the assignment operator and the order of calculations.
You will find similar lines in almost all programs, in almost all programming languages:
The meaning of this line is “make the value of a equal to 10”. Similarly, the following line means “make the value of b equal to 20”:
Based on the above information, what about the next line?
Unfortunately, this line does not contain a comparison operator from mathematics, which, I assume, everyone knows. The expression above does not mean "a is b"! When we use the same logic as in the previous two lines, the expression above should mean “make the value of a equal to the value of b ”.
The well-known symbol = in mathematics has a completely different meaning in programming: "make the value on the left equal to the value on the right side."
Program operations are performed step by step in a specific order. The previous three expressions in the program can be seen in the following order:
The meaning of these lines together: "make the value of a equal to 10, then make the value of b equal to 20, then make the value of a equal to the value of b ". Accordingly, after performing these three operations, both a and b will be equal to 20.
Learn how the following three operations interchange the values of a and b . If at the beginning their values were equal to 1 and 2, respectively, then after performing the operations, the values become equal to 2 and 1:
Assignment Operator and Calculation Order
The first two stumbling blocks that await students of programming are the assignment operator and the order of calculations.
Assignment operator
You will find similar lines in almost all programs, in almost all programming languages:
a = 10;
The meaning of this line is “make the value of a equal to 10”. Similarly, the following line means “make the value of b equal to 20”:
b = 20;
Based on the above information, what about the next line?
a = b;
Unfortunately, this line does not contain a comparison operator from mathematics, which, I assume, everyone knows. The expression above does not mean "a is b"! When we use the same logic as in the previous two lines, the expression above should mean “make the value of a equal to the value of b ”.
The well-known symbol = in mathematics has a completely different meaning in programming: "make the value on the left equal to the value on the right side."
Calculation order
Program operations are performed step by step in a specific order. The previous three expressions in the program can be seen in the following order:
a = 10;
b = 20;
a = b;
The meaning of these lines together: "make the value of a equal to 10, then make the value of b equal to 20, then make the value of a equal to the value of b ". Accordingly, after performing these three operations, both a and b will be equal to 20.
Exercise
Learn how the following three operations interchange the values of a and b . If at the beginning their values were equal to 1 and 2, respectively, then after performing the operations, the values become equal to 2 and 1:
c = a;
a = b;
b = c;
Decision
The values a , b, and c are printed on the right side of each operation:
At the end, the values of a and b are interchanged.
в начале → a 1, b 2, c неважно
c = a → a 1, b 2, c 1
a = b → a 2, b 2, c 1
b = c → a 2, b 1, c 1
At the end, the values of a and b are interchanged.