سؤال من النوع C
-
28-10-2019 - |
سؤال
كيف أجعل الوظيفة أدناه عامة لـ uint8_t، uint16_t، uint32_t، int8_t، int16_t، int32_t و float_t؟
لا أحب تكرار نفس المنطق في كل حالة كما ترون.والفرق الوحيد في كل حالة هو الصب.
أرغب بشكل مثالي في الحصول على حل يلتزم بمعيار C وبالتالي يكون محمولاً.أي أفكار هي موضع ترحيب.
شكرًا.
static bool_t IsWithinLimits(const dbKey_t *key, const void *data)
{
bool_t isWithinLimits = TRUE;
limits_t limits = getDefinedLimits(key);
switch(key->type)
{
case TYPE_UINT8:
if((*(const UINT8*)data > (UINT8)limits.max) || (*(const UINT8*)data < (UINT8)limits.min))
{
isWithinLimits = FALSE;
}
break;
case TYPE_UINT16:
if((*(UINT16*)pData > (UINT16)limits.max) || (*(UINT16*)data < (UINT16)limits.min))
{
isWithinLimits = FALSE;
}
break;
case TYPE_UINT32:
...
break;
case TYPE_INT8:
...
break;
case TYPE_INT16:
...
break;
case TYPE_INT32:
...
break;
case TYPE_FLOAT:
...
break;
}
return isWithinLimits;
}
المحلول
كذلك أنت استطاع استخراج القوالب:
int64_t loadptr_uint8(const void *p) {
return *(uint8_t*)p;
}
int64_t convert_uint8(int64_t val) {
return (uint8_t)val;
}
int testLimits(const limits_t *plimits, const void *pData, int64_t(*loadptr)(void*), int64_t (*convert)(int64_t)) {
return loadptr(pData) <= convert(limits->max) && loadptr(pData) >= convert(limits->min);
}
switch(key->type) {
case TYPE_UINT8:
isWithinLimits = testLimits(&limits, pData, loadptr_uint8, convert_uint8);
break;
// etc
}
أو، إذا كانت الأنواع المختلفة تشكل نطاقًا متجاورًا من القيم من 0، فيمكنك إنشاء صفيفين من مؤشرات الوظائف والقيام بما يلي:
bool isWithinLimits = testLimits(&limits, pData, loadptrs[key->type], converts[key->type]);
ملحوظات:
- لا يزال يتعين عليك كتابة دالتين لكل نوع، على الرغم من أنه من السهل إنشاءهما بشكل كلي إذا كنت تفضل ذلك.
- لا يبدو الأمر يستحق كل هذا العناء حقًا بالنسبة لهذا الرمز الصغير.
- لقد اخترت
int64_t
نظرًا لأنه قادر على تمثيل جميع قيم جميع أنواع الأعداد الصحيحة التي تستخدمها، وبالتالي فإن التحويلات إليهاint64_t
لا تتجاهل أبدًا المعلومات ولا تغير أبدًا نتيجة المقارنة فيما يتعلق بإجراء نفس المقارنة في نوع المصدر.ولكن إذا كنت تريد أيضًا التغطيةuint64_t
, ، فلا يمكنك استخدام نفس النوع لكل شيء، نظرًا لعدم وجود نوع عدد صحيح يمكنه تمثيل جميع قيم جميع أنواع الأعداد الصحيحة.ستحتاج أيضًا إلى منفصلtestLimitsf
وظيفة لfloat
, وربما باستخدامlong double
كنوع شائع للمرونة المستقبلية. - [يحرر:لقد أدركت للتو، بافتراض IEEE-754،
double
في الواقع يمكن أن يمثل تمامًا جميع قيم جميع الأنواع التي تستخدمها.لذلك، مع وجود قيود طفيفة على قابلية النقل، يمكنك استخدامهاtestLimitsf
لكل شيء والتعامل في الزوجي] - هل أنت متأكد من أنه يستحق التحويل إلى (على سبيل المثال)
uint8_t
قبل المقارنة؟إما أن تكون القيمة في النطاق لـ auint8_t
, ، وفي هذه الحالة لا تحتاج إلى التحويل، يمكنك فقط إجراء المقارنة.وإلا فإن القيمة ليست في النطاق، وفي هذه الحالة، يؤدي تقليل المعامل إلى جعل المقارنة لا معنى لها إلا في الحالات الخاصة 0 و-1.لذلك قد يكون الأمر يستحق ذلك، إذا كان هناك شيء لم تذكره يجعله كذلك، لكنه يبدو مريبًا بالنسبة لي. - لقد قلت في تعليق: "أحاول أن أجعل هذه الوظيفة أكثر كفاءة".وهذا قد يتعارض مع ذلك.من الممكن منطقيًا التضمين
testLimits
وكذلك المكالمات إلى وظائف الصب فيswitch
, ، لكنني لن أعتمد على ذلك.
نصائح أخرى
لا توجد طريقة سهلة للقيام ببرمجة عامة مثل هذه في ج. إذا كنت قلقًا بشأن الصيانة ، فقد تكون هذه مناسبة نادرة حيث يكون الماكرو مناسبًا.
لماذا لا تستخدم ماكرو؟ Genacodicetagpre
ستضيف المراجعة التالية للمعيار (C1x) دعمًا للتعبيرات العامة من النوع (مثال من ويكيبيديا): Genacodicetagpre
يتوفر لدى دول مجلس التعاون الخليجي بعض دعم C1x الأولي.أعتقد أن _Generic لم يتم دعمه بعد ، ولكن ضع ذلك في اعتبارك للمستقبل.