PPP2_Ch18.pdf

(804 KB) Pobierz
18
Vectors and Arrays
“Caveat emptor!”
—Good advice
T
his chapter describes how vectors are copied and accessed
through subscripting. To do that, we discuss copying in
general and consider
vector
’s relation to the lower-level notion of
arrays. We present arrays’ relation to pointers and consider the
problems arising from their use. We also present the five essential
operations that must be considered for every type: construction,
default construction, copy construction, copy assignment, and
destruction. In addition, a container needs a move constructor
and a move assignment.
627
Stroustrup_book.indb 627
4/22/14 9:42 AM
628
CHAPTER 18 • VECTORS AND ARRAYS
18.1 Introduction
18.2 Initialization
18.3 Copying
18.3.1
18.3.2
18.3.3
18.3.4
Copy constructors
Copy assignments
Copy terminology
Moving
18.6 Arrays
18.6.1
18.6.2
18.6.3
18.6.4
Pointers to array elements
Pointers and arrays
Array initialization
Pointer problems
18.7 Examples: palindrome
18.7.1 Palindromes using
string
18.7.2 Palindromes using arrays
18.7.3 Palindromes using pointers
18.4 Essential operations
18.4.1 Explicit constructors
18.4.2 Debugging constructors and
destructors
18.5 Access to
vector
elements
18.5.1 Overloading on
const
18.1 Introduction
To get into the air, a plane has to accelerate along the runway until it moves fast
enough to “jump” into the air. While the plane is lumbering along the runway,
it is little more than a particularly heavy and awkward truck. Once in the air, it
soars to become an altogether different, elegant, and efficient vehicle. It is in its
true element.
In this chapter, we are in the middle of a “run” to gather enough program-
ming language features and techniques to get away from the constraints and dif-
ficulties of plain computer memory. We want to get to the point where we can
program using types that provide exactly the properties we want based on logical
needs. To “get there” we have to overcome a number of fundamental constraints
related to access to the bare machine, such as the following:
• An object in memory is of fixed size.
• An object in memory is in one specific place.
• The computer provides only a few fundamental operations on such ob-
jects (such as copying a word, adding the values from two words, etc.).
Basically, those are the constraints on the built-in types and operations of C++ (as
inherited through C from hardware; see §22.2.5 and Chapter 27). In Chapter 17,
we saw the beginnings of a
vector
type that controls all access to its elements and
provides us with operations that seem “natural” from the point of view of a user,
rather than from the point of view of hardware.
This chapter focuses on the notion of copying. This is an important but rather
technical point: What do we mean by copying a nontrivial object? To what extent
Stroustrup_book.indb 628
4/22/14 9:42 AM
18.2
INITIALIZATION
629
are the copies independent after a copy operation? What copy operations are
there? How do we specify them? And how do they relate to other fundamental
operations, such as initialization and cleanup?
Inevitably, we get to discuss how memory is manipulated when we don’t
have higher-level types such as
vector
and
string
. We examine arrays and point-
ers, their relationship, their use, and the traps and pitfalls of their use. This is
essential information to anyone who gets to work with low-level uses of C++
or C code.
Please note that the details of
vector
are peculiar to
vector
s and the C++
ways of building new higher-level types from lower-level ones. However, every
“higher-level” type (
string
,
vector
,
list
,
map
, etc.) in every language is somehow
built from the same machine primitives and reflects a variety of resolutions to the
fundamental problems described here.
18.2 Initialization
Consider our
vector
as it was at the end of Chapter 17:
class vector {
int sz;
//
the size
double* elem;
//
a pointer to the elements
public:
vector(int s)
//
constructor
:sz{s}, elem{new double[s]} { /*
. . .
*/ } //
allocates memory
~vector()
//
destructor
{ delete[] elem; }
//
deallocates memory
//
. . .
};
That’s fine, but what if we want to initialize a vector to a set of values that are not
defaults? For example:
vector v1 = {1.2, 7.89, 12.34 };
We can do that, and it is much better than initializing to default values and then
assigning the values we really want:
vector v2(2);
v2[0] = 1.2;
v2[1] = 7.89;
v2[2] = 12.34;
//
tedious and error-prone
Stroustrup_book.indb 629
4/22/14 9:42 AM
630
CHAPTER 18 • VECTORS AND ARRAYS
Compared to
v1
, the “initialization” of
v2
is tedious and error-prone (we delib-
erately got the number of elements wrong in that code fragment). Using
push_
back()
can save us from mentioning the size:
vector v3;
v2.push_back(1.2);
v2.push_back(7.89);
v2.push_back(12.34);
//
tedious and repetitive
But this is still repetitive, so how do we write a constructor that accepts an initial-
izer list as its argument? A
{ }
-delimited list of elements of type
T
is presented to
the programmer as an object of the standard library type
initializer_list<T>
, a list
of
T
s, so we can write
class vector {
int sz;
//
the size
double* elem;
//
a pointer to the elements
public:
vector(int s)
//
constructor (s is the element count)
:sz{s}, elem{new double[sz]} //
uninitialized memory for elements
{
for (int i = 0; i<sz; ++i) elem[i] = 0.0; //
initialize
}
vector(initializer_list<double> lst)
:sz{lst.size()}, elem{new double[sz]}
{
copy( lst.begin(),lst.end(),elem); //
initialize (using std::copy(); §B.5.2)
}
//
. . .
};
//
initializer-list constructor
//
uninitialized memory
//
for elements
We used the standard library
copy
algorithm (§B.5.2). It copies a sequence of ele-
ments specified by its first two arguments (here, the beginning and the end of the
initializer_list
) to a sequence of elements starting with its third argument (here,
the
vector
’s elements starting at
elem
).
Now we can write
vector v1 = {1,2,3};
vector v2(3);
//
three elements 1.0, 2.0, 3.0
//
three elements each with the (default) value 0.0
Stroustrup_book.indb 630
4/22/14 9:42 AM
18.3
COPYING
631
Note how we use
( )
for an element count and
{ }
for element lists. We need a
notation to distinguish them. For example:
vector v1 {3};
vector v2(3);
//
one element with the value 3.0
//
three elements each with the (default) value 0.0
This is not very elegant, but it is effective. If there is a choice, the compiler will in-
terpret a value in a
{ }
list as an element value and pass it to the initializer-list con-
structor as an element of an
initializer_list
.
In most cases — including all cases we will encounter in this book — the
=
before an
{ }
initializer list is optional, so we can write
vector v11 = {1,2,3};
vector v12 {1,2,3};
//
three elements 1.0, 2.0, 3.0
//
three elements 1.0, 2.0, 3.0
The difference is purely one of style.
Note that we pass
initializer_list<double>
by value. That was deliberate and
required by the language rules: an
initializer_list
is simply a handle to elements
allocated “elsewhere” (see §B.6.4).
18.3 Copying
Consider again our incomplete
vector
:
class vector {
int sz;
//
the size
double* elem;
//
a pointer to the elements
public:
vector(int s)
:sz{s}, elem{new double[s]} { /*
. . .
*/ }
~vector()
{ delete[] elem; }
//
. . .
};
//
constructor
//
allocates memory
//
destructor
//
deallocates memory
Let’s try to copy one of these vectors:
void f(int n)
{
vector v(3);
v.set(2,2.2);
//
define a vector of 3 elements
//
set v[2] to 2.2
Stroustrup_book.indb 631
4/22/14 9:42 AM
Zgłoś jeśli naruszono regulamin