A function declaration specifies
A declaration does not require specifying the function body that implements the function.
A function definition specifies the items in a declaration, but also gives the function body that implements the function.
So a function definition is similutaneously a function declaration.
A function must be declared in a file, before any code that calls the function.
However, as noted a function definition also serves as a function declaration.
This means that for a program that consists of several functions, there are several ways the function definitions/declarations can be arranged in a file.
A main function will print a table of the factorial(n) for the first 20 integers. integers by writing and calling a factorial function. Here is the main function
int main()
{
for(int i = 1; i <= 20; i++) {
cout << setw(3) << k << "."
<< setw(15) << factorial(k) << endl;
}
}
and here is the factorial function:
double factorial(int n)
{
if ( n <= 1 ) {
return 1.0;
} else {
return n * factorial(n-1);
}
}
The main function calls factorial, so there must be a declaration of factorial before this call.
Declare factorial before main and put the definition of factorial after the definition of main:
#include <iostream>
#include <iomanip>
using namespace std;
double factorial(int n); // A declaration only
int main()
{
for(int i = 1; i <= 20; i++) {
cout << setw(3) << k << "."
<< setw(15) << factorial(k) << endl;
}
}
// definition
double factorial(int n)
{
if ( n <= 1 ) {
return 1.0;
} else {
return n * factorial(n-1);
}
}
Put the definition of factorial before main. This also serves as a declaration.
#include <iostream>
#include <iomanip>
using namespace std;
// definition, also serves as a declaration
double factorial(int n)
{
if ( n <= 1 ) {
return 1.0;
} else {
return n * factorial(n-1);
}
}
int main()
{
for(int i = 1; i <= 20; i++) {
cout << setw(3) << k << "."
<< setw(15) << factorial(k) << endl;
}
}
If there are many functions called, version 2 (placing the definition of a function before any function that calls it) can result in the main function being at the very end of the file and requires that the definitions must be carefully ordered.
Version 1 that only places the declaration of a function before any function that calls it, allows you to collect all the definitions after main and they can occur in any order. Similarly, if all the declarations are collected before main, they can also be in any order.
C++ has two basic styles of parameter passing:
The default method of parameter passing is by value.
The variable or expression that is passed to a function will be called an actual parameter.
The variable occurring in the function definition's paramter list will be called the formal parameter.
The main points of passing a parameter to a function by value are:
double sum(int n) // call by value (the default)
{
double result = 0.0;
while( n > 0 ) {
result += n * n;
n--; // Formal parameter value changes!
}
return result;
}
int main()
{
int x = 5;
double y;
y = sum(x);
// y is 55.0, and x is still 5
}
The value of the actual parameter x is passed to the formal parameter n
| main | sum(int n) | |||
|---|---|---|---|---|
| x | 5 | ----> | n | 5 |
| y | ? | result | 0.0 | |
The formal parameter n has changed, but there is no change to the actual parameter x
| main | sum(int n) | |||
|---|---|---|---|---|
| x | 5 | n | 0 | |
| y | ? | <---- | result | 55.0 |
The main points of passing a parameter to a function by reference are:
double sum(int& n) // call by reference
{
double result = 0.0;
while( n > 0 ) {
result += n * n;
n--; // Formal parameter value changes!
}
return result;
}
int main()
{
int x = 5;
double y;
y = sum(x);
// y is 55.0, but x is 0
}
A reference to the actual parameter x is passed to the formal parameter n so that in sum n is just an alias for x.
| main | sum(int& n) | |||
|---|---|---|---|---|
| x | 5 | <=== refers to | n | ref. to main's x |
| y | ? | result | 0.0 | |
The formal parameter n has changed, but n is just
an alias for the actual parameter x.
So the
change is actually to the value of x.
| main | sum(int& n) | |||
|---|---|---|---|---|
| x | 0 | <=== refers to | n | ref. to main's x |
| y | ? | <--- return value | result | 55.0 |
In pass by value, changes to the formal parameter have no effect on the actual parameter value.
With pass by reference, the formal parameter behaves as an alias for the actual parameter, so each change to the formal parameter is actually a change of the actual parameter value.
Write a function sort3 that has three integer parameters.
Calling sort3(a,b,c) should rearrange the values of a, b, and c so that a <= b <= c.
For example, here is a main function that calls sort3:
int main()
{
int a = 5, b = 2, c = 3;
sort3(a,b,c);
cout << "a = " << a << ", b = " << b << ", c = " << c << endl;
return 0;
}
The output should be:
a = 2, b = 3, c = 5
A program is to read and process the data in a file, "input.txt". Each line of this file represents a task and contains the beginning time and ending time for the task. E.g., 2 5 means the task starts at time 2 and ends at time 5. Here is a sample input file contents:
1 4
1 2
3 6
4 5
5 6
Here is a beginning program that tries to read the data and for now the "processing" is just to print out each task.
Unfortunately, there is a logical error (the program compiles and runs with no compiler errors or runtime errors).
int main()
{
ifstream ifs;
int start, finish;
ifs.open("input.txt");
if ( ifs.fail() ) {
cout << "Unable to open input file 'input.txt'" << endl;
exit(0);
}
cout << setw(8) << "Start"
<< setw(8) << "Finish"
<< endl;
while( !ifs.eof() ) {
ifs >> start >> finish;
cout << setw(8) << start
<< setw(8) << finish
<< endl;
}
return 0;
}
Arrays in C++ are created by declaring the array name and giving the array size:
char a[5];
int b[5];
float c[5];
double d[5];
In each case each of these declarations allocates a block of storage to hold 5 consecutive values of the element type.
The size of the storage for these arrays can be printed using the sizeof operator:
cout << "a" << setw(5) << sizeof(a) << endl;
On one machine the sizes were:
Declaration Size char a[5]; sizeof(a) = 5 * sizeof(char) = 5*1 = 5 int b[5]; sizeof(b) = 5 * sizeof(int) = 5*4 = 20 float c[5]; sizeof(c) = 5 * sizeof(float) = 5*4 = 20 double d[5]; sizeof(d) = 5 * sizeof(double) = 5*8 = 40
An array type is not a class and so it doesn't have a 'length' property.
However, using sizeof, there is a 'trick' to get the number of elements in an declared array.
Example:
float c[5];
double d[5];
int n = sizeof(c)/sizeof(int); // 20/4 = 5
int m = sizeof(d)/sizeof(double); // 40/8 = 5
That is, for a declared array, x, whose element type is elemType, the number of elements of the array is:
sizeof(x)/sizeof(elemType)
It is often convenient to give the initial values of an array in the declaration and let the compiler the number of those elements.
Only in this case are you allowed to have an array declaration that uses an empty [], omitting the number of elements:
int main()
{
int x[] = {1,1,2,3,5,8,13,21};
for(int i = 0; i < 8; i++) {
cout << x[i] << endl;
}
return 0;
}
The storage for x is for exactly the number of elements in the initial value list.
Instead of [], you can specify the number elements even when there is an initial value list. The number specified should be greater than or equal to the number of elements in the initial list.
If the number is greater than the number of elements in the initial list, the remaining values are initialized to the default value for the type - 0 for an int array.
But what if you decide to add a few more initial values: 34, 55.
Now you have to rememeber to change the element count from 8 to 10 in the for loop.
We can use the sizeof trick to avoid having to count the array elements in the initial value list:
int main()
{
int x[] = {1,1,2,3,5,8,13,21};
int n = sizeof(x)/sizeof(int); // 8 in this case
for(int i = 0; i < n; i++) {
cout << x[i] << endl;
}
return 0;
}
Now if we add more elements to the initial list for x, the value of n will be recalculated correctly as the count of the array elements.
What is the value of n in each case below if
int n = sizeof(x)/sizeof(int);
int x[] = {10,30,50};
int x[3] = {10,30,50};
int x[5] = {10,30,50};
double x[5];
For the array variable x declared here, the element type is double, so the type of the expression x[i] is clearly double!
But what is the type of the expression x by itself without a subscript?
Answer: The type is double [5], and array of 5 doubles.
But in C++, assignment is not defined to an array.
That is, an array cannot appear as the target (on the left side) of an assignment.
However, an array can be converted to a pointer type with value equal to the address of the first element of the array.
int x[] = {10,20,30};
int y[3];
int *p;
y = x; // Illegal!
p = x; // Ok. x is implicitly converted to pointer to x[0]
The assignment to p is equivalent to explicitly calculating the address of x[0]:
p = &x[0]; // p now points to x[0]
Passing an actual parameter to a formal parameter by value is essentially the same as assignment except that the formal parameter will be holding some prior value.
Just as the target of an assignment can't be an array in C++, a formal parameter to receive an array, must be a pointer type.
When an actual parameter is an array type and is passed to a function, it is converted to a pointer with value being the address of the first element of the array.
In short, when an array is passed to a function, the value passed is just the address of the first array element.
This means that the type of the formal parameter should be a pointer!
Write a function, sum, that computes the sum of the elements in an array of ints.
int sum(int *a, int n);
The first formal parameter, a, will correspond to an actual array parameter. The value passed will be converted to a pointer to the first element of the array and stored in a.
The second parameter, n, is for the number of elements in the array.
This is necessary since the sizeof trick doesn't work here to find the number of elements in the array.
Why not? Because the type of a is just pointer, not a declared array, so sizeof(a)/sizeof(int) = 4/4 = 1.
Although the formal parameter is a pointer, C++ allows you to write the formal parameter declaration to "look like" an array, but with the size omitted:
int sum(int a[], int n);
Note that the type of the formal parameter a is still pointer to int.
We might try to define the sum function:
int sum(int *a, int n) {
int result = 0;
for(int k = 0; k < n; k++) {
result += ???;
}
return result;
}
Here the ??? should access the i-th element of the array that a points to.
How?
We can try to use the pointer dereferencing operator *
result += *a;
But this only accesses the first element of the array.
How can we access the remaining elements using the pointer a?
Pointer arithmetic allows you to add an int to a pointer with the result being another pointer value.
The definition of this operation is precisely defined to solve the problem of accessing array elements through pointers.
Example:
int nums[4];
int *p = nums; // that is, p = &num[0]
What is the value of p + 1?
ValueOf(p + 1) = ValueOf(p) + 1 * sizeof(int)
That is, the address value of p + 1 is NOT in general just 1 more byte that the address of p.
Instead it is defined so that the address value of p + 1 is the address of 1 more int; or equivalently the address of the next int.
How do we access the actual next int, not just its address? By using the dereferencing operator *
*(p+1)
For any int value, k, p + k is:
Valueof(p + k) = ValueOf(p) + k * sizeof(int)
We can now complete the definition of sum:
int sum(int *a, int n) {
int result = 0;
for(int k = 0; k < n; k++) {
result += *(a+k);
}
return result;
}
Are we happy? Not so much.
The subscript operator [] is really a binary operator.
In the expression a[k] the two operands are a and k.
The type of the second operand, the index, should be int.
The type of the first operand, a, is typically an array.
But we also know that in C++, a can be converted to a pointer to the first element of a.
In fact, this is the way C++ treats all arrays. To the compiler:
address of a[k]: a + k
value of a[k] : *(a + k)
The nice consequence is that we can use the subscript operator on pointers:
For declarations:
int *p;
int k;
the compiler interprets
p[k] as *(p + k)
But the right side is already defined and is the value of the k-th integer after the integer pointed to by p.
Now we can write the defintion of sum either as:
int sum(int *a, int n) {
int result = 0;
for(int k = 0; k < n; k++) {
result += a[k];
}
return result;
}
or using the suggestive notation for the formal parameter:
int sum(int a[], int n) {
int result = 0;
for(int k = 0; k < n; k++) {
result += a[k];
}
return result;
}
Pointer values can be compared using the usual comparison operators, ==, <, <=, etc.
Since addition of an int to a pointer is defined, the increment and decrement operators ++ and -- are also defined.
p++;
has the same effect as
p = p + 1;
So if p points to the first element of an array, another way to access the second element is:
int x[4];
int *p = x;
cout << *p << endl; // Prints the first element of x
p++; // p is changed to point to the second element
cout << *p << endl; // Prints the second element of x
int sum(int *a, int n) {
int result = 0;
for(int *p = a; p < a + n; p++) {
result += *p;
}
return result;
}
This can potentially produce fewer machine instructions than the subscript version with a naive compiler.
However, modern optimizing compilers will generally produce code for the subscript version that is as efficient as it would for the pointer version.
++ and the dereferencing operator * have the same precedence and both are right associative.
So if p is a poitner, which operator is applied first in the expression: *p++. Also what is the value and what is the side effect?
Since the two operators have the same precedence, the first operator is determined by associativity.
Since these operators are right associative, the ++ is applied first; that is, as if parentheses had been applied:
*(p++)
int a[] = {5,10,15,20};
int *p = a;
cout << *p++ << endl;
cout << *p++ << endl;
What is the output?
What is *p after the two cout statements?
C++ has two ways of representing character strings:
A C-string type is just an array with element type char.
But with one additional condition: The characters of a string are stored in a char array with one additional character, '\0', the null character.
The null character allows functions to find the end of the C-String.
Using arrays of char in this way to represent strings means we can't assign one string value to another using the assignment operator =.
Instead there is a library of functions that accepts C-Strings (arrays of char) as parameters and includes functions for assignment, string comparison, and for breaking a string into substrings.
The Documents page contains a link to a quick reference for C and C++ standard libraries. Look under the Standard C Library heading for Standard C String & Character
The strcpy function copies one C-String to another.
It serves as a substitute for the assignment statement missing for C-Strings.
1
2 #include <iostream>
3 #include <cstring>
4
5 using namespace std;
6 int main()
7 {
8 char str1[20] = {'L', 'i', 'n', 'u', 'x', '\0'};
9 char str2[6];
10
11 strcpy(str2, str1);
12 cout << "str1: " << str1 << endl;
13 cout << "str2: " << str2 << endl;
14 return 0;
15 }
16
To use the C-String library, you must include the header file <cstring> which contains declarations of the library functions.
Line 8 allocates an array of size 20, but only the specifies the first 6 values. The remaining 14 values are all '\0'.
Line 8 could be abbreviated as char str1[20] = "Linux";
Although "Linux" only has 5 characters, the extra null terminator, '\0' is added at the end for any quoted string. So this also specifies 6 initial characters.
The cout and cin predefined values work for C-Strings as expected.
The declaration of strcpy in <cstring> is
char * strcpy(char *to, const char *from);
The return value should just be the value of the parameter to.
The second parameter has the qualifier const. Its type is still pointer to char, but the const means that changes to the string from points to are not allowed. (The compiler will enforce this.)
The string pointed to by the first parameter should change and so it does not have the const qualifier.
1 char *strcpy(char *to, const char *from)
2 {
3 int k;
4 for(k = 0; from[k] != '\0'; k++) {
5 to[k] = from[k];
6 }
7 to[k] = '\0';
8
9 return to;
10 }
What happens if the array for the 'to' string is too small?
int main()
{
char str1[8] = "Linux";
char str2[4];
strcpy(str2, str1);
...
}
With the gnu compiler the storage for str2 comes first, then immediately after it is the storage for str1.
Here is what happens with that compiler if the arrays, their addresses and values are printed before the call to strcpy and then after:
There is only enough room in str2 to hold the 4 characters { 'L','i', 'n', 'u'}.
Where do the remaining 2 charactres, 'x' and the trailing '\0', go?
The first 4 characters of "Linux" are copied to the array str2, but then the 'x' and the trailing null character overwrite the first two characters of str1. So the 'L' and the 'i' in str1 are replaced by 'x' and the null character. The const qualifier on the 'from' string str1 doesn't prevent it being changed.
&str1[0] = 0x22f060 &str2[0] = 0x22f05c Before calling strcpy(str2, str1): str2[0] 0x22f05c . str2[1] 0x22f05d E str2[2] 0x22f05e ^A str2[3] 0x22f05f a str1[0] 0x22f060 L str1[1] 0x22f061 i str1[2] 0x22f062 n str1[3] 0x22f063 u str1[4] 0x22f064 x str1[5] 0x22f065 '\0' str1[6] 0x22f066 '\0' str1[7] 0x22f067 '\0' str1 = 'x' str2 = 'Linux' After calling strcpy(str2, str1): str2[0] 0x22f05c L str2[1] 0x22f05d i str2[2] 0x22f05e n str2[3] 0x22f05f u str1[0] 0x22f060 x str1[1] 0x22f061 '\0' str1[2] 0x22f062 n str1[3] 0x22f063 u str1[4] 0x22f064 x str1[5] 0x22f065 '\0' str1[6] 0x22f066 '\0' str1[7] 0x22f067 '\0'
The C Library function strlen has declaration:
size_t strlen(char *s);
Note: size_t is defined to be an unsigned integer type, for example unsigned int.
The type unsigned int uses no sign bit, so 1 extra bit is available for the value. However, only non-negative values are represented.
For example, a 1 byte signed integer can represent 256 values in the range -128 to +127. One bit, the sign bit, is used to indicate whether the number is negative or not.
But an unsigned 1 byte value can also represent 256 values, but the range is 0 to 255. No bit is used for the sign.
The strlen function returns the number of characters in the C-String, not counting the trailing null character.
Give an implementation of the strlen function.
A two dimensional array is declared by giving the limits for the index for two subscripts.
A declaration of a two dimensional array, named table, with the first index limit 2 and the second limit 3. As usual the first dimension is thought of as the number of rows and the second, the number of columns.
int table[2][3];
In C, two dimensional arrays are stored in row major order in memory. The second row comes immediately after the first row.
Two dimensional arrays can be initialized in a declaration by listing the values (in row major order) enclosed in curly braces, separated by commas.
For readability, rows can be grouped in their own curly braces with each row group separated by commas
int table[2][3] = { 1, 2, 3, 4, 5, 6};
or
int table[2][3] = {
{ 1, 2, 3},
{ 4, 5, 6}
};
int table[2][3] = {
{ 1, 2, 3},
{ 4, 5, 6}
};
As for one dimensional arrays, you can't use the = operator to assign one multi-dimensional array to another one. You must copy one element at a time.
The name of multi-dimensional array is converted to a pointer to the first element of the first index ( at [0]), as for the 1 dimensional case.
However, what is table[0]? It is itself an array of size 3.
Here is a correct pointer declaration to which the 2-dimensional array table can be assigned:
int table [2][3];
int (*p)[3]; // p is a pointer to an int array of size 3
p = table; // table is converted to &table[0] and assigned to p.
// table is NOT converted to &table[0][0]
The parentheses are necessary:
int (*p)[3]; // p is a pointer to an int array of size 3 // (one pointer) int *q[3]; // q is an array of size 3 of pointers to int // (3 pointers)
int table [2][3];
int (*p)[3]; // p is a pointer to an int array of size 3
p = table; // table is converted to &table[0] and assigned to p.
Passing actual parameters to formal parameters of a function is similar to assignment.
Passing the 2-dimensional array table to a function converts table to a pointer of type of p above.
For example if we write a function printArr to print the elements of the array, the formal parameter could be declared exactly as above:
void printArr(int (*p)[3], int n);
The actual parameter is an array of size 2 of arrays of size 3
As with 1-dimensional arrays, the second parameter tells the dimension of the first index.
Although a pointer is passed to p we can emphasize that it really points to the first element of an array of arrays by using the empty [] for the first dimension:
void printArr(int p[][3], int n);
Here is the definition of the example printArr:
void printArr(int p[][3], int n)
{
for(int r = 0; r < n; r++) {
for(int c = 0; c < 3; c++) {
cout << setw(4) << p[r][c];
}
cout << endl;
}
}
This does NOT work.
void printArr(int p[][], int n);
The second dimension gives the number of elements in a row.
Without this, the compiler cannot determine how to find where the second row begins. For the actual parameter table:
int table[2][3] = { {1,2,3}, {4,5,6} };
the values are laid out in memory like this:
| 1 | 2 | 3 | 4 | 5 | 6 |
p[0] should point to the beginning of the first row (at the 1).
p[1] should point to the beginning of the second row (at the 4).
Unless the compiler knows how big a row is, this will not work.
Here is a main function that declares two multi-dimensional arrays of different sizes. It calls a function printArray2 to print each of the arrays. However, it doesn't pass the name of the array. Instead it passes the address of the [0][0] element and the dimensions of both the first and the second dimensions.
The values in the two dimensional array can be printed 1 value per output line.
For an extra challenge, print the output so that the values are printed in rows and columns.
Write the function printArray2.
void printArray2(int *p, int dim1, int dim2);
int main()
{
int tbl1[2][3] = {1,2,3,4,5,6};
int tbl2[4][5] = {
{1,2,3,4,5},
{6,7,8,9,10},
{11,12,13,14,15}
{16,17,18,19,20}
};
cout << "tbl1[2][3]" << endl;
printArray2(&tbl1[0][0], 2, 3);
cout << "\ntbl2[4][5]" << endl;
printArray2(&tbl2[0][0], 4, 5);
return 0;
}
A struct was originally in C just a record type in C; that is, it allowed you to group a number of data members, possibly of different types into one value.
In C++, a struct is really a class. It defines a new type and can have member functions as well as member data.
Until next time, we'll just ignore the member functions.
The members of a struct can be accessed using the "." (dot operator) with this syntax:
var.member
where var is a variable of some struct type and member is the name of one of the data members.
struct pair
{
int x;
int y;
};
int main()
{
pair pr;
pair arr[3];
int a, b;
cout << "Enter a pair of integers:";
cin >> pr.x >> pr.y;
cout << "Enter three more pairs of integers:" << endl;
for(int k = 0; k < 3; k++) {
cin >> a >> b;
arr[k].x = a;
arr[k].y = b;
}
// swap arr[0] and arr[2];
pair tmp;
tmp = arr[0];
arr[0] = arr[2];
arr[2] = tmp;
// print arr[0
cout << "arr[0].x = " << arr[0].x
<< ", arr[0].y = " << arr[0].y
<< endl;
return 0;
}
If p is a pointer to a struct, instead of using the "." operator to access members, the "->" operator should be used:
struct pair
{
int x;
int y;
};
int main()
{
pair pr;
pair *p;
pr.x = 5;
pr.y = 10;
p = ≺
// Print the x and y members using the pointer p
cout << "x = " << p-> x
<< ", y = " << p->y
<< endl;
return 0;
}