This note will record all of Cherno’s C++ series that will be interesting to beginners. Simple syntax and basic operations will not be noted here. Beginners are strongly recommended to watch the whole series of tutorials in their entirety.

Note that each P knowledge point is not isolated, and may be further and comprehensively expanded and explained in the future. Some simple knowledge points may focus more on the bottom layer and optimization, so they are worthy of careful study and careful consideration.

P5. How does C++ work

These are pre-processed statements that are executed before compilation, such as the include statement in this code, which looks for a file called iostream and copies the entire contents of the iostream file into the current file. In addition, this type of file is also called a head file.

#include <iostram>
#define
#ifndef
#endif
Copy the code

P6. How does the C++ compiler work

In C++ any symbol needs to be declared, and we compile each file individually. If you call an external function and don’t declare it in the current file, the current file doesn’t know that such a function exists. If you declare a function but do not define it, the compiler still trusts you completely, but the linker will report a link error when it tries to find the function you defined and cannot find it. If everything is correct, the compiler will compile each file individually as an.obj file, and the linker will combine them into an executable, such as an exe file.

The compiler converts the text into an intermediate format called an object file. In this process, the compiler preprocesses the code, turns our code into an abstract syntax tree, and then generates the code that the CPU executes.

P7. How does the C++ linker work

C++ programs must have an entry function, such as main. You can also change the name of this function in the IDE. However, the linker will always look for the entry function you specify and will report a linker error if it is not defined.

Because the compiler trusts you, if you declare a function in a file without actually defining it, only the linker will find this error. Even if you do not call the undefined function, the linker will still report an error because the linker cannot be sure that other files will not be called. However, if you decorate a function with the static keyword, then the function only works in the current file, meaning that the linker doesn’t think the function was called from an external file.

Link error

If the same definition exists in the same file, the compiler will detect this error, but if the same definition exists in different files, only the linker will detect this error. This type of link error is triggered when using the #include statement, which can cause the header file to be copied multiple times in different files. We can avoid copying definitions by putting all declarations in one header file, and each translation unit contains its own header file.

P.8 variables in C++

The size of a variable’s type is directly related to how large a number it can store. An integer is 4 bytes, or 32 bits of data. If the type is signed, one of the bits is required to represent plus or minus, which means that the type can actually represent 2312^{31}231 possible values. The range of integer variables is ±231±2^{31}±231. If you remove the sign, the type is unsigned and its value range increases, but only for values greater than or equal to zero. It is important to note that bool values are either 0 or 1, which theoretically requires only 1bit of memory. However, addressing in memory cannot find only 1bit of memory, so declaring a bool variable still takes 1 byte of memory. Note that how big a type is depends on the compiler, but we can query the type size using the sizeof operator.

sizeof(int);
Copy the code

P9. Functions in C++

The purpose of functions is to reduce duplication and not to create functions too often, because the compiler generates a Call instruction every time a function is called. The compiler needs to create a stack structure for each function, and then push the return address and function parameters onto the stack. The runtime then jumps to a different part of the binary execution file to execute our function commands.

Main doesn’t return a value. This is a feature of modern C++. The compiler will give you a line at the end of return 0; This is just to make your code cleaner. Other functions that do not return a value will not report an error in Release mode. If you do call this function, you will still report an “undefined behavior” error. In Debug mode, you will be told that there is no value returned.

P10. Header files

In C++, header files are usually used to declare certain types of functions for easy calls in programs. The preprocessing statement #pragma once supervises that the current header file is included only once. For example, the command checks if _LOG_H is defined, and if so, none of the code it contains is included.

Compared to the #ifndef and #endif forms, there is no difference between the two, except that the #pragma once preprocessing statements are more concise.

#ifndef _LOG_H
#define _LOG_H
/ /...
#endif
Copy the code

The include symbol of a header file

Header files introduced by the <> symbol will search for header files under the absolute path, and header files introduced by the “” symbol will contain header files relative to the current file. While Cherno’s own style is to include his own written header file is, all use “”. In addition, iostream-like headers do not have file extensions, as the C++ library requires to distinguish them from C, but this does not prevent the compiler from recognizing them as headers.

Conditional statements and branching in p12. C++

The if condition is just dealing with numbers, 0 is false, 0 is true. When we create a Boolean value that actually takes up 1 byte of space, it’s not necessary to know which bits in the byte are set to 1, as long as one of them is not 0, then the 1 byte of memory of type bool represents true. The underlying process is that the MOVE instruction loads 0 into an in-memory register, which is equal to false, so the Boolean value is set to false, and the IF instruction loads some value into the EAX register. If the compiler can determine the result of the comparison itself and does not need to do the comparison at runtime, this is called constant folding, and the optimization automatically skips the associated bool, if condition, and so on.

int x = 6;
bool result = x==5;
Copy the code

Else if is not a keyword, but a syntactic sugar.

else if(){
}
//equals
else{
    if() {}}Copy the code

P16. Pointers in C++

A pointer is an integer, a number that stores the address of memory. Think of memory as a straight line, a row of houses, where each house is a zero or one of 8 bits in a word, and each house has an address. We need a way to address arbitrary bytes. And type is just a myth that we invented to make life easier. A pointer to any type is an integer that holds a memory address. In addition, 0 is not a valid memory address, and assigning a pointer to 0 means that it is an invalid pointer. In fact, NULL constant is 0, and nullptr can also be assigned

P17. References in C++

References are Pointers in disguise, just syntactic sugar on top of Pointers to make them easier to read and understand. We can set the value of a pointer to 0 for invalid Pointers, but we cannot do this for a reference, because a reference must be able to “reference” a variable, which must be an existing variable. The reference itself does not take up memory, has no storage space of its own, and the compiler does not create a new variable. Instead, it replaces the reference with the original variable. In other words, the reference is just an alias for the variable.

fuction(&var);
void fuction(int* value){
    (*value)++;
}
//equals
function(var);
void fuction(int& value){
    value++;
}
Copy the code

P.19 classes VS constructs in C++

The only difference between a class and a struct is that class members default to private and struct members default to public. The reason why the two are so similar but not unified is to keep the struct keyword for compatibility with C language.

P.21 static in C++

The meaning of the static keyword in C++ depends on the context. Using the static keyword outside of a class or structure is different from using it inside. Using static outside the class means that the member is only valid internally, means that its scope is limited to the CPP file, and means that it is visible only to the compilation unit, outside which the linker does not look for its definition.

If the same variable is defined in two separate files, this will obviously not be passed by the linker. If we add the static keyword to one of these variables, it won’t be searched by the linker, but only in its own file. It’s kind of like declaring a private variable that no other translation unit can see. But if we delete one of these assignments and extern, The compiler then looks for the variable in the external translation unit, which also avoids conflicts with the same name variables.

P22. Static state of classes and structures

Static in a class or structure means that the modified member is shared by all instances of the type. Static class member variables or methods do not have direct access to class non-static variables or methods. A static method of a class, for example, can only be accessed as an instance of the class passed in as a function parameter, not as a non-static class member variable or method directly within the function.

P23. Local static

When declaring a variable, we need to consider the scope and life cycle of the variable, whereas local static variables are only valid in scope, but their life cycle lasts until the program terminates.

The first time a function is called when it has a static variable, the static variable is initialized, and the same variable is available for all subsequent calls to the function, rather than creating new variables. The latter provides the same effect, but the latter way also causes the static variable to be globally callable in the file, whereas the former only works in the scope in which it was declared.

void function(a){
    static int i = 0;
    i++;
}

static int i = 0;
void function(a){
    i++;
}
Copy the code

P28. Virtual functions in C++

Virtual functions introduce dynamic dispatch, which is compiled through virtual function tables. A virtual function table is a table that contains a map of all virtual functions in the base class to properly override the function at run time.

Virtual functions are not cost-free; we need extra memory to store the virtual function table in order to overwrite the function properly; The base class also has a member pointer to the virtual function table. When we call a virtual function, we need to traverse the virtual function table once to determine which function is mapped to, which is an additional performance penalty, albeit a very small one.

P31. Arrays in C++

A C++ array is a collection of variables, usually a line of memory of the same data type. Memory access violation occurs when you access a nonexistent array element through an array index. In Debug mode you are notified, but in Release mode no error message is generated. So when you use native arrays you have to always be aware of boundaries.

In memory, arrays are allocated consecutively in a memory segment whose size is the size of the data type multiplied by the length of the array. An array variable is actually a pointer to the address of the first element of the array. So we just add the address of the pointer +n, and we move to the address of the other member of the array.

int array[5];
int* ptr = array;

array[2] = 5;
//equals
*(ptr + 2) =6;
Copy the code

The +n here is not an increase of n bytes, but is actually automatically multiplied by the byte size of the type. For example, we could first convert the pointer address to a char* pointer. Since char is only 1 byte, we need +8 to move to the address of the third element of the array. We then convert the char* pointer that already points to the address of the third element of the array to an int* pointer, and finally dereference the value in memory that the address points to.

* (int((*)char*)ptr + 8) = 6;
Copy the code

An array is just a contiguous block of data that can be indexed like a book. Creating an array on the heap leads to indirection, where the array variables stored on the stack no longer point directly to the array, but instead to the address stored in the array on the heap, which needs to be jumped, obviously affecting performance.

How do strings in P32. C++ work & how do I use them

Strings are immutable and are allocated a fixed block of memory. It is no longer allowed to declare strings of the form without the const keyword. A string ends with an extra 00 bytes in memory in addition to its own characters, which is the terminator. We can tell the compiler how long the string is by seeing the terminator string.

const char* name = "Cherno";
Copy the code

In memory view, we can see that there are a lot of cc bytes around the stored data. These bytes are called array guards. Allocating arrays and inserting stack guards and so on in Debug mode can tell if we are allocating data out of memory. In addition, we should be careful to write strings as references in function arguments, because string copying costs money and it is worth avoiding unnecessary performance waste.

void Print(const std::string& string){
    string += "h";
}
Copy the code

P33. String literals in C++

For example, “Cherno” is a string literal, but its length is 7 instead of 6, because a null terminator \n is automatically added.

"Cherno"
//equals
"Cherno\n"
Copy the code

When we use strlen to print the string length, we see that the length stops when we encounter a terminator, not the length of the declared array of strings. Similarly, even if there is no terminator in the string, it still does not record the terminator at the end of the string as a length, so the length will be 1 less than the size of the array you declared.

const char name[8] = "Che\nrno";
Copy the code

The string literal is stored in the CONST part of the binary, and when we refer to the string literal, we actually refer to a constant area that we can’t edit. But if you declare a string as an array, instead of pointing to a constant area, you can modify the string. A string constant declared as a pointer. Modifying it is an undefined behavior that you may not be warned about in Release mode, but will not pass at runtime.

Other types of characters

Char16_t is a two-byte, 16-bit character, and char32_t is a four-byte, 32-bit character, namely utF8 and UTf16. Wchar_t is a wide character, usually two bytes and 16 bits, but the actual size of this type depends on the platform and compiler.

const char* name = u8"Cherno";
const wchar_t* name = L"Cherno";
const char16_t* name = u"Cherno";
const char32_t* name = U"Cherno";
Copy the code

Since string literals are essentially char* Pointers and cannot be concatenated by adding two Pointers directly, we can make the former a STD :: String object by passing the string literal in the form of the former using a constructor of type STD :: String.

std::string name = std::string("Cherno") + " hello";
Copy the code

C++14 provides a new feature that makes it possible to add strings. Adding s to the end of the string makes STD ::string objects, and adding u8, L, and other prefixes makes other string objects.

using namespace std::string_literals;
std::string name = "Cherno"s + " hello";
Copy the code

P34. Const in C++

Const is a fake keyword that does nothing but make you promise not to change. But promises can be broken, and it’s up to you to keep them.

Combination of const and pointer

In the following code, the former two are the same, can not modify *a. The third party can not modify A, the last one is * A,a can not be modified. Const *a = a; const *a = a; const *a = a; Const int* const a; Const * const a = new int; const * const a = new int; .

const int* a'
int const* a;
int* const a;
const int* const a;
Copy the code

Application of const to classes and functions

When you modify a function with the const keyword, you cannot modify a class member variable in the function. If you do, you need to modify the variable by adding a mutable keyword. If you need to call a const type instance of the class non-static function, it is not allowed, because you can’t guarantee the function does not modify the type of member variables, while the function type const modifiers can guarantee that so often provide a function in the development of const keyword modified version and a normal version.

class Entity{
private:
    int x;
    mutable int y;
public:
    int GetX(a){returnx; }int GetX(a) const {
        y = 0;
        returnx; }}void PrintEntity(const Entity& e){
    std::cout<<e.GetX()<<std::endl;
}
Copy the code

Pointers and references to const function arguments

The const Entity* e parameter is a parameter that cannot change what the pointer points to, but can change the pointer. The const Entity &e in the argument is the variable itself passed to the function. Simply using a reference instead of a variable in the function does not make a copy of the instance, reducing overhead. So in the function, e is the content of the instance and can be called directly.

void PrintEntity(const Entity* e) {
    std::cout << e->GetX() << std::endl;
}
//equals
void PrintEntity(const Entity& e) {
    std::cout << e.GetX() << std::endl;
}
Copy the code