Robot operating system ROS: from entry to abandonment (4) C++ classes, namespaces, templates, introduction to CMakeLists (I recommend this article, and explain it in detail!!!)

Namespaces, templates are all mentioned, these are what I have recently realized, in the Prometheus code.

Reprinted from: https://www.jianshu.com/p/79bc4ff1a8f8?utm_source=desktop&utm_medium=timeline

Robot operating system ROS: from entry to abandonment (4) C++ class,Namespace , template, CMakeLists introduction

Chen Guagua_ARPG

Robot operating system ROS: from entry to abandonment (4) C++ classes, namespaces , templates, CMakeLists introduction

Because the next lecture will talk about how to pub and sub messages in the class. Then consider that some students are not very familiar with classes. Let's review it a bit. But there are actually a lot of things about the online search, and they all start from the beginning. So I will definitely not repeat those contents here. The few things to introduce are actually quite complicated to use well. We will only touch on the skin. We will focus on comparing with the previous code to understand why many of the syntaxes we talked about in the previous three can be written like that.
For example, the object msg of geometry_msgs::PoseStamped in the previous lecture contains member variables header and pose, and heaer contains member variables stamp, etc. Why can we use msg.header.stampthis syntax to get variables of type time?
Another example is how the syntax of std_msgs::Int8 comes from, what does the middle :: mean, and what is the difference between std_msgs and In8 before and after it.
For example, when we define ROS publisher

ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);

Why use <std_msgs::String>this syntax to define the object to be sent?
These three examples involve classes, namespaces and templates respectively. Students who are familiar with the grammar or don't want to know the reason can skip this chapter and go directly to the next chapter on the pub and sub messages in the class.

This lecture assumes that everyone has mastered the most basic C++ things such as functions, parameters, and loops. If these are not clear, it is really not suitable to operate ROS with C++ haha.

Class

I won’t elaborate on the functions and meanings of the same classes. If you grab a lot of them on the Internet, you can search for their basic meanings on the Internet. Simply put, after defining the class, we can create its objects. Direct manipulation of classes and their objects is one of the most important things in C++. Start the example directly. Open a terminal and enter the following

mkdir ~/C++Test
cd C++Test
mkdir classTest
mkdir namespaceTest
mkdir templateTest

Let's create a folder called C++Test, and then create three subfolders for testing three things. After that, create a classBasic.cppfile called and a file called in the classTest folder CMakeLists.txt. Enter the following in classBasic.cpp.

#include <iostream>

class poorPhd{
public:
    /*define constructor*/
    poorPhd(){
        std::cout<<"we create a poor phd class"<<std::endl;
    }

    /*public member variable*/
    int hairNumber = 100;

    /*public member function*/
    int getGirlFriendNumber(){
        return girlFriendNumber;
    }

private:
    /*private member variable*/
    int girlFriendNumber = 0;
};

int main(){
    /*define the object*/
    poorPhd phd;//will use constructor function 
 
    /*call the public memberfunction*/
    std::cout<<"girlFriendnNumber is "<<phd.getGirlFriendNumber()<<std::endl;

    /*change tha value of member variale*/
    phd.hairNumber = 101;

    /*call the member variable*/
    std::cout<<"hairNumber is "<<phd.hairNumber<<std::endl;

    /*define class pointer*/
    poorPhd *phdPointer;

    /*assign the pointer to an object*/
    phdPointer = &phd;

    /*call the member variable*/
    std::cout<<"use pointer, hair number is "<<phdPointer->hairNumber<<std::endl;
}

Explain line by line.
1:#include<> Include header files, so you can use std::cout<<...std::endl;

2: class poorPhd defines a class called poorPhd. The class is followed by the parentheses {}. The content in the parentheses is the content of the class.

3: The content after public plus the colon is 公有. The functions defined in the public scope are public member functions, and the variables are public member variables.

4:poorPhd(). This function is called 构造函数(constructor function). It will be called automatically when the class is created. The name of the constructor and the name of the class must be the same and have no return value.

5: int hairNumber = 100. A public member variable of type int is defined and assigned a value of 100.

6: int getGirlFriendNumber(). Defines a function with a return value of int, which returns the private member variable girlFriendNumber.

7: The content after private plus the colon is 私有. The functions defined in the private scope are private member functions, and the variables are private member variables.

8: int girlFriendNumnber=0. A private member variable of type int is defined girlFriendNumberand assigned a value of 0


9: poorPhd phd in the main function creates an object of the class called phd. For each class to be actually used, an object needs to be created. The object will have everything we defined in the class before. The so-called possession means that they can be called. There is no limit to the number of objects, and there will be no interference between them. You can also use a similar method to create an abcobject with a name plus , it will also have all the things of the poorPhd class.
When the object is created, it will automatically call the constructor.

10: The method for std::cout....phd.getGirlFriendNumber()<<std::endl;
class objects to call member functions or member variables is 对象名.成员. Public members can be directly called in this way outside the definition of the class, and private members cannot be called directly . So if we use phd.girlFriendNumberit, an error will be reported. Because outside the class, you cannot directly call private member variables. What if we still want to see or modify private member variables sometimes? Then we can write gerGirlFrienda public member function similar to this . The public member function is defined in the class, so it can use private member variables and take the value of the variable as the return value, so that we can get the value of the private member variable.
Why divide into private and public? Sometimes we write a class and don't want everything in it to be used by users. For example, we have car-making related technologies. All these technologies together form a class. The concrete realization is that we built a car. Each car is called an object. Every car has the same content, but they do not interfere with each other. We just want users to understand things like brakes, accelerators and so on. Do not want users to understand the internal structure of the car. The brake accelerator is public here, and the internal structure of the car is private. If the user really wants to get the internal structure, the user can go to the auto sales shop to learn some relevant information. The sales shop is equivalent to the get...function interface we wrote , building a bridge of friendship between the user and the private members of the class. Of course, if some content is particularly private and we don't want users to know its related information, we just don't write that get...function.

  1. phd.hairNumber = 101;
    Assign values ​​to public member variables 101.

12. std::cout<<...phd.hairNumber...
Call the public member and print it out.

13. poorPhd *phdPointerCreate a pointer to a class. The constructor is not called when the pointer of the class is created. It needs to point to an object.

14. phdPointer = &phd Assign the address of the object just created to the pointer, this pointer has phdall the contents of the object.

  1. ...phdPointer->hairNumber... The only difference in calling a member of a class by a class pointer is the use of 指针名->成员invocation instead of 对象名.成员invocation.

The connection with the previously written ROS code : As we defined before std_msgs::Int8 msg, msg is an object of class Int8. We know that Int8 contains the member variable data of type int8 by checking roswiki http://docs.ros.org/api/std_msgs/html/msg/Int8.html , so we msg.datause this member.

After writing the file, exit and save, and open the CMakeLists.txt file created before. Enter the following.

project(class_test)

cmake_minimum_required(VERSION 2.8)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAG} -std=c++11 -Wall")

add_executable(classBasic classBasic.cpp)

This is basically the simplest CMakeLists.txt file. CMakeLists.txt is used to compile C++ files.
The first line indicates the name of the project.
Enter the minimum version number used by CMake on the second line, usually 2.8 and above.
The third line sets the compiler. Use c++11. Although our project did not use C++11, considering that C++ has been widely used today, it is best to add it. we are atThe content commented in the CMakeLists of ROSadd_compile_options(-std=c++11) also achieves the effect of compiling with c++11.
The fourth line specifies the file to be compiled. The file to be compiled is classBasic.cpp, and the name of the compiled executable file is classBasic.
After writing the above content, save and exit.
In the terminal, cd to classTestthis folder and enter the following content

mkdir build
cd build
cmake ..
make

The first and second lines of commands create a folder called build and enter the
third line of commands to use the cmake command and ..use the CMakeLists.txt that indicates the use of the previous folder. After executing this command, the CMakeLists we wrote will generate a series of files in the build, one of which is Makefile. Others are not introduced here. The
fourth line of command is to use makefile. The role of makefile is to directly compile the files you have set in CMakeLists.
It is not necessary to create a build folder, but it is recommended, because you see a series of files generated in the compiled CMakeLists.txt in the build. It will be more convenient for you to delete or modify them later, so that they will not be mixed with other files.
After executing the above command, you will see an classBasicadditional file called without a suffix. This is our executable binary file. Using ./classBasicthe obtained perform the following output

we create a poor phd class
girlFriendnNumber is 0
hairNumber is 101
use pointer, hair number is 101

Please refer to the source code line by line to see why the output is like this.

In the previous chapter, we said that there is a passage like msgs_header.stamp calls stamp, stamp.sec calls sec to get the epoch time, then msgs_header.stamp.sec can get the current time, in seconds . Before writing a paragraph, we created the Header object msg_header, and learned from the ros wiki that the object contains the data member stamp, which contains the data member sec, and then we can use this msg_header.stamp.secto call the sec data member. How does this seeming continuity of data actually be achieved?
Let's create a new file called classBasic2.cpp under the classTest folder created earlier. And enter the following content.

#include <iostream>

class poorPhd{
public:
    /*define constructor*/
    poorPhd(){
        std::cout<<"we create a poor phd class"<<std::endl;
    }

    /*public member variable*/
    int hairNumber = 100;

    /*public member function*/
    int getGirlFriendNumber(){
        return girlFriendNumber;
    }

private:
    /*private member variable*/
    int girlFriendNumber = 0;
};

class master1 {
public:
    /*define constructor*/
    master1(){
        std::cout<<"we create a master class"<<std::endl;
    }
    /*member variable*/
    poorPhd future;
};


int main(){
    /*define the object*/
    master1 mStudent1;

    /*use inheritance*/
    std::cout<<"hairNumber of master student 1 is "<<mStudent1.future.hairNumber<<std::endl;
}

poorPhdThe class is exactly the same as the previous file, we have added a new class called master1. master1 also has a constructor. In addition, it has a member variable, which is an object future of type poorPhd. Then in the main function, the object mStudent1 of master1 is defined. We can use mStudent1.future to call the variable future, and since future is a variable of type poorPhd, we can call hairNumber with future.hairNumber. Linked together, you can define the object of msater1 but finally call the member variable of poorPhd.
After saving and exiting, add the following content in CMakeLists.txt.

add_executable(classBasic2 classBasic2.cpp)

Enter the classTest/build file and input in the terminal

cmake ..
make

At this time, there is an additional binary file classBasic2, execute the binary file and you will see

we create a poor phd class
we create a master class
hairNumber of master student 1 is 100

It can be seen from this input that when creating the master1 object mStudent1, C++ will first initialize its member variables, so what we get first is create a poor phd class, and then call the constructor.
There are a lot of content in the class, it's up to you to learn it yourself. Here we just briefly introduce the part that is connected with the previous code.

Namespace (namespace)

You must have used namespaces . Basically everyone who writes C++ will use using namespace stdthis statement. This statement represents the use of the namespace std. The effect achieved is, for example, if you want to use a coutstatement to print something on the screen, if there is no std, what you need to enter is

std::cout<<"....."<<std::endl;

If you use the using namespace std statement, then you only need the following content print statement

cout<<"...."<<endl;

But if you have not written a large program, you may not have the opportunity to write a namespace yourself . Namespaces are generally used to avoid renaming. There are generally many classes and countless functions defined in large libraries. There may be functions or even classes between different large librariesThe naming of duplicates will cause a lot of trouble. The naming syntax of
namespace is also very simple

namespace name{
    //内容
}

The following program simply shows a simple program that defines classes with the same name in two namespaces and uses the two classes respectively.

#include <iostream>

/*define a phd namespace*/
namespace phd {

    /*define a student class in phd namespace*/
    class student{
    public:
        student(){
            std::cout<<"create a student class in phd namespace"<<std::endl;
        }
        int graduateYear = 5;
        int hairNumber   = 100;
    };
}

/*define a master namespace*/
namespace master{

    /*define a student class in master namespace*/
    class student{
    public:
        student(){
            std::cout<<"create a student class in master namespace"<<std::endl;
        }
        int graduateYear = 2;
        int hairNumber   = 10000;
    };
}

int main(){

    /*create an object of student class, in phd namespace*/
    phd::student     phdStudent;

    /*create an object of student class, in master namespace*/
    master::student  masterStudent;

    std::cout<<"phd normally graduate in "<<phdStudent.graduateYear<<" years"<<std::endl;

    std::cout<<"master normally graduate in "<<masterStudent.graduateYear<<" years"<<std::endl;
}

The above program defines two namespaces , one is called phdand the other is called master. These two namespaces have a class, and the class name is both called student.
The way to define objects of classes in the namespace is 命名空间名::类名 对象名. ::It is called the scope symbol (scope resolution operator). In the main function, we define the object phdStudent of the student class under the phd namespace and the object masterStudenrt of the student class under the master namespace. The next two lines each output member variables graduateYear.
In our previous ros program, we encountered two namespaces , one is std_msgs, the other is geometry_msgs. Int8, Float64Are all std_msgs this namespace under the category, PoseStampedand so is geometry_msgs this namespace under the category.
Going back to the above program, we can use it after defining the phd namespaceusing namespace phd , so we can phd::define a student class object under phd without using it in the main function student phdStudent. Similarly, if we add using namespace master, we can also directly use student masterStudentto define msaterThe object of the student class under the namespace .
But if you add in the program at the same time

using namespace phd;
using namespace master;

At this time, if you write in the main function, student object_nameyou will definitely get an error. Because the computer cannot know which namespace the student class you want to use belongs to . So generally for the convenience of the diagram, when we are sure that no class name will be repeated, we add using namespace ...this line after defining the header file, so that we can save the need to use namespace_name::类名this format naming when defining the class . But sometimes if two libraries are likely to have the same class name, don't use it using namespace ..., otherwise it is likely to cause misunderstanding of the program.

After writing the above program, the steps are exactly the same as the process of writing classBasic.cpp, creating CMakeLists.txt and a build folder to compile.

Some readers may ask, what if the names of the namespace are repeated? Just delete one of the programs and put == ....
Similarly, some of the namespace is academic, and interested students can study it by themselves.

Template

If you are a user of C++, you must have been exposed to the template. Why do you say that? When you define a std::vector, you have already used the template. But you may not have written a template yourself (this situation seems a bit similar to namespace).
The template is developed in order to avoid re-defining functions of the same function.
For example, you are now implementing the function of squaring a number. Very simple, similar to the following

#include <iostream>

int square(int a){
    return a*a;
}

int main(){
   double x = 5.3;
   std::cout<<"the square of "<<x <<" is "<<square(x)<<std::endl;
}

This program has an obvious shortcoming. When writing a function or using a variable, you must specify the type first. Since the parameter type and return value of the C++ function have been specified as the int type, you can only pass in the int type, if you pass the double type Enter the variable, the variable will be truncated to int type by coercion. And can only return integer variables. So you can only get 25.
The basic solution is to overload the function, that is, I can name the same function but the variable type or number is different to realize the processing of different inputs. Similar to the following

#include <iostream>

int square(int a){
    return a*a;
}

double suqare(double a ){
    return a*a;
}

int main(){
   double x = 5.3;
   std::cout<<"the square of "<<x <<" is "<<square(x)<<std::endl;
}

In this way, when square(x) is called, functions with the same formal parameters will be automatically matched. We can get the square of 5.3. But it is conceivable that if I have many different types of variables to pass in, I have to write many different functions that are exactly the same except for the variable types! Is there a way to use all types of formal parameters?
The template came into being. The way the template is defined is

template <typename T>

or

template <class T>

The function or class to be implemented is followed immediately after the definition. This class is not the kind of class we understood before. The role of class and typename here are exactly the same, meaning that a new type T is defined. I don't know what this new type is. We have to wait for us to use it. The program will judge by itself based on the type passed in.
Let's first upload the code to achieve the same function as the number square.

#include <iostream>

template <typename T>
T square(T a){
    return a*a;
}

int main(){
    double x = 5.3;
    std::cout<<"square of "<<x<<" is "<<square(x)<<std::endl;
}

Now no matter what type of data you pass in, you will get its square. The function parameter and return value type specified by sqaure are both T. It can be understood that when we pass in a variable of type double, T will automatically become double, and when we pass in int, T will automatically become int.
Here is a slightly more complicated example. Realize the addition of two vectors (it doesn't seem to be too complicated...). Vectors cannot be added directly in C++. When we define a vector, we must specify the type of vector element. For example std::vector<int> a, std::vector<double> betc. As in the previous example, in order to avoid passing in overloaded functions, we use templates. code show as below

#include <iostream>
#include <vector>

template <typename T, typename U>
U addVector(T vec1, U vec2){
    
    U result;

    if(vec1.size()!=vec2.size()){
        std::cout<<"cannot add two vector, they must be the same length. Return a null vector"<<std::endl;
        return result;
    }

    for(int i = 0; i<vec1.size(); i++){
        result.push_back(vec1[i]+vec2[i]);
    }
    return result;
}

int main(){
    std::vector<int> vec1 = {1,2,3};
    std::vector<double> vec2 = {4.0,5.0,6.0};

    auto addVec = addVector(vec1,vec2);

    for(auto i:addVec)
        std::cout<<i<<",";

    std::cout<<std::endl;
}

Our tempalte defines two types, one is called U and the other is called T. Why define two? Because we said that the specific type defined by the template is determined when it is used, in the main function we need to add two vectors, one of which is of type int, and pass in addVector as the first parameter, then T will be std::vector<int>, and the second The second parameter is a vector of type double. U will be equivalent to U after being passed into the function as the second parameter std::vector<double>, and the return type of the
function is also U. autoThis keyword is used in the third line of the main function of the program . Only compile with c++11 can use auto. This is a very useful keyword. auto will automatically assign the type of the object defined by it, according to the type of the assigned variable. What addVector returns is U, which is std::vector<double> in this program. Then auto will automatically call addVec as a vector of type dpuble.
The for loop in the fourth line of the main function uses a form different from our usual for loop.

for(auto i:addVec)

Which i:addVecrole is to addVec the elements sequentially assigned to i, which requires the same type as was addVec and the elements of the i, but there is help auto, we also do not care so much, the type definition of i If it is auto, then the program will automatically make i the type of the element to be assigned to i in addVec, here it is double.
Having said so much, we have not yet reached what we originally wanted to talk about, that is, how does this type of grammar similar to that defined when std::vector<int>we use rosadvertise<std_msgs::String> come from? First of all, according to the study of namespaces , we know that std must be a name representing a namespace , vector is a class, and <int> comes from a template. If we use a template to define a class, similar content will appear. Let's use the simple square function as an example. Let's create a simple sqaure class.

#include <iostream>

template <typename T>
class square{
public:
    T a;
    /*constructor function will store _a*_a as public member a*/
    square(T _a){
        a = _a*_a;
    }
};


int main(){
    double x = 5.5;
    square<double> test(x);
    std::cout<<"the square of "<<x<<" is "<<test.a<<std::endl;
}

Immediately after declaring the template, we declare a class whose public member function is a value of type T a. In the main function, when we declare the object of the class defined under the template, we need <>to indicate the type of T in it. Only after this can the object be defined. That is, the definition format of the ordinary class object is as follows

类名 对象名(构造函数参数)

The format of the object definition of the class under the template is

类名<模版变量类型> 对象名(构造函数参数)

This definition method in the second line of the main function is similar to our std::vector<int> ABCdefinition method, the latter is mostly defined in the namespace of the template. Then define the class under the template.

to sum up

In this talk, we roughly involved several simple and complex systems, classes, namespaces and templates in C++, which have appeared more or less in the grammar we usually use. It's just that we don't often define it ourselves. Learning to use them is very helpful for building a complex code system. We also introduced the contents of the most brief CMakeLists that need to be included. Let's return to ROS in the next lecture. On the basis of this lecture, explain how to publish/receive messages in the ros class

Guess you like

Origin blog.csdn.net/sinat_16643223/article/details/114518179
Recommended