Прототипы функций

Возврат указателей

Передача строк

 

Строка представляет собой обычный массив символов с нулем в конце, и когда вы передаете строку в функцию, фактически передается только указатель на начало строки. Этот указатель имеет тип char *. Рассмотрим в качестве примера следующую программу. В ней определена функция strInvertCase( ), которая преобразует строчные буквы строки в прописные и наоборот:

 

// Передача в функцию строки.

 

#include <iostream>

#include <cstring> I

#include <cctype>

using namespace std;

 

void strlnvertCase(char *str);

 

int main(){

char str[80];

strcpy(str, "This Is A test");

strlnvertCase(str);

cout << str; // вывод модифицированной строки

 

return 0;

}

// Преобразование регистра букв внутри строки,

void strlnvertCase(char *str)

{

while(*str) {

 

// преобразуем регистр

if(isupper(*str) ) *str = tolower(*str) ;

else if(islower(*str) ) *str = toupper(*str) ;

str++; // сместимся к следующему символу

}

}

 

Вот вывод этой программы:

 

tHIS iS a tEST

 

Функции могут возвращать указатели. Указатели возвращаются, как и данные любых других типов, и не вызывают никаких особых проблем. Если, однако, учесть, что указатели являются одним из наиболее сложных средств С++, краткое обсуждение этой темы вполне уместно.

В приводимой ниже программе демонстрируется использование указателя в качестве возвращаемого значения. Функция get_substr( ) просматривает строку в поисках заданной подстроки. Функция возвращает указатель на первую найденную подстроку. Если заданной подстроки найти не удалось, возвращается нулевой указатель. Например, если анализируется строка "Я люблю С++", а в качестве искомой задана подстрока "люблю", то функция вернет указатель на первую букву л в слове "люблю".

//Возврат указателя.

 

#include <iostream>

using namespace std;

 

char *get_substr(char *sub, char *str);

int main ()

{

char *substr;

substr = get_substr("three", "один два three four");

cout << "подстрока найдена: " << substr;

return 0;

}

 

// Возвращает указатель на подстроку или нулевой указатель, если подстрока не найдена.

char *get_substr(char *sub, char *str)

{

int t;

char *p, *p2, *start;

 

for(t = 0; str[t] ; t ++) {

p = &str[t]; // начальная установка указателей

start = p;

p2 = sub;

while(*p2 && *p2==*p) { // проверка на наличие подстроки

p ++;

p2 ++;

}

/* Если р2 указывает на завершающий 0 (т. е. на.

конец подстроки), значит, вся подстрока найдена. */

if(!*p2)

return start; // вернуть указатель на начало подстроки

}

return 0; // подстрока не найдена

}

 

Вот вывод этой программы:

 

подстрока найдена: три четыре

 

 

В начале этого модуля был кратко затронут вопрос о прототипах функций. Теперь наступил момент, когда мы должны рассмотреть эту тему более подробно. В С++ все функции перед тем, как их использовать, должны быть объявлены. Объявление функции выполняется с помощью ее прототипа. В прототипе задаются три характеристики функции:

§ Тип возвращаемого значения.

§ Типы параметров функции.

§ Число параметров функции.

 

Прототипы позволяют компилятору выполнить три важных операции:

§ Они говорят компилятору, с помощью какого рода кода должна вызываться функция. Различные типы возвращаемых значений должны по-разному обрабатываться компилятором.

§ Они позволяют компилятору выявить недопустимые преобразования типов аргументов, используемых при вызове функции, в объявленные типы параметров функции. При обнаружении несоответствий типов компилятор выдает сообщение об ошибке.

§ Они позволяют компилятору обнаружить несоответствие числа аргументов, используемых при вызове функции, и числа ее параметров.

 

Общая форма прототипа функции показан ниже. Она совпадает с определением функции за исключением отсутствия тела функции:

 

тип имя-функции(тип имя-параметра 1, тип имя-параметра2,…, тип имя-параметраN);

 

Использование имен параметров необязательно. Однако их использование позволяет компилятору при обнаружении расхождений в типах аргументов и параметров включить в сообщение об ошибке имя спорного параметра, что облегчает отладку. Поэтому разумно включать в прототипы имена параметров.

Для того, чтобы продемонстрировать полезность прототипов функций, рассмотрим следующую программу. Если вы попытаетесь ее оттранслировать, будет выдано сообщение об ошибке, потому что программа пытается вызвать функцию sqr_it() с целочисленным аргументом вместо требуемого указателя на целое. (Компилятор не выполняет автоматические преобразование целого числа в указатель.)

 

/*Эта программа использует прототип функции для активизации строгой проверки типов.

*/

 

void sqr_it(int *i); // прототип

 

int main (){

int х;

х = 10;

sqr_it(х); // Ошибка! Несоответствие типов!;

 

return 0;

}

void sqr_it(int *i)

{

*i = *i * *i;

}

 

Определение функции может одновременно служить ее прототипом, если оно расположено в программе до первого вызова этой функции. Например, это вполне правильная программа:

// Использование определения функции в качестве ее прототипа

#include <iostream>

using namespace std;

 

// Определим, является ли число четным.

bool isEven(int num) {

if(! (num %2)) return true; // num четно

return false;

}

 

int main()

{

if (isEven (4) ) cout << "4 четно\n";

if(isEven(3)) cout << "это не будет выведено";

 

return 0; I

}

 

В этой программе функция isEven() определена перед ее использованием в main(). В этом случае определение функции может служить ее прототипом, и в отдельном прототипе нет необходимости.

В действительности обычно оказывается проще и лучше определить прототипы всех функций, используемых программой, чем следить за тем, чтобы определения всех функций располагались в программе до их вызовов. Это особенно справедливо для больших программ, в которых не всегда легко проследить, из каких функций какие другие функции вызываются. Более того, вполне возможно иметь две функции, которые вызывают друг друга. В этом случае прототипы функций необходимы.

I