[C/C++ Learning] Summary of Common Interview Knowledge Points

Table of contents

1. Computer Basics 1. What types of C/C++ memory are there?

2. What is the difference between heap and stack?

3. What is the difference between heap and free storage area?

4. What is the process of program compilation?

 5. How do negative numbers and floating point numbers be stored internally in a computer?

6. What is the process of function calling? 

7. Lvalues ​​and rvalues

8. What is a memory leak? What methods do you have in the face of memory leaks and pointer out-of-bounds? What methods do you usually use to avoid and reduce these types of errors?

2. C VS C++

1. What is the difference between C and C++?

2. What is the difference between int fun() and int fun(void)?

3. Purpose of const

4. Can real constants be defined using const in C? What about const in C++?

5. Comparison between macros and inline functions? 

6. With malloc/free in C++, why do we still need new/delete?

7. Casting in C and C++?

expand

 8. The purpose of static

9. What are the characteristics of class static member variables and static member functions? 

static member variables

static member function

10. When calling a function compiled by a C compiler in a C++ program, why do we need to add extern "C"? 

11. What is the use of ifndef/define/endif in the header file? What is the difference between this usage and program once? 

12. When i is an integer, which one is faster, ++i or i++? What is the difference between i++ and ++i?

 3. Arrays, pointers & references

1. What is the difference between pointers and references?

2. Do references occupy memory space?

3. Ternary operator

4. The difference between pointer array and array pointer

5. lvalue reference and rvalue reference

6. The meaning of rvalue reference

4. C++ features

4. What is the execution order of constructors and destructors?

5. What is the role of virtual destructor?

6. Take a closer look at the copy constructor

7. C++ compilation environment

5. STL

1. STL six components

2. There are pop() and top() methods in the stack. Why not directly use pop() to realize the pop-up and value-retrieval functions?

3. What is the difference between map and unordered_map? What are the advantages and disadvantages of each?


1. Computer Basics
1. What types of C/C++ memory are there?

        In C language, memory is divided into 5 areas: heap (malloc), stack (such as local variables, function parameters), program code area (storage binary code), global/static storage area (global variables, static variables) and constants Storage area (constant).

In addition, there is a free storage area (new) in C++. Global variables and static variables will be initialized to default values, while variables on the heap and stack are random and uncertain.

2. What is the difference between heap and stack?

  1. The heap stores dynamically allocated objects - that is, those objects that are dynamically allocated when the program is running, such as new objects, whose lifetime is controlled by the program;
  2. The stack is used to save non-static objects defined within a function, such as local variables, which only exist when the program block in which they are defined is run;
  3. Static memory is used to save static objects, class static data members and variables defined outside any function. Static objects are allocated before use and destroyed when the program ends;
  4. Stack and static memory objects are automatically created and destroyed by the compiler.

3. What is the difference between heap and free storage area?

        In general, the heap is a terminology for the C language and operating system. It is a dynamically allocated memory maintained by the operating system; free storage is an abstract concept in C++ that dynamically allocates and releases objects through new and delete. They are not exactly the same.
Technically, heap is the term for the C language and operating system. The heap is a special memory maintained by the operating system. It provides the function of dynamic allocation. When the running program calls malloc(), it will be allocated from it. The memory can be returned by calling free later. Free storage is an abstract concept in C++ that dynamically allocates and releases objects through new and delete. The memory area applied for through new can be called a free storage area. Basically, all C++ compilers use the heap to implement free storage by default, that is, the default global operators new and delete may be implemented in the manner of malloc and free. In this case, the objects allocated by the new operator , it is also correct to say that it is on the heap, and it is also correct to say that it is on the free storage area.

4. What is the process of program compilation?

        The process of program compilation is the process of converting the user's text source code (c/c++) into machine code that can be directly executed by the computer. It mainly goes through four processes: preprocessing, compilation, assembly and linking. Specific examples are as follows.
A C language program of hello.c is as follows.

#include <stdio.h>
int main()
{
    printf("happy new year!\n");
    return 0;
}

Insert image description here

 5. How do negative numbers and floating point numbers be stored internally in a computer?

        Negative numbers are represented by a flag and two's complement.
        Expansion questions:

  • What is complement code? Answer: The complement of a negative number is the complement plus 1, and the complement of a positive number is the original code.
  • Why use complement for negative numbers? Answer: Unified addition and subtraction, positive and negative zero problems

        Floating-point data is stored using single-precision type (float) and double-precision type (double). Float data occupies 32 bits and double data occupies 64 bits. How do we allocate memory when we declare a variable float f= 2.25f? What about? If it is allocated randomly, wouldn't the world be in chaos? In fact, both float and double comply with IEEE specifications in terms of storage methods. Float complies with IEEE R32.24, while double complies with R64.53. For more information, see Floating Point Number Representation.
Whether it is single precision or double precision, storage is divided into three parts:

  • 1). Sign bit (Sign): 0 represents positive, 1 represents negative
  • 2). Exponent bit (Exponent): used to store exponent data in scientific notation, and uses shift storage
  • 3). Mantissa (Mantissa): The mantissa part
    is stored as shown in the figure below:

Insert image description here

 The storage method of double precision is as follows:

Insert image description here

 

6. What is the process of function calling? 

Code with the following structure:

int main(void)
{
  ...
  d = fun(a, b, c);
  cout<<d<<endl;
  ...
  return 0;
}

The process of calling fun() is roughly as follows:

main()========
1). Copy parameters (push onto the stack), note that the order is from right to left, that is, cba;
2). Save the next instruction of d = fun(a, b, c) , that is, cout<<d<<endl (actually the starting position of the assembly instruction corresponding to this statement);
3). Jump to the fun() function. Note that so far, these are all in main() carried out in;
fun()======
4). Move ebp and esp to form a new stack frame structure;
5). Push the stack to form temporary variables and perform related operations;
6). Return a value;
7 ).Pop;
8).Restore the stack frame structure of the main function;
9).Return to the main function;
main()========

7. Lvalues ​​and rvalues

        Not very strictly speaking, lvalue refers to a variable (or expression) that can appear on the left side of the equal sign as well as on the right side of the equal sign, and rvalue refers to a variable that can only appear on the right side of the equal sign ( or expression). For example, the variable a we defined is an lvalue, and malloc returns an rvalue. Or an lvalue is something that can be addressed in the program, and an rvalue is a specific real value or object whose address cannot be obtained (not completely accurate), so the rvalue cannot be assigned, but Rvalues ​​are not unmodifiable. For example, a self-defined class can modify rvalues ​​through its member functions.

To sum it up:

        Those that can take addresses, have names, and are non-temporary are lvalues
        ​​that cannot take addresses. Those that have no name, are temporary, and usually have a life cycle within a certain expression are rvalues.

8. What is a memory leak? What methods do you have in the face of memory leaks and pointer out-of-bounds? What methods do you usually use to avoid and reduce these types of errors?

        The space dynamically opened using the dynamic storage allocation function is not released after use. As a result, the memory unit is always occupied, which is a memory leak.

1). Remember the length of the pointer when using it.
2). Make sure it is free when malloc.
3). When assigning a value to a pointer, you should pay attention to whether the assigned pointer needs to be released.
4). Pointers to dynamically allocated memory It is best not to assign values ​​again.
5). Smart pointers should be given priority in C++.
9. What smart pointers are there in C++11? How is reference counting of shared_ptr implemented? How is the uniqueness of unique_ptr implemented? What are the functions of make_shared and make_unique? What are the precautions for using smart pointers?
Smart pointers in C++ 11 are: shared_ptr, unique_ptr and weak_ptr.

The reference count of shared_ptr is stored on the heap, and the reference counts of multiple shared_ptr objects point to the same heap address.

The copy constructor and assignment operator in unique_ptr are both declared as delete or private.

The reason for using make_shared and make_unique in preference is to avoid memory leaks. Refer to Smart Pointer (shared_ptr/weak_ptr/unique_ptr) in C++11 Summary

Things to note when using smart pointers:

Do not initialize with the same built-in pointer value, or reset multiple smart pointers

Do not delete the pointer returned by get()

Do not use get() to initialize or reset another smart pointer

The smart pointer returned by get() may become a dangling pointer

If the memory managed by the smart pointer is not new, a deleter needs to be provided.

Extension question
: Is shared_ptr thread-safe?
Intrusive smart pointers?

2. C VS C++

1. What is the difference between C and C++?

1). C++ is a superset of C;
2). C is a structured language, its focus is on algorithms and data structures. The primary consideration in the design of a C program is how to calculate the input (or environmental conditions) through a process to obtain the output (or implement process (transaction) control). For C++, the primary consideration is how to construct an object model so that This model can fit the corresponding problem domain, so that output or process (transaction) control can be achieved by obtaining the state information of the object.

2. What is the difference between int fun() and int fun(void)?

What is examined here is the default type mechanism in c.

In c, int fun() will be interpreted as the return value is int (this is true even if there is no int in front, but in c++ an error will be reported if there is no return type). There is no limit on the input type and number, and int fun(void) Then limit the input type to a void.
Under C++, both cases will be interpreted as returning int type and entering void type.

3. Purpose of const

There are three main points:

1). Define read-only variables or constants (refer to the following article for the difference between read-only variables and constants);
2). Modify the parameters of the function and the return value of the function;
3). Modify the definition body of the function, where the function is a class Member functions, member functions modified by const mean that the value of member variables cannot be modified, so const member functions can only call const member functions, and non-const members can be accessed, but cannot be modified; 4). Read-only objects
. Read-only objects can only call const member functions.

class Screen {
public:
const char cha; //const成员变量
char get() const; //const成员函数
};

const Screen screen; //只读对象

4. Can real constants be defined using const in C? What about const in C++?

cannot. Const in c is only limited from the compilation layer. Assignment operations to const variables are not allowed. It is invalid during runtime, so it is not a real constant (for example, the value of a const variable can be modified through a pointer), but in c++ There is a difference in C++. When compiling, C++ will add const constants to the symbol table. When encountering this variable later (still during compilation), it will be searched from the symbol table, so it is impossible to modify const variables in C++.
Replenish:

1). Local const constants in c are stored in stack space, and global const constants exist in read-only storage areas, so global const constants cannot be modified. It is a read-only variable.
2). What needs to be explained here is that constants are not just unmodifiable, but relative to variables, their values ​​have been determined at compile time, not at runtime.
3) There is a difference between const and macro definitions in c++. Macros directly perform text replacement during the pre-compilation period, while const occurs during the compilation period and can be type checked and scope checked.
4) Only enum in C language can realize real constants.
5). In C++, only const constants initialized with literals will be added to the symbol table, while const constants initialized by variables are still only read-only variables.
6). The const member in C++ is a read-only variable. The value of the const member can be modified through a pointer. In addition, the const member variable can only be initialized in the initialization list.

Let's look at the difference through code.
For the same piece of code, under the c compiler, the printed result is *pa = 4, a = 4.
Under the c++ compiler, the printed result is *pa = 4, a = 8.

int main(void)
{
    const int a = 8;
    int *pa = (int *)&a;
    *pa = 4;
    printf("*pa = %d, a = %d", *pa, a);
    return 0;
}

It is also worth mentioning that since the value of const constants in C++ has been determined at compile time, the following approach is OK, but it cannot be compiled in C.


int main(void)
{
    const int a = 8;
    const int b = 2;
    int array[a+b] = {0};
    return 0;
}

In addition, constexpr was introduced in C++ 11, which is specifically used to represent constants (before this, const represented read-only and constants). Therefore, after C++11, it is recommended to use constexpr in all scenarios with "constant" semantics, and use const only for "read-only" semantics. For functions modified by constexpr, the result can be calculated at compile time (provided that the things it depends on can also be calculated at compile time). For more information, please refer to: What is the difference between C++ const and constexpr?

5. Comparison between macros and inline functions? 

1). First of all, macro is a preprocessing function introduced in C;
2). Inline (inline) function is a new keyword introduced in C++; in C++, it is recommended to use inline functions to replace macro code snippets;
3 ). Inline functions extend the function body directly to the place where the inline function is called, which reduces the process of parameter pushing, jumping, returning, etc.; 4). Since inlining
occurs in the compilation stage, inline compared to macros, There are parameter checks and return value checks, so it is safer to use;
5). It should be noted that inline will make an inline request to the compiler, but whether to inline is determined by the compiler (of course you can set the compiler , forcing the use of inlining);
6). Since inlining is an optimization method, in some cases, even if there is no explicit declaration of inlining, such as a method defined inside a class, the compiler may treat it as inline function.
7). Inline functions should not be too complex. Initially, C++ was restricted to not have any form of loops, too many conditional judgments, or address-taking operations on functions, etc. However, current compilers have almost no restrictions, and basically everything is fine. Implement inline.
For more information, please refer to the introduction to the usage of the inline keyword in C++

6. With malloc/free in C++, why do we still need new/delete?

1). malloc and free are standard library functions of C++/C language, and new/delete are operators of C++. They can both be used to apply for dynamic memory and release memory.
2). For objects of non-internal data types (custom types), maloc/free alone cannot meet the requirements of dynamic objects. The object must automatically execute the constructor when it is created, and the object must automatically execute the destructor before it dies.
Since malloc/free is a library function rather than an operator, it is not within the control of the compiler. The task of executing constructors and destructors cannot be imposed on malloc/free. Therefore, the C++ language needs an operator new that can complete dynamic memory allocation and initialization work, and an operator delete that can complete the work of cleaning up and releasing memory. Note that new/delete are not library functions.
Finally, a little digression, new can be initialized when applying for memory (code below), but malloc is not allowed. In addition, since malloc is a library function and requires corresponding library support, some simple platforms may not support it. However, new does not have this problem because new is an operator that comes with the C++ language.

int *p = new int(1);

Specifically, in C++, the following code uses new to create an object (new will trigger the constructor, delete will trigger the destructor), but malloc only applies for a space, so new and delete are introduced in C++ to support oriented object.

#include <cstdlib>
class Test
{
    ...
}

Test* pn = new Test;
Test* pm = (Test*)malloc(sizeof(Test));

7. Casting in C and C++?

In C, the target type (enclosed in parentheses) is directly added in front of the variable or expression to perform conversion. It is easy to operate, but it is too direct and lacks checks, so it is easy to cause compilation and unchecked errors. , and manual inspection is extremely difficult to detect; and the following four conversions are introduced in C++:

1). static_cast
        a. Used for conversion between basic types
        b. Cannot be used for conversion between basic type pointers
        c. Used for conversion between class objects with inheritance relationships and conversion between class pointers
2). dynamic_cast
        a. Used for Conversion between class pointers with inheritance relationship
        b. Used for conversion between class pointers with cross-relationship
        c. Has type checking function
        d. Requires the support of virtual functions
3). reinterpret_cast
        a. Used for type conversion between pointers
        b. . Used for type conversion between integers and pointers
4). const_cast
        a. Used to remove the const attribute of a variable
        b. The target type of conversion must be a pointer or reference

expand

        In C++, ordinary types can be converted to class types through type conversion constructors, so can classes be converted to ordinary types? The answer is yes. However, type conversion functions are generally not used in engineering applications because implicit calls to type conversion functions cannot be suppressed (type conversion constructors can be suppressed from being called implicitly through explicit), and implicit calls are often the source of bugs. The alternative in actual projects is to define a common function and achieve type conversion through explicit calls.

class test{
    int m_value;
    ...
public:
    operator int()  //类型转换函数
    {
        return m_value;
    }

    int toInt() //显示调用普通函数来实现类型转换
    {
        return m_value
    }
};

int main()
{
    ...
    test a(5);
    int i = a;	// 相当于 int i = test::operator int(&a)
    ...

    return 0;
}

 8. The purpose of static

  • 1). Static (local/global) variables
  • 2). Static function
  • 3). Static data members of the class
  • 4). Static member functions of the class

9. What are the characteristics of class static member variables and static member functions? 

static member variables

  • 1). Static member variables need to be declared within the class (add static) and initialized outside the class (static cannot be added), as shown in the following example;
  • 2). Static member variables are allocated storage space separately outside the class and are located in the global data area. Therefore, the life cycle of static member variables does not depend on an object of the class, but all objects of the class share static member variables;
  • 3). Public static member variables can be directly accessed through the object name;
  • 4). Public static member variables can be called directly through the class name, that is, there is no need to pass the object, which is not available in ordinary member variables.
class example{
public:
static int m_int; //static成员变量
};

int example::m_int = 0; //没有static

cout<<example::m_int; //可以直接通过类名调用静态成员变量

static member function

 1). Static member functions are shared by classes;
2). Static member functions can access static member variables, but cannot directly access ordinary member variables (need to access through objects); it should be noted that ordinary member functions can access ordinary member variables. Member variables can also access static member variables;
3). Public static member functions can be directly accessed through the object name;
4). Public static member functions can be directly called through the class name, that is, there is no need to pass the object, which is an ordinary member function do not have. 

 

class example{
private:
static int m_int_s; //static成员变量
int m_int;
static int getI() //静态成员函数在普通成员函数前加static即可
{
  return m_int_s; //如果返回m_int则报错,但是可以return d.m_int是合法的
}
};

cout<<example::getI(); //可以直接通过类名调用静态成员变量

10. When calling a function compiled by a C compiler in a C++ program, why do we need to add extern "C"? 

The C++ language supports function overloading, but the C language does not support function overloading. The name of the function in the library after being compiled by the C++ compiler is different from that of the C language. Assume that the prototype of a function is:

          void foo(int x, int y);

The function's name in the library after being compiled by the C compiler is _foo, while the C++ compiler will generate names like: _foo_int_int. In order to solve this kind of name matching problem, C++ provides the C link exchange designated symbol extern "C".

11. What is the use of ifndef/define/endif in the header file? What is the difference between this usage and program once? 

Similarity:
        Their function is to prevent header files from being included repeatedly.
difference:

        1). ifndef is supported by the language itself, but program once is generally supported by the compiler. In other words, there may be situations where the compiler does not support it (mainly older compilers).
        2). Generally, ifndef is slower than program once in terms of running speed. Especially in large projects, the difference will be obvious, so more and more compilers are beginning to support program once.
        3). ifndef acts on a certain section of code that is included (between define and endif), while program once targets the file containing the statement, which is why program once is faster.
        4). If you use ifndef to include a certain macro definition, when the macro name appears "crash", the macro may prompt that the macro is undefined in the program (you need to pay special attention when writing large programs, because there are many programs members are writing code at the same time). On the contrary, since program once targets the entire file, there is no macro name "crash". However, if a header file is copied multiple times, program once cannot guarantee that it will not be included multiple times, because program once physically determines whether it is The same header file, not from the content.

12. When i is an integer, which one is faster, ++i or i++? What is the difference between i++ and ++i?

Answer: Theoretically, ++i is faster, but it actually depends on compiler optimization. There is usually almost no difference.

//i++实现代码为:
int operator++(int)
{
    int temp = *this;
    ++*this;
    return temp;
}//返回一个int型的对象本身

// ++i实现代码为:
int& operator++()
{
    *this += 1;
    return *this;
}//返回一个int型的对象引用

There are many test points for i++ and ++i. To put it simply, i++ returns the value of i, while ++i returns the value of i+1. That is to say, ++i is a definite value and a modifiable lvalue, which can be used as follows:

cout << ++(++(++i)) << endl;
cout << ++ ++i << endl;

 You can nest ++i continuously.
There are many classic written test questions here, let’s take a look at them:

int main()
{
    int i = 1;
    printf("%d,%d\n", ++i, ++i);    //3,3
    printf("%d,%d\n", ++i, i++);    //5,3
    printf("%d,%d\n", i++, i++);    //6,5
    printf("%d,%d\n", i++, ++i);    //8,9
    system("pause");
    return 0;
}

First, the parameters of the function are pushed onto the stack from right to left, and the calculation order is also calculated from right to left, but they are all pushed onto the stack after the calculation is completed:

For the first printf, first execute ++i, the return value is i, then the value of i is 2, execute ++i again, the return value is i, get i=3, push i onto the stack, at this time i is 3, that is, 3 and 3 are pushed in;
for the second printf, i++ is executed first, and the return value is the original i, which is 3. Then ++i is executed, and the return value is i, and 3 and 5 are pushed in sequence. Push the output result into the stack to get the output result.
For the third printf, first execute i++, the return value is 5, then execute i++ and the return value is 6. Push 5 and 6 onto the stack in turn to get the output result. For the fourth printf, first execute
+ +i, return i, i is 8 at this time, and then execute i++, the return value is 8, i is 9 at this time, push i, 8, that is, 9, 8 into the stack in turn to get the output result.
The above analysis is also based on VS, but to be precise, the order of evaluation of function arguments is undefined (the order of evaluation of function arguments are undefined). The results of written test questions vary with different compilers.

Here is another typical application case of i++.

    map<char, int> b = {
   
   {'a', 1}, {'b', 2}};

    for(auto iter = b.begin(); iter != b.end();){
        if(iter->first == 'a'){
            b.erase(iter++);	// 等价于 auto t = iter; iter = iter + 1; b.erase(t);
        }
        else{
            iter++;
        }
    }

 3. Arrays, pointers & references

1. What is the difference between pointers and references?

Same point:

        1). They are all concepts of address;
        2). They all “point to” a piece of memory. The pointer points to a piece of memory, and its content is the address of the pointed memory; while the reference is the alias of a certain piece of memory;
        3). References are implemented internally with the help of pointers. In some cases, references can replace pointers, such as function parameters.
Differences:
        1). A pointer is an entity, while a reference (seemingly, this is important) is just an alias;
        2). A reference can only be initialized once when it is defined, and is immutable thereafter; a pointer is mutable; a reference "from "Once and for all", the pointer can "change according to different ideas";
        3). The reference cannot be empty, the pointer can be empty;
        4). "sizeof reference" gets the size of the variable (object) pointed to, and "sizeof pointer" gets is the size of the pointer itself;
        5). The meaning of the increment (++) operation of pointers and references is different;
        6). References are type-safe, but pointers are not (references have more type checks than pointers)
        7). References Has better readability and practicality.

2. Do references occupy memory space?

In the following code, the address of the reference is actually the address of the memory space corresponding to the reference. This phenomenon makes it seem as if the reference is not an entity. However, references occupy memory space, and they occupy the same memory as pointers, because the internal implementation of references is completed through pointers.

For example, Type& name; <===> Type* const name.

int main(void)
{
        int a = 8;
        int &b = a;
        int *p = &b;		// 等价于 int *p = &a;
        *p = 0;
        cout<<a; //output 0
   		return 0;
}

3. Ternary operator

 In C, the result of the ternary operator (? :) can only be used as an rvalue. For example, the following method will report an error under the C compiler, but it can pass in C++. This progress is achieved through references, because the return result of the following ternary operator is a reference, and then assignment to the reference is allowed.

int main(void)
{
        int a = 8;
        int b = 6;
        (a>b ? a : b) = 88;
        cout<<a; //output 88
    return 0;
}

4. The difference between pointer array and array pointer

An array pointer is a pointer to an array, and a pointer array means that the elements of the array are all pointers.

An array pointer is a pointer to an array. Its essence is a pointer and its form is as follows. For example, int (*p)[n], p is the pointer to the array, () has high priority. First, p is a pointer, pointing to a one-dimensional array of integers. The length of this one-dimensional array is n, so It can be said to be the step size of p. That is to say, when executing p+1, p must span the length of n integer data. The array pointer is a pointer pointing to the address of the first element of the array. It is essentially a pointer and can be regarded as a secondary pointer.

类型名 (*数组标识符)[数组长度]

Pointer array. In C language and C++, an array whose array elements are all pointers is called a pointer array. The definition of a one-dimensional pointer array is as follows. Each element in the pointer array is a pointer, and its essence is an array. For example, int *p[n], [] has high priority. It is first combined with p to form an array, and then int* indicates that this is an integer pointer array, which has n array elements of pointer type. When p+1 is executed here, p points to the next array element. This assignment is wrong: p=a; because p is an unknown representation, only p[0], p[1], p[2]... p[n-1], and they are pointer variables that can be used to store variable addresses. But it can be like this *p=a; Here *p represents the value of the first element of the pointer array and the value of the first address of a.

类型名 *数组标识符[数组长度]

5. lvalue reference and rvalue reference

An lvalue reference is what we usually call a reference, as shown below. An lvalue reference can usually be thought of as an alias for a variable.

type-id & cast-expression 

// demo
int a = 10
int &b = a

int &c = 10	// 错误,无法对一个立即数做引用

const int &d = 10	// 正确, 常引用引用常数量是ok的,其等价于 const int temp = 10; const int &d = temp	

Rvalue reference is a new feature of C++11, and its form is as follows. An rvalue reference is used to bind to an rvalue. The lifetime of an rvalue that would have been destroyed after being bound to an rvalue will be extended to the lifetime of the rvalue reference bound to it.

type-id && cast-expression  

// demo
int &&var = 10;	// ok

int a = 10
int &&b = a	// 错误, a 为左值

int &&c = var	// 错误,var 为左值

int &&d = move(a)	// ok, 通过move得到左值的右值引用

 At the assembly level, rvalue references do the same thing as constant references, that is, they generate temporary amounts to store constants. However, the only difference is that rvalue references can perform read and write operations, while constant references can only perform read operations.

6. The meaning of rvalue reference

Rvalue references support the implementation of move semantics, which can reduce copies and improve program execution efficiency.

The following code is implemented without rvalue references.

class Stack
{
public:
    // 构造
    Stack(int size = 1000) 
		:msize(size), mtop(0)
    {
			cout << "Stack(int)" << endl;
			mpstack = new int[size];
    }
	
    // 析构
    ~Stack()
    {
			cout << "~Stack()" << endl;
			delete[]mpstack;
			mpstack = nullptr;
    }
	
    // 拷贝构造
    Stack(const Stack &src)
		:msize(src.msize), mtop(src.mtop)
    {
			cout << "Stack(const Stack&)" << endl;
			mpstack = new int[src.msize];
			for (int i = 0; i < mtop; ++i) {
	   			 mpstack[i] = src.mpstack[i];
			}
    }
	
    // 赋值重载
    Stack& operator=(const Stack &src)
    {
			cout << "operator=" << endl;
			if (this == &src)
     	    	return *this;

			delete[]mpstack;

			msize = src.msize;
			mtop = src.mtop;
			mpstack = new int[src.msize];
			for (int i = 0; i < mtop; ++i) {
	   			 mpstack[i] = src.mpstack[i];
			}
			return *this;
    }

    int getSize() 
    {
			return msize;
    }

private:
    int *mpstack;
    int mtop;
    int msize;
};

Stack GetStack(Stack &stack)
{
    Stack tmp(stack.getSize());
    return tmp;
}

int main()
{
    Stack s;
    s = GetStack(s);
    return 0;
}

The running results are as follows.

Stack(int)             // 构造s
Stack(int)             // 构造tmp
Stack(const Stack&)    // tmp拷贝构造main函数栈帧上的临时对象
~Stack()               // tmp析构
operator=              // 临时对象赋值给s
~Stack()               // 临时对象析构
~Stack()               // s析构

 

During the execution of the code, the copy constructor is called to copy the contents in the memory one by one. In C++11, rvalue references can be used to implement move copy constructor and move assignment to solve this problem.

Stack(Stack &&src)
    :msize(src.msize), mtop(src.mtop)
{
    cout << "Stack(Stack&&)" << endl;

    /*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/
    mpstack = src.mpstack;  
    src.mpstack = nullptr;
}

// 带右值引用参数的赋值运算符重载函数
Stack& operator=(Stack &&src)
{
    cout << "operator=(Stack&&)" << endl;

    if(this == &src)
        return *this;
	    
    delete[]mpstack;

    msize = src.msize;
    mtop = src.mtop;

    /*此处没有重新开辟内存拷贝数据,把src的资源直接给当前对象,再把src置空*/
    mpstack = src.mpstack;
    src.mpstack = nullptr;

    return *this;
}

The execution results are as follows. It can be seen that when there are copy constructors and move copy constructors, the move copy constructor and move assignment are called first. In the move copy construction and move assignment, the resource ownership is directly transferred instead of copying, which greatly improves the execution efficiency.

Stack(int)             // 构造s
Stack(int)             // 构造tmp
Stack(Stack&&)         // 调用带右值引用的拷贝构造函数,直接将tmp的资源给临时对象
~Stack()               // tmp析构
operator=(Stack&&)     // 调用带右值引用的赋值运算符重载函数,直接将临时对象资源给s
~Stack()               // 临时对象析构
~Stack()               // s析构

 

Rvalue references can make overloaded functions more concise. Rvalue references can accept parameters of the form const T& and T&.

struct W  
{  
   W(int&, int&) {}  
};  
  
struct X  
{  
   X(const int&, int&) {}  
};  
  
struct Y  
{  
   Y(int&, const int&) {}  
};  
  
struct Z  
{  
   Z(const int&, const int&) {}  
};


template <typename T, typename A1, typename A2>  
T* factory(A1& a1, A2& a2)  
{  
   return new T(a1, a2);  
} 


template <typename T, typename A1, typename A2>  
T* factory_new(A1&& a1, A2&& a2)  
{  
   return new T(std::forward<A1>(a1), std::forward<A2>(a2));  
}  

// demo
int a = 2;
int b = 2;

W* c = factory<w>(a, b);	// ok
Z* d = factory<Z>(2, 2);	// 错误,2 是右值

W* pw = factory_new<W>(a, b);	// ok
X* px = factory_new<X>(2, b);	// ok
Y* py = factory_new<Y>(a, 2);	// ok
Z* e = factory_new<Z>(2, 2);	// ok
W* f = factory_new<W>(2, 2);	// 错误, 

4. C++ features

 

1. What is object-oriented (OOP)? What does object-oriented mean?
Object Oriented Programming, Object-oriented is a method and idea for understanding and abstracting the real world, and an idea for problem solving by converting demand elements into objects. The core ideas are data abstraction, inheritance and dynamic binding (polymorphism).
The significance of object-oriented is to: introduce the habitual way of thinking in daily life into programming; intuitively map concepts in requirements to solutions; build reusable software systems centered on modules; improve the maintainability of software products performance and scalability.

2. Explain encapsulation, inheritance and polymorphism?
1). Encapsulation:
Encapsulation is the first step in realizing object-oriented programming. Encapsulation is to collect data or functions into units (we call them classes).
The meaning of encapsulation is to protect or prevent code (data) from being inadvertently destroyed by us.
From an encapsulation perspective, the characteristics of public, private and protected properties are as follows.

Regardless of the attribute, the inner class is accessible.
Public is a means of exposure, such as exposing an interface. Objects of the class can access it.
Private is a means of hiding. Objects of the class cannot access
protected members:
they can be subsumed like public. Class inheritance
, like private, cannot be called directly outside the class.
Special case: it can be accessed through the derived class object in the derived class, as shown in the following code

class Base  
{  
public:  
    Base(){};  
    virtual ~Base(){};  
protected:  
    int int_pro;  
};
class A : public Base  
{  
public:  
    A(){};  
    A(int da){int_pro = da;}  
    // 通过 obj 对象直接访问 protected 成员
    void Set(A &obj){obj.int_pro = 24;}	
    void PrintPro(){cout << "The proteted data is " << int_pro <<endl;}  
};  

2). Inheritance:
Inheritance mainly realizes code reuse and saves development time.
Subclasses can inherit some things from their parent class.
a. Public inheritance (public) The characteristic of public inheritance is that when the public members and protected members of the base class are members of the derived class, they maintain their original status (the private members of the base class are still private and cannot be used by this derived class). accessed by subclasses).
b. Private inheritance (private) The characteristic of private inheritance is that the public members and protected members of the base class are used as private members of the derived class (and cannot be accessed by subclasses of this derived class).
c. Protected inheritance (protected) The characteristic of protected inheritance is that all public members and protected members of the base class become protected members of the derived class (and can only be accessed by its derived class member functions or friends. Private members of the base class remain is private).
Special mention here of virtual inheritance. Virtual inheritance is a means to solve the problem of multiple inheritance in C++ (first, a waste of storage space; second, there is an ambiguity problem). For example, diamond inheritance, a typical application is iostream, which inherits from istream and ostream, and istream and ostream inherit from ios.

3).Polymorphism: Polymorphism
refers to the act of dynamically calling the actual bound object function at runtime through a pointer or reference of the base class. The corresponding compile-time binding function is called static binding. Polymorphism is the basis of design patterns, and polymorphism is the basis of frameworks.

3. When is the default constructor (no-argument constructor) generated? When is the default copy constructor generated? What is a deep copy? What is a shallow copy? What kind of copy is the default copy constructor? When to use deep copy?
1). When there is no constructor, the compiler will automatically generate a default constructor, which is a parameterless constructor; when the class does not have a copy constructor, a default copy constructor will be generated.
2). Deep copy means that the logical state of the object after copying is the same, while shallow copy means that the physical state of the object after copying is the same; the default copy constructor is a shallow copy.
3). When there are members in the system that refer to resources in the system, a deep copy is required. For example, pointing to the dynamic memory space, opening a file in external memory or using the network interface in the system, etc. If deep copying is not performed, for example, dynamic memory space may be released multiple times. The principle of whether a copy constructor needs to be defined is whether a member of the class calls system resources. If a copy constructor is defined, it must be a deep copy, otherwise it is meaningless.
You can refer to the following code for more information. What is easier to confuse is the assignment operator. In fact, the difference is very simple. When the equal sign appears, if a new object is constructed, the constructor is called, otherwise the assignment operator is called. Pay special attention to b and f below, one is copy construction and the other is construction.

class A {
public:
	A() {
		m = new int[4]{ 1,2,3,4 };
		std::cout << "constructor" << std::endl;
	}
	~A() {
		if (m != nullptr) {
			delete[] m;
		}
	}
	A(const A& a) {
		this->m = new int[4];
		memcpy(a.m, this->m, this->len * sizeof(int));
		std::cout << "copy constructor" << std::endl;
	}
	// 移动构造
    A(A&& a) : m(a.m) {
		a.m = nullptr; 
		std::cout << "move constructor" << std::endl;
	}
    // 赋值操作符重载
    A& operator= (const A& a) {
        memcpy(a.m, this->m, this->len * sizeof(int));
        std::cout << "operator" << std::endl;
        return *this;
    }
private:
    int len = 4;
    int* m = nullptr;
};
A getA(A a) {
    return a;
}
int main(void)
{
    A a;    // construct
    
    A b = a;    // copy construct
    A c(a); // copy construct
    
    A d;    // construct
    d = a;  // operate
    A e = getA(a);  // construct, move construct
	A f = A();	// construct
    return 0;
}

 

4. What is the execution order of constructors and destructors?

Constructor
1). First call the constructor of the parent class;
2). Call the constructor of the member variable;
3). Call the constructor of the class itself.
Destructor
For stack objects or global objects, the calling order is exactly the opposite of the calling order of the constructor, that is, the destructor is destructed first. For heap objects, the destruction order is related to the delete order.

5. What is the role of virtual destructor?

Using virtual destructors in base classes can prevent memory leaks. For example, in the following code, if there is no virtual destructor in base class A, the destructor of B will not be called, thus causing a memory leak.

class A{
public:
  A(){}
  //~A(){}
  virtual ~A(){cout << "A disconstruct" << endl;}  // 虚析构
//   ~A(){cout << "A disconstruct" << endl;}  // 析构

};

class B : public A{
public:
  B(){
    // new memory
    // ...
    cout << "B construct" << endl;
  }
  ~B(){
    // delete memory
    // ...
    cout << "B disconstruct" << endl;
  }
};

int main(int argc, char **argv)
{
  A *p = new B;
  
  // some operations
  // ...
  
  delete p;  // 由于基类中是虚析构,这里会先调用B的析构函数,然后调用A的析构函数
  
  return 0;
}

But it is not necessary to write the destructors of all classes as virtual functions . Because when there are virtual functions in a class, the compiler will add a virtual function table to the class to store virtual function pointers, which will increase the storage space of the class. Therefore, only when a class is used as a base class , the destructor should be written as a virtual function.

6. Take a closer look at the copy constructor

For class A, its copy constructor is as follows:

 A::A(const A &a){}

1) Why does it have to be a reference to the current class?
Loop call. If the parameter of the copy constructor is not a reference to the current class, but an object of the current class, then when the copy constructor is called, another object will be passed directly to the formal parameter, which itself is a copy, and the copy constructor will be called again. , and then an object is passed directly to the formal parameter, and the copy constructor will continue to be called... This process will continue without end, falling into an infinite loop.

Only when the parameter is a reference to the current class, it will not cause the copy constructor to be called again. This is not only a logical requirement, but also a requirement of C++ syntax.

2) Why is it a const reference?
The purpose of the copy constructor is to initialize the current object with the data of other objects, and does not expect to change the data of other objects. After adding the const restriction, this meaning becomes clearer.

Another reason is that after adding the const restriction, const objects and non-const objects can be passed to formal parameters, because non-const types can be converted to const types. Without the const restriction, you cannot pass const objects to formal parameters, because const types cannot be directly converted to non-const types, which means that you cannot use const objects to initialize the current object.

7. C++ compilation environment

As shown in the figure below, the C++ compilation environment consists of the following parts: C++ standard library, C language compatibility library, compiler extension library and compilation module.
Insert image description here

#include<iostream>  //C++标准库,不带".h"
#include<string.h>  //C语言兼容库,由编译器厂商提供

 It is worth noting that the C language compatibility library is functionally the same as the C language sub-library in the C++ standard library. It is mainly stored to be compatible with the C language compiler. That is to say, if a file only contains the C language compatibility library (not including C++ standard library), then it can still be compiled and passed in the C language compiler.

5. STL

1. STL six components

        The six major components of STL: Container, Algorithm, Iterator, Function object, Adaptor and allocator.

2. There are pop() and top() methods in the stack. Why not directly use pop() to realize the pop-up and value-retrieval functions?

        If the stack stores large content, such as vector type, copying will occur when the value is retrieved. If the copy fails, then, assuming there is a stack<vector>, vector is a dynamic container. When you copy a vector, the standard library will allocate a lot of memory from the heap to complete this copy. When the system is under heavy load, or has severe resource constraints, this memory allocation will fail, so the vector copy constructor may throw a std::bad_alloc exception. This is more likely to happen when there are a large number of elements in the vector. When the pop() function returns the "pop value" (that is, removes the value from the stack), there is a potential problem: the stack is not changed until the value is returned to the calling function; but when the data is copied What happens if an exception is thrown when calling a function? If this were to happen, the data being popped would be lost; it was indeed removed from the stack, but the copy failed! The designers of std::stack divided this operation into two parts: first getting the top element (top()), and then removing the element from the stack (pop()). In this way, when the element cannot be copied safely, the data in the stack still exists and is not lost. When the problem is insufficient heap space, the app may free some memory before trying again.

Reference: Why do the member functions top() and pop() in the adapter stack need to be implemented separately?

3. What is the difference between map and unordered_map? What are the advantages and disadvantages of each?

        The internal implementation of map is a red-black tree (a red-black tree is a non-strictly balanced binary search tree, while AVL is a strictly balanced binary search tree), which has the following properties:

The red-black tree has automatic sorting function, so all elements inside the map are ordered.

The time complexity of search, insertion and deletion is log(n)

The elements in the map are based on a binary search tree (also known as a binary search tree and a binary sorting tree). The characteristic is that the key values ​​of all nodes in the left subtree are less than the key values ​​of the root node, and the key values ​​of all nodes in the right subtree are are greater than the key value of the root node), use in-order traversal to traverse the key values ​​from small to large.

The internal implementation of unordered_map is a hash table. It has the following properties:

The average time complexity of search, insertion, and deletion can reach O(1). The
establishment of a hash table is time-consuming and takes up more memory than a red-black tree
. Generally, map is used because the construction of unordered_map is time-consuming. For search problems, unordered_map will be more efficient, so when encountering search problems, unordered_map is often considered first.

Question expansion:

What are red and black numbers? A red-black tree is a binary search tree, but a storage bit is added to each node to represent the color of the node, which can be red or black (either red or black). By restricting the way each node is colored on any path from the root to a leaf, the red-black tree ensures that no path is twice as long as any other path. Therefore, the red-black tree is a weakly balanced binary tree, relative to the requirements For a strict AVL tree, its number of rotations is small, so when there are many search, insertion, and deletion operations, a red-black tree is usually used.

What is AVL? Red-black trees are proposed based on AVL trees. Balanced binary tree, also known as AVL tree, is a special binary sorting tree. The left and right subtrees are both balanced binary trees, and the absolute value of the height difference between the left and right subtrees does not exceed 1. The absolute value of the difference in height between the left and right subtrees of all nodes in the AVL tree as the root of the tree does not exceed 1. The value of the depth of the left subtree minus the depth of the right subtree of a node on a binary tree is called the balance factor BF. Then the balance factors of all nodes on the balanced binary tree can only be -1, 0 and 1. As long as the absolute value of the balance factor of a node in the binary tree is greater than 1, the binary tree is unbalanced.

Why does map use red-black trees instead of AVL? The AVL tree is highly balanced. Frequent insertion and deletion will cause frequent rebalance, leading to a decrease in efficiency. The red-black tree is not highly balanced, which is a compromise. The performance of search, insertion and deletion is O(logn). And the performance is stable (insert up to two rotations, delete up to three rotations).

Guess you like

Origin blog.csdn.net/qq_35097289/article/details/124661567