"Strongly typed" languages are fussy about mixing different
types of variables. This is supposed to help reduce the
number of programming errors at the expense of more verbose source code.
C++ is one of the more strongly typed languages, but programmers still need
to take responsibility for reducing type-related errors because there are
many (too many, some think) implicit conversion rules that allow risky
conversions. Compilers can detect these risky, "value-destroying",
conversions but they're not
often under an obligation to report them even if the resulting value is
not defined in the language.
Here is a collection of small examples where types are mixed. Have a look at
them, assess the level of risk, and try to guess how the compiler will react.
I used gcc/g++ version 3.3.4 to test them, using the -Wall -ansi
options.
// chars and ints
int main() {
char c='A';
int i=7;
i=c;
c=999;
}
This compiles without complaint. i=c is harmless. A char is byte-sized and has a
range of -128 to 127 though, so int values (whose range is often -2147483646 to 2147483648) won't necessarily fit in. In such cases the result is undefined. If you print the value of c out at the end of this program it won't be 999.
// floats and doubles
int main() {
double d=9.9;
float f=9.9;
d=f;
f=d;
}
Doubles can store more digits than floats, so doing f=d may lose
accuracy. Also doubles can have a wider range than floats
(up to 10308 compared with a typical float maximum of about 1038). Setting a float to a number bigger than it can handle leads to undefined results. Again, no warning.
// ints and doubles
int main() {
double d=9.9;
int i;
d=i;
i=d;
}
This time we get a compiler warning (but only a warning) for the i=d line - the
fractional part of d won't be stored in i.
// strings and chars
#include <string>
using namespace std;
int main() {
char c='A';
string str;
str=c;
c=str;
}
This time we get an error - c=str can't be done, though str=c can. Despite what some documentation says, string str=c; (creating and initialising the string in one go) doesn't work for me.
// signed and unsigned ints
int main() {
int i=-9;
unsigned int ui;
ui=i;
i=ui;
}
Here ui is a natural number. Setting it to -9 won't provoke
a compiler warning, but if you printed out ui afterwards you wouldn't
get -9.
// ints and pointers
int main() {
int *pointer;
pointer=0;
pointer=4;
}
pointer=0; is ok, but pointer=4; is an error.
// const and non-const
void fun(int& i) {
;
}
int main() {
const int i;
fun(i);
}
The compiler considers this an error - it doesn't want to risk
i being changed from the fun function with call-by-reference.
Guess the answer!
See if you can guess what these programs will print. Compile and run the
programs to see if you're right. Then try to explain the results.
#include <iostream>
int main() {
unsigned int k=13;
int j = 12;
std::cout << "12 - 13=" << j - k << std::endl;;
}
So, C++ doesn't fully protect you from type-conversion errors, but you can
reduce the risks
Unsigned values - Don't assume that using unsigned values will give you any type-safe protection
Casting - The above examples mostly show simple types. Once you write your own classes,
types can become complicated and conversions become correspondingly trickier.
Read about Casting for more information about how to "cast" (convert) from one
type to another. Note that in C there was a single casting method, a method
also available in C++. The following, where 4 is cast into an int*, is legal but not recommended.
int main() {
int *pointer;
pointer=(int*)4;
}
This type of casting can too easily lead to bugs. Use C++'s alternatives.
Limits - You can find out about numerical representation
on your system by using <limits>. With the information
it provides you can often check to see whether you risk "undefined behaviour".
Here's an example showing how to determine limits
#include <limits>
#include <iostream>
using namespace std;
int main() {
cout << "The maximum double value is "
<< numeric_limits<double>::max() << endl;
}