Начало Решаване на проблеми



страница10/19
Дата20.01.2017
Размер4.54 Mb.
#13105
ТипГлава
1   ...   6   7   8   9   10   11   12   13   ...   19

#include


const SZ = 12;

const ODD = 1;


main()

{

IntList i1; // empty lilst


if ( // test that empty list is handled

i1.isEmpty() &&

i1.length() == 0 &&

i1.remove() == 0

)

cout << "Empty List: ok.\n";


// every odd item is set to value of ODD

for ( int i = 0; i < SZ; ++i )

i1.append( i%2 == 0 ? i : ODD );

i1.display();


// illustrate remove( someValue );

cout << i1.remove( ODD ) << " items of value "



<< ODD << " removed: ";

i1.display();


// illustrate remove()

int len = i1.length();

if ( i1.remove() == len )

cout << "All " << len << " items removed: ";

i1.display();

return 0;

}
Когато компилираме и изпълним тази програма ще получим

следните резултати:


Empty List: ok.

( 0 1 2 1 4 1 6 1 8 1 10 1 )

6 items of value 1 removed: ( 0 2 4 6 8 10 )

All 6 items removed: ( empty )


Упражнение 4-4. Реализирайте IntList::removeFirst().

Нека стойността, която връща тази член-функция е стойността на

члена val. Уверете се, че обработвате и случая на празен

списък.


ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

149.
Упражнение 4-5. Реализирайте IntList::removeLast().

Нека отново, стойността, който връща тази член-функция да бъде

стойността на члена val. Уверете се, че обработвате и случая

на празен списък.


Една много разпространена операция над списъци е

обединение. Самата операция е проста, но често се греши при

реализацията й. Написаното по-долу вероятно ще причини на

потребителя известни неприятности:


#include "IntList.h"
void IntLIst::concat( IntList& i1 )

{

( atEnd() )->next = i1.list;



}
Проблемът се състои в това, че два IntList обекта ще сочат

една и съща последователност от елементни. Много е вероятно

двата класови обекта да трият елементи по различно време в

програмата. Ако вторият обект се опитва да получи достъп до

елементи, които вече са изтрити, ще се появят висящи

псевдоними (указатели), които вероятно ще причинят грешки по

време на изпълнение на програмата. Ако това не се случи,

съществува възможност вторият обект по-късно да се опита да

изтрие елемент, чиято памет вече да се окаже отделена за

някаква съвсем различна цел. Отново е съвсем вероятно

програмата да бъде прекъсната по време на изпълнение. Едно

общо решение е да се осигури брояч-псевдоним, за всеки елемент

на списъка. Всеки път, когато се отстранява елемент,

броячът-псевдоним се намалява с 1. Когато той стане 0,

елементът може да бъде изтрит фактически. Една алтернативна

стратегия е да се копира всеки елемент, който участвува в

обединението. Тази версия на concat() изглежда така:
void IntList::concat( IntList& i1)

{ // append i1.list to invoking list object

IntItem *pt = i1.list;

while ( pt )

{

append( pt->val );



pt = pr->next;

}

}


Една интересна операция над списъци е обръщане. В този

случай списъкът се обръща като последният елемент застава в

началото и обратно. Въпреки, че реализацията на операцията е

кратка, указателите се обработват по интересен начин и е

лесно да сгрешите ако сега започвате да програмирате със

списъци. Ето и реализацията:

ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

150.
void IntList::reverse()

{

IntItem *pt, *prv, *tmp;



prv = 0; pt = list; list = atEnd();
while ( pt != list )

{

tmp = pt->next;



pt->next = prv;

prv = pt;

pt = tmp;

}

list->next = prv;



}
Следната малка програма илюстрира concat() и

reverse():


#include "IntList.h"

const SZ = 8;


main()

{

IntLIst i1, i12;



for ( int i = 0; i < SZ/2; ++i )

i1.append( i );

for ( i = SZ/2; i < SZ; ++i )

i12.append( i );

i1.display(); i12.display();

i1.concat( i12 ); i1.display(); // concat

i1.reverse(); i1.display(); // reverse

return 0;

}
Когато компилираме и изпълним тази програма ще получим

следния резултат:


( 0 1 2 3 )

( 4 5 6 7 )

( 0 1 2 3 4 5 6 7 )

( 7 6 5 4 3 2 1 0 )


Упражнение 4-6. Реализирайте член функция за добавяне

на елемент в списъка, така че IntItem, който го следва да има

стойност, която да е първата стойност в списъка, по-голяма

от стойността на добавяния елемент.


Упражнение 4-7. Променете IntList, така че да

притежава и елемент


IntItem *endList;
Когато изменяте public член функции се уверете, че не

нарушавате нещо в съществуващия текст (трите примерни програми

в този раздел).

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

151.
4.3. Презаредими имена на функции

За дадена дума се казва, че е презаредима, ако има две

или повече различни значения. Смисълът, в който е употребена

думата, се определя в зависимост от контекста. Ако напишем


static int depth;
значението на static се определя от обхвата на появата й. Това

е или локална статична променлива или е декларирана с файлов

обхват. (В следващия раздел, ние ще въведем едно трето

значение на static, което се отнася за статичен член на клас).

Във всеки случай значението на static напълно се

изяснява от контекста, в който се използува. Когато такъв

контекст липсва, казваме, че думата е двусмислена. Такива думи

могат да имат две или повече значения, всяко от които да бъде

еднакво възможно.

В естествените езици двусмислието често е умишлено. В

литературата, например, двусмислието може да обогати нашето

разбирането на героите и тематиката на книгите. Едно лице,

може да бъде описано като ограничено (задължено, обвързано)

и решително (непоколебимо, твърдо). (ў) Един от героите може

да се обърне към друг и да каже: "Хората никога не са

справедливи (верни, точни)".(ўў) Читателят може да възприеме

различните значения на думата едновременно.

Двусмислието, обаче, е неподходящо за компилатора. Ако

контекстът, в който се появява даден идентификатор или

оператор не е достатъчен за да се изясни значението му

компилаторът издава съобщение за грешка. Двусмислието, обаче,

е особено важно при презаредимите имена на функции, темата на

този раздел, както и на наследствеността при класовете, което

е предмет на обсъждане на глави 7 и 8.

Защо да презареждаме имената на функциите?
В С++ на две или повече функции могат да бъдат дадени

едни и същи имена, но с уникална сигнатура, като се различават

по броя или типа на аргументите си. Например,
int max( int, int);

double max(double, double );

Complex &( const Complex, const Complex );
Необходима е отделна реализация за всеки уникален

набор от аргументи на max(). Всяка от тях, обаче, изпълнява

едно и също общо действие - връща по-големия от двата

аргумента.

От потребителска гледна точка съществува само една

операция, която определя максимална стойност. Детаилите по

реализацията относно начина, по който това се извършва, се

отнасят до един по-широк кръг интереси. Чрез презереждането на

функциите потребителят може просто да напише следното:

ЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇЇ

(ў) На английски употребените думи са bound and determined.

(ўў) Изречението има вида: "People are never just".

ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

152.
int i = max( j, k );

Complex c = max( a, b );


Един аналог ни предлага аритметичният оператор.

Изразът
1 + 3


извиква операцията събиране за цели операнди, докато израза
1.0 + 3.0
извиква различна операция за събиране, която о бработва

операнди с плаваща запетая.

За потребителя реализацията на този механизъм е

прозрачна понеже операцията събиране е презаредена така, че

да подава различни свои представители. Компилаторът, а не

програмистът, се грижи за разграничаването на тези различни

представители. Презареждането на имена на функции предлага

подобна прозрачност за потребителски дефинирани функции.

Без способността за презереждане на име на функция на

всеки нейн представител трябва да бъде дадено собствено

уникално име. Например, нашето множество от max() функции ще

придобие вида:


int max( int, int );

double fmax( double, double );

Complex &Cmax( const Complex&, const Complex& );
Тази лексикална сложност не е присъща на проблема за

определяне на по-голямия от два обекта от различни типове

данни, а по-скоро отразява едно ограничение на програмната

среда - всеки оператор, който се явява в определен обхват,

трябва да бъде уникален. Тази сложност изправя програмиста

пред един практически проблем - той трябва или да помни или да

търси всяко име.

Презареждането на имената освобождава програмиста от

тази лексикална сложност.

Как да презаредим име на функция


Когато едно име на функция се декларира повече от един

път в една програма компилаторът ще интерпретира втората

декларация по следния начин:
- Ако както типът за връщане, така и сигнагурата на

двете функции съвпадат напълно, то втората се

разглежда като повторна декларация на първата.

Например,


// declares the same function

extern void print( int *ia, int sz );

void print( int *array, int size );
Имената на аргументите не са съществени за

сравнението на сигнатурите.


- Ако сигнатурите на две функции съвпадат точно, но

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

153.
типовете за връщане са различни, втората

декларация се разглежда като неправилна повторна

декларация на първата и се отбелязва като грешка

по време на компилация. Например,


unsigned int max( int*, int sz );

extern int max( int *ia, int ); // error


- Ако сигнатурите на две функции се различават по

броя или типа на аргументите си, се счита, че

двата представителя на функцията са презаредими.

Например,


extern void print( int *, int );

void print(double *da, int sz );


Една декларация typedef предлага алтернативно име за

съществуващ тип данни; то не създава нов тип данни. Следните

два представителяна search() се третират като притежаващи една

и съща сигнатура. Декларацията на втория представител ще

предизвика грешка по време на компилация понеже въпреки, че

притежава същата сигнатура, тя има различен тип за връщане.


// typedef does not introduce a new type

typedef char *string;


extern int search( string );

extern char *search( char* ); // error


Кога да не използуваме презареждането на функции ?

Механизмът на презереждането позволява множество от

функции, които изпълняват сходна операция, такава като

print(), да бъдат извиквани чрез едно общо мнемонично име.

Свързването с подходящия представител на функцията е прозрачно

за пртребителя, като при това отстранява лексикалната

сложност, породена от необходимостта на всяка функция да се

дава уникално име, като iPrint() и iaPrint().

След като разгледахме предимствата на този механизъм

нека кажем кога не се препорчва той да бъде използуван. Един

случай имаме, когато множеството от фуннкции не изпълнява

сходна операция. Например, ето един набор от функции, които

работят с обща абстрактна съвкупност от данни. Първоначално те

могат да ни се сторят като евентуални кандидати за

презареждане:


void setDate( Date&, int, int, int );

Date& convertDate( char* );

void printDate( const Date& );
Тези функции работят с едни и същи типове данни, но не

изпълняват една и съща операция. В тоози случай лексикалната

сложност е програмистко споразумение, което свързва набора от

функции с общия даннов тип. Класовият механизъм В С++ прави

ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

154.
този тип съглащения ненужни. Тези функции трябва да бъдат

направени член функции на класа Date. Например,


class Date

{

set( int, int, int );



Date &convert( char* );

void print();

// ...

};
Следният набор от пет член функции на класа Screen



изпълняват различни операции за движение. Отново те могат

да бъдат презареждани чрез едно общо име move().


Screen& moveHome();

Screen& moveAbs( int, int );

Screen& moveRel( int, int, char *direction );

Screen& moveX( int );

Screen& moveY( int );
Последните два преставителя не могат да бъдат презареждани;

техните сигнатури са едни и същи. За да осигурим уникалност на

сигнатурата трябва да обединим двете функции в една:
Screen& ( int, char xy );
Така получаваме уникална сигнатура. Освен това, ако някакво

проучване покаже, че по оста x или y промените са по-чести,

можем да зададем стойност по подразбиране:
Screen& move( int, char xy = 'x');
Проучването може да покаже също, че най-честото движение е

преместване напред с една позиция по оста х. Ако се поддържа

стойност по подразбиране за първият аргумент, обаче,

сигнатурата вече не е уникална:


Screen& move( int sz = 1, char xy = 'x' );
Сега и двете функции move() и moveHome() могат да бъдат викани

без аргументи. Не е необходимо аргумент с инициализатор по

подразбиране да бъде разглеждан, когато се опитваме да

съпоставим определен представител при извикване на презаредима

функция.

В този момент прогламистът може да оспори смислеността

на презаредимостта на тези две функции. В този случай

изглежда, че презареждането е процес на отхвърляне на ненужна

информация. Въпреки, че движението на курсора е обща операция

за тези функции, специфичното естество на това движение е

уникално при всяка от тях.

moveHome(), която е един специален случай на движение

на курсора, ни дава друг подобен пример. Името moveHome()

предлага повече информация отколкото move(). Програмата

може да се подобри с едно специално име на функция:

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

155.
inline Screen&

Screen::home()

{

return move( 0, 0 );



}
Това освобождава втората и третата функция за движение. Отново

те могат да бъдат презареждани. Както е по-лесно, обаче, те

могат да бъдат обединени в един преставител чрез инициализатор

на аргумента по подразбиране:


move( int, int, char* = 0 );
Най-добре е програмистът да не мисли, че всяка езикова

характеристика е следващата планина, която трябва да изкачи.

Използуването на дадена характеристика трябва да бъде

предизвикано от логиката на приложението, а не просто от

факта, че такава съществува.

Свързване на обръщение към презаредима функция


Сигнатурата на функцията разграничава един

представител от друг при набор от презаредими функции.

Например, ето четири различни представителя на print():
extern void print( unsigned int );

extern void print( char* );

extern void print( char );

extern void print( int );


Едно обръщение към презаредима функция се свързва с подходящ

представител по време на процеса, наречен съпоставяне на

аргументите, за който може да се мисли като за процес на

разрешаване на двусмислието. Съпоставянето на аргументите

предизвиква сравняване на фактическите аргументи на

повикването с формалните аргументи на всеки деклариран

представител. Съществуват три възможни резултата от

обръщението към презаредима функция:


1. Успешно съпоставяне. Обръщението се свързва с

подходящ представител. Например, например всяко от

следните три обръщения към print() има като

резултат съпоставяне:


unsigned a;
print( 'a' ); // matches print(char);

print( "a" ); // matches print(char*);

print( a ); // matches print(unsigned);
2. Неуспешно съпоставяне. Фактическите аргументи не

могат да бъдат поставени в съответствие с

аргументите на дефинираните представители. Всяко от

следните две обръщения към print() има като

резултат неуспешно съпоставяне:

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

156.
int *ip;

SmallInt si;


print( ip ); // error: no match

print( si ); // error: no match


3. Двусмислено съпоставяне. Фактическите аргументи

могат да бъдат съпоставени с повече от един

дефиниран представител. Следното обръщение е един

пример за двусмислие при съпоставянето, понеже

такова може да бъде осъществено с всеки от

представителите на print(), като изключим този,

който получава аргумент от тип char*.
unsigned long u1;
print( u1 ); // error: ambiguous
Съпоставянето може да бъде извършено по един от

следните три начина, в зависимост от приоритета:


1. Точно съпоставяне. Типът на фактическите аргументи

съответства точно на типа на един от дефинираните

представители. Например,
extern ff( int );

extern ff( char* );


f( 0 ); // matches ff( int )
0 е от тип int. Обръщението точно съответства на

ff(int).
2. Съпоставяне чрез прилагане на стандартни

преобразувания. Ако не бъде намерено точно

съпоставяне се прави опит да се извърши съпотавяне

чрез стандартно преобразуване на фактическия

аргумент. Например,


class X;

extern ff( X& );

extern ff( char* );
ff( 0 ); // matches ff(char*)
3. Съпоставяне чрез прилагане на дефинирани от

потребителя преобразувания. Ако не бъде намерено

точно съпоставяне или стандартно преобразуване се

използува дефинираното от потребителя. Например,

ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

157.
class SmallInt

{

operator int();



// ...
SmallInt si;

extern ff( char* );

extern ff( int );
ff( si ); // matches ff(int);
operator int() се нарича оператор за преобраазуване. Той

позволява на класа да дефинира собствен набор от "стандартни"

преобразувания. Раздел 6.5 (стр 282) раазглежда подробно тези

дефинирани от потребителя преобразувания.

Особености на точното съпоставяне
Фактическите аргументи от тип char, short и float се

обработват като специален случай, като се спазва изискването

за точно съпоставяне. Правят се два прегледа на набора от

презаредими функции винаги когато съществува фактически

аргумент за една от тях.

При първия преглед се прави опит за точно съпоставяне

на аргументите. Например,
ff( char );

ff( long );


ff( 'a' ); // ff(char)
Символната константа точно съответства на презаредимия

представител, който има формален аргумент от тип char.

Търсенето на съответствие приключва.

Ако при първия преглед не бъде намерено точно

съответствие се извършва следното:
- Аргументи от тип char, unsigned char или short

се привеждат към тип int. Аргументи от тип unsigned

short се привеждат към тип int ако машинния размер

на int е по-голям от този на short; иначе се

првеждат към тип unsigned int.
- Аргументи от тип float се првеждат към тип double.
При втория преглед се прави опит за намиране на точно

съответствие за аргументите на основата на извършените

преобразуваня. Например,

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

158.
ff( int );

ff( short );

ff( long );
ff( 'a' ); // ff(int);
Символната константа точно съответства на презаредимия

представител, който има формален аргумент от тип int.

Съпоставянето на някой от типовете short или long изисква

прилагане на стандартно преобразуване. Търсенето на

съответствие приключва.

Един фактически аргумент от тип int не се съпоставя

точно на формални аргументи от тип char или short. Съответно

double не съответствува точно на аргумент от тип float.

Например, дадена е следната двойка от презаредими функциии,
ff( long );

ff( float );


при които следното обръщение предизвиква двусмислие:
ff( 3.14 ); // error: ambiguous
Литералната константа е от тип double. Тя не съответства точно

на нито един представител. С двата представителя се постига

съпоставяне чрез прилагане на стандартните преобразувания.

Понеже съществуват две възможни преобразувания обръщението се

отбелязва като двусмислено. На нито едно стандартно

преобразувание на се дава приоритет спрямо друго. Програмистът

трябва да разреши проблема с двусмислието или чрез явно

конвертиране, такова като:


ff( long( 3.14 )); // ff(long)
или като използва суфикс за означаване на константа float:
ff( 3.14F ); // ff(float)
В следния пример, където са дадени следните

декларации:


ff( unsigned );

ff( int );

ff( char );
обръщение с фактически аргумент от тип unsigned char се

съпоставя на формален аргумент от тип int. Другите два

престаавителя изискват прилагането на стандартни

преобразувания.


unsigned char uc;

ff( uc ); // ff(int)


При съпоставянето на аргументите може да бъдат

разграничени константните от "обичайните" указателни и

псевдонимни аргументи. Например,

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

159.
extern void ff( const char* );

extern void ff( char* );


char *cp;

const char *pcc;


ff( pcc ); // ff( const char* )

ff( cp ); // ff( char* )

ff( 0 ); // error: ambiguous
Последното обръщение е двусмислено понеже 0 точно

съответствува на тип int. Но тя може да се съпостави също и на

представителя на ff() чрез прилагане на стандартно

преобразуване. Преобразуването на 0 към тип указател прави

съпоставими двата преставителя на ff().

Всеки именуван тип данни с изброими стойности дефинира

уникален тип, който се съпоставя точно само с изброените си

елементи и идентификатори от този тип. Например,


enum Bool { FALSE, TRUE } found;

enum Stat { FAIL, PASS };


extern void ff( Bool );

extern void ff( Stat );

extern void ff( int );
ff( PASS ); // ff( Stat )

ff( 0 ); // ff( int )

ff( found ); // ff( Bool )
Точното съпоставяне може да бъде избегнато като се

използува явно преобразуван. Например, даден е следния набор

от презаредими функции,
extern vid ff(int);

extern void ff(void *);


и обръщението
ff( 0xffbc );
съответства на ff(int) точно, понеже 0xffbc е цяла литерална

константа, написана в шестнадесетичен вид. Програмистът може

да предизвика обръщение към void* представителя на ff(),

обаче, като приложи явно преобразуване. Това може да се

направи така:
ff( (void *) 0xffbc ); // ff(void* )
Явното преобразуване на фактическия аргумент осигурява

точното съпоставяне на аргумента с типа на преобразуването.

ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

160.
Особености на съпоставянето чрез стадартно

преобразуване


Ако не може да бъде намерено свързване за обръщението

към презаредима функция чрез точно съпоставяне се търси

възможност за прилагане на стандартно преобразуване. Например,
extern ff( char* );

extern ff( ddouble );


ff( 'a' ); // ff( double );
При прилагане на стандартни преобразувания на типовете

към фактическите аргументи на обръщението към функцията


1. произволен числов тип ще бъде съпоставен на

формален аргумент от който и да е друг числов тип,

включително и unsigned;
2. изброимите типове ще бъдат съпоставени на формални

аргументи от числов тип;

3. нулата ще бъде съпоставена както на формален

аргумент от тип указател, така и на формален

аргумент от числов тип; и
4. указател от произволен тип ще бъде съпоставен на

формален аргумент void*.


Ето няколко примера:
extern ff( char* );

extern ff( void* );

extern ff( double );
main()

{

int i;



ff( i ); // matches ff( double );

ff( &i ); // matches ff( void* );

ff( "a" ); // matches ff( char* );

}
Всички стандартни пеобразувания се разглеждат като

изискващи еднаква работа. Преобразуването от char към unsigned

char, например, няма по-висок приоритет спрямо преобразуването

то char към double. Близост на типовете не се разглежда. Ако е

възможно повече от едно съпоставяне при прилагането на

стандартните преобразувания обръщението е двусмислено и се

отбелязва от компилатора като грешно. Например, нека е дадена

следната двойка от презаредими функции:
extern ff( unsigned );

extern ff( float );


и всяко от следните обръщения е съпоставимо на всеки от двата

представителя. Всяко от обръщенията е двусмислено и се

отбелязва като грешно.

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

161.
// each call is ambiguous

ff( 'a' );

ff( 0 );

ff( 2uL );

ff( 3=14159 );
Двусмислието може да бъде отстранено чрез явно конвертиране на

типовете.

Съпоставяне на аргументи псевдоними (reference)
Винаги, когато даден аргумент псевдоним се изпраща

като стойност за четене ( rvalue ) или като идентификатор,

чийто тип не съответствува точно на типа на псевдонима се

генерира временна поменлива. (Раздел 3.7 (стр. 120) разглежда

генерирането на временни променливи за аргументите

псевдоними). В случая, когато са възможни две или повече

стандартни преобразувания за свързването на обръщение към

презаредима функция, пиоритет има преобразуванието, което не

изисква генерирането на временен обект пред това, което

изисква.


В следващото обръщение, например, за да бъде направено

съпоставяне е необходимо прилагане на стандартно преобразуване

и за двата представителя. Преобразуването на int към short,

обаче, не изисква генериране на временен обект, докато

преобразуването към char& изисква. Преобраазуването към short,

следователно, има приоритет. В този случай няма двусмислие.


extern ff( char& );

extern ff( short );

int i;
ff( i ); // ff(short), standart conversion
Ако, обаче, аргументът от тип short също беше деклариран като

псевдоним обръщението ще бъде двусмислено, тъй като и двата

представителя ще изискват генерирането на временен обект.

Забележете, че генерирането на временен обект не се взема под

внимание в случая на точно съпоставяне:
ff( 'a' ); // ff(char&), exact match

Обръщения, съдържащи множество аргументи


Едно обръщение, съдържащо множество аргументи се

свързва, като последователно се прилагат правилата за

съпоставяне. Избира се тази функция, за която свързването на

аргументите е същото или по-добро от това за останалите

функции от презаредимото множество. Например,

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

162.
extern ff( char*, int );

exetrn ff( int, int );


// ff( int, int )

ff( 0, 'a' );


Извиква се представителя на ff(), който получава два аргумента

от тип int, понеже


1. Съпоставянето на първия аргумент е максимално

добро. 0 точно отговаря на формалния аргумент от

тип int.
2. При втория аргумент имаме еднаквост. 'a' в една и

съща степен съответствува на втория аргумент на

двете функции.
Едно обръщение се счита за двусмислено ако няма

представител, който да е съпоставим максимално добре. В седния

пример и за двата представителя е необходимо да бъдат

извършени стандартни преобразувания за да се получи

съпоставяне:
int i, j;

extern min ( long, long );

extern min ( double, double );
// error: amabiguous, no "best" match

min( i, j );


Едно обръщение се счита за двусмислено и в случая,

когато за повече от един представител на функцията може да се

постигне максимална съпоставимост. Например,
extern foo( int, int );

extern foo( double, double );


// error: ambiguous: two "best" matches

foo( 'a', 3.14F );


В този случай се счита, че и двата представителя са максимално

съпоставими.

Аргументи с подразбиращи се инициализатори
Един представител на презередима функция с

подразбиращи се инициализатори за някои от аргументите си може

да бъде съпоставен на обръщение, което предлага всичките или

само някои от аргументите си. Например,

ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

163.
extern ff( int );

extern ff( long, int = 0 );


main()

{

ff( 2L ); // matches ff( long, 0 );



ff( 0, 0 ); // matches ff( long, int );

ff( 0 ); // matches ff( int );

ff( 3.14 ); // error: ambiguous

}
Последното обръщение е двусмислено, понеже могат да

бъдат съпоставени и двата представителя чрез прилагане на

стандартни преобразувания. Няма зададен приоритет за ff(int)

понеже, тя има точно един аргумент.

Презареждане и обхват


Всяко презаредимо множество от функции, свързано с

определено име, трябва да бъде декларирано в един и същ

обхват. Една локално декларирана функция, например, по-скоро

скрива, отколкото да се презарежда като представител на

функция, декларирана с файлов обхват. Например,
extern void print( char* );

extern void print( double ); // ovoerloads print


void fooBar( int ival )

{

// separate scope: hides both instances of print



extern void print( int );
// error: print(char*) is not visible in this scope

print("Value: ");

print( ival ); // ok: print(int);

}
Тъй като всеки клас, дефиниран от потребителя,

поддържа свой собствен обхват член функциите на два различни

класа никога не могат да се припикриват.


Упражнение 4-8. Как трябва да бъде дефинирана

функцията error() за да обработва следните обръщения:


error( "Array out of bounds: ", index, upperBound );

error( "Division by zero" );

error( "Invalid selection", selectVal );

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

164.
Презареждане на оператора new
Операторът new може да бъде презареждан от

програмиста. Предварително дефинираният негов придставител има

следния прототип:
void *operator new( long size );
където size указва паметта в байтове, необходима на типа.

Всеки представител на new, дефиниран от потребителя, трябва да

връща void* и да определя първия се аргумент от тип long.

Например, един втори презаредим представител на оператора new

се предлага от една от стандартните за С++ билиотеки. Нейният

прототип изглежда така:


void * oprerator new( long size, void *memAddress );
Аргументът size се предлага автоматично от

компилатора. Допълнителният аргумент трябва да бъде зададен

чрез списък от аргументи, разделени със запетая, поместен

между ключовата дума new и спецификатора на тип.


#include

char buf[ sizeof(IntArray) ];


main()

{

// default instance of new



IntArray *pa = new IntArray( 10 );
// operator new( long, void* )

IntArray *pbuf = new (buf) IntArray( 10 );

}

4.4. Указатели към функции


Нека да трябва да напишем една обобщена функция за

сортиране. В повечето случаи особено подходящ се оказва

алгоритъма за бързото сортиране. От потребителя се изсква само

да напише следното:


sort( array, lowBound, highBound );
При някои обстоятелства, обаче, бързото сортиране не е

подходящо. Ако масивът е подреден в низходящ ред, например,

методът със сливането ще работи по-добре. Ако разполагате с

повече памет, по-подходящо може да се окаже сортирането с

натрупване. За малко множество от елементи обикновено

достатъчно добъро е сортирането по метода на мехурчето.

Функцията sort() трябва да предлага някакви улеснения

за избиране и на алтернативни сортиращи алгоритми. Проблемът

се състои в това как да осигурим необходимата гъвкавост за

едно малко множество от случаи без да усложним особено много

общото използване на функцията. Обикновено, решение предлага

една подразбираща се стойност. В нашият случай подразбиращата

се стойност е функцията qickSort().

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

165.
Едно второ изискване е осигуряването на способност за

избор на подходящ сортиращ алгоритъм без да е необходимо да се

променят текстовете на програмите, което да позволява

насторйване след като програмата е готова и е в процес на

изпълнение. Обикновено това се осъществява чрез написване на

съвкупност от оператори, които обработват аргументите на

функците или указателите. В този случай самата функция ще се

явява аргумент. Тъй като една функция не може да бъде

изпращана като аргумент ще бъде необходимо да се използува

указател към функция.

Този раздел разглежда как могат да бъдат удовлетворени

тези две изисквания.

Типът указател към функция
Какво представлява един указател към функция? Какъв ще

бъде типа му? Как може да бъде деклариран? Ето декларацията на

quickSort():
void quickSort( int*, int, int );
Разбира се, името на функцията не е част от типа й. Типът на

функцията се определя от стойността, която връща, и

сигнатурата на списъка от аргументите й. Един указател към

quickSort() може да бъде определен като се използува същата

сигнатура и тип за връщане:
void *pf( int*, int, int );
Написаното е почти правилно. Проблемът се състои в това, че

компилаторът интерпретира оператора като дефиниция на функция

pf(), която има три аргумента и тип за връщаане void*.

Операторът * се свързва със спецификатора на тип, а не с pf.

Необходимо е задаването на скоби за да бъде свързан опреатора

* с pf:
void (*pf) ( int*, int, int );


Този оператор декларира pf като указател към функция, която

има три аргумента и тип за връщане void - т.е., указател от

типа на quickSort().

Някои от останалите функции могат да имат или да нямат

същия тип. Следните три функции за сортиране имат един и същ

тип:
void bubbleSort( int*, int, int );

void mergeSort( int*, int, int );

void heapSort( int*, int, int );


Обаче, двойката функции min() и max() декларират един друг

тип:
int min( int*, int sz );

int max( int*, int sz );
Указател към тези функции може да бъде дефиниран по следния

начин:


ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

166.
int (*pfi) ( int*, int );
Съществуват толкова различни типове на функции, колкото са

комбинациите от сигнатури на функции и типове за връщане.

Инициализация и присвояване
Нека да припомним, че един иднтификатор на масив

съдържа стойността на указателя към първия елемент на масива

когато не е изменен чрез индексен оператор. Едно име на

функция, когато не е видоизменяно чрез оператор за обръщение,

получава като стойност указател към функция от нейния тип.

Например,


quickSort;
получава стойност като неименуван указател от тип
void (*)( int*, int, int );
Прилагането на оператора адрес-на към името на

функцията също дава като резултат указател към типа на

функцията. По такъв начин както quickSort, така и &quickSort

сочат към един и същ тип. Един указател към функция се

инициализира по следния начин:
void (*pfv) ( int*, int, int ) = quickSort;

void (*pfv2)( int*, int, int ) = pfv;


Присвояването на стойност се извършва по подобен

начин:
pfv = quickSort;

pfv2 = pfv;
Инициалзацията и присвояването се считат за правилни само ако

списъкът от аргументи и типът за връщане съвпадат точно. В

противен случай се издава съобщение за грешка по време на

компилация. Например,


extern int min( int*, int );

extern void (*pfv)( int*, int, int ) = 0;

extern (*pfi)( int*, int ) = 0;

main()


{

pfi = min; // ok

pfv = min; // error

pfv = pfi; // error

}
Един указател към функция може да бъде инициализиран и чрез

присвояване на стойност 0.

Указателите могат също така да адресират и

представители на презаредими функции. Когато една функция е

презаредима, компилаторът свързва този представител, чийто тип

за връщане и сигнатура съответствуват точно на типа на

указателя. Ако такъв представител не бъде намерен се издава

съобщение за грешка по време на компилация. Например,

ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

167.
extern void ff( char );

extern void ff( unsigned );


void (*pf)(char) = ff; // ok: void ff(char)

void (*pf2)(int) = ff; // error: no exact match

Извикване
Операторът * не е необходим за да бъде извикана една

функция чрез указател. Както директното, така и индиректното

обръщение към функция се прави по един и същ начин. Например,
#include
extern min( int*, int );

int (*pf)( int, int ) = min;


const int iaSize = 5;

int ia[ iaSize ] = { 7, 4, 9, 2, 5 };


main()

{

cout << "Direct call: min: "



<< min( ia, iaSize ) << "\n";

cout << "Indirect call: min: "



<< pf( ia, iaSize ) << "\n";
}
min( int* ia, int sz)

{

int minVal = ia[ 0 ];



for ( int i = 0; i< sz; ++i )

if ( minVal > ia[ i ] )

minVal = ia[ i ];

return minVal;

}
Обръщението
pf( ia, iaSize );
е кратък запис на явното указване на указтеля:
(*pf)( ia, iaSize );
Двете форми са еквивалентни.

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

168.

Масиви, аргументи и типове за връщане


Може да бъде деклариран масив от указатели към

функции. Например,


int (*testCases[10])();
декларира testCases като масив от десет елемента. Всеки

елемент е указтел към функцция, която няма аргументи и има тип

за връщане int. Изпълнението на елементи от testCases може да

има вида:


extern const SIZE = 10;

extern int (*testCase[SIZE])();

extern int testResults[SIZE];
void runtest()

{

for ( int = 0; i < SIZE; ++i )



testResults[ i ] = testCases[ i ]();

}
Един масив от указатели може да бъде инициализиран по следния

начин:
extern void quickSort( int*, int, int );

extern void mergeSort( int*, int, int );

extern void heapSsort( int*, int, int );

extern void bubbleSort( int*, int, int );


void ( *sortFuncs[] )( int*, int, int ) =

{

quickSort,



mergeSort,

heapSort,

bubbleSort

};
Към sortFuncs също може да бъде деклариран указател,

който ще бъде от типа "указател към масив от указатели към

функции". Дефиницията ще зиглежда така:


void ( **pfSort ) ( int*, int, int ) = sortFuncs;
Двата оператора ** декларират pfSort като указател към

указател.


*pfSort;
има стойност адреса на sortFuncs.
**pfSort;
има стойност адреса на quickSort(), първият елемент на

sortFuncs - еквивалентно на записа:

ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

169.
*pfSort[ 0 ];
За да изпълни quickSort() чрез pfSort, програмистът трябва да

напише едно от следните две неща:


// equivalent invocation

pfSort[ 0 ]( ia, 0, iaSize-1); // shorthand

(*pfSort[ 0 ])( ia, 0, iaSize-1 ); // explicit
Указатели към функции могат също да бъдат декларирани

и като аргументи на функции и за тях могат да се задават

стойности по подразбиране.
exetrn void quickSort( int*, int, int );

void sort( int*, int, int,

void (*)(int*, int, int) = quickSort );
Една проста дефиниция на sort() може да изглежда така:
void sort( int *ia, int low, int high,

void (*pf)(int*, int, int ))

{

if ( !ia ) return;



if ( !pf ) return;

if ( high < low + 2 ) return;

pf( ia, low, high );

}
sort() може да бъде извикана във всики от следните

начини:
// normally, these would be in a header file

extern int *ia;

extern const iaSize;

extern void quickSort( int*, int, int );

extern void bubbleSort( int*, int, int );
typedef void (*PFV)( int*, int, int );

extern void sort( int*, int, int, PFV=quickSort );

extern void setSortPointer ( PFV& );
PFV mySort;

void ff()

{

sort( ia, 0, iaaSize );



sort( ia, 0, iaSsize, bubbleSort );
setSortPointer( mySort );

sort( ia, 0, iaSize, mySort );

}
Един указател към функция може също да бъде деклариран като

тип за връщане на функция. Например,

ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

170.
int ( *ff( int ))( int*, int );
декларира ff() като функция, която получава един аргумент от

тип int. Тя връща указател към функция от тип


int (*)( int*, int );
Използуването на име typedef може да направи използуването на

указател към функция значително по-лесно за четене. Например,


typedef int (*PFI)( int*, int );

PFI ff( int );


е една еквивалентна декларация на ff().
Упражнение 4-9. В раздел 3.9 ( стр. 126) беше

дефинирана функцията fibonacci(). Дефинирайте указател към

функция, който да може да сочи към fibonacci(). Извикайте

функцията чрез този указател за да генерирате редица на

Фибуначи от осем елемента.
Упражнение 4-10. В раздел 3.10 (стр. 131) беше

дефинирана функцията binSearch(). Дефинирайте функция

search(), която да може да бъде извиквана така:
extern int size, val, ia[]]

int index = search( ia, size, val, binSearch );


_new_handler

_new_handler представлява указател към функция,

предлагана от една от стандартните библиотеки на С++, които се

разпространяват заедно с езиковата система на AT&T и има

стойност 0 по подразбиране. Декларацията на _new_handler

изглежда така:
void ( *_new_handler ) ();
_new_handler е указател към функция, която има тип за връщане

void и не получава аргументи.

Когато изпълнението на функцията new не приключи

успешно, се порверява дали _new_pointer не сочи към функция.

Ако _new_handler съдържа стойността по подразбиране, new връща

0; иначе се извиква функцията, към която сочи _new_handler.

Функцията, към която сочи _new_pointer трябва да бъде

дефинирана от потребителя. Освен това, той трябва явно да даде

на _new_handler стойност - указател към функция. Това може

да бъде направено директно по следния начин:

ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

171.
// to be invoked if new fails

extern void freeStoreExeption()


// set_new_handler to freeStoreExeption

_new_handler = freeStoreExeption;


или чрез библиотечната функция set_new_handler(). Последното

може да бъде извършено по следния начин:


// new.h contains a declaration of set_new_handler

#include


// set _new_handler with library function

set_new_handler( freeStoreExeption );


Тогава предназначението на _new_handlre е да предлага достъп

до функция, която се извиква при изключителни условия, когато

свободната памет се изчерпи. Наличието на _new_handlre,

свързан с някаква функция, освобождава потребителя от

необходимостта да проверява дали дадено обръщение към new е

завършило успешно.

Най-простата функция, с която се свързва _new_handler

издава съобщение за грешка и осигурява изход от програмата:


#include

#include


extern char *progName; // current file

enum Exeptions { FS_EXHAUST = 1, /* ... */ };


void freeStoreExeption()

{

cerr << progName



<< ": free store exhausted!\n";
// do any clean-up here ...

exit( FS_EXHAUST );

}

Упражнение 4-11. Инициализирайте _new_handler да сочи



към freeStoreExeption и изпълнете отново програмата

exhaustFreeStore(), дефинирана в раздел 4.1 (стр. 139) на тази

глава.

4.5. Свързване, безопасно относно типовете


Презаредимостта дава възможност за многократни появи

на един и същ нестатичен идентификатор на функция. Това е едно

лексикално удобство, което се поддържа на нивото на текста на

програмата. По-нататъшните компоненти на ситемата за

компилация, обаче, изисква всеки нестатичен идентификатор на

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

172.
функция да бъде именуван уникално. Например, повечето

свързващи редактори свързват външните псевдоними (references)

лексикално. Ако свързващият редактор срещне два или повече

представителя на идентификатора print, той не може да

анализира типовете на сигнатурите за да направи разлика между

представителите ( в този момент на компилацията тази

информация обикновено е загубена ). По-скоро свързващият

редактор отбелязва print като дефинирана многократно и

приключва работата си.

За да бъде решен този проблем всеки идентификатор на

фуннкция се кодира с уникално вътрешно име. Така

по-нататъщните компоненти на системата за компилация работят

само с тези кодирани имена. Подробностите, свързани с

трансформацията на имената не са особено важни; вероятно те са

различни за различните реализации. Общият алгоритъм кодира

информацията за сигнатурата и я добавя към името на функцията.

В два специални случая кодирането ще предизвика грешка по

време на свързване и ще причини неуспешно приключване на

компилацията:
1. Несъвместими декларации на функция в отделни

файлове.
2. Обръщения от функции от други езици, съществени

за С.
Този раздел разглежда тези два специални случая.

Вътрешнофайлови декларации


В първия случай дадена функция непреднамерено е

декларирана различно в два отделни файла; двете декларации

трябва да представят една и съща функция. Например, във файла

token.C функцията addToken() е дефинирана да получава един

аргумент от тип unsigned char. Във файла lex.C, кедето тази

функция се вика, addToken() е декларирана да получава един

аргумент от тип char.
// in file token.C

addToken( unsigned char tok ) { /* ... */ }


// in file lex.C

extern addToken( char );


Едно обръщение към addToken() в lex.C ще предизвика

грешка във фазата на свързващото редактиране. Аргументи от тип

unsigned char и char се кодират различно. Функцията

addToken(), декларирана в lex.C, ще бъде отбелязана като

недефинирана функция. Ако програмата бъде компилирана успешно

може да се случи следното:

Компилираната програма се тества на AT&T 3B20.

Тя се изпълнява правилно и се изпраща на ново място, където се

използува VAX 8550. Компилира се без какъвто и да е проблем.

За нещастие, още при първото изпълнение тя приключва

неуспешно. Даже най-простият тест на програмата не работи.

Какво се случва? Ето част от зададените от Token

декларации:

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

173.
enum Tokens

{

// ...



INLINE = 128;

VIRTUAL = 129;

// ...

};
Обръщението към addToken() изглежда така:


curTok = INLINE;

addToken( curTok );


Стойностите от тип char са реализирани като тип със знак върху

8550. Върху 3B20 те са реализирани като unsigned. Неправилната

декларация на addToken() не е показана на 3B20; на 8550,

обаче, всеки знак стойност по-голяма от 127 предизвиква

препълване.

Понеже във всеки момент компилаторът обработва по един

файл той не може обикновено да открие нарушенията на типовете

в различните файлове. Както вече видяхме, това нарушаване на

типовете може да стане източник на сериозни програмни грешки.

Вътрешното кодиране на имената на функциите и тяхната

сигнатура предоставя някаква мярка за вътрешнофайлова проверка

на обръщенията към функции. Такива грешки и други като тях се

откриват по време на свързването.

Погрешните декларации на външни променливи между

различни файлове, обаче, не могат да бъдат открити по време на

компилация. Грешки от типа на описаната по-долу могат да бъдат

открити само по време на изпълнение ако се получи някакво

изключение или неправилен изход на програмата.


// in token.C

unsigned char lastTok = 0;


// in lex.C

extern char lastTok; // one token history

Няколко думи за заглавните файлове
Описаното използуване на заглавните файлове е

фундаментално за предпазване от такъв тип вътрешни грешки.

Всеки заглавен файл предлага едно централизирано разполагане

на декларациите на всички extern променливи, прототипи на

функции, дефиниции на класове и inline функции. Файлове,

които трябва да декларират променлива, функция или клас

включват заглавен/ни/ файл/ове/.

Това осигурява две предохранителни мерки. Първо,

гарантира се, че всички файлове съдържат една и съща

декларация. Второ, когато декларацията на дадена променлива

трябва да бъде променена е необходимо да се направи промяна

само в заглавния файл. Така се игнорира възможността за

неправилно коригиране на декларацията в някой файл. Нашият

пример с addToken() ще предостави заглавния файл token.h.

ЇЇЇЇЇЇЇЇЇЇЇЇ

ЇЇЇЇЇЇЇЇЇЇЇЇ

174.
// in token.h

enum Tokens { /* ... */ };

extern unsigned char lastTok;

extern addToken( unsigned char );


// in lex.C

#include "token.h"


// in token.C

#include "token.h"


Трябва обаче да бъде отделено известно внимание на

проектирането на заглавните файлове. Предлаганите декларации

трябва логично да се допълват. Всеки заглавен файл отнема

време при компилация. Ако е прекалено дълъг или с много

напълно различни елементи, програмистите с неохота ще приемат

загубата на време при компилация, включвайки ги.

Едно второ съображение е, че даден заглавен файл не

трябва никога да съдържа нестатични дефиниции. Ако два файла в

една и съща програма включват заглавен файл с външни дефиниции

повечето свързващи редактори ще отстранят програмата поради

моногократно дефинирани идентификатори. Понеже стойности от

тип const често се включват в заглавните файлове свързването

по подразбиране на идентификатори const е статично.

Следователно коннстантите могат да бъдат дефинирани само вън

от заглавите файлове.

Обръщения към функции от други езици


Ако програмистът иска да се обърне към функция,

написана на друг език за програмиране - най-вероятно С - е

необходим заобикалящ механизъм, който да предотврати

кодирането на името на функцията. Този механизъм, наречен

свързваща директива, има едноредова съставна синтактична

форма:
extern "C" void exit(int);


extern "C"

{

printf( const char* ... );



scanf( const char* ... );
}
extern "C"

{

#include



}
Свързващата директива се състои от ключовата дума

extern, следвана от низов литерал и един "обичаен" прототип на

функция. Въпреки, че функцията е написана на друг език,

обръщението към нея също се проверява за типово съответствие.

Във фигурните скоби могат да бъдат записани много функции. Те

/скобите/ служат като ограничител и не въвеждат ново ниво на

обхват.

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

175.
Свързващата директива може да бъде зададена само с

файлов обхват. Следните кодови фрагменти са некоректни и ще

бъдат отбелязани като грешки по време на компилация:
char *copy( char *src, char *dst )

{

// error: linkage directive must be at file scope



extern "C" strlen( const char* );

if ( !dst )

dst = new char [ strlen(scr)+1 ];

// ...


return dst;

}
Ако преместите свързващата директива до файлов обхват

функцията ще се компилира:
extern "C" strlen( const char* );
char *copy( char *src, char *dst )

{

if ( !dst )



dst = new char [ strlen(scr)+1 ];

// ...


return dst;

}
Най-подходящото място на свързващата директива, обаче,

е във заглавен файл.

Ако се предвижда С++ функции да бъдат викани от други

езици, те също трябва да бъдат заобиколени.

Свързваща директива може да бъде определена само за

един представител на презаредима функция. Програма, която

включва следните два заглавни файла е неправилна:


// in string.h

extern "C" strlen( const char* );


// in String.h

extern "C" strlen( const String& );


Презареждането на sqrt(), което следва, илюстрира едно

типично използване на свързваща директива:


class Complex;

class BigNum;


extern Complex& sqrt( Complex& );

extern "C" double sqrt( double );

extern BigNum& sqrt(BigNum& );

ЇЇЇЇЇЇЇЇЇЇЇЇ


ЇЇЇЇЇЇЇЇЇЇЇЇ

176.
Представителят sqrt() на С е заобиколен; допълненият

представител на С++ класане е.


Упражнение 4-12. exit(), printf(), malloc(), strcpy()

и strlen() са библиотечни функции на езика С. Изменете

следната програма на С така, че да може да се компилира и

свързва в С++.


char *str ="hello";
main()

{

/* C language program */



char *s, *malloc(), *strcpy();
s = malloc( strlen(str)+1 );

strcpy( s, str );

printf("%s, world\n", s );

exit( 0 );

}
Упражнение 4-13. Раздел 3.10 (стр. 131) дефинира

функцията binSearch(). Ние желаем да я направим достъпна за

програми, написани на С. Как трябва да я декларираме?
Упражнение 4-14. Библиотечните тригонометрични функции

на С са декларирани по следния начин:


double sin( double );

double cos( double );

double tan( double );
Покажете как тези декларации биха могли да бъдат

презаредени за да възприемат аргументи от класовия тип RealNum

и Complex?

ЇЇЇЇЇЇЇЇЇЇЇЇ

177.


Каталог: files -> tu files
tu files -> Увод в компютърната графика
tu files -> Xii. Защита и безопасност на ос
tu files -> Електрически апарати
tu files -> Средства за описание на синтаксиса
tu files -> Stratofortress
tu files -> Писане на скриптове за bash шел : версия 2
tu files -> 6Технологии на компютърната графика 1Модели на изображението
tu files -> Z=f(x), където x- входни данни; z
tu files -> Body name библиотека global Matrix imports (достъп по име) … var m[N, N] := … end decl., proc … resource f final code imports node, Matrix end name var x: node node; if x … Matrix m[3,4] :=: … end


Сподели с приятели:
1   ...   6   7   8   9   10   11   12   13   ...   19




©obuch.info 2024
отнасят до администрацията

    Начална страница