C++ Arrays and Vectors Contents 1 Overview of Arrays and Vectors 2 2 Arrays 3 2.1 Declaring Arrays................................................. 3 2.2 Initializing Arrays................................................ 4 2.3 Referring to Elements of an Array....................................... 4 2.4 Using Constants to Prevent Hardcoding of Array Sizes............................ 5 2.5 Passing Arrays to Functions........................................... 5 2.6 Multidimensional Arrays............................................. 6 2.7 List of Array Difficulties............................................. 8 3 Vectors 9 3.1 Creating Vectors................................................. 9 3.2 Referring to Elements of Vectors........................................ 10 3.3 Determining the Length of a Vector...................................... 11 3.4 Growing/Shrinking Vectors........................................... 11 3.5 Passing Vectors to Functions.......................................... 12 3.6 Returning Vectors from Functions....................................... 13 3.7 An Overview of all Vector methods/operators................................. 13 3.8 Common Misconceptions about Vectors.................................... 14 1
1 Overview of Arrays and Vectors So far in C++ we ve been working with variables of different types We ve talked about data structures, but haven t actually used any Today we start talking about and working with simple data structures known as arrays and vectors Both arrays and vectors are essentially the same thing, but vectors are a bit more flexible Arrays are built-in to the C++ language, but vectors are provided as a standard extension, the same way strings are As such, we ll talk about arrays first Arrays and vectors allow one to define an ordered sequence of variables, each of the same time, and each accessible by a different name We can use such data structures to store a list of data, such as grades (ints or floats), usernames (strings), etc. 2
2 Arrays 2.1 Declaring Arrays As mentioned above, an array is a sequence of variables each of the same type Arrays are defined by adding a set of square brackets ([ ]) after a variables name, and... Inside the square brackets goes a whole number, 0, that tells C++ how many elements will be stored in the array (call this the size of the array) So, to define an array named grades which contains 10 grades, each stored as floats, we would do the following float grades[10]; If we wanted to define an array containing the price (in pennies) of 20 different items, we could do the following int prices[20]; When array declarations are made, and those arrays come into scope, the amount of memory allocated is equal to the amount of memory needed for a single variable, times the size of the array Note that with arrays, each of the n variables (where n is the size of the array) are stored continuously in memory (right next to each other, in order) Note also that the size of an array can never change! They are a fixed size data structure (we ll see how vectors can overcome this difficulty) When declaring an array, its size must be able to be determined at compile time!, NOT at run time! E.g. the following will not work the way you expect... cout << "Enter number of students: "; cin >> numstudents; float studentgrades[numstudents]; string studentnames[numstudents]; Specifically, the size of an array in an array declaration must be an integer constant 0! We ll see later how vectors can also overcome this difficulty 3
2.2 Initializing Arrays When we define normal (non-array) variables, we ve seen how we can set the value of the variable in the declaration. E.g. double pi = 3.14159; We can do the same thing with arrays, setting their values in their declaration This is accomplished by putting the sequence of values in a set of curly braces ({ }), each separated by a comma (,). float grades[3] = { 100.0, 92.3, 79.5 }; Note that the number of elements in curly braces must be the the size of the array If fewer elements are initialized than the size of the array, then only the first few are set If more elements are initialized then the size of the array, a compiler error is generated 2.3 Referring to Elements of an Array Now that we can declare arrays and initialize them, how do we access them? To access each element of an array named foo, use the name foo (just like you would for a variable) with a set of square brackets after the name ([ ]) containing the index of the element you want CAVEAT: Note that array indices start at 0, not 1!!! cout << foo[5] << endl; So if you define an array of size n, valid indices are 0 to n 1 EVEN WORSE CAVEAT: If we declare an array of size, say 10, only indices of 0 to 9 will actually access memory within the array Values larger than 9 will compile, and retrieve something But that something is usually garbage! Note that you can also modify memory outside the range of the array by using indices which are too large This is called an (array) out-of-bounds error, and is (sadly) extremely common Failing to check an index to make sure it s valid for a given array is the key component in code injection techniques (responsible for many, many security breaches) 4
2.4 Using Constants to Prevent Hardcoding of Array Sizes In the examples so far, all of the arrays have been declared using fixed integers like 5 or 10 This can become cumbersome when we use the size of the array for things like looping through the array As we said earlier, the size must be a constant integer expression so that the size can be determined at compile time To make things easier, we can use const int constants to make things a little easier const int arraysize = 5; float studentgrades[arraysize]; string studentnames[arraysize]; 2.5 Passing Arrays to Functions Now that we have arrays, and have recently covered functions, we ll see how to pass an array to a function The syntax is rather easy... To pass an array to a function, the parameter of the function that will be filled with an array simply needs an empty set of square brackets ([]) after the parameter name int sumarray(int somearray[], int size) It s not required to pass the size of the array as a parameter, but if you do not, you will be unable to determine the size of the array from within the function! Again, vectors do not have this problem/requirement C++ always passes arrays to functions by reference, since the cost of allocating a copy of the array for each function call would be rather costly example... This is a nice optimization, but if the function should not modify the array (like in the previous example), const should be used to ensure this example... int sumarray(const int somearray[], int size) 5
2.6 Multidimensional Arrays We ve seen how to construct and use simple arrays, which are just sequences of values Now we (briefly) look at multidimensional arrays, which are used primarily to store tables of values Multidimensional arrays are initialized just like regular arrays, only we have to include additional brackets & sizes for each dimension E.g. So, if we wanted to create a 10x2 array of integers... type name[size1][size2][size3];\verb int mytable[10][2]; To initialize multidimensional arrays we use the curly braces just as we did for normal arrays, only each element of the array is treated as another array, and thus specified using another set of curly braces For example, int mytable[4][2] = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } }; Array elements are accessed just as we did for regular arrays, only we must specify an index for each dimension mytable[0][2] = -5.6; 6
Be sure to note the following caveat of multidimensional arrays, discussed in the book! When passing multidimensional arrays to functions, we use the syntax you would probably expect, except that we must include the size for all dimensions except the first! example... Keep in mind that a 2-dimensional array is simply an array of arrays. A 3-dimensional array is simply an array of arrays of arrays. With multidimensional arrays, each sub-array must be of the same size. I.e. one cannot have an array containing one 10 element array, one 2 element array, and one 5 element array 7
2.7 List of Array Difficulties As we ve seen above, arrays in C++ have the following problems 1. The size of an array must be able to be determined at compile time 2. The size of an array cannot change/grow/shrink with the needs of the program 3. When working with arrays, we usually need a constant set to the size to eliminate use of integer constants such as 5 and 10 4. When passing arrays to functions, we need to pass the size as well 5. Array out-of-bounds errors! One of the most common bugs in large software 6. When passing multidimensional arrays to functions, we have to include all but the first dimension in the function definition, making it difficult to define functions over general multidimensional arrays 7. Also, multidimensional arrays are of fixed size 8. Arrays are always allocated continuously in memory. Although this makes for quick access to each element, allocating large arrays may require more time, and if enough continuous memory isn t available an error will occur 9. Common operators like assignment (=), equality checking (== and!=), and ordering (<, <=, etc.) do not work as expected with arrays For each of these problems, C++ vectors have a solution 8
3 Vectors Unlike arrays, vectors are not a built-in data type to C++ Vectors are, however, a standard extension As such, in any environment (OS/compiler/architecture), you can simply use #include <vector> include the vector library 3.1 Creating Vectors Before we show how to create a vector, let s consider the following... As said previously, vectors are very much like arrays We would like to be able to create vectors of, say, integers (int), doubles (double), and even strings (string). BUT, we want to be able to create a vector of any type To do this, C++ provides a mechanism called templated classes, which are very similar to the notion of a templated function Vectors are implemented this way, so for any data type you can create a vector The general format for declaring a vector is the following vector< type > name; where type is the type of vector you want to create (int, double, string, etc.) and name is the name of the vector For example, a vector containing a list of, say, student names would be vector< string > studentnames; Unlike arrays, we do not have to specify a size in the declaration of a vector. It can grow or shrink as we need it! However if you know ahead of time how large of a vector you would need, you can specify how many elements by 9
vector< string > studentnames(20); Note that the size is NOT placed in square brackets, but rather parentheses! If you wanted to initialize a vector by simply setting all elements to the same value, you could use E.g. the following creates a vector named studentgrades which contains 20 doubles, all initialized to 0 vector<double> studentgrades(20, 0.0); There is no way to initialize vectors to individual values like we can with arrays! 3.2 Referring to Elements of Vectors Similar to an array, individual elements of a vector can be read or modified using square brackets with an index within them E.g. studentnames[0] = "Optimus Prime"; cout << studentnames[0] << endl; Using the square brackets provides direct access to each of the elements HOWEVER, just like with arrays, use of the square brackets can result in out-of-bounds problems To solve this, the vector class has a method called at, which you pass an index (just like brackets) and it provides direct access both for retrieving and modifying individual elements of a vector studentnames.at(0) = "Optimus Prime"; cout << studentnames.at(0) << endl; That is, the at method is used exactly the same as the square brackets 10
The only difference, however, is that any attempt to access elements beyond the size of the array will result in a run-time error! So why do the brackets not check for out-of-bounds errors? 3.3 Determining the Length of a Vector Given a vector named, say, studentnames, we can determine the current size of the vector (how many elements it has), simply by using studentnames.size(); So, rather than passing around constants for the size of an array, we can retrieve the length from the object itself! for (int count = 0; count < studentnames.size(); count++) studentnames.at(count) = rand() % 20 + 1 3.4 Growing/Shrinking Vectors As mentioned previously, vectors can grow and shrink with the needs of our program We ll cover two methods for growing & shrinking vectors... Suppose we have a vector that we want to incrementally add new elements to, without knowing how many total ahead of time We could use the method push back(element) which extends the size of the vector by one and inserts the element element into the new position (It pushes a new element onto the back of the vector) Note that this method only increases the size of a vector, by exactly one element 11
Similarly, we may want to remove a single element from a vector at a time, and shrink the size of the vector by one element as-we-go For this, we could use the pop back() method, which removes the last element of the vector (freeing the memory) and shrinking the size of the vector by exactly 1 In addition to the above methods, we could have also use the method resize(newsize) to set the size of the vector to newsize If newsize is greater than the current size of the vector, new un-assigned elements are added to the end of the vector If newsize is less than the current size of the vector, then all elements after the newsize th element will be removed! 3.5 Passing Vectors to Functions We can pass vectors to functions much easier that we can arrays void printstudentgrades(vector<double> grades); Note that unlike arrays, vectors will not be passed by reference by default If you want to do that, you ll have to explicitly say so... be sure to use const if you re only doing this to save memory! void printstudentgrades(const vector<double> &grades); 12
3.6 Returning Vectors from Functions In addition to passing vectors as individual values to functions, we can also return vectors! vector<int> multiplypolynomials(vector<int> polya, vector<int> poly2); 3.7 An Overview of all Vector methods/operators The following operators work with vectors. Suppose vec, vec1, and vec2 are vectors... vec[i] (Bracket-access) Provides direct access to the i th element of a vector. vec1 = vec2 (Assignment) Copies the vector vec2 into vec1. All memory consumed by vec1 is either re-used or freed. vec1 == vec2 (Equality) Returns true if and only if vec1 and vec2 have the same size, and each element in each vector are equal vec1!= vec2 (Inequality) Returns the negation of vec1 == vec2 vec1 < vec2 (Less-Than) Compares vectors lexicographically (same for <=, >, >=). Additionally, the following methods are provided. Suppose vec is a vector... vec.at(i) Provides direct access to the element at location i in the vector. If there is no i th element, an error is produced (this is meant to replace the [i] operators with vectors). Note that this can also be used for assignment (e.g. vec.at(i) = 5). vec.size() Returns an integer indicating how many elements are in the vector. Does not modify the vector. vec.resize(newsize) Will shrink/grow the vector to contain newsize elements. If newsize is less than vec.size(), all elements after the newsize th element are deleted. vec.empty() Returns a boolean value. True is returned if the vector is empty (has no elements). Otherwise, false is returned. Does not modify the vector. vec.clear() Clears all data from the vector, freeing all memory allocated. Does not return a value. vec.push back(newelement) Extends the size of the vector by one and inserts newelement into this new location. Nothing is returned. vec.pop back() Deletes the last element in the vector and shrinks the vector by one element. vec.capacity() Returns a positive integer indicating how many more elements can be added to the array before more memory will need to be allocated. (Vectors allocate memory in chunks ) Does not modify the vector. 13
3.8 Common Misconceptions about Vectors Many people think that the standard C++ vector class is inefficient (either in terms of memory or time, or both). Nothing could be further from the truth! The vector class is highly optimized for both time and memory. Although the vector class appears the same no matter where you use it (in UNIX, Linux, Mac OS X, Windows, Windows using Visual Studio compiler, Windows using Borland compiler, on Intel architecture, AMD architecture, Sparc64, PowerPC architecture, etc.) each combination has a highly specific implementation that takes advantage of all the system has available! In addition, the standard C++ vector class(es) are extremely well tested. The chance of finding an error in this class (or any of the standard C++ classes) is quite small. Using these standard C++ data structures such as vectors is considered an extremely good practice 14