CS420 - Midterm Cheat Sheet

Download as pdf or txt
Download as pdf or txt
You are on page 1of 16

7.

1 Pass by pointers ( C programming)


void MovePoint(int originalXVal, int originalYVal, int* movedXVal, int* movedYVal) { // * is a pointer
*movedXVal = originalXVal + 30;
*movedYVal = originalYVal + 30;
}
int main(void) {
int originalX;
int originalY;
int movedX;
int movedY; // not declared as a pointer initially
scanf("%d", &originalX);
scanf("%d", &originalY);
MovePoint(originalX, originalY, &movedX, &movedY); //use & to pass the value of the variable
printf("Original point coordinates: (%d, %d)\n", originalX, originalY);
printf("Translated point coordinates: (%d, %d)\n", movedX, movedY);
return 0;
}
7.2 Pointer Basics (C programming)
Ex: int* maxItemPointer declares an integer pointer named maxItemPointer.Typically, a pointer is initialized with another variable's address. The reference operator (&)
obtains a variable's address. ASSIGNS ADDRESS

The dereference operator (*) is prepended to a pointer variable's name to retrieve the data to which the pointer variable points. Ex: If valPointer points to a memory
address containing the integer 123, then printf("%d", *valPointer); dereferences valPointer and outputs 123. ASSIGNS VALUE OF ADDRESS

Null pointer assignments and initialization - *pointer = NULL; // to assign as null or “0” on some systems

int main(void) {
int someInt;
int* valPointer; // integer pointer
someInt = 5;
printf("someInt address is %p\n", (void*) &someInt); //using & to obtain address
valPointer = &someInt; // assigning int* pointer to the address of someInt
printf("valPointer is %p\n", (void*) valPointer); //will print the address in hex using %p
return 0;

int main() {
int someInt;
int* valPointer;
someInt = 5;
printf("someInt address is %p\n", (void*) &someInt); //PRINTS ADDRESS i.e. 76

valPointer = &someInt; //assigns to the address of someInt i.e. 76


printf("valPointer is %p\n", (void*) valPointer); // prints the address (not val)

printf("*valPointer is %d\n", *valPointer); // prints the actual value at 76 not address

*valPointer = 10; // Changes someInt to 10

printf("someInt is %d\n", someInt);


printf("*valPointer is %d\n", *valPointer);

return 0;
}

7.3 Malloc & Free functions [Memory Allocation] (C programming)


malloc() is a function defined within the standard library, which can be used by adding #include <stdlib.h> to the beginning of a program. The malloc() function is named
for memory allocation. malloc() allocates memory of a given number of bytes and returns a pointer (i.e., the address) to that allocated memory. A basic call to malloc()
has the form: malloc(bytes)
malloc() returns a pointer to allocated memory using a void*, referred to as a void pointer. A void pointer is a special type of pointer that stores a memory address without
referring to the type of variable stored at that memory location. The void pointer return type allows a single malloc() function to allocate memory for any data type. The
pointer returned from malloc() can then be written into a particular pointer variable by casting the void pointer to the data type using the form:
pointerVariableName = (type*)malloc(sizeof(type)); //used mainly for struct types, arrays, and strings.
free() is a function that deallocates a memory block pointed to by a given pointer, which must have been previously allocated by malloc(). In other words, free() does the
opposite of malloc(). Deallocating memory using free() has the following form: free(pointerVariable); //Good coding practice

7.4 Using Malloc & Pointers with Struct Types [Memory Allocation] (C programming)
#include <stdio.h> int main(void) {
#include <stdlib.h> myItem* myItemPtr1 = NULL;

typedef struct myItem_struct { myItemPtr1 = (myItem*)malloc(sizeof(myItem));


int num1;
int num2; myItemPtr1->num1 = 5;
} myItem; (*myItemPtr1).num2 = 10;

void myItem_PrintNums(myItem* itemPtr) { myItem_PrintNums(myItemPtr1);


if (itemPtr == NULL) return; free(myItemPtr1);
printf("num1: %d\n", itemPtr->num1); return 0;
printf("num2: %d\n", itemPtr->num2); }
}
7.5 String functions with pointers (C programming)
if (strchr(orgName, 'D') != NULL) { // 'D' exists in orgName?
subString = strchr(orgName, 'D'); // Points to first 'D'
strchr(sourceStr, searchChar)
strcpy(newText, subString); // newText now "Dept. of
Redundancy Dept."
strchr() Returns NULL if searchChar does not
}
exist in sourceStr. Else, returns
if (strchr(orgName, 'Z') != NULL) { // 'Z' exists in orgName?
pointer to first occurrence.
... // Doesn't exist, branch not taken
}

strrchr(sourceStr, searchChar)
if (strrchr(orgName, 'D') != NULL) { // 'D' exists in orgName?
Returns NULL if searchChar does not subString = strrchr(orgName, 'D'); // Points to last 'D'
strrchr()
exist in sourceStr. Else, returns strcpy(newText, subString); // newText now "Dept."
pointer to LAST occurrence (searches in }
reverse, hence middle 'r' in name).

strstr(str1, str2)
subString = strstr(orgName, "Dept"); // Points to first 'D'
Returns NULL if str2 does not exist in if (subString != NULL) {
strstr() str1. Else, returns a char pointer strcpy(newText, subString); // newText now "Dept. of
pointing to the first character of the Redundancy Dept."
first occurrence of string str2 within }
string str1.

7.6 The malloc function for arrays and strings (C programming)


Previously, the programmer had to declare arrays to have a fixed size, referred to as a statically allocated array. Recall that arrays are stored in sequential memory
locations. malloc() can be used to allocate a dynamically allocated array by determining the total number of bytes needed to store the desired number of elements,
using the following form: pointerVariableName = (dataType*)malloc(numElements * sizeof(dataType))
The realloc function re-allocates an original pointer's memory block to be the newly-specified size. realloc() can be used to both increase or decrease the size of
dynamically allocated arrays. realloc() returns a pointer to the re-allocated memory: pointerVariable = (type*)realloc(pointerVariable, numElements * sizeof(type))
//Figure 7.6.3: Concatenating strings using a dynamically allocated array.
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>

int main(void) { int main(void) {


char nameArr[100]; // User specified name char nameArr[50]; // User specified name
char* greetingPtr = NULL; // Pointer to output greeting and name char* nameCopy = NULL; // Output greeting and name

// Prompt user to enter a name // Prompt user to enter a name


printf("Enter name: "); printf("Enter name: ");
fgets(nameArr, 100, stdin); fgets(nameArr, 50, stdin);

// Eliminate end-of-line char // Create dynamically allocated copy of name


nameArr[strlen(nameArr) - 1] = '\0'; // +1 is for the null character
nameCopy = (char*)malloc((strlen(nameArr) + 1) * sizeof(char));
// Dynamically allocate greeting strcpy(nameCopy, nameArr);
greetingPtr = (char*)malloc((strlen(nameArr) + 8) * sizeof(char));

// Modify string, hello + user specified name // Output greeting and name
strcpy(greetingPtr, "Hello "); printf("Hello %s", nameCopy);
strcat(greetingPtr, nameArr);
strcat(greetingPtr, "."); // Deallocate memory
free(nameCopy);
// Output greeting and name
printf("%s\n", greetingPtr); return 0;
}
free(greetingPtr);

return 0;
7.8 Vector ADT (C programming)
Structs and pointers can be used to implement a computing concept known as an abstract data type (ADT), which is a data type whose creation and update are supported
by specific well-defined operations. A key aspect of an ADT is that the internal implementation of the data and operations are hidden from the ADT user, a concept known
as information hiding

void vector_create(vector* v, Initializes the vector pointed to by v with vectorSize number of


elements. Each element with the vector is initialized to 0. vector_create(&v, 20)
unsigned int vectorSize)

void vector_destroy(vector* v) Destroys the vector by freeing all memory allocated within the vector. vector_destroy(&v)

void vector_resize(vector* v, Resizes the vector with vectorSize number of elements. If the vector
size increased, each new element within the vector is initialized to 0. vector_resize(&v, 25)
unsigned int vectorSize)

int* vector_at(vector* v, unsigned


Returns a pointer to the element at the location index. x = *vector_at(&v, 1)
int index)

Returns the vector's size — i.e. the number of elements within the
unsigned int vector_size(vector* v) vector. if (vector_size(&v) == 0)

// adds "47" onto the end of


void vector_push_back(vector* v, int Inserts the value x to a new element at the end of the vector, increasing
the vector size by 1. the vector
value)
vector_push_back(&v, 47);

// inserts "33" at index 2


void vector_insert(vector* v, Inserts the value x to the element indicated by position, making room // Before: 18, 26, 47, 52
by shifting over the elements at that position and higher, thus
unsigned int index, int value) increasing the vector size by 1. // After: 18, 26, 33, 47, 52
vector_insert(&v, 2, 33);

// erases element at index 0


Removes the element from the indicated position. All elements at
void vector_erase(vector* v, // Before: 18, 26, 47, 52
higher positions are shifted over to fill the gap left by the removed
unsigned int index) element. Thus, the vector size decreases by 1. // After: 26, 47, 52
vector_erase(&v, 0

Figure 7.8.2: Function definitions for vector ADT.


#include <stdio.h> // Return pointer to element at specified index
#include <stdlib.h> int* vector_at(vector* v, unsigned int index) {
#include "vector.h" if (v == NULL || index >= v->size) return NULL;

// Initialize vector with specified size return &(v->elements[index]);


void vector_create(vector* v, unsigned int vectorSize) { }
int i;
// Insert new value at specified index
if (v == NULL) return; void vector_insert(vector* v, unsigned int index, int value) {
int i;
v->elements = (int*)malloc(vectorSize * sizeof(int));;
v->size = vectorSize; if (v == NULL || index > v->size) return;
for (i = 0; i < v->size; ++i) {
v->elements[i] = 0; vector_resize(v, v->size + 1);
} for (i = v->size - 1; i > index; --i) {
} v->elements[i] = v->elements[i-1];
}
// Destroy vector v->elements[index] = value;
void vector_destroy(vector* v) { }
if (v == NULL) return;
// Insert new value at end of vector
free(v->elements); void vector_push_back(vector* v, int value) {
v->elements = NULL; vector_insert(v, v->size, value);
v->size = 0; }
}
// Erase (remove) value at specified index
// Resize the size of the vector void vector_erase(vector* v, unsigned int index) {
void vector_resize(vector* v, unsigned int vectorSize) { int i;
int oldSize;
int i; if (v == NULL || index >= v->size) return;

if (v == NULL) return; for (i = index; i < v->size - 1; ++i) {


v->elements[i] = v->elements[i+1];
oldSize = v->size; }
v->elements = (int*)realloc(v->elements, vectorSize * sizeof(int)); vector_resize(v, v->size - 1);
v->size = vectorSize; }
for (i = oldSize; i < v->size; ++i) {
v->elements[i] = 0; // Return number of elements within vector
} int vector_size(vector* v) {
} if (v == NULL) return -1;

return v->size;
7.11 Memory regions: Heap/Stack (C programming)
A program's memory usage typically includes four diZerent regions:
• Code — The region where the program instructions are stored.
• Static memory — The region where global variables (variables declared outside any function) as well as static local variables (variables declared inside
functions starting with the keyword "static") are allocated. Static variables are allocated once and stay in the same memory location for the duration of a
program's execution.
• The stack — The region where a function's local variables are allocated during a function call. A function call adds local variables to the stack, and a return
removes them, like adding and removing dishes from a pile; hence the term "stack." Because this memory is automatically allocated and deallocated, it is also
called automatic memory.
• The heap — The region where the malloc function allocates memory, and where the free function deallocates memory. The region is also called free store

7.12 Memory leaks (C programming)


A memory leak occurs when a program that allocates memory loses the ability to access the allocated memory, typically due to failure to properly destroy/free
dynamically allocated memory. A program's leaking memory becomes unusable, much like a water pipe might have water leaking out and becoming unusable. A memory
leak may cause a program to occupy more and more memory as the program runs, which slows program runtime. Even worse, a memory leak can cause the program to
fail if memory becomes completely full and the program is unable to allocate additional memory.
Some programming languages, such as Java, use a mechanism called garbage collection wherein a program's executable includes automatic behavior that at various
intervals finds all unreachable allocated memory locations (e.g., by comparing all reachable memory with all previously-allocated memory), and automatically frees such
unreachable memory.

13.1 Derived classes (C++ programming)


Derived class concept - Commonly, one class is similar to another class but with some additions or variations. Ex: A store inventory system might use a class called GenericItem
that has itemName and itemQuantity data members. But for produce (fruits and vegetables), a ProduceItem class with data members itemName, itemQuantity, and expirationDate
may be desired.
Inheritance -A derived class (or subclass) is a class that is derived from another class, called a base class (or superclass). Any class may serve as a base class. The derived class is
said to inherit the properties of the base class, a concept called inheritance. An object declared of a derived class type has access to all the public members of the derived class as
well as the public members of the base class. A derived class is declared by placing a colon ":" after the derived class name, followed by a member access specifier like public and a
base class name. Ex: class DerivedClass: public BaseClass { ... };. The figure below defines the base class GenericItem and derived class ProduceItem that inherits from
GenericItem.
Examples of Derived Classes
#include <iostream> class Restaurant : public Business { int main() {
#include <string>
public: Business someBusiness;
using namespace std;
void SetRating(int userRating) { Restaurant favoritePlace;
class Business { rating = userRating;
public: } someBusiness.SetName("ACME");
void SetName(string busName) { someBusiness.SetAddress("4 Main St");
name = busName; int GetRating() const {
} return rating; favoritePlace.SetName("Friends Cafe");
void SetAddress(string busAddress) { } favoritePlace.SetAddress("500 W 2nd Ave");
address = busAddress; favoritePlace.SetRating(5);
} private:
string GetDescription() const { int rating; cout << someBusiness.GetDescription() << endl;
return name + " -- " + address; }; cout << favoritePlace.GetDescription() << endl;
} cout << " Rating: " << favoritePlace.GetRating()
private: << endl;
string name;
string address; return 0;
}; }
13.2 Access by members of derived classes
Member access - The members of a derived class have access to the public members of the base class, but not to the private members of the base class. This is logical—
allowing access to all private members of a class merely by creating a derived class would circumvent the idea of private members. Thus, adding the following member
function to the Restaurant class yields a compiler error.
Protected member access - Recall that members of a class may have their access specified as public or private. A third access specifier is protected, which provides
access to derived classes but not by anyone else. The following illustrates the implications of the protected access specifier.
Class definitions -
Separately, the keyword "public" in a class definition like class DerivedClass: public BaseClass {...} has a diZerent purpose, relating to the kind of inheritance being carried
out:
• public : "public-->public, protected-->protected" -- public members of BaseClass are accessible as public members of DerivedClass, and protected members
of BaseClass are accessible as protected members of DerivedClass.
• protected : "public-->protected, protected-->protected" -- public and protected members of BaseClass are accessible as protected members of DerivedClass.
• private : "public-->private, protected-->private" -- public and protected members of BaseClass are accessible as private members of DerivedClass. Incidentally,
if the specifier is omitted as in "class DerivedClass: BaseClass {...}", the default is private.
Most derived classes created when learning to program use public inheritance.
class Business { int main() {
protected: // Members accessible by self and derived classes ...
string name;
private: // Members accessible only by self Business business;
string address; Restaurant restaurant;
public: // Members accessible by anyone
void PrintMembers(); ...
};
class Restaurant : public Business { // Attempted accesses
private: business.PrintMembers(); // OK
int rating; business.name = "Gyro Hero"; // ERROR (protected only applies to derived classes)
public: business.address = "5 Fifth St"; // ERROR
...
void DisplayRestaurant() { restaurant.PrintMembers(); // OK
// Attempted accesses restaurant.name = "Gyro Hero"; // ERROR
PrintMembers(); // OK restaurant.rating = 5; // ERROR
name = "Gyro Hero"; // OK ("protected" above made this possible)
address = "5 Fifth St"; // ERROR return 0;
} }
// Other class members ...
};

13.3 Overriding member functions


Overriding
When a derived class defines a member function that has the same name and parameters as a base class's function, the member function is said to override the base
class's function. The example below shows how the Restaurant's GetDescription() function overrides the Business's GetDescription() function.
class Business { Overriding vs. overloading
public: Overriding diLers from overloading. In overloading, functions with the same name must
... have diLerent parameter types or number of parameters. In overriding, a derived class
string GetDescription() const { member function must have the same parameter types, number of parameters, and
return name + " -- " + address; return value as the base class member function with the same name. Overloading is
} performed if derived and base member functions have diLerent parameter types; the
protected: member function of the derived class does not hide the member function of the base
string name; class.
string address;
};
class Restaurant : public Business {
public:
...
string GetDescription() const {
return name + " -- " + address +
"\n Rating: " + to_string(rating);
}
Figure 13.3.1: Function calling overridden function of base class. A common error is to leave oj the prepended base class name when wanting to call the
class Restaurant : public Business { base class's function. Without the prepended base class name, the call to
... GetDescription() refers to itself (a recursive call), so GetDescription() would call itself,
string GetDescription() const { which would call itself, etc., never actually printing anything.
return Business::GetDescription() + "\n Rating: " + to_string(rating);
};
...
};
13.4 Polymorphism and virtual member functions
Polymorphism refers to determining which program behavior to execute depending on data types. Two main types of polymorphism exist:
• Compile-time polymorphism is when the compiler determines which function to call at compile-time.
• Runtime polymorphism is when the compiler is unable to determine which function to call at compile-time, so the determination is made while the program
is running.
Function overloading is an example of compile-time polymorphism where the compiler determines which of several identically-named functions to call based on the
function's arguments.
One scenario requiring runtime polymorphism involves derived classes. Programmers commonly create a collection of objects of both base and derived class types. Ex:
The statement vector<Business*> businessList; creates a vector that can contain pointers to objects of type Business or Restaurant, since Restaurant is derived from
Business. Similarly, polymorphism is also used for references to objects. Ex: Business& primaryBusiness declares a reference that can refer to Business or Restaurant
objects.
compile-time polymorphism runtime polymorphism
void DriveTo(string restaurant) { void DriveTo(Business* businessPtr) {
cout << "Driving to " << restaurant << endl; cout << "Driving to " << businessPtr->GetDescription() << endl;
} }

void DriveTo(Restaurant restaurant) { int main() {


cout << "Driving to " << restaurant.GetDescription() << endl; int index;
} vector<Business*> businessList;
Business* businessPtr;
int main() { Restaurant* restaurantPtr;
DriveTo("Big Mac's"); // Call string version ...
} businessList.push_back(businessPtr);
businessList.push_back(restaurantPtr);

index = rand() % businessList.size();


DriveTo(businessList.at(index));
}
The program above uses a C++ feature called derived/base class pointer conversion, where a pointer to a derived class is converted to a pointer to the base class without
explicit casting. The above statement businessList.push_back(restaurantPtr); uses derived/base class pointer conversion to convert the Restaurant pointer to a Business
pointer (businessList is a vector of Business pointers).

Virtual functions
Runtime polymorphism only works when an overridden member function in a base class is virtual. A virtual function is a member function that may be overridden in a
derived class and is used for runtime polymorphism. A virtual function is declared by prepending the keyword "virtual". Ex: virtual string GetDescription() const. At runtime,
when a virtual function is called using a pointer, the correct function to call is dynamically determined based on the actual object type to which the pointer or reference
refers.
The override keyword is an optional keyword used to indicate that a virtual function is overridden in a derived class. Good practice is to use the override keyword when
overriding a virtual function to avoid accidentally misspelling the function name or typing the wrong parameters.

class Business {
public:
string GetDescription() const {
return name + " -- " + address;
}
No virtual function:
...
}; Friends Cafe -- 500 2nd Ave
class Restaurant : public Business {
public:
string GetDescription() const {
return name + " -- " + address +
"\n Rating: " + to_string(rating);
With virtual function:
}
... Friends Cafe -- 500 2nd Ave
}; Rating: 5
int main() {
Business* businessPtr;
Restaurant favoriteCafe;
favoriteCafe.SetName("Friends Cafe");
Virtual table
favoriteCafe.SetAddress("500 2nd Ave"); To implement virtual functions, the compiler creates a virtual table that allows
favoriteCafe.SetRating(5); the computer to quickly lookup which function to call at runtime. The virtual table
contains an entry for each virtual function with a function pointer that points to
// Point to favoriteCafe the most-derived function that is accessible to each class. Looking up which
businessPtr = &favoriteCafe; function to call makes runtime polymorphism slower than compile-time
polymorphism.
cout << businessPtr->GetDescription();
}

Pure virtual functions


Sometimes a base class should not provide a definition for a member function, but all derived classes must provide a definition. Ex: A Business may require all derived
classes to define a GetHours() function, but the Business class does not provide a default GetHours() function.
A pure virtual function is a virtual function that provides no definition in the base class, and all derived classes must override the function. A pure virtual function is
declared like a virtual function with the "virtual" keyword but is assigned with 0. Ex: virtual string GetHours() const = 0; declares a pure virtual function GetHours().
A class that has at least one pure virtual function is known as an abstract base class. An abstract base class object cannot be declared. Ex: The variable
declaration Business someBusiness; generates a syntax error if Business is an abstract base class.
class Business { In the left example, the programmer may intend to create several
public:
void SetName(string busName) { classes derived from Business, such as Restaurant, LawnService,
name = busName; and Co>eeShop. The abstract base class Business implements
} functionality common to all derived classes, thus avoiding
void SetAddress(string busAddress) {
redundant code in all derived classes, and supporting uniform
address = busAddress; treatment of a collection (e.g., vector) of objects of derived
} classes via polymorphism. Not overriding the pure virtual
virtual string GetDescription() const {
function in a derived class makes the derived class an abstract
return name + " -- " + address; base class too.
}

virtual string GetHours() const = 0; // pure virtual function

protected:
string name;
string address;
};

13.5 Abstract classes: Introduction (generic)


Abstract classes
Object-oriented programming (OOP) is a powerful programming paradigm, consisting of several features. Three key features include:
• Classes: A class encapsulates data and behavior to create objects.
• Inheritance: Inheritance allows one class (a subclass) to be based on another class (a base class or superclass). Ex: A Shape class may encapsulate data and
behavior for geometric shapes, like setting/getting the Shape's name and color, while a Circle class may be a subclass of a Shape, with additional features like
setting/getting the center point and radius.
• Abstract classes: An abstract class is a class that guides the design of subclasses but cannot itself be instantiated as an object. Ex: An abstract Shape class might
also specify that any subclass must define a ComputeArea() function.

13.6 Abstract classes


Abstract and concrete classes
A pure virtual function is a virtual function that is not implemented in the base class, thus all derived classes must override the function. A pure virtual function is declared
with the "virtual" keyword and is assigned with 0. Ex: virtual double ComputeArea() const = 0; declares a pure virtual function ComputeArea().
An abstract class is a class that cannot be instantiated as an object, but is the superclass for a subclass and specifies how the subclass must be implemented. Any class
with one or more pure virtual functions is abstract.
A concrete class is a class that is not abstract, and hence can be instantiated.
Let's imagine you're in a school with diVerent types of teachers: math teachers, science teachers, and art teachers. All these teachers must follow
some common rules, like taking attendance. But the way they teach their specific subjects can be diVerent. In programming, we can have something
called an "abstract class," which is like a blueprint for these teachers. This abstract class says, "Every teacher must take attendance, but I won't say
exactly how. Each specific teacher will decide how they do it." A "pure virtual function" in programming is like saying, "This rule (function) must be
followed, but I won't give any details on how to do it here. Each specific type of teacher (class) must provide the details."
Here’s an example in C++:
#include <iostream> // ScienceTeacher class inherits from Teacher
class ScienceTeacher : public Teacher {
// Abstract class: Teacher public:
class Teacher { void takeAttendance() override {
public: std::cout << "Science teacher takes attendance using a seating
// Pure virtual function: takeAttendance chart." << std::endl;
virtual void takeAttendance() = 0; // = 0 means it's a pure virtual }
function };
};
int main() {
// MathTeacher class inherits from Teacher MathTeacher mathTeacher;
class MathTeacher : public Teacher { ScienceTeacher scienceTeacher;
public:
void takeAttendance() override { mathTeacher.takeAttendance(); // Output: Math teacher takes
std::cout << "Math teacher takes attendance using a roll call." attendance using a roll call.
<< std::endl; scienceTeacher.takeAttendance(); // Output: Science teacher takes
} attendance using a seating chart.
};
return 0;
}

14.1 – 14.3 Exceptions


Error-checking code is code a programmer writes to detect and handle errors that occur during program execution. An exception is a circumstance that a program was
not designed to handle, such as if the user enters a negative height.
The language has special constructs, try, throw, and catch, known as exception-handling constructs, to keep error-checking code separate and to reduce redundant
checks.
• A try block surrounds normal code, which is exited immediately if a throw statement executes.
• A throw statement appears within a try block; if reached, execution jumps immediately to the end of the try block. The code is written so only error situations lead to
reaching a throw. The throw statement provides an object of a particular type, such as an object of type "runtime_error", which is a class defined in the stdexcept
library. The statement is said to throw an exception of the particular type. A throw statement's syntax is similar to a return statement.
• A catch clause immediately follows a try block; if the catch was reached due to an exception thrown of the catch clause's parameter type, the clause executes. The
clause is said to catch the thrown exception. A catch block is called a handler because it handles an exception.

bad_alloc Failure in allocating memory

ios_base::failure Failure in a stream (Ex: cin, stringstream, fstream)

logic_error To report errors in a program's logic. Ex: out_of_range error (index out of bounds)

runtime_error To report errors that can only be detected at runtime. Ex: overflow_error (arithmetic overflow)

#include <iostream> // Simulate more code


#include <stdexcept> // for runtime_error, logic_error, std::cout << "This code will not run after any of the
bad_alloc exceptions." << std::endl;
#include <new> // for bad_alloc }
#include <fstream> // for ios_base::failure catch (const std::runtime_error& e) {
std::cout << "Caught a runtime error: " << e.what() <<
int main() { std::endl;
try { }
// Simulate normal code catch (const std::logic_error& e) {
std::cout << "Starting the program..." << std::endl; std::cout << "Caught a logic error: " << e.what() <<
std::endl;
// Example causing runtime_error }
int divisor = 0; catch (const std::bad_alloc& e) {
if (divisor == 0) { std::cout << "Caught a bad alloc error: " << e.what() <<
throw std::runtime_error("Division by zero is not std::endl;
allowed!"); }
} catch (const std::ios_base::failure& e) {
int result = 10 / divisor; // This line won't execute std::cout << "Caught an I/O error: " << e.what() <<
std::endl;
// Example causing logic_error }
bool condition = false; catch (...) {
if (!condition) { std::cout << "Caught an unknown exception!" << std::endl;
throw std::logic_error("Logic error: Condition is }
false!");
} // Execution continues here
std::cout << "Execution continues after handling exceptions."
// Example causing bad_alloc << std::endl;
try {
int* hugeArray = new int[100000000000]; // Attempt to return 0;
allocate a huge array }
} catch (const std::bad_alloc& e) {
throw; // Re-throw the bad_alloc exception
}

// Example causing ios_base::failure


std::ifstream file("non_existent_file.txt");
if (!file) {
throw std::ios_base::failure("Failed to open file!");
}
15.1 Range-based for loop
The range-based for loop is a for loop that iterates through each element in a vector or container. A range-based for loop is also known as a for each loop. The range-
based loop declares a new variable that will be assigned with each successive element of a container, such as a vector, from the first element to the last element.

Regular for loop: Range-based for loop:

vector<string> teamRoster; vector<string> teamRoster;


string playerName;
unsigned int i; // Adding player names
teamRoster.push_back("Mike");
// Adding player names teamRoster.push_back("Scottie");
teamRoster.push_back("Mike"); teamRoster.push_back("Toni");
teamRoster.push_back("Scottie");
teamRoster.push_back("Toni"); cout << "Current roster: " << endl;

cout << "Current roster: " << endl; for (string playerName : teamRoster) {
cout << playerName << endl;
for (i = 0; i < teamRoster.size(); ++i) { }
playerName = teamRoster.at(i);
cout << playerName << endl;
}
Figure 15.1.2: Modifying a vector using a range-based for loop. Figure 15.1.3: Using a range-based for loop with auto to output a
#include <iostream> vector's elements.
#include <vector>
using namespace std; vector<double> examGrades = {45.7, 72.5, 53.2};

int main() { for (auto gradeVal : examGrades) {


vector<double> examGrades(4); // User's exam grades cout << gradeVal << endl;
double averageGrade; }

// Prompt user for exam grades


for (double& gradeVal : examGrades) { Figure 15.1.4: Using a range-based for loop with auto and & to
double userGrade; modify a vector's elements.

cin >> userGrade; vector<double> examGrades = {45.7, 72.5, 53.2};


gradeVal = userGrade;
} for (auto& gradeVal : examGrades) {
gradeVal += 0.5;
averageGrade = 0.0; }
for (double gradeVal : examGrades) {
averageGrade += gradeVal;
}
averageGrade = averageGrade / examGrades.size();

cout << "Average grade: " << averageGrade << endl;

return 0;
}

Figure 15.1.5: Using a range-based for loop with auto to compute average and to modify and output a vector's elements.

#include <iostream>
#include <vector>
using namespace std;

int main() {
vector<double> examGrades = {45.7, 72.3, 53.6}; // User's exam grades
double averageGrade;

// Find the average of three values


averageGrade = 0.0;
for (auto gradeVal : examGrades) {
averageGrade += gradeVal;
}
averageGrade = averageGrade / examGrades.size();
cout << "Average grade: " << averageGrade << endl;

// Increase each value by 0.5


for (auto& gradeVal : examGrades) {
gradeVal += 0.5;
}

cout << "Adjusted grades:" << endl;


// Output adjusted values
for (auto gradeVal : examGrades) {
cout << gradeVal << endl;
}

return 0;
}
15.2 List
The list class defined within the C++ Standard Template Library (STL) defines a container of ordered elements, i.e., a sequence. The list class supports functions for
inserting, modifying, and removing elements.
The list type is an ADT implemented as a templated class that supports diZerent types of elements. A C++ list is implemented as a doubly-linked list. A list can be declared
and created as list<T> newList; where T represents the list's type, such as int or string. #include <list> enables use of a list within a program.
front()
// Assume list is: 6, 3, 1
front()
Returns element at the front of the list. exList.front(); // returns 6

back()
// Assume list is: 6, 3, 1
back()
Returns element at the back of the list. exList.back(); // returns 1

void push_back(newElement) // Assume list is empty


push_back() exList.push_back(4); // List is: 4
Adds newElement to the end of the list. List's size is increased by one.
exList.push_back(5); // List is: 4, 5

void push_front(newElement) // Assume list is empty


push_front() exList.push_front(7); // List is: 7
Adds newElement to the beginning of the list. List's size is increased
by one. exList.push_front(9); // List is: 9, 7

// Assume list is empty


exList.size(); // returns 0
size()
exList.push_back(25);
size()
Returns the number of elements in the List. exList.push_back(13);
// List is now: 25, 13
exList.size(); // returns 2

void pop_back() // Assume list is: 3, 11, 6, 6


pop_back() exList.pop_back(); // List is: 3, 11, 6
Removes element from the end of the list. List's size is decreased by
one. exList.pop_back(); // List is: 3, 11

void pop_front() // Assume list is: 3, 11, 6, 6


pop_front() exList.pop_front(); // List is: 11, 6, 6
Removes element from the front of the list. List's size is decreased by
one. exList.pop_front(); // List is: 6, 6

void remove(existingElement) // Assume List is: 3, 11, 6, 6


remove() exList.remove(6); // List is now: 3, 11
Removes all occurrences of elements which are equal to existingElement.
List's size is decreased by number of existingElement occurrences. exList.remove(11); // List is now: 3

An iterator is an object that points to a location in a list and can be used to traverse the list bidirectionally.

// Assume exList is: 1 2


begin()
exIterator = exList.begin();
begin()
Returns an iterator that points to the first element in the list. // exList is now: 1 2
// exIterator position: ^

// Assume exList is: 1 2


end()
exIterator = exList.end();
end()
Returns an iterator that points to after the last element of the list. // exList is now: 1 2
// exIterator position: ^

// Assume exList is: 1 2


exIter = ++exList.begin();
// exList is now: 1 2
insert(iteratorPosition, newElement)
// exIter position: ^
insert()
Inserts newElement into the list before the iteratorPosition.
exList.insert(exIter, 4);
// exList is now: 1 4 2
// exIter position: ^
// Assume exList is: 1 2 3 4
exIter = exList.begin();
// exList is now: 1 2 3 4
// exIter position: ^
erase(iteratorPosition)

Removes a single element at iteratorPosition. exIter = exList.erase(exIter);


erase() // exList is now: 2 3 4
erase(iteratorFirst, iteratorLast)
// exIter position: ^
Removes a range of elements from iteratorFirst (inclusive) to
iteratorLast (exclusive).
exIter = exList.erase(exIter, --
exList.end());
// exList is now: 4
// exIter position: ^

15.3 Pair
The pair class
The pair class in the C++ Standard Template Library (STL) defines a container that consists of two data elements. #include <utility> enables use of the pair class. Many
STL container classes internally use or return pair objects. Ex: a map contains key-value pairs.
The pair type is an ADT implemented as a templated class (discussed elsewhere) that supports diZerent types values. A pair can be declared as pair<F, S> newPair; where
F represents the pair's first element type and S represents the pair's second element type. Ex: pair<int, string> newPair; declares a pair with an integer first element and a
string second element.
The make_pair() function creates a pair with the specified first and second values, with which a pair variable can be assigned. Each element in a pair can be accessed and
modified using the pair's first and second members.
Figure 15.3.1: Creating a pair and accessing the pair's elements.
#include <iostream>
#include <string>
#include <utility>

using namespace std;

int main() {
pair<string, int> caPair;

// 2013 population data from census.gov Population of California in 2013 was 38332521.
caPair = make_pair("California", 38332521); Population of California in 2010 was 37253965.

cout << "Population of " << caPair.first << " in 2013 was "
<< caPair.second << "." << endl ;

// 2010 population data from census.gov


caPair.second = 37253965;

cout << "Population of " << caPair.first << " in 2010 was "
<< caPair.second << "." << endl ;

return 0;
}

15.4 Map
Map container
A programmer may wish to lookup values or elements based on another value, such as looking up an employee's record based on an employee ID. The map class within
the C++ Standard Template Library (STL) defines a container that associates (or maps) keys to values. #include <map> enables use of a map.
The map type is an ADT implemented as a templated class (discussed elsewhere) that supports diZerent types of keys and values. Generically, a map can be declared
and created as map<K, V> newMap; where K represents the map's key type and V represents the map's value type.
The emplace() function associates a key with the specified value. If the key does not already exist, a new entry within the map is created. If the key already exists, the
associated map entry is not updated. Thus, a map associates at most one value for a key.
The at() function returns the value associated with a key, such as statePopulation.at("CA").
A map entry can be updated by assigning the entry with a new value. Ex: statePopulation.at("CA") = 39776830;
Table 15.4.1: Common map functions.
// map originally empty
emplace(key, value) exMap.emplace("Tom", 14);
emplace() Associates key with specified value. If key already exists, the map // map now: Tom->14,
entry is not changed. exMap.emplace("John", 86);
// Map now: Tom->14, John->86

// Assume map is: Tom->14, John->86, Mary->13


at(key) exMap.at("Mary") = 25;
at() Returns the entry associated with key. If key does not exist, throws an // Map now: Tom->14, John->86, Mary->25
out_of_range exception. exMap.at("Tom") // returns 14
exMap.at("Bob") // throws exception
// Assume map is: Tom->14, John->86, Mary->13
count(key)
count() exMap.count("Tom") // returns 1
Returns 1 if key exists, otherwise returns 0.
exMap.count("Bob") // returns 0

// Assume map is: Tom->14, John->86, Mary->13


erase(key)
erase() exMap.erase("John");
Removes the map entry for the specified key if the key exists.
// map is now: Tom->14, Mary->13

// Assume map is: Tom->14, John->86, Mary->13


clear()
clear() exMap.clear();
Removes all map entries.
// map is now empty

[ ] operator
The map class' [ ] operator can be used to add map entries (Ex: exMap["Dan"] = 25;) and access map entries (Ex: exMap["Dan"];).
However, if a map does not contain the specified key, the [ ] operator creates a new entry in the map with the key and the default
value for the map's value type. Ex: If the key "Bob" does not exist in the map, myVal = exMap["Bob"]; creates a new map entry with
key "Bob" and value 0. When using the [ ] operator, if creating a map entry with a default value is not desired, a programmer should
first check that the key exists before accessing the map with that key. This material uses emplace() and at() to add and access map
entries, but the examples above can be modified to use the [ ] operator.

emplace()
Like many C++ containers, the map class has both an insert() function and an emplace() function to add new entries to a map. insert()
requires that an entry be explicitly constructed before insertion. emplace() does not have this requirement, and constructs entries
of the correct type upon insertion by automatically calling an appropriate constructor. This material uses emplace() for the map
class as an intuitive way of adding a pair entry to a map without explicitly constructing the pair beforehand.

15.5 Set
Set container
The set class defined within the C++ Standard Template Library (STL) defines a collection of unique elements. The set class supports functions for adding and removing
elements, as well as querying if a set contains an element. For example, a programmer may use a set to store employee names and use that set to determine which
customers are eligible for employee discounts.
The set type is an ADT implemented as a templated class that supports diZerent types of elements. A set can be declared as set<T> newSet; where T represents the set's
type, such as int or string. #include <set> enables use of a set within a program.

// Set originally empty


exSet.insert("Kasparov"); // returns pair
insert(element)
(iterator at element "Kasparov", true)
If element does not exist, adds element to the set and returns
// Set is now: Kasparov
insert() pair object (iterator to element location, true). If element
exSet.insert("Kasparov"); // returns pair
already exists, returns pair object (iterator to element
(iterator at element "Kasparov", false) (element
location, false).
"Kasparov" already exists)
// Set is unchanged

// Assume Set is: Kasparov, Fisher


exSet.erase("Fisher"); // returns 1 (element
erase(element) "Fisher" exists)
erase() If element exists, removes element from the set and returns 1. // Set is now: Kasparov
If the element does not exist, returns 0. exSet.erase("Carlsen"); // returns 0 (element
"Carlsen" does not exist)
// Set is unchanged

// Assume Set is: Kasparov, Fisher, Carlsen


count(element)
count() exSet.count("Carlsen") // returns 1
Returns 1 if element exists, otherwise returns 0.
exSet.count("Anand") // returns 0

// Set originally empty


exSet.size(); // returns 0
size() exSet.insert("Nakamura");
size()
Returns the number of elements in the set. exSet.insert("Carlsen");
// Set is now: Nakamura, Carlsen
exSet.size(); // returns 2

15.6 Queue
queue class
The queue class defined within the C++ Standard Template Library (STL) defines a container of ordered elements that supports element insertion at the tail and element
retrieval from the head.
The queue type is an ADT implemented as a templated class that supports diZerent types of elements. A queue can be declared as queue<T> newQueue; where T
represents the element's type, such as int or string. #include <queue> enables use of a queue within a program.

// Assume exQueue is: "down"


push(newElement)
"right" "A"
push() exQueue.push("B");
Adds newElement element to the tail of the queue. The queue's size increases by
// exQueue is now: "down"
one.
"right" "A" "B"
// Assume exQueue is: "down"
"right" "A"
pop()
exQueue.pop(); // Removes
pop()
"down"
Removes the element at the head of the queue.
// exQueue is now: "right"
"A"

// Assume exQueue is: "down"


front() "right" "A"
exQueue.front(); // Returns
front()
Returns, but does not remove, the element at the head of the queue. If the queue "down"
is empty, the behavior is not defined. // exQueue is still: "down"
"right" "A"

// Assume exQueue is: "down"


back() "right" "A"
back() exQueue.back(); // Returns "A"
Returns, but does not remove, the element at the tail of the queue. // exQueue is still: "down"
"right" "A"

// Assume exQueue is: "down"


exQueue.size(); // Returns 1
size()
size()
exQueue.pop(); // exQueue is
Returns the size of the queue.
empty
exQueue.size(); // Returns 0

15.7 Deque
deque class
The deque (pronounced "deck") class defined within the C++ Standard Template Library (STL) defines a container of ordered elements that supports element insertion
and removal at both ends (i.e., at the head and tail of the deque).
A deque can be declared as deque<T> newDeque; where T represents the element's type, such as int or string. #include <deque> enables use of a deque within a
program.
deque's push_front() function adds an element at the head of the deque and increases the deque's size by one. The push_front() function shifts elements in the deque to
make room for the new element. deque's front() function returns the element at the head of the deque. And, deque's pop_front() function removes the element at the
head of the deque. If the deque is empty, front() results in undefined behavior.
The push_front(), front(), and pop_front() functions allow a deque to be used as a stack. A stack is an ADT in which elements are only added or removed from the top of a
stack
Table 15.7.1: Common deque functions.

push_front(newElement)
// Assume exDeque is: 3 5 6
push_front() exDeque.push_front(1);
Adds newElement element at the head of the deque. The deque's
// exDeque is now: 1 3 5 6
size increases by one.

push_back(newElement)
// Assume exDeque is: 3 5 6
push_back() exDeque.push_back(7);
Adds newElement element at the tail of the deque. The deque's
// exDeque is now: 3 5 6 7
size increases by one.

pop_front() // Assume exDeque is: 3 5 6


pop_front() exDeque.pop_front();
Removes the element at the head of the deque. // exDeque is now: 5 6

pop_back() // Assume exDeque is: 3 5 6


pop_back() exDeque.pop_back();
Removes the element at the tail of the deque. // exDeque is now: 3 5

front()
// Assume exDeque is: 3 5 6
front() exDeque.front(); // Returns 3
Returns, but does not remove, the element at the head of the
// exDeque is still: 3 5 6
deque. If the deque is empty, the behavior is not defined.

back()
// Assume exDeque is: 3 5 6
back() exDeque.back(); // Returns 6
Returns, but does not remove, the element at the tail of the
// exDeque is still: 3 5 6
deque. If the deque is empty, the behavior is not defined.

// Assume exDeque is: 3


size() exDeque.size(); // Returns 1
size()
Returns the size of the deque exDeque.pop_front(); // exDeque is empty
exDeque.size(); // Returns 0
SYNTAX/GRAMMER HW Assignment
1. With the following set of BNF grammar rules, write the leftmost derivation for the following statement as well as show the parse
tree representing the code structure of this statement:
• X = (X + Y) * Z
<assign> → <id> = <expr>
<id> → X | Y | Z
<expr> → <expr> + <term>
| <term>
<term> → <term> * <factor>
| <factor>
<factor> → (<expr> ) | <id>
Answer:
BNF Derivation: Parse Tree:
<assign> → <id> = <expr>
=> X = <expr>
=> X = <term>
=> X = <term> * <factor>
=> X = <factor> * <factor>
=> X = (<expr>) * <factor>
=> X = (<expr> + <term>) * <factor>
=> X = (<term> + <term>) * <factor>
=> X = (<factor> + <term>) * <factor>
=> X = (<id> + <term>) * <factor>
=> X = (X + <term>) * <factor>
=> X = (X + <factor>) * <factor>
=> X = (X + <id>) * <factor>
=> X = (X + Y) * <factor>
=> X = (X + Y) * <id>
=> X = (X + Y) * Z

2. Change the following BNF grammar rules so that:


• the operator + will have a higher precedence than operator *, and make operator * to be right associative.

Answer: Explanation:
<assign> ® <id> = <expr> • I switched the positions of + and * to make + have higher precedence, since the
<id> ® A | B | C lower the symbol on the parse tree the higher the precedence
<expr> ® <term> * <expr> • I switched <expr> to the right side of the * operator to make sure that we
| <term> recurse on the right-side. In doing so, we become right associative.
<term> ® <term> + <factor>
| <factor>
<factor> ® (<expr>)
| <id>
3. Show that the following set of grammar rule is ambiguous by demonstrating two different parse trees can be
generated for this statement: X * Y * Z
<stmt> → <term>
<term> → <term> * <term> | <id>
<id> → X | Y | Z
Answer:
For a grammar rule to be ambiguous there must be more than one way to parse a tree. Below you will see that if we
create the parse tree in both Left and Right associativity (recursion) we can prove that it is ambiguous.

4. Write the grammar rule or rules for a language so that:


• A string can have an arbitrary number of letters of X, followed by the same number of letters of Y.
Some examples are:
• XY, XXYY, XXXXYYYY, XXXXXXXYYYYYYY
• But X, XYY, YX, and XXXYY are no good.
And with the grammar rules, write a leftmost derivation to arrive at the sentential form below:
• XXXYYY

Grammar Rules: Leftmost Derivation:

<id> ® X | Y <stmt> ® <expr>


<stmt> ® <expr> ®<id><expr><id>
<expr> ® <id><expr><id> ®X<expr><id>
| <id><id> ®X<id><expr><id><id>
®XX<expr><id><id>
®XX<id><id><id><id>
®XXX<id><id><id>
®XXXY<id><id>
®XXXYY<id>
®XXXYYY
5. Start from the following set of BNF grammar rules, modify them to become the EBNF format. Explain every
change of the grammar rules from BNF to EBNF.

Answer:

EBNF: Explanation:

<program> ® begin <stmt_list> end We use the curly braces to indicate repition of 0 or more
<stmt_list> ® <stmt> {( ; ) <stmt>} because <stmt_list> can be just <stmt> or it can recurse
<stmt> ® <var> = <expression> itself with right associativity and be <stmt>; <stmt_list>.
<var> ® A | B | C
<expression> ® <var> ( + | - ) <var> I used parenthesis for the last line because it has the
option of being + - or just one <var>.

INSTRUCTOR SOLUTION: INSTURCTOR EXPLANATION:


Based on EBNF:
<stmt> {; <stmt>} would extend each statement for 0 or
<program> → begin <stmt_list> end N occurrences separated by semi-colon, which
<stmt_list> → <stmt> {; <stmt>} would be equivalent to the BNF <stmt> | <stmt>;
<stmt> → <var> = <expression> <stmt_list>
<var> → A | B | C <var> [(+|-) <var>] would start from <var> and it could
<expression> → <var> [(+|-) <var>] be augmented to + <var>, or -<var>:
1) the (+|-) gives the possibility of having either + or – as
connector between two <var>;
2) the square brackets indicate either 0 or 1 occurrence of
what’s inside

You might also like