Previous Lecture lect08 Next Lecture

lect08, Tue 04/30

Intro to lab04: Arrays; C++ Build Process, Makefiles, TDD

Arrays

Declaring Arrays

int arr[10];

Bracket Syntax

int arr[100];
for (int i = 0; i < 100; i++) {
	cout << arr[i] << endl;
}

Example of reading / writing values from / to the array

arr[0] = 1;
arr[1] = -5;
arr[1] = arr[0]; // fetching and storing array values
int arr[10];
cout << "Enter a number: ";
cin >> arr[0];
cout << "arr[0] = " << arr[0] << endl;
void passArrayValueExample(int x) {
	cout << Parameter value:  << x << endl;
}

int main() {
	int x[1];
	x[0] = 3;
	passArrayValueExample(a[0]); // prints 3
}

Example of iterating through the entire array

int arr[10];
for (int i = 0; i < 10; i++) {
	arr[i] = i; // initializes elements using i
}
int num = arr[5] + 2;
cout << num << endl; // 7

C++ Build Process

Header Files

Example

------------------------------------
// drawShapes.h
#include <string>
using namespace std;

string drawRightTriangle(int height);
string drawSquare(int length);
------------------------------------
// drawShapes.cpp
#include <string>
using namespace std;

string drawRightTriangle(int height) {
	string result = "";
	int rowLength = 1;

	for (int i = 0; i < height; i++) {
		for (int j = 0; j < rowLength; j++) {
			result += "*";
		}
		result += "\n";
		rowLength++;
	}
	return result;
}

string drawSquare(int length) {
	string result = "";

	for (int i = 0; i < length; i++) {
		for (int j = 0; j < length; j++) {
			result += "*";
		}
		result += "\n";
	}
	return result;
}
------------------------------------
//program1.cpp
#include <iostream>
#include "drawShapes.h"

int main() {
	cout << drawRightTriangle(5) << endl;
	cout << drawSquare(5) << endl;	
return 0;
}
------------------------------------

What’s with the different type of #include<> or #include “”?

Similar to how we compile C++ programs with a single file, we can compile all source (.cpp) files using:

g++ -o program1 program1.cpp drawShapes.cpp

g++ -c -o drawShapes.o drawShapes.cpp
g++ -c -o program1.o program1.cpp
g++ -o program1 program1.o drawShapes.o

C++ Build Process

  1. Preprocessing: Text-based program that runs before the compilation step. Looks for statements such as #include and modifies the source which is the input for compilation.
    • Think of the compiler “copying / pasting” the contents of the included file everytime #include is used.
  2. Compilation: Translates source code into “object code,” which is a lower-level representation optimized for executing instructions on the specific platform. Lower level representations are usually stored in a .o (object) file.

  3. Linking: Resolves dependencies and maps appropriate functions located in various object files. The output of the linker is an executable file for the specific platform.

But what happens if we have hundreds of source files to compile?

Makefiles

General Format of a Makefile

[target]: [dependencies]
	[commands]

Check out more information on compilation / makefiles.

Example

------
# Makefile

# Single line comments in Makefiles use '#'

program1: program1.o drawShapes.o
	g++ -o program1 program1.o drawShapes.o

clean:
	/bin/rm -f *.o program1
------

Example

$ make program1
c++    -c -o program1.o program1.cpp
c++    -c -o drawShapes.o drawShapes.cpp
g++ -o program1 program1.o drawShapes.o
$ make program1
c++    -c -o program1.o program1.cpp
g++ -o program1 program1.o drawShapes.o
$ make program1
make: `program1' is up to date.

Another Example of a Test Program

Test-Driven Development (TDD)

------------------------------------
// tdd.h
#include <string>
using namespace std;

void assertEqual(string expected, string actual, string message="");
------------------------------------
// tdd.cpp
#include <iostream>
#include <string>
using namespace std;

void assertEqual(string expected, string actual, string message = "") {
	if (expected == actual) {
		cout << "PASSED: " << message << endl;
	} else {
		cout << "\tFAILED: " << message << endl;
		cout << "Expected: " << "[\n" << expected <<
		"\n]" << "Actual: [\n" << actual << "\n]" << endl;
	}
}
------------------------------------
//testDrawShapes.cpp
#include <iostream>
#include <string>
#include "drawShapes.h"
#include "tdd.h"
using namespace std;

void testDrawRightTriangle() {
	string expected1 =
	"*\n"
	"**\n";

	string actual1 = drawRightTriangle(2);
	assertEqual(expected1, actual1, " testHeight:2");

	string expected2 =
	"*\n"
	"**\n"
	"***\n";

	string actual2 = drawRightTriangle(3);
	assertEqual(expected2, actual2, " testHeight:3");
}

void testDrawSquare() {
	string expected1 =
	"**\n"
	"**\n";

	string actual1 = drawSquare(2);
	assertEqual(expected1, actual1, " testLength: 2");

	string expected2 =
	"***\n"
	"***\n"
	"***\n";

	string actual2 = drawSquare(3);
	assertEqual(expected2, actual2, " testLength: 3");
}

int main() {
	testDrawRightTriangle();
	testDrawSquare();
}
------------------------------------
# Makefile

testDrawShapes: testDrawShapes.o drawShapes.o tdd.o
	g++ testDrawShapes.o drawShapes.o tdd.o -o testDrawShapes

clean:
	/bin/rm -f *.o testDrawShapes
------------------------------------
make testDrawShapes

Code from Lecture

We first looked at how to declare an array and output the values of an uninitialized array (notice the junk values!).

// main.cpp

#include <iostream>
using namespace std;

int main()
{
    const int SIZE = 20;
    int arr[SIZE];

    for (int i = 0; i < SIZE; i++)
    {
        cout << "[" << i << "] ";
        cout << arr[i] << endl;
    }

    cout << "Last element " << arr[SIZE] << endl;

    return 0;
}

We then asked the user to provide integer values that were stored in the array.

Update: C++ does not allow a variable for the length of the array, and it turns out that in order for g++ to tell you this, you need to use the -pedantic flag when you are compiling (try it yourself, remove const and use: g++ -pedantic populate_arr.cpp).

// populate_arr.cpp
#include <iostream>
using namespace std;

int main()
{
    const int SIZE = 3;
    int arr[SIZE];

    cout << "Enter " << SIZE << " numbers.\n";
    for (int i = 0; i < SIZE; i++)
    {
        cout << "[" << i << "] ";
        cin >> arr[i];
    } //end for

    cout << "Contents of the array\n";
    for (int i = 0; i < SIZE; i++)
    {
        cout << "[" << i << "] ";
        cout << arr[i] << endl;
    }

    return 0;
}

We tried to create a function to print the contents of the array. Interestingly, this code resulted in a compiler error (print_Arr.cpp:21:16: error: use of undeclared identifier 'аrr'), which apparently was caused by the accidental keyboard/language switch: the a that’s in the arr variable is from the Cyrilic alphabet, which is outside the expected ASCII range. Mystery solved! The code should compile and run for you (unless you copy it from here without fixing it first ;-)).

// print_Arr.cpp
#include <iostream>
using namespace std;

void print_arr(int array[], int arr_size);

int main()
{
    const int SIZE = 3;
    int arr[SIZE];

    cout << "Enter " << SIZE << " numbers.\n";
    for (int i = 0; i < SIZE; i++)
    {
        cout << "[" << i << "] ";
        cin >> arr[i];
    } //end for

    cout << "Increment the contents of the array\n";
    //print_аrr(аrr, SIZE);
    for (int i =0; i < SIZE; i++)
    {
        //cout << arr[i] = arr[i] + 3 << endl;
        arr[i] = arr[i] + 3;
        cout << arr[i] << endl;
    }


    return 0;
}
void print_arr(int array[], int arr_size)
{
    for (int i = 0; i < arr_size; i++)
    {
        cout << "[" << i << "] ";
        cout << array[i] << endl;
    }
}

Lastly, we created a very simple setup using a header and a corresponding cpp file along with the main test program.

The declaration

// print.h
#include <string>
using namespace std;

void print_smth(string text);

The definition

print.cpp
#include <iostream> // notice that you don't need to include print.h here
using namespace std;

void print_smth(string text)
{
    cout << text << endl;
}

The test program

// testprint.cpp

#include <iostream>
#include "print.h"

int main()
{
    print_smth("Hello!");
    return 0;
}

If we try to compile only the test program, we will get a linker error:

g++ testprint.cpp
Undefined symbols for architecture x86_64:
  "print_smth(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >)", referenced from:
      _main in testprint-d213ed.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

In order to successfully compile it, we need to also compile the source file for the functions in print.h:

lec08 $ g++ testprint.cpp print.cpp
lec08 $ ./a.out
Hello!

Practice Questions

  1. Given an int array of size 10, write a void function called convertNegative that counts then returns the number of negative numbers, and turns all negative numbers in the array into 0.
  2. Since in the C++ build process, each file is compiled into an object file, why aren’t object files created when programs are compiled with the -o flag?
  3. What happens if you declare an integer array, and initialize only 1 value?