当前位置: 首页 > news >正文

Go基础11-理解Go语言的包导入

Go语言是使用包(package)作为基本单元来组织源码的,可以说一个Go程序就是由一些包链接在一起构建而成的。虽然与Java、Python等语言相比这算不上什么创新,但与祖辈C语言的头文件包含机制相比则是“先进”了许多。

编译速度快是这种“先进性”的一个突出表现,即便每次编译都是从零开始。Go语言的这种以包为基本构建单元的构建模型使依赖分析变得十分简单,避免了C语言那种通过头文件分析依赖的巨大开销。

Go编译速度快的原因具体体现在以下三方面。

● Go要求每个源文件在开头处显式地列出所有依赖的包导入,这样Go编译器不必读取和处理整个文件就可以确定其依赖的包列表。

● Go要求包之间不能存在循环依赖,这样一个包的依赖关系便形成了一张有向无环图。由于无环,包可以被单独编译,也可以并行编译。

● 已编译的Go包对应的目标文件(file_name.o或package_name.a)中不仅记录了该包本身的导出符号信息,还记录了其所依赖包的导出符号信息。

这样,Go编译器在编译某包P时,针对P依赖的每个包导入(比如导入包Q),只需读取一个目标文件即可(比如:Q包编译成的目标文件中已经包含Q包的依赖包的导出信息),而无须再读取其他文件中的信息。
Go语言中包的定义和使用十分简单。通过package关键字声明Go源文件所属的包:

// xx.go
package a
...
上述源码表示文件xx.go是包a的一部分。
使用import关键字导入依赖的标准库包或第三方包:
package mainimport ("fmt" // 标准库包导入"a/b/c" // 第三方包导入
)
func main() {
c.Func1()
fmt.Println("Hello, Go!")
}

很多Gopher看到上面的代码会想当然地将import后面的“c”“fmt”与c.Func1()和fmt.Println()中的c和fmt认作同一个语法元素:包名。但在深入学习Go语言后,大家会发现事实并非如此。

比如在使用实时分布式消息框架nsq提供的官方client包时,我们的包导入是写成这样的:

import "github.com/nsqio/go-nsq"

但在使用该包提供的导出函数时,我们使用的不是go-nsq.xx而是

nsq.xxx:
q, _ := nsq.NewConsumer("write_test", "ch", config)

很多Gopher在学习Go包导入时或多或少有些疑惑:import后面路径中的最后一个分段到底代表的是什么?是包名还是一个路径?

在本条中我就和大家一起来深入探究和理解一下Go语言的包导入。

Go程序构建过程

我们先来简单了解一下Go程序的构建过程,作为后续理解Go包导入的前导知识。和主流静态编译型语言一样,Go程序的构建简单来讲也是由**编译(compile)和链接(link)**两个阶段组成的。

一个非main包在编译后会对应生成一个.a文件,该文件可以理解为Go包的目标文件,
该目标文件实际上是通过pack工具( G O R O O T / p k g / t o o l / d a r w i n a m d 64 / p a c k )对 . o 文件打包后形成的。默认情况下,在编译过程中 . a 文件生成在临时目录下,除非使用 g o i n s t a l l 安装到 GOROOT/pkg/tool/darwin_amd64/pack)对.o文件打 包后形成的。默认情况下,在编译过程中.a文件生成在临时目录下,除非使用go install 安装到 GOROOT/pkg/tool/darwinamd64/pack)对.o文件打包后形成的。默认情况下,在编译过程中.a文件生成在临时目录下,除非使用goinstall安装到GOPATH/pkg下(Go 1.11版本之前),否则你看不到.a文件。如果是构建可执行程序,那么.a文件会在构建可执行程序的链接阶段起使用。

标准库包的源码文件在 G O R O O T / s r c 下面,而对应的 . a 文件存放在 GOROOT/src下面,而对应的.a文件存放在 GOROOT/src下面,而对应的.a文件存放在GOROOT/pkg/
darwin_amd64下(以macOS为例;如果是Linux系统,则是linux_amd64):

// Go 1.16
$tree -FL 1 $GOROOT/pkg/darwin_amd64
├── archive/
├── bufio.a
├── bytes.a
├── compress/
├── container/
├── context.a
├── crypto/
├── crypto.a
...

“求甚解”的读者可能会提出这样一个问题:构建Go程序时,编译器会重新编译依赖包的源文件还是直接链接包的.a文件呢?下面通过一个实验来给大家答案。

Go 1.10版本引 入了build cache,为了避免build cache给实验过程和分析带来的复杂性,我们使用Go1.9.7版本来进行这个实验。
我们建立实验环境的目录结构如下:

chapter3-demo1
├── cmd/
│ └── app1/
│ └── main.go
└── pkg/
└── pkg1/
└── pkg1.go

由于仅是为了演示,pkg1.go和main.go的源码都很简单:

// cmd/app1/main.go
package main
import (
"github.com/bigwhite/effective-go-book/chapter3-demo1/pkg/pkg1"
)
func main() {
pkg1.Func1()
}
// pkg/pkg1/pkg1.go
package pkg1
import "fmt"
func Func1() {
fmt.Println("pkg1.Func1 invoked")
}

进入chapter3-demo1,执行下面的命令:

$go install github.com/bigwhite/effective-go-book/chapter3-demo1/pkg/pkg1

之后,我们就可以在

$GOPATH/pkg/darwin_amd64/github.com/bigwhite/effective-
go-book/chapter3-demo1/pkg下看到pkg1包对应的目标文件pkg1.a:
$ls $GOPATH/pkg/darwin_amd64/github.com/bigwhite/effective-go-book/chapter3-demo1/pkg
pkg1.a

我们继续在chapter3-demo1路径下编译可执行程序app1:

$go build github.com/bigwhite/effective-go-book/chapter3-demo1/cmd/app1

执行完上述命令后,我们会在chapter3-demo1下看到一个可执行文件app1,执行该文件:

$ls
app1* cmd/ pkg/
$./app1
pkg1.Func1 invoked

这符合我们的预期,但现在我们仍无法知道编译app1使用的到底是pkg1包的源码还是目标文件pkg1.a,因为目前它们的输出都是一致的。

修改一下pkg1.go的代码:

// pkg/pkg1/pkg1.go
package pkg1
import "fmt"
func Func1() {
fmt.Println("pkg1.Func1 invoked - Again")
}

重新编译执行app1,得到如下结果:

$go build github.com/bigwhite/effective-go-book/chapter3-demo1/cmd/app1
$./app1
pkg1.Func1 invoked - Again

究竟是路径名还是包名

通过前面的实验,我们了解到编译器在编译过程中必然要使用的是编译单元(一个包)所依赖的包的源码。

而编译器要找到依赖包的源码文件,就需要知道依赖包的源码路
径。这个路径由两部分组成:基础搜索路径和包导入路径

基础搜索路径是一个全局的设置,下面是其规则描述。
1)所有包(无论是标准库包还是第三方包)的源码基础搜索路径都包括 G O R O O T / s r c 。 2 )在上述基础搜索路径的基础上,不同版本的 G o 包含的其他基础搜索路径有不同。● G o 1.11 版本之前,包的源码基础搜索路径还包括 GOROOT/ src。 2)在上述基础搜索路径的基础上,不同版本的Go包含的其他基础搜索路径有不同。 ● Go 1.11版本之前,包的源码基础搜索路径还包括 GOROOT/src2)在上述基础搜索路径的基础上,不同版本的Go包含的其他基础搜索路径有不同。Go1.11版本之前,包的源码基础搜索路径还包括GOPATH/src。
● Go 1.11~Go 1.12版本,包的源码基础搜索路径有三种模式:
● 经典gopath模式下(GO111MODULE=off): G O P A T H / s r c 。● m o d u l e − a w a r e 模式下( G O 111 M O D U L E = o n ): GOPATH/src。 ● module-aware模式下(GO111MODULE=on): GOPATH/srcmoduleaware模式下(GO111MODULE=on):GOPATH/pkg/mod。
● auto模式下(GO111MODULE=auto):在 G O P A T H / s r c 路径下,与 g o p a t h 模式相同;在 GOPATH/src路径下,与gopath模式 相同;在 GOPATH/src路径下,与gopath模式相同;在GOPATH/src路径外且包含go.mod,与module-aware模式相同。
● Go 1.13版本,包的源码基础搜索路径有两种模式:
● 经典gopath模式下(GO111MODULE=off): G O P A T H / s r c 。● m o d u l e − a w a r e 模式下( G O 111 M O D U L E = o n / a u t o ): GOPATH/src。 ● module-aware模式下(GO111MODULE=on/auto): GOPATH/srcmoduleaware模式下(GO111MODULE=on/auto):GOPATH/pkg/mod。
● 未来的Go版本将只有module-aware模式,即只在module缓存的目录下搜索包的源码。
而搜索路径的第二部分就是位于每个包源码文件头部的包导入路径。基础搜索路径与
包导入路径结合在一起,Go编译器便可确定一个包的所有依赖包的源码路径的集合,这个
集合构成了Go编译器的源码搜索路径空间。看下面这个例子:

// p1.go
package p1
import (
"fmt"
"time"
"github.com/bigwhite/effective-go-book"
"golang.org/x/text"
"a/b/c"
"./e/f/g"
)
...

包名冲突问题

同一个包名在不同的项目、不同的仓库下可能都会存在。同一个源码文件在其包导入
路径构成源码搜索路径空间下很可能存在同名包。比如:我们有另一个chapter3-demo2,
其下也有名为pkg1的包,导入路径为github.com/bigwhite/effective-go-book/chapter3-
demo2/pkg/pkg1。如果cmd/app3同时导入了chapter3-demo1和chapter3-demo2的pkg1包,
会发生什么呢?

// cmd/app3
package main
import (
"github.com/bigwhite/effective-go-book/chapter3-demo1/pkg/pkg1"
"github.com/bigwhite/effective-go-book/chapter3-demo2/pkg/pkg1"
)
func main() {
pkg1.Func1()
}

编译一下cmd/app3:

$go build github.com/bigwhite/effective-go-book/chapter3-demo1/cmd/app3
# github.com/bigwhite/effective-go-book/chapter3-demo1/cmd/app3
./main.go:5:2: pkg1 redeclared as imported package name
previous declaration at ./main.go:4:2

我们看到的确出现了包名冲突的问题。怎么解决这个问题呢?还是用为包导入路径下
的包显式指定包名的方法:

package main
import (
pkg1 "github.com/bigwhite/effective-go-book/chapter3-demo1/pkg/pkg1"
mypkg1 "github.com/bigwhite/effective-go-book/chapter3-demo2/pkg/pkg1"
)
func main() {
pkg1.Func1()
mypkg1.Func1()
}

上面的pkg1指代的就是chapter3-demo1/pkg/pkg1下面的包,mypkg1则指代的是chapter3-demo1/pkg/pkg1下面的包。就此,包名冲突问题就轻松解决掉了。

我们通过实验进一步理解了Go语言的包导入,Gopher应牢记以下几个结论:

● Go编译器在编译过程中必然要使用的是编译单元(一个包)所依赖的包的源码;

● Go源码文件头部的包导入语句中import后面的部分是一个路径,路径的最后一个分段是目录名,而不是包名;

● Go编译器的包源码搜索路径由基本搜索路径和包导入路径组成,两者结合在一起后,编译器便可确定一个包的所有依赖包的源码路径的集合,这个集合构成了Go编译器的源码搜索路径空间;

● 同一源码文件的依赖包在同一源码搜索路径空间下的包名冲突问题可以由显式指定包名的方式解决。

相关文章:

Go基础11-理解Go语言的包导入

Go语言是使用包(package)作为基本单元来组织源码的,可以说一个Go程序就是由一些包链接在一起构建而成的。虽然与Java、Python等语言相比这算不上什么创新,但与祖辈C语言的头文件包含机制相比则是“先进”了许多。 编译速度快是这种…...

【MySQL数据库原理】在MySQL Workbench界面运行SQL代码——学生管理系统

在 MySQL Workbench 8.0 中,你可以使用以下步骤新建内容并运行 MySQL 语言代码: 1、打开 MySQL Workbench 并连接到你的 MySQL 数据库服务器。 2、在左侧的导航栏中,展开你的连接以查看数据库。选择你要在其中运行 SQL 代码的数据库。 3…...

高分三号1米分辨率飞机检测识别数据集

二、背景介绍 合成孔径雷达(Synthetic Aperture Radar, SAR) 是一种主动式的微波成像系统,它不受光照、云雾 和气候等自然条件影响,具备全天时、全天候对地 观测的能力,已成为遥感领域重要的信息获取平 台。近年来,随着遥感成像技…...

Unity 之Material 类型和 MeshRenderer 组件中的 Materials 之间有一些重要的区别

文章目录 区别代码例子 区别 在Unity中,Material 类型和 MeshRenderer 组件中的 Materials 之间有一些重要的区别。 Material 类型: Material 是 Unity 中用来定义渲染属性的资源。它包含了一系列定义了如何绘制一个对象的属性,比如颜色、纹…...

【LeetCode-简单题】977. 有序数组的平方

文章目录 题目方法一:双指针方法二: 题目 方法一:双指针 class Solution { // 方法一 :双指针public int[] sortedSquares(int[] nums) {int left 0;int right nums.length -1 ;int[] res new int[nums.length];//结果集新数组…...

【笔试强训选择题】Day39.习题(错题)解析

作者简介:大家好,我是未央; 博客首页:未央.303 系列专栏:笔试强训选择题 每日一句:人的一生,可以有所作为的时机只有一次,那就是现在!!!&#xff…...

Prometheus-Alertmanager 警报管理器-部署和设置

文章目录 一、介绍二、核心概念1 Grouping 分组2 Inhibition 抑制3 Silences 静默(静音)5 High Availability 高可用性 三、部署1 二进制方式下载配置 systemd 2 docker-compose 方式 四、配置1 配置文件介绍1.1 全局配置1.2 receiver 接收器标准接收器相…...

恒运资本:小盘股的优点?投资小盘股要注意哪些方面?

股市是一个充溢时机和危险的当地,不同出资者有不同的偏好,有的人喜爱追逐大盘蓝筹股,有的人则钟情于小盘股。那么小盘股的长处?出资小盘股要注意哪些方面?恒运资本也为我们准备了相关内容,以供参考。 小盘股…...

LeetCode:2. 两数之和

这个解题思路来自代码随想录&#xff1a;代码随想录 (programmercarl.com) class Solution { public:vector<int> twoSum(vector<int>& nums, int target) {std::unordered_map <int,int> map;for(int i 0; i < nums.size(); i) {// 遍历当前元素&am…...

OpenCV(二十四):可分离滤波

目录 1.可分离滤波的原理 2.可分离滤波函数sepFilter2D() 3.示例代码 1.可分离滤波的原理 可分离滤波的原理基于滤波器的可分离性。对于一个二维滤波器&#xff0c;如果它可以表示为水平方向和垂直方向两个一维滤波器的卷积&#xff0c;那么它就是可分离的。也就是说&#x…...

【JS进阶】防抖与节流

防抖与节流 1.防抖 1.1 为什么要防抖&#xff1f; 在项目中&#xff0c;有的操作是高频触发的&#xff0c;但是其实触发一次就好了&#xff0c;比如我们短时间内多次缩放页面&#xff0c;那么我们不应该每次缩放都去执行操作&#xff0c;应该只做一次就好。再比如说监听输入…...

【css】linear-gradient()的用法

linear-gradient() CSS函数创建一个由两种或多种颜色沿一条直线进行线性过渡的图像,其结果是<gradient>数据类型的对象,此对象是一种特殊的<image> 数据类型。 语法 /* 渐变轴为 45 度&#xff0c;从蓝色渐变到红色 */ linear-gradient(45deg, blue, red);/* 从右…...

java: 读取snakeyaml-1.26.jar各种jar包时出错; error in opening zip file

可能的问题 jar有问题idea没有权限等等其他问题。但执行后报错就是读取不了&#xff0c;还报error in opening zip file这个错。 解决问题 我的错就是jar包有问题。我先后进行了很多次把jar包位置里的东西全部删除&#xff0c;然后重新maven下载但是不管用。最后从网站上下载…...

医疗知识图谱 neo4j

开源项目&#xff1a; https://github.com/liuhuanyong/QASystemOnMedicalKG 一.效果 二.需要安装&#xff1a; pip install pyahocorasick pip install py2neo 三.需要修改&#xff1a; 需要改的点&#xff1a; 1.改连接的方式 2.改读文件的方式 MedicalGraph 运行&am…...

【LeetCode-简单题】367. 有效的完全平方数

文章目录 题目方法一&#xff1a;二分查找 题目 方法一&#xff1a;二分查找 找 1 - num 之间的 mid&#xff0c; 开方是整数 就找得到 mid&#xff0c; 不是整数自然找不到mid class Solution { // 二分查找 &#xff1b;找 1 - num 之间的mid 开方是整数 就找得到 不是…...

vben-admin中渲染table表格时怎么处理不同的数据结构

最近在用vben admin开发后台管理系统&#xff0c;vben admin这个后管端框架封装的非常细&#xff0c;颗粒度非常细&#xff0c;如果了解里面的组件或者api用法&#xff0c;那开发起来非常快。如果不了解&#xff0c;那就非常痛苦了&#xff0c;目前关于vben admin这块的开发问题…...

从零开始在树莓派上搭建WordPress博客网站并实现公网访问

文章目录 序幕概述1. 安装 PHP2. 安装MySQL数据库3. 安装 Wordpress4. 设置您的 WordPress 数据库设置 MySQL/MariaDB创建 WordPress 数据库 5. WordPress configuration6. 将WordPress站点发布到公网安装相对URL插件修改config.php配置 7. 支持好友链接样式8. 定制主题 序幕 …...

Go基础18-理解方法的本质以选择正确的receiver类型

Go语言虽然不支持经典的面向对象语法元素&#xff0c;比如类、对象、继承等&#xff0c;但Go语言也有方法。和函数相比&#xff0c;Go语言中的方法在声明形式上仅仅多了一个参数&#xff0c;Go称之为receiver参数。receiver参数是方法与类型之间的纽带。 Go方法的一般声明形式…...

Go基础12-理解Go语言表达式的求值顺序

Go语言在变量声明、初始化以及赋值语句上相比其先祖C语言做了一些改进&#xff0c;诸如&#xff1a; ● 支持在同一行声明和初始化多个变量&#xff08;不同类型也可以&#xff09; var a, b, c 5, "hello", 3.45 a, b, c : 5, "hello", 3.45 // 短变量…...

OJ练习第165题——修车的最少时间

修车的最少时间 力扣链接&#xff1a;2594. 修车的最少时间 题目描述 给你一个整数数组 ranks &#xff0c;表示一些机械工的 能力值 。ranksi 是第 i 位机械工的能力值。能力值为 r 的机械工可以在 r * n2 分钟内修好 n 辆车。 同时给你一个整数 cars &#xff0c;表示总…...

纯前端实现 导入 与 导出 Excel

最近经常在做 不规则Excel的导入&#xff0c;或者一些普通Excel的导出&#xff0c;当前以上说的都是纯前端来实现&#xff1b;下面我们来聊聊经常用到的Excel导出与导入的实现方案&#xff0c;本文实现技术栈以 Vue2 JS 为例 导入分类&#xff1a; 调用 API 完全由后端来解析数…...

关于一次两段式提交和数据库恢复数据我的一些想法

binlog是服务层的功能&#xff0c;而redolog是innodb引擎的功能&#xff0c;binlog主要用于主从复制&#xff0c;redolog主要用做数据的恢复&#xff0c;我们必须保证binlog和redolog日志数据的一致性。恢复数据时也必须遵守此一致性。 1.如果只写一次redolog会出现什么问题&a…...

阿里巴巴springcloud的gateway网关如何用继承接口WebExceptionHandler定义一个json格式的404错误页面实例

如果你想通过实现 WebExceptionHandler 接口来定义一个返回 JSON 格式的 404 错误页面的实例&#xff0c;可以按照以下方式操作&#xff1a; import org.springframework.core.annotation.Order; import org.springframework.http.HttpStatus; import org.springframework.ster…...

『力扣每日一题07』字符串最后一个单词的长度

气死我啦&#xff0c;今天这道题花了快一个小时&#xff0c;我学完了答案的解法&#xff0c;放上去在线 OJ &#xff0c;一直报错&#xff0c;找来找去都找不到自己错在哪&#xff0c;明明跟答案一模一样。后来还是学了另一种解法&#xff0c;才跑出来的(̥̥̥̥̥̥̥̥o̥̥…...

成都睿趣科技:抖音开店初期要注意什么

随着社交媒体和短视频平台的崛起&#xff0c;抖音已经成为了一个风靡全球的短视频应用&#xff0c;拥有着庞大的用户群体。因此&#xff0c;越来越多的创业者开始在抖音上开设自己的线上店铺&#xff0c;希望借助这个平台赚取丰厚的利润。然而&#xff0c;在抖音开店初期&#…...

QT 5.13保姆级安装教程

辨清关系 要想学习一个新的东西,我们必须知其事,达其理,悟其道,然后才能无往而不利也! 我们常听到QT、Qt Creator 和 Qt SDK ,这三者究竟是什么,他们之间的关系又是如何的?在安装QT之前我们先来了解一下他们之间的关系: Qt:Qt 是一个跨平台的 C++ 应用程序开发框架,…...

js 创建DOM,并添加父DOM上,移除某个DOM的所有子节点

在sectionIdDiv上&#xff0c;添加子DOM <div ref"sectionIdDiv" class"sectionIdDiv"> </div>创建你要添加的子DOM ## 创建DOM let elementDom document.createElement(div)## 设置DOM的样式 elementDom.style.height "15px" e…...

element el-input 二次封装

说明&#xff1a;为实现输入限制&#xff0c;不可输入空格&#xff0c;长度限制。 inputView.vue <template><!-- 输入框 --><el-input:type"type":placeholder"placeholder"v-model"input"input"inputChange":maxle…...

[源码系列:手写spring] IOC第十三节:Bean作用域,增加prototype的支持

为了帮助大家更深入的理解bean的作用域&#xff0c;特意将BeanDefinition的双例支持留到本章节中&#xff0c;创建Bean,相关Reader读取等逻辑都有所改动。 内容介绍 在Spring中&#xff0c;Bean的作用域&#xff08;Scope&#xff09;定义了Bean的生命周期和可见性。包括单例和…...

【性能优化】事件委托

一、为什么要用事件委托 当 dom 有事件处理程序时&#xff0c;我们一般都会直接给它设置事件处理程序&#xff0c;设想一下&#xff0c;如果在一个父元素中有很多个 dom 需要添加事件处理呢&#xff1f;比如 ul 中处在100个 li&#xff0c;每个 li 都有相同的 click 事件&…...

安卓开发技术/seo站长工具综合查询

一、数据类型1、列表&#xff1a;# 列表中的每个元素都是可变的# 列表的元素是有序的&#xff0c;# 列表用中括号表示ab [ ]# ab.insert(‘位置’&#xff0c;‘元素‘) ## 在指定位置插入某个元素# ab.append() # 在末尾增加元素# ab.remove(1) # 删除‘1‘这个元素# ab.po…...

做网站的挣钱么/seo刷词

作为入门级L2的升级版&#xff0c;NOA&#xff08;自动辅助导航驾驶&#xff0c;从A点到B点&#xff09;是近年来不少车企主打的亮点组合功能。同时&#xff0c;通过增加激光雷达的感知冗余&#xff0c;一些车企也在推动从高速场景向城区场景的落地。 按照行业内通用的功能定义…...

网站top排行榜/网页推广怎么做的

基本配置&#xff1a; Configuration public class RestConfig {Beanpublic RestClient getClient() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException {// 如果有多个从节点可以持续在内部new多个HttpHost&#xff0c;参数1是ip,参数2是HTTP端口…...

做网站和网站维护需要多少钱/如何制作一个网页页面

1、ctrln 新建对话框&#xff08;针对画布进行设置&#xff09; 设置完毕后敲回车 2、ctrlo (字母) 打开素材对话框 3、画布的三种显示方式切换&#xff1a;f 4、隐藏工具箱、工具属性栏、悬浮面板&#xff1a;Tab 5、缩放工具&#xff1a;z 缩放后恢复到实际大小&#xf…...

上海做什么工作最赚钱/关键词排名优化流程

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼全国计算机二级考试大题把题目给定程序删除了&#xff0c;正确运行&#xff0c;这样会不会给分&#xff1f;50、请编写函数fun, 函数的功能是: 将M行N列的二维数组中的数据, 按列的顺序依次放到一维数组中。函数fun中给出的语句仅供…...

阿里云镜像wordpress/广州网络推广平台

130年来&#xff0c; 以千里挑一的苛刻&#xff0c; 记录下世界各个角落的绝美风景。 国家地理杂志的拍摄者 深入常人难以到达的环境&#xff0c; 冒着重重危险拍下的每一帧&#xff0c; 都承载着所有摄影记者的梦想。 37000英尺的高空&#xff0c; 太平洋上巨大的积雨云。 肯尼…...