Autumn recruitment algorithm post C++ interview experience

Table of contents

1. The difference between pointers and references

2.const keyword

3. The difference between overloading and rewriting (overwriting)

4.The difference between new and malloc (new encapsulates malloc)

5.The difference between static and const 

6. Three major features of c++

7.Virtual function

8.Pure virtual function

 9.Virtual inheritance

10. Smart pointers

11. Memory leak

12.C++ memory distribution

13.STL introduction

14.What does enable shared from this mean?

15.The use of this pointer

16. lvalue, rvalue and related operations

1. The difference between pointers and references

a. A pointer requires an additional address to store itself, while a reference does not require additional memory. It is just an alias for a variable.
b. The address pointed to by the pointer can be changed, but the reference cannot be changed after it is initialized.
c. The pointer can be null, but the reference must be initialized when declared.

2.const keyword

The value modified by it cannot be changed and must be assigned an initial value when it is defined.

Constant pointers and pointer constants

The data type const* pointer variable is in the form of a constant pointer, and the value of the address it points to cannot be changed.

The data type *const pointer variable is in the form of a pointer constant, and the address it points to cannot be changed.

3. The difference between overloading and rewriting (overwriting)

Overriding is generally used when a subclass inherits a parent class and overrides the virtual function method of the parent class. The override keyword is to ensure that the virtual function parameters of the parent and child classes are consistent, otherwise an error will pop up.

Overloading exists in a class with the same method name but different parameter forms.

The parameter list of the overridden method must be consistent with the overridden return value.

Static methods cannot be overridden to be non-static. Because static methods belong to classes, while non-static methods belong to objects, and overriding is based on object polymorphism, so it doesn't work.

The overridden method cannot be a private method. Private methods cannot be inherited, which would destroy their privacy.

4.The difference between new and malloc (new encapsulates malloc)

When new fails to allocate memory, an exception will be thrown, and malloc will return NULL.

When new applies for memory allocation, there is no need to specify the size, while malloc needs to be pointed out explicitly.

new/delete supports overloading, but malloc/free does not.

new/delete will call the object's constructor and destructor, but malloc will not

new allocates memory from the free storage area, malloc allocates from the heap

5.The difference between static and const 

Variables modified by const will be released after exceeding their scope. They must be initialized when they are defined and cannot be changed later. And static will not be released immediately after the function is executed. The static modified member function is called a static member function, which does not depend on any instance and can be called directly through the class name. Because it does not have this pointer, it cannot access non-static member variables or functions. At the same time, it cannot be declared as virtual, because the calling of virtual functions is implemented through the object's virtual function table (pointers to each virtual function are stored in it), and static member functions are associated with the class, not with the object of the class. linked.

  • Static is used to declare static variables and static functions. They have only one copy in memory and have nothing to do with any object instance.

6. Three major features of c++

From outside the class, members of public properties can only be accessed through the object.

Inheritance: You can use all the functions of an existing class and extend these functions without rewriting the original class.

Encapsulation: Abstract objective things into classes and share and hide information.

Polymorphism: allows a pointer of a subclass type to be assigned to a pointer of a parent class type. Overloading implements compilation polymorphism, and virtual functions implement runtime polymorphism. There are two ways to achieve polymorphism, overwriting: the subclass redefines the virtual function of the parent class. Overloading: Multiple functions with the same name but different parameter lists.

class Base {
public:
    virtual void foo() {
        cout << "Base::foo()" << endl;
    }
};

class Derived : public Base {
public:
    void foo() override {   // 重写了基类的虚函数foo()
        cout << "Derived::foo()" << endl;
    }
};
Base* ptr = new Derived();
ptr->foo();  // 会调用Derived::foo()

7.Virtual function

When the base class wants derived class definitions to suit its own version, it declares these functions as virtual functions. Virtual functions are dynamically bound. The pointer of the base class points to whose class the virtual function table of that class is queried. This mechanism ensures that the virtual function of the derived class is called.

Virtual functions implement polymorphism: the object calling the function must be a pointer or reference, and the called function must be a virtual function, and the rewriting of the virtual function must be completed.

The constructor cannot be a virtual function, because the constructor is called when the object is created. The virtual function needs to determine which function version to call based on the dynamic type of the object. How to know its type before the object is created.

The destructor can be a virtual function. When this class implements polymorphism as a base class, the base class pointer points to the object of the derived class. At this time, in order to destruct the derived class object, the destructor of the base class must be a virtual function.

Inline functions cannot be virtual functions, because inline does not know the type of the object at compile time, and virtual functions need to know the type of the object to know which virtual function to call, so inline function expansion cannot be performed at compile time. Let’s talk about the inline function again. It is usually written in front of the function definition and declaration to solve the problem of frequently called small functions that consume a lot of stack memory. Generally used for small frequently called function bodies. The inline function is just a suggestion to the compiler. If it feels that the function is too large, it may not be used as an inline function.

inline int add(int x, int y); //声明时
inline int add(int x, int y) {
    return x + y;
}//定义时

Static functions cannot become virtual functions because static members do not have this pointers, and virtual functions must be called through objects and have hidden this pointers.

8.Pure virtual function

A pure virtual function is declared in the base class and has no specific implementation of the virtual function. It only provides an interface and needs to be implemented by the derived class itself. Classes that use pure virtual functions are abstract classes and cannot be instantiated. Therefore, the derived class must override the pure virtual function, otherwise it will also be an abstract class and cannot be instantiated.

class AbstractBase {
public:
    virtual void pureVirtualFunc() = 0;  // 纯虚函数声明
};

class Derived : public AbstractBase {
public:
    void pureVirtualFunc() override {
        // 派生类提供的纯虚函数实现
    }
};

AbstractBase* ptr = new Derived();
ptr->pureVirtualFunc();  // 调用派生类对象的纯虚函数实现
delete ptr;  // 删除派生类对象

 9.Virtual inheritance

Virtual inheritance is to solve the diamond inheritance problem during multiple inheritance and prevent ambiguity.

//间接基类A
class A{
protected:
    int m_a;
};

//直接基类B
class B: virtual public A{  //虚继承
protected:
    int m_b;
};

//直接基类C
class C: virtual public A{  //虚继承
protected:
    int m_c;
};

//派生类D
class D: public B, public C{
public:
    void seta(int a){ m_a = a; }  //正确
    void setb(int b){ m_b = b; }  //正确
    void setc(int c){ m_c = c; }  //正确
    void setd(int d){ m_d = d; }  //正确
private:
    int m_d;
};

int main(){
    D d;
    return 0;
}

10. Smart pointers

Smart pointers are proposed to solve the problem of memory leaks caused by dynamic allocation of memory and multiple releases of the same memory. Place it in the <memory> header file. Including shared pointers, exclusive pointers, weak pointers

shared_ptr: is a reference-counted smart pointer that uses a counter to track how many smart pointers share the same resource. When the count reaches 0, the resource is released. That is, when there is no smart pointer pointing to this resource, this resource will be released.

Its multi-threading is safe in the case of read-only. If multiple threads modify the reference count at the same time, additional synchronization is required to ensure thread safety. Otherwise, data competition and undefined may occur when concurrently modifying the reference count. Behavior.

#include <memory>

std::shared_ptr<int> ptr1 = std::make_shared<int>(42); //第三种构造方式
std::shared_ptr<int> ptr2 = ptr1; // 共享所有权

'''
这两种不常用
int* a = new int(4);
std::shared_ptr<int> ptr3(a); //第一种构造方式
std::shared_ptr<int> ptr1(new int); //第二种构造方式
'''

 Only when both ptr1 and ptr2 are destroyed, the int type resource is released.

unique_ptr: It is a smart pointer with exclusive ownership and cannot share management rights with others. That is, it does not support ordinary copying and assignment. When it is destroyed, the resources it manages will also be destroyed.

#include <memory>

std::unique_ptr<int> ptr = std::make_unique<int>(42);

weak_ptr: Introduced to cooperate with shared_ptr, it can observe resource usage like a bystander, but it does not share resources, and its construction will not cause the pointer reference count to increase.

11. Memory leak

Heap memory leak: refers to the use of malloc and new in the program to allocate a piece of memory from the heap, and forget to delete it with free or delete after use.

System resource leakage: refers to the program using resources allocated by the system but not calling the corresponding function to release them, resulting in a waste of system resources.

The destructor of the base class is not defined as a virtual function: when the pointer of the base class points to an object of the subclass, if the destructor of the base class is not a virtual function, the destructor of the subclass will not be called, causing Memory leak.

How to prevent memory leaks? Encapsulate memory allocation in a class, the constructor allocates memory, and the destructor releases memory. Use smart pointers.

12.C++ memory distribution

The stack area stores local variables and function parameter values. From high address to low address.

Dynamically allocated memory stored in the heap area.

Global variables and static variables stored in the global area. Memory is already allocated when the program is compiled.

13.STL introduction

Container: various data structures, such as pair, vector, list, deque, set, map, etc.

Adapter: queue and stack, their bottom layers are implemented by deque.

pair: The elements inside call a->first, a->second. When inserting pair, insert({first, second}) or insert(pair<type, type>(data 1, data 2));

vector: A contiguous memory space is allocated in the heap. Its expansion is doubled every time. If the capacity is not enough, it will be doubled. capacity is always greater than size. The underlying implementation is an array.

操作:push_back,emplace_back,pop_back, clear,

  • insert(position, value): Insert an element at the specified position.
  • erase(position): Remove the element at the specified position.
  • erase(first, last): Remove elements within the specified range

 list: The memory space is discontinuous, and data is accessed through pointers. Usually used for random insertion and deletion operations. It is a doubly linked list.

deque: double-ended queue, due to the need for internal jumps, random access is not as fast as vector. Since the iterator of deque is more complex than that of vector, when sorting deque, you can first copy the deque into the vector, sort it, and then put it back into the deque. Operation: push_back, pop_back, push_front, pop_front

stack and queue are called adapters, and the bottom layer is deque. Operations: push, pop, empty, size. In addition, the queue also has front, back, stack and top.

Map and set: The bottom layer is implemented using red-black trees, and the search complexity is log(n).

unordered_map and unordered_set: The underlying implementation is a hash table, with a lookup complexity of 1

14.What does enable shared from this mean?

In some cases, we need to manage the object itself as a smart pointer. Usually there are other objects or functions inside the object, and they need to reference the current object. If a raw pointer is used to pass or copy the object, there will be difficulties in resource management.

"enable_shared_from_this" is a base class mainly used to solve shared resource management problems when the object itself is used as a shared_ptr resource. This base class defines the member function shared_from_this(), through which a shared_ptr pointing to the current object can be returned to ensure the correct management and sharing of resources.

#include <iostream>
#include <memory>

class MyClass : public std::enable_shared_from_this<MyClass> {
public:
    std::shared_ptr<MyClass> getShared() {
        return shared_from_this();
    }
};

int main() {
    std::shared_ptr<MyClass> obj1 = std::make_shared<MyClass>();

    std::shared_ptr<MyClass> obj2 = obj1->getShared();

    std::cout << "obj1 use count: " << obj1.use_count() << std::endl;
    std::cout << "obj2 use count: " << obj2.use_count() << std::endl;

    return 0;
}

In the above example, Myclass inherits the base class enable_shared_from_this and defines a member function getshared(), which returns a smart pointer pointing to the current object by calling shared_from_this().

15.The use of this pointer

When we define a variable in a class, and at the same time define the same variable in a member function of the class, and want to use the variable in the class, we need this pointer.

When a non-static member function of a class returns the class object itself, use return *this directly.

The scope of this pointer is inside the class and can only be used in member functions, not static member functions and global ones.

16. lvalue, rvalue and related operations

An lvalue means that it still exists after the expression ends, has a specific memory address, etc.

An rvalue is a temporary object that no longer exists after the expression ends

int a = 5; // 5 是一个右值
int b = a + 3; // a + 3 是一个右值表达式

C++11 introduces the concept of rvalue references, so that rvalues ​​can also be referenced and can be bound to rvalues, allowing move semantics and move construction to be applied to them. Rvalue references allow us to effectively implement efficient operations such as transfer semantics, perfect forwarding, and move semantics.

int&& rref = 42; // 右值引用绑定到右值

std::move() exists in the <utility> library. Its function is to convert an lvalue into an rvalue reference and force the lvalue into an rvalue, thus triggering move semantics and perfect forwarding.

#include <utility>

void func(int&& value) {
    // 对右值引用的参数进行操作
}

int main() {
    int x = 42; // x 是一个左值

    int&& rref = std::move(x); // 使用 std::move 将 x 转换为右值引用
    func(std::move(x)); // 将 x 转换为右值引用并传递给函数

    return 0;
}

std::forward passes a parameter and saves its original value type, such as lvalue and rvalue. Generally only used in template functions to complete perfect forwarding . It doesn't make sense to use std::forward in a normal function. Template functions can generate multiple specific function instances based on different types of parameters. The definition of a template function uses the keyword templateand a template parameter list to specify the template parameters, followed by a function.

#include <utility>

// 使用 std::forward 完美转发参数
template<typename T>  //模板函数
void wrapper(T&& arg) {    //和万能引用放在一起,这样T既可以接收左值,也可以接收右值。
    other_function(std::forward<T>(arg)); // 将参数 arg 以其原始的值类别(左值引用或右值引用)传递给其他函数
}

void other_function(int& x) {
    // 处理左值引用参数
}

void other_function(int&& x) {
    // 处理右值引用参数
}

int main() {
    int x = 42;
    wrapper(x); // 调用传递左值引用的版本
    wrapper(123); // 调用传递右值引用的版本

    return 0;
}

Move constructor: An rvalue reference is passed in, which can avoid unnecessary memory copies. And move the resource pointer or state pointer of the source object to the target object, and put the source object in a valid but undefined state so that the release of resources can be correctly processed during destruction. Move constructors should generally be marked noexceptto ensure that no exceptions are thrown during object movement. This allows the compiler to optimize the code and improve performance.

Move semantics refers to transferring resources from one object to another object without performing a deep copy operation when the object is passed or assigned. Move operations are more efficient than copy operations, especially when working with large objects or objects that hold a large number of resources. Move semantics are implemented through move constructors and move assignment operators.

Transfer semantics refers to marking an object as a transferable rvalue reference (Rvalue Reference) through a transfer operation (such as std::move) for use in move operations.

17.The difference between resize and reserve in vector

resize changes the size of the vector and adds and deletes elements in the vector. Reserve does not change the original value of vector, but is used to pre-allocate its space size to avoid frequent memory allocation.

18.The difference between vector and list

The elements in the vector are stored sequentially, so the memory needs to be expanded if there is not enough memory. The underlying implementation of list is a doubly linked list, and its memory is discretely distributed.
Since it is a doubly linked list structure, the time complexity of inserting and deleting the list at any position is o(1), but it cannot access elements through subscripts and can only traverse to find elements. Vector can access elements O(1) through subscripts, but insertion and deletion are O(n).

Guess you like

Origin blog.csdn.net/slamer111/article/details/131522109