C وC++ من الصفر إلى الاحتراف: 100 مثال مترجم مع المخرجات الحقيقية
لغة C لغة صغيرة وسريعة تعمل قريبًا من العتاد. وتضيف C++ إلى C الأصناف والقوالب ومكتبة قياسية كبيرة، وتُستخدم في الألعاب والمتصفحات وقواعد البيانات والأنظمة المضمّنة. تأخذك هذه المقالة من أول printf إلى C++20 الحديثة في 100 مثال. الخمسون الأولى بلغة C: المتغيرات والتحكم في التدفق والدوال والمصفوفات والمؤشرات والبنى والذاكرة والملفات والمعالج المسبق. والخمسون الأخيرة بلغة C++: المراجع والأصناف وRAII والمؤشرات الذكية والقوالب والمكتبة القياسية والمفاهيم والاستثناءات والخيوط والتنسيق. جُمعت كل الأمثلة وشُغّلت باستخدام GCC 16، وتظهر المخرجات الحقيقية بعد الشيفرة.
ما الذي تحتاجه
حاسوب مثبت عليه GCC أو Clang (على Windows ثبّت WinLibs GCC أو MSYS2، وعلى macOS أدوات سطر الأوامر من Xcode، وعلى Linux مدير الحزم). تحقق بالأمرين gcc --version وg++ --version. يكفي محرر نصوص عادي. لا تحتاج إلى خبرة سابقة في البرمجة، لكن الأمثلة الأولى تفترض أنك تعرف ما هو الرقم وما هي الكلمة.
كيفية الترجمة والتشغيل
تستخدم أمثلة C الأمر gcc -std=c17، وتستخدم أمثلة C++ الأمر g++ -std=c++20، وكل مثال في ملف واحد. مثلًا احفظ برنامج C++ باسم main.cpp، ثم ترجمه بالأمر g++ -std=c++20 -Wall -Wextra main.cpp -o main، وشغّله بالأمر ./main. المخرجات الواردة في المقالة مأخوذة من تشغيل حقيقي باستخدام GCC 16.
الموضوع 1: البداية والمتغيرات والأنواع
يبدأ كل برنامج C من main. تُظهر هذه الأمثلة كيفية الطباعة، وتخزين القيم في متغيرات، واختيار النوع المناسب لكل قيمة.
بنية برنامج C
برنامج C مجموعة من الدوال، ويبدأ التنفيذ من main. يضم #include <stdio.h> الدالة printf. تطبع printf النصوص، وتطبع المحددات %d و%f و%c قيمة int وdouble وchar. تُعيد main القيمة 0 للدلالة على النجاح.
الأنواع الصحيحة والعشرية
تخزن int وshort وlong أعدادًا صحيحة، لكن حجمها الدقيق يعتمد على المنصة، لذلك تحقق منه بـ sizeof. تخزن float وdouble كسورًا بدقة محدودة. فالعدد 1.0 / 3.0 ليس ثلث العدد تمامًا، ولهذا يظهر الخطأ عند الطباعة بعدد أكبر من المنازل.
الثوابت
ينشئ const قيمة ذات نوع يمنع المترجم من تغييرها. أما #define فيستبدل النص قبل أن يعمل المترجم، فلا يكون له نوع. في الشيفرة الجديدة استخدم const للقيم ذات النوع، واستخدم #define أساسًا لإعدادات وقت الترجمة.
رسوم توضيحية
مثال 1: مرحبًا يا عالم
أول برنامج لك: اطبع سطرًا واحدًا من النص.
#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
return 0;
}
المخرجات:
Hello, World!
مثال 2: المتغيرات ومحددات التنسيق
احفظ القيم واطبعها بمحددات printf.
#include <stdio.h>
int main(void) {
int apples = 7;
double price = 2.5;
char initial = 'A';
printf("%d apples, %.2f each, initial %c\n", apples, price, initial);
printf("total = %.2f\n", apples * price);
return 0;
}
المخرجات:
7 apples, 2.50 each, initial A
total = 17.50
مثال 3: الأنواع الصحيحة وsizeof
يشغل كل نوع صحيح عددًا مختلفًا من البايتات.
#include <stdio.h>
int main(void) {
printf("char : %zu byte(s)\n", sizeof(char));
printf("short : %zu byte(s)\n", sizeof(short));
printf("int : %zu byte(s)\n", sizeof(int));
printf("long : %zu byte(s)\n", sizeof(long));
printf("long long: %zu byte(s)\n", sizeof(long long));
return 0;
}
المخرجات:
char : 1 byte(s)
short : 2 byte(s)
int : 4 byte(s)
long : 4 byte(s)
long long: 8 byte(s)
مثال 4: الفاصلة العائمة والدقة
تخزن float وdouble كسورًا بدقة محدودة.
#include <stdio.h>
int main(void) {
float f = 1.0f / 3.0f;
double d = 1.0 / 3.0;
printf("float : %.10f\n", f);
printf("double : %.10f\n", d);
printf("0.1 + 0.2 == 0.3 ? %s\n", (0.1 + 0.2 == 0.3) ? "yes" : "no");
return 0;
}
المخرجات:
float : 0.3333333433
double : 0.3333333333
0.1 + 0.2 == 0.3 ? no
مثال 5: char وقيم ASCII
char عدد صحيح صغير، وقيمة 'a' هي 97.
#include <stdio.h>
int main(void) {
char c = 'a';
printf("%c is %d\n", c, c);
printf("upper of %c is %c\n", c, c - 'a' + 'A');
printf("digit '7' minus '0' = %d\n", '7' - '0');
return 0;
}
المخرجات:
a is 97
upper of a is A
digit '7' minus '0' = 7
مثال 6: الثوابت: const وdefine
قيم ثابتة لا يجوز للبرنامج تغييرها.
#include <stdio.h>
#define MAX_USERS 100
static const double TAX_RATE = 0.07;
int main(void) {
double price = 200.0;
printf("max users: %d\n", MAX_USERS);
printf("price with tax: %.2f\n", price * (1.0 + TAX_RATE));
return 0;
}
المخرجات:
max users: 100
price with tax: 214.00
الموضوع 2: العوامل والتحويل بين الأنواع
تجمع العوامل بين القيم. والتحويل بين الأنواع مصدر شائع للأخطاء، لذلك تستحق قواعده أن تُتعلَّم مبكرًا.
الحساب والقسمة الصحيحة
تعمل +، و-، و*، و/، و% على الأعداد. قسمة int على int تُسقط الكسر، فالناتج 17 / 5 هو 3. لإبقاء الكسر استخدم معاملًا من نوع double مثل 17.0 / 5. ويعطي المعامل % الباقي.
الزيادة والإسناد المركب والمقارنة
تغيّر ++ و-- المتغير بواحد، ويحدد موضعها قبل الاسم أو بعده متى يُرى التغيير. وتحدّث += و*= القيمة في خطوة واحدة. في C تعطي المقارنة 1 للصواب و0 للخطأ، فلا يوجد نوع منطقي منفصل ما لم تُضمّن stdbool.h.
عوامل البتات والتحويل القسري
تعمل &، و|، و^، و~ وإزاحتا << و>> على بتات العدد الصحيح، وهي مفيدة للأعلام. والتحويل القسري مثل (double) يغيّر نوع القيمة في تعبير واحد فقط، ولا يغيّر المتغير نفسه.
مثال 7: الحساب والقسمة الصحيحة
قسمة int على int تُسقط الجزء الكسري.
#include <stdio.h>
int main(void) {
int a = 17, b = 5;
printf("%d %d %d %d %d\n", a + b, a - b, a * b, a / b, a % b);
printf("17.0 / 5 = %.2f\n", 17.0 / b);
return 0;
}
المخرجات:
22 12 85 3 2
17.0 / 5 = 3.40
مثال 8: الزيادة والإسناد المركب
تغيّر ++ و-- و+= المتغير في خطوة واحدة.
#include <stdio.h>
int main(void) {
int n = 5;
printf("%d\n", n++);
printf("%d\n", ++n);
n += 10;
n *= 2;
printf("final %d\n", n);
return 0;
}
المخرجات:
5
7
final 34
مثال 9: المقارنة والعوامل المنطقية
في C تكون القيمة 1 للصواب و0 للخطأ.
#include <stdio.h>
int main(void) {
int age = 20;
int has_ticket = 1;
printf("adult: %d\n", age >= 18);
printf("can enter: %d\n", age >= 18 && has_ticket);
printf("needs help: %d\n", !has_ticket || age < 12);
return 0;
}
المخرجات:
adult: 1
can enter: 1
needs help: 0
مثال 10: عوامل البتات والإزاحة
تعمل على كل بت من بتات العدد الصحيح.
#include <stdio.h>
int main(void) {
unsigned int flags = 0x5;
printf("flags & 4 = %u\n", flags & 4u);
printf("flags | 2 = %u\n", flags | 2u);
printf("flags ^ 1 = %u\n", flags ^ 1u);
printf("1 << 4 = %u\n", 1u << 4);
printf("256 >> 2 = %u\n", 256u >> 2);
return 0;
}
المخرجات:
flags & 4 = 4
flags | 2 = 7
flags ^ 1 = 4
1 << 4 = 16
256 >> 2 = 64
مثال 11: التحويل القسري
حوّل القيمة إلى نوع آخر باستخدام (النوع).
#include <stdio.h>
int main(void) {
int total = 7, count = 2;
double average = (double)total / count;
printf("average = %.2f\n", average);
printf("truncated = %d\n", (int)3.99);
printf("mixed = %.1f\n", total / 2 + 0.5);
return 0;
}
المخرجات:
average = 3.50
truncated = 3
mixed = 3.5
الموضوع 3: القرارات والحلقات
يحدد التحكم في التدفق ما الذي يُنفَّذ وكم مرة. اختر الجملة المناسبة للمشكلة: if للاختيار، وswitch لمجموعة من القيم، والحلقة للتكرار.
if وswitch والعامل الشرطي
يختار if وelse فرعًا واحدًا من شروط. ويختار switch الحالة حسب القيمة، وبدون break ينتقل التنفيذ إلى الحالة التالية، وهذا مفيد أحيانًا لكنه خطأ شائع. العامل الشرطي هو if-else مختصر يعيد قيمة.
for وwhile وdo-while
تناسب for الحالة التي نعرف فيها عدد التكرارات. ويفحص while الشرط قبل كل دورة، وتُنفَّذ do-while مرة واحدة على الأقل. اختر الحلقة التي يكون شرطها أسهل في القراءة.
break وcontinue والحلقات المتداخلة
تخرج break من أعمق حلقة، وتنتقل continue إلى الدورة التالية. والحلقة داخل حلقة هي الطريقة المعتادة لاجتياز شبكة أو جدول. اجعل الحلقات المتداخلة قصيرة، وأعطِ عدّادات الحلقات أسماء واضحة.
مثال 12: if وelse if وelse
اختر فرعًا واحدًا من عدة شروط.
#include <stdio.h>
int main(void) {
int score = 73;
if (score >= 80) {
printf("A\n");
} else if (score >= 70) {
printf("B\n");
} else {
printf("C\n");
}
return 0;
}
المخرجات:
B
مثال 13: switch والسقوط المتتالي
اختر حالة حسب القيمة، ويمنع break السقوط إلى الحالة التالية.
#include <stdio.h>
int main(void) {
for (int day = 1; day <= 3; day++) {
switch (day) {
case 1:
case 2:
printf("day %d: early\n", day);
break;
default:
printf("day %d: later\n", day);
break;
}
}
return 0;
}
المخرجات:
day 1: early
day 2: early
day 3: later
مثال 14: العامل الشرطي
if-else مختصر يعيد قيمة.
#include <stdio.h>
int main(void) {
int a = 4, b = 9;
int larger = (a > b) ? a : b;
printf("larger = %d\n", larger);
printf("%s\n", (larger % 2 == 0) ? "even" : "odd");
return 0;
}
المخرجات:
larger = 9
odd
مثال 15: حلقة for
كرر كتلة باستخدام عداد.
#include <stdio.h>
int main(void) {
int sum = 0;
for (int i = 1; i <= 10; i++) {
sum += i;
}
printf("sum 1..10 = %d\n", sum);
return 0;
}
المخرجات:
sum 1..10 = 55
مثال 16: while وdo-while
كرر حسب شرط، وتُنفَّذ do-while مرة واحدة على الأقل.
#include <stdio.h>
int main(void) {
int n = 3;
while (n > 0) {
printf("while %d\n", n--);
}
int m = 0;
do {
printf("do-while runs once, m = %d\n", m);
} while (m > 0);
return 0;
}
المخرجات:
while 3
while 2
while 1
do-while runs once, m = 0
مثال 17: break وcontinue
اخرج من الحلقة مبكرًا، أو انتقل إلى الدورة التالية.
#include <stdio.h>
int main(void) {
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) continue;
if (i > 7) break;
printf("%d ", i);
}
printf("\n");
return 0;
}
المخرجات:
1 3 5 7
مثال 18: الحلقات المتداخلة: جدول
حلقة داخل حلقة تطبع شبكة.
#include <stdio.h>
int main(void) {
for (int row = 1; row <= 3; row++) {
for (int col = 1; col <= 3; col++) {
printf("%3d", row * col);
}
printf("\n");
}
return 0;
}
المخرجات:
1 2 3
2 4 6
3 6 9
الموضوع 4: الدوال
تعطي الدالة اسمًا لكتلة من الشيفرة، وتأخذ مدخلات وتعيد نتيجة. الدوال الجيدة تؤدي عملًا واحدًا وتسهل اختبارها.
المعاملات والقيم المعادة
صرّح بنوع كل معامل ونوع القيمة المعادة قبل اسم الدالة. تمرر C المعاملات بالقيمة، فتعمل الدالة على نسخة. أعِد النتيجة بـ return، واستخدم void للدالة التي لا تعيد شيئًا.
المؤشرات معاملات
لتغيير متغير المستدعي مرّر عنوانه باستخدام &، واقرأه أو اكتبه باستخدام *. تُظهر دالة التبديل الكلاسيكية الفرق: النسخة التي تمرر القيمة تبدّل نسختها فقط.
الاستدعاء الذاتي والمتغيرات الساكنة المحلية
تستدعي الدالة المتكررة نفسها، وتحتاج إلى شرط توقف، وإلا لن تنتهي وتفيض المكدّس. يحتفظ المتغير static المحلي بقيمته بين الاستدعاءات، وهو مفيد للعدّادات والذاكرات المؤقتة، لكنه يجعل الدالة أصعب في الاختبار.
مثال 19: دوال تعيد قيمة
أعطِ الكود اسمًا وأعد نتيجة.
#include <stdio.h>
static int add(int a, int b) {
return a + b;
}
static double average(int a, int b) {
return (a + b) / 2.0;
}
int main(void) {
printf("%d\n", add(3, 4));
printf("%.1f\n", average(3, 4));
return 0;
}
المخرجات:
7
3.5
مثال 20: التمرير بالقيمة والتمرير بالمؤشر
النسخ لا تغيّر المتغير عند المستدعي، والمؤشرات تغيّره.
#include <stdio.h>
static void try_swap_value(int a, int b) {
int t = a; a = b; b = t;
}
static void swap(int *a, int *b) {
int t = *a; *a = *b; *b = t;
}
int main(void) {
int x = 1, y = 2;
try_swap_value(x, y);
printf("after value swap: %d %d\n", x, y);
swap(&x, &y);
printf("after pointer swap: %d %d\n", x, y);
return 0;
}
المخرجات:
after value swap: 1 2
after pointer swap: 2 1
مثال 21: الاستدعاء الذاتي
دالة تستدعي نفسها ولها شرط توقف.
#include <stdio.h>
static unsigned long long factorial(unsigned int n) {
return n <= 1 ? 1ULL : n * factorial(n - 1);
}
static unsigned long long fib(unsigned int n) {
return n < 2 ? n : fib(n - 1) + fib(n - 2);
}
int main(void) {
printf("10! = %llu\n", factorial(10));
printf("fib(20) = %llu\n", fib(20));
return 0;
}
المخرجات:
10! = 3628800
fib(20) = 6765
مثال 22: المتغيرات المحلية الساكنة
متغير محلي يحتفظ بقيمته بين الاستدعاءات.
#include <stdio.h>
static int next_id(void) {
static int counter = 0;
counter++;
return counter;
}
int main(void) {
for (int i = 0; i < 3; i++) {
printf("id %d\n", next_id());
}
return 0;
}
المخرجات:
id 1
id 2
id 3
الموضوع 5: المصفوفات والنصوص
تخزن المصفوفات عدة قيم من نوع واحد، وفهرسها يبدأ من 0. وفي C، النص مصفوفة من char تنتهي ببايت صفري.
المصفوفة وطولها
ينشئ int scores[5] خمسة أعداد صحيحة. لا تتحقق C من الفهارس، لذلك تؤدي الكتابة خارج الحدود إلى إفساد الذاكرة بصمت. ويعطي sizeof array / sizeof array[0] عدد العناصر عندما تُعلن المصفوفة في الدالة نفسها.
المصفوفات ثنائية الأبعاد ومصفوفات النصوص
يمثل int grid[2][3] جدولًا من صفين وثلاثة أعمدة. وتحتفظ مصفوفة من مؤشرات const char بعدة نصوص بأطوال مختلفة، وهي الطريقة المعتادة لتخزين قائمة أسماء.
دوال النصوص
تعد strlen الأحرف قبل البايت الصفري. وتنسخ strcpy وstrcat النصوص وتدمجها، لكنهما لا تتحققان من حجم المخزن، لذلك يسهل إساءة استخدامهما. وتقارن strcmp النصوص، وتعيد 0 عندما تكون متساوية.
مثال 23: المصفوفات
عدد ثابت من القيم من نوع واحد، والفهرس يبدأ من 0.
#include <stdio.h>
int main(void) {
int scores[5] = {90, 75, 88, 64, 95};
for (int i = 0; i < 5; i++) {
printf("scores[%d] = %d\n", i, scores[i]);
}
return 0;
}
المخرجات:
scores[0] = 90
scores[1] = 75
scores[2] = 88
scores[3] = 64
scores[4] = 95
مثال 24: المجموع والأصغر والأكبر
امرّ على المصفوفة مرة واحدة وتتبع ثلاث نتائج.
#include <stdio.h>
int main(void) {
int values[] = {12, 7, 19, 3, 25};
int n = (int)(sizeof values / sizeof values[0]);
int sum = 0, min = values[0], max = values[0];
for (int i = 0; i < n; i++) {
sum += values[i];
if (values[i] < min) min = values[i];
if (values[i] > max) max = values[i];
}
printf("sum=%d min=%d max=%d avg=%.2f\n", sum, min, max, (double)sum / n);
return 0;
}
المخرجات:
sum=66 min=3 max=25 avg=13.20
مثال 25: المصفوفات ثنائية الأبعاد
جدول من القيم له صفوف وأعمدة.
#include <stdio.h>
int main(void) {
int grid[2][3] = {{1, 2, 3}, {4, 5, 6}};
for (int r = 0; r < 2; r++) {
for (int c = 0; c < 3; c++) {
printf("%d ", grid[r][c]);
}
printf("\n");
}
return 0;
}
المخرجات:
1 2 3
4 5 6
مثال 26: النص كمصفوفة من char
النص مصفوفة char تنتهي ببايت صفري.
#include <stdio.h>
int main(void) {
char name[] = "Ada";
printf("%s has %zu characters\n", name, sizeof name - 1);
name[0] = 'E';
printf("now: %s\n", name);
return 0;
}
المخرجات:
Ada has 3 characters
now: Eda
مثال 27: دوال النص من string.h
strlen وstrcpy وstrcat وstrcmp.
#include <stdio.h>
#include <string.h>
int main(void) {
char buffer[32] = "Hello";
strcat(buffer, ", C");
printf("%s (%zu)\n", buffer, strlen(buffer));
printf("compare: %d\n", strcmp("apple", "banana") < 0);
char copy[32];
strcpy(copy, buffer);
printf("copy: %s\n", copy);
return 0;
}
المخرجات:
Hello, C (8)
compare: 1
copy: Hello, C
الموضوع 6: المؤشرات
يحتفظ المؤشر بعنوان قيمة. وهو قلب C، فهو يتيح للدوال تغيير البيانات، والتنقل في المصفوفات، وبناء هياكل ديناميكية.
العنوان وإلغاء المرجع
يعطي & عنوان متغير، ويقرأ * القيمة عند عنوان المؤشر أو يكتبها. ونوع المؤشر يخبر المترجم بحجم كل خطوة، فالمؤشر int * يتقدم بمقدار sizeof(int).
حساب المؤشرات على المصفوفات
يتصرف اسم المصفوفة كمؤشر إلى أول عنصر فيها. ويشير p + 2 إلى عنصرين بعده، ويقرأ *(p + 2) ذلك العنصر، وهو مثل p[2]. والتجاوز بعد نهاية المصفوفة سلوك غير معرّف.
تمرير المصفوفات والمؤشرات إلى الدوال
تستقبل الدالة المصفوفة كمؤشر، لذلك يجب أن تستقبل طولها أيضًا. وتخبر الكلمة const على المؤشر القارئ والمترجم بأن الدالة لن تغيّر البيانات.
مثال 28: المؤشرات: العنوان وإلغاء المرجع
تعطي & العنوان، ويقرأ * القيمة في ذلك الموقع أو يكتبها.
#include <stdio.h>
int main(void) {
int value = 42;
int *p = &value;
printf("value via pointer: %d\n", *p);
*p = 100;
printf("value now: %d\n", value);
return 0;
}
المخرجات:
value via pointer: 42
value now: 100
مثال 29: حساب المؤشرات
حرّك المؤشر على طول المصفوفة باستخدام + و-
#include <stdio.h>
int main(void) {
int nums[4] = {10, 20, 30, 40};
int *p = nums;
for (int i = 0; i < 4; i++) {
printf("%d ", *(p + i));
}
printf("\n");
printf("third via index: %d, via pointer: %d\n", nums[2], *(nums + 2));
return 0;
}
المخرجات:
10 20 30 40
third via index: 30, via pointer: 30
مثال 30: تمرير المصفوفات إلى الدوال
تُمرَّر المصفوفة كمؤشر مع طولها.
#include <stdio.h>
static int sum(const int *values, size_t count) {
int total = 0;
for (size_t i = 0; i < count; i++) {
total += values[i];
}
return total;
}
int main(void) {
int data[] = {1, 2, 3, 4, 5};
printf("sum = %d\n", sum(data, sizeof data / sizeof data[0]));
return 0;
}
المخرجات:
sum = 15
مثال 31: مصفوفات من النصوص
مصفوفة من المؤشرات التي تشير إلى نصوص.
#include <stdio.h>
int main(void) {
const char *fruits[] = {"apple", "banana", "cherry"};
size_t n = sizeof fruits / sizeof fruits[0];
for (size_t i = 0; i < n; i++) {
printf("%zu: %s\n", i, fruits[i]);
}
return 0;
}
المخرجات:
0: apple
1: banana
2: cherry
الموضوع 7: البنى والاتحادات والتعدادات وtypedef
تجمع هذه الأنواع القيم معًا، وتعطي أسماء لمجموعات من الثوابت. وهي أساس بنى البيانات في C.
البنى ومؤشراتها
تجمع struct الحقول تحت اسم واحد. ويصل عامل السهم -> إلى الحقل عبر المؤشر، فيكون p->x مثل (*p).x. مرّر مؤشرًا إلى بنية كبيرة بدلًا من نسخها.
الاتحادات والتعدادات
تتشارك union مساحة ذاكرة واحدة بين عدة أنواع، لذلك لا تكون إلا قيمة واحدة ذات معنى في كل مرة. وتعطي enum أسماء لثوابت صحيحة، فتصبح جمل switch أسهل في القراءة.
typedef
يعطي typedef للنوع اسمًا أقصر، مثل Rect للبنية التي تحمل قيمتين من نوع double. ويجعل typedef أنواع مؤشرات الدوال أسهل بكثير في القراءة.
مثال 32: الهياكل struct
اجمع القيم المترابطة تحت اسم واحد.
#include <stdio.h>
struct Point {
int x;
int y;
};
int main(void) {
struct Point p = {3, 4};
p.x += 1;
printf("point (%d, %d)\n", p.x, p.y);
return 0;
}
المخرجات:
point (4, 4)
مثال 33: مؤشرات الهياكل والرمز ->
صل إلى حقول الهيكل عبر المؤشر.
#include <stdio.h>
struct Book {
const char *title;
int pages;
};
static void print_book(const struct Book *b) {
printf("%s has %d pages\n", b->title, b->pages);
}
int main(void) {
struct Book books[] = {{"C Basics", 120}, {"Pointers", 96}};
for (int i = 0; i < 2; i++) {
print_book(&books[i]);
}
return 0;
}
المخرجات:
C Basics has 120 pages
Pointers has 96 pages
مثال 34: الاتحادات union
عدة أنواع تشترك في الذاكرة نفسها، وقيمة واحدة في كل مرة.
#include <stdio.h>
union Value {
int as_int;
float as_float;
};
int main(void) {
union Value v;
v.as_int = 65;
printf("as int: %d\n", v.as_int);
v.as_float = 1.5f;
printf("as float: %.1f\n", v.as_float);
return 0;
}
المخرجات:
as int: 65
as float: 1.5
مثال 35: التعدادات enum
أسمِّ مجموعة من الثوابت الصحيحة.
#include <stdio.h>
enum Color { RED, GREEN = 5, BLUE };
int main(void) {
enum Color c = BLUE;
printf("RED=%d GREEN=%d BLUE=%d\n", RED, GREEN, BLUE);
printf("chosen: %d\n", c);
return 0;
}
المخرجات:
RED=0 GREEN=5 BLUE=6
chosen: 6
مثال 36: typedef وأنواع مؤشرات الدوال
أعطِ النوع اسمًا أقصر.
#include <stdio.h>
typedef struct {
double width;
double height;
} Rect;
typedef double (*Measure)(Rect);
static double area(Rect r) {
return r.width * r.height;
}
int main(void) {
Rect r = {3.0, 4.5};
Measure m = area;
printf("area = %.2f\n", m(r));
return 0;
}
المخرجات:
area = 13.50
الموضوع 8: الذاكرة الديناميكية والملفات
تسمح الذاكرة الديناميكية للبرنامج بطلب مساحة أثناء التشغيل. وتسمح الملفات بحفظ البيانات بعد توقف البرنامج. كلاهما يحتاج إلى عناية في C، لأن المبرمج مسؤول عن التنظيف.
malloc وcalloc وrealloc وfree
يطلب malloc كتلة من البايتات، ويعيد calloc ذاكرة مُصفّرة، ويغيّر realloc حجم كتلة. يجب أن يقابل كل استدعاء ناجح استدعاء free واحد. تحقق من أن النتيجة ليست NULL قبل استخدامها، لأن الحجز قد يفشل.
تجنب التسريبات والمؤشرات المتدلية
بعد free اجعل المؤشر NULL حتى لا تستخدمه بالخطأ. حرّر كل كتلة مرة واحدة. تتبّع الدالة التي تملك كل كتلة، فلا تستخدم دالة كتلة حرّرتها دالة أخرى.
الملفات النصية والثنائية
يفتح fopen الملف، ويحدد الوضع هل هو قراءة أو كتابة أو إلحاق. وتتعامل fprintf وfgets مع النصوص، بينما تنسخ fwrite وfread البايتات الخام. تحقق دائمًا من قيمة fopen المُعادة، وأغلق كل ملف فتحته.
مثال 37: الذاكرة الديناميكية بـ malloc وfree
اطلب الذاكرة أثناء التشغيل وأعدها عند الانتهاء.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 5;
int *squares = malloc(n * sizeof *squares);
if (squares == NULL) {
fprintf(stderr, "out of memory\n");
return 1;
}
for (int i = 0; i < n; i++) {
squares[i] = i * i;
}
for (int i = 0; i < n; i++) {
printf("%d ", squares[i]);
}
printf("\n");
free(squares);
return 0;
}
المخرجات:
0 1 4 9 16
مثال 38: calloc وrealloc
ذاكرة مصفّرة، وتغيير حجم الكتلة.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *items = calloc(3, sizeof *items);
printf("zeroed: %d %d %d\n", items[0], items[1], items[2]);
int *bigger = realloc(items, 6 * sizeof *bigger);
if (bigger == NULL) {
free(items);
return 1;
}
items = bigger;
items[5] = 99;
printf("last item: %d\n", items[5]);
free(items);
return 0;
}
المخرجات:
zeroed: 0 0 0
last item: 99
مثال 39: تحرير الذاكرة بأمان
حرّر مرة واحدة، ثم اجعل المؤشر فارغًا.
#include <stdio.h>
#include <stdlib.h>
static char *make_greeting(const char *name) {
char *out = malloc(64);
if (out == NULL) return NULL;
snprintf(out, 64, "Hello, %s", name);
return out;
}
int main(void) {
char *msg = make_greeting("Mali");
if (msg != NULL) {
printf("%s\n", msg);
free(msg);
msg = NULL;
}
return 0;
}
المخرجات:
Hello, Mali
مثال 40: الملفات النصية: الكتابة والقراءة
fopen وfprintf وfgets وfclose.
#include <stdio.h>
int main(void) {
FILE *out = fopen("notes.txt", "w");
if (out == NULL) return 1;
fprintf(out, "first line\nsecond line\n");
fclose(out);
FILE *in = fopen("notes.txt", "r");
if (in == NULL) return 1;
char line[64];
while (fgets(line, sizeof line, in) != NULL) {
printf("read: %s", line);
}
fclose(in);
remove("notes.txt");
return 0;
}
المخرجات:
read: first line
read: second line
مثال 41: الملفات الثنائية بـ fwrite وfread
احفظ القيم الخام في ملف واقرأها لاحقًا.
#include <stdio.h>
int main(void) {
int data[] = {10, 20, 30};
FILE *f = fopen("values.bin", "wb");
if (f == NULL) return 1;
fwrite(data, sizeof data[0], 3, f);
fclose(f);
int back[3] = {0};
f = fopen("values.bin", "rb");
if (f == NULL) return 1;
size_t got = fread(back, sizeof back[0], 3, f);
fclose(f);
remove("values.bin");
printf("read %zu values: %d %d %d\n", got, back[0], back[1], back[2]);
return 0;
}
المخرجات:
read 3 values: 10 20 30
الموضوع 9: المعالج المسبق والمؤشرات للدوال والدوال ذات الوسائط المتغيرة
تغيّر هذه الأدوات طريقة بناء الشيفرة وطريقة استدعاء الدوال. تُكسب الاستخدامُ الجيد البرنامجَ مرونة، أما الاستخدام المتسرع فيجعل تصحيح الأخطاء صعبًا.
الماكرو والترجمة الشرطية
الماكرو نص يُستبدل قبل الترجمة. ضع أقواسًا حول كل وسيط في الماكرو وحول التعبير كله، وإلا فإن SQUARE(2 + 1) يعطي نتيجة خاطئة. تُضمّن #if و#ifdef الشيفرة فقط عندما يتحقق الشرط وقت البناء.
مؤشرات الدوال وqsort
يخزّن مؤشر الدالة عنوان الدالة، فيمكنك تمرير سلوك إلى دالة أخرى. تستخدم qsort دالة مقارنة تُمرَّر إليها، فتستطيع روتين واحد أن يرتب مصفوفة من أي نوع.
الدوال ذات الوسائط المتغيرة ووسائط سطر الأوامر
يتيح stdarg.h للدالة أن تأخذ أي عدد من الوسائط، وتُقرأ بالدالة va_arg. تستقبل main القيمتين argc وargv، وهما تحملان الوسائط التي كتبها المستخدم بعد اسم البرنامج.
مثال 42: وسائط سطر الأوامر
يحمل argc وargv الوسائط التي كتبها المستخدم.
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("argument count: %d\n", argc);
for (int i = 1; i < argc; i++) {
printf("arg %d: %s\n", i, argv[i]);
}
return 0;
}
المخرجات:
argument count: 1
مثال 43: الماكرو والماكرو الشبيه بالدالة
استبدال نصي قبل الترجمة، ويجب وضع الوسائط بين أقواس.
#include <stdio.h>
#define SQUARE(x) ((x) * (x))
#define MAX(a, b) ((a) > (b) ? (a) : (b))
int main(void) {
printf("SQUARE(4) = %d\n", SQUARE(4));
printf("SQUARE(2 + 1) = %d\n", SQUARE(2 + 1));
printf("MAX(3, 9) = %d\n", MAX(3, 9));
return 0;
}
المخرجات:
SQUARE(4) = 16
SQUARE(2 + 1) = 9
MAX(3, 9) = 9
مثال 44: الترجمة الشرطية
أدرج الشيفرة فقط عندما يكون الشرط صحيحًا وقت البناء.
#include <stdio.h>
#define DEBUG_MODE 1
int main(void) {
#if DEBUG_MODE
printf("debug build\n");
#else
printf("release build\n");
#endif
#ifdef __STDC_VERSION__
printf("C standard version is known\n");
#endif
return 0;
}
المخرجات:
debug build
C standard version is known
مثال 45: مؤشرات الدوال
خزّن دالة في متغير واستدعها عبر المؤشر.
#include <stdio.h>
static int add(int a, int b) { return a + b; }
static int mul(int a, int b) { return a * b; }
static int apply(int (*op)(int, int), int x, int y) {
return op(x, y);
}
int main(void) {
int (*ops[])(int, int) = {add, mul};
printf("add: %d\n", apply(ops[0], 6, 7));
printf("mul: %d\n", apply(ops[1], 6, 7));
return 0;
}
المخرجات:
add: 13
mul: 42
مثال 46: الفرز بـ qsort
رتّب أي مصفوفة بدالة مقارنة تُمرَّر إليها.
#include <stdio.h>
#include <stdlib.h>
static int compare_ints(const void *a, const void *b) {
int x = *(const int *)a;
int y = *(const int *)b;
return (x > y) - (x < y);
}
int main(void) {
int nums[] = {42, 7, 19, 3, 25};
size_t n = sizeof nums / sizeof nums[0];
qsort(nums, n, sizeof nums[0], compare_ints);
for (size_t i = 0; i < n; i++) {
printf("%d ", nums[i]);
}
printf("\n");
return 0;
}
المخرجات:
3 7 19 25 42
مثال 47: الدوال ذات الوسائط المتغيرة
دالة تقبل أي عدد من الوسائط عبر stdarg.
#include <stdarg.h>
#include <stdio.h>
static int sum_all(int count, ...) {
va_list args;
va_start(args, count);
int total = 0;
for (int i = 0; i < count; i++) {
total += va_arg(args, int);
}
va_end(args);
return total;
}
int main(void) {
printf("%d\n", sum_all(3, 1, 2, 3));
printf("%d\n", sum_all(5, 10, 20, 30, 40, 50));
return 0;
}
المخرجات:
6
150
الموضوع 10: البتات والأخطاء والنطاق
عمليات البتات منخفضة المستوى، وفحص الأخطاء، وقواعد النطاق هي المهارات اليومية لمبرمجي C.
عدّ البتات واختبارها
تمسح x &= x - 1 أقل بت مضبوط، لذلك تعدّ الحلقة التي تكرر ذلك البتات المضبوطة. وتختبر أقنعة البتات مثل flags & 4 بتًا واحدًا دون أن تغيّر غيره.
فحص الأخطاء بـ errno
تعيد كثير من دوال المكتبة قيمة خاصة عند الفشل، وتضبط errno لتوضيح السبب. افحص قيمة الإرجاع أولًا، ثم اقرأ errno، واستخدم strerror لتحويله إلى نص.
static على مستوى الملف
تكون الدالة أو المتغير static على مستوى الملف مرئيًا داخل ذلك الملف فقط. وهذا يمنع الدوال المساعدة الداخلية من التعارض مع أسماء في ملفات أخرى، وهي خطوة أساسية نحو بنية جيدة للشيفرة.
مثال 48: عدّ البتات المضبوطة
امسح أقل بت مضبوط في كل دورة حتى لا يبقى شيء.
#include <stdio.h>
static int count_bits(unsigned int x) {
int count = 0;
while (x != 0) {
x &= x - 1;
count++;
}
return count;
}
int main(void) {
printf("bits in 0 = %d\n", count_bits(0));
printf("bits in 7 = %d\n", count_bits(7));
printf("bits in 255 = %d\n", count_bits(255));
return 0;
}
المخرجات:
bits in 0 = 0
bits in 7 = 3
bits in 255 = 8
مثال 49: معالجة الأخطاء بـ errno
افحص القيمة المعادة، ثم اقرأ errno لمعرفة السبب.
#include <errno.h>
#include <stdio.h>
#include <string.h>
int main(void) {
FILE *f = fopen("no-such-file-here.txt", "r");
if (f == NULL) {
printf("open failed: errno set = %s\n", errno != 0 ? "yes" : "no");
return 0;
}
fclose(f);
return 0;
}
المخرجات:
open failed: errno set = yes
مثال 50: الدوال static والحالة العامة
تحصر static الاسم في الملف الحالي فقط.
#include <stdio.h>
static int call_count = 0;
static void record_call(void) {
call_count++;
}
static int calls_so_far(void) {
return call_count;
}
int main(void) {
record_call();
record_call();
printf("calls: %d\n", calls_so_far());
return 0;
}
المخرجات:
calls: 2
الموضوع 11: أساسيات C++: المراجع والتحميل الزائد وstring وvector
تحافظ C++ على سرعة C وتضيف الأنواع والمراجع والمكتبة القياسية. تقدّم هذه الأمثلة الأدوات اليومية.
المراجع والتحميل الزائد
المرجع اسم آخر لمتغير موجود، فتغيير المرجع يغيّر المتغير. ويمكن لعدة دوال أن تشترك في الاسم عندما تختلف أنواع معاملاتها، ويختار المترجم الصحيحة.
std::string وstd::vector
يكبر std::string ويصغر، ويعرف طوله، فلا تحتاج إلى إدارة الذاكرة يدويًا. وstd::vector مصفوفة قابلة لتغيير الحجم وتملك عناصرها. يعمل كلاهما جيدًا مع حلقات for على المدى.
auto والوسائط الافتراضية وiostream
يتيح auto للمترجم استنتاج النوع من القيمة، وتسمح الوسائط الافتراضية للمستدعي بحذف معامل. ويحل std::cout مع << محل printf، ويتحقق من الأنواع وقت الترجمة.
مثال 51: Hello باستخدام iostream
اطبع النص إلى وحدة التحكم باستخدام std::cout.
#include <iostream>
int main() {
std::cout << "Hello, C++!" << '\n';
return 0;
}
المخرجات:
Hello, C++!
مثال 52: المراجع
المرجع اسم آخر للمتغير نفسه.
#include <iostream>
void increment(int &value) {
++value;
}
int main() {
int count = 5;
int &alias = count;
alias += 10;
increment(count);
std::cout << count << '\n';
}
المخرجات:
16
مثال 53: التحميل الزائد للدوال
دوال متعددة بالاسم نفسه، تُختار حسب أنواع الوسائط.
#include <iostream>
#include <string>
std::string describe(int n) { return "int " + std::to_string(n); }
std::string describe(double d) { return "double " + std::to_string(d); }
std::string describe(const std::string &s) { return "string " + s; }
int main() {
std::cout << describe(7) << '\n';
std::cout << describe(2.5) << '\n';
std::cout << describe(std::string("hi")) << '\n';
}
المخرجات:
int 7
double 2.500000
string hi
مثال 54: الوسائط الافتراضية
معاملات يمكن حذفها عند الاستدعاء.
#include <iostream>
int power(int base, int exponent = 2) {
int result = 1;
for (int i = 0; i < exponent; ++i) result *= base;
return result;
}
int main() {
std::cout << power(3) << ' ' << power(3, 4) << '\n';
}
المخرجات:
9 81
مثال 55: أساسيات std::string
ادمج النص وقِس طوله واقتطعه وابحث فيه بأمان.
#include <iostream>
#include <string>
int main() {
std::string name = "Ada";
name += " Lovelace";
std::cout << name << " has " << name.size() << " characters\n";
std::cout << name.substr(0, 3) << '\n';
std::cout << (name.find("Love") != std::string::npos) << '\n';
}
المخرجات:
Ada Lovelace has 12 characters
Ada
1
مثال 56: أساسيات std::vector
مصفوفة قابلة للنمو تملك عناصرها.
#include <iostream>
#include <vector>
int main() {
std::vector<int> scores{70, 85, 92};
scores.push_back(64);
int total = 0;
for (int s : scores) total += s;
std::cout << "count=" << scores.size() << " total=" << total << '\n';
}
المخرجات:
count=4 total=311
مثال 57: حلقة for على المدى وauto
كرر على الحاوية دون حساب الفهارس.
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> stock{{"apple", 3}, {"pear", 0}};
for (const auto &[item, qty] : stock) {
std::cout << item << ": " << qty << '\n';
}
}
المخرجات:
apple: 3
pear: 0
الموضوع 12: الأصناف وRAII والنسخ والنقل
يجمع الصنف البيانات والسلوك، وهنا تصبح C++ قوية. يربط RAII الموارد بعمر الكائن، فتصبح البرامج آمنة دون تنظيف يدوي.
الأصناف والمُنشئات والأعضاء const
يهيّئ المُنشئ الكائن عند إنشائه. ويعد التابع المعلَّم بـ const بعدم تغيير الكائن. اجعل البيانات خاصة، واعرض فقط ما يحتاجه المستدعي، وتحقق من المدخلات قبل حفظها.
المدمّرات وRAII
يعمل المدمّر عندما يخرج الكائن من نطاقه، فيتم تحرير الموارد التي اكتُسبت في المُنشئ هناك. هذا هو RAII: يتحرر الملف أو القفل أو كتلة الذاكرة تلقائيًا حتى عند حدوث خطأ.
النسخ والنقل والعوامل
يحتاج الكائن الذي يملك ذاكرة إلى مُنشئ نسخ وإسناد نسخ، وإلا تشارك كائنان الكتلة نفسها وحرّراها مرتين. يتيح std::move للكائن أن يسلّم موارده بدلًا من نسخها. وتجعل إعادة تعريف العوامل + و<< تعمل مع أنواعك الخاصة.
مثال 58: الأصناف والمُنشئات
اجمع البيانات والسلوك، وجهّز الكائن في المُنشئ.
#include <iostream>
#include <string>
class Book {
public:
Book(std::string title, int pages) : title_(std::move(title)), pages_(pages) {}
std::string title() const { return title_; }
int pages() const { return pages_; }
private:
std::string title_;
int pages_;
};
int main() {
Book b{"Clean C++", 320};
std::cout << b.title() << " (" << b.pages() << " pages)\n";
}
المخرجات:
Clean C++ (320 pages)
مثال 59: الدوال الأعضاء const
وعد بأن الطريقة لا تغيّر الكائن.
#include <iostream>
class Counter {
public:
void add(int n) { total_ += n; }
int total() const { return total_; }
private:
int total_ = 0;
};
int report(const Counter &c) {
return c.total();
}
int main() {
Counter c;
c.add(4);
c.add(6);
std::cout << report(c) << '\n';
}
المخرجات:
10
مثال 60: التغليف والاستثناءات
اجعل البيانات خاصة، وارفض المدخلات غير الصالحة.
#include <iostream>
#include <stdexcept>
class Account {
public:
explicit Account(int opening) : balance_(opening) {}
void deposit(int amount) {
if (amount <= 0) throw std::invalid_argument("amount must be positive");
balance_ += amount;
}
int balance() const { return balance_; }
private:
int balance_;
};
int main() {
Account a{100};
a.deposit(50);
std::cout << a.balance() << '\n';
}
المخرجات:
150
مثال 61: المدمّرات وRAII
يحرر المدمّر المورد عند خروج الكائن من نطاقه.
#include <iostream>
#include <string>
class Trace {
public:
explicit Trace(std::string name) : name_(std::move(name)) {
std::cout << "open " << name_ << '\n';
}
~Trace() { std::cout << "close " << name_ << '\n'; }
private:
std::string name_;
};
int main() {
Trace outer{"outer"};
{
Trace inner{"inner"};
std::cout << "work\n";
}
std::cout << "done\n";
}
المخرجات:
open outer
open inner
work
close inner
done
close outer
مثال 62: منشئ النسخ وإسناد النسخ
انسخ كائنًا يملك ذاكرة دون مشاركتها.
#include <iostream>
#include <cstring>
class Buffer {
public:
explicit Buffer(const char *text) : data_(new char[std::strlen(text) + 1]) {
std::strcpy(data_, text);
}
Buffer(const Buffer &other) : data_(new char[std::strlen(other.data_) + 1]) {
std::strcpy(data_, other.data_);
}
Buffer &operator=(const Buffer &other) {
if (this != &other) {
delete[] data_;
data_ = new char[std::strlen(other.data_) + 1];
std::strcpy(data_, other.data_);
}
return *this;
}
~Buffer() { delete[] data_; }
const char *text() const { return data_; }
private:
char *data_;
};
int main() {
Buffer a{"hello"};
Buffer b = a;
b = Buffer{"world"};
std::cout << a.text() << ' ' << b.text() << '\n';
}
المخرجات:
hello world
مثال 63: دلالات النقل
انقل المورد بدلًا من نسخه.
#include <iostream>
#include <string>
#include <utility>
#include <vector>
int main() {
std::string source = "a long piece of text";
std::string target = std::move(source);
std::cout << "target: " << target << '\n';
std::cout << "source empty: " << source.empty() << '\n';
std::vector<std::string> items;
items.push_back(std::move(target));
std::cout << "items: " << items.size() << '\n';
}
المخرجات:
target: a long piece of text
source empty: 1
items: 1
مثال 64: تحميل المعاملات
اجعل + و<< تعمل مع نوعك الخاص.
#include <iostream>
struct Vec2 {
double x, y;
Vec2 operator+(const Vec2 &o) const { return {x + o.x, y + o.y}; }
Vec2 operator*(double k) const { return {x * k, y * k}; }
};
std::ostream &operator<<(std::ostream &os, const Vec2 &v) {
return os << '(' << v.x << ", " << v.y << ')';
}
int main() {
Vec2 a{1, 2}, b{3, 4};
std::cout << (a + b) * 2.0 << '\n';
}
المخرجات:
(8, 12)
الموضوع 13: الوراثة والمؤشرات الذكية وأنواع المكتبة
تتيح الوراثة والدوال الافتراضية لشيفرة واحدة أن تعمل مع أنواع كثيرة. وتعبّر المؤشرات الذكية وoptional وvariant عن الملكية والاحتمال داخل نظام الأنواع.
الوراثة والدوال الافتراضية
يرث الصنف المشتق من صنف أساسي ويعيد تعريف الدوال الافتراضية. يستدعي المؤشر إلى الصنف الأساسي النسخة الصحيحة وقت التشغيل. ويجب أن يملك الصنف الأساسي متعدد الأشكال مُدمّرًا افتراضيًا دائمًا.
unique_ptr وshared_ptr
يملك unique_ptr مالكًا واحدًا فقط، ويحرر كائنه تلقائيًا. ويعدّ shared_ptr عدد المالكين، ويراقب weak_ptr الكائن دون أن يُبقيه حيًا. استخدم unique_ptr أساسًا، ولا تستخدم shared_ptr إلا عندما تكون الملكية مشتركة فعلًا.
optional وvariant وstd::function
يحتفظ std::optional بقيمة قد تغيب. ويحتفظ std::variant بواحد من عدة أنواع، ويعالج std::visit كل حالة. ويخزّن std::function أي شيء قابل للاستدعاء، مثل تعبير لامدا، في متغير.
مثال 65: الوراثة والدوال الافتراضية
تغيّر الأصناف المشتقة السلوك عبر مؤشر الصنف الأساسي.
#include <iostream>
#include <string>
class Animal {
public:
virtual ~Animal() = default;
virtual std::string sound() const { return "..."; }
};
class Dog : public Animal {
public:
std::string sound() const override { return "Woof"; }
};
class Cat : public Animal {
public:
std::string sound() const override { return "Meow"; }
};
int main() {
Dog d;
Cat c;
const Animal *pets[] = {&d, &c};
for (const Animal *p : pets) std::cout << p->sound() << '\n';
}
المخرجات:
Woof
Meow
مثال 66: الأصناف المجردة
لا يمكن إنشاء صنف أساسي يحتوي دالة virtual نقية مباشرة.
#include <iostream>
#include <memory>
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
};
class Square : public Shape {
public:
explicit Square(double s) : side_(s) {}
double area() const override { return side_ * side_; }
private:
double side_;
};
int main() {
std::unique_ptr<Shape> s = std::make_unique<Square>(3.0);
std::cout << s->area() << '\n';
}
المخرجات:
9
مثال 67: تعدد الأشكال مع المؤشرات الذكية
خزّن أنواعًا مختلفة في vector واحدة من unique_ptr.
#include <iostream>
#include <memory>
#include <vector>
struct Task {
virtual ~Task() = default;
virtual void run() const = 0;
};
struct Print : Task {
void run() const override { std::cout << "print\n"; }
};
struct Save : Task {
void run() const override { std::cout << "save\n"; }
};
int main() {
std::vector<std::unique_ptr<Task>> queue;
queue.push_back(std::make_unique<Print>());
queue.push_back(std::make_unique<Save>());
for (const auto &t : queue) t->run();
}
المخرجات:
print
save
مثال 68: ملكية unique_ptr
مالك واحد فقط، ويتحرر الكائن تلقائيًا.
#include <iostream>
#include <memory>
struct Resource {
explicit Resource(int id) : id_(id) { std::cout << "acquire " << id_ << '\n'; }
~Resource() { std::cout << "release " << id_ << '\n'; }
int id() const { return id_; }
private:
int id_;
};
std::unique_ptr<Resource> make(int id) {
return std::make_unique<Resource>(id);
}
int main() {
auto r = make(7);
std::cout << "using " << r->id() << '\n';
}
المخرجات:
acquire 7
using 7
release 7
مثال 69: shared_ptr وweak_ptr
ملكية مشتركة مع عداد، ومراقب ضعيف.
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> a = std::make_shared<int>(42);
std::weak_ptr<int> watcher = a;
std::cout << "use count: " << a.use_count() << '\n';
{
std::shared_ptr<int> b = a;
std::cout << "use count: " << a.use_count() << '\n';
}
std::cout << "after scope: " << a.use_count() << '\n';
std::cout << "still alive: " << !watcher.expired() << '\n';
}
المخرجات:
use count: 1
use count: 2
after scope: 1
still alive: 1
مثال 70: std::optional
قيمة قد تغيب دون استخدام مؤشرات فارغة.
#include <iostream>
#include <optional>
#include <string>
std::optional<int> parse_int(const std::string &text) {
try {
return std::stoi(text);
} catch (...) {
return std::nullopt;
}
}
int main() {
for (const char *s : {"42", "oops"}) {
if (auto v = parse_int(s)) {
std::cout << "value " << *v << '\n';
} else {
std::cout << "not a number: " << s << '\n';
}
}
}
المخرجات:
value 42
not a number: oops
مثال 71: std::variant وstd::visit
قيمة واحدة يمكن أن تكون أحد عدة أنواع.
#include <iostream>
#include <string>
#include <variant>
using Value = std::variant<int, double, std::string>;
std::string kind(const Value &v) {
return std::visit([](const auto &x) -> std::string {
using T = std::decay_t<decltype(x)>;
if constexpr (std::is_same_v<T, int>) return "int";
else if constexpr (std::is_same_v<T, double>) return "double";
else return "string";
}, v);
}
int main() {
std::cout << kind(Value{3}) << ' ' << kind(Value{2.5}) << ' ' << kind(Value{std::string("x")}) << '\n';
}
المخرجات:
int double string
مثال 72: التعابير اللامدا والالتقاط
دوال صغيرة بلا اسم تستطيع استخدام متغيرات خارجية.
#include <iostream>
int main() {
int base = 10;
auto add_base = [base](int x) { return x + base; };
auto count = 0;
auto bump = [&count]() { ++count; };
bump();
bump();
std::cout << add_base(5) << ' ' << count << '\n';
}
المخرجات:
15 2
مثال 73: std::function
خزّن أي شيء قابل للاستدعاء في متغير.
#include <functional>
#include <iostream>
#include <vector>
int main() {
std::vector<std::function<int(int)>> steps{
[](int x) { return x + 1; },
[](int x) { return x * 3; },
};
int value = 2;
for (const auto &step : steps) value = step(value);
std::cout << value << '\n';
}
المخرجات:
9
الموضوع 14: الخوارزميات والحاويات والقوالب
توفر المكتبة القياسية حاويات وخوارزميات ومكررات تعمل معًا. وتتيح لك القوالب كتابة الشيفرة مرة واحدة لأنواع كثيرة.
الخوارزميات والمكررات
تعمل std::sort وstd::find_if وstd::transform على أي مدى عبر المكررات. تجعل تعابير لامدا الشروط قصيرة وواضحة. فضّل الخوارزمية على الحلقة اليدوية عندما تتوفر.
الخرائط والمجموعات والمصفوفات
يحافظ std::map على ترتيب المفاتيح، ويستخدم std::unordered_map التجزئة للبحث السريع. ويحتفظ std::set بقيم فريدة مرتبة، ولـ std::array حجم ثابت، ويعيد std::pair مع الربط المنظم قيمتين بشكل مرتب.
القوالب والتخصيص والمفاهيم
تعمل دالة القالب مع أي نوع يدعم العمليات التي تستخدمها. وتكتب قوالب الأصناف مثل Stack<T> مرة واحدة. تسمّي المفاهيم في C++20 الشروط التي يجب أن يستوفيها نوع القالب، فتظهر الأخطاء في موضع الاستدعاء.
مثال 74: الخوارزميات: sort وfind_if وtransform
الخوارزميات القياسية تعمل على أي نطاق من القيم.
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
std::vector<int> v{5, 2, 9, 1, 7};
std::sort(v.begin(), v.end());
auto big = std::find_if(v.begin(), v.end(), [](int x) { return x > 4; });
std::transform(v.begin(), v.end(), v.begin(), [](int x) { return x * 10; });
std::cout << "first > 4: " << *big << '\n';
for (int x : v) std::cout << x << ' ';
std::cout << '\n';
}
المخرجات:
first > 4: 50
10 20 50 70 90
مثال 75: المكررات وstd::map
امرّ على حاوية مفاتيح-قيم مرتبة باستخدام المكررات.
#include <iostream>
#include <map>
int main() {
std::map<int, const char *> names{{3, "c"}, {1, "a"}, {2, "b"}};
for (auto it = names.begin(); it != names.end(); ++it) {
std::cout << it->first << '=' << it->second << '\n';
}
auto found = names.find(2);
std::cout << "found " << found->second << '\n';
}
المخرجات:
1=a
2=b
3=c
found b
مثال 76: عدّ الكلمات بـ unordered_map
احسب الكلمات باستخدام خريطة تجزئة.
#include <iostream>
#include <sstream>
#include <string>
#include <unordered_map>
int main() {
std::string text = "the cat and the hat and the bat";
std::unordered_map<std::string, int> counts;
std::istringstream words(text);
std::string w;
while (words >> w) ++counts[w];
std::cout << "the=" << counts["the"] << " and=" << counts["and"] << " bat=" << counts["bat"] << '\n';
}
المخرجات:
the=3 and=2 bat=1
مثال 77: std::set وstd::multiset
مجموعات مرتبة، بدون وبمكررات.
#include <iostream>
#include <set>
int main() {
std::set<int> unique{3, 1, 3, 2};
std::multiset<int> all{3, 1, 3, 2};
std::cout << "set size " << unique.size() << '\n';
std::cout << "multiset size " << all.size() << '\n';
for (int x : unique) std::cout << x << ' ';
std::cout << '\n';
}
المخرجات:
set size 3
multiset size 4
1 2 3
مثال 78: std::array
مصفوفة بحجم ثابت لها حجم ومكررات.
#include <array>
#include <iostream>
#include <numeric>
int main() {
std::array<int, 4> a{1, 2, 3, 4};
int sum = std::accumulate(a.begin(), a.end(), 0);
std::cout << "size " << a.size() << " sum " << sum << '\n';
}
المخرجات:
size 4 sum 10
مثال 79: std::pair والربط المنظم
أعِد قيمتين واستخرجهما بالأسماء.
#include <iostream>
#include <utility>
std::pair<int, int> divide(int a, int b) {
return {a / b, a % b};
}
int main() {
auto [quotient, remainder] = divide(17, 5);
std::cout << quotient << " remainder " << remainder << '\n';
}
المخرجات:
3 remainder 2
مثال 80: قوالب الدوال
دالة واحدة تعمل مع أنواع كثيرة.
#include <iostream>
#include <string>
template <typename T>
T largest(T a, T b) {
return (a > b) ? a : b;
}
int main() {
std::cout << largest(3, 8) << '\n';
std::cout << largest(2.5, 1.5) << '\n';
std::cout << largest(std::string("pear"), std::string("apple")) << '\n';
}
المخرجات:
8
2.5
pear
مثال 81: قوالب الأصناف: Stack
حاوية عامة تكتبها مرة واحدة.
#include <iostream>
#include <stdexcept>
#include <vector>
template <typename T>
class Stack {
public:
void push(const T &value) { items_.push_back(value); }
T pop() {
if (items_.empty()) throw std::out_of_range("stack is empty");
T top = items_.back();
items_.pop_back();
return top;
}
bool empty() const { return items_.empty(); }
private:
std::vector<T> items_;
};
int main() {
Stack<int> s;
s.push(1);
s.push(2);
std::cout << s.pop() << ' ' << s.pop() << '\n';
}
المخرجات:
2 1
مثال 82: التخصيص في القوالب
نسخة خاصة من القالب لنوع واحد.
#include <iostream>
#include <string>
template <typename T>
std::string describe(const T &) { return "other"; }
template <>
std::string describe<int>(const int &x) { return "int " + std::to_string(x); }
template <>
std::string describe<std::string>(const std::string &s) { return "string of " + std::to_string(s.size()); }
int main() {
std::cout << describe(4) << '\n';
std::cout << describe(std::string("hello")) << '\n';
std::cout << describe(1.5) << '\n';
}
المخرجات:
int 4
string of 5
other
مثال 83: القوالب ذات المعاملات المتغيرة
قوالب تقبل أي عدد من الوسائط.
#include <iostream>
template <typename... Args>
int count_args(Args... args) {
return sizeof...(args);
}
template <typename T, typename... Rest>
void print_all(const T &first, const Rest &...rest) {
std::cout << first;
((std::cout << ' ' << rest), ...);
std::cout << '\n';
}
int main() {
std::cout << count_args(1, 'a', 2.0) << '\n';
print_all("values:", 1, 2.5, 'x');
}
المخرجات:
3
values: 1 2.5 x
مثال 84: المفاهيم Concepts (C++20)
سمِّ المتطلبات التي يجب أن يستوفيها نوع القالب.
#include <concepts>
#include <iostream>
template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template <Numeric T>
T twice(T value) {
return value * 2;
}
int main() {
std::cout << twice(21) << '\n';
std::cout << twice(1.25) << '\n';
}
المخرجات:
42
2.5
مثال 85: constexpr وstatic_assert
احسب القيم وقت الترجمة وتحقق منها.
#include <iostream>
constexpr long long factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr long long kTenFactorial = factorial(10);
static_assert(kTenFactorial == 3628800, "factorial check failed at compile time");
int main() {
std::cout << kTenFactorial << '\n';
}
المخرجات:
3628800
الموضوع 15: C++ الحديثة: الاستثناءات والتزامن والتنسيق والاختبار
تغطي الموضوعات الأخيرة الأدوات التي تستخدمها شيفرة C++ الاحترافية يوميًا: معالجة الأخطاء، والخيوط، والتنسيق، وإدخال الملفات وإخراجها، وإطار اختبار صغير.
الاستثناءات والفضاءات ومعدّد enum class
يرسل throw الخطأ إلى أعلى مكدس الاستدعاء حتى يصل إلى catch مطابق. وتُشتق الاستثناءات المخصصة من std::runtime_error. وتتجنب فضاءات الأسماء التعارض في الأسماء، ويُبقي enum class المُعدّدات محصورة النطاق وقوية النوع.
الخيوط والأقفال والمستقبليات
يشغّل std::thread الدالة بالتوازي، وينتظر join انتهاءها. ويحمي std::mutex مع lock_guard البيانات المشتركة من سباقات البيانات. ويعيد std::async كائن future يعطي النتيجة عند استدعاء get.
النطاقات والتنسيق والملفات والاختبارات
تبني النطاقات (ranges) خطوط معالجة واضحة بـ filter وtransform. ويتحقق std::format من وسائطه مقابل نص التنسيق وقت الترجمة. ويقرأ fstream الملفات ويكتبها، ويعرض إطار الاختبار الصغير النجاح أو الفشل لكل فحص.
مثال 86: الاستثناءات: try وcatch وthrow
أبلغ عن الخطأ وعالجه في مستوى أعلى.
#include <iostream>
#include <stdexcept>
int safe_divide(int a, int b) {
if (b == 0) throw std::domain_error("division by zero");
return a / b;
}
int main() {
try {
std::cout << safe_divide(10, 2) << '\n';
std::cout << safe_divide(1, 0) << '\n';
} catch (const std::domain_error &e) {
std::cout << "caught: " << e.what() << '\n';
}
}
المخرجات:
5
caught: division by zero
مثال 87: تسلسل استثناءات مخصص
أنواع أخطاء خاصة بك ترث من std::runtime_error.
#include <iostream>
#include <stdexcept>
#include <string>
class AppError : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
class NotFound : public AppError {
public:
explicit NotFound(const std::string &what) : AppError("not found: " + what) {}
};
void load(const std::string &key) {
if (key != "config") throw NotFound(key);
}
int main() {
try {
load("cache");
} catch (const AppError &e) {
std::cout << e.what() << '\n';
}
}
المخرجات:
not found: cache
مثال 88: فضاءات الأسماء namespace
جمّع الأسماء لتجنب التعارض.
#include <iostream>
namespace geometry {
namespace units {
constexpr double kMeter = 1.0;
}
double area(double w, double h) { return w * h * units::kMeter; }
}
namespace app = geometry;
int main() {
std::cout << app::area(3.0, 2.0) << '\n';
std::cout << geometry::units::kMeter << '\n';
}
المخرجات:
6
1
مثال 89: enum class
تعداد قوي الأنواع.
#include <iostream>
enum class Level { Low = 1, Medium = 5, High = 10 };
std::string_view name(Level l) {
switch (l) {
case Level::Low: return "low";
case Level::Medium: return "medium";
case Level::High: return "high";
}
return "unknown";
}
int main() {
Level l = Level::Medium;
std::cout << name(l) << ' ' << static_cast<int>(l) << '\n';
}
المخرجات:
medium 5
مثال 90: مدد std::chrono
قيم زمنية لها وحدات يتحقق منها المترجم.
#include <chrono>
#include <iostream>
int main() {
using namespace std::chrono;
using namespace std::chrono_literals;
milliseconds timeout{1500};
seconds rounded = duration_cast<seconds>(timeout);
seconds wait = rounded + 2min;
std::cout << timeout.count() << " ms = " << rounded.count() << " s\n";
std::cout << "wait " << wait.count() << " s\n";
}
المخرجات:
1500 ms = 1 s
wait 121 s
مثال 91: std::thread
شغّل دالة في خيط آخر وانتظر انتهاءها.
#include <iostream>
#include <thread>
#include <vector>
int main() {
std::vector<int> results(4, 0);
std::vector<std::thread> workers;
for (int i = 0; i < 4; ++i) {
workers.emplace_back([&results, i] { results[i] = (i + 1) * (i + 1); });
}
for (auto &t : workers) t.join();
for (int r : results) std::cout << r << ' ';
std::cout << '\n';
}
المخرجات:
1 4 9 16
مثال 92: std::mutex وlock_guard
احمِ البيانات المشتركة من تعارض الوصول.
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>
int main() {
int counter = 0;
std::mutex m;
std::vector<std::thread> threads;
for (int t = 0; t < 4; ++t) {
threads.emplace_back([&] {
for (int i = 0; i < 1000; ++i) {
std::lock_guard<std::mutex> lock(m);
++counter;
}
});
}
for (auto &t : threads) t.join();
std::cout << "counter = " << counter << '\n';
}
المخرجات:
counter = 4000
مثال 93: std::async وstd::future
نفّذ العمل في الخلفية، واجمع النتيجة لاحقًا.
#include <future>
#include <iostream>
long long sum_range(long long from, long long to) {
long long total = 0;
for (long long i = from; i <= to; ++i) total += i;
return total;
}
int main() {
auto left = std::async(std::launch::async, sum_range, 1, 500);
auto right = std::async(std::launch::async, sum_range, 501, 1000);
std::cout << "total = " << left.get() + right.get() << '\n';
}
المخرجات:
total = 500500
مثال 94: النطاقات Ranges (C++20)
صفِّ سلسلة وحوّلها في خط معالجة واضح.
#include <iostream>
#include <ranges>
#include <vector>
int main() {
std::vector<int> nums{1, 2, 3, 4, 5, 6, 7, 8};
auto result = nums
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * n; });
for (int v : result) std::cout << v << ' ';
std::cout << '\n';
}
المخرجات:
4 16 36 64
مثال 95: std::format (C++20)
تنسيق آمن للأنواع بقالب واضح.
#include <format>
#include <iostream>
#include <string>
int main() {
std::string item = "Coffee";
double price = 45.5;
std::cout << std::format("{:<10}|{:>8.2f}", item, price) << '\n';
std::cout << std::format("{} items, {:03d} id", 3, 7) << '\n';
}
المخرجات:
Coffee | 45.50
3 items, 007 id
مثال 96: المُهيّئات المسماة (C++20)
اضبط أعضاء الهيكل بالأسماء.
#include <iostream>
#include <string>
struct Server {
std::string host;
int port = 80;
bool tls = false;
};
int main() {
Server s{.host = "example.com", .tls = true};
std::cout << s.host << ':' << s.port << (s.tls ? " tls" : " plain") << '\n';
}
المخرجات:
example.com:80 tls
مثال 97: إدخال وإخراج الملفات بـ fstream
اكتب ملفًا ثم اقرأه سطرًا بسطر.
#include <fstream>
#include <iostream>
#include <string>
int main() {
{
std::ofstream out("log.txt");
out << "first\n" << "second\n";
}
std::ifstream in("log.txt");
std::string line;
while (std::getline(in, line)) {
std::cout << "read: " << line << '\n';
}
in.close();
std::remove("log.txt");
}
المخرجات:
read: first
read: second
مثال 98: التحليل بـ stringstream
قسّم سطرًا نصيًا إلى قيم مُحددة الأنواع.
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::istringstream row("apple,12,0.5");
std::string name;
int qty = 0;
double price = 0.0;
std::getline(row, name, ',');
row >> qty;
row.ignore(1);
row >> price;
std::cout << name << " x" << qty << " @ " << price << '\n';
}
المخرجات:
apple x12 @ 0.5
مثال 99: إطار اختبار صغير
تحقق من النتائج وأبلغ عن النجاح أو الفشل.
#include <iostream>
#include <string>
int failures = 0;
void check(bool ok, const std::string &name) {
std::cout << (ok ? "PASS " : "FAIL ") << name << '\n';
if (!ok) ++failures;
}
int add(int a, int b) { return a + b; }
int main() {
check(add(2, 2) == 4, "add small numbers");
check(add(-1, 1) == 0, "add opposites");
check(add(3, 3) == 6, "add positives");
std::cout << failures << " failure(s)\n";
return failures == 0 ? 0 : 1;
}
المخرجات:
PASS add small numbers
PASS add opposites
PASS add positives
0 failure(s)
مثال 100: الخلاصة: تقرير المخزون
اجمع الأصناف والمؤشرات الذكية وoptional وformat معًا.
#include <algorithm>
#include <format>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
#include <vector>
struct Item {
std::string name;
int quantity;
double unit_price;
};
class Inventory {
public:
void add(Item item) { items_.push_back(std::move(item)); }
std::optional<Item> cheapest() const {
if (items_.empty()) return std::nullopt;
return *std::min_element(items_.begin(), items_.end(),
[](const Item &a, const Item &b) { return a.unit_price < b.unit_price; });
}
double total_value() const {
double sum = 0.0;
for (const auto &i : items_) sum += i.quantity * i.unit_price;
return sum;
}
const std::vector<Item> &items() const { return items_; }
private:
std::vector<Item> items_;
};
int main() {
auto inventory = std::make_unique<Inventory>();
inventory->add({"pen", 40, 5.0});
inventory->add({"notebook", 12, 45.5});
inventory->add({"stapler", 3, 120.0});
for (const auto &i : inventory->items()) {
std::cout << std::format("{:<10}{:>4} x {:>8.2f}\n", i.name, i.quantity, i.unit_price);
}
if (auto c = inventory->cheapest()) {
std::cout << "cheapest: " << c->name << '\n';
}
std::cout << std::format("total value: {:.2f}\n", inventory->total_value());
}
المخرجات:
pen 40 x 5.00
notebook 12 x 45.50
stapler 3 x 120.00
cheapest: pen
total value: 1106.00
مشروع تدريبي: تقرير مخزون صغير
اجمع الأفكار في برنامج واحد. اكتبه بنفسك أولًا، ثم قارنه بالأمثلة. كل خطوة تعادل عمل مثال إلى مثالين تقريبًا.
1. اكتب بنية Item بها الاسم والكمية وسعر الوحدة، واحفظ ثلاث سلع في مصفوفة.
2. أعد كتابة البرنامج نفسه بلغة C++ بصنف Inventory يحتوي std::vector من السلع، وله تابع total_value.
3. أضف تابع cheapest يعيد std::optional<Item>، ويكون فارغًا عندما لا توجد سلع في المخزون.
4. اطبع كل سطر باستخدام std::format، بحيث يُحاذى الاسم إلى عشرة أحرف والسعر إلى منزلتين عشريتين.
5. أضف إطار اختبار صغيرًا بثلاثة فحوص، واجعل البرنامج يعيد رمز خروج غير صفري عندما يفشل أي فحص.
الخطوات التالية
بعد ذلك، تعلّم نظام بناء مثل CMake، واكتب اختبارات الوحدة بإطار مثل Catch2 أو GoogleTest، واقرأ C++ Core Guidelines. تم التقاط المخرجات الموضحة لكل مثال من تشغيل حقيقي باستخدام GCC 16، فإذا اختلفت مخرجاتك فتحقق من إصدار المترجم وخياراته أولًا.
الخلاصة
تمنحك لغة C تحكمًا مباشرًا في الذاكرة، وتضيف C++ الأصناف والقوالب ومكتبة قياسية كبيرة مع الحفاظ على ذلك التحكم. عبر 100 مثال مترجم، غطت هذه المقالة المتغيرات والتحكم في التدفق والدوال والمصفوفات والمؤشرات والبنى والذاكرة الديناميكية والملفات والمعالج المسبق، والأصناف وRAII في C++، والمؤشرات الذكية، والحاويات والخوارزميات القياسية، والقوالب، والمفاهيم، والاستثناءات، والخيوط، والتنسيق. يعرض كل مثال الشيفرة الكاملة ومخرجاتها الحقيقية. أفضل طريقة للتدرب أن تغيّر قيمة، وتتوقع النتيجة، ثم تشغّل البرنامج للتحقق.