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

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

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

68

CHAPTER 3

wait till the user presses the Enter key. At that point it should add a row of two pixels with the existing one above and central. This process should be repeated until the screen displays the tenth triangular number.

10.Write a program that separates the whole numbers from 1 to 100 into two sets so that the sum of the square roots of the numbers in each set are equal, to six significant figures. Be careful: you are dealing with floating-point numbers, and the concept of equality will need careful handling. You may use the Library function std::sqrt. You will need to include the <cmath> header.

11.Write a program that takes a number of words as input and outputs their average (arithmetic mean) length and the overall ratio of vowels to consonants. You may use the fact that objects of the std::string type, like those of a std::vector type can report their size (number of characters) by using size( ).

12.Write a program that will take in pairs of words and then output ‘anagram’ or ‘not anagram’ depending on whether they are or are not composed of the same letters. std::string objects can be sorted in exactly the same way that we sorted a std::vector object. You can also compare strings for equality using == between them. That should be enough help.

13.Write a program that accepts integer values in the range −100 to 100 and outputs their names in text. So if the input were 27, the output should be ‘twenty-seven’. If the input were −39 the output would be ‘minus thirty-nine’ or ‘negative thirty-nine’. If you feel up to it, try converting your program to work with some other natural language such as French. Even harder, try writing a program where the user types in the text and the output is the number.

REFERENCE SECTION

Decisions

C++ provides three primary mechanisms for making decisions:

1.if-else, for an essentially two-way decision. The else is optional, but if it is present it must be the next statement after the one controlled by the if. The controlled statement may be either a simple statement or a compound one (a block of statements in braces).

2.switch, allowing a multi-way selection controlled by an integer value. The selections are identified by the case keyword followed by a constant integer value and a colon. Execution of a selection is terminated by the next break or return statement or by the closing brace of the switch statement. A switch statement may include a single catch-all option identified by the default keyword.

3.A conditional operator that selects which of two expressions to evaluate depending on a control expression.

The form is: control-expression ? expression-for-true : expression-for-false.

The difference between mechanisms 1 and 3 is that in the former we have controlled statements, and in the latter we have controlled expressions. Expressions are evaluated to provide values that may be used as part of larger expressions within the same statement. Statements are complete and do not have values. The following code snippets (intended to be part of the exit from a function) demonstrate the difference in use:

std::string response; std::cout << "Yes or no? "; std::cin >> response;

if(response[0] == 'n') return 0;

LOOPING AND MAKING DECISIONS

69

else return 1;

or

std::string response; std::cout << "Yes or no? "; std::cin >> response;

return (response[0] == 'n') ? 0 : 1;

The first case uses two return statements selected by the if-else construct; the second case returns a value selected by a conditional expression.

Looping, Repetition, and Iteration

These are just three terms with very similar meanings. C++ provides three main mechanisms for looping.

1.while(control-expression) action. The action statement (usually a compound statement enclosed in braces) is repeated so long as the control expression evaluates as true (or non-zero). The control expression is evaluated before every repetition including the first. If it evaluates to false (or zero) the action statement is not executed and processing resumes with the next statement. Note that this means that the action part of the statement may sometimes not be executed even once.

2.do{actions} while(control-expression); This variant tests for repetition after each execution of the action block. It is used when the actions must always be executed at least once. Otherwise, it is similar to the while loop..

3.for(initialization; test; termination) action. This form of looping is largely equivalent to writing:

initialization; while(test){

action termination;

}

We never need to use a for statement (or alternatively we can always write a while statement as a for statement). However, using both constructs allows us to provide idioms that help other programmers follow our intentions. It is generally idiomatic to use a for statement when the number of repeats is determined by some value (e.g. when we want to count through a number of cases). We use a while statement when we expect the end of repetition will result from some other condition such as reaching the end of a file that we are processing.

The most common use of do-while is for cases where some form of data must be obtained and processed with an option to get more data depending on the result of the current repetition.

C++ provides several ways to terminate processing of a loop. The continue keyword allows the program to abort the current iteration and go directly to the test for the next repetition. In the case of a for loop, continue immediately resumes execution with the termination statement before testing for another repetition.

The break statement is used to exit the loop altogether. In other words, break forces execution to resume with the statement immediately after the current loop construct.

There are other statements such as return statements that will result in early termination of a loop, but those are consequences of their designed behavior rather than behavior designed to get out of a loop.

70

CHAPTER 3

Standard C++ Library Types

std::string

This type provides text type behavior. It also provides suitable behavior for a sequence of char values. It includes the functionality we expect for text such as supporting appending text and comparing text for equality. As a sequence type, it provides the functionality of a C++ sequence. Sequence objects know their length or size, they know where they start and end, they can usually be indexed and they can be sorted. We will learn more of the functionality of std::string in later chapters.

std::vector<type>

std::vector is an instance of what C++ calls a class template. It provides the functionality we expect for a dynamically resizable array of something. Because the functionality of a sequence is largely independent of the type of object in the sequence, we want a way to specify that functionality independent of the type of object being stored in the sequence. One of the major uses of a class template is providing such generic functionality. We can plug in an extensive range of types at the point of use. Only relatively few types will fail to work correctly with std::vector. The basic criterion for a type to be usable to instantiate a std::vector is that objects of the type must be copiable and assignable. In other words, you must be able to pass objects of the type by value, and you must be able to assign a value of the type to an object of the same type. At this stage in your study of C++, those restrictions are not likely to mean much, but they will by the time you have finished using the tutorial part of this book.

C H A P T E R 4

Namespaces and the C++

Standard Library

I will be introducing several services provided by the C++ Standard Library. I will explain namespaces and how to avoid having to type such things as std:: and fgw:: over and over again.

The Library is a substantial resource for any C++ programmer and one that it pays to know about. Nicolai Josuttis took almost 800 pages to cover it his excellent tutorial and reference, The C++ Standard Library [Josuttis 1999] and there will shortly be a volume by Dietmar Kuel¨ detailing a substantial set of recent additions. I will be limiting my coverage to those parts that I use in this book. However, you should get used to checking whether the Library provides what you need for a program before spending time writing your own code. The easiest way to check is to ask. Usenet newsgroups such as comp.lang.c++.moderated and alt.comp.lang.learn.c-c++ are invaluable resources for asking questions about what C++ provides that might help with solving a problem.

Wide Versus Narrow Character Set Support

C++ provides full support for two kinds of character set: narrow and wide. We can represent characters in the C++ narrow character sets with 8-bit values. These character sets are fine for Standard English and even have room for values representing the more common accented letters. However, 8 bits (256 values) are insufficient for representing the character sets used elsewhere in the world. Even 16 bits (65536 values) are insufficient for a simple encoding of all the characters used somewhere in the world.

For more than a decade the Unicode Consortium has been working at providing a universal encoding for all character sets being used anywhere in the world. The latest Unicode Standard (version 4) requires 20 bits for a flat encoding (i.e. one that does not involve special codes to shift from one character set to another). Unicode is effectively equivalent to the ISO 10646 character-encoding standard.

C++ provides the wchar t type to support extended character encoding, i.e. characters that belong to some extended character representation. C++ does not require that the values for wchar t represent Unicode. However, C++ provides a standard representation for Unicode literals.

C++ also provides support for all types and objects based on wchar t that are analogs of the types and objects based on char. I will confine myself, in this book, to the narrow character set and its support through char-based objects and types. The current release of the MDS implementation at the time of writing does not provide full support for the wide-character alternatives of std::string and the console I/O objects. In the context of learning C++, that is not a serious handicap. When you have learned to use the narrow versions correctly, you will find it easy to switch to the wide ones when you need them.

72

CHAPTER 4

Namespaces

In the early ’90s it became clear that C++ programmers would make extensive use of third-party libraries. In addition, the Standard Library was itself likely to grow. Names in different libraries were likely to clash. Worse, compilers would not always be able to identify such cases as being errors. As we will see in the next chapter, it is possible to use a single name with several meanings visible in the same scope (it is called overloading). Devising a mechanism that would distinguish names from different libraries seemed useful. The namespace mechanism was designed to deal with these problems.

Definition: A scope, sometimes called a declarative scope, is a region of source code in which declared names retain the meaning given to them by the declaration. The smallest normal scope is that provided by a matched pair of braces inside a function definition. A try block is an example of such a scope. The largest scope is that called ‘the global scope’ and includes all names that are declared outside of any more restrictive scope. The concept of scope is important but one that is best acquired by giving examples in relevant contexts as and when they occur. Names in inner scopes can hide the same names in outer ones.

We can provide a scope for a library or part of a library using the following syntax:

namespace X{ declarations-and-definitions

}

The effect of this construct is to prefix all the names declared/defined between the braces with X:: to provide a fully elaborated name. Within a namespace scope, the names declared in that namespace can be used without any elaboration, but the default outside the braces requires the full elaboration by prefixing the namespace name and a double colon. For example:

namespace example{ int i(0);

int j(i); // initialize j from i

}

int k(i); // error, no i in scope

int k(example::i); // OK, use the i found in namespace example

All blocks that are qualified by the same namespace name are part of the same scope. So

namespace example{

int m(i); // initialize a new int, m, with the value of the i // that was declared in namespace example above

}

extends the namespace example by adding a new name, m, and initializing it with the current value stored in example::i. Perhaps you wonder about code such as

namespace example{ int i(0);

}

int i(1); int n(i);

NAMESPACES AND THE C++ STANDARD LIBRARY

73

(in other words, code where a name declared inside a namespace matches one declared outside the namespace). Declaring the same name in different scopes creates different instances of the name and so they name different objects: n will be initialized with 1, not 0.

WARNING!

Reusing names in different scopes is a constant cause of confusion and is best avoided.

It is useful to know where a function or other entity has been declared, but that is sometimes information that we do not need to make explicit. I would never call an object cout because that, to both me and every other C++ programmer, is the name of the console output object. In just about every context, we expect it to be synonymous with std::cout. Indeed many older books on C++ just use cout; many of them were written before namespaces were invented and the full name was adjusted to std::cout.

The designers of C++ did not invent namespaces to force programmers to type more symbols but to provide a mechanism for disambiguation when two programmers pick on the same name for a non-local entity such as a function or a user-defined type. How can we regain the simplicity of unelaborated names?

C++ provides two main mechanisms and a way to shorten long namespace names.

using Directives

A using directive is a mechanism to allow a programmer to use all the names from a namespace without explicitly prefixing them with the namespace name. We should use such a crude tool with care. Nonetheless, using directives are frequent in books because they save space and reduce the need to write source-code statements over two or more lines. On the other hand, we normally avoid using directives in production-quality code.

The form of a using directive is simply:

using namespace X;

From that point, the compiler will check for declarations in visible parts of namespace X when it is searching for a name’s declaration. The reason that I specified visible parts is that a using directive is not some magical incantation that allows the compiler to see names declared elsewhere or at some future place such as in files that have not been included into the present one. A using directive is not an instruction to the compiler to search for all the places that declare names in that namespace. It simply tells the compiler that when looking for a name it must look inside any currently visible blocks belonging to that namespace. Note that source code is not visible until the compiler has passed through it. Declarations are not visible until after the point of declaration. C++ treats declarations in the strict order in which they appear in a translation unit (the technical term for a file of source code after all the included files have been processed into it).

using Declarations

A using declaration is a mechanism that allows the compiler to use simple names (names without namespace qualification) from any visible declarations of a specific fully elaborated name. This use is ‘as if’ we had declared the name at the point of the using declaration without the elaboration of the namespace prefix. Superficially, a using declaration may seem to do the same thing as a using directive, just confined to a single name. Far from it, it does something quite different: a using declaration brings a name into the current scope, whereas a using directive tells a compiler another place to look for names that it has not yet found.

When I write

using std::cout;

74

CHAPTER 4

I tell the compiler to find all the declarations of cout in currently visible blocks for namespace std and treat those declarations as if they replace the using declaration.

Example

In the next chapter, we will be looking at some aspects of C++ functions. That will include demonstrating that two functions can share a name (called function overloading) as long as the types of their parameters allow them to be distinguished. However, I want to clarify the difference between using declarations and using directives. Please compile and execute the following two programs and note the difference in output:

Program 1

#include <iostream>

namespace x{

void foo(int){std::cout << "int case";}

}

A using namespace x;

namespace x{

void foo(double){std::cout << "double case";}

}

int main( ){ foo(1.3);

}

Program 2

#include <iostream>

namespace x{

void foo(int){std::cout << "int case";}

}

B using x::foo;

namespace x{

void foo(double){std::cout << "double case";}

}

int main( ){ foo(1.3);

}

Commentary

In the first case the compiler is told at line A that it can look for names in namespace x if it does not find the name in the current scope. When it comes to foo(1.3) it does not find a candidate outside namespace x but finds two candidates inside; it chooses the one that uses a double because that exactly matches the type of the argument.

NAMESPACES AND THE C++ STANDARD LIBRARY

75

In the second case, line B instructs the compiler that all the declarations of x::foo that it has so far seen should be treated as if they had been declared at line B (without the elaboration of x::). However, it has no impact on any subsequent declarations of foo in namespace x. When it comes to foo(1.3) the only declaration available is void foo(int), so that is the one it uses by implicitly converting 1.3 to 1, which is then ignored because foo does not actually make any further use of the value passed to its parameter.

Do not worry about how function calls and function overloading work: we will tackle that in the next chapter. For now, I want you to keep focused on the different behaviors provided by a using declaration and a using directive as shown by the above code.

Namespace std and Namespace fgw

All those places where you have had to type std:: are examples of the using fully elaborated names from the Standard Library. There are advantages to using fully elaborated names. In the early chapters of this book it is always clear which names come from the Standard Library, because they always start with std::. That tells you where you can look for documentation.

In contrast, I have always prefixed the names from my support library with fgw::, which tells you that you need to refer to any documentation that I have provided for my library if you want to learn more about such names.

In this book, I will generally use using declarations for names from the C++ Standard Library. Otherwise, I will use the fully elaborated names (as I have been doing up to now). I will do much the same for names from my library except that I will sometimes use a using directive. In particular, I will do this when using the color names such as fgw::red1. There are a lot of them and it gets tedious to bring them in with using declarations. Using fully elaborated names can also be tiresome if there is no actual conflict with another library.

T R Y T H I S

Choose some of the code you typed in for the earlier chapters and modify it so that it no longer uses fully elaborated names. It is your choice whether you use using directives, using declarations, or a mixture. Experiment until you feel reasonably confident with both.

Fully Elaborated Global Names

One advantage of namespaces is that we have a mechanism to distinguish identically spelled names from different namespaces; we can use their fully elaborated names even when the short versions of the names collide. However, how should we tackle the problem of collisions that involve a global name, i.e. a name that has been declared outside of any namespace or other limited scope?

The answer is simple once you know it: just prefix the global name with a pair of colons. C++ calls the double colon the scope operator, though I think it is pushing the concept of an operator by using that terminology. Here is a silly program that demonstrates the use of a fully elaborated global name:

1 #include <iostream>

2 using namespace std;

3

4 int cout(1);

5

6 int main( ){

7 std::cout << ::cout;

8 }

76

CHAPTER 4

T R Y T H I S

Type in and execute that program. It should work and result in outputting 1 to the console window. Now try replacing either or both of the fully elaborated names in line 7 by the simple name. When you try to compile, you will receive an ‘ambiguity’ error: the compiler cannot tell which declaration of cout you meant it to use.

Now go back to the original code and replace line 2 with using std::cout;

Try to compile the code. You get a quite different error, because the compiler considers that you tried to declare the same name but with a different type in the same scope. You can have functions sharing a name in the same scope but that does not extend to variables.

With using directives, there is no collision of declared names because they are each in their own scope, and we can use full elaboration to resolve any ambiguity; with using declarations, we import declarations and so get an irresolvable conflict. We have to either remove the using declaration or change the name of one of the objects.

Namespace Aliases

Both std and fgw are very short names for namespaces. There is a real risk that using short names will result in a collision of the names of namespaces used by different library writers. The designers of C++ wanted to encourage the use of long names for namespaces while allowing users to provide shorter alternatives. This led to the idea of a namespace alias. Here is an example of both the idea and its use:

namespace Company_with_very_long_name{ int data;

}

namespace cwvln = Company_with_very_long_name;

Now

cwvln::data = 1;

means exactly the same as:

Company_with_very_long_name::data = 1;

WARNING!

The idea was fine but there are some unfortunate surprises when it comes to using namespace aliases that have resulted in them not being widely used. This may change in the future if those responsible for the design of C++ can remove the causes of the surprises.

Input from std::cin

The Library provides various mechanisms for extracting data from an input object. We are currently limited to using std::cin for input, and I am going to tackle three ways of extracting data from it.

NAMESPACES AND THE C++ STANDARD LIBRARY

77

The first – using get( ) – extracts the next character and returns its value as a char. So we can write

char c;

c = std::cin.get( );

and c will now contain the value for the next character in the input. If we wanted to, we could extract a whole line of input with this code fragment:

std::string input_line; char c(0);

while(true){

c = std::cin.get( ); if(c == '\n') break; input_line += c;

}

T R Y T H I S

Write a short program to use that fragment and write out the result to the console window. Try to produce an alternative formulation that uses while(c != '\') rather than the conditional break statement. Your solution should do exactly the same: read input up to the first newline, saving the input up to the newline but discarding the actual newline character.

Reading an entire line of input is quite a common requirement, so C++ provides a way to do it directly with a function called getline( ). Here is a code snippet that demonstrates using getline( ) to read a whole line of input into a std::string object:

std::string input_line; std::getline(std::cin, input_line);

It does precisely the same as the previous code, but it is shorter, and the use of a suitably named function documents what we are doing. The resulting code is both shorter and easier to understand.

Perhaps you are puzzled by the different syntax for using get( ) and getline( ). get( ) is preceded by the name of the object it is getting data from, while getline( ) has the source of data named within the following parentheses. This is not an arbitrary difference but one that represents the different natures of the two methods. Getting characters from input is part of the basic behavior (called semantics) of input objects, and so we use the syntax that represents such behavior in C++ – the source object’s name followed by a dot and the name of the function that provides the desired behavior. However, reading whole lines into std::string objects requires the cooperation of two objects, so we use a syntax that reflects the equal status of std::cin and the instance of std::string that we are calling input line.

T R Y T H I S

Modify your previous program to use std::getline( ), and check that it gives the same results.

The third way to extract data from std::cin is by using the streaming or extraction operator, >>. We have used this operator in earlier programs. The extraction operator works by identifying the type of the