C++: Getting started learning C++, what modifications has it made on the basis of C?

Namespaces

First look at this code:

#include <stdlib.h>
#include <stdio.h>

int rand = 0;

int main()
{
    
    
	printf("%d", rand);
	return 0;
}

Does the above code compile and pass? Obviously not, the reason is that the rand function is included in the header file stdlib, so you can no longer use rand as your variable when defining variables

In the future writing of engineering projects, there will be many such situations. After including a certain header file, many variables in the code can no longer be used, which reflects a part of the limitations of the C language.

Then C++ improves this problem very well on the basis of C language. C++ introduces the concept of namespace and names variables in a certain space, which can solve this problem very well.

The definition of namespace is very free, you can define variables, functions, structures, and even nested definitions

namespace zbh
{
    
    
	//定义变量
	int test = 0;

	//定义函数
	int Add(int x, int y)
	{
    
    
		return x + y;
	}

	//定义结构体
	struct MyStruct
	{
    
    
		int a;
		int b;
	};

	//命名空间可以嵌套
	namespace free
	{
    
    
		int print1()
		{
    
    
			return 1;
		}
	}
}

How are namespaces used? How does C++ guarantee the independence of namespaces?

  1. Indicate separately when using variables
  2. The previous definition uses a function or variable in the namespace, etc.
  3. Expand directly
#include <stdio.h>

namespace zbh
{
    
    
	//定义变量
	int test = 0;

	//定义函数
	int Add(int x, int y)
	{
    
    
		return x + y;
	}

	//定义结构体
	struct MyStruct
	{
    
    
		int a;
		int b;
	};

	//命名空间可以嵌套
	namespace free
	{
    
    
		int print1()
		{
    
    
			return 1;
		}
	}
}

using zbh::Add;

int main()
{
    
    
	printf("%d\n", zbh::test);
	printf("%d\n", Add(1,2));
	printf("%d", zbh::free::print1());
}

The above definition of the namespace can also be omitted, just add before the main function

using namespace zbh;

It can be used directly in the following functions. In this way, the independence of the function namespace is realized.

default parameters

C++ defines default parameters for function parameters. It can be understood that if I assign an initial value to a member of a function parameter, then in the process of subsequent calls, if I do not pass parameters to the function, then the function will use the default parameters

Specific examples are as follows

#include <iostream>
using namespace std;

void f(int a = 10, int b = 20, int c = 30)
{
    
    
	cout << a << " " << b << " " << c << endl;
}

int main()
{
    
    
	f();
	f(1);
	f(1, 2);
	f(1, 2, 3);
	return 0;
}

The running results are as follows:
insert image description here
What is the effect of such an operation in practice? ?

In defining the sequence table, we use a dynamically developed sequence table, so can we use default parameters to optimize some steps during the initialization phase?

First look at the method in the implementation process of C language

#include <stdio.h>
#include <stdlib.h>

typedef int SLDataType;
typedef struct Seqlist
{
    
    
	SLDataType* a;
	int size;
	int capacity;
};

void SeqlistInit(Seqlist* s)
{
    
    
	s->a = (SLDataType*)malloc(sizeof(SLDataType) * 4);
	s->size = 0;
	s->capacity = 4;
}

This kind of implementation actually writes the capacity to death. No matter how big the sequence table is to be opened, we will first open the sequence table with a capacity of 4 and then expand the capacity. The realloc used for capacity expansion is consuming.

But suppose if we use the default parameters to implement this function, we can optimize a lot

#include <iostream>
using namespace std;

typedef int SLDataType;
typedef struct Seqlist
{
    
    
	SLDataType* a;
	int size;
	int capacity;
}Seqlist;

void SeqlistInit(Seqlist* s,int capacity=4)
{
    
    
	s->a = (SLDataType*)malloc(sizeof(SLDataType) * capacity);
	s->size = 0;
	s->capacity = capacity;
}

int main()
{
    
    
	Seqlist sq,sl;
	SeqlistInit(&sl);
	SeqlistInit(&sq, 100);
	return 0;
}

Call monitoring to observe that you can see

insert image description here
Using the default parameters, we have indeed achieved the freedom to determine the capacity we want

function overloading


C++ allows several functions of the same name with similar functions to be declared in the same scope. The formal parameter lists of these functions with the same name are different (the number or type of parameters or the order of types), which is often used to deal with the problem of implementing similar functions with different data types.

The following shows what function overloading is

#include <iostream>
using namespace std;

void func(int a, double b)
{
    
    
	cout << "void func(int a, double b)" << endl;
}

void func(double b, int a)
{
    
    
	cout << "void func(double b, int a)" << endl;
}

int main()
{
    
    
	func(1, 1.1);
	func(2.2, 2);
	return 0;
}

When the number or type of parameters in the function with the same name is different, it constitutes a function overload, and the compiler will automatically recognize that the number and type of parameters passed correspond to different types of function overloading

Then why does C++ support it, but C doesn't?

Here we need to talk about the process that a program needs to go through when it runs

preprocessing, compiling, assembling, linking

Assuming that an Add function is defined now, this Add function is defined in the a.cpp file, and I want to use this function in b.cpp, when the program is run after use, the compiler will use this function in b.cpp function, but the address of this function cannot be found, so the compiler will go to the symbol table of b.cpp to find the
address of Add, and then link the two together

In the process of linking, C and C++ have different places. Each compiler has its own rules for modifying function names.

The following shows the display results of the C language compiler:
insert image description here
it can be seen that after the compilation is completed, the modification of the function name is still the name of the function, and there is no substantial change

Let's look at the sample in the C++ compiler

insert image description here
Comparing with the C language compiler, it can be clearly seen that the C++ compiler modifies the function name to a certain extent during the compilation process. Under the Linux compiler, the parameter type and number of parameters of the function are also modified. into the function name

Therefore, we know from it why C language does not support function overloading, but C++ supports it. It is because the C language compiler treats the function name as the function name, while the C++ compiler modifies the function name by introducing parameters. So the overloaded function has a different function name

From this, it is easy to understand why the type and number of function parameters are the same, only when the return value is different, the function overload cannot be constituted, because the compiler does not know which one to call for two functions with the same name

quote

Another major adjustment made by C++ on the basis of C is the reference

Simply put, a reference is an alias for a variable, and the variable itself and the reference together control the area where the variable is located

There is the following code:

void test2()
{
    
    
	int a = 10;
	int& b = a;
	cout << &a << endl;
	cout << &b << endl;
}

So what is the result of running this program?

insert image description here
It can be seen from this that the reference does not open up a separate space to manage the pointed object, but directly controls the content of a certain area together with the original variable

Some features of the reference

  1. References must be initialized when they are defined
  2. A variable can have multiple references
  3. References Once an entity is confirmed, no other content can be referenced

References as function return values

References as function return values ​​are a dangerous business, but highly rewarding if used correctly

Let's take the following operation as an example

int& Count()
{
    
    
	int n = 1;
	n++;
	return n;
}

void test2()
{
    
    
	int a = 10;
}

int main()
{
    
    
	int& ret = Count();
	cout << ret << endl;
	test2();
	cout << ret << endl;
	return 0;
}

The output is 2 and 10, why?

The reason rises to the problem of the function stack frame, first draw the function stack frame

The following is the operation of the main function when executing the first two lines, and the follow-up has not been drawn

insert image description here
&ret is defined here, which receives the return value int& n from the Count function. In fact, ret here already has the ability to manage the area of ​​n, and we know that the stack frame will be destroyed after the function ends. The destruction here just loses the right to manage this area in the memory, and the area itself still exists in the memory

Therefore, when the test2 function is executed below, a new stack frame will be opened, and the position of this stack frame and the stack frame of the Count function have a large overlapping area

insert image description here
Therefore, the ret here already has the ability to manage the space that does not belong to it. It can access an area that does not belong to it at any time. Therefore, the space that was originally n here has now become a, but it can still be accessed. So the result of the visit is 10

So here are some things to watch out for:

==If the function returns, it is out of the scope of the function, if the returned object is still there (not returned to the system), then you can use reference return here, if it is a temporary variable or local variable like this, you must use value passing return

Then again, since references are so dangerous as return values ​​or parameters, why is it necessary to use them?

quoted earnings

Take function parameter passing as an example. If you don’t pass a reference, a formal parameter will be constructed when the function is passing parameters. If you pass an object, many default member functions will be executed, which is also a performance loss. If you pass an object Reference, directly refer to the created object here, omitting the process of formal parameter creation and destruction

Advantages of passing parameters by reference

  1. Improve efficiency
  2. Output parameters (changes in formal parameters can affect actual parameters, similar to pointers, but simpler than pointers)

Continuing to talk about the advantages of returning by reference, there are many benefits of returning by reference, of course, the correct premise is used; with such a principle, when can return by reference be used? The conclusion is that when the returned content is out of the scope of the function, it can not be destroyed, which is quite high-yield. In many member functions in the class, *this is usually returned, and this will not be destroyed. It's worth using the return value by reference

Advantages of return by reference

  1. Improve efficiency
  2. Can modify the returned object

To sum up, passing parameters by value or returning values ​​by value will create objects, and using references can avoid invalid and redundant creation of performance loss

Difference between reference and pointer?

In terms of grammatical concept, a reference is an alias and has no space of its own. It exists in itself to share a space with the entity

But the reference has its own space
in the underlying implementation , and the reference itself is implemented in the form of a pointer

Comparison of references and pointers

Differences between references and pointers:

  1. A reference conceptually defines an alias for a variable, and a pointer stores the address of a variable
  2. References must be initialized when they are defined, pointers are not required
  3. After the reference refers to an entity during initialization, it cannot refer to other entities, and the pointer can point to any entity of the same type at any time
  4. There are no NULL references, but there are NULL pointers
  5. The meaning is different in sizeof: the reference result is the size of the reference type, but the pointer is always the number of bytes occupied by the address space (
    4 bytes under the 32-bit platform)
  6. The self-increment of the reference means that the referenced entity increases by 1, and the self-increment of the pointer means that the pointer offsets the size of a type backward
  7. Multi-level pointers, but no multi-level references
  8. There are different ways to access entities, the pointer needs to be explicitly dereferenced, and the reference compiler handles it by itself
  9. References are relatively safer to use than pointers

inline function

First, what is an inline function?

A function decorated with inline is called an inline function, and the C++ compiler will expand it at the place where the inline function is called during compilation, without the overhead of creating a stack
frame for function calls, and the inline function improves the efficiency of program operation.

We all know that when the main function executes a certain function, it will create a stack frame of the function in the memory, and then call (call), and the establishment of a function stack frame is costly. If you create some frequently, there may be only a few The function of the statement will waste the efficiency of the program running, so the generation of the inline function solves this problem

The working principle of the inline function is to directly replace the function body with a function call at the compilation stage. It can be seen from the assembly that normal functions are created by function calls and function stack frames, while inline functions directly replace the function body put it all into assembly

Now we have code like this:

int add(int a, int b)
{
    
    
	return a + b;
}

int main()
{
    
    
	int a = 1;
	int b = 2;
	int c = add(a, b);
}

to observe by compiling

insert image description here
When the add function is executed, the add function is indeed called, so if the add function is set as an inline function

inline int add(int a, int b)
{
    
    
	return a + b;
}

int main()
{
    
    
	int a = 1;
	int b = 2;
	int c = add(a, b);
}

Looking at its assembly code again, you will find that it is no longer a function call, but directly expands the function body

insert image description here

Features of Inline Functions

From this point of view, inline functions are indeed very useful, but inline functions have their disadvantages that cannot be ignored

The inline function is a method of exchanging space for time. It is undeniable that the time consumption required by the function to build the stack frame is omitted here, but in the compilation stage, the inline function will replace all function calls with the function body, which means will make the target file larger

Therefore, the inline function is just a suggestion. It is just a suggestion that the compiler can treat this function as an inline function, but whether it is treated as an inline function or not depends on the compiler itself. The scale of the function is relatively small, it is not a recursive function, and the function that is called frequently is set as an inline function

At the same time, the inline function should not be declared and defined separately. After the inline function is expanded, the function address will no longer exist, and the link cannot be found during the linking process.

Relationship between inline functions and macros

In the C language, macros are introduced. Macros seem to be a good feature, but there are also many disadvantages.

Pros and cons of macros?

advantage:

  1. Enhance code reusability.
  2. Improve performance.

shortcoming:

  1. Inconvenient to debug macros. (Because the precompilation phase is replaced)
  2. It leads to poor code readability, poor maintainability, and easy misuse.
  3. There are no checks for type safety.

Therefore, the appearance of inline functions can be regarded as making up for the inconvenience caused by macro definition functions.

pointer null problem

In good programming habits, defining a variable requires giving it a certain initial value, so in C language, when we define a pointer, its initialization is often NULL

NULL is actually a macro

#define NULL  0

As you can see, NULL may be defined as the literal constant 0, or as a constant of an untyped pointer (void*). No matter what kind of definition is adopted, some troubles will inevitably be encountered when using pointers of null values, such as:

void f(int)
{
    
    
	cout << "f(int)" << endl;
}
void f(int*)
{
    
    
	cout << "f(int*)" << endl;
}
int main()
{
    
    
	f(0);
	f(NULL);
	f((int*)NULL);
	return 0;
}

The original intention of the program is to call the pointer version of the f(int*) function through f(NULL), but since NULL is defined as 0, it is contrary to the original intention of the program.

In C++98, the literal constant 0 can be either an integer number or an untyped pointer (void*) constant, but the compiler treats it as an integer constant by default. To use it in pointer mode, it must be cast to (void*)0.

Therefore, in the new standard of C++, null pointers are introduced:

  1. When using nullptr to represent the null value of the pointer, there is no need to include the header file, because nullptr was introduced as a new keyword in C++11.
  2. In C++11, sizeof(nullptr) and sizeof((void*)0) occupy the same number of bytes.
  3. In order to improve the robustness of the code, it is recommended to use nullptr when representing the null value of the pointer later.

Guess you like

Origin blog.csdn.net/qq_73899585/article/details/131822214