C++调用GDAL库读取并输出tif文件,并计算斑块面积输出景观指数:CSD
// readfile.cpp : 定义控制台应用程序的入口点。
//
/*
part of the codes were cite from: http://www.gdal.org/gdal_tutorial.html
and remaining of code were created :by www.cnblogs.com/AmatVictorialCuram/
and please mark the site if you cite the program fo commercial of publication.
*/
#include "stdafx.h"
#include <iostream>
#include "gdal.h"
#include "gdal_priv.h"
/**/
#include <iomanip>
#include <fstream>
using namespace std;
/**/
int minLabel(int a,int b); int maxLabel(int a,int b); double csd(int *Parea,int length); int main()
{
/*
part of the codes were cite from: http://www.gdal.org/gdal_tutorial.html
and remaining of code were created :by www.cnblogs.com/AmatVictorialCuram/
and please mark the site if you cite the program fo commercial of publication.
*/
const char *pszFilename="E:\\tif/fragstats/sample.tif";
GDALDataset *poDataset; GDALAllRegister(); poDataset = (GDALDataset *) GDALOpen( pszFilename, GA_ReadOnly );
if( poDataset == NULL )
{
cout<<"file read error!"<<endl;;
}
double adfGeoTransform[]; printf( "Driver: %s/%s\n",
poDataset->GetDriver()->GetDescription(),
poDataset->GetDriver()->GetMetadataItem( GDAL_DMD_LONGNAME ) ); printf( "Size is %dx%dx%d\n",
poDataset->GetRasterXSize(), poDataset->GetRasterYSize(),
poDataset->GetRasterCount() ); if( poDataset->GetProjectionRef() != NULL )
printf( "Projection is `%s'\n", poDataset->GetProjectionRef() ); if( poDataset->GetGeoTransform( adfGeoTransform ) == CE_None )
{
printf( "Origin = (%.6f,%.6f)\n",
adfGeoTransform[], adfGeoTransform[] ); printf( "Pixel Size = (%.6f,%.6f)\n",
adfGeoTransform[], adfGeoTransform[] );
} GDALRasterBand *poBand;
int nBlockXSize, nBlockYSize;
int bGotMin, bGotMax;
double adfMinMax[]; poBand = poDataset->GetRasterBand( );
poBand->GetBlockSize( &nBlockXSize, &nBlockYSize );
printf( "Block=%dx%d Type=%s, ColorInterp=%s\n",
nBlockXSize, nBlockYSize,
GDALGetDataTypeName(poBand->GetRasterDataType()),
GDALGetColorInterpretationName(
poBand->GetColorInterpretation()) ); adfMinMax[] = poBand->GetMinimum( &bGotMin );
adfMinMax[] = poBand->GetMaximum( &bGotMax );
if( ! (bGotMin && bGotMax) )
GDALComputeRasterMinMax((GDALRasterBandH)poBand, TRUE, adfMinMax); printf( "Min=%.3fd, Max=%.3f\n", adfMinMax[], adfMinMax[] ); if( poBand->GetOverviewCount() > )
printf( "Band has %d overviews.\n", poBand->GetOverviewCount() ); if( poBand->GetColorTable() != NULL )
printf( "Band has a color table with %d entries.\n",
poBand->GetColorTable()->GetColorEntryCount() );
/*****
Reading Raster Data
*****/
float *pafScanline;
int nXSize = poBand->GetXSize();
int nYSize=poBand->GetYSize(); //读取图像的nXSize*nYSize数据
pafScanline = (float*) CPLMalloc(sizeof(float)*nXSize*nYSize);//创建指针
poBand->RasterIO(
GF_Read,//第一个参数表示要读入数据还是写入数据
, ,//nXOff, nYOff表示读取或者写入图像数据的起始坐标图像的左上角坐标为(0,0)
nXSize, nYSize,/*nXSize, nYSize表示读取或者写入图像数据的窗口大小,nXSize表示宽度,
nYSize表示高度,均使用像素为单位,该宽度和高度是从第二个和第三个参数处开始计算。
这两个参数和第二第三个参数一起表示就是,读取和写入图像的窗口位置和大小。*/
pafScanline, //指向存储数据的一个指针
nXSize, nYSize,//指定缓冲区的大小
GDT_Float32,
, );
// poBand->RasterIO( GF_Read, 0, 0, nXSize, nYSize, pafScanline, nXSize, nYSize, GDT_Float32, 0, 0 );
cout<<"列数:nXSize="<<nXSize<<endl;
cout<<"行数:nYSize="<<nYSize<<endl;
//system("pause"); //创建nXSize*nYSize的float数组
float **data=new float *[nXSize];//每列有nXSize列,data数组的大小与dataLabel数组的大小相同,类型不同
int **dataLabel=new int *[nXSize];//创建标签数组,有nYSize行,nXSize列
int t=,i=,j=;
for(int i=;i<nYSize;i++)//nYSize行
{
dataLabel[i]=new int [nXSize];
data[i] = new float [nXSize];
} cout<<"输出元数据数组data:"<<endl;
for(int i=;i<nYSize;i++)
{
for(int j=;j<nXSize;j++)
{
data[i][j]=pafScanline[t++];
cout<<setw()<<data[i][j];
}
cout<<endl;
}
cout<<endl;
//system("pause");
cout<<"输出标签数组dataLabel数组:"<<endl;
for(int i=;i<nYSize;i++)
{
cout<<endl;
for(int j=;j<nXSize;j++)
{
dataLabel[i][j]=nYSize*nXSize;
cout<<setw()<<dataLabel[i][j];
}
}
//system("pause");
cout<<endl;
cout<<"联通区域标记算法开始:"<<endl;
dataLabel[][]=;//把左上角的第一个标签赋值为1
int indexcolor=;
for(i=;i<nYSize;i++)
{
for(j=;j<nXSize;j++)
{
if(i==)
{
if(j==)
continue;
if(data[i][j]==data[i][j-])
dataLabel[i][j]=dataLabel[i][j-];
else
{
dataLabel[i][j]=++indexcolor;
}
continue;
}
if(j==)
{
if(data[i][j]==data[i-][j])
{
if(dataLabel[i-][j]<dataLabel[i][j])
dataLabel[i][j]=dataLabel[i-][j];
}
if(data[i][j]==data[i-][j+])
{
if(dataLabel[i-][j+]<dataLabel[i][j])
dataLabel[i][j]=dataLabel[i-][j+];
}
if(dataLabel[i][j]==nYSize*nXSize)
{
dataLabel[i][j]=++indexcolor;
}
continue;
}
//if(edgeCheckL(i,j) && data[i][j]==data[i][j-1])//左边的,
if(j> && data[i][j]==data[i][j-])
{
if(dataLabel[i][j-]<dataLabel[i][j])
dataLabel[i][j]=dataLabel[i][j-]; }
//if(edgeCheckLU(i,j) && data[i][j]==data[i-1][j-1])//左上角的
if((i> && j>)&& data[i][j]==data[i-][j-])
{
if(dataLabel[i-][j-]<dataLabel[i][j])
dataLabel[i][j]=dataLabel[i-][j-];
}
//if(edgeCheckU(i,j) && data[i][j]==data[i-1][j])//上面的
if(i> && data[i][j]==data[i-][j])
{
if(dataLabel[i-][j]<dataLabel[i][j])
dataLabel[i][j]=dataLabel[i-][j];
}
//if(edgeCheckUR(i,j) && data[i][j]==data[i-1][j+1])//右上角的
if((i> && j<nYSize-) && data[i][j]==data[i-][j+])
{
if(dataLabel[i-][j+]<dataLabel[i][j])
dataLabel[i][j]=dataLabel[i-][j+];
}
if(dataLabel[i][j]==nYSize*nXSize)
dataLabel[i][j]=++indexcolor;
}
}
//system("pause");
cout<<endl;
cout<<"输出第一次标记数组"<<endl<<endl;
for(i=;i<nYSize;i++)
{
for(j=;j<nXSize;j++)
{
cout<<setw()<<dataLabel[i][j];
}
cout<<endl;
}
//system("pause");
cout<<"合并首次生成的标签数组。。。。"<<endl<<endl; //合并:像素值相同,但是标签不同,就把标签值大的变为小的。
for(i=;i<nYSize;i++)//行
{
for(j=;j<nXSize;j++)//列
{
if(i==)//第0行,只判左边的!
{
if(j==)//第一个元素
{
j=;//跳过第一个元素,直接从第二个元素:data[0][1]判断
//判断并执行合并:
if(data[i][j-]==data[i][j] && dataLabel[i][j-]!=dataLabel[i][j])//像素值相同 && 标签不同:合并!
{
//把所有的大标签替换为当前两个标签中的一个较小的值
//执行一次遍历
//cout<<"第"<<i<<"行"<<"第"<<j<<"列:"<<"第"<<++replace<<"次替换"<<endl;
for(int m=;m<nYSize;m++)
{
for(int n=;n<nXSize;n++)
{//把大的标签替换为小的标签
if(dataLabel[m][n]==maxLabel(dataLabel[i][j],dataLabel[i][j-]))
{
dataLabel[m][n]=minLabel(dataLabel[i][j],dataLabel[i][j-]);
}
}
}
}
}
}
else//非0行
{
if(j==)//第0列,但不是第一行的:只判断上、右上两个方向
{
if(data[i-][j]==data[i][j] && dataLabel[i-][j]!=dataLabel[i][j])//像素值相同 && 标签不同:合并!
{
//把所有的大标签替换为当前两个标签中的一个较小的值
//执行一次遍历
//cout<<"第"<<i<<"行"<<"第"<<j<<"列:"<<"第"<<++replace<<"次替换"<<endl;
for(int m=;m<nYSize;m++)
{
for(int n=;n<nXSize;n++)
{//把大的标签替换为小的标签
if(dataLabel[m][n]==maxLabel(dataLabel[i][j],dataLabel[i-][j]))
{
dataLabel[m][n]=minLabel(dataLabel[i][j],dataLabel[i-][j]);
}
}
}
}
if(data[i-][j+]==data[i][j] && dataLabel[i-][j=]!=dataLabel[i][j])//像素值相同 && 标签不同:合并!
{
//把所有的大标签替换为当前两个标签中的一个较小的值
//执行一次遍历
//cout<<"第"<<i<<"行"<<"第"<<j<<"列:"<<"第"<<++replace<<"次替换"<<endl;
for(int m=;m<nYSize;m++)
{
for(int n=;n<nXSize;n++)
{//把大的标签替换为小的标签
if(dataLabel[m][n]==maxLabel(dataLabel[i][j],dataLabel[i-][j+]))
{
dataLabel[m][n]=minLabel(dataLabel[i][j],dataLabel[i-][j+]);
}
}
}
}
}
else if(j==nYSize-)//非0行且最后一列的:判断左、左上、上三个方向
{
if(data[i][j-]==data[i][j] && dataLabel[i][j-]!=dataLabel[i][j])//像素值相同 && 标签不同:合并!
{
//把所有的大标签替换为当前两个标签中的一个较小的值
//执行一次遍历
//cout<<"第"<<i<<"行"<<"第"<<j<<"列:"<<"第"<<++replace<<"次替换"<<endl;
for(int m=;m<nYSize;m++)
{
for(int n=;n<nXSize;n++)
{//把大的标签替换为小的标签
if(dataLabel[m][n]==maxLabel(dataLabel[i][j],dataLabel[i][j-]))
{
dataLabel[m][n]=minLabel(dataLabel[i][j],dataLabel[i][j-]);
}
}
}
}
if(data[i-][j-]==data[i][j] && dataLabel[i-][j-]!=dataLabel[i][j])//像素值相同 && 标签不同:合并!
{
//把所有的大标签替换为当前两个标签中的一个较小的值
//执行一次遍历
//cout<<"第"<<i<<"行"<<"第"<<j<<"列:"<<"第"<<++replace<<"次替换"<<endl;
for(int m=;m<nYSize;m++)
{
for(int n=;n<nXSize;n++)
{//把大的标签替换为小的标签
if(dataLabel[m][n]==maxLabel(dataLabel[i][j],dataLabel[i-][-]))
{
dataLabel[m][n]=minLabel(dataLabel[i][j],dataLabel[i-][j-]);
}
}
}
}
if(data[i-][j]==data[i][j] && dataLabel[i-][j]!=dataLabel[i][j])//像素值相同 && 标签不同:合并!
{
//把所有的大标签替换为当前两个标签中的一个较小的值
//执行一次遍历
//cout<<"第"<<i<<"行"<<"第"<<j<<"列:"<<"第"<<++replace<<"次替换"<<endl;
for(int m=;m<nYSize;m++)
{
for(int n=;n<nXSize;n++)
{//把大的标签替换为小的标签
if(dataLabel[m][n]==maxLabel(dataLabel[i][j],dataLabel[i-][j]))
{
dataLabel[m][n]=minLabel(dataLabel[i][j],dataLabel[i-][j]);
}
}
}
} }
else//非0行且(既不是第一列,也不是最后一列的):判断左、左上、上、右上四个方向
{
if(data[i][j-]==data[i][j] && dataLabel[i][j-]!=dataLabel[i][j])//像素值相同 && 标签不同:合并!
{
//把所有的大标签替换为当前两个标签中的一个较小的值
//执行一次遍历
//cout<<"第"<<i<<"行"<<"第"<<j<<"列:"<<"第"<<++replace<<"次替换"<<endl;
for(int m=;m<nYSize;m++)
{
for(int n=;n<nXSize;n++)
{//把大的标签替换为小的标签
if(dataLabel[m][n]==maxLabel(dataLabel[i][j],dataLabel[i][j-]))
{
dataLabel[m][n]=minLabel(dataLabel[i][j],dataLabel[i][j-]);
}
}
}
}
if(data[i-][j-]==data[i][j] && dataLabel[i-][j-]!=dataLabel[i][j])//像素值相同 && 标签不同:合并!
{
//把所有的大标签替换为当前两个标签中的一个较小的值
//执行一次遍历
//cout<<"第"<<i<<"行"<<"第"<<j<<"列:"<<"第"<<++replace<<"次替换"<<endl;
for(int m=;m<nYSize;m++)
{
for(int n=;n<nXSize;n++)
{//把大的标签替换为小的标签
if(dataLabel[m][n]==maxLabel(dataLabel[i][j],dataLabel[i-][-]))
{
dataLabel[m][n]=minLabel(dataLabel[i][j],dataLabel[i-][j-]);
}
}
}
}
if(data[i-][j]==data[i][j] && dataLabel[i-][j]!=dataLabel[i][j])//像素值相同 && 标签不同:合并!
{
//把所有的大标签替换为当前两个标签中的一个较小的值
//执行一次遍历
//cout<<"第"<<i<<"行"<<"第"<<j<<"列:"<<"第"<<++replace<<"次替换"<<endl;
for(int m=;m<nYSize;m++)
{
for(int n=;n<nXSize;n++)
{//把大的标签替换为小的标签
if(dataLabel[m][n]==maxLabel(dataLabel[i][j],dataLabel[i-][j]))
{
dataLabel[m][n]=minLabel(dataLabel[i][j],dataLabel[i-][j]);
}
}
}
}
if(data[i-][j+]==data[i][j] && dataLabel[i-][j+]!=dataLabel[i][j])//像素值相同 && 标签不同:合并!
{
//把所有的大标签替换为当前两个标签中的一个较小的值
//执行一次遍历
//cout<<"第"<<i<<"行"<<"第"<<j<<"列:"<<"第"<<++replace<<"次替换"<<endl;
for(int m=;m<nYSize;m++)
{
for(int n=;n<nXSize;n++)
{//把大的标签替换为小的标签
if(dataLabel[m][n]==maxLabel(dataLabel[i][j],dataLabel[i-][j+]))
{
dataLabel[m][n]=minLabel(dataLabel[i][j],dataLabel[i-][j+]);
}
}
}
}
}
} }
}
t=;
//system("pause");
cout<<"输出合并后的标签数组dataLabel:"<<endl;
for(i=;i<nYSize;i++)
{
for(j=;j<nXSize;j++)
{
if(dataLabel[i][j]>t)
{
t++;
}
cout<<setw()<<dataLabel[i][j]; }
cout<<endl;
}
cout<<endl<<"t="<<t<<endl;
cout<<"联通区域标记算法结束end!!!"<<endl;
//system("pause");
int max=;
for(int i=;i<nYSize;i++)
{
for(int j=;j<nXSize;j++)
{
if(dataLabel[i][j]>max)
{
max=dataLabel[i][j];
}
}
}
cout<<"maxLabel="<<max<<endl;
int *Pid=new int [max+];
int *Parea=new int [max+];
for(i=;i<max+;i++)
{
Pid[i]=i;
Parea[i]=;
}
/////////////////////////////
for(int i=;i<nYSize;i++)
{
for(int j=;j<nXSize;j++)
{
Parea[dataLabel[i][j]]++;
}
}
t=max+;
for(i=;i<t;i++)
{ cout<<"dataLabel为"<<i<<"的面积为:"<<Parea[i]<<endl;
} double Xi=,Si=;
int NPatch=;
for(i=;i<t;i++)
{
if(Parea[i]!=)
{
NPatch++;
Xi=Xi+Parea[i];//求出板块总面积
}
/*cout<<"NPatch="<<NPatch<<endl;
cout<<"Xi="<<Xi<<endl;*/
}
cout<<"NPatch="<<NPatch<<endl;
//cout<<"Xi="<<Xi<<endl;
Xi=Xi/NPatch;//面积平均数
cout<<"Initial Value:Xi="<<Xi<<endl;
cout<<"Initial Value:Si="<<Si<<endl;
//计算出所有
for(i=;i<t;i++)
{
if(Parea[i]!=)
{
Si=Si+((Parea[i]-Xi)*(Parea[i]-Xi))/NPatch;
}
}
cout<<"方差="<<Si<<endl;
Si=sqrt(Si);
cout<<"Standard Deviation(标准差)="<<Si<<endl;
cout<<endl; /****输出内容写出到文件当中****/
ofstream ocout;
//ocout.open("result.csv");
//ocout.open("result.txt");
ocout.open("result.csv");
/****将计算出的结果输出到屏幕****/
ocout<<"PatchID"<<","<<"Area"<<","<<"CSD"<<endl;
for(i=;i<t;i++)
{
if(Parea[i]!=)
{ //ocout<<"Patch: Label="<<i<<setw(4)<<" Area="<<Parea[i]<<setw(10)<<"CSD="<<(Parea[i]-Xi)/Si<<endl;
ocout<<i<<",";
ocout<<Parea[i]<<",";
ocout<<(Parea[i]-Xi)/Si<<","<<endl;
}
}
/*******后面的这一部分没有太大的实质性用处,可有可无**********/
const char *pszFormat = "GTiff";
GDALDriver *poDriver;
char **papszMetadata; poDriver = GetGDALDriverManager()->GetDriverByName(pszFormat); if( poDriver == NULL )
exit( ); papszMetadata = poDriver->GetMetadata();
if( CSLFetchBoolean( papszMetadata, GDAL_DCAP_CREATE, FALSE ) )
printf( "Driver %s supports Create() method.\n", pszFormat );
if( CSLFetchBoolean( papszMetadata, GDAL_DCAP_CREATECOPY, FALSE ) )
printf( "Driver %s supports CreateCopy() method.\n", pszFormat ); /*释放读取文件用的指针*/
CPLFree(pafScanline);
//关闭文件
GDALClose((GDALDatasetH)poDataset);
free(dataLabel);
free(data);
free(Parea);
free(Pid);
//
ocout.close();
}
int minLabel(int a,int b)
{
return (a>b)?b:a;
}
int maxLabel(int a,int b)
{
return (a<b)?b:a;
}
double csd(int *Parea,int length)
{
return ;
}
C++调用GDAL库读取并输出tif文件,并计算斑块面积输出景观指数:CSD的更多相关文章
- GDAL库——读取图像并提取基本信息
GDAL库是一个跨平台的栅格地理数据格式库,包括读取.写入.转换.处理各种栅格数据格式(有些特定的格式对一些操作如写入等不支持).它使用了一个单一的抽象数据模型就支持了大多数的栅格数据.这里有GDAL ...
- [原创]java WEB学习笔记41:简单标签之带属性的自定义标签(输出指定文件,计算并输出两个数的最大值 demo)
本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...
- 基于GDAL库,读取.grd文件(以海洋地形数据为例)C++版
技术背景 海洋地形数据主要是通过美国全球地形起伏数据(GMT)获得,数据格式为grd(GSBG)二进制数据,打开软件通过是Surfer软件,surfer软件可进行数据的编辑处理,以及进一步的可视化表达 ...
- 基于GDAL库,读取.nc文件(以海洋表温数据为例)C++版
对于做海洋数据处理的同学,会经常遇到nc格式的文件,nc文件的格式全称是NetCDF,具体的详细解释请查询官网[https://www.unidata.ucar.edu/software/netcdf ...
- 使用C#版本的gdal库打开hdf文件
作者:朱金灿 来源:http://blog.csdn.net/clever101 最近应同事的请求帮忙研究下使用C#版的gdal库读取hdf文件,今天算是有一点成果,特地做一些记录. 首先是编译C#版 ...
- GDAL C#读取shp中文属性值乱码问题
GDAL的C#版本读取shp中,如果属性值中含有中文,读出来有可能是乱码的问题,根据SWIG生成的C#代码调试发现问题所在,在Ogr.cs文件中有这么一个函数,代码如下: internal stati ...
- 浅析GDAL库C#版本支持中文路径问题
GDAL库对于C#的支持问题还是蛮多的,对于中文路径的支持就是其中之一(另一个就是通过OGR库获取图形的坐标信息). 关于C#支持中文路径,看过我之前博客的应该都不陌生,如果使用的是我修改过的GDAL ...
- GDAL库调试(包括跨语言调试)
很多时候都需要调试GDAL库,尤其是像学习GDAL库中的某些算法是如何实现的时候,调试就必不可少了. 首先说明用C++的调试.以VS2008为例进行说明. 编译DEBUG版本的GDAL库,这个可以参考 ...
- 转 mysql查询结果输出到文件
mysql查询结果输出到文件 mysql查询结果导出/输出/写入到文件 方法一:直接执行命令:mysql> select count(1) from table into outfile ...
随机推荐
- AutoCompleteTextView输入汉字拼音首字母实现过滤提示(支持多音字,Filterable的使用)
AutoCompleteTextView具有输入提示的功能,但是它的这种提示不适合对股票列表的过滤,如果你玩过股票软件,就会知道只要输入股票名称的首字母或股票代码就会出现符合匹配的股票,这种过滤怎么实 ...
- innobackupex的备份和恢复
http://blog.itpub.net/15480802/viewspace-1173479/ 1 原理 分3个阶段:备份backup – 预恢复prepare -- 恢复restore 注 ...
- Fedora 23如何安装LAMP服务器
LAMP 是开源系统上 Web 服务器的梦幻组合.LAMP 是 Linux. Apache HTTP 服务. MySQL/MariaDB 数据库和 PHP. Perl 或 Python 的简称. 下面 ...
- sqlserver 行转列 语文,数学,物理,化学
数据库查询行转列 1.原数据库值 stdname stdsubject result 张三 语文 张三 数学 张三 物理 李四 语文 李四 数学 李四 物理 李四 化学 李四 化学 2.要得到如下表 ...
- Data Structure 之 最优二叉树
给定n个权值作为n的叶子结点,构造一棵二叉树,若带权路径长度达到最小,称这样的二叉树为最优二叉树,也称为哈夫曼树(Huffman tree).哈夫曼树是带权路径长度最短的树,权值较大的结点离根较近. ...
- iOS 手机 邮箱 正则表达式
//iOS代码//判断邮箱格式是否正确的代码: //利用正则表达式验证 -(BOOL)isValidateEmail:(NSString *)email { NSString *emailRegex ...
- 基于 Equinox 的 OSGi Console 的研究和探索
自定制 OSGi Console 进行组建和服务生命周期管理模块化编程的好处已经很好地被理解了约 40 年,但在 OSGi 之前,开发人员不得不自己发明模块化设计和系统.随着软件应用体系结构的不断发展 ...
- ArcGIS Engine断开其他ArcSDE用户连接的解决方案
来自:http://blog.csdn.net/linghe301/article/details/38925481 最近有很多用户咨询在ArcGIS Engine中希望能够实现断开其他客户端连接Ar ...
- 如何使上层的div遮住的链接可以点击
pointer-events属性 http://www.css88.com/book/css/properties/user-interface/pointer-events.htm 在上层的div中 ...
- web 分类 和使用Dreamweaver
1.iE 2.火狐的fox 3.谷歌的Chrome 在编写的代码要符合W3c统一的国际标准 二:使用Dreamweaver: Dreamweaver只能用辅助,生成的代码要修改,…… ———编写的代码 ...