Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Строки.doc
Скачиваний:
1
Добавлен:
14.11.2019
Размер:
86.02 Кб
Скачать

Строки

Большинство программ на C++ широко используют символьные строки, которые хранятся в массиве типа char, заканчивающемся символом NULL (или ASCII 0). Следует помнить, что:

  • Чтобы объявить символьную строку, вы должны объявить массив типа char.

  • Чтобы присвоить символы символьной строке, ваши программы просто присваивают символы элементам массива символьных строк.

  • Программы C++ используют символ NULL (ASCII 0), чтобы отметить последний символ строки.

  • Символьные строки можно инициализировать при объявлении.

  • Программы могут передавать символьные строки в функцию, как и любой массив.

  • Большинство библиотек этапа выполнения C++ обеспечивают набор функций, которые управляют символьными строками.

Главное различие между символьными строками и другими типами массивов заключается в том, как C++ указывает последний элемент массива. Программы на C++ представляют конец символьной строки с помощью символа NULL, который в C++ изображается как специальный символ '\0'. При присваивании символов символьной строке следует поместить символ NULL ('\0') после последнего символа в строке.

Если вы используете строковые константы, заключенные в двойные кавычки, компилятор C++ автоматически добавляет символ NULL.

Следующая программа LOOPNULL.CPP слегка изменяет предыдущую программу, используя цикл for для вывода содержимого строки:

#include <iostream.h>

void main(void)

{    char alphabet[34]; //33 символа плюс NULL char letter;    int index;    for (letter = 'A', index = 0; letter <= 'Я'; letter++,index++) alphabet[index] = letter;    alphabet[index] = NULL;    for (index = 0; alphabet[index] 1= NULL; index++) cout << alphabet[index];    cout << endl; }

При рассмотрении программ на C++ вы можете встретить символы, заключенные в одинарные кавычки (например, 'А') и символы, заключенные в двойные кавычки ("А"). Символ внутри одинарных кавычек представляет собой символьную константу. Компилятор C++ выделяет только один байт памяти для хранения символьной константы. Однако символ в двойных кавычках представляет собой строковую константу — указанный символ и символ NULL (добавляемый компилятором). Таким образом, компилятор будет выделять два байта для символьной строки.

Для инициализации символьной строки при объявлении укажите требуемую строку внутри двойных кавычек:

char title[64] = "Учимся программировать на языке C++";

Функции

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

function_name();

Если программа передает информацию (параметры) в функцию, она размещает эту информацию внутри круглых скобок, разделяя ее запятыми:

payroll(employee_name, employee_id, salary);

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

Когда функция возвращает значение, вызвавшая программа может присвоить возвращенное значение переменной, используя оператор присваивания, как показано ниже:

payroll_amount = payroll (employee, hours, salary);

В дополнение к этому вызвавшая программа просто может обращаться к функции. Например:

cout << "Служащий получил" << payroll(employee, hours, salary) < < endl;

Вызвавшая функция может также использовать возвращаемое значение в условии, как показано ниже:

if (payroll(employee, hours, salary) < 500.00) cout << "Этот служащий нуждается в повышении" << endl;

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

extern void swap(int*, int*); // описание

void swap(int*, int*) // определение

{

int t = *p;

*p =*q;

*q = t;

}

Когда вызывается функция, дополнительно выделяется память под ее формальные параметры, и каждый формальный параметр инициализируется соответствующим ему фактическим параметром. Тип фактического параметра сопоставляется с типом формального параметра, и выполняются все стандартные и определенные пользователем преобразования типов. Есть особые правила для передачи векторов, средство передавать параметр без проверки и средство для задания параметров по умолчанию [1]. Из функции, которая не описана как void, можно (и должно) возвращать значение. Возвращаемое значение задается оператором return. Например:

int fac(int n) {return (n>1) ? n*fac(n-1) : 1; }

В функции может быть больше одного оператора return:

int fac(int n)

{

if (n > 1)

return n*fac(n-1);

else

return 1;

}

Возвращаемое значение рассматривается как инициализатор переменной возвращаемого типа. Тип возвращаемого выражения проверяется на согласованность с возвращаемым типом, и выполняются все стандартные и определенные пользователем преобразования типов. Например:

double f()

{

// ...

return 1; // неявно преобразуется к double(1)

}

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

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

int strlen(const char*);

void f()

{

char v[] = "a vector"

strlen(v);

strlen("Nicholas");

};

Иначе говоря, при передаче как параметр типа T[] преобразуется к T*. Следовательно, присваивание элементу векторного параметра изменяет значение элемента вектора, который является параметром. Другими словами, вектор отличается от всех остальных типов тем, что вектор не передается (и не может передаваться) по значению. Размер вектора недоступен вызываемой функции. Это может быть неудобно, но эту сложность можно обойти несколькими способами. Строки оканчиваются нулем, поэтому их размер можно легко вычислить. Для других векторов можно передавать второй параметр, который задает размер, или определить тип, содержащий указатель и индикатор длины, и передавать его вместо просто вектора. Например:

void compute1(int* vec_ptr, int vec_size); // один способ

struct vec { // другой способ

int* ptr;

int size;

};

void compute2(vec v);

С многомерными массивами все сложнее, но часто можно вместо них использовать векторы указателей, которые не требуют специального рассмотрения. Например:

char* day[] = {

"mon", "tue", "wed", "thu", "fri", "sat", "sun"

};

С другой стороны, рассмотрим определение функции, которая работает с двумерными матрицами. Если размерность известна на стадии компиляции, то никаких проблем нет:

void print_m34(int m[3][4])

{

for (int i = 0; i<3; i++) { for (int j="0;" j<4; j++) cout << " " << m[i][j]; cout << "\n"; } }

Матрица, конечно, все равно передается как указатель, а размерности используются просто для удобства записи. Первая размерность массива не имеет отношения к задаче отыскания положения элемента. Поэтому ее можно передавать как параметр.

Часто в самом общем случае функции требуется больше параметров, чем в самом простом и более употребительном случае. Например, в библиотеке потоков есть функция hex(), порождающая строку с шестнадцатиричным представлением целого. Второй параметр используется для задания числа символов для представления первого параметра. Если число символов слишком мало для представления целого, происходит усечение, если оно слишком велико, то строка дополняется пробелами. Часто программист не заботится о числе символов, необходимых для представления целого, поскольку символов достаточно. Поэтому для нуля в качестве второго параметра определено значение "использовать столько символов, сколько нужно". Чтобы избежать засорения программы вызовами вроде hex(i,0), функция описывается так:

extern char* hex(long, int =0);

Инициализатор второго параметра является параметром по умолчанию. То есть, если в вызове дан только один параметр, в качестве второго используется параметр по умолчанию. Например:

cout << "**" << hex(31) << hex(32,3) << "**";

интерпретируется как

cout << "**" << hex(31,0) << hex(32,3) << "**";

и напечатает:

** 1f 20**

Параметр по умолчанию проходит проверку типа во время описания функции и вычисляется во время ее вызова. Задавать параметр по умолчанию возможно только для последних параметров, поэтому

int f(int, int =0, char* =0); // ok

int g(int =0, int =0, char*); // ошибка

int f(int =0, int, char* =0); // ошибка

Заметьте, что в этом контексте пробел между * и = является существенным (*= является операцией присваивания):

int nasty(char*=0); // синтаксическая ошибка

Как правило, давать разным функциям разные имена - мысль хорошая, но когда некоторые функции выполняют одинаковую работу над объектами разных типов, может быть более удобно дать им одно и то же имя. Использование одного имени для различных действий над различными типами называется перегрузкой (overloading). Метод уже используется для основных операций C++: у сложения существует только одно имя, +, но его можно применять для сложения значений целых, плавающих и указательных типов. Эта идея легко расширяется на обработку операций, определенных пользователем, то есть, функций. Чтобы уберечь программиста от случайного повторного использования имени, имя может использоваться более, чем для одной функции только если оно сперва описано как перегруженное. Например:

overload print;

void print(int);

void print(char*);

Для некоторых функций невозможно задать число и тип всех параметров, которые можно ожидать в вызове. Такую функцию описывают, завершая список описаний параметров многоточием (...), что означает "и может быть, еще какие-то параметры". Например:

int printf(char* ...);

Это задает, что в вызове printf должен быть по меньшей мере один параметр, char*, а остальные могут быть, а могут и не быть.

Такая функция полагается на информацию, которая недоступна компилятору при интерпретации ее списка параметров. В случае printf() первым параметром является строка формата, содержащая специальные последовательности символов, позволяющие printf() правильно обрабатывать остальные параметры. %s означает "жди параметра char*", а %d означает "жди параметра int". Однако, компилятор этого не знает, поэтому он не может убедиться в том, что ожидаемые параметры имеют соответствующий тип. Например:

printf("Мое имя %s %s\n",2);

откомпилируется и в лучшем случае приведет к какой-нибудь странного вида выдаче.

Встроенные inline функции.

Функцию можно определить как встроенную с помощью модификатора inline, который рекомендует компилятору вместо обращения к функции помещать ее код непосредственно в каждую точку вызова. Модификатор inline ставиться перед типом функции. Он применяется для коротких функций, что бы снизить накладные расходы на вызов (сохранение и восстановление регистров, передача управления). Директива inline носит рекомендательный характер и выполняется компилятором по мере возможности. Использование встроенных функций может увеличить объем исполняемой программы. Определение функции должно предшествовать ее вызовам, иначе компилятор сгенерирует обычный вызов.

Возвращаемые значения, параметры, аргументы.

Тип возвращаемого значения и типы параметров совместно определяют тип функции.

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

Пример функции, возвращающей сумму двух целых величин:

#include <iostream.h>

int sum(int a, int b); //объявление функции

int main() {

int a = 2, b = 3, c, d;

c = sum(a, b); //вызов функции

cout << "Please, enter number";

cin >> d;

cout << "\n" << sum(c, d); //вызов функции

return 0;

}

int sum(int a, int b){ //определение функции

return (a + b);

}

Локальные и глобальные переменные.

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

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

#include <iostream.h>

void f(int a){

int m = 0;

cout << "n m p\n";

while (a--){

static int n = 0;

int p = 0;

cout << n++ << " " << m++ << " " << p++ << "\n";

}

}

int main() {

f(3);

f(2);

return 0;

}

Статическая переменная n размещается в сегменте данных и инициализируется один раз при первом выполнении оператора, содержащего ее определение. Автоматическая переменная m инициализируется при каждом входе в блок цикла. Программа выведет на экран:

n m p

0 0 0

1 1 0

2 2 0

n m p

3 0 0

4 1 0

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

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

Механизм возврата из функции в вызвавшую ее функцию реализуется оператором

return [выражение];

Функция может содержать несколько операторов return (это определяется потребностями алгоритма). Если функция описана как void, выражение не указывается, Оператор return можно опускать для функций типа void, если возврат из нее происходит перед закрывающей фигурной скобкой, и для функции main. Выражение указанное после return неявно преобразуется к типу, возвращаемого функцией значения и передается в точку вызова.

int f1(){return 1;} //правильно

void f2(){return 1;} //не правильно, f2 не должна возвращать значение

double f3(){return 1;} //правильно, 1 преобразуется к double

Нельзя возвращать функции указатель на локальную переменную, поскольку память, выделенная под локальные переменные при входе в функцию, освобождается после возврата из нее.

int* f(){

int a = 5;

return &a; // Нельзя!!!

}

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

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

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

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

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

#include <iostream.h>

void f(int i, int* j, int&k);

int main (){

int i = 1, j = 2, k = 3;

cout << "i j k\n";

cout << i << ' ' << j << ' ' << k << '\n';

f(i,&j,k);

cout << i << ' ' << j << ' ' << k << '\n';

return 0;

}

void f(int i, int* j, int& k){

i++; (*j)++; k++;

}

Результат работы программы:

i j k

1 2 3

1 3 4

Первый параметр i передается по значению. Его изменение в функции не влияет на исходное значение. Второй параметр j передается по адресу с помощью указателя, при этом для передачи в функцию адреса фактического параметра используется операция взятия адреса, а для получения его значения в функции требуется операция разыменования. Третий параметр k передается по адресу с помощью ссылки.

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

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

int f(const char*); или

char* t(char* a, const int* b);

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]