new
to Create Dynamic ArraysIf all a program needs is a single value, you might as well declare a simple variable because that is simpler, if less impressive, than using new
and a pointer to manage a single small data object. More typically, you use new
with larger chunks of data, such as arrays, strings, and structures. This is where new
is useful. Suppose, for example, you’re writing a program that might or might not need an array, depending on information given to the program while it is running. If you create an array by declaring it, the space is allocated when the program is compiled. Whether or not the program finally uses the array, the array is there, using up memory. Allocating the array during compile time is called static binding, meaning that the array is built in to the program at compile time. But with new
, you can create an array during runtime if you need it and skip creating the array if you don’t need it. Or you can select an array size after the program is running. This is called dynamic binding, meaning that the array is created while the program is running. Such an array is called a dynamic array. With static binding, you must specify the array size when you write the program. With dynamic binding, the program can decide on an array size while the program runs.
For now, we’ll look at two basic matters concerning dynamic arrays: how to use C++’s new
operator to create an array and how to use a pointer to access array elements.
new
It’s easy to create a dynamic array in C++; you tell new
the type of array element and number of elements you want. The syntax requires that you follow the type name with the number of elements, in brackets. For example, if you need an array of 10 int
s, you use this:
int * psome = new int [10]; // get a block of 10 ints
The new
operator returns the address of the first element of the block. In this example, that value is assigned to the pointer psome
.
As always, you should balance the call to new
with a call to delete
when the program finishes using that block of memory. However, using new
with brackets to create an array requires using an alternative form of delete
when freeing the array:
delete [] psome; // free a dynamic array
The presence of the brackets tells the program that it should free the whole array, not just the element pointed to by the pointer. Note that the brackets are between delete
and the pointer. If you use new
without brackets, you should use delete
without brackets. If you use new
with brackets, you should use delete
with brackets. Earlier versions of C++ might not recognize the bracket notation. For the ANSI/ISO Standard, however, the effect of mismatching new
and delete
forms is undefined, meaning that you can’t rely on some particular behavior. Here’s an example:
int * pt = new int;
short * ps = new short [500];
delete [] pt; // effect is undefined, don't do it
delete ps; // effect is undefined, don't do it
In short, you should observe these rules when you use new
and delete
:
• Don’t use delete
to free memory that new
didn’t allocate.
• Don’t use delete
to free the same block of memory twice in succession.
• Use delete []
if you used new []
to allocate an array.
• Use delete
(no brackets) if you used new
to allocate a single entity.
• It’s safe to apply delete
to the null pointer (nothing happens).
Now let’s return to the dynamic array. Note that psome
is a pointer to a single int
, the first element of the block. It’s your responsibility to keep track of how many elements are in the block. That is, because the compiler doesn’t keep track of the fact that psome
points to the first of 10 integers, you have to write your program so that it keeps track of the number of elements.
Actually, the program does keep track of the amount of memory allocated so that it can be correctly freed at a later time when you use the delete []
operator. But that information isn’t publicly available; you can’t use the sizeof
operator, for example, to find the number of bytes in a dynamically allocated array.
The general form for allocating and assigning memory for an array is this:
type_name * pointer_name = new type_name [num_elements];
Invoking the new
operator secures a block of memory large enough to hold num_elements
elements of type type_name
, with pointer_name
pointing to the first element. As you’re about to see, you can use pointer_name
in many of the same ways you can use an array name.
After you create a dynamic array, how do you use it? First, think about the problem conceptually. The following statement creates a pointer, psome
, that points to the first element of a block of 10 int
values:
int * psome = new int [10]; // get a block of 10 ints
Think of it as a finger pointing to that element. Suppose an int
occupies 4 bytes. Then, by moving your finger 4 bytes in the correct direction, you can point to the second element. Altogether, there are 10 elements, which is the range over which you can move your finger. Thus, the new
statement supplies you with all the information you need to identify every element in the block.
Now think about the problem practically. How do you access one of these elements? The first element is no problem. Because psome
points to the first element of the array, *psome
is the value of the first element. That leaves nine more elements to access. The simplest way to access the elements may surprise you if you haven’t worked with C: Just use the pointer as if it were an array name. That is, you can use psome[0]
instead of *psome
for the first element, psome[1]
for the second element, and so on. It turns out to be very simple to use a pointer to access a dynamic array, even if it may not immediately be obvious why the method works. The reason you can do this is that C and C++ handle arrays internally by using pointers anyway. This near equivalence of arrays and pointers is one of the beauties of C and C++. (It’s also sometimes a problem, but that’s another story.) You’ll learn more about this equivalence in a moment. First, Listing 4.18 shows how you can use new
to create a dynamic array and then use array notation to access the elements. It also points out a fundamental difference between a pointer and a true array name.
// arraynew.cpp -- using the new operator for arrays
#include <iostream>
int main()
{
using namespace std;
double * p3 = new double [3]; // space for 3 doubles
p3[0] = 0.2; // treat p3 like an array name
p3[1] = 0.5;
p3[2] = 0.8;
cout << "p3[1] is " << p3[1] << ".
";
p3 = p3 + 1; // increment the pointer
cout << "Now p3[0] is " << p3[0] << " and ";
cout << "p3[1] is " << p3[1] << ".
";
p3 = p3 - 1; // point back to beginning
delete [] p3; // free the memory
return 0;
}
Here is the output from the program in Listing 4.18:
p3[1] is 0.5.
Now p3[0] is 0.5 and p3[1] is 0.8.
As you can see, arraynew.cpp
uses the pointer p3
as if it were the name of an array, with p3[0]
as the first element, and so on. The fundamental difference between an array name and a pointer appears in the following line:
p3 = p3 + 1; // okay for pointers, wrong for array names
You can’t change the value of an array name. But a pointer is a variable, hence you can change its value. Note the effect of adding 1 to p3
. The expression p3[0]
now refers to the former second element of the array. Thus, adding 1 to p3
causes it to point to the second element instead of the first. Subtracting one takes the pointer back to its original value so that the program can provide delete []
with the correct address.
The actual addresses of consecutive int
s typically differ by 2 or 4 bytes, so the fact that adding 1 to p3
gives the address of the next element suggests that there is something special about pointer arithmetic. There is.
The near equivalence of pointers and array names stems from pointer arithmetic and how C++ handles arrays internally. First, let’s check out the arithmetic. Adding one to an integer variable increases its value by one, but adding one to a pointer variable increases its value by the number of bytes of the type to which it points. Adding one to a pointer to double
adds 8 to the numeric value on systems with 8-byte double
, whereas adding one to a pointer-to-short
adds two to the pointer value if short
is 2 bytes. Listing 4.19 demonstrates this amazing point. It also shows a second important point: C++ interprets the array name as an address.
// addpntrs.cpp -- pointer addition
#include <iostream>
int main()
{
using namespace std;
double wages[3] = {10000.0, 20000.0, 30000.0};
short stacks[3] = {3, 2, 1};
// Here are two ways to get the address of an array
double * pw = wages; // name of an array = address
short * ps = &stacks[0]; // or use address operator
// with array element
cout << "pw = " << pw << ", *pw = " << *pw << endl;
pw = pw + 1;
cout << "add 1 to the pw pointer:
";
cout << "pw = " << pw << ", *pw = " << *pw << "
";
cout << "ps = " << ps << ", *ps = " << *ps << endl;
ps = ps + 1;
cout << "add 1 to the ps pointer:
";
cout << "ps = " << ps << ", *ps = " << *ps << "
";
cout << "access two elements with array notation
";
cout << "stacks[0] = " << stacks[0]
<< ", stacks[1] = " << stacks[1] << endl;
cout << "access two elements with pointer notation
";
cout << "*stacks = " << *stacks
<< ", *(stacks + 1) = " << *(stacks + 1) << endl;
cout << sizeof(wages) << " = size of wages array
";
cout << sizeof(pw) << " = size of pw pointer
";
return 0;
}
Here is the output from the program in Listing 4.19:
pw = 0x28ccf0, *pw = 10000
add 1 to the pw pointer:
pw = 0x28ccf8, *pw = 20000
ps = 0x28ccea, *ps = 3
add 1 to the ps pointer:
ps = 0x28ccec, *ps = 2
access two elements with array notation
stacks[0] = 3, stacks[1] = 2
access two elements with pointer notation
*stacks = 3, *(stacks + 1) = 2
24 = size of wages array
4 = size of pw pointer
18.118.139.224