Interview Recitation Edition—C++

C++ surprise interview

1. Compile memory related

1.1. C++ program compilation process

The compilation process is divided into four processes: compilation (compilation preprocessing, compilation, optimization), assembly, and linking.

  • Compile preprocessing : process instructions beginning with #;

  • Compile and optimize : translate the source code .cpp file into .s assembly code;

  • Assembly : translate assembly code .s into machine instruction .o file;

  • Link : The object file generated by the assembler, that is, the .o file, will not be executed immediately, because it may appear that a function in the .cpp file refers to a symbol defined in another .cpp file or calls a library file. The function. The purpose of the link is to connect the target files corresponding to these files into a whole, so as to generate an executable program .exe file.
    insert image description here
    There are two types of links:

  • Static link : The code is copied from the static link library where it is located to the final executable program. When the program is executed, the code will be loaded into the virtual address space of the process.

  • Dynamic link : The code is placed in a dynamic link library or an object file of a shared object, and the linker only records some information such as the name of the shared object in the final executable program. When the program is executed, the entire content of the dynamic link library will be mapped to the corresponding virtual address space at runtime.

    Advantages and disadvantages of both:

  • Static link : a waste of space, each executable program will have a copy of the target file, so if the target file is updated, it needs to be recompiled and linked to generate an executable program (updating is difficult); the advantage is the running speed when executing Fast, because the executable has everything the program needs to run.

  • Dynamic link : saves memory and is convenient to update. However, dynamic link is required for each execution when the program is running. Compared with static link, there will be a certain performance loss.

1.2. C++ memory management

C++ memory partitions: stack, heap, global/static storage area, constant storage area, code area .

  • Stack : store local variables, function parameters, return addresses, etc. of the function, which are automatically allocated and released by the compiler. The stack grows from higher addresses to lower addresses. is a continuous space. The stack generally allocates several M of memory.
  • Heap : The dynamically applied memory space is the memory block allocated by malloc, and the programmer controls its allocation and release. If the program execution is not released, the operating system will automatically reclaim it. The heap grows from lower to higher addresses. Generally, several gigabytes of memory can be allocated.
  • Global area/static storage area (.bss section and .data section) : store global variables and static variables, and the operating system will automatically release the program after running. In C language, the uninitialized ones are placed in the .bss section, and the initialized ones are placed in In the .data section, no distinction is made in C++.
  • Constant storage area (.data section ): Stores constants, which cannot be modified, and will be released automatically after the program runs.
  • Code area (.text segment) : store code, modification is not allowed, but it can be executed. The compiled binaries are stored here.

1.3. Difference between stack and heap

  • Application method : the stack is automatically allocated by the system, and the heap is actively applied by the programmer.
  • System response after application : allocate stack space, if the remaining space is larger than the requested space, the allocation is successful, otherwise the allocation fails and the stack overflows; when applying for heap space, the way the heap is presented in memory is similar to a linked list (a linked list that records free address space), in the linked list Search for the first node larger than the application space and assign it to the program, and delete the node from the linked list. In most systems, the first address of the block space stores the size of the allocated space for easy release. The remaining space is connected to the free list again.
  • The stack is a continuous piece of space in memory (extends to low addresses), the maximum capacity is predetermined by the system, and the space in memory (extends to high addresses) of the heap is discontinuous.
  • Application efficiency : the stack is automatically allocated by the system, and the application efficiency is high, but the programmer cannot control it; the heap is actively applied by the programmer, which is inefficient and convenient to use but prone to fragmentation.
  • Stored content : local variables and function parameters are stored in the stack; the content stored in the heap is controlled by the programmer.

1.4. The difference between variables

The difference between global variables, local variables, static global variables, and static local variables:

C++ variables have different scopes according to the different life cycles of the defined positions. The scopes can be divided into 6 types: global scope, local scope, statement scope, class scope, namespace scope and file scope .

From the perspective of scope:

  • Global variables : have global scope. Global variables only need to be defined in one source file, and they can be applied to all source files. Of course, other source files that do not contain global variable definitions need to declare this global variable again with the extern keyword.
  • Static global variables : have file scope. The difference between it and global variables is that if the program contains multiple files, it acts on the file where it is defined, and cannot affect other files, that is, variables modified by the static keyword have file scope. In this way, even if two different source files define static global variables with the same name, they are different variables.
  • Local variables : have local scope. It is an automatic object (auto), which does not exist all the time during the running of the program, but only exists during the execution of the function. After a call of the function is executed, the variable is revoked and the memory it occupies is also recovered.
  • Static local variables : have local scope. It is only initialized once, and it exists from the first time it is initialized until the end of the program. The difference between it and the global variable is that the global variable is visible to all functions, while the static local variable is always only for the function body that defines itself visible.

From the allocation of memory space:

  • Static storage area : global variables, static local variables, static global variables.
  • Stack : local variables.

1.5. What's wrong with defining global variables in header files?

If a global variable is defined in a header file, when the header file is included by multiple files, the global variable in the header file will be defined multiple times, resulting in repeated definitions, so global variables cannot be defined in the header file.

1.6. Memory Alignment

What is memory alignment? The principle of memory alignment? Why memory alignment and what are the advantages?

Memory alignment : The compiler arranges each "data unit" in the program in the memory pointed to by an address that is an integer multiple of a word.
Principles of memory alignment:

  1. The first address of a structure variable can be divisible by the smaller of its widest basic type member size and alignment base;
  2. The offset (offset) of each member of the structure relative to the first address of the structure
    is an integer multiple of the smaller of the size of the member and the alignment base, and the compiler will add padding bytes between the members if necessary (internal padding);
  3. The total size of the structure is an integer multiple of the smaller of the size of the widest basic type member of the structure and the alignment base. If necessary, the compiler will add trailing padding after the last member.

Reasons for memory alignment : (mainly hardware issues)

  1. Some hardware devices can only access aligned data, and accessing unaligned data may cause exceptions;
  2. Some hardware devices cannot guarantee that the operation when accessing unaligned data is an atomic operation;
  3. Accessing unaligned data takes more time than accessing aligned data;
  4. Although some processors support access to unaligned data, it will trigger an alignment trap (alignment trap);
  5. Some hardware devices only support unaligned access of simple data instructions, but do not support unaligned access of complex data instructions.

Advantages of memory alignment :

  1. It is easy to port between different platforms, because some hardware platforms cannot support data access at arbitrary addresses, and can only fetch certain specific data at certain addresses, otherwise an exception will be thrown;
  2. Improve memory access efficiency, because when the CPU reads memory, it reads one by one.

1.7. What is a memory leak

memory leak: The failure of a program to free memory that is no longer in use, due to inadvertence or error.
Further explanation:

  • It does not mean that the memory disappears physically, but that the program loses control of the memory due to negligence or errors during the running process, resulting in a waste of memory.
  • It often refers to heap memory leaks, because the heap is dynamically allocated and controlled by the user. If used improperly, memory leaks will occur.
  • When using malloc, calloc, realloc, new, etc. to allocate memory, call the corresponding free or delete to release the memory after use, otherwise this memory will cause a memory leak.
  • pointer reassignment
char *p = (char *)malloc(10);
char *p1 = (char *)malloc(10);
p = np;

At the beginning, the pointers p and p1 respectively point to a memory space, but the pointer p is reassigned, so that the memory space initially pointed to by p cannot be found, thus a memory leak occurs.

1.8. How to prevent memory leaks? How does the memory leak detection tool work?

Ways to prevent memory leaks:

  1. Internal encapsulation : Encapsulate the allocation and release of memory into the class, apply for memory at the time of construction, and release the memory at the time of destruction. (Note: But this is not the best practice, when the object of the class is copied, the program will release the same memory space twice)
  2. Smart pointer : Smart pointer is a tool that has encapsulated memory leaks in C++ and can be used directly. Smart pointers will be explained in detail in the next question.

Method for detecting memory leaks under VS (CRT):

  1. Run with F5 in debug mode:
#define CRTDBG_MAP_ALLOC  
#include <stdlib.h>  
#include <crtdbg.h>  
//在入口函数中包含 _CrtDumpMemoryLeaks();  
//即可检测到内存泄露
 
//以如下测试函数为例:
int main()
{
    
    
	char* pChars = new char[10];
	_CrtDumpMemoryLeaks();
	return 0;
}

1.9. What types of smart pointers are there? How does the smart pointer work?

Smart pointers are proposed to solve memory leaks caused by dynamic memory allocation and to release the same memory space multiple times. In C++11, it is encapsulated in the <memory> header file.

Smart pointers in C++11 include the following three types:

  • Shared pointer (shared_ptr) : The resource can be shared by multiple pointers, and the use counting mechanism indicates that the resource is shared by several pointers. Check the number of resource owners through use_count(), which can be constructed through unique_ptr and weak_ptr, call release() to release the ownership of the resource, and the count will be reduced by one. When the count is reduced to 0, the memory space will be released automatically, thus avoiding memory leak.

  • Exclusive pointer (unique_ptr) : A smart pointer with exclusive ownership, the resource can only be occupied by one pointer, and the pointer cannot be copied and assigned. But move construction and move assignment construction (calling the move() function), that is, one unique_ptr object is assigned to another unique_ptr object, can be assigned through this method.

  • Weak pointer (weak_ptr) : points to the object pointed to by shared_ptr, which can solve the circular reference problem caused by shared_ptr.

The realization principle of smart pointer: counting principle.

1.10. How to assign a unique_ptr to another unique_ptr object?

With the help of std::move(), one unique_ptr object can be assigned to another unique_ptr object, and its purpose is to realize the transfer of ownership.

// A 作为一个类 
std::unique_ptr<A> ptr1(new A());
std::unique_ptr<A> ptr2 = std::move(ptr1);

1.11 What's wrong with using smart pointers? How to deal with it?

Possible problems with smart pointers: circular references

In the following example, two classes Parent and Child are defined, and the shared pointer of the object of the other class is defined in the two classes. After the program ends, the two pointers point to each other's memory space, resulting in the memory cannot be released.

The solution to circular references: weak_ptr

Circular reference : The called destructor is not called, resulting in a memory leak.

  • weak_ptr has a non-owning (weak) reference to the object managed by shared_ptr , which must be converted to shared_ptr before accessing the referenced object;
  • weak_ptr is used to break the circular reference problem of the object managed by shared_ptr. If this ring is isolated (there is no external shared pointer pointing to the ring), the reference count of shared_ptr cannot reach 0, and the memory is leaked; let one of the pointers in the ring be Weak pointers can avoid this situation;
  • weak_ptr is used to express the concept of temporary ownership. When an object only needs to be accessed when it exists, and may be deleted by others at any time, you can use weak_ptr to track the object; when you need to obtain ownership, convert it to shared_ptr. At this time, if the original shared_ptr is destroyed, the lifetime of the object is extended until this temporary shared_ptr is also destroyed.

1.12 VS detects memory leaks and locates the location of the leaked code

Checking method:
Add a sentence of _CrtDumpMemoryLeaks() in the last line of the main function . Debug the program, close the program naturally to let it exit (do not customize debugging), and view the output:

Detected memory leaks!
Dumping objects ->
{
   
   453} normal block at 0x02432CA8, 868 bytes long.
 Data: <404303374       > 34 30 34 33 30 33 33 37 34 00 00 00 00 00 00 00 
{
   
   447} normal block at 0x024328B0, 868 bytes long.
 Data: <404303374       > 34 30 34 33 30 33 33 37 34 00 00 00 00 00 00 00 
{
   
   441} normal block at 0x024324B8, 868 bytes long.
 Data: <404303374       > 34 30 34 33 30 33 33 37 34 00 00 00 00 00 00 00 
{
   
   435} normal block at 0x024320C0, 868 bytes long.
 Data: <404303374       > 34 30 34 33 30 33 33 37 34 00 00 00 00 00 00 00 
{
   
   429} normal block at 0x02431CC8, 868 bytes long.
 Data: <404303374       > 34 30 34 33 30 33 33 37 34 00 00 00 00 00 00 00 
{
   
   212} normal block at 0x01E1BF30, 44 bytes long.
 Data: <`               > 60 B3 E1 01 CD CD CD CD CD CD CD CD CD CD CD CD 
{
   
   204} normal block at 0x01E1B2C8, 24 bytes long.
 Data: <                > C8 B2 E1 01 C8 B2 E1 01 C8 B2 E1 01 CD CD CD CD 
{
   
   138} normal block at 0x01E15680, 332 bytes long.
 Data: <                > 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
{
   
   137} normal block at 0x01E15628, 24 bytes long.
 Data: <(V  (V  (V      > 28 56 E1 01 28 56 E1 01 28 56 E1 01 CD CD CD CD 
Object dump complete.

Take one of the details: {453} normal block at 0x02432CA8, 868 bytes long. The
453 surrounded by {} is the memory leak location value we need, and 868 bytes long means that there are 868 bits of memory in this place that have not been released. Add: _CrtSetBreakAlloc(453)
to the first line of the main function ; it means to break at the position of applying for 453 memory. Then debug the program, ... the program is interrupted. view call stack

insert image description here
Double-click the last function called by our code, here is CDbQuery::UpdateDatas(), and locate the code that applies for memory: Well,
insert image description here
we finally know what went wrong, the memory has not been released. Change the code and fix this. Then continue... until there is no normal block in the debug output, and the program has no memory leaks.

Remember to add the header file: #include <crtdbg.h>

Finally, it should be noted that not all normal blocks must have memory leaks. When there are global variables in your program, the release of the global variables is shown after the main function exits, so at the end of the main function _CrtDumpMemoryLeaks() will think that The memory allocated globally is not released, causing the illusion of a memory leak. How to avoid it? I usually declare the global variable as a pointer, new in the main function, delete in the main function, and then call _CrtDumpMemoryLeaks(), so that there will be no misjudgment.

1.13 Linux detects memory leaks and locates the location of the leaked code

1.14 Deep Copy and Shallow Copy

  • The default copy constructor of c++ is shallow copy.
    Shallow copy is a simple assignment between data members of objects. If you design a class without providing its copy constructor, when you use an object of this class to assign an object The process performed at this time is a shallow copy. When there is no pointer in the data member, shallow copy is feasible; but when there is a pointer in the data member, if a simple shallow copy is used , the two pointers in the two classes will point to the same address. When the object is about to end, The destructor will be called twice, causing the pointer to hang. Therefore, at this time, deep copy must be used.
  • The difference between deep copy and shallow copy is that deep copy will apply for additional space in the heap memory to store data instead of a simple assignment process , thus solving the problem of pointer hanging.

2. Language Comparison

2.1 New features of C++11

1. auto type deduction
auto keyword: automatic type deduction, the compiler will deduce the type of the variable through the initial value during compilation, and the variable defined by auto must have an initial value.
The basic usage syntax of the auto keyword is as follows:

2. decltype type derivation
decltype keyword: decltype is the abbreviation of "declare type", translated as "declare type". Same function as auto, both are used for automatic type deduction at compile time. If you want to infer the type of the variable to be defined from the expression, but you don't want to initialize the variable with the value of the expression, you can no longer use auto. The role of decltype is to select and return the data type of the operand.

the difference:

auto var = val1 + val2; 
decltype(val1 + val2) var1 = 0; 
  • auto deduces the variable type according to the initial value val1 + val2 on the right side of =, and assigns the initial value to the variable var; decltype deduces the variable type according to the val1 + val2 expression, and the initial value of the variable has nothing to do with the value of the expression .
  • auto requires that the variable must be initialized, because it deduces the type of the variable based on the initialized value, but decltype does not require it, and it can be initialized or not initialized when defining the variable.

3. Lambda expression
Lambda expression, also known as lambda function or lambda anonymous function.

Definition of lambda anonymous function:

[capture list] (parameter list) -> return type
{
    
    
   function body;
};

in:

  • capture list: The capture list refers to the list of local variables defined in the function where the lambda resides, and is usually empty.
  • return type, parameter list, and function body: respectively indicate the return value type, parameter list, and function body, which are the same as ordinary functions.
#include <iostream>
#include <algorithm>
using namespace std;

int main()
{
    
    
    int arr[4] = {
    
    4, 2, 3, 1};
    //对 a 数组中的元素进行升序排序
    sort(arr, arr+4, [=](int x, int y) -> bool{
    
     return x < y; } );
    for(int n : arr){
    
    
        cout << n << " ";
    }
    return 0;
}

4. Range for statement

for (declaration : expression){
    
    
    statement
}

The meaning of the parameters:

  • expression: must be a sequence, such as an initial value list enclosed in curly braces, an array, vector, string, etc. The common feature of these types is that they have beign and end members that can return iterators.
  • declaration: A variable is defined here, and each element in the sequence can be converted into the type of the variable. The auto type specifier is commonly used.

5. Rvalue references
Rvalue references: references bound to rvalues, use && to obtain rvalue references, rvalue references can only be bound to objects to be destroyed. To distinguish it from rvalue references, regular references are called lvalue references.

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    
    
    int var = 42;
    int &l_var = var;
    int &&r_var = var; // error: cannot bind rvalue reference of type 'int&&' to lvalue of type 'int' 错误:不能将右值引用绑定到左值上

    int &&r_var2 = var + 40; // 正确:将 r_var2 绑定到求和结果上
    return 0;
}

6. Standard library move() function
move() function: through this function, an rvalue reference bound to an lvalue can be obtained, and this function is included in the utility header file. This knowledge point will be explained in detail in subsequent chapters.

7.
The relevant knowledge of smart pointers has been explained in detail in the first chapter, and will not be repeated here.

8. delete function and default function

  • delete function: = delete means that the function cannot be called.
  • default function: = default means that the compiler generates a default function, for example: generate a default constructor.
#include <iostream>
using namespace std;

class A
{
    
    
public:
	A() = default; // 表示使用默认的构造函数
	~A() = default;	// 表示使用默认的析构函数
	A(const A &) = delete; // 表示类的对象禁止拷贝构造
	A &operator=(const A &) = delete; // 表示类的对象禁止拷贝赋值
};
int main()
{
    
    
	A ex1;
	A ex2 = ex1; // error: use of deleted function 'A::A(const A&)'
	A ex3;
	ex3 = ex1; // error: use of deleted function 'A& A::operator=(const A&)'
	return 0;
}

2.2 Difference between C and C++

Let me talk about object-oriented and process-oriented first:

  • Process-oriented thinking: Process-oriented programming is to analyze the steps to solve the problem, and then implement these steps step by step, and call them one by one when using them.
  • Object-oriented thinking: Object-oriented programming is to decompose the problem into various objects. The purpose of establishing an object is not to complete a step, but to describe the behavior of something in the entire problem-solving step.

For example : (playing backgammon)
(1) Thinking with a process-oriented thinking is: start the game, the white piece goes first, draw the picture, judge the win or lose, it is the turn of the sunspot, draw the picture, judge the win or loss, repeat the previous process, and output the final result.
(2) Thinking with object-oriented thinking is: black and white (the behavior of both is the same), chessboard system (responsible for drawing the picture), regulation system (regulation of winning or losing, fouls, etc.), output system (output winner).
Object-oriented is a high degree of physical abstraction (functional division), process-oriented is top-down programming (step division)

Differences and connections:

  • A typical difference between C and C++ lies in dynamic memory management . C language allocates and releases heap memory through malloc and free, while C++ manages heap memory through new and delete;
  • The mandatory type conversion is also different . The mandatory type conversion of C uses the type in () parentheses to perform type coercion, while C++ has four types of coercion methods of its own, namely const_cast, static_cast, reinterpret_cast and dynamic_cast;
  • The input and output methods of C and C++ are also different . The difference between printf/scanf and cout/cin of C++ is that the former group is C library functions, and the latter are objects of type ostream and istream.
  • C++ also supports the namespace namespace, allowing users to define new namespace scopes to avoid global name conflicts.
  • Application fields: C language is mainly used in embedded fields, driver development and other fields that directly deal with hardware, and C++ can be used in application layer development, user interface development and other fields that deal with operating systems.
  • C++ not only inherits the powerful low-level operation characteristics of C, but also is endowed with an object-oriented mechanism. It has many features, such as multiple inheritance in object-oriented languages, the distinction between passing by value and passing by reference, the const keyword, and so on.
  • The "enhancement" of C++ to C is manifested in the following aspects: Type checking is stricter. Added object-oriented mechanism, generic programming mechanism (Template), exception handling, operator overloading, standard template library (STL), namespace (to avoid global naming conflicts).

A procedural language:

Advantages : The performance is higher than that of object-oriented, because the class needs to be instantiated when calling, the overhead is relatively large, and it consumes resources; such as single-chip microcomputer, embedded development, Linux/Unix, etc. generally adopt process-oriented development, and performance is the most important factor.
Disadvantages : no object-oriented, easy to maintain, easy to reuse, easy to expand

Object-oriented language:

Advantages : easy to maintain, easy to reuse, and easy to expand. Due to the characteristics of object-oriented encapsulation, inheritance, and polymorphism, a low-coupling system can be designed to make the system more flexible and easier to maintain. Disadvantages: performance is lower than process
- oriented

2.3 Differences between Python and C++

the difference:

  • Language itself: Python is a scripting language, interpreted and executed without compilation; C++ is a language that needs to be compiled before it can run, and it runs after compilation on a specific machine.
  • Operating efficiency: C++ has high operating efficiency, safety and stability. Reason: Python code and C++ will eventually become CPU instructions to run, but in general, such as reversing and merging two strings, Python will eventually convert a lot more CPU instructions than C++. First of all, Python involves more content than C++, after more layers, even numbers in Python are objects; secondly, Python is interpreted and executed, and there is an interpreter layer between the CPU of the physical machine, while C++ is What is compiled and executed is directly the machine code, and the compiler can perform some optimizations during compilation.
  • Development efficiency: Python development efficiency is high. Reason: The functions that Python can achieve with one or two lines of code, C++ often requires more code to achieve.
  • Different writing format and grammar: Python's grammatical format is different from its C++ definition and declaration, and it is extremely flexible and completely oriented to higher-level developers.

3. Object-oriented

3.1 What is object-oriented? The three main characteristics of object-oriented

Object-oriented : An object refers to a specific thing, and the abstraction of these things is a class, which contains data (member variables) and actions (member methods).

The three main characteristics of object-oriented:

  • Encapsulation : Encapsulate the specific implementation process and data into a function, which can only be accessed through the interface to reduce coupling.
  • Inheritance : The subclass inherits the characteristics and behaviors of the parent class. The subclass has non-private methods or member variables of the parent class. The subclass can rewrite the method of the parent class, which enhances the coupling between classes, but when the parent class When the member variable, member function or class itself is modified by the final keyword, the modified class cannot be inherited, and the modified members cannot be rewritten or modified.
  • Polymorphism : Polymorphism refers to objects of different inheritance classes that respond differently to the same message. The pointer of the base class points to or binds to the object of the derived class, so that the pointer of the base class presents different expressions.

3.2 The difference between overloading, rewriting and hiding

  1. Overloading : refers to several functions with the same name declared in the same accessible area with different parameter columns (parameter type, number, order), which function to call is determined according to the parameter list, and overloading does not care about the return type of the function.
class A
{
    
    
public:
    void fun(int tmp);
    void fun(float tmp);        // 重载 参数类型不同(相对于上一个函数)
    void fun(int tmp, float tmp1); // 重载 参数个数不同(相对于上一个函数)
    void fun(float tmp, int tmp1); // 重载 参数顺序不同(相对于上一个函数)
    int fun(int tmp);            // error: 'int A::fun(int)' cannot be overloaded 错误:注意重载不关心函数返回类型
};
  1. Hidden : It means that the function of the derived class shields the base class function with the same name, mainly as long as the function with the same name, regardless of whether the parameter list is the same, the base class function will be hidden.
#include <iostream>
using namespace std;

class Base
{
    
    
public:
    void fun(int tmp, float tmp1) {
    
     cout << "Base::fun(int tmp, float tmp1)" << endl; }
};

class Derive : public Base
{
    
    
public:
    void fun(int tmp) {
    
     cout << "Derive::fun(int tmp)" << endl; } // 隐藏基类中的同名函数
};

int main()
{
    
    
    Derive ex;
    ex.fun(1);       // Derive::fun(int tmp)
    ex.fun(1, 0.01); // error: candidate expects 1 argument, 2 provided
    return 0;
}

Explanation : In the above code ex.fun(1, 0.01); an error occurs, indicating that the function with the same name of the base class is hidden in the derived class. If you want to call the function with the same name in the base class, you can add the type name to specify ex.Base::fun(1, 0.01);, so that you can call the function with the same name in the base class.

  1. Rewriting (covering) : refers to the presence of redefined functions in derived classes. The function name, parameter list, and return value type must be the same as the overridden function in the base class, only the function body is different. When the derived class calls, the overridden function of the derived class will be called, and the overridden function will not be called. The overridden function in the overridden base class must have virtual decoration.
#include <iostream>
using namespace std;

class Base
{
    
    
public:
    virtual void fun(int tmp) {
    
     cout << "Base::fun(int tmp) : " << tmp << endl; }
};

class Derived : public Base
{
    
    
public:
    virtual void fun(int tmp) {
    
     cout << "Derived::fun(int tmp) : " << tmp << endl; } // 重写基类中的 fun 函数
};
int main()
{
    
    
    Base *p = new Derived();
    p->fun(3); // Derived::fun(int) : 3
    return 0;
}

The difference between rewriting and overloading:

  • Scope difference : For the overloading or rewriting of functions in a class, the overloading occurs inside the same class, and the rewriting occurs between different classes (between the subclass and the parent class).
  • Parameter difference : the overloaded function needs to have the same function name and different parameter list as the original function, and does not pay attention to the return value type of the function; the function name, parameter list and return value type of the rewritten function need to be the same as the original function , the overridden function in the parent class needs to be decorated with virtual.
  • virtual keyword : The rewritten function base class must be decorated with the virtual keyword, and the overloaded function can be decorated with the virtual keyword or not.

The difference between hiding and rewriting, overloading:

  • Scope difference : hiding is different from overloading scope, hiding happens in different classes.
  • Parameter difference : the hidden function and the hidden function parameter list can be the same or different, but the function name must be the same; when the parameters are different, no matter whether the function in the base class is modified by virtual, the base class function is hidden instead
    of rewrite.

3.3 How to understand that C++ is object-oriented programming

Explanation: It is best to explain this question in combination with your own project experience, or give some appropriate examples, and compare it with process-oriented programming.

  • Procedure-Oriented Programming : An approach to writing software centered on procedures, or functions, that perform program operations. Program data is usually stored in variables, separate from these procedures. So variables must be passed to functions that need to use them. Cons: As programs become more complex, the separation of program data from running code can cause problems. For example, the specification of a program often changes, requiring changes to the format of data or the design of data structures. When the data structure changes, the code that operates on the data must also be changed to accept the new format. Finding all the code that needs to be changed creates extra work for the programmer and increases the chances of making the code buggy.
  • Object-Oriented Programming (OOP) : Centered on creating and using objects. An object (Object) is a software entity that combines data and programs in one unit. An object's data items, also known as its properties, are stored in member variables. The procedures an object performs are called its member functions. Binding an object's data and procedures together is called encapsulation.

Object Oriented Programming explains further:

Object-oriented programming encapsulates data members and member functions into a class, and declares the access levels (public, private, protected) of data members and member functions, so as to control the access of class objects to data members and functions, and play a role in data members. Certain protective effect. Moreover, when an object of a class calls a member function, it only needs to know the name, parameter list, and return value type of the member function, without knowing the implementation principle of the function. When the data members or member functions inside the class are changed, the code outside the class is not affected.

3.4 What is polymorphism? How is polymorphism implemented?

Polymorphism : Polymorphism refers to objects of different inheritance classes that respond differently to the same message. The pointer of the base class points to or binds to the object of the derived class, so that the pointer of the base class presents different expressions. Add the virtual keyword before the function of the base class, rewrite the function in the derived class, and the corresponding function will be called according to the actual type of the object at runtime. If the object type is a derived class, the function of the derived class is called; if the object type is a base class, the function of the base class is called.

Implementation method : polymorphism is realized through virtual functions, the address of the virtual function is stored in the virtual function table, and the address of the virtual function table is stored in the memory space of the instance object of the class containing the virtual function.

Implementation process:

  1. A function declared with the virtual keyword in a class is called a virtual function;
  2. Classes with virtual functions have a virtual function table. When an object of this class is created, the object has a virtual table pointer pointing to the virtual function table (the virtual function table corresponds to the class, and the virtual table pointer corresponds to the object) ;
  3. When the base class pointer points to the derived class object and the base class pointer calls the virtual function, the base class pointer points to the virtual table pointer of the derived class. Since the virtual table pointer points to the derived class virtual function table, the corresponding virtual function is found by traversing the virtual table .

3.4.1 Static polymorphism and dynamic polymorphism:

  1. Static polymorphism: also known as polymorphism during compilation, which is completed by the compiler during compilation. The compiler can infer which function to call based on the type of the function argument (may be implicitly type converted), if there is a corresponding The function calls the function, otherwise a compilation error occurs.
  2. Dynamic polymorphism (dynamic binding): That is, polymorphism at runtime. During program execution (non-compilation period), the actual type of the referenced object is judged, and the corresponding method is called according to its actual type. :
    ● The base class must contain virtual functions, and the derived class must rewrite the virtual functions in the base class.
    ● Call the virtual function through the pointer or reference of the base class object.
#include <iostream>
using namespace std;

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

	virtual void fun1() {
    
     cout << "Base::fun1()" << endl; }

	virtual void fun2() {
    
     cout << "Base::fun2()" << endl; }
};
class Derive : public Base
{
    
    
public:
	void fun() {
    
     cout << "Derive::fun()" << endl; }

	virtual void D_fun1() {
    
     cout << "Derive::D_fun1()" << endl; }

	virtual void D_fun2() {
    
     cout << "Derive::D_fun2()" << endl; }
};
int main()
{
    
    
	Base *p = new Derive();
	p->fun(); // Derive::fun() 调用派生类中的虚函数
	return 0;
}

Simple explanation: When the pointer of the base class points to the object of the derived class, find the virtual function table (the object virtual function table of the derived class) through the virtual table pointer of the derived class object, and then find the corresponding virtual function Derive::f() to make the call.

4. Class related

5.1 What is a virtual function? What is a pure virtual function?

Virtual function : A member function modified by the virtual keyword is a virtual function.

Pure virtual function:

  • When the pure virtual function is declared in the class, add =0;
  • A class containing a pure virtual function is called an abstract class (as long as it contains a pure virtual function, the class is an abstract class), and there are only interfaces in the class, and there is no specific implementation method;
  • If a derived class that inherits a pure virtual function does not fully implement the pure virtual function of the base class, it is still an abstract class and cannot instantiate an object.

illustrate:

  • Abstract class objects cannot be used as parameters of functions, cannot create objects, and cannot be used as function return types;
  • You can declare abstract class pointers, and you can declare abstract class references;
  • The subclass must inherit the pure virtual functions of the parent class and realize all of them before creating an object of the subclass.

5.2 What is the difference between a virtual function and a pure virtual function?

  • Virtual functions and pure virtual functions can appear in the same class, which is called an abstract base class. (Classes with pure virtual functions are called abstract base classes)
  • The usage is different: the virtual function can be used directly, and the pure virtual function must be implemented in the derived class before it can be used;
  • The definition forms are different: when defining a virtual function, add the virtual keyword on the basis of the ordinary function, and when defining a pure virtual function, in addition to adding the virtual keyword, you need to add =0;
  • The virtual function must be implemented, otherwise the compiler will report an error;
  • For a derived class that implements a pure virtual function, the pure virtual function is called a virtual function in the derived class, and both the virtual function and the pure virtual function can be rewritten in the derived class;
  • The destructor is best defined as a virtual function, especially for classes with inheritance relationships; the destructor can be defined as a pure virtual function, at this time, the class it is in is an abstract base class, and instantiated objects cannot be created.

5.3 Implementation mechanism of virtual functions

Implementation mechanism : Virtual functions are implemented through virtual function tables. The address of the virtual function is stored in the virtual function table. In the memory space where the object of the class is located, a pointer to the virtual function table (called "virtual table pointer") is stored. The virtual function corresponding to the class can be found through the virtual table pointer. surface. The virtual function table solves the inheritance problem of the base class and the derived class and the coverage problem of the member functions in the class. When a pointer of the base class is used to operate a derived class, this virtual function table indicates the actual function that should be called.

Virtual function table related knowledge points:

  • The content stored in the virtual function table: the address of the virtual function of the class.
  • The time when the virtual function table is established: the compilation stage, that is, the address of the virtual function will be placed in the virtual function table during the compilation process of the program.
  • The location where the virtual table pointer is saved: the virtual table pointer is stored at the frontmost location in the memory space of the object, this is to ensure that the offset of the virtual function is correctly fetched.

Note: virtual function table and class binding, virtual table pointer and object binding. That is, the virtual function tables of different objects of the class are the same, but each object has its own virtual table pointer to point to the virtual function table of the class.

Example:

Cases without virtual function coverage:

#include <iostream>
using namespace std;

class Base
{
    
    
public:
    virtual void B_fun1() {
    
     cout << "Base::B_fun1()" << endl; }
    virtual void B_fun2() {
    
     cout << "Base::B_fun2()" << endl; }
    virtual void B_fun3() {
    
     cout << "Base::B_fun3()" << endl; }
};

class Derive : public Base
{
    
    
public:
    virtual void D_fun1() {
    
     cout << "Derive::D_fun1()" << endl; }
    virtual void D_fun2() {
    
     cout << "Derive::D_fun2()" << endl; }
    virtual void D_fun3() {
    
     cout << "Derive::D_fun3()" << endl; }
};
int main()
{
    
    
    Base *p = new Derive();
    p->B_fun1(); // Base::B_fun1()
    return 0;
}

The pointer p of the base class in the main function points to the object of the derived class. When the function B_fun1() is called, the address of the function is found through the virtual function table of the derived class to complete the call.

5.4 Virtual function table structure of single inheritance and multiple inheritance

The compiler processes virtual function tables:

  • The compiler puts the pointer of the virtual function table in the memory space of the instance object of the class. When the object calls the virtual function of the class, it finds the virtual function table through the pointer, and finds the corresponding virtual function table according to the address of the virtual function stored in the virtual function table. virtual function.
  • If the derived class does not redefine the virtual function A of the base class, the virtual function table of the derived class stores the address of the virtual function A of the base class, that is to say, the addresses of the virtual function A of the base class and the derived class are the same .
  • If a derived class rewrites a virtual function B of the base class, the derived virtual function table stores the address of the rewritten virtual function B, that is to say, there are two versions of the virtual function B, which are stored in the base class respectively. In the virtual function table of the class and derived classes.
  • If the derived class redefines a new virtual function C, the virtual function table of the derived class stores the address of the new virtual function C.

5.5 Why can't constructors be virtual functions?

The virtual function call needs a virtual function table pointer, and the pointer is stored in the memory space of the object; if the constructor is declared as a virtual function, then since the object has not been created, there is no memory space, and there is no virtual function table address to call Virtual function - constructor.

5.6 Why can a destructor be a virtual function, and what problems might exist if it is not a virtual function?

To prevent memory leaks, when deleting p (base class), it wisely executes the destructor of the derived class first, and then executes the destructor of the base class.
If the destructor of the base class is not a virtual function, when deleting p (base class), when calling the destructor, it will only look at the data type of the pointer, not the assigned object, which will cause a memory leak.
For example:
subclass B inherits from base class A; A *p = new B; delete p;
 1) At this time, if the destructor of class A is not a virtual function, then delete p; will only call the destructor of A function, only the A part of the B object is released, and the derived new part is not released.
2) If the destructor of class A is a virtual function, delete p; will first call the destructor of B, and then call the destructor of A to release all the space of the B object.
Supplement: B *p = new B; delete p; also calls the destructor of B first, and then calls the destructor of A.

5.7. Which functions cannot be declared as virtual functions

1. Static member functions ; 2. Ordinary functions outside the class ; 3. Constructor functions ; 4. Friend functions
Virtual functions are for polymorphism. The virtual function call can only know which function is called when the program is running. The following points need to be noted: (1) The constructor
of the class cannot be a virtual function. The constructor is for constructing objects, so in When calling the constructor, you must know which object calls the constructor, so the constructor cannot be a virtual function. (2) The static member function of a class cannot be a virtual function. The static member function of a class is shared by the class and has nothing to do with the object of the class. There is no this pointer in the static function, so it cannot be a virtual function. (3) Inline functions The purpose of inline functions is to reduce the function call time. It replaces the function body of the inline function with the function call during compiler preprocessing, so that when the code runs here, it does not need to spend time calling the function. Inline means that the compiler replaces the function class content with the function call, which is statically compiled. The virtual function is called dynamically. The compiler does not know whether the virtual function of the parent class or the subclass needs to be called, so the inline declaration cannot be expanded, so the compiler will ignore it. (4) Friend function Friend function has nothing to do with this class, there is no this pointer, so it cannot be a virtual function.






5. Keyword library functions

4.1 The difference between sizeof and strlen

  1. strlen is a function in the header file and sizeof is an operator in C++.
  2. strlen measures the actual length of the string (its source code is below), terminated by \0 . And sizeof measures the allocated size of the character array.
strlen 源代码:
size_t strlen(const char *str) {
    
    
    size_t length = 0;
    while (*str++)
        ++length;
    return length;
}
#include <iostream>
#include <cstring>

using namespace std;

int main()
{
    
    
    char arr[10] = "hello";
    cout << strlen(arr) << endl; // 5
    cout << sizeof(arr) << endl; // 10
    return 0;
}
  1. If the character array arr is used as the formal parameter of the function, arr in sizeof(arr) is treated as a character pointer, and arr in strlen(arr) is still a character array, as can be seen from the running results of the following program.
#include <iostream>
#include <cstring>

using namespace std;

void size_of(char arr[])
{
    
    
    cout << sizeof(arr) << endl; // warning: 'sizeof' on array function parameter 'arr' will return size of 'char*' .
    cout << strlen(arr) << endl; 
}

int main()
{
    
    
    char arr[20] = "hello";
    size_of(arr); 
    return 0;
}
/*
输出结果:
8
5
*/
  1. strlen itself is a library function, so the length is calculated during the running of the program; while sizeof is calculated at the time of compilation;

  2. The parameter of sizeof can be a type or a variable; the parameter of strlen must be a variable of type char*.

4.2 Specific applications and usage scenarios of lambda expressions (anonymous functions)

The definition form of lambda expression is as follows:

[capture list] (parameter list) -> reurn type
{
    
    
   function body
}

in:

  • capture list: The capture list refers to the list of local variables defined in the function where the lambda expression is located. It is usually empty, but if the local variable of the function where the lambda expression is located is used in the function body, the variable must be captured, that is, the variable must be written in capture list. Capture methods are divided into: reference capture method [&], value capture method [=].
  • return type, parameter list, and function body: respectively indicate the return value type, parameter list, and function body, which are the same as ordinary functions.

Use Scenario 1 : Sorting Algorithm

bool compare(int& a, int& b)
{
    
    
    return a > b;
}

int main(void)
{
    
    
    int data[6] = {
    
     3, 4, 12, 2, 1, 6 };
    vector<int> testdata;
    testdata.insert(testdata.begin(), data, data + 6);
    
    // 排序算法
    sort(testdata.begin(), testdata.end(), compare);    // 升序

	// 使用lambda表达式
	sort(testdata.begin(), testdata.end(), [](int a, int b){
    
     return a > b; });

    return 0;
}

Use Scenario 2 : Sorting Algorithm

4.3 The role of explicit (how to avoid implicit type conversion by the compiler)

Function: It is used to declare that the class constructor is called explicitly, not implicitly, and can prevent implicit conversion when calling the constructor. It can only be used to modify single-argument constructors, because no-argument constructors and multi-argument constructors are explicitly called, and the addition of the explicit keyword is meaningless.

Implicit conversion:

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

class A
{
    
    
public:
    int var;
    A(int tmp)
    {
    
    
        var = tmp;
    }
};
int main()
{
    
    
    A ex = 10; // 发生了隐式转换
    return 0;
}

In the above code, A ex = 10; Implicit conversion is performed at compile time, 10 is converted into an object of type A, and then the object is assigned to ex, which is equivalent to the following operation:

In order to avoid implicit conversion, it can be declared with the explicit keyword:

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

class A
{
    
    
public:
    int var;
    explicit A(int tmp)
    {
    
    
        var = tmp;
        cout << var << endl;
    }
};
int main()
{
    
    
    A ex(100);
    A ex1 = 10; // error: conversion from 'int' to non-scalar type 'A' requested
    return 0;
}

4.4 Difference between C and C++ static

  • In C language, use static to define local static variables, external static variables, static functions
  • In C++, use static to define local static variables, external static variables, static functions, static member variables, and static member functions. Because there is the concept of class in C++, static member variables and static member functions are all concepts related to classes.

4.5 The role of static

Function:
static defines static variables and static functions.

  • Keep variable content persistent: static acts on local variables, changing the life cycle of local variables so that the variable exists from the time it is defined until the end of the program.

  • Hidden: static acts on global variables and functions, changing the scope of global variables and functions, so that global variables and functions can only be used in the file in which they are defined, and do not have global visibility in the source file. (Note: Ordinary global variables and functions have global visibility, that is, other source files can also be used.)

  • static acts on the member variables and member functions of the class, so that the class variables or member functions are related to the class, that is to say, these static members can be accessed through the class without defining the object of the class. Note: Static member functions of a class can only access static member variables or static member functions, and static member functions cannot be defined as virtual functions.

4.6 Precautions for using static in classes (definition, initialization and use)

static static member variables:


  1. Static member variables are declared within the class, defined and initialized outside the class, and the static keyword and private, public, and protected access rules should not appear when defining and initializing outside the class .
  2. Static member variables are equivalent to global variables in the class domain and are shared by all objects of the class, including objects of derived classes.
  3. Static member variables can be used as parameters of member functions, but ordinary member variables cannot.
#include <iostream>
using namespace std;

class A
{
    
    
public:
    static int s_var;
    int var;
    void fun1(int i = s_var); // 正确,静态成员变量可以作为成员函数的参数
    void fun2(int i = var);   //  error: invalid use of non-static data member 'A::var'
};
  1. The type of a static data member can be the type of the class to which it belongs, while the type of an ordinary data member can only be a pointer or reference of the class type.
#include <iostream>
using namespace std;

class A
{
    
    
public:
    static A s_var; // 正确,静态数据成员
    A var;          // error: field 'var' has incomplete type 'A'
    A *p;           // 正确,指针
    A &var1;        // 正确,引用
};

static static member function:

  1. Static member functions cannot call non-static member variables or non-static member functions, because static member functions do not have this pointer. Static member functions act as global functions at class scope.
  2. Static member functions cannot be declared as virtual functions (virtual), const functions and volatile functions.

4.7 Similarities and differences between static global variables and ordinary global variables

Same point:

  • Storage method: ordinary global variables and static global variables are both static storage methods.

difference:

  • Scope: The scope of ordinary global variables is the entire source program. When a source program is composed of multiple source files, ordinary global variables are valid in each source file; static global variables limit their scope, namely It is only valid in the source file that defines the variable, and cannot be used in other source files of the same source program. Because the scope of the static global variable is limited to one source file, it can only be shared by the functions in this source file, so it can avoid causing errors in other source files.
  • Initialization: Static global variables are only initialized once to prevent them from being used in other files.

4.8 The function and usage of const

effect:

  • const modifies member variables and defines them as const constants. Compared with macro constants, type checking can be performed, which saves memory space and improves efficiency.
  • const modifies the function parameter so that the value of the passed function parameter cannot be changed.
  • The member function is modified by const, so that the member function cannot modify any type of member variable (except the variable modified by mutable), nor can it call the non-const member function, because the non-const member function may modify the member variable.

Usage in class:

const member variables:

  • const member variables can only be declared and defined within the class, and initialized in the constructor initialization list.
  • A const member variable is only constant during the lifetime of an object, but it is variable for the entire class, because a class can create multiple objects, and the values ​​of const member variables of different classes are different. Therefore, the const member variable cannot be initialized in the declaration of the class, the object of the class has not been created, and the compiler does not know its value.

const member functions:

  • The value of a member variable cannot be modified unless it is modified with mutable; only member variables can be accessed.
  • Non-constant member functions cannot be called to prevent modification of the value of member variables.

4.9 Difference between define and const

the difference:

  • Compilation stage: define is to be replaced in the compilation preprocessing stage, and const is to determine its value in the compilation stage.
  • Security: The macro constants defined by define have no data type and are simply replaced without type safety checks; the constants defined by const are typed and need to be judged to avoid some low-level mistakes.
  • Memory occupation: The macro constant defined by define will be replaced as many times as it is used in the program. There are multiple backups in the memory, which occupy the space of the code segment; the constant defined by const occupies the space of the static storage area, and the program runs There is only one copy in the process.
  • Debugging: The macro constants defined by define cannot be debugged because they have been replaced in the precompilation stage; the constants defined by cons can be debugged.

Advantages of const:

  • There are data types, and security checks can be performed in the definition.
  • adjustable.
  • Take up less space.

4.10 Difference between define and typedef

  • Principle: #define is used as a preprocessing instruction, and the replacement operation is performed during compilation and preprocessing, and no correctness check is performed. Only when the source program that has been expanded will it find possible errors and report an error. typedef is a keyword, processed at compile time, has type checking function, and is used to give an alias to an existing type, but typedef cannot be used in a function definition.
  • Function: typedef is used to define the alias of the type, which is convenient to use. #define can not only alias the type, but also define constants, variables, compilation switches, etc.
  • Scope: #define has no scope limitation, as long as it is a previously predefined macro, it can be used in future programs, while typedef has its own scope.
  • Operation of pointers: typedef and #define are not exactly the same when dealing with pointers
#include <iostream>
#define INTPTR1 int *
typedef int * INTPTR2;

using namespace std;

int main()
{
    
    
    INTPTR1 p1, p2; // p1: int *; p2: int
    INTPTR2 p3, p4; // p3: int *; p4: int *

    int var = 1;
    const INTPTR1 p5 = &var; // 相当于 const int * p5; 常量指针,即不可以通过 p5 去修改 p5 指向的内容,但是 p5 可以指向其他内容。
    const INTPTR2 p6 = &var; // 相当于 int * const p6; 指针常量,不可使 p6 再指向其他内容。
    
    return 0;
}

4.11 Use macros to realize comparison size and the minimum value of two numbers

#include <iostream>
#define MAX(X, Y) ((X)>(Y)?(X):(Y))
#define MIN(X, Y) ((X)<(Y)?(X):(Y))
using namespace std;

int main ()
{
    
    
    int var1 = 10, var2 = 100;
    cout << MAX(var1, var2) << endl;
    cout << MIN(var1, var2) << endl;
    return 0;
}
/*
程序运行结果:
100
10
*/

4.12 inline function and usage

Function:
inline is a keyword that can be used to define inline functions. Inline functions are called like ordinary functions, but they are not called through the function call mechanism but are directly expanded at the call point, which can greatly reduce the overhead caused by function calls, thereby improving the operating efficiency of the program.

Instructions:

  1. The default member function defined in the class is an inline function
    . The member function defined in the class does not need to add the inline keyword in the function head, because the compiler will automatically define the function (constructor, destructor, ordinary member function) in the class etc.) declared as an inline function, the code is as follows:
#include <iostream>
using namespace std;

class A{
    
    
public:
    int var;
    A(int tmp){
    
     
      var = tmp;
    }    
    void fun(){
    
     
        cout << var << endl;
    }
};

  1. Define a member function outside the class. If you want to define it as an inline function, you need to declare it with a keyword.
    When declaring a function inside a class and defining a function outside the class, if you want to define the function as an inline function, you can declare it inside the class The inline keyword is not added when the function is defined, but the inline keyword is added when the function is defined outside the class.
#include <iostream>
using namespace std;

class A{
    
    
public:
    int var;
    A(int tmp){
    
     
      var = tmp;
    }    
    void fun();
};

inline void A::fun(){
    
    
    cout << var << endl;
}

In addition, you can add inline when declaring the function and defining the function; you can also add inline only when the function is declared, but not when defining the function. Just make sure to tell the compiler about the inline before calling the function.

4.13 How inline functions work

  • The inline function does not have a control transfer relationship when it is called, but embeds the function body into each statement block that calls the function at the compilation stage, and the compiler uses the inline function in the calling expression that appears in the program. to replace the body of the function.
  • Ordinary functions transfer program execution to the memory address stored by the called function, and return to the place before the function was executed after the function is executed. The transfer operation needs to protect the site, and then restore the site after the called function is executed, which requires a large resource overhead.

4.14 Difference between macro definition (define) and inline function (inline)

  1. Inline functions are expanded at compile time, while macros are expanded during compilation and preprocessing; at compile time, inline functions are directly embedded into the object code, and macros are just a simple text replacement.
  2. An inline function is a real function, which is expanded directly at the call point in the same way as a normal function call, avoiding the push operation of function parameters and reducing the call overhead. The macro definition is more complicated to write, and it is often necessary to add some parentheses to avoid ambiguity.
  3. The macro definition only performs text replacement, and does not check the type of parameters, whether the statement can be compiled normally, etc. The inline function is a real function, which will check the type of parameters and whether the statements in the function body are written correctly.
#include <iostream>

#define MAX(a, b) ((a) > (b) ? (a) : (b))

using namespace std;

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

int main()
{
    
    
    int var = 1;
    cout << MAX(var, 5) << endl;     
    cout << fun_max(var, 0) << endl; 
    return 0;
}
/*
程序运行结果:
5
1

*/

4.15 What is the function of new?

new is a keyword in C++, used to dynamically allocate memory space, the implementation is as follows:

int *p = new int[5]; 

4.16 How do new and malloc determine whether memory is allocated?

  • malloc: If the memory is successfully applied for, a pointer to the memory is returned; if the allocation fails, a NULL pointer is returned.
  • new: If the memory is allocated successfully, a pointer of the object type is returned; if the allocation fails, a bac_alloc exception is thrown.

4.17 What is the implementation principle of delete? The difference between delete and delete[]?

The implementation principle of delete:

  • First execute the destructor of the class to which the object belongs;
  • And then release the occupied memory space by calling the standard library function of operator delete.

The difference between delete and delete []:

  • delete is used to release the space occupied by a single object, and only calls the destructor once;
  • delete [] is used to release the array space, and the destructor will be called once for each member in the array.

4.18 The difference between new and malloc, the difference between delete and free

When using, new and delete are used together, and malloc and free are used together.

  • malloc and free are library functions, while new and delete are keywords.
  • When new applies for space, there is no need to specify the size of the allocated space, the compiler will calculate it by itself according to the type; when malloc applies for space, it needs to determine the size of the requested space.
  • When new applies for space, the returned type is the pointer type of the object, which is a type-safe operator without mandatory type conversion; when malloc applies for space, the returned type is void*, which needs to be converted to object type by mandatory type conversion. pointer.
  • When new allocation fails, a bad_alloc exception will be thrown, and when malloc allocation fails, a null pointer will be returned.
  • For a custom type, new first calls the operator new() function to apply for space (the bottom layer is implemented by malloc), then calls the constructor to initialize, and finally returns a pointer to the custom type; delete first calls the destructor, and then calls operator delete( ) to release space (the bottom layer is realized by free). malloc and free cannot construct and destruct objects of custom types.
  • The new operator dynamically allocates memory for an object from free storage, while the malloc function dynamically allocates memory from the heap. (free store is not equal to heap)

4.19 What is the principle of malloc? The underlying implementation of malloc?

The principle of malloc:

  • When the allocated space is less than 128K, call the brk() function by moving _enddata;
  • When the allocated space is larger than 128K, call the mmap() function, which is realized by opening up a memory space in the virtual address space.

The underlying implementation of malloc:

  • The realization principle of the brk() function: move the pointer _enddata pointing to the high address of the data segment to the direction of the high address.

  • The principle of mmap memory mapping: 1. The process starts the mapping process, and creates a virtual mapping area for the mapping in the virtual address space; 2. Calls the system call function mmap()
    in the kernel space to realize the one-to-one mapping between the physical address of the file and the virtual address of the process
    3.
    The process initiates access to this mapped space, causing a page fault exception, and realizing the copy of the file content to the physical memory (main memory).

4.20 What is the difference between C and C++ struct?

  • In C language, struct is a user-defined data type; in C++, struct is an abstract data type that supports the definition of member functions.
  • In C language, struct has no setting of access rights, it is a collection of some variables, and cannot define member functions; in C++, struct can have access rights like classes, and can define member functions.
  • For the self-defined data type defined by struct in C language, the struct keyword needs to be added when defining the variable of this type, for example: struct A var;, to define the variable of type A; while in C++, this keyword is not required, for example :A var;

4.21 Why do we keep struct when we have class?

  • C++ is developed on the basis of C language. In order to be compatible with C language, struct is reserved in C++.

4.22 The difference between struct and union

Explanation: union is a union, and struct is a structure.

the difference:

  • Unions and structures are composed of several data members of different data types. When used, unions have only one valid member; all members of structs are valid.
  • Assigning values ​​to different members of the union will overwrite the values ​​of other members, while assigning values ​​to different members of the structure will not affect each other.
  • The size of the union is the maximum value of all internal variables, and the size is allocated in multiples of the largest type; the size of memory allocated by the structure follows the principle of memory alignment.

4.23 Similarities and differences between class and struct

  • Both struct and class can customize data types and also support inheritance operations.
  • The default access level in struct is public, and the default inheritance level is also public; the default access level in class is private, and the default inheritance level is also private.
  • When class inherits struct or struct inherits class, the default inheritance level depends on the class or struct itself, class (private inheritance), struct (public inheritance), that is, depends on the default inheritance level of derived classes.
struct A{
    
    }class B : A{
    
    }; // private 继承 
struct C : B{
    
    }// public 继承
  • class can be used to define template parameters, and struct cannot be used to define template parameters.

4.24 What does volatile do? Is it atomic and what impact does it have on the compiler?

The role of volatile : When the value of an object may be changed outside the control or detection of the program, the object should be declared as volatile, telling the compiler that such an object should not be optimized.

volatile is not atomic.

The impact of volatile on the compiler : After using this keyword, the compiler will not optimize the corresponding object, that is, it will not cache variables from memory to registers, preventing multiple threads from using variables in memory. Use a variable in a register, causing a program error.

4.25 Under what circumstances must use volatile, can it be used together with const?

Scenarios using the volatile keyword:

  • When multiple threads will use a certain variable, and the value of the variable may change, the variable needs to be modified with the volatile keyword;
  • The variable accessed in the interrupt service routine or the variable of the hardware register of the parallel device is preferably modified with the volatile keyword.

The volatile keyword and the const keyword can be used at the same time, a certain type can be both volatile and const, and have the properties of both.

4.26 What happens when I return the address of a static variable in a function?

#include <iostream>
using namespace std;

int * fun(int tmp){
    
    
    static int var = 10;
    var *= tmp;
    return &var;
}

int main() {
    
    
    cout << *fun(5) << endl;
    return 0;
}

/*
运行结果:
50
*/

Explanation: In the above code, the static local variable var is defined in the function fun, so that the variable will not be destroyed after leaving the scope of the function, and the variable still exists when returning to the main function, so that the program can get the correct running result . However, the static local variable is not destroyed until the program finishes running, wasting memory space.

4.27 What is the role of extern C?

The main function of extern "C" is to correctly implement C++ code to call other C language code. Adding extern "C" will instruct the compiler to compile this part of the code in C language (instead of C++). Since C++ supports function overloading, the compiler will add the parameter types of the function to the compiled code during the process of compiling the function, not just the function name; and the C language does not support function overloading, so compiling C The function of the language code will not bring the parameter type of the function, and generally only includes the function name.
Example:

// 可能出现在 C++ 头文件<cstring>中的链接指示
extern "C"{
    
    
    int strcmp(const char*, const char*);
}

4.28 What is the result of sizeof(1==1) in C and C++?

C language code: 4

C++ code: 1

4.29 What is the underlying principle of the memcpy function?

void *memcpy(void *dst, const void *src, size_t size)
{
    
    
    char *psrc;
    char *pdst;

    if (NULL == dst || NULL == src)
    {
    
    
        return NULL;
    }

    if ((src < dst) && (char *)src + size > (char *)dst) // 出现地址重叠的情况,自后向前拷贝
    {
    
    
        psrc = (char *)src + size - 1;
        pdst = (char *)dst + size - 1;
        while (size--)
        {
    
    
            *pdst-- = *psrc--;
        }
    }
    else
    {
    
    
        psrc = (char *)src;
        pdst = (char *)dst;
        while (size--)
        {
    
    
            *pdst++ = *psrc++;
        }
    }

    return dst;
}

4.30 What's wrong with the strcpy function?

The defect of the strcpy function: the strcpy function does not check the size boundary of the destination buffer, but assigns all the source strings one by one to a continuous memory space starting from the address of the destination string, and adds the string terminator at the same time, which will cause Other variables are overwritten.

Explanation : It can be seen from the above code that the last six digits of the variable var are changed by the three characters "d!\0" of the string "hello world!", and the corresponding ascii code of the three characters is in hexadecimal It is: \0(0x00), !(0x21), d(0x64).
Reason : The variable arr only allocates 10 memory spaces. From the address in the above program, it can be seen that arr and var are continuously stored in the memory, but when calling the strcpy function to copy, the source string "hello world!" The memory space occupied is 13, so the memory space of var will be occupied during the copying process, causing the last six digits of var to be overwritten.

4.31 Principle of auto type deduction

The principle of auto type deduction:

The compiler deduces the type of the variable according to the initial value, and it is required to have an initial value when defining a variable with auto. Sometimes the auto type inferred by the compiler is not exactly the same as the initial value type, and the compiler will appropriately change the result type to make it more consistent with the initialization rules.

4.32 How much memory space can malloc apply for at one time?

malloc is not a system call. So it is not the ultimate manager of the memory space. The maximum amount of space that can be applied for is not something malloc alone can decide.
There are many implementations of malloc, and different implementations have different characteristics. In general, malloc takes memory pages from the system, and then organizes these pages into "blocks" of different sizes. When the user program applies for memory, if the size does not exceed the size of the "block", malloc will internally match the block with the closest size and not smaller than the requested size, record the allocation information, and return the address. The purpose of this is to speed up allocation and reduce system calls.
If the requested space is large, malloc will directly apply for multiple pages from the system, map them to a continuous range in the virtual address space of the user program (this is also a system call), and then return the address.

Paging management is done by the operating system. Most modern operating systems support virtual memory, that is, use not only RAM, but also swap files on disk as memory space. The operating system divides these spaces into pages according to a certain size and maintains them in a structure called a page table. Memory management at the operating system level is based on pages.
The actual storage location of pages is managed by the operating system. When multiple applications are running simultaneously, the operating system moves inactive pages from RAM to disk files. When the application retries to access the page, it is read from the disk file into RAM, which is called page swapping.

Also because of this feature of the page, the operating system can allocate a page that does not actually exist to malloc until the page is actually used. So in theory, there may be no problem with malloc 1TB, although the RAM and the swap file (or swap partition) on the disk add up to dozens of GB. This is because the operating system does not actually allocate complete space to these pages, but only records a sum in the page table: A program needs XXXX pages, and sets aside corresponding lines to record "unsecured space" in it.

When the application really starts to read and write the address returned by malloc, if the operating system detects that the page has not allocated actual space, it will look for a suitable space to fill in the page table at this time. If no suitable space can be found, a page fault (overcommit error) will be triggered.

In Linux, we can turn off the overcommit function and require the operating system to ensure that the page is available every time it allocates a page. Then the maximum size that malloc can allocate is limited by the actual RAM+swap file/partition size . In Linux, in order to ensure the operation of the operating system itself, this size is usually half of the RAM + the size of the swap area. (Settings can of course be changed).

In addition, the size that can be allocated once and the size that can be allocated multiple times are not the same concept, because there is a problem of memory fragmentation in multiple allocations. It's like a restaurant has 40 seats, but maybe you only need a few people and you can't find a table for 8 people.

Therefore, in contemporary operating systems, the maximum size of space that malloc can allocate is first of all restricted by the number of bits of the machine , that is, the size of the space that can be directly addressed; on this basis, it is further restricted by the strategy of the operating system ; Restricted by the actual amount of available resources ; furthermore, it is also restricted by the algorithm of malloc itself ; finally, it is also restricted by the application method and frequency of the application .

4.33 The difference between public, protected, and private

First: private, public, protected access scope:

  • private: It can only be accessed by functions in this class and its friend functions, and cannot be accessed by any other, nor can objects of this class
  • protected: It can be accessed by functions in this class, functions of subclasses, and its friend functions, but not by objects of this class
  • public: It can be accessed by functions in this class, functions of subclasses, and its friend functions, and can also be accessed by objects of this class

Second: Method attribute changes after class inheritance:

  • Using private inheritance, all methods of the parent class become private in the subclass;
  • Using protected inheritance, the protected and public methods of the parent class become protected in the subclass, while the private method remains unchanged;
  • Using public inheritance, the method attributes in the parent class do not change;

6. Language feature related

6.1 What is the difference between lvalue and rvalue? The difference between lvalue reference and rvalue reference, how to convert lvalue to rvalue?

Lvalue: Refers to the persistent object that still exists after the expression ends.

Rvalue: A temporary object that ceases to exist when the expression ends.

The difference between lvalue and rvalue: lvalue is persistent, rvalue is short-lived

Difference between rvalue reference and lvalue reference:

  • An lvalue reference cannot be bound to an expression being converted, a literal constant, or an expression returning an rvalue. Rvalue references are just the opposite, and can bind to such expressions, but not to an lvalue.
  • An rvalue reference must be bound to an rvalue reference, obtained with && . An rvalue reference can only be bound to an object that is about to be destroyed, so its resources can be moved freely.

std::move can coerce an lvalue to an rvalue, which can then be used through an rvalue reference for move semantics.

#include <iostream>
using namespace std;

void fun1(int& tmp) 
{
    
     
  cout << "fun1(int& tmp):" << tmp << endl; 
} 

void fun2(int&& tmp) 
{
    
     
  cout << "fun2(int&& tmp)" << tmp << endl; 
} 

int main() 
{
    
     
  int var = 11; 
  fun1(12); // error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
  fun1(var);
  fun2(1); 
}

6.2 Implementation principle of std::move() function

std::move() function prototype:

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
    
    
	return static_cast<typename remove_reference<T>::type &&>(t);
}

Explanation: Quote the principle of folding

  • The formal parameter T&& passed from an rvalue to the above function is still an rvalue, that is, T&& && is equivalent to T&&.
  • The formal parameter T&& passed from the lvalue to the above function is still an lvalue, that is, T&& & is equivalent to T&.

Summary: By referring to the principle of folding, we can know that the formal parameter of the move() function can be either an lvalue or an rvalue.

The specific implementation of remove_reference :

//原始的,最通用的版本
template <typename T> struct remove_reference{
    
    
    typedef T type;  //定义 T 的类型别名为 type
};
 
//部分版本特例化,将用于左值引用和右值引用
template <class T> struct remove_reference<T&> //左值引用
{
    
     typedef T type; }
 
template <class T> struct remove_reference<T&&> //右值引用
{
    
     typedef T type; }   
  
//举例如下,下列定义的a、b、c三个变量都是int类型
int i;
remove_refrence<decltype(42)>::type a;             //使用原版本,
remove_refrence<decltype(i)>::type  b;             //左值引用特例版本
remove_refrence<decltype(std::move(i))>::type  b;  //右值引用特例版本 

Example:

int var = 10; 

转化过程:
1. std::move(var) => std::move(int&& &) => 折叠后 std::move(int&)

2. 此时:T 的类型为 int&typename remove_reference<T>::type 为 int,这里使用 remove_reference 的左值引用的特例化版本

3. 通过 static_castint& 强制转换为 int&&

整个std::move被实例化如下
string&& move(int& t) 
{
    
    
    return static_cast<int&&>(t); 
}

Summary:
std::move() implementation principle:

  • Use the principle of reference folding to pass rvalues ​​through T&& to keep the type unchanged or rvalues, and to turn lvalues ​​into
    ordinary lvalue references through T&&, so as to ensure that the template can pass any actual parameter and keep the type unchanged;
  • Then remove the reference through remove_refrence to get the specific type T;
  • Finally, static_cast<> is used for mandatory type conversion, and a T&& rvalue reference is returned.

6.3 What is a pointer? Pointer size and usage?

Pointer : A composite type that points to another type.
Size of pointers : On 64-bit computers, pointers occupy 8 bytes of space.

#include<iostream>

using namespace std;

int main(){
    
    
    int *p = nullptr;
    cout << sizeof(p) << endl; // 8

    char *p1 = nullptr;
    cout << sizeof(p1) << endl; // 8
    return 0;
}

Usage of pointers:

  1. pointer to plain object
#include <iostream>

using namespace std;

class A
{
    
    
};

int main()
{
    
    
    A *p = new A();
    return 0;
}
  1. pointer to constant object: const pointer
#include <iostream>
using namespace std;

int main(void)
{
    
    
    const int c_var = 10;
    const int * p = &c_var;
    cout << *p << endl;
    return 0;
}
  1. pointer to function: function pointer
#include <iostream>
using namespace std;

int add(int a, int b){
    
    
    return a + b;
}

int main(void)
{
    
    
    int (*fun_p)(int, int);
    fun_p = add;
    cout << fun_p(1, 6) << endl;
    return 0;
}
  1. Pointers to object members, including pointers to object member functions and pointers to object member variables.
    Special attention: When defining a pointer to a member function, it is necessary to indicate the class to which the pointer belongs.
#include <iostream>

using namespace std;

class A
{
    
    
public:
    int var1, var2; 
    int add(){
    
    
        return var1 + var2;
    }
};

int main()
{
    
    
    A ex;
    ex.var1 = 3;
    ex.var2 = 4;
    int *p = &ex.var1; // 指向对象成员变量的指针
    cout << *p << endl;

    int (A::*fun_p)();
    fun_p = A::add; // 指向对象成员函数的指针 fun_p
    cout << (ex.*fun_p)() << endl;
    return 0;
}
  1. this pointer: A pointer constant pointing to the current object of the class.
#include <iostream>
#include <cstring>
using namespace std;

class A
{
    
    
public:
    void set_name(string tmp)
    {
    
    
        this->name = tmp;
    }
    void set_age(int tmp)
    {
    
    
        this->age = age;
    }
    void set_sex(int tmp)
    {
    
    
        this->sex = tmp;
    }
    void show()
    {
    
    
        cout << "Name: " << this->name << endl;
        cout << "Age: " << this->age << endl;
        cout << "Sex: " << this->sex << endl;
    }

private:
    string name;
    int age;
    int sex;
};

int main()
{
    
    
    A *p = new A();
    p->set_name("Alice");
    p->set_age(16);
    p->set_sex(1);
    p->show();

    return 0;
}
  1. What are wild pointers and dangling pointers?
    dangling pointer:

If the pointer points to a memory space, after the memory space is released, the pointer still points to the memory space. At this time, the pointer is called a "dangling pointer".

void *p = malloc(size);
free(p); 
// 此时,p 指向的内存空间已释放, p 就是悬空指针。

wild pointer:

"Wild pointer" refers to a pointer whose point is uncertain, and an uninitialized pointer is a "wild pointer".

void *p; 
// 此时 p 是“野指针”。

6.5 Advantages of C++11 nullptr over NULL

  • NULL: The preprocessing variable is a macro whose value is 0 and defined in the header file, ie #define NULL 0.
  • nullptr: A keyword in C++11, which is a special type of literal that can be converted to any other type.

Advantages of nullptr:

  1. There are types, the type is typdef decltype(nullptr) nullptr_t;, use nullptr to improve the robustness of the code.
  2. Function overloading: Because NULL is essentially 0, in the process of function calling, if the function is overloaded and the actual parameter passed is NULL, it may appear that it does not know which function matches; but passing the actual parameter nullptr will not This happens.

6.6 What is the difference between a pointer and a reference?

  • The memory space pointed to by the pointer can be changed during the running of the program, but the object bound by the reference cannot be changed once bound. (whether variable)
  • The pointer itself occupies the memory space in the memory, and the reference is equivalent to the alias of the variable, and does not occupy the memory space in the memory. (whether it occupies memory)
  • Pointers can be null, but references must bind objects. (can be empty)
  • Pointers can have multiple levels, but references can only have one level. (Can it be multi-level)

6.7 Difference between constant pointer and pointer constant

Constant pointer:
A constant pointer is essentially a pointer, except that the object pointed to by this pointer is a constant.
Features : The position of const is on the left side of the pointer declaration operator *. As long as const is to the left of *, whether it is to the left or right of the type name, it means a pointer to a constant. (It can be understood that the left side of * indicates the object pointed to by the pointer, and the object is a constant, so the pointer is a constant pointer.)

const int * p;
int const * p;

Note 1: The object pointed by the pointer cannot be modified through this pointer, that is to say, the constant pointer can be assigned as the address of the variable. The reason why it is called a constant pointer is that it limits the value of the variable through this pointer.

#include <iostream>
using namespace std;

int main()
{
    
    
    const int c_var = 8;
    const int *p = &c_var; 
    *p = 6;            // error: assignment of read-only location '* p'
    return 0;
}

Note 2: Although the object pointed to by the constant pointer cannot be changed, because the constant pointer itself is a variable, it can be reassigned.
For example:

#include <iostream>
using namespace std;

int main()
{
    
    
    const int c_var1 = 8;
    const int c_var2 = 8;
    const int *p = &c_var1; 
    p = &c_var2;
    return 0;
}

Pointer constant:
A pointer constant is essentially a constant, except that the value of this constant is a pointer.
Features: const is located on the right side of the pointer declaration operator, indicating that the object itself is a constant, and the left side of * indicates the type pointed to by the pointer, that is, with * as the dividing line, the left side indicates the type pointed to by the pointer, and the right side indicates the pointer itself nature.

const int var;
int * const c_p = &var; 

Note 1: The value of a pointer constant is a pointer. Because this value is a constant, the pointer itself cannot be changed.

#include <iostream>
using namespace std;

int main()
{
    
    
    int var, var1;
    int * const c_p = &var;
    c_p = &var1; // error: assignment of read-only variable 'c_p'
    return 0;
}

Note 2: The content of the pointer can change.

#include <iostream>
using namespace std;

int main()
{
    
    
    int var = 3;
    int * const c_p = &var;
    *c_p = 12; 
    return 0;
}

6.8 Difference between function pointer and pointer function

Pointer function:
A pointer function is essentially a function, except that the return value of the function is a pointer. Compared with ordinary functions, only the return value is a pointer.

#include <iostream>
using namespace std;

struct Type
{
    
    
  int var1;
  int var2;
};

Type * fun(int tmp1, int tmp2){
    
    
    Type * t = new Type();
    t->var1 = tmp1;
    t->var2 = tmp2;
    return t;
}

int main()
{
    
    
    Type *p = fun(5, 6);
    return 0;
}

Function pointer:
A function pointer is essentially a pointer variable, except that the pointer points to a function. A function pointer is a pointer to a function.

Example:

#include <iostream>
using namespace std;
int fun1(int tmp1, int tmp2)
{
    
    
  return tmp1 * tmp2;
}
int fun2(int tmp1, int tmp2)
{
    
    
  return tmp1 / tmp2;
}

int main()
{
    
    
  int (*fun)(int x, int y); 
  fun = fun1;
  cout << fun(15, 5) << endl; 
  fun = fun2;
  cout << fun(15, 5) << endl; 
  return 0;
}
/*
运行结果:
75
3
*/

The difference between function pointer and pointer function:

  • The essence is different
    1. The pointer function is essentially a function whose return value is a pointer.
    2. The function pointer is essentially a pointer variable, which points to a function.
  • The definition forms are different
    1. Pointer function: int* fun(int tmp1, int tmp2); where * means that the return value type of the function is a pointer type.
    2. Function pointer: int ( fun)(int tmp1, int tmp2);, here indicates that the variable itself is a pointer type.
  • usage is different

6.9 What types of mandatory type conversions are there?

  • static_cast : Used for mandatory type conversion of data, forcibly converting one data type to another data type.
    1. Used for conversion of basic data types.
    2. It is used for the conversion of pointers or references between base classes and derived classes between class hierarchies (it is not required to contain virtual functions, but must be classes that are related to each other), and perform uplink conversion (pointer or reference conversion of derived classes It is safe to perform downcasting (the pointer or reference of the base class is converted into the representation of the derived class) because there is no dynamic type check, so it is unsafe, and it is best to use dynamic_cast for downcasting.
    3. A null pointer can be converted into a null pointer of the target type.
    4. Any type of expression can be converted into void type.
  • const_cast : Forcibly remove the constant attribute, can not be used to remove the constant of the variable, can only be used to remove the constant of the pointer or reference, convert the constant pointer to a non-constant pointer or convert a constant reference to a non-constant reference (note: the expression is the same type as the type to be converted).
  • reinterpret_cast : Change the type of pointer or reference, convert the pointer or reference to an integer of sufficient length, and convert the integer to a pointer or reference type.
  • dynamic_cast :
    1. The other three types are completed at compile time. Dynamic type conversion is processed when the program is running, and type checking will be performed at runtime.
    2. It can only be used for the conversion of pointers or reference objects of base classes or derived classes with virtual functions. If the conversion succeeds, it returns a pointer or reference to the type, and if the conversion fails, it returns NULL; it cannot be used for the conversion of basic data types.
    3. When converting upward, that is, the pointer of the derived class is converted to the pointer of the base class, and the effect of static_cast is the same. (Note: here only the type of the pointer is changed, and the type of the object pointed to by the pointer has not changed) .
    4. During the downstream conversion, the pointer type of the base class is converted to the pointer type of the derived class. Only when the object type pointed to by the pointer to be converted is the same as the converted object type, the conversion will succeed.
#include <iostream>
#include <cstring>

using namespace std;

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

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

int main()
{
    
    
    Base *p1 = new Derive();
    Base *p2 = new Base();
    Derive *p3 = new Derive();

    //转换成功
    p3 = dynamic_cast<Derive *>(p1);
    if (p3 == NULL)
    {
    
    
        cout << "NULL" << endl;
    }
    else
    {
    
    
        cout << "NOT NULL" << endl; // 输出
    }

    //转换失败
    p3 = dynamic_cast<Derive *>(p2);
    if (p3 == NULL)
    {
    
    
        cout << "NULL" << endl; // 输出
    }
    else
    {
    
    
        cout << "NOT NULL" << endl;
    }

    return 0;
}

6.10 How to judge whether the structures are equal? Can the memcmp function be used to judge structure equality?

Operator == needs to be overloaded to judge whether two structures are equal, and the function memcmp cannot be used to judge whether two structures are equal, because the memcmp function is compared byte by byte, and there are characters when the structure is saved in the memory space Section alignment, when byte alignment, the complemented byte content is random, which will generate garbage values, so it cannot be compared.

Use operator overloading to compare struct objects:

#include <iostream>

using namespace std;

struct A
{
    
    
    char c;
    int val;
    A(char c_tmp, int tmp) : c(c_tmp), val(tmp) {
    
    }

    friend bool operator==(const A &tmp1, const A &tmp2); //  友元运算符重载函数
};

bool operator==(const A &tmp1, const A &tmp2)
{
    
    
    return (tmp1.c == tmp2.c && tmp1.val == tmp2.val);
}

int main()
{
    
    
    A ex1('a', 90), ex2('b', 80);
    if (ex1 == ex2)
        cout << "ex1 == ex2" << endl;
    else
        cout << "ex1 != ex2" << endl; // 输出
    return 0;
}

6.11 When passing parameters, what is the difference between passing by value, passing by reference, and passing by pointer?

Three ways to pass parameters:

  • Passing by value: The formal parameter is a copy of the actual parameter, and all operations of the function on the formal parameter will not affect the actual parameter.
  • Pointer passing: It is essentially value passing, except that the value of the pointer is copied. After copying, the actual parameter and the formal parameter are different pointers. Through the pointer, the object pointed to by the pointer can be accessed indirectly, so that the object it points to can be modified. value.
  • Pass by reference: When a formal parameter is a reference type, we say that its corresponding actual parameter is passed by reference.

6.12 What is a template? How to achieve?

Template : Create a blueprint or formula for a class or function, divided into function templates and class templates.
Implementation : A template definition begins with the keyword template , followed by a list of template parameters.

  • The template parameter list cannot be empty;
  • The keyword class or typename must be used before the template type parameter. In the template parameter list, these two keywords have the same meaning and can be used interchangeably.
template <typename T, typename U, ...>

Function templates: By defining a function template, you can avoid defining a new function for each type.

  • For function templates, the template type parameter can be used to specify the return type or parameter type of the function, as well as variable declaration or type conversion in the function body.
  • Function template instantiation: When calling a template, the compiler uses the function arguments to infer the template arguments, thereby using the types of the arguments to determine the types bound to the template parameters.
#include<iostream>

using namespace std;

template <typename T>
T add_fun(const T & tmp1, const T & tmp2){
    
    
    return tmp1 + tmp2;
}

int main(){
    
    
    int var1, var2;
    cin >> var1 >> var2;
    cout << add_fun(var1, var2);

    double var3, var4;
    cin >> var3 >> var4;
    cout << add_fun(var3, var4);
    return 0;
}

Class templates: Like function templates, class templates begin with the keyword template, followed by a list of template parameters. However, the compiler cannot infer the template parameter type for a class template, and the type needs to be specified in angle brackets after the template name when using the class template.

#include <iostream>

using namespace std;

template <typename T>
class Complex
{
    
    
public:
    //构造函数
    Complex(T a, T b)
    {
    
    
        this->a = a;
        this->b = b;
    }

    //运算符重载
    Complex<T> operator+(Complex &c)
    {
    
    
        Complex<T> tmp(this->a + c.a, this->b + c.b);
        cout << tmp.a << " " << tmp.b << endl;
        return tmp;
    }

private:
    T a;
    T b;
};

int main()
{
    
    
    Complex<int> a(10, 20);
    Complex<int> b(20, 30);
    Complex<int> c = a + b;

    return 0;
}

6.13 What is the difference between a function template and a class template?

  • The instantiation methods are different: the instantiation of function templates is automatically completed by the compiler when processing function calls, and the instantiation of class templates needs to be explicitly specified in the program.
  • The result of instantiation is different: the function template is a function after instantiation, and the class template is a class after instantiation.
  • Default parameters: Class templates can have default parameters in the template parameter list.
  • Specialization: Function templates can only be fully specialized; class templates can be fully or partially specialized.
  • The calling methods are different: function templates can be called implicitly or explicitly; class templates can only be called explicitly.
    Example of calling a function template:
#include<iostream>

using namespace std;

template <typename T>
T add_fun(const T & tmp1, const T & tmp2){
    
    
    return tmp1 + tmp2;
}

int main(){
    
    
    int var1, var2;
    cin >> var1 >> var2;
    cout << add_fun<int>(var1, var2); // 显式调用

    double var3, var4;
    cin >> var3 >> var4;
    cout << add_fun(var3, var4); // 隐式调用
    return 0;
}

6.14 What are variadic templates?

Variadic template: A template function or class that accepts a variable number of arguments. The variable number of parameters is called a parameter pack, including template parameter packs and function parameter packs.

  • Template parameter pack: represents zero or more template parameters;
  • Function parameter pack: Indicates zero or more function parameters.

Use an ellipsis to indicate that a template parameter or function parameter indicates a package, and in a template parameter list, class... or typename... indicates that the following parameters indicate a list of zero or more types; a typename followed by an ellipsis indicates zero or a list of multiple non-type parameters of the given type. When you need to know how many elements are in the bag, you can use the sizeof... operator.

template <typename T, typename... Args> // Args 是模板参数包
void foo(const T &t, const Args&... rest); // 可变参数模板,rest 是函数参数包
#include <iostream>

using namespace std;

template <typename T>
void print_fun(const T &t)
{
    
    
    cout << t << endl; // 最后一个元素
}

template <typename T, typename... Args>
void print_fun(const T &t, const Args &...args)
{
    
    
    cout << t << " ";
    print_fun(args...);
}

int main()
{
    
    
    print_fun("Hello", "wolrd", "!");
    return 0;
}
/*运行结果:
Hello wolrd !

*/

Explanation: Variadic functions are usually recursive, the first version of print_fun is responsible for terminating the recursion and printing the last argument in the initial call. The second version of print_fun is the variadic version that prints the arguments bound to t and is used to call itself to print the remaining values ​​in the function's argument pack.

6.15 What is template specialization? Why specialize?

Reasons for template specialization: Templates are not suitable for any template argument and can be instantiated. In some cases, the definition of a general template is not suitable for a specific type, and compilation may fail, or the correct result may not be obtained. Thus, a specialization of a class or function template can be defined when the template version is not desired.

Template specialization: The concrete implementation of a template parameter under a specific type. Divided into function template specialization and class template specialization

  • Function template specialization: specialization of all types in the function template is called function template specialization.
  • Class template specialization: specialization of some or all types in the class template is called class template specialization.

Specialization is divided into full specialization and partial specialization:

  • Full specialization: All template parameters in the template are specialized.
  • Partial specialization: Only part of the template parameters in the template are determined, and the rest needs to be determined when the compiler compiles.

Explanation: To distinguish between function overloading and function template specialization
, defining a specialized version of a function template essentially takes over the work of the compiler and defines a special instance for the original function template instead of function overloading. Function template specialization Normalization does not affect function matching.

#include <iostream>
#include <cstring>

using namespace std;
//函数模板
template <class T>
bool compare(T t1, T t2)
{
    
    
    cout << "通用版本:";
    return t1 == t2;
}

template <> //函数模板特化
bool compare(char *t1, char *t2)
{
    
    
    cout << "特化版本:";
    return strcmp(t1, t2) == 0;
}

int main(int argc, char *argv[])
{
    
    
    char arr1[] = "hello";
    char arr2[] = "abc";
    cout << compare(123, 123) << endl;
    cout << compare(arr1, arr2) << endl;

    return 0;
}
/*
运行结果:
通用版本:1
特化版本:0
*/

6.16 The difference between include " " and <>

The difference between include<filename> and #include "filename" :

  • Find the location of the file: include<file name> search in the directory where the standard library header file is located, if not, then search in the directory where the current source file is located; #include "file name" search in the directory where the current source file is located, If not; then look in the system directory.
  • Usage habits: For header files in the standard library, include<filename> is often used, and for header files defined by yourself, #include "filename" is often used

6.17 How to implement generic programming?

The foundation of generic programming: templates. A template is a blueprint or formula for creating a class or function. When using a generic type like vector or a generic function like find, it will be converted into a specific class or function during compilation.

Generic programming involves a wide range of knowledge points, for example: containers, iterators, algorithms, etc. are all implementation examples of generic programming. The interviewer can choose to expand on the aspect that he or she has a solid grasp of.

  • Container: It involves containers in STL, such as: vector, list, map, etc., and you can choose to explain the containers that are familiar with the underlying principles.
  • Iterator: Traverse the elements in the container without knowing the underlying principle of the container.
  • Templates: You can refer to template-related issues in this chapter.

6.18 C++ Namespaces

The purpose of using namespaces is to localize the names of identifiers to avoid naming conflicts. In C++, variables, functions, and classes exist in large numbers. If there is no namespace, the names of these variables, functions, and classes will all exist in the global namespace, causing many conflicts. For example, if we define a function functionA() in our program, this will override the functionA() function in the standard library, because both functions are located in the global namespace

Guess you like

Origin blog.csdn.net/luanfenlian0992/article/details/120022236