Getting Started with C++ - 1 (Getting started with C++ quickly in a few minutes)

C++ is based on C, incorporating object-oriented programming ideas, and adding many useful libraries, programming paradigms, etc. After becoming familiar with C language, it will be helpful to learn C++. The main goals of this article are:

1. Supplement the shortcomings of C language grammar, and how C++ optimizes the unreasonable design of C language, such as: scope, IO, functions, pointers, macros, etc.
2. Lay the foundation for subsequent class and object learning.

1. C++ keywords (C++98)

For a detailed introduction, click here: C++ keywords provide a complete introduction to
32 keywords in C language, and a total of 63 keywords in C++:

Insert image description here

Note: C++ is compatible with C language. Keywords and syntax that can be used in C language can also be used in C++.

2. Namespace

In C/C++, there are a large number of variables, functions and classes. The names of these variables, functions and classes will all exist in the global scope, which may cause many conflicts. The purpose of using namespace is to localize the name of the identifier to avoid naming conflicts or name pollution. The namespace keyword appears to address this problem.
C language example is as follows:

#include <stdio.h>
#include <stdlib.h>
int rand = 10;
// C语言没办法解决类似这样的命名冲突问题,所以C++提出了namespace来解决
int main()
{
    
    
printf("%d\n", rand);
return 0;
}
// 编译后后报错:error C2365: “rand”: 重定义;以前的定义是“函数”

2.1 Namespace definition

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

1. Normal namespace definition.
Variables/functions/types can be defined in the namespace.

namespace test
{
    
    
 // 命名空间中可以定义变量/函数/类型
 int rand = 10;
 int Add(int left, int right)
 {
    
    
 return left + right;
 }
  • test is the name of the namespace. In general development, the project name is used as the namespace name.
  • What I use here is test. Veterans can practice naming it by themselves after going down.

2. Namespaces can be nested

// test.cpp
namespace N1
{
    
    
int a;
int b;
int Add(int left, int right)
 {
    
    
     return left + right;
 }
namespace N2
 {
    
    
     int c;
     int d;
     int Sub(int left, int right)
     {
    
    
         return left - right;
     }
 }
}

3. Multiple namespaces with the same name are allowed to exist in the same project, and the compiler will eventually synthesize them into the same namespace.
Key point: test.h in a project and the two N1s in test.cpp above will be merged into one

// test.h
namespace N1
{
    
    
int Mul(int left, int right)
 {
    
    
     return left * right;
 }
}

Note: A namespace defines a new scope, and everything in the namespace is limited to that namespace.

2.2 Namespace usage

How to use members in the namespace? for example:

namespace test
{
    
    
	 // 命名空间中可以定义变量/函数/类型
	 int a = 0;
	 int b = 1;
	 int Add(int left, int right)
	 {
    
    
	 return left + right;
	 }
	 
	 struct Node
	 {
    
    
		 struct Node* next;
		 int val;
	 };
}
int main()
{
    
    
 // 编译报错:error C2065: “a”: 未声明的标识符
 printf("%d\n", a);
return 0;
}

There are three ways to use namespaces:

1. Add namespace name and scope qualifier:
This method uses fully qualified names every time a member in the namespace is used, including the namespace name and member name.

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

2. Use using to introduce a member in the namespace:
This method is to use the using declaration indicator at the beginning of the code file to introduce a member in a specific namespace, so that the member name can be used directly in subsequent code.

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

3. Use the using namespace namespace name introduction:
This method is to use the namespace keyword to create an alias of the namespace. The alias can be used in subsequent code to refer to members in the namespace.

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

Note: In actual complex projects, it is best not to use the third method, because it is likely to lead to various redefinitions and renames.

3. C++ input & output

Newborn babies will greet this new world in their own unique way. When C++ came out, it was considered a new thing.

Insert image description here
So should C++ also send greetings to this wonderful world? Let’s take a look at how C++ implements greetings:

#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;
int main()
{
    
    
cout<<"Hello world!!!"<<endl;
return 0;
}

std is the namespace name of the C++ standard library. C++ puts all the definitions and implementations of the standard library into this namespace.

illustrate:

  1. When using the cout standard output object (console) and cin standard input object (keyboard), you must include the <iostream> header file and use std by namespace usage.
  2. cout and cin are global stream objects, and endl is a special C++ symbol that represents newline output. They are both included in the <iostream> header file.
  3. << is the stream insertion operator and >> is the stream extraction operator.
  4. It is more convenient to use C++ for input and output. There is no need to manually control the format like printf/scanf input and output. C++ input and output can automatically identify variable types.
    5. Note: In the early standard library, all functions were implemented in the global domain and declared in the header file with the .h suffix. When using it, you only need to include the corresponding header file. Later, it was implemented under the std namespace and became the C header. To distinguish between files and to use namespaces correctly, it is stipulated that C++ header files do not contain .h; the old compiler (vc 6.0) also supports the <iostream.h> format, but subsequent compilers no longer support it, so it is recommended to use <iostream>+ std way.

C++ input and output can automatically identify variable types:

#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;
}

Usage conventions of std namespace:

std is the namespace of the C++ standard library. How to expand std and use it more reasonably?

1. In daily practice, it is recommended to use namespace std directly, which is very convenient.
2. Using namespace std is expanded, and the standard library is fully exposed. If we define types/objects/functions with the same name as the library, there will be conflicts. This problem rarely occurs in daily practice, but when there is a lot of code and a large scale in project development, it can easily occur. Therefore, it is recommended to use it in project development. When using it like std::cout, specify the namespace + using std::cout to expand commonly used library objects/types.

4.Default parameters

4.1 Concept of default parameters

Default parameters specify a default value for the function's parameters when declaring or defining a function. When calling this function, if no actual parameter is specified, the default value of the formal parameter is used, otherwise the specified actual parameter is used.

void Func(int a = 0)
{
    
    
 cout<<a<<endl;
}
int main()
{
    
    
 Func();     // 没有传参时,使用参数的默认值
 Func(10);   // 传参时,使用指定的实参
return 0;
}

4.2 Default parameter classification

  • Full default parameters:
void Func(int a = 10, int b = 20, int c = 30)
 {
    
    
     cout<<"a = "<<a<<endl;
     cout<<"b = "<<b<<endl;
     cout<<"c = "<<c<<endl;
 }
  • Semi-default parameters:
void Func(int a, int b = 10, int c = 20)
 {
    
    
     cout<<"a = "<<a<<endl;
     cout<<"b = "<<b<<endl;
     cout<<"c = "<<c<<endl;
 }

Notice:

  1. Semi-default parameters must be given sequentially from right to left, and cannot be given at intervals.
  2. Default parameters cannot appear in both function declaration and definition. The value of the default parameter is determined in the function declaration, so there is no need to re-specify the default value when defining and calling the function.
  3. When calling a function, you can optionally provide parameters to override the default values.
  4. When a function has multiple parameters and one of the parameters has a default value, you can choose to omit a parameter as needed and only provide the following parameters.
 //a.h
  void Func(int a = 10);
  
  // a.cpp
  void Func(int a = 20)
 {
    
    }
  // 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。

To sum up:
Default parameters provide a more flexible and convenient way to call functions, which can reduce the number of parameters that need to be provided when calling functions, while retaining the default value settings for commonly used parameters.

5. Function overloading

In natural language, a word can have multiple meanings, and people can judge the true meaning of the word through the context, that is, the word is overloaded.

For example: There used to be a joke that you don’t need to watch or worry about the two state-owned sports. One is table tennis and the other is men's football. The former is "No one can win!" and the latter is "No one can win!"

5.1 Concept of function overloading

Function overloading : It is a special case of functions. C++ allows you to declare several functions of the same name with similar functions in the same scope . These functions with the same name have different formal parameter lists (number of parameters or types or type order). This is often used to deal with Implement functions similar to the problem of different data types.

1. Different parameter types

int Add(int left, int right)
{
    
    
 cout << "int Add(int left, int right)" << endl;
 return left + right;
}

double Add(double left, double right)
{
    
    
 cout << "double Add(double left, double right)" << endl;
 return left + right;
}

2. The number of parameters is different

void f()
{
    
    
 cout << "f()" << endl;
}

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

3. The order of parameter types is different

void f(int a, char b)
{
    
    
 cout << "f(int a,char b)" << endl;
}

void f(char b, int a)
{
    
    
 cout << "f(char b, int a)" << endl;
}

5.2 The principle of C++ supporting function overloading – name mangling

The principle of C++ language supporting function overloading is achieved through name modification . In C++, function names can be modified, that is, by adding a parameter type list in front of the function name to generate a function name. This function name is called a "function name with parameter list" .

Each data type will be assigned a specific identifier by the compiler to identify the type. This identifier is called the "type identifier". When the function name is modified, the compiler will add the type list of the function parameters and the type of the return value to the end of the function name to form a new function name. This process is called "name modification " .

The principle of function overloading:

When a program calls a function, the compiler will look for the corresponding function based on the function name consisting of the function name and the parameter list. If there are multiple functions with the same function name, the compiler will determine which function should be called based on the type and number of parameters. This is the implementation principle of function overloading.

Let's first look at an example of C++ name modification when using function overloading:

Under Linux, after function modification in the g++ environment, it becomes [ _Z+function length+function name+type initial letter ]

#include <iostream>
using namespace std;

void func(int x, double y)
{
    
    }
void func(double x, int y)
{
    
    }
int main()
{
    
    
	func(1, 1.1);
	func(1.1, 1);
	return 0;
}

Illustration:

Insert image description here
Insert image description here

Question: Why is there no function overloading in C language?

In C/C++, for a program to run, it needs to go through the following stages:
preprocessing, compilation, assembly, and linking.

Insert image description here

Insert image description here


  1. Actual projects usually consist of multiple header files and multiple source files. Through the compilation links learned in the C language stage, we can know that [when the Add function defined in b.cpp is called in the current a.cpp], after compilation Before linking, there is no Add function address in the ao target file, because Add is defined in b.cpp, so the Add address is in bo. So what to do?
  2. Therefore, the link stage is dedicated to dealing with this problem. When the linker sees that ao calls Add, but there is no Add address, it will find the Add address in bo's symbol table, and then link them together.
  3. So when linking, when facing the Add function, which name will the linker use to find it? Each compilation here has its own function name modification rules.
  4. Since the modification rules of vs under Windows are too complicated, and the modification rules of g++ under Linux are simple and easy to understand, below we use g++ to demonstrate the modified name.
  5. From the following we can see that the name of the gcc function does not change after modification. After modification, the function of g++ becomes [_Z+function length+function name+type first letter].
int Add(int a, int b)
{
    
    
	return a + b;
}
int func(int a, double b, int* c)
{
    
    }

int main()
{
    
    
	Add(1, 2);
	func(1, 2, NULL);
	return 0;
}

The result after compilation using C language compiler:
Insert image description here

Conclusion: Under Linux, after compilation in C language, the modification of the function name has not changed.


The result after compilation using C++ compiler:
Insert image description here

Conclusion: Under Linux, after compilation with g++ is completed, the modification of the function name changes, and the compiler adds function parameter type information to the modified name.


Therefore, the main reason why C language does not have function overloading is that its function calling method is based on matching based on function names (also called static binding), rather than matching based on function parameter types and numbers (also called dynamic binding) . In C language, a function name can only represent one function, and functions with different parameter types and numbers cannot be distinguished.

To sum up:
From here, we understand 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.

6. Quote


References can make code clearer and easier to understand because they provide a level of abstraction that hides direct manipulation of pointers. Reference is one of the very important and commonly used features in C++, which provides a simple but powerful mechanism to deal with variables and objects.


6.1 Reference concept

A reference does not define a new variable, but gives an alias to an existing variable. The compiler will not allocate memory space for the reference variable. It shares the same memory space with the variable it refers to.
For example: Lu Xun, whose pen name is "Zhou Shuren". Lu Xun and Zhou Shuren both refer to the same person. The change in Lu Xun means that Zhou Shuren has also changed.

Define reference types:

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

Example:

void TestRef()
{
    
    
    int a = 10;
    int& ra = a;//<====定义引用类型
    printf("%p\n", &a);
    printf("%p\n", &ra);
}

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

6.2 Citation characteristics

1. References must be initialized when defined.
2. A variable can have multiple references
. 3. Once a reference refers to an entity, it cannot refer to other entities.

Example:

void TestRef()
{
    
    
   int a = 10;
   // int& ra;   // 该条语句编译时会出错
   int& ra = a;
   int& rra = a;
   printf("%p %p %p\n", &a, &ra, &rra);  
}

6.3 Frequently quoted

C++ constant reference refers to a reference type modified with the const keyword when defining a variable, which is used to avoid modifying the passed variable inside the function .

Commonly cited characteristics are as follows:

1. Constant references must be initialized when declared.
2. The object pointed to by a constant reference cannot be modified.
3. A constant reference can point to a constant or a non-constant.
4. A constant reference can be passed to a function, but the function cannot modify the object pointed to by the reference.
5. Constant references can be initialized using references to non-const objects, but cannot be initialized using references to const objects.

void TestConstRef()
{
    
    
    const int a = 10;
    //int& ra = a;   // 该语句编译时会出错,a为常量
    const int& ra = a;
    // int& b = 10; // 该语句编译时会出错,b为常量
    const int& b = 10;
    double d = 12.34;
    //int& rd = d; // 该语句编译时会出错,类型不同
    const int& rd = d;
}

6.4 Usage scenarios

6.4.1 Making parameters

void Swap(int& left, int& right)
{
    
    
   int temp = left;
   left = right;
   right = temp;
}

6.4.2 Making return values

int& Count()
{
    
    
   static int n = 0;
   n++;
   // ...
   return n;
}

Let's look at a piece of code:

What is the output of the following code? Why?

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;
}

Actually there are some problems with this code. It attempts to return a reference to a local variable, which is unsafe because local variables are destroyed after the function call ends. This can lead to dangling references, accessing memory space that has already been destroyed, and possibly undefined behavior.

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

This is a function definition. The function is named Add and accepts two integer parameters a and b. Inside the function, it creates an integer variable c and assigns the result of adding a and b to c. Then it tries to return a reference to c, which is a return value of type int&. However, since c is a local variable, it will be destroyed when the function ends. (Note: Space destruction does not mean that the space no longer exists, but that the space has been reclaimed by the system)

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

This is the main function main. In the main function, it declares an integer reference variable ret and assigns the return value of Add(1, 2) to it. Since the Add function returns a reference to a local variable, the ret here will become a dangling reference, pointing to the memory that has been destroyed.
Then it calls the Add function again, passing parameters 3 and 4, but does not save the return value in any variable.
Finally, it tries to print the value of ret, but since ret is a dangling reference, accessing memory that has already been destroyed, this will lead to undefined behavior.

Take note

If the function returns out of the function scope, if the returned object is still there (it has not been returned to the system), you can use reference return. If it has been returned to the system, you must use value return.

6.5 Comparison of efficiency of passing by value and passing by reference

Use value as parameter or return value type. During parameter passing and return, the function will not directly pass the actual parameter or return the variable itself directly. Instead, it will pass the
actual parameter or return a temporary copy of the variable, so use the value as the parameter. Or the return value type, the efficiency is very low, especially when the parameter or return value type is very large, the efficiency is even lower.

6.5.1 Performance comparison of values ​​and references as function parameters:

#include <time.h>
struct A{
    
     int a[10000]; };
void TestFunc1(A a){
    
    }
void TestFunc2(A& a){
    
    }
void TestRefAndValue()
{
    
    
 A a;
 // 以值作为函数参数
 size_t begin1 = clock();
 for (size_t i = 0; i < 10000; ++i)
 TestFunc1(a);
 size_t end1 = clock();
 // 以引用作为函数参数
 size_t begin2 = clock();
 for (size_t i = 0; i < 10000; ++i)
 TestFunc2(a);
 size_t end2 = clock();
// 分别计算两个函数运行结束后的时间
 cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
 cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

operation result:
Insert image description here

6.5.2 Performance comparison of values ​​and references as return value types:

#include <time.h>
struct A{
    
     int a[10000]; };
A a;
// 值返回
A TestFunc1() {
    
     return a;}
// 引用返回
A& TestFunc2(){
    
     return a;}
void TestReturnByRefOrValue()
{
    
    
 // 以值作为函数的返回值类型
 size_t begin1 = clock();
 for (size_t i = 0; i < 100000; ++i)
 TestFunc1();
 size_t end1 = clock();
 // 以引用作为函数的返回值类型
 size_t begin2 = clock();
 for (size_t i = 0; i < 100000; ++i)
 TestFunc2();
 size_t end2 = clock();
 // 计算两个函数运算完成之后的时间
 cout << "TestFunc1 time:" << end1 - begin1 << endl;
 cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

Running results:
Insert image description here
Through comparison of the above codes, it is found that the efficiency of passing values ​​and pointers as parameters and return value types is very different:

In general:
1. The advantage of passing by value is that it is simple and does not need to consider that the value of the original variable will change, so it is easier to understand and maintain. At the same time, since the copied value is passed, the value of the original variable will not be affected, and there will be no unexpected side effects.
2. The disadvantage of passing by value is that it may cause efficiency problems when passing large objects, because the entire object needs to be copied and additional memory space will be occupied during the transfer process.
3. The advantage of passing by reference is that it can avoid the problem of copying objects and occupying additional memory. Especially when the object passed is large, using pass by reference can significantly improve the efficiency of the program. In addition, passing a reference can also modify the original variable directly, making it easier to implement some functions.
4. The disadvantage of passing by reference is that the value of the original variable may be changed, so you need to be more careful when using it to avoid unexpected side effects
. **

6.6 The difference between references and pointers

1. In terms of grammatical concept, a reference is an alias. It has no independent space and shares the same space with the entity it refers to.

int main()
{
    
    
int a = 10;
int& ra = a;
cout<<"&a = "<<&a<<endl;
cout<<"&ra = "<<&ra<<endl;
return 0;
}

2. 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;
}

The difference between references and pointers:

  1. A reference conceptually defines an alias for a variable, and a pointer stores a variable address.
  2. References must be initialized when defined, pointers are not required
  3. After a reference refers to an entity during initialization, it can no longer refer to other entities, and a pointer can point to any entity of the same type at any time.
  4. No NULL reference, but NULL pointer
  5. The meaning in sizeof is different: 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 on a 32-bit platform)
  6. Reference self-increment means that the referenced entity is increased by 1, and pointer self-increment means that the pointer is offset backward by the size of the type.
  7. There are multi-level pointers but no multi-level references
  8. The way to access entities is different. The pointer needs to be explicitly dereferenced, and the reference compiler handles it by itself.
  9. References are relatively safer to use than pointers

In general:
C++ references provide a more flexible and efficient variable aliasing mechanism, which can simplify code, improve performance, and play an important role in scenarios such as function transfer, return values, containers, and iterators. The use of references makes code clearer, readable, and maintainable.

Guess you like

Origin blog.csdn.net/originalHSL/article/details/131763182