[C/C++] Language-related question types (1)

1. Type conversion in C/C++

1.1 Type conversion in C

In the C language, explicit type conversion can be performed in the following two ways:

  1. (T) exp: In this way, you will convert the expression (exp) to type T. This method of type conversion is called "C-style type conversion" or "traditional type conversion".

  2. T(exp): In this way, you will also convert the expression (exp) to type T. This method of type conversion is more common in C++, and is called "function-style type conversion" or "constructor type conversion".

However, these two methods have the same effect in the C language, that is, the type of the expression exp is converted to T. For example, if you have an integer int i = 10;and you want to convert it to a float, you can do: (float) ior float(i).

But it should be noted that although both methods can be used in the C language, functional-style type conversions are T(exp)more common in C++. If you are writing a C program, it is recommended to use it (T) exp, because this method is acceptable in all C compilers.

Finally, I want to emphasize that no matter what kind of type conversion method is used, it should be used with caution. Converting between different types may result in loss of data or loss of precision. For example, when converting a floating-point number to an integer, the decimal part will be discarded; and if a large integer is converted to a small integer type, data overflow may occur. Therefore, when writing a program, make sure you understand why type conversion is required and what impact the conversion may have.

1.2 Type conversion in C++

In C++, four explicit type conversion operators are provided, namely static_cast, const_cast, , dynamic_castand reinterpret_cast. Each type conversion operator has its specific usage scenarios and rules.

  1. static_cast<T>(exp):
    This is the most common type conversion method, which can convert between any data types, including pointer types. However, it cannot convert a const object to a non-const object. Besides that, when you do it on pointers static_cast, you have to make sure that the type of conversion is safe, because there is no runtime type checking for this type of conversion.

    • Conversion between class hierarchies
      • Upconversion is safe
      • Downcasting is not safe, no dynamic type checking
    • Basic type conversion
    • A null pointer is converted to a null pointer of the target type
    • non-const converted to const
    • Limitations: properties such as const and volitale cannot be removed
  2. const_cast<T>(exp):
    const_castMainly used to modify the const or volatile properties of the type. For example, it can constconvert a pointer to a non- constpointer, or constan object to a non- constobject. It should be noted that const_castthe underlying data cannot be changed, only the access rights to the data are changed.

    • Remove the object pointer or object reference const attribute
    • Purpose: Modify the permission of the pointer (reference), you can modify the value of a block of memory through the pointer or reference
  3. dynamic_cast<T>(exp):
    dynamic_castMainly used for safe downcasting (transition from base class to derived class in inheritance hierarchy). That is, it can do type checking at runtime, and if the conversion is illegal, the result of the conversion will be nullptr. But this kind of type conversion can only be used for classes with virtual functions, because only such classes can perform runtime type checking.

    • For polymorphism, type conversion at runtime
    • Safely typecast in a class hierarchy, converting base class pointers (references) to derived class pointers (references)
    • Because the reference does not have a null reference, bad_castan exception will be thrown if the conversion fails, and the return of the pointer conversion failure isnullptr
  4. reinterpret_cast<T>(exp):
    reinterpret_castIt is the least safe type conversion method, which can convert between any types, including conversion between pointers and integers. However, using it reinterpret_castmay produce invalid data, so it should be used only when absolutely necessary.

    • Change pointer (reference) type
    • Converts a pointer (reference) to an integer
    • Convert an integer to a pointer (reference)
    • T must be a pointer, reference, integer, function pointer, member pointer
    • Note that it is only a copy of bits, no security checks

1.3 Examples

In the C language, common type conversions are mainly conversions between numeric types. For example:

int i = 10;
double d = (double)i;  // 将整数i转换为double类型

In this example, we used (double)ia type conversion to iconvert an integer to doublea type.

In C++, type conversion is more complicated. The following are examples using static_cast, const_cast, dynamic_castand :reinterpret_cast

  1. static_cast
int i = 10;
double d = static_cast<double>(i);  // 将整数i转换为double类型

In this example, we used static_cast<double>(i)a type conversion to iconvert an integer to doublea type.

  1. const_cast
const int i = 10;
int* pi = const_cast<int*>(&i);  // 将const int指针转换为非const int指针

In this example, we used const_cast<int*>(&i)a type conversion to const intconvert a pointer to a non- const intpointer.

  1. dynamic_cast
class Base {
    
    
    virtual void foo() {
    
    }
};
class Derived : public Base {
    
    
    void foo() override {
    
    }
};

Base* b = new Derived();
Derived* d = dynamic_cast<Derived*>(b);  // 安全地将Base*转换为Derived*

In this example, we used dynamic_cast<Derived*>(b)a typecast, which safely Base*converts to Derived*.

  1. reinterpret_cast
int i = 10;
int* pi = &i;
char* pc = reinterpret_cast<char*>(pi);  // 将int指针转换为char指针

In this example, we used reinterpret_cast<char*>(pi)the type conversion, which will be int*converted to char*.

1.4 Summary

  • Remove the const attribute and use const_cast
  • Basic type conversion with static_cast
  • Dynamic_cast is used for type conversion between polymorphic classes
  • Different types of pointer type conversion with reinterpret_cast

2. When does C++ generate a default constructor

2.1 The case where the default constructor will not be generated

An empty class definition will not generate a constructor, which is meaningless. Even if it contains member variables, and the member variables are of the basic type, the default constructor will not be generated, and the compiler does not know what value to initialize

2.2 The case where a default constructor will be generated

  1. The data member in class A is object B, and class B provides a default constructor
    • In order for the constructor of B to be called, a default constructor has to be generated for A
  2. The base class of the class provides a default constructor
    • The subclass constructor must first initialize the parent class, and then initialize its own member variables
    • If the parent class does not provide a default constructor, the subclass does not need to provide a default constructor
    • If the parent class provides a default constructor, the subclass has to generate a default constructor
  3. A virtual function is defined in the class
    • In order to implement the polymorphic mechanism, it is necessary to maintain a virtual function table for the class
    • All objects of the class need to save a pointer to the virtual function table
    • The object needs to initialize the pointer to the virtual function table
    • Had to provide a default constructor to initialize the vtable pointer
  4. The class uses virtual inheritance
    • The virtual base class table records the offset positions of all virtual base class subobjects inherited by the class within the object defined by this class
    • In order to achieve virtual inheritance, the object needs to maintain a pointer to the virtual base class table during the initialization phase
    • Had to provide a default constructor to initialize the virtual base class pointer

3. When does C++ generate a default copy constructor

3.1 Do not explicitly specify a copy constructor

In C++, the compiler will generate a default copy constructor (that is, an implicit copy constructor) for a class as long as you don't explicitly define a copy constructor for the class. This default copy constructor will perform a copy of each member, which usually means performing the member's copy constructor (for members of class types), or performing a simple bit copy (for members of built-in types).

This behavior is defined by the C++ standard. The default copy constructor is usually sufficient unless your class needs to manage its own resources, such as dynamically allocated memory. In this case, you need to define your own copy constructor to properly perform deep copy, otherwise problems may occur (for example, multiple deletion of resources caused by shallow copy).

It should be noted that if you define any other constructor (such as a move constructor or a parameterized constructor), but do not define a copy constructor, the compiler will still generate a default copy constructor for you. Only when you explicitly define a copy constructor will the compiler not generate a default copy constructor.

3.2 Situations where a copy constructor must be generated

Same as 2.2

4. Shallow copy and deep copy

4.1 C++ will appear to copy

  1. Initialization: A copy operation occurs when an object is initialized with another object. For example, if you declare a new object with another object as its initial value, a copy operation will occur:

    MyClass obj1;
    MyClass obj2 = obj1;  // 拷贝操作
    
  2. Function parameter passing: When an object is used as a function parameter, if the parameter is passed by value, then a copy operation will occur when the function is called:

    void foo(MyClass obj) {
          
           /* ... */ }
    
    MyClass obj;
    foo(obj);  // 调用 foo 时,obj 会被拷贝
    
  3. Function returns: When a function returns an object, if the object is returned by value, then a copy operation occurs when the function returns:

    MyClass foo() {
          
          
        MyClass obj;
        return obj;  // 返回时,obj 会被拷贝
    }
    
  4. Assignment: A copy operation occurs when an object is assigned the value of another object:

    MyClass obj1;
    MyClass obj2;
    obj2 = obj1;  // 拷贝操作
    
  5. As a container element: When an object is inserted into a container (eg, , std::vectoretc. std::list), a copy operation occurs:

    std::vector<MyClass> vec;
    MyClass obj;
    vec.push_back(obj);  // obj 被拷贝到 vec 中
    

4.2 Shallow Copy

A shallow copy means copying only the object's member values, regardless of whether those values ​​represent references to other resources. If an object has a pointer to dynamically allocated memory or other resources, shallow copy will only copy the value of the pointer (that is, copy the memory address), but not the resource pointed to by the pointer.

This can cause problems because when multiple objects share the same resource, once one of the objects frees the resource on destruction, the other objects hold an invalid pointer. This is known as the dangling pointer problem.

Here is an example of a simple shallow copy:

class ShallowCopyExample {
    
    
    int* data;
public:
    ShallowCopyExample(int value) {
    
    
        data = new int;
        *data = value;
    }

    // 浅拷贝复制构造函数
    ShallowCopyExample(const ShallowCopyExample& source) {
    
    
        data = source.data;
    }

    ~ShallowCopyExample() {
    
    
        delete data;
    }
};

4.3 Deep Copy

In contrast, a deep copy not only copies the object's member values, but also creates new copies of any dynamically allocated resources. That is, if an object has a pointer to dynamically allocated memory, deep copy will create a new copy of this memory and point the new object's pointer to this new memory.

Doing this avoids the dangling pointer problem, since each object has its own resources that are not shared with other objects.

The following is an example of a simple deep copy:

class DeepCopyExample {
    
    
    int* data;
public:
    DeepCopyExample(int value) {
    
    
        data = new int;
        *data = value;
    }

    // 深拷贝复制构造函数
    DeepCopyExample(const DeepCopyExample& source) {
    
    
        data = new int;  // 为新对象分配内存
        *data = *source.data;  // 复制资源
    }

    ~DeepCopyExample() {
    
    
        delete data;  // 释放对象的资源
    }
};

It should be noted that the default copy constructor and assignment operator of C++ perform shallow copying. If your class manages dynamically allocated resources, you need to override your own copy constructor and assignment operator to implement deep copying .

5. The role of the const keyword

5.1 Define variables

  1. local const

    Local const refers to const variables declared inside a function. This kind of variable is only visible inside the body of the function that declares it, and its value cannot be changed within its scope.

    For example:

    void func() {
          
          
        const int x = 10;  // x是局部const
    }
    
  2. global const

    Global const refers to const variables declared outside a function. This variable is visible throughout the program and its value cannot be changed.

    For example:

    const int y = 20;  // y是全局const
    
    void func() {
          
          
        // 可以在这里使用y
    }
    
  3. Symbol table

    The symbol table is information used by the compiler to store identifiers for all variables, functions, etc. used in the program. For const variables, their value and type are stored in the symbol table. Has the following effects:

    1. Type checking: The compiler performs type checking by consulting the type information in the symbol table. The compiler can issue a warning or an error if a constvariable is used in an expression that requires a different type.

    2. Value substitution and optimization: Since the values ​​of constvariables are known at compile time, the compiler can directly use these values ​​during compilation and optimization. For example, if you have an expression const int x = 5; int y = x * 2;, the compiler may directly optimize it to int y = 10;. This can improve runtime performance.

    3. Protect the value of constthe variable : Since constthe value of the variable is in the symbol table, the compiler can ensure that this value will not be changed anywhere in the program. The compiler issues an error if an attempt is made to modify the value of a constvariable .

  4. constant pointer

    A constant pointer is a pointer to a const object, that is, you cannot modify the value it points to through this pointer, but you can change the address pointed to by this pointer.

    For example:

    const int x = 10;
    const int* p = &x;
    

    In this example, pis a pointer to const int, so you can't pchange xthe value by passing it.

  5. pointer constant

    A pointer constant is a pointer that you cannot change the address it points to, but you can modify the value it points to through this pointer.

    For example:

    int x = 10;
    int* const p = &x;
    

    In this example, pis a const pointer, so you can't change pthe value (that is, you can't make it ppoint to another address), but you can pchange xthe value by passing it.
    6. Function

    • avoid modification
    • Avoid multiple memory allocations
    • type checking, scope checking

5.2 Modified function parameters

In C++, constkeyword modification function parameters have the following main functions:

  1. Prevent parameter values ​​from being modified: When you use consta keyword to modify a function parameter, you cannot modify the value of the parameter within the function body. This prevents erroneous modifications in functions.

    void foo(const int x) {
          
          
        x = 42;  // 编译错误,因为x是const
    }
    
  2. Fulfilling interface promises: constParameters can convey important information to the programmer using the function. It explicitly states that the function does not modify the value of the parameter. This is a great form of self-documentation and helps prevent errors from people using the function.

  3. Improve the versatility of functions: constreference or pointer parameters allow functions to accept constand non- constactual parameters, while constnon-reference or pointer parameters can only accept non- constactual parameters. This allows functions to be used in more contexts.

    void bar(const std::string& str) {
          
          
        // str 是 const 引用,不能在函数体内被修改
    }
    
    const std::string str1 = "Hello";
    std::string str2 = "World";
    
    bar(str1);  // 有效,因为str1是const
    bar(str2);  // 也有效,因为非const实参可以转换为const引用
    
  4. Improve performance: For large objects, constpassing parameters by reference can avoid copy operations, while ensuring that the function will not modify the parameters.

    It should be noted that for basic data types (such as int, floatetc), passing parameters by value and using constdecoration usually does not bring substantial performance benefits, because the cost of copying these types is small. However, for large classes and structures, passing parameters by constreference can provide significant performance benefits.

5.3 Modified function return value

In C++, constkeyword modification function return value mainly has the following functions:

  1. Return a read-only version of an object: If a function returns a reference or pointer to an object, and you don't want the caller to modify the object through the reference or pointer, you can use keywords to decorate the return type const. This means that the reference or pointer returned by the function is read-only and cannot be used to modify the object.

    const int& foo() {
          
          
        static int x = 0;
        return x;
    }
    

    In this example, foothe function returns a reference to int, constwhich means you cannot modify it through this reference x.

  2. Prevent Accidental Object Modification: constKeywords prevent you from inadvertently modifying objects that should not be modified. This is a safety mechanism that can help you avoid some behaviors that may lead to errors.

  3. Enhanced interface semantics: In some cases, constkeywords can provide clearer semantics. For example, if you have a member function that returns a reference to a member variable, and the function is not supposed to change the state of the object, you might choose to return a reference const.

It should be noted that for functions that return a local object, the return type should generally not be consta reference or pointer, because when the function returns, the local object does not exist. In this case you should return a value or a pointer to dynamically allocated memory.

5.4 Constant member functions in classes

  1. important point

    • You cannot modify any non-static member variables of the class in a constant member function
    • Read-only objects can only call constant member functions
    • Any non-member function and non-member variable in the class cannot be called in the constant member function, because the non-member function may modify the member variable of the class, and the non-member variable will also be modified
  2. effect

    • Avoid unintentional modification of member variables, which is more secure
    • It can be used for function overloading. Constant member functions and non-member functions are two different functions, so that different versions of functions are called according to whether the object is a constant
    • Allow constant objects to call their member functions. If there is a member function that needs to be called on a constant object, then this member function should be a constant member function

6. Supplement

  1. The difference between wild pointers and dangling pointers
    Wild pointers (Wild Pointer) usually refer to any uninitialized or incorrectly set pointers, while dangling pointers (Dangling Pointer) usually refer to pointers to objects that have been released or have exceeded their lifetime.

  2. Operator Overloading
    In C++, operator overloading is a language feature that enables you to change the behavior of operators on a type, especially user-defined types. You can overload operators by defining special member functions or global functions.

    The following is an example of overloading +the operator . We define a Complexclass that represents complex numbers, and then overload +the operator to add complex numbers.

    class Complex {
          
          
    public:
        Complex(double real, double imag) : real_(real), imag_(imag) {
          
          }
        double real() const {
          
           return real_; }
        double imag() const {
          
           return imag_; }
        Complex operator+(const Complex& other) const {
          
          
            return Complex(real_ + other.real_, imag_ + other.imag_);
        }
    
    private:
        double real_;
        double imag_;
    };
    
    int main() {
          
          
        Complex a(1.0, 2.0);
        Complex b(3.0, 4.0);
        Complex c = a + b;  // 使用我们重载的 + 运算符
        // c.real() 的值为 4.0,c.imag() 的值为 6.0
        return 0;
    }
    

    Note the following points:

    • Operator overloading does not change operator precedence. For example, even if you overload +the and *operator, *the operator still has higher precedence than +the operator .
    • Not all operators can be overloaded. For example, .(member access) operator, ::(scope resolution) operator, sizeofoperator and ?:(condition) operator cannot be overloaded.
    • You can't change the "basic semantics" of an operator. For example, you cannot define an overload that causes the &&operator to perform multiplication.
    • You cannot create new operators. You can only overload operators that already exist.
    • For binary operators like +or -, you can choose to overload it as a member function or as a global function. If you choose to overload it as a member function, the first operand is the object on which the function is called. If you choose to overload it as a global function, then you need to provide arguments for both operands.

Guess you like

Origin blog.csdn.net/weixin_52665939/article/details/130794948