热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

机器人操作系统ROS:从入门到放弃(四)C++类,命名空间,模版,CMakeLists介绍(这篇我很推荐,讲得很到位详细!!!)

转载自:https:www.jianshu.comp79bc4ff1a8f8?utm_sourcedesktop&utm_mediumtimeline机器人操作系统ROS:

命名空间,模板都讲到了,这些是我最近意识到的,在普罗米修斯代码里。

转载自:https://www.jianshu.com/p/79bc4ff1a8f8?utm_source=desktop&utm_medium=timeline



机器人操作系统ROS:从入门到放弃(四) C++类,命名空间,模版,CMakeLists介绍

陈瓜瓜_ARPG

机器人操作系统ROS:从入门到放弃(四) C++类,命名空间,模版,CMakeLists介绍

由于下一讲要讲到怎么在类中pub和sub消息.那么考虑到有些同学对类不甚熟悉.我们稍微回顾一下.但关于类网上一查其实一大堆东西,而且都是从入门讲起.所以我这儿肯定不会重复书写那些内容.要介绍的几个东西,其实本来要用得好的话蛮复杂,我们只会涉及到皮毛,重心会放在和之前的代码比较,以了解之前我们之前三讲的很多语法为什么可以那么写.
比如上一讲的geometry_msgs::PoseStamped的对象msg包含成员变量header和pose,heaer包含成员变量stamp等,为什么我们就可以使用msg.header.stamp这种语法来获取类型为time的变量?
再比如std_msgs::Int8这种语法怎么来的,中间那个::表示什么意思,以及它前后的std_msgs和In8有什么区别.
再比如我们定义ROS publisher时

ros::Publisher chatter_pub = n.advertise("chatter", 1000);

为什么通过这种语法来定义要发送的对象?
这三个比如分别涉及到类,命名空间和模版.对语法熟悉或者不想究其所以然的同学可以跳过这一章直接进入下一章的讲在类中pub和sub消息.

这一讲假设大家对函数,参数,循环等最基本的C++的东西已经掌握了.如果这些不清楚那么用C++操作ROS确实不太合适哈哈.

类(class)

同样类的作用和意义我就不详细阐释了,网上一抓一大把,他们的基本意义大家可以上网搜索.简单来讲,定义了类之后我们可以创建它的对象.对类和其对象直接操作是c++最重要的东西之一.直接开始例子.打开一个terminal,输入下面的内容

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

咱们创建一个叫C++Test的文件夹,再创建三个用于测试三种东西的子文件夹.之后,在classTest文件夹下创建一个叫classBasic.cpp的文件和一个叫CMakeLists.txt的文件.在classBasic.cpp中输入下面内容.

#include
class poorPhd{
public:
/*define constructor*/
poorPhd(){
std::cout<<"we create a poor phd class"< }
/*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 "< /*change tha value of member variale*/
phd.hairNumber = 101;
/*call the member variable*/
std::cout<<"hairNumber is "< /*define class pointer*/
poorPhd *phdPointer;
/*assign the pointer to an object*/
phdPointer = &phd;
/*call the member variable*/
std::cout<<"use pointer, hair number is "<hairNumber<}

逐行解说.
1:#include<> 包含头文件,这样可以使用std::cout<<...std::endl;

2:class poorPhd 定义了一个叫poorPhd的类.类后跟这宗括号{}.宗括号中的内容为类的内容.

3:public 加冒号之后的内容,即为公有.公有范围内定义的函数为公有成员函数,变量为公有成员变量.

4:poorPhd(). 这个函数称为构造函数(constructor function).在类创建时,会自动调用.构造函数的名字和类的名字必须一样并且没有返回值.

5:int hairNumber = 100. 定义了一个int类型公有成员变量,赋值100.

6:int getGirlFriendNumber(). 定义了一个返回值为int的函数,该函数会返回私有成员变量girlFriendNumber.

7:private加冒号之后的内容,即为私有.私有范围内定义的函数为私有成员函数,变量为私有成员变量.

8: int girlFriendNumnber=0. 定义了一个int类型的私有成员变量girlFriendNumber并赋值为0

main函数中
9: poorPhd phd  创建了一个类的对象(object),名字叫phd.每一个类,要想实际被使用,都需要创建一个对象.对象会拥有之前我们在类中定义的所有东西.所谓拥有,即是可以调用他们.对象的数量是没有限制的,并且他们之间不会干扰.你还可以用类似方法创建一个名字加abc的对象,它也会拥有poorPhd这个类的全部东西.
对象在创建时,会自动调用构造函数.

10:std::cout....phd.getGirlFriendNumber()<
类对象调用成员函数或者成员变量的方法是对象名.成员公有成员可以在类的定义外使用这种方式直接调用,私有成员是不可以被直接调用的.所以如果我们使用phd.girlFriendNumber就会报错.因为在类外,不可以直接调用私有成员变量.那有时候我们仍然想看到或者修改私有成员变量怎么办呢?那么我们可以写类似于这个gerGirlFriend的公有成员函数.公有成员函数定义在类中,所以它可以使用私有成员变量,并把变量的值作为返回值,这样我们就得到可私有成员变量的值.
为什么要分私有公有呢?有时候我们写了一个类,并不想其中所有东西都被使用者使用,比如我们有了造车相关技术,所有这些技术和在一起,就是类.具体实现,就是我们造了一辆辆车子.每一辆车子就称为对象.每一辆车子都有相同的内容,但是他们互不干扰.我们只想用户了解刹车,油门等东西.并不想用户了解车子内部构造.那刹车油门在这儿就是公有,而车子内部构造就是私有.如果用户实在想获取内部构造,用户可以去汽车销售店了解些相关资料,销售店就相当于咱们写的那个get...函数接口,架起用户和类私有成员友谊的桥梁.当然,如果有些内容特别私密,我们并不想用户了解它的相关资料,就不写那个get...函数就行了.


  1. phd.hairNumber = 101;
    为公有成员变量赋值101.

12.std::cout<<...phd.hairNumber...
调用公有成员并print出来.

13.poorPhd *phdPointer 创建一个类的指针.类的指针被创建时不会调用构造函数.它需要指向一个对象.

14.phdPointer = &phd 刚才创建的对象的地址赋值给指针,这个指针就有了phd对象的所有内容.


  1. ...phdPointer->hairNumber... 类指针调用类的成员的唯一不同之处就是使用指针名->成员调用而不是对象名.成员调用.

和之前写的ROS代码的联系: 之前我们定义过std_msgs::Int8 msg,msg即是类Int8的对象.我们通过查看roswiki http://docs.ros.org/api/std_msgs/html/msg/Int8.html 得知Int8包含类型为int8的成员变量data,所以我们通过msg.data使用这个成员.

写好文件后退出保存,打开之前建立的CMakeLists.txt文件.输入以下内容.

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)

这基本上算是一个最简单的CMakeLists.txt文件了.CMakeLists.txt是用来编译C++文件的.
第一行表明了项目名称.
第二行输入CMake使用的最小版本号,一般是2.8以及以上.
第三行设定编译器.使用c++11.虽然我们的项目没用到c++11但是考虑到如今c++已经被普遍使用了,所以最好加上.我们在ROS的CMakeLists里注释过这个内容add_compile_options(-std=c++11)达到的也是使用c++11编译的效果.
第四行指定要编译的文件.要编译的文件是classBasic.cpp,编译后的可执行文件名字叫classBasic.
写完上面的内容后,保存退出.
在terminal中cd 到classTest这个文件夹输入下面的内容

mkdir build
cd build
cmake ..
make

第一二行命令创建一个叫build的文件夹并进入
第三行命令使用是使用cmake命令并通过..表示使用上一个文件夹的CMakeLists.txt.执行这行命令之后我们写的CMakeLists就会产生一系列的文件在build中,其中一个是Makefile.其他的这里不作介绍
第四行命令是使用makefile.makefile的作用就是直接编译你在CMakeLists里设定好的文件了.
建立一个build文件夹不是必须的但是推荐,因为你看到build里有一系列编译CMakeLists.txt里产生的文件,你以后要删除或者修改他们会比较方便,不至于和其他文件混在一起.
执行完上面的命令后,你会看到多了一个叫classBasic的文件没有后缀,这就是我们的可执行二进制文件了.使用./classBasic执行后得到下面的输出

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

请对应源代码一行行查看输出为何如此.

上一章我们说过有这样一段话msgs_header.stamp调用stamp,stamp.sec调用sec得到epoch的时间,那么msgs_header.stamp.sec就可以获取当前的时间,秒为单位.写段话之前我们创建了Header的对象msg_header,并通过ros wiki知道了该对象包含数据成员stamp,stamp包含数据成员sec,然后我们我们可以用这种msg_header.stamp.sec来调用sec这个数据成员.这种数据之间看起来的连续性具体是怎么实现的呢?
咱们在之前创建的classTest文件夹下再创建一个新的文件叫 classBasic2.cpp.并输入下面的内容.

#include
class poorPhd{
public:
/*define constructor*/
poorPhd(){
std::cout<<"we create a poor phd class"< }
/*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"< }
/*member variable*/
poorPhd future;
};
int main(){
/*define the object*/
master1 mStudent1;
/*use inheritance*/
std::cout<<"hairNumber of master student 1 is "<}

poorPhd类和上一个文件完全一样,我们新添加了一个类叫master1.master1同样有一个构造函数.另外它有一个成员变量,这个成员变量是poorPhd类型的对象future.那么在main函数中,定义了master1的对象mStudent1.咱们就可以用mStudent1.future调用变量future,再由于future是poorPhd类型的变量,所以可以用future.hairNumber调用hairNumber.连在一起就可以通过定义msater1的对象却最终调用了poorPhd的成员变量了.
保存退出后,在CMakeLists.txt中添加下面的内容.

add_executable(classBasic2 classBasic2.cpp)

terminal中进入classTest/build文件加输入

cmake ..
make

这时候就多了一个二进制文件classBasic2,执行该二进制文件你会看到

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

从这个输入可以看出,创建master1的对象mStudent1的时候c++会首先初始化它的成员变量,所以咱们先得到的是create a poor phd class,之后再调用了构造函数.
类还有很多很多的内容,就靠大家自己取学习了,咱们这儿只是简单地介绍了和前面的代码联系的部分.

命名空间(namespace)

你肯定使用过命名空间,基本上每一个写c++的人都会用过using namespace std这条语句.这条语句代表使用命名空间std.达到的效果是,例如你要使用cout语句在屏幕上打印什么东西,如果没有std,你需要输入的是

std::cout<<"....."<

如果你使用了using namespace std这条语句,那么你就只需要下面的内容打印语句

cout<<"...."<

但是你如果没写过大型程序的话,可能没有机会自己写过命名空间命名空间一般是用来避免重命名的.大型的库里面一般定义了很多类,无数的函数.不同的大型的库之间很可能会有函数甚至类的命名重复,这会造成很大的麻烦.
namespace的命名语法也很简单

namespace name{
//内容
}

下面这个程序简单地展示了两个命名空间里定义相同名字的类,并分别使用两个类的简单程序.

#include
/*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"< }
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"< }
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 "< std::cout<<"master normally graduate in "<}

上面的这个程序定义了两个命名空间,一个叫phd,一个叫master,这两个命名空间拥有一个类,类名都叫student
定义命名空间中的类的对象的方法是命名空间名::类名 对象名::被称为作用域符号(scope resolution operator).在main函数中我们定义了phd命名空间下的student类的对象phdStudent和master命名空间下的类student的对象masterStudenrt. 后面的两行各自输出了成员变量graduateYear
在我们之前的ros程序中,遇到了两个命名空间,一个是std_msgs,另一个是geometry_msgsInt8, Float64等都是std_msgs这个命名空间下的类,PoseStamped等是geometry_msgs这个命名空间下的类.
回到上面的程序我们在定义完phd这个命名空间后,可以使用using namespace phd,这样在main函数中我们可以不使用phd::来定义一个phd下的student类的对象,直接student phdStudent即可.同样,如果我们添加using namespace master,我们也可以直接使用student masterStudent来定义msater命名空间下student类的对象.
但是如果在程序中同时添加了

using namespace phd;
using namespace master;

这时候你在main函数中写student object_name就肯定会报错.因为电脑无法知道你要使用的student类是属于哪个命名空间的.所以一般为了图方便,在我们确定没有类名会重复时,我们添加using namespace ...这一行在定义完头文件之后,这样我们就可以省去在定义类时一直使用namespace_name::类名这种格式命名.但是有些时候如果两个库很有可能有相同的类名,就不要使用using namespace ...,不然很有可能造成程序的误解.

写好上面的程序后和咱们写classBasic.cpp的过程完全一样的步骤,创建CMakeLists.txt和一个build文件夹进行编译.

可能有的读者会问那如果命名空间的名字都重复了呢?你就删掉其中一个程序把 = = ....
同样,命名空间有的是学问,有兴趣的同学自行研究.

模版(Template)

模版这个东西,你如果是c++的使用者,那必定也接触过.为什么这么说呢?当你定义一个std::vector的时候,你就已经使用了模版了.但是你可能没自己写过模版(这种情况好像和namespace有点相似).
模版是为了避免重复定义同样功能的函数而开发的.
打个比方,你现在要实现平方一个数的函数.很简单,类似于下面这样

#include
int square(int a){
return a*a;
}
int main(){
double x = 5.3;
std::cout<<"the square of "<}

这个程序有个很明显的缺点,编写函数或者使用变量时,都必须先指定类型,由于c++函数形参类型和返回值已经指定为int类型了,你只能传int类型进去,如果传double类型的变量进去,变量会被强制转换截断为int类型.而且只能return整型的变量.所以你只能得到25.
基本的解决方法是函数的重载,即我可以命名相同的函数但是变量类型或者个数不同以实现对不同输入的处理.类似于下面这样

#include
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 "<}

这样调用square(x)时会自动匹配形参相同的函数.我们可以得到5.3的平方.但是可以想象,如果我有很多不同类型的变量要传入,我就得写好多不同的除了变量类型不同,其他的一模一样的函数了!有没有一种方法,形参什么类型都是可以的呢?
模版应运而生.模版的定义方式是

template

或者

template

定义完之后后面紧跟要实现的函数或者是类.这个class不是我们之前理解的那种class了.这儿的class和typename作用完全一样,表示定义了一个新的类型T.这个新的类型具体是什么不知道,要等我们具体使用时程序根据传入的类型自行判断.
咱们先上代码,实现数字平方相同的功能.

#include
template
T square(T a){
return a*a;
}
int main(){
double x = 5.3;
std::cout<<"square of "<}

现在你无论传什么类型的数据进去,都会得到它的平方.sqaure指定的函数形参和返回值类型都为T.可以这样理解,现在当我们传入一个double类型的变量时,T就会自动变成double,传入int时,T就自动变为int.
下面来一个稍微复杂一点的例子的.实现两个向量的相加(好像也不怎么复杂...). 向量在c++里是不能直接相加的.我们定义向量时要指定向量元素的类型.比如std::vector astd::vector b等.和上一个例子一样,为了避免传入重载函数,我们使用模版.代码如下

#include
#include
template
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"< return result;
}
for(int i = 0; i result.push_back(vec1[i]+vec2[i]);
}
return result;
}
int main(){
std::vector vec1 = {1,2,3};
std::vector vec2 = {4.0,5.0,6.0};
auto addVec = addVector(vec1,vec2);
for(auto i:addVec)
std::cout< std::cout<}

我们的tempalte定义了两个类型,一个叫U,一个叫T.为什么要定义两个呢?因为前面说过模板定义的具体类型在使用时确定的,在主函数中我们要加两个vector,一个是int类型的,作为第一个参数传入addVector,那么T就会是std::vector,而第二个参数是double类型的向量,作为第二个参数传入函数后U就会相当于std::vector,函数返回的类型也是U.
程序主函数第三行使用了auto这个关键字.使用c++11编译才可使用auto.这个是很有用的关键字.auto会自动分配被它定义的对象的类型,根据赋值的变量的类型.addVector返回的是U,在这个程序里也就是std::vector了.那么auto会自动让addVec称为dpuble类型的vector.
主函数第四行的for循环采用的是有别于我们常用的for循环的形式.

for(auto i:addVec)

其中i:addVec的作用是把addVec中的元素依次赋值给i,这就要求i的类型得和addVec中的元素的类型相同,不过有auto的帮助,我们也就不用管这么多了,把i的类型定义为auto,那么程序会自动让i成为addVec中要赋值给i的元素的类型,这儿也就是double了.
说了这么多,还没到我们最初想讲的,那就是类似于std::vector和我们使用ros的时候定义的advertise这种类型的语法是怎么来的?首先根据命名空间那儿的学习我们知道std肯定是代表命名空间的名字了,vector是一个类,而则来源于模版.如果我们使用模版定义了一个类,则会出现类似的内容.还是用简单的square函数来举例.我们来建立一个简单的sqaure类.

#include
template
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 test(x);
std::cout<<"the square of "<}

在声明了模版之后紧接着我们声明了一个类,类的公有成员函数是一个类型为T的值a.主函数中,在我们声明模版下定义的类的对象时,我们需要在<>之中表明T的类型.再这之后才能定义对象.即普通的类的对象的定义格式如下

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

模版下的类的对象定义的格式就是

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

main函数第二行的这种定义方法,就类似于我们std::vector ABC这种定义方法了,后者多的不过是在命名空间下定义了模版.然后再在模版下定义类.

总结

这一讲我们粗略地涉及到c++中几个简单又庞杂的系统,类,命名空间和模版,在我们平常使用的语法中多多少少都出现过他们的影子.只是我们自己不经常定义罢了.学会使用他们对建立庞杂的代码系统很有帮助.我们还介绍了最简要的CMakeLists的需要包含的内容.下一讲咱们回到ROS.在这一讲的基础之上,讲解在ros的类中发布/接收消息


推荐阅读
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 目录实现效果:实现环境实现方法一:基本思路主要代码JavaScript代码总结方法二主要代码总结方法三基本思路主要代码JavaScriptHTML总结实 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 本文介绍了PhysioNet网站提供的生理信号处理工具箱WFDB Toolbox for Matlab的安装和使用方法。通过下载并添加到Matlab路径中或直接在Matlab中输入相关内容,即可完成安装。该工具箱提供了一系列函数,可以方便地处理生理信号数据。详细的安装和使用方法可以参考本文内容。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • PHP中的单例模式与静态变量的区别及使用方法
    本文介绍了PHP中的单例模式与静态变量的区别及使用方法。在PHP中,静态变量的存活周期仅仅是每次PHP的会话周期,与Java、C++不同。静态变量在PHP中的作用域仅限于当前文件内,在函数或类中可以传递变量。本文还通过示例代码解释了静态变量在函数和类中的使用方法,并说明了静态变量的生命周期与结构体的生命周期相关联。同时,本文还介绍了静态变量在类中的使用方法,并通过示例代码展示了如何在类中使用静态变量。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • 本文介绍了PE文件结构中的导出表的解析方法,包括获取区段头表、遍历查找所在的区段等步骤。通过该方法可以准确地解析PE文件中的导出表信息。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • 开发笔记:实验7的文件读写操作
    本文介绍了使用C++的ofstream和ifstream类进行文件读写操作的方法,包括创建文件、写入文件和读取文件的过程。同时还介绍了如何判断文件是否成功打开和关闭文件的方法。通过本文的学习,读者可以了解如何在C++中进行文件读写操作。 ... [详细]
  • 本文讨论了在openwrt-17.01版本中,mt7628设备上初始化启动时eth0的mac地址总是随机生成的问题。每次随机生成的eth0的mac地址都会写到/sys/class/net/eth0/address目录下,而openwrt-17.01原版的SDK会根据随机生成的eth0的mac地址再生成eth0.1、eth0.2等,生成后的mac地址会保存在/etc/config/network下。 ... [详细]
author-avatar
草原华子无敌_531
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有