C++ knowledge points that are easy to be confused

The difference between initialization and assignment

Specifying the initial value for a variable or constant when it is defined is called initialization, and after defining a variable or constant, using the value operator to modify its value is called assignment. Don't confuse initialization with assignment.

Class composition and friend functions

In the case of class composition, the object of class A is embedded in class B, but the member function of B directly accesses the private member x of A.
The friendship relationship provides a mechanism for data sharing between member functions of different classes or objects, and between member functions of a class and general functions. , Through the friendship relationship, an ordinary function or a member function of a class can access data encapsulated in another class. A friend function is a non-member function modified with the keyword friend in a class.
If class A is a friend class of class B, all member functions of class A are friend functions of class B, and can access private and protected members of class B.
The general form of the composite class constructor definition is:
Circle::Circle(float r): radius® ()

Regular object

If an object is described as a constant object, only its constant member function can be called through the constant object, but other member functions cannot be called (this is the protection of the constant object from the syntax mechanism of C++, and it is also the only external interface of the constant object the way).
This constant member function cannot update the data member of the destination object, nor can it call the member function of the class that is not decorated with const for the destination object (this ensures that the value of the data member of the destination object will not be changed in the constant member function).
The const keyword can be used to distinguish overloaded functions. For example, if you declare this in the class:
void print ();
void print () const;
Habit: Using the const keyword in the appropriate place is a good habit to improve the quality of the program. For member functions that do not need to change the state of the object, const
constant references should be used.
If you use const when declaring a reference, the declared reference is a constant reference. Objects referenced by frequent references cannot be updated. If you use a constant reference as a formal parameter, you will not accidentally change the actual.

Pointers and references

A significant difference between references and pointers is that ordinary pointers can be assigned multiple times
, that is to say, the object it points to can be changed multiple times, while references can only specify the referenced object during initialization and cannot be changed afterwards. Therefore, the function of a reference is similar to a pointer constant.
It should be pointed out that although p and &r are equivalent for the purpose of "reading the address of v", p and &r have different meanings. p can be addressed again, but &r cannot. That is to say, the address of the reference itself (not the referenced object) is not available. After the reference is defined, all its behaviors are for the referenced object, and the space occupied by the reference itself is It's completely hidden. Therefore, the referenced function is still slightly worse than the pointer constant.
There are only constant references, but no reference constants, that is, you cannot use T &. const as a reference type.
This is because the reference can only specify the object it refers to when it is initialized, and it cannot be changed later, which makes the reference itself (not the referenced object) already have a constant nature.
For the two purposes of data parameter passing and reducing the parameter passing overhead of large objects, references can well replace pointers, and using references is more concise and safer than pointers. Among them, if it is only for the latter purpose, in general, you should use frequent references as parameters.

If you use const modification when declaring a reference, the declared reference is a constant reference. Objects referenced by frequent references cannot be updated.
const type specifier & reference name;
non-const references can only be bound to ordinary objects, not to regular objects, but regular references can be bound to regular objects. A constant reference, whether it is bound to an ordinary object or a constant object, when the object is accessed through the reference, the object can only be regarded as a constant object

(1) A pointer to a constant can be declared. At this time, the value of the pointed object cannot be changed through the pointer, but the pointer itself can be changed and can point to another object

int a;
const int *p1= &a;
int b;
p1= &b;//正确, p1 本身的值可以改变
*pl= 1;//编译时出错,不能通过p1 改变所指的对象

(2) You can declare a constant of the pointer type, at this time the value of the pointer itself cannot be changed. For example:
int *const p2= &a;
p2= &b; //error, p2 is a pointer constant, the value cannot be changed

Pointer arithmetic

The use of pointer arithmetic operations must be limited to a pointer to an element in the drum set to get a pointer to another element in the same dare set. Other uses of pointer arithmetic operations will get uncertain results.
The integer 2 and the double-precision floating-point type 2 are represented by different binary sequences in memory. However, when
the operation of static_cast (i) is executed , the compiler will generate object code to convert the integer binary representation of i to the floating-point binary representation. This conversion is called content-based conversion. But with pointers participating in conversion, the situation is different. For example:
int i = 2;
float*p = reinterpret cast< float *> (&i);
reinterpret_cast is a type conversion operator parallel to static_ cast. It One type of pointer can be converted to another type of pointer. Here, the &i of int* type is converted to float * type.
reinterpret_cast can not only convert between pointers to different types of objects, but also between pointers to different types of functions, pointers to data members of different types, pointers to function members of different types, and references of different types. . The conversion process of reinterpret_cast is not clearly stipulated in the C+ ten standard, and it will vary depending on the compilation environment. The C++ standard only guarantees that the reinterpret_cast operator is used to convert the p of type A to the type B of q, and
then the reinterpret_cast operator is used to convert the q of type B to the r of type A. After that, (p= =r) should be established.

Two-dimensional array:

int line1[]={l, 0, 0};
int line2[]={0, 1, 0};
int line3[]= {0, 0, 1};
int pLine[3]={line1, line2, line3};
pLine[i] [j] is equivalent to
(pLine[i]+j), that is, first read the i-th pointer stored in the pointer array pLine, and then read the j-th pointer after the address it points to Number. Its representation is very similar to accessing elements of a two-dimensional array, but the specific access process is different.
Two-dimensional arrays are stored in memory in a row-first manner in a one-dimensional order. Therefore, for a two-dimensional array, it can be understood as a one-dimensional array of a one-dimensional array, the array name is its first address, the number of elements in this array is the number of rows, and each element is a one-dimensional array.

When the program is running, not only the data occupies the memory space, but the code that executes the program is also transferred into the memory and occupy a certain space. Every function has a function name. In fact, this function name indicates the starting address of the function's code in memory. From this point of view, the essence of the usual form of calling a function "function name (parameter table)" is "function code first address (parameter table)".

Function pointer

When declaring a function pointer, it is also necessary to explain the return value and formal parameter list of the function. The general syntax is as follows:
data type (head function pointer name) (formal parameter list)
data type description function pointer return value type; The content in a parenthesis indicates the name of a number pointer; the formal parameter table lists the type and number of formal parameters of the function pointed to by the pointer.
typedef int (*DoubleInt Function) (double);
This declares DoublelntFunction as
an alias of the type "pointer to a function with a double parameter and return type int" . Below, when you need to declare this type of variable, you can use it directly:
DoubleIntFunction funcPtr;

void printStuff (float) {
    
    
cout<< "This is the print stuff function. "<< endl;

}
void (* functionPointer) (float) ;
printStuff(PI);
functionPointer=printStuff;//函数指针指向prìntStuff

Scope

The general rules for scope visibility are as follows.
.Identifiers should be declared before and quoted after.
· In the same scope, you cannot declare an identifier with the same name.
·Identifiers with the same name declared in different scopes that do not contain each other do not affect each other.
· If an identifier with the same name is declared in two or more containment scopes, the outer identifier is not visible in the inner layer.
If the lifetime of the object is the same as the runtime of the program, it is said to have a static lifetime. All objects declared in the scope of the namespace have a static lifetime. If you want to declare an object with a static lifetime in the local scope inside the function, use the keyword statìc.
The characteristic of a static variable in the local scope is that it does not produce a copy with each function call, nor does it become invalid when the function returns. That is to say, when a function returns, the next time it is called, the variable will retain the previous value. Even if a recursive call occurs, a new copy of the variable will not be created. The variable will be Sharing between calls.
When defining a static variable, you can also assign an initial value to it, for example:
static int i= 5;
This means that i will be initialized with 5 instead of assigning i to 5 every time the function is executed.
The basic type static lifetime transaction that does not specify the initial value when it is defined will be initialized with a value of 0. For dynamic lifetime variables, not specifying the initial value means that the initial value is uncertain.
The local lifetime object is born at the point of declaration and ends when the block in which the declaration is located is executed.

External variables

If a variable can be used by other files in addition to the source file in which it is defined, then
the variable is said to be an external variable. Variables defined in the namespace scope are external variables by default, but if you need to use this variable in other files, you need to declare it with the extern keyword.
. External variables are global variables that can be shared by multiple games and files.

In projects that contain multiple source files, anonymous namespaces are often used to shield identifiers that are not expected to be exposed to other source files. This is because the anonymous namespace of each source file is different from each other. In a source file There is no way to access
the anonymous namespace of other source files.

  • In the same scope, an identifier with the same name cannot be declared.

  • Identifiers with the same name declared in different scopes that do not contain each other do not affect each other.

  • If an identifier with the same name is declared in two or more containment scopes, the outer identifier is not visible in the inner layer.
    Constant data members can only get the initial value through the initialization list
    class A (
    public:
    A(int i);
    void print ();
    private:
    const int a;
    static const int b;)

//静态常数据成员
const A: :b= 10;
//静态常数据成员在类外说明和初始化
//常数据成员只能通过初始化列表来获得初值
A::A(int i) : a(i) {
    
    }
int main () (
//建立对象a 和b ,并以100 和0 作为初值,分别调用构造函//数,通过构造函数的初始化列表给对象的常数据成员赋初值/
A a1 (100) ,a2 (0);
a1. print ();
a2.print();
return 0;
}

The static variables and constants in the details of the class members should be defined outside the class definition, but the C++ standard provides for an exception: if the static constant of the class has an integer type or an enumeration type, it can be directly defined in the class definition Specify the constant value, for example, you can write it directly in the class definition:
static const int b=10;

The function of undef is to delete the macro defined by #define so that it no longer works.
A new keyword is mutable. For certain types of data members, you can use the
mutable keyword to modify them, so that even in constant member functions, you can modify their values. Member objects modified by mutable are not regarded as constant objects at any time, which is the more general meaning of mutable.
The program is stored on the disk. Before execution, the operating system needs to load it into the memory first, and allocate enough memory space for it to accommodate the code segment and data segment, and then combine the code segment stored in the file with The contents of the initialized data segment are loaded into the initialization of some of the static lifetime objects in this way.

Memory space access method

How to use memory unit to access data in C++ program? One is through variable name, and the other is through address. The variables declared in the program must occupy a certain amount of memory space. For example, for some common 32-bit systems, the short type occupies 2 bytes and the long type occupies 4 bytes. Variables with static lifetime have been allocated memory space before the program starts running. Variables with dynamic lifetime are allocated memory space when the variable declaration statement is encountered when the program is running. When the variable obtains the memory space, the variable name becomes the name of the corresponding memory space. This name can be used to access the memory space during the entire lifetime of the variable. The expression in the program statement is to access the content of the variable through the variable name. However, sometimes it is not convenient to use variable names or there are no variable names available at all. At this time, you need to directly use the address to access the memory unit. **For example, when transferring a large amount of data between different functions, if you do not transfer the value of the variable, but only the address of the variable, it will reduce system overhead and improve efficiency. If it is a dynamically allocated memory unit (to be introduced in Section 6.3), there is no name at all, and it can only be accessed by address.

point = new int (2);
The memory space for storing int type data is dynamically allocated, and the initial value 2 is stored in the space, and then the first address is
assigned to the pointer point.
If the brackets are reserved, but no value is written in the brackets, it means use. Initialize the object, for example:
int *point = new int ();

assert

Assert takes effect only in debug mode, and does not perform any operation in release mode, which takes into account both the debugging requirements of debug mode and the efficiency requirements of release mode.
Reminder Since assert is only effective in debug mode, generally use assert to check the logic errors of the program itself, and errors caused by improper input by the user should be handled in other ways.

Access array elements as pointers:

int main() {
    
    
f1oat (*cp) [9] [8]=new f1oat[8] [9] [8];
for (int i=O; i<8; i++)
for (int j=O; j< 9; j+)
for (int k=O; k<8; k++)
//以指针形式访问数组元素
*(*(*(+.i. )+j)+k)=static cast< float> (i *100+ j *10+ k) ;

string

Because the string class has a constructor that accepts the const char header type, both string constants and string variables represented by character numbers can be implicitly converted to stnng objects. For example, you can directly use string constants to initialize the stnng object. 2
string str = "Hello world!";
stnng type operator:
s + t connects the strings s and t into a new string
s!= t to determine whether s and t are Unequal
string append (const char *s); add the string s at the end of this string
string assign (const char *s); assign, assign the string pointed to by s to this object
int compare (const string &str) const ;
Compare the size of this string with the string in str. When this string is <str string, return a negative number J. When this string> str string, return a positive number J. If the
two strings are equal, return 0.
void swap (string& str);
// Swap this string with the string in str
unsigned int find(const basic_string &str) const;
//Find and return the position of the first occurrence of str in this string

Base class and derived class

The type compatibility rule means that wherever an object of a base class is needed, an object of a public derived class can be used instead. Through public inheritance, the derived class gets all the members of the base class except the constructor and destructor. In this way, the public derived class actually has all the functions of the base class. Any problem that the base class can solve can be solved by the public vibrating class. The substitution referred to in the type compatibility rule includes the following cases.
· Objects of derived classes can be implicitly converted to objects of base classes.
. Objects of derived classes can initialize references to the base class.
· The pointer of the derived class can be implicitly converted to the pointer of the base class.
After being replaced, the derived class object can be used as the base class object, but only the members inherited from the base class can be used.
If class B is a base class and D is a public derived class of class B, then class D includes all members of base class B except for the constructor and destructor. At this time, according to the type compatibility rules, wherever objects of base class B can appear, objects of derived class D can be substituted.
class B {…}
class D: public B {…}
B b1 ,*pb1;
D dl;
objects of derived classes can also initialize references to base class objects:
B &rb=d1;,
you can assign the addresses of public derived class objects Give a pointer to the base type,

After the derived class is defined, to use the derived class, you need to declare the objects of that class. The object must be initialized before use. The member object of the derived class is composed of all the member objects of the base class and the newly added member objects of the derived class.
Therefore, when constructing the object of the derived class, it is necessary to initialize the member object of the base class and the newly added member object. The
constructor of the base class is not inherited. To complete these tasks, you must add a new constructor to the derived class. The derived class cannot directly access many member objects of the base class. Therefore, to complete the initialization of the member objects of the base class, it is necessary to call the constructor of the base class. The constructor of the derived class needs to take appropriate initial values ​​as parameters. Some of the parameters need to be passed to the constructor of the base class to initialize the corresponding members, and other parameters need to be used to initialize the new member objects of the derived class. **When constructing an object of a derived class, the constructor of the base class will be called first to initialize their data members, and then the new member objects of the derived class will be initialized according to the method specified in the constructor initialization list, and finally executed The function body of the derived class constructor.

The calling order of the base class constructor is in accordance with the order when the derived class is defined, so it should be Base2, then Base1,
and finally Base3; and the constructor call order of the embedded object should be in the order in which the members are declared in the class

Virtual base class

. In the object of the derived class, these data members with the same name have multiple copies in memory at the same time, and there will be multiple mappings for the same function name. You can use the scope discriminator to uniquely identify and access them separately, or you can set the common base class as a virtual base class. In this case, data members with the same name inherited from different paths have only one copy in memory, the same function name There is also only one mapping. This solves the problem of unique identification of members with the same name.

class 派生类名:virtual 继承方式基类名
class BaseO {
    
    
public:
int varO;

void funO () {
    
    cout<< "Member of BaseO"<< endl; }
}
class Basel: virtuaJ. public BaseO {
    
    
public:
int varl;
}

The general order of constructing objects of a class is:
(1) If the class has a direct or indirect virtual base class, the constructor of the virtual base class is executed first.
(2) If the class has other base classes, their constructors are executed in the order in which they appear in the inheritance declaration list, but the constructors of their virtual base classes are no longer executed during the construction process.
(3) According to the order of appearance in the class definition, initialize the newly added member objects in the derived class. For a member object of a class type, if it appears in the constructor initialization list, the constructor is executed with the specified parameters, if not, the default constructor is executed; for a member object of basic data type, if it appears in the constructor In the initialization list, use the specified value to assign the initial value, otherwise do nothing.
(4) The body of the function that executes the constructor.

Derived pointers can be implicitly converted to base pointers. The reason why this conversion is allowed to happen implicitly is because it is a safe conversion. If a derived pointer is to be converted to a base pointer, the conversion must be done explicitly. For example:
Base*pb = new Derived (); // implicitly convert Derived pointer to Base pointer
Derived *pd = static cast< Derived *> (pd); // Explicitly convert Base pointer to Derived pointer

Virtual function

Virtual functions are the basis of dynamic binding. The virtual function must be a non-static member function. After the virtual function is derived, the polymorphism during operation can be realized in the class family.
If you use the pointer of the base class type to point to the derived class object, you can access the object through this pointer. The problem is that only the members of the same name inherited from the base class are accessed. The solution to this problem is: if you need to point to the object of the derived class through the pointer of the base class and access a member with the same name as the base class, then first declare the function of the same name as a virtual function in the base class. In this way, through the pointer of the base class type, different objects belonging to different derived classes can produce different behaviors, thereby realizing the polymorphism of the running process.
The virtual function declaration can only appear in the function prototype declaration in the class definition, not when the member function is implemented. The non-virtual functions declared in the base class usually represent functions that do not want to be changed by the derived class, and cannot be polymorphic. Generally do not rewrite inherited non-virtual functions (although the syntax does not impose restrictions on this), because that will cause different results when calling the same-named function through the base class pointer and the derived class pointer or object, which will cause confusion.

, The destructor function of the base class is called when deleting the Pi Niu class object through the base class pointer, and the destructor function of the derived class is not executed. Therefore, the dynamically allocated memory space in the derived class object is not released, causing a memory leak.
That is to say, the memory space pointed to by the member p of the derived class object can neither be used by the program nor selected after the object disappears. For a program that requires a large amount of memory and runs continuously for a long time, it is very dangerous if such errors continue to occur, and will eventually cause the program to terminate due to insufficient memory.
An effective way to avoid the above errors is to declare the destructor as a virtual function:

class Base (
public:
virtual-Base () ;)
The declaration format of pure virtual function is 2

Virtual function type function name (parameter list) = 0
In fact, the difference between it and the general virtual function member prototype in writing format is that "= 0" is added at the end.
After it is declared as a pure virtual function, the implementation part of the function can no longer be given in the base class. The function body of a pure virtual function is given by the derived class.

Type conversion when base class pointer points to subclass object

The pointer of the base class can point to the object of the derived class. Although polymorphism can be used to perform the functions provided by the derived class through such a pointer, this is limited to calling the virtual function declared in the base class. If you want to call a new function in the derived class for a part of the object of the derived class, you cannot use the base class pointer .
dynamic_cast is one of the 4 type conversion operators parallel to static_cast. const_cast and reinterpret_cast. It can explicitly convert the pointer of the base class to the pointer of the derived class, or explicitly convert the reference of the base class to the reference of the derived class. {!3. Unlike static_cast, it does not perform an unconditional conversion. Before the conversion, it checks whether the actual type of the object pointed to by the pointer (or reference) is compatible with the type of the conversion date. A compatible conversion will only occur. , In order to get the pointer (or reference) of the derived class. Then:
· If you perform a pointer type conversion, you will get a null pointer.
·If the conversion of reference type is performed, an exception will be thrown.
Example:

class Base{
    
    
public:
virtual void funl () {
    
    cout<< "Base: : fun1 () "<< endl;}
}
class Derived1: public Base {
    
    
public:
virtual void fun1 () {
    
    cout<< "Derivedl : : funl () "<< endl; }
virtual void fun2 () {
    
    cout<< "Derived1: :fun2 () "<<endl;}
}
void fun (Base *b) {
    
    
b- > funl ();
Derived1 * d= dnamic_cast<Derived1*> (b) ; //尝试将b 转换为Derived1 指针
if (d!=O) d->fun2(); //判断转换是否成功

Since the fun1 function is a function defined in the base class Base, the fun1() function can be called directly through the pointer b of the Base class. The fun2 function is an attractive new function in the derived class Derived1, which can only be called on objects of the Derived1 class.

Use typeid to get runtime type information

typeid is a keyword of C++, with which you can get information about a type.

Guess you like

Origin blog.csdn.net/qq_41358574/article/details/111413318