Զրույցներ C ծրագրավորման լեզվի մասին

Զրույց հինգերորդ

Մինչ այժմ, երբ օրինակներում փոփոխականներ կամ ֆունկցիաներ էի հայտարարում, օգտագործում էի C լեզվի պարզ (սկալյար) տիպերը՝ int, double, float և այլն։ Բայց, առավել հաճախ կարիք է լինում ծրագրերում օգտագործել բաղադրյալ օբյեկտներ, այնպիսիք, որոնց կառուցվածքը սահմանում է ծրագրավորողը։ Օրինակ, հենց նույն այդ դեկարտյան կետերի հետ աշխատելիս ավելի հարմար կլինի ունենալ մի օբյեկտ, որն ունի x և y հատկությունները (դաշտերը), քան աշխատել երկու double թվերի կամ նրանց ցուցիչների հետ։

C լեզվի struct ծառայողական բառը ծրագրավորողին տալիս է այդ հնարավորությունը։ Օրինակ․

struct { double x, y; } p0;

Այս հրամանով p0 փոփոխականը հայտարարեցի որպես x և y դաշտերն ունեցող բաղադրյալ օբյեկտ՝ ստրուկտուրա։

Ստրուկտուրայի դաշտերին դիմում են . գործողության միջոցով։ Օրինակ, p0 օբյեկտը տպելու համար պետք է գրել․

printf( "%lf, %lf", p0.x, p0.y );

Ստրուկտուրային օբյեկտներ սահմանելիս struct ծառայողական բառից հետո կարելի է գրել ստրուկտուրայի անուն, որն էլ հետագայում կարելի է օգտագործել նույն տիպի այլ օբյեկտներ սահմանելիս։ Օրինակ, p1 դեկարտյան կետի սահմանումը կարող է ունենալ այսպիսի սահմանում․

struct point { double x, y; } p1;

Այս տիպի արտահայտությունը թույլ է տալիս ծրագում ամեն անգամ struct { double x, y; } գրելու փոխարեն պարզապես գրել struct point։

Հիմա հետ վարադառնամ իմ հին խնդիրն, երբ գրեցի polar ֆունկցիան, որը տրված կետի դեկարտյան կոորդինատներից հաշվում է դրա բևեռային կոորդինատները։ Նախ հայտարարեմ cartesian_point ստրուկտուրան՝ իր x, y դաշտերով, և polar_point ստրուկտուրան՝ rho, phi դաշտերով։

/* դեկարտյան կետը */
struct cartesian_point {
  double x; /* աբսցիս */
  double y; /* օրդինատ */
};

/* բևեռային կետը */
struct polar_point {
  double rho; /* շառավիղ */
  double phi; /* ազիմուտ */
};

ՈՒնենալով cartesian_point և polar_point ստրուկտուրաների հայտարարությունները, ես կարող եմ սահմանել to_polar ֆունկցիան, որը ստանում է դեկարտյան կետ և վերադարձնում է համարժեք բևեռային կետը։

struct polar_point to_polar( struct cartesian_point c )
{
  struct polar_point res;
  res.rho = sqrt( c.x * c.x + c.y * c.y );
  res.phi = atan2( c.y, c.x );
  return res;
}

Ճիշտ նույն կերպ կարող եմ սահմանել to_cartesian ֆունկցիան, որը ստանում է բևեռային կետ և վերադարձնում է դրա դեկարտյան ներկայացումը։

struct cartesian_point to_cartesian( struct polar_point p )
{
  struct cartesian_point res;
  res.x = p.rho * cos( p.phi );
  res.y = p.rho * sin( p.phi );
  return res;
}

Հիմա գրեմ main ֆունկցիան, որտեղ ցուցադրված են վերը հայտարարված ստրուկտուրաների ու սահմանված ֆունկցիաների կիրառությունները։

int main()
{
  struct cartesian_point c0;
  puts( "Ներածիր դեկարտյան կոորդինատները x,y․ " );
  scanf( "(%lf, %lf)", &c0.x, &c0.y );

  struct polar_point p0 = to_polar( c0 );
  puts( "Բևեռային կոորդնատներն են․ " );
  printf( "ρ = %lf, φ = %lf\n", p0.rho, p0.phi );

  struct cartesian_point c1 = to_cartesian( p0 );
  puts( "Դեկարտյան կոորդնատներն են․ " );
  printf( "x = %lf, y = %lf\n", c1.x, c1.y );

  return 0;
}

Ակնհայտ է, որ cartesian_point և polar_point ստրուկտուրաները, ըստ էության, նույնն են։ Երկուսն էլ մոդելավորում են կետի գաղափարը, բայց տարբերվում են իրենց դաշտերի անուններով։ Կարելի է երկուսի փոխարեն սահմանել մեկ point ստրուկտուրա՝ դաշտերին տալով first (առաջին) և second (երկրորդ) պայմանական անունները, և այդ միակ ստրուկտուրան օգտագործել և՛ դեկարտյան, և՛ բևեռային կետերը ներկայացնելու համար։ Օրինակ.

struct point {
  double first;
  double second;
};

Դեկարտյան կետերը բևեռային կետերից տարբերելու համար կարող եմ typedef հրամանով սահմանել struct point տիպի երկու հոմանիշ անուններ։

typedef struct point cartesian;
typedef struct point polar;

typedef հրամանը ծրագրում սահմանում է արդեն գոյություն ունեցող տիպի համարժեք, հոմանիշ անուն։ Օրինակ, կարող եմ իմ ծրագրում real անունը սահմանել որպես long double տիպի անուն․

typedef long double real;

Առավել հաճախ typedef հայտարարությունն օգտագործվում է հենց ստրուկտուրաների համար կարճ անուններ հայտարարելու նպատակով։ Մեկ typedef արտահայտությամբ կարելի է մի քանի համարժեք անուններ։ Օրինակ.

typedef struct point {
  double first;
  double second;
} cartesian, polar;

Ավելին, typedef հայտարարություններ անելիս, կարելի է բաց թողնել ստրուկտուրայի անունը (եթե այն այլևս չի օգտագործվելու)։ Այսպես.

typedef struct {
  double first;
  double second;
} cartesian, polar;

Նման հայտարարությունից հետո վերը սահմանված to_polar ֆունկցիան կունենա հետևյալ տեսքը.

polar to_polar( cartesian c )
{
  polar res;
  res.first = sqrt( c.first * c.first + c.second * c.second );
  res.second = atan2( c.second, c.first );
  return res;
}

Ստրուկտուրայի նոր նմուշ ստեղծելիս դրա դաշտերին տրվող արժեքները կարելի է թվարկել { և } փակագծերի մեջ առնված ցուցակում։ Օրինակ, to_polar ֆունկցիան կարելի է սահմանել նաև հետևյալ կերպ.

polar to_polar( cartesian c )
{
  polar res = {
    sqrt( c.first * c.first + c.second * c.second ),
    atan2( c.second, c.first )
  };
  return res;
}

Եթե ինչ-որ պատճառով պետք է խախտել արժեքների թվարկման հաջորդականությունը, ապա պետք է նշել, թե հերթական արժեքը որ դաշտին է վերագրվում՝ անունից առաջ . նիշը գրելով։ Օրինակ, այսպես.

struct cartesian c1 = { .y = 1.2, .x = 3.4 };

Բայց մի անհարմար բան էլ կա. ստիպված եմ և՛ cartesian, և՛ polar օբյեկտների դաշտերին դիմելու համար օգտագործել նրանց first և second անունները, որի պատճառով գրված կոդը նվազ հասկանալի է դառնում։ Ես այդ խնդիրը կարող եմ լուծել մի քանի պարզ մակրոսներ սահմանելով.

#define X(p) p.first
#define Y(p) p.second
#define RHO(p) p.first
#define PHI(p) p.second

Կոմպիլյացիայից առաջ C լեզվով գրված ծրագրերը մշակվում են նախապրոցեսորի կողմից, և մակրոսները հենց նախապրոցեսորի սահմանումներ են։ Մակրոսի սահմանումն ունի հետևյալ տեսքը․

#define ⟨name⟩[(⟨parameters⟩)] ⟨body⟩

Ամեն անգամ, երբ նախապրոցեսորը ծրագրի տեքստում հանդիպում է ⟨name⟩ անունը, դա փոխարինվում է մակրոսի ⟨body⟩ մարմնով՝ իհարկե, համապատասխան տեղերում տեղադրելով մակրոսի պարամետրերի արժեքները։ C լեզվում ընդունված է մակրոսների անունները գրել մեծատառերով։

Դեկարտյան և բևեռային կոորդինատների դաշտերը ընտրող մակրոսները սահմանելուց հետո, օրինակ, to_cartesian ֆունկցիան կարող եմ սահմանել հետևյալ կերպ.

cartesian to_cartesian( polar p )
{
  cartesian res;
  X(res) = RHO(p) * cos( PHI(p) );
  Y(res) = RHO(p) * sin( PHI(p) );
  return res;
}

Երբ C լեզվի նախապրոցեսորը կմշակի այս տեքստը, բոլոր մակրոսները կփոխարինվեն իրենց սահմանումներով և կոմպիլյացիան կշարունակվի այնպես, կարծես թե մակրոսներ չեն էլ եղել։

Ստրուկտուրայի չափը՝ հիշողություն մեջ նրա նմուշի զբաղեցրած բայթերի քանակը, որոշվում է որպես նրա դաշտերի չափերի գումար։ Քանի որ point ստրուկտուրան ունի երկու double տիպի դաշտեր, ապա ճիշտ է sizeof(struct point) >= 2 * sizeof(double) հավասարությունը։