Out.close();//закрытие потока 3 глава




//pUndefPointer выступает в роли указателя на объект типа int.

(*(int *)pUndefPointer)++;

Для указателя на объект неопределённого типа не существует способа непосредственной перенастройки указателя на следующий объект с помощью операции инкрементации. В операторе, реализующем операции инкрементации и декрементации, только с помощью операций явного преобразования типа можно сообщить транслятору величину, на которую требуется изменить первоначальное значение указателя.

pUndefPointer++; // Это неверно, инкрементация не определена…

(int *)pUndefPointer++; // И так тоже ничего не получается…

((int *)pUndefPointer)++; // А так хорошо… Сколько скобок!

++(int *)pUndefPointer; // И вот так тоже хорошо…

С помощью операции разыменования и с дополнительной операцией явного преобразования типа изменили значение переменной mmm.

pUndefPointer = (int *)pUndefPointer + sizeof(int);

//Теперь перенастроили указатель на следующий объект типа int.

pUndefPointer = (int *)pUndefPointer + 1;

И получаем тот же самый результат.

Специфика указателя на объект неопределённого типа позволяет выполнять достаточно нетривиальные преобразования:

(*(char *)pUndefPointer)++;

А как изменится значение переменной mmm в этом случае?

pUndefPointer = (char *)pUndefPointer + 1;

Указатель перенастроился на объект типа char. То есть просто сдвинулся на 1байт.

Работа с указателями на объекты определённого типа не требует такого педантичного напоминания о типе объектов, на которые настроен указатель. Транслятор об этом не забывает.

int * pInt; int mmm = 10;

pInt = &mmm; // Настроили указатель.

pInt++; // Перешли к очередному объекту.

*pInt++; // Изменили значение объекта, идущего следом за

// переменной mmm.

Напомним, что происходит в ходе выполнения этого оператора.

  • после выполнения операции разыменования вычисляется значение (адрес объекта mmm),
  • это значение становится значением выражения,
  • после чего это значение увеличивается на величину, кратную размеру того типа данного, для которого был объявлен указатель.

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

int mmm = 10;

char ccc = 'X';

float fff = 123.45;

pInt = &mmm;

pNullInt = (int *)&ccc;

pNullInt = (int *)&fff; // Здесь будет выдано предупреждение об

// опасном преобразовании.

Это обстоятельство имеет определённые последствия, которые связаны с тем, что все преобразования над значениями указателей будут производиться без учёта особенностей структуры тех объектов, на которые указатель в самом начале был настроен.

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

 

 

5.2. Динамические массивы

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

int array[10];

происходит следующее. Программа резервирует 10 смежных ячеек памяти, по 4 байта в каждой ячейке. Именно такое количество байтов необходимо для хранения числа типа int. Кроме того, компьютер запоминает адрес первого элемента массива array[0]. Этот адрес, как мы увидим позже, хранится в указателе, которым является имя массива. Адреса всех остальных элементов не запоминаются, а вычисляются относительно адреса array[0]. То есть

 

адрес второго элемента =адресу первого элемента +4,

адрес третьего элемента =адресу первого элемента +2*4,

и т.д.

 

Все это говорит о том, что имя массива, на самом деле, представляет собой переменную указатель, которая указывает на первый элемент массива array[0]. Чтобы убедиться в этом рассмотрим маленькую программу.

#include <iostream>

using namespace std;

typedef int* pointer; /*определение типа, т.е. далее указатель типа int можно объявлять как тип pointer*/

int main(){

int array[10]; //объявляется массив с именем array

pointer point; //объявление указателя соответствующего типа

for(int i=0; i<10; i++){

array[i]=i; //заполнение массива целыми числами

cout<< array[i];

}

cout<<"\n";

point=array; //Значение одного указателя присваивается другому

for(int i=0; i<10; i++)

cout<< point[i];

return 0;

}

В результате выполнения программы получим следующее

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

Вот еще интересный пример, это фрагмент программы:

int a[10]; //объявляется массив с именем a

int i;

for (i=0; i<10; i++) {

a[i] = i; //в массив записываются числа по порядку

cout<<i[a]; //выводятся элементы несуществующего массива

}

Вот результат

Этот пример говорит о том, что для С++ массивы a[i] и i[a] – это одно и тоже. Точнее для С++ идентификаторы a[i] и i[a] это адреса, которые вычисляются как сумма адресов – адреса первой ячейки массива a и адреса сдвига i.

 

Теперь, наконец, обратимся к динамическим массивам. Динамические массивы создаются с помощью оператора new. Рассмотрим несколько примеров

//пример массива типа int

int *array1; //указатель типа int с именем array1

array1=new int[10]; /* просим дать свободный адрес для

* массива из 10-ти элементов типа.

* int, т.к объявление размера массива

* должно быть проведено до его

* использования в программе */

// пример массива типа double

double *array2;

array2=new double[10];

// пример массива типа char

typeof char* dinamicArray;

dinamicArray array3;

array3=new char[10];

 

Вот пример программы в которой используются эти массивы.

#include <iostream>

# include<windows.h>

using namespace std;

int main(){

int n;

SetConsoleOutputCP(1251);

cout<<”Размер массива=”;

cin>>n;

int *array1;

array1=new int[n+2]; //создание массива int

for(int i=0; i<n+2; i++){ //цикл для заполнения массива

array1[i]=i; //заполнение массива целыми числами

cout<< array1[i];

}

cout<<endl;

double *array2;

array2=new double[n]; //создание массива double

for(int i=n-1; i>=0; i--){

array1[i]=i+0.0;

cout<< array1[i];

}

cout<<endl;

typedef char* dinamicArray;

dinamicArray array3=new char[n]; // объявление с

// инициализацией

for(int i=0; i<n; i++){

array1[i]=i;

cout<< array1[i];

}}

А вот результаты выполнения программы:

 

Многомерный массив в С++ рассматривается как массив массивов. Например,

int array[3][4][5];

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

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

int **a=new int*[r]; //r- количество строк

Каждый из указателей, входящий в массив, является указателем на строку двумерного массива.

Далее организуется цикл для выделения памяти под каждую строку массива

for(int i=0; i<r;i++)

каждому элементу массива указателей на строки присваевается адрес начала участка памяти, выделенного под строку двумерного массива

a[i]=new int[c]; // с- количество столбцов

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

 

#include <iostream.h>

#include <windows.h>

int main(int argc, char* argv[])

{

int n = 0; //Количество строк

int m = 0; //Количество столбцов

SetConsoleOutputCP(1251);

cout << "Введите n: ";

cin >> n;

cout << " Введите m: ";

cin >> m;

//Создаем массив указателей

int** arr=new int*[n];

for (int i=0; i<n; ++i)

arr[i]=new int[m];

//Заполняем массив

for (int i=0; i<n; ++i)

{

for (int j=0; j<m; ++j)

{

cout<<"arr["<<i<<"]["<<j<<"]=";

cin>>arr[i][j];

}

}

//Поиск отрицательных чисел в столбце

cout << "В столбце:\n";

//в следующем цикле объявлены 2 переменные!

for (int i=0, index=-1; i<m; ++i)

{

for (int j=0; j<n; ++j)

if(arr[j][i]<0) index=j;

if (index!=- 1) cout<<i<<") "<<index<<endl;

}

cout << "В строке:\n";

for (int i = 0, index=-1; i<m; ++i)

{

for (int j = 0; j<n; ++j)

if (arr[i][j]<0) index=j;

if (index!= - 1) cout<<i<<") "<<index<<endl;

}

//Удаляем массив

//сначала удаляются указатели на столбцы

for (int i=0; i<n; ++i) delete [] arr[i];

//потом указатели на строки

delete [] arr;

_flushall(); //очистка всех буферов

char z;

cin>>z;return 0;

}

 

 

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

 

int *array[5]; /*массив с именем array, его элементы указатели*/

означает, что в программе используется одномерный массив из указателей на объекты типа int. Имя каждого указателя array[0], array[1], array[2], array[3], array[4].

С массивами указателей следует быть очень внимательным. Так если эту строку записать так:

 

int (*array)[5];

то компилятор будет рассматривать ее как указатель на безымянный массив из 5-ти элементов типа int. Имени у массива нет, но имеется адрес, который находится в указателе array.

Рассмотрим пример:

#include <iostream>

using namespace std;

void main(){

int (*point)[4][5]; /* объявление указателя на двумерный массив без имени */

point=new int[3][4][5]; /* команда на выделение памяти для трехмерного массива или иначе массива состоящего из 3-х массивов 4×5 */

for(int i=0;i<3;i++)

for(int j=0;j<3;j++)

for(int k=0;k<3;k++)

{

point[i][j][k]=100*i+10*j+k;

cout<<"point["<<i<<","<<j<<","<<k<<"]="<< point[i][j][k]<<endl;

}

char z;

cin>>z;

}

 

 

Второй пример

 

#include <iostream.h>

int fdArr(int **, int, int);

int fdArr(int ***, int, int, int);

// Одноимённые функции. Различаются списками списками параметров.

// Это так называемые перегруженные функции. О них позже.

Void main()

{

int i, j;

/* Переменные (!) для описания характеристик массивов.*/

int dim1 = 5, dim2 = 5, dim3 = 10, wDim = dim2;

/*

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

Сначала создаётся одномерный массив указателей, а затем каждому элементу этого массива присваивается адрес одномерного массива. Для характеристик размеров массивов не требуется константных выражений.

*/

int **pArr = new int*[dim1];

for (i = 0; i < dim1; i++) pArr[i] = new int[dim2];

pArr[3][3] = 100;

cout << pArr[3][3] << endl;

fdArr(pArr,3,3);

/*

Последовательное уничтожение двумерного массива…

*/

for (i = 0; i < dim1; i++) delete[]pArr[i];

delete[]pArr;

/*

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

*/

int **pXArr = new int*[dim1];

for (i = 0; i < dim1; i++, wDim--) pXArr[i] = new int[wDim];

pXArr[3][3] = 100;

cout << pArr[3][3] << endl;

fdArr(pXArr,3,3);

/*

Последовательное уничтожение двумерного массива треугольной конфигурации…

*/

for (i = 0; i < dim1; i++) delete[]pXArr[i];

delete[]pXArr;

/*

Создание и уничтожение трёхмерного массива требует дополнительной итерации.

Однако здесь также нет ничего принципиально нового.

*/

int ***ppArr;

ppArr = new int**[dim1];

for (i = 0; i < dim1; i++) ppArr[i] = new int*[dim2];

for (i = 0; i < dim1; i++)

{

for (j = 0; j < dim2; j++) ppArr[i][j] = new int[dim3];

}

ppArr[1][2][3] = 750; cout << ppArr[1][2][3] << endl; fdArr(ppArr,1,2,3);

for (i = 0; i < dim1; i++)

{

for (j = 0; j < dim2; j++) delete[]ppArr[i][j];

}

for (i = 0; i < dim1; i++) delete[]ppArr[i];

delete[] ppArr;

}

int fdArr(int **pKey, int index1, int index2)

{

cout << pKey[index1][index2] << endl;

}

int fdArr(int ***pKey, int index1, int index2, int index3)

{

cout << pKey[index1][index2][index3] << endl;

}

 

 

Многомерный массив в C++ по своей сути одномерен. Операции new[] и delete[] позволяют создавать и удалять динамические массивы, поддерживая при этом иллюзию произвольной размерности. Деятельность по организации динамического массива требует дополнительного внимания, которое окупается важным преимуществом: характеристики массива (операнды операции new) могут не быть константными выражениями. Это позволяет создавать многомерные динамические массивы произвольной конфигурации.

 

Использование указателей в функциях и указатели на функции

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

Рассмотрим задачу умножения двух векторов.

#include <iostream>

#include <windows.h>

using namespace std;

double multVect(int n,double x[],double y[]){ /*скалярное произведение*/

double z=0;

for(int i=0;i<n;i++) z=z+x[i]*y[i];

return z;

}

void main(){

SetConsoleOutputCP(1251);

double u[]={1.0, 2.0, 3.0}, v[]={4.0, 5.0, 6.0};

cout<<”Произведение векторов=”<<multVect(3,u,v);

}

Иногда возвращаемое значение функции должно быть указателем. Рассмотрим программу на примере объединения массивов.

#include <iostream>

#include <windows.h>

using namespace std;

//объявляем и описываем фунцию

double *connectVect(int n, double* x, int m, double* y){ int i;

double *z=new double[n+m]; //выделяем память

for(i=0;i<n;i++) z[i]=x[i]; //заносим в память

for(i=n;i<n+m;i++) z[i]=y[i-n];

return z;

}

void main(){

SetConsoleOutputCP(1251);

double u[]={1.0, 2.0, 3.0}, v[]={4.0, 5.0, 6.0, 7.0, 8.0}, *w;

w=connectVect(3,u,5,v);

cout<<"Объединение массивов"<<endl;

for(int i=0;i<8;i++)

cout<<w[i]<<endl;

}

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

тип_функции (*имя_указателя)(список_спецификаций_параметров);

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

int (*multiplay)(int, int);

Обратите внимание, что имя указателя записывается в круглых скобках, в противном случае компилятор поймет его как прототип функции multiplay, которая возвращает значение указателя типа int.

В качестве примера вновь запишем программу для произведения векторов.

#include <iostream>

#include <windows.h>

using namespace std;

double multVect(int n,double x[],double y[]){ /*объявление функции*/

double z=0;

for(int i=0;i<n;i++) z=z+x[i]*y[i];

return z;

}

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

void main(){

SetConsoleOutputCP(1251);

double u[]={1.0, 2.0, 3.0}, v[]={4.0, 5.0, 6.0};

double (*ptrFun)(int, double[], double[]); /*объявление указателя с именем ptrFun */

ptrFun=multVect; /*в новый указатель записывается адрес функции */

cout<<"Произведение векторов="<<ptrFun(3,u,v)<<endl;

cout<<"Произведение векторов="<<(ptrFun)(3,u,v)<<endl; /* При вызове функции через указатель его имя можно взять в скобки */

}

 

 

Функция main()

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

Стандарт функции main() допуcкает два формата – без параметров,

тип main(){/*тело функции*/}

и с двумя параметрами

тип main(int argc, char* argv[]){/*тело функции*/}

Имена параметров в программе могут быть любыми, но общепринятыми являются argc и argv.

Первый параметр argc определяет количество аргументов, второй argv является указателем на массив указателей типа char. Каждый элемент массива это указатель на отдельный параметр командной строки, хранящейся в виде С-строки, оканчивающейся нуль-символом. Первый элемент массива argv[0] ссылается на полное имя запускаемого на выполнение файла, и поэтому количество элементов массива не может быть меньше 1. Следующий argv[1] указывает на первый параметр, argv[2] указывает на второй параметр и т.д. Параметр argv[argс] должен быть равен нулю.

Если функция main() выполнилась до конца, то вызвавшая ее система получит значение 0, означающее успешное завершение. Ненулевое значение означает аварийное завершение. Оператор возврата return из main() можно опускать. Вот простой пример, иллюстрирующий сказанное.

Наберите следующую программу.

#include<iostream.h>

#include<windows.h>

//#include<string.h>

int main(int argc,char* argv[]){

SetConsoleOutputCP(1251);

SetConsoleCP(1251);

if(argc!=2){

cout<<"Вы забыли указать свое имя.\n";

exit(1);

}

cout<<"Привет!"<<argv[1];

return 0;

}

Откомпилируйте и сохраните ее под каким-нибудь именем. Если вы используете Builder, то для сохранения воспользуйтесь File/save All. Далее воспользуйтесь каким-нибудь файловым менеджером, например, Windows Commander, Far или Volkov Commander. Найдите свой файл. Пусть его имя будет Project2.exe, и запустите его. Если после запускаемого файла указать свое имя, то программа выведет приветствие

 

 

 

Указатель классов

Иногда приходится создавать динамические массивы элементами которых являются объекты некого класса. Вкачестве примера рассмотрим класс Book

 

#include<iostream.h>

#include<windows.h>

#include<string.h>

class Book{

public:

void show_title();

void show_book();

Book(char *title,char *author,char *publisher, float price);

~Book();

private:

char title[256];

char author[64];

float price;

char publisher[256];

void show_publisher();

};

 

int main(int argc,char* argv[]){

SetConsoleOutputCP(1251);

SetConsoleCP(1251);

Book *library[5];

library[0]=new Book("Война и мир","Толстой","Советская литература", 3);

library[1]=new Book("Java в примерах","Флэнаган","Символ", 500);

library[2]=new Book("C/C++","Шилдт","Вильямс", 150);

library[3]=new Book("Техническая диагностика","Глущенко","Вузовская книга", 200);

library[4]=new Book("Язык СИ++","Подбельский","Финансы и статистика", 130);

for(int i=0;i<5;i++)

library[i]->show_book();

/*Удаление одного экземпляра для освобождения динамической памяти*/

for(i=0;i<5;i++)

delete library[i];

return 0;}

Book::Book(char *title,char *author,char *publisher, float price){

strcpy(Book::title,title);

strcpy(Book::author,author);

strcpy(Book::publisher,publisher);

Book::price=price;

}

void Book::show_title(){

cout<<title<<" "<<author<<endl;

}

void Book::show_book(){

show_title(); show_publisher();}

Book::~Book(){

cout<<"Разрушенте объекта"<<endl;}

void Book::show_publisher(){

cout<<publisher<<endl;}

 

Шаблоны



Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2016-02-16 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: