Уроки по C/C++ (Части с 21 по 40) - C, C++, C# - Программирование
Навигация по сайту
Сайт:

Дополнительно:

Файловый архив:

Каталог статей:

Форум:


Категории раздела
Delphi, Pascal, ObjectPascal [18]
Программирование на Delphi, Pascal, ObjectPascal
C, C++, C# [7]
Программирование на C, C++, C#
ПХП (PHP) [6]
Все что связано с программированием на PHP.
DirectX [0]
Программирование с использованием графического API DirectX
OpenGL [0]
Программирование с использованием графического API OpenGL
Работа с базами данных (БД) [0]
Работа с базами данных MySQL и т.д. Разработка, теории, алгоритмы.
Сетевое программирование [0]
Сетевое программирование, организация сетей.
Программирование игр [0]
Все что связано с программированием игр, организацией их разработки.
Работа с мультимедиа данными [0]
Загрузка, обработка, воспроизведение и все что связано со звуком и видео.
Работа с устройсвами ввода и вывода [0]
Программирование устройств ввода и вывода. Работа с геймпадом, рулем и многим другим.
Программирование HTML 5 игр [0]
Программирование HTML 5 игр, html верстка, JS (JavaScript)
Остальное [0]
Все остальное, что не попадает ни под одну категорию.

Мини-Опрос
Какова Ваша специальность?
Всего ответов: 1057

Партнеры сайта
....

 Главная » Статьи » Программирование » C, C++, C# » Уроки по C/C++ (Части с 21 по 40)

Уроки по C/C++ (Части с 21 по 40)

21:01
Часть 21. Передача массива как параметра функции
Массивы в качестве параметра функции передаются по ссылке. Это означает, что если вы внутри такой функции изменяете элементы массива, то эти изменения будут внесены и в оригинал массива. Вот пример:

#include
void f(int arr[2]){
// Изменяем внутри функции массив.
arr[0]=11;
arr[1]=12;
}
void main(){
int a[2]={1, -1};
// Вызов функции.
f(a);
// Выводим элементы массива.
cout< cout< }

Указанный фрагмент выведет числа 11 и 12. Обратите внимание, что для массивов, в отличие от отдельных переменных (т. е. тех, которые не являются массивами), не надо писать знак амперсанда (&). Вообще говоря, в таком поведении массивов ничего удивительного нет - как вы знаете, имя массива (а именно оно и передается в функцию) есть адрес начала массива. Так что мы передаем адрес массива, т. е. его оригинал.

Часть 22. Шаблоны функций

Шаблоны служат для ситуции, когда у нас есть разные типы данных, с которыми мы должны работать по одному, не зависящему от этих типов данных, алгоритму. Например, метод сортировки массива не зависит от типов данных - такой алгоритм будет одинаков и для, например, чисел типа int и для чисел типа float. Приведем пример использования шаблона. Вот код для простейшего щаблона функции, которая из двух передаваемых в нее параметров возвращает максимальный:

#include
//Объявление шаблона функции.
template
T max(T a, T b)
{
if(a>b)
{
return a;
}
else
{
return b;
}
}
void main()
{
//Использование шаблона функции для целых.
int x = 45, y = 32;
cout< //Использование шаблона функции для вещественных.
float s = 4.18, t = 34.08;
cout< }

Обратите внимание на синтаксис. Сначала мы пишем ключевое слово template и задаем формальный тип T (T - это просто произвольное имя):
template
...
Далее мы пишем непосредственно функцию, причем в качестве типа параметров и возвращаемого значения пишем введенный ранее формальный тип T:
T max(T a, T b)
...
Разумеется, количество параметров может быть любое и не все из них должны иметь тип T. То же относится и к типу возвращаемого значения - в нашем случае это тот же тип T, но в принципе тип может быть любой, хоть void. Также обратите внимание, что заданием формального параметра и объявлением класса не должно быть никаких операторов. Теперь пара слов о использовании шаблона. При использовании мы просто пишем имя функции (в нашем случае это max) с параметрами конкретных типов (int и float в нашем примере). Компилятор в этом месте сам сгенерирует на основании этого шаблона функцию с параметрами конкретных типов. Шаблоны существую не только для функций, но и для классов. Такие шаблоны мы рассмотрим позже.

Часть 23. Пример шаблона функции

Вот еще конкретный пример на шаблон функции. Этот шаблон ищет в массиве определенный элемент и если такой элемент есть, то шаблон функции возвращает номер этого элемента, а если такой элемент не найден, то возвращается -1.
#include
//Объявление шаблона функции.
template
int find(T *arr, T a, int n)
{
for(int i=0; i {
if(arr[i]==a)
{
return i;
}
}
return -1;
}
void main()
{
//Использование шаблона функции для целых.
int z[4] = {5, 7, -2, 66};
int res;
res = find(z, 66, 4);
cout< //Использование шаблона функции для булевских.
bool b[3] = {true, true, true};
res = find(b, false, 3);
cout< }
У нашего шаблона 3 параметра: первый - это массив, в котором мы ищем наш элемент (помните, что имя массива - это указатель на нулевой элемент массива, поэтому мы и пишем T *arr), второй параметр шаблона - это искомый элемент, и третий - количество элементов в массиве.

Часть 24. Введение в классы

В 8 части мы с вами рассматрисали структуры. Классы чем-то напоминают структуры - у них также внутри есть переменные разных типов. Но наряду с этими переменными у класса есть и несколько отличий, которые мы сейчас и рассмотрим на примере. Вот пример класса:
#include
//Объявление класса прямоугольника.
class CRect
{
float m_a, m_b; //Стороны.
public:
//Методы класса.
//Методы по чтению и записи сторон.
void SetA(float a);
void SetB(float b);
float GetA();
float GetB();

float GetSquare(); //Площадь.
float GetPerim(); //Периметр.
bool IsSquare(); //Не является ли прямоульник квадратом.
}; //Точка с запятой обязательна!
void main()
{
//Использование класса.
CRect r;
r.SetA(5);
r.SetB(3);
cout<<"Perimeter = "< cout<<"Square = "< if(r.IsSquare())
{
cout<<"Square\n"; //Квадрат.
}
else
{
cout<<"Not a quare\n"; //Не квадрат.
}
}
//Реализация методов класса.
//Методы по чтению и записи сторон.
void CRect::SetA(float a)
{
if(a>0)
{
m_a = a;
}
else
{
m_a = 1;
}
}
void CRect::SetB(float b)
{
if(b>0)
{
m_b = b;
}
else
{
m_b = 1;
}
}
float CRect::GetA()
{
return m_a;
}
float CRect::GetB()
{
return m_b;
}
//Площадь.
float CRect::GetSquare()
{
return m_a*m_b;
}
//Периметр.
float CRect::GetPerim()
{
return (m_a+m_b)*2;
}
//Не является ли прямоульник квадратом.
bool CRect::IsSquare()
{
return (m_a==m_b);
}
После запуска программы выведет, что периметр равен 16, площадь - 15 и что это - не квадрат. Этого, собственно, и следовало ожидать. Как вы видите, у класса есть несколько особенностей. Во-первых, у него есть функции (вообще говоря, те функции, которые принадлежат классу, называются методами). Во-вторых, его части имеют разный тип доступа (у нас это public и private (последний тип доступа является типом доступа по умолчанию и слово private можно не писать, что мы и сделали для переменных m_a и m_b)). В методе main мы создали так называемый экземпляр класса (с именем r) - можно считать, что это конкретный прямоугольник. Экземпляр класса объявляется аналогично объявлению обычной переменной. Потом мы в main вызываем функции (методы) класса для нашего экземпляра r. Дальнейший разбор класса мы произведем на следующем уроке.

Часть 25. Разбор первого класса

На этом уроке мы с вами разберем наш первый класс, написанный на прошлом уроке. Итак, для объявления класса мы используем следующую конструкцию:
class MyClass
{
//Внутренность класса.
};
Тут вместо MyClass мы можем написать, разумеется, любое имя. Обратите так же внимание на точку с запятой в конце класса - она обязательна. Объявление класса - это как бы конструирование нового типа данных. В C/C++ есть встроенные типы данных - int, char, float и другие. Но для реальной задачи удобнее создать свои типы, которые будет лучше моделировать поставленную задачу. Классы как раз для этого и предназначены. Теперь несколько слов о том, что может находиться внутри класса. А именно, там могут находиться переменные разных типов и функции (они же методы) класса. Переменные могут быть самых разных типов - в том числе и экземпляры других классов (и даже экземпляры того же самого класса). Вообще внутренность класса делится на 3 части - public (доступна всем), private (доступна только самому классу) и protected (доступна классу и его потомкам (об этом мы будем говорить на следующих уроках)). Слово private можно не писать - оно действует по умолчанию. Т. е. наши переменные m_a и m_b нахадятся в private части класса:
class CRect
{
float m_a, m_b; //Стороны.
...
Зачем мы поместили m_a и m_b в private часть класса? Так как на их значения существуют ограничения - а именно они не могут быть отрицательные. Поэтому для доступа к этим переменным мы добавили по две функции GetA/B - для чтения и SetA/B - для записи:
...
void SetA(float a);
void SetB(float b);
float GetA();
float GetB();
...
Эти функции мы объявили, разумеется, в public части класса. Обратите внимание, что функции мы только объявили, а реализацию функций мы пишем вне класса:
...
void CRect::SetA(float a)
{
if(a>0)
{
m_a = a;
}
else
{
m_a = 1;
}
}
...
При этом мы перед именем функции пишем обязательно имя класс с двойным двоеточием (для того, что бы показать, что эта функция именно из нашего класса):
void CRect::SetA(...)
...
Функции SetA/B мы написали так, что они позволяют записать только положительно значение. Если в такую функцию мы передадим для стороны отрицательное значение, то запишется не оно, а единица. С функциями, вычисляющими площадь, периметр и выясняющими, не есть ли наш прямоугольник квадратом, тоже все ясно - они используют соответствующие формулы из математики и возвращают нужное значение посредством return. В функции main мы создаем экземпляр нашего класса:
void main()
{
//Использование класса.
CRect r;
...
и затем с этим экземпляром работаем - устанавливаем для него значения сторон A и B, считаем периметр и др.:
...
r.SetA(5);
r.SetB(3);
cout<<"Perimeter = "< cout<<"Square = "< ...
Обратите внимание, что функции мы вызываем не сами по себе, а именно для нашего экземпляра класса r. Синтаксис тут такой: имя экземпляра, точка, имя функции:
r.SetA(5);
Можно в программе объявить несколько экземпляров класса или даже массив:
CRect w, v;
CRect z[5];
Все такие экземпляры будут независимы друг от друга.

Часть 26. Конструкторы и деструкторы

Конструкторы и деструкторы - это специальные методы класса. Это надо понять в первую очередь. Разумеется, эти методы обладают целым рядом особенностей (именно по этому они и выделены в специальную группу). Сейчас мы об этих особенностях и поговорим. Первое. В отличие от других методов они должны называться особым образом. Если обычные методы могут называться как угодно, то имя констуктора должно совпадать с именем класса, а имя деструктора - с именем класса с приписанной в начале тильдой. Например, если класс называется CRect, то конструктор этого класса обязан называться тоже CRect, а деструктор - ~CRect. Второе. В отличии от других методов конструктор и деструктор вызываются сами (а другие методы мы вызываем явным образом). Конструктор вызывается в момент создания экземпляра класса, а деструктор - в момент уничтожения. Т. е. их не надо вызывать явным образом - они вызываются сами. Именно поэтому конструкторы обычно используются для задания некоторых начальных значений для переменных класса, а деструкторы - для освобождения памяти (в случае если у вас есть внутри класса переменные-указатели). Третье. Ни конструктор, ни деструктор не возвращают никакого значения (даже типа void). Это означает, в частности, что при обяъвлении конструтора и деструктора в классе мы перед ними не пишем ни какой тип. Четвертое. В классе может быть несколько конструкторов (и они должны различаться параметрами), и только один деструктор (у него параметров вообще быть не может). Вот пример на все вышесказанное (этот пример основан на части 24):
class CRect
{
float m_a, m_b; //Стороны.
public:
//Конструкторы и деструктор.
CRect(); // Конструктор без параметров.
CRect(float a. float b); // Конструктор с двумя параметрами.
~CRect(); // Деструктор.
...
};
...
// Реализация конструкторов и деструкторов.
CRect::CRect()
{
// Задание стандартных (нулевых) значений.
m_a = 0;
m_b = 0;
}
CRect::CRect(float a. float b)
{
// Задание значений, задаваемых параметрами.
m_a = a;
m_b = b;
}
CRect::~CRect()
{
// Просто вывод некоторой надписи.
cout<<"Destructor\n";
}

А вот так можно использовать класс в функции main:
void main()
{
//Использование класса.

CRect r; // Вызов конструктора без параметров.
cout<<"Perimeter = "< cout<<"Square = "< CRect r1(2, 3); // Вызов конструктора с параметрами.
cout<<"Perimeter = "< cout<<"Square = "< // В этом месте вызовутся 2 деструктора - для переменных r и r1.
// Соответственно, на консоль выведется два раза слово "Destructor".
}

В приведенном примере методы GetPerim и GetSquare мы берем из части 24.

Часть 27. Классы и указатели

На прошлых уроках мы видели только один способ объявления экземпляров класса - а именно мы писали что-то вроде:
CMyClass z;
Вообще же говоря экземпляры класса лучше во многих случаях заводить не в стеке, а в куче (at heap - подробности см. в части 15. Это, в частности, связано с тем, что стек - вещь довольно-таки ограниченная по объему, классы же часто представляют из себя довольно-таки большие и сложные объекты, и если вы будете размещать в стеке что-то вроже массива экземпляров класса, то объема стека просто может не хватить (хотя, конечно, для учебных программм это случится навряд ли). Так же как и для встроенных типов данных для размещения экземпляра класса в куче использется оператор new. Именно он и занимается выделением памяти. Вот пример:
CMyClass * z;
z = new CMyClass; // Непосредственное выделение памяти.
Конструктор класса при этом вызовется на второй строке, т. е. в операторе new. Эти два оператора можно объединить:
CMyClass * z = new CMyClass; // Непосредственное выделение памяти.
Если в классе есть конструктор с параметрами, то он вызывается примерно так:
// Вызов конструтора с одним параметром.
CMyClass * z = new CMyClass(22);
// Вызов конструтора с двумя параметрами.
CMyClass * z = new CMyClass(22, 44);
Еще одно отличие между двумя способами создания экземпляра класса (в стеке или в куче) - это вызов методов класса. Если при создании экземпляра в стеке между именем экземпляра и именем метода используется точка, то при создании экземпляра класса в куче (т. е. через оператор new) используется стрелочка. Вот пример:
CMyClass * z = new CMyClass;
z->SomeMethod();

Часть 28. Практика: список - добавление элементов

С этого урока мы с вами применим полученные знания на практике - а именно, мы создадим динамический список. В него можно будет добавлять элементы (экземпляры некоторого класса CData), показывать все элементы списка, удалять элементы, сортировать, выяснять, есть ли определенный элемент в списке или нет и делать другие стандартные для списка операции. Итак, начинаем. Сначала напишем класс CData:
// Класс данных.
class CData
{
public:
// Собственно данные класса.
int a;
int b;
// Указатель на следующий экземпляр класса CData.
CData * m_pNext;

// Конструкторы класса.
CData()
{
a = 0;
b = 0;
m_pNext = NULL;
}
CData(int a1, int b1)
{
a = a1;
b = b1;
m_pNext = NULL;
}
};
Как вы видите, класс небольшой - в нем всего пара переменных для данных (a и b) и указатель m_pNext типа CData * - он показывает на слудующий элемент в нашем списке. Т. е. первые данные в списке (а данные в нашем случае - это тоже самое, что и экземпляр класса CData) указывают на следующие, те - на следующие и т. п. Последние ни на что показывать не будут - у них в поле m_pNext записан NULL (это мы делаем в конструкторе класса). Теперь начинаем писать класс списка. Назовем его естественным образом - CList. На этом уроке мы напишем только метод по добавлению элемента в наш список. Вот так пока будет выглядеть наш класс:
//Класс списка.
class CList
{
public:
// Указатель на первый элемент списка.
CData * m_pFirst;
// Конструктор.
CList();
// Добавление данных в конец списка.
void Add(CData * pNext);
// Поиск последнего элемента в списке.
CData * GetLast();
};
Обратите внимание, что мы в наш класс ввели переменную m_pFirst типа CData *. Это указатель на первый элемент списка. Он же внутри себя (см. класс CData) содержит указатель на следующий и т. д. А вот реализация конструктора и методов класса CList:
// Реализация методов класса списка.
CList::CList()
{
m_pFirst = NULL;
}

void CList::Add(CData * pNext)
{
// Ищем последний элемент в списке
// и приписываем pNext к нему.

// Если список не пуст.
if(m_pFirst!=NULL)
{
GetLast()->m_pNext = pNext;
}
else
{
// Если список пуст, то
// добавлем данные в начало списка.
m_pFirst = pNext;
}
}

CData * CList::GetLast()
{
// Указатель на очередные данные в списке.
CData * pCurr = m_pFirst;
// Если список пуст, то возвращаем NULL.
if(pCurr == NULL)
{
return NULL;
}
// Пока есть следующий элемент списка.
while(pCurr->m_pNext!=NULL)
{
pCurr = pCurr->m_pNext;
}
return pCurr;
}
Как вы видите, в конструкторе мы просто обнуляем указатель на первый элемент списка (m_pFirst = NULL). Далее мы пишем метод Add, который добавляет передаваемые в параметре данные к списку. Добавляем мы новые данные в конец списка, который получаем методом GetLast, тело которого тоже приводится. Также обратите внимание на то, что мы особым образом перед добавлением или перед поиском последнего элемента в списке обрабатываем ситуацию, когда в списке вообще нет пока элементов.

Часть 29. Показ элементов списка

Напишем метод класса CList, который будет выводить на консоль все элементы нашего списка. Для этого воспользуемся циклом while, так как мы не знаем, сколько всего элементов нам надо вывести. Добавьте в класс CList объявление метода ShowAll:
class CList
{
public:
...
// Показ всех элементов списка.
void ShowAll();
};
Реализация у метода будет такая:
void CList::ShowAll()
{
// Указатель на очередные данные в списке.
CData * pCurr = m_pFirst;
// Если список пуст, то это и напишем.
if(pCurr == NULL)
{
cout<<"List is empty.\n";
}
// Пока есть следующий элемент списка.
do{
// Выводим на консоль очередной элемент.
cout<<"a="<a<<", b="<b<<"\n";
// Переводим pCurr на следующий элемент в списке.
// Если он равен NULL, то заканчиваем.
}while((pCurr = pCurr->m_pNext)!=NULL);
}
Теперь можно испытать наш класс списка в работе. Например, вот так:
void main(){
CList list;
CData *pData;
int a, b;
for(int i = 0; i<3; i++)
{
cout<<"Enter a:";
cin>>a;
cout<<"\n";
cout<<"Enter b:";
cin>>b;
cout<<"\n";
pData = new CData(a, b);
list.Add(pData);
list.ShowAll();
}
}
Теперь можно запускать программу. Три раза мы будем вводить данные a и b для каждого нового элемента, добавляемого в список, и каждый раз после этого весь список будет выводиться на экран.

Часть 30. Ищем элемент в списке

Продолжаем писать наш класс. В этом уроке мы с вами напишем метод FindData нашего класса, который находит некоторый элемент в нашем списке. Элемент будет задаваться параметрами - так как в каждом элементе списка два параметра (a и b), то и у метода FindData будет два параметра. Вот тело метода:
CData * CList::FindData(int a, int b)
{
// Указатель на очередные данные в списке.
CData * pCurr = m_pFirst;
// Если список пуст, то возвращаем NULL.
if(pCurr == NULL)
{
return NULL;
}
// Обходим все элементы списка.
do{
// Если нашли искомый элемент, то его и возвращаем.
if(pCurr->a == a && pCurr->b == b)
{
return pCurr;
}
// Переводим pCurr на следующий элемент в списке.
// Если он равен NULL, то заканчиваем.
}while((pCurr = pCurr->m_pNext)!=NULL);

// Если ничего не нашли, то возвращаем NULL.
return NULL;
}
Алгоритм метода ясен из комментариев. Если искомый элемент найден, то возвращается указатель на него, если нет, то возвращается нулевой указатель NULL. Дополнить код в функции main для проверки можно, например, так:
void main(){
...
// Есть ли в списке элемент с a = 22 и b = 2?
if(list.FindData(22, 2) == NULL)
{
cout<<"Nothing\n";
}
else
{
cout<<"Find\n";
}
}
Если мы при запуске программы зададим элемемент нашего списка с a равным 22 и b равным 2, то программа выдаст на консоль "Find", в противном случае - "Nothing".

Часть 31. Пара вспомогательных методов для списка

Сейчас мы с вами напишем пару вспомогательных методов для нашего класса списка. Внесите в заголовочный файл класса списка объявления наших методов:
//Класс списка.
class CList
{
public:
// Не пустой ли наш список?
bool IsEmpty();
// Предыдущий элемент в списке.
CData * GetPrev(CData * p);
...
Первый метод короткий - с него и начнем. Он просто отвечает на вопрос, не пуст ли наш список. Вот его код:
bool CList::IsEmpty()
{
return m_pFirst==NULL;
}
Второй метод возвращет по указателю на передаваемый в параметре элемент списка предыдущий для него (такой метод может оказаться весьма полезным, так как наш класс CData содержит только указатель на следующий элемент, а на предыдущий - нет). Вот его код:
CData * CList::GetPrev(CData *p)
{
// Если предыдущего элемента списка нет
// (т. е. наш элемент совпадает с первым)
// то возвращем NULL.
if(p==m_pFirst)
return NULL;
// Если предыдущий элемент есть.
CData * pCurr = m_pFirst;
do{
// Если для очередного элемента в списке
// следующий для него элемент есть
// передаваемый нами в параметре,
if(pCurr->m_pNext==p){
// то и возвращаем этот очередной элемент.
return pCurr;
}
// Переводим pCurr на следующий элемент в списке.
}while((pCurr = pCurr->m_pNext)!=NULL);
return NULL;
}
Этими методами мы воспользуемся на следующих уроках.

Часть 32. Удаление элемента из списка

Рассмотрим стандартную задачу - удаление элемента из списка. Алгоритмически мы должны рассмотреть два случая - когда удаляем самы первый элемент из списка и когда удаляем элемент из середины или конца списка. Вот код для нашего метода (не забудьте внести объявление этого метода в сам класс):
bool CList::RemoveData(int a, int b)
{
// Если список пуст
if(IsEmpty())
// то выходим.
return false;
// Получаем элемент для удаления.
CData * pData = FindData(a, b);
// Если такого элемента в списке нет
if(pData == NULL)
// то выходим.
return false;
// Если удаляемый элемент - первый в списке.
if(pData == m_pFirst){
// Делаем первым следующий за ним элемент списка.
m_pFirst = pData->m_pNext;
// Освобождаем память, которую занимал удаляемый элемент.
delete pData;
// И выходим.
return true;
}
// Если удаляемый элемент - не первый в списке.
// То делаем так, чтобы элемент, находящийся в списке
// перед удаляемым, показывал на следующий за удаляемым элемент.
GetPrev(pData)->m_pNext = pData->m_pNext;
// Освобождаем память, которую занимал удалаемый элемент.
delete pData;
// И выходим.
return true;
}
Код должен быть ясен из комментария. Если удаление произошло, то возвращяем true, если нет, то false. В этом методе мы использовали другие методы (GetPrev, FindData), написанные на прошлых уроках.

Часть 33. Функция с переменным числом параметров

Вот пример функции, принимающий переменное число параметров. Пример классический - а именно наша функция возвращает сумму своих параметров. Обратите внимание, что первым параметром мы передаем число чисел для суммирования (т. е. сам первый параметр суммироваться не будет, он говорит только, сколько всего параметров будут суммироваться (это все оставшиеся параметры)).
#include
// Задаем функцию с переменным числом параметров.
int sum(int n, ...)
{
// Получаем адрес первого параметра.
int *p = &n;
// Переводим указатель на второй параметр.
p++;
// Объявляем переменную для суммы
// и присваиваем ей ноль.
int res = 0;
// Сумирование оставшихся параметров.
for(int i=0; i // Добавление к сумме очередного параметра.
res+=(*p);
// Первод указателя на следующий параметр.
p++;
}
// Возврат суммы.
return res;
}

void main(){
int r = 0;
// Суммируем 5 чисел.
r = sum(5, 1, 2, 3, 4, 500);
cout<<"Sum = "< }

Переменное число параметров в функции обозначаается посредством многоточия (...). В нашем объявлении функции мы указываем, что обязотельно должен присутствовать первый параметр (типа int), после которого может быть любое число параметров любого типа. Внутри функция устроена так - мы получаем адрес в адресном пространстве, по которому расположены передаваемые в функцию параметры. Это адрес первого параметра:
...
int *p = &n;
...
Далее мы перебираем все параметры (а всего их n) через указатель - он постоянно переводится на следующий параметр посредством строки:
...
p++;
...
Результатом выполнения указанного фрагмента будет 510.

Часть 34. Считаем элементы в списке

На этом и следующем уроках мы добавим еще пару вспомогательных методов для нашего списка - которыми мы воспользуемся на следующем уроке. Это будут методы GetCount() - он возвратит общее число элементов нашего списка и Change(CData * p) - этот метод поменяет 2 элемента списка - а именно тот, который мы передадим в него в качестве параметра и следующий за ним. Сначала добавьте объявление метода GetCount в класс:
class CList
{
public:
...
// Общее число элементов.
int GetCount();
...
Напишите реализацию этого метода:
int CList::GetCount()
{
// Указатель на очередные данные в списке.
CData * pCurr = m_pFirst;
// Если список пуст, то возвращаем 0.
if(pCurr == NULL)
{
return 0;
}
int count = 1; //Число элементов в списке.
// Пока есть следующий элемент списка.
while(pCurr->m_pNext!=NULL)
{
// Переходим на следуюющего.
pCurr = pCurr->m_pNext;
// Увеличиваем счетчик.
count++;
}
return count;
}
Реализация тут похожая на реализации других методов - мы пробегаем до конца списка, по дороге подсчитывая число элементов.

Часть 35. Обмен соседних элементов в списке

Продолжаем работать с нашим класом списка. Этот урок мы посвятим еще одному вспомогательному методу для нашего класса списка - а именно методу, меняющему два соседних элемента. Какие элементы будут меняться - это будет определяться одним параметром, который и будет задавать пару для обмена - некий элемент и следующий за ним. Добавьте в класс метод с подходящим для этой цели названием - Change:
class CList
{
public:
...
// Обмен элемента p и следующего.
void Change(CData * p);
...
А вот реализация для метода Change(CData * p):
void CList::Change(CData *p)
{
// Если следующего элемента просто нет.
if (p->m_pNext==NULL)
{
// то выходим.
return;
}
// Переменная для предыдущего элемента.
CData * pPrev;
if (p==m_pFirst)
{
// Если наш элемент - первый.
// Обмен указателей через вспомогательную переменную.
pPrev = m_pFirst;
CData * pAux = m_pFirst;
CData * pNext = p->m_pNext;
m_pFirst = p->m_pNext;
p->m_pNext = p->m_pNext->m_pNext;
pNext->m_pNext = pAux;
}
else
{
// Если наш элемент - не первый.
// Обмен указателей через вспомогательную переменную.
pPrev = GetPrev(p);
CData * pAux = pPrev->m_pNext;
CData * pNext = p->m_pNext;
pPrev->m_pNext = p->m_pNext;
p->m_pNext = p->m_pNext->m_pNext;
pNext->m_pNext = pAux;
}
}
Метод обмена достаточно прост - мы меняем значения указателей в полях m_pNext элементво нашего списка. Это мы делаем посредством вспомогательных переменных. При этом у нашего алгоритма 2 части - одна для случая, когда наш элемент первый в списке и другая - когда он в списке не первый.

Часть 36. Получаем элемент списка по его номеру

Наш метод, который будет получать указатель на элемент списка по его номеру, будет называться GetAt. Добавьте его объявление в класс:
class CList
{
public:
...
// Взятие n-го элемента.
CData * GetAt(int n);
...
Реализация у него будет такая:
CData * CList::GetAt(int n)
{
// Указатель на очередные данные в списке.
CData * pCurr = m_pFirst;
// Если список пуст
// или элемента с таким номером просто нет,
// то возвращаем NULL.
if(pCurr == NULL || n>GetCount()-1)
{
return NULL;
}
// Добираемся до элемента с нужным номером.
for(int i=0; i {
if(i==n)
{
return pCurr;
}
pCurr = pCurr->m_pNext;
}
return NULL;
}
Алгоритм тут прост и естественен - мы просто перебираем все элементы, начиная с первого. Как только добираемся до n-го, так и выходим. Перед этой несложной операцией мы убеждаемся, что элемент с таким номером существует вообще (если нет, то просто возвращяем NULL).

Часть 37. Сортируем элементы списка

Продолжим работу с нашим списком, начатую в 28 части Для сортировки элементов списка мы воспользуемся методом пузырьков. Алгоритм его приблизительно такой - просматриваем элементы нашего списка соседними парами от начала до конца. Т. е. сначала смотрим пару нулевого и первого элементов, потом первого и второго и т. п. В каждой паре при необходимости меняем элементы - если они стоят не в том порядке. Так доходим до конца списка. Если мы поменяли хотя бы одну пару, то повторяем все с начала - т. е. начинаем смотреть пары от начала и до конца. И так будем делать до тех пор, пока при очередном проходе мы ничего не поменяем - тогда список и окажеться отсортированным. Приступаем к коду. В класс CList добавим объявление метода Sort:
class CList
{
public:
...
// Сортировка.
void Sort();
...
Добавим, естественно, и реализацию для этого метода:
void CList::Sort()
{
bool b; // Меняли ли соседние элементы списка?
do
{
b = false; // пока ничего не меняли.
// Смотрим элементы до предпоследнего.
for (int i = 0; i {
if (GetAt(i)->a > GetAt(i+1)->a)
{
Change(GetAt(i));
b = true;
}
}

}while (b);
}
С этим методом все.

Часть 38. Сортировка с перегрузкой оператора

Чем был не слишком хорош рассмотренный в 37 части метод сортировки? Тем, что мы в нем использовали внутреннее устройство класса класса CData:
...
if (GetAt(i)->a > GetAt(i+1)->a)
...
Видите, что мы тут делали? Мы предполагали, что класс CData содержит переменную 'a' (и он ее действительно содержит). А если мы поменяем класс CData? Например, переименуем эту переменную или захотим определять, кто больше, по другой переменной ('b'), или, скажем, перенесем переменную 'a' в закрытую часть класса и добавим метод GetA для доступа к этой переменной? Тогда нам придется менять и метод сортировки, и это не очень хорошо, так как надо помнить, что эти две части нашей программы как-то связаны. Правильнее же будет поступить так: переопределить в самом классе CData, что такое для него означают знаки < и > (т. е. сделать перегрузку операторов < и >). Это можно сделать, например, так:
class CData
{
public:
...
bool operator>(CData v)
{
return a>v.a;
}
bool operator<(CData v)
{
return a }
};
И после этого изменить метод Sort:
void CList::Sort()
{
bool b; // Меняли ли соседние элементы списка?
do
{
b = false; // пока ничего не меняли.
// Смотрим элементы до предпоследнего.
for (int i = 0; i {
if (*GetAt(i) > *GetAt(i+1))
{
Change(GetAt(i));
b = true;
}
}

}while (b);
}
По сравнению со старым методом мы тут только изменили метод сравнения - раньше мы сравнивали два экземпляра наших данных через переменную 'a' из класса CData, а сейчас мы сравниваем сами данные:
...
if (*GetAt(i) > *GetAt(i+1))
...
Обратите внимание, что мы разыменовываем значения, возвращаемые методом GetAt (так как он возвращает указатель). Результат сортировки будет, разумеется, таким же, как и в прошлый раз.

Часть 39. Перегрузка оператора []

На этом занятии мы с вами посмотрим, как можно перегрузить оператор [] для некоторого класса. В этом случае вы сможете использовать экземпляр класса как массив - т. е. просто писать после имени экземпляра квадратные скобки с целочисленным индексом. Вот пример кода:
#include
// Класс с перегруженным оператором [].
class CMyClass{
int m_arr[4];
public:


Категория: C, C++, C# | Просмотров: 3998 | Добавил: ДядяВолк (11.08.2010) | Рейтинг: 0.0/0
Источник: http://quadrathell.cn.ua/ |
HTML ссылка на материал:
BB ссылка на материал:
Похожие материалы :
Возможно вам будет интересно:
Создание 3D игр на Game Maker (3)
24 совета по программированию в Delphi (Дельфи) часть 2 (0)
Учимся писать игру на Delphi (3)
Полиморфизм, Инкапсуляция и Наследование (0)
Уроки по BGE для начинающих. (0)
Создание многопользовательской(online) игры на Game Maker. (часть 2) (0)
Game Maker. Начало (1)
Учебник по Blitz 3D (0)
То, что нужно знать всем, кто движется в сфере геймдева. (1)
Функции D3D в Game Maker (2)
Устанавливаем на машину скин 3dRad (0)
Работа с движком Newton (Blitz 3D) (0)
Дизайн персонажей для игр (0)
Создаем танчики (Урок 2) (0)
Создание MMORPG игр. (3)
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Поиск
Поиск по всему сайту:
Поиск по разделу:

Панель пользователя
Здравствуйте, Гость


Ник:
Пароль:
Запомнить :

Ваш IP: 18.216.186.164

Случайные конструкторы

Случайные движки

Случайные статьи

Статистика
Онлайн всего: 1
Гостей: 1
Пользователей: 0

На сайте были:
Rainbow_Sparkle

При полном или частичном копировании материалов сайта ссылка на Make-Games.ru обязательна. Make-Games.ru © 2008 - 2024 Хостинг от uCoz
Топ Разработка игр