c语言读取24位BMP文件并实现翻转90度、180度、270度

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/BigDream123/article/details/102536571

BMP图片格式

BMP图片,是Bitmap(位图)的简称,它是windows显示图片的基本格式。在windows下,任何格式的图片文件(包括视频播放)都要转化为位图才能显示出来。各种格式的图片文件也都是在位图格式的基础上采用不同的压缩算法生成的。

位图文件主要分为如下3个部分:

块名称 对应Windows结构体定义 大小(Byte)
文件信息头 BITMAPFILEHEADER 14
位图信息头 BITMAPINFOHEADER 40
RGB颜色阵列 BYTE* 由图像长宽尺寸决定

文件信息头结构体定义如下:

typedef struct BMP_FILE_HEADER
{
	//WORD bType;      // 文件标识符,这里为0x4d42 
	DWORD bSize;     // 文件的大小 
	WORD bReserved1; // 保留值,必须设置为0  
	WORD bReserved2; // 保留值,必须设置为0 
	DWORD bOffset;  // 文件头的最后到图像数据位开始的偏移量,
					//说明从文件头开始到实际的图象数据之间的字节的偏移量
					//因为位图信息头和调色板的长度会根据不同情况而变化
					//所以你可以用这个偏移值迅速的从文件中读取到位数据
} BMPFILEHEADER;    // 14 字节
bType 说明文件的类型,该值必需是0x4D42(十六进制ASCII码4D代表“B”,42代表“M”),也就是字符'BM'
bSize 说明该位图文件的大小,用字节为单位
bReserved1 保留,必须设置为0
bfReserved2 保留,必须设置为0
bOffBits 说明从文件头开始到实际的图象数据之间的字节的偏移量。这个参数是非常有用的,因为位图信息头和调色板的长度会根据不同情况而变化,所以你可以用这个偏移值迅速的从文件中读取到位数据

位图信息头结构体定义如下:

typedef struct BMP_INFO
{
	DWORD bInfoSize;            // 信息头的大小   
	DWORD bWidth;               // 图像的宽度     
	DWORD bHeight;              // 图像的高度,如果该值是一个正数,说明图像是倒向的,如果该值是一个负数,则说明图像是正向的。  
	WORD bPlanes;               // 图像的位面数,其值将总是被设为1 
	WORD bBitCount;             // 每个像素的位数,这里为 24位
	DWORD bCompression;         // 压缩类型 
	DWORD bmpImageSize;         // 图像的大小,以字节为单位 
	DWORD bXPelsPerMeter;       // 水平分辨率 
	DWORD bYPelsPerMeter;       // 垂直分辨率 
	DWORD bClrUsed;             // 位图实际使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项)
	DWORD bClrImportant;        // 对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要
} BMPINF;            // 40 字节
bInfoSize 说明BITMAPINFOHEADER结构所需要的字数。
bWidth 说明图象的宽度,以象素为单位。
bHeight 说明图象的高度,以象素为单位。注:这个值除了用于描述图像的高度之外,它还有另一个用处,就是指明该图像是倒向的位图,还是正向的位图。如果该值是一个正数,说明图像是倒向的,如果该值是一个负数,则说明图像是正向的。大多数的BMP文件都是倒向的位图,也就是说,高度值是一个正数。
bPlanes 为目标设备说明位面数,其值将总是被设为1。
BitCount 说明比特数/象素,其值为1、4、8、16、24、或32。我们平时用到的图像绝大部分是24位和32位的
bCompression 说明图象数据压缩的类型,同样我们只讨论没有压缩的类型:BI_RGB。
bmpImageSize 说明图象的大小,以字节为单位。当用BI_RGB格式时,可设置为0。
bXPelsPerMeter 说明水平分辨率,用象素/米表示。
bYPelsPerMeter 说明垂直分辨率,用象素/米表示。
bClrUsed 说明位图实际使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项)。
bClrImportant 说明对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要。

RGB颜色通道:

 在windows下面,RGB颜色矩阵的存储格式是BGR。颜色数据区记录着每个像素对应的颜色值。其记录方式随着颜色模式的不同而不同。

BitCount=1
    表示位图最多有两种颜色,缺省情况下是黑色和白色,你也可以自己定义这两种颜色。图像信息头调色板中将有两个调色板项,称为索引0和索引1。图象数据阵列中的每一位表示一个象素。如果一个位是0,显示时就使用索引0的RGB值,如果位是1,则使用索引1的RGB值。
BitCount=4
    表示位图最多有16种颜色。每个象素用4位表示,并用这4位作为彩色表的表项来查找该象素的颜色。例如,如果位图中的第一个字节为0x1F,它表示有两个象素,第一象素的颜色就在彩色表的第2表项中查找,而第二个象素的颜色就在彩色表的第16表项中查找。此时,调色板中缺省情况下会有16个RGB项。对应于索引0到索引15。
BitCount=8
    表示位图最多有256种颜色。每个象素用8位表示,并用这8位作为彩色表的表项来查找该象素的颜色。例如,如果位图中的第一个字节为0x1F,这个象素的颜色就在彩色表的第32表项中查找。此时,缺省情况下,调色板中会有256个RGB项,对应于索引0到索引255。

BitCount=16
    表示位图最多有65536种颜色。每个色素用16位(2个字节)表示。这种格式叫作高彩色,或叫增强型16位色,或64K色。它的情况比较复杂。

当 biCompression成员的值是BI_RGB时,它没有调色板。16位中,最低的5位表示蓝色分量,中间的5位表示绿色分量,高的5位表示红色分量,一共占用了15位,最高的一位保留,设为0。这种格式也被称作555。

当biCompression成员的值是BI_BITFIELDS,那么情况就复杂了,首先是原来调色板的位置被三个DWORD变量占据,称为红、绿、蓝掩码。分别用于描述红、绿、蓝分量在16位中所占的位置。在Windows95(或98)中,系统可接受两种格式的位域:555和565,在555格式下,红、绿、蓝的掩码分别是:0x7C00、0x03E0、0x001F,而在565格式下,它们则分别为:0xF800、0x07E0、0x001F。你在读取一个像素之后,可以分别用掩码“与”上像素值,从而提取出想要的颜色分量(当然还要再经过适当的左右移操作)。在NT系统中,则没有格式限制,只不过要求掩码之间不能有重叠。(注:这种格式的图像使用起来是比较麻烦的,不过因为它的显示效果接近于真彩,而图像数据又比真彩图像小的多,所以,它更多的被用于游戏软件)。

BitCount=24
    表示位图最多有2的24次方,大约1670万种颜色。这种位图没有调色板(bmiColors成员尺寸为0),在位数组中,每3个字节代表一个象素,分别对应于颜色R、G、B。
BitCount=32
    表示位图最多有2的32次方种颜色。这种位图的结构与16位位图结构非常类似

当biCompression成员的值是BI_RGB时,它也没有调色板,32位中有24位用于存放RGB值,顺序是:最高位—保留,红8位、绿8位、蓝8位。这种格式也被成为888。

当biCompression成员的值是BI_BITFIELDS时,原来调色板的位置将被三个DWORD变量占据,成为红、绿、蓝掩码,分别用于描述红、绿、蓝分量在32位中所占的位置。在Windows95(or 98)中,系统只接受888格式,也就是说三个掩码的值将只能是:0xFF0000、0xFF00、0xFF。而在NT系统中,你只要注意使掩码之间不产生重叠就行。(注:这种图像格式比较规整,因为它是DWORD对齐的,所以在内存中进行图像处理时可进行汇编级的代码优化(简单))。

注意:
BMP图像数据区在存储时,2色图像每点占1位(8位为1字节);16色图像每点占4位(半字节);256色图像每点占8位(1字节);真彩色图像每点占24位(3字节)。所以,整个数据区的大小也会随之变化。究其规律而言,可的出如下计算公式:图像数据信息大小=(图像宽度*图像高度*记录像素的位数)/8。
对于未压缩的图像信息区的大小。除了真彩色模式外,其余的均大于或等于数据信息的大小。这是为什么呢?原因有两个:
1.BMP文件记录一行图像是以字节为单位的。因此,就不存在一个字节中的数据位信息表示的点在不同的两行中。也就是说,设显示模式为16色,在每个字节分配两个点信息时,如果图像的宽度位奇数,那么最后一个像素点的信息将独占一个字节,这个字节的后4位将没有意义。接下来的一个字节将开始记录下一行的信息。
2.为了显示的方便,除了真彩色外,其他的每种颜色模式的行字节数要用数据“00”补齐为4的整数倍。如果显示模式为16色,当图像宽为19时,存储时每行则要补充4-(19/2+1)%4=2个字节(加1是因为里面有一个像素点要独占了一字节)。如果显示模式为256色,当图像宽为19时,每行也要补充4-19%4=1个字节。
 

24位BMP图片

由上面BMP图片格式可知,24位BMP图像称为真彩色图像,这种位图没有调色板,并且每三个字节代表一个像素,分别对应于R、G、B。对于24位BMP图片,大小计算公式为:图像宽度*图像高度*3字节。

注意:每次读取图片都是三个字节三个字节地读取(分别为BGR),对于图像宽度不是4的整数倍的图片,读取像素的时候需要在读取每一行像素的时候,跳过最后所补的0。计算跳过的0的个数公式如下:4 - ((width * 3 )%4),其中width是图像的宽度。

具体实现代码细节如下:

#include <stdio.h>
#include <stdlib.h>
#include<malloc.h>
#include <string>
#include <iostream>
typedef unsigned char  BYTE;//一字节
typedef unsigned short WORD;//两字节
typedef unsigned long DWORD;//四字节
typedef long LONG;
/* 位图文件头主要是对位图文件的一些描述 位图信息头主要是对位图图像方面信息的描述 */

/*************************** 位图标准信息(54字节) ******************************************/
/* 位图文件头 (位图头的字节数 = 位图文件字节数 - 位图图像数据字节数)*/
typedef struct BMP_FILE_HEADER
{
	//WORD bType;      // 文件标识符,这里为0x4d42 
	DWORD bSize;     // 文件的大小 
	WORD bReserved1; // 保留值,必须设置为0  
	WORD bReserved2; // 保留值,必须设置为0 
	DWORD bOffset;  // 文件头的最后到图像数据位开始的偏移量,
					//说明从文件头开始到实际的图象数据之间的字节的偏移量
					//因为位图信息头和调色板的长度会根据不同情况而变化
					//所以你可以用这个偏移值迅速的从文件中读取到位数据
} BMPFILEHEADER;    // 14 字节
					/* 位图信息头 */
typedef struct BMP_INFO
{
	DWORD bInfoSize;            // 信息头的大小   
	DWORD bWidth;               // 图像的宽度     
	DWORD bHeight;              // 图像的高度,如果该值是一个正数,说明图像是倒向的,如果该值是一个负数,则说明图像是正向的。  
	WORD bPlanes;               // 图像的位面数,其值将总是被设为1 
	WORD bBitCount;             // 每个像素的位数,这里为 24位
	DWORD bCompression;         // 压缩类型 
	DWORD bmpImageSize;         // 图像的大小,以字节为单位 
	DWORD bXPelsPerMeter;       // 水平分辨率 
	DWORD bYPelsPerMeter;       // 垂直分辨率 
	DWORD bClrUsed;             // 位图实际使用的彩色表中的颜色索引数(设为0的话,则说明使用所有调色板项)
	DWORD bClrImportant;        // 对图象显示有重要影响的颜色索引的数目,如果是0,表示都重要
} BMPINF;            // 40 字节

					 /* 彩色表:调色板 */
					 // 24位的真彩图不需要调色板
typedef struct RGB_QUAD
{
	WORD rgbBlue;     // 蓝色强度 
	WORD rgbGreen;    // 绿色强度 
	WORD rgbRed;      // 红色强度 
	WORD rgbReversed; // 保留值 
} RGBQUAD;

//位图数据:这部分的内容根据BMP位图使用的位数不同而不同
//在24位真彩图中,直接使用RGB
//其他的小于24位的则使用调色板中颜色的索引值
typedef struct tagIMAGEDATA
{
	BYTE blue;
	BYTE green;
	BYTE red;
}IMAGEDATA;

typedef struct {
	int width;
	int height;
	
	BYTE  *imageData;

}IMAGE;

//函数声明
void rotation90();
void rotation270();
void rotation180();
WORD bType;//文件标识符,BMP文件为0x4d42,这里单独读取
BMPFILEHEADER bmpFileHeader;  // 定义一个 BMP 文件头的结构体
BMPINF bmpInfo;               // 定义一个 BMP 文件信息结构体 	
FILE *fp;                     // 定义一个文件指针
IMAGE *bmpImg = (IMAGE*)malloc(sizeof(IMAGE));				//定义一个bmp指针,并分配地址空间
BYTE  pixVal;				//定义一个一字节的数据

int width,height;
int offset;
int i, j, k;


int main()
{

	if ((fp = fopen("pic.bmp", "rb")) == NULL) // fp = 0x00426aa0
	{
		printf("Cann't open the file!\n");
		return 0;
	}
	// 让 fp 指向 bmp 文件的开始 
	// 第 2 个参数是偏移量 第三个参数是文件起始地址 所以此函数执行成功后文件指针会指向文件的开始
	fseek(fp, 0, SEEK_SET);

	fread(&bType, sizeof(WORD), 1, fp);//读取文件类型

	if (bType != 0x4d42)
	{
		printf("不是bmp文件");
		return 0;
	}

	fread(&bmpFileHeader, sizeof(BMPFILEHEADER), 1, fp);
	fread(&bmpInfo, sizeof(BMPINF), 1, fp);

	/*
	// 输出BMP文件的位图文件头的所有信息
	printf("位图文件头主要是对位图文件的一些描述:BMPFileHeader\n\n");
	printf("文件标识符 = 0X%X\n", bType);
	printf("BMP 文件大小 = %d 字节\n", bmpFileHeader.bSize);
	printf("保留值1 = %d \n", bmpFileHeader.bReserved1);
	printf("保留值2 = %d \n", bmpFileHeader.bReserved2);
	printf("文件头的最后到图像数据位开始的偏移量 = %d 字节\n", bmpFileHeader.bOffset);
	// 输出BMP文件的位图信息头的所有信息
	printf("\n\n位图信息头主要是对位图图像方面信息的描述:BMPInfo\n\n");
	printf("信息头的大小 = %d 字节\n", bmpInfo.bInfoSize);
	printf("位图的高度 = %d \n", bmpInfo.bHeight);
	printf("位图的宽度 = %d \n", bmpInfo.bWidth);
	printf("图像的位面数(位面数是调色板的数量,默认为1个调色板) = %d \n", bmpInfo.bPlanes);
	printf("每个像素的位数 = %d 位\n", bmpInfo.bBitCount);
	printf("压缩类型 = %d \n", bmpInfo.bCompression);
	printf("图像的大小 = %d 字节\n", bmpInfo.bmpImageSize);
	printf("水平分辨率 = %d \n", bmpInfo.bXPelsPerMeter);
	printf("垂直分辨率 = %d \n", bmpInfo.bYPelsPerMeter);
	printf("使用的色彩数 = %d \n", bmpInfo.bClrUsed);
	printf("重要的色彩数 = %d \n", bmpInfo.bClrImportant);

	printf("\n\n\n压缩说明:有0(不压缩),1(RLE 8,8位RLE压缩),2(RLE 4,4位RLE压缩,3(Bitfields,位域存放)");
	fclose(fp);
	*/
	width = bmpInfo.bWidth;
	height = bmpInfo.bHeight;

	bmpImg->width = width;
	bmpImg->height = height;
	bmpImg->imageData = (BYTE*)malloc(sizeof(BYTE)*width*height*3);

	//如果行的字节数不是4的整数倍,则进行如下处理
	offset = (3 * width) % 4;
	if (offset != 0)
	{
		offset = 4 - offset;
	}

	//读取数据
	for (i = 0; i < height; i++)
	{
		for (j = 0; j < width; j++)
		{
			for (k = 0; k < 3; k++)
			{
				fread(&pixVal, sizeof(BYTE), 1, fp);
				bmpImg->imageData[i * width * 3 + j * 3 + k] = pixVal;
			}
		}
		if (offset != 0)
		{
			for (j = 0; j < offset; j++)
			{
				fread(&pixVal, sizeof(BYTE), 1, fp);
			}
		}
	}

	//输出数据
	//将原始图片输出
	/*
	for (i = 0; i<height; i++)
	{
		for (j = 0; j<width; j++)
		{
			pixVal = bmpImg->imageData[i*bmpImg->width * 3 + j * 3];
			fwrite(&pixVal, sizeof(BYTE), 1, pFile);
			pixVal = bmpImg->imageData[i*bmpImg->width * 3 + j * 3 + 1 ];
			fwrite(&pixVal, sizeof(BYTE), 1, pFile);
			pixVal = bmpImg->imageData[i*bmpImg->width * 3 + j * 3 + 2 ];
			fwrite(&pixVal, sizeof(BYTE), 1, pFile);
		}

		if (offset != 0)
		{
			for (j = 0; j<offset; j++)
			{
				pixVal = 0;
				fwrite(&pixVal, sizeof(BYTE), 1, pFile); 
			}
		}
	}
	*/
	//rotation90();
	rotation180();
	//rotation270();
	fclose(fp);
	return 0;

}
void rotation180()
{
	//旋转180度
	FILE *pFile;
	pFile = fopen("D:\\C++-work\\rotate180.bmp", "wb");
	if (!pFile)
	{
		printf("读取文件失败");
	}
	int step = bmpImg->width * 3;
	offset = step % 4;

	if (offset != 0)
	{
	step += 4 - offset;
	offset = 4 - offset;
	}

	fwrite(&bType, sizeof(WORD), 1, pFile);
	fwrite(&bmpFileHeader, sizeof(BMPFILEHEADER), 1, pFile);
	fwrite(&bmpInfo, sizeof(BMPINF), 1, pFile);

	for (i = height-1; i>-1; i--)
	{
		for (j = width-1; j>-1; j--)
	{
			pixVal = bmpImg->imageData[i*bmpImg->width * 3 + j * 3];
			fwrite(&pixVal, sizeof(BYTE), 1, pFile);
			pixVal = bmpImg->imageData[i*bmpImg->width * 3 + j * 3 + 1];
			fwrite(&pixVal, sizeof(BYTE), 1, pFile);
			pixVal = bmpImg->imageData[i*bmpImg->width * 3 + j * 3 + 2];
			fwrite(&pixVal, sizeof(BYTE), 1, pFile);
	}

	if (offset != 0)
	{
		for (j = 0; j<offset; j++)
		{
			pixVal = 0;
			fwrite(&pixVal, sizeof(BYTE), 1, pFile);
		}
	}
	}
	fclose(pFile);
	
}
void rotation90()
{
	FILE *pFile;
	pFile = fopen("D:\\C++-work\\rotate90.bmp", "wb");
	if (!pFile)
	{
		printf("读取文件失败");
	}
	
	BMPFILEHEADER new_bmpFileHeader;  // 定义一个 BMP 文件头的结构体
	BMPINF new_bmpInfo;               // 定义一个 BMP 文件信息结构体 

	int new_w = bmpInfo.bHeight;
	int new_h = bmpInfo.bWidth;

	int step = new_w * 3;
	int offset = step % 4;
	if (offset != 0)
	{
		step += 4 - offset;
		offset = 4 - offset;
	}
	new_bmpFileHeader.bSize = (DWORD)(step * new_h + sizeof(WORD) + sizeof(BMPFILEHEADER) + sizeof(BMPINF));
	new_bmpFileHeader.bReserved1 = bmpFileHeader.bReserved1;
	new_bmpFileHeader.bReserved2 = bmpFileHeader.bReserved2;
	new_bmpFileHeader.bOffset = bmpFileHeader.bOffset;
	new_bmpInfo.bBitCount = bmpInfo.bBitCount;
	new_bmpInfo.bClrImportant = bmpInfo.bClrImportant;
	new_bmpInfo.bClrUsed = bmpInfo.bClrUsed;
	new_bmpInfo.bCompression = bmpInfo.bCompression;
	new_bmpInfo.bHeight = bmpInfo.bWidth;
	new_bmpInfo.bWidth = bmpInfo.bHeight;
	new_bmpInfo.bInfoSize = bmpInfo.bInfoSize;
	new_bmpInfo.bmpImageSize = (DWORD)(step * new_h);
	new_bmpInfo.bPlanes = bmpInfo.bPlanes;
	new_bmpInfo.bXPelsPerMeter = bmpInfo.bXPelsPerMeter;
	new_bmpInfo.bYPelsPerMeter = bmpInfo.bYPelsPerMeter;

	fwrite(&bType, sizeof(WORD), 1, pFile);
	fwrite(&new_bmpFileHeader, sizeof(BMPFILEHEADER), 1, pFile);
	fwrite(&new_bmpInfo, sizeof(BMPINF), 1, pFile);

	//旋转90度
	for (i = width-1; i>-1; i--)
	{
		for (j = 0; j<height; j++)
		{
			pixVal = bmpImg->imageData[j*width * 3 + i * 3];
			fwrite(&pixVal, sizeof(BYTE), 1, pFile);
			pixVal = bmpImg->imageData[j*width * 3 + i * 3 + 1];
			fwrite(&pixVal, sizeof(BYTE), 1, pFile);
			pixVal = bmpImg->imageData[j*width * 3 + i * 3 + 2];
			fwrite(&pixVal, sizeof(BYTE), 1, pFile);
		}

		if (offset != 0)
		{
			for (j = 0; j<offset; j++)
			{
				pixVal = 0;
				fwrite(&pixVal, sizeof(BYTE), 1, pFile);
			}
		}
	}

}
void rotation270()
{
	FILE *pFile;
	pFile = fopen("D:\\C++-work\\rotate270.bmp", "wb");
	if (!pFile)
	{
		printf("读取文件失败");
	}

	BMPFILEHEADER new_bmpFileHeader;  // 定义一个 BMP 文件头的结构体
	BMPINF new_bmpInfo;               // 定义一个 BMP 文件信息结构体 

	int new_w = bmpInfo.bHeight;
	int new_h = bmpInfo.bWidth;

	int step = new_w * 3;
	int offset = step % 4;
	if (offset != 0)
	{
		step += 4 - offset;
		offset = 4 - offset;
	}
	new_bmpFileHeader.bSize = (DWORD)(step * new_h + sizeof(WORD) + sizeof(BMPFILEHEADER) + sizeof(BMPINF));
	new_bmpFileHeader.bReserved1 = bmpFileHeader.bReserved1;
	new_bmpFileHeader.bReserved2 = bmpFileHeader.bReserved2;
	new_bmpFileHeader.bOffset = bmpFileHeader.bOffset;
	new_bmpInfo.bBitCount = bmpInfo.bBitCount;
	new_bmpInfo.bClrImportant = bmpInfo.bClrImportant;
	new_bmpInfo.bClrUsed = bmpInfo.bClrUsed;
	new_bmpInfo.bCompression = bmpInfo.bCompression;
	new_bmpInfo.bHeight = bmpInfo.bWidth;
	new_bmpInfo.bWidth = bmpInfo.bHeight;
	new_bmpInfo.bInfoSize = bmpInfo.bInfoSize;
	new_bmpInfo.bmpImageSize = (DWORD)(step * new_h);
	new_bmpInfo.bPlanes = bmpInfo.bPlanes;
	new_bmpInfo.bXPelsPerMeter = bmpInfo.bXPelsPerMeter;
	new_bmpInfo.bYPelsPerMeter = bmpInfo.bYPelsPerMeter;

	fwrite(&bType, sizeof(WORD), 1, pFile);
	fwrite(&new_bmpFileHeader, sizeof(BMPFILEHEADER), 1, pFile);
	fwrite(&new_bmpInfo, sizeof(BMPINF), 1, pFile);

	//旋转270度
	for (i = 0; i<width;i++)
	{
		for (j = height-1; j>-1; j--)
		{
			pixVal = bmpImg->imageData[j*width * 3 + i * 3];
			fwrite(&pixVal, sizeof(BYTE), 1, pFile);
			pixVal = bmpImg->imageData[j*width * 3 + i * 3 + 1];
			fwrite(&pixVal, sizeof(BYTE), 1, pFile);
			pixVal = bmpImg->imageData[j*width * 3 + i * 3 + 2];
			fwrite(&pixVal, sizeof(BYTE), 1, pFile);
		}

		if (offset != 0)
		{
			for (j = 0; j<offset; j++)
			{
				pixVal = 0;
				fwrite(&pixVal, sizeof(BYTE), 1, pFile);
			}
		}
	}

}

参考博客:https://blog.csdn.net/carson2005/article/details/6227047

猜你喜欢

转载自blog.csdn.net/BigDream123/article/details/102536571
今日推荐