c/c++ classic problem (1)

c/c++ classic questions

  • What are virtual functions? How to achieve polymorphism?
  • What are smart pointers? What are their advantages and disadvantages?
  • What are abstract classes and interfaces? What’s the difference between them?
  • How to use operator overloading? Please give an example.
  • What is exception handling? How to use try-catch-finally statement?
  • How to use templates? Please give an example.
  • What is STL? What components does it contain?
  • How to implement inheritance and encapsulation?
  • How to use constructor and destructor?
  • What are friend functions and friend classes? What are they for?

What is a virtual function? How to achieve polymorphism?

Virtual functions are modified by the vartual keyword and can be recreated by subclasses. Virtual functions are one of the ways to achieve polymorphism, and templates can also achieve polymorphism.
Among them, the virtual function implements runtime polymorphism, that is, when the program is running, it decides which function to call according to the actual type of the object. The template implements compile-time polymorphism, that is, at compile time, different functions are generated according to the type of the template parameter. Runtime polymorphism requires additional memory space and time overhead, but is more flexible and versatile. Compile-time polymorphism requires no additional overhead, but is more static and restricted.
Example The following is an example of a virtual function implementing polymorphism:

// base class
class Shape {
public:
    // virtual function
    virtual void draw() {
        cout << "Drawing a shape" << endl;
    }
};

// Derived class
class Circle : public Shape {
public:
    // override virtual function
    void draw() override {
        cout << "Drawing a circle" << endl;
    }
};

// Derived class
class Square : public Shape {
public:
    // override virtual function
    void draw() override {
        cout << "Drawing a square" << endl;
    }
};

int main() {
    // The base class pointer points to the derived class object
    Shape* s1 = new Circle();
    Shape* s2 = new Square();

    // Call the virtual function and execute the corresponding function according to the actual type of the object
    s1->draw(); // Drawing a circle
    s2->draw(); // Drawing a square

    delete s1;
    delete s2;

    return 0;
}

The following is an example of template polymorphism:

// template class
template <typename T>
class Calculator {
public:
   // template member function, perform different operations according to the type of the template parameter
   T add(T x, T y) {
       return x + y;
   }

   T subtract(T x, T y) {
       return x - y;
   }
};

int main() {
   // Instantiate the template class and pass in different type parameters
   Calculator<int> c1;
   Calculator<double> c2;

   // Call the template member function to perform different operations according to the type of the object
   cout << c1.add(10, 20) << endl; // 30 (integer addition)
   cout << c2. add(10.5, 20.5) << endl; // 31 (double addition)

   cout << c1. subtract(30, 10) << endl; // 20 (integer subtraction)
   cout << c2. subtract(30.5, 10.5) << endl; // 20 (double subtraction)

   return 0;
}
Virtual Functions and Runtime Polymorphism in C++. https://www.geeksforgeeks.org/virtual-functions-and-runtime-polymorphism-in-cpp/
(2) 18.2 - Virtual functions and polymorphism – Learn C++ - LearnCpp.com. https://www.learncpp.com/cpp-tutorial/virtual-functions/
(3) Mastering Polymorphism in C++ : Understanding and Implementing Virtual .... https://dev.to/iflis7/mastering-polymorphism-in-c-understanding-and-implementing-virtual-functions-and-template- classes-ee5

Advantages and disadvantages of virtual functions and templates and suggestions for usage scenarios:

The advantage of virtual functions is that runtime polymorphism can be achieved, which makes the code more flexible and versatile, and can handle different types of objects. The disadvantage of virtual functions is that additional memory space and time overhead are required, because each object requires a virtual function table to store virtual function addresses, and dynamic binding is required at runtime. Virtual functions are suitable for dealing with complex or uncertain inheritance relationships. For example, when designing a graphical interface library, virtual functions can be used to implement different types of controls.
The advantage of templates is that they can achieve compile-time polymorphism, making the code more efficient and reusable, and can handle different types of data. The downside of templates is that they take longer to compile because each template parameter generates a copy of the code and can lead to code bloat. Templates can also be difficult to understand and debug. Templates are suitable for dealing with generic programming or metaprogramming, such as designing a container class or algorithm class, and you can use templates to implement operations on any type of data.

What is a smart pointer? What are their advantages and disadvantages?

A smart pointer is a class object that encapsulates a raw pointer. It can automatically manage the memory pointed to by the pointer and avoid memory leaks or dangling pointers. Smart pointers have the following advantages:

It can implement the programming paradigm of RAII (resource acquisition is initialization), that is, resources are acquired when objects are created and resources are released when objects are destroyed.
It avoids manually calling new and delete to allocate and free memory, simplifying code and reducing errors.
It can support polymorphism and inheritance because they overload the * and -> operators to access the members of the pointed object.
Smart pointers also have the following disadvantages:

Smart pointers do not prevent the problem of circular references, i.e. if two smart pointers refer to each other, neither of them will be destroyed, resulting in a memory leak. At this time, you need to use weak references or manually break the cycle.
Smart pointers have different types and semantics, such as std::unique_ptr, std::shared_ptr, std::weak_ptr, etc. You need to choose the appropriate type according to different scenarios, and pay attention to the conversion and assignment rules between them.
Smart pointers can affect program performance and memory usage because they require additional space to store information such as reference counts or destructors, and may increase function call overhead or lock contention.

What are abstract classes and interfaces? What’s the difference between them?

An abstract class is a class that contains pure virtual functions (no implementation body), it cannot be instantiated, and can only be inherited by other classes as a base class. Abstract classes are usually used to define a common interface or behavior, and subclasses provide specific implementations.

An interface is a special abstract class that only contains pure virtual functions (no data members), it cannot be instantiated, and can only be implemented by other classes. Interfaces are usually used to define a contract or specification, and these specifications are followed by implementing classes.
There are following differences between abstract classes and interfaces:

Abstract classes can contain non-pure virtual functions (with implementation bodies) and data members (properties), while interfaces can only contain pure virtual functions (without data members).
Abstract classes can implement some functions and leave some functions to be completed by subclasses (template method pattern), while interfaces can only define functions but cannot provide any implementation.
Abstract classes can inherit from other abstract classes or non-abstract classes, and a subclass can only inherit from one parent class (single inheritance), while interfaces can inherit from other interfaces, and a subclass can implement multiple interfaces ( multiple inheritance).

How to use operator overloading? Please give an example.

Operator overloading is a feature that allows custom classes or structures to use built-in operators, which can improve code readability and flexibility. Operator overloading is implemented by defining a member function or friend function, with the operator as the function name, and accepting appropriate parameters and return values.

For example, if we wanted to define a complex number class and be able to add complex numbers using the + operator, we could write:

class Complex {
public:
    // Constructor
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
    // + operator overloading (member function)
    Complex operator + (const Complex & c) const {
        return Complex(real + c. real, imag + c. imag);
    }
private:
    double real; // real part
    double imag; // imaginary part
};

// test code
int main() {
    Complex c1(1.0, 2.0); // create a complex object c1
    Complex c2(3.0, 4.0); // create a complex object c2
    Complex c3 = c1 + c2; // Use the + operator to add complex numbers
    return 0;
}

What is exception handling? How to use try-catch-finally statement?

Exception handling is a mechanism used to deal with errors or abnormal conditions that may occur in the program, which can avoid program crashes or unexpected results. Exception handling is usually implemented using the try-catch-finally statement, and its basic syntax is as follows:

try {
    // Attempt to execute the block of code that may fail
}
catch (exception_type e) {
    // Catch and handle a specific type of exception e
}
catch (...) {
    // catch and handle all other types of exceptions
}
finally {
    // A code block that will be executed regardless of whether an exception occurs (optional)
}

For example, if we wanted to implement a division function that could handle division by zero or other illegal inputs, we could write:

double divide(double a, double b) {
    try {
        if (b == 0) { // if the divisor is zero, throw an exception object
            throw std::runtime_error("Divide by zero");
        }
        return a / b; // otherwise return the quotient normally
    }
    catch (std::runtime_error e) { // capture and handle exception e of type std::runtime_error
        std::cout << "Error: " << e.what() << std::endl; // print error message
        return 0; // return a default value (can also re-throw an exception or terminate the program)
    }
}

// test code
int main() {
   double x = divide(10, 2); // normal call, x = 5
   double y = divide(10, 0); // exception call, y = 0, and print "Error: Divide by zero"
   return 0;
}

How to use templates? Please give an example.

A template is a feature used to define a generic class or function, which allows us to use different types of parameters to instantiate a class or function, thereby improving code reusability and flexibility. Templates are usually declared using the template keyword and specify one or more type parameters within angle brackets.

For example, if we wanted to define a swap function that would swap the values of any two variables of the same type, we could write:

template <typename T> // declare a type parameter T
void swap(T & amp; a, T & amp; b) { // define a template function swap
    T temp = a; // use variable temp of type T
    a = b;
    b = temp;
}

// test code
int main() {
    int x = 10, y = 20; // define two variables x and y of type int
    swap(x, y); // call the swap function, at this time T is inferred as int
    cout << x << " " << y << endl; // output 20 10

    string s1 = "Hello", s2 = "World"; // define two variables s1 and s2 of type string
    swap(s1, s2); // call the swap function, at this time T is deduced as string
    cout << s1 << " " << s2 << endl; // Output World Hello

    return 0;
}

What is STL? What components does it contain?

STL is C++’s Standard Template Library (Standard Template Library), which is a set of template classes and template functions that provide common data structures and algorithms, which can help us develop programs quickly and efficiently. STL mainly consists of the following four components:

Container: A class used to store data elements, such as vector, list, map, set, etc.
Iterator (iterator): The class used to traverse the elements in the container, such as begin(), end(), rbegin(), rend(), etc.
Algorithm: A function used to operate or process elements in a container, such as sort(), find(), count(), reverse(), etc.
Adapter: A class used to modify or extend the functionality of a container or iterator, such as stack, queue, priority_queue, etc.
For example, if we want to use the STL to implement a simple program that reads some integers from standard input, outputs them in ascending order, and counts how many times each number occurs, we can write:

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

int main() {
    vector<int> v; // create an empty vector container
    int x;
    while (cin >> x) { // read an integer from standard input
        v.push_back(x); // insert the integer at the end of the vector
    }
    sort(v.begin(), v.end()); // use the sort algorithm to sort the vector
    map<int, int> m; // create an empty map container
    for (auto it = v.begin(); it != v.end(); + + it) { // Use an iterator to traverse the elements in the vector
        cout << *it << " "; // output element value
        m[*it] + + ; // Use the element value as the key of the map, and the number of occurrences as the value of the map
    }
    cout << endl;
    for (auto it = m.begin(); it != m.end(); + + it) { // Use an iterator to traverse the key-value pairs in the map
        cout << it->first << ": " << it->second << endl; // output key and value
    }
    return 0;
}

How to achieve inheritance and encapsulation?

Inheritance is a feature that expresses the relationship between classes. It allows a class (subclass) to inherit the member variables and member functions of another class (parent class), and can be rewritten or extended as needed. Inheritance is usually declared using: , and the parent class name is added after the subclass name to indicate that this is an inheritance relationship.

Encapsulation is a feature that hides data and implementation details, provides a public interface, and protects data security. It allows us to divide class member variables and member functions into different access levels, such as public (public), private (private) and protected (protected). Encapsulation is usually defined using the class keyword, and the corresponding access modifiers are added in front of member variables or member functions to specify their access levels.

For example, if we want to define an animal (animal) class, and let the dog (dog) and cat (cat) inherit from the animal class respectively, and rewrite the make_sound() function defined in the animal class, and set the animal class The name variable is set to be protected, we can write like this:

class Animal {
public:
    Animal(string n) : name(n) {} // constructor
    virtual void make_sound() const { // virtual function
        cout << name << " makes a sound. " << endl;
    }
protected:
    string name; // name
};

class Dog : public Animal { // Dog inherits from Animal
public:
    Dog(string n) : Animal(n) {} // constructor
    void make_sound() const override { // override virtual function
        cout << name << " barks." << endl;
    }
};

class Cat : public Animal { // cat inherits from Animal
public:
   Cat(string n) : Animal(n) {} // constructor
   void make_sound() const override { // override virtual function
       cout << name << "meows."<< endl;
   }
};

How to use constructor and destructor?

A constructor is a special member function used to initialize the state of an object. It usually has the same name as the class and has no return value. Constructors can have different numbers or types of parameters, resulting in different construction methods. Constructors can also use initializer lists to initialize member variables.

A destructor is a special member function used to clean up object resources or perform other finishing work. It is usually prefixed with ~ and has no parameters and return values. Destructors are called automatically when an object is destroyed.

For example, if we wanted to define a student class with the ability to create student objects in different ways, and print a message when the student object is destroyed, we could write:

class Student {
public:
   Student() : name("Unknown"), age(0), score(0) {} // default constructor
   Student(string n, int a, double s) : name(n), age(a), score(s) {} // constructor with parameters
   ~Student() { // destructor
       cout << "Student " << name << " is destroyed." << endl;
   }
private:
   string name; // name
   int age; // age
   double score; // score
};

// test code
int main() {
   Student s1; // create student object s1 using default constructor
   Student s2("Alice", 18, 90); // Create student object s2 using constructor with parameters
   return 0;
}

What are friend functions and friend classes? What are they for?

A friend function is a non-member function that can access private or protected members of a class. It can improve the efficiency and simplicity of the code, but it can also destroy the encapsulation of the class. Friend functions are usually declared using the friend keyword, and specify which functions in the class are its friends.

A friend class is a class that can access private or protected members of another class. It can implement some special functions or relationships, but it also breaks the encapsulation of the class. Friend classes are usually declared using the friend keyword, and specify which classes in a class are its friends.

For example, if we wanted to define a point class and give the distance function and line class access to the x and y variables in the point class, we could write:

class Point {
public:
    Point(int x, int y) : x(x), y(y) {} // constructor
    friend double distance(const Point & amp; p1, const Point & amp; p2); // declare distance function as friend
    friend class Line; // Declare the line segment class as a friend
private:
    int x; // x coordinate
    int y; // y coordinate
};

// define the distance function
double distance(const Point & amp; p1, const Point & amp; p2) {
    return sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2)); // You can directly access the x and y variables of the point object
}

// define segment class
class Line {
public:
    Line(const Point & amp; p1, const Point & amp; p2) : start(p1), end(p2) {} // constructor
    double length() const { // Calculate the length of the line segment
        return distance(start, end); // call the distance function
    }
private:
    Point start; // starting point
    Point end; // end point
};