Revision Guide for e3r [1]#

C++ Questions#

Warning

All the answers are generated by ChatGPT, but I have corrected some mistakes (like code quality problems)

What is a namespace?#

In programming, a namespace is a way to group related identifiers together and to prevent name clashes between different parts of a program or external libraries. A namespace is typically defined using the namespace keyword in C++, and it provides a scope for the identifiers that are defined within it. For example, if you have two different functions with the same name, you could put them in different namespaces to avoid a naming conflict. Namespaces are often used in large programs to organize code and make it easier to read and maintain.

How to use the input/output in C++?#

In C++, the cin object is used to read input from the standard input stream (usually the keyboard), and the cout object is used to write output to the standard output stream (usually the screen). Here’s an example of how you can use cin and cout to read a number from the user and then write “Hello, [number]!” to the screen:

#include <iostream>

int main() {
  // Read a number from the user
  int number;
  std::cin >> number;

  // Write "Hello, [number]!" to the screen
  std::cout << "Hello, " << number << "!" << std::endl;

  return 0;
}

To read input, you can use the >> operator (known as the “extraction operator”) with cin. For example, the line std::cin >> number; reads an int value from the standard input stream and stores it in the variable number.

To write output, you can use the << operator (known as the “insertion operator”) with cout. For example, the line std::cout << "Hello, " << number << "!" << std::endl; writes the string “Hello, “ followed by the value of the number variable, followed by the string “!”, followed by a newline character, to the standard output stream.

Keep in mind that when using cin and cout, you need to include the iostream header file at the top of your program and you need to use the std:: namespace, as shown in the example above.

What are operator and function overloading in C++?#

Operator overloading in C++ allows operators, such as + and -, to have different meanings for different types of data. For example, the + operator can be used to add two numbers together, but it can also be used to concatenate two strings. Function overloading, on the other hand, allows a single function to have different implementations depending on the type and number of arguments passed to it. This allows for greater flexibility and code reuse.

Operators are also functions.

What is the difference between delete and delete[] in C++?#

In C++, the delete keyword is used to deallocate memory that was previously allocated on the heap. This is typically used to free memory that is no longer needed by the program. The delete[] operator is similar to delete, but it is used to deallocate memory for arrays. This is necessary because arrays are allocated differently than single objects, and delete[] ensures that the memory for the entire array is deallocated properly.

How to concatenate strings in C++?#

In C++, there are several ways to concatenate strings. One way is to use the + operator. For example:

string s1 = "Hello";
string s2 = "World";
string s3 = s1 + " " + s2;  // s3 is now "Hello World"

Another way to concatenate strings in C++ is to use the append() method of the string class. This method adds the characters of one string to the end of another string. For example:

string s1 = "Hello";
string s2 = "World";
s1.append(" ");  // s1 is now "Hello "
s1.append(s2);  // s1 is now "Hello World"

Both of these methods can be used to concatenate multiple strings together, and they can also be used to concatenate string literals and variables of type char* or char[].

What does a vector brings, compared to an array?#

Vectors and arrays are both data structures used for storing collections of data in C++. Both vectors and arrays allow for efficient access to individual elements by their index, and both can be used to store data of any type. However, there are some key differences between vectors and arrays.

One main difference between vectors and arrays is that vectors are dynamic, while arrays have a fixed size. This means that the size of a vector can be changed at runtime, while the size of an array must be fixed when it is declared. This means that vectors are more flexible than arrays, but it also means that they may be less efficient in some cases.

Another difference between vectors and arrays is that vectors provide additional features and functions that are not available with arrays. For example, vectors provide methods for accessing and modifying elements, such as push_back(), pop_back(), and insert(). These methods make it easier to manipulate the elements of a vector, and they are not available with arrays.

Overall, vectors provide a more powerful and flexible alternative to arrays, but they may not be as efficient in some cases. The choice between using a vector or an array often depends on the specific needs of the application.

How to use file input/output in C++?#

To use file input/output in C++, you need to include the fstream header. This header defines the ifstream and ofstream classes, which are used for reading from and writing to files, respectively.

To read from a file, you can use an ifstream object. To open a file for reading, you can use the ifstream constructor, which takes the name of the file as its argument. Once the file is open, you can read data from it using the >> operator, or using methods such as getline() and read().

To write to a file, you can use an ofstream object. To open a file for writing, you can use the ofstream constructor, which takes the name of the file as its argument. Once the file is open, you can write data to it using the << operator, or using methods such as write() and put(). For example:

#include <iostream>
#include <fstream>
#include <string>
using namespace std;
void FileIO() {
    string s;
    ifstream a("1.txt");
    // Output to the end of the file
    ofstream b("2.txt", ios::app);
    if (a.is_open() && b.is_open()) {
        while(getline(a, s)) {
            b << s << endl; cout << s;
        }
        b.close();
        a.close();
    } else cerr << "cannot open file" << endl;
}
int main() { FileIO(); return 0; }

In both cases, it is important to check if the file exists (by using is_open) and close the file when you are finished reading from or writing to it, to ensure that any remaining data is flushed to the file and to release any resources that were allocated for the file.

What is an inline function?#

An inline function is a function that is expanded in-line when it is called, rather than being called like a normal function. This means that the code for the inline function is inserted at the point where the function is called, rather than being executed as a separate unit of code.

Inline functions can be useful for reducing function call overhead, which can improve performance in some cases. However, they can also increase the size of the resulting binary, so they should be used with caution.

In C++, inline functions are declared using the inline keyword. For example:

inline int max(int a, int b) {
  return (a > b) ? a : b;
}

This function returns the maximum of two integers. When this function is called, the compiler will insert the code for the function at the point of the call, rather than generating a function call.

Inline functions are not always expanded in-line, even if they are declared with the inline keyword. The compiler may choose not to expand an inline function if it determines that doing so would not improve performance. Therefore, the use of the inline keyword is only a suggestion to the compiler, and it is not guaranteed that a function declared with inline will be expanded in-line.

What are the keywords const and static doing?#

In C++, the const keyword is used to declare a variable or a function that cannot be modified. This means that a const variable must be initialized when it is declared, and its value cannot be changed afterwards. Similarly, a const function cannot modify any of the objects in the program, and it must also be called with the same arguments and in the same context every time it is called.

For example, the following code declares a const variable and a const function:

const int MAX_SIZE = 100;  // const variable

int sum(const vector<int>& numbers) {  // const function
  int result = 0;
  for (int i = 0; i < numbers.size(); i++) {
    result += numbers[i];
  }
  return result;
}

In this code, MAX_SIZE is a const variable that cannot be modified, and sum() is a const function that cannot modify any passed arguments. (There are also const functions in class, which allows you to make the member function constant, and the member variable cannot be modified in the function.)

The static keyword has a different meaning in C++, depending on where it is used. When used inside a function, it declares a static local variable, which means that the variable is only initialized once, the first time the function is called, and it retains its value between function calls. For example:

void print_hello() {
  static int count = 0;  // static local variable
  count++;
  cout << "Hello! (count = " << count << ")" << endl;
}

In this code, the count variable is declared as static, which means that it is only initialized the first time print_hello() is called, and it retains its value between function calls.

When used outside a function (in a class), the static keyword is used to declare a static class member, which means that the member is associated with the class itself, rather than with individual objects of the class. For example:

class Counter {
public:
    static int count;  // static class member
};

int Counter::count = 0;  // static class member definition

In this code, the count member of the Counter class is declared as static, which means that it is associated with the class itself, rather than with individual objects of the class. This means that there is only one count variable for the entire class, rather than one count variable for each object of the class.

Warning

Don’t use global variables and static anywhere else than defining the singleton.

This code is only for demonstration.

What is an Object Oriented Programming language?#

An object-oriented programming (OOP) language is a type of programming language that is based on the concept of “objects”, which represent data and the functions that operate on that data. In OOP languages, the focus is on creating reusable objects that can be used in different parts of a program, rather than on the overall structure of the program.

Object-oriented languages are designed to support the following key principles of OOP:

  • Encapsulation: This refers to the idea of wrapping data and the functions that operate on that data into a single unit, called an object. Encapsulation allows the data and functions within an object to be hidden from the rest of the program, which helps to reduce the complexity of the program and to prevent unintended changes to the data.

  • Abstraction: This refers to the idea of hiding the details of how an object works, and only exposing the object’s interface, which is the set of functions that can be called on the object. Abstraction allows objects to be used without the need to understand the details of how they work, which makes the program easier to understand and maintain.

  • Inheritance: This refers to the ability of one object, called a subclass, to inherit the properties and functions of another object, called a superclass. Inheritance allows objects to be organized into a hierarchy, where objects at the lower levels of the hierarchy inherit the properties and functions of objects at higher levels. This allows for code reuse and makes it easier to extend and modify the behavior of existing objects.

  • Polymorphism: This refers to the ability of objects to have different forms, depending on the context in which they are used. In OOP languages, this is typically achieved through the use of virtual functions, which allow different objects to provide their own implementation of a function that is defined in a common base class. This allows for greater flexibility and code reuse.

Overall, OOP languages support the development of modular, reusable, and extensible code, which can make programs easier to understand, maintain, and modify. Examples of OOP languages include C++, Java, Python, and C#.

When specifying a class, what should be defined first, the attributes or the methods?#

Methods.

Methods build the connection between the class and its user.

They decide what a class can do, while data members not.

Once methods are fixed, they should not be changed easily, while the internal data representation can be changed freely.

What is the difference between public, private and protected#

In C++, the public, private, and protected access specifiers are used to specify the visibility of class members. Public members are visible to all classes, private members are only visible to the class they are declared in, and protected members are visible to the class they are declared in and its derived classes.

For the type of inheritance, see the figure below:

../_images/inheritance.png

Inheritance types#

What are the constructor and destructor in C++?#

In C++, the constructor is a special member function of a class that is automatically called whenever an object of that class is created. It is typically used to initialize member variables of the class and allocate any resources that the object may need. The destructor is another special member function that is automatically called whenever an object of a class is destroyed. It is used to free up any resources that the object may have been using, such as memory that was dynamically allocated by the object.

Constructors and destructors are essential components of object-oriented programming in C++, and they play a crucial role in managing the lifetime of objects and ensuring that they are properly created and destroyed. For example, a constructor can be used to initialize the member variables of an object, while a destructor can be used to release any resources that the object may have been using, such as memory or file handles. Together, these functions help to ensure that objects are properly created and destroyed, and that they do not cause memory leaks or other issues in the program.

What are inheritance and polymorphism?#

Inheritance and polymorphism are two fundamental concepts in object-oriented programming.

Inheritance is a mechanism that allows a class to inherit the properties and behavior of another class. This allows for code reuse and helps to reduce duplication of code. For example, if you have a base class called “Animal” that defines common properties and behavior for animals, such as the ability to eat and move, you can create derived classes for specific types of animals, such as “Dog” or “Cat”, that inherit the properties and behavior of the “Animal” class. This means that the “Dog” and “Cat” classes automatically have the ability to eat and move, without you having to explicitly define these capabilities in each class.

Polymorphism is the ability of different objects to respond to the same method or message in different ways. This allows for objects of different classes to be used interchangeably, even if they have different properties and behavior. For example, if you have a base class called “Shape” that defines common behavior for shapes, such as the ability to draw themselves on the screen, you can create derived classes for specific types of shapes, such as “Circle” or “Square”, that each have their own unique implementation of the “draw” method. When you call the “draw” method on an object, the object will automatically use its own implementation of the method, rather than the one defined in the base class. This allows for flexibility and makes it easier to add new types of shapes to the program without having to modify existing code.

Here are some examples:

../_images/concepts.png

Inheritance and Polymorphism#

What are the benefits of using inheritance and polymorphism?#

The main benefits of using inheritance and polymorphism in object-oriented programming are code reuse, flexibility, and extensibility.

One of the key benefits of inheritance is that it allows for code reuse. By defining a base class that contains common properties and behavior, and then creating derived classes that inherit from the base class, you can avoid duplicating code and make your program more efficient. This can save time and effort, and make your code easier to maintain and modify.

Polymorphism also helps with code reuse, by allowing you to write generic code that can be used with objects of different classes. For example, if you have a method that accepts an object of the base class “Shape”, you can pass it objects of different derived classes, such as “Circle” or “Square”, and the method will automatically use the appropriate implementation of the method for each object. This means that you can write code that is flexible and can be used with a wide range of objects, without having to write separate code for each individual class.

In addition to code reuse, inheritance and polymorphism also provide flexibility and extensibility. Inheritance allows you to create a hierarchy of classes, with more specialized classes inheriting from more general ones. This allows you to create a flexible and modular structure for your program, and makes it easier to add new features and functionality. Polymorphism allows you to write code that is not tied to a specific class, but can be used with objects of different classes. This makes it easier to extend your program by adding new classes, without having to modify existing code.

Overall, the use of inheritance and polymorphism can make your code more efficient, flexible, and extensible, and can help you to create more robust and maintainable object-oriented programs.

What is the diamond problem?#

The diamond problem is a situation that can arise in object-oriented programming when a class inherits from multiple classes that have a common ancestor. This can cause ambiguity and lead to unexpected behavior in the program.

The diamond problem is named after the diagram that is often used to illustrate the situation. In this diagram, a class “D” inherits from two classes, “B” and “C”, which in turn both inherit from a common ancestor class, “A”. This creates a diamond-shaped inheritance hierarchy, with “A” at the top, and “B” and “C” on the bottom left and right, respectively, and “D” in the middle.

The problem arises when “D” inherits properties or methods from both “B” and “C” that have the same name and signature. In this case, it is not clear which version of the property or method “D” should use, and this can lead to unexpected behavior in the program. For example, if “B” and “C” define different implementations of a method called “doSomething”, and “D” inherits from both “B” and “C”, it is not clear which implementation of “doSomething” “D” should use. This can cause confusion and lead to errors in the program.

To avoid the diamond problem, many programming languages, including C++, provide mechanisms such as virtual inheritance, which allow a class to explicitly specify which version of a property or method it should use when inheriting from multiple classes. This can help to avoid ambiguity and ensure that the correct version of the property or method is used in the program.

Warning

It’s better to avoid diamond problem when your are designing the hierarchy diagram.

You can see Solution 2 for one example to deal with diamond problems.

How to use a template?#

To use a template in C++, you first need to define the template by specifying the template parameters in angle brackets (<>) after the keyword “template”. For example, to define a generic function that can be used with any data type, you would write:

template <typename T>
void myFunction(T arg)
{
    // Function body
}

Once the template is defined, you can use it by calling the function and passing the desired data type as the template argument. For example, to use the above function with the data type “int”, you would write:

myFunction<int>(10);

Alternatively, you can use the template argument deduction feature of C++ to automatically deduce the template argument from the function argument. In this case, you do not need to explicitly specify the template argument when calling the function. For example, you can use the above function with the data type “int” as follows:

myFunction(10); // The template argument will be deduced as "int"

Templates are a powerful feature of C++ that allow you to write generic code that can be used with a wide range of data types. They can help to make your code more flexible and reusable, and can make it easier to write code that is not tied to a specific data type.

How to use an iterator?#

To use an iterator in C++, you first need to obtain an iterator for the container that you want to iterate over. Different containers have different methods for obtaining an iterator, but a common method is to use the “begin” and “end” member functions of the container. For example, to obtain an iterator for a vector, you can write:

std::vector<int> myVector = { 1, 2, 3, 4, 5 };
std::vector<int>::iterator it = myVector.begin();

Once you have an iterator, you can use it to traverse the container and access the elements of the container. To do this, you can use the dereference operator (*) to access the element that the iterator is currently pointing to, and the increment operator (++) to move the iterator to the next element in the container. For example, you can use an iterator to print all the elements of a vector as follows:

while (it != myVector.end())
{
    std::cout << *it << std::endl;
    ++it;
}

Iterators are a powerful feature of C++ that allow you to write code that is independent of the specific type of container that you are using. This makes it easier to write generic algorithms and data structures that can be used with different containers. It also allows you to write code that is more efficient and can take advantage of the specific characteristics of the container that you are using.

How to use the algorithm functions from the STL?#

The Standard Template Library (STL) in C++ provides a number of algorithm functions that can be used to perform common operations on containers, such as searching, sorting, and transforming the elements of the container.

To use the algorithm functions from the STL, you first need to include the appropriate header file. For example, to use the algorithm functions that are defined in the “algorithm” header file, you would write:

#include <algorithm>

Once you have included the necessary header file, you can use the algorithm functions by calling them and passing the appropriate arguments. For example, to use the “sort” algorithm function to sort the elements of a vector, you can write:

std::vector<int> myVector = { 3, 2, 1, 4, 5 };
std::sort(myVector.begin(), myVector.end());

The STL algorithm functions are a useful and powerful tool for working with containers in C++. They can help to make your code more efficient and concise, and can make it easier to perform common operations on containers. By using the algorithm functions from the STL, you can take advantage of the well-tested and optimized implementations of these algorithms, and avoid having to write your own code to perform these operations.

Draw a bus#

  • Make use of the classes you have implemented for p3 / hw8

  • Some classes you need: Circle, Triangle, Rectangle

  • Parallelogram, Trapezium

  • Know how to combine your shape classes.

A bus trip#

Define the classes:

bus.cpp#
class Ticket {
public:
    Ticket(int _n, int _s) : n(_n), s(_s) {
        if (s < 0 || s > 10) throw std::exception();
    }
    int  number() { return n; }
    int  size() { return s; }
    void print() { std::cout << "number:" << n << ",luggage:" << s; }

private:
    int n, s;
};

class Bus {
public:
    Bus(int _seats, int _capacity) : seats(_seats), capacity(_capacity) {}
    void getIn(std::vector<Passenger> &v);
    void travel();
    void getOut();

private:
    static bool           passengerCmp(Passenger const &a, Passenger const &b);
    std::stack<Passenger> stack;
    int                   seats, capacity;
};

class Passenger {
public:
    Passenger(Ticket _t) : t(_t) {}
    Ticket ticket() { return t; }

private:
    Ticket t;
};