PNG是一种非常流行的图片格式,它不仅支持透明效果,而且图片数据经过了压缩处理,所以广泛用于web等应用。

PNG的文件格式:

  PNG文件中的数据,总是以一个固定的8个字节开头:

    (图片来自http://blog.csdn.net/bisword/article/details/2777121

  除此之外,PNG的其他数据都是以数据块的方式组织,它们被分为标准数据块和辅助数据块,其中的辅助数据块是可选的。关键数据块包含我们必须的图片信息,我们之后要重点解析的也是关键数据块。

  

  (图片来自http://blog.csdn.net/bisword/article/details/2777121

每种数据块的结构:

  

    (图片来自http://blog.csdn.net/bisword/article/details/2777121

  Length:该数据块的中Chunk Data的长度;

  Chunk Type Code:数据类型,就是指上面提到的IHDR,IEND等;

  Chunk Data:数据区域,如果是IDAT,就表示存储的还未解压的图片数据;

  CRC:循环冗余效验码;

具体实现:(实现中没有处理png数据中变形的情况,部分头中的宏定义来自libpng,实例不具备实用性,仅作参考)

头文件:

 #ifndef __PNG__
#define __PNG__ #include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "zlib/zlib.h" /**
* 类型标志
*/
#define PNG_FLAG_HEX "89504E470D0A1A0A" /**
* 数据块类型
*/
#define DATA_CHUNK_TYPE_IHDR "IHDR"
#define DATA_CHUNK_TYPE_IDAT "IDAT"
#define DATA_CHUNK_TYPE_IEND "IEND"
#define DATA_CHUNK_TYPE_tEXt "tEXt"
#define DATA_CHUNK_TYPE_iTXt "iTXt" /**
* 过滤方式
*/
#define DATA_FILTER_TYPE_DEFAULT 0
#define DATA_FILTER_TYPE_ADD_ROW 1
#define DATA_FILTER_TYPE_ADD_UP 2
#define DATA_FILTER_TYPE_AVERGE 3
#define DATA_FILTER_TYPE_PAETH 4 /* color type masks */
#define PNG_COLOR_MASK_PALETTE 1
#define PNG_COLOR_MASK_COLOR 2
#define PNG_COLOR_MASK_ALPHA 4 /* color types. Note that not all combinations are legal */
#define PNG_COLOR_TYPE_GRAY 0
#define PNG_COLOR_TYPE_PALETTE (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_PALETTE)
#define PNG_COLOR_TYPE_RGB (PNG_COLOR_MASK_COLOR)
#define PNG_COLOR_TYPE_RGB_ALPHA (PNG_COLOR_MASK_COLOR | PNG_COLOR_MASK_ALPHA)
#define PNG_COLOR_TYPE_GRAY_ALPHA (PNG_COLOR_MASK_ALPHA) #define RGB_USE_ALPHA(vr, vg, vb, va) \
(unsigned)(((unsigned)((unsigned char)(vr) * ((unsigned char)(va) + )) >> ) | \
((unsigned)((unsigned char)(vg) * ((unsigned char)(va) + ) >> ) << ) | \
((unsigned)((unsigned char)(vb) * ((unsigned char)(va) + ) >> ) << ) | \
((unsigned)(unsigned char)(va) << )) /**
* 一次解压图片数据的限制
*/
#define DECOMPRESSION_MAX_BYTES 8192 /**
* 数据块信息
*/
typedef struct _DataChunkHeader
{
// 数据长度
unsigned char length[];
// 数据类型
unsigned char type[];
} DataChunkHeader; /**
* IHDR数据
*/
typedef struct _IDHRData
{
unsigned char width[];
unsigned char height[];
unsigned char bitDepth[];
unsigned char colorType[];
unsigned char compressionMethod[];
unsigned char filterMethod[];
unsigned char interlaceMethod[];
} IDHRData; /**
* PNG图片类
*/
class PNG
{
public:
PNG();
PNG(const char* filePath); ~PNG(); int getWindth();
int getHeight(); /**
* 获取图片宽度
*/
unsigned char* getImageData(); private:
int m_width;
int m_height; unsigned char m_bitDepth;
unsigned char m_colorType;
unsigned char m_compressionMethod;
unsigned char m_filterMethod;
unsigned char m_interlaceMethod;
unsigned char m_chanels; unsigned char* m_imageData; /**
* 从文件加载图片数据
*/
bool loadImageDataFromFile(const char* filePath); /**
* 解析数值
*/
int parseNumber(const unsigned char* data, int len); /**
* 解压数据
*/
int decompressData(z_stream* zStream, unsigned char* data, int dataLen, int leftLen, FILE *pFile); /**
* 生成图片数据
*/
void generateImageData(unsigned char* data, unsigned long dataLen); /**
* 默认的过滤方式
*/
void defaultFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes); /**
* 当前行相加的过滤方式
*/
void addCurrentRowFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes); /**
* 前一行相加的过滤方式
*/
void addUpRowFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes); /**
* 平均的过滤方式
*/
void avergeFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes); /**
* paeth的过滤方式
*/
void paethFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes); /**
* 解析IHDR数据
*/
void parseIHDRData(DataChunkHeader& dataChunkHeader, FILE* pFile); /**
* 解析IDAT数据
*/
void parseIDATData(DataChunkHeader& dataChunkHeader, FILE* pFile); /**
* 解析IEND数据
*/
void parseIENDData(DataChunkHeader& dataChunkHeader, FILE *pFile); /**
* 解析其他数据
*/
void parseCommonData(DataChunkHeader& dataChunkHeader, FILE *pFile);
}; #endif

cpp文件:

 #include "png.h"
#include "utils/cUtil.h"
#include <stdlib.h> #include <windows.h> /**
* 默认构造函数
*/
PNG::PNG()
{
this->m_width = ;
this->m_height = ; this->m_imageData = ;
} /**
* 构造函数
* @param filePath 图片路径
*/
PNG::PNG(const char *filePath)
{
this->m_width = ;
this->m_height = ; this->loadImageDataFromFile(filePath);
} /**
* 析构函数
*/
PNG::~PNG()
{ } /**
* 从文件加载图片数据
*/
bool PNG::loadImageDataFromFile(const char* filePath)
{
FILE* pFile = fopen(filePath, "rb");
if (!pFile)
return false; // 解析PNG标志
char flag[];
char hexFlag[];
fread(flag, , , pFile);
toHexStr(flag, , hexFlag);
if (strcmp(hexFlag, PNG_FLAG_HEX) != )
return false; // 解析图片数据
DataChunkHeader dataChunkHeader;
char dataChunkHeaderType[];
do {
fread(&dataChunkHeader, , sizeof(DataChunkHeader), pFile); memcpy(dataChunkHeaderType, dataChunkHeader.type, );
dataChunkHeaderType[] = '\0'; // IHDR
if ( strcmp(dataChunkHeaderType, DATA_CHUNK_TYPE_IHDR) == ) {
this->parseIHDRData(dataChunkHeader, pFile);
}
// IDAT
else if ( strcmp(dataChunkHeaderType, DATA_CHUNK_TYPE_IDAT) == ) {
this->parseIDATData(dataChunkHeader, pFile);
}
// IEND
else if ( strcmp(dataChunkHeaderType, DATA_CHUNK_TYPE_IEND) == ) {
this->parseIENDData(dataChunkHeader, pFile);
}
// 其他数据
else {
this->parseCommonData(dataChunkHeader, pFile);
}
} while( strcmp(dataChunkHeaderType, DATA_CHUNK_TYPE_IEND) != ); int i = ; return true;
} /**
* 解析数值
*/
int PNG::parseNumber(const unsigned char* data, int len)
{
char localNum[]; bool isLittleEndian = checkEndian();
for (int i = ; i<; i++) {
char ch; if (isLittleEndian) {
if (i <= len-)
ch = data[len - - i];
else
ch = '\0';
}
else {
if (i <= len-)
ch = data[i];
else
ch = '\0';
}
localNum[i] = ch;
} int num;
memcpy(&num, localNum, );
return num;
} /**
* 解析IHDR数据
*/
void PNG::parseIHDRData(DataChunkHeader& dataChunkHeader, FILE* pFile)
{
int dataLen = this->parseNumber(dataChunkHeader.length, ); IDHRData idhrData;
char crc[]; fread(&idhrData, , sizeof(IDHRData), pFile);
fread(crc, , , pFile); this->m_width = this->parseNumber(idhrData.width, );
this->m_height = this->parseNumber(idhrData.height, );
this->m_bitDepth = this->parseNumber(idhrData.bitDepth, );
this->m_colorType = this->parseNumber(idhrData.colorType, );
this->m_compressionMethod = this->parseNumber(idhrData.compressionMethod, );
this->m_filterMethod = this->parseNumber(idhrData.filterMethod, );
this->m_interlaceMethod = this->parseNumber(idhrData.interlaceMethod, );
this->m_chanels = ; switch (this->m_colorType) {
case PNG_COLOR_TYPE_GRAY:
case PNG_COLOR_TYPE_PALETTE:
this->m_chanels = ;
break;
case PNG_COLOR_TYPE_RGB:
this->m_chanels = ;
break;
case PNG_COLOR_TYPE_GRAY_ALPHA:
this->m_chanels = ;
break;
case PNG_COLOR_TYPE_RGB_ALPHA:
this->m_chanels = ;
break;
default:
this->m_chanels = ;
break;
}
} /**
* 解压数据
*/
int PNG::decompressData(z_stream* zStream, unsigned char* data, int dataLen, int leftLen, FILE *pFile)
{
int result = ; int leftBytesCount = leftLen;
int avail_out = -;
do {
if (zStream->avail_in == ) {
if (avail_out == )
break;
else {
if (leftBytesCount == ) {
DataChunkHeader dataChunkHeader;
fread(&dataChunkHeader, , sizeof(DataChunkHeader), pFile); int newDataLen = this->parseNumber(dataChunkHeader.length, );
unsigned char* newData = new unsigned char[dataLen + newDataLen];
char crc[]; fread(newData + dataLen, , newDataLen, pFile);
fread(crc, , , pFile);
memcpy(newData, data, dataLen); delete data;
data = newData; zStream->next_in = newData + dataLen;
zStream->avail_in = newDataLen; dataLen = dataLen + newDataLen; return this->decompressData(zStream, data, dataLen, , pFile);
}
} // 导出数据是否超过限制
if (leftBytesCount > DECOMPRESSION_MAX_BYTES)
zStream->avail_in = DECOMPRESSION_MAX_BYTES;
else
zStream->avail_in = leftBytesCount; leftBytesCount -= zStream->avail_in;
} if (avail_out > )
zStream->avail_out = avail_out;
else
zStream->avail_out = m_width * + ; result = inflate(zStream, Z_NO_FLUSH);
if (result != Z_OK)
break; avail_out = zStream->avail_out;
} while (zStream->avail_in >= ); return result;
} /**
* 生成图片数据
*/
void PNG::generateImageData(unsigned char* data, unsigned long dataLen)
{
// 行字节数
int rowBytes = this->m_chanels * this->m_width; // 初始化图片数据
this->m_imageData = new unsigned char[rowBytes * this->m_height]; unsigned char* pImageData = this->m_imageData;
unsigned char* pRowData = data; for (int rowIndex = ; rowIndex < this->m_height; rowIndex++) {
// 过滤类型
unsigned char filterType = pRowData[]; pRowData += ; switch (filterType) {
// 不需要过滤处理
case DATA_FILTER_TYPE_DEFAULT:
this->defaultFilterType(pImageData, pRowData, rowBytes);
break;
// 当前行相加
case DATA_FILTER_TYPE_ADD_ROW:
this->addCurrentRowFilterType(pImageData, pRowData, rowBytes);
break;
// 和前一行相加
case DATA_FILTER_TYPE_ADD_UP:
this->addUpRowFilterType(pImageData, pRowData, rowBytes);
break;
// 求平均
case DATA_FILTER_TYPE_AVERGE:
this->avergeFilterType(pImageData, pRowData, rowBytes);
break;
// Paeth
case DATA_FILTER_TYPE_PAETH:
this->paethFilterType(pImageData, pRowData, rowBytes);
break;
// 类型错误
default:
break;
} pImageData += rowBytes;
pRowData += rowBytes; char text[];
sprintf(text, "filter type:%d, rowIndex:%d \n", filterType, rowIndex);
OutputDebugString(text);
} int channel = rowBytes / this->m_width;
if (channel == ) {
unsigned int *tmp = (unsigned int *)this->m_imageData; for (unsigned short i = ; i < this->m_height; i++) {
for (unsigned int j = ; j < rowBytes; j+=) {
unsigned int offset = i * rowBytes + j; *tmp++ = RGB_USE_ALPHA(
this->m_imageData[offset],
this->m_imageData[offset+],
this->m_imageData[offset+],
this->m_imageData[offset+]
);
}
}
}
} /**
* 默认的过滤方式
*/
void PNG::defaultFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes)
{
for (int i = ; i < rowBytes; i++) {
*pImageData++ = *pRowData++;
}
} /**
* 当前行相加的过滤方式
*/
void PNG::addCurrentRowFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes)
{
for (int i = ; i < rowBytes; i++) {
if (i == ) {
memcpy(pImageData, pRowData, );
i += ;
pImageData += ;
pRowData += ;
}
else {
*pImageData++ = (unsigned char)(((int)*(pRowData++) + (int)*(pImageData-)) & 0xFF);
}
}
} /**
* 前一行相加的过滤方式
*/
void PNG::addUpRowFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes)
{
for (int i = ; i < rowBytes; i++) {
*pImageData++ = (unsigned char)(((int)*(pRowData++) + (int)*(pImageData-rowBytes)) & 0xFF);
}
} /**
* 平均的过滤方式
*/
void PNG::avergeFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes)
{
for (int i = ; i < rowBytes; i++) {
int averge = ; if (i <= ) {
averge = ((int)*(pImageData-rowBytes)) / ; *pImageData++ = (unsigned char)((averge + (int)*(pRowData++)) & 0xFF);
}
else {
averge = (((int)*(pImageData-)) + ((int)*(pImageData-rowBytes))) / ; *pImageData++ = (unsigned char)((averge + (int)*(pRowData++)) & 0xFF);
}
}
} /**
* paeth的过滤方式
*/
int Paeth(int a, int b, int c)
{
int p = a + b - c;
int pa = abs(p - a);
int pb = abs(p - b);
int pc = abs(p - c); int Paeth;
if(pa <= pb && pa <= pc)
Paeth = a;
else if (pb <= pc)
Paeth = b;
else
Paeth = c;
return Paeth ;
}
void PNG::paethFilterType(unsigned char* pImageData, unsigned char* pRowData, int rowBytes)
{
for (int i = ; i < rowBytes; i++) {
if (i <= ) {
*pImageData++ = (unsigned char)(((int)*(pRowData++) + (int)*(pImageData-rowBytes)) & 0xFF);
}
else {
unsigned char left = *(pImageData - );
unsigned char up = *(pImageData - rowBytes);
unsigned char leftUp = *(pImageData - rowBytes - ); int value = Paeth((int)left, (int)up, (int)leftUp); *pImageData++ = (unsigned char)(((int)*(pRowData++) + value) & 0xFF);
}
}
} /**
* 解析IDAT数据
*/
void PNG::parseIDATData(DataChunkHeader& dataChunkHeader, FILE* pFile)
{
// 解压后的图片数据
unsigned char* imageData = new unsigned char[m_width * m_height * ]; int dataLen = this->parseNumber(dataChunkHeader.length, );
// 解压前的图片数据
unsigned char* data = new unsigned char[dataLen];
char crc[];
// 提取数据
fread(data, , dataLen, pFile);
fread(crc, , , pFile); // 存放临时的解压数据
unsigned long decompressDataLen = m_width * m_height * + m_height;
unsigned char* decompressData = new unsigned char[decompressDataLen]; z_stream* zStream = new z_stream();
zStream->next_in = data;
zStream->next_out = decompressData; inflateInit(zStream); // 解压数据
this->decompressData(zStream, data, dataLen, dataLen, pFile);
// 生成图片数据
this->generateImageData(decompressData, decompressDataLen); /*
int result = 0;
// 开始解压数据
int leftBytesCount = dataLen;
int avail_out = -1;
do {
if (zStream->avail_in == 0) {
if (avail_out == 0)
break;
else {
if (leftBytesCount == 0) { }
} // 导出数据是否超过限制
if (leftBytesCount > DECOMPRESSION_MAX_BYTES)
zStream->avail_in = DECOMPRESSION_MAX_BYTES;
else
zStream->avail_in = leftBytesCount; leftBytesCount = dataLen - zStream->avail_in;
} if (avail_out > 0)
zStream->avail_out = avail_out;
else
zStream->avail_out = m_width * 4 + 1; result = inflate(zStream, Z_NO_FLUSH);
if (result != Z_OK)
break; avail_out = zStream->avail_out;
} while (zStream->avail_in >= 0);
// 数据解压是否成功
if (result == Z_STREAM_END) {
int i = 1;
}
*/
} /**
* 解析IEND数据
*/
void PNG::parseIENDData(DataChunkHeader& dataChunkHeader, FILE *pFile)
{
char crc[];
fread(crc, , , pFile);
} /**
* 解析其他数据
*/
void PNG::parseCommonData(DataChunkHeader& dataChunkHeader, FILE *pFile)
{
int dataLen = this->parseNumber(dataChunkHeader.length, );
fseek(pFile, dataLen + , SEEK_CUR);
} /**
* 获取图片宽度
*/
unsigned char* PNG::getImageData()
{
return this->m_imageData;
} /**
* 获取图片宽度
*/
int PNG::getWindth()
{
return this->m_width;
} /**
* 获取图片高度
*/
int PNG::getHeight()
{
return this->m_height;
}

如果需要绘制图片,可以使用opengl库

参考代码:

 glViewport(, , winWidth, winHeight);    

 glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0f, winWidth - 1.0, 0.0, winHeight - 1.0, -10.0, 10.0); glMatrixMode(GL_MODELVIEW);
glLoadIdentity(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glEnable(GL_TEXTURE_2D); int width = png->getWindth();
int height = png->getHeight();
unsigned char* data = png->getImageData(); GLuint name1;
glGenTextures(, &name1);
glBindTexture(GL_TEXTURE_2D, name1);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexImage2D(GL_TEXTURE_2D, , GL_RGBA, width, height, ,GL_RGBA, GL_UNSIGNED_BYTE, data);
glBegin(GL_POLYGON);
glTexCoord2f(, );
glVertex3d(, , );
glTexCoord2f(, );
glVertex3d(, , );
glTexCoord2f(, );
glVertex3d(, , );
glTexCoord2f(, );
glVertex3d(, , );
glEnd();

PNG图片数据解析的更多相关文章

  1. Silverlight项目笔记7:xml/json数据解析、TreeView、引用类型与数据绑定错误、图片加载、虚拟目录设置、silverlight安全机制引发的问题、WebClient缓存问题

    1.xml/json数据解析 (1)xml数据解析 使用WebClient获取数据,获取到的数据实例化为一个XDocument,使用XDocument的Descendants(XName)方法获得对应 ...

  2. Gprinter热敏打印机光栅位图点阵数据解析工具

    最近参与的项目有一个需求,解析佳博热敏打印机的光栅位图点阵数据并保存为图片文件.数据是通过Bus Hound抓取的,如下图所示. 其中1b 40为初始化打印机的指令,对应的ASCII码为ESC @,1 ...

  3. iOS开发——实战篇Swift篇&UItableView结合网络请求,多线程,数据解析,MVC实战

    UItableView结合网络请求,多线程,数据解析,MVC实战 学了这么久的swift都没有做过什么东西,今天就以自己的一个小小的联系,讲一下,怎么使用swift在实战中应用MVC,并且结合后面的高 ...

  4. BLE 广播数据解析

    从上一篇GATT Profile 简介中提到过,BLE 设备工作的第一步就是向外广播数据.广播数据中带有设备相关的信息.本文主要说一下 BLE 的广播中的数据的规范以及广播包的解析. 广播模式 BLE ...

  5. iOS学习——JSON数据解析(十一)

    在之前的<iOS学习——xml数据解析(九)>介绍了xml数据解析,这一篇简单介绍一下Json数据解析.JSON 即 JavaScript Object Natation,它是一种轻量级的 ...

  6. Python网络爬虫之三种数据解析方式

    1. 正则解析 正则例题 import re # string1 = """<div>静夜思 # 窗前明月光 # 疑是地上霜 # 举头望明月 # 低头思故乡 ...

  7. 05.Python网络爬虫之三种数据解析方式

    引入 回顾requests实现数据爬取的流程 指定url 基于requests模块发起请求 获取响应对象中的数据 进行持久化存储 其实,在上述流程中还需要较为重要的一步,就是在持久化存储之前需要进行指 ...

  8. 初探iOS网络开发,数据解析。

    通过大众点评平台开发来简单了解一下,oc的网络编程和数据解析(json) 首先我们需要到大大众点评开发者平台申请一个key.http://developer.dianping.com/app/tech ...

  9. 数据解析之xpath

    一.环境安装 下载lxml pip install lxml 二.使用 XPath 使用路径表达式来选取 XML 文档中的节点或节点集.节点是通过沿着路径 (path) 或者步 (steps) 来选取 ...

随机推荐

  1. TCP非阻塞通信

    一.SelectableChannel SelectableChannel支持阻塞和非阻塞模式的channel 非阻塞模式下的SelectableChannel,读写不会阻塞 SelectableCh ...

  2. Eclipse快捷键 列表整理

    参考: http://www.roboticfan.com/blog/user_2005/104/archives/2005/20051213213755.shtml Ctrl+1 快速修复(最经典的 ...

  3. 手势识别(一)--手势基本概念和ChaLearn Gesture Challenge

    以下转自: http://blog.csdn.net/qq1175421841/article/details/50312565 像点击(clicks)是GUI平台的核心,轻点(taps)是触摸平台的 ...

  4. Spark on Yarn年度知识整理

    大数据体系结构: Spark简介 Spark是整个BDAS的核心组件,是一个大数据分布式编程框架,不仅实现了MapReduce的算子map 函数和reduce函数及计算模型,还提供更为丰富的算子,如f ...

  5. PLSQL_闪回删除FlashBack Delete表误删除如何进行恢复(案例)

    2014-07-02 Created By BaoXinjian

  6. easy dp

    1.将一堆正整数分为2组,要求2组的和相差最小. //File Name: nod1007.cpp //Author: long //Mail: 736726758@qq.com //Created ...

  7. [复变函数]第17堂课 5 解析函数的 Laurent 展式与孤立奇点 5. 1 解析函数的 Laurent 展式

    0.  引言 (1)  $f$ 在 $|z|<R$ 内解析 $\dps{\ra f(z)=\sum_{n=0}^\infty c_nz^n}$ (Taylor 级数). (2)  $f$ 在 $ ...

  8. Python深入04 闭包

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 闭包(closure)是函数式编程的重要的语法结构.函数式编程是一种编程范式 (而 ...

  9. bootstrap-按钮组、字体图标

    字体图标: <div class="container"> <div class="row"> <span class=" ...

  10. JavaScript【面向对象】-静态方法-私有方法-公有方法-特权方法

    JavaScript面向对象是近年来比较火的一个概念了,由于小弟才疏学浅,虽然做过不少的web项目,看了网上很多深奥的资料和教程,还是对他们深奥 的理论一知半解,前段时间看了点书,总算有了自己的理解, ...