30 Essential C++ Interview Questions and Answers

Today, I will share some classic C++ interview questions, hoping they can help everyone.

1. The relationship between new, delete, malloc, and free

malloc and free are standard library functions in C/C++ language, while new/delete are operators in C++.

new calls the constructor, while delete will call the destructor of the object, whereas free only releases memory.

They can all be used to allocate and release dynamic memory. However, for non-internal data type objects, simply using malloc/free cannot meet the requirements for dynamic objects. When an object is created, the constructor must be executed automatically, and the destructor must be executed automatically before the object is destroyed. Since malloc/free are library functions and not operators, they cannot impose the task of executing the constructor and destructor.

Therefore, C++ requires an operator new that can complete dynamic memory allocation and initialization, and an operator delete that can handle cleanup and memory release. Note: new/delete are not library functions.

2. The difference between delete and delete []

delete only calls the destructor once, while delete[] will call the destructor for each member function.

More Effective C++ provides a more detailed explanation: “When the delete operator is used for an array, it calls the destructor for each array element and then calls operator delete to release the memory.” delete is paired with new, and delete[] is paired with new [].

MemTest *mTest1=new MemTest[10];

MemTest *mTest2=new MemTest;

int *pInt1=new int [10];

int *pInt2=new int;

delete[]pInt1; //-1-

delete[]pInt2; //-2-

delete[]mTest1;//-3-

delete[]mTest2;//-4-

Error occurs at -4-.

This indicates that for built-in simple data types, delete and delete[] function the same. For custom complex data types, delete and delete[] cannot be used interchangeably. delete[] deletes an array, while delete deletes a pointer. In simple terms, memory allocated with new should be deleted with delete; memory allocated with new[] should be deleted with delete[]. delete[] will call the destructors of the array elements. Internal data types do not have destructors, so the issue is not significant. If you do not use parentheses with delete, it will assume you are pointing to a single object; otherwise, it will assume you are pointing to an array.

3. What are the characteristics of C++ (object-oriented features)?

Encapsulation, inheritance, and polymorphism.

4. Should the destructor of a subclass call the destructor of the parent class?

The order of destructor calls is that the derived class destructor is called before the base class destructor, meaning that when the base class destructor is called, all information from the derived class has been destroyed. When defining an object, the base class constructor is called first, followed by the derived class constructor; during destruction, the order is reversed: the derived class destructor is called first, then the base class destructor.

5. Introduce polymorphism, virtual functions, and pure virtual functions.

Polymorphism: It refers to different actions when different objects receive the same message. The polymorphism of C++ is specifically manifested in two aspects: runtime polymorphism is embodied through inheritance and virtual functions; compile-time polymorphism is reflected in function and operator overloading;

Virtual functions: Member functions in the base class prefixed with the keyword virtual. They provide an interface. They allow redefining the base class’s virtual function in derived classes.

The role of pure virtual functions: They retain a function name in the base class for derived classes to define as needed. Pure virtual functions, existing as interfaces, do not possess function functionality and generally cannot be called directly.

Pure virtual functions inherited from the base class remain virtual functions in the derived class. If a class has at least one pure virtual function, it is called an abstract class.

An abstract class can include both pure virtual functions and virtual functions. An abstract class must be used as a base class for deriving other classes and cannot be used to create object instances directly. However, pointers to abstract classes can still be used to support runtime polymorphism.

Note:

Defining a function as a virtual function does not imply that the function is not implemented. Defining it as a virtual function allows using a pointer to the base class to call this function in the subclass. Defining a function as a pure virtual function indicates that the function has not been implemented.

6. What is the return value of the following function (Microsoft)?

int func(x)
{
    int countx = 0;
    while(x)
    {
        countx++;
        x = x & (x - 1);
    }
    return countx;
}

Assuming x = 9999. Answer: 8

Thought process: Convert x to binary and count the number of 1s.

7. What is a “reference”? What issues should be noted when declaring and using “references”?

A reference is an alias for a target variable; operations performed on it are identical to operations performed on the variable directly. When declaring a reference, remember to initialize it. Once a reference is declared, it effectively means that the target variable has two names: the original name and the reference name; the reference name cannot be used as an alias for another variable name. Declaring a reference does not create a new variable; it simply indicates that the reference name is an alias for the target variable name. A reference is not a data type itself, as it does not occupy a storage unit, and the system does not allocate a storage unit for the reference. References cannot be established for arrays.

8. What are the characteristics of using “references” as function parameters?

(1) Passing a reference to a function has the same effect as passing a pointer. In this case, the parameter of the called function becomes an alias for the actual variable or object in the calling function, so operations on the parameter in the called function are actually operations on the corresponding target object (in the calling function).

(2) Using reference to pass function parameters does not create a copy of the actual parameter in memory; it operates directly on the actual parameter. When passing general variables as function parameters, memory units must be allocated for the parameters, and the parameter variable is a copy of the actual parameter; if passing an object, the copy constructor will also be called. Therefore, when the data passed as parameters is large, using references is more efficient and consumes less space than using general variables.

(3) Using pointers as function parameters can also achieve the same effect as using references, but the called function must also allocate memory units for the parameters and repeatedly use the “*pointer variable” form for operations, which is prone to errors and makes the program less readable; on the other hand, at the calling point in the calling function, the address of the variable must be used as the actual parameter. References are easier to use and clearer.

9. When should “const references” be used?

If you want to utilize references to improve program efficiency while also protecting the data passed to the function from being changed within the function, you should use const references. The declaration of const reference is: const type identifier & reference name = target variable name;

Example 1

int a;
const int &ra = a;
ra = 1; // Error
 a = 1; // Correct

Example 2

string foo();
void bar(string &s);

Then the following expressions will be invalid:

bar(foo());
bar("hello world");

The reason is that foo() and the string “hello world” will both create temporary objects, which are const types in C++. Therefore, the above expressions attempt to convert a const type object into a non-const type, which is illegal. Reference parameters should be defined as const whenever possible.

10. What is the format, benefits, and rules to follow when returning “references” as function return types?

Format: type identifier & function name (parameter list and type description) { // function body }

Benefits: No copies of the returned value are created in memory; (Note: precisely because of this reason, returning a reference to a local variable is inadvisable, as the lifetime of the local variable ends with the function return, causing the reference to become “dangling”, leading to runtime errors!

Notes:

(1) Cannot return a reference to a local variable. This can be referenced in Effective C++[1] Item 31. The main reason is that local variables are destroyed after the function returns, making the returned reference a “dangling” reference, leading to an unknown state in the program.

(2) Cannot return a reference to memory allocated with new within the function. This can also be referenced in Effective C++[1] Item 31. Although there is no problem with the passive destruction of local variables, this situation (returning a reference to memory allocated with new within the function) faces other awkward situations. For example, the returned reference may only appear as a temporary variable and not be assigned to an actual variable, meaning that the space pointed to by this reference (allocated with new) cannot be released, causing a memory leak.

(3) Can return a reference to class members, but it is best to be const. This principle can be referenced in Effective C++[1] Item 30. The main reason is that when an object’s attributes are associated with a certain business rule, their assignment often relates to other attributes or the object’s state. Therefore, it is necessary to encapsulate the assignment operation within a business rule. If other objects can obtain a non-const reference (or pointer) to this attribute, a simple assignment to this attribute could violate the integrity of the business rule.

(4) The effect of declaring the return value of stream operator overloads as “reference”: The stream operators << and >> are often expected to be used consecutively, e.g., cout << “hello” << endl; Therefore, the return value of these operators should be a stream reference that still supports these two operators. Other optional solutions include returning a stream object and returning a pointer to a stream object. However, returning a stream object requires reconstructing a new stream object, meaning that consecutive << operators are actually targeting different objects! This is unacceptable. Returning a stream pointer would also prevent the consecutive use of << operators. Therefore, returning a stream object reference is the only choice. This unique choice is crucial, as it demonstrates the importance and irreplaceability of references, and perhaps this is the reason why the concept of references was introduced in the C++ language.

Assignment operator =. This operator, like stream operators, can be used consecutively, e.g., x = j = 10; or (x=10)=100; the return value of the assignment operator must be an lvalue to allow further assignment. Thus, references became the only choice for the return value of this operator.

#include<iostream>
using namespace std;
int &put(int n);
int vals[10];
int error=-1;
int main()
{
    put(0)=10; // Treating put(0) function value as an lvalue, equivalent to vals[0]=10;
    put(9)=20; // Treating put(9) function value as an lvalue, equivalent to vals[9]=20;
    cout<<vals[0];
    cout<<vals[9];
    return 0;
}
int &put(int n)
{
    if (n>=0 && n<=9 ) return vals[n];
    else
    {
        cout<<"subscript error";
        return error;
    }
}

(5) In some other operators, returning references is strictly prohibited: the arithmetic operators +, -, *, and /. They cannot return references, as discussed in detail in Effective C++[1] Item 23. The main reason is that these four operators have no side effects, so they must construct an object as a return value. Optional solutions include returning an object, returning a reference to a local variable, returning a reference to an object allocated with new, or returning a reference to a static object. Based on the previously mentioned three rules for returning references, options 2 and 3 are both negated. The reference to a static object is also negated due to the issue that ((a+b) == (c+d)) will always be true, leading to errors. Therefore, the only remaining option is to return an object.

11. What is the difference between structures and unions?

(1). Both structures and unions consist of multiple different data type members, but at any given moment, a union only stores one selected member (all members share the same address space), while all members of a structure exist (different members occupy different addresses).

(2). Assigning different members in a union will overwrite the values of other members, while assigning different members in a structure does not affect each other.

12. Write the program result:

int a=4;
int &f(int x)
{
    a = a + x;
    return a;
}
int main()
{
    int t = 5;
    cout<<f(t)<<endl;  // a = 9
    f(t) = 20;           // a = 20
    cout<<f(t)<<endl;  // t = 5, a = 25
    t = f(t);            // a = 30, t = 30
    cout<<f(t)<<endl;  // t = 60
    return 0;
}

13. What is the difference between overload and override?

From the definition:

Overloading: It refers to allowing multiple functions with the same name, but with different parameter lists (either different number of parameters, different types of parameters, or both).

Overriding: It refers to redefining a parent class’s virtual function method in a subclass.

From the implementation principle:

Overloading: The compiler modifies the name of the same function based on different parameter lists, making these functions different (at least for the compiler). For example, if there are two functions with the same name: function func(p:integer):integer; and function func(p:string):integer;. The compiler modifies the function names to something like int_func, str_func. The calls to these two functions are determined during compilation, which is static. In other words, their addresses are bound at compile time (early binding).

Overriding: When a subclass redefines a parent class’s virtual function, the base class pointer dynamically calls the subclass’s function based on the different subclass pointers assigned to it. This function call cannot be determined during compilation (the address of the subclass’s virtual function cannot be given). Therefore, the function address is bound at runtime (late binding).

14. In what situations can only initialization lists be used instead of assignment?

When a class contains const or reference member variables; when the base class’s constructor requires initialization lists.

15. Is C++ type-safe?

No. Different types of pointers can be forcibly converted (using reinterpret cast). C# is type-safe.

16. What code is executed before the main function?

Constructors of global objects are executed before the main function.

17. Describe memory allocation methods and their differences?

1) Allocate from the static storage area. Memory is allocated when the program is compiled, and this memory exists for the entire duration of the program’s execution. For example, global variables, static variables.

2) Create on the stack. Local variable storage units within a function can be created on the stack, and these storage units are automatically released when the function execution ends. Stack memory allocation operations are built into the processor’s instruction set.

3) Allocate from the heap, also known as dynamic memory allocation. The program can request any amount of memory using malloc or new during runtime, and the programmer is responsible for when to use free or delete to release memory. The lifetime of dynamic memory is determined by the programmer, making it very flexible but also prone to problems.

18. Write comparison statements for variables a of type bool, int, float, and pointer with “zero”.

1. bool: if(!a) or if(a) 2. int: if(a == 0) 3. float: const EXPRESSION EXP = 0.000001 4. if (a < EXP && a > -EXP) 5. pointer: if(a != NULL) or if(a == NULL)

19. What are the advantages of const compared to #define?

The role of const: to define constants, to modify function parameters, and to modify function return values. Items modified by const are protected, preventing unintended changes and improving program robustness.

1) const constants have data types, while macro constants do not. The compiler can perform type safety checks on the former, while the latter only performs character replacement without type safety checks, and character replacement may cause unexpected errors.

2) Some integrated debugging tools can debug const constants, but cannot debug macro constants.

20. Briefly describe the differences between arrays and pointers?

Arrays are either created in the static storage area (like global arrays) or on the stack. Pointers can point to any type of memory block at any time.

(1) Differences in modifying content

char a[] = "hello";
a[0] = 'X';
char *p = "world"; // Note: p points to a constant string
p[0] = 'X'; // The compiler cannot detect this error, leading to a runtime error

(2) The operator sizeof can calculate the size (in bytes) of an array. sizeof(p), where p is a pointer, yields the size of a pointer variable, not the size of the memory pointed to by p. C/C++ languages cannot determine the memory size pointed to by a pointer unless it is remembered when allocating memory. Note that when an array is passed as a function parameter, it automatically decays into a pointer of the same type.

char a[] = "hello world";
char *p = a;

// Calculate the memory capacity of arrays and pointers
cout<< sizeof(a) << endl; // 12 bytes
cout<< sizeof(p) << endl; // 4 bytes

// Passing an array as a function parameter
void Func(char a[100])
{
    cout<< sizeof(a) << endl; // 4 bytes, not 100 bytes
}

21. What are the differences between references and pointers?

  1. References must be initialized; pointers do not have to be.

  2. Once initialized, references cannot be changed; pointers can change the object they point to.

  3. There are no references pointing to null values, but there are pointers pointing to null values.

22. What problems arise if the destructor of the base class is not a virtual function?

The destructor of the derived class will not be used, causing resource leaks.

23. What are the differences between global variables and local variables? How are they implemented? How do the operating system and compiler know?

Different lifecycles:

Global variables are created and destroyed with the main program; local variables exist within local functions, loops, etc., and disappear upon exit.

Different usage methods:

Global variables can be accessed by all parts of the program after declaration; local variables can only be used locally; they are allocated in the stack.

Different memory allocation locations:

Global variables are allocated in the global data segment and loaded when the program starts running. Local variables are allocated in the stack.

24. Write the complete strcpy function:

If the total score for writing a standard strcpy function is 10, the following are several answers with different scores:

2 points

void strcpy(char *strDest, char *strSrc)
{
    while ((*strDest++ = *strSrc++) != '\0');
}

4 points

void strcpy(char *strDest, const char *strSrc) // Adding const to the source string indicates it is an input parameter, +2 points
{
    while ((*strDest++ = *strSrc++) != '\0');
}

7 points

void strcpy(char *strDest, const char *strSrc)
{
    // Add non-zero assertions for source and destination addresses, +3 points
    assert((strDest != NULL) && (strSrc != NULL));
    while ((*strDest++ = *strSrc++) != '\0');
}

10 points

// To enable chain operations, return the destination address, +3 points!
char * strcpy(char *strDest, const char *strSrc)
{
    assert((strDest != NULL) && (strSrc != NULL));
    char *address = strDest;
    while ((*strDest++ = *strSrc++) != '\0');
    return address;
}

25. Why do standard header files have structures like the following?

#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */

The purpose of the compilation macro in the header file

#ifndef __INCvxWorksh
#define __INCvxWorksh
#endif

is to prevent being included multiple times.

As an object-oriented language, C++ supports function overloading, while procedural language C does not. The name of a function compiled by C++ differs from that compiled by C. For example, if a function prototype is:

void foo(int x, int y);

The name in the symbol library compiled by the C compiler would be _foo, while the C++ compiler would produce names like _foo_int_int. Such names include the function name and the number and type of function parameters, and C++ uses this mechanism to implement function overloading.

To achieve mixed programming between C and C++, C++ provides the extern “C” linkage specification to resolve name matching issues. By adding extern “C” before the function declaration, the compiler will compile the function as _foo in the C language format, allowing C functions to call C++ functions.

26. What are the sizes of class in various situations?

class A {};
int main(){
  cout<<sizeof(A)<<endl; // Outputs 1;
  A a;
  cout<<sizeof(a)<<endl; // Outputs 1;
  return 0;
}

The size of an empty class is 1. In C++, an empty class occupies one byte to ensure that instances of the object can be distinguished from each other. Specifically, empty classes can also be instantiated, and each instance has a unique address in memory. Therefore, the compiler implicitly adds one byte to empty classes, ensuring that instances of empty classes have unique memory addresses. When this empty class serves as a base class, its size is optimized to zero, and the size of the subclass is only the size of the subclass itself. This is known as empty base optimization.

The size of an empty class instance is equal to the size of the class, so sizeof(a)=1 byte. If a is a pointer, then sizeof(a) is the size of the pointer, which is 4 bytes.

class A { virtual void Fun(){} };
int main(){
  cout<<sizeof(A)<<endl; // Outputs 4 (32-bit machine)/8 (64-bit machine);
  A a;
  cout<<sizeof(a)<<endl; // Outputs 4 (32-bit machine)/8 (64-bit machine);
  return 0;
}

Classes with virtual functions have a virtual function table pointer __vptr, which is 4 bytes in size.

class A { static int a; };
int main(){
  cout<<sizeof(A)<<endl; // Outputs 1;
  A a;
  cout<<sizeof(a)<<endl; // Outputs 1;
  return 0;
}

Static members are stored in the static storage area and do not occupy class size; ordinary functions also do not occupy class size.

class A { int a; };
int main(){
  cout<<sizeof(A)<<endl; // Outputs 4;
  A a;
  cout<<sizeof(a)<<endl; // Outputs 4;
  return 0;
}

class A { static int a; int b; };
int main(){
  cout<<sizeof(A)<<endl; // Outputs 4;
  A a;
  cout<<sizeof(a)<<endl; // Outputs 4;
  return 0;
}

Static member a does not occupy class size, so the size of the class is the size of variable b, which is 4 bytes.

27. What factors affect the size of class objects?

  1. The size of the class’s non-static member variables; static members do not occupy class space, nor do member functions;
  2. The size of additional space allocated for memory alignment; data within the class also requires memory alignment operations;
  3. If there are virtual functions, a vptr pointer will be inserted into the class object, adding the size of the pointer;
  4. If the class is a derived class of another class, the data members of the inherited base class will also exist in the space of the derived class, extending the size of the derived class.

15. What happens to the stack when the this pointer calls member variables?

When a non-static member function of a class accesses non-static members of the class, the compiler automatically passes the address of the object as an implicit parameter to the function. This implicit parameter is the this pointer.

Even if you do not write the this pointer, the compiler will add it during linking; all accesses to members are done through this.

For example, when you create multiple objects of a class and call member functions, you may not know which specific object is calling. At this point, you can check the this pointer to see which object is calling. The this pointer is pushed onto the stack first, followed by the parameters of the member function pushed onto the stack from right to left, and finally, the return address is pushed onto the stack.

28. Write the constructor, destructor, and assignment function for the class String, given the prototype:

class String
{
public:
    String(const char *str = NULL); // Normal constructor
    String(const String &other); // Copy constructor
    ~String(void); // Destructor
    String &operator =(const String &other); // Assignment function
private:
    char *m_data; // Used to store the string
};
// Normal constructor
String::String(const char *str)
{
    if(str==NULL)
    {
        m_data = new char[1];   // +points: Automatically allocate space for the null terminator '\0'
        *m_data = '\0';         // +points: Add NULL check for m_data
    }
    else
    {
        int length = strlen(str);
        m_data = new char[length+1];    // Better to add NULL check
        strcpy(m_data, str);
    }
}
// Destructor of String
String::~String(void)
{
    delete [] m_data;
}
// Copy constructor
String::String(const String &other) // +points: Input parameter is const type
{
    int length = strlen(other.m_data);
    m_data = new char[length+1];    // Better to add NULL check
    strcpy(m_data, other.m_data);
}
// Assignment function
String &String::operator =(const String &other) // +points: Input parameter is const type
{
    if(this == &other)  // +points: Check for self-assignment
        return *this;
    delete [] m_data;   // +points: Free existing memory resources
    int length = strlen(other.m_data);
    m_data = new char[length+1];    // Better to add NULL check
    strcpy(m_data, other.m_data);
    return *this;   // +points: Return reference to this object
}

29. Please list as many roles of the static and const keywords as possible?

The static keyword has at least the following five roles: (1) The scope of static variables within a function body is limited to that function body; unlike auto variables, the memory for this variable is allocated only once, so its value persists in the next call; (2) static global variables within a module can be accessed by functions within the module but not by functions outside the module; (3) static functions within a module can only be called by other functions within that module, limiting the function’s usage scope to the module declaring it; (4) static member variables within a class belong to the entire class, and there is only one copy for all objects of the class; (5) static member functions within a class belong to the entire class, and this function does not receive the this pointer, thus can only access static member variables of the class.

The const keyword has at least the following five roles: (1) To prevent a variable from being changed, the const keyword can be used. When defining the const variable, it usually needs to be initialized, as there will be no opportunity to change it later; (2) For pointers, you can specify that the pointer itself is const, that the data pointed to by the pointer is const, or both are const; (3) In a function declaration, const can modify parameters, indicating they are input parameters and cannot be changed in the function; (4) For member functions of a class, if specified as const, it indicates that it is a constant function and cannot modify the member variables of the class; (5) For member functions of a class, sometimes the return value must be specified as const to ensure that the return value is not an “lvalue”. For example:

const classA operator*(const classA& a1, const classA& a2);
The return value of operator* must be a const object. If not, the following bizarre code would not compile:
classA a, b, c;
(a * b) = c; // Assigning to the result of a*b
The operation (a * b) = c clearly does not align with the programmer's intent and has no meaning.

30. Please write a C function that returns 0 if the processor is Big_endian and 1 if it is Little_endian.

int checkCPU()
{
    union w
    {
        int a;
        char b;
    } c;
    c.a = 1;
    return (c.b == 1);
}

Source | Embedded Microprocessors

Copyright belongs to the original author. If there is any infringement, please contact for deletion.


END




关于安芯教育


安芯教育是聚焦AIoT(人工智能+物联网)的创新教育平台,提供从中小学到高等院校的贯通式AIoT教育解决方案。
安芯教育依托Arm技术,开发了ASC(Arm智能互联)课程及人才培养体系。已广泛应用于高等院校产学研合作及中小学STEM教育,致力于为学校和企业培养适应时代需求的智能互联领域人才。


Leave a Comment