剛學(xué)C 語言要如何實(shí)現(xiàn)面向?qū)ο缶幊蹋ㄒ呀鉀Q)

  1. 具體和抽象

  具體:客觀存在著的或在認(rèn)識(shí)中反映出來的事物的整體,是具有多方面屬性、特點(diǎn)、關(guān)系的統(tǒng)一;

  抽象:從具體事物中被抽取出來的相對獨(dú)立的各個(gè)方面、屬性、關(guān)系等。

  以 Person

為例:“pmst”,“numbbbbb”,“MM”等都是客觀存在的,稱之為具體;然后我們抽取共同的特性:姓名,性別,年齡和介紹自己等(當(dāng)然這是極小、極小的一部分)。

  這個(gè)資料可以進(jìn)行學(xué)習(xí)參考:

? ? ? ?C語言實(shí)現(xiàn)面向?qū)ο缶幊蹋篬http://www.makeru.com.cn/live/1392_1051.html?s=45051

  2. C 語言抽象的雛形

  先用 C 語言抽象,實(shí)現(xiàn)如下:

  typedef struct Person Person;

  typedef void (*Method)(Person *my_self);

  typedef struct Person {

  char name[12];

  int age;

  int sex;

  Method behavior1; // 行為1

  } Person;

  void selfIntroducation(Person *my_self) {

  printf("my name is %s,age %d,sex

%d\n",my_self->name,my_self->age,my_self->sex);

  }

  int main(int argc, const char * argv[]) {

  // 1

  Person *pmst = (Person *)malloc(sizeof(Person));

  // 1.1

  strcpy(pmst->name, "pmst");

  pmst->age = 18;

  pmst->sex = 0;

  // 2

  pmst->behavior1 = selfIntroducation;

  // 3

  pmst->behavior1(pmst);

  return 0;

  }

  int,float,struct 等類型在編譯之后轉(zhuǎn)變成對內(nèi)存地址的訪問,比如 1 中的 pmst 指針在調(diào)用 malloc 方法后返回分配的地址為

0x12345678,會(huì)標(biāo)識(shí)占 sizeof(Person) 個(gè)字節(jié);pmst->age = 18 其實(shí)是對 0x12345678 偏移 12

字節(jié)內(nèi)存的賦值,占4個(gè)字節(jié);

  函數(shù)在編譯之后放置在代碼段,入口為函數(shù)指針;

  pmst->behavior1(pmst); 先取到 0x12345678 偏移 20 字節(jié)內(nèi)存的值————函數(shù)指針,然后 call

命令調(diào)用

  編譯之后代碼都變成了對內(nèi)存地址的訪問,稱之為靜態(tài)綁定;那么該如何實(shí)現(xiàn) Runtime

運(yùn)行時(shí)的動(dòng)態(tài)訪問呢?比如在UI界面上(ps:Terminal那種古老的輸入輸出方式也是OK的)輸入一個(gè)類的名稱以及調(diào)用方法名稱,緊接著我們要實(shí)例化一個(gè)該類的對象,然后調(diào)用方法。

  3. C 語言實(shí)現(xiàn)動(dòng)態(tài)性

  3.1 運(yùn)行時(shí)如何實(shí)現(xiàn)抽象->具體

  想要運(yùn)行時(shí)隨心所欲地將抽象轉(zhuǎn)變成具體,就需要在內(nèi)存中保存一份對抽象的描述,這里的描述并非指 typedef struct Person

{...}Person 定義 ———— 這是靜態(tài)的,而是開辟一塊內(nèi)存加載一份 json 抽象描述:

  {

  "Name": "Person",

  "VariableList":[

  {

  "VarName":"name",

  "Type":"char[]",

  "MemorySize":12,

  },

  {

  "VarName":"age",

  "Type":"int",

  "MemorySize":4,

  },

  {

  "VarName":"sex",

  "Type":"int",

  "MemorySize":4,

  },

  ],

  "MethodList":[

  {

  "name":"selfIntroducation",

  "methodAddress":0x12345678

  },

  ]

  }

  關(guān)于這串json描述,可以是在編譯階段生成的,運(yùn)行時(shí)使用 char *description 加載到堆內(nèi)存上,需要時(shí)通過對應(yīng)的 Key 取到

Value:例如 Key=Name 可以取到類名,Key=VariableList 可以取到變量列表,Key=MethodList

可以取到方法列表。這里可能需要有個(gè)小小的Parser解析器。

  3.2 二次抽象,生成類對象

  倘若每次用到時(shí)就要進(jìn)行一次 char *description 信息 parser

解析,性能這關(guān)都過不去,正確做法是解析成某個(gè)數(shù)據(jù)結(jié)構(gòu),保存到堆內(nèi)存中:

  // 偽代碼如下

  typedef struct Variable {

  char *name;

  char *type;

  int memorySize;

  }Variable;

  typedef struct Method {

  char *name;

  void (*callAddress)(void *my_self);// 顯然這里多參傳入 當(dāng)然這些暫時(shí)不考慮

  }Method;

  typedef struct Class {

  char *className;

  /// Variable List 是一個(gè)數(shù)組 類型為 Variable

  Variable *variableList;

  /// Method List 也是一個(gè)數(shù)組 類型為 Method

  Method *methodList

  }

  上述是最簡單的抽象定義,現(xiàn)在我們將解析json信息,然后分配內(nèi)存填充信息(抽象->具體)的過程,首先 Class

是一個(gè)抽象概念,抽象出類名、變量列表和方法列表等信息,但此刻我們開辟了一塊內(nèi)存填充信息———— 客觀存在了,稱之為對象(通常我們稱之為class

object,類對象)

  //////////////偽代碼如下/////////////////

  //////////////////////////////////////////

  // parse person json proccess get result

  ///////////////////end////////////////////

  // 分配一塊內(nèi)存

  Class *personClsObject = (Class *)malloc(sizeof(Class));

  strcpy(personClsObject->className, "Person");

  personClsObject->variableList = (Variable *)malloc(sizeof(Variable) *

3);// 有3個(gè)變量

  Variable *nameVar = (Variable *)malloc(sizeof(Variable));

  strcpy(nameVar->name, "name");

  strcpy(nameVar->type, "char[]");

  nameVar->memorySize = 12;

  //... ageVar & sexVar 生成

  personClsObject->variableList[0] = nameVar;

  personClsObject->variableList[1] = ageVar;

  personClsObject->variableList[2] = sexVar;

  // 同理生成一個(gè)個(gè)Method 然后填充到 personClsObject->methodList;

  你、我、他是客觀存在的稱之為對象,進(jìn)一步抽象出了姓名、性別和年齡三個(gè)方面,使用 struct Person

結(jié)構(gòu)體定義,之前說了編譯之后不存在所謂的結(jié)構(gòu)體,都會(huì)轉(zhuǎn)而變成對內(nèi)存地址的訪問;我們換了種思路,又抽象出了一個(gè) Class

結(jié)構(gòu)體,然后分配一塊具體客觀存在的內(nèi)存保存信息,即 personClsObject 類對象(class

object),然后將所有變量信息存儲(chǔ)到variableList,方法信息存儲(chǔ)到 methodList。

  舉一反三,如果繼續(xù)定義typedef struct Animal,typedef struct Car

等一系列的類,那么必定也會(huì)各自在堆內(nèi)存上生成有且僅有一個(gè) AnimalClsObject 、CarClsObject 類對象!

  3.2 使用類對象來生成實(shí)例對象

  上文說到內(nèi)存中保存了一份對 Person

抽象描述,即PersonClsObject類對象,包含類名稱,變量列表,方法列表等信息。此刻進(jìn)一步具體到現(xiàn)實(shí)生活中某個(gè)具體的人,生成 “pmst”

博主我,"numbbbbb" 幫主梁杰,“mm”靈魂畫師,有種God創(chuàng)世的趕腳。這一個(gè)個(gè)都是現(xiàn)實(shí)存在的,即實(shí)例對象————自然要分配一塊內(nèi)存給各自,填充

name名字,sex性別,age年齡。

  [圖片上傳失敗...(image-3d0165-1519970136607)]

  ///////////// 以下為偽代碼 ////////////////////

  // 可以遍歷 personClsObject 中variableList所有變量

  // 取到每個(gè)變量所占的內(nèi)存大小memorySize,累加得到總的需要分配的內(nèi)存大小

  int size = 0;

  for variable in personClsObject->variableList {

  size = variable->memorySize;// 當(dāng)然這里肯定要考慮內(nèi)存對齊問題

  }

  Person *pmst = (Person *)malloc(size); // 分配內(nèi)存 得到指針0x10000000

  Person *numbbbb = (Person *)malloc(size); // 分配內(nèi)存 得到指針0x10001000

  Person *MM = (Person *)malloc(size); // 分配內(nèi)存 得到指針0x10002000

  note:

這里只為實(shí)例變量分配了內(nèi)存,章節(jié)2中我們還包含一個(gè)8字節(jié)的函數(shù)指針,那么問題來了,現(xiàn)在我們該如何調(diào)用selfIntroducation函數(shù)呢?

  3.3 實(shí)例對象和類對象

  因?yàn)槲覀冊趦?nèi)存中保存了一份對 Person

的抽象描述,在運(yùn)行時(shí)就知道Person包含哪些允許調(diào)用的函數(shù)名稱,函數(shù)類型以及位于代碼段的函數(shù)入口地址。

  章節(jié) 2 中使用了 pmst->behavior1(pmst) 調(diào)用方式 :先取到函數(shù)指針,然后把實(shí)例對象自身指針作為傳參傳入調(diào)用?,F(xiàn)在有了

personClsObject 我們又該如何實(shí)現(xiàn)這種調(diào)用呢?

  /// 偽代碼如下

  /// C語言函數(shù)返回類型為函數(shù)指針寫法如下:

  /// ps:當(dāng)然也可以先typedef 然后替換返回類型,

  void (*findMethod(char *methodName))(void *myself) {

  for method in personClsObject->methodList {

  if methodName == method->name {

  return method->callAddress;

  }

  }

  return NULL;

  }

  可以看到我們會(huì)通過傳入函數(shù)名稱,遍歷類對象中的方法列表進(jìn)行匹配,找到函數(shù)指針,接下去就是和章節(jié)2調(diào)用一樣。

  void (*call)(void *) = findMethod("selfIntroducation");

  call(pmst);

  現(xiàn)在的運(yùn)行時(shí)動(dòng)態(tài)性方案存在很多缺陷,隨便舉幾點(diǎn):

  實(shí)例對象會(huì)有很多個(gè),但是對應(yīng)的類對象有且僅有一個(gè),因?yàn)轭悓ο笫且环莩橄竺枋?,一個(gè)足矣。但是你會(huì)發(fā)現(xiàn)實(shí)例對象和類對象并沒有聯(lián)系在一起,導(dǎo)致我們得到一個(gè)實(shí)例對象無法運(yùn)行時(shí)得知其屬于什么類(對應(yīng)哪個(gè)

class object 類對象)!這也是后面我們要解決的;

  存在太多的硬編碼,比如 findMethod 寫死了是從 personClsObject 中去遍歷方法列表

  小總結(jié):1.實(shí)例對象允許很多個(gè),但是對應(yīng)的類對象有且僅有一個(gè),運(yùn)行時(shí)保存在堆上;2.類對象是一份抽象描述,我們可以在運(yùn)行通過查詢類對象,拿到關(guān)于類的信息,比如第一個(gè)變量名稱,占字節(jié)數(shù),變量類型等等,拿到這些信息可以幫助我們實(shí)際訪問實(shí)例對象指針指向內(nèi)存中的數(shù)據(jù)啦!——————

因?yàn)槲覀冎雷止?jié)偏移和變量類型。

  3.4 改進(jìn):實(shí)例對象關(guān)聯(lián)類對象

  3.3節(jié)中我們僅考慮只有一個(gè)personClsObject,并且在 findMethod 查詢函數(shù)中也硬編碼寫死了是從 Person

類對象方法列表中遍歷匹配,現(xiàn)在開始加入不同的類對象,findMethod 只需要新增一個(gè)入?yún)⒓纯?

  /// 分離硬編碼部分,傳入 `Class *classObject` 不同的類對象

  void (*findMethod(Class *classObject, char *methodName))(void *myself)

{

  for method in classObject->methodList {

  if methodName == method->name {

  return method->callAddress;

  }

  }

  return NULL;

  }

  /// 現(xiàn)在調(diào)用方式改為:

  void (*call)(void *) = findMethod(personClsObject,

"selfIntroducation");

  call(pmst);

  但是這樣實(shí)現(xiàn) findMethod 的前提是知道 pmst 這個(gè)實(shí)例對象對應(yīng)的類對象為

personClsObject,單純拿到指向?qū)嵗龑ο髢?nèi)存的指針(0x1000 0000)顯然信息不足:

  [圖片上傳失敗...(image-e11e38-1519970136607)]

  試想知曉一個(gè)實(shí)例對象的指針 0x1000,0000,指針類型為 void * ,我們可以訪問這塊內(nèi)存的數(shù)據(jù),但是問題來了:

  這個(gè)實(shí)例對象到底占幾個(gè)字節(jié)呢?

  內(nèi)存布局怎樣————比如第一個(gè)成員類型是Int,要讀入4個(gè)字節(jié),ps:這里可能要考慮內(nèi)存對齊問題;

  我們依舊不知道這個(gè)實(shí)例對象對應(yīng)的類對象是哪個(gè),或者說類對象所占的內(nèi)存地址是多少。

  正如第三點(diǎn)指出,問題根本在于我們的實(shí)例對象沒有綁定類對象的內(nèi)存地址!這樣問題就很好解決了,我們只需在內(nèi)存頭部“塞入”類對象的指針就OK了,假設(shè)

personClsObject 類對象地址為0x2000 0000

  ————————————————— ——————————————————————————————————————————————

  | 0x2000 0000 -|--------------->| "Person"(char *className) |

  |_______________ | |____________________________________________|

  | "pmst" | | 0x2000 1000(Variable *variableList) |

  | 26 | |____________________________________________|

  | 0 | | 0x2000 2000(Method *methodList) |

  ————————————————— |____________________________________________|

  其中 0x2000,1000 0x02000,2000 都是指針,分別指向變量列表和方法列表內(nèi)存地址。

  這樣的結(jié)構(gòu)意味著要修改 Person 的結(jié)構(gòu)體:

  typedef struct Person {

  Class *clsObject;

  char name[12];

  int age;

  int sex;

  } Person;

  ////////// 偽代碼(前提我們已經(jīng)得到了person 類對象) /////////////

  int size = 0;

  for variable in personClsObject->variableList {

  size = variable->memorySize;// 當(dāng)然這里肯定要考慮內(nèi)存對齊問題

  }

  Person *pmst = (Person *)malloc(size + 8); // 因?yàn)槎嗔艘粋€(gè)指針,32位平臺(tái)占4字節(jié)

64位平臺(tái)占8字節(jié)

  pmst->clsObject = personClsObject;

  //...

  這樣就可以解決我們之前的問題了,給一個(gè)實(shí)例變量的指針,我們先取到內(nèi)存首地址開始的8個(gè)字節(jié),解釋成 Class *

指針指向了我們的類對象,愉快地獲取想要的所有信息。

  3.5 關(guān)于實(shí)例對象

  不過問題來了,上述實(shí)現(xiàn)必須在抽象出來的數(shù)據(jù)結(jié)構(gòu)頂部插入一個(gè) Class *clsObject ,如下:

  typedef struct Person {

  Class *clsObject; // 指向 personClsObject 類對象

  char name[12];

  int age;

  int sex;

  } Person;

  typedef struct Car {

  Class *clsObject; // 指向 carClsObject 類對象

  char brand[12];

  int color;

  int EngineDisplacement;

  //...

  } Car;

  //... 還有其他很多抽象類定義

  不同類的實(shí)例對象聲明如下:

  // Person 實(shí)例對象:pmst numbbbb

  Person *pmst = (Person *)malloc(person_size);

  pmst->clsObject = personClsObject;

  Person *numbbbb = (Person *)malloc(person_size);

  numbbbb->clsObject = personClsObject;

  // Car 實(shí)例對象:pmst's bmw & benz 以下表示客觀存在的兩輛車

  Car *bmw_pmst = (Car *)malloc(car_size);

  bmw_pmst->clsObject = carClsObject;

  Car *benz_pmst = (Car *)malloc(car_size);

  benz_pmst->clsObject = carClsObject;

  盡管 pmst numbbbb bmw_pmst benz_pmst 都是指針,但是指向類型分別是 Person 和 Car

結(jié)構(gòu)體,那么在此種情況下,我們使用能夠使用一種統(tǒng)一的方式來定義一個(gè)實(shí)例對象呢?

  觀察上述實(shí)例對象聲明以及抽象類的定義,我們找出相同點(diǎn):數(shù)據(jù)結(jié)構(gòu)頂部都為 Class *clsObject 指針。

  struct object {

  Class *clsObject;

  };

  struct object *pmst = (Person *)malloc(person_size)

  pmst->clsObject = personClsObject;

  struct object *bmw_pmst = (Car *)malloc(car_size);

  bmw_pmst->clsObject = carClsObject;

  Person 和 Car 后面的成員,我們無法使用 -> 訪問了,轉(zhuǎn)而變成查詢各自的類對象中 variableList

變量列表————變量類型和地址偏移量。這樣可以間接訪問pmst這個(gè)實(shí)例指向內(nèi)存內(nèi)容了(當(dāng)然內(nèi)存前8個(gè)字節(jié)保存的是類對象指針)。

  至于為什么能這么做,先來說說C語言實(shí)現(xiàn)變長結(jié)構(gòu)體。

  struct Data

  {

  int length;

  char buffer[0];

  };

  結(jié)構(gòu)體中,length 其實(shí)表示分配的內(nèi)存大小,而buffer是一個(gè)空數(shù)組,可理解為占位名稱罷了;buffer的真實(shí)地址緊隨 Data

結(jié)構(gòu)體包含數(shù)據(jù)之后,可以看到這個(gè)結(jié)構(gòu)體僅占4個(gè)字節(jié),倘若我們在malloc時(shí)候分配了100個(gè)字節(jié),那么剩下100-4=96個(gè)字節(jié)就是屬于 buffer

數(shù)組,非常巧妙不是嗎?

  char str[10] = "pmst demo";

  Data *data = (Data *)malloc(sizeof(Data) + 10);

  data->length = 10;

  memcpy(data->data,str, 10);

  回歸正題,現(xiàn)在我們可以使用 struct object 結(jié)構(gòu)來統(tǒng)一指向我們的實(shí)例對象了,但是這并不意味著我們不需要Person類

Car類的定義,只不過現(xiàn)在抽象數(shù)據(jù)結(jié)構(gòu)體的頂部不需要嵌入 Class *clsObject了。

  3.6 改寫實(shí)例對象的分配方式

  前文demo中是這么實(shí)例化一個(gè)對象的:

  /// ...

  Person *pmst = (Person *)malloc(person_size)

  pmst->clsObject = personClsObject;

  /// ...

  PersonClsObject 知道 Person 的一切。

  以 (Person *)malloc(person_size) 方式實(shí)例化一個(gè)對象,首先是要拿到 personClsObject 對象,然后遍歷

variableList 累加所有變量占的內(nèi)存得到 person_size,最后調(diào)用 malloc 方法開辟內(nèi)存返回指針。

  分配一塊內(nèi)存給person實(shí)例對象 這種行為屬于person類對象的職責(zé),因此將這種行為添加到 personClsObject 類對象的

methodList 中(其實(shí)細(xì)細(xì)想來,是不太恰當(dāng)?shù)?,之后還會(huì)繼續(xù)改進(jìn)),命名為 mallocInstance。

  /// 簡單修改下定義 真實(shí)定義結(jié)構(gòu)名稱改為了 `Person_IMP`

  typedef struct object Person;

  struct Person_IMP {

  char name[12];

  int age;

  int sex;

  }

  /// 增加一個(gè)分配內(nèi)存的方法 注意傳參為實(shí)例對象 對于當(dāng)前方法來說應(yīng)該傳NULL

  (struct object *)mallocIntance(void *myself) {

  /// 偽代碼

  int size = 0;

  for variable in personClsObject->variableList {

  size = variable->memorySize;// 當(dāng)然這里肯定要考慮內(nèi)存對齊問題

  }

  return (struct object *)malloc(size);

  }

  personClsObject->methodList[xx]=mallocInstance;

  /// 分配內(nèi)存改寫如下:

  void (*mallocIntance)(void *) = findMethod(personClsObject,

"mallocIntance");

  Person *pmst = mallocIntance(NULL); //

之前是要傳一個(gè)實(shí)例對象進(jìn)去,為了方便操作,但是分配內(nèi)存比較特殊,要知道此刻連實(shí)例都不存在!

  3.8 對象調(diào)用函數(shù)方式的思考

  實(shí)例對象函數(shù)調(diào)用過程: findMethod 傳入對應(yīng)的類對象和函數(shù)名稱,遍歷 methodList

找到匹配項(xiàng)返回函數(shù)指針,傳入實(shí)例對象指針調(diào)用即可,譬如之前person實(shí)例調(diào)用自我介紹方法的demo。

  /// 現(xiàn)在調(diào)用方式改為:

  void (*call)(void *) = findMethod(personClsObject,

"selfIntroducation");

  call(pmst);

  那么問題來了,實(shí)例對象的屬性變量如何修改呢?比如name,age和sex?,F(xiàn)在已經(jīng)不能像最開始之前那樣直接訪問內(nèi)存地址進(jìn)行修改了,盡管personClsObject知道這些變量的信息:變量名稱,類型以及所占內(nèi)存大小。

  其實(shí)解決方案也很簡單,既然不能直接訪問變量,間接總可以把!

  Any problem in computer science can be solved by anther layer of

indirection.

  現(xiàn)在為每個(gè)屬性變量都創(chuàng)建一個(gè)讀寫方法,通過調(diào)用這兩個(gè)方法來修改實(shí)例對象內(nèi)存中的變量值(Note:前8個(gè)字節(jié)保存的是類對象地址)

  void setName(void *my_self, char *newName) {}

  char *getName(void *my_self) {}

  注意到不同函數(shù)的傳參個(gè)數(shù)也不同,如 selfIntroducation 傳參僅 void *my_self一個(gè),而 setName

方法傳參個(gè)數(shù)為2。這其實(shí)是ok,Method封裝的是個(gè)函數(shù)指針(占4或8個(gè)字節(jié)),指向某塊代碼段的地址,C語言又是支持可變參數(shù)的函數(shù),原理自行g(shù)oogle關(guān)鍵字"C語言

函數(shù) 可變參數(shù)"。

  這里講下我的理解,函數(shù)其實(shí)就是封裝好的代碼塊,編譯之后轉(zhuǎn)成一串指令保存在代碼段。

  關(guān)于函數(shù)調(diào)用:正常的調(diào)用方式

functionName(variable1,variable2,variable3),編譯器會(huì)把functionName替換成函數(shù)地址(0x12345678),匯編可能是使用類似

call 0x12345678 來調(diào)用函數(shù);

  關(guān)于函數(shù)入?yún)?shí)現(xiàn):variable1 variable2 variable3,應(yīng)該是會(huì)調(diào)用 push 指令一個(gè)個(gè)入棧(這里注意是先 push

variable1 還是 push variable3 是由ABI決定的!)

  如果說函數(shù)是指令,那么棧就是給函數(shù)提供數(shù)據(jù)的源!函數(shù)實(shí)現(xiàn)是一串指令,使用push和pop操作棧上的數(shù)據(jù),拿上面的函數(shù)入?yún)碚f,我們就使用 push

命令將variable1 variable2 variable3壓到棧里,其中 ebp 寄存器指向當(dāng)前函數(shù)調(diào)用上下文的棧 base

address,而esp寄存器則是根據(jù)push和pop改變指針地址,一開始ebp和esp指針都是指向 base address。

  [圖片上傳失敗...(image-f28fc2-1519970136607)]

  上面就是簡單的一個(gè)調(diào)用方式,至于variable1這些函數(shù)入?yún)⑷绾稳?,?yīng)該是依靠 ebp+ offset得到。

  當(dāng)然如果是學(xué)習(xí)C語言的話也可以參考下面的資料

  C語言編程基礎(chǔ)

  [http://www.makeru.com.cn/live/1758_311.html?s=45051

  結(jié)構(gòu)體普及與應(yīng)用

  [http://www.makeru.com.cn/live/5413_1909.html?s=45051

  C語言玩轉(zhuǎn)鏈表

  [http://www.makeru.com.cn/live/1392_338.html?s=45051

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容