2018年12月21日 星期五

[C/C++][STL][C#]集合處理筆記

標準模板函式庫(STL)Wiki:

STL 將「在資料上執行的操作」與「要執行操作的資料分開」

對於構築集合處理的方式,.NET與STL思想上略有小小不同。

NET趨向物件導向想法(結合資料與操作成為一物件),將集合操作用函數以成員函數形式(member function)附屬在樣板容器之下,相當於在集合操作用函數中隱含(implicity)帶入自身參考。

.NET集合操作用函數無法直接帶入函數指標,而是要帶入委派(delegate),建立對應操作的委派的方式之一:



public struct Data
    {
        public int field1;
        public double field2;
        
        public Data(int f1,double f2)
        {
            field1 = f1;
            field2 = f2;
        }
    }
// The Predicate function should be non-member
static bool isField1Odd(Data data)
{
   return (data.field1%2)==1;
}

// Define the Predicate<T> delegate.
Predicate<Data> predicate = isField1Odd;


泛型容器在帶入元素型別T後,即變成一獨立的強型別,帶入的樣板委派Predicator<T>之中的T也須與泛型容器元素型別T一致,才能通過在編譯期的型別檢查,概念相當於

  • 泛型容器:List<T1>與List<T2>是完全不同型別
  • 委派:Predicate<T1>與Predicate<T2>也是不同型別
  • 以List<T1>.Find(Predicate<T1>) 來說,Find()只接受Predicate<T1>作為引數,帶入Predicate<T2>會被視為錯誤型別遭到排除

帶錯Predicate型別編譯器報錯:
Predicate<Data2> predicate2 = isField1OddData2;
            //list.Find(predicate2);// cannot convert from 'System.Predicate<Rextester.Data2>' to 'System.Predicate<Rextester.Data>'


正確的範例實作:

public static void Main(string[] args)
        {
            List<data> list = new List<data>();
            list.Add(new Data(4,2));
            list.Add(new Data(3,4));
            list.Add(new Data(8,6));
            
            // Define the Predicate<t> delegate.
            Predicate<data> predicate = isField1Odd;
            Data found = list.Find(predicate);
            Console.WriteLine(found.field1);
        }


STL則是分離函數與容器,函數與容器之間以迭代器(Iterator)作為遍歷(Traversal)媒介,明確(explicity)帶入函數,概念上較貼近C語言的純函數工具;

cplusplus.com:
public static void Main(string[] args)
template<class InputIterator, class UnaryPredicate>
  InputIterator find_if (InputIterator first, InputIterator last, UnaryPredicate pred)
{
  while (first!=last) {
    if (pred(*first)) return first;
    ++first;
  }
  return last;
}


以find_if()來說,除了兩個InputIterator引數須限制在同一型別,集合操作的函數限制為UnaryPredicate,僅接受一元素引數並回傳布林值,倒是沒限定元素引數型別需要與容器型別一致,以下兩組UnaryPredicate皆能通過編譯並執行:

bool IsOdd (int i) {
  return ((i%2)==1);
}

bool IsOddDouble(double i){
  return (((int)i%2)==1);
}

std::vector<int>::iterator it = std::find_if (myvector.begin(), myvector.end(), IsOdd);
it = std::find_if (myvector.begin(), myvector.end(), IsOddDouble);

胡思亂想:UnaryPredicate無強型別檢查似乎給了一些彈性?比如記憶體多型解析?


同場加映Lambda寫法:

C#:

官網範例,陳述式Lambda(有{}叫做陳述式):
found = list.Find(delegate(Data data)
{
  return (data.field1%2)==1;
});
把delegate拿掉的陳述式寫法:
found = list.Find((Data data) =>
{
    return (data.field1%2)==1;
});
運算式寫法(無{},單行達成):
Predicate<Data> func = (Data data) => (data.field1%2)==1;
found = list.Find(func); //way1
            
found = list.Find((Data data) => (data.field1%2)==1); //way2

C++11,陳述式Only
it = std::find_if (myvector.begin(), myvector.end(), [](int i){
  return ((i%2)==1);
});


後記1:STL Algorithm也能用在 Qt容器(QList)上



沒有留言:

張貼留言