[C + +] first glimpse of the path - beginner level chapter

catalogue

preface

1. Namespace

1.1 namespace definition

1.2 namespace usage

2. Input and output of C + +

3. Default parameters

3.1 concept

3.2 default parameter classification

4. Function overloading

4.1 concept

4.2 name mangling

5.extern "C"

6. Reference

6.1 concept

  6.2 reference characteristics

6.3 frequently cited

6.4 usage scenarios

six point five   The difference between reference and pointer

7. Inline function

7.1 concept

7.2 characteristics

8. auto keyword (C++11)

8.1 introduction to auto

8.2 use of Auto

8.3 scenarios that cannot be deduced by auto

9. Range based for loop (C++11)

9.1 syntax of scope for

9.2 scope and conditions of use

10. Pointer null nullptr(C++11)

preface

After the previous preliminary study of C language and data structure, I have also initially entered the programming world.

From the initial extreme interest, to the reduction of enthusiasm, to plain, and now the code has gradually become a part of life.

The more I feel the wonder of the code and the vastness of the programming world.

I hope I can look back and sigh for it in two years~

Thank you for your praise and support, and encourage you!

Starting from this chapter, we will enter the C + + learning stage.

1. Namespace

In C/C + +, there are a large number of variables, functions and classes to be learned later. The names of these variables, functions and classes will exist in the global scope, which may lead to many conflicts.

The purpose of using namespace is to localize the name of identifier to avoid naming conflict or name pollution. The emergence of namespace keyword is aimed at this problem.

  For example, conflicts between self-defined variable (function, class) names and C + + built-in function names cannot be solved in C language.

            In large-scale projects, the names of variables (functions, classes) defined by themselves conflict with those defined by others.

1.1 namespace definition

To define a namespace, you need to use the namespace keyword, followed by the name of the namespace, followed by a pair of {}, which are the members of the namespace.

//1. Common namespace
namespace N1 // N1 is the name of the namespace
{
	// Namespace, you can define either variables or functions
	int a;
	int Add(int left, int right)
	{
		return left + right;
	}
}

//2. Namespaces can be nested
namespace N2
{
	int a;
	int b;
	int Add(int left, int right)
	{
		return left + right;
	}
	namespace N3
	{
		int c;
		int d;
		int Sub(int left, int right)
		{
			return left - right;
		}
	}
}

//3. Multiple namespaces with the same name are allowed in the same project, and the compiler will finally synthesize them into the same namespace.
namespace N1
{
	int Mul(int left, int right)
	{
		return left * right;
	}
}

Note: a namespace defines a new scope, and all contents in the namespace are limited to the namespace.

1.2 namespace usage
 

A namespace is a scope. If you only write the variable name when using the variables in it, the compiler will certainly not recognize it.

For example:

namespace N
{
    int a = 10;
    int b = 20;

    int Add(int x, int y)
    {
        return x + y;
    }

    int Sub(int x, int y)
    {
        return x - y;
    }
}
int main()
{
    printf("%d\n", a); // The statement compilation error, unrecognized variable a
    return 0;
}

How do you use members in namespaces?

There are three ways:

Method 1: add namespace name and scope qualifier (::) (the most secure and reliable, but it is not convenient to use)

int main()
{
    printf("%d\n", N::a);
    return 0;
}

Method 2: use using to introduce the members in the namespace (expand the common members in the namespace)

using N::b; // After deployment, it can be used directly later
int main()
{
    printf("%d\n", N::a);
    printf("%d\n", b);
    return 0;
}

Method 3: use the using namespace namespace name to import (this method is unreliable and cannot be used in the project) (expand all members in the namespace)

using namespce N;
int main()
{
    printf("%d\n", N::a);
    printf("%d\n", b);
    Add(10, 20);
    return 0;
}

be careful:

  • Members defined in namespaces are global in nature and placed in static areas.
  • Variables in the namespace can be initialized, but cannot be assigned.
namespace N
{
	int a;// Variable a defined, uninitialized
	a = 0;// Cannot assign a value in a namespace
	int b = 10;// Define variable b and initialize it at the same time
}

2. Input and output of C + +

There are new input and output methods in C + +, but C + + contains C language, so the input and output of in C language are also applicable in C + +.

Hello world!!! 

#include<iostream>
using namespace std;
int main()
{
    cout<<"Hello world!!!"<<endl;//C + + output
    return 0;
}
#include <iostream>
using namespace std;
int main()
{
    int a;
    double b;
    char c;

    cin>>a;
    cin>>b>>c;
    cout<<a<<endl;
    cout<<b<<" "<<c<<endl;
    return 0;
}

It doesn't matter if you have many doubts about the above code. Let's answer them in turn.

  1.   When using cout standard output (console) and cin standard input (keyboard), the < iostream > header file and std standard namespace must be included.
  2. In the usual code writing exercise, we can expand the std namespace directly.
  3. The input in C + + is cin and the output is cout.
  4. As for > > and < < it means inputting and outputting data to the stream, and it can also mean shift operation (it is overloaded in C + +, which will not be discussed here later)
  5. endl(end line) indicates a line break.
  6.   It is more convenient to use c + + input and output without adding data format control, such as shaping --% d, character --% c.

Note: in the early standard library, all functions were implemented in the global domain and declared in the header file with. h suffix. When used, it only needs to include the corresponding header file. Later, it is now in the std namespace. In order to distinguish it from the C header file and correctly use the namespace, it is stipulated that the C + + header file does not contain. h; The old compiler (vc 6.0) also supports < iostream. h > format, which is no longer supported by subsequent compilers. Therefore, the method of < iostream > + std is recommended.

3. Default parameters

3.1 concept

The default parameter is to specify a default value for the parameter of the function when the function is declared or defined.

When calling the function, if no argument is specified, the default value is adopted; otherwise, the specified argument is used.
For example:

void TestFunc(int a = 0)//The default parameter is 0
{
    cout<<a<<endl;
}
int main()
{
    TestFunc(); // When no parameter is passed, the default value of the parameter is used
    TestFunc(10); // When passing parameters, use the specified arguments
}

Compilation result:

3.2 default parameter classification

  • All default parameters
// All parameters in the function specify default values
void TestFunc(int a = 10, int b = 20, int c = 30)
{
    cout<<"a = "<<a<<endl;
    cout<<"b = "<<b<<endl;
    cout<<"c = "<<c<<endl;
}
  • Semi default parameter
//Some parameters in the function specify default values
void TestFunc(int a, int b = 10, int c = 20)
{
    cout<<"a = "<<a<<endl;
    cout<<"b = "<<b<<endl;
    cout<<"c = "<<c<<endl;
}

be careful:

  1.   Semi default parameters must be given from right to left, and cannot be given at intervals
  2.   Default parameters cannot appear in both function declarations and definitions
  3. The default value must be a constant or a global variable
  4. C language not supported (compiler not supported)

//The following two are wrong!
void TestFunc(int a = 10, int b, int c)
void TestFunc(int a = 10, int b, int c = 30)

//a. H (Declaration)
void TestFunc(int a = 10);
// a. C (definition)
void TestFunc(int a = 20)
{}
// Note: if the life and the defined location appear at the same time, and the values provided by the two locations happen to be different, the compiler cannot determine which to use
 There are two default values.

4. Function overloading

4.1 concept

Function overloading: it is a special case of functions. C + + allows to declare several functions with the same name with similar functions in the same scope. The formal parameter list (number or type or order of parameters) of these functions with the same name must be different. It is commonly used to deal with the problems of similar functions and different data types.

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

double Add(double a, double b)
{
    return a + b;
}

long Add(long a, long b)
{
    return a + b;
}

int main()
{
    Add(10, 20);
    Add(10.0, 20.0);
    Add(10L, 20L);
    return 0;
}
//The functions of the above functions are the same, the function names are the same, and the parameter types are different.
//Of course, the overloaded function here can also have different number of parameters and different order of parameters

Are the following two functions overloaded?

short Add(short a, short b)
{
    return a + b;
}
int Add(short a, short b)
{
    return a + b;
}

// Note: the above two functions do not belong to function overloading. The return value is not the standard for defining function overloading, and cannot be distinguished when calling
// 1. The default value is different and cannot constitute an overload
void f(int a)
{
	cout << "f()" << endl;
}

void f(int a = 0)
{
	cout << "f(int a)" << endl;
}

// 2. Constitute an overload, but there will be problems when using: f()// Ambiguous call
void f()
{
	cout << "f()" << endl;
}

void f(int a = 0)
{
	cout << "f(int a)" << endl;
}

4.2 name mangling

Why does C + + support function overloading and C language does not support function overloading?
In C/C + +, a program needs to go through the following stages: preprocessing, compilation, assembly and linking.

1. in fact, our project is usually made up of multiple header files and multiple source files, and we can know that through the compilation link in our C language stage, when the Add function defined in b.cpp is called in the current a.cpp, there is no Add function address in the a.o's target file before the compiled link, because Add is defined in b.cpp, so Add's address is in b.o. So what?
2. Therefore, the link stage is dedicated to this problem. When the linker sees that a.o calls Add, but there is no address of Add, it will find the address of Add in the symbol table of b.o and link it together.
3. When linking, which name will the connector use to find the Add function? Here, each compiler has its own function name modification rules.
4. Because the modification rules of vs under Windows are too complex, and the modification rules of gcc under Linux are simple and easy to understand, we use gcc to demonstrate the modified name.
5. We can see from the following that the name of gcc function remains unchanged after modification. After modification, the function of g + + becomes [_Z + function length + function name + type initial].

The results are compiled with C language compiler
 

  Conclusion: under linux, the modification of function name has not changed after gcc compilation
 

Results compiled with C + + compiler

Conclusion: under linux, after g + + compilation, the modification of function name changes, and the compiler changes the type information of function parameters
Add to modified name
 

The name modification rules under Windows are complex and will not be explained here.

6. It is understood here that C language cannot support overloading because functions with the same name cannot be distinguished. C + + is distinguished by function modification rules. As long as the parameters are different, the modified names are different, and overloading is supported.
7. In addition, we also understand why function overloading requires different parameters! It has nothing to do with the return value.

5.extern "C"
 

Sometimes in C + + projects, it may be necessary to compile some functions in the style of C. add extern "C" before the function, which means to tell the compiler to compile the function according to the rules of C language (such as the modification rules of function name).

extern "C" int Add(int a, int b);
int main()
{
    Add(1,2);
    return 0;
}

6. Reference

6.1 concept

Instead of defining a new variable, a reference gives an alias to an existing variable. The compiler will not open up memory space for the referenced variable. It shares the same memory space with the variable it references.

Type & reference variable name (object name) = reference entity
 

void TestRef()
{
    int a = 10;
    int& ra = a;//< = = = = define reference type
    printf("%p\n", &a);
    printf("%p\n", &ra);
}

  Note: the reference type must be of the same type as the reference entity

It can be seen from the results above that ra and a share a space.

  6.2 reference characteristics
 

1. Reference must be initialized when defining
2. A variable can have multiple references
3. Reference once an entity is referenced, other entities cannot be referenced

void TestRef()
{
    int a = 10;
    // int& ra; //  An error will occur when compiling this statement
    int& ra = a;// A variable can have multiple references
    int& rra = a;
    printf("%p %p %p\n", &a, &ra, &rra);
}

6.3 frequently cited

When quoting a constant, you need to add const before the type.

If implicit type conversion exists during reference, const should also be added before the type, and the reference is not the variable itself, but the temporary space during implicit type conversion.

void TestConstRef()
{
    const int a = 10;
    //int& ra = a; //  An error will occur when compiling this statement. A is a constant
    const int& ra = a;

    // int& b = 10; //  An error will occur when compiling this statement. B is a constant
    const int& b = 10;

    double d = 12.34;
    //int& rd = d; //  There will be errors when compiling this statement. The types are different
    const int& rd = d;
}

 

6.4 usage scenarios

1. Make parameters

void Swap(int& a, int& b)//Because references use the original space of variables, they can be exchanged directly
{
    int temp = a;
    a = b;
    b = temp;
}

2. Return value
Since there is an address in the stack and it is returned to the operating system after the function is completed, the address in the stack cannot be used when the reference is used as the return value.

int& Add(int a, int b)
{
    int c = a + b;
    return c;
}
int main()
{
    int& ret = Add(1, 2);
    Add(3, 4);
    cout << "Add(1, 2) is :"<< ret <<endl;
    return 0;
}

Here, c is a local variable, the reference is used as the return value, the return is the alias of c, and c has been returned to the operating system.

  The result here is:

Friends who know the function stack frame should know that after calling the Add function for the first time, the c space is released, but this space still exists in the stack, and the value (3) in it has not been modified;

After the second call to Add, the c space will be opened up at the same address. At this time, the value in the c space will be overwritten by the result obtained the second time. Therefore, when outputting the value in this space, you will get   7.

int& Count()
{
    static int n = 0;// After adding static, the variable is stored in the static area, and the space will not be released after the function returns
    n++;
    // ...
    return n;
}

Note: if the function scope is given when the function returns, you can use reference return if the returned object has not been returned to the system. If it has been returned to the system, you must use value passing return.
 

six point five   The difference between reference and pointer

Syntactically, a reference is an alias. There is no independent space, and it shares the same space with its reference entity.

There is actually space in the underlying implementation, because references are implemented in the form of pointers.

int main()
{
	int a = 10;
	int& ra = a;
	ra = 20;
	int* pa = &a;
	*pa = 20;
	return 0;
}

  Assembly code comparison:

Differences between references and pointers:

  1.   References must be initialized when defined, and pointers are not required
  2.   After a reference references an entity during initialization, it can no longer reference other entities, and the pointer can point to any entity of the same type at any time
  3.   There is no NULL reference, but there is a NULL pointer
  4.   Different meanings in sizeof: the reference result is the size of the reference type, but the pointer is always the number of bytes in the address space (4 bytes in 32-bit platform)
  5.   Reference self addition means that the referenced entity increases by 1, and pointer self addition means that the pointer is offset by one type backward
  6.   There are multi-level pointers, but there are no multi-level references
  7. The access to entities is different. The pointer needs to be explicitly dereferenced, and the reference compiler handles it by itself
  8.   References are relatively safer to use than pointers

7. Inline function

7.1 concept

The function decorated with inline is called inline function. During compilation, the C + + compiler will expand where the inline function is called, without the overhead of function stack. The inline function improves the efficiency of program operation.

If it is not an inline function, there will be an operation to call the function:

If you add the inline keyword before the above function and change it to an inline function, the compiler will replace the function call with the function body during compilation. There is no function stack overhead.

7.2 characteristics

1. inline is a method of exchanging space for time, which saves the cost of calling functions. Therefore, functions with long code or loop / recursion are not suitable for inline functions.
2. inline is only a suggestion for the compiler. The compiler will optimize automatically. If there are loops / recursions in the function defined as inline, the compiler will ignore the inline during optimization.
3. inline does not recommend the separation of declaration and definition, which will lead to link errors. Because the inline is expanded, there will be no function address, and the link will not be found.

4. Frequently used small functions can be defined as inline functions

For example:

// F.h
#include <iostream>
using namespace std;
inline void f(int i);

// F.cpp
#include "F.h"
void f(int i)
{
    cout << i << endl;
}

// main.cpp
#include "F.h"
int main()
{
    f(10);
    return 0;
}

In the above code, the declaration and definition of the f function in the main function are separated. After the F.h expansion, the f function will also be expanded directly, while there are only declarations in F.h. if the address of the f function cannot be found during the link, the program will have a link error.

8. auto keyword (C++11)

8.1 introduction to auto

In the early C/C + +, the meaning of auto is that the variable modified by auto is a local variable with automatic memory, but unfortunately, no one has used it.

In C++11, the standards committee gives a new meaning to auto, that is, auto is no longer a storage type indicator, but as a new type indicator to indicate the compiler. The variables declared by auto must be derived by the compiler at compile time.
 

int TestAuto()
{
    return 10;
}
int main()
{
    int a = 10;
    auto b = a;//auto automatically identifies the type of a
    auto c = 'a';
    auto d = TestAuto();
    cout << typeid(b).name() << endl;//In C + +, tyoeid().name can output variable types
    cout << typeid(c).name() << endl;
    cout << typeid(d).name() << endl;
    //auto e;  Unable to compile, variables must be initialized when using auto to define them
    return 0;
}

[note]
When using auto to define variables, it must be initialized. In the compilation stage, the compiler needs to deduce the actual type of auto according to the initialization expression. Therefore, auto is not a "type" declaration, but a "placeholder" for type declaration. The compiler will replace Auto with the actual type of the variable at compile time.

 

8.2 use of Auto

1. auto is used in combination with pointer and reference
When declaring a pointer type with auto, there is no difference between auto and auto *, but when declaring a reference type with auto, you must add&  

int main()
{
    int x = 10;
    auto a = &x;
    auto* b = &x;
    auto& c = x;
    cout << typeid(a).name() << endl;
    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;
    *a = 20;
    *b = 30;
    c = 40;
    return 0;
}

2. Define multiple variables on the same line
When multiple variables are declared on the same line, these variables must be of the same type, otherwise the compiler will report an error, because the compiler actually deduces only the first type, and then uses the derived type to define other variables.

void TestAuto()
{
    auto a = 1, b = 2;
    auto c = 3, d = 4.0; // This line of code will fail to compile because the initialization expression types of c and d are different
}

 

8.3 scenarios that cannot be deduced by auto

1. auto cannot be used as a parameter of a function

// The code here fails to compile. auto cannot be used as a formal parameter type because the compiler cannot deduce the actual type of A
void TestAuto(auto a)
{}

2. auto cannot be directly used to declare arrays

void TestAuto()
{
    int a[] = {1,2,3};
    auto b[] = {4,5,6};
}

3. In order to avoid confusion with auto in C++98, C++11 only retains the use of auto as a type indicator
4. The most common advantage of auto in practice is to use it with the new for loop provided by C++11 and lambda expression.


9. Range based for loop (C++11)

9.1 syntax of scope for

In C++98, if you want to traverse an array, you can do it as follows:

void TestFor()
{
    int array[] = { 1, 2, 3, 4, 5 };
    for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
    array[i] *= 2;
    for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
    cout << *p << endl;
}

For a set with a range, it is superfluous for programmers to explain the range of loops, and sometimes they are easy to make mistakes.

Therefore, a range based for loop is introduced in C++11.

The parentheses after the for loop are divided into two parts by the colon ":": the first part is the variables used for iteration in the range, and the second part represents the range to be iterated.
 

void TestFor()
{
    int array[] = { 1, 2, 3, 4, 5 };
    for(auto& e : array)
    e *= 2;
    for(auto e : array)
    cout << e << " ";
    return 0;
}

Note: similar to ordinary loops, you can use continue to end this loop or break to jump out of the whole loop.
 

9.2 scope and conditions of use
 

1. The scope of for loop iteration must be determined

For an array, it is the range of the first element and the last element in the array; For classes, you should provide methods of begin and end, which are the scope of the for loop iteration.
Note: the following code is problematic because the scope of for is uncertain

void TestFor(int array[])
{
    for(auto& e : array)
    cout<< e <<endl;
}

2. The iterated object should implement the operations of + + and = =.
 

10. Pointer null nullptr(C++11)

In good C/C + + programming habits, when declaring a variable, it is best to give the variable an appropriate initial value, otherwise unexpected errors may occur, such as uninitialized pointers.

If a pointer does not have a legal point, we basically initialize it as follows:
 

void TestPtr()
{
    int* p1 = NULL;
    int* p2 = 0;
    // ......
}

NULL is actually a macro. In the traditional C header file (stddef.h), you can see the following code:

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

As you can see, NULL may be defined as a literal constant 0 or as a constant of a typeless pointer (void *). No matter what definition is adopted, some troubles will inevitably be encountered when using NULL pointers, 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 results are as follows:

 

 

The original intention of the program is to call the pointer version of 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. However, the compiler regards it as an integer constant by default. If it is to be used as a pointer, it must be forcibly converted (void *)0.

be careful:
1. When nullptr is used to represent pointer null value, it is not necessary to include header file, because nullptr is introduced by C++11 as a new keyword.
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 pointer null values later.

Tags: C++

Posted on Sun, 28 Nov 2021 21:38:01 -0500 by rigbyae