2. Static typing
The static typing means that the compiler decides about the (static) type of all expressions and subexpressions in compilation time. Under run-time these types do not change.
Second C++ program: Fahrenheit to Celsius conversion
(aka. The Bad, the Ugly and the Good)
The task is to convert Fahrenheit values to Celsius between -100 and +400 with stepping by 25.
We start with a C-like solution.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
* BAD VERSION !!!
* Convert Fahrenheit to Celsius
* between -100F and +400F by 25F
*/
#include <stdio.h>
int main()
{
int fahr;
for ( fahr = -100; fahr <= 400; fahr += 25 )
{
printf( "Fahr = %d\tCels = %d\n", fahr, 5/9*(fahr-32) );
}
return 0;
}
The reason is that in strongly typed programming languages the compiler decides the type of all (sub)expressions in compilation time, and that depends only from the type of the operands (and from the operator), not from the value of the operands. Thus 5/9 is always integer (with value 0).
Let try to fix it using 5./9. istead of 5/9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
* BAD VERSION !!!
* Convert Fahrenheit to Celsius
* between -100F and +400F by 25F
*/
#include <stdio.h>
int main()
{
int fahr;
for ( fahr = -100; fahr <= 400; fahr += 25 )
{
printf( "Fahr = %d\tCels = %d\n", fahr, 5./9.*(fahr-32) );
}
return 0;
}
Still bad. Now the value of the celsius is computed correctly, but we try to print it as an integer: using %d format. What happened: we placed (a likely 8 byte) double to the stack, but used only 4 bytes (interpreted as integer) to print. Obviously, this is wrong.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
* UGLY VERSION
* Convert Fahrenheit to Celsius
* between -100F and +400F by 25F
*/
#include <stdio.h>
int main()
{
int fahr;
for ( fahr = -100; fahr <= 400; fahr += 25 )
{
printf( "Fahr = %d\tCels = %f\n", fahr, 5./9.*(fahr-32) );
}
return 0;
}
In C++ we use operators to read from an input stream and write to an output stream. These operators are overloaded on parameter types. Therefore we cannot make errors like the previous ones, because the either the correct operator is selected based on the parameter(s) or there is syntax error if no available operator.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//
// fahr2cels: convert fahrenheit to celsius in [-100 ... 400] step 20
// the ugly
//
#include <iostream>
int main()
{
for( int fahr = -100; fahr <= 400; fahr += 20 )
{
std::cout << "fahr = " << fahr
<< ", cels = " << 5./9. * (fahr-32)
<< std::endl;
}
return 0;
}
This works, but the input is not nicely formatted. We can use input
and output formatters, called manipulators defined in
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//
// fahr2cels: convert fahrenheit to celsius in [-100 ... 400] step 20
// the good
//
#include <iostream>
#include <iomanip>
int main()
{
for( int fahr = -100; fahr <= 400; fahr += 20 )
{
std::cout << "fahr = " << std::setw(5) << fahr
<< ", cels = " << std::setw(8) << 5./9. * (fahr-32)
<< std::endl;
}
return 0;
}
There are still room to improve. To help maintenance, we can lift out the magic constants from the source to the head of the program.
We can use named constants to remove magic numbers from the source and define program parameters in one well-defined place.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//
// fahr2cels: using const
//
#include <iostream>
#include <iomanip>
const int lower = -100;
const int upper = 400;
const int step = 40;
int main()
{
for( int fahr = lower; fahr <= upper; fahr += step )
{
std::cout << "fahr = " << std::setw(4) << fahr
<< ", cels = " << std::fixed << std::setw(7)
<< std::setprecision(2) << 5./9. * (fahr-32)
<< std::endl;
}
return 0;
}
Also, the complex expression inside the printf expression is not easy to understand or maintainde. We refactor the Fahrenheit to Celsius conversion to a separate function.
Recognize, that the signature of the function is double fahr2cels(double) and we pass an integer parameter. This is ok, since we declared the full prototype of the function, and the integer will be converted to double on parameter passing. This happens only when we declare the parameterlist.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//
// fahr2cels: with function call
//
#include <iostream>
#include <iomanip>
const int lower = -100;
const int upper = 400;
const int step = 20;
// declaration of the fahr2cels function
double fahr2cels(double);
int main()
{
for( int fahr = lower; fahr <= upper; fahr += step )
{
std::cout << "fahr = " << std::setw(4) << fahr
<< ", cels = " << std::fixed << std::setw(7)
<< std::setprecision(2)
<< fahr2cels(fahr) // call of fahr2cels(double)
<< std::endl;
}
return 0;
}
// definition of the fahr2cels function
double fahr2cels(double f)
{
return 5./9. * (f-32);
}
##Homework:
-
Create a program printing your name. Compile, link, run.
-
Separate the program to 2 sources: one return the name, other prints Hints: - use char *my_name() as a function prototype. - use “%s” as a format string for printf