使用make命令编译项目文件入门

使用make命令编译项目文件入门


目录:
一、make命令的运行过程
二、基本gcc编译命令
三、简单Makefile文件的编写
四、实例

一、make命令的运行过程
    在shell的提示符号下,若输入"make",则它会到目前的目录下找寻Makefile这个文件.然后依照Makefile中所记录的步骤一步一步的来执行.在我们写程序的时候,如果事先就把compiler程式所需要的步骤先写在Makefile中的话,想要compiler程序的时候就只要打入make的指令.只要程序无误的话,就可以获得所需要的结果了!
    在项目文件中,如果有成百上千个源程序,每次修改其中的一个都需要全部重新编译是不可想象的事情.但通过编辑Makefile文件,利用make命令就可以只针对其中修改的源文件进行编译,而不需要全体编译.这就是make命令在编译项目文件时体现出来的优势.能做到这点,主要是基于Makefile文件的编写,和make命令对Makefile文件的调用.Makefile文件作为make命令的默认参数,使一个基于依赖关系编写的结构文件.
    大家经常看到使用make all, make install, make clean等命令,而他们处理的目标都是一个Makefile文件,那么all、install、clean参数是如何调用Makefile文件的运行呢?在这里,如果向上面的命令如果能够正确运行的话,那么在Makefile文件里一定有这样的几行,他们的以all、install、clean开始
all: ×××××××
       ×××××××××××
install: ××××××
       ×××××××××××
clean: ×××××××××
        ×××××××××××
all,install,clean我们可以用其他的变量来代替,他们是编译时的一个参数,在Makefile文件中作为一个标志存在,也就是我们所说的目标.make all命令,就告诉make我们将执行all所指定的目标.为了便于理解Make程序的流程,我们给大家看一个与gcc毫无关系的Makefile文件:
#Makefile begin
all:
        @echo you have typed command "make all"
clean:
        @echo you have typed command "make clean"
install:
        @ehco you have typed command "make $@"
#Makefile end
注意在这里,all:、clean:、install:行要顶格些,而所有的@echo前要加tab键来跳格缩进.下面是运行结果
[root@xxx test]#make all
you have typed command "make all"
[root@xxx test]#make clean
you have typed command "make clean"
[root@xxx test]#make install
you have typed command "make install"

二、基本gcc编译命令

1、源程序的编译
    在Linux下面,使用GNU的gcc编译器编译一个C语言的源程序.下面我们简单介绍几个常用的Gcc编译命令和参数,这里不是讲解Gcc的使用,只是介绍简单的基础知识是我们能看懂一般的makefile文件.
    我们先看一个使用gcc编译器的实例.假设我们有下面一个非常简单的源程序(hello.c):
    int main(int argc,char **argv)
    {
       printf("Hello Linux/n");
     }
要编译这个程序,我们只要在命令行下执行:     gcc -o hello hello.c
    gcc 编译器就会为我们生成一个hello的可执行文件.执行./hello就可以看到程序的输出结果了.命令行中 gcc表示我们是用gcc来编译我们的源程序,-o 选项表示我们要求编译器给我们输出的可执行文件名为hello 而hello.c是我们的源程序文件.
    gcc的基本格式就是:
       gcc [-option] objectname sourcename
    其中-option是参数,用来控制gcc的编译方式,常见的参数有如下几个:
    -o 表示我们要求输出的可执行文件名:-o binaryname
    -c 表示我们只要求编译器进行编译,输出目标代码,而不进行连接: -c objectivename.o
    -g 表示我们要求编译器在编译的时候提供我们以后对程序进行调试的信息: -g
    -O2 表示我们希望编译器在编译的时候对我们的程序进行一定程度的优化.2表示我们优化的级别是2.范
        围是1-3.不过习惯上我们都使用2的优化级别.
    -Wall是警告选项,表示我们希望gcc在编译的时候,让gcc输出她认为的一些程序中可能会出问题的一些警
        告信息,比如指针没有初始化就进行赋值等等一些警告信息.
    -l 与之紧紧相连的是表示连接时所要的链接库,比如多线程,如果你使用了pthread_create函数,那么         你就应该在编译语句的最后加上"-lpthread","-l"表示连接,"pthread"表示要连接的库,注意他们         在这里要连在一起写.如:gcc -o test test1.o test2.o -lpthread
    -I 表示将系统缺省的头文件路径扩展到当前路径,默认的路径保存在/etc/ld.conf文件中。
    gcc的例子:
        gcc -c test.c,表示只编译test.c文件,成功时输出目标文件test.o
        gcc -o test test.o,将test.o连接成可执行的二进制文件test
        gcc -o test test.c,将test.c编译并连接成可执行的二进制文件test
        gcc -c test.c -o test.o ,与上一条命令完全相同
        gcc test.c -o test,与上一条命令相同
        gcc -c test1.c,只编译test1.c,成功时输出目标文件test1.o
        gcc -c test2.c,只编译test2.c,成功时输出目标文件test2.o
        gcc -o test test1.o test2.o,将test1.o和test2.o连接为可执行的二进制文件test
        gcc -c test test1.c test2.c,将test1.o和test2.o编译并连接为可执行的二进制文件test

2、程序库的链接
试着编译下面这个程序
/* temp.c */
#include
int main(int argc,char **argv)
{
double value =15;
printf("Value:%f/n",log(value));
}
这个程序相当简单,但是当我们用 gcc -o temp temp.c 编译时会出现下面所示的错误.
/tmp/cc33Kydu.o: In function `main':
/tmp/cc33Kydu.o(.text+0xe): undefined reference to `log'
collect2: ld returned 1 exit status
    出现这个错误是因为编译器找不到log的具体实现.虽然我们包括了正确的头文件,但是我们在编译的时候还是要连接确定的库.在Linux下,为了使用数学函数,我们必须和数学库连接,为此我们要加入 -lm 选项. gcc -o temp temp.c -lm这样才能够正确的编译.也许有人要问,前面我们用printf函数的时候怎么没有连接库呢?是这样的,对于一些常用的函数的实现,gcc编译器会自动去连接一些常用库,这样我们就没有必要自己去指定了. 有时候我们在编译程序的时候还要指定库的路径,这个时候我们要用到编译器的 -L选项指定路径.比如说我们有一个库在 /home/hoyt/mylib下,这样我们编译的时候还要加上 -L/home/hoyt/mylib.对于一些标准库来说,我们没有必要指出路径.只要它们在起缺省库的路径下就可以了.系统的缺省库的路径/lib、/usr/lib、/usr/local/lib(你可以查看你的/etc/ld.conf文件来看看你的系统指定了那几个缺省的路径) 在这三个路径下面的库,我们可以不指定路径.
    还有一个问题,有时候我们使用了某个函数,但是我们不知道库的名字,这个时候怎么办呢?很抱歉,对于这个问题我也不知道答案,我只有一个傻办法.首先,我到标准库路径下面去找看看有没有和我用的函数相关的库,我就这样找到了线程(thread)函数的库文件(libpthread.a). 当然,如果找不到,只有一个笨方法.比如我要找sin这个函数所在的库. 就只好用 nm -o /lib/*.so|grep sin>~/sin 命令,然后看~/sin文件,到那里面去找了. 在sin文件当中,我会找到这样的一行libm-2.1.2.so:00009fa0 W sin 这样我就知道了sin在libm-2.1.2.so库里面,我用 -lm选项就可以了(去掉前面的lib和后面的版本标志,就剩下m了所以是 -lm). 如果你知道怎么找,请赶快告诉我,我回非常感激的.谢谢!

3、程序的调试
    我们编写的程序不太可能一次性就会成功的,在我们的程序当中,会出现许许多多我们想不到的错误,这个时候我们就要对我们的程序进行调试了.最常用的调试软件是gdb.如果你想在图形界面下调试程序,那么你现在可以选择xxgdb.记得要在编译的时候加入-g选项.关于gdb的使用可以看gdb的帮助文件. 由于我很少使用这个软件,所以我也不能够详细的说出如何使用. 不过我不喜欢用gdb.跟踪一个程序是很烦的事情,我一般用在程序当中输出中间变量的值来调试程序的.当然你可以选择自己的办法,没有必要去学别人的.现在有了许多IDE环境,里面已经自己带了调试器了.你可以选择几个试一试找出自己喜欢的一个用.

4、头文件和系统求助
    有时候我们只知道一个函数的大概形式,不记得确切的表达式,或者是不记得着函数在那个头文件进行了说明.这个时候我们可以求助系统.比如说我们想知道 fread这个函数的确切形式,我们只要执行 man fread 系统就会输出着函数的详细解释的.和这个函数所在的头文件说明了. 如果我们要write这个函数的说明,当我们执行man write时,输出的结果却不是我们所需要的. 因为我们要的是write这个函数的说明,可是出来的却是write这个命令的说明.为了得到write的函数说明我们要用 man 2 write. 2表示我们用的write这个函数是系统调用函数,还有一个我们常用的是3表示函数是C的库函数.


三、简单Makefile文件的编写

1、Makefile文件的一般组成
(1)注释:
    在Makefile中,任何以"#"起始的文字都是注释,make在解释Makefile的时候会忽略它们.
(2)转接下行标志:
    在Makefile中,若一行不足以容纳该命令的时候.可在此行之后加一个反斜线(/)表示下一行为本行的延续
   ,两行应视为一行处理
(3)宏(macro)
    宏的格式为: =
    例如:
                CFLAGS = -O -systype bsd43
    其实make本身已有许多的default的macro,如果要查看这些macro的话,可以用make -p的命令.
    宏主要是作为运行make时的一些环境变量的设置,比如制定编译器等。
    CC 表示我们的编译器名称,缺省值为cc.
    CFLAGS 表示我们想给编译器的编译选项
    LDLIBS 表示我们的在编译的时候编译器的连接库选项.(我们的这个程序中还用不到这个选项)
(4)规则(Rules)
    格式如下:
        :
              
              
                ....
        :
              
              
                ....
    注意:需要顶格写,而需要在下一行tab之后写,由于其是一个批处理形式的文件,所以不
         可以随便的换行写,被迫换行的时候要用上面的转接下行标志进行连接.          
(5)符号标志及缺省规则
   $@ 代指目标文件
   $< 第一个依赖文件
   $^ 所有的依赖文件
   $? 为该规则的依赖
   -   若在command的前面加一个"-",表示若此command发生错误不予理会,继续执行下去.
   $(macro) 应用这个变量,可以自动的将定义的宏加以展开,并替换使用。
   .c.o:
      gcc -c $<   这个规则表示所有的 .o文件都是依赖与其相应的.c文件的.例如mytool.o依赖于mytool.c
   再一次简化后的Makefile
    main:main.o mytool1.o mytool2.o
         gcc -o $@ $^
    .c.o:
         gcc -c $< -I.;
    使用宏后进一步的简化Makefile可以是
    CC=gcc
    CFLAGS=-g -Wall -O2 -I.
    main:main.o mytool1.o mytool2.o
    .c.o:

2、依赖
    我们现在提出这样一个问题:我如何用一个make命令将替代所有的make all, make install,make clean命令呢?当然我们可以象刚才那样写一个Makefile文件:
all:
        @echo you have typed command "make all"
clean:
        @echo you have typed command "make clean"
install:
        @ehco you have typed command "make $@"
doall:
        @echo you have typed command "make $@l"
        @echo you have typed command "make all"
        @echo you have typed command "make clean"
        @ehco you have typed command "make install"
[root@xxx test]#make doall
you have typed command "make doall"
you have typed command "make all"
you have typed command "make clean"
you have typed command "make install"
    在这里,doall:目标有4调语句,他们都是连在一起并都是由tab键开始的.当然,这样能够完成任务,但是太笨了,我们这样来写:
[root@xxx test]#cat Makefile
# #表示Makefile文件中的注释,下面是Makefile文件的具体内容
all:
        @echo you have typed command "make all"
clean:
        @echo you have typed command "make clean"
install:
        @ehco you have typed command "make $@"
doall: all clean install
        @echo you have typed command "make $@l"
    相信大家已经看清了doall:的运行方式,它先运行all目标,然后运行clean目标,然后是install,最后是自己本身的目标,并且每个$@还是保持着各自的目标名称.效果大致是一样的。在这里,我们称all, clean, install为目标doall所依赖的目标,简称为doall的依赖.也就是你要执行doall,请先执行他们(all, clean, install),最后在执行我的代码.
    注意依赖一定是Makefile里面的目标,否则你非要运行;一般写在最前边,而不是像这样写在最后边。

3、Makefile的编写
   在Makefile中,一般采用引导的注释行开始;下边一般紧跟macro定义;接下来是标签(如上面的all、clean等);最后就是Makefile中最重要的是描述文件的依赖关系的说明.一般的格式是:
   #describe
   macro
   label: label1,label2
   label1:
   :
              
              
   label2:
   :
              
              
   ......
  
我们来看一个例子:
   /* main.c */
#include
#include
int main(int argc,char **argv)
{
mytool1_print("hello");
mytool2_print("hello");
}

/* mytool1.h */
#ifndef _MYTOOL_1_H
#define _MYTOOL_1_H
void mytool1_print(char *print_str);
#endif

/* mytool1.c */
#include
void mytool1_print(char *print_str)
{
printf("This is mytool1 print %s/n",print_str);
}

/* mytool2.h */
#ifndef _MYTOOL_2_H
#define _MYTOOL_2_H
void mytool2_print(char *print_str);
#endif

/* mytool2.c */
#include
void mytool2_print(char *print_str)
{
printf("This is mytool2 print %s/n",print_str);
}
    因为我们在程序中使用了我们自己的2个头文件,而在包含这2个头文件的时候,我们使用的是<> 这样编译器在编译的时候会去系统默认的头文件路径找我们的2个头文件,由于我们的2个头文件不在系统能够的缺省路径下面,所以我们自己扩展系统的缺省路径,为此我们使用了-I.选项,表示将系统缺省的头文件路径扩展到当前路径.这样的话我们也可以产生main程序,而且也不是很麻烦.但是考虑一下如果有一天我们修改了其中的一个文件(比如说mytool1.c)那么我们难道还要重新逐一编译?也许你会说,这个很容易解决啊,我写一个SHELL脚本,让她帮我去完成不就可以了.但是当我们把事情想的更复杂一点,如果我们的程序有几百个源程序的时候,难道也要编译器重新一个一个的去编译? 为此,聪明的程序员们想出了一个很好的工具来做这件事情,这就是make.我们只要执行一下make,就可以把上面的问题解决掉.在我们执行make之前,我们要先编写一个非常重要的文件.--Makefile.对于上面的那个程序来说,可能的一个Makefile的文件是:
# 这是上面那个程序的Makefile文件
main:main.o mytool1.o mytool2.o
gcc -o main main.o mytool1.o mytool2.o
main.o:main.c mytool1.h mytool2.h
gcc -c main.c -I.
mytool1.o:mytool1.c mytool1.h
gcc -c mytool1.c -I.
mytool2.o:mytool2.c mytool2.h
gcc -c mytool2.c -I.
    有了这个Makefile文件,不管我们什么时候修改了源程序当中的什么文件,我们只要执行make命令,我们的编译器都只会去编译和我们修改的文件有关的文件,其它的文件她连理都不想去理的.


四、实例
1、实例一
一个非常简单的Makefile
   假设我们有一个程式,共分为下面的部份:
   menu.c       主要的程式码部份
   menu.h       menu.c的include file
   utils.c      提供menu.c呼叫的一些function calls
   utils.h      utils.c的include file
   同时本程式亦叫用了ncurses的function calls.
   而menu.c和utils.c皆放在/usr/src/menu下.
   但menu.h和utils.h却放在/usr/src/menu/include下.
   而程式做完之后,执行档名为menu且要放在/usr/bin下面.
# This is the Makefile of menu
CC = gcc
CFLAGS = -DDEBUG -c
LIBS = -lncurses
INCLUDE = -I/usr/src/menu/include

all: clean install
install: menu
        chmod 750 menu
        cp menu /usr/bin
menu: menu.o
        $(CC) -o $@ $? $(LIBS)
menu.o:
        $(CC) $(CFLAGS) -o $@ menu.c $(INCLUDE)
utils.o:
        $(CC) $(CFLAGS) -o $@ utils.c $(INCLUDE)
clean:
        -rm *.o
        -rm *~

在上述的Makefile中,要使用某个macro可用$(macro_name)如此的形式.make会自动的加以展开.
$@为该rule的Target,而$?则为该rule的depend.
若在command的前面加一个"-",表示若此command发生错误则不予理会,继续执行下去.
上述的Makefile的关系可以表示如下:
                        all
                        / /
                   clean   install
                               /
                               menu
                              /    /
                          menu.o    utils.o

若只想清除source以外的档案,可以打make clean;若只想做出menu.o可以打make menu.o;若想一次全部做完,可以打make all或是make;要特别注意的是command之前一定要有一个TAB(即TAB键).

2、实例二
有了上面的说明,我们就可以开始写一些简单的Makefile文件了.比如我们有如下结构的文件:
tmp/
   +---- include/
   |      +---- f1.h
   |      +----f2.h
   +----f1.c    #include "include/f1.h"
   +----f2.c    #include"include/f2.h"
   +---main.c   #include"include/f1.h", #include"include/f2.h"
要将他们联合起来编译为目标为testmf的文件,我们就可以按下面的方式写Makefile:
#Makefile,Create testmf from f1.c f2.c main.c
main: main.o f1.o f2.o
        gcc -o testmf main.o f1.o f2.o
f1.o: f1.c
        gcc -c -o file1.o file1.c
f2.o: f2.c
        gcc -c -o file2.o file2.c
main.o
        gcc -c -o main.o main.c
clean:
        rm -rf f1.o f2.o main.o testmf
执行这个Makefile文件
[root@xxx test]make
gcc -c -o main.o main.c
gcc -c -o file1.o file1.c
gcc -c -o file2.o file2.c
gcc -o testmf main.o f1.o f2.o
[root@xxx test]ls
f1.c f1.o f2.c f2.o main.c main.o include/ testmf
如果你的程序没有问题的话,就应该可以执行了./testmf
    这是一个很简单的例子,但复杂的例子都是构建在简单功能基础上的.后面将继续介绍详细的makefile的编写方法。

  • 1
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
1.项目代码均经过功能验证ok,确保稳定可靠运行。欢迎下载体验!下载完使用问题请私信沟通。 2.主要针对各个计算机相关专业,包括计算机科学、信息安全、数据科学与大数据技术、人工智能、通信、物联网等领域的在校学生、专业教师、企业员工。 3.项目具有丰富的拓展空间,不仅可作为入门进阶,也可直接作为毕设、课程设计、大作业、初期项目立项演示等用途。 4.当然也鼓励大家基于此进行二次开发。在使用过程中,如有问题或建议,请及时沟通。 5.期待你能在项目中找到乐趣和灵感,也欢迎你的分享和反馈! 【资源说明】 基于C语言实现的一个简单的编译器项目源码+使用教程.zip 一个简单的编译器,能将fdmj语言编译成llvm ir或者RPI(树莓派) arm # 运行方法 RPI 使用`make RPI`命令生成RPI编译器 使用`./RPI xx.fmj` 生成`xx.s`文件 使用 `arm-linux-gnueabihf-gcc -mcpu=cortex-a72 xx.s libsysy.a --static -o xx.out`链接外部库并生成`xx.out`的最终代码。 使用`qemu-arm -B 0x1000 xx.out`命令运行代码。 LLVM IR 使用`make LLVM`命令生成LLVM编译器 使用`./LLVM xx.fmj` 命令生成`xx.ll`文件使用`make lib.ll`命令生成`lib.ll`文件使用`llvm-link xx.ll lib.ll -S -o xx.out`命令生成最终代码 使用`lli xx.out`命令运行代码 中间文件 AST树打印在`xx.ast`文件中 IR+代码打印在`xx.irp`文件中 基本块打印在`xx.stm`文件中 数据流图打印在`xx.liv`文件中 # 运行测试 使用带testx.fmj 和 libsys.a的tests文件替换tests文件 然后使用`make testrpi`命令进行测试 将自动生成arm代码然后执行,并echo返回值。 然后使用`make testllvm`命令进行测试 将自动生成llvm代码然后执行,并echo返回值。 使用`make clean`命令删除所有生成的文件
### 回答1: Makefile是一种用来构建和管理程序的工具,它通过一个文本文件来指导编译器如何去编译、链接程序。理解并掌握Makefile使用可以提高程序的编译效率和可维护性。下面是关于Makefile入门到精通的解释: 入门阶段: 在初学者阶段,需要掌握Makefile的基本语法和规则。学习如何编写一个简单的Makefile,并且能够使用Makefile来管理和编译一个简单的程序。掌握Makefile的目标、依赖关系、命令和变量的使用。 进阶阶段: 在进阶阶段,需要了解更多的Makefile的高级特性和技巧。学习如何使用条件语句、循环语句和函数来编写更灵活和复杂的Makefile。了解如何使用Makefile来管理多个源文件目录结构。 精通阶段: 在精通阶段,需要深入理解Makefile背后的原理和机制。了解Makefile的工作原理,包括依赖关系的自动推导、文件更新的判断和并行编译等。还需要掌握一些高级的技巧,例如使用Makefile来实现代码自动生成、跨平台编译项目的自动化构建等。 实践阶段: 在实践阶段,需要应用所学的知识来解决实际的编译和构建问题。学会分析和优化Makefile,以提高构建过程的效率和可维护性。实践中可能还需要了解如何与其他构建工具和版本控制系统进行集成,以及如何处理复杂的项目依赖关系。 总之,掌握Makefile需要从入门到精通经过一定的学习和实践过程。通过不断的学习和实践,逐渐提高对Makefile的理解和应用能力,才能真正驾驭Makefile并充分发挥其作用。 ### 回答2: Makefile是一种用于自动化编译和构建软件的脚本文件。它是基于依赖关系的构建工具,通过定义文件依赖关系和编译规则,可以自动检测源代码的变化并重新编译相关文件,从而提高软件开发的效率。 要学习Makefile,首先需要了解Makefile的基本语法和规则。Makefile由多个规则组成,每个规则包含目标文件、依赖文件命令。目标文件是生成的文件,依赖文件是生成目标文件所需的源文件或其他目标文件命令是生成目标文件的具体步骤。 在Makefile中,可以定义变量来存储常用的路径或编译选项,这样可以方便地在多个规则中复用。还可以使用条件判断、循环和函数等高级语法来实现更复杂的功能。 经常使用命令makemake clean和make install。make命令用于编译源代码并生成目标文件,如果源文件有更新,make会自动重新编译相关文件make clean命令用于清理生成的目标文件和临时文件make install命令用于安装生成的可执行文件或库文件到指定位置。 学习Makefile还需要掌握一些常用的编译规则和选项。例如,可以通过定义编译规则来指定编译器编译选项和链接选项。可以使用特殊变量$@表示目标文件,$^表示所有的依赖文件,$<表示第一个依赖文件。还可以使用通配符和模式匹配来处理多个文件目录。 除了基本的语法和规则,还可以学习一些高级的技巧和技术,例如使用配置文件、自动化测试、并行编译等。通过不断实践和积累经验,可以逐渐提高对Makefile的掌握程度,从入门到精通。 总结来说,要从入门到精通Makefile,需要掌握基本的语法和规则,学习常用的命令和选项,并通过实际项目的练习来加深理解和提高技能水平。 ### 回答3: makefile是一种用于自动化构建和管理项目的工具。它可以根据一组规则,自动推断出需要重新编译文件,并且根据这些规则自动执行相应的指令,从而简化了项目的构建流程和维护工作。 首先,我们需要了解makefile的基本语法和组成部分。makefile由一系列规则组成,每个规则包含一个目标、依赖关系和执行的指令。目标是需要生成的文件,依赖关系是该文件生成所依赖的文件指令则是实际执行的操作。 其次,学习makefile中的变量。变量可以用于存储常用的路径、命令等信息。使用变量可以简化项目配置和维护,提高可维护性。 然后,理解makefile中的模式规则。模式规则是一种通用的规则,可以根据目标和依赖的模式,推断出需要执行的指令使用模式规则可以减少重复的规则定义,提高makefile的灵活性。 接下来,学习条件判断和循环控制。条件判断可以根据不同的情况选择执行不同的指令,循环控制可以重复执行某些指令。这些功能可以使makefile更加灵活和自动化。 最后,掌握makefile中的常用函数和命令makefile提供了一系列内置函数和命令,用于处理文本、路径、文件等操作。熟练掌握这些函数和命令,可以提高makefile的处理能力和效率。 总结而言,要精通makefile需要掌握其基本语法、规则定义、变量使用、模式规则、条件判断、循环控制、内置函数和命令等知识。通过实践和不断学习,掌握这些内容后,就可以灵活运用makefile来构建和管理项目,提高开发效率和代码质量。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值