Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

You Can Program In C++ (2006) [eng]

.pdf
Скачиваний:
80
Добавлен:
16.08.2013
Размер:
4.33 Mб
Скачать

18

CHAPTER 1

The data-capture window should look like this when you have finished:

As long as you typed it in correctly, emptyplaypen.cpp should compile without errors or warnings. Now try to build it (F7). You get link-time error messages referring to things that you have not written. The problem is that playpen.h made some promises to the compiler about what the linker would be able to find, but we never told the linker which library files provide the necessary pre-compiled (object) code. The linker will need two library files: one for the fgw library (my library) and one for the gdi32 library (a system-dependent library providing some basic graphics facilities used by my library).

Open the Project Settings dialog box again and select the Link tab. If you are working on a Microsoft Windows machine, enter ‘fgw;gdi32’ in the Libraries box.

GETTING STARTED

19

This is what you should see on the screen:

Return to the project and build it. Everything should work.

If you are using a system that can support graphics using the X Window System (Linux, Mac OS X, etc.) you will need to insert ‘fgw;X11;pthread’ into the libraries box instead of the ‘fgw;gdi 32’ that you would use on a Microsoft Windows system. You will also need something like ‘C:/tutorial/fgw headers;/

usr/X11R6/lib’, where those two paths take you to the headers directory for my library and the location of the X11R6 library directory respectively. You may need help from someone else if you are not familiar with the location of X11 support files on your computer.

If you are using a different set of development tools, you may need to know the names of the library files rather than just the names of the libraries. MDS (along with GCC, the GNU Compiler Collection, on which MDS is based) uses a naming convention that libraries are kept in files prefixed with ‘lib’ and followed

20

CHAPTER 1

by ‘.a.’ Therefore, the library files libfgw.a and libgdi32.a provide the two libraries for Windows, and linfgw.a, libX11.a, and libpthread.a provide them on systems that use X11 for graphics support.

Finally execute the program and you will see:

On top you will see a square white window with the title ‘Playpen’. We will be using this graphics window during your study of C++. Immediately underneath it, there is the standard console window (usually black unless you have changed the default settings of your computer). On some computers, you will also see a white area at the top left of the screen. Do not worry: some versions of Windows forget to update the screen when they create the Playpen window. If you move a window over the area and then away again, Windows will correctly update the screen.

Use the taskbar to select the console window (or just click on a bit that you can see on the screen to bring it to the top). Follow the displayed instructions.

What the Code Means

The first line after the comment is a different form of #include. The use of quotation marks tells the compiler that this is a header file (as opposed to a system or language header) and that it should look for it in the places designated for such files. By default, the compiler looks in the same directory as the current file. As we want it to look for the header file in another directory, we amend the Project Settings by adding fgw headers to

the include path. Using the wrong syntax for a #include directive is a common error; it matters whether we use angle brackets (headers and system header files) or double quotes (user and third-party header files).

GETTING STARTED

21

The next three lines are the same as for our first program, with the same significance. The line fgw::playpen blank; tells the compiler that at this stage in the program you want a Playpen and that you are naming the object representing it blank. If you are familiar with the concept of declarations and types, fgw::playpen is a type and the whole statement is a declaration of blank. I will be covering the details of declarations in the next chapter.

I hope that the meaning of std::cout << "Please press the 'ENTER' key"; is obvious. The last statement of the program, std::cin.get( ); may seem strange to some readers. std::cin is the standard C++ console input object (the input equivalent of std::cout). We largely use it to obtain information from the keyboard. The remainder of the statement tells the compiler that you want to get a single character from the keyboard. C++ only extracts data from the keyboard when signaled that input is complete (usually by the user of the program pressing the Enter key).

We have to use some way to keep the program running until we have finished with the Playpen, because the window will close automatically when the program ends. Try removing the std::cin statement (or commenting it out by inserting // at the beginning of the line) and then running the revised program. The Playpen just flashes on the screen. The std::cin.get( ) causes the program to wait until the user provides some input by pressing Enter.

Something to Play With

Neither of the programs you have tried does anything even vaguely interesting, but if you would like to try something a little more exciting you can read Chapter 1 of You Can Do It! [Glassborow 2004], which will provide you with more information about what can be done in a Playpen window.

Summary

MinGW Development Studio supplies various tools for software development with the MinGW suite of development tools.

When starting a new project you may need to change the Project Settings. In particular, you may need to tell the compiler where to find some header files, and you may need to tell the linker about any special libraries your program uses. The mechanism may not be the same for other IDEs but the substance will be. Any IDE will need to know where to look for header files and which libraries are used. In addition, the search order of libraries usually matters. In other words, the order of the library list matters. A library name is specified on the command line with the -l (minus el) prefix. A library is conventionally stored in a file that prefixes the library name with lib.

Every C++ program includes a function called main( ), which is used as the entry point for the program.

C++ includes a pair of objects, std::cout and std::cin, which represent the console output device (defaulting to a window on the monitor) and console input device (defaulting to the keyboard). The iostream header makes these objects available.

We can send (shift out) data to std::cout by using << and we can extract a single character from std::cin by using the get( ) function.

C H A P T E R 2

Fundamental Types,

Operators, and Simple

Variables

The main objective of this chapter is to introduce you to some of the most fundamental elements of C++. You may already have a clear idea about the meaning of the terms ‘type’, ‘operator’, and ‘variable’ or ‘object’ in programming, but even so you should do a quick pass through this chapter to check that your understanding of these things concurs with the way C++ uses those terms.

I am going to cover some simple theory and terminology to start with. You may find it indigestible at first, or it may be telling you what you already know. By putting it up front in this chapter, you will be able to come back to it when you need to, as you work through the rest of the chapter.

If you have a background in a language that provides strict ranges of values for its fundamental types (such as Java), you will need to pay particular attention to the details of C++’s types: C++ has more liberal requirements for most of its fundamental types. If you are coming from a non-procedural language (such as Lisp or Scheme), you may find some aspects of coding in C++ strange. You may even have a gut reaction that they illustrate bad programming. Mainstream procedural languages such as C++ rely heavily on assignment operators and explicit loops.

A Simple Program

Those without a great deal of experience of programming often find understanding the programming concept of type difficult to grasp. This will be particularly true for those whose prior experience has been with languages such as Python, where a great deal of type information is implicit. With that in mind, here is a simple C++ program. Please create a project (as you did for the ‘Hello World’ program in the last chapter) and then enter the following source code. When you have done that, compile and link it, and then execute the result.

1 // written by FGw 06/08/2004

2 #include <iostream>

3

4 int main( ){

5int count(0);

6double total(0.0);

7while(true){

8 std::cout << "Next value please: ";

9 double value;

24

CHAPTER 2

10std::cin >> value;

11if(value > 9999.0) break;

12++count;

13total += value;

14}

15std::cout << "\nYou input " << count << " values. \n";

16std::cout << "Their total is " << total << ".\n";

17std::cout << "The arithmetic mean (average) of those values is "

18<< total/count << ".\n";

19return 0;

20}

Now I will use this source code in the following discussion of type, operators, and variables.

What Is a Type?

Types are specifications for entities that have specified behavior. As well as behavior, most types also specify data that can vary from one instance to another. We roughly divide types into two categories: value types and object types. A value type is one where it makes sense to treat instances with the same data as being interchangeable. In other words, instances do not have any special significance beyond their data. We can reasonably ask whether two instances of a value type are equal. An object type is one where the identity of an instance is significant and it makes sense to ask if two potentially different instances are actually the same object, rather than just having the same characteristics.

For example, such concepts as integer, word, color, and time are all examples of value types. However, such things as programmatic representations of cars, pictures, and diaries are examples of objects. A copy of a picture is distinctly different from the original (try selling a copy as an original masterpiece and see how long a jail sentence you get).

Another characteristic of value types is that they can exist as pure values (usually described as ‘literals’) that do not have some explicit place where they are stored. In the above program, 0 (line 5) is an example of a numeric literal (whose type, we will discover, is int); "Next value please: "(line 8) is an example of a string literal. String literals actually do need to be stored somewhere, but they do not have a unique identity. If a program uses an identical string literal in several places, it only needs to store a single copy.

We will be mainly concerned with value types in this chapter. However, we have already made use of several examples of object types. The fgw::playpen objects we used in the last chapter to handle the Playpen window do not allow the existence of pure values (i.e. there are no fgw::playpen literals). std::cout and std::cin are also examples of object types (ones designed to handle output and input). Such objects have state, which represents their condition, but we cannot isolate that information from an object that stores the information. For example, an fgw::playpen object remembers what scale it is using for plotting pixels, and we can even ask an fgw::playpen object what scale it is currently using, but scale is an attribute of an fgw::playpen object and has no independent existence. If the concept of ‘state’ as opposed to that of ‘value’ is unclear or confusing, put it to one side for now. Eventually, to master use of C++ for object-oriented programming, you will need to understand the difference, but that requirement is still some time in the future.

Most programming languages have some form of integer type. Such a type consists of a range of allowed values together with operations that can be performed on those values. Typical operations for an integer type are addition, subtraction, multiplication, and division. In C++ (as in C), such behavior is normally provided by the use of operators. The addition and subtraction operators are those normally used in handwritten arithmetic (+ and -), but the division operator in C++ is the slash, or solidus (/), which is less frequently used in elementary arithmetic than ‘÷’. The programming choice was made because ‘÷’ was easily confused with + when handwritten code was being input by professional key-punch operators in the early days of computing. The traditional symbols for multiplication (‘×’ and ‘·’ – a vertically centered dot) are too easily

FUNDAMENTAL TYPES, OPERATORS, AND SIMPLE VARIABLES

25

confused with other uses of those symbols, so the programming sign for multiplication in C++ (and many other computer languages) is *. The juxtaposition of variables to mean implicit multiplication (e.g. xy = x × y) does not work in programming languages, because xy (for instance) is a legitimate name for a single variable.

C++ (and C) provide many other arithmetic operators, such as the += (add the value on the right into the value stored in the object on the left) used in line 13 above. You will find a complete listing of the operators for integral and floating-point types in the reference section of this chapter.

Language Note: If you come from a background of functional languages such as Haskell, you may be surprised by the extensive use of assignment in C++. This is one of the major visible differences between functional languages and procedural ones.

An instance of a value type will need some memory in which to store the value. This is even true for pure values, though we then leave the mechanism entirely up to the compiler. (For those interested in such things, values are often held in CPU registers or in some other form of scratch memory.) For the purpose of this book, I am going to use the terms value and object. An object provides storage for a value (as well as storage for the state of an instance of an object type).

Lines 5 and 6 of the above program are examples of creating objects (of two different types) and initializing them with values. In line 5 we tell the compiler that we intend to use an object called count. We tell the compiler that the type of count is int and that we want it to start with the int value 0. Line 6 tells the compiler that we want another object called total, with type double and initial value 0.0. Note that 0 and 0.0 are literals (pure values) of types int and double respectively.

If we do not provide an initial value, the compiler will create a default object of the specified type. In the case of types int and double, a default object will have an indeterminate value (i.e. it can be anything, and it can even change from moment to moment). There are very few ways to use such objects until you have stored a value in them. Line 9 gives an example of creating a default object, named value, of type double. At line 10 we extract a value from the console, which is then stored in value. One of the few things that you can do with an object with an indeterminate value is assign a value to it, after which the value is whatever was assigned.

Line 11 provides another example of a double value (literal) rather than an object of that type. The 9999.0 is a value of type double. I know that it is a double value because the rules of C++ say that a numeric literal with a decimal point included has type double (unless explicitly stated to be of some other type, but we will get to that much later.)

Note that we can have a value without an object if we do not need to store it. Such ‘naked’ values come in two forms: literals (which are provided within the code) and temporaries (which result from evaluating expressions). 17 is a literal and value + 2.2 is a temporary. 17 + 21 is strictly a temporary that results from evaluating that expression (though the compiler usually evaluates simple integer arithmetic on literal values, rather than leave the task to the program).

In addition to having a range of allowed values, a type also has behavior. Loosely, for value types, that means ways in which the values can be transformed into other values, accessed by other objects, or used to create some external (to the program) result. A value of one type can sometimes be transformed into a value of a different type; that is also part of the behavior of a type. I could replace line 6 of the above program with:

double total(0);

The literal 0 has type int (no decimal point so it isn’t a floating-point value). However, in the context, the compiler recognizes that a double value is required. It will silently convert the 0 into 0.0. As this is entirely safe in the context, a compiler would not normally trouble the programmer with any form of warning message.

What Are Fundamental Types?

C++ provides a number of elementary types that are part of the language. These come in two major flavors, integral types and floating-point types. All other types are built by using these. At a minimum, we need four

26

CHAPTER 2

types for simple programming. These are bool, char, int and double. The first three are integral types and the last one is the most frequently used floating-point type. You will find a complete list of all the fundamental types in the reference section of this chapter, together with some guidance as to their designed usage, but here are some details of the essential four.

bool is a type that has only two values, true and false. In simple terms, it can represent a single bit of information – the answer to a question that has only two possible answers, such as a simple ‘yes’ or ‘no’. bool, true and false are all keywords (words with language-defined meanings) of the C++ language. We cannot use keywords for anything else. In the above code ‘(value > 9999.0)’ is an example of an expression of type bool. When the program comes to execute the code that the compiler produces for line 11, it generates a temporary bool value that is either true or false depending on whether value is or is not greater than 9999.0.

char is the type that is intended for values that represent characters. For historical reasons it is an integral type. C++ requires that it can represent at least 256 distinct values, which can either be -127 to 127 (together with either -128 or -0) inclusive or 0 to 255 inclusive. C++ does not specify that char cannot represent a wider range, but on most common desktop machines, the range is either -128 to 127 or 0 to 255. int is an integral type that is required to be able to represent all values in the range -32767 to 32767 (together with either -32768 or -0). The curious alternative of -0 is because C++ allows an implementation to use any one of three binary representations for negative integers: two’s complement, one’s complement, or sign and magnitude. Most modern desktop computers use two’s complement. Two’s complement does not

have a negative zero.

Representing Negative Integers

C++, along with many other programming languages, uses a binary representation for natural numbers (zero upwards). This matches well with all the generally available computing hardware (hardware that uses non-binary representations is sometimes developed for experimental purposes but cannot be used efficiently by the majority of programming languages, which assume the hardware will use binary representations).

The problem arises with the representation of negative numbers. There are three different ways to handle negative integers with an essentially binary representation. There is hardware around that uses each of the three possibilities, though most hardware uses two’s complement. As a general-purpose language, C++ provides rules for integers that allow the use of any of the three representations. The highest bit determines the sign in all three representations. A zero for the highest bit designates a positive value that is determined by the remaining bits. A one as the highest bit signifies that the rest of the bits represent a negative value in some way or other. At that stage, we need to know which representation is used, because the value represented by those bits will depend on the choice. The three options are called ‘sign and magnitude’, ‘one’s complement’ and ‘two’s complement’.

Consider the 8-bit pattern 10000111 (I am using just eight bits to keep the arithmetic simple). As the left-hand (highest) bit is one, the remaining seven bits (0000111) represent a negative value. The value depends on the representation used for negative values.

Sign and magnitude is the easiest representation for humans to understand, but very few computers use it. The remaining bits are the magnitude and are a normal binary representation of a value. The value of the example pattern will be negative 0000111, that is, −(4 + 2 + 1). Therefore, the eight bits 10000111 represent -7 when we use sign and magnitude.

One’s complement treats the remaining bits by inverting them (swapping one for zero and vice versa). In this case, 10000111 represents negative 1111000, that is, −(64 + 32 + 16 + 8). Therefore, 10000111 in one’s complement represents -120.

Two’s complement is by far the most common representation used by computing hardware while also being the hardest for many humans to understand. It is like one’s complement except that we add 1 to the result of flipping the value bits. Now 10000111 represents negative (1111000 + 1) which evaluates to -121. The reason is the curious mathematical property of this representation that ensures correct answers to arithmetic without having to provide any special support for negative numbers. Well that is true as long as all

FUNDAMENTAL TYPES, OPERATORS, AND SIMPLE VARIABLES

27

the values remain within the range provided by the type. If you try adding 10000111 (-121) to 10000001 (-127) you will get the wrong answer, because the result of adding together those two negative numbers is too large a negative value (-248) to fit into 8 bits. Most systems will produce 8 as the answer (though according to the strict letter of the C++ Standard such overflow has undefined behavior).

One curiosity of both sign-and-magnitude and one’s complement representations is that they have representations for both -0 (10000000 in the 8-bit sign-and-magnitude case; 11111111 in the 8-bit one’s complement case) and 0 (00000000 in both cases). Effectively they have two representations for zero.

Two’s complement has its own curiosity in that the most negative value has no matching positive value. In the 8-bit representations we have been using, 10000000 represents -128, i.e. −((64 + 32 + 16 + 8 + 4 + 2 + 1) +1). If we try to negate that by flipping all the bits and adding 1 we find ourselves back where we started. (Actually there is a final carry but that is lost because we have a limited number of bits at our disposal.) Mathematically that leads to the odd feature that negative 10000000 equals itself.

The above curiosities show that in terms of computer representation of signed integral values all choices have their surprises. Fortunately, most of the time, we do not need to concern ourselves with the underlying representation. The exception to this is when we are actually making use of bit patterns rather than the values they would represent as some kind of integer. For example, the shift-left operation common to most hardware does not consistently multiply a negative value by 2. On the other hand, shift-right does divide by 2 for one’s and two’s complement representations, but the treatment of negative odd numbers may not be what you expect.

Most recent machines provide a far wider range for int (-2147483648 to 2147483647) but you should not rely on this if you are writing programs that may be used on older or more limited equipment.

Language Note: If you come from languages such as Java or C#, you should take special note that C++ only provides minimum ranges for its fundamental types. You must not assume that an integral type will have the same range of values for all compilers. Some compilers even allow the programmer to choose whether an int uses a 16-bit range or a 32-bit range. In addition, some systems use other ranges. The only rules are that the range must be symmetric (except for the extra negative value) and that it must be at least 16 bits for an int.

Derivative Types

The types I am now going to write about are called derived types by C; however, I want to keep that term for later use when we start working with user-defined types.

There are various ways that we can create new types from existing types. We can qualify any type as const and/or volatile. A const-qualified type is the type for an object whose value (state) must not be changed from within the program. const can be used to limit the kind of access provided to an object. Accessing an object through a const-qualified reference or pointer (I will be dealing with exactly what those are later on, but for now it is sufficient to know that they are ways of providing access to an existing object) prohibits changes to the underlying object. Think of const as a way to specify that a name provides read-only access to an object. It can also be used to tell a compiler that an object is immutable (think of the difference between protecting a file from being changed and placing a file in ROM). In C++, a const object cannot be changed. However, a const reference or pointer to const type cannot be used to change the object referred or pointed to even if that object is not declared as being const. volatile types are highly specialized; they are types whose values can be changed by events outside the program. We use C++ objects of volatile type for things such as memory-mapped ports. This area is too specialized for this book, and little more will be said about the use of volatile types.

There are two other forms of derivative type, references and pointers. Any type that is not already a reference type has a corresponding reference type as a derivative. That is the end of the line; you cannot have a derivative type from a reference type.

Any non-reference type has a corresponding pointer type as a derivative type. This includes pointer types themselves: we can have pointers to pointers to . . . (Fortunately, we usually manage to avoid these.) const (and volatile, in theory) can be used to create derivatives from pointer types. Therefore, for example,