Speaking of sorting, that suggests we have multiple elements of the same, or at least comparable, type; which leads to arrays. Arrays, references, and pointers, are all examples of compound types, types defined in terms of another type. Arrays are collections of variables of the same type, roughly anyway, and of fixed size, usually, that we access by position. This is a pretty qualified statement.
It is possible to have dynamic arrays, but generally ...
... if we aren’t sure of the number of elements to be stored ...
... we are better off using a vector, more on these later.
Why not use the dynamic vectors all the time?
Because we may be able to optimise operations for the fixed number of elements that we have.
But we probably better using array containers rather than classical arrays for a fixed number of elements anyway.
Array declaration uses
type array_Name[dimension];
int class_Marks[10];
The dimension has to be known at compile time, so dimension needs to be a constant.
The [] is referred to as subscripting.
It’s a good time to introduce the qualifier used to make sure something is constant, const:
is similar, but not the same as final in Java. C++11 has final too, more on this later because it’s tied up with classes.
Operators cannot change an object with the const qualifier. So ...
int i = 10;
const int ci = 7;
int j = ci;
ci = 2;
... this last one isn’t okay, and neither would leaving ci uninitialized.
keyword is used for constant expressions, with values that cannot change but could be evaluated at compile time.
It’s an instruction to try to evaluate the expression at compile time.
Any const object initialized from a constant expression is a constant expression.
constexpr int ci = 7;
constexpr int sz = size();
It’s not unusual to want to have values that are used in several places throughout a file, or are going to be fixed.
It may be that you aren’t quite sure what the value should be.
Sizes, for arrays for example, are a typical example.
Or it’s a recognised constant, like e or pi.
In both cases, it’s better to have a constant variable that holds the value rather than using a magic number.
If I set something equal to 3.14, did I really mean that’s pi and I just couldn’t be bothered putting more digits?
Or is that value exactly 3.14?
If I’m using the same size repeatedly, it’s clearer if I set them in a single place and use
them multiple times.
const int SIZE = 10;
When we declare an array like this:
const int postCodeLength = 4;
int postCode[postCodeLength];
... the memory location is set up but there is no initialisation. To initialise all four locations to 0 ...
int postCode[4]={0};
The size of an array is constant.
The array name/identifier represents a memory address
There are a few different ways to initialise.
For an array of three ints with values 0, 1, and 2.
const unsigned sz = 3;
int ia1[sz] = {0, 1, 2};
The size can be inferred from the initialiser ...
int a2[] = {0, 1, 2};
But we might not have all the initial values so ...
int a3[5] = {0, 1, 2};
string a4[3] = {"hi", "bye"};
Careful with the size ...
int a5[2] = {0, 1, 2};
... interesting difference between CC and g++...
The uninitialized parts are value-initialized, int to 0, string to an empty string.
Character arrays can be initialised using a string literal, and they end with a null character \0
These are referred to as C-strings.
This can be explicit in element by element declarations.
char a1[] = {'C', '+', '+'};
char a2[] = {'C', '+', '+', '\0');
char a3[] = "C++";
const char a4[6]="123456";
The last one will complain because there is no space for the null to be added
Warning ... against using c-strings... no length checking.
void function(const char * first, const char * last)
{
...
first_name = new char[strlen(first) + 1];
strcpy(first_name, first);
last_name = new char[strlen(last) + 1];
strcpy(last_name, last);
}
Arrays and pointers are quite closely related:
Mostly when we use an object of array type we are actually using a pointer to the first element of the array.
Note that arrays are, by default, passed by reference.
Therefore arrays passed to functions can be changed by the function unless the keyword const is used.
void sum_array_values(
int size, // size of the arrays
const int arr1[ ], // array passed as input
const int arr2[ ], // array passed as input
int sum_of_array[ ]) // array passed for output
{
for (int i = 0; i < size; i++)
c[i] = a[i] + b[i];
}
int main()
{
const int ArySize = 5;
int Ary1[ArySize] = {1, 2, 3, 4, 5 };
int Ary2[ArySize] = {6, 7, 8, 9, 10};
int Ary3[ArySize];
sum_array_values(ArySize, Ary1, Ary2, Ary3);
...
}
“Multidimensional arrays” must have their dimensions specified within the function's parameters, although the 1st dimension may be omitted, for example:
void print3DMatrix (const float A[][3][3]);
int main()
{
float Matrix[3][3][3] ={
{{1,2,3},{4,5,6},{7,8,9}},
{{1,1,1},{2,2,2},{3,3,3}},
{{4,4,4},{5,5,5},{6,6,6}}
};
print3DMatrix(Matrix);
...
}
void print3DMatrix (const float A[][3][3])
{
for(int i = 0; i < 3; i++)
for(int j = 0; j < 3; j++)
for(int k = 0; k < 3; k++)
cout << i << j << k << " = " << A[i][j][k] << endl;
}
That output format isn’t the nicest.
Depending on how you interpret the indices here goes an alternative output format ...
for (int j = 0; j < 3; ++j) {
for (int i = 0; i < 3; ++i) {
for (int k = 0; k < 3; ++k) {
cout << Matrix[i][j][k] << " ";
}
cout << " ";
}
cout << "\n";
}
int SumArray(int arr[], int n)
{
int i, sum=0;
for (i=0;i<n;i++)
sum += arr[i];
return sum;
}
int A[10] = {1,2,3,4,5,6,7,8,9,10};
we can sum the entire array as
SumArray(A,10);
or
SumArray(&A[0],10);
But the same function can also sum the last nine elements using
SumArray(&A[1],9);
So an array name and an address seem to be equivalent. And indeed, a pointer type can be referenced like an array.
int A[10];
int* B = A;
... meaning the pointer B gets the address of the array A, its name, so B can be used just like A.
If we dereference B, so *B, we get the value of A[0]
But B[0] is the same as referring to A[0].
Similarly B[5] is the same variable as A[5], and
B[10] is still off the end of the array.
But it gets worse, in that we can reference the address of other variables and do the same kind of position addition, subscripting, even though it’s not an array.
So with
int A;
int* B = &A;
... we can use variables such as B[7] and B[50].
When a C++ program references array elements, the compiler has to do some pointer arithmetic.
For example, A[1] refers to the memory location one after the address A.
In pointer arithmetic this is *(A+1).
One what?
- One memory location.
What's that?
- Depends on type of A.
- The operator sizeof can help here.
If you are doing pointer arithmetic the compiler will figure out how far to jump, but it is still sometimes useful to know how much space is taken by a variable.
C++ provides an operator called sizeof to give the programmer this information.
The operator usually appears looking like a function as in:
sizeof(type)
sizeof(int)
sizeof(int) returns the number of bytes that the int type occupies – in this particular implementation of C++. The parentheses are not needed, but are usually used.
sizeof(type)
sizeof type
... both tell us the number of bytes for that type.
What do you get if you apply sizeof to a pointer? You can do something like ...
cout << sizeof(int*) << endl;
Note that sizeof can act on a type, variable or pointer to a variable type ...
So this is fine ...
double value;
cout << sizeof(value) << sizeof(double) << sizeof(double*);
The size of a string is different because it’s a class and there is dynamically allocated memory in there.
Sometimes we use pointers to refer to functions.
That is, a pointer that points to the address of the executable code of the function.
The pointers can be used to:
Call functions.
Pass functions as arguments to other functions.
You cannot perform pointer arithmetic on pointers to functions. Consider the following illustrations:
int *f(int);
char (*g)(int);
char (*h)(int, int);
The first is not a pointer to a function, since the () operator has higher precedence than *. Rather, f() which takes an int and returns type int*.
The precedence means we need to bracket the pointer name, as in the second and third examples.
So:
(*g)() is a pointer to a function taking an int and returning a char.
(*h)() is a pointer to a function taking two int’s and returning a char. Pointers to functions have types associated with both the return type and the parameter types of function.
Pointers to functions are particularly useful to describe how, in some function which takes them as an argument, we are to interpret some relationship.
For example, the function describes a comparison rule.
int (*Compare)(const char*, const char*);
This defines a function pointer Compare which can hold the address of any function that takes two constant character pointers as arguments and returns an integer.
A function pointer can also be defined and initialised in one line.
int (*Compare)(const char*, const char*) = strcmp;
When a function address is assigned to a function pointer, the two types must match.
The above definition is valid because strcmp() from <stdlib> has matching parameters and return type:
int strcmp(const char*, const char*);
Now strcmp can be either called directly, or indirectly via Compare.
The following three calls are equivalent:
strcmp("Cat", "Bat"); // direct
(*Compare)("Cat", "Bat"); // indirect
Compare("Cat", "Bat"); // indirect
A common use of a function pointer is to pass it as an argument to another function. This is because the receiving function requires different versions of the passed function in different circumstances.
We’ve talked about arrays, how to set them up and use them.
We’ve also mentioned that you are often better using vectors, so we are going to introduce vectors now.
#include <iostream>
#include <vector>
using namespace std;
intArray:vector<int>
int main()
{
size_t size;
cout << "Enter the size of the container: ";
cin >> size;
// get space for size integers and initialize them to 0
vector<int> intArray( size );
for(int i = 0; i < size; ++i)
intArray[i] = i;
}
The variable size is taken care of.
No need to use dynamic memory allocation.
To reference elements of the vector we can use subscripting again, so [], like we did with arrays.
Later we will come across a more generic way of accessing containers, iterators.
This is in Java so shouldn’t be a big deal for the undergraduates.
We typically find void as the return type of functions that don’t return values.
We don’t define variables of type void.
There are no operations on void, and it doesn’t have an associated value.
A void pointer is used to hold the address of any type, but without the type being held being known.
And you don’t access content through the void pointer, dereferencing won’t work.
This is usually used when we want to deal with memory as memory, without accessing the content.
So in comparing locations for example...
If we are want to access the content of the memory a void pointer addresses, we need to type cast it first.
The cast
(type *)vptr
... will convert the void pointer vptr to a type pointer.
So we can have collections of void pointers to be used to store data of a range of types.
If we are to access the content of the memory a void Pointer addresses, we need to type cast it first.
int i = 5;
int *ip;
void *vp;
ip = &i;
vp = ip;
cout << *vp << endl;
cout << *((int*)vp) << endl;
#include<iostream>
#include<string>
#include<sstream>
using namespace std;
string itos(int i) // convert int to string
{
stringstream s;
s << i;
return s.str();
}
This code is from: http://www.stroustrup.com/bs_faq2.html
Changing int to something else will work as long as the something has << overloaded for it!
int main()
{
int i = 127;
string ss = itos(i);
const char* p = ss.c_str();
cout << ss << " " << p << "\n";
}