Examples of arrays and vectors in C++

1. Basic usage

The following illustrates the basic usagle of vectors.

// array-ex1.cpp (click here to download)

#include <iostream>
#include <vector>

using namespace std;

int main() {
  
  vector<int> v(10,0); // initialise to 10 copies of 0
  
  int s = v.size();
  cout << "size of v is " << s << endl;
  
  for (int n=0; n<s; n++) {
    v[n] = n;
  }
  
  for (int n=0; n<s; n++) {
    cout << "v[" << n << "] == " << v[n] << endl;
  }
  
  // the following code is very bad
  
  /*
    
    for (int n=0; n<20; n++) {
      cout << "v[" << n << "] == " << v[n] << endl;
    }
    
  */
  
  // the following code is very VERY bad
  
  /*
    
    for (int n=0; n<20; n++) {
      v[n] = n;
    }
    
  */
  
  return 0;
}

Note the code vector<int> v(10,0); to declare v and initialise it to 10 copies of 0. Without the (10,0) the vector would be initially empty.

For reasons we will see shortly, passing vectors to functions should be done using call-by-reference. (At least almost always!) Functions frequently change vectors as side effects. Here is an example with a void function operating on an vector.

// array-ex2.cpp (click here to download)

#include <iostream>
#include <vector>

using namespace std;

void printvector(vector<double>& v) {
  int s = v.size();
  for (int n=0; n<s; n++) {
    cout << v[n] << " ";
  }
  cout << endl;
}

/* assigns v[n]=1 v[n+1]=2 v[n+2]=3 where n==index */
void set123(vector<double>& v, int index) {
  v[index] = 1.0;
  v[index+1] = 2.0;
  v[index+2] = 3.0;
}

int main() {
  
  vector<double> v(10,0); // initialise to 10 copies of 0
  
  int s = v.size();
  cout << "size of v is " << s << endl;
  
  for (int n=0; n<s; n++) {
    v[n] = n; // convert n to double
  }
  
  printvector(v);
  
  set123(v,5);
  
  printvector(v);
  
  set123(v,13); // bad but how to fix it?
  
  printvector(v);
  
  return 0;
}

The code contains a bad line set123(v,13);. Suppose you didn't know that the size of this vector was 10. Then how should the code be fixed? There are many choices here.

There are three problems with changing main: first it takes computer time to to get the size and compare; second there seems to be no way of checking what values for index are valid (and surely it is the job of the function set123 to know what are valid values for index?); thirdly, if set123 is used elsewhere we have the same problem all over again. So it is better to change set123. This was my solution:

// array-ex3.cpp (click here to download)

#include <iostream>
#include <vector>

using namespace std;

void printvector(vector<double>& v) {
  int s = v.size();
  for (int n=0; n<s; n++) {
    cout << v[n] << " ";
  }
  cout << endl;
}

/* 
   assigns v[n]=1 v[n+1]=2 v[n+2]=3 where n==index 
   
   Here, index is checked and must be in the range 0 .. v.size()-3
   If index is out of range then nothing is done and a warning 
   message is sent to cerr.
   
*/
void set123(vector<double>& v, int index) {
  if (index<0 || index>v.size()-3) {
    cerr << endl << "WARNING: in function set123, input value " << index << " is out of range!" <<endl;
    return; // quit function doing nothing
  }
  v[index] = 1.0;
  v[index+1] = 2.0;
  v[index+2] = 3.0;
}

int main() {
  
  vector<double> v(10,0); // initialise to 10 copies of 0
  
  int s = v.size();
  cout << "size of v is " << s << endl;
  
  for (int n=0; n<s; n++) {
    v[n] = n; // convert n to double
  }
  
  printvector(v);
  
  set123(v,0);
  
  printvector(v);
  
  set123(v,13);
  
  printvector(v);
  
  return 0;
}

Note that I had other choices to make: what to do if a problem occurs. I chose to make a loud warning to the error console cerr.

2. Sieve of Eratosthenes

Vectors of bool are very useful. The following needs little additional explanation.

// eratosthenes.cpp (click here to download)

#include <iostream>
#include <vector>
using namespace std;

// Sieve of Eratosthenes

/* sets the input vector isprime[] so that it has size
   nentries and isprime[n]==true iff n is prime for all
   n < nentries */
void setsieve( vector<bool>& isprime, int nentries) {
  // resets isprime to a vector of size nentries, all true
  isprime.resize(nentries,true);
  isprime[0] = isprime[1] = false;
  int trialn = 2;
  for (int trialn = 2; trialn < nentries; trialn++) {
    if (isprime[trialn]) {
      int multiple = trialn + trialn;
      while (multiple < nentries) {
	isprime[multiple] = false;
	multiple = multiple + trialn;
      }
    }
  }
}

int main() {
  vector<bool> isprime;
  int nentries;
  cout << "Number of entries? " << endl;
  cin >> nentries;
  setsieve(isprime, nentries);
  for (int i=0; i<nentries; i++) {
    if (isprime[i]) cout << i << " ";
  }
  cout << endl;
  return 0;
}

Interestingly, this gives a table of all primes less than 1000000000 in very reasonable time. To go much beyond this will require care with int overflow.

3. Call by reference

Finally, why do I insist vector arguments in functions should be call by reference? Find out with this.

// arrayrefs.cpp (click here to download)

// this program illustrates why call-by-reference is neccessary 
// when passing arrays (vectors) as arguments to functions

#include <iostream>
#include <vector>

using namespace std;

/* This function returns the ith value in the vector v,
   i.e. it is equivalent to v[i]
   This is a trivial operation that you'd not normally put
   in a function, but you can easily imagine that more complicated 
   functions that read several values from v might be useful.
   
   Here we (incorrectly) use call-by-value for v
*/ 
double badf(vector<double> v, int i) {
  return v[i];
}

/* This function returns the ith value in the vector v,
   i.e. it is equivalent to v[i].
   
   Here we (correctly) use call-by-reference for v.
*/ 
double goodf(vector<double>& v, int i) {
  return v[i];
}

int main() {
  // size of the vector in this example
  int vsize = 100000;
  // set the next two variables as you want!
  bool do_badf = true;
  bool do_goodf = true;
  
  // create a vector of doubles, initially empty
  vector<double> myvector;
  for (int i=0; i<vsize; i++) {
    myvector.push_back(i);	
  }
  
  // now calculate the sum of the values badf(myvector,-) in myvector
  if (do_badf) { 
    double runningsum = 0.0;
    for (int i=0; i<vsize; i++) {
      runningsum = runningsum + badf(myvector,i);
    }	
    cout << runningsum << endl;
  }
  
  // now calculate the sum of the values goodf(myvector,-) in myvector
  if (do_goodf) {
    double runningsum = 0.0;
    for (int i=0; i<vsize; i++) {
      runningsum = runningsum + goodf(myvector,i);
    }
    cout << runningsum << endl;
  }
  
  // return success as usual
  return 0;
}

In this example program the functions badf and goodf do something quite trivial, and could be replaced by a single expression v[i]. But it's not difficult to imagine that even only slightly more complicated functions would be useful when written as a function in a similar way.