C++ of study notes

C++

basic concept

inline function

  • Inline functions are automatically expanded when the function is compiled , and do not exist after compilation, similar to macro expansion.
  • Inline functions can be defined in header files, and repeated definition errors will not occur even if they are included multiple times.
  • Inline functions are like macros at compile time, they don't exist at link time after compilation .
  • It is necessary to add an inline keyword when the function is defined , and it is invalid when used when the function is declared .
  • Inline functions should not have a declaration, but the declaration will not report an error.
  • Putting the definition and declaration of the inline function in different files will cause an error, to be precise, an error will be reported when linking. Therefore, the definition of inline functions is generally placed in the header file.
  • Inline functions have two functions, the first isEliminate function call overhead, the second isreplace macros with parameters

default parameters

When creating a function, you can set a default parameter for the formal parameter. The default parameter can also use an expression. The default parameter can only be at the end of the formal parameter list . Once a certain formal parameter has a default value, then the formal parameters behind it will also be There must be a default value. C++ stipulates that in a given scope, default parameters can only be specified once.

If the default parameters at the definition function and the declaration function are different, then the default parameters of the current scope are used at compile time. The same function can be declared multiple times.

function overloading

C++ allows multiple functions to have the same name, as long as the parameter lists are different, which isfunction overloading(Function Overloading)parameter listAlso called parameter signature, including the type of parameter , the number of parameters, and the order of parameters , as long as there is a difference, it is called a different parameter list.

Rules for function overloading:

  1. function names must be the same
  2. Argument lists must be different
  3. Function return types can be the same or different
  4. Merely different return types cannot be used as the basis for function overloading

C++ will rename the function according to the parameter list when compiling, for example, void Swap(int a, int b)it will be renamed to Swap_int_int, void Swap(float x, float y)it will be renamed to Swap_float_float. When a function call occurs, the compiler will match the incoming actual parameters one by one and select the corresponding function. If the matching fails, the compiler will report an error, which is called overload resolution.

Function overloading is only at the grammatical level, but essentially different functions occupy different memory . Function overloading, too few or too many parameter types are likely to cause ambiguity errors.

extern “C”There are roughly 2 usages:

  1. When only modifying a sentence of C++ code, just add it directly to the beginning of the line of code;
  2. If it is used to decorate a piece of C++ code, just add a pair of curly braces {} to extern "C", and wrap the code to be decorated in the brackets.

classes and objects

concept

Similar to the template for creating objects, a class can create multiple objects, and each object is a variable of the class type; the process of creating an object is also called the instantiation of the class . Each object is a concrete instance of a class, with member variables and member functions of the class.

Like structures, classes are just declarations of complex data types and do not occupy memory space .

Use the class keyword to define a class. The first letter of the class name is generally capitalized to distinguish it. Inside {} are the member variables and member functions of the class, which are collectively referred to as members of the class . Class definitions are followed by a semicolon.

A class is just a template and does not take up memory space after compilation, soMember variables cannot be initialized when defining a class, because there is nowhere to store the data . Only after the object is created will the memory be allocated to the member variable, and then the value can be assigned.

When a function is defined directly in a class, it is not necessary to add the class name before the function name, but when the function is defined outside the class, it is necessary to add the class name to the function name, which is called a domain resolver ::.

Member functions defined in the class body will automatically become inline functions , while those defined outside the class will not. The inline function will automatically replace the function call with the function body at compile time, which is not what we expect, so it is recommended to declare the function inside the class body and define it outside the class body .

class access rights

C++ passpublicprotectedprivateThree keywords to control access to member variables and member functions. They are public, protected, and private, respectively, and are called member access qualifiers . The so-called access right is whether you can use the members of this class.

Inside the class, all member functions and member variables can access each other without restrictions on access rights.

Outside the class, members can only be accessed through the object , and only members of public properties can be accessed through the object.

Most of the member variables m_start with , which is a customary way of writing. The beginning of m_the member variable can be seen at a glance, and the names of the formal parameters in the member function can be distinguished. Assignment to member variables generally uses public functions, set and get, and the variable name can be followed by the function. If public and private are not written, the default is private . In a class body, private and public can appear multiple times, and there is no valid scope until the occurrence of another access qualifier or the end of the class body.

In addition to the set function and get function, you can also call the constructor to initialize each member variable when creating an object, but the constructor can only initialize the value of each member variable, and cannot assign values ​​​​to member variables.

A class is a template for creating an object, it does not occupy memory space, and does not exist in the compiled executable file, but the object is real data that requires memory to store. When an object is created, memory is allocated in the stack or heap. The values ​​of member variables of different objects may be different, so separate memory is required for storage.

== The codes of the member functions of different objects are the same, and the code fragments can be compressed into one copy. ==The compiler will store the member variables and member functions of the object separately,Member variables allocate memory separately, but member functions share a piece of function code. Member variables allocate memory in the stack area or heap area,Member functions allocate memory in the code area. The size value of an object is only affected by member variables, and has nothing to do with member functions . The memory distribution of the class will also have memory alignment problems.

Compilation of C++ functions

The function of C language is simply added with an underscore when compiling (different compiler implementations are different).

When C++ is compiled, the function will be renamed according to the namespace , the class it belongs to , and the parameter list (parameter signature) and other information. Form a new function name, this new function name is known only to the compiler, this process of renaming the function is calledname code. The name encoding process is reversible, and knowing one of the names can infer the other.

Note : If you want to see the new function name generated by the compiler, you can only declare the function without defining the function, so that an error will appear when calling the function, and you can see the new function name from the error message.

member function call

The member function is finally compiled into a function that has nothing to do with the object. If the member variable is not used in the function, it can be called directly.

If a member variable is used in the member function, an additional parameter needs to be added when compiling the member function, and the current object is passed in as a pointer, and the member variable is accessed through the pointer. This is contrary to what we imagined when using objects to find functions, but actually finds objects through functions.

Constructor

There is a special member function whose name is the same as the class name , has no return value , does not need to be called explicitly by the user, and cannot be called by the user, but is a function that is automatically executed when the object is created . This special member function isConstructorConstructor)。

If you want to call the constructor, you need to pass the actual parameters while creating the object, and the actual parameters are ()surrounded by , which is very similar to a normal function call. When creating an object on the stack , the actual parameter is located after the object name, Student stu("小明", 15, 92.5f);. When an object is created on the heap , the implementation is placed after the class name, new Student("李华", 16, 96). The constructor is a public attribute , otherwise it will not be called when creating an object

The constructor has no return value , because there is no variable to receive the return value, so whether it is defined or declared, the return value type cannot appear before the function name and the return statement cannot be used in the function body . The constructor is allowed to be overloaded. A class can have multiple overloaded constructors. When creating an object, it is judged which constructor to call according to the parameters passed.

The constructor call ismandatoryYes, once the constructor is defined in the class, it will be called when the object is created. If there are multiple overloaded constructors, the actual parameters provided must match one of the constructors when the object is created. Only one constructor is called when an object is created. Constructors are generally used to do some initialization work in projects. If no constructor is defined, the compiler will automatically generate a constructor , which is empty inside the default constructor. A class must have a constructor, which is either defined by the user or automatically generated by the compiler. If the user defines the constructor, the compiler will not generate it automatically. Parentheses can be omitted when calling a constructor with no parameters .

Using constructor initialization can also useinitialization list, that is, when defining a constructor, it is not necessary to assign values ​​to variables in the function body, but to use a colon between the function header and the function body, followed by a statement, which is equivalent to the inside of the m_name(name), m_age(age), m_score(score)function bodym_name = name; m_age = age; m_score = score . For example:

Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){
		//TODO:
	}

There is no efficiency advantage to using an initializer list, only for writing and reading convenience. The initialization order of member variables has nothing to do with the order of the variables listed in the initialization list , only with the order in which the member variables are declared in the class . To allocate memory on the stack, the initial value is uncertain, to allocate memory on the heap, the initial value is 0.

Another important function of the constructor initialization list is to initialize const member variables, and this is the only way to initialize const variables

destructor

When destroying an object, the system will automatically call a function to clean up, such as releasing memory, closing open files, etc. This function isdestructordestructorDestructor) is also a special member function, has no return value, does not need to be called explicitly, and cannot be called, and will be called automatically when the object is destroyed.

The name of the constructor is the same as the class name , and the name of the destructor is added in front of the class name~ . Destructors have no parameters and cannot be overloaded, so a class can only have one destructor .

C++ uses new and delete to allocate and release memory. The biggest difference from malloc and free is that the constructor will be called when new is used to allocate memory, and the destructor will be called when delete is used to release memory .

global objectThe global data area stored in the memory area, the destructor of these objects will be called at the end of program execution.local objectThe stack area stored in memory, the destructor of these objects will be called when the function execution ends. The object created by new is located in the heap area in memory, and the destructor is called when delete is called. If there is no delete, the destructor will not be executed.

array of C++ objects

Each element of an array is an object, and such an array is called an object array . When the constructor has more than one element, the array initializer list explicitly contains the call to the constructor .

C++ member object

If a member variable of a class is an object of a class, it is called a member object . A class that contains member objects is called an enclosing class . When creating an object of the closed class, the member objects it contains also need to be created, which will trigger the call of the member object constructor.

When the closed class object is generated, the constructors of all member objects are executed first, and then the closed class's own constructor is executed . The execution order of member object constructors is consistent with the order of member objects in the class definition, regardless of the order in which they appear in the constructor initialization list.

When the object of the closed class dies, the destructor of the closed class is executed first, and then the destructor of the object member is executed. The execution order of the destructor of the member object is opposite to the execution order of the destructor, that is, the first constructed and then the executed destruct.

this pointer

thisIt is a keyword of C++ and also a const pointer ,point to the current object, through which all members of the current object can be accessed .this can only be used inside the class, all members of the class including public, protrcted, and private attributes can be accessed through this.

Although this is inside the class, it will only be assigned a value after the object is created , and this assignment process is automatically completed by the compiler.This is actually a formal parameter of a member function. When calling a member function, the address of the object is passed to this as an actual parameter, but this is added to the parameter list by the compiler itself during the compilation phase.

this as a parameter is essentially a local variable of the member function, which can only be used inside the member function, and is only assigned when the function is called through the object

Member functions will be compiled into ordinary functions that have nothing to do with the object during compilation. Except for member variables, all information will be lost. Therefore, when compiling, an additional parameter must be added to the member function to pass the current object as a pointer. In order to associate member variables and member functions, this additional parameter is this .

static member variable

Sometimes you want to share data between multiple objects, you can use static member variables to achieve the goal of multiple objects sharing data . A static member variable is a special member variable that is modified with the keyword static.Static member variables belong to the class and do not belong to a specific object. Even if multiple objects are created, a piece of memory is allocated, and all objects use the data in this memory

Static member variables must be initialized outside of the class declaration . Static member variables cannot be added static when initializing, but must have a data type .

The memory of the static member variable is neither allocated when the class is declared nor when the object is created, but allocated when it is initialized outside the class. So static member variables that are not initialized outside the class cannot be used .Static member variables can be accessed either through the class or through member variables. Static member variables do not occupy the memory of the object, but open up memory outside of all objects, and can be accessed even if the object is not created .

Like ordinary static variables, static member variables are allocated memory in the global data area in the memory partition , and are not released until the end of the program. Static member variables must be initialized and initialized outside the class. The initialization can be assigned or not assigned, and the default assignment is 0 .

Static member variables can be accessed either by object name or by class name . Requires keyword access restrictions.

static member function

Ordinary member functions can access all members, and static member functions can only access static members . When the compiler compiles an ordinary member function, it will add a formal parameter this to the function, and pass the address of the current object to this, so ordinary member functions can only be called through the object after the object is created.

Static member functions can be called directly through the class, the compiler will not add a this parameter to him, and does not need the address of the current object. Ordinary member variables occupy the memory of the object, but static members do not have this pointer, so they do not know which object it points to, and cannot access the member variables of the object . So static members cannot access ordinary member variables, only static member variables.

In C++, the main function of static member functions is to access static members. Static member functions can be called through objects and classes, but are generally called through classes.

const member functions and member variables

const member variableInitialization is only through the initializer list of the constructor .

const member functionAll member variables in the class can be used, but their values ​​cannot be modified. const member functions are also calledconst member function, usually set the get function to a const function.

Const member functions and const member variables need to add the const keyword at the definition and declaration, for example: int getage() const.

The const at the beginning of the function is used to modify the return value of the function, indicating that the return value is of const type. The const at the end of the function header means a constant member function,This function can only read the value of the member variable, but cannot modify the value of the member variable

const object

The const modified object is calledconstant object, the constant object can only call the const member of the class ,

Friend functions and friend classes

with the help ofTomomotofriend), allowing member functions in other classes and functions in the global scope to access the private members of the current class.

friend function, functions that are defined outside the current class and do not belong to the current class can also be declared in the current class, but the keyword keyword friend must be added in front of it, thus forming a friend function, which can not belong to any class Non-member functions can also be member functions of other classes.

Friend functions can access all members of the current class, including public, protected, and private attributes. The friend function is different from the member function of the class. In the friend function, the members of the class cannot be directly accessed, and the object must be used to access it.

In some cases, as long as the early declaration is made, the class can be tried first, but the scope of the early declaration of the class is limited, and it can only be used to create objects after a formal declaration. Because creating an object requires allocating memory, but there is no formal declaration, the compiler does not know how much memory to allocate.

A function can be declared as a friend function by multiple classes, so that private members in multiple classes can be accessed.

friend classAll member functions in are friend functions of another class.

Note:Friend relationships are one-way, not two-way. Friend relationships cannot be passed.

A class is also a kind of scope. Each class defines its own scope. Outside the scope of a class, ordinary members can only be accessed through objects. Static members can be accessed through both objects and classes. However, typedef defines The type of is only accessible through the class.

The difference between struct and class

  1. The members in the class are private by default, and the members in the struct are public.
  2. Class inheritance defaults to private inheritance, and struct inheritance defaults to public inheritance
  3. Class can use templates, but struct cannot use templates

string string

String is a class in C++, and header files need to be included when using it <string>. If the definition string is not initialized, the compiler will default to an empty string .

There is no end mark "\0" at the end of the string in C++, if you want to know the length of the string, you can use the one provided in string length(), and the string returns the official length instead of length+1 .

In actual programming, sometimes it is necessary to use C-style strings (such as the path when opening a file), for this,string classprovides us with aconversion functionc_str(), this function can convert the string string to a c-style string and return the const pointer of the string .

string path = "D:\\demo.txt";
FILE *fp = fopen(path.c_str(), "rt");

The string class overloads the input and output operators, you can treat string variables like ordinary variables, that is, use >>for input and use <<for input. And for the string class, you can use the +and +=operator to directly concatenate strings, which is very convenient, and you don't need to use strcat(), strcpy(), malloc()to concatenate strings.

Addition, deletion, modification and query of string strings

insert: function can insert another stringinsert() at the position specified in string : .string& insert(size_t pos, const string& str);

  • pos indicates the position to be inserted, that is, the subscript; str indicates the string to be inserted.

delete: erase()function can delete a substring in string: string& erase(size_t pos = 0, size_t len = npos);.

  • pos indicates the starting subscript of the substring to be deleted; len indicates the length of the substring to be deleted.

extract: substr()function can extract substring from string string: string substr (size_t pos = 0, size_t len = npos) const;.

  • pos is the actual subscript of the string to be extracted; len is the length of the string to be extracted

look up: find()The function can find the position where the substring occurs in the string string: Size_t find (const string& str, size_t pos = 0) const;.

  • The first parameter is the string to find; the second parameter is the subscript to start searching.
  • findThe function finally returns the actual subscript where the substring first appears in the string, or an infinite number if it is not found.

rfindThe difference between a function and finda function is that rfindthe function only finds the position of the second parameter. find_first_of()Function to find the first occurrence in a string of a character that both a substring and a string have in common.

In C language, there are two ways to represent strings:

  1. Use character arrays to hold strings, such strings are readable and writable.
  2. Represented by a string constant, such a string can only be read, not written .

In C++, string encapsulates information related to memory and capacity internally, so string knows its starting position in memory , the string sequence it contains , and the length of the character sequence .When the memory space is insufficient, string will also automatically adjust the memory size

quote

concept

Types such as char, int, and float are calledbasic type, data, structures, classes, etc. are calledaggregation type

In C++, there is a faster way to pass aggregate type data than pointers , that isquote. A reference can be regarded as an alias of data , and the data can be found through this alias and the original name.

References are defined like pointers, using the symbol & : type &name = data;.

  • type is the data type being referenced.
  • name is the name of the reference.
  • data is the referenced data.

References must be initialized at the time of definition, and will be consistent in the future, and no other data can be referenced, which is a bit similar to a constant (const variable).

When defining a reference, & should be added, but cannot be added when using it , and adding & means taking the address when using it. Since both the reference and the original variable point to the same address, the reference can also modify the data stored in the original variable.

If you don't want to modify the original data by reference, you can add a const when defining, const type &name = valueor type const &name = value, this reference method isOften quoted

When defining or declaring a function, you can specify the formal parameter of the function as a reference , so that when the function is called, the actual parameter and the formal parameter will be bound together so that they all refer to the same piece of data . If the data of the formal parameter is modified in the function body, the data of the actual parameter will also be modified , so as to affect the data outside the function inside the function. Passing parameters by reference is more intuitive than pointers in terms of usage, and encourages the use of references instead of pointers.

The problem that should be paid attention to when using references as function return values ​​is that local data cannot be returned , because local data will be destroyed after the function ends.

A reference simply encapsulates a pointer, and its bottom layer is still implemented through a pointer. The memory occupied by a reference is the same as the memory occupied by a pointer, which is 4 bytes in a 32-bit environment and 8 bytes in a 64-bit environment. bytes , the reason why the address of the reference cannot be obtained is because the compiler performs an internal conversion. Otherwise, the compiler obtains the address of the reference, and the reference will still occupy memory.

The difference between references and pointers

  1. When defining a reference, it must be initialized , and it cannot be changed later, that is, it cannot point to other data. The pointer does not have this restriction.
  2. Pointers can have multiple levels, but references can only have one level.

A pointer is the address of data or code in memory, and a pointer variable points to the data or code in memory. The pointer can only point to memory, not to registers or hard disks, because registers or hard disks cannot be addressed . Some data, such as the result of an expression, the return value of a function, etc., may be stored in a register. Once placed in a register, their addresses cannot be accessed through &, so pointers cannot be used to point to them.

The type of the pointer corresponds strictly to the type of the data it points to.

constant expression

An expression that contains no variables is calledconstant expression, because the constant expression does not contain variables, it can be evaluated at the compilation stage . The compiler will not allocate a separate memory to store the value of the constant expression, but will combine the value of the constant expression with the code and put it in A region of code in the virtual address space . Therefore, the constant expression is an immediate value from the perspective of assembly , which will be hard-coded into the instruction and cannot be addressed .

Therefore, although a constant expression is stored in memory, it cannot be addressed, so you cannot use & to obtain its address, let alone use a pointer to point to it .

The compiler will create a temporary variable for the const reference, making the reference more flexible and versatile. After adding const qualification to the reference, not only the reference can be bound to temporary data, but also the reference can be bound to data of similar type, which makes the reference more flexible and general, and the mechanisms behind them are all temporary variables. Therefore, the function parameter of the reference type should use coonst as much as possible. When the reference is used as a function parameter, if the value of the reference will not be modified inside the function, try to use the const restriction.

To sum it up, there are three reasons for adding a const-restricted type to a parameter of a reference type:

  1. Using const avoids programming errors that inadvertently modify data .
  2. const allows the function to receive const and non-const type arguments, otherwise it will only receive non-const type arguments .
  3. Using const references allows functions to correctly generate and use temporary variables.

Inheritance and Derivation

concept

inheritinheritance) is the process by which a class obtains member variables and member functions from another class .

derivationderive) and inheriting a concept, only from a different standpoint.

The inherited class is called parent class or base class , and the inherited class is called subclass or derived class . Derived classes have members of the base class and can also define their own new members to enhance the functionality of the class. Inherited members are accessible through subclass objects just like their own.

General syntax for inheritance: class 派生类 : 继承方式 基类{派生类新增加的成员};. Inheritance methods include public, private, protected, if not written, the default is private.

Protected members are similar to private functions and cannot be accessed through objects. But when there is an inheritance relationship, the protected members in the base class can be used in the derived class, but the private members in the base class cannot be used.

Access rights of different inheritance methods in derived classes

public inheritance

  • Members of public in the base class in the derived class are also public properties.
  • The properties of protected members in the base class are also protected in the derived class .
  • Private members of a base class cannot be used in derived classes.

protected inheritance

  • The public members in the base class are protected in the derived class attribute .
  • The protected member in the base class has the property protected in the derived class .
  • All private members in the base class cannot be used in the derived class.

private inheritance

  • All public members in the base class are private properties in the derived class .
  • All protected members in the base class are private in the derived class .
  • All private members in the base class cannot be used in the derived class.

Base class members must have no higher access rights in derived classes than those specified in Inheritance . Regardless of the inheritance method, private members in the base class cannot always be used in the derived class. Private members in the base class cannot be used by derived classes, not to say that they cannot be inherited. In development, public inheritance is generally used.

The only way to access private members of a base class in a derived class is through non-private member functions of the base class

Using the using keyword can modify the access rights of public and protected members in the base class, but cannot change the access rights of private members.

	//派生类Student
	class Student : public People {
	public:
		void learning();
	public:
		using People::m_name;  //将protected改为public
		using People::m_age;  //将protected改为public
		float m_score;
	private:
		using People::show;  //将public改为private
	};

Members in the derived class and members of the base class have the same name, and the members inherited from the base class will be shadowed . The base class member function will cause shadowing as long as the name is the same regardless of the parameters, so the base class member function and the derived class member function will not constitute overloading .

The scope of the derived class is nested in the scope of the base class . If a name cannot be found in the scope of the derived class, the compiler will continue to look for the definition of the name in the outer base class scope. If a name is defined or declared in the outer scope, then the inner scope can access this name. The name lookup is gradually searched from the inner layer to the outer layer. If the inner layer is found, it will not be searched. This process is calledname lookup

The compiler will only use functions with the same name in the same scope as options for overloaded functions

Memory Model When Inheriting

When inheriting, the memory of the derived class can be regarded as the sum of the base class members and the newly added member variables , allMember functions are still stored in the code area, shared by all objects. In the object model of the derived class, all base class member variables will be included. This method can directly access the base class member variables without going through several layers of indirect calculations.

Constructors for base and derived classes

The constructor of the class cannot be inherited , and the initialization of the member function inherited by the derived class also needs the constructor of the derived class to complete, but the private variable in the base class cannot be initialized with the constructor of the derived class, because the derived class cannot be accessed.

In the initialization list of the derived class, you can call the constructor of the base class to initialize variables. The derived class always calls the constructor of the base class first , and then executes other code. The following code is that the constructor of student calls the constructor of people to initialize.

Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }

It should be noted that the constructor will not be inherited, so the constructor of the base class can only be placed in the head of the function, not in the body of the function, and cannot be used as an ordinary function. Here, the function header is a call to the constructor, not a declaration, so what is passed here is the actual parameter, so what is passed here can be not only the parameters in the parameter list of the derived class constructor, but also local variables, constants, etc.

The order in which constructors are called is top-down, in accordance with the hierarchy of inheritance, from the base class to the derived class. Derived classes can only call constructors of direct base classes, not indirect base classes.

When creating an object from a derived class, the constructor of the base class must be called.

Destructors for base and derived classes

The destructor cannot be inherited either . In the destructor of the derived class, there is no need to explicitly call the destructor of the base class, because each class has a destructor, and the compiler knows how to choose.Destructors execute in the opposite order of constructors

multiple inheritance

A derived class with multiple base classes is called multiple inheritance . Multiple inheritance can easily complicate code logic, so multiple inheritance is canceled in Java, C#, and PHP.

class D: public A, private B, protected C {
    //类D新增加的成员
}

The order in which the base class constructors are called has nothing to do with the order in which they appear in the derived class constructors, but the order in which the base classes appear when the derived class is declared.

When there are members with the same name in multiple base classes, direct access will cause naming conflicts, and the compiler does not know which member of the base class to use. In this case, the class name and domain resolver must be added:: .

Object Memory Model in Multiple Inheritance

The base class objects are listed in the same order as they were declared when inherited.

Access private members with pointers

C++ does not allow access to private members through objects, whether through object variables or through object pointers. However, this restriction is at the grammatical level, and private member variables can still be accessed through pointers.

use offset

There is a certain distance between the member variable and the beginning of the object. As long as you know the offset of the member variable from the head of the object, you can access it through the pointer. So if you know the starting address of the object, and then add the offset of the variable, you can know the address of the variable, and if you know the address and type of the member variable, you can know its value.

In fact, when accessing member variables through object pointers, the compiler actually obtains values ​​​​in this way, as shown in the following code.

class A{
public:
    A(int a, int b, int c);
public:
    int m_a;
    int m_b;
    int m_c;
};

int b = p->m_b;
//上一句在编译器内部会转换为下面这样
int b = *(int*)( (int)p + sizeof(int) );
//其中p 是对象 obj 的指针,(int)p将指针转换为一个整数,这样才能进行加法运算;sizeof(int)用来计算 m_b 的偏移;(int)p + sizeof(int)得到的就是 m_b 的地址,不过因为此时是int类型,所以还需要强制转换为int *类型;开头的*用来获取地址上的数据

//如果使用对象指针访问变量m_a的话如下
int a = p -> m_a;
//在编译器中转换为
int a = * (int*) ( (int)p + 0 );

Breaking through the restrictions on access rights requires us to manually convert, rather than using the compiler to automatically convert. As long as the offset can be calculated correctly.

The following code is the offset of the pointer to access the private variable

#include <iostream>
using namespace std;
class A{
public:
    A(int a, int b, int c);
private:
    int m_a;
    int m_b;
    int m_c;
};
A::A(int a, int b, int c): m_a(a), m_b(b), m_c(c){ }
int main(){
    A obj(10, 20, 30);
    int a1 = *(int*)&obj;
    int b = *(int*)( (int)&obj + sizeof(int) );
    A *p = new A(40, 50, 60);
    int a2 = *(int*)p;
    int c = *(int*)( (int)p + sizeof(int)*2 );
   
    cout<<"a1="<<a1<<", a2="<<a2<<", b="<<b<<", c="<<c<<endl;
    return 0;
}

The result of the operation is that it can be seen that C++ does not allow access to private variables only at the grammatical level, which means that access rights only work a1=10, a2=40, b=20, c=60on the sum of member operators . But there is no way to prevent direct access through pointers..->

Virtual Inheritance and Virtual Base Classes

In order to solve the problems of naming conflicts and redundant data in multiple inheritance, the proposedvirtual inheritance, so that only one indirect base class member is reserved in the derived class. Adding a keyword in front of the inheritance method virtualis virtual inheritance.

class B: virtual public A{  //虚继承
 //todo
};

The purpose of virtual inheritance is to allow a class to make a declaration that it is willing to share its base class. The shared base classes are called virtual base classes . No matter how many times the virtual base class appears in the inheritance system, only one member of the virtual base class is included in the derived class.

Therefore, the operation of virtual derivation must be completed before the real demand for virtual derivation appears. Virtual derivation only affects classes that are further derived from the derived class of the virtual base class, and does not affect the derived class itself.

Visibility of virtual members

Only one member of the virtual base class is saved in the final derived class of virtual inheritance, so this member can be directly accessed without ambiguity. If a member of a virtual base class is covered by only one derivation path, then the covered member can still be accessed. If the member is covered by two or more paths, it cannot be accessed directly for a long time, and it is necessary to indicate which class the member belongs to.

The use of multiple inheritance is discouraged .

Constructors with virtual inheritance

In virtual inheritance, the virtual base class is initialized by the final derived class, so the constructor of the final derived class must call the constructor of the virtual base class. For the ultimate derived class, the virtual base class is an indirect base class, not a direct base class. In normal inheritance, the derived class constructor can only call the constructor of the direct base class, but not the indirect base class.

The execution order of the virtual inheritance constructor is different from that of ordinary inheritance. In the constructor call list of the final derived class, regardless of the order in which the constructors appear, the compiler always calls the constructor of the virtual base class first, and then calls other constructors in order. Constructor. In normal inheritance, the order in which constructors are called is in accordance with the order in which the base classes appear when the class is declared.

Memory model when virtual inheritance

For ordinary inheritance, the base class object is always located opposite the derived class object, no matter how deep the inheritance hierarchy is, its offset relative to the top of the derived class object is fixed.

For virtual inheritance, contrary to ordinary inheritance, most compilers will put the base class member variables behind the derived class member variables, so that as the inheritance level increases, the offset of the base class member variables will change, so that It is no longer possible to use fixed offsets to access base class members.

Virtual inheritance divides the derived class into a fixed part and a shared part , and puts the shared part at the end.

Assign derived class to base class (upcast)

A class is also a data structure, and data type conversion can occur, but this conversion is only meaningful between the base class and the derived class , and only the derived class can be assigned to the base class , including theAssignment of derived class object to base class object,WillAssignment of derived class pointer to base class pointer,WillAssignment of derived class reference to base class reference, which in C++ is calledUpward transformation. Of course, assigning the base class to the derived class is calledDownward transformation

Upcasting is very safe and can be done automatically by the compiler, downcasting is risky and requires manual intervention.

The essence of assignment is to write the existing data into the allocated memory. The memory of the object only contains member variables, so the assignment between objects is the assignment of member variables, and there is no assignment problem in member functions .

Assigning a derived class object to a base class object will discard the new members of the derived class. This conversion relationship is irreversible. Only derived class objects can be used to assign values ​​to base class objects, and base class objects cannot be assigned to derived class objects. Because the base class object does not contain the newly added members in the derived class object.

Assign derived class pointer to base class pointer

A derived class pointer can be assigned to a base class pointer. Unlike the assignment of object variables, the assignment of object pointers does not need to copy the member variables of the object, nor does it modify the data of the object, but only changes the pointing of the pointer.

Access derived class members through base class pointers

When the derived class pointer is assigned to the base class pointer, only the member variables of the derived class can be used through the base class pointer, and the member functions of the derived class cannot be used. The member functions used are still of the base class .

This is because after the pointer of the derived class is assigned to the pointer of the base class, the pointer of the base class points to the object of the derived class, so that the this pointer changes and points to the object of the derived class, so the member variable accessed is derived Category. But the compiler accesses member variables through base class pointers, but not through pointers to member functions.The compiler accesses member functions through the type of pointer. So even if the pointer of the derived class is assigned to the pointer of the base class, the type of the pointer of the base class does not change.

So in summary:The compiler accesses member variables through pointers, and the data of that object is used when the pointer points to the object; the compiler accesses member functions through the type of the pointer, and the member variable of the class is used for the type of the class to which the pointer belongs

Assign derived class reference to base class reference

References are essentially implemented using pointers, so the effects of pointers and references are the same.

Assign derived class pointer to base class pointer procedure

After assigning the derived class pointer to the base class pointer, you will find that their values ​​may or may not be equal.

The compiler may perform some processing on the existing value before assignment, such as assigning double type data to an int type variable, the compiler will erase the decimal part. The same is true when assigning a pointer of a derived class to a pointer of a base class. The compiler may perform some processing on the value before the assignment.

Polymorphism and virtual functions

concept

Because the base class pointer can only access the member variables of the derived class, but cannot access the member functions of the derived class. C++ addedvirtual function, to let the base class pointer access the member function of the derived class , and the virtual function only needs to add keywords in front of the function declaration virtual.

Using virtual functions, the members of the base class are used when the base class pointer points to the base class object, that is, member variables and member functions can be used. When pointing to a derived class, a member of the derived class is used. So the base class pointer can do things in the way of the base class, and can also do things in the way of the derived class. There are many forms, or ways of expression. This phenomenon is calledpolymorphism

The only role of virtual functions is to form polymorphism .

The purpose of providing polymorphism is: through the base class pointer, the member variables and member functions of the derived class (directly derived and indirectly derived) can be accessed in all directions. If there is no polymorphism, only member variables of derived classes can be accessed. The virtual function is called according to the point of the pointer, and the function of which class is called when the pointer points to the object of the class .

References are essentially pointers, so references can also achieve polymorphism, but because references cannot be changed after initialization, they lack expressiveness in terms of polymorphism.

For large and medium-sized programs with complex inheritance relationships, polymorphism can increase its flexibility and make the code more expressive. If there are many derived classes in a program, multiple pointers need to be defined if polymorphism is not used, but if polymorphism is used, only one pointer variable is needed to call the virtual functions of all derived classes.

virtual function

virtual functionYou only need to add the keyword when the function is declaredvirtual , and it can be added or not when the function is defined. You can only declare the functions in the base class as virtual functions, so that all functions with the same name in the derived class that have a shadow relationship will automatically become virtual functions.

When a virtual function is defined in the base class, if no derived class defines a new virtual function to shadow this function, then the virtual function of the base class will be used. Only when the virtual function of the derived class overrides the virtual function of the base class can polymorphism be formed, that is, the derived class function is accessed through the base class pointer.

Constructor cannot be virtual

Destructors can, and sometimes must, be declared virtual.

Encapsulation, inheritance, and polymorphism—the three major characteristics of object-oriented

conditions that constitute polymorphism

  1. An inheritance relationship must exist.
  2. There must be a virtual function with the same name in the inheritance relationship, and there must be an overlapping relationship between them.
  3. There is a pointer to the base class, and the virtual function is called through the pointer.
  4. The base class pointer can only access the members inherited from the base class, and cannot access the newly added members of the derived class.
  5. If a member function of a class wants to change its function after inheritance, it is generally declared as a virtual function.

virtual destructor

Constructors cannot be virtual because:

  1. Derived classes cannot inherit the constructor of the base class, and there is no point in declaring the constructor as a virtual function.
  2. The constructor in C++ is used to initialize the object when it is created. The object has not been created before the constructor is executed, and the virtual function table does not exist.

Destructors are generally declared virtual by default :

In the case of default writing, if the destructor is not defined as a virtual function, it is easy to cause memory leaks. Because when deleting the object pointer, if it is a derived class pointed to by the base class pointer, then when deleting the derived class, the destructor will be called. Because when calling a member function through a pointer, the compiler will call the member function according to the type of the pointer instead of the object pointed to by the pointer . So this will cause the destructor of the base class to be called instead of the destructor of the derived class. If the memory is applied in the constructor of the derived class, it will not be released, resulting in a memory leak.

The destructor of the derived class always calls the destructor of the base class. Declaring the destructor of the base class as a virtual function automatically makes the destructor of the derived class virtual. At this time, the compiler will not select the member function according to the type of the pointer, but according to the object pointed to by the pointer.

In most cases, the destructor of the base class is declared as a virtual function .

Pure virtual functions and abstract classes

A virtual function can be declared aspure virtual function

virtual 返回值类型 函数名 (函数参数) = 0;

A pure virtual function has no function body, only a function declaration, which is added at the end of the virtual function declaration =0to indicate that it is a pure virtual function

最后的=0并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。

A class containing pure virtual functions is calledabstract class, the reason why it is called an abstract class is becausecannot be instantiated, that isCould not create object. A pure virtual function has no function body, is not a complete function, cannot be called, and cannot allocate memory space for it.

Abstract classes are usually used as base classes to allow derived classes to implement pure virtual functions , and derived classes must implement pure virtual functions before they can be instantiated . Define an abstract base class, which only completes part of the functions, and the unfinished functions are handed over to derived classes for implementation. These functions are often not required by the base class, or cannot be implemented in the base class.

Although the pure virtual function declared by the abstract class is not implemented, it is mandatory for the derived class to implement, otherwise the instantiation cannot be completed .

In addition to restricting the functions of derived classes, abstract classes can also implement polymorphism. Because if there is no declaration in the base class, an error will occur when using the base class pointer to call the member function in the derived class.

Only virtual functions can be declared pure virtual.

Polymorphic Implementation Mechanism

If the function is a virtual function and it is covered by a function of the same name of a derived class, the compiler will find the function according to the pointer. That is to say, the function of that class will be called when the object pointed to by the pointer belongs to which class. This is polymorphism .

The reason why the compiler can find the virtual function through the object pointed to by the pointer is because an additionalvirtual function table

If a class contains a virtual function, an additional array will be added when creating an object of this class, and each element of the array is the entry address of the virtual function. However, the array and the object are stored separately. In order to associate the object with the array, the compiler also needs to insert a pointer into the object, pointing to the starting address of the array. This array is the virtual function table, abbreviated as vtable .

There is a pointer at the beginning of the object, pointing to the virtual function table, and this pointer is always located at the beginning of the object .

In the virtual function table, the subscript of the virtual function of the base class in the vtable is fixed and will not change with the increase of the inheritance level. The new virtual function of the derived class is placed at the end of the vtable. If there are If the virtual function of the same name shadows the virtual function of the base class, then the virtual function of the derived class will be used to replace the virtual function of the base class. Such a virtual function with a shadowing relationship will only appear once in the vtable .

When calling a virtual function through a pointer, first find the entry address of the virtual function according to the pointer vfptr.

p -> display()
( *( *(p+0) + 0 ) )(p);//上面的调用在编译器中会转换成下面这样

It can be found that the converted expression is fixed, as long as the virtual function is called, this expression will be used no matter which class the value is in. In other words, the compiler doesn't care where the base class pointer points. Always convert to the same expression.

The converted expression does not use the information related to the type of p, and the function can be called as long as the pointer is known, which is fundamentally different from the name encoding algorithm.

typeid operator

typeid is used to obtain the type information of an expression. For basic types of data, the type information is relatively simple, mainly referring to the type of data. For class-type data, that is, objects, the type information refers to the class to which the object belongs, the members it contains, the inheritance relationship it is in, and so on.

Type information is the template for creating data. How much memory the data occupies and what operations can be performed are all determined by type information. The operation object of typeid can be either an expression or a data type .

typeid( dataType )
typeid( expression )

typeid must be enclosed in parentheses, and typeid will save the obtained type information in an object of type_info type, andreturns a constant reference to the object. Several member functions of the returned type_info class:

  • name()The name used to return the type.
  • raw_name()Used to return the new name generated by the name encoding algorithm.
  • hash_code()Used to return the hash value corresponding to the current type.

The C++ standard stipulates that the type_info class must have at least the following four public functions:

  1. const char* name() const;: Returns a string representing the type name.
  2. bool before (const type_info& rhs) const;: To determine whether a type is in front of another type, the rhs parameter is a reference to a type_info object.
  3. bool operator== (const type_info& rhs) const;: Overloaded operator ==to determine whether two types are the same, the rhs parameter is a reference to a type_info object.
  4. bool operator!= (const type_info& rhs) const;: Overloaded operator !=to determine whether two types are different, the rhs parameter is a reference to a type_info object.

The typeid operator is often used to determine whether two types are equal.No matter how many times a type is used, the compiler will only create an object for it, and all typeids will return a reference to this object.

But in order to reduce the compiled file size, the compiler will not create type_info objects for all types, only for types that use the typeid operator. But for classes with virtual functions, regardless of whether the typeid operator is used, the compiler will create a type_info object for the class with virtual functions.

type recognition mechanism

Generally, the type of an expression can be determined during compilation, but when there is polymorphism, the type of some expressions cannot be determined during compilation, and must be determined according to the actual environment after the program runs.

object memory model

  • If there are no virtual functions and no virtual inheritance, then there are only member variables in the object memory model
  • If the class contains virtual functions , an additional virtual function table will be added , and a pointer will be inserted into the object memory to point to this virtual function table. At the same time, if the object memory of this class will also add additional type information, that is, the type_info object.
  • If the class contains virtual inheritance , an additional virtual base class table will be added , and a pointer will be inserted in the object memory to point to this virtual base class table.

If the class contains virtual functions, the compiler will not only create a virtual function table at the beginning of the object, but also insert a pointer at the beginning of the virtual function table, pointing to the corresponding type_info object in the current. When the current program obtains the type information at the running stage, it can find the virtual function table pointer through the object pointer, and then find the pointer of the type_info object through the virtual function table pointer, and then obtain the type information.

The compiler cannot determine which object the base class pointer points to during the compilation phase, so it cannot obtain the type information of the object pointed to by the pointer, but the compiler can make various preparations during the compilation phase, so that the program can use these prepared data after running to get type information, which includes:

  • Create a type_info object, and insert a pointer to the type_info object at the beginning of the virtual function table.
  • Transform operations that get type information into pointer-like statements. **(p->vfptr - 1).

This mechanism of determining the type information of an object after the program runs is calledruntime type recognitionRTTI). In C++, the RTTI mechanism is only used when virtual functions are included, and the type information can be determined at the compilation stage in all other cases.

Polymorphism is an important feature of object-oriented programming, which greatly increases the flexibility of the program, but the cost of supporting polymorphism is also very high. Some information cannot be determined in advance at the compilation stage, which will consume more memory and CPU resources.

What the cpu needs to access the memory is the address, and the operation of matching the variable name, function name and address is calledsymbol binding. Under normal circumstances, the address corresponding to the function name can be found during compilation, the binding of the function is completed, and the program can be used directly after running. This is calledstatic binding. Sometimes it is not possible to determine which function to call during compilation, and it must be decided according to the specific environment or operation after the program is running. This is calleddynamic binding

static language: The type is explicitly specified at the time of definition, and it cannot be changed after the type is specified, so the compiler can know the type of most expressions during compilation. This language is called a static language.

dynamic language: There is no need to specify the type when defining a variable, and the type of the variable can be changed at any time. The compiler cannot determine the type information of the expression during compilation. It can only be obtained after the program is running. It is called a dynamic language.

In order to be flexible and easy to deploy, dynamic languages ​​are usually compiled and executed at the same time, blurring the traditional process of compiling and running.

operator overloading

concept

operator overloadingIt is to let the same operator have different functions. In practical application, +the addition operation of different types of data <<can be used as a shift operator or as a symbol of cout to output, these are operator overloading.

Operator overloading is to define a function and realize the desired function inside the function body. When an operator is needed, the compiler will automatically call this function. soOperator overloading is achieved through functions, which is essentially function overloading. The format of operator overloading is:

返回值类型 operator 运算符名称(形参列表){
    //TO DO
}
operator是关键字,专门用于定义重载运算符的函数。

Operator overloading functions are no different from ordinary functions except that the function name has a specific format . Operator overloading functions can be used not only as member functions of classes, but also as global functions.

  1. Not all operators can be overloaded , length operators sizeof, conditional operators :?, member operators ., domain resolution operators ::cannot be overloaded.
  2. Overloading cannot change the precedence and associativity of operators.
  3. Overloading does not change the usage of the operator , how many operands there are, whether the operands are on the left or right, these do not change.
  4. Operator overloading functions cannot have default parameters , otherwise the number of operator operands will be changed.
  5. Operator overloading functions can be used either as member functions of a class or as global functions.
  6. Arrow operators ->, subscript operators [], function call operators (), and assignment operators =can only be overloaded as member functions.

When the operator overloading function is used as a member function of a class, the binary operator has only one parameter, and the unary operator does not need a parameter. Because when used as a member function of a class, one parameter is implicit. For example, when it is overloaded +, it will be converted to access the member variable of c1 implicitly through the this pointer c3 = c1 + c2during compilation .c3 = c1.operator+(c2)

When the operator overloading function is used as a global function, the binary operator needs two parameters, and the unary operator needs one parameter, and one of the parameters must be an object, so that the compiler knows that this is an operator defined by the programmer to prevent Modify the operators used for built-in types.

When an operator overloaded function is used as a global function, it is generally necessary to declare the function as a friend function in the class.

overload <<and>>

The left shift and right shift operators have been overloaded in the standard library so that they can be used for different data input and output. But the input and output objects can only be C++ built-in data types and data types contained in the standard library. If you define a data type yourself, you need to overload it yourself if you want to use <<and >>.

cout is an object of the ostream class, and cin is an object of the istream class . If you want to overload <<and , >>you must overload and in the form of a global function , otherwise you must modify the class in the standard library.<<>>

Overloading the input operator >>:

istream & operator>>(istream &in, complex &A){
	in >> A.m_real >> A.m_imag;
  return in;
}

Among them, istream represents the input stream, and cin is an object of the istream class, but this object is defined in the standard library.The purpose of returning references is to be able to read complex numbers continuously. If you do not return references, you can only read them one by one.. And the operator overloading function uses the private variable in the complex, which must be declared as a friend function in the complex.

Overloaded output operator <<:

ostream & operator<<(ostream &out, complex &A){
	out << A.m_real << " + " << A.m_imag << "i";
  return out;
}

ostream represents the output stream, and cout is an object of the ostream class.

overloaded[]`

C++ stipulates that the subscript operator must be overloaded in the form of a member function .

1.返回值类型 & operator[](参数);
或者是:
2.const 返回值类型 & operator[](参数) const;

With the first declaration, []you can not only access elements, but also modify them.

With the second method, []the element can only be accessed and cannot be modified. In practical applications, we should provide two forms to adapt to const objects, because only const member functions can be called through const objects, and if the second form is not provided, any elements of const objects cannot be accessed .

overload ++and--

The self-increment ++and self-decrement operators are unary operators , and both their pre- and post-forms can be overloaded.

1.返回值类型 operator++(); //++i,前置形式
2.返回值类型 operator++(int i); //i++,后置形式

operator++();function implements auto-incrementprefix form, returns the running result by itself.

operator++(int i);function implements auto-incrementpost form, the return value is the object itself, but when the object is used again later, the object is self-incremented, so in the function body of the function, first save the object, then call the run() function once, and then save the first saved object returned. In this function, the parameter i has no meaning, it just distinguishes whether it is a preposition or a postposition.

overload newanddelete

The memory operators new, new[], delete[], deletecan also be overloaded, and the overloaded form can be either a member function or a global function .

Overload new as a member function

void* classname::operator new(size_t size){
	//todo;
}

Overload new as a global function

void* operator new(size_t size){
	//todo;
}

When overloading new and new[], whether as a member function or a global function,The first parameter must be of type size_t. size_t represents the size of the space to be allocated.For the overloaded function of new[], size_t represents the sum of all the spaces that need to be allocated

size_t 在头文件<cstdio>中被定义为 typedef unsigned int size_t;就是无符号整形

Of course, the overloaded function can have other parameters, but they all must have a default value, and the type of the first parameter must be size_t.

Overload delete as a member function

void* classname::operator delete(void *ptr){
	//todo;
}

Overload delete as a global function

void* operator delete(void *ptr){
	//todo;
}

Overloading the cast operator()

In C++, the name of the type (including the name of the class) itself is also an operator, that is, the type cast operator .

The type coercion operator is a unary operator and can also be overloaded, but it can only be overloaded as a member function , not a global function. After overloading, (类型名)对象the expression that casts the object is equivalent to 对象.operator 类型名(), that is, becomes a call to the operator function.

When overloading the cast operator, there is no need to specify the return value type, because the return value type is determined, which is the type represented by the operator itself .

Precautions

  • The meaning of the operator after overloading should conform to the original usage habits.
  • operator overloadingDoes not change operator precedence
  • ., .*, ::, ?:, sizeofetc. operatorscannot be overloaded
  • When overloading operators (), [], ->,=Can only be overloaded as a member function, cannot be overloaded as a global function.
  • Operator overloading is actually overloading the operator into a function, and the expression using the operator is interpreted as a call to the overloaded function.
  • An operator can be overloaded as a global function. At this time, the number of parameters of the function is the number of operands of the operator, and the operands of the operator become the actual parameters of the function.
  • An operator can be overloaded as a member function. At this time, the number of function parameters is the operand of the operator minus one. One of the operands of the operator becomes the object of action, and the rest become the actual parameters of the function.
  • The name of the type can be used as a cast operator, or it can be overloaded as a member function of the class, so that the object is automatically converted to a certain type.
  • The self-increment and self-decrement operators each have two overloading modes, which are used to distinguish between pre-usage and post-usage.

template

concept

In C++, the type of data can also be passed through parameters. When the function is defined, the specific data type can not be named. When the function is called, the compiler can automatically infer the data type according to the actual parameters passed in. This istype parameterization. Value and type are the two main characteristics of data, both of which can be parameterized in C++ .

function templateis to create aUniversal function, the data types to be used (including the return value type, formal parameter type, and local variable type) can not be specified specifically, but a virtual type is used instead, and when a function call occurs, it is reversed according to the actual parameter passed in Bring out the real type.

Once a function template is defined, type parameters can be used in function definitions and function declarations. intWhere built-in types such as , float, etc. were originally used char, function parameters can be used instead.

templateIt is a keyword to define a function template , followed by angle brackets <>, and the angle brackets surround type parameters. typename is another keyword used to declare specific type parameters, template<typename T>calledtemplate header

template <typename 类型参数1, typename 类型参数2, ...> 返回值类型 函数名(形参列表){
    //在函数体中可以使用类型参数
}

There can be many type parameters, but they need to be separated by commas, the type parameters are surrounded by <>, and the formal parameters are ()surrounded by . The typename keyword can be replaced by the class keyword , because early c++ did not introduce new keywords, but used the class keyword to indicate the type parameter.

Function templates can be declared in advance, but the declaration needs to be accompanied by a template header, and the template header and function definition (declaration) are an inseparable whole , which can be wrapped, but semicolons cannot be added.

In addition to function templates, class templates are also supported. Type parameters defined in function templates can be used in function declarations and function definitions, and type parameters defined in class templates can be used in class declarations and class implementations. The purpose of class templates is also to parameterize data types.

template <typename 类型参数1, typename 类型参数2,...> class 类名{
  //todo
};

Class templates are the same as function templates, the type parameters cannot be empty, and multiple type parameters are separated by commas.

Once a class template is declared, type parameters can be used in the class's member functions and member variables. In addition to adding a template header to the class declaration, you also need to add a template header when defining member functions outside the class.

template <typename 类型参数1, typename 类型参数2, ...>
返回值类型 类名<类型参数1, 类型参数2, ...>::函数名(形参列表){
//todo
}
  • Note: In addition to the need to specify the type parameter after the template keyword, the type parameter is also required after the class name point, but the typename keyword is not added.

Unlike function templates, class templates must specify the data type when instantiating , and the compiler cannot infer the data type based on the given data. When using object pointers for instantiation, it is also necessary to specify specific data types on both sides and keep them consistent.

The types supported by templates are broad and unlimited, and can be replaced by any type. This programming is calledgeneric programming. The parameter T can be regarded as a generic type, and int, char, etc. can be regarded as a specific type.

C++ allows overloading of function templates.

  • Note: Programming languages ​​can be divided into strongly typed languages ​​and weakly typed languages ​​according to whether the language needs to explicitly indicate the data type when defining variables . A strongly typed language needs to specify the data type when defining a variable, and once a certain data type is specified for the variable, the variable cannot be assigned other types of data in the future, unless it undergoes mandatory type conversion and implicit conversion.
int a = 100;  //不转换
a = 12.34;  //隐式转换(直接舍去小数部分,得到12)
a = (int)"http://c.biancheng.net";  //强制转换(得到字符串的地址) 

A weakly typed language does not need to explicitly specify the data type when defining a variable. The compiler will automatically deduce the type according to the data assigned to the variable, and can assign different types of data to the variable, and can assign different types of data to the variable.

var a = 100;  //赋给整数
a = 12.34;  //赋给小数
a = "http://c.biancheng.net";  //赋给字符串
a = new Array("JavaScript","React","JSON");  //赋给数组

Whether it is a strongly typed or weakly typed language, there is a type system inside the compiler to maintain various information about variables.

For a strongly typed language, the compiler can detect whether the operation of the variable is correct during compilation, so that there is no need to maintain a set of type information when the program is executed, reducing memory usage and speeding up the running of the program. However, strongly typed languages ​​also have some types that cannot be determined during compilation, such as polymorphism in C++. The compiler will add virtual function tables, type_info and other auxiliary information to the object memory model during compilation to maintain a complete inheritance Chain, wait until the program is running to determine which function to call. Compilation is of little significance for weakly typed languages, because even if it is compiled, there are many things that cannot be determined.

Weakly typed languages ​​​​are generally compiled and executed once, so that a lot of useful information can be derived according to the context. This kind of language that is compiled and executed once is called an interpreted language , while the traditional one that is compiled first and then executed is called a compiled language. .

Strongly typed languages ​​are more rigorous, and many errors can be found during compilation, and are suitable for developing large-scale, system-level, and industrial-level projects; while weakly typed languages ​​are more flexible, have high coding efficiency, easy deployment, and low learning costs. Show your skills. In addition, IDEs for strongly typed languages ​​are generally more powerful, with good code awareness and rich prompt information; while for weakly typed languages, codes are generally written directly in the editor.

argument inference

When creating an object using a class template, you need to explicitly specify the actual parameters. For example, when creating an object below, you need to specify the type of the actual parameter. In this way, when compiling, the compiler does not need to infer it by itself, and can be used directly.

template<typename T1, typename T2> class Point;
Point<int, int> p1(10, 20);  //在栈上创建对象
Point<char*, char*> *p = new Point<char*, char*>("东京180度", "北纬210度");  //在堆上创建对象

For function templates, the actual parameters can not be explicitly specified when calling the function

//函数声明
template<typename T> void Swap(T &a, T &b);
//函数调用
int n1 = 100, n2 = 200;
Swap(n1, n2);
float f1 = 12.5, f2 = 56.93;
Swap(f1, f2);

The compiler will automatically infer the type of T according to the type of the actual parameter . This process of determining the template argument through the function argument is calledtemplate argument inference

Actual parameter inference of a function template refers to the process of finding the specific type of the type parameter according to the type of the actual parameter during the function call process. This works in most cases, but when there are many type parameters, there will be Individual types cannot be inferred. In this case, the actual parameters must be explicitly specified. In other words, if the compiler cannot infer all the types in the template based on the actual parameters, a call error will occur.

The method of specifying actual parameters for function templates and class templates is the same, they are all added after the function name <>, including the specific type. Explicitly specified template arguments are matched against corresponding template arguments in left-to-right order.

func<int, int>(10);

non-type parameter

Template is a generic technology , the purpose is to parameterize the type of data and enhance the flexibility of the language. In addition to supporting type parameters, templates also support non-type parameters. Non-type parameters are used to pass parameters, not types. Like ordinary formal parameters, specific types need to be specified. When calling a function template or creating an object through a class template, non-type parameters will be provided by the user or the compiler infer.

The type of a non-type parameter cannot be specified arbitrarily, it can only be an integer or a pointer to an object or a function. When the non-type parameter is an integer, the actual parameter passed to it, or the actual parameter deduced by the compiler must be a constant expression.

When the non-type parameter is a pointer, the actual parameter bound to the pointer must have a static lifetime, so the actual parameter must be stored in the static data area of ​​the virtual address space. Local variables are located in the stack area, and dynamically created objects are located in the heap area.

instantiate

Templates don't take up memory, the final generated function or class will occupy memory,The process of generating a function or class from a template is called template instantiation, a specific version of a function or class generated for a type is called an instance of a template.

A template can be seen as a set of instructions for the compiler, which instructs the compiler to generate the code we want. The instantiation of the template is carried out on demand, whichever type is used will generate a function or class for that type, and will not generate too much code in advance. The compiler generates a specific version of the function or class based on the actual parameters passed to the type parameter, and the same type is generated only once.

The instantiation of class templates does not instantiate all member functions when creating objects through class templates. It will only be instantiated when it is actually called. If a member function is never called, it will never be instantiated. So instantiation is lazily local.

template reference multi-file programming

Regardless of whether it is a class or a function, the separation of declaration and definition is the same. The function definition is placed in other files. There is only one problem to be solved in the end, which is to match the function call with the function definition (find the address of the function definition , and filled into the function call), the completion of this work is the connector.

But in templates, it's not right to split the template's declaration and definition into separate concerns. Templates are not real functions or classes, but just a drawing for generating functions or classes.

  • The instantiation of the template is carried out on demand , and the function or class for that type will be generated for which type is used, and the code will not be generated in advance.
  • Instantiation of templates is done by the compiler, not by the linker.
  • During the instantiation process, you need to know the details of the template, including declarations and definitions.

So the template is just a template, which does not occupy memory. When compiling, the compiler generates the corresponding type of code according to the needs, so the instantiation of the template is done by the compiler. If it is separated in two files, it is linked The compiler completes the work of function filling, which may cause the corresponding instance not to be found during linking. Therefore, the definition and declaration of the template are generally placed in the header file .

Definitions and declarations can be placed in two files using explicit instantiation as follows. The template is not added after the function <>, and it is directly followed by the function prototype, that is, the template is instantiated into a specific version corresponding to the function prototype.

template void Swap(double &a, double &a);

In the same way as explicitly instantiating a class template, you need to add class. When you explicitly instantiate a class template, all members of the class, including member functions and member variables, are instantiated at once.

Class Templates and Inheritance

  • A class template is derived from a class template
  • A class template is derived from a template class
  • A class template is derived from a normal class
  • Ordinary classes are derived from template classes

Class templates and friends

  • Functions, classes, member functions of classes as friends of class templates
  • Function templates as friends of class templates
  • Function templates as friends of classes
  • Class templates as friends of class templates

exception handling

Program errors are mainly divided into three types: syntax errors , logic errors , and runtime errors . The exception mechanism is to be able to catch errors that occur during runtime, tell the user what happened and then terminate the program.

The syntax for catching exceptions is:

try{
    //可能抛出异常的语句
}catch(exceptionType variable){
    //处理异常的语句
}

try and catch are keywords, followed by a statement block. The exceptionType variable behind the catch keyword indicates the type of exception that catch can handle. The variable variable receives exception information. When the program throws an exception, a piece of data will be created. This data contains error information, and the programmer can judge the exception based on this information.

Once the exception is detected, it will be thrown immediately, and it will be detected by try immediately, and the statement after the exception will not be executed. That is, when an exception is detected, it will jump to the catch position, and the statement after the exception point will not be executed again. The throw keyword is used to throw an exception, which will be detected by try and then caught by catch.

The exception type can be a basic type or an aggregate type. The exceptions thrown by C++ itself or the standard library are exceptions of the exception class. So when an exception is thrown, an object of the exception class or its subclass will be created.

Exceptions are generated during the running phase. They can be of any type and cannot be predicted in advance. Therefore, it is not possible to judge whether the type is correct during the compilation phase. Only after the program runs and an exception is thrown can the thrown exception type match the type handled by the catch. A try can be followed by multiple catches. Once a matching catch type is found, other catch statements will not be executed.

Exceptions need to be explicitly thrown before they can be detected, and throw can be used to throw exceptions.

throw exceptionData;

exception is abnormal data, which can contain any information. In addition to throwing exceptions in the function body, the throw keyword can also indicate the type of exceptions that the current function can throw between the function header and the function body, calledexception specification

double func (char param) throw (int);

Indicates that the return value type of the function func is double, has a parameter of type char, and can only throw an exception of type int. If the function needs to throw multiple exceptions, you can use commas to distinguish them.

double func (char param) throw (int, char, exception);

The exception class is called a standard exception, and the exception class is located in the header file, declared as:

class exception{
public:
    exception () throw();  //构造函数
    exception (const exception&) throw();  //拷贝构造函数
    exception& operator= (const exception&) throw();  //运算符重载
    virtual ~exception() throw();  //虚析构函数
    virtual const char* what() const throw();  //虚函数
}

object oriented

copy constructor

Object creation consists of two parts, the first isallocate space, then ininitialization

When initializing an object by copy , a special constructor is called, which iscopy constructor. The copy constructor has only one parameter, and the type is generally a reference to the current class, and is generally a const reference .

A class can have two copy constructors at the same time, one whose parameter is a const reference and the other whose parameter is a non-const reference .

If no copy constructor is explicitly defined, the compiler will automatically generate a default copy constructor, which uses the member variables of the old object to assign values ​​to the member variables of the new object. But when the class holds other resources, such as dynamically allocated memory, open files, pointers to other data, network connections, etc., the default copy constructor cannot copy these resources, we must explicitly define the copy constructor , to completely copy all the data of the object.

The copy constructor is called when an object is copy-initialized . Initializing an object refers to filling the memory with data for the first time after allocating memory for the object. This process calls the constructor, and the object must be initialized immediately after creation.

Both initialization and assignment write data into memory, inAssignment at the same time as definition is called initializationAssignment after definition is called assignment, there is only one initialization, and there can be many assignments. For basic types, there is no difference between initialization and assignment, but for classes, there is a difference, because the constructor will be called when the object is initialized (the copy constructor will be called when the object is initialized by copy), and the reassignment function will be called when the value is assigned. Overloaded assignment operator .

When an object is initialized, the constructor is called, and different initialization methods call different constructors. If the actual parameter passed is for initialization, the normal constructor will be called; if the object is initialized with the data of other objects, the copy constructor will be called to complete the initialization by copying.

shallow copy and deep copy

Copying between primitive types and simple object data is to copy memory bit by bit , this method is calledshallow copy, which is similar to the effect of calling the memcpy function.

When a class holds resources such as dynamically allocated memory and pointers to other data, the default copy function cannot copy these resources, and a copy constructor needs to be explicitly defined. Explicitly define a copy constructor, in addition to copying the member variables of the original object to the new object, it will also allocate a block of memory for the new object, and copy the memory held by the original object. In this way, the original object and the new object are not associated and are independent of each other, and changing the data of one object will not affect the other object. This behavior of copying the resources held by the object is calleddeep copy, the copy constructor must be explicitly defined.

Instead of using an explicit copy constructor, when using the default copy constructor to initialize an object with resources such as pointers, it will cause the pointer of the copied object to point to the same piece of memory as the original one.Therefore, it is necessary to use deep copy when having a member variable of pointer type, so that the original object and the new object are independent of each other .

When an object is initialized by copy, the copy constructor is called, and when an object is assigned a value, the overloaded assignment operator is called . Even if there is no explicit overloading of the assignment operator, the editor will overload it in the default way. The default way to overload the operator is to assign all the member variables of the original object to the new object, which is different from the default The copy constructor functions similarly .

Converting Constructors and Type Conversion Functions

Different data types can be converted to each other without the user specifying how to convert.automatic type conversion, which needs to be explicitly specified by the user is calledcast. Coercion applies only to classes, because type conversion rules can only appear in the form of member functions of classes.

conversion constructorIt is a constructor that converts other types to the current class type, and the conversion constructor has only one parameter. The conversion constructor is also a constructor, which can be used to convert other types to the current class type, and can also be used to initialize objects, which is the original meaning of the constructor.

When copy-initializing an object, the compiler calls the converting constructor before copying the data to the variable. The constructor is to initialize the object when the object is created, and the compiler will match different constructors according to passing different actual parameters.

  • Default constructor: A constructor automatically generated by the compiler.
  • Ordinary constructor: User-defined constructor.
  • Copy constructor: Called when an object is initialized by copying.
  • Conversion constructor: Called when converting other types to the current class type.

No matter what kind of constructor, it is used to initialize the object. In addition to initializing the object when creating the object, the constructor will also be called in other cases. For example, when the object is initialized by copying, the copy constructor will be called. The converting constructor is called when converting other types to the current class type.

A conversion constructor can convert other types to the current type, but not vice versa.type conversion functionThe current class type can be converted to other types, and the type conversion function can only appear in the class as a member function.

operator type(){
    //todo
    return data;
}

operator is a C++ keyword, type is the target type to be converted, and data is the type data to be returned.

Because it is known that the data of the type type is to be returned, there is no need to give the return value type . The type conversion function does not seem to have a return value type, but actually indicates the return value type implicitly. The type conversion function also has no parameters, because to convert the object of the current class to another type, the compiler will assign the address of the current object to the this pointer, so that the current object can be manipulated in the function body.

Type conversion functions are similar to operator overloading, both use the operator keyword, so type conversion functions are also called type conversion operators .

Type conversion functions and conversion constructors work in reverse: The conversion constructor will convert other types to the current class type, and the type conversion function will convert the current class type to other types. Without these two functions, a large number of operator overloading functions will be written to implement type conversion and operations.

type conversion

Different data types can be converted to each other without the user specifying how to convert.automatic type conversion(implicit type conversion), which requires the user to explicitly indicate how to convert is calledcast

Implicit type conversions use the compiler's internal conversion rules or user-defined conversion constructors and type conversion functions.

Data is stored in memory in binary format, variousThe data type refers to the way the data is interpreted, how such data should be interpreted must be determined before use. This interpretation method is determined by the data type. Data types are used to describe the type of data and determine how the data is interpreted. Data types include built-in types and user-defined types .

Data type conversion is to reinterpret the binary bits occupied by the data, and if necessary, modify the data while reinterpreting. Implicit type conversion, the compiler can decide whether to modify the binary bits of the data according to known conversion rules; and for mandatory type conversion, since there is no corresponding conversion rule, all that can be done is to reinterpret the binary bits of the data , but the binary bits of the data cannot be corrected, which isThe most fundamental difference between implicit type conversion and mandatory type conversion

Modifying the binary bits of the data is very important to be able to adjust the converted data to the correct value.

Implicit type conversion must use known conversion rules, although the flexibility is limited, but it is safer (almost no risk) because the data can be adjusted appropriately. Casting can convert between a wider range of data types, such as conversions between different types of pointers (references), conversions from const to non-const, conversions from int to pointers (some compilers also allow the reverse ), etc. Although this increases flexibility, it is also full of risks because the data cannot be adjusted properly, and programmers should use it carefully.

Coercion is not a panacea. Type conversion can only occur between related types or similar types. Two irrelevant types cannot be converted to each other, even if coercion is used. Two classes that have no inheritance relationship cannot be converted to each other, a base class cannot be converted to a derived class (downcast), a class type cannot be converted to a basic type, and pointers and class types cannot be converted to each other.

I/O stream

C++ contains many io classes, collectively referred to asstream class

  • istream: often used to receive data input from the keyboard;
  • ostream: often used to output data to the screen;
  • ifstream: used to read the data in the file;
  • ofstream: used to write data to the file;
  • iostream: Inherited from the istream and ostream classes, because the functions of this class are both in one, which can be used for both input and output;
  • fstream: It combines the functions of ifstream and ofstream, which can not only read the data in the file, but also write data to the file.

In fact, cin is the object of istream class, and cout is the object of ostream. They are all declared in <iostream>. In addition, this header file also declares ostream class objects, which are cerr and clog respectively. cerr is used to output warning and error messages, and clog is used to output log information during program execution.

cout supports redirection, clog and cerr do not support redirection, and can only output data to the screen. This kind of object created in advance in c++ is calledbuilt-in object, which can be used directly.

Input and output redirection

There are three ways to redirect

  • freopen()Function implementation redirection: freopen()defined in <stdio.h>, is a function in the standard library, which is specially used to redirect input stream and output stream.
    string name, url;
    //将标准输入流重定向到 in.txt 文件
    freopen("in.txt", "r", stdin);
    cin >> name >> url;
    //将标准输出重定向到 out.txt文件
    freopen("out.txt", "w", stdout); 
    cout << name << "\n" << url;
  • rdbuf()Function to achieve redirection: rdbuf()The function is defined in <ios>the header file and is used to realize the redirection of input and output streams. ios is the base class of istream and ostream, so cin and cout can directly call this function to realize redirection.
streambuf * rdbuf() const;//仅是返回一个指向当前流缓冲区的指针
streambuf * rdbuf(streambuf * sb);//用于将 sb 指向的缓冲区设置为当前流的新缓冲区,并返回一个指向旧缓冲区的对象

ifstream fin("in.txt");//打开 in.txt 文件,等待读取
ofstream fout("out.txt");//打开 out.txt 文件,等待写入

oldcin = cin.rdbuf(fin.rdbuf());//用 rdbuf() 重新定向,返回旧输入流缓冲区指针
oldcout = cout.rdbuf(fout.rdbuf());//用 rdbuf() 重新定向,返回旧输出流缓冲区指针
  • Realize redirection through the console: when running the program, add <in.txt >out.txt, this parameter <in.txtredirects the cin input stream in the program, and also >out.txtredirects the cout output stream in the program.

output buffer

Each stream manages a buffer, used to save the data read and written by the program. With the buffering mechanism in place, the operating system can combine multiple outputs from a program into a single system-level write operation. Because write operations are time-consuming, allowing the operating system to combine multiple output operations as a single device write operation can provide significant performance gains.

The reasons for flushing the buffer (where the data is actually written to the output device or file) are as follows:

  • The program ends normally , and buffer flushing is performed as part of the return operation of the main function.
  • When the buffer is full , the buffer needs to be flushed before new data can continue to be written into the buffer.
  • Use operators such as endl to flush the buffer.
  • After each output operation, the internal state of the stream can be set using the operator unitbuf to clear the buffer. By default, cerr is set unitbuf, so cerr is flushed immediately.
  • An output stream may be associated with another stream , in which case the associated stream's buffer will be flushed when reading and writing to the associated stream.

In addition to the endl operator that can flush the buffer, there are flush and ends. flush flushes the buffer but does not output any characters. ends inserts a null character into the buffer, then flushes the buffer.

When an input stream is associated with an output stream, any attempt to read data from the output stream will first flush the associated output stream. The standard library associates cout and cin together. tie()function canused to bind the output stream, there are two overloaded versions.

ostream* tie() const;//返回指向绑定的输出流的指针
ostream* tie(ostream* os);//将os指向的输出流绑定在该对象上,并返回上一个绑定的输出流指针

cin.tie(&cout);  //仅仅是用来展示,标准库已经将 cin 和 cout 关联在一起
//old_tie 指向当前关联到 cin 的流(如果有的话)
ostream *old_tie = cin.tie(nullptr);  // cin 不再与其他流关联
//将 cin 与 cerr 关联,这不是一个好主意,因为 cin 应该关联到 cout
cin.tie(&cerr);  //读取 cin 会刷新 cerr 而不是 cout
cin.tie(old_tie);  //重建 cin 和 cout 间的正常关联

read a single character: get()It is a member function of the istream class. It reads a character from the output stream, and when it reaches the end of the input, the return value isEOF. EOF is an integer constant defined in the iostream class with a value of -1.

read a line of string: get()is a member function of the istream class, there are two overloaded versions:

istream & getline(char* buf, int bufSize);
istream & getline(char* buf, int bufSize, char delim);

The first version reads bufsize-1 characters from the input stream into the buffer buf, or until a newline character is encountered, the function automatically appends at the end of the end of the data read in buf \0.

The difference between the second version and the first one is that the first version reads \0up to , and the second version reads up to the delim character. \nor delim will not be read into buf, but will be taken from the output stream.

If the number of characters in the input stream \nand before delim reaches or exceeds bufsize, it will cause a reading error. As a result, this reading has been completed, but subsequent readings will fail.

How to ignore specified characters: ignore()It is a member function of the istream class, the function prototype is istream & ignore(int n =1, int delim = EOF);, this function skips n characters in the specified input stream, and skips one character by default.

look at the next character in the input stream: peek()It is a member function of the istream class. The function prototype is that int peek()the peek function returns the next character in the input stream, but does not take the character from the input stream. In case the input stream has ended, peek returns EOF.

Judgment input end: After redirecting the standard input to a file, cin can read data from the file. When the amount of input data is unknown and there is no end mark, you can enter a special character in the console to indicate the end. Enter ctrl+D in linux to indicate the end, output ctrl+Z in windows and press Enter to indicate the end.

How cin judges the end of the input: judges the end of the console reading: whether it is the end of the file, or ctrl+z or ctrl+D, it is the end sign, cin returns true when it reads normally, and returns false when it encounters the end sign , so you can judge whether the reading is finished according to the return value of cin.

file operation

file stream

From the perspective of data storage, the essence of all files is the same, and they are all composed of bytes. In addition to plain text files, images, videos, and executable files are generally calledbinary file

The file stream class is mainly three classes provided in the standard library to implement file operations. ifstream, ofstream, fstream, ifstreamand are derived ofstreamfrom istreamand , so these three classes have all member methods of and respectively.ostreamistreamostream

open&close

Before operating on the file, the file needs to be opened first. This ensures that by specifying the file name, the association between the file and the file stream object is established, and when the file is to be operated later, it can be performed through the stream object associated with it. The second is that you can specify how to open the file when you open it.

There are two ways to open a file:

  • Open the file using the open member function of the stream object. void open(const char* szFileName, int mode), the first parameter is a pointer to the file name, and the second parameter is the way to open the file. When opening a file through the open member function, it can be judged by using the return value, and success is true.
  • When defining a file stream object, the file is opened through the constructor. ifstream::ifstream (const char* szFileName, int mode = ios::in, int);, the first parameter is a pointer to the file name, the second parameter is the way to open the file, and the third parameter is generally not used.

The difference between file text and binary text: From a physical point of view, there is no difference between binary files and character files, and they are all stored on disk in binary. Text files use character encodings such as ASCII and UTF-8, and text editors can recognize these encoding formats and convert the encoding values ​​into characters for display. There is no essential difference between the text mode and the binary mode, but the handling of line breaks is different. There is no difference between opening a binary file and a text file on the linux platform. On the windows platform, the text file will be connected together as a line break. If the file is opened as a text file to read the file, the program will remove \r\neverything in the file. .\r\n\r

Using open to open a file is the process of establishing an association between the file stream object and the file, and closing is to cut off the association between the file stream object and the file, but the file stream is not destroyed.

Open files must be closed with close. flush can refresh the buffer, because the buffer will only write data to the file when it is full or the file is closed, but using flush can flush the output stream buffer and write data to the file.

read&write

Reading and writing data in binary can save space and is also easy to find, because each data occupies the same size space.

To read and write data in binary, you can no longer use <<and >>to read and write data, you need to use read and write, where the read method is used to read data from the file in binary form, and the write method is used to write data to the file in binary form.

ostream & write(char* buffer, int count);

istream & read(char* buffer, int count);

get&put

To read the characters stored in the file one by one or store the characters into the file one by one, you can use get and put.

Since the files are stored in the hard disk, the access speed of the hard disk is much lower than that of the memory. If you have to access the hard disk every time you write a file, the read and write speed will become very slow, so the operating system receives the put or get When requesting, it will first store the specified characters in a piece of memory or read a piece of data from the hard disk and store it in a piece of memory (file stream output buffer, file stream input buffer), and when the buffer is refreshed, a piece of The data is written to the hard disk together or directly obtained from the file stream input buffer when the data is to be obtained next time.

The usage getline()method can read a line of strings from the cin input stream buffer, and can also read a line of data in the specified file.

Move and get the file read pointer

When reading and writing files, sometimes you want to jump directly to a certain place in the file to start reading and writing, you need to move the file reading and writing pointer, and then perform reading and writing operations.

ostream & seekp (int offset, int mode);//设置文件读指针的位置
istream & seekg (int offset, int mode);//设置文件写指针的位置

mode has three options:

  1. ios::beg points to the offset byte after the beginning of the file, offset equal to 0 means from the beginning of the file, in this case, offset needs to be a non-negative number.
  2. ios::cur points to the offset byte of the current position.
  3. ios::end points to offset bytes from the end of the file.

tellg()and tellp()the position of the current pointer can be obtained.

Guess you like

Origin blog.csdn.net/qq_41323475/article/details/127856347