Прототипы функций
Возврат указателей
Передача строк
Строка представляет собой обычный массив символов с нулем в конце, и когда вы передаете строку в функцию, фактически передается только указатель на начало строки. Этот указатель имеет тип 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