Lecture 5 - Sept 15, 2023

Summary

In this lecture, we discuss #ifndef include guards for header files, and introduce C++ I/O.

Last lecture

Multiple file programs and separate compilation.

Today

Macros and C++ I/O.

Recap from previous lecture

  • main.cpp: print.o and input.o will not change. Only main.o will change.
g++ -c main.cpp
g++ main.o print.o input.o -o main.so
  • print.cpp: main.o and input.o will not change. Only print.o will change.
g++ -c print.cpp
g++ main.o print.o input.o -o main.so
  • print.h: This changes #include "print.h" in main.cpp and print.cpp. This is tricky!
g++ -c main.cpp
g++ -c print.cpp
g++ main.o print.o input.o -o main.so

IDEs, like VS Code, and Make files keep track of what file you changed and minimizes compile commands as they take time.

In short, dividing code across multiple files can save compilation time.

Macros

What happens if I include the same header (.h file) multiple times?

Why would someone do that?

Example

What happens if we have this structure?

diagrams/lecture5-diagram1.svg

To solve this, a preprocessor #ifndef guards against including a header file several times.

// a.h

#ifdef A_H
#define A_H

struct A {
    ...
}

#endif

The first time “a.h” is read, it will find A_H is not defined.

Then it will define A_H and struct A.

The next time it reads “a.h”, A_H will be defined and hence would skip instructions until #endif.

In general, #ifdef [MACRO]ignores the whole code until #endif if [MACRO] is defined.

#ifndef [MACRO] ignores the whole code until #endif if [MACRO] is not defined.

Why do we need header files? Can we just have .cpp files and include it?

Example
// main .cpp

#include "print.cpp"

int main() {
  ...
}
// print.cpp

#include <iostream>

void printNum(int x) {
    ...
}

This means wheter I change print.cpp, I will have to re-compile main.cpp and print.cpp.

Also, linking will fail as main.cpp will have printNum() implemented and print.cpp too. You will have multiple definitions of printNum in the executable main, which is not allowed.

diagrams/lecture5-diagram2.svg

  • main.o will have printNum() implementation
  • print.o already has printNum() implementation
  • printNum() is defined twiece, so linking will fail.

C++ I/O

There are many ways to take input and produce output.

Standard input-output

Using cout and cin from iostream.

#include <iostream>

using namespace std;

int main() {
    int x;
    cout "Hello world" << endl;
    cin >> x;
    return 0;
}

File input-output

Using ifstream (input file) and ofstream (output file) from fstream.

Output to a file

#include <fstream>

using namespace std;

int main() {
    ofstream outFile("myFile.txt");
    string name = "We are engineers!";
    outFile << name;
    outFile.close();
}

If a file does not exist , it will be created. If it exists, its contents will be overwritten.

To append a file, use ofstream outFile("myFile.txt", ios::app);.

Input from a file

#include <fstream>

using namespace std;

int main() {
    ifstream inputFile;
    inputFile.open("myFile.txt");

    // or ifstream inputFile("myFile.txt");
    // to replace the two lines above

    int num1, num2, num3;
    
    // input from file
    inputFile >> num1 >> num2 >> num3;

    inputFile.close();

    return 0;
}

Where to find the file?

```cpp
// absolute path
inFile.open("/u/prof/emarasal/ece244/lab1/myFile.txt")

// relative path
inFile.open("lab1/myFile.txt")
inFile.open("../myFile.txt")

// current directory
inFile.open("myFile.txt")

Buffering

  • The output is not immediately written to a file.
  • It will be written in “chunks”.
  • Why buffering? Writing in a buffer is much faster than writing in a file.
  • To optimize resources, writing in files happens in chunks.
  • To force output, use outputFile.flush() or outputFile << endl;.

diagrams/lecture5-diagram3.svg

Ungraded homework

Solve the exercises of Chapter 1: Programming Basics

Exercise 1

Question 2 in Fall 2022 Midterm Exam [Easy]

When you compile the following program, what happens? If there is an error, explain what the error is (one sentence max).

#include <iostream>
using namespace std;

int main() {
  hello(1);
  return 0;
}
void hello(int i) {
  cout << "Hello !" << i << endl;
  return;
}

There will be a compilation error because hello() is called before it is declared.

The correct code is:

#include <iostream>
using namespace std;

// Forward function
void hello(int i);  

int main() {
  hello(1);
  return 0;
}

// Function definition
void hello(int i) {
    cout << "Hello !" << i << endl;
    return;
}
Exercise 2

Question 4 in Fall 2022 Midterm Exam [Easy]

Which is the output of the following program?

#include <iostream>
using namespace std;

void increment(int& a) {
  a = a + 1;
}

void decrement(int a) {
  a = a - 1;
}

void doubling(int* a) {
  *a = (*a) * 2;
}

int main() {
  int a = 3;
  increment(a);
  cout << a << endl;
  decrement(a);
  cout << a << endl;
  doubling(&a);
  cout << a << endl;
  return 0;
}

4, 4 and 8 because increment() is called by reference, decrement() is called by value and doubling() takes a pointer and dereferences it to modify the original value.

Exercise 3

Question 6 in Fall 2022 Midterm Exam [Easy]:

Compared to C, passing by reference is introduced in C++. Both of the following two functions can be used to swap the value of two integers:

void swap_by_p(int* a, int* b); // swap version.1
void swap_by_r(int& a, int& b); // swap version.2

Part 1. Write the implementations for these two functions (no more than 4 lines of code for each function)

void swap_by_p(int* a, int* b) {
    ...
}

void swap_by_r(int& a, int& b) {
    ...
}

Part 2. If given two int variables x and y, write a function call that swaps the value of x and y, using swap_by_p.

Part 3. If given two int variables x and y, write a function call that swaps the value of x and y, using swap_by_r.

#include <iostream>
using namespace std;

void swap_by_p(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

void swap_by_r(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 200;
    int y = 100;
    swap_by_p(&x, &y);
    cout << x << " " << y << endl;
    swap_by_r(x, y);
    cout << x << " " << y << endl;
    return 0;
}
Exercise 4

Question 7 in Fall 2022 Midterm Exam [Easy]

Ellie writes a program to make a simple database for ECE students who like drinking soy milk from 2T2 to 2T6. She designs two classes: student and ECE, and puts them into different files below. The main function is in the main.cpp.

ECE.h

#ifndefine ECE
#define ECE

#include "student.h"

class ECE{
    ...
};

#endif

ECE.cpp

#include "ECE.h"

ECE::ECE() {
    ...
}

student.h

#include <string>

class Student {
    ...
};

student.cpp

#include "student.h"

Student::Student() {
    ...
}

main.cpp

#include "ECE.h"
#include "student.h"

int main() {
    ...
}

Part 1. Ellie tries to compile this program with g++. What’s the Unix (i.e., terminal) command that compiles the entire program using one command, which generates an executable called small_database?

Part 2. However, it fails to compile. Can you point out the compile-time error and fix this error for her?

Part 3. With your help, Ellie has fixed the compile-time error. Now, Ellie wants to use separate compilation learned from ECE244 to compile her project. Write down all the Unix commands necessary to separately compile the above files and generate an executable small_database.

Part 4. Ellie then changes some code in ECE.cpp. Write down the Unix commands necessary to regenerate the executable by compiling the smallest number of files needed. Assume the generated executable is called small_database.

Compile with

g++ main.cpp ECE.cpp student.cpp -o small_database

The first problem is that student.h has no header guard, and it is included in both ECE.h and student.cpp. This will cause a multiple definition error.

The solution is to add a header guard to student.h.

#ifndef STUDENT
#define STUDENT

#include <string>

class Student {
    ...
};

The second problem is that ECE.h says #ifndefine ECE, but it should be #ifndef ECE.

To compile separately, use

g++ -c student.cpp -o student.o
g++ -c ECE.cpp -o ECE.o
g++ -c main.cpp -o main.o
g++ student.o ECE.o main.o -o small_database

After changing ECE.cpp, use

g++ -c ECE.cpp -o ECE.o
g++ student.o ECE.o main.o -o small_database

Question 2 Fall 2018 Midterm Exam

You are given a program that has a main function and 3 classes: First, Second and Third. For each of these classes, you have a definition file and an implementation file. Thus, you have seven files in total: First.h, First.cpp, Second.h, Second.cpp, Third.h, Third.cpp and main.cpp. All the files exist in the same directory.

The first few lines of each file are shown below. The rest of the contents of each file is irrelevant to the question and is shown as …. You may assume the definition/implementation files are error-free.

First.h

#ifndef FIRST_H
#define FIRST_H

class First {
    ...
};
#endif

First.cpp

#include “First.h”

First::First() {
    ...
}

Second.h

#ifndef SECOND_H
#define SECOND_H

class Second {
  ...
};

#endif

Second.cpp

#include "First.h"
#include "Second.h"

Second::Second() {
  ...
}

Third.h

#ifndef THIRD_H
#define THIRD_H

class Third {
  ...
};

#endif

Third.cpp

#include "Second.h"

Third::Third() {
  ...
}

main.cpp

#include "First.h"
#include "Second.h"
#include "Third.h"

int main() {
  ...
}

The files are to be separately compiled and then linked into a single executable called main.

Part 1. Write down the Unix commands necessary to separately compile the above files and generate the executable.

Part 2. You modify the file Second.cpp. Write down the Unix commands necessary to regenerate the executable by compiling the smallest number of files possible.

Part 3. You modify the file First.h. Write down the Unix commands necessary to regenerate the executable by compiling the smallest number of files possible.

Part 1

g++ -c First.cpp -o First.o
g++ -c Second.cpp -o Second.o
g++ -c Third.cpp -o Third.o
g++ -c main.cpp -o main.o
g++ First.o Second.o Third.o main.o -o main

Part 2

g++ -c Second.cpp -o Second.o
g++ First.o Second.o Third.o main.o -o main

Part 3

g++ -c First.cpp -o First.o
g++ -c Second.cpp -o Second.o
g++ First.o Second.o Third.o main.o -o main