This shows how to implement a simple dynamic array using functions (i.e. a
non-object-oriented way). At the end a few examples of the standard C++
vector
class are given to show that C++ provides a much nicer solution
using classes and objects.
A Dynamic Array Using FunctionsΒΆ
// dynamic_arr1.cpp
#include "cmpt_error.h"
#include <iostream>
#include <cassert>
#include <algorithm>
#include <vector>
using namespace std;
// The variables in int_vec are public, which means any code could modify them
// at any time.
struct int_vec {
int* arr; // pointer to the underlying array
int size; // # of elements in this int_vec from user's perspective
int capacity; // length of underlying array
}; // struct int_vec
// When an empty vector is created, its size is set to 0. But it is not
// obvious what its capacity should be. If the capacity is too big then it
// wastes space, but if it is too small then it will waste time re-sizing the
// underlying vector after only a few items are appended.
const int DEFAULT_CAPACITY = 10;
//
// Returns a new int_vec of size 0.
//
int_vec make_empty_vec(int cap = DEFAULT_CAPACITY) {
int_vec result = int_vec{new int[cap], 0, cap};
return result;
}
//
// Returns a new int_vec of size n, with every element set to fill_item.
//
int_vec make_filled_vec(int n, int fill_item) {
int cap = DEFAULT_CAPACITY;
// Set the capacity to be a little bit more than n so that appending a few
// extra elements is fast.
if (n > 0) {
cap += n;
}
int_vec result = int_vec{new int[cap], n, cap};
for(int i = 0; i < n; ++i) {
result.arr[i] = fill_item;
}
return result;
}
//
// Returns a new int_vec that is a copy of v. The returned int_vec does *not*
// share the same underlying array as v.
//
int_vec make_copy(const int_vec& v) {
int_vec result = int_vec{new int[v.capacity], v.size, v.capacity};
for(int i = 0; i < v.size; ++i) {
result.arr[i] = v.arr[i];
}
return result;
}
//
// Returns a copy of the int at index location i of v.
//
int get(const int_vec& v, int i) {
if (i < 0 || i >= v.size) {
cmpt::error("get: index out of range");
}
return v.arr[i];
}
//
// Sets index location i of v to be the value x.
//
void set(int_vec& v, int i, int x) {
if (i < 0 || i >= v.size) {
cmpt::error("get: index out of range");
}
v.arr[i] = x;
}
//
// Print v to the console in a nice format.
//
void print(const int_vec& v) {
if (v.size == 0) {
cout << "{}";
} else {
cout << "{";
cout << v.arr[0];
for (int i = 1; i < v.size; ++i) { // i starts at 1 (not 0)
cout << ", " << v.arr[i];
}
cout << "}";
}
}
//
// Print v to the console in a nice format, followed by a newline.
//
void println(const int_vec& v) {
print(v);
cout << '\n';
}
//
// Append adds x to the right end of v. If v doesn't have enough space to hold
// x, then the size of its underlying array is doubled.
//
void append(int_vec& v, int x) { // note that v is not const
if (v.size >= v.capacity) {
v.capacity = 2 * v.capacity; // double the capacity
int* new_arr = new int[v.capacity]; // make new underlying array
// twice size of old one
for(int i = 0; i < v.size; ++i) { // copy elements of v
new_arr[i] = v.arr[i]; // into new_arr
}
delete[] v.arr; // delete old arr
v.arr = new_arr; // assign new_arr
}
assert(v.size < v.capacity);
v.arr[v.size] = x;
v.size++;
}
//
// The programmer must remember to call deallocate exactly once on any int_vec
// they no longer want. Forgetting to call it results in a memory leak, and
// calling it more than once will most likely corrupt memory!
//
void deallocate(int_vec& v) {
delete[] v.arr; // delete with []-brackets is used because v.arr
} // points to an array
void test1() {
int_vec v = make_filled_vec(6, -1);
// int_vec v = make_empty_vec();
for(int i = 0; i < 15; ++i) {
append(v, i);
println(v);
}
int_vec w = make_copy(v);
for(int i = 0; i < w.size; ++i) {
set(w, i, i * i);
}
println(w);
deallocate(v);
deallocate(w);
// deallocate(v); // oops: uncommenting this will de-allocate v twice!
}
// To run this program at the command-line, you can do this:
//
// $ make dynamic_arr1.cpp
//
// $ ./dynamic_arr1 < numbers.txt
//
// numbers.txt is a text file containing numbers, e.g.:
//
// 3 1 2
// -5
// 1
//
// The output is:
//
// Sum = 2
// Average = 0.4
//
// The < is the BASH re-direction symbol, i.e. it makes the file numbers.txt
// be treated as cin from the program's point of view.
void test2() {
int_vec v = make_empty_vec();
// Keep reading ints from cin until end-of-file (ctrl-d) is encountered.
int x;
while (cin >> x) {
append(v, x);
}
// sum the elements of v
int sum = 0;
for(int i = 0; i < v.size; ++i) {
sum += get(v, i);
}
cout << "Sum = " << sum << "\n";
cout << "Average = " << sum / double(v.size)
<< "\n";
deallocate(v); // always remember to deallocate your intvecs
}
void sort(int_vec a) {
// sort is from the C++ Standard Teplate Library (the STL), which is a
// standard part of C++. The sort function that works on any type of of
// sequence, including arrays. It takes two pointers as input: a pointer
// to the first element, and a pointer one past the last element. In
// general, sort is an extremely efficient algorithm that you can use in
// most cases when you need to sort something in practice.
sort(a.arr, a.arr + a.size);
}
void test3() {
int_vec v = make_empty_vec();
// Keep reading ints from cin until end-of-file (ctrl-d) is encountered.
int x;
while (cin >> x) {
append(v, x);
}
println(v);
sort(v);
println(v);
deallocate(v); // always remember to deallocate your intvecs
}
// Here are some examples of things that can go wrong with an int_vec if
// you're not careful.
void test4() {
int_vec v; // oops: forgot to call a make function to initialize v;
println(v); // this compiles, runs, and prints a junk value.
v = make_filled_vec(5, 0); // okay, now v is initialized to be a
println(v); // an array of 5 0s
v = make_filled_vec(5, 6); // the order of the parameters to make_filled_vec
// can be confusing; this is a 5-element
// int_vec initialized to all 6s
println(v);
v = make_filled_vec(6, 5); // this is a 6-element int_vec initialized to all
// 5s
println(v);
deallocate(v); // Of course you should always deallocate int_vecs, but
// this one call to deallocate only deallocate the most
// recent int_vec. The arrays created by the previous calls
// to make are all still there, inaccessible! This is an
// a memory leak.
//
// Here are some examples of incorrectly accessing values of an int_vec.
//
int_vec w = make_filled_vec(10, 0);
w.arr = nullptr; // oops, memory leak: the underlying array is now
// inaccessible
println(w);
int_vec s = make_filled_vec(10, 0);
s.size = 100; // oops: now all functions that use size have the wrong
// value
println(s); // uh oh: this prints values past the end of the underlying
// array
int_vec t = make_filled_vec(10, 0);
t.capacity = 2 * t.capacity; // oops: now t has the wrong length for
// the underlying array
println(t); // interesting: should still work because println depends
// upon t.size, not t.capacity
}
//
// Now lets see some uses of the C++ standard vector class. The basic idea of
// a standard vector is essentially the same as our int_vec, but it is
// implemented using a class instead of a group of functions. This makes using
// a standard vector much easier and more pleasant than an int_vec.
//
void test5() {
vector<int> v; // creates an empty vector
v.push_back(5); // push_back does the same thing as the int_vec
v.push_back(2); // append function
v.push_back(4);
sort(v.begin(), v.end()); // The standard sort function sorts a vector
// using begin and end.
// v[i] is the element at index location i of v
for(int i = 0; i < v.size(); ++i) {
cout << "v[" << i << "]=" << v[i] << "\n";
}
// You can also use a for-each loop to get individual vector elements.
for(int n : v) {
cout << n << " ";
}
cout << "\n";
vector<int> w = {3, 5, 6, 1}; // very convenient initialization
v = w; // w is assigned to v: no memory leak!
// == (and !=) are pre-defined for standard vectors
if (v == w) {
cout << "v and w are the same!\n";
} else {
cout << "v and w are different!\n";
}
// You can put almost any type of value in a vector.
vector<string> names = {"Bob", "Neil", "Judy", "Ada"};
for(string s : names) {
cout << s << " ";
}
cout << "\n";
// We don't need to explicitly de-allocate vectors: they automatically
// delete their underlying array.
}
// The C++ standard vector is quite useful and efficient in practice: it is
// rarely ever necessary to use a basic array.
void test6() {
// Read an unknown number of strings from cin and add them to words. Then
// sort them in alphabetical order.
vector<string> words;
string s;
while (cin >> s) {
words.push_back(s);
}
sort(words.begin(), words.end());
for(string s : words) {
cout << s << "\n";
}
cout << "\n";
// No need to explicitly deallocate words: it automatically deletes its
// underlying array when words goes out of scope.
}
int main() {
// test1();
// test2();
// test3();
// test4();
// test5();
test6();
}