Мегаобучалка Главная | О нас | Обратная связь


Указатели символов и функции



2019-12-29 214 Обсуждений (0)
Указатели символов и функции 0.00 из 5.00 0 оценок




 

Строчная константа, как, например, "Я - строка" является массивом символов. Компилятор завершает внутреннее представление такого массива символом \0, так что программы могут находить его конец. Таким образом, длина массива в памяти оказывается на единицу больше числа символов между двойными кавычками.

По-видимому чаще всего строчные константы появляются в качестве аргументов функций, как, например, в:

printf ("Здравствуй, Мир !\n");

 

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


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

char *message;

 

то в результате оператора:

message = "Now is the time";

 

переменная message станет указателем на фактический массив символов. Это не копирование строки; здесь участвуют только указатели. В языке «C» не предусмотрены какие-либо операции для обработки всей строки символов как целого.

 

Мы проиллюстрируем другие аспекты указателей и массивов, разбирая две полезные функции из стандартной библиотеки ввода-вывода, которая будет рассмотрена в главе 9.

Первая функция – это strcpy(s,t), которая копирует строку t в строку s. Аргументы написаны именно в этом порядке по аналогии с операцией присваивания, когда для того, чтобы присвоить t к s, обычно пишут s = t .

Пример 6-6. Сначала приведем версию с массивами:

void strcpy(char s[],char t[])// Скоприровать t в s

{

int i;

i = 0;

while ((s[i] = t[i]) != '\0')

i++;

}

 

Пример 6-7. Для сопоставления ниже даются 3 варианта strcpy с указателями:

void strcpy(char *s, char *t) // Вариант 1

{

while ((*s = *t) != '\0')

{

s++;

t++;

}

}

 

Так как аргументы передаются по значению, функция strcpy может использовать s и t так, как она пожелает. Здесь они с удобством полагаются указателями, которые передвигаются вдоль массивов, по одному символу за шаг, пока не будет скопирован в s завершающий в t символ \0.

Пример 6-8. На практике функция strcpy была бы записана не так, как мы показали выше. Вот вторая возможность:

strcpy(char *s, char *t) // Вариант 2

{

while ((*s++ = *t++) != '\0')

;

}

 

Здесь увеличение s и t внесено в проверочную часть. Значением *t++ является символ, на который указывал t до увеличения; постфиксная операция ++ не изменяет t, пока этот символ не будет извлечен. Точно так же этот символ помещается в старую позицию s, до того как s будет увеличено. Конечный результат заключается в том, что все символы, включая завершающий \0, копируются из t в s.

Пример 6-9. И как последнее сокращение мы опять отметим, что сравнение с \0 является излишним, так что функцию можно записать в виде:

strcpy(char *s, char *t) // Вариант 3

{

while (*s++ = *t++)

;

}

 

хотя с первого взгляда эта запись может показаться загадочной, она дает значительное удобство. Этой идиомой следует овладеть уже хотя бы потому, что вы с ней будете часто встречаться в «C»-программах.

Вторая функция – это strcmp(s,t), которая сравнивает символы строк s и t, возвращая отрицательное, нулевое или положительное значение в соответствии с тем, меньше, равно или больше лексикографически s, чем t.

Пример 6-10. Возвращаемое значение получается в результате вычитания символов из первой позиции, в которой s и t не совпадают.

// Получить return < 0, если s<t,

// return = 0, если s == t,

// return > 0, если s > t

strcmp(char s[], char t[]){

int i;

i = 0;

while (s[i] == t[i])

if (s[i++] == '\0')

    return(0);

return(s[i]-t[i]);

}

Пример 6-11. А вот версия strcmp с указателями:

 

// Получить return < 0, если s<t,

// return = 0, если s == t,

// return > 0, если s > t

strcmp(char *s, char *t)

{

for ( ; *s == *t; s++, t++)

if (*s == '\0')

return(0);

return(*s-*t);

}

 

Так как ++ и -- могут быть как постфиксными, так и префиксными операциями, то встречаются и другие комбинации * и ++ и --, хотя менее часто. Например *++p увеличивает p до извлечения символа, на который указывает p, а *--p сначала уменьшает p.

Упражнение 6-2. Напишите вариант с указателями функции strcat из главы 3: strcat(s,t) копирует строку t в конец s.

Упражнение 6-3. Напишите макрос для strcpy.

Упражнение 6-4. Перепишите подходящие программы из предыдущих глав и упражнений, используя указатели вместо индексации массивов. Хорошие возможности для этого предоставляют функции getline (главы 2 и 6), atoi, itoa и их варианты (главы 3, 4 и 5), reverse (глава 4), index и getop (глава 5).

 

Указатели – не целые

 

Вы, возможно, обратили внимание в предыдущих «С»-программах на довольно непринужденное отношение к копированию указателей. В общем это верно, что на большинстве машин указатель можно присвоить целому и передать его обратно, не изменив его; при этом не происходит никакого масштабирования или преобразования и ни один бит не теряется. К сожалению, это ведет к вольному обращению с функциями, возвращающими указатели, которые затем просто передаются другим функциям, – необходимые описания указателей часто опускаются.

Пример 6-12. Рассмотрим функцию strsave(s), которая копирует строку s в некоторое место для хранения, выделяемое посредством обращения к функции alloc, и возвращает указатель на это место. Правильно она должна быть записана так:

 


char *strsave(char *s) /* save string s somewhere */

{

char *p, *alloc();

if ((p = alloc(strlen(s)+1)) != null)

strcpy(p, s);

return(p);

}

 

На практике существует сильное стремление опускать описания:

  

*strsave(s) /* save string s somewhere */

{

char *p;

if ((p = alloc(strlen(s)+1)) != null)

strcpy(p, s);

return(p);

}

 

    Эта программа будет правильно работать на многих машинах, потому что по умолчанию функции и аргументы имеют тип int, а указатель и целое обычно можно безопасно пересылать туда и обратно. Однако такой стиль программирования в своем существе является рискованным, поскольку зависит от деталей реализации и архитектуры машины и может привести к неправильным результатам на конкретном используемом вами компиляторе. Разумнее всюду использовать полные описания. Среда программирования Visual C ++ или отладочная программа lint предупредят о таких конструкциях, если они по неосторожности все же появятся.

 

Многомерные массивы

 

В языке «C» предусмотрены прямоугольные многомерные массивы, хотя на практике существует тенденция к их значительно более редкому использованию по сравнению с массивами указателей. В этом разделе мы рассмотрим некоторые их свойства.

Пример 6-13. Рассмотрим задачу преобразования дня месяца в день года и наоборот. Например, 1-ое марта является 60-м днем невисокосного года и 61-м днем високосного года. Давайте введем две функции для выполнения этих преобразований: day_of_year преобразует месяц и день в день года, а month_day преобразует день года в месяц и день. Так как эта последняя функция возвращает два значения, то аргументы месяца и дня должны быть указателями:

 

month_day(1977, 60, &m, &d)

 

Полагает m равным 3 и d равным 1 (1-ое марта).

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

 

static int day_tab[2][13] =

{

(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31),

(0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)

};

 

// Определить день года по месяцу и дню

day_of_year(int year, int month, int day)

{

int i, leap;

leap= year%4 == 0 && year%100 != 0 || year%400 == 0;

for (i = 1; i < month; i++)

day += day_tab[leap][i];

return(day);

}

 

// Опредлить месяц и день по дню года

month_day(int year, int yearday,

     int *pmonth, int *pday)

{

leap= year%4 == 0 && year%100 != 0 || year%400 == 0;

for (i = 1; yearday > day_tab[leap][i]; i++)

yearday -= day_tab[leap][i];

*pmonth = i;

*pday = yearday;

}

 

Массив day_tab должен быть внешним как для day_of_year, так и для month_day, поскольку он используется обеими этими функциями.

Массив day_tab является первым двумерным массивом, с которым мы имеем дело. По определению в «C» двумерный массив по существу является одномерным массивом, каждый элемент которого является массивом. Поэтому индексы записываются следующим образом: 

day_tab[i][j]

Нельзя писать так:

day_tab[i,j]

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

 

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

Если двумерный массив передается функции, то описание соответствующего аргумента функции должно содержать количество столбцов; количество строк несущественно, поскольку, как и прежде, фактически передается указатель. В нашем конкретном случае это указатель объектов, являющихся массивами из 13 чисел типа int. Таким образом, если бы требовалось передать массив day_tab функции f, то описание в f имело бы вид:

f(int day_tab[2][13])

{

...

}

Так как количество строк является несущественным, то описание аргумента в f могло бы быть и таким:

                                 int day_tab[][13];

или таким

                                 int (*day_tab)[13];

 

в которм говорится, что аргумент является указателем массива из 13 целых. Круглые скобки здесь необходимы, потому что квадратные скобки [ ] имеют более высокий уровень старшинства, чем *; как мы увидим в следующем разделе, без круглых скобок

                                 int *day_tab[13];

является описанием массива из 13 указателей на целые.

         




2019-12-29 214 Обсуждений (0)
Указатели символов и функции 0.00 из 5.00 0 оценок









Обсуждение в статье: Указатели символов и функции

Обсуждений еще не было, будьте первым... ↓↓↓

Отправить сообщение

Популярное:
Как построить свою речь (словесное оформление): При подготовке публичного выступления перед оратором возникает вопрос, как лучше словесно оформить свою...
Модели организации как закрытой, открытой, частично открытой системы: Закрытая система имеет жесткие фиксированные границы, ее действия относительно независимы...



©2015-2024 megaobuchalka.ru Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. (214)

Почему 1285321 студент выбрали МегаОбучалку...

Система поиска информации

Мобильная версия сайта

Удобная навигация

Нет шокирующей рекламы



(0.009 сек.)