C: المكتبة القياسية
المكتبة القياسية كصندوق أدوات — لا تحتاج أن تصنع المطرقة والمفك بنفسك، فقط التقطها واستخدمها. فهم ما تفعله كل أداة يساعدك في اختيار الأنسب.
1. العمليات الحسابية — math.h
استخدام الدوال الحسابية يتطلب تضمين الملف الرأسي وربط مكتبة الرياضيات:
#include <math.h>
الترجمة بعلامة -lm: gcc program.c -lm.
(1) الدوال الحسابية الشائعة
| الدالة | الغرض | مثال |
|---|---|---|
fabs(x) |
القيمة المطلقة | fabs(-3.5) ← 3.5 |
sqrt(x) |
الجذر التربيعي | sqrt(16.0) ← 4.0 |
pow(x, y) |
x مرفوعة للقوة y | pow(2.0, 10.0) ← 1024.0 |
ceil(x) |
تقريب لأعلى | ceil(3.2) ← 4.0 |
floor(x) |
تقريب لأدنى | floor(3.8) ← 3.0 |
round(x) |
تقريب لأقرب | round(3.5) ← 4.0 |
fmod(x, y) |
باقِ قسمة عشري | fmod(7.5, 2.5) ← 0.0 |
log(x) |
لوغاريتم طبيعي | log(2.718) ← 1.0 |
log10(x) |
لوغاريتم عشري | log10(100.0) ← 2.0 |
sin(x) |
جيب | sin(3.14/2) ← 1.0 |
cos(x) |
جيب التمام | cos(0.0) ← 1.0 |
tan(x) |
ظل | tan(0.0) ← 0.0 |
rad = deg * 3.14159265 / 180.0.
▶ مثال
حساب المسافة بين نقطتين:
#include <stdio.h>
#include <math.h>
typedef struct {
double x;
double y;
} Point;
double distance(Point a, Point b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
int main(void) {
Point p1 = {3.0, 4.0};
Point p2 = {0.0, 0.0};
printf("Distance: %.2f\n", distance(p1, p2));
return 0;
}
Distance: 5.00
2. الأدوات العامة — stdlib.h
(1) الأعداد العشوائية
int rand(void);
void srand(unsigned int seed);
تُرجع rand() عددًا صحيحًا عشوائيًا زائفًا بين 0 وRAND_MAX. دون استدعاء srand، كل تشغيل للبرنامج يُنتج نفس تسلسل الأعداد العشوائية.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
srand((unsigned int)time(NULL));
for (int i = 0; i < 5; i++) {
printf("%d ", rand());
}
printf("\n");
for (int i = 0; i < 5; i++) {
printf("%d ", rand() % 100);
}
printf("\n");
return 0;
}
1804289383 846930886 1681692777 1714636915 1957747793
83 86 77 15 93
rand() % N لا يوفر عشوائية جيدة في معظم التطبيقات — البتات الدنيا قد تتبع نمطًا. للسيناريوهات التي تتطلب عشوائية عالية الجودة، استخدم مولّد أعداد عشوائية أكثر تقدمًا.
(2) تحويل الأنواع
| الدالة | الغرض |
|---|---|
atoi(str) |
سلسلة إلى int |
atol(str) |
سلسلة إلى long |
atof(str) |
سلسلة إلى double |
strtol(str, &end, base) |
سلسلة إلى long (بأساس) |
strtod(str, &end) |
سلسلة إلى double (مع كشف الأخطاء) |
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int a = atoi("42");
double b = atof("3.14");
long c = strtol("0xFF", NULL, 16);
printf("a = %d\n", a);
printf("b = %.2f\n", b);
printf("c = %ld\n", c);
char *end;
long d = strtol("123abc", &end, 10);
printf("d = %ld, Unconverted part: %s\n", d, end);
return 0;
}
a = 42
b = 3.14
c = 255
d = 123, Unconverted part: abc
atoi لا يمكنها كشف الأخطاء — الإدخال غير الصالح يُرجع 0، والذي لا يمكن تمييزه عن 0 شرعي. فضّل strtol/strtod، اللتين تتيحان تحديد نجاح التحويل عبر مؤشر end.
3. إدارة الذاكرة الديناميكية
void *malloc(size_t size);
void *calloc(size_t count, size_t size);
void *realloc(void *ptr, size_t size);
void free(void *ptr);
malloc: تُخصصsizeبايت، لا تُهيّئcalloc: تُخصصcount*sizeبايت، تُهيّئ الكل إلى 0realloc: تغيّر حجم الذاكرة المُخصصة مسبقًا، قد تنقل العنوانfree: تُحرر الذاكرة
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr = calloc(5, sizeof(int));
if (arr == NULL) {
return 1;
}
for (int i = 0; i < 5; i++) {
arr[i] = i * 10;
}
int *new_arr = realloc(arr, 10 * sizeof(int));
if (new_arr == NULL) {
free(arr);
return 1;
}
arr = new_arr;
for (int i = 5; i < 10; i++) {
arr[i] = i * 10;
}
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
0 10 20 30 40 50 60 70 80 90
realloc، تُرجع NULL لكن الذاكرة الأصلية لا تُحرر! لذا يجب استخدام متغير مؤقت لاستقبال القيمة المُرجعة من realloc — عند الفشل، يمكنك تحرير الذاكرة الأصلية.
(1) الترتيب والبحث
qsort
void qsort(void *base, size_t count, size_t size,
int (*compare)(const void *, const void *));
قواعد دالة المقارنة: تُرجع سالبًا إذا a < b، و0 إذا تساوى، وموجبًا إذا a > b.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int cmp_int(const void *a, const void *b) {
return *(const int *)a - *(const int *)b;
}
int cmp_str(const void *a, const void *b) {
return strcmp(*(const char **)a, *(const char **)b);
}
int main(void) {
int nums[] = {42, 17, 8, 95, 3, 61};
int n = sizeof(nums) / sizeof(nums[0]);
qsort(nums, n, sizeof(int), cmp_int);
for (int i = 0; i < n; i++) {
printf("%d ", nums[i]);
}
printf("\n");
const char *names[] = {"Alice", "Bob", "Charlie", "Diana"};
int m = sizeof(names) / sizeof(names[0]);
qsort(names, m, sizeof(char *), cmp_str);
for (int i = 0; i < m; i++) {
printf("%s ", names[i]);
}
printf("\n");
return 0;
}
3 8 17 42 61 95
Alice Bob Charlie Diana
bsearch
void *bsearch(const void *key, const void *base, size_t count,
size_t size, int (*compare)(const void *, const void *));
تُجري بحثًا ثنائيًا على مصفوفة مرتبة. تُرجع مؤشرًا للعنصر الموجود، أو NULL إذا لم يُوجد.
#include <stdio.h>
#include <stdlib.h>
int cmp_int(const void *a, const void *b) {
return *(const int *)a - *(const int *)b;
}
int main(void) {
int nums[] = {3, 8, 17, 42, 61, 95};
int n = sizeof(nums) / sizeof(nums[0]);
int key = 42;
int *result = bsearch(&key, nums, n, sizeof(int), cmp_int);
if (result != NULL) {
printf("Found %d at index %ld\n", key, result - nums);
} else {
printf("%d not found\n", key);
}
return 0;
}
Found 42 at index 3
4. معالجة الوقت — time.h
(1) دوال الوقت
| الدالة/النوع | الغرض |
|---|---|
time_t |
نوع الوقت (عادةً ثوانٍ منذ 1970-01-01) |
time(&t) |
الحصول على الوقت الحالي |
clock() |
الحصول على دورات ساعة المعالج المستخدمة |
localtime() |
التحويل إلى بنية الوقت المحلي |
gmtime() |
التحويل إلى بنية الوقت UTC |
strftime() |
تنسيق الوقت كسلسلة |
difftime() |
حساب الفرق بين وقتين (ثوانٍ) |
(2) البنية tm
struct tm {
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};
tm_mon يتراوح من 0-11 (0 = يناير)، وtm_year هو عدد السنوات منذ 1900، وtm_wday يستخدم 0 للأحد.
▶ مثال
#include <stdio.h>
#include <time.h>
int main(void) {
time_t now = time(NULL);
struct tm *local = localtime(&now);
char buf[64];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", local);
printf("Current time: %s\n", buf);
printf("Today is day %d of the week (0=Sun)\n", local->tm_wday);
return 0;
}
Current time: 2025-03-15 14:30:22
Today is day 6 of the week (0=Sun)
(3) توقيت البرنامج
#include <stdio.h>
#include <time.h>
int main(void) {
clock_t start = clock();
volatile long sum = 0;
for (long i = 0; i < 100000000L; i++) {
sum += i;
}
clock_t end = clock();
double elapsed = (double)(end - start) / CLOCKS_PER_SEC;
printf("Elapsed: %.3f seconds\n", elapsed);
return 0;
}
Elapsed: 0.235 seconds
clock() يقيس وقت المعالج، لا الوقت الفعلي. إذا كان البرنامج نائمًا أو ينتظر إدخال/إخراج، لا يُحسب ذلك الوقت. استخدم difftime لقياس الوقت الفعلي المنقضي.
5. معالجة الأحرف — ctype.h
| الدالة | شرط الفحص |
|---|---|
isalpha(c) |
حرف |
isdigit(c) |
رقم |
isalnum(c) |
حرف أو رقم |
isupper(c) |
حرف كبير |
islower(c) |
حرف صغير |
isspace(c) |
مسافة بيضاء (مسافة، جدول، سطر جديد، إلخ) |
ispunct(c) |
علامة ترقيم |
isprint(c) |
حرف قابل للطباعة |
toupper(c) |
تحويل إلى كبير |
tolower(c) |
تحويل إلى صغير |
هذه الدوال تتطلب قيمة unsigned char أو EOF كمعامل. تمرير قيمة char سالبة سلوك غير معرّف.
▶ مثال
عدّ الأحرف والأرقام والأحرف الأخرى في سلسلة:
#include <stdio.h>
#include <ctype.h>
int main(void) {
char str[] = "Hello, World! 123";
int letters = 0, digits = 0, others = 0;
for (int i = 0; str[i] != '\0'; i++) {
if (isalpha((unsigned char)str[i])) {
letters++;
} else if (isdigit((unsigned char)str[i])) {
digits++;
} else {
others++;
}
}
printf("Letters: %d, Digits: %d, Others: %d\n", letters, digits, others);
return 0;
}
Letters: 10, Digits: 3, Others: 6
6. التأكيدات — assert.h
void assert(int expression);
عندما يكون التعبير خاطئًا، ينتهي البرنامج ويطبع رسالة خطأ (اسم الملف، رقم السطر، التعبير). تعريف الماكرو NDEBUG قبل #include <assert.h> يعطّل جميع التأكيدات.
#include <stdio.h>
#include <assert.h>
double safe_divide(double a, double b) {
assert(b != 0 && "Divisor cannot be zero");
return a / b;
}
int main(void) {
printf("10 / 2 = %.1f\n", safe_divide(10.0, 2.0));
printf("10 / 0 = %.1f\n", safe_divide(10.0, 0.0));
return 0;
}
10 / 2 = 5.0
Assertion failed: b != 0 && "Divisor cannot be zero", file main.c, line 5
assert تُطبع، لذا كتابة "Divisor cannot be zero" أكثر فعالية من تعليق — عند فشل التأكيد، يمكنك رؤية السبب مباشرة.
❓ أسئلة شائعة
srand(time(NULL)) للبذر بالوقت الحالي.📖 ملخص
math.hتوفر دوال حسابية؛ الدوال المثلثية تستخدم راديان؛ الترجمة بـ-lmrand/srandتولّد أعدادًا عشوائية زائفة؛ استخدمsrand(time(NULL))للبذر بالوقتstrtol/strtodأكثر أمانًا منatoi/atofويمكنها كشف أخطاء التحويلqsort/bsearchهما أداتا الترتيب والبحث في المكتبة القياسية، وتتطلبان دوال مقارنة مخصصةassertللتنقيح أثناء التطوير؛ عطّلها في بنيات الإنتاج عبرNDEBUG
📝 تمارين
- اكتب برنامجًا يُولّد 100 عدد صحيح عشوائي من 1 إلى 1000، يرتبها بـ qsort، ويُخرج القيمتين القصوى والدنيا
- اكتب برنامجًا يستخدم clock() لمقارنة فارق الوقت بين الترتيب بالفقاعات وqsort على 10000 عدد صحيح
- اكتب دالة تستخدم دوال ctype.h لتنفيذ تحويل حالة السلسلة (إدخال سلسلة، وإخراج نسخة بأحرف كبيرة وأخرى بأحرف صغيرة)