Структуры и алгоритмы компьютерной обработки данных

Решение задач с использованием указателей

Показывать лекцию целиком

Цель лекции: изучить функции с переменным числом параметров и приемы построения программ, используя указатели, научиться решать задачи с использованием функций с переменным числом параметров в языке С++.

Указатель на указатель

Указатель на указатель является формой многочисленного перенаправления или цепочки указателей (рис 6.1).

(рис 6.1) Представление одиночного и многочисленного перенаправлений

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

Многочисленное перенаправление может и дальше расширяться. Но существует немного случаев, когда необходимо что-то более мощное, чем указатель на указатель. Излишнее перенаправление приводит к концептуальным ошибкам, которые очень трудно исправлять. Переменная, являющаяся указателем на указатель, должна быть описана определенным образом. В С++ это выполняется путем помещения двух звездочек перед именем. Например, следующее объявление сообщает компилятору, что nb – это указатель на указатель типа float:

float **nb;
/*nb – это не указатель на число с плавающей точкой, 
а указатель на указатель на вещественное число*/

Для получения доступа к значению, адресуемому указателем на указатель, следует применить оператор разыменования * два раза, как показано в следующем примере:

#include "stdafx.h"
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[]){
  int *p, x, **q;
  x = 10;
  p = x;
  q = p;
  printf("%d", **q); /* вывод значения х */
  system("pause");
  return 0;
}

Здесь р объявляется как указатель на целое, а q – это указатель на указатель на целое. Вызов printf() выводит число 10 на экран.

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

//цепочка указателей на указатели
#include "stdafx.h"
#include <iostream>
using namespace std;
int _tmain(int argc, _TCHAR* argv[]){
  int i=88;
  int *pi=i;
  int **ppi=pi;
  int ***pppi=ppi;
  cout << "\n***pppi = " << ***pppi;
  system("pause");
  return 0;
}

Ассоциативность унарной операции разыменования выполняется справа налево, поэтому последовательно обеспечивается доступ к участку памяти с адресом pppi, затем к участку с адресом (*pppi)== ppi, затем к (*ppi)== pi, затем к (*pi)== i. С помощью скобок последовательность разыменований можно пояснить таким выражением *(*(*pppi))).

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

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

Синтаксис определения функции с переменным числом параметров:

тип имя (спецификация_явных_параметров,...){
  тело_функции 
}

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

После списка обязательных параметров ставится запятая, а затем многоточие (...), которое показывает, что дальнейший контроль соответствия количества и типов параметров при обработке вызова функции производить не нужно. При обращении к функции все параметры: и обязательные, и необязательные – будут размещаться в памяти друг за другом. Следовательно, определив адрес обязательного параметра как *p=k, где p – указатель, а k – обязательный параметр, можно получить адреса и всех остальных параметров: оператор k++; выполняет переход к следующему параметру списка. Еще одна сложность заключается в определении конца списка параметров, поэтому каждая функция с переменным числом параметров должна иметь механизм определения количества и типов параметров. Существует два подхода:

  • известно количество параметров, которое передается как обязательный параметр (см. Пример 1 );
  • известен признак конца списка параметров (см. Пример 2 ).
  • Пример 1. Найти сумму последовательности чисел, если известно количество чисел.

    #include "stdafx.h"
    #include <iostream>
    using namespace std;
    float sum(int k,...);
    //явный параметр k задает количество чисел
    
    int _tmain(int argc, _TCHAR* argv[]){
      cout << "\n4+6=" << sum(2,4,6);
      cout << "\n1+2+3+4=" << sum(4,1,2,3,4);
      system("pause");
      return 0;
    }
    float sum(int k, ...) {
      int *p=k;//настроили указатель на параметр k
      float s=0;
      for(;k!=0;k--)
        s+=*(++p);
      return s;
    }

    Пример 2. Найти среднее арифметическое последовательности чисел, если известен признак конца списка параметров (цифра '0').

    #include "stdafx.h"
    #include <iostream>
    using namespace std;
    float arifm(int k, ...);
    
    int _tmain(int argc, _TCHAR* argv[]){ 
      cout << "\n(4+6)/2=" << arifm(4,6,0);
      cout << "\n(1+2+3+4)/4=" << arifm(1,2,3,4,0);
      system("pause");
      return 0;
    }
    float arifm(int k, ...) {
      int *p=k;//настроили указатель на параметр k
      float s=*p; //значение первого параметра присвоили s
      int i;
      for(i=1;(*p)!=0;i++) //пока нет конца списка
        s+=*(++p);
      return s/(i-1);
    }

    Проблемы, связанные с использованием указателей

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

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

    Классическим примером ошибки с указателем является неинициализированный указатель. Например:

    // программа некорректна  
    void main (){
      int x, *p;
      x = 10;
      *p = x;
    }

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

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

    // программа некорректна  
    void main (){
      int x, *p;
      x = 10;
      p = x;
      printf("%d",*p);
    }

    Вызов printf() не выводит значения x, которое равно 10, на экран. В результате возникает ошибка из-за неправильного оператора присваивания

    p = x;

    Этот оператор присваивает значение 10 указателю р, который должен содержать адрес, а не значение. Для устранения ошибки следует написать

    p = x;

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

    Ключевые термины

    Многочисленное перенаправление – это адресация указателем адреса другого указателя, что в конечном итоге сводится к адресации участка памяти.

    Необязательные параметры – это неизвестные на момент компиляции параметры в прототипе функции.

    Обязательные параметры – это известные на момент компиляции параметры в прототипе функции.

    Одиночное перенаправление – это адресация указателем некоторого участка памяти.

    Указатель на указатель – это указатель, реализующий многочисленное перенаправление.

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

    Краткие итоги

  • Указатель может реализовать одиночное или многочисленное перенаправление в зависимости от его объявления.
  • Для получения значения, адресуемого указателем при многочисленном перенаправлении, необходимо применить операцию разыменования несколько раз.
  • В языке С++ предусмотрены объявление и вызов функций с переменным числом параметров.
  • В прототипе функции с переменным числом параметров описываются спецификации обязательных параметров, список необязательных параметров обозначается многоточием.
  • Для функций с переменным числом параметров существуют два основных способа контроля количества параметров в списке: через передачу количества параметров и по признаку конца списка параметров.
  • Использование указателей в программах требует внимательно относиться к их инициализации, присваиванию значений, выполнению операций. Ошибки, связанные с указателями, относятся к трудноустранимым.
  • Лабораторная работа 6. Решение задач с использованием указателей

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

    При выполнении лабораторной работы для каждого задания требуется написать программу на языке С++, которая получает на входе данные различных типов (в соответствии с условиями задач), выполняет их обработку в соответствии с требованиями задания и выводит результат на экран. Ввод данных осуществляется с клавиатуры с учетом требований к входным данным, содержащихся в постановке задачи. Ограничениями на входные данные является допустимый диапазон значений используемых числовых типов в языке С++.

    Теоретические сведения.

    Выполните приведенные ниже задания.

  • Напишите функцию, параметрами которой служат адреса трех переменных типа double. Функция должна возвращать адрес (значение указателя) той из переменных, адресуемых параметрами, которая имеет максимальное значение. В основной программе с помощью обращения к функции поменяйте знак значения максимальной из трех переменных. Для решения данной задачи используйте вспомогательную функцию, возвращающую адрес максимальной из переменных, адресуемых двумя параметрами-указателями.
  • Напишите функцию для вычисления значения многочлена Pn(x)=anxn+an-1xn-1+...+a1x1+a0. Коэффициенты an,an-1,...,a0, аргумент x и коэффициенты передавайте в функцию с помощью списка параметров изменяемой длины. В основной программе вычислите и напечатайте значения нескольких полиномов. Для вычисления полиномов используйте схему Горнера:
    Pn(x)=(...(anx+an-1)x+an-2)x+...+a1)x+a0.
  • Напишите функцию, которая упорядочивает значение трех переменных, адреса которых передаются ей в качестве параметров. В основной программе продемонстрируйте применение написанной функции. Для решения данной задачи определите и используйте вспомогательную функцию, которая упорядочивает значения только двух переменных, адресуемых ее двумя аргументами.
  • Напишите функцию с переменным числом параметров в соответствии с заданием, продемонстрировать вызов функции с различным числом параметров. Выполните вычисления для трех и десяти чисел. Решите задачу двумя способами: через передачу количества параметров как параметр функции и через использование признака конца списка параметров. В задаче требуется найти среднее гармоническое элементов числового ряда.
  • Указания к выполнению работы.

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

    Следует реализовать каждое задание в соответствии с приведенными этапами:

  • изучить словесную постановку задачи, выделив при этом все виды данных;
  • сформулировать математическую постановку задачи;
  • выбрать метод решения задачи, если это необходимо;
  • разработать графическую схему алгоритма;
  • записать разработанный алгоритм на языке С++;
  • разработать контрольный тест к программе;
  • отладить программу;
  • представить отчет по работе.
  • Требования к отчету.

    Отчет по лабораторной работе должен соответствовать следующей структуре.

  • Титульный лист.
  • Словесная постановка задачи. В этом подразделе проводится полное описание задачи. Описывается суть задачи, анализ входящих в нее физических величин, область их допустимых значений, единицы их измерения, возможные ограничения, анализ условий при которых задача имеет решение (не имеет решения), анализ ожидаемых результатов.
  • Математическая модель. В этом подразделе вводятся математические описания физических величин и математическое описание их взаимодействий. Цель подраздела – представить решаемую задачу в математической формулировке.
  • Алгоритм решения задачи. В подразделе описывается разработка структуры алгоритма, обосновывается абстракция данных, задача разбивается на подзадачи. Схема алгоритма выполняется по ЕСПД (ГОСТ 19.003-80 и ГОСТ 19.002-80).
  • Листинг программы. Подраздел должен содержать текст программы на языке программирования С++, реализованный в среде MS Visual Studio 2010.
  • Контрольный тест. Подраздел содержит наборы исходных данных и полученные в ходе выполнения программы результаты.
  • Выводы по лабораторной работе.
  • Ответы на контрольные вопросы.
  • Контрольные вопросы

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