简介




compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'

在对Android 开发中,我们都是从原生的 HttpUrlConnection到经典的 Apache公司的HttpClient,再到对前面这些网络基础框架的封装,比如Volley、AsyncHttpClient等。Retrofit是一个 RESTful (一种架构风格)的 HTTP 网络请求框架的封装。注意这里并没有说它是网络请求框架,主要原因在于网络请求的工作并不是Retrofit来完成的。Retrofit2.0 内置OkHttp,前者专注于接口的封装,后者专注于网络请求的高效,二者分工协作!我们的应用程序通过 Retrofit请求网络,实际上是使用Retrofit接口层封装请求参数、Header、Url 等信息,之后由OkHttp完成后续的请求操作,在服务端返回数据之后,OkHttp将原始的结果交给Retrofit,后者根据用户的需求对结果进行解析的过程。

Retrofit 2.0版本中一些引人注意的地方。
在Retrofit 2.0中,最大的改动莫过于减小库的体积。
首先,Retrofit 2.0去掉了对所有的HTTP客户端的兼容,而钟情于OkHttpClient一个,极大地减少了各种适配代码;
其次,拆库,比如将对RxJava的支持设置为可选(需要额外引入库);
再者,将各个序列化反序列化转换器支持设置为可选(需要额外引入库)。

于2.0抛弃HttpClient和HttpURLConnection,为了减小库体积是一方面,另外一个重要的原因作为一个专门为Android&Java 应用量身打造的Http客户端,OkHttpClient越来越受到各个大的开源项目的青睐。此外,OkHttpClient与HttpClient相比,它最大的改进是自带工作线程池,所以上层应用无需自己去维护复杂的并发模型,而HttpClient仅仅提供了一个线程安全的类,所以还需要上层应用去处理并发的逻辑(实际上Volley一部分工作就是干这个)。从这个角度来说,Retrofit得益于OkHttpClient的优势,较之于Volley是一种更加先进的网络框架。

由于Retrofit不需要去关心并发工作线程的维护,所以它可以全力关注于如何精简发送一个请求的代价!

官方文档

【一、Introduction】
Retrofit turns your HTTP API into a Java interface.
Retrofit可以将你的HTTP API转化为JAVA的接口的形式
  1. public interface GitHubService {
  2. @GET("users/{user}/repos")
  3. Call<List<Repo>> listRepos(@Path("user") String user);
  4. }
The Retrofit class generates an implementation of the GitHubService interface.
而Retrofit类能够生成对应接口的实现
  1. Retrofit retrofit = new Retrofit.Builder()
  2. .baseUrl("https://api.github.com/")
  3. .build();
  4. GitHubService service = retrofit.create(GitHubService.class);
Each Call from the created GitHubService can make a synchronous or asynchronous HTTP request to the remote webserver.
每一个由接口返回的Call对象都可以与远程web服务端进行同步或者异步的HTTP请求通信。
  1. Call<List<Repo>> repos = service.listRepos("octocat");
Use annotations to describe the HTTP request:
Retrofit使用注解来描述HTTP请求:
  • URL parameter replacement and query parameter support。URL参数的替换和query参数的支持
  • Object conversion to request body (e.g., JSON, protocol buffers)。对象转化为请求体(如:JSON,protocol buffers等)
  • Multipart request body and file upload。多重请求体和文件上传



【二、API Declaration】
Annotations on the interface methods and its parameters indicate how a request will be handled.
Retrofit需要注解接口的请求方法和方法的参数来表明该请求需要怎么样的处理。

【2.1、REQUEST METHOD 请求方法】
Every method must have an HTTP annotation that provides the request method and relative URL. There are five built-in annotations: GET, POST, PUT, DELETE, and HEAD. The relative URL of the resource is specified in the annotation.
每一个方法必须要有一个HTTP注解来标明请求的方式和相对URL。有五种内置的注解方式:GET、POST、PUT、DELETE以及HEAD。资源的相对URL需要在注解里面明确给出:
  1. @GET("users/list")
You can also specify query parameters in the URL.
当然你也可以将query参数直接写在URL里:
  1. @GET("users/list?sort=desc")

【2.2、URL MANIPULATION  URL操作】
A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }. A corresponding parameter must be annotated with @Path using the same string.
一个请求的URL可以通过替换块和请求方法的参数来进行动态的更新。替换块是由被{}包裹起来的数字或字母组成的字符串构成的,相应的方法参数需要由@Path来注解同样的字符串。
  1. @GET("group/{id}/users")
  2. Call<List<User>> groupList(@Path("id") int groupId);
Query parameters can also be added.
Query参数也能同时添加。
  1. @GET("group/{id}/users")
  2. Call<List<User>> groupList(@Path("id") int groupId, @Query("sort") String sort);
For complex query parameter combinations a Map can be used.
复杂的query参数可以用Map来构建
  1. @GET("group/{id}/users")
  2. Call<List<User>> groupList(@Path("id") int groupId, @QueryMap Map<String, String> options);

【2.3、REQUEST BODY  请求体】
An object can be specified for use as an HTTP request body with the @Body annotation.
能够通过@Body注解来指定一个方法作为HTTP请求主体
  1. @POST("users/new")
  2. Call<User> createUser(@Body User user);
The object will also be converted using a converter specified on the Retrofit instance. If no converter is added, only RequestBody can be used.
这个参数对象会被Retrofit实例中的converter进行转化。如果没有给Retrofit实例添加任何converter的话,则只有RequestBody可以作为参数使用。

【2.4、FORM ENCODED AND MULTIPART】
Methods can also be declared to send form-encoded and multipart data.
方法也可以通过声明来发送form-encoded和multipart类型的数据。
Form-encoded data is sent when @FormUrlEncoded is present on the method. Each key-value pair is annotated with @Field containing the name and the object providing the value.
可以通过@FormUrlEncoded注解方法来发送form-encoded的数据。每个键值对需要用@Filed来注解键名,随后的对象需要提供值。
  1. @FormUrlEncoded
  2. @POST("user/edit")
  3. Call<User> updateUser(@Field("first_name") String first, @Field("last_name") String last);
Multipart requests are used when @Multipart is present on the method. Parts are declared using the @Part annotation.
也可以通过@Multipart注解方法来发送Mutipart请求。每个部分需要使用@Part来注解。
  1. @Multipart
  2. @PUT("user/photo")
  3. Call<User> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);
Multipart parts use one of Retrofit's converters or they can implement RequestBody to handle their own serialization.
多个请求部分需要使用Retrofit的converter或者是自己实现 RequestBody来处理自己内部的数据序列化。

【2.5、HEADER MANIPULATION  头部操作】
You can set static headers for a method using the @Headers annotation.
你可以通过使用@Headers注解来设置请求静态头。
  1. @Headers("Cache-Control: max-age=640000")
  2. @GET("widget/list")
  3. Call<List<Widget>> widgetList();
  1. @Headers({
  2. "Accept: application/vnd.github.v3.full+json",
  3. "User-Agent: Retrofit-Sample-App"
  4. })
  5. @GET("users/{username}")
  6. Call<User> getUser(@Path("username") String username);
Note that headers do not overwrite each other. All headers with the same name will be included in the request.
注意的是头部参数并不会相互覆盖,同一个名称的所有头参数都会被包含进请求里面。
A request Header can be updated dynamically using the @Header annotation. A corresponding parameter must be provided to the @Header. If the value is null, the header will be omitted. Otherwise, toString will be called on the value, and the result used.
当然你可以通过 @Header 注解来动态更新请求头。一个相应的参数必须提供给 @Header 注解。如果这个值是空(null)的话,那么这个头部参数就会被忽略。否则的话, 值的 toString 方法将会被调用,并且使用调用结果。
  1. @GET("user")
  2. Call<User> getUser(@Header("Authorization") String authorization)
Headers that need to be added to every request can be specified using an OkHttp interceptor.
当然你可以通过OkHttp interceptor来指定每一个需要的头部参数。

【2.6、SYNCHRONOUS VS. ASYNCHRONOUS  同步 VS 异步】
Call instances can be executed either synchronously or asynchronously. Each instance can only be used once, but calling clone() will create a new instance that can be used.
你可以同步或者异步地来执行实例。每个示例只能使用一次,但是可以使用 clone() 来创建一个可以使用的新的实例。
On Android, callbacks will be executed on the main thread. On the JVM, callbacks will happen on the same thread that executed the HTTP request.
在Android环境中,callback将会在主线程中执行;而在JVM环境中,callback将会在和Http请求的同一个线程中执行。



【三、Retrofit Configuration  配置】
Retrofit is the class through which your API interfaces are turned into callable objects. By default, Retrofit will give you sane defaults for your platform but it allows for customization.
Retrofit类会通过你定义的API接口转化为可调用的对象。默认情况下,Retrofit会返还给你合理的默认值,但也允许你进行指定。

【3.1、CONVERTERS  转化器】
By default, Retrofit can only deserialize HTTP bodies into OkHttp's ResponseBody type and it can only accept its RequestBody type for @Body.
默认情况下,Retrofit只能将HTTP体反序列化为OKHttp的 ResonseBody 类型,而且只能接收 RequestBody类型作为 @Body。
Converters can be added to support other types. Six sibling modules adapt popular serialization libraries for your convenience.
转化器的加入可以用于支持其他的类型。以下六个同级模块采用了常用的序列化库来为你提供方便。
  • Gson: com.squareup.retrofit2:converter-gson
  • Jackson: com.squareup.retrofit2:converter-jackson
  • Moshi: com.squareup.retrofit2:converter-moshi
  • Protobuf: com.squareup.retrofit2:converter-protobuf
  • Wire: com.squareup.retrofit2:converter-wire
  • Simple XML: com.squareup.retrofit2:converter-simplexml
  • Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
Here's an example of using the GsonConverterFactory class to generate an implementation of the GitHubService interface which uses Gson for its deserialization.
下面提供一个使用GsonConverterFactory类生成 GitHubService的接口实现gson反序列化的例子。
  1. Retrofit retrofit = new Retrofit.Builder()
  2. .baseUrl("https://api.github.com")
  3. .addConverterFactory(GsonConverterFactory.create())
  4. .build();
  5. GitHubService service = retrofit.create(GitHubService.class);

【3.2、CUSTOM CONVERTERS  自定义转化器】
If you need to communicate with an API that uses a content-format that Retrofit does not support out of the box (e.g. YAML, txt, custom format) or you wish to use a different library to implement an existing format, you can easily create your own converter. Create a class that extends the Converter.Factory class and pass in an instance when building your adapter.
如果你需要与没有使用Retrofit提供的内容格式的API进行交互的话或者是你希望使用一个不同的库来实现现有的格式,你也可以轻松创建使用自己的转化器。你需要创建一个继承自Converter.Factory的类并且在构建适配器的时候加入到实例里面。



【四、Download】
The source code to the Retrofit, its samples, and this website is available on GitHub.
源代码、例子和网站在 https://github.com/square/retrofit 
 
MAVEN
  1. <dependency>
  2. <groupId>com.squareup.retrofit2</groupId>
  3. <artifactId>retrofit</artifactId>
  4. <version>2.1.0</version>
  5. </dependency>

GRADLE
  1. compile 'com.squareup.retrofit2:retrofit:2.1.0'

Retrofit requires at minimum Java 7 or Android 2.3.
Retrofit支持最低 Java7 和 Android 2.3

PROGUARD
If you are using Proguard in your project add the following lines to your configuration:
如果你的工程中使用了代码混淆,那么你的配置中需要添加一下的几行
  1. # Platform calls Class.forName on types which do not exist on Android to determine platform.
  2. -dontnote retrofit2.Platform
  3. # Platform used when running on RoboVM on iOS. Will not be used at runtime.
  4. -dontnote retrofit2.Platform$IOS$MainThreadExecutor
  5. # Platform used when running on Java 8 VMs. Will not be used at runtime.
  6. -dontwarn retrofit2.Platform$Java8
  7. # Retain generic type information for use by reflection by converters and adapters.
  8. -keepattributes Signature
  9. # Retain declared checked exceptions for use by a Proxy instance.
  10. -keepattributes Exceptions
2016-11-12

简单使用示例

retrofit在使用的过程中,需要定义一个接口对象,用于描述程序中都需要什么请求操作,如:
public interface IPhoneService {
    //通过@GET注解标识为get请求,@GET中所填写的value和baseUrl组成完整的路径,baseUrl在构造retrofit对象时给出。
    @GET("/apistore/mobilenumber/mobilenumber")
    Call<PhoneResult> getResult(@Header("apikey") String apikey, @Query("phone") String phone);
}
下面看如何通过retrofit完成上述的请求:
        Retrofit retrofit = new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create()).baseUrl(BASE_URL).build();
        //Converter.Factory用于对象转化,本例因为服务器返回的是json格式的数组,所以这里设置了GsonConverterFactory完成对象的转化  
        IPhoneService service = retrofit.create(IPhoneService.class);
        Call<PhoneResult> call = service.getResult(API_KEY, NUMBER);
        call.enqueue(new Callback<PhoneResult>() {//异步请求
            @Override
            public void onResponse(Call<PhoneResult> call, Response<PhoneResult> response) {
                if (response.isSuccessful()) {
                    PhoneResult result = response.body();
                    if (result != null) {
                        textView.setText(result.toString());
                        Toast.makeText(MainActivity.this, result.getRetData().getCity(), Toast.LENGTH_SHORT).show();
                    }
                }
            }
            @Override
            public void onFailure(Call<PhoneResult> call, Throwable t) {
                Toast.makeText(MainActivity.this, "失败", Toast.LENGTH_SHORT).show();
            }
        });
这里可以看到很神奇,我们通过retrofit.create就可以拿到我们定义的IPhoneService的实例,调用其方法即可拿到一个Call对象,通过call.enqueue即可完成异步的请求。
这里需要指出的是:
  • 接口中的方法必须有返回值,且必须是Call<T>类型
  • .addConverterFactory(GsonConverterFactory.create())这里如果使用gson,需要额外导入【compile 'com.squareup.retrofit2:converter-gson:2.0.2'】
  • 当然除了gson以外,还提供了以下的选择:(详见【3.1、CONVERTERS  转化器】),当然也支持自定义,你可以选择自己写转化器完成数据的转化。
  • 既然call.enqueue是异步的访问数据,那么同步的访问方式为call.execute,这一点非常类似okhttp的API,实际上默认情况下内部也是通过okhttp3.Call实现。

MainActivity

public class MainActivity extends Activity {
    private static final String BASE_URL = "http://apis.baidu.com";
    private static final String API_KEY = "";
    private static final String NUMBER = "18680536603";
    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        textView = new TextView(this);
        setContentView(textView);
        Retrofit retrofit = new Retrofit.Builder().addConverterFactory(GsonConverterFactory.create()).baseUrl(BASE_URL).build();
        //Converter.Factory用于对象转化,本例因为服务器返回的是json格式的数组,所以这里设置了GsonConverterFactory完成对象的转化
        IPhoneService service = retrofit.create(IPhoneService.class);
        Call<PhoneResult> call = service.getResult(API_KEY, NUMBER);
        call.enqueue(new Callback<PhoneResult>() {//异步请求
            @Override
            public void onResponse(Call<PhoneResult> call, Response<PhoneResult> response) {
                if (response.isSuccessful()) {
                    PhoneResult result = response.body();
                    if (result != null) {
                        textView.setText(result.toString());
                        Toast.makeText(MainActivity.this, result.getRetData().getCity(), Toast.LENGTH_SHORT).show();
                    }
                }
            }
            @Override
            public void onFailure(Call<PhoneResult> call, Throwable t) {
                Toast.makeText(MainActivity.this, "失败", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

接口

public interface IPhoneService {
    //每一个方法必须要有一个HTTP注解来标明请求的方式和【相对URL】;有五种内置的注解方式:GET、POST、PUT、DELETE以及HEAD
    //通过@GET注解标识为get请求,@GET中所填写的value和baseUrl组成完整的路径,baseUrl在构造retrofit对象时给出,相对URL需要在注解里面明确给出
    @GET("group/{id}/users")
    //一个请求的URL可以通过替换块和请求方法的参数来动态的更新,替换块是由被{}包裹起来的字符串构成的,相应的方法参数需要由@Path来注解同样的字符串
    //这里你可以把{id}当做占位符,而实际运行中会通过@PATH("id")所标注的参数进行替换
    Call<List<PhoneResult>> groupList(@Path("id") int groupId);


    //注解@Query用于一般的传参,当然你也可以将query参数直接写在@GET注解中的相对URL里
    @GET("group/{id}/users")
    Call<List<PhoneResult>> groupList(@Path("id") int groupId, @Query("sort") String sort);


    //复杂的query参数可以用Map来构建
    @GET("group/{id}/users")
    Call<List<PhoneResult>> groupList(@Path("id") int groupId, @QueryMap Map<String, String> options);


    //能够通过@Body注解来指定一个方法作为HTTP请求主体
    @POST("users/new")
    Call<PhoneResult> createUser(@Body PhoneResult result);


    //发送form-encoded的数据(即:以表单的方式传递简单的键值对)。每个键值对需要用@Filed来注解键名,随后的对象需要提供值
    @FormUrlEncoded
    @POST("user/edit")
    Call<PhoneResult> updateUser(@Field("first_name") String first, @Field("last_name") String last);

    //也可以通过@Multipart注解方法来发送Mutipart请求(多文件上传)。每个部分需要使用@Part来注解。
    @Multipart
    @PUT("user/photo")
    Call<PhoneResult> updateUser(@Part("photo") RequestBody photo, @Part("description") RequestBody description);


    //@PartMap用于标识一个Map,Map的key为String类型,代表上传的键值对的key,value即为RequestBody,有点类似@Part的封装版本。
    //可以在Map中put进一个或多个文件,键值对等,当然你也可以分开,单独的键值对也可以使用@Part
    @Multipart
    @POST("register")
    Call<PhoneResult> registerUser(@PartMap Map<String, RequestBody> params, @Part("password") RequestBody password);


    //可以通过使用@Headers注解来设置请求静态头。
    @Headers("Cache-Control: max-age=640000")
    @GET("widget/list")
    Call<List<PhoneResult>> widgetList();


    //注意的是头部参数并不会相互覆盖,同一个名称的所有头参数都会被包含进请求里面。
    @Headers({ "Accept: application/vnd.github.v3.full+json", "User-Agent: Retrofit-Sample-App" })
    @GET("users/{username}")
    Call<PhoneResult> getUser(@Path("username") String username);


    //当然你可以通过 @Header 注解来动态更新请求头。一个相应的参数必须提供给 @Header 注解。
    //如果这个值是空(null)的话,那么这个头部参数就会被忽略。否则的话, 值的 toString 方法将会被调用,并且使用调用结果。
    @GET("user")
    Call<PhoneResult> getUser2(@Header("Authorization") String authorization);


    //真实案例中使用到的方法
    @GET("/apistore/mobilenumber/mobilenumber")
    Call<PhoneResult> getResult(@Header("apikey") String apikey, @Query("phone") String phone);
}

配置

AndroidStudio

eclipse

<uses-permission android:name="android.permission.INTERNET" />

配置OkHttpClient
这个需要简单提一下,很多时候,比如你使用retrofit需要统一的log管理,给每个请求添加统一的header等,这些都应该通过okhttpclient去操作。你可以单独写一个OkhttpClient的单例生成类,在这个里面完成你所需的所有的配置,然后将OkhttpClient实例通过方法公布出来,设置给retrofit。比如
    OkHttpClient client = new OkHttpClient.Builder().addInterceptor(new Interceptor() {//log,统一的header等你需要的配置
        @Override
        public okhttp3.Response intercept(Chain chain) throws IOException {
            return null;
        }
    }).build();  
Retrofit retrofit = new Retrofit.Builder().callFactory(client ).build();
callFactory方法接受一个okhttp3.Call.Factory对象,OkHttpClient即为一个实现类。

附件列表

Retrofit2 简介 语法 案例的更多相关文章

  1. RxJava RxPermissions 动态权限 简介 原理 案例 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  2. nodeJs学习-10 模板引擎 ejs语法案例

    ejs语法案例 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <t ...

  3. 关于Ruby常用语法案例累积

    变量问题: 类变量和方法变量的区别是什么? 类变量:可以直接使用 方法变量:需要实例化后,才能使用该变量 案例一: class Person @@name = "Tom" @@na ...

  4. Shell_Shell调用SQLPlus简介(案例)

    2014-06-20  Created By BaoXinjian

  5. CSS_简介/语法结构/长度单位/应用方式/标签的样式重置/表单样式重置

    一.CSS简介:  w3c(World Wide Web Consortium):万维网联盟,是规定网页标准的一个组织(叫做Web标准) Web标准:是由w3c和其他标准化组织制定的一系列标准的集合, ...

  6. Sass入门——简介+语法格式及编译调试

    本文来自慕课网大漠. Sass简介 Sass和SCSS区别 1.后缀名不同,很好理解 2.Sass以严格的缩进语法规则书写,不带大括号和分号:而SCSS的语法规则和CSS的语法很类似. Sass: $ ...

  7. ch1-vuejs基础入门(hw v-bind v-if v-for v-on v-model 应用组件简介 小案例)

    1 hello world 引入vue.min.js 代码: ----2.0+版本 <div id="test"> {{str}} </div> <s ...

  8. Xposed 框架 hook 简介 原理 案例 MD

    Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...

  9. Lambda表达式 简介 语法 示例

    Lambda 表达式也称为闭包,是匿名类的简短形式.Lambda 表达式简化了[单一抽象方法声明接口]的使用,因此 lambda 表达式也称为功能接口. 在 Java SE 7 中,单一方法接口可使用 ...

随机推荐

  1. C#操作xml的3种方式

    C#操作Xml有很多种方式,这里写出个人常使用的三种方式 XmlDocument DataSet linq to xml  首先声明本次操作使用的xml文件:books.xml:内容如下 <?x ...

  2. 测试gcc的优化选项

    一.测试准备及原理 测试代码: static void wait(volatile unsigned long dly) { ; dly--); } int main(void) { unsigned ...

  3. SharePoint 2013 弹窗效果之本地HTML打开方式(二)

    上一篇我们主要讲述如何通过showModalDialog方法进行弹出窗体,同时弹出信息定义在新的页面(Application Page),使用 SP.UI.$create_DialogOptions( ...

  4. Canny边缘检测-Wiki

    Canny edge dector 由 John F. Canny 在1986年提出. Canny 算法的发展 Canny算法的步骤 2.1 降噪 2.2 寻找图像的亮度梯度 2.3 非极大值抑制 2 ...

  5. Unity OF 3DMax毛坯房制作标准

    Unity OF 3DMax毛坯房制作标准 1.模型 2.贴图 3.模型塌陷展UV 4.灯光 5.Radiosity 6.Render  To   Texture 7.烘焙 8.导出 1.模型回目录 ...

  6. Yaroslav and Divisors

    Codeforces Round #182 (Div. 1) D:http://codeforces.com/contest/301/problem/D 题意:给一个1-n,n个数的序列,然后查询一个 ...

  7. 中文简体windows CMD显示中文乱码解决方案

    因为重装系统,以前是英文的,现在的镜像文件是中文简体windows 10.所以只能将就使用. 下载了JDK,CMD 写了命令java,结果一堆乱码(问号???).发现System的locale默认设置 ...

  8. 【转】Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例

    原文网址:http://www.cnblogs.com/skywang12345/p/3308556.html 上一章,我们学习了Collection的架构.这一章开始,我们对Collection的具 ...

  9. Maximum Product Subarray——LeetCode

    Find the contiguous subarray within an array (containing at least one number) which has the largest ...

  10. 在mac中用终端来运行.c文件

    第一步:打开终端,位置在lauchpad中去找搜索. 第二步:建一个.c文件. 第三步: 在终端输入.c路径.用cd命令 第五步:cc -c +tab键.生成.O文件 第六步:cc +tab键.生成. ...