前言

Java8在Java中通过lambda表达式、Stream API引入了函数式编程,那么C++中是否也支持函数式编程呢?答案是肯定的。目前关于C++进行函数式编程的语法探究的相关博客、文章并不多,本篇博客的目的就是阐述利用C++进行函数式编程的几种方法。

为了避免本博客篇幅过大,本博客中只涉及C++函数式编程的语法,不对函数式编程的相关思想和关键概念进行详细阐述,但本博客的最后会给出笔者认为比较好的关于函数式编程的相关参考书籍。如有问题或发现本博客中涉及的bug可以在评论区中提出,谢谢!

阅读完本博客你将会理解和掌握:

  • 函数式编程简化代码、复用已有函数、降低编码复杂度的优点
  • 函数指针的声明和使用方法
  • lambda表达式的形式和用法
  • 利用函数指针和lambda表达式实现C++的函数式编程
  • 利用fuction<T>模板的声明和用法,并用fuction<T>模板替代函数指针
  • functional头文件中的函数对象和用法
  • 利用函数式编程对已有代码进行重构

一个简单的编程问题

我想大家在刚刚开始接触编程的时候应该都遇到过这样一个简单的编程问题:

给出3个int型整数abc,当:

  • c0时,求出a+b
  • c1时,求出a-b
  • c2时,求出a*b
  • c3时,求出a/b

看到这个问题,你会想到怎么实现呢?如果有了想法快去自己实现一下呀(~ ̄▽ ̄)~

我们的第一想法(好吧,只是我的第一想法)应该是用一个mapc值和对应的计算方式建立映射关系,显然这个方法目前不可行,因为C++中不支持定义“函数类型”。我们只能退而求其次,使用if-else语句或者switch-case-break语句来完成该程序,代码清单1如下:

//代码清单1
#include<iostream>
using namespace std;
int Plus(int a,int b){//加法
return a+b;
}
int Minus(int a,int b){//减法
return a-b;
}
int Multiplies(int a,int b){//乘法
return a*b;
}
int Divides(int a,int b){//除法
return a/b;
}
int main(){
int a,b,c;
cin>>a>>b>>c;
if(c==0)
cout<<Plus(a,b)<<endl;
else if(c==1)
cout<<Minus(a,b)<<endl;
else if(c==2)
cout<<Multiplies(a,b)<<endl;
else if(c==3)
cout<<Divides(a,b)<<endl;
return 0;
}

显然这样的代码过于臃肿,不够简洁,如果我们可以用mapc值和对应的计算方式建立起映射关系,输入c,直接调用对应的函数,代码会简洁很多,那么我们有什么办法去实现这样的map吗?(当然有!不然我写这篇博客干啥︿( ̄︶ ̄)︿)

函数指针

C++中没有函数类型,但是有函数指针的概念,函数指针指向的是函数而非对象。和其他指针一样,函数指针也具有类型,其类型由指向函数的形参类型和返回类型共同决定。函数指针声明方式是函数返回类型 (*函数指针名) (形参类型列表),我们可以按照下面的声明方式声明一个函数指针:

//定义指向包含两个int形参,返回类型为int的函数的函数指针
int (*f)(int,int)=nullptr;//当前该函数指针没有指向任何一个函数

当我们把函数名作为一个值使用时,该函数名自动转换成指针,例如,我们可以这样让f指针指向我们上一个程序中定义的Plus函数(注意函数指针与函数的类型必须匹配):

f=Plus;//f指向名为Plus的函数
f=&Plus;//等价的赋值语句,取地址符是可选的

我们可以直接使用函数指针调用一个函数,无须解引用:

int t1=Plus(1,2);//调用Plus函数
int t2=f(1,2);//等价的调用语句

虽然我们不能将多个函数存放到一个数组中,但我们可以将多个函数指针存放在一个数组中,语法是函数返回类型 (*函数指针名[数组维度]) (形参类型列表),我们可以按照下面的声明方式声明一个4维的函数指针数组`:

int(*v[4])(int,int);

如何使用标准库中的容器,例如vectormap,可以按照下面的声明方式声明一个存放函数指针类型的vector以及存放int->函数指针键值对的map

vector<int(*)(int,int)>v;
map<int,int(*)(int,int)>m;

哇,好复杂的声明,难道我需要定义函数指针的地方,都要这么声明吗?当然不是,我们可以利用C++中的类型别名机制using来简化声明:

using pf=int(*)(int,int);//pf是该函数指针类型的一个别名
vector<pf>v;//存放函数指针的vector
map<int,pf>m;//存放`int->函数指针`键值对的`map`
pf f[4];//函数指针数组

因此我们可以利用下面的代码清单2解决刚刚提出的加、减、乘、除的编程问题:

//代码清单2
#include<iostream>
#include<map>
using namespace std;
int Plus(int a,int b){//加法
return a+b;
}
int Minus(int a,int b){//减法
return a-b;
}
int Multiplies(int a,int b){//乘法
return a*b;
}
int Divides(int a,int b){//除法
return a/b;
}
int main(){
int a,b,c;
cin>>a>>b>>c;
using pf=int(*)(int,int);//pf是该函数指针类型的一个别名
map<int,pf>m={
{0,Plus},{1,Minus},{2,Multiplies},{3,Divides}
};
cout<<m[c](a,b)<<endl;
return 0;
}

哇,我们实现了按照c值直接调用相应的函数,你已经掌握了很多码农没有掌握的实现方法!快给自己鼓个掌!ㄟ(≧◇≦)ㄏ

兴奋完我们冷静一下,还要学习。我们继续看代码清单2,虽然它实现了按照c值直接调用相应的函数,但是如果需要增加一个新的计算方式,例如求余运算,还是需要预先定义一个新函数,再在主函数中调用它,这显然很不方便,而且定义新函数容易引发命名冲突。有没有什么不需要预先定义函数,而是按需要在main函数体内“定义函数”的即需即用的方法吗?(想知道吗?想知道快往下看,不许开小差(๑╹◡╹)ノ""")

lambda表达式

C++11中引入了lambda表达式的概念。一个lambda表达式标识一个可调用的代码单元,我们可以将其理解为一个未命名内联函数。你可能会问内联是啥?自己查书,别问我,这篇博客不负责解释这个问题,我奏是这么傲娇(`へ´*)ノ。

一个lambda表达式具有如下形式:

[捕获列表](参数列表)->返回类型{函数体}

我们可以忽略参数列表、箭头和返回类型,但必须永远包含捕获列表和函数体。我们可以按照下面的方式定义一个实现两个数加法的lambda表达式:

int(*f)(int,int)=[](int a,int b){return a+b;};//lambda表达式的类型是函数指针类型

我们可以使用auto关键字进行类型自动推导,

auto f=[](int a,int b){return a+b;};//与上面lambda表达式的声明是等价的
cout<<f(1,2)<<endl;//输出3

因此我们可以利用下面的代码清单3解决加、减、乘、除的编程问题:

//代码清单3
#include<iostream>
#include<map>
using namespace std;
int main(){
int a,b,c;
cin>>a>>b>>c;
using pf=int(*)(int,int);//f是该函数指针类型的一个别名
map<int,pf>m={
{0,[](int a,int b){return a+b;}},
{1,[](int a,int b){return a-b;}},
{2,[](int a,int b){return a*b;}},
{3,[](int a,int b){return a/b;}}
};
m.insert({4,[](int a,int b){return a%b;}});//假设增加了一个求余运算
cout<<m[c](a,b)<<endl;
return 0;
}

显然,这份代码就显得简洁多了。我们通过用户指定的不同的c值触发了不同的计算行为。相比于代码清单2,使用了lambda表达式的代码清单3做到了即需即用,即我什么时候需要一个函数,我就可以在main函数体内临时定义它,而不是需要预先定义在main函数体外,这是lambda表达式的一个非常重要的优势。

事实上,我们在这里才真正接触到函数式编程的思想。什么是函数式编程?简而言之,函数式编程就是把函数当作普通的数值一样使用。就像刚才,我们把一个函数(lambda表达式)赋值给了一个变量f,也把一个函数当作参数传递给了mapinsert函数,从而插入到了map的键值对中。将一个函数行为当作参数传递给另一个函数的方式,在函数式编程的思想中有一个专门术语,称为行为参数化(behavior parameterization)。函数式编程往往都是通过lambda表达式来实现的,这不仅仅局限于C++语言,在Java、Scala等等已经引入函数式编程思想的语言中,都是如此。

练习1

在这里,我想留给大家一个练习。

假设不使用map,仍使用if-else的语句,你能通过传递lambda表达式作函数参数的形式,在main函数外,仅实现一个函数compute来实现加减乘除求余这5种运算吗?(你当然可以的啊,是不是~(。≧3≦)ノ⌒☆棒棒哒)

为了简单起见,我希望你能通过代码填空的形式完成该练习。我给出了一份缺失部分代码的代码清单4,由你来填写空白部分,以使整个程序能够顺利运行并实现相应的功能。缺失部分代码的代码清单4如下:

//代码清单4
#include<iostream>
#include<map>
using namespace std;
//这里写你自己的compute函数
int main(){
int a,b,c;
cin>>a>>b>>c;
if(c==0)
cout<</*填写你对compute函数的调用代码*/<<endl;
else if(c==1)
cout<</*填写你对compute函数的调用代码*/<<endl;
else if(c==2)
cout<</*填写你对compute函数的调用代码*/<<endl;
else if(c==3)
cout<</*填写你对compute函数的调用代码*/<<endl;
else if(c==4)
cout<</*填写你对compute函数的调用代码*/<<endl;
return 0;
}

函数指针更好的替代选择——fuction标准库类型

类模版fuction<T>是一种通用、多态的函数封装,封装的实体包括普通函数、Lambda表达式、函数指针、以及其它函数对象等。fuction<T>对象是对C++中现有的可调用实体的一种类型安全的包裹,而函数指针这类可调用实体,是类型不安全的。所以任何用到函数指针的地方,我们都应该尽可能使用fuction<T>来替代。fuction<T>很棒哦,往下看你就知道了(^-^)V

fuction<T>是一个模板,定义在头文件functional中,和我们使用过的其他模板一样,当创建一个具体的fuction<T>类型时,我们需要提供额外的类型信息。我们可以按照下面的代码声明一个接受两个int,返回一个int的可调用对象:

function<int(int,int)>f1=[](int a,int b){return a+b;};//注意没有*
function<int(int,int)>f2=Plus;//注意没有*

因此你完全可以将代码清单3中的函数指针全部替换成fuction<T>,并进行细微修改,即可完成和代码清单3一样的功能,如代码清单5所示:

//代码清单5
#include<iostream>
#include<map>
#include<functional>
using namespace std;
int main(){
int a,b,c;
cin>>a>>b>>c;
using pf=int(int,int);//注意没有*
map<int,function<pf>>m={//注意变为了function模板
{0,[](int a,int b){return a+b;}},
{1,[](int a,int b){return a-b;}},
{2,[](int a,int b){return a*b;}},
{3,[](int a,int b){return a/b;}}
};
m.insert({4,[](int a,int b){return a%b;}});//假设增加了一个求余运算
cout<<m[c](a,b)<<endl;
return 0;
}

返回一个函数

那么C++函数式编程的语法到这里就结束了吗?当然不是,对于我们开头提出的编程问题而言,代码清单5的确已经相当完美了,但是对函数式编程的思想来说,还远远不够。我们已经知道可以传递一个函数行为当作另一个函数的参数,但是考虑另一个问题,我们可以返回一个函数行为吗?答案自然是可以。代码清单6实现了返回一个函数的方法。

//代码清单6
#include<iostream>
#include<functional>
using namespace std;
using pf=function<int(int,int)>;//f是该函数指针类型的一个别名
int Plus(int a,int b){//加法
return a+b;
}
pf g(){
return Plus;//返回预先定义的函数
}
pf h(){
return [](int a,int b){return a+b;};//返回lambda表达式
}
int main(){
auto i=g();
cout<<i(1,2)<<endl;//输出3
auto j=h();
cout<<j(1,2)<<endl;//输出3
return 0;
}

functional头文件中的函数对象

functional头文件中还定义了许多函数对象,其中有一些你可能使用过(比如lessgreater),在这做一下总结:

模板关键字 作用
plus<T> (T t1,T t2)->(t1+t2)
minus<T> (T t1,T t2)->(t1-t2)
multiplies<T> (T t1,T t2)->(t1*t2)
divides<T> (T t1,T t2)->(t1/t2)
modulus<T> (T t1,T t2)->(t1%t2)
negate<T> (T t1)->(-t1)
equal_to<T> (T t1,T t2)->(t1==t2)
not_equal_to<T> (T t1,T t2)->(t1!=t2)
greater<T> (T t1,T t2)->(t1>t2)
greater_equal<T> (T t1,T t2)->(t1>=t2)
less<T> (T t1,T t2)->(t1<t2)
less_equal<T> (T t1,T t2)->(t1<=t2)
logical_and<T> (T t1,T t2)->(t1&t2)
logical_or<T> (T t1,T t2)->(t1|t2)
logical_not<T> (T t1)->(~t1)

那么我们可以用以下语法表示两数之和(注意导入functional头文件)

//代码清单7
#include<iostream>
#include<functional>
using namespace std;
int main(){
int a,b;
cin>>a>>b;
auto i=plus<int>();
cout<<i(a,b)<<endl;
cout<<plus<int>()(a,b)<<endl;//与上面的两个语句等价
return 0;
}

练习2

请你仅使用mapfunctional头文件中的函数对象,不再使用任何函数和lambda表达式对代码清单3进行重构,并和代码清单3实现加减乘除求余这5种运算。

函数式编程的简单应用

本博客所要介绍的C++中实现函数式编程的全部语法已经介绍完了,下面我们看一下函数式编程的一些简单应用。

自定义排序

假设我们有一个vector<int>v变量,我们想用algorithm头文件中的sort函数对变量v中的元素进行从大到小排序,应该怎么做呢?

在C++11以前,可以这样进行排序:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
bool cmp(int a,int b){//自定义的排序函数
return a>b;
}
int main(){
vector<int>v={1,2,3,4,5};
sort(v.begin(),v.end(),cmp);//进行从大到小排序
for(int i:v)
cout<<i<<" ";
return 0;
}

显然,这种方法并不完美,因为你每需要一个排序规则,就要预先定义一个排序函数。在C++11以后我们可以使用lambda表达式进行排序,代码如下:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main(){
vector<int>v={1,2,3,4,5};
sort(v.begin(),v.end(),[](int a,int b){
return a>b;
});
for(int i:v)
cout<<i<<" ";
return 0;
}

这种代码要好得多,但是我们还可以直接利用functional头文件中的函数对象greater直接取代lambda表达式进行从大到小排序,代码如下:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main(){
vector<int>v={1,2,3,4,5};
sort(v.begin(),v.end(),greater<int>());//进行从大到小排序
for(int i:v)
cout<<i<<" ";
return 0;
}

线性函数的定积分

我们在数学上学习过如何求解一个函数的定积分,那么如何编程实现求解任意函数在某个区间上的定积分呢?为了简单起见,这里只求解线性函数(一次函数)的定积分。在看下面的分析之前你可以自己先手动实现一下(o゜▽゜)o☆,不要犯懒,动一下手啊~~

假定一元函数表达式为f(x)=kx+b,我们需要两个参数kb确定一个一次函数,求解其在区间[a,b](b>a)上的定积分可以用公式\((f(b)+f(a))×(b-a)/2\)来表示。关键在于如何保证线性函数是任意的呢?

这里就必须要把线性函数当作参数传递给计算定积分的函数了,一元函数需要针对每个给定的参数x,应该有一个确定的y值,所以该一元函数类型应该为(double)(*)(double),即接受一个参数x,返回一个函数值y,而参数kb由传入的实参确定。

这里直接给出实现代码:

#include<iostream>
#include<functional>
using namespace std;
double compute(double a,double b,function<double(double)>f){
return (f(b)+f(a))*(b-a)/2;
}
int main(){
cout<<compute(3.0,7.0,[](double x){//求出y=x+10在[3,7]上的定积分
return x+10.0;
})<<endl;
cout<<compute(-3.0,7.0,[](double x){//求出y=-3x+5在[-3,7]上的定积分
return -3.0*x+5.0;
})<<endl;
return 0;
}

科里化

假设我们有如下一个涉及三个参数的函数,代码如下:

int test(int a,int b,int c){
return (a+b)*c;
}

假设你有许多个应用,其中每个应用涉及的ab参数都是一致的,所以每个应用中,只提供参数c就可以得到结果。

你可以在每次调用test函数时都使用3个参数,但是每次都提供ab参数比较繁琐,并且你还极有可能输入错误。

你也可以为每一个应用编写一个新方法,将test方法中的所有ab参数替换成默认值,类似下面的代码这样:

int testForApplication(int c){
return test(1,2,c);//假设该应用的a、b 参数分别为1、2
}

这样做的确复用了底层代码,但是针对每一个应用你都需要定义一个这样的函数,应用有多少个,你就需要写多少个函数,假如应用有很多,带来的编码代价也很大。

这里提供一种使用函数式编程的更简洁的解法,它既能充分利用已有的逻辑,又能让test函数针对每个应用进行定制,代码如下:

function<int(int)>testForApplication(int a,int b){
return [a,b](int c){return test(a,b,c);};
}

testForApplication函数返回一个函数,它只需要一个参数c就可以运行。因此针对每个应用,你只需要先提供该应用的两个参数ab,获取针对该应用只需输入参数c的函数,然后针对该应用下的所有数据,输入一个参数c就可以得到相应结果了。像这样:

auto i=testForApplication(1,2);//获取参数a、b是1、2的应用对应的只需一个参数的函数
cout<<i(3);//获取该应用下参数c为3的最终结果
cout<<i(4);//获取该应用下参数c为4的最终结果

该应用中涉及到两个概念,即

高阶函数(Higher-order function):如果一个函数接受至少一个函数作为参数或返回的结果是一个函数,该函数就可以称为高阶函数。

科里化(Currying):将具备2个参数(比如,xy)的函数f转化为使用一个参数的函数g,并且这个函数的返回值也是一个函数,它会作为新函数的一个参数。后者的返回值和初始函数的返回值相同,即f(x,y) = (g(x))(y)。当然,我们可以由此推出:你可以将一个使用了6个参数的函数科里化成一个接受第2、4、6号参数,并返回一个接受5号参数的函数,这个函数又返回一个接受剩下的第1号和第3号参数的函数。

总结

本博客的主要目的在于介绍C++中实现函数式编程的语法,对函数式编程的主要概念和思想没有详细展开,在此做一下总结。

  • 本博客涉及到的函数式编程的概念:

    1. 函数式编程:把函数当作普通的数值一样使用,包括可以用函数进行赋值、传参、从函数中返回等操作。
    2. 行为参数化:将一个函数行为当作参数传递给另一个函数的方式。
    3. 高阶函数:如果一个函数接受至少一个函数作为参数或返回的结果是一个函数,该函数就可以称为高阶函数。
    4. 科里化:将具备2个参数(比如,xy)的函数f转化为使用一个参数的函数g,并且这个函数的返回值也是一个函数,它会作为新函数的一个参数。后者的返回值和初始函数的返回值相同,即f(x,y) = (g(x))(y)
  • 本博客涉及的C++语法:
    1. 函数指针:声明方式是函数返回类型 (*函数指针名) (形参类型列表)。我们可以按照下面的方式定义指向包含两个int形参,返回类型为int的函数的函数指针int (*f)(int,int)
    2. lambda表达式:声明方式是[捕获列表](参数列表)->返回类型{函数体}。我们可以忽略参数列表、箭头和返回类型,但必须永远包含捕获列表和函数体。我们可以按照下面的方式定义一个实现两个数加法的lambda表达式:[](int a,int b){return a+b;};
    3. fuction<T>:模板,定义在头文件functional中,当创建一个具体的fuction<T>类型时,我们需要提供额外的类型信息。我们可以按照下面的代码声明一个接受两个int,返回一个int的可调用对象:function<int(int,int)>f
    4. functional头文件中的函数对象
  • 相关技巧:
    1. 函数指针声明比较复杂,可以用using声明简化。
    2. 尽可能用 fuction<T>模板代替函数指针,因为函数指针是类型不安全的。
    3. 函数指针和 fuction<T>模板类型名都很长,应尽可能用auto关键字进行自动推导。
  • 参考及推荐书籍:
    1. 《C++ Primer》第5版:该书纸质书在中国有中文版和英文版
    2. 《Java 8 in Action》:该书纸质书在中国只有中文版
    3. 《Modern Java in Action》:该书是《Java 8 in Action》的第2版,目前在中国没有上市,也没有中文版,但是网上有英文版的pdf

浅析C++的函数式编程的更多相关文章

  1. guava函数式编程

    [Google Guava] 4-函数式编程 原文链接 译文链接 译者:沈义扬,校对:丁一 注意事项 截至JDK7,Java中也只能通过笨拙冗长的匿名类来达到近似函数式编程的效果.预计JDK8中会有所 ...

  2. 简明python教程 --C++程序员的视角(九):函数式编程、特殊类方法、测试及其他

    函数式编程 Lambda exec,eval和assert语句,repr函数   lambda语句 用来创建简短的单行匿名函数 print_assign = lambda name, value: n ...

  3. angular2系列教程(六)两种pipe:函数式编程与面向对象编程

    今天,我们要讲的是angualr2的pipe这个知识点. 例子

  4. [学习笔记]JavaScript之函数式编程

    欢迎指导与讨论:) 前言 函数式编程能使我们的代码结构变得简洁,让代码更接近于自然语言,易于理解. 一.减少不必要的函数嵌套代码 (1)当存在函数嵌套时,若内层函数的参数与外层函数的参数一致时,可以这 ...

  5. 函数式编程之柯里化(curry)

    函数式编程curry的概念: 只传递给函数一部分参数来调用函数,然后返回一个函数去处理剩下的参数. var add = function(x) { return function(y) { retur ...

  6. 关于Java8函数式编程你需要了解的几点

    函数式编程与面向对象的设计方法在思路和手段上都各有千秋,在这里,我将简要介绍一下函数式编程与面向对象相比的一些特点和差异. 函数作为一等公民 在理解函数作为一等公民这句话时,让我们先来看一下一种非常常 ...

  7. Haskell 函数式编程快速入门【草】

    什么是函数式编程 用常规编程语言中的函数指针.委托和Lambda表达式等概念来帮助理解(其实函数式编程就是Lambda演算延伸而来的编程范式). 函数式编程中函数可以被非常容易的定义和传递. Hask ...

  8. java1.8函数式编程概念

    有关函数式编程 ·1 函数作为一等公民 特点:将函数作为参数传递给另外一个函数:函数可以作为另外一个函数的返回值 ·2 无副作用 函数的副作用指的是函数在调用过程中,除了给出了返回值外,还修改了函数外 ...

  9. 让JavaScript回归函数式编程的本质

    JavaScript是一门被误会最深的语言,这话一点不假,我们看下它的发展历史. 1995年,Netscape要推向市场,需要一门脚本语言来配套它.是使用一门已有的语言,还是发明一门新的语言,这也不是 ...

随机推荐

  1. calico官网网络拓扑实现:基于eNSP与VMVare

    Calico官网提供了两种网络设计模式: AS per rack: 每个rack(机架)组成一个AS,每个rack的TOR交换机与核心交换机组成一个AS AS per server: 每个node做为 ...

  2. 使用基于centos7 dbus问题总结

    Authorization not available. Check if polkit Authorization not available. Check if polkit service is ...

  3. vue常见错误

    错误集锦 错误一 错误二 原因是写太多的import,修改呈如下方式 错误三 源码如下 原因是没有在return后面添加值 应该为 return false

  4. [设计模式] 读懂UML图

    类之间关系(由强到弱) realize(继承):三角+实线(指向类),继承类(SUV是一种汽车) generalization(实现):三角+虚线(指向接口),实现接口(汽车是一种车) composi ...

  5. [算法] O(nlogn)和O(n^2)算法性能比较

    选择排序.插入排序.归并排序 main.cpp 1 #include <iostream> 2 #include "Student.h" 3 #include &quo ...

  6. 用源码搭建LNMP环境+部署WordPress

    首先要做的是就是关闭Centos7.4的防火墙及selinux #systemctl stop firewalld #systemctl disable firewalld #sed -ri 's/^ ...

  7. Linux_配置加密的https

    一.配置https 1.安装好httpd服务后,安装mod_ssl模块 //首先查看是否安装mod_ssl [root@localhost ~]# rpm -qa | grep mod_ssl //安 ...

  8. 搜狗拼音输入法v9.6a (9.6.0.3568) 去广告精简优化版本

    https://yxnet.net/283.html 搜狗拼音输入法v9.6a (9.6.0.3568) 去广告精简优化版本 软件大小:29.2 MB 软件语言:简体中文 软件版本:去广告版 软件授权 ...

  9. 戴尔 R730xd 服务器更改管理口密码 图文教程

    一.开机根据提示按F2进入配置界面 - 选择中间的iDRAC Setting选项,回车确认 二.进入之后选择 user configuration 选项 三.在change password 处键入新 ...

  10. LINUX创建文件和目录的默认权限

    在linux中,一位用户在创建文件和目录时,对其具有的权限都是一样的,如需更改,需要chmod命令做相应的更改.为什么?其实是权限掩码起作用了. 权限掩码的作用就是规范初创文件和目录时候的权限设置,免 ...