1. 程式人生 > >結構體與char型轉換

結構體與char型轉換

因udp程式設計需要傳送的內容為char型,而定義的傳送資料為結構體,所以需要進行格式轉換。

轉自:http://www.cnblogs.com/qicosmos/p/3601737.html

結構體自動化轉換為char陣列這個需求,來自於一個最近開發的一個專案,在專案開發過程中遇到一個小問題,需要將各種結構體拷貝到char陣列中,這對於一個簡單的結構體來說是很簡單的事情,比如下面這個只有整形欄位的結構體:

  struct A

  {

  int a;

  int b;

  };

  char buf[100];

  A a = {1,2};

  memcpy(buf, &a, sizeof(A));

  一句memcpy就能將結構體a拷貝到char陣列中去了,直接通過memcpy拷貝結構體只對於記憶體連續的結構體有效。如果結構體記憶體不連續,結構體中含有double、string、指標甚至巢狀結構體時,直接拷貝是錯誤的,這時需要一個一個欄位的轉換,比如下面的結構體:

  struct A

  {

  int x;

  string y;

  };

  char buf[100];

  A a = {1, "test"};

  char* p = buf;

  *((int*)p) = x;

  p+=sizeof(int);

  strcpy(p, t.c_str());

  可以看到這種一個一個欄位轉換的方法是很繁瑣的,欄位越多轉換就越繁瑣,而且偏移量還容易寫錯。當結構體欄位是指標或者結構體時就更繁瑣了,比如下面的結構體:

  struct A

  {

  int x;

  int y;

  };

  struct B

  {

  int x;

  int count; //標示指標p中的元素個數

  int *p;

  A a;

  };

  char buf[100];

  B b = {1, 2, new int[2]{3, 4}, {2, 3}};

  char* p = buf;

  *((int*)p) = b.x;

  p+=sizeof(int);

  *((int*)p) = b.count;

  p+=sizeof(int);

  for(int i=0; i<count; p="" i++)<="">

  {

  *((int*)p) = b.p[i];

  }

  p+=sizeof(int)*b.count;

  *((int*)p) = b.a.x;

  p+=sizeof(int);

  *((int*)p) = b.a.y;

  可以看到這種帶指標或者巢狀結構體的結構體轉換為char陣列時非常繁瑣,而且很容易出錯,其實大部分的工作都是重複的,比如不斷的賦值與偏移。這個過程如果能做到自動化就很方便了,直接傳一個結構體,然後自動將結構體中的欄位一個一個拷貝到陣列中,不必關心偏移是否出錯了,也不用關心內部的字串或者巢狀結構體如何拷貝等細節,總之 ,只要傳一個結構體就能自動化的將其轉換為char陣列。

這種自動化的轉換不僅僅大大降低了轉換的複雜度還解決了手工拷貝的時候容易出錯的問題,程式自動化的拷貝保證了拷貝的正確性。目前我還沒看到有類似的轉換類或者函式能夠自動地很方便地將複雜結構體轉換為char陣列,想想自己實現一個也不是難事,其實要實現自動轉換最關鍵的是要獲取結構體的元資訊,有了元資訊我們就能對結構體中的每個欄位進行轉換了。在c#中可以通過反射很方便的獲取結構體的元資訊,而c++中沒有反射,就要想其它辦法來獲取元資訊了。而且這個獲取元資訊的方法還要非常簡單,幾乎不增加額外的負擔。這裡我是通過tuple來獲取原資訊,要求每個結構體要定義一個Get方法來返回其欄位的基本資訊,比如:

  struct A

  {

  int x;

  double y;

  auto Get()->decltype(std::make_tuple(x,y))

  {

  return std::make_tuple(x,y);

  }

  };

  這個Get方法非常簡單,只要將欄位放到tuple中返回出去就行了,幾乎沒有增加額外的負擔,這個看似簡單函式卻有著巨大的作用,只要有這個Get函式我就可以獲取結構體的基本元資訊了,有了這個以後,所有的轉換都可以實現自動化了。還是來看看具體是如何實現自動化的轉換吧:

  #include

  #include

  #include

  namespace Cosmos

  {

  namespace Detail

  {

  struct Functor

  {

  Functor(char* buf, int len) :m_buf(buf), m_bufLen(len)

  {

  }

  template

  typename std::enable_if::value>::type operator()(T t)

  {

  FillForward(t);

  }

  template

  typename std::enable_if<std::is_class::value>::type operator()(T& t)

  {

  FillForward(t);

  }

  private:

  template

  void FillForward(T&& t)

  {

  if (std::is_same::value || std::is_same::value)

  m_latestInt = t;

  FillIn(std::forward(t), m_buf + m_curPos);

  m_curPos += sizeof(T);

  }

  template

  typename std::enable_if<std::is_integral::value>::type FillIn(T t, char* p)

  {

  *((T*) p) = t;

  }

  template

  typename std::enable_if<std::is_floating_point::value>::type FillIn(T t, char* p)

  {

  if(std::is_same::value)

  sprintf(p, "%.15f", t);

  else

  sprintf(p, "%f", t);

  }

  template

  typename std::enable_if<std::is_pointer::value&&std::is_class<typename std::remove_pointer::type>::value>::type FillIn(T t,

  char* p)

  {

  Fill(t, p, [this, &t](int i, char* p, int size){Put(p + i*size, size, t[i]); });

  }

  template

  typename std::enable_if<std::is_pointer::value&&std::is_arithmetic<typename std::remove_pointer::type>::value>::type FillIn(T

  t, char* p)

  {

  Fill(t, p, [this, &t](int i, char* p, int size){FillIn(t[i], p + i*size); });

  }

  template

  void Fill(T t, char* p, F&& f)

  {

  int count = m_latestInt.AnyCast();

  using U = typename std::remove_pointer::type;

  for (int i = 0; i < count; i++)

  {

  f(i, p, sizeof(U));

  }

  }

  template

  typename std::enable_if<std::is_pointer::value&&std::is_void<typename std::remove_pointer::type>::value>::type FillIn(T t,

  char* p)

  {

  int count = m_latestInt.AnyCast();

  int* temp = (int*) t;

  for (int i = 0; i < count; i++)

  {

  FillIn(temp[i], p + i*sizeof(int));

  }

  }

  template

  typename std::enable_if<std::is_same::value>::type FillIn(T& t, char* p)

  {

  strcpy(p, t.c_str());

  }

  template

  typename std::enable_if<std::is_class::value&&!std::is_same::value>::type FillIn(T& t, char* p)

  {

  Put(p, sizeof(T), t);

  }

  char* m_buf;

  int m_bufLen;

  int m_curPos = 0;

  Any m_latestInt = 0;

  };

  template

  void for_each_impl(Func&& f, Last&& last)

  {

  f(last);

  }

  template

  void for_each_impl(Func&& f, First&& first, Rest&&...rest)

  {

  f(first);

  for_each_impl(std::forward(f), rest...);

  }

  template

  void for_each_helper(Func&& f, IndexTuple, std::tuple&& tup)

  {

  for_each_impl(std::forward(f), std::forward(std::get(tup))...);

  }

  }

  template

  void tp_for_each(Func&& f, std::tuple& tup)

  {

  using namespace details;

  for_each_helper(forward(f), typename make_indexes::type(), std::tuple(tup));

  }

  template

  void tp_for_each(Func&& f, std::tuple&& tup)

  {

  using namespace details;

  for_each_helper(forward(f), typename make_indexes::type(), forward<std::tuple>(tup));

  }

  template

  void Put(char* p, int len, T&& t)

  {

  using namespace Detail;

  tp_for_each(Functor(p, len), t.Get());

  }

  }

  View Code

  程式碼中用到了Any,這個Any就是我前面的博文中實現的Any,想檢視可以點這裡。

再看看測試程式碼:

  //巢狀的結構體

  struct MySubStruct

  {

  int a;

  double b;

  auto Get()->decltype(std::make_tuple(a, b))

  {

  return std::make_tuple(a, b);

  }

  };

  //含指標和巢狀結構體的結構體

  struct MyStruct

  {

  int a;

  double b;

  int count; //對於指標,必須將指標元素個數count放在指標元素的前面

  int* p;

  MySubStruct t;

  auto Get()->decltype(std::make_tuple(a, b, count, p, t))

  {

  return std::make_tuple(a, b, count, p, t);

  }

  };

  //巢狀結構體指標的結構體

  struct MyStruct2

  {

  int a;

  double b;

  int count;//對於指標,必須將指標元素個數count放在指標元素的前面

  MySubStruct* t;

  auto Get()->decltype(std::make_tuple(a, b, count, t))

  {

  return std::make_tuple(a, b, count, t);

  }

  };

  //含void指標的結構體

  struct MyStruct3

  {

  bool r;

  int a;//對於指標,必須將指標元素個數count放在指標元素的前面

  void* b;

  auto Get()->decltype(std::make_tuple(r,a, b))

  {

  return std::make_tuple(r, a, b);

  }

  };

  //含字串的結構體

  struct MyStruct4

  {

  int a;

  string b;

  auto Get()->decltype(std::make_tuple(a, b))

  {

  return std::make_tuple(a, b);

  }

  };

  void TestArray()

  {

  MyStruct t = { 9, 1.256, 2, new int[2]{3, 4}, {14, 5.36} };

  char buf[100];

  Put(buf, sizeof(buf), t);

  char buf1[100];

  MySubStruct* st = new MySubStruct[2];;

  st[0] = { 6, 7 };

  st[1] = { 8, 9 };

  MyStruct2 t2 = { 3, 4, 2, st };

  Put(buf1, sizeof(buf1), t2);

  int* p3 = new int[2]{5,6};

  MyStruct3 t3 = { false, 2, (void*) p3 };

  char buf3[100];

  Put(buf3, sizeof(buf3), t3);

  MyStruct4 t4 = { 6, "test" };

  char buf4[100];

  Put(buf4, sizeof(buf4), t4);

  }

  可以看到不管結構體有多少欄位,還是是否含有字串、指標或者嵌套了結構體,都可以通過一個Put函式搞定,沒有了繁瑣而又重複的賦值和偏移操作,不用擔心偏移錯了,整個繁瑣的過程程式都自動化的完成了,簡單利落。

  要用這個自動化的將結構體轉換為char陣列的功能有一點約束條件:

  每個結構體需要提供一個Get函式返回元資訊;

  結構體欄位如果為指標的話,必須要將指標元素個數放到該欄位前面;陣列的話也需要提供陣列元素個數的欄位,因為Get函式會將陣列轉為指標,陣列長度資訊會丟掉。

  我覺得這個約束條件相對於它實現的自動化的轉換的便利性來說是幾乎可以忽略的負擔,是微不足道的。



轉自:http://blog.sina.com.cn/s/blog_6748cd1f0100juq8.html

對C的指標一直有所欠缺,這裡學到一些皮毛,分享一下:

結構體:
#defined MAX_LENGTH 200
typedef struct _TEST_EXAMPLE
{
 char name[MAX_LENGTH+1];
 int age;
} test_example;

結構體的首位置轉換成Char指標,比如有10位同學:
test_example *te = (test_example *)malloc(sizeof(test_example) * 10);
//...結構體讀取資料..
for (size_t i = 0; i < 10; i ++)
{
te[i].name = student[0];
te[i++].age = student[1];
}
char *result = (char *)te;

當用指標(char *)result做了一些操作後,最後要將Char*指到的首位和長度還原成結構體陣列(結構體陣列在記憶體中是連續的);列印結構體如下:
方法一:
size_t value_length = sizeof(test_example) * 10;
for (size_t i = 0; i < value_length; i ++)
{
 printf("%s\t", ((test_example *)(result + i))->name);
 printf("%d\n", ((test_example *)(result + i))->age);
 i += sizeof(test_example) - 1;
}

另外一種方法:
test_example *tmp = (test_example *)result;
for (size_t i = 0; i < value_length / sizeof(test_example); i ++)
{
 printf("%s\t%d\n", (tmp + i)->name, (tmp + i)->age);
}

看起來非常簡單,起先我是使用純指標去擷取每個name和age的位置,但是結構體有個問題,隨平臺不同,結構體每個物件中所佔用的空間也不一樣,一般是int的整數倍,比如char a[10]是結構的唯一成員變數,其實相當於結構體仍然需要佔用12個位元組,具體bool這種型別是否也是int的整數倍,需要進行測試確認;所以最好的辦法還是使用直接OO的方法指定,雖然指標也是一種OO。