Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
236
Добавлен:
20.03.2016
Размер:
17.83 Mб
Скачать

Глава 7. Работа с указателями и структурами данных

117

Передача в качестве аргумента функции массивов размерности больше единицы

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

int m[2][13]; int m[][13];

Здесь m указывает на начало массива, поэтому компилятору достаточно знать только количество столбцов массива и начало его первого элемента.

Другой вариант описания:

int (*m)[13];

Здесь m указывает на начало массива.

Массивы указателей

Мы видели, что с помощью массива, объявленного, например, как char M[n][m], можно задавать множество символьных строк постоянной длины. Иначе и не задать, потому что компилятор не сможет найти заданный элемент массива. Однако в жизни чаще всего приходится работать со строками переменной длины. Тогда жесткая конструкция двумерного массива для их хранения не подойдет.

Для решения этой проблемы существует конструкция, называемая массивом указателей. Создается одномерный массив, элементами которого служат указатели на заданный тип данных. Например, массив char *s[10]; — это десять указателей (s[0],s[1],...,s[9]), каждый из них указывает на строку, которая может быть переменной длины.

Такой массив формируется так: в некоторой памяти размещается первая строка, ее адрес заносится в s[0]. Затем размещается вторая строка, ее адрес заносится в s[1] и т. д. Чтобы обратиться к элементам такого массива, нужно воспользоваться определением указателя. Обратиться к нулевому элементу нулевой строки следует как *s[0], к первому элементу той же строки как *s[0]++ и т. д. К нулевому элементу первой строки нужно обратиться как *s[1], к ее первому элементу как *s[1]++ и т. д.

Инициализация массива указателей на строки символов, например, char *s[3]; будет выглядеть так:

char *s[3]={"Первая строка символов", "Вторая строка символов", "Третья строка символов"};

В чем же различие между записями, например, int n[10][20] и int *b[10]?

Под первый вариант компилятор выделяет 200 единиц памяти. И поиск элемента этого массива производится путем вычисления обычных прямоугольных индексов.

118

Часть I. Изучение языка С/С++

 

 

При втором варианте (если предположить, что и там строки содержат по 20 элементов) под них также будет выделено 200 единиц памяти, но еще понадобится память для хранения десяти указателей. То есть памяти при втором варианте размещения данных требуется больше. Но это неудобство перекрывается тем, что в таких конструкциях можно хранить строки переменной длины, и что доступ к таким строкам происходит напрямую — по их адресам, без вычисления индексов массивов.

Указатели на функции

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

<Тип возвращаемого функцией значения> (*имя функции) (список параметров)

Например:

int (*comp)(char s1,char s2);

Это указатель на функцию comp(s1,s2), которая возвращает результат типа int. Если мы подействуем операцией разыменования (*) на этот указатель (по определению указателя записав воздействие в виде (*comp)(s1,s2)), то функция comp(s1,s2) выполнится и возвратит некое целое число.

Ранее мы видели, что имена массивов можно передавать в качестве аргументов функции. Теперь, поскольку есть указатели на функции, функции можно передавать в качестве аргументов другим функциям. В таких функциях их аргументы-

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

Листинг 7.5

// 7.5_2011.cpp

#include "stdafx.h"

#include <stdio.h> //для getchar(),putchar()

#include <conio.h>

#include <stdlib.h> //для atoi() using namespace System;

#define eof '?' #define maxline 1000

Глава 7. Работа с указателями и структурами данных

119

//--- Функция подсчета количества битов в целом числе

 

int bitcount(unsigned int n)

 

{

 

 

int b;

 

 

for(b=0; n != 0; n>>=1)

 

if(n & 01) //01 — восьмеричная единица

 

b++;

 

 

return(b);

 

 

}

 

 

//-------Ввод строки с клавиатуры

 

int getline(char s[],int lim)

 

{

 

 

int c,i;

 

 

for(i=0; i<lim-1 && (c=getchar()) != eof && c != '\n'; i++)

 

s[i]=c;

 

 

s[i]='\0';

 

i++;

//для учета количества

 

return(i);

 

 

}

 

 

//Функция вводит число n с клавиатуры //и подсчитывает количество единиц в нем

int gener(int (*getline)(char s[],int lim),int (*bitcount)(unsigned int n))

{

char s[maxline]; int lim=100;

printf("Enter any number >\n"); (*getline)(s,lim);

unsigned int n=atoi(s); n=(*bitcount)(n); return(n);

}

//-------------------------------------------------------------

int main()

{

int n=gener(getline,bitcount);

printf("The amount of ones in the input n=%o\n",n); _getch();

return 0;

}

Мы уже знакомы с функциями getline() и bitcount() (последнюю составляли, когда изучали операции сдвига — она подсчитывает в целом числе без знака количество единиц (ненулевых битов)).

120

Часть I. Изучение языка С/С++

 

 

Рассмотрим вызывающую функцию gener(). Мы видим, что оба ее аргумента описаны как указатели на функции: первый — на функцию getline(), второй — на bitcount(). Затем идет выполнение первой переданной в качестве аргумента функции. Чтобы заставить выполниться функцию, находящуюся по адресу, который содержится в указателе getline, надо подействовать на него операцией разыменования (по определению указателя).

Получим (*getline)(s,lim);

Функция getline() выполнится и результатом ее работы станет введенное в строку s число, которое переводится в беззнаковое n c помощью функции atoi(). После этого выполнится функция bitcount(), тоже описанная как указатель. Результат ее работы и возвращается в качестве результата функции gener(). Результат расчета показан на рис. 7.5.

Рис. 7.5. Результат расчета программы листинга 7.5

Структуры. Объявление структур

Структуры — это такие конструкции языка С, которые объединяют в себе данные разных типов, в том числе и подструктуры (такие же структуры, но являющиеся членами главной структуры). Эти конструкции полезны тем, что во многих ситуациях позволяют группировать связанные данные таким образом, что с ними можно работать как с единым целым. Как объявляется структура, покажем на примере объявления данных некоторого человека:

struct man

{

char name[80]; //имя

char phone_number[80]; //телефон int age; //возраст

int height; //рост

};

Так задается шаблон будущего экземпляра структуры. Здесь man — имя шаблона. То, что находится в теле, ограниченном фигурными скобками, — это члены струк- туры-шаблона (под такое объявление компилятор память не выделяет). На основе такого шаблона создается экземпляр структуры, под который память уже выделяется и с которым можно работать в программе. Чтобы начинающему изучать С было более понятно, скажем, что объявленный шаблон — это тип "структура". А имея

Глава 7. Работа с указателями и структурами данных

121

такой тип, станем объявлять данные этого типа. Точно так же, как имея, например, тип int, начинаем объявлять данные типа int.

Экземпляры структуры создаются несколькими путями: по шаблону man:

struct man friends[100],others;

Здесь созданы два экземпляра структуры: один — это массив структур (каждый элемент такого массива представляет собой структуру шаблона man. Можно сказать так: friends — это переменная типа man), другой — обычный экземпляр по шаблону man. В языке С++ ключевое слово struct можно опускать, т. е. в С++ уже пришли к аналогии с простыми типами данных: там экземпляр структуры уже можно объявить так:

man friends[100],others;

Видите сходство с объявлением простых переменных? И friends и others объявлены как переменные типа man;

при объявлении шаблона:

struct man

{

char name[80];

char phone_number[80]; int age;

int height; }others;

Здесь создан один экземпляр структуры — others;

с помощью квалификатора типа typedef, который изменяет имя шаблона и позволяет воспользоваться новым именем в качестве типа данных. Этот квалификатор позволяет вводить в объявления имена-синонимы, более удобные в использовании в программе. Например, можно записать объявление некой переменной в виде typedef int step; и далее в программе при объявлении какой-то переменной писать вместо int ее синоним step: step aa;. Компилятор потом все вернет на свои места. По этому же принципу можно сократить работу со структурой, объявив, например,

typedef struct

{

char phone_number[80]; int age;

int height; }aa;

То есть заменили всю структуру на один тип aa.

Теперь можно писать: aa d1,d2[20],*p;. Здесь объявлено три переменных типа aa: экземпляр d1 структуры шаблона man, массив структур d2[20] и р — указатель на структуру.