الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

phone book in C

مغلق
بدأه walidaly في 2 يوليو 2004 · 11 رد · 1,747 مشاهدة · في الأسئلة المجابة
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

phone book بلغة السى

يعمل أندر دوس

والأهم إنه أوبين سورس

Phone_Book_v1.zip

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <io.h>
#include <conio.h>

#define PHONE_MIN 7

struct strc{
	int id;
	char name[20];
	char address[25];
	char phone[20];
}info;


void getentry();
void putentry(int);
void delentry(int);
int countentry();
int readentry(int,int);
void searchentry();


int main(void)
{
	register char ch;
	register int sz;
	int id;
	char intr[]="############################################################\n"
  "## Insert Entry (I) || call by ID (C) || See All (A)       #\n"
  "## Edit Entry (T) || Delete (D)  || search (S) || Exit (X) #\n"
  "############################################################";

	puts(intr);
	do{
  ch=toupper(ch=getch());
  fflush(stdin);

  switch(ch)
  {
  case 'I':
 	 printf("Inserting Entry Number: %d\n", (countentry()+1));
 	 getentry();
 	 putentry(0);
 	 puts("Done");
 	 puts(intr);
 	 break;
  case 'C':
 	 if(!(sz=countentry()))
 	 {
    puts("Couldn't find the entry file");
    puts(intr);
    break;
 	 }
 	 printf("You have %d Entries\n", countentry());
 	 puts("Enter the ID of your entry");
 	 do{
    scanf("%d", &id);
    fflush(stdin);
    if(id>sz) printf("You have only %d entries\ntry again\n", sz);
 	 }while(id>sz);
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 printf("|ID |  %-20s|  %-25s|  %-20s|\n", "   NAME","   ADDRESS","   PHONE #");
 	 readentry(id,1);
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 puts("Done");
 	 puts(intr);
 	 break;
  case 'A':
 	 if(!(sz=countentry()))
 	 {
    puts("Couldn't find the entry file");
    puts(intr);
    break;
 	 }
 	 puts("Displaying All Entries");
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 printf("|ID |  %-20s|  %-25s|  %-20s|\n", "   NAME","   ADDRESS","   PHONE #");
 	 for(id=1; id<=sz; id++)
 	 {
    readentry(id,1);
    if(id==20) 
    {
   	 puts("+---+----------------------+---------------------------+----------------------+");
   	 puts("Press any key to continue..");
   	 getch();
    }
 	 }
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 puts("Done");
 	 puts(intr);
 	 break;
  case 'T':
 	 if(!(sz=countentry()))
 	 {
    puts("Couldn't find the entry file");
    puts(intr);
    break;
 	 }
 	 puts("Editing Entry");
 	 puts("Enter ID of your entry");
 	 do{
    scanf("%d", &id);
    fflush(stdin);
    if(id>sz) printf("You have only %d entries\ntry again\n", sz);
 	 }while(id>sz);
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 printf("|ID |  %-20s|  %-25s|  %-20s|\n", "   NAME","   ADDRESS","   PHONE #");
 	 readentry(id,1);
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 getentry();
 	 putentry(id);
 	 puts("Done");
 	 puts(intr);
 	 break;
  case 'D':
 	 if(!(sz=countentry()))
 	 {
    puts("Couldn't find the entry file");
    puts(intr);
    break;
 	 }
 	 puts("Deleting Entry");
 	 puts("Enter ID of your entry");
 	 do{
    scanf("%d", &id);
    fflush(stdin);
    if(id>sz) printf("You have only %d entries\ntry again\n", sz);
 	 }while(id>sz);
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 printf("|ID |  %-20s|  %-25s|  %-20s|\n", "   NAME","   ADDRESS","   PHONE #");
 	 readentry(id,1);
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 delentry(id);
 	 puts("Done");
 	 puts(intr);
 	 break;
  case 'S':
 	 if(!(sz=countentry()))
 	 {
    puts("Couldn't find the entry file");
    puts(intr);
    break;
 	 }
 	 searchentry();
 	 puts(intr);
 	 break;
  case 'X':
 	 printf("%c\n",ch);
 	 printf("Ending...\n\n");
  default:
  ;
  }
	}while(ch!='X');
	return 0;
}


void getentry()
{
	register int i;

	puts("Full Name:");
	fgets(info.name, 20, stdin);
	fflush(stdin);
	if(info.name[0]=='\n') info.name[0]='\0';
	else strtok(info.name, "\n");

	puts("address:");
	fgets(info.address, 25, stdin);
	fflush(stdin);
	if(info.address[0]=='\n') info.address[0]='\0';
	else strtok(info.address, "\n");


	do{
  puts("phone #:");
  fgets(info.phone, 20, stdin);
  fflush(stdin);
  if(info.phone[0]=='\n') puts("You should enter a phone #");
  else {
 	 strtok(info.phone,"\n");
 	 for(i=0;info.phone;i++)
    if(!isdigit(info.phone))
    {
   	 puts("You can only enter digits");
   	 info.phone[0]='\n';
   	 break;
    };
 	 if(i<PHONE_MIN && info.phone[0]!='\n')
 	 {
    puts("The Phone # should contain more than 6 digits");
    info.phone[0]='\n';
 	 }
  }
	}while(info.phone[0]=='\n');

	if(info.id=countentry()) info.id++;
	else info.id=1;

}

void putentry(register int id)
{
	FILE* fp;
	register int sz;

	if(!(sz=countentry()))
	{
  fp=fopen("list.phn", "a");
  fclose(fp);
  id=1;
	}

	if(!(fp=fopen("list.phn", "r+b")))
  puts("Couldn't find the entry file");
	else
	{
  if(id)
  {
 	 info.id=id;
 	 fseek(fp, --id*sizeof(struct strc), SEEK_SET);
  }else fseek(fp, sz*sizeof(struct strc), SEEK_SET);
  fwrite(&info, sizeof(struct strc), 1, fp);
  fclose(fp);
	}
}



int countentry()
{
	FILE* fp;
	register int sz;

	if(!(fp=fopen("list.phn", "rb")))
  return 0;
	else{
  sz=filelength(fileno(fp));
  sz=sz/sizeof(struct strc);
  fclose(fp);
  return sz;
	}
}


int readentry(register int id,register int display)
{
	FILE* fp;

	if(!(fp=fopen("list.phn", "rb")))
  return 0;
	else{
  if(id>1) fseek(fp, --id*sizeof(struct strc), SEEK_SET);

  fread(&info, sizeof(struct strc), 1, fp);
  if(display)
  {
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 printf("|%-3d|  %-20s|  %-25s|  %-20s|\n", info.id,info.name,info.address,info.phone);
  }
  fclose(fp);
  return id;
	}
}

void delentry(register int id)
{
	FILE* tmpfp;
	register int sz=countentry(), i, j;
	char in;

	puts("are you sure you want to delete?");
	in=toupper(in=getchar());
	fflush(stdin);
	if(in=='N') 
	{
  puts("Entry wasn't removed");
  return;
	}
	puts("YES");

	tmpfp=tmpfile();
	for(i=1,j=1; i<=sz;i++)
	{
  readentry(i,0);
  if(i!=id)
  {
 	 info.id=j;
 	 fwrite(&info, sizeof(struct strc), 1, tmpfp);
 	 j++;
  }
	}
	rewind(tmpfp);
	remove("list.phn");

	for(id=1;id<j;id++)
	{
  fread(&info, sizeof(struct strc), 1, tmpfp);
  putentry(id);
	}
	puts("Entry deleted successfully");
	_rmtmp();
}

void searchentry()
{
	register char ch;
	char key[25], *result,  search_str[100];
	register int i, id, sz=countentry();

	puts("Do you like to search by NAME:(N),or ADDRESS:(A),or PHONE#(P)?");
	do{
  ch=toupper(ch=getchar());
  fflush(stdin);
	}while(ch!='N' && ch!='A' && ch!='P');

	puts("Enter your search:");
	strtok(fgets(search_str, 100, stdin),"\n");
	fflush(stdin);



	for(id=0, i=0; id<=sz; id++)
	{
  readentry(id,0);
  switch (ch)
  {
  case 'N': strcpy(key,info.name); break;
  case 'A': strcpy(key,info.address); break;
  case 'P': strcpy(key,info.phone); break;
  default :;
  }
  result=strstr(strlwr(key), strlwr(search_str));

  if(result)
  {
 	 if(i==0)
 	 {
    printf("The Entries containing \"%s\" are :\n", search_str);
    puts("+---+----------------------+---------------------------+----------------------+");
    printf("|ID |  %-20s|  %-25s|  %-20s|\n", "   NAME","   ADDRESS","   PHONE #");
 	 }
 	 readentry(id,1);
 	 i++;
  }
	}


	if(i)
	{
  puts("+---+----------------------+---------------------------+----------------------+");
  printf("Found %d Result\n", i);
	}else
  puts("No Results found");

}
#2

ماشاء الله تبارك الله !!

موضوع ممتاز ومفيد لمن يسأل مثل هذه الأسئلة ,,

أعتقد أنك زودتنا بمناعة وحصانة ذاتية للاجابة على أسئلة كهذه مستقبلا :)

تمت اضافة الموضوع للمواضيع المهمة ,, المثبت في القسم ,,

بارك الله فيك ,,

banner_60_468.gif

NOTHING IS IMPOSSIBLE

#3

شكرا اخي على هذا البرنامج المتميز..

حاولت ان اشغل البرنامج لكن طلع لي Error

فإذا ممكن تشوف ويش المشكله

استخدمت برنامج ++Borland C

#4

ماهو ال Error الذي طلع ؟

banner_60_468.gif

NOTHING IS IMPOSSIBLE

#5

شكرا أستاذ HGB لهذا التشجيع

وبما أنك تعتبره من المواضيع المهمه سأضيف تعليقات على الكود ليسهل فهمه إن شاء الله

الأستاذ albasri2010

أعتذر عن عدم التوضيح, فالبرنامج مكتوب فى Microsoft Visual Studio وإن كان يتلائم أيضا مع borland C++BuilderX وأعتقد gcc أيضا

#6

لم أستطع تغيير المشاركة السابقة ولكن غيرت الملف المدرج وهو يحتوى على التعليقات

وهذا الكود مرة أخرى

/**************************************
//     
// Name: Phonebook_v1
// Description:Phonebook is a commandline program that stores your 
// phone numbers along with person's name and address
// Compatibility: C, Microsoft Visual C++
//
// By: Kurtubba@gawab.com
// Website : Fly.to/kurtubba
// Assumes:it's just a Homework program 
//
// Redistribution and use in source and binary forms, with or without
// modification, is permitted provided that you contact kurtubba@gawab.com   
//
//**************************************/
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <io.h>
#include <conio.h>

#define PHONE_MIN 7

// the info structure
struct strc{
	int id;
	char name[20];
	char address[25];
	char phone[20];
}info;


void getentry();
void putentry(int);
void delentry(int);
int countentry();
int readentry(int,int);
void searchentry();


int main(void)
{
	register char ch;
	register int sz;
	int id;
	char intr[]="############################################################\n"
  "## Insert Entry (I) || call by ID (C) || See All (A)       #\n"
  "## Edit Entry (T) || Delete (D)  || search (S) || Exit (X) #\n"
  "############################################################";

	//Inserting the Main Menu
	puts(intr);
	do{
  ch=toupper(ch=getch());
  fflush(stdin);

  switch(ch)
  {
  case 'I':
 	 //Inserting a new Entry
 	 printf("Inserting Entry Number: %d\n", (countentry()+1));
 	 //Get the new Entry from the user
 	 getentry();
 	 //Insert that entry in the file
 	 putentry(0);
 	 puts("Done");
 	 puts(intr);
 	 break;
  case 'C':
 	 //Calling an entry by its ID
 	 //checking if we have the file
 	 if(!(sz=countentry()))
 	 {
    puts("Couldn't find the entry file");
    puts(intr);
    break;
 	 }
 	 //getting the entry ID which should be not greater than the entries total
 	 printf("You have %d Entries\n", countentry());
 	 puts("Enter the ID of your entry");
 	 do{
    scanf("%d", &id);
    fflush(stdin);
    if(id>sz) printf("You have only %d entries\ntry again\n", sz);
 	 }while(id>sz);
 	 //showing the chosen entry
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 printf("|ID |  %-20s|  %-25s|  %-20s|\n", "   NAME","   ADDRESS","   PHONE #");
 	 readentry(id,1);
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 puts("Done");
 	 puts(intr);
 	 break;
  case 'A':
 	 //showing all the entries
 	 //checking if we have the file
 	 if(!(sz=countentry()))
 	 {
    puts("Couldn't find the entry file");
    puts(intr);
    break;
 	 }
 	 //showing all entries
 	 puts("Displaying All Entries");
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 printf("|ID |  %-20s|  %-25s|  %-20s|\n", "   NAME","   ADDRESS","   PHONE #");
 	 for(id=1; id<=sz; id++)
 	 {
    readentry(id,1);
    //pause every 20 entries
    if(id==20) 
    {
   	 puts("+---+----------------------+---------------------------+----------------------+");
   	 puts("Press any key to continue..");
   	 getch();
    }
 	 }
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 puts("Done");
 	 puts(intr);
 	 break;
  case 'T':
 	 //editing an entry
 	 //checking if we have the file
 	 if(!(sz=countentry()))
 	 {
    puts("Couldn't find the entry file");
    puts(intr);
    break;
 	 }
 	 puts("Editing Entry");
 	 puts("Enter ID of your entry");
 	 //getting the entry ID which should be not greater than the entries total
 	 do{
    scanf("%d", &id);
    fflush(stdin);
    if(id>sz) printf("You have only %d entries\ntry again\n", sz);
 	 }while(id>sz);
 	 //diplaying the entry
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 printf("|ID |  %-20s|  %-25s|  %-20s|\n", "   NAME","   ADDRESS","   PHONE #");
 	 readentry(id,1);
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 //Get the new Entry from the user
 	 getentry();
 	 //Insert that entry in the file in the same place
 	 putentry(id);
 	 puts("Done");
 	 puts(intr);
 	 break;
  case 'D':
 	 //Deleting an entry
 	 //checking if we have the file
 	 if(!(sz=countentry()))
 	 {
    puts("Couldn't find the entry file");
    puts(intr);
    break;
 	 }
 	 puts("Deleting Entry");
 	 puts("Enter ID of your entry");
 	 //getting the entry ID which should be not greater than the entries total
 	 do{
    scanf("%d", &id);
    fflush(stdin);
    if(id>sz) printf("You have only %d entries\ntry again\n", sz);
 	 }while(id>sz);
 	 //diplaying the entry
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 printf("|ID |  %-20s|  %-25s|  %-20s|\n", "   NAME","   ADDRESS","   PHONE #");
 	 readentry(id,1);
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 //deleting
 	 delentry(id);
 	 puts("Done");
 	 puts(intr);
 	 break;
  case 'S':
 	 //Searching for an entry
 	 //checking if we have the file
 	 if(!(sz=countentry()))
 	 {
    puts("Couldn't find the entry file");
    puts(intr);
    break;
 	 }
 	 //searching
 	 searchentry();
 	 puts(intr);
 	 break;
  case 'X':
 	 //Exit
      printf("%c\n",ch);
 	 printf("Ending...\n\n");
  default:
  ;
  }
	}while(ch!='X');
	return 0;
}


//getting the user's input
void getentry()
{
	register int i;

	//using fgets to get the user's input
	//so we avoud the array overflow
	puts("Full Name:");
	fgets(info.name, 20, stdin);
	//must flush the input stream before useing fgets again
	fflush(stdin);
	//checking if the user passed this input
	if(info.name[0]=='\n') info.name[0]='\0';
	//removing the newline from the string
	else strtok(info.name, "\n");

	puts("address:");
	fgets(info.address, 25, stdin);
	fflush(stdin);
	if(info.address[0]=='\n') info.address[0]='\0';
	else strtok(info.address, "\n");


	do{
  puts("phone #:");
  fgets(info.phone, 20, stdin);
  fflush(stdin);
  //user must enter some number
  if(info.phone[0]=='\n') puts("You should enter a phone #");
  else {
 	 strtok(info.phone,"\n");
 	 //the number shouldn't contain characters
 	 for(i=0;info.phone;i++)
    if(!isdigit(info.phone))
    {
   	 puts("You can only enter digits");
   	 info.phone[0]='\n';
   	 break;
    };
 	 //the number is less than 7 numbers
 	 if(i<PHONE_MIN && info.phone[0]!='\n')
 	 {
    puts("The Phone # should contain more than 6 digits");
    info.phone[0]='\n';
 	 }
  }
	}while(info.phone[0]=='\n');

	//getting the entry id
	if(info.id=countentry()) info.id++;
	else info.id=1;

}

//inserting the entry to the file
void putentry(register int id)
{
	FILE* fp;
	register int sz;

	//checking if the file exists
	if(!(sz=countentry()))
	{
  //creat one and close it
  fp=fopen("list.phn", "a");
  fclose(fp);
  id=1;
	}

	//open it again for binary reading and writing
	if(!(fp=fopen("list.phn", "r+b")))
  puts("Couldn't find the entry file");
	else
	{
  if(id)
  {
 	 //if we have and id so we're editing an entry
 	 //so we need to seek to the same place
 	 info.id=id;
 	 fseek(fp, --id*sizeof(struct strc), SEEK_SET);
  }else fseek(fp, sz*sizeof(struct strc), SEEK_SET);   //we don't have id seek to end
  //writing the structure
  fwrite(&info, sizeof(struct strc), 1, fp);
  fclose(fp);
	}
}



//that's for checking the if the file exists and return the number of entires
int countentry()
{
	FILE* fp;
	register int sz;

	//opening binary readonly
	if(!(fp=fopen("list.phn", "rb")))
  return 0;
	else{
  //dividing the file size over the size of the structure
  //to get the total
  sz=filelength(fileno(fp));
  sz=sz/sizeof(struct strc);
  fclose(fp);
  return sz;
	}
}


//read the entry by its id and displaying result on demand
int readentry(register int id,register int display)
{
	FILE* fp;

	//opening binary readonly
	if(!(fp=fopen("list.phn", "rb")))
  return 0;
	else{
  //if we have an id seek to it
  if(id>1) fseek(fp, --id*sizeof(struct strc), SEEK_SET);

  fread(&info, sizeof(struct strc), 1, fp);
  //if we need to print the result
  if(display)
  {
 	 puts("+---+----------------------+---------------------------+----------------------+");
 	 printf("|%-3d|  %-20s|  %-25s|  %-20s|\n", info.id,info.name,info.address,info.phone);
  }
  fclose(fp);
  return id;
	}
}

//deleting the file
void delentry(register int id)
{
	FILE* tmpfp;
	register int sz=countentry(), i, j;
	char in;

	//make sure user wants to delete
	puts("are you sure you want to delete?");
	in=toupper(in=getchar());
	fflush(stdin);
	if(in=='N') 
	{
  puts("Entry wasn't removed");
  return;
	}
	puts("YES");

	//creating a temp file
	tmpfp=tmpfile();
	//coping all entries from the phonebook file
	//to the temp file except for the deleted one
	for(i=1,j=1; i<=sz;i++)
	{
  readentry(i,0);
  //passing the deleted entry and inserting a new id=j
  if(i!=id)
  {
 	 info.id=j;
 	 fwrite(&info, sizeof(struct strc), 1, tmpfp);
 	 j++;
  }
	}
	//seeking to the beginning of the file
	rewind(tmpfp);
	//deleting the old phonebook file
	remove("list.phn");

	//coping the entries from the temp file to the new phonebook file
	for(id=1;id<j;id++)
	{
  fread(&info, sizeof(struct strc), 1, tmpfp);
  putentry(id);
	}
	puts("Entry deleted successfully");
	_rmtmp();
}

//searching the phonebook
void searchentry()
{
	register char ch;
	char key[25], *result,  search_str[100];
	register int i, id, sz=countentry();

	//Which part user likes to search
	puts("Do you like to search by NAME:(N),or ADDRESS:(A),or PHONE#(P)?");
	do{
  ch=toupper(ch=getchar());
  fflush(stdin);
	}while(ch!='N' && ch!='A' && ch!='P');

	//getting the searvh string
	puts("Enter your search:");
	strtok(fgets(search_str, 100, stdin),"\n");
	fflush(stdin);



	//starting our search 
	for(id=1, i=1; id<=sz; id++)
	{
  //loading current entry inside our structure
  readentry(id,0);
  //what are we going to search
  switch (ch)
  {
  case 'N': strcpy(key,info.name); break;
  case 'A': strcpy(key,info.address); break;
  case 'P': strcpy(key,info.phone); break;
  default :;
  }
  //checking the current string 
  result=strstr(strlwr(key), strlwr(search_str));

  if(result)
  {
 	 //we found an Entry & printing it
 	 if(i==1)
 	 {
    printf("The Entries containing \"%s\" are :\n", search_str);
    puts("+---+----------------------+---------------------------+----------------------+");
    printf("|ID |  %-20s|  %-25s|  %-20s|\n", "   NAME","   ADDRESS","   PHONE #");
    //pause every 20 result

 	 }
 	 readentry(id,1);
    if(i==20) 
    {
   	 puts("+---+----------------------+---------------------------+----------------------+");
   	 puts("Press any key to continue..");
   	 getch();
    }
 	 i++;
  }
	}


	if(--i)
	{
  //inserting footer for ending our search
  puts("+---+----------------------+---------------------------+----------------------+");
  printf("Found %d Result\n", i);
	}else
  //didn't find any
  puts("No Results found");

}
#7

الله يبارك فيك ,, ويعافيك ,, جعلها الله في ميزان الحسنات ,,

أتحفنا بالمزيد فنحن لانشبع !! :)

بالتوفيق ,,

banner_60_468.gif

NOTHING IS IMPOSSIBLE

#8

حقيقه تحفه وعمل رائع

وجالس اعمل برنامج للهاتف بحيث استفيد من خوارزميات في مخيلتي الى الان

لكم مني خالص التقدير

*********************

دروس الاكسس

قاعدة بيانات بالسي++

قاعدة بيانات اخرى بالسي++

الفريق العربي للبرمجه

*********************

كان الله في عون العبد مادام العبد في عون اخيه

#9

مشكورين على هاذا الجهد الرائع

بس انا كمبتديء غير فاهم اي شيء في الكود حيث انني خلصت كورس كامل بس كله sequential وليس oop كما في كود دليل الهاتف

الرجاء اعطاءنا كود يكون سهل ولا يحتوي على oop وشكرا

ابو رواد.. فلسطين

ودمتم

#10

الكود غير مبنى على oop لأنه مكتوب بالسى وهيه structured language ربما وجود الstruct خيل إليك أنها class لأنها تأخذ بعض صفاتها

حاول مع الكود مرة أخرى ولن تجد صعوبة فيه إن شاء الله

#11

شكرا اخوي ولكن عندي سؤال وأتمنى تجاوبني عليه هل البيانات اللي يدخلها المستخدم تبقى في القرص ولا تنمسح بعد إغلاق البرنامج وأتمنى منك شرح كيف أخلي البرنامج يخزن المعلومات على القرص

وشكرا

أخوك The Cpp

abofahad5515@hotmail.com

#12

حاولت تحميل البرنامج من الرابط و لكن الرابط لا يعمل

Sr. Software Development Engineer
Hulu, LLC
My Blogs

هذا الموضوع مغلق.

مواضيع مشابهة