2023 Weile Video Technology Co., Ltd. - C++ Engineer First Round of Written Test Questions

overall evaluation

Stereotype essays have a lot of foundation and are not difficult overall. It is a little difficult for me as a rookie ( because I have returned them to the teacher ). The following answers are supplemented by chatGpt+ online related content.

1. Multiple choice questions

1. What are the default attributes of CPP class members?

Depends on access specifier like public, private, protected. Generally, if you don’t write it out, it will default to private

The details are as follows :

class MyClass{
    
    
    int a;//默认private,私有的成员变量
public:
    void pfunc();//默认public,公有的成员函数
protected:
    void prfunc();//默认protected,受保护的成员函数
}

2. The stacking sequence of a stack is A, B, C, D, E, what is the output sequence that the stack cannot appear?

EDCBA,DECBA,DCEAB,ABCDE

The stack follows the principle of first in first out, last in first out, and edge in and edge out.

Here ChatGpt did not answer well, and did not give various situations of entering and exiting.

If the stacking sequence of a stack is a, b, c, d, e, then the impossible output sequence of the stack is ( ). _Stack Impossible Output Sequence_ddrrnnpp's Blog-CSDN Blog

3. If the derived class inherits the base class in a protected manner, what is the accessibility of the protected and public members of the original base class in the derived class?

When a derived class protectedinherits from a base class, the members of the original base class remain accessible protectedin the derived class , while the members become accessible.protectedpublicprotected

This means that inside the derived class, protectedmembers of the base class can be directly accessed, while for external classes and objects, member functions of the derived class can access protectedmembers of the base class, but other classes and objects cannot be directly accessed.

Here is an example to illustrate this:

class Base {
    
    
protected:
    int protectedMember;
public:
    int publicMember;
};

class Derived : protected Base {
    
    
public:
    void foo() {
    
    
        protectedMember = 10; // 在派生类内部可以直接访问基类的protected成员
        publicMember = 20; // 在派生类内部可以直接访问基类的public成员
    }
};

int main() {
    
    
    Derived derivedObj;
    derivedObj.foo();
   
    // 在外部无法直接访问基类的protected和public成员
    // derivedObj.protectedMember; // 错误:无法访问
    // derivedObj.publicMember; // 错误:无法访问
   
    return 0;
}

In the above example, Derivedthe class protectedinherits Basethe class in a way. In Derivedthe member functions of the class foo(), you can directly access protectedMemberthe sum of the base class publicMember. However, in main()functions, we cannot directly access derivedObjthe protectedMemberand publicMembermembers. Only member functions inside derived classes and derived classes can directly access these members.

4. What are the ways to transfer values ​​in the CPP function?

In C++ functions, the parameter passing methods can be divided into the following types:

  1. Pass by Value: The function receives a copy of the actual parameter. Modifications to parameters inside the function will not affect the original data.

    Example:

    void func(int num) {
          
          
        num = 10; // 修改的是副本,不会改变原始数据
    }
    
    int main() {
          
          
        int value = 5;
        func(value);
        // value仍然是5
        return 0;
    }
    
  2. Pass by Reference: The function receives a reference to the actual parameter, and modification of the parameter will affect the original data.

    Example:

    void func(int& num) {
          
          
        num = 10; // 修改的是原始数据
    }
    
    int main() {
          
          
        int value = 5;
        func(value);
        // value变为10
        return 0;
    }
    
  3. Pass by Pointer: The function receives a pointer to the actual parameter, and the original data can be modified through the pointer.

    Example:

    void func(int* ptr) {
          
          
        *ptr = 10; // 修改的是原始数据
    }
    
    int main() {
          
          
        int value = 5;
        func(&value);
        // value变为10
        return 0;
    }
    

Which of these transfer methods to choose depends on the requirements when calling the function, and factors such as the type of parameters and whether the original data needs to be modified need to be considered.

5. For a frequently used short function, what implementation is generally used in CPP?

In C++, for frequently used short functions, common implementation methods include the following:

  1. Inline functions (inline functions): Use keywords inlineto define functions. The function of an inline function is to directly insert the code of the function at each call site, rather than executing it through a function call. This reduces the overhead of function calls and improves performance. Inline functions are suitable for functions with small function bodies and are frequently used in multiple places.
inline int add(int a, int b) {
    
    
    return a + b;
}
  1. Macros: Use preprocessor macros to define functions. Macros do simple text substitution at compile time, without the overhead of a function call. The definition of a macro can contain parameters and can be expanded into complex expressions, but be aware that the use of macros may introduce some side effects and difficult to debug problems.
#define ADD(a, b) ((a) + (b))
  1. constexpr function: A feature introduced in C++11 for functions evaluated at compile time. constexpr functions can be evaluated at compile time, thus avoiding runtime overhead. A constexpr function requires that the function body and all arguments to which the function is called must be compile-time evaluable.
constexpr int add(int a, int b) {
    
    
    return a + b;
}

These implementation methods have their own advantages and disadvantages, and choosing the appropriate method depends on specific scenarios and requirements. Inline functions are usually a relatively common and intuitive choice, but whether the compiler actually executes inline functions depends on the compiler's strategy and the complexity of the function. Macros and constexpr functions can provide better performance in some cases, but be aware of their limitations and potential problems.

6. The difference between reference and pointer in CPP

answer analysis

The correct options are:
A. The reference must be initialized, the pointer is not necessary
B. The reference cannot be modified after initialization, the pointer can
C. There is no reference to NULL, but there is a pointer to NULL

D. References and pointers are equivalent This option is incorrect.

Allow me to explain each option:

A. References must be initialized, pointers not: This is correct. In C++, a reference must be initialized at definition time, that is, a reference must refer to an object. The pointer can not be initialized when it is defined, you can declare the pointer first, and then assign it a value, or you can set the pointer to nullptr to indicate a null pointer.

B. References cannot be modified after initialization, pointers can: This is also true. Once a reference is initialized, it will always refer to the same object and the target of the reference cannot be changed. The pointer can change its point at any time, and can point to another object or a null value.

C. There is no reference to NULL, but there is a pointer to NULL: This is also true. References must refer to valid objects, not null values. The pointer can point to a null value, that is, nullptr, which means a null pointer.

D. References and pointers are equivalent: This option is incorrect. References and pointers are different concepts, and they have distinct differences in syntax and semantics, as explained above. References provide a convenient aliasing mechanism, while pointers provide greater flexibility and lower-level manipulation capabilities. Although references can be implemented through pointers, they are not equivalent.

Full version

In C++, reference (reference) and pointer (pointer) are two different concepts, and they have some important differences in usage and semantics.

  1. Definition and initialization:
    • A reference must be initialized at definition time, and once initialized, it will always refer to the same object, and the target of the reference cannot be changed. For example:int a = 10; int& ref = a;
    • Pointers can be defined without initialization, or they can point to other objects afterwards. For example:int a = 10; int* ptr = &a;
  2. Empty value (null):
    • A reference cannot refer to a null value and must always refer to a valid object.
    • A pointer can point to a null value, a null pointer (nullptr).
  3. Reassignment:
    • Once a reference is initialized, it can no longer refer to other objects, it can only always refer to the same object.
    • A pointer can change its point at any time, and can point to another object or a null value.
  4. use:
    • References are more convenient to use, and you can use references like objects without dereference operator (*).
    • Pointers need to use the dereference operator to access the object pointed to by the pointer.
  5. Space occupied:
    • A reference doesn't take up extra space, it's just an alias.
    • The space occupied by the pointer is related to the platform. It usually occupies 4 bytes in a 32-bit system and 8 bytes in a 64-bit system.
  6. Space address:
    • A reference does not have its own address, it shares the same address as the object it refers to.
    • A pointer has its own address, it stores the address of the pointed object.

To sum up, references provide a convenient way to manipulate objects, making code more concise and readable, but it has some limitations, such as the inability to reference null values ​​and the inability to change the target of a reference. Pointers are more flexible and can be changed at runtime, but you need to be more careful when using them, because the pointer may be null or point to an invalid object. When choosing to use a reference or a pointer, you can choose according to your specific needs and circumstances.

7. const symbol constant:

(1) const char *p
(2) char const *p
(3) char * const p

Explain the difference between the above three descriptors, the correct one is ()

A. (1)p is a pointer to const char, and p can be changed. But the value pointed to by p cannot be changed;

B. (2) The content pointed to by p cannot be changed;

C. (3) p is a pointer, the content pointed to by p can be changed, but p cannot be changed

D. The definitions of (1) and (2) are the same

Answer analysis: choose A

The differences between the above three descriptors are as follows:

  1. const char *p: This is a const charpointer to a type. It means that pthe character pointed to by the pointer is unmodifiable, that is, pthe value of the pointed character cannot be modified by passing. But the pointer pitself can change and point to other characters.
  2. char const *p: This is also a const charpointer to type, as described above. constThe keyword is placed charbefore, indicating charthat it is not modifiable, which is completely equivalent to the first description.
  3. char * const p: This is a constant pointerchar to type . It means that the pointer itself is not modifiable, and once it is initialized to a certain memory address, it cannot point to another address. However, the value of the pointed character can be modified through the pointer , that is, the content of the pointed character can be changed.pp

Summarize:

  • (1)and (2)are const charpointers to types. The characters pointed to cannot be modified, but the pointer itself can be modified. Note that the definitions are different.
  • (3)It is a constant pointer, the pointer itself cannot be modified, but the content of the character pointed to can be modified.

It should be noted that constthe positions of the keywords here can be swapped between types and pointers, and the result is the same, because they both mean the same thing. This is because in C++, consta modifier is interpreted with the modifier or type to its left, not with the modifier or type to its right.

Choice D is wrong because the definitions of (1)and (2)are not the same.

Although they are both const charpointers to pointers, they have a slightly different syntactic representation, i.e. consta different position of the keywords.

  • (1): constBefore char, it means a pointer to a constant.
  • (2): After constit char, it means a constant pointer.

Although they have the same meaning, they both indicate that the character pointed to by the pointer is unmodifiable, but from a grammatical point of view, their definitions are different. Therefore, option D is wrong.

The difference between const char *p, char const *p and char *const p (common interview questions)_Lawrence_121's Blog-CSDN Blog

(1) const char *p (2) char const *p (3) char * const p Explain the difference between the above three descriptions. - dartagnan - Blog Garden (cnblogs.com)

const char *p shows that p is a constant pointing to a character string__Niuke.com(nowcoder.com)

8. What is the execution result of the following code?

class Base{
    
    
    public:
    Base(){
    
    printf("Base \n");};
    virtual ~Base(){
    
    printf("~Base \n");};
};

class Derived:public Base{
    
    
    public:
    Derived(){
    
    printf("Derived \n");};
    ~Derived(){
    
    printf("~Derived \n");};
};

int main(){
    
    
    Derived derived;
}

The result is:

Base
Derived
~Derived
~Base

So how did you get it?

This is because two classes are defined in the code: Base and Derived. The derived object of the Derived class is created in the main function. When creating a derived object, the constructor of the Base class is called first, and then the constructor of the Derived class is called. Therefore, "Base" and "Derived" are printed.

When the program ends, the objects are destroyed in reverse order. The destructor of the Derived class is called first, and then the destructor of the Base class is called. So, " Derived" and " Base" are printed .

Note that there is a typo in the destructor declaration of the Base class, it should be " Base" instead of " Basse".

2. Application questions

1. Under a 32-bit system, please calculate the value of sizeof for the CPP program.

char str[] = "www.wellav.com";
char *p = str;
int n = 10;
sizeof(str) = ? (1)值是多少?
sizeof(p)   = ? (2)值是多少?
sizeof(n)   = ? (3)值是多少?
void Foo(char str[100])
{
    
    
    sizeof(str) = ? (4) 值是多少?
}
void *p = malloc(100);
sizeof(p) = ? (5)值是多少?

From the code and instructions given, the following sizeofvalues ​​can be calculated:

The value of (1) sizeof(str)is 15. strIt is a character array, which contains 14 characters, plus the ending null character \0, occupying a total of 15 bytes of memory space.

The value of (2) sizeof(p)is 4. pIt is a pointer, no matter what type of data it points to, the size of the pointer itself is fixed. On a 32-bit system, the size of a pointer is 4 bytes.

The value of (3) sizeof(n)is 4. nIs a inttype of variable, in a 32-bit system, intthe type occupies 4 bytes of memory space.

The value of (4) sizeof(str)is 4. In the function, strit is a pointer, and the array parameter passed to the function is automatically converted to a pointer. Therefore, sizeof(str)what is returned is the size of the pointer, not the size of the array. On a 32-bit system, the size of a pointer is 4 bytes.

The value of (5) sizeof(p)is 4. pIt is a void*type of pointer, no matter what type of data it points to, the size of the pointer itself is fixed. On a 32-bit system, the size of a pointer is 4 bytes.

2. Is the following code correct? If wrong, please explain why

typedef vector<int> IntArray;
IntArray array;
array.push_back(1);
array.push_back(2);
array.push_back(2);
array.push_back(4);
//删除array数组中所有的2
for(IntArray::iteratot itor=array.begin();itor!=array.end();++itor)
{
    
    
    if(2==*itor) itor=array.erase(itor);
}

answer analysis

wrong reason:

  1. In a for loop, the iterator itormay be modified inside the loop. When executing itor=array.erase(itor), if the current element is deleted, itorit will point to the next position of the deleted element, and then the auto-increment operation will be performed again in the auto-increment part of the loop itor, causing the iterator to be invalid.

Solution:

The method of std::removecombining algorithms can be used to realize the operation of deleting specified elements. The corrected code is as follows:vectorerase

typedef vector<int> IntArray;
IntArray array;
array.push_back(1);
array.push_back(2);
array.push_back(2);
array.push_back(4);
//删除array数组中所有的2
array.erase(std::remove(array.begin(), array.end(), 2), array.end());

In this way, the algorithm will be used std::removeto move all 2 to the end of the array, and then the method will be used vectorto eraseerase the part starting from the moved 2 to achieve the effect of deleting all 2.

3. When to use a pure virtual function (pure virtual function)?

Pure virtual functions are used when:

  1. Define an abstract class (Abstract Class): An abstract class is a class that cannot be instantiated, and it is mainly used as a base class for other classes. Abstract classes contain pure virtual functions, which have no actual implementation and need to be implemented in derived classes. A class can be made abstract by declaring at least one of its functions as pure virtual. Derived classes must implement pure virtual functions in order to be instantiated.

for example:

class Shape {
    
    
public:
    virtual void draw() = 0; // 纯虚函数
    virtual void calculateArea() = 0; // 纯虚函数
};

class Circle : public Shape {
    
    
public:
    void draw() override {
    
    
        // 实现绘制圆形的代码
    }

    void calculateArea() override {
    
    
        // 实现计算圆形面积的代码
    }
};

class Square : public Shape {
    
    
public:
    void draw() override {
    
    
        // 实现绘制正方形的代码
    }

    void calculateArea() override {
    
    
        // 实现计算正方形面积的代码
    }
};

int main() {
    
    
    // 不能实例化Shape对象,但可以通过指针或引用调用接口函数
    Shape* shapePtr = new Circle();
    shapePtr->draw();
    // Circle和Square类是具体派生类,可以被实例化
    Circle circle;
    Square square;
    circle.draw();
    square.calculateArea();
    return 0;
}

In the above example, Shapeit is an abstract class with pure virtual functions draw()and calculateArea()no concrete implementation. Derived classes Circleand Squaremust implement these two pure virtual functions in order to be instantiated.

  1. Interface Class (Interface Class): Pure virtual functions are used to define interface classes. Interface classes are mainly used to define a set of interface specifications, and derived classes must implement these interfaces. The functions in the interface class are all pure virtual functions, and there is no specific implementation.

for example:

class Printable {
    
    
public:
    virtual void print() const = 0; // 纯虚函数
};

class Book : public Printable {
    
    
public:
    void print() const override {
    
    
        // 打印图书信息的具体实现
    }
};

class Magazine : public Printable {
    
    
public:
    void print() const override {
    
    
        // 打印杂志信息的具体实现
    }
};

int main() {
    
    
    Book book;
    Magazine magazine;
    book.print();
    magazine.print();
    return 0;
}

In the above example, Printableit is an interface class that defines a pure virtual function print()with no concrete implementation. Derived classes Bookand functions Magazinemust implement print()the function to satisfy the interface specification. In this way, different types of printing operations can be realized, and the specific implementation is completed by each derived class.

4. What's wrong with the following function

int &pe(int r,int i)
{
    
    
    Int re = r*i;
    return re
}

answer analysis

The function below has the following problems:

  1. The function returns a reference to a local variable: A variable defined inside a function reis a local variable that is destroyed after the function finishes executing. However, the function returns rea reference to , which causes the returned reference to point to an invalid memory address. Accessing this reference will result in undefined behavior.
  2. TypeError in function declaration: Misspelled ' Intshould' in function declaration.int

The corrected function looks like this:

int pe(int r, int i)
{
    
    
   int re = r * i;
   return re;
}

In the corrected function, recorrect the type of the local variable to int, and return it as a normal return value. This ensures that a valid value is returned rather than a reference to a local variable.

5. What method is used to prevent class objects from being copied and assigned?

To prevent class objects from being copied and assigned, the following two methods can be used:

  1. Disable copy constructor and assignment operator: You can declare a class's copy constructor and assignment operator as private and provide no implementation. In this way, objects of the class cannot be copied and assigned.
class MyClass {
    
    
private:
    MyClass(const MyClass&); // 禁用拷贝构造函数
    MyClass& operator=(const MyClass&); // 禁用赋值操作符

public:
    // 其他成员和函数的定义
};

In the above code, by declaring the copy constructor and assignment operator as private, external code cannot access these functions, thereby preventing the copying and assignment of objects.

  1. Using C++11's delete keyword: The default implementations of copy constructors and assignment operators can be explicitly deleted using the delete keyword introduced in C++11.
class MyClass {
    
    
public:
    MyClass(const MyClass&) = delete; // 显式删除拷贝构造函数
    MyClass& operator=(const MyClass&) = delete; // 显式删除赋值操作符

    // 其他成员和函数的定义
};

In the above code, = deletecopying and assignment of objects are prevented by using explicit deletion of the copy constructor and the default implementation of the assignment operator.

These two methods can effectively prevent the copying and assignment of class objects, so that the objects of the class can only be created and assigned in a specific way when they are used, so as to better control the life cycle and state of the object.

6. What is the effect of using #ifndef/#define/#endif in the header file?

answer analysis

In C++, #ifndef、#define和#endifthe function of using combinations is to prevent repeated inclusion of header files, that is, to prevent the same header file from being included in the same source file multiple times.

The specific functions are as follows:

  1. #ifndef(if not defined): This command is used to check whether a macro has been defined in the current code. If the specified macro is already defined, skip the subsequent code block and enter #endifeverywhere . If the specified macro is undefined, execution continues with the subsequent block of code.
  2. #define(Define): This command is used to define a macro. In a header file, a specific macro name is usually used to identify the header file, usually using a combination of uppercase letters and underscores. The value of this macro can be empty or a non-empty value.
  3. #endif(end): This instruction indicates the end of conditional compilation. Used in pairs, it #ifndefmarks the end of the scope of conditional compilation.

Multiple inclusion of header files can be prevented by using #ifndef, #define, and #endif. When the header file is first included, since the macro is undefined, conditional compilation passes and defines the macro. When the same header file is encountered again, since the macro has already been defined, the conditional compilation will skip the content of the header file, thus avoiding the problem of repeated inclusion.

For example, the following is an example:

#ifndef MY_HEADER_H
#define MY_HEADER_H

// 头文件的内容

#endif // MY_HEADER_H

In the above example, MY_HEADER_His a custom macro name used to identify the header file. If the header file is included multiple times in the same source file, conditional compilation will pass and the macro will be defined only the first time it is included. Subsequent inclusion operations will skip the content of the header file because the macro has already been defined, thus avoiding the problem of repeated inclusion.

What is Header Duplication Protection

Double inclusion of header files refers to the situation where the same header file is included multiple times in the same source file. When a header file is included multiple times, it causes some problems and errors.

Issues and errors include:

  1. Duplicate definition: If the header file contains type definitions, global variables, macro definitions, etc., including the same header file multiple times will cause these definitions to appear repeatedly, resulting in a redefinition error.
  2. Duplicate definition of functions: If the definition of a function is included in the header file, including the same header file multiple times will result in multiple definitions of these functions, which will cause a redefinition error.
  3. Dependency errors: If multiple header files include each other, and these header files include the same header file, a circular inclusion will be formed, resulting in a compilation error.

The problem of repeated inclusion of header files can be solved by using conditional compilation directives, such as using #ifndef、#define和#endifcombinations. This can prevent the same header file from being included in the same source file multiple times, ensure that the definition and declaration of the header file only appear once, and avoid repeated definitions and compilation errors.

7. Please explain the application scenarios of the namespace keyword in CPP

In C++, the namespace keyword is used to create a namespace (namespace), which is used to separate the global scope into different namespaces to solve naming conflicts and organize code. The application scenarios of the namespace keyword are as follows:

  1. Resolution of naming conflicts: When using multiple third-party libraries or multiple modules in a program, different libraries or modules may define the same name. This can lead to naming conflicts where the compiler cannot tell which definition is being used. By placing different code in different namespaces, problems with naming conflicts can be avoided. Names in each namespace do not collide with names in other namespaces.
  2. Code organization and modularization: Namespaces can be used to organize related code together to form a logical module or subsystem. By using a namespace, related classes, functions, variables, etc. can be placed in the same namespace, making the code more structured and modular, and improving the readability and maintainability of the code.
  3. Encapsulation of third-party libraries and frameworks: When developing programs that use third-party libraries or frameworks, you can place the code of the libraries or frameworks in an independent namespace to avoid naming conflicts with your own code. This can better encapsulate the functions of the third-party library, and clearly specify the namespace when using it, improving the readability and maintainability of the code.
  4. Nested namespaces: Namespaces can be nested, that is, define another namespace in one namespace. This can further organize and divide the structure of the code to form a more fine-grained namespace.

Example:

namespace Math {
    
    
    // Math命名空间中的函数和变量
    int add(int a, int b);
    int subtract(int a, int b);

    namespace Geometry {
    
    
        // Geometry命名空间中的函数和变量
        double calculateArea(double radius);
        double calculatePerimeter(double side);
    }
}

int main() {
    
    
    int result = Math::add(3, 4);
    double area = Math::Geometry::calculateArea(2.5);
    return 0;
}

In the above example, two functions add() and subtract() are defined in the Math namespace, and the nested Geometry namespace defines the calculateArea() and calculatePerimeter() functions. By using a namespace, you can clearly specify the namespace to which the used function or variable belongs, avoiding naming conflicts, and better organizing and managing code.

8. Please tell whether the changed names of these class members are reasonable, and why?

string valueA; //姓名
int iValueB; //年龄
double m_iValueC; //分数

open question

For a given class member variable name:

  1. string valueA;- Name

The name of this member variable valueArepresents the name, which is reasonable. Variable names should be able to clearly describe what they represent, using meaningful names can increase the readability and understandability of the code.

  1. int iValueB;- age

The name of this member variable iValueBrepresents age, which is also reasonable. iThis is a common naming convention, although the type information (representing an integer) is included in the name . When naming, consider using more descriptive names, for example age, to further improve code readability.

  1. double m_iValueC;- Fraction

The name of this member variable, m_iValueCrepresenting the score, is also reasonable. mThe prefix may indicate a member variable (member), and imay indicate an integer type, although here it is a doublevariable of a type. Naming conventions for class member variables can vary by project, team, or individual, but you should choose descriptive names that convey their meaning more clearly ( old euphemism ). scoreSo for this variable it might be better to use a more descriptive name, e.g.

Summary: Try to name variables with descriptive names to make the code easier to understand and maintain. Choosing an appropriate variable name can improve the readability of the code and help other developers quickly understand the meaning and purpose of the variable.

Extension: Nomenclature

Camel Case is a common naming style in which there are no separators between words, but instead words are distinguished by capitalizing the first letter of each word. Here are some common forms of camelCase:

  1. Lower Camel Case: The first letter of the first word is lowercase, and the first letter of each subsequent word is capitalized. For example: myVariableName, firstName, totalCount.
  2. Upper Camel Case: The first letter of each word is capitalized. Usually used for class names or type names. For example: Person, MyClass, PhoneNumber.

CamelCase is widely used in many programming languages, including Java, C++, C#, JavaScript, etc.

In addition to camel case naming, common naming styles include the following:

  1. Snake Case: Connect words with underscores and use lowercase letters for each word. For example: value_a, i_value_b, m_i_value_c. Underscore nomenclature is widely used in many programming languages, including C, Python, etc.
  2. UPPER CASE: All words are capitalized and words are separated by underscores. Usually used for naming constants. For example: VALUE_A, I_VALUE_B, M_I_VALUE_C.
  3. Lower Case: All letters are in lower case, and there are no separators between words. For example: valuea, ivalueb, mivaluec. This naming style is widely used in some programming languages, such as Lisp and Scheme.
  4. Hungarian Notation: Add one or more prefixes indicating the type before the variable name, followed by camel case or underscore nomenclature. For example: strValueA, nIValueB, dM_IValueC. Hungarian notation was popular in the past, but is less common in modern programming practice.

The choice of these naming styles depends on the programming language, project conventions, and personal preference. It is important to be consistent and follow the same naming style within the same project or team to improve code readability and maintainability.

9. What does the following code output?

class A
{
    
    
public:
    virtual void fun1() {
    
     printf("in A fun1\n"); };
    void fun2() {
    
     printf("in A fun2\n"); };
};

class B :public A {
    
    
public:
    virtual void fun1() {
    
     printf("in B fun1\n"); };
    void fun2() {
    
     printf("in B fun2\n"); };
};

int main() {
    
    
    B obj;
    A* p = new B();
    obj.fun1();
    obj.fun2();
    p->fun1();
    p->fun2();
}

The results obtained are as follows

in B fun1
in B fun2
in B fun1
in A fun2

Why?

answer analysis

This is because there is an inheritance relationship between class A and class B. The functions fun1() and fun2() in class A are declared as virtual functions, and class B overrides these virtual functions by inheriting class A.

In the code, the object obj is an instance of class B, so calling obj.fun1() will call the rewritten fun1() function in class B, and output "in B fun1". Similarly, calling obj.fun2() will call the fun2() function in class B, outputting "in B fun2".

The pointer p is declared as a pointer to class A, and an object of class B is created through the new operator and assigned to the pointer p. When using the pointer p to call p->fun1(), since fun1() is declared as a virtual function in class A and rewritten in class B, the fun1() function in class B will be called, and the output "in B fun1". However, when the pointer p is used to call p->fun2(), since fun2() is not declared as a virtual function in class A, the corresponding function will be called according to the type of pointer (class A), and the output will be output "in A fun2".

To sum up, the call of a virtual function depends on the actual type of the object pointed to by the pointer or reference, not the type of the pointer or reference itself. Non-virtual function calls depend on the type of pointer or reference.

3. Comprehensive questions

1. What are the 7 layers of the OSI reference model? What are the 4 layers of TCP/IP? Which layer protocol do FTP and TCP belong to?

Answer Question 1: The OSI (Open Systems Interconnection) reference model includes the following seven layers:

  1. Physical Layer (Physical Layer): Responsible for transmitting the bit stream and handling the electrical, mechanical and functional characteristics related to the interface with the physical medium.

  2. Data Link Layer (Data Link Layer): Provides reliable point-to-point data transmission, and converts the original bit stream into meaningful frames through mechanisms such as physical addressing, error detection, and flow control.

  3. Network Layer (Network Layer): Responsible for routing and forwarding of data packets, realizing communication between different networks, including functions such as network addressing, logical addressing, and routing.

  4. Transport Layer: Provides end-to-end reliable data transmission to ensure data integrity, reliability, and flow control, such as segmentation, reassembly, acknowledgment, and retransmission.

  5. Session Layer (Session Layer): Responsible for establishing, managing, and terminating sessions (a session is a communication session between two applications), and providing session layer control and synchronization.

  6. Presentation Layer: handles data representation and conversion, ensures that data formats of different systems can understand each other, and provides functions such as data encryption, decryption, compression, and format conversion.

  7. Application Layer: Provides application-specific services and protocols, such as email, file transfer, and remote login.

Answer question 2: The TCP/IP protocol family uses a more simplified four-layer model, including the following layers:

  1. Network Interface Layer (Network Interface Layer): It directly interacts with the physical network medium and is responsible for the functions of the data link layer and the physical layer.

  2. Internet Layer (Internet Layer): Similar to the network layer of the OSI model, it is responsible for network addressing, routing, and packet forwarding.

  3. Transport Layer (Transport Layer): It has the same function as the transport layer of the OSI model, providing end-to-end reliable data transmission, mainly using TCP and UDP protocols.

  4. Application Layer (Application Layer): Similar to the application layer of the OSI model, it provides services and protocols for various applications, such as HTTP, FTP, SMTP, etc.

answer question 3

FTP (File Transfer Protocol) is a protocol belonging to the application layer, which is used for file transfer between the client and the server. TCP (Transmission Control Protocol) is a protocol belonging to the transport layer, providing reliable end-to-end data transmission.

2. What are the ways of communication between linux processes?

In Linux, there are many ways of inter-process communication (IPC, Inter-Process Communication), and the commonly used ones include the following:

  1. Pipeline (Pipe): Pipeline is a half-duplex communication method that can transfer data between the parent process and the child process. It is divided into Anonymous Pipe and Named Pipe. Unnamed pipes can only be used between related processes, while named pipes can be used by unrelated processes.
  2. Named pipe (FIFO): Named pipe is also known as FIFO (First-In, First-Out), which provides a named communication method between processes, allowing communication between unrelated processes.
  3. Shared Memory: Shared memory is an efficient inter-process communication method that allows multiple processes to access the same physical memory area. Processes can directly read and write this memory area, avoiding data duplication.
  4. Semaphore (Semaphore): A semaphore is a mechanism for process synchronization and mutual exclusion, which controls access to shared resources through a counter. The process can operate on the semaphore, such as waiting for the semaphore, releasing the semaphore, and so on.
  5. Message Queue (Message Queue): Message Queue is a mechanism that can transfer messages between processes. Messages are placed in a queue, and other processes can read messages from the queue.
  6. Socket: A socket is a mechanism for inter-process communication between different hosts. When communicating between local processes, you can also use Unix domain sockets (Unix Domain Socket).
  7. Signal: A signal is an asynchronous communication mechanism used to notify a process that an event has occurred. Processes can register signal handlers to catch and handle signals.

These methods have their own characteristics and are suitable for different scenarios and needs. Developers can choose the appropriate inter-process communication method according to the specific situation.

3. MySQL operation, table T_USER(id,name,info), write SQL statement to meet the following requirements.

(1) How to insert a piece of data into the table?

(2) Find the user with id=100?

(3) Delete the user with id=20?

(4) Change the name of the user with id=10 to 123456?

answer analysis

The following is the SQL statement implementation corresponding to the requirements:

(1) Insert a piece of data into the table:

INSERT INTO T_USER (id, name, info) VALUES (1, 'John', 'Some information');

(2) Find the user with id=100:

SELECT * FROM T_USER WHERE id = 100;

(3) Delete the user with id=20:

DELETE FROM T_USER WHERE id = 20;

(4) Change the name of the user with id=10 to 123456:

UPDATE T_USER SET name = '123456' WHERE id = 10;

Please note that the table names, field names and specific values ​​in the above examples are replaced according to the actual situation.

4. Which of the following search methods is the fastest? Why?

binary search, traversal search, hash search, random search

answer analysis

In general, binary search is the fastest way to search for the following reasons:

  1. Time complexity: The time complexity of binary search is O(log n), where n is the number of elements in the search range. In contrast, the time complexity of traversal lookup, random lookup, and hash lookup is usually O(n).
  2. Data ordering requirements: Binary search requires data to be in order, but once the data is in order, the efficiency of binary search is very high. By halving the search range each time, binary search can quickly locate the location of the target element.
  3. Scope of application: Traversal lookup, random lookup, and hash lookup are generally suitable for lookups of unordered data . In these cases, it is necessary to traverse the entire data set or calculate the index position through a hash function, which cannot quickly narrow down the search range like binary search.

It should be noted that the above conclusions are established in general cases. Exactly how to find it is most relevant to the specific situation of the problem. For example, if the amount of data is small and not highly ordered, traversal search may be faster than binary search. Therefore, when choosing the fastest search method, it is necessary to comprehensively consider the characteristics of the data, the ordering requirements, and the actual performance requirements.

Supplement: Why are hash search and random search not optimal?

  1. Limitation of Hash lookup: Hash lookup relies on the hash function to map the key to the storage location, so uniform distribution and collision avoidance need to be considered when designing the hash function. If the hash function design is unreasonable or there are a large number of hash collisions, the search efficiency will decrease. In addition, if range search or fuzzy matching is required, Hash search cannot meet these needs.
  2. Inefficiency of random lookup: Random lookup is usually achieved by randomly selecting elements across the entire dataset for comparison. This search method is inefficient because, on average, most of the data needs to be traversed to find the target element. Random lookup works well for small datasets, but is less efficient for large datasets.
  3. Not suitable for ordered data: Hash lookup and random lookup are not suitable for ordered data sets. In an ordered data set, binary search can take advantage of the order of the data, and can quickly narrow the search range in each comparison, so it is more efficient. However, Hash search and random search do not take advantage of the orderliness of data and cannot give full play to their advantages.

5. Write C/CPP code and write a function to convert the string into an integer.

Here is an example of a simple C++ function that converts a string to an integer:

#include <iostream>
#include <string>

int stringToInt(const std::string& str) {
    
    
    int result = 0;
    int sign = 1; // 符号位,默认为正数
    int i = 0;

    // 跳过字符串前面的空格
    while (str[i] == ' ') {
    
    
        i++;
    }

    // 处理正负号
    if (str[i] == '-' || str[i] == '+') {
    
    
        sign = (str[i++] == '-') ? -1 : 1;
    }

    // 转换数字部分
    while (str[i] >= '0' && str[i] <= '9') {
    
    
        // 判断是否溢出
        if (result > INT_MAX / 10 || (result == INT_MAX / 10 && (str[i] - '0') > INT_MAX % 10)) {
    
    
            return (sign == 1) ? INT_MAX : INT_MIN;
        }
        result = result * 10 + (str[i++] - '0');
    }

    return result * sign;
}

int main() {
    
    
    std::string str = "12345";
    int result = stringToInt(str);
    std::cout << "Result: " << result << std::endl;

    return 0;
}

This function traverses the characters of the string and converts them according to the rules of numbers, including handling sign bits, spaces, overflows, etc. Finally returns the converted integer value.

It should be noted that the above example is only a simple implementation and does not consider all input situations and error handling. In actual development, the function may need to be further improved and optimized to deal with more input situations and error handling.

6. Write C/CPP code to implement a function to reverse the order of the string, such as "abcd" becomes "dcba" after the reverse order.

The following is a simple C++ function example to realize the function of reversing the string:

#include <iostream>
#include <string>

std::string reverseString(const std::string& str) {
    
    
    std::string reversedStr;
    int length = str.length();

    // 从字符串末尾开始遍历,逐个字符添加到新字符串中
    for (int i = length - 1; i >= 0; i--) {
    
    
        reversedStr += str[i];
    }

    return reversedStr;
}

int main() {
    
    
    std::string str = "abcd";
    std::string reversedStr = reverseString(str);
    std::cout << "Reversed String: " << reversedStr << std::endl;

    return 0;
}

This function reverses the order of a string by traversing from the end of the string and adding it character by character to a new string. In the loop, the initial index is the length of the string minus one, and the characters are added to the new string in reverse order by decrementing until the index is zero.

It should be noted that the above example is only a simple implementation, and the case where the input is an empty string is not considered. In actual development, the function may need to be further improved and optimized to deal with more input situations and error handling.

Guess you like

Origin blog.csdn.net/kokool/article/details/130855117