Article directory
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:
-
(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". -
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) i
or 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_cast
and reinterpret_cast
. Each type conversion operator has its specific usage scenarios and rules.
-
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 pointersstatic_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
- Conversion between class hierarchies
-
const_cast<T>(exp)
:
const_cast
Mainly used to modify the const or volatile properties of the type. For example, it canconst
convert a pointer to a non-const
pointer, orconst
an object to a non-const
object. It should be noted thatconst_cast
the 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
-
dynamic_cast<T>(exp)
:
dynamic_cast
Mainly 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 benullptr
. 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_cast
an exception will be thrown if the conversion fails, and the return of the pointer conversion failure isnullptr
-
reinterpret_cast<T>(exp)
:
reinterpret_cast
It is the least safe type conversion method, which can convert between any types, including conversion between pointers and integers. However, using itreinterpret_cast
may 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)i
a type conversion to i
convert an integer to double
a type.
In C++, type conversion is more complicated. The following are examples using static_cast
, const_cast
, dynamic_cast
and :reinterpret_cast
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 i
convert an integer to double
a type.
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 int
convert a pointer to a non- const int
pointer.
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*
.
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
- 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
- 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
- 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
- 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
-
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; // 拷贝操作
-
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 会被拷贝
-
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 会被拷贝 }
-
Assignment: A copy operation occurs when an object is assigned the value of another object:
MyClass obj1; MyClass obj2; obj2 = obj1; // 拷贝操作
-
As a container element: When an object is inserted into a container (eg, ,
std::vector
etc.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
-
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 }
-
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 }
-
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:
-
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
const
variable is used in an expression that requires a different type. -
Value substitution and optimization: Since the values of
const
variables are known at compile time, the compiler can directly use these values during compilation and optimization. For example, if you have an expressionconst int x = 5; int y = x * 2;
, the compiler may directly optimize it toint y = 10;
. This can improve runtime performance. -
Protect the value of
const
the variable : Sinceconst
the 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 aconst
variable .
-
-
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,
p
is a pointer to const int, so you can'tp
changex
the value by passing it. -
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,
p
is a const pointer, so you can't changep
the value (that is, you can't make itp
point to another address), but you canp
changex
the value by passing it.
6. Function- avoid modification
- Avoid multiple memory allocations
- type checking, scope checking
5.2 Modified function parameters
In C++, const
keyword modification function parameters have the following main functions:
-
Prevent parameter values from being modified: When you use
const
a 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 }
-
Fulfilling interface promises:
const
Parameters 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. -
Improve the versatility of functions:
const
reference or pointer parameters allow functions to acceptconst
and non-const
actual parameters, whileconst
non-reference or pointer parameters can only accept non-const
actual 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引用
-
Improve performance: For large objects,
const
passing 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
,float
etc), passing parameters by value and usingconst
decoration usually does not bring substantial performance benefits, because the cost of copying these types is small. However, for large classes and structures, passing parameters byconst
reference can provide significant performance benefits.
5.3 Modified function return value
In C++, const
keyword modification function return value mainly has the following functions:
-
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,
foo
the function returns a reference toint
,const
which means you cannot modify it through this referencex
. -
Prevent Accidental Object Modification:
const
Keywords 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. -
Enhanced interface semantics: In some cases,
const
keywords 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 referenceconst
.
It should be noted that for functions that return a local object, the return type should generally not be const
a 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
-
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
-
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
-
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. -
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 aComplex
class 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,sizeof
operator 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.
- Operator overloading does not change operator precedence. For example, even if you overload