
- •Int z[n]; //Неверно!
- •Урок 10. Рекурсивные функции
- •Int z[3]; //Массив
- •Урок 26. Конструкторы и деструкторы
- •Урок 27. Классы и указатели
- •Урок 28. Практика: список - добавление элементов
- •Урок 29. Показ элементов списка
- •Урок 30. Ищем элемент в списке
- •Урок 31. Пара вспомогательных методов для списка
- •Урок 32. Удаление элемента из списка
- •Урок 33. Функция с переменным числом параметров
- •Урок 34. Считаем элементы в списке
- •Урок 35. Обмен соседних элементов в списке
- •Урок 36. Получаем элемент списка по его номеру
- •Урок 36. Получаем элемент списка по его номеру
- •Урок 37. Сортируем элементы списка
- •Урок 38. Сортировка с перегрузкой оператора
Int z[3]; //Массив
cout<<z; //Адрес начального элемента массива.
Тут выведется адрес начала массива (т. е. адрес его нулевого элемента). Таким образом имя массива - это адрес его начала.
C/C++. Урок 15. Указатели
Указатель - одно из важных понятий языка C/C++. Начинающие программисты поначалу путаются, но со временем все должно встать на свои места.
Итак, каждая переменная имеет свой адрес в адресном пространстве. Так вот, указатель - это переменная, которая хранит адрес другой переменной. Т. е. если в переменной обычного типа хранится, например, целое число, то в переменной-указателе может храниться адрес переменой, в котрой хранится целое число. Все это справедливо, разумеется, не только для целого типа, но и любого другого.
Указатели различаются по типам. Например, указатель на целое может хранить адрес переменной целого типа, а указатель на тип float - адрес переменной типа float.
Указатель на определенный тип объявляем так: сначала пишем этот тип, потом пишем звездочку и затем произвольное имя переменной. Пример:
int * p1; // Указатель на целое.
bool * p2; // Указатель на тип bool.
Так же как и для переменных обычного типа для переменных типа указатель мы при объявлении имеем в такой переменной мусор - т. е. некотрый непонятный адрес. Существует два способа записи инициализации указателя - записать в него адрес существующей переменной соответствующего типа или выделить новый участок памяти через ключевое слово new.
Вот пример на первый способ:
int z=20;
int *p=&z;
Тут мы в указатель p записали адрес переменной z.
А вот пример на второй способ инициалиазации указателя - через ключевое слово new:
int *p=new int;
В этом случае мы говорим, что память у нас выделена динамически. Синоним этого - память выделена в куче (heap). В противоположность этому память для переменных обычного типа (не указателей) выделяется в стеке.
И в первом, и во втором способах в переменной p будет храниться указатель (адрес) целого типа. Для того, чтобы через этот адрес работать с тем местом, на котрое он указывает (т. е. в нашем случае с некоторым целым числом), мы используем так называемое разыменование указателя. Вот пример:
...
*p=123; //Записываем 123 в место, на которое указывает указатель p.
cout<<p<<"\n"; //Выведется некий адрес (например, 0x0012FF7C)
cout<<*p<<"\n"; //Выведется 123;
Т. е. для доступа к тому месту, на которое указывает указатель, мы перед его именем добавляем звездочку.
Таким образом по сути разыменование указателя - это доступ к тому месту на которое указатель указывает.
Для указателей существует некий аналог нуля - это значение NULL. Вот пример:
int *p=NULL;
На самом деле NULL определена как 0, поэтому в большинстве способов можно писать 0 вместо NULL.
Пока это было краткое знакомство с указателями. Подробности - в следующих уроках.
C/C++. Урок 16. Оператор sizeof
Оператор sizeof позволяет выяснить, сколько байт занимает тот или иной тип. В качестве параметра он принимает или название типа, или переменную соответствующего типа. Вот пример:
int z;
cout<<sizeof(int)<<"\n"; //Выведется 4.
cout<<sizeof(z)<<"\n"; //Выведется 4.
Параметр может быть не только встроенным типом. Пример со структурой:
struct vector
{
float x;
float y;
};
void main()
{
vector s;
cout<<sizeof(s)<<"\n"; //Выведется 8.
}
Пример выведет 8, так как float занимает 4 байта и в нашей структуре 2 переменной типа float.
Наверх
C/C++. Урок 17. Комментарии
Комментарии - это некоторые пояснения, которые программист пишет сам для себя (чтобы потом не пришлось мучительно долго вспоминать, что делает та или иная функция или для чего нужна та или иная переменная). При компиляции комментарии игнорируются и, например, на размер exe-файла никак не влияют. Использование комментариев - эо хороший стиль в программировании.
Комментарии в C++ бывают двух видов - однострочные и многострочные.
Пример однострочного комментария:
int fig; //Количество фигур.
Однострочный комментарий может занимать как строчку целиком, так и часть строки (до конца строки). Началом однострочного комментария служит знак //.
Пример многострочного комментария:
/*
Координаты точки.
*/
int x;
int y;
Границами многострочного комментария служат знаки /* и */.
Кроме своего прямого предназначения, многострочные комментарии используются для времменного исключения некоторого куска в программе.
C/C++. Урок 18. Параметры командной строки
В программу могут передаваться параметры командной строки. Несколько примеров таких программ - это команда ping (в качестве параметра выступает ip-адрес компьютера, с которым вы хотите проверить связь), команда copy (в качестве параметра выступают имя компируемого файла и новое местоположение и имя).
Для работы с параметрами командной строки мы должны добавить два параметра в функцию main. Первый параметр задает общее количество передаваемых в программу параметров (при этом имя самого exe-файла тоже считается параметром), второй - сами параметры.
Вот пример функции, которая выведет все свои параметры командной строки:
#include <iostream.h>
void main(int argc, char* argv[])
{
for(int i=0; i<argc; i++)
{
//Выводим имя exe-файла
//и все параметры командной строки.
cout<<argv[i]<<"\n";
}
}
Тут первый параметр: argc - это общее количество параметров. При простом запуске программы (например, простым щелчком на exe-файле) он будет равен 1. Второй параметр представляет из себя массив типа char*. Тип char* - это указатели на символ, который интерпретируется как строка. Нулевой элемент в этом массиве строк - это имя самого exe-файла, первый - это первый передаваемый параметр, второй - второй передаваемый параметр (разумеется, если эти параметры вообще есть).
Откомпилируем и запустим нашу программу из командной строки с параметрами. В результате будет выведено имя exe-файла и все параметры.
В средах разработки часто существует возможность задать параметры командной строки в самой IDE. Вот так, например, это делается в Visual C++.NET. Из контекстного меню для проекта выбираем Properties, затем в появившемся окне Property Pages в разделе Debugging задаем Command Arguments.
C/C++. Урок 19. Преобразование типов
Преобразование типов - это приведение (превращение) одного типа в другой. Такое преобразование созможно далеко не всегда. А иногда в нем вообще нет особой необходимости - например, когда вы присваиваете переменной типа float значение переменной типа int - тут нет никакой потери точности, так что компилятор даже не выдаст вам предупреждения. А вот, например, обратный пример:
...
int a;
float b=2.78;
...
a=b; //Потеря точности!
Тут мы в переменную типа int записываем значение переменной типа float. Синтаксической ошибки не будет, но скорей всего компилятор выдась предупреждение о возможной потери точности.
Для того, чтобы компилятор не ругался, мы сделаем преобразование типа float к типу int. Вот так:
...
a=(int)b;
...
Этот случай достаточно очевидный - мы преобразуем один числовой тип к другому. А вот так, например, можно преобразовать тип char к типу int или наоборот:
...
char ch='A';
//Выведется 65 - код символа 'A'.
cout<<(int)ch;
int a=7;
//Раздастся звонок (код 7).
cout<<(char)a;
...
На самом деле такое преобразование будет возможно, так как char - это тоже целочисленный тип как и int.
C/C++. Урок 20. Работаем со строками
Вообще говоря в C/C++ нет строкового типа. Его роль выполняет либо указатель на char, либо массив char'ов. Для работы с такими строками в C++ есть несколько функций, тиена которых начинаются на str. Вот пример их использования:
#include <iostream.h>
#include <string.h>
void main()
{
char* ch="Roman";
char ch1[30];
//Показывем длину строки
cout<<strlen(ch)<<"\n";
//Копируем строку
strcpy(ch1, ch);
cout<<ch1<<"\n";
//Длина скопированной строки такая же, как и у старой
cout<<strlen(ch1)<<"\n";
//Сравнение строк
if(strcmp(ch, "Roma")==0)
{
cout<<"Strings are equal\n";
}
else
{
cout<<"Strings are not equal\n";
}
//Конкатенация (сложение) строк
strcpy(ch1, "Roman");
strcat(ch1, " Alexeev");
//Выведется "Roman Alexeev"
cout<<ch1<<"\n";
}
Для использования таких функций мы должны написать соответствующий include:
...
#include <string.h>
...
Пару слов следует сказать о функции strcmp. Она возвращает ноль, если строки равны; -1, если строка, задаваемая первым параметром, расположена в алфавитном порядке раньше строки, задаваемой вторым параметром; и 1, если первая строка расположена в алфавитном порядке дальше, чем вторая.
C/C++. Урок 21. Передача массива как параметра функции
Массивы в качестве параметра функции передаются по ссылке. Это означает, что если вы внутри такой функции изменяете элементы массива, то эти изменения будут внесены и в оригинал массива.
Вот пример:
#include <iostream.h>
void f(int arr[2]){
// Изменяем внутри функции массив.
arr[0]=11;
arr[1]=12;
}
void main(){
int a[2]={1, -1};
// Вызов функции.
f(a);
// Выводим элементы массива.
cout<<a[0]<<"\n";
cout<<a[1]<<"\n";
}
Указанный фрагмент выведет числа 11 и 12.
Обратите внимание, что для массивов, в отличие от отдельных переменных (т. е. тех, которые не являются массивами), не надо писать знак амперсанда (&).
Вообще говоря, в таком поведении массивов ничего удивительного нет - как вы знаете, имя массива (а именно оно и передается в функцию) есть адрес начала массива. Так что мы передаем адрес массива, т. е. его оригинал.
C/C++. Урок 22. Шаблоны функций
Шаблоны служат для ситуции, когда у нас есть разные типы данных, с которыми мы должны работать по одному, не зависящему от этих типов данных, алгоритму. Например, метод сортировки массива не зависит от типов данных - такой алгоритм будет одинаков и для, например, чисел типа int и для чисел типа float.
Приведем пример использования шаблона.
Вот код для простейшего щаблона функции, которая из двух передаваемых в нее параметров возвращает максимальный:
#include <iostream.h>
//Объявление шаблона функции.
template <class T>
T max(T a, T b)
{
if(a>b)
{
return a;
}
else
{
return b;
}
}
void main()
{
//Использование шаблона функции для целых.
int x = 45, y = 32;
cout<<max(x, y)<<"\n"; //Выведется 45.
//Использование шаблона функции для вещественных.
float s = 4.18, t = 34.08;
cout<<max(s, t)<<"\n"; //Выведется 34.08.
}
Обратите внимание на синтаксис. Сначала мы пишем ключевое слово template и задаем формальный тип T (T - это просто произвольное имя):
template <class T>
...
Далее мы пишем непосредственно функцию, причем в качестве типа параметров и возвращаемого значения пишем введенный ранее формальный тип T:
T max(T a, T b)
...
Разумеется, количество параметров может быть любое и не все из них должны иметь тип T. То же относится и к типу возвращаемого значения - в нашем случае это тот же тип T, но в принципе тип может быть любой, хоть void.
Также обратите внимание, что заданием формального параметра и объявлением класса не должно быть никаких операторов.
Теперь пара слов о использовании шаблона. При использовании мы просто пишем имя функции (в нашем случае это max) с параметрами конкретных типов (int и float в нашем примере). Компилятор в этом месте сам сгенерирует на основании этого шаблона функцию с параметрами конкретных типов.
Шаблоны существую не только для функций, но и для классов. Такие шаблоны мы рассмотрим позже.
C/C++. Урок 23. Пример шаблона функции
Вот еще конкретный пример на шаблон функции. Этот шаблон ищет в массиве определенный элемент и если такой элемент есть, то шаблон функции возвращает номер этого элемента, а если такой элемент не найден, то возвращается -1.
#include <iostream.h>
//Объявление шаблона функции.
template <class T>
int find(T *arr, T a, int n)
{
for(int i=0; i<n; 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<<res<<"\n"; //Выведется 3.
//Использование шаблона функции для булевских.
bool b[3] = {true, true, true};
res = find(b, false, 3);
cout<<res<<"\n"; //Выведется -1.
}
У нашего шаблона 3 параметра: первый - это массив, в котором мы ищем наш элемент (помните, что имя массива - это указатель на нулевой элемент массива, поэтому мы и пишем T *arr), второй параметр шаблона - это искомый элемент, и третий - количество элементов в массиве.
C/C++. Урок 24. Введение в классы
В 8 уроке мы с вами рассматрисали структуры. Классы чем-то напоминают структуры - у них также внутри есть переменные разных типов. Но наряду с этими переменными у класса есть и несколько отличий, которые мы сейчас и рассмотрим на примере.
Вот пример класса:
#include <iostream.h>
//Объявление класса прямоугольника.
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 = "<<r.GetPerim()<<"\n";
cout<<"Square = "<<r.GetSquare()<<"\n";
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.
Дальнейший разбор класса мы произведем на следующем уроке.
C/C++. Урок 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 = "<<r.GetPerim()<<"\n";
cout<<"Square = "<<r.GetSquare()<<"\n";
...
Обратите внимание, что функции мы вызываем не сами по себе, а именно для нашего экземпляра класса r. Синтаксис тут такой: имя экземпляра, точка, имя функции:
r.SetA(5);
Можно в программе объявить несколько экземпляров класса или даже массив:
CRect w, v;
CRect z[5];
Все такие экземпляры будут независимы друг от друга.
C/C++