This is my first blog post. I participated in the creation activity of gold diggers and started my writing path together.
1. The difference between overwriting and overloading
overloading
- Refers to several functions of the same name declared in the same accessible area with different parameter columns (parameter types, numbers, and sequences)
- The argument list determines which function to call. Overloading does not care about the function return type.
#include<bits/stdc++.h>
using namespace std;
class A
{
void fun(a) {};
void fun(int i) {};
void fun(int i, int j) {};
};
Copy the code
Overwrite
Redefined functions exist in derived classes. The function name, argument list, and return value type must all be the same as the function being overridden in the base class. Only the function body is different (in curly braces). The overridden function of the derived class is called when the derived class is called, but not when the overridden function is called. The overridden function in the overridden base class must have a virtual modifier.
#include<bits/stdc++.h>
using namespace std;
class A
{
public:
virtual void fun(a)
{
cout << "A"; }};class B :public A
{
public:
virtual void fun(a)
{
cout << "B"; }};int main(void)
{
A* a = new B(a); a->fun(a);/ / output B
}
Copy the code
The difference between overloading and overwriting
- Scope difference: Overridden and overridden functions are in different classes, and overloaded and overridden functions are in the same class.
- Argument differences: Overridden and overridden function argument lists must be the same, overloaded and overloaded function argument lists must be different.
- The difference between virtual: overriding base class functions must have the virtual modifier. Overloaded functions and overloaded functions may or may not have the virtual modifier.
The difference between overloading and overwriting in C++
C++ memory model
C++ memory model (memory layout) memory regions C++ memory is divided into five regions:
- Heap heap:
The block of memory allocated by new, which the compiler does not care about, is controlled by our program (each new corresponds to a delete). If the programmer does not release it, the OS automatically recycles it at the end of the program. Problems involved: buffer overflow, memory leak
- Stack stack:
These are areas of storage that the compiler allocates when it needs them and automatically purges when it doesn’t. Store local variables, function parameters. The data stored on the stack is only valid for the current function and the next level of function. Once the function returns, the data is released automatically.
- Global/static storage (.bss and.data) :
Global and static variables are allocated to the same block of memory. In C, the uninitialized ones are in the.bSS segment, the initialized ones are in the.data segment; In C++ it doesn’t.
- Constant store (.rodata segment) :
Store constants that cannot be modified (even by illegal means)
- Code area (.text) :
Stores code (such as functions) that cannot be modified (like a constant store) but can be executed (unlike a constant store)
The C++ memory model has three different memory regions according to the C++ object life cycle:
- A storage area (stack) for local non-static variables
- Dynamic area: memory allocated with operator New,malloc (heap)
- Static area: where global variables, static variables, and string constants exist
References:
- C++ memory model – MrYun – cnblogs.com
- C++ memory model _yj_android_develop blog -CSDN blog
3. Class access modifier. Does a subclass have private members of its parent class, and can subclasses access private members of their parent class?
1. Subclasses can indirectly access private members of their parent class
- A private member function of a parent class, like a private member variable, can be called only by other member functions inside the class. If a subclass inherits a common function of the parent class and the public function of the parent class calls its internal private function, the subclass can access the private member function of the parent class by calling the public function of the parent class.
#include <iostream>
using namespace std;
class A
{
public:
void outpulic(a); // Public function of base class
private:
void outprivate(a); // Base class private functions
};
void A::outpulic(a) // The public function of the base class calls the private function of the class
{
outprivate(a); }void A::outprivate(a) // Base class private member function definition, output function name
{
cout<<"outprivate"<<endl;
}
class B:public A
{
};
int main(a)
{
B b;
b.outpulic(a);return 0;
}
Copy the code
Running results:
outprivate
Copy the code
2. How do private members inherit?
- Physically, subclasses do contain private members of their parent class, but they cannot be accessed through normal channels.
#include<iostream>
using namespace std;
class A
{
private:
int a;
void funa(a){cout<<"A"<<endl;}
};
class B:public A
{
public:
int b;
void funb(a){cout<<"B"<<endl;}
};
int main(a)
{
A a;
B b;
cout<<sizeof(a)<<endl<<sizeof(b)<<endl;
}
Copy the code
Running results:
4
8
Copy the code
For private member access, we can use inline assembly to get the entry address of the function, and then access is smooth. References:
- CSDN blog whether a private member of a c++ parent class can inherit _fledging
- 【c++】 can I access the base class’s private variables from a subclass? _ Haida blog -CSDN blog _c++ Subclass calls parent class private
4. Differences and connections between C language and C++
Difference is 1:
- C is procedural oriented and C++ is object oriented
Process oriented: Process oriented programming is to analyze the steps to solve a problem, and then implement these steps step by step, when using one by one call can be.
- The idea is to actually implement it.
- Generally from the top down step by step refinement.
- The most important is modular thinking. The process-oriented approach also has an advantage when the program is not very large, because the flow of the program is clear and can be well organized by module and function.
Object Oriented: Object oriented programming is the decomposition of a problem into objects that are built not to complete a step, but to describe the behavior of something in the whole step of solving a problem.
- Encapsulation: Abstract objects into classes, each of which protects access to its own data and methods
- Inheritance: You can take all the functionality of an existing class and extend it without having to rewrite the original class.
- Polymorphism: can be simply summarized as “one interface, many methods”, the program only at run time to decide the function to call, object-oriented core, the purpose of polymorphism is for interface reuse.
Process oriented is top-down programming (step partitioning), object oriented is high physical abstraction (function partitioning)
Differences between the 2:
grammatically
- C++ has three characteristics: overloading, inheritance and polymorphism.
- C++ adds many type safety features (such as cast)
- Type-safe code refers to access to authorized memory locations.
- For example, type-safe code cannot read values from private fields of other objects. It can only be read from a well-defined allowed access type.
C + + :
- (1) The new operator returns a pointer type that strictly matches the object, not void*;
- (2) Many C functions that take void* as an argument can be rewritten as C++ template functions, and templates support type checking;
- (3) Const replaced #define Constants, which is typed and scoped, while #define Constants is just a simple text replacement;
- (4) Some #define macros can be rewritten as inline functions, which can support multiple types with type-safe overloading.
- (5) C++ provides the dynamic_cast keyword to make the conversion process more secure, because dynamic_cast than
Static_cast involves more specific type checking.
Based on the above examples, the pros and cons of the two are summarized as follows:
- Process-oriented language
Advantages: Higher performance than object-oriented, because class invocation needs to be instantiated, the overhead is relatively large, more consumption of resources; For example, SCM, embedded development, Linux/Unix and so on generally adopt process-oriented development, performance is the most important factor. Disadvantages: No object-oriented easy maintenance, easy reuse, easy to expand
- Object-oriented languages:
Advantages: easy maintenance, easy reuse, easy expansion, due to the characteristics of object-oriented encapsulation, inheritance, polymorphism, can design a low coupling system, make the system more flexible, easier to maintain disadvantages: lower performance than process oriented
References:
- C language and C++ differences and links (details) _cherryDreamsover blog -CSDN blog _C language and C++ differences and links
- The difference between C ++ and C _ hahaha hahaha blog -CSDN blog _c++ and C differences
5. The difference between C++ overloading, rewriting, and redefining
- Overloading: If the function name is the same, there must be at least one different parameter number, parameter type, or parameter order. Functions can return values of the same or different types. Occurs within a class and cannot cross scope.
- Overwriting: Also called overwriting, occurs between a subclass and a parent class inheritance relationship. Subclasses redefine virtual functions that have the same name and arguments in their parent class. (override)
- Redefinition: Also known as hiding, when a subclass redefines a non-virtual function with the same name in its parent class (the argument list can be different), meaning that the function of the derived class hides the function of the base class with the same name. You can think of it as an overload that occurs in inheritance.
If a derived class has a redefined function, the class will hide its parent’s method unless you cast it to the parent’s type at call time. Otherwise, trying to make overload-like calls to subclasses and superclasses will not succeed.
Rewriting needs to be noted:
- The function being overridden cannot be static. It must be virtual
- Overriding functions must have the same type, name, and argument list
- Overriding functions can have different access modifiers.
The redefinition rules are as follows:
- If the derived class function has the same name as the base class function, but the arguments are different, the base class function is hidden with or without virtual.
- If the derived function has the same name and argument as the base function, but the base function does not have the vitual keyword, the base function is hidden (if Virtual is the same, it is overridden).
#include<iostream>
using namespace std;
class Animal
{
public:
void func1(int tmp)
{
cout << "I'm an animal -" << tmp << endl;
}
void func1(const char *s)// Function overload
{
cout << "I'm an animal func1 -" << s << endl;
}
virtual void func2(int tmp)
{
cout << "I'm virtual animal func2 -" << tmp << endl;
}
void func3(int tmp)
{
cout << "I'm an animal func3 -"<< tmp << endl; }};class Fish :public Animal
{
public:
void func1(a)// A redefinition of a function hides a method of the same name as its parent
{
cout << "I'm a fish func1" << endl;
}
void func2(int tmp) // Override the override method of the parent class
{
cout << "I'm a fish func2 -" << tmp << endl;
}
void func3(int tmp) { // A redefinition of a function hides a method of the same name as its parent
cout << "I'm a fish func3 -"<< tmp << endl; }};int main(a)
{
Fish fi;
Animal an;
fi.func1(a);// The method is hidden because it is redefining the parent class
// The declaration needs to be displayed. Overloading cannot cross scope
fi.Animal::func1(1);
dynamic_cast<Animal *>(&fi)->func1(11); // the method whose parent class is hidden can be called after the override
dynamic_cast<Animal *>(&fi)->func1("hello world"); // the method whose parent class is hidden can be called after the override
fi.func2(2); // Call the subclass
dynamic_cast<Animal *>(&fi)->func2(22); // Call "subclass method "(because it is a virtual function, it will be overridden by subclasses)
dynamic_cast<Animal *>(&fi)->func3(222); // Call the parent class
fi.func3(2222); // Call the subclass
cout << endl << "* * * * * * * * * * * *" << endl;
an.func1(1);
an.func1("I'm an animal");
an.func2(1);
system("pause");
return 0;
}
Copy the code
Output result:
I'm a fish func1
I'm an animal - 1
I'm an animal - 11
I'm an animal func1 -hello world
I'm a fish func2 2 -
I'm a fish func2 - 22
I'm an animal func3 - 222.
I'm a fish func3 - 2222.
************
I'm an animal - 1
I'm an animal func1 -I'm an animal
I'm virtual animal func2 - 1
Copy the code
The difference between overloading, overwriting, and redefining
6. How to determine whether the machine is 32-bit or 64-bit
- The first step is to understand the difference between a 32-bit system and a 64-bit system.
The main difference between 32-bit and 64-bit systems is whether the CPU can process data in 32 or 64 bits at a time. So from this point of view, 64-bit systems are more efficient than 32-bit systems. For C++ programmers, it has implications for us in addition to X86 and X64 architecture platforms, as well as storage address implications. 32-bit addressing range is 2^32 = 4G, which means 32-bit systems can support up to 4G memory, while 64-bit systems can support 2^64 = 4G*4G memory, which is astronomical for my current technology.
Since the addressing space of a 32-bit system is only 32 bits, the address of a 32-bit system pointer should be 32 bits. If we use sizeof to evaluate the sizeof a pointer of any type, we should get 4 bytes. The same is 8 bytes on a 64-bit system.
cout << "sizeof(int*):" << sizeof(int*) << endl;
// 4 bytes for 32-bit systems and 8 bytes for 64-bit systems
Copy the code
Method two: second-level pointer
// A simple example to determine whether the system is 64-bit or 32-bit
char *test = nullptr;
char *start = (char *)&test;
char *end = (char *)(&test + 1);
/ /..
if (4 == (end - start))
cout << "32 bit" << endl;
if (8 == (end - start))
cout << "64" << endl;
Copy the code
References:
- How to use C++ code to check whether you are using a 64-bit or 32-bit programming environment? _3D Matrix – CSDN blog
- How to determine whether a 64-bit or 32-bit system is stable using C++ code (without using any macro definitions, or apis
7. Virtual destructors (why don’t we mention make-believe?)
Virtual ~ class name () {}
- Only destructors can be declared virtual, constructors cannot be declared virtual!! \
- Virtual functions perform different actions depending on the type of object. Constructors are called before the object is generated. If the object has not been generated, then there is no point in fabricating functions
- If the destructor of a class is virtual, then the destructors of all derived classes, whether or not specified as virtual, are also virtual destructors. Ensure that Pointers of the base class type can be used to call the appropriate destructor to clean up for different objects
Resources: Tell me about your understanding of virtual functions
8. What is polymorphism
- What is polymorphism?
Polymorphism is the ability to have many different manifestations or forms of the same behavior. Polymorphism is the same interface, using different instances to perform different operations. 2
- Decouple the types
- replaceability
- scalability
- Interface,
- flexibility
- Simplify the sex
Three necessary conditions for the existence of polymorphism 1. Inheritance 2. Overwriting 3
Resources: What is polymorphism? What is the meaning of _LeeMxuan_’s blog -CSDN blog _JN
9. How to judge big end and small end
What is a major endian and a minor endian? It’s the order in which the bytes are stored, so if it’s all one byte, it doesn’t matter how it’s stored, but if it’s multiple bytes, like ints, double, etc., it’s the order in which the bytes are stored. For example, a 32-bit int 0x11223344 occupies four bytes, 11 occupies one, 22 occupies one, 33 occupies one, and 44 occupies one. The storage address is 0x100 0x101 0x102 0x103; So the question is, which byte does 11 take up? Does 11 take up the 0x100 byte or does it take up the 0x103 byte? There are two ways to sort:
1. Large endian sequence:
Big-endian ordering, where the highest digit occupies the lowest address and the lowest digit occupies the highest address, is also the most intuitive2. Microendian sequence:
The little endian order means that the low digit occupies the low address and the high digit occupies the high addressJudgment Method 1:
Realization idea:
- Define a 32-bit int variable 0x11223344
- Retrievals the 8-bit value starting with the low address of the int variable by using a cast
- If this value is 0x11, then the low address stores the high value, so it is big-endian
- If this value is 0x44 then the low address stores the low value and is therefore little endian
#include "stdio.h"
#include "stdlib.h"
// Check whether it is big or small.
// Return 1 if it is a big-endian function
// Returns 0 if it is a little endian function
int Judge_BS(int n) {
// If it is big-endian, the lower part of the number n is stored in the higher address
// 44 is stored in the high address and 11 is stored in the low address
// Address: 0x100 0x101 0x102 0x103
// Number: 11, 22, 33, 44
// If it is little endian, the lower part of the digit n is stored in the lower address
// 11 is stored in the high address, 44 is stored in the lower address
// Address: 0x100 0x101 0x102 0x103
// Number: 44 33 22 11
// So we can pull out the lower 8 bits of the 32-bit number n
// If the lower 8 bits are 11, it is the big-endian sequence
// If the lower 8 bits are 44, it is microendian
// The address stored here is a low address
char* p = &n;
printf("%x\n", *p);
printf("%x\n", *(p + 1));
printf("%x\n", *(p + 2));
printf("%x\n", *(p + 3));
// Use p to get the low address of 32, print the number every 8 bits, print the result is
// 44 33 22 11
// A low address corresponds to a low address, which is a small endian
char t = *p;
if (t == 11) {
return 1;
}
return 0;
}
int main(a) {
int n = 0x11223344;
if (Judge_BS(n)) {
printf("Big endian!");
} else {
printf("Small endian!");
}
system("pause");
return 0;
}
Copy the code
Judgment Method 2:
- Realization idea: the idea is basically the same as the first method, the difference lies in the use of the union this time, the use of the characteristics of the common memory of the union.
#include <stdio.h>
#include <stdlib.h>
int main(a) {
union Un {
int a;
char b;
} Un;
Un.a = 0x11223344;
if (Un.b == 0x11) {
printf("The big end \ n");
} else {
printf("Little end \ n");
}
system("pause");
return 0;
}
Copy the code
Resources: Determine whether a machine is big-endian or small-endian (two ways) _Z7436 -CSDN Blog _ Whether your machine is big-endian or small-endian
10. Constructor and destructor call order (including multiple inheritance, including object members in class)
Constructor order: The constructor of the base class is called first (which base class is called first in the same order as the derived class inherits from the base class). Then the constructor of the object member of the class is called (the constructor of which object member is called first is defined in the order in which the object members are defined). Finally, the constructor of the derived class is called in the opposite order. Talk about your understanding of virtual functions _ Floating time blog -CSDN blog _ How to understand virtual functions
Pure virtual functions and abstract classes
Classes that contain pure virtual functions are called abstract classes
class <The name of the class > {virtual< class name > < function name > (< parameter list >) =0;
}
Copy the code
The purpose of a pure virtual function is to reserve the name of a function in the base class for the derived class so that the derived class can define it as needed. Resources: Tell me about your understanding of virtual functions
12. Why refer to abstract base classes and pure virtual functions
<1> To facilitate the use of polymorphism <2> In many cases, it is not reasonable for the base class itself to generate objects. For example: animal as a base class can be derived from tiger, lion and other subclasses, but the animal itself generated objects are obviously unreasonable. Abstract base classes cannot be instantiated; they define pure virtual functions that act as interfaces to extract the common behavior of derived classes. Resources: Tell me about your understanding of virtual functions
13. Difference between virtual function and pure virtual function
<1> Virtual functions are implemented, even if they are null; A pure virtual function is just an interface, a function declaration, and requires a subclass to implement the <2> virtual function. However, a pure virtual function must be implemented in a subclass of <3> the class of the virtual function is used for “implementation inheritance”, that is, inheriting the interface at the same time also inherits the implementation of the parent class, of course, can also complete their own implementation; Classes with pure virtual functions are called virtual base classes (abstract classes). These classes cannot instantiate objects directly. They can only be used after they are inherited and implemented as pure virtual functions
Both virtual and pure virtual functions can be overridden in their subclasses. The differences are as follows: (1) pure virtual functions are only defined, not implemented; Virtual functions have both definition and implementation code. Pure virtual functions generally have no code implementation parts, such as \
virtual void print(a) = 0;
Copy the code
In general, virtual functions must have an implementation part of the code, otherwise the function will be undefined error. (2) Classes containing pure virtual functions cannot define their objects, while classes containing virtual functions can.
virtual void print(a)
{ printf("This is virtual function\n"); }
Copy the code
(1) If a class declares a virtual function, the function is implemented, even if it is null, so that the function can be overridden in its subclasses, so that the compiler can use late binding to achieve polymorphism. A pure virtual function is just an interface, a declaration of a function, left to a subclass to implement. (2) Virtual functions in subclasses may not be overloaded; But pure virtual functions must be implemented in subclasses, just like Java interfaces. It is often a good practice to add many functions to Virtual, sacrificing some performance but increasing object-oriented polymorphism, because it is difficult to anticipate that the function in the parent class is not in the subclass without modifying its implementation. (3) The class of the virtual function is used for “implementation inheritance”, inheriting the interface and also inheriting the implementation of the parent class. Of course, you can do your own implementation. Pure virtual functions are concerned with the uniformity of interfaces, and implementation is done by subclasses. (4) The class with pure virtual function is called virtual base class, this base class can not directly generate objects, and only be inherited, rewrite its virtual function, can be used. Such classes are also called abstract classes. References:
- What is a virtual function? What is a pure virtual function? Why virtual and pure virtual functions? _ Qian Mo Yang Yang’s blog -CSDN blog
- Talk about your understanding of virtual functions _ Floating time blog -CSDN blog _ How to understand virtual functions
Operator overloading/function overloading compares the advantages and disadvantages of virtual and pure virtual functions
<1> Operator overloading and function overloading static association: The association is completed in the compile and link phases, during which the system can decide which function of the same name to call based on the type and number of arguments of the function. Advantages and disadvantages of static association: high efficiency of program execution, but high requirements for programmers
<2> Virtual function and pure virtual function dynamic correlation: the correlation work is completed in the program run phase. Advantages and disadvantages of dynamic correlation: Provides better programming flexibility, problem abstraction, and easy maintenance of programs, but slower function calls
15. Solutions to reduce latency and jitter
The delay in network refers to the delay time of information from sending to receiving, which is generally composed of transmission delay and processing delay. Jitter is the difference between the maximum delay and the minimum delay. For example, if the maximum delay is 20 ms and the minimum delay is 5 ms, network jitter is 15 ms, which indicates the stability of a network. First, network jitter:
- Definition: Jitter is a commonly used concept in QOS, which means the change degree of packet delay.
- Cause: If the network is congested, the queuing delay will affect the end-to-end delay and result in different packet delays transmitted over the same connection. Jitter is used to describe the extent of this delay change.
Ii. Network latency:
- Definition: Network delay is the time taken to transmit in the transmission medium, i.e. the time between when a message starts to enter the network and when it starts to leave the network.
- Cause: Network delay refers to the transmission of various types of data through network protocols (such as TCP/IP) in the network media. If the amount of information is too large and the network traffic is not restricted, the device responds slowly and causes network delay.
I. Solutions to network delay:
- By improving WAN performance, WAN operators can also contribute to latency reduction by choosing shorter and more efficient routing paths, deploying low-latency switches and routing equipment, and proactively avoiding downtime for network equipment.
- Increasing WAN bandwidth can improve application performance, and in practice, WAN application performance can also be improved by using various technologies that make more efficient use of existing WAN bandwidth.
Two, to solve the network jitter method:
- The queue entry thread of the jitter buffer pointer queue at the receiving end receives the data packets, sorts the received data packets, and inserts the received data packets into the corresponding position of the jitter buffer pointer queue
- The dequeue thread timer of the jitter buffer pointer queue triggers the dequeue thread at a certain interval. The dequeue thread determines whether the packet in the queue head of the jitter buffer pointer queue should be dequeued at the current triggering moment, if so, the packet will be dequeued.
Resources: What is jitter and latency in networks? How did it come about? Baidu.com
NULL,0, nullPTR
When writing C program, we only saw NULL, but in C++ programming, we can see NULL and NULlptr. Nullptr is actually newly added in C++11 version, which is to solve the problem that NULL indicates NULL pointer is ambiguous in C++. In order to understand this problem, I searched for some information and summarized as follows.
In C, NULL is usually defined as: #define NULL ((void *)0); #define NULL ((void *)0); Converts a void pointer to a pointer of the corresponding type.
int *pi = NULL;
char *pc = NULL;
Copy the code
C++ program NULL
Void * is not implicitly converted to Pointers of other types, so the compiler actually provides a header file that does the same:
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
Copy the code
As you can see, NULL is actually 0 in C++, not (void*)0. Since we cannot implicitly convert a pointer of type void* to a pointer of another type in C++, we introduced 0 to represent a NULL pointer to solve the problem of NULL pointer representation, thus creating the NULL macro definition in the code above. In practice, however, using NULL instead of 0 to indicate a NULL pointer can cause problems when a function is overloaded, and the result of the program execution can be different from what we think, as shown in the following example:
#include <iostream>
using namespace std;
void func(void* i)
{
cout << "func1" << endl;
}
void func(int i)
{
cout << "func2" << endl;
}
void main(int argc,char* argv[])
{
func(NULL);
func(nullptr);
getchar(a); }Copy the code
In this code, we are overloading the func function with void* and int parameters, but the result is that we use NULL in the wrong way, because we want to use NULL instead of a NULL pointer, but when we input NULL into the function, Instead, it chose the function version of the int parameter (because NULL is defined as 0), which is problematic because it is ambiguous in C++ programs to use NULL instead of a NULL pointer.
C++ nullptr
Nullptr was introduced in C++11 (2011) to address the ambiguity of nullptr. As we can see from the above example, using nullptr as an argument does select the correct version of the function that takes void*.
Conclusion:
- NULL is 0 in C++. This is because void* is not allowed to be converted implicitly to other types, so C++ used 0 to represent a NULL pointer, but in the case of overloading an integer, the above problem occurs.
- Therefore, C++11 has added nullptr to ensure that nullptr will always be used as a NULL pointer, and NULL will be used as a 0.
References:
- Nullptr: nullPTR: nullPTR: nullPTR: nullPTR: nullPTR: nullPTR: nullPTR – Porter_ Code Worker – Blog Park (CNblogs.com)
- C++ nullptr和 NULL PTR
17. A quick overview of common new C++11 features
1. nullptr
Nullptr appears as an alternative to NULL. In a sense, traditional C++ treats NULL and 0 as the same thing, depending on how the compiler defines NULL. Some compilers will define NULL as ((void*)0), and others will define it as 0 directly. C++ does not allow direct implicit conversions of void* to other types, but if NULL is defined as ((void*)0), then when compiling char *ch = NULL; , NULL has to be defined as 0. This can still be a problem, and will lead to confusion over overloading features in C++. Consider:
void foo(char *);
void foo(int);
Copy the code
For both functions, foo(NULL) if NULL is defined to 0 again; This statement will call foo(int), making the code unintuitive. To solve this problem, C++11 introduced the nullptr keyword to distinguish between null Pointers and zeros. Nullptr is of type NULlPTR_T, and can be implicitly cast to any pointer or member pointer type, and can be compared equally or unequally with them. Get into the habit of using NULlPTR directly when NULL is required.
2. Type derivation
C++11 introduces the auto and decltype keywords for type derivation, letting the compiler worry about the type of a variable.
Auto auto has been in C++ for a long time, but it always exists as a storage type indicator, alongside register. In traditional C++, a variable that is not declared as a register variable is automatically treated as an auto variable. With register deprecated, semantic changes to Auto are natural. One of the most common and notable examples of type derivation using Auto is iterators. In the past we needed to write an iterator like this:
for(vector<int>::const_iterator itr = vec.cbegin(a); itr ! = vec.cend(a); ++itr)Copy the code
With Auto, you can:
// Since cbegin() returns vector
::const_iterator
// So itR should also be vector
::const_iterator
for(auto itr = vec.cbegin(a); itr ! = vec.cend(a); ++itr);Copy the code
Some other common uses:
auto i = 5; // I is derived to int
auto arr = new auto(10) // arr is derived to int *
Copy the code
Note: Auto cannot be used to pass arguments to functions, so the following will not compile (we should use templates to consider overloading) :
int add(auto x, auto y);
Copy the code
In addition, auto cannot be used to derive array types:
#include <iostream>
int main(a) {
auto i = 5;
int arr[10] = {0};
auto auto_arr = arr;
auto auto_arr2[10] = arr;
return 0;
}
Copy the code
Decltype Decltype keyword is to solve the defect that the auto keyword can only type deduce variables. Its usage is similar to sizeof:
decltype(Expression)Copy the code
In this process, the compiler parses the expression and gets its type without actually evaluating the expression’s value. Sometimes, we may need to evaluate the type of an expression, for example:
auto x = 1;
auto y = 2;
decltype(x+y) z;
Copy the code
You might wonder if auto could be used to derive the return type of a function. Consider an example of an addition function, which in traditional C++ we would have to write:
template<typename R, typename T, typename U>
R add(T x, U y) {
return x+y
}
Copy the code
This code turns out to be ugly because the programmer must specify the return type when using the template function. But we don’t really know what add() does or what return type it gets.
This problem was solved in C++11. Although you might immediately derive x+y types using decltype, write code like this:
decltype(x+y) add(T x, U y);
Copy the code
But it’s not actually going to compile. This is because x and y are not yet defined when the compiler reads declType (x+y). To solve this problem, C++11 also introduced a trailing return type, which is postfixed using the auto keyword: trailing return type
template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y) {
return x+y;
}
Copy the code
Starting with C++14, it is possible to make plain functions have return value derivations directly, so it is legal to write:
template<typename T, typename U>
auto add(T x, U y) {
return x+y;
}
Copy the code
3. Interval iteration
C++11 introduced scope-based iteration, and we now have the ability to write loops as concise as Python. The most common STD ::vector traversal will look like this:
std::vector<int> arr(5.100);
for(std::vector<int>::iterator i = arr.begin(a); i ! = arr.end(a); ++i) { std::cout << *i << std::endl; }Copy the code
It becomes very simple:
// & References are enabled
for(auto &i : arr) {
std::cout << i << std::endl;
}
Copy the code
4. Initialize the list
C++11 provides a uniform syntax for initializing arbitrary objects, such as:
struct A {
int a;
float b;
};
struct B {
B(int _a, float _b): a(_a), b(_b) {}
private:
int a;
float b;
};
Copy the code
A a {1.1.1}; // Uniform initialization syntax
B b {2.2.2};
Copy the code
C++11 also binds the concept of initializer lists to types, calling them STD ::initializer_list, allowing constructors and other functions to use initializer lists as arguments. This provides a unified bridge between the initialization of class objects and the initialization methods of ordinary arrays and pods, for example:
#include <initializer_list>
class Magic {
public:
Magic(std::initializer_list<int> list) {}
};
Magic magic = {1.2.3.4.5};
std::vector<int> v = {1.2.3.4};
Copy the code
5. Template enhancement
In traditional C++, templates are instantiated by the compiler only when they are used. Whenever a fully defined template is encountered in code compiled in each compilation unit (file), it is instantiated. This results in increased compile time due to repeated instantiations. Also, there is no way to tell the compiler not to trigger template instantiation.
C++11 introduces external templates, extending the syntax that forces the compiler to instantiate templates at a specific location, so that it can explicitly tell the compiler when to instantiate a template:
template class std::vector<bool>; // forcibly instantiate
extern template class std::vector<double>; // Do not instantiate the template in the compile file
Copy the code
Angle brackets “>” in traditional C++ compilers, >> is treated as the right shift operator. But we can easily write code for nested templates:
std::vector<std::vector<int>> wow;
Copy the code
This cannot be compiled under traditional C++ compilers, but starting with C++11, successive close Angle brackets will be legal and will compile smoothly.
In traditional C++, a typedef can define a new name for a type, but there is no way to define a new name for a template. Because templates are not types. Such as:
template< typename T, typename U, int value>
class SuckType {
public:
T a;
U b;
SuckType() :a(value),b(value){}
};
template< typename U>
typedef SuckType<std::vector<int>, U, 1> NewType; / / is illegal
Copy the code
C++11 uses using to introduce the following form of writing, while supporting the same functionality as a traditional typedef:
template <typename T>
using NewType = SuckType<int, T, 1>; / / legal
Copy the code
The default template argument we might define an addition function:
template<typename T, typename U>
auto add(T x, U y) -> decltype(x+y) {
return x+y
}
Copy the code
However, it turns out that to use add, you must specify the type of its template parameter each time. C++11 provides the convenience of specifying the default parameters of a template:
template<typename T = int.typename U = int>
auto add(T x, U y) -> decltype(x+y) {
return x+y;
}
Copy the code
constructors
Delegate construction C++11 introduced the concept of delegate construction, which allows constructors to call another constructor in the same class to simplify code:
class Base {
public:
int value1;
int value2;
Base() {
value1 = 1;
}
Base(int value) : Base() { // Delegate the Base() constructor
value2 = 2; }};Copy the code
Inheritance Constructors In inheritance systems, if a derived class wants to use the constructor of the base class, it needs to be explicitly declared in the constructor. If the base class has many different versions of constructors, then many of the corresponding “pass-through” constructors will have to be written in the derived class. As follows:
struct A
{
A(int i) {}
A(double d,int i){}
A(float f,int i,const char* c){}
/ /... And so on};struct B:A
{
B(int i):A(i){}
B(double d,int i):A(d,i){}
B(folat f,int i,const char* c):A(f,i,e){}
/ /... And so on, multiple constructors corresponding to the base class constructor};Copy the code
C++11 inheritance constructs:
struct A
{
A(int i) {}
A(double d,int i){}
A(float f,int i,const char* c){}
/ /... And so on};struct B:A
{
using A::A;
// Base class constructor inheritance in one sentence
/ /...};Copy the code
If an inherited constructor is not used by the related code, the compiler does not generate the actual function code for it, thus saving object code space rather than passing through the various base class constructors.
Lambda expressions
Lambda expressions, in effect, provide a feature similar to anonymous functions, which are used when a function is needed but you don’t want to bother naming a function.
The basic syntax for Lambda expressions is as follows:
[ caputrue ] ( params ) opt -> ret { body; };
Copy the code
- Capture is a list of things to capture.
- Params is the parameter list; (optional)
- Opt is the function option; Can fill mutable, exception, attribute (optional)
- Mutable means that code in the body of a lambda expression can modify captured variables and access non-const methods of captured objects.
- Exception indicates whether and what kind of exception a lambda expression throws.
- Attribute Is used to declare attributes.
- Ret is the return value type (trailing return type). (optional)
- Body is the body of a function.
Capture list: The capture list of a lambda expression finely controls which external variables a lambda expression can access and how to access them.
- [] No variables are captured.
- [&] captures all variables in the external scope and uses them as references in the function body (capture by reference).
- [=] Captures all variables in the external scope and uses them as copies in the function body (captured by value). Note that value capture assumes that variables can be copied, and that captured variables are copied when the lambda expression is created, not when the lambda expression is called. If we want lambda expressions to have immediate access to external variables when called, we should capture them by reference.
int a = 0;
auto f = [=] { return a; };
a+=1;
cout << f() << endl; //输出0
int a = 0;
auto f = [&a] { return a; };
a+=1;
cout << f() <<endl; / / output 1
Copy the code
- [=,&foo] captures all variables in the external scope by value and foo by reference.
- [bar] The bar variable is captured by value, while no other variables are captured.
- [this] captures the this pointer in the current class, giving the lambda expression the same access as the member function of the current class. This option is added by default if ampersand or = are already used. The purpose of capturing this is to use the member functions and variables of the current class in LAMda.
class A
{
public:
int i_ = 0;
void func(int x,int y){
auto x1 = [] { return i_; }; //error, no external variables are captured
auto x2 = [=] { return i_ + x + y; }; //OK
auto x3 = [&] { return i_ + x + y; }; //OK
auto x4 = [this] { return i_; }; //OK
auto x5 = [this] { return i_ + x + y; }; //error, no capture of x,y
auto x6 = [this, x, y] { return i_ + x + y; }; //OK
auto x7 = [this] { return i_++; }; //OK
};
int a=0 , b=1;
auto f1 = [] { return a; }; //error, no external variables are captured
auto f2 = [&] { return a++ }; //OK
auto f3 = [=] { return a; }; //OK
auto f4 = [=] {return a++; }; // Error,a is captured in copy mode and cannot be modified
auto f5 = [a] { return a+b; }; //error, variable b was not captured
auto f6 = [a, &b] { return a + (b++); }; //OK
auto f7 = [=, &b] { return a + (b++); }; //OK
Copy the code
Note that f4, although the values of variables captured by value are all copied and stored in lambda expression variables, changing them doesn’t really affect the outside world, but we still can’t change them. If you want to modify external variables captured by value, you need to specify lambda expressions as mutable. Lambda expressions that are mutable require a list of arguments even if they have none.
The reason: Lambda expressions are the syntactic sugar of in-place functor closures. Any external variables captured by its capture list will eventually become members of the closure type. By the C++ standard, the operator() of a lambda expression is const by default. A const member function cannot modify the value of a member variable. A mutable function is to cancel const operator().
int a = 0;
auto f1 = [=] { return a++; }; //error
auto f2 = [=] () mutable { return a++; }; //OK
Copy the code
The general principle of lambda expressions:
- Each time you define a lambda expression, the compiler automatically generates an anonymous class (which overrides the () operator) called a closure Type.
- At run time, the lambda expression returns an anonymous instance of the closure, an rvalue.
- So, the result of our lambda expression above is one closure after another. For copy-passed value capture, non-static data members of the corresponding type are added to the class.
- At run time, these member variables are initialized with copied values to generate the closure. For reference capture, whether marked mutable or not, the captured value can be modified in a lambda expression.
- As for whether a closure class has a corresponding member, the C++ standard gives the answer: unclear, implementation-dependent.
Lambda expressions cannot be assigned:
auto a = [] { cout << "A" << endl; };
auto b = [] { cout << "B" << endl; };
a = b; Lambda cannot be assigned
auto c = a; // make a copy
Copy the code
The closure type disables the assignment operator, but not the copy constructor, so you can still initialize one lambda expression with another to produce a copy.
Among the various capture methods, it is best not to use the [=] and [&] defaults to capture all variables.
By default references capture all variables. You are more likely to have Dangling references because capture by reference does not extend the life of the referenced variable:
std::function<int(int)> add_x(int x) { return [&](int a) { return x + a; }; } 1, 2, 3, 4} 1, 2, 3, 4} 1, 2, 3, 4} 1, 2, 3, 4} 1, 2, 3, 4} 1, 2, 3, 4} 1, 2, 3, 4 In this case, using the default pass-through method avoids the hanging reference problem.
But capturing all variables with default values is still risky, as shown in the following example:
class Filter { public: Filter(int divisorVal): divisor{divisorVal} {}
std::function<bool(int)> getFilter()
{
return [=](int value) {return value % divisor == 0; };
}
Copy the code
private: int divisor; };
This class has a member method that returns a lambda expression using the class’s data member Divisor. And all variables are captured with default values. You might think that the lambda expression also captures a copy of the Divisor, but it doesn’t. Since the data member divisor is not visible to lambda expressions, you can verify this with the following code:
Class method, which cannot be compiled below because divisor is not in the scope of lambda capture
std::function<bool(int)> getFilter(a)
{
return [divisor](int value) {return value % divisor == 0; };
}
Copy the code
In the original code, the lambda expression actually captures a copy of the this pointer, so the original code is equivalent to:
std::function<bool(int)> getFilter(a)
{
return [this] (int value) {return value % this->divisor == 0; };
}
Copy the code
Although still to capture value way, the capture is a pointer, it is equivalent to capture the current class object in the form of reference, so the closure of the lambda expressions and a class object binding together, this is very dangerous, because you still have the object in the class destructor after using the lambda expressions, so similar to the “hanging” reference questions also can produce. Therefore, it is still not safe to capture all variables with default values, mainly due to copying of pointer variables, which are actually passed by reference.
Lambda expressions can be assigned to function Pointers of the corresponding type. But using function Pointers is not that convenient. So the STL definition in the < functional > header provides a polymorphic function object that encapsulates STD ::function, similar to a function pointer. It can bind any class function object, as long as the argument is the same as the return type. A function wrapper that returns a bool and accepts two ints looks like this:
std::function<bool(int.int)> wrapper = [](int x, int y) { return x < y; };
Copy the code
A more important use of lambda expressions is that they can be used as arguments to functions, in which way callback functions can be implemented.
The most common is in the STL algorithm. For example, if you want to count the number of elements in an array that satisfy a certain condition, pass the condition to count_if as a lambda expression:
int value = 3;
vector<int> v {1, 3, 5, 2, 6, 10};
int count = std::count_if(v.beigin(), v.end(), [value](int x) { return x > value; });
Copy the code
Or if you want to generate the Fibonacci sequence and store it in an array, you can use the generate function to aid lambda expressions:
vector<int> v(10);
int a = 0;
int b = 1;
std::generate(v.begin(), v.end(), [&a, &b] { int value = b; b = b + a; a = value; return value; });
V {1, 1, 2, 3, 5, 8, 13, 21, 34, 55}
Copy the code
When we need to traverse the container and operate on each element:
std::vector<int> v = { 1.2.3.4.5.6 };
int even_count = 0;
for_each(v.begin(), v.end(), [&even_count](int val){
if(! (val &1)){ ++ even_count; }}); std::cout <<"The number of even is " << even_count << std::endl;
Copy the code
Most STL algorithms can be very flexible with lambda expressions to achieve the desired effect.
8. Add containers
1. std::array
STD :: Array is stored in stack memory and can be accessed flexibly compared to STD :: Vector in heap memory, resulting in higher performance.
STD ::array creates a fixed-size array at compile time. STD ::array cannot be converted to Pointers implicitly. To use STD ::array, specify its type and size:
std::array<int, 4> arr= {1.2.3.4};
int len = 4;
std::array<int, len> arr = {1.2.3.4}; The array size argument must be a constant expression
Copy the code
When we started using STD :: Array, it was inevitable that we would have to make it compatible with a C-style interface. There are three ways to do this:
void foo(int *p, int len) {
return;
}
std::array<int 4> arr = {1.2.3.4};
// C-style interface parameters are transmitted
// foo(arr, arr.size()); // Invalid, cannot be converted implicitly
foo(&arr[0], arr.size());
foo(arr.data(), arr.size());
/ / use ` STD: : sort `
std::sort(arr.begin(), arr.end());
Copy the code
2. std::forward_list
STD ::forward_list is a list container that is used in much the same way as STD ::list. Unlike STD :: List’s bidirectional list implementation, STD ::forward_list is implemented using a one-way list, provides O(1) complexity for element insertion, does not support fast random access (which is also characteristic of linked lists), and is the only library container that does not provide the size() method. Higher space utilization than STD :: List when two-way iteration is not required.
3. Unordered containers
C++11 introduced two sets of unordered containers: STD ::unordered_map/ STD ::unordered_multimap and STD ::unordered_set/ STD ::unordered_multiset.
Elements in unordered containers are not sorted, but are internally implemented by Hash tables, and the average complexity of inserting and searching elements is O(constant).
4. A tuple STD: : a tuple
The use of tuples has three core functions:
STD ::make_tuple: construct a tuple STD ::get: obtain a value at a location in the tuple STD :: :tie: tuple unpack
#include <tuple>
#include <iostream>
auto get_student(int id)
{
// The return type is inferred to be STD ::tuple
,>
if (id == 0)
return std::make_tuple(3.8.'A'."Zhang");
if (id == 1)
return std::make_tuple(2.9.'C'."Bill");
if (id == 2)
return std::make_tuple(1.7.'D'."Fifty");
return std::make_tuple(0.0.'D'."null");
// If only 0 is written, inference errors will occur and compilation will fail
}
int main(a)
{
auto student = get_student(0);
std::cout << "ID: 0, "
<< "GPA: " << std::get<0>(student) << ","
<< "Result:" << std::get<1>(student) << ","
<< "Name:" << std::get<2>(student) << '\n';
double gpa;
char grade;
std::string name;
// Unpack the tuple
std::tie(gpa, grade, name) = get_student(1);
std::cout << "ID: 1, "
<< "GPA: " << gpa << ","
<< "Result:" << grade << ","
<< "Name:" << name << '\n';
Copy the code
Merging two tuples can be done by STD ::tuple_cat.
auto new_tuple = std::tuple_cat(get_student(1), std::move(t));
Copy the code
Regular expressions
Regular expressions describe a pattern of string matching. Regular expressions are generally used to meet the following three requirements:
- Check if a string contains a substring of some form;
- Replaces the matched substring;
- Retrieves a substring from a string that matches the condition.
C++11 provides a regular expression library that operates on STD ::string objects, initializes the pattern STD ::regex (essentially STD ::basic_regex), matches with STD ::regex_match, This results in STD ::smatch (essentially a STD :: match_Results object).
Let’s use this library with a simple example. Consider the following regular expression:
TXT: In this regular expression, [a-z] matches one lowercase letter, and + can match the previous expression multiple times. Therefore, [a-z]+ can match one or more lowercase letters. In the regular expression, a. Is used to match any character, a. Is escaped to match the character., and the last TXT is used to match the three letters TXT strictly. Therefore, the regular expression matches text files with pure lowercase characters. STD ::regex_match is used to match strings and regular expressions, and there are many different overloads. The simplest form is to pass STD :: String and a STD ::regex to match, returning true if the match is successful and false otherwise. Such as:
#include <iostream>
#include <string>
#include <regex>
int main(a) {
std::string fnames[] = {"foo.txt"."bar.txt"."test"."a0.txt"."AAA.txt"};
// in C++, '\' is used as an escape in the string. In order for '\.' to take effect as a regular expression, '\' needs to be escaped twice, resulting in '\\.'
std::regex txt_regex("[a-z]+\\.txt");
for (const auto &fname: fnames)
std::cout << fname << ":" << std::regex_match(fname, txt_regex) << std::endl;
}
Copy the code
Another common form is in turn to STD: : string/STD: : smatch/STD: : regex three parameters, including STD: : the essence of the smatch is STD: : match_results, in the standard library, STD ::smatch is defined as STD :: match_Results, which is match_Results of a substring iterator type. Using STD ::smatch makes it easy to obtain the result of a match, for example:
std::regex base_regex("([a-z]+)\\.txt");
std::smatch base_match;
for(const auto &fname: fnames) {
if (std::regex_match(fname, base_match, base_regex)) {
// The first element of sub_match matches the entire string
// The second element of sub_match matches the first parenthesis expression
if (base_match.size() = =2) {
std::string base = base_match[1].str(a); std::cout <<"sub-match[0]: " << base_match[0].str() << std::endl;
std::cout << fname << " sub-match[1]: "<< base << std::endl; }}}Copy the code
The output of the two code snippets above is:
foo.txt: 1
bar.txt: 1
test: 0
a0.txt: 0
AAA.txt: 0
sub-match[0]: foo.txt
foo.txt sub-match[1]: foo
sub-match[0]: bar.txt
bar.txt sub-match[1]: bar
Copy the code
10. Language level threading support
std::thread std::mutex/std::unique_lock std::future/std::packaged_task std::condition_variable
Code compilation requires the -pthread option
Rvalue references and move semantics
Rvalue references
Let’s look at a simple example to get an idea:
string a(x); // line 1
string b(x + y); // line 2
string c(some_function_returning_a_string()); // line 3
Copy the code
If you use the following copy constructor:
string(const string& that)
{
size_t size = strlen(that.data) + 1;
data = new char[size];
memcpy(data, that.data, size);
}
Copy the code
Of the above three lines, only a deep copy of x for line 1 is necessary because we may use x later, which is an lvalue.
The arguments to the second and third lines are rvalues, because the string produced by the expression is an anonymous object that can’t be used later.
C++ 11 introduced a new mechanism called “rvalue reference” so that we can use rvalue arguments directly through overloading. All we need to do is write a constructor that takes an rvalue reference as an argument:
string(string&& that) // string&& is an rvalue reference to a string
{
data = that.data;
that.data = 0;
}
Copy the code
Instead of making a deep copy of the heap memory, we simply copy the pointer and null the pointer to the source object. In effect, we “stole” memory data belonging to the source object. Since the source object is an rvalue and will not be used again, the customer will not be aware that the source object has been changed. Here, we don’t really copy, so we call this constructor “move constructor,” whose job is to transfer resources from one object to another, not copy them.
With an rvalue reference, let’s look at the assignment operator:
string& operator=(string that)
{
std::swap(data, that.data);
return *this;
}
Copy the code
Notice that we’re passing that directly, so that gets initialized just like any other object. So how exactly is that initialized? For C++ 98, the answer is the copy constructor, but for C++ 11, the compiler chooses between the copy constructor and the transfer constructor depending on whether the argument is lvalue or rvalue.
If it is a=b, then the copy constructor is called to initialize that (because b is an lvalue), and the assignment operator exchanges data with the newly created object, deep copy. This is the idiomatic definition of copy and swap: construct a copy, exchange data with the copy, and let the copy self-destruct in scope. Same thing here.
If it is a = x+y, then the transfer constructor is called to initialize that (because x+y is an rvalue), so there is no deep copy, just efficient data transfer. That is still a separate object from the argument, but its constructor is trivial, so the data in the heap is not necessarily copied, but merely transferred. There is no need to copy it, because x+y is an rvalue, and again, there is no problem moving from the object to which the rvalue points.
To summarize: the copy constructor performs a deep copy because the source object itself must not be altered. The transfer constructor, on the other hand, copies the pointer and nullifies the pointer to the source object. In this form, it is safe because the user cannot use the object again.
Let’s take a closer look at rvalue references and move semantics.
A unique possessive smart pointer, STD ::auto_ptr, is provided in the C++98 library. This type was deprecated in C++11 because its “copy” behavior was dangerous.
auto_ptr<Shape> a(new Triangle);
auto_ptr<Shape> b(a);
Copy the code
Notice how B initializes with A. Instead of copying triangle, it passes ownership of triangle from A to B, or “A is transferred into B” or “Triangle is transferred from A to B.”
The copy constructor for auto_ptr might look like this (simplified) :
auto_ptr(auto_ptr& source) // note the missing const
{
p = source.p;
source.p = 0; // now the source no longer owns the object
}
Copy the code
The danger with auto_ptr is that it looks like a copy, but it’s really a transfer. Calling a member function of the migrated auto_ptr will result in unpredictable consequences. So you must be very careful to use auto_ptr if it has been transferred.
auto_ptr<Shape> make_triangle(a)
{
return auto_ptr<Shape>(new Triangle);
}
auto_ptr<Shape> c(make_triangle()); // move temporary into c
double area = make_triangle() - >area(a);// perfectly safe
auto_ptr<Shape> a(new Triangle); // create triangle
auto_ptr<Shape> b(a); // move a into b
double area = a->area(a);// undefined behavior
Copy the code
Obviously, there must be some underlying difference between an A expression that holds an auto_ptr and a make_triangle() expression that holds the type of the auto_ptr value returned by the calling function, which creates a new auto_ptr every time it is called. Here a is an example of an lvalue, and make_triangle() is an example of an rvalue.
Shifting an lvalue like a is dangerous because we might call a member function of A, which can lead to unpredictable behavior. On the other hand, transferring an rvalue like make_triangle() is quite safe because after copying the constructor, we can no longer use the temporary object because the transferred temporary object is destroyed before the next line.
We now know that transferring an lvalue is dangerous, but transferring an rvalue is safe. If C++ had language level support for distinguishing an lvalue from an rvalue parameter, I could eliminate lvalue transfers altogether, or expose the transfer lvalue calls so that we don’t inadvertently transfer an lvalue.
C++ 11’s answer to this question is an rvalue reference. An rvalue reference is a new reference type for an rvalue with the syntax X&&. The old reference type X& is now called an lvalue reference.
One of the most useful functions to use an rvalue referencing X&& as an argument is the transfer constructor X::X(X&& source), which transfers the local resources of the source object to the current object.
In C++ 11, STD ::auto_ptr< T > has been replaced by STD ::unique_ptr< T >, which is the utilized rvalue reference.
Its transfer constructor:
unique_ptr(unique_ptr&& source) // note the rvalue reference
{
ptr = source.ptr;
source.ptr = nullptr;
}
Copy the code
The transfer constructor does the same thing as the copy constructor in auto_ptr, but it can only accept an rvalue as an argument.
unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a); // error
unique_ptr<Shape> c(make_triangle()); // okay
Copy the code
The second line will not compile because a is an lvalue, but the parameter unique_ptr&& source can only accept an rvalue, which is exactly what we need to prevent dangerous implicit transfers. The third line is compiled fine, because make_triangle() is an rvalue, and the transfer constructor transfers ownership of the temporary object to object C, which is exactly what we need.
Transfer lvalue &&move semantics
Sometimes we might want to transfer an lvalue, that is, sometimes we want the compiler to treat an lvalue as an rvalue so that we can use the transfer constructor, even if this is a bit unsafe. For this purpose, C++ 11 provides a template function, STD ::move, in the standard library header < utility >. In fact, STD :: Move simply converts an lvalue to an rvalue; it doesn’t transfer anything by itself. It just makes the object transferable.
Here’s how to transfer an lvalue correctly:
unique_ptr<Shape> a(new Triangle);
unique_ptr<Shape> b(a); // still an error
unique_ptr<Shape> c(std::move(a)); // okay
Copy the code
Notice that after the third line, A no longer owns the Triangle object. This doesn’t matter though, because by explicitly writing STD :: Move (a), we are clear about our intent: Dear transfer constructor, you can do whatever you want to a to initialize C; I don’t need “A” any more. For “A,” suit yourself.
Of course, if you continue using MOVA (A) after using movA (A), you will shoot yourself in the foot and still cause serious runtime errors.
In summary, STD :: Move (some_lvalue) converts an lvalue to an rvalue (think of it as a type conversion), making subsequent transitions possible.
An example:
class Foo
{
unique_ptr<Shape> member;
public:
Foo(unique_ptr<Shape>&& parameter)
: member(parameter) // error{}};Copy the code
Parameter is a reference to an rvalue, and parameter itself is an lvalue. (Things that are declared as rvalue reference can be lvalues or rvalues. The distinguishing criterion is: If it has a name, then it is an lvalue. Otherwise, it is an rvalue.
Therefore, the above transition to parameter is not allowed, and STD ::move is used to show the conversion to rvalue. Jiange_zh is a new feature in C++11
Conclusion:
- Nullptr replaces the ambiguous NULL for a NULL pointer
- C++11 introduced the auto (for variables) and decltype(for expressions) keywords for type derivation, leaving the compiler to worry about the type of variables.
- Interval iteration is introduced
for(auto &i : arr) { std::cout << i << std::endl; }
- Initializer list, C++11 provides a uniform syntax for initializing arbitrary objects,
B(int _a, float _b): a(_a), b(_b) {}
C++11 also binds the concept of initializer lists to types, calling them STD ::initializer_list, allowing constructors and other functions to use initializer lists as arguments. This provides a unified bridge between the initialization of class objects and the initialization methods of ordinary arrays and pods - Template enhancement, the introduction of external templates, expanded the original forcing the compiler to instantiate templates in a specific location syntax, enabling the compiler to explicitly tell when to instantiate templates, reducing compilation time; In traditional C++, a typedef can define a new name for a type, but there is no way to define a new name for a template. Because templates are not types. This can be implemented in new features.
- C++11 introduced the concept of delegate construction, which allows a constructor to call another constructor in the same class, thus simplifying code. Inheritance constructs were also introduced
- Lambda expression, like an anonymous function; Most STL algorithms can be very flexible with lambda expressions to achieve the desired effect.
- STD ::array creates a fixed-size array at compile time. STD :: Array cannot be implicitly converted to Pointers. To use STD ::array, simply specify its type and size; STD ::forward_list is a list container that is used in much the same way as STD ::list. The implementation uses a one-way list, list uses a bidirectional list, provides O(1) complexity element insertion, does not support fast random access, does not provide the size() method, and has a higher space utilization than STD ::list when bidirectional iteration is not required; Unordered containers, STD ::unordered_map/ STD ::unordered_multimap and STD ::unordered_set/ STD ::unordered_multiset. Elements are not sorted, but internally implemented by hash tables. The average complexity of inserting and searching elements is O(n). Tuples tuple
- Regular expressions that check whether a string contains substrings of some form; Replaces the matched substring; Retrieves a substring from a string that matches the condition.
- Language-level threading support, with code compiled using the -pthread option
- Rvalue reference and MOVE semantics, copy constructors are divided into copy constructors and transfer constructors. The transfer constructor does not deeply copy the data in the heap memory, but only copies the pointer and nulfies the pointer to the source object. Since the source object is an rvalue and will not be used again, the customer will not be aware that the source object has been changed. Here, we don’t really copy, so we call this constructor “move constructor,” whose job is to transfer resources from one object to another, not copy them. In the new C++ feature, for C++ 11, the compiler chooses between the copy constructor and the transfer constructor based on whether the argument is lvalue or rvalue. In summary, the copy constructor performs a deep copy because the source object itself must not be altered. The transfer constructor, on the other hand, copies the pointer and nullifies the pointer to the source object. In this form, it is safe because the user cannot use the object again. The old auto_ptr pointer can be used to make an lvalue look like a copy but actually move, with unknown consequences. It is known that transferring an lvalue is very dangerous, but transferring an rvalue is very safe. C++11 considers an rvalue reference. Auto_ptr becomes unique_ptr. Unique_ptr uses an rvalue reference, which is similar to auto_ptr but only accepts an rvalue as an argument. To move an lvalue, you need to use move(), which converts an lvalue to an rvalue and makes an lvalue transferable. Objects moved by move are no longer usable.
18. Hash table insert time complexity
The time complexity of hash table insertion is related to the number of conflicts, O(number of conflicts /n). In the best case, the number of conflicts is 0, and the time complexity is O(1). The worst case is that all values correspond to the same key value, which is the most likely to conflict, 0+1+2+3+4+… +(n-1)= N *(n-1)/2, average number of comparisons is (n-1)/2, time complexity is O(n)
Resources: What is wrong with the following data structure statement? Nowcoder.com
19. When must C++ initializer lists be used
In theory:
- Initialize the! = assignment.
A. Initialize means to allocate memory for a variable. Variables are initialized by the compiler at their definition (compile time). In functions, initialization of function parameters occurs when the function is called (run time).b. Assignment means “erases the current value of the object and assigns a new value.” It is not obligated to allocate memory for the object.
- In C++, class members are initialized in the initializer list, before the constructor body. That is, the actual initialization of members occurs in the initializer list, not in the constructor body.
Explain again.
- If a member of a class is a reference, since the reference must be given an initial value, the reference must use an initializer list.
- Likewise, the const attribute must be given an initial value, and an initializer list must be used.
- To call a base-class initializer constructor from an inherited class, you actually construct the base-class object first, and must use the initializer list.
Nothing else. It is obvious when initializers must be used; In addition, simply put, initializer lists are encouraged at all times. Other things (such as allocating resources in constructors) can be done inside constructors.
Resources: when must you use C++ initializer lists _WingC’s blog -CSDN blog _c++ when should you use initializer lists
20. Where is the reference count for the smart pointer
On the heap, if it’s not on the heap one might be checking different objects, zeros multiple times, freeing multiple times, memory leaks
Resources: is the reference count for c++ smart Pointers on the stack or on the heap? Nowcoder.com
21. A red-black tree
Linear search — low performance — > binary search — binary search will degenerate into a linked list — > AVL balanced binary tree — data changes frequently update nodes — > red black tree what is red black tree? The Red Black Tree is a self-balancing BST, where each node of the Tree follows the following rules:
- Each node has a color, either red or black;
- The root of the tree is black;
- There are no two adjacent red nodes in the tree (that is, neither the parent node nor the child node of the red node can be red).
- Every path from any node (including the root node) to any of its descendants (which are black by default) has the same number of black nodes.
Reference: What is a Red black tree? An article that solves all the doubts.
22. Smart Pointers
C++11 introduced three smart pointer types:
std::unique_ptr<T>
: indicates exclusive resource ownership.std::shared_ptr<T>
: Indicates the ownership of shared resources.std::weak_ptr<T>
: An observer of a shared resource that needs to be used with STD ::shared_ptr without affecting the lifetime of the resource.
STD ::auto_ptr is deprecated.
std::unique_ptr
Simply put, when we have exclusive ownership of a resource, we can use STD ::unique_ptr to manage the resource — when we leave the scope of the unique_PTR object, the resource is automatically released. That’s the basic RAII idea.
STD :: unique_PTR is easy to use and is a smart pointer that is often used. So let’s go straight to the example.
std::unique_ptr
Simply put, when we have exclusive ownership of a resource, we can use STD ::unique_ptr to manage the resource — when we leave the scope of the unique_PTR object, the resource is automatically released. That’s the basic RAII idea.
STD :: unique_PTR is easy to use and is a smart pointer that is often used. So let’s go straight to the example.
- When using bare Pointers, remember to free memory.
{ int* p = new int(100); / /... delete p; // Remember to free memory}Copy the code
- Automatically manage memory using STD ::unique_ptr.
{ std::unique_ptr<int> uptr = std::make_unique<int>(200); / /... // Automatically free memory when leaving uptr scope}Copy the code
- STD ::unique_ptr is move-only.
{ std::unique_ptr<int> uptr = std::make_unique<int>(200); std::unique_ptr<int> uptr1 = uptr; STD ::unique_ptr<T> is a move-only STD ::unique_ptr<int> uptr2 = STD ::move(uptr); assert(uptr == nullptr); }Copy the code
std::shared_ptr
STD :: shareD_ptr is simply a reference count of a resource – when the reference count reaches zero, the resource is automatically released.
Implementation of STD :: shareD_Ptr
A shared_ptr object has a slightly higher memory overhead than a bare pointer or unique_ptr without a custom deleter.
Shared_ptr needs to maintain two parts of the information:
- Pointer to a shared resource.
- Control information for shared resources, such as reference counts — essentially maintaining a pointer to control information.
std::weak_ptr
STD :: Weak_Ptr is used with STD :: shareD_PTR. A STD :: Weak_ptr object is regarded as an observer of the resource managed by STD :: ShareD_PTR object, which does not affect the life cycle of the shared resource:
- If you need to use the resource that Weak_Ptr is observing, you can promote Weak_Ptr to shared_PTR.
- When shared_PTR managed resources are released, Weak_PTR automatically becomes NullPTR.
When shared_ptr destructs and releases the shared resource, the control block is retained as long as the Weak_ptr object exists, and the Weak_Ptr can observe whether the object is alive or not through the control block.
enable_shared_from_this
The correct way for a member function to get the shared_ptr of this is to inherit STD :: enable_shareD_from_this.
summary
Smart Pointers are essentially abstractions of resource ownership and lifecycle management:
- When a resource is an exclusive, use STD ::unique_ptr to manage the resource.
- When resources are to be shared, they are managed using STD ::shared_ptr.
- Use STD :: Weak_ptr as an observer of the STD :: shareD_PTR managed object.
- Get the STD ::shared_ptr object of this by inheriting STD :: enable_shareD_from_this.
References:
- Modern C++ : understanding intelligent pointer – zhihu (zhihu.com)
- C++11 smart pointer — lonely cat — blogblogs.com