1. Arrays, Pointers, and Structures
Objective: Discuss these three C++ features: arrays, pointers, and structures. We'll show
Reference: Weiss, Chapter One.
Example:
int x; int *ip; // LEGAL: ip is uninitialized int *jp = &x; // LEGAL: jp now points to x int *kp = x; // ILLEGAL: x is not an address
Pointers to different base types are considered to be different data types by C++ and they cannot be assigned to one another directly.
int x = -35; int *ip = &x;
if x has been allocated the cell with address 1028 as shown above, then the relation between ip and x can be depicted as follows:
1028
+--------+ +--------------------+
ip | 1028 o-|--------> | -35 | x
+--------+ +--------------------+
+------+ +------+
ptr | o---|----> | *ptr |
+------+ +------+
int *ip, i, j, k;
are the following statements legal?
(*ip)++; *ip++; k = i**j; i = ip; j = &ip; ip = &i;
int i, j, k, *p = &i,
*q = &j, *r;
double x;
are the following expressions legal?
p == &i **&p r = &x &(*p + 2) &100 *p/ *q *p/*q *p**q
int i = 5, *p = &i, **pp = &p, ***ppp = &pp;
In this case i, *p, **pp, and ***ppp all refer to the same cell.
The integer added or subtracted is scaled by the size of the base type of the pointer. That is, if p is a pointer to data type type, then (numerical value of p + i) is equal to (numerical value of p) + i * sizeof(type). Hence p + i is the address of the i-th cell, of size of the base type, beyond p.
Example:
int *ip = reinterpret_cast<int *>(1320); double *dp = reinterpret_cast<double *>(1600); ip++; dp += 20; // What are the values of ip and // dp at this point?
Example:
int strcmp(const char *r, const char *s)
{
while (*r == *s)
{
if (*r == '\0')
return 0;
r++; s++;
}
return (*r < *s) ? -1 : 1;
}
Example:
int strcpy(char *s, const char *cs)
{
char *tmp = s;
while (*cs != '\0')
{
*(tmp++) = *(cs++);
}
*tmp = '\0';
return s;
}
int x = 0; int& y = x; y += 3; cout << "x = " << x << endl;
#include <iostream>
using namespace std;
void swapWrong(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
}
void swapPtr(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
void swapRef(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
An array's name represents a pointer to element zero of that array. Hence for the definitions:
int a[10]; int *iptr;
The name a is the same as &a[0]. The assignment
iptr = a; // same as iptr = &a[0];
is well-defined. In this case, a[i], *(a+i), iptr[i], and *(iptr+i) are all equivalent. In fact, a[i] is changed to *(a+i) by the compiler immediately.
Even though iptr and a can be used interchangeably in the above case, there are significant differences between them:
If an array is a parameter in a function prototype, its name can only be a passed by value. Since the name of an array is the address of its first element, when an array is passed as an argument in a function call, the address of its first element is copied. Hence the entries of the array are passed by reference.
Example: The following are equivalent implementation of the same function:
int find_max (int a[], int n)
// find index of maximum element/
// in array a of size n./
{
int max = a[0], index = 0, i;
for (i = 1; i < n; i++)
{
if (max < a[i])
{
max = a[i];
index = i;
}
}
return index;
}
int find_max (int *a, int n)
// find index of maximum element
// in array a of size n.
{
int max = *a, index = 0, i;
for (i = 1; i < n; i++)
{
if (max < *(a+i))
{
max = *(a+i);
index = i;
}
}
return (index);
}
int a[m][n];
The entries are referenced by
a[i][j], where 0 <= i < m, and 0 <= j < n
int a[5][6]; cout << sizeof(a) << endl; cout << sizeof(a[0]) << endl;
*(a[i] + j) *(*(a + i) + j) (*(a + i))[j] *(&a[0][0] + i * n + j)
Since a pointer can be used to refer to a variable, a program can manipulate variables even if the variables have no identifiers to name them. This is a very important feature of C++ because in many applications, the actual number of variables cannot be determined until the execution time. Some variables should be created as needed while the program is being run. These variables are called dynamic variables. Nameless dynamic variables can be created using the new operator.
Example:
int *p; p = new int; cin >> *p; *p += 7; cout << *p;
The operation new int above creates a nameless variable of type int and returns a pointer to it. Thus p is a pointer to this newly created variable. As a result, the variable can be refered to as *p. If new fails to create a new variable of the required type, it invokes a handling routine. The default action is to exit the program.
Example: What is printed?
// Program to demonstrate dynamic varaibles.
#include <iostream>
using namespace std;
int main()
{
int *p, *q;
p = new int;
*p = 42;
q = p;
cout << "*p == " << *p << endl;
cout << "*q == " << *q << endl;
*q = 53;
cout << "*p == " << *p << endl;
cout << "*q == " << *q << endl;
p = new int;
*p = 88;
cout << "*p == " << *p << endl;
cout << "*q == " << *q << endl;
cout << "Hope you got the point!.\n";
return 0;
}
delete p;
eliminates the dynamic variable pointed to by p and returns the memory cell to the system. The memory can then be reused to create new dynamic variables.
char *stupid()
{
char *s = "stupid";
return s;
}
int main()
{
cout << stupid() << endl;
return 0;
}
Making s static in stupid() will fix the problem.
C++ demands that the sizes of all regular arrays must be known at the compilation time. However, one may not know the size of the array when the program is written. The size is only determined in run time. With the regular arrays, one has to estimate the largest possible size one may need. There are two problems with this:
Dynamic arrays can solve this problem very nicely.
Example:
// Program to demonstrate dynamic array.
char *dstrcpy (const char *cs)
{
char *s, *tmp;
unsigned size = strlen(cs) + 1;
tmp = s = new char[size];
while (*cs != '\0')
{
*(tmp++) = *(cs++);
}
*tmp = '\0';
return s;
}
To destroy a dynamic array, p say, one can use
delete [] p;
The square bracket tell C++ that a dynamic array variable is being eliminated, so the system checks the size of the array and removes that many cells.
Example:
// Program that sorts a dynamic array.
#include <cstdlib>
#include <iostream>
using namespace std;
typedef int* IntPtr;
void fill_array(int a[], int size);
void sort(int a[], int size);
int main()
{
int array_size, i;
IntPtr a;
cout << "How many numbers will be sorted? ";
cin >> array_size;
a = new int[array_size];
fill_array(a, array_size);
sort(a, array_size);
cout << "In sorted order the numbers are:\n";
for (int i = 0; i < array_size; i++)
{
cout << a[i] << ' ';
}
cout << endl;
delete [] a;
return 0;
}
void fill_array(int a[], int size)
{
cout << "Enter " << size << " integers.\n";
for (int i = 0; i < size; i++)
{
cin >> a[i];
}
}
void sort(int a[], int size)
{
...
}
Syntax of dynamic allocation does not allow direct two-dimensional notations. Thus, code such as
double **arr = new double[m][n]; // syntax error
are not possible. However, the function dyn2d returns a double** that can be used as a two-dimensional array:
double **dyn2d(int m, int n)
{
double *arr = new double[m*n];
double **a = new double*[m];
for (int i = 0; i < m; i++)
{
a[i] = arr + i*n;
}
return a;
}
void del2d(double **a)
{
delete [] *a; // free data cells
delete [] a; // free pointer cells
}
Example: Allocating and deallocating two-dimensional arrays can be done alternatively as follows:
double **dyn2d_1(int m, int n)
{
double **a = new double*[m]; // pointer cells
for (int i = 0; i < m; i++)
{
a[i] = new double[n]; // data cells
}
return a;
}
void del2d_1(double **a, int m)
{
for (int i = 0; i < m; i++)
{
delete [] a[i]; // free data cells
}
delete [] a; // free pointer cells
}
#include <vector>
with a using directive if one has not been provided.
vector<int> a(3);
which defines a vector of three integers which can be referenced as a[0], a[1], and a[2]. An equivalent declaration would be
vector<int> a; // 0 int object a.resize(3); // 3 int objects: a[0], a[1], a[2]
#include <cstdlib>
#include <iostream>
#include <vector>
using namespace std;
// Generate numbers (from 1 - 100).
// Print number of occurrences of each.
int main()
{
const int SIZE = 100;
int i, totalNumbers;
cout << "How many numbers to generate? ";
cin >> totalNumbers;
vector<int> numbers(SIZE + 1);
for (i = 1; i < SIZE; i++)
{
numbers[i] = 0;
}
for (i = 0; i < totalNumbers; i++)
{
numbers[rand() % SIZE + 1]++;
}
for (i = 1; i <= SIZE; i++)
{
cout << i << " occurs " << numbers[i]
<< " times\n";
}
return 0;
}
#include <iostream>
#include <vector>
using namespace std;
// Read an unlimited number of ints with no attempts
// at error recovery.
void getInts(vector<int>& array)
{
int itemsRead = 0;
int inputVal;
cout << "Enter any number of integers: ";
while (cin >> inputVal)
{
if (itemsRead == array.size())
{
array.resize(array.size() * 2 + 1);
}
array[itemsRead++] = inputVal;
}
array.resize(itemsRead);
}
int main()
{
vector<int> array;
getInts(array);
for (int i = 0; i < array.size(); i++)
{
cout << array[i] << endl;
}
return 0;
}
void getInts(vector<int>& array)
{
int inputVal;
array.resize(0);
cout << "Enter any number of integers: ");
while (cin >> inputVal)
{
array.push_back(inputVal);
}
}
The push_back function increases the size by 1, and adds a new item to the array at the appropriate position, expanding capacity if needed.
Why is it necessary to resize the array to 0 initially?
// Return the index of the maximal entry.
int findMax(const vector<vector>& a)
{
int maxIdx = 0;
for (int i = 1; i < a.size(); i++)
{
if (a[i] > a[maxIdx])
{
maxIdx = i;
}
}
return maxIdx;
}
int findMax(vector<vector> a)
or
int findMax(vector<vector>& a)
#include <string>
with a using directive if one has not been provided.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string a = "hello";
string b = "world";
string c; // should be ""
c = a + ' '; // "hello "
c += b; // "hello world"
cout << "c is: " << c << endl;
cout << "c is: ";
for (int i = 0; i < c.length(); i++)
{
cout << c[i];
}
cout << endl;
char d[20];
strcpy(d, c.c_str());
cout << "c.c_str is: " << d << endl;
return 0;
}
void insertSort(vector<string>& strs)
{
string tmpStr;
for (int i = 1; i < strs.size(); i++)
{
tmpStr = strs[i];
// insert tmpStr into the right position
int j;
for (j = i - 1; j >= 0 && tmpStr < strs[j]; j--)
{
strs[j + 1] = strs[j];
}
strs[j + 1] = strs[j];
}
}
Example:
struct Student
{
string firstName;
string lastName;
int studentNum;
double gradePointAvg;
}
void printInfo(const Student& s)
{
cout << "ID is " << s.studentNum << endl;
cout << "Name is " << s.firstName << ' '
<< s.lastName << endl;
cout << "GPA is " << s.gradePointAvg << endl;
}
int main()
{
Student mary;
mary.lastName = "Smith";
mary.firstName = "Mary";
mary.gradePointAvg = 4.0;
mary.studentNum = 123456789;
printInfo(mary);
return 0;
}
struct DynArr
{
int *store;
size_t size;
};
DynArr a, b;
a.store = new int[100];
a.size = 100;
for (int i = 0; i < a.size; i++)
{
a.store[i] = 0;
}
b = a;
for (int j = 0; j < b.size; j++)
{
b.store[i] = 1;
}
void deepCopy(DynArr& target, const DynArr& source)
{
target.size = source.size;
target.store = new int[target.size];
for (int i = 0; i < target.size; i++)
{
target.store[i] = source.store[i];
}
}
+------+---+ +-------+---+ +----+---+ +-----+---+ | Dale | o-+--->| Katie | o-+--->| Ed | o-+--->| Rob | ^ | +------+---+ +-------+---+ +----+---+ +-----+---+
Assume the following structure of a node:
+---------------------+---+ | item (of type data) | o-+------> +---------------------+---+ next (of type link)
A node is represented by a link to it, and a linked list is represented by a link to its first node, the head node. The symbol ^ is used to denote the empty link, one that does not link to any node.
struct Node {
Object item;
Node *next;
};
Node *getNode (void)
{
return new Node;
}
void freeNode (Node *p)
{
delete p;
}
Object getItem (Node *p)
{
return (p -> item);
}
Node *getNext (Node *p)
{
return (p -> next);
}
void setItem (Node *p, const Object& x)
{
p -> item = x;
}
void setNext (Node *p, const Node *q)
{
p -> next = q;
}