日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

cJSON源码及解析流程详解

發布時間:2024/2/28 javascript 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 cJSON源码及解析流程详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

cJSON簡介

cJSON.h

cJSON結構

cJSON結點的創建

cJSON解析

parse_objec( )

parse_array( )

parse_number( )

parse_string( )

cJSON打印

其他函數

?

cJSON簡介

JSON(JavaScript?Object Notation, JS 對象簡譜) 是一種輕量級的數據交換格式。它基于?ECMAScript?(歐洲計算機協會制定的js規范)的一個子集,采用完全獨立于編程語言的文本格式來存儲和表示數據。簡潔和清晰的層次結構使得 JSON 成為理想的數據交換語言。 易于人閱讀和編寫,同時也易于機器解析和生成,并有效地提升網絡傳輸效率。cJSON從名字可知,整個項目都是以極標準的C來寫的,意思說,可以跨各種平臺使用了。cJSON是一個超輕巧,攜帶方便,單文件,簡單的可以作為ANSI-C標準的JSON解析器。

cJSON官網下載:https://sourceforge.net/projects/cjson/

cJSON ?GitHub:https://github.com/DaveGamble/cJSON

說明:本篇博文分析的源碼是在官網下載的,最后更新時間是2016-9-23。GitHub上的源代碼更新,同時其代碼量也更大。但它們的核心代碼和思想差不多,各位可以根據自身需求選擇。

?

cJSON.h

因為頭文件的內容不是很多,所以我將其全部羅列出來,其中的核心函數是解析函數和打印函數。只要清楚cJSON的數據結構后,看相應的代碼才能比較簡單。頭文件中并沒有包含所有的函數,有的函數是在cJSON.c中定義的,一般是在解析過程中重復次數較多的函數。

#ifndef cJSON__h #define cJSON__h#ifdef __cplusplus extern "C" //extern "C"的主要作用就是為了能夠正確實現C++代碼調用其他C語言代碼。加上extern "C"后, //會指示編譯器這部分代碼按C語言的進行編譯,而不是C++的。由于C++支持函數重載,因此編譯 //器編譯函數的過程中會將函數的參數類型也加到編譯后的代碼中,而不僅僅是函數名;而C語言 //并不支持函數重載,因此編譯C語言代碼的函數時不會帶上函數的參數類型,一般之包括函數名。 { #endif/* cJSON Types: */ #define cJSON_False 0 #define cJSON_True 1 #define cJSON_NULL 2 #define cJSON_Number 3 #define cJSON_String 4 #define cJSON_Array 5 #define cJSON_Object 6#define cJSON_IsReference 256 #define cJSON_StringIsConst 512/* cJSON結構體: */ typedef struct cJSON {struct cJSON *next,*prev; /* next/prev結構體指針允許你遍歷數組/對象鏈。另外,使用GetArraySize 、 GetArrayItem 、 GetObjectItem方法獲取相應的項。 */struct cJSON *child; /* 數組或對象將有一個child指針指向數組/對象中的頭結點。 */int type; /* 項的類型 */char *valuestring; /* 如果type==cJSON_String,則是字符串。 */int valueint; /* 如果type==cJSON_Number,則是int。 */double valuedouble; /* 如果type==cJSON_Number,則是double。 */char *string; /* json對象的名稱。 */ } cJSON;/* 該結構體提供了分配和釋放空間的函數指針 */ typedef struct cJSON_Hooks {void *(*malloc_fn)(size_t sz);void (*free_fn)(void *ptr); } cJSON_Hooks;/* 向cJSON提供malloc和free函數 */ extern void cJSON_InitHooks(cJSON_Hooks* hooks);/* 提供一個JSON塊,然后返回一個可以查詢的cJSON對象。完成后調用cJSON_Delete。 */ extern cJSON *cJSON_Parse(const char *value); /* 將cJSON實體呈現為用于傳輸/存儲的文本。完成后釋放char*。 */ extern char *cJSON_Print(cJSON *item); /* 將cJSON實體呈現為用于傳輸/存儲的文本,而不進行任何格式化。完成后釋放char*。 */ extern char *cJSON_PrintUnformatted(cJSON *item); /* 使用緩沖策略將cJSON實體呈現為文本。預緩沖是對最終大小的猜測。猜測減少了重新分配。fmt=0表示無格式,=1表示有格式 */ extern char *cJSON_PrintBuffered(cJSON *item,int prebuffer,int fmt); /* 刪除一個cJSON實體和所有子實體。 */ extern void cJSON_Delete(cJSON *c);/* 返回數組(或對象)中的項數。 */ extern int cJSON_GetArraySize(cJSON *array); /* 從數組“數組”中檢索項目編號“項目”。如果不成功,返回NULL。 */ extern cJSON *cJSON_GetArrayItem(cJSON *array,int item); /* 從對象中獲取項目"string"。不區分大小寫。 */ extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);/* 用于分析失敗的語法。這將返回一個指向解析錯誤的指針。 你可能需要回頭看幾個字才能理解它。當cJSON_Parse()返回0時定義。當cJSON_Parse()成功時為0。 */ extern const char *cJSON_GetErrorPtr(void);/* 這些調用創建適當Type的cJSON項。 */ extern cJSON *cJSON_CreateNull(void); extern cJSON *cJSON_CreateTrue(void); extern cJSON *cJSON_CreateFalse(void); extern cJSON *cJSON_CreateBool(int b); extern cJSON *cJSON_CreateNumber(double num); extern cJSON *cJSON_CreateString(const char *string); extern cJSON *cJSON_CreateArray(void); extern cJSON *cJSON_CreateObject(void);/* 根據count創建數組。 */ extern cJSON *cJSON_CreateIntArray(const int *numbers,int count); extern cJSON *cJSON_CreateFloatArray(const float *numbers,int count); extern cJSON *cJSON_CreateDoubleArray(const double *numbers,int count); extern cJSON *cJSON_CreateStringArray(const char **strings,int count);/* 向指定的數組/對象追加項。 */ extern void cJSON_AddItemToArray(cJSON *array, cJSON *item); extern void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item); extern void cJSON_AddItemToObjectCS(cJSON *object,const char *string,cJSON *item); /* 當字符串確實是常量(例如,是一個文本,或者與常量一樣好),并且肯定會在cJSON對象中存活時,就使用這個方法 */ /* 將對項的引用追加到指定的數組/對象。當您想要將現有的cJSON添加到新的cJSON中,但又不想破壞現有的cJSON時,可以使用此方法。 */ extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); extern void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item);/* 從數組/對象中刪除/分離項。 */ extern cJSON *cJSON_DetachItemFromArray(cJSON *array,int which); extern void cJSON_DeleteItemFromArray(cJSON *array,int which); extern cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string); extern void cJSON_DeleteItemFromObject(cJSON *object,const char *string);/* 更新數組項 */ extern void cJSON_InsertItemInArray(cJSON *array,int which,cJSON *newitem); /* 將已存在的項目向右移動。 */ extern void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem); extern void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);/* 復制一個cJSON項目 */ extern cJSON *cJSON_Duplicate(cJSON *item,int recurse); /* Duplicate將在需要釋放的新內存中創建一個與傳遞的cJSON相同的新項目。 遞歸!=0,它將復制連接到該項的所有子元素。條目->next和->prev指針在從Duplicate返回時總是0。*//* ParseWithOpts允許您要求(并檢查)JSON是否以null結尾,并檢索指向解析后的最終字節的指針。 */ extern cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated);extern void cJSON_Minify(char *json);/* 用于快速創建內容的宏。 */ #define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) #define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) #define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) #define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) #define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) #define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s))/* 在分配整數值時,也需要將其傳播到valuedouble。 */ #define cJSON_SetIntValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val)) #define cJSON_SetNumberValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val))#ifdef __cplusplus } #endif#endif

?

?

cJSON結構

cjson的存儲結構整體可以簡單的看成一條鏈表(單向),同一級別下的兄弟結點之間通過prev和next指針連接起來(雙向),prev和next分別指向cjson對象的前驅和后繼child則指向下一級結點,這里通過名字也可以理解為指向孩子結點。以下是例子及其結構圖:

{"Asd": [{ "賬號":"a123456789" , "密碼":"asdasdasd123456" },{ "賬號":"b123456789" , "密碼":"qweqweqwe123456" }] }

例子中的Asd是一個數組,里面存儲的內容是對象。對象存儲的內容是賬號和密碼,賬號結點和密碼結點的類型都是string,而string的值分別賦予了賬號和密碼意義。這里直接將cJSON結構圖列出,是為了方便大家閱讀源代碼時方便理解。

?

cJSON結點的創建

結點創建函數很簡單,就是為cJSON結構體申請空間并用memset進行初始化,然而沒有設置結構體內的成員,后續根據需求再對結構體中的類型進行設置。可以看到下面的創建基本類型函數,都是先調用cJSON_New_Item( ),再進行成員設置。

/* 內部構造函數,創建一個新的Item */ static cJSON *cJSON_New_Item(void) {cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON));if (node) memset(node,0,sizeof(cJSON)); //memset是計算機中C/C++語言初始化函數。作用是將某一塊內存中的內容全部設置為指定的值//這個函數通常為新申請的內存做初始化工作。 原型:void *memset(void *s, int ch, size_t n);return node; } /* 創建基本類型: */ // 通過調用cJSON_New_Item()函數創建cJSON結構體,再根據需求設置相應的type,最后返回item。 cJSON *cJSON_CreateNull(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_NULL;return item;} cJSON *cJSON_CreateTrue(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_True;return item;} cJSON *cJSON_CreateFalse(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_False;return item;} cJSON *cJSON_CreateBool(int b) {cJSON *item=cJSON_New_Item();if(item)item->type=b?cJSON_True:cJSON_False;return item;} cJSON *cJSON_CreateNumber(double num) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_Number;item->valuedouble=num;item->valueint=(int)num;}return item;} cJSON *cJSON_CreateString(const char *string) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_String;item->valuestring=cJSON_strdup(string);}return item;} cJSON *cJSON_CreateArray(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Array;return item;} cJSON *cJSON_CreateObject(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Object;return item;}/* 頭文件中快速創建內容的宏 */ /* 用于快速創建內容的宏。 */ #define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) #define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) #define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) #define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) #define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) #define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s))

?

?

cJSON解析

解析代碼是cJSON項目的核心代碼,它的流程圖如下:

cJSON解析的二次封裝:

/* cJSON_Parse的默認選項,用于將文本解析為JSON */ cJSON *cJSON_Parse(const char *value) {return cJSON_ParseWithOpts(value,0,0);} /* 解析一個對象,創建一個新的根,然后填充。 */ cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated) {const char *end=0;cJSON *c=cJSON_New_Item(); //只是創建了一個cJSON結構體,沒有其他操作。ep=0;if (!c) return 0; /* 內存分配失敗,返回0 */end=parse_value(c,skip(value)); //skip只跳過開頭的控制字符 if (!end) {cJSON_Delete(c);return 0;} /* 解析失敗 *//* 如果我們需要沒有附加以null結尾的JSON,跳過,然后檢查是否有null終止符 *///函數中參數中提供了require_null_terminated是為了確保json字符串必須以'\0'字符作為結尾。//若參數提供了return_parse_end,將返回json字符串解析完成后剩余的部分。if (require_null_terminated) {end=skip(end);if (*end) {cJSON_Delete(c);ep=end;return 0;}}if (return_parse_end) *return_parse_end=end;return c; } /* 解析器核心——遇到文本時,適當地處理。 */ static const char *parse_value(cJSON *item,const char *value) {if (!value) return 0; /* 不能為空,若為空,返回0 *///strncmp函數為字符串比較函數,函數聲明為int strncmp ( const char * str1, const char * str2, size_t n );//若str1與str2的前n個字符相同,則返回0;若s1大于s2,則返回大于0的值;若s1 小于s2,則返回小于0的值。//對于json為null、true或者false的情況,直接將type置為對應的類型即可。if (!strncmp(value,"null",4)) { item->type=cJSON_NULL; return value+4; }if (!strncmp(value,"false",5)) { item->type=cJSON_False; return value+5; }if (!strncmp(value,"true",4)) { item->type=cJSON_True; item->valueint=1; return value+4; }//以下需要了解JSON的結構if (*value=='\"') { return parse_string(item,value); }if (*value=='-' || (*value>='0' && *value<='9')) { return parse_number(item,value); }if (*value=='[') { return parse_array(item,value); }if (*value=='{') { return parse_object(item,value); }ep=value;return 0; /* 失敗 */ }

前三步很簡單,調用cJSON_Parse(),其內部為cJSON_ParseWithOpts(),它調用parse_value()函數進行解析,這里函數首先調用cJSON_NewItem()創建節點,用于存放解析的JSON結構數據。然后根據不同的Type,再調用相應的parse_string(),parse_number(),parse_array()或parse_objec()。對應上面提到的cJSON的結構圖,若是同一級下的結點,會形成一個雙向鏈表,用于連接該級下的兄弟結點。若不是同一級,則會讓上一次結點的child指針指向新的結點。

?

parse_objec( )

接下來的代碼分析需要結合官方圖表才能更好的理解其原理。先說一下圖表,對于一個object而言,一定是從 " { " 開始, " } "結束。程序會先判斷該對象是不是為空,即" { " 后除了控制字符外,是不是只剩下?" } "。若是,則說明這個對象是空的。若不是,接下來會遇到whitespace和字符串。程序用skip() 巧妙地跳過了控制字符,方便了對象內容的解析。你會發現代碼中有一段代碼是 value=skip(parse_string(child,skip(value))); ,這是因為跳過whitespace后,會直接遇到string類型的數據。而后會遇到 " :?",代碼中也進行了相應的處理,即value=skip(parse_value(child,skip(value+1)));?。當string和value都處理完了后,可能還有別的內容。這里的判斷條件是如果遇到 " , ",說明后續還有內容,所以在while循環內的代碼重復了上述的工作。在while中,會調用cJSON_New_Item( )創建新的結點,然后將它們連接在一起(此時的內容是同級的)。這里是對圖片和代碼的一個講解,大家可以先看看圖片,再去看代碼,這樣思路會清晰很多。

/* 跳轉空格和cr/lf的實用程序,但只會跳過第一個能顯示的字符之前的控制字符 */ // ASCII碼值小于32的為控制字符 static const char *skip(const char *in) {while (in && *in && (unsigned char)*in<=32) in++; return in;}/* 從文本構建一個對象。 */ static const char *parse_object(cJSON *item,const char *value) {cJSON *child;if (*value!='{') {ep=value;return 0;} /* 不是一個對象! */item->type=cJSON_Object; //類型置為對象value=skip(value+1); //開始找{后的非控制字符if (*value=='}') return value+1; /* 若第一個非控制字符為},則內容為空,即{}。 */item->child=child=cJSON_New_Item(); //為child分配空間,用child指向下一層結點 if (!item->child) return 0; value=skip(parse_string(child,skip(value))); //由JSON的object結構可知,在排除內容為空的前提下,{后遇到的可能是//空格或字符串,所以用skip跳過空格,那么第一個直接遇到的就是字符串//所以直接調用parse_string()函數。最外層的skip函數可以直接指向':'。if (!value) return 0;child->string=child->valuestring;child->valuestring=0;if (*value!=':') {ep=value;return 0;} /* 失敗! */value=skip(parse_value(child,skip(value+1))); /* 跳過任何間距,獲取值。 */if (!value) return 0;while (*value==',') //','表明后面還有內容,那么就將上述操作再進行多次即可。{ cJSON *new_item;if (!(new_item=cJSON_New_Item())) return 0; /* 內存錯誤。 */child->next=new_item;new_item->prev=child;child=new_item;value=skip(parse_string(child,skip(value+1)));if (!value) return 0;child->string=child->valuestring;child->valuestring=0;if (*value!=':') {ep=value;return 0;} /* 失敗! */value=skip(parse_value(child,skip(value+1))); /* 跳過任何間距,獲取值。 */if (!value) return 0;}if (*value=='}') return value+1; /* 數組尾部 */ep=value;return 0; }

?

parse_array( )

同理對于一個array而言,一定是從 " [ " 開始, " ]?"結束。程序會先判斷該數組是不是為空,即" [?" 后除了控制字符外,是不是只剩下?" ]?"。若是,則說明這個數組是空的。若不是,接下來會遇到value。程序用skip() 跳過了控制字符,方便數組內容的解析。這里的操作流程和思想和上述parse_objec()很像,而且array相對于object更簡單點。

/* 從輸入文本構建一個數組。 */ static const char *parse_array(cJSON *item,const char *value) {//解析數組時,其基本思想是對數組中每一個元素遞歸調用parse_value,再將這些元素連接形成一個鏈表。cJSON *child;if (*value!='[') {ep=value;return 0;} /* 不是一個數組! */item->type=cJSON_Array; //置類型為itemvalue=skip(value+1);if (*value==']') return value+1; /* 若為[],則為空數組。 */item->child=child=cJSON_New_Item();if (!item->child) return 0; /* 內存錯誤 */value=skip(parse_value(child,skip(value))); /* 跳過任何間距,獲取值。 */if (!value) return 0;while (*value==',') //','表明后面還有內容,那么就將上述操作再進行多次即可。 { //這一部分相對于parse_object()簡單的原因是','后是value。cJSON *new_item;if (!(new_item=cJSON_New_Item())) return 0; /* 內存錯誤 */child->next=new_item;new_item->prev=child;child=new_item;value=skip(parse_value(child,skip(value+1)));if (!value) return 0; /* 內存錯誤 */}if (*value==']') return value+1; /* 數組的尾部 */ep=value;return 0; }

?

parse_number( )

這一部分,函數比較簡單,反而是圖比較復雜,建議直接看代碼。

/* 解析輸入文本以生成數字,并將結果填充到項中。 */ static const char *parse_number(cJSON *item,const char *num) {double n=0,sign=1,scale=0;int subscale=0,signsubscale=1;if (*num=='-') sign=-1,num++; /* 有標志? */if (*num=='0') num++; /* 是0 */if (*num>='1' && *num<='9') do n=(n*10.0)+(*num++ -'0'); while (*num>='0' && *num<='9'); /* 數字? */if (*num=='.' && num[1]>='0' && num[1]<='9') {num++; do n=(n*10.0)+(*num++ -'0'),scale--; while (*num>='0' && *num<='9');} /* 小數部分? */if (*num=='e' || *num=='E') /* 指數? */{ num++;if (*num=='+') num++; else if (*num=='-') signsubscale=-1,num++; /* 帶標志? */while (*num>='0' && *num<='9') subscale=(subscale*10)+(*num++ - '0'); /* 數字? */}n=sign*n*pow(10.0,(scale+subscale*signsubscale)); /* number = +/- number.fraction * 10^+/- exponent *///double pow(double x, double y); 返回 x 的 y 次冪,即x^y。item->valuedouble=n;item->valueint=(int)n;item->type=cJSON_Number;return num; }

?

parse_string( )

對string的處理思想和上面的基本一樣,不同的是判斷條件的不一樣。因為源代碼中有一句注釋 /* UTF16 surrogate pairs。 */。百度后才知道它是什么意思,字面意義是:代理對。實際上是指:對于一個UTF16編碼改用兩個UTF16編碼來代替。本來一個字符用一個UTF16編碼(兩個字節)來表示即可,但是由于需要被編碼的字符越來越多,只用一個UTF16編碼已經不足于表示所有的字符。因此,就需要考慮使用2個UTF16來表示一個字符(四個字節)。

static unsigned parse_hex4(const char *str) {unsigned h=0;if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0;h=h<<4;str++;if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0;h=h<<4;str++;if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0;h=h<<4;str++;if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0;return h; } /* 將輸入文本解析為未轉義的cstring,并填充項。 */ static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; static const char *parse_string(cJSON *item,const char *str) {const char *ptr=str+1;char *ptr2;char *out;int len=0;unsigned uc,uc2;if (*str!='\"') {ep=str;return 0;} /* 若開頭不是'"',則不是一個字符串! *///由定義ptr=str+1;可知,ptr指向字符串的第個字符while (*ptr!='\"' && *ptr && ++len) if (*ptr++ == '\\') ptr++; /* 跳過了引號。 */// "Asd qwe zxc!" 長度為12,"Asd \"qwe\" zxc!"長度為14。/* 除“或\或控制字符外的任何代碼點 */out=(char*)cJSON_malloc(len+1); /* 這大概是我們需要的長度。 */if (!out) return 0;ptr=str+1;ptr2=out;while (*ptr!='\"' && *ptr){if (*ptr!='\\') *ptr2++=*ptr++;else{ptr++;switch (*ptr){case 'b': *ptr2++='\b'; break; //退格case 'f': *ptr2++='\f'; break; //跳頁case 'n': *ptr2++='\n'; break; //換行case 'r': *ptr2++='\r'; break; //回車case 't': *ptr2++='\t'; break; //水平tabcase 'u': /* utf16轉換utf8。 */uc=parse_hex4(ptr+1);ptr+=4; /* 獲取unicode字符。 */if ((uc>=0xDC00 && uc<=0xDFFF) || uc==0) break; /* 檢查無效的。 */if (uc>=0xD800 && uc<=0xDBFF) /* UTF16 surrogate pairs。 字面意義是:代理對。實際上是指:對于一個UTF16編碼改用兩個UTF16編碼來代替。*/{ //本來一個字符用一個UTF16編碼(兩個字節)來表示即可,但是由于需要被編碼的字符越來越多,//只用一個UTF16編碼已經不足于表示所有的字符。因此,就需要考慮使用2個UTF16來表示一個字符(四個字節)。if (ptr[1]!='\\' || ptr[2]!='u') break; /* surrogate的下半部分不見了。 */uc2=parse_hex4(ptr+3);ptr+=6;if (uc2<0xDC00 || uc2>0xDFFF) break; /* surrogate的后半部分無效。*/uc=0x10000 + (((uc&0x3FF)<<10) | (uc2&0x3FF));}len=4;if (uc<0x80) len=1;else if (uc<0x800) len=2;else if (uc<0x10000) len=3; ptr2+=len;switch (len) {case 4: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6;case 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6;case 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6;case 1: *--ptr2 =(uc | firstByteMark[len]);}ptr2+=len;break;default: *ptr2++=*ptr; break;}ptr++;}}*ptr2=0;if (*ptr=='\"') ptr++;item->valuestring=out;item->type=cJSON_String;return ptr; }

?

?

cJSON打印

打印的過程類似于鏈表的遍歷,它的流程圖如下:

/* 將cJSON項/實體/結構呈現為文本。 */ char *cJSON_Print(cJSON *item) {return print_value(item,0,1,0);} char *cJSON_PrintUnformatted(cJSON *item) {return print_value(item,0,0,0);} //cjson打印就是從根對象的結構體開始遍歷,得到每個item結點的名稱和數據,并經過處理成特定的cjson字符串的輸出形式。 //cJSON_Print(root)和cJSON_PrintUnformatted(root)函數都是打印成json字符串的函數,兩者的區別就是 //cJSON_PrintUnformatted(root)處理過的字符串里沒有\t\n這類格式

我用官方給實例代碼運行程序時,因為沒有涉及到printbuffer(即令該值為0),所以程序走的都是else內的操作。

/* 為文本呈現一個值。 */ static char *print_value(cJSON *item,int depth,int fmt,printbuffer *p) { // fmt傳入時值為1char *out=0;if (!item) return 0;if (p){switch ((item->type)&255){case cJSON_NULL: {out=ensure(p,5); if (out) strcpy(out,"null"); break;}case cJSON_False: {out=ensure(p,6); if (out) strcpy(out,"false"); break;}case cJSON_True: {out=ensure(p,5); if (out) strcpy(out,"true"); break;}case cJSON_Number: out=print_number(item,p);break;case cJSON_String: out=print_string(item,p);break;case cJSON_Array: out=print_array(item,depth,fmt,p);break;case cJSON_Object: out=print_object(item,depth,fmt,p);break;}}else{switch ((item->type)&255){case cJSON_NULL: out=cJSON_strdup("null"); break;case cJSON_False: out=cJSON_strdup("false");break;case cJSON_True: out=cJSON_strdup("true"); break;case cJSON_Number: out=print_number(item,0);break;case cJSON_String: out=print_string(item,0);break;case cJSON_Array: out=print_array(item,depth,fmt,0);break;case cJSON_Object: out=print_object(item,depth,fmt,0);break;}}return out; }

?

該函數,開始使用numentries記錄對象的數量。因為上面提到了printbuffer *p = 0,所以在組成輸出格式的時候,分配2個指針數組存儲對象里的鍵和值。最后通過對兩個指針數組的內容的拼接,將數據組成輸出的格式。可以看到代碼末端" { " 和 " } "之間的代碼都是拼接操作。這部分的代碼,要先將else內的邏輯流程看懂。

/* 將對象呈現為文本。 */ static char *print_object(cJSON *item,int depth,int fmt,printbuffer *p) {char **entries=0,**names=0;char *out=0,*ptr,*ret,*str;int len=7,i=0,j;cJSON *child=item->child;int numentries=0,fail=0;size_t tmplen=0;/* numentries記錄對象的數量。 */while (child) numentries++,child=child->next;/* 顯式處理空對象情況 */if (!numentries){if (p) out=ensure(p,fmt?depth+4:3);else out=(char*)cJSON_malloc(fmt?depth+4:3);if (!out) return 0;ptr=out;*ptr++='{';if (fmt) {*ptr++='\n';for (i=0;i<depth-1;i++) *ptr++='\t';}*ptr++='}';*ptr++=0;return out;}if (p){/* 組成的輸出: */i=p->offset;len=fmt?2:1; ptr=ensure(p,len+1); if (!ptr) return 0;*ptr++='{'; if (fmt) *ptr++='\n'; *ptr=0; p->offset+=len;child=item->child;depth++;while (child){if (fmt){ptr=ensure(p,depth); if (!ptr) return 0;for (j=0;j<depth;j++) *ptr++='\t';p->offset+=depth;}print_string_ptr(child->string,p);p->offset=update(p);len=fmt?2:1;ptr=ensure(p,len); if (!ptr) return 0;*ptr++=':';if (fmt) *ptr++='\t';p->offset+=len;print_value(child,depth,fmt,p);p->offset=update(p);len=(fmt?1:0)+(child->next?1:0);ptr=ensure(p,len+1); if (!ptr) return 0;if (child->next) *ptr++=',';if (fmt) *ptr++='\n';*ptr=0;p->offset+=len;child=child->next;}ptr=ensure(p,fmt?(depth+1):2); if (!ptr) return 0;if (fmt) for (i=0;i<depth-1;i++) *ptr++='\t';*ptr++='}';*ptr=0;out=(p->buffer)+i;}else{//分配2個指針數組存儲對象里的鍵和值。entries=(char**)cJSON_malloc(numentries*sizeof(char*));if (!entries) return 0;names=(char**)cJSON_malloc(numentries*sizeof(char*));if (!names) {cJSON_free(entries);return 0;}memset(entries,0,sizeof(char*)*numentries);memset(names,0,sizeof(char*)*numentries);/* 收集所有結果到我們的數組: */child=item->child;depth++;if (fmt) len+=depth;while (child){names[i]=str=print_string_ptr(child->string,0);entries[i++]=ret=print_value(child,depth,fmt,0);if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1;child=child->next;}/* 嘗試分配輸出字符串 */if (!fail) out=(char*)cJSON_malloc(len);if (!out) fail=1;/* 處理失敗 */if (fail){for (i=0;i<numentries;i++) {if (names[i]) cJSON_free(names[i]);if (entries[i]) cJSON_free(entries[i]);}cJSON_free(names);cJSON_free(entries);return 0;}/* 組成的輸出: */*out='{';ptr=out+1;if (fmt)*ptr++='\n';*ptr=0;for (i=0;i<numentries;i++){if (fmt) for (j=0;j<depth;j++) *ptr++='\t';tmplen=strlen(names[i]);memcpy(ptr,names[i],tmplen);ptr+=tmplen;*ptr++=':';if (fmt) *ptr++='\t';strcpy(ptr,entries[i]);ptr+=strlen(entries[i]);if (i!=numentries-1) *ptr++=',';if (fmt) *ptr++='\n';*ptr=0;cJSON_free(names[i]);cJSON_free(entries[i]);}cJSON_free(names);cJSON_free(entries);if (fmt) for (i=0;i<depth-1;i++) *ptr++='\t';*ptr++='}';*ptr++=0;}return out; }

?

該函數,開始使用numentries記錄數組中元素的數量。這里的printbuffer *p = 0,在組成輸出格式的時候,分配一個指針數組存儲數組里的每一個元素的打印結果。這里多的一個操作是,數組內容可能是對象,所以要調用print_value()打印相應內容。可以在源代碼中看到ret=print_value(child,depth+1,fmt,0);??。獲取數據后,接下來組成數組的輸出形式。代碼末端" [?" 和 " ]?"之間的代碼都是拼接操作。

/* 將數組呈現為文本 */ static char *print_array(cJSON *item,int depth,int fmt,printbuffer *p) {char **entries;char *out=0,*ptr,*ret;int len=5;cJSON *child=item->child;int numentries=0,i=0,fail=0;size_t tmplen=0;/* numentries記錄數組中有多少元素 */while (child) numentries++,child=child->next;/* 處理numentries == 0 */if (!numentries){if (p) out=ensure(p,3);else out=(char*)cJSON_malloc(3);if (out) strcpy(out,"[]");return out;}if (p){/* 組合輸出數組。*/i=p->offset;ptr=ensure(p,1);if (!ptr) return 0; *ptr='['; p->offset++;child=item->child;while (child && !fail){print_value(child,depth+1,fmt,p);p->offset=update(p);if (child->next) {len=fmt?2:1;ptr=ensure(p,len+1);if (!ptr) return 0;*ptr++=',';if(fmt)*ptr++=' ';*ptr=0;p->offset+=len;}child=child->next;}ptr=ensure(p,2);if (!ptr) return 0; *ptr++=']';*ptr=0;out=(p->buffer)+i;}else{/* 分配一個指針數組存儲數組里的每一個元素的打印結果。 */entries=(char**)cJSON_malloc(numentries*sizeof(char*));if (!entries) return 0;memset(entries,0,numentries*sizeof(char*));/* 檢索所有結果: */child=item->child;while (child && !fail){ret=print_value(child,depth+1,fmt,0); //數組內容可能是對象,所以要調用print_value打印相應內容。返回值為char*entries[i++]=ret;if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1;child=child->next;}/* 如果沒有失敗,嘗試malloc輸出字符串 */if (!fail) out=(char*)cJSON_malloc(len);/* 如果失敗了,我們就失敗了。 */if (!out) fail=1;/* 處理失敗。 */if (fail){for (i=0;i<numentries;i++) if (entries[i]) cJSON_free(entries[i]);cJSON_free(entries);return 0;}/* 數據已經獲取了,接下來組成數組的輸出形式。*/*out='[';ptr=out+1;*ptr=0;for (i=0;i<numentries;i++){tmplen=strlen(entries[i]);memcpy(ptr,entries[i],tmplen);ptr+=tmplen;if (i!=numentries-1) {*ptr++=',';if(fmt)*ptr++=' ';*ptr=0;}cJSON_free(entries[i]);}cJSON_free(entries);*ptr++=']';*ptr++=0;}return out; }

?

在看這部分代碼時,我一直在糾結ensure(),最后發現它是一個協助printbuffer分配內存的函數,而代碼中的printbuffer *p = 0。所以只需要跳過相應的代碼就可以理解它的邏輯流程了。這里遇到不懂的是DBL_EPSILON和sprintf(str,"%d",item->valueint);,這兩個問題的解釋在注釋中。

/* 將給定項中的數字漂亮地呈現為字符串。 */ static char *print_number(cJSON *item,printbuffer *p) {char *str=0;double d=item->valuedouble;if (d==0){if (p) str=ensure(p,2);else str=(char*)cJSON_malloc(2); /* 0的特殊情況。 */if (str) strcpy(str,"0");}else if (fabs(((double)item->valueint)-d)<=DBL_EPSILON && d<=INT_MAX && d>=INT_MIN){ //fabs 作用是求浮點數的絕對值。 double fabs(double x );//DBL_EPSILON和 FLT_EPSILON主要用于單精度和雙精度的比較當中:if (p) str=ensure(p,21);else str=(char*)cJSON_malloc(21); /* 2^64+1 可以用21個字符表示。 */if (str) sprintf(str,"%d",item->valueint); //函數功能:格式化字符串,將格式化的數據寫入字符串中。//函數原型:int sprintf(char *buffer, const char *format, [argument]…)//格式化數字字符串:在這點上sprintf和printf的用法一樣,只是打印到的位置不同而已,前者打印給buffer字符串//后者打印給標準輸出,所以sprintf也可以用來將整型轉化為字符串//比itoa效率高且簡便,比如:sprintf(buffer, "%d", 123456);執行后buffer即指向字符串"123456"。}else{if (p) str=ensure(p,64);else str=(char*)cJSON_malloc(64); /* 這是一個很好的權衡。 */if (str){if (fabs(floor(d)-d)<=DBL_EPSILON && fabs(d)<1.0e60)sprintf(str,"%.0f",d);else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9) sprintf(str,"%e",d);else sprintf(str,"%f",d);}}return str; }

?

這里主要看print_string_ptr()中while里的內容。

/* 引用print_string_ptr()函數。 */ static char *print_string(cJSON *item,printbuffer *p) {return print_string_ptr(item->valuestring,p);} /* 將提供的cstring呈現給一個可以打印的轉義版本。 */ static char *print_string_ptr(const char *str,printbuffer *p) {const char *ptr;char *ptr2,*out;int len=0,flag=0;unsigned char token;for (ptr=str;*ptr;ptr++) flag|=((*ptr>0 && *ptr<32)||(*ptr=='\"')||(*ptr=='\\'))?1:0;if (!flag) //對字符串中不含'\','/',空格等字符的字符處理{len=ptr-str;if (p) out=ensure(p,len+3);else out=(char*)cJSON_malloc(len+3);if (!out) return 0;ptr2=out;*ptr2++='\"';strcpy(ptr2,str);ptr2[len]='\"';ptr2[len+1]=0;return out;}if (!str){if (p) out=ensure(p,3);else out=(char*)cJSON_malloc(3);if (!out) return 0;strcpy(out,"\"\"");return out;}ptr=str;while ((token=*ptr) && ++len) {if (strchr("\"\\\b\f\n\r\t",token)) len++; else if (token<32) len+=5;ptr++;}if (p) out=ensure(p,len+3);else out=(char*)cJSON_malloc(len+3);if (!out) return 0;ptr2=out;ptr=str;*ptr2++='\"';while (*ptr){if ((unsigned char)*ptr>31 && *ptr!='\"' && *ptr!='\\') *ptr2++=*ptr++;else{*ptr2++='\\';switch (token=*ptr++){case '\\': *ptr2++='\\'; break;case '\"': *ptr2++='\"'; break;case '\b': *ptr2++='b'; break;case '\f': *ptr2++='f'; break;case '\n': *ptr2++='n'; break;case '\r': *ptr2++='r'; break;case '\t': *ptr2++='t'; break;default: sprintf(ptr2,"u%04x",token);ptr2+=5; break; /* escape and print */}}}*ptr2++='\"';*ptr2++=0;return out; }

?

?

其他函數

聲明函數指針,方便分配和釋放空間。以下代碼就不細講了,注釋中有相應的解釋。

static void *(*cJSON_malloc)(size_t sz) = malloc; //聲明函數指針,例如:double (*pf)(int); static void (*cJSON_free)(void *ptr) = free; //這兩個函數返回值都是void * /* 該結構體提供了分配和釋放空間的函數指針 */ typedef struct cJSON_Hooks {void *(*malloc_fn)(size_t sz);void (*free_fn)(void *ptr); } cJSON_Hooks;void cJSON_InitHooks(cJSON_Hooks* hooks) {if (!hooks) { /* 若hooks為空 */cJSON_malloc = malloc;cJSON_free = free;return;}cJSON_malloc = (hooks->malloc_fn)?hooks->malloc_fn:malloc;cJSON_free = (hooks->free_fn)?hooks->free_fn:free; }

?

cJSON_strcasecmp()

static int cJSON_strcasecmp(const char *s1,const char *s2) //功能和strcasecmp一樣,忽略大小寫比較字符串。 {if (!s1) return (s1==s2)?0:1; //s1若為空,根據s1和s2是否相等返回0或者1。if (!s2) return 1; //s2若為空,返回1。for(; tolower(*s1) == tolower(*s2); ++s1, ++s2) //tolower的功能是把字母字符轉換成小寫,非字母字符不做出處理。if(*s1 == 0) //函數說明:若參數 c 為大寫字母則將該對應的小寫字母返回。return 0; //返回值:返回轉換后的小寫字母,若不須轉換則將參數c 值返回。return tolower(*(const unsigned char *)s1) - tolower(*(const unsigned char *)s2); }

?

cJSON_strdup()

static char* cJSON_strdup(const char* str) //cJSON的字符串拷貝函數 { //strdup()函數是c語言中常用的一種字符串拷貝庫函數,一般和free()函數成對出現。size_t len; //strdup()在內部調用了malloc()為變量分配內存,不需要使用返回的字符串時char* copy; //需要用free()釋放相應的內存空間,否則會造成內存泄漏。len = strlen(str) + 1;if (!(copy = (char*)cJSON_malloc(len))) return 0;memcpy(copy,str,len); //memcpy指的是C和C++使用的內存拷貝函數return copy; //函數原型為void *memcpy(void *destin, void *source, unsigned n); } //函數的功能是從源內存地址的起始位置開始拷貝若干個字節到目標內存地址中。

?

cJSON_Delete()

/* 刪除cJSON結構體 */ void cJSON_Delete(cJSON *c) {cJSON *next;while (c){next=c->next;//遞歸刪除結點if (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child);if (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_free(c->valuestring);if (!(c->type&cJSON_StringIsConst) && c->string) cJSON_free(c->string);cJSON_free(c);c=next;} }

?

ensure()

/* 例如x = 6或8,則返回8。 x = 12或13,則返回16。 總結:返回 >= x的最小2的n次方。 */ static int pow2gt (int x) { --x; x|=x>>1; x|=x>>2; x|=x>>4; x|=x>>8; x|=x>>16; return x+1; }typedef struct {char *buffer; int length; int offset; } printbuffer; //printbuffer的數據結構:地址、長度和偏移量。static char* ensure(printbuffer *p,int needed) //ensure 函數是一個協助printbuffer分配內存的一個函數 //p->length表示當前字符串的長度 {char *newbuffer;int newsize;if (!p || !p->buffer) return 0; //p空或p->buffer空,則返回0。needed+=p->offset; //需要額外分配的內存,也就是偏移量if (needed<=p->length) return p->buffer+p->offset;newsize=pow2gt(needed); //pow2gt返回newsize所需要的空間。newbuffer=(char*)cJSON_malloc(newsize); //malloc出新內存 放buffer里面的內容。if (!newbuffer) {cJSON_free(p->buffer);p->length=0,p->buffer=0;return 0;}if (newbuffer) memcpy(newbuffer,p->buffer,p->length); //分配成功就將原先的p的內容復制過去。cJSON_free(p->buffer);p->length=newsize;p->buffer=newbuffer;return newbuffer+p->offset; }

?

創建數組的一些列函數

/* 用于數組列表處理的實用工具。 */ // prev->next指向item,item->prev指向prev。 static void suffix_object(cJSON *prev,cJSON *item) {prev->next=item;item->prev=prev;}/* 創建數組: */ cJSON *cJSON_CreateIntArray(const int *numbers,int count) { int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateNumber(numbers[i]);if(!i) //若i為0。a->child=n;else suffix_object(p,n); // p->next指向n,n->prev指向p。p=n;}return a; } cJSON *cJSON_CreateFloatArray(const float *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateNumber(numbers[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;} cJSON *cJSON_CreateDoubleArray(const double *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateNumber(numbers[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;} cJSON *cJSON_CreateStringArray(const char **strings,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && i<count;i++){n=cJSON_CreateString(strings[i]);if(!i)a->child=n;else suffix_object(p,n);p=n;}return a;}

?

添加項到數組/對象中

/* 用于數組列表處理的實用工具。 */ // prev->next指向item,item->prev指向prev。 static void suffix_object(cJSON *prev,cJSON *item) {prev->next=item;item->prev=prev;}/* 將項目添加到數組/對象中。 */ void cJSON_AddItemToArray(cJSON *array, cJSON *item) {cJSON *c=array->child;if (!item) return; if (!c) {array->child=item;} else {while (c && c->next) c=c->next; suffix_object(c,item); //c->next指向item,item->prev指向c。} }void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item) {if (!item) return; if (item->string) cJSON_free(item->string);item->string=cJSON_strdup(string);cJSON_AddItemToArray(object,item);} void cJSON_AddItemToObjectCS(cJSON *object,const char *string,cJSON *item) {if (!item) return; if (!(item->type&cJSON_StringIsConst) && item->string) cJSON_free(item->string);item->string=(char*)string;item->type|=cJSON_StringIsConst;cJSON_AddItemToArray(object,item);} void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) {cJSON_AddItemToArray(array,create_reference(item));} void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item) {cJSON_AddItemToObject(object,string,create_reference(item));}

?

總結

以上是生活随笔為你收集整理的cJSON源码及解析流程详解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 被黑人啪到哭的番号922在线 | 亚洲黄网在线 | 日韩涩 | 亚洲最大av网 | 久久久久99精品成人片试看 | 欧美一级日韩 | 久久婷婷国产麻豆91天堂 | 国产鲁鲁视频在线观看免费 | 亚洲色中色 | 女生毛片 | 国v精品久久久网 | 亚洲第一色视频 | 中国免费观看的视频 | 青娱乐欧美 | 天天色天天操天天射 | 超碰不卡 | 香蕉视频在线网站 | 91瑟瑟 | 超碰国产在线观看 | 黄频在线观看 | 国产一区二区免费 | 九九热视频精品在线观看 | 久久久久亚洲av无码专区首jn | av伊人久久 | 亚洲精品国产av | 五月导航 | 视色视频 | 成人黄色性视频 | 欧美日韩精品国产 | 午夜痒痒网 | 大尺度一区二区 | 91黑丝在线观看 | 色多多在线视频 | 新天堂在线 | 久久久久久人妻一区二区三区 | 亚洲色图国产精品 | 在线观看的av网址 | 亚洲一区二区影院 | 精品综合久久 | www.亚洲综合 | 日本va在线| 国产高清色 | 国产精品一区无码 | 日本暧暧视频 | 久国产 | 处女朱莉第一次 | 色先锋影院 | 色老头在线视频 | 色噜噜在线观看 | 黄色大片av| 日本黄色片网址 | 国产精品-区区久久久狼 | 在线国产一区二区三区 | 国产91丝袜在线播放0 | 三上悠亚在线观看一区二区 | 99国产精品视频免费观看一公开 | 日本特级淫片 | 天天干人人干 | 日本一区二区三区免费视频 | 国产日韩激情 | 免费久久精品视频 | 日韩少妇毛片 | 免费在线看污视频 | 大陆av在线| 精品国产av无码 | 国产成人三级 | 亚洲第一成年人网站 | 好吊色欧美一区二区三区视频 | 九九热只有精品 | 国产18在线 | 伊人久久九 | 囯产精品一品二区三区 | 国产sm主人调教女m视频 | 国产精品久久久久久久久久免费看 | 麻豆性视频 | 91精品啪在线观看国产线免费 | 亚洲综合小说网 | 国产精品黄视频 | 伊人网在线视频观看 | 黄色片地址 | 久久偷看各类女兵18女厕嘘嘘 | 欧美性色网站 | 欧美片一区二区 | 国产亚洲精品成人无码精品网站 | 999国产精品视频免费 | 久草中文网 | 99久久久久| 亚洲校园激情 | 日韩在线视频一区二区三区 | 潘金莲一级淫片aaaaa | www.夜色| 国产精品系列在线 | 大屁股白浆一区二区三区 | 禁果av一区二区三区 | 伊人成年网 | 精品熟女一区二区 | jzzijzzij日本成熟少妇 | 男女在线免费观看 | 成熟女人毛片www免费版在线 |