С++ для тех кто не шарит
Предыстория
Около трех лет назад, когда я пошел в девятый класс, остро встал вопрос о том, что мне делать дальше. Было несколько вариантов, но в итоге я выбрал Киевский Колледж Связи, который, по словам знакомого, лучший в Киеве из области программирования. Ожидая потока знаний в мою голову, я стал сам потихоньку углубляться в IT и ко второму курсу, когда в учебной программе наконец начали появляться специализированные предметы, уже имел некоторый, относительно неплохой багаж знаний. Окрыленный надеждой получить если не все знания этого мира, то хотя бы половину, я с разбегу ударился в стену разумных доводов о том, что не стоит ожидать от этого места чего-то заоблачного. И вот я сижу на очередной паре и вставляю спички в глаза, ибо все это я успел выучить за первые две недели целенаправленного погружения в это "болото". К концу года, когда вот-вот должна начаться сессия и практика, я окончательно осознал, что на всю группу найдется максимум 3 человека, которые все поняли и могут спокойно оперировать полученными знаниями. (Двое из этих людей и так варились в этом программистском котле, а третий очень заинтересовался где-то к концу первой четверти и достиг очень неплохого уровня за минимальный промежуток времени). Тогда у меня и родилась идея написать максимально подробную "шпаргалку" для тех, кто заинтересован в успешной сдаче сессии но не понял что происходило весь прошлый год. К сожалению мало кто пользовался ею, по этому я решил предоставить ее более широкой общественности, ибо жалко добру пропадать.
Теперь по сути
Для начала стоило бы рассказать как работает любая программа на элементарном уровне.
Любая программа, будь то на телефоне или компьютере, строится на взаимодействии с оперативной памятью устройства. Вся RAM делится на ячейки по 1 байту каждая. Но для удобного использования все эти ячейки, когда до них доходит очередь, зачастую группируются для хранения большего объема данных. Так например целое число, если не вдаваться в подробности, храниться в блоке из четырех ячеек памяти, то есть занимает 4 байта памяти. В результате 32-битное целое число, а в 4 байтах именно 32 бита, может достигать значений от -2^31 до (2^31)-1. Степень 31, а не 32 потому что первый бит отвечает за знак числа, если 0 то +, если 1 то -.
Перейдем непосредственно к тому, как это выглядит на практике. Простейший пример:
int a;
a = 1;
Сразу стоит оговориться что в конце почти каждой строки, за некоторыми исключениями, следует писать
;
Для того чтобы смотреть что выдает код который ми пишем есть такая команда:
cout << a;
гдеa
, это любая перменная или фраза записанная в кавычках. Например:cout << "переменная а = " << a << endl;
endl
означает конец строки. Вместо него также можно писать"\n"
, результат будет одинаковый. Ты можешь вписывать это в любом месте где тебе хочеться посмотреть чему равна переменная, я же этого делать не буду, чтоб не засорять примеры.
Строка int a;
это непосредственно выделение тех четырех байт памяти под целое число. Мы взяли где-то в памяти 4 байта, присвоили им имя а
и теперь можем обращаться к этой памяти как к чему-то единому, называя это целочисленной переменной.
Строка a = 1;
задает нашей переменной значение равное 1
, фактически это значит, что в те 4 байта памяти запишется нечто вот такое: 00000000 00000000 00000000 00000001
. Тут 31 ноль и 1 единица в конце, это двоичное представление числа, что в нашем случае не сильно отличается от десятичного. Это действие, присвоение переменной начального значения, называется инициализацией.
Теперь немного упростим эту запись:
int a = 1;
Это абсолютно то же самое, только короче. Более того, многие среды разработки для С++ не дадут запустить код, если вы попытаетесь прочитать значение у переменной, которой не было задано значения. По этому присваивать переменным какое-то значение перед основными действиями являеться хорошим тоном.
Теперь попробуем немного похимичить:
int a = 1;
int b = 2;
int c = a + b;
Не сложно догадаться чему будет равно c
. Это, на самом деле, и есть вся суть программирования, на основе одних данных получать другие. Все что тут произошло, можно переписать в виде 3 = 1 + 2
.
А сейчас покажу как довести учителя математики до истерики.
int c = 1;
c = c + 1;
c += 1;
c++;
Строки со второй по четвертую на самом деле делают одно и тоже: просто к значению переменной c
прибавляют 1
. Первый способ это прировнять c
к этой же c
только + 1
. Старая двойка, которая там была, перезапишется новым значением, в данном случае тройкой. То же самое, что мы делали в первом блоке, но теперь не просто число, а математическое выражение. Второй способ называется сложение с присваиванием. Это действие прибавляет к изменяемой переменной то число, которое написано в правой части, и потом записывает, получившийся результат, в эту же переменную - тройка перезапишется четверкой. Есть так же -=
, *=
, /=
, думаю достаточно очевидно, что они сделают. Ну и третий способ: два плюса под ряд возле переменной - четверка перезапишется пятеркой.
++
/--
меняет значение ТОЛЬКО на единицу (--
отнимает единицу).
Все три способа на программном уровне работают одинаково, и один не является более правильным, а другой менее, просто где-то удобно так, а где-то по другому.
Попробуем записать в перменную результат математического выражения 5 / 2
:
int a = 5 / 2;
На выходе мы ожидаем получить 2.5
, поскольку 5 / 2 = 2.5
Но компилятор уже спешит нас обламать и выводит в консоль ровно 2. Вернемся к битам и байтам. Наше число 5
(если обрезать левые 24 нолика) выглядит вот так: 00000101
а число 2
выглядит так: 00000010
Поговорим о типах данных. Мы все время использовали целое число, соответственно, если мы попытаемся присвоить целочисленной переменной дробное число, то компьютеру ничего не останется, кроме как записать в переменную целую часть числа, а дробную просто отбросить.
В следующем примере попробуем получить дробное число. Для этого воспользуемся типом float
при создании переменной а
:
float a = 5. / 2;
После 5
стоит точка для того чтобы указать компилятору что нам требуеться дробный ответ. Если же оставить там 5 / 2
то ответ все равно будет целым числом.
Ура, дробное число получено, можешь отметить этот день в своем календаре)))
Попробуем разные способы установки значений для дробных чисел:
float a = 3;
a = 3.5;
a = 1.;
Все выглядит вполне логично, за исключением последнего варианта. В нем мы просто не указывали дробную часть, но указали что это число дробное, тут это не особо полезно, но в других случаях это может пригодиться.
И так, мы уже знаем как создавать и работать с числовыми переменными. Но на этом много не напрограммируешь, необходимо как-то получать и обрабатывать данные из вне. Наподобие команды cout
есть функция cin
, которая принимает данные и записывает их в переменную.
int a;
cout << "Введи число, а после нажми Enter:" << endl;
cin >> a;
cout << "a = " << a << endl;
Мы создали переменную a
, но не задали ей никакого значения, потому что на третей строке ожидаем что пользователь введет его сам. Как видишь значение у переменной a
ровно такое, какое было введено.
Нам осталось разобраться с последней недостающей деталькой, логическим типом данных. Давай разбираться. Создадим переменную с новым типом данных, логическим. Он занимает всего 1 байт и может быть равен либо 0
, либо 1
, 0
это ложь
, 1
это правда
.
bool a = true;
a = 1 == 1;
a = 1 == 2;
a = a == false;
a = !a;
Строка h = 1 == 1;
. Что здесь происходит? Мы сравниваем 1
и 1
, равна ли единица единице. Разумеется что ответ правда, по этому на выходе мы получим значение true
.
Вот сейчас очень важно,
=
это приравнивание, с помощью этого оператора мы задаем значение, а==
это сравнивание, мы сравниваем два значения.
Есть так же:
!
- отрицание!=
- не равно<
- меньше>
- больше<=
- меньше или равно>=
- больше или равно
И так, переходим на третью строку: a = 1 == 2;
1
не равно 2
(как неожиданно), по этому результат сравнения будет false
. Четвертая строка демонстрирует возможность сравнивать переменную с каким то значением или другой переменной. Поскольку на прошлой строке у нас получилась ложь, то сравнение false
и false
вернет, ожидаемо, правду. Ну и последняя строка, на которой мы берем значение обратное значению переменной a
. Простыми словами это выглядит как не правда
, что по другому является ложью
.
Что ж, мы теперь полностью готовы к познанию того, как обрабатывать данные.
if(true){
cout << "Это условие правдиво" << endl;
}
Это, так называемое, условие if(условие){ действие }
. Дословно это можно перевести так: "Если утверждение в круглых скобках правдиво, то выполнить действие в фигурных скобках". В нашем случае утверждение в скобках всегда правдиво, поскольку мы туда передаем заведомо правдивое значение.
Теперь немного усложним предыдущее условие.
bool a = false;
if(a){
cout << "Это условие правдиво" << endl;
}
else{
cout << "Это условие ложно" << endl;
}
Вторая часть это else{ действие }
, которая дословно обозначает "Иначе, если условие не выполнилось, то...". В данном случае, поскольку наша переменная a
равна false
, то выполниться блок else
Продолжаем познавать все прелести условий.
int a;
cout << "Введи число, а после нажми Enter:" << endl;
cin >> a;
if(a < 5){
cout << "a < 5" << endl;
}
else if(i > 5){
cout << "a > 5" << endl;
}
else{
cout << "a = 5" << endl;
}
На этот раз у нас добавился блок else if(условие){ действие }
, который будет выполнен в случае если первое условие не сработало. Дословно это значит "Иначе если первое условие не сработало, то проверить другое условие". Стоит заметить, что таких блоков с дополнительными условиями может быть сколько угодно, главное соблюдать порядок: if
-> else if
-> else
.
Условия это конечно превосходно, но вся их сила раскрывается в тандеме с циклами. Всего есть три вида циклов, что на самом деле ложь, так как два из них в своей основе содержат третий. Сейчас станет понятнее.
Разберем фундаментальный вид циклов. Выглядит он так: while(условие){ действие }
, и дословно значит: "Повторять действие в фигурных скобках, до тех пор, пока правдиво условие в круглых".
int a = 0;
while(a != 5){
cout << "Введи число, а после нажми Enter:" << endl;
cin >> a;
}
Правда вот есть один нюанс, если мы поместим на место условия true
, то цикл будет повторяться вечно. В нашем случае цикл будет повторяться до тех пор, пока не будет введено число 5.
А что делать если нам нужно четко установить количество повторений цикла? Нужна дополнительная переменная, которая будет выступать в роли счетчика.
int k = 0;
while(k < 5){
cout << "k = " << k << endl;
k++;
}
Мы создали переменную i
, поставили в цикле условие i < 5
, а в самом цикле на каждом этапе прибавляем к ее значению 1.
Теперь посмотрим на следующий тип циклов.
for (int i = 0; i < 5; i++) {
cout << "l = " << l << endl;
}
Делает этот цикл ровно то же самое что и предыдущий, но в написании он компактнее. Разберем все по порядку. В самом начале мы объявляем переменную-счетчик (так же туда можно поместить уже объявленную переменную ранее переменную, тогда бы это выглядело так: for (i = 0; i < 5; i++)
. Так же эту часть можно полностью опустить, написав вместо нее ;
). Дальше мы ставим условие, в нашем случае i < 5;
(эту часть также можно опустить написав просто ;
, но тогда цикл будет повторяться бесконечно). Ну и последняя часть, действие которое будет выполнено при переходе на следующий круг цикла. Вместо i++
вполне написать i *= 2
. В зависимости от того, что написано в последней части объявления цикла будет меняться и его поведение. К слову, это тоже можно пропустить: просто не писать ничего на том месте, тогда нам придется или где-то в другом месте как-то менять переменную i
, или же цикл будет бесконечным.
Есть такое понятие как область видимости. Каждый цикл, условие или функция, о который мы поговорим ниже, создают свою область видимости. В них имена переменных не могут повторяться. Есть еще понятие вложенности, оно значит, что внутри цикла может быть условие, и переменные, объявленные внутри цикла, будут доступны в условии, но вот если мы объявим переменную внутри условия, то внутри цикла за пределами условия эта переменная будет недоступна. В нашем случае переменная
i
может быть использована только внутри цикла. Если нам нужно использовать ее где-то еще, то стоит объявлять ее за пределами цикла.
Теперь разберем последний вид циклов. Его используют реже всего, потому что его специфика в том, что тело цикла выполнится хотя бы один раз, а потом произойдет проверка условия. В случае если условие ложно, цикл не пойдет на второй круг. Попробуем повторить то что мы писали в предыдущих двух блоках.
int i = 0;
do {
cout << "i = " << i << endl;
i++;
} while (i < 5);
Как видим, условие не поменялось, вывод тоже, но вот цикл мы использовали другой. Ниже я продемонстрировал что цикл выполниться хотя бы раз, даже при ложном условии.
int i = 5;
do {
cout << "i = " << i << endl;
i++;
} while (i < 5);
Теперь разберемся, что делать если нужно обработать не одно, не два, а, к примеру, 10 значений. Мы конечно можем создать все 10 переменных вручную и обрабатывать их по отдельности, но это, как можно догадаться, не то что бы очень эффективно. Для этого были придуманы массивы. Посмотрим как они выглядят:
int A[10];
cout << "A = " << A << endl;
cout << A[0] << " " << A[1] << " " << A[2] << " " << A[3] << " " <<A[4] << " " <<A[5] << " " << A[6] << " " << A[7] << " " << A[8] << " " << A[9] << endl;
В начале, как и при создании переменных, у нас стоит тип данных, в нашем случае int
. После идет название массива и в квадратных скобках его длинна, то есть то, сколько значений в него можно поместить. Попробуем не вводить никаких значений, а просто вывести массив в консоль. Что ж, это немного не то, чего мы ожидали. Вместо набора из десяти случайных чисел мы получили нечто, что называется адресом. Про адреса мы поговорим чуть позже, а сейчас просто перепишем вывод так, чтоб на экран вывелисль значения из ячеек массива
Нумерация в массивах, и вообще в программировании в целом, начинается с нуля, по этому первый элемент массива это 0, а последний - его длинна минус один.
Все выглядит вполне естественно, за исключением того, что мы не задали значения ячейкам массива, и вместо них вывелось то, что лежало в тех местах памяти которую занимают ячейки массива, то есть просто мусор.
То же самое произойдет если мы выведем обычную перменную, не присваивая ей значения. Правда вот не везде программа запуститься, ибо довольно часто компиляторы запрещают использовать неинициализированные переменные.
Вот и настал тот момент, когда мы будем использовать циклы для каких-то осмысленных действий. В данном случае используем цикл for
. Значения переменной i
будут меняться от 0 до 9, что нам идеально подходит для использования в массиве.
for (int i = 0; i < 10; i++) {
A[i] = i;
}
for (int i = 0; i < 10; i++) {
cout << A[i] << " ";
}
Первый цикл задает значения ячейкам массива, а второй их читает и выводит в консоль. Теперь это проще в написании и чтении.
Теперь вернемся к адресам. Но для начала расскажу что такое ссылки и как их использовать. Ссылочная переменная, хранит в себе исключительно ссылку на первый байт переменной, на которую он ссылается (Каждая ячейка памяти имеет свой адрес, записанный в шестнадцатеричной системе. Для доступа к значению переменной по ссылке необходимо лишь знать ее тип данных и ссылку на первый байт). Объявляется путем прибавления к имени ссылочной переменной знака &
.
int a = 2;
int &ref = a;
cout << "a = " << a << endl;
cout << "ref = " << ref << endl;
a++;
cout << "a = " << a << endl;
cout << "ref = " << ref << endl;
Теперь мы можем получить значение переменной a
, обратившись к переменной ref
.
И есть еще такое понятие как указатели. Указатель, при обращении к нему напрямую, вернет адрес переменной, ссылку на которую в него поместили. Посмотрим как это выглядит в коде и при выводе.
int a = 2;
int *ptr = &a;
cout << "ptr = " << ptr << endl;
cout << "*ptr = " << *ptr << endl;
Несложно заметить, что при выводе значения переменной ptr
, в консоли мы видим как раз тот адрес, о котором я говорил выше. Теперь посмотрим на вывод *ptr
. Знак *
берет не адрес, а значение переменной которая находиться по этому адресу - производит разыменовывание.
Ну и напоследок посмотрим на вывод &a
и *&a
.
int a = 2;
cout << "&a = " << &a << endl;
cout << "*&a = " << *&a << endl;
&a
Достает адрес переменной a
(который, к слову, и записался в переменную ptr
), а *&a
разыменовывает только что взятую ссылку, что, по сути, отменяет действие &
(как степень и корень =) )
Отмечу так же, что ни разыменовывание, ни взятие адреса не меняет переменную
a
.
Взятие адреса от ссылочной переменной вернет адрес переменной, на которую она ссылается.
Теперь мы в полной мере можем понять, что вернул нам вывод в консоль массива A
. Это была ссылка на первую ячейку массива. Более того, это ссылка на первый байт первой ячейки. Теперь посмотрим что будет если разыменовать эту ссылку.
int A[10];
A[0] = 3;
A[1] = 1;
cout << "A = " << A << endl;
cout << "*A = " << *A << endl;
cout << "A + 1 = " << A + 1 << endl;
cout << "*(A + 1) = " << *(A + 1) << endl;
Мы получили то значение, которое записывали туда раее. Если мы напишем нечто вот такое: A + 1
, то увидим еще одну ссылку, как не трудно догадаться, это ссылка на вторую ячейку массива. Внимательно посмотрев на ее адрес, мы увидим что разница между адресом первой ячейки и второй составляет ровно 4, что доказывает нам: во-первых, то что ссылка содержит адрес именно первого байта выделенного под переменную, и, во-вторых, что цело число занимает ровно 4 байта памяти. Возвращаясь к нашему массиву, если мы разыменуем ссылку A + 1
, то получим значение второй ячейки, в нашем случае это 1.
Рассмотренный метод чтения значений массива имеет место, но необходимость применения возникает не часто, по этому в дальнейшем я буду использовать метод с квадратными скобками, который на программном уровне не отличается от метода с разыменовыванием ссылок на элементы, но при этом писать его проще, и выглядит он понятнее.
Кстати, то, что чтение массива через квадратные скобки основано на переборе и разыменовывании ссылок, объясняет, почему нумерация ячеек начинается с нуля, а не с единицы. Для взятия первого элемента, нам нужно просто разыменовать ссылку на первую ячейку, которая по совместительству является и ссылкой на массив (если мы прибавим 0, то это все еще будет ссылка на первый элемент), а для второй ячейки, нам нужно прибавить 1 к ссылке массива. Именно по этому нумерация в массивах начинается с нуля.
Самое время поговорить о константах. Мы их раньше уже использовали, просто не придавали этому значения. Если коротко, то константа, это значение, которое не меняется на протяжении всего выполнения программы. Например, в строках int a = 4;
, b = 5;
и c += 2;
значения 4
, 5
и 2
являются константами. Вообще любые числа, которые мы явно записываем в код программы, являются константами.
Так же мы можем задать константе некий псевдоним, записав его в память, и потом использовать его, обращаясь к нему по имени, как к переменной.
int const а = 6;
cout << "а = " << а << endl;
float const pi = 3.14;
cout << "а + pi = " << а + pi << endl;
Обрати внимание, что задать значение константе нужно сразу при объявлении, потом поменять его уже будет нельзя. Ну и в дальнейшем мы можем достать это значение обратившись по имени к той ячейке которая хранит эту константу.
Опять вспомним о массивах. При создании массива A
мы воспользовались константой, значит мы можем передать в скобки создания массива объявленную константу.
int const а = 6;
float const pi = 3.14;
int В[a];
B[0] = 2;
cout << B[0] << " ";
for (int l = 1; l < m; l++) {
B[l] = B[l - 1] * 2;
cout << B[l] << " ";
}
Создавать массив можно только с целым количеством ячеек, по этому передать туда pi
не получиться, хоть это и константа. Заполним массив степенями двойки и выведем его на экран. Как видим, элементов у нас 6, как и было записано в константе m
.
Но что если мы попробуем при создании массива передать туда не константу, а переменную? У нас ничего не получиться, потому что при запуске нашего кода компилятору - программе, которая превращает наш текст в понятный для компьютера вид, нужно выделить под массив какое-то место, но если там стоит значение, которое может поменяться по ходу выполнения кода, то компилятор не будет знать сколько места ему выделять.
Если же нам все-таки нужен массив, с заранее не известным количеством ячеек, то мы можем воспользоваться динамическим массивом. Для его объявления воспользуемся уже изученным указателем.
int a;
cout << "Введи число, а после нажми Enter:" << endl;
cin >> a;
int *C = new int[a];
for (int i = 0; i < a; i++) {
cout << "Введите " << l + 1 << "-ое число" << endl;
cin >> C[i];
}
for (int i = 0; i < a; i++) {
cout << C[i] << " ";
}
Мы создаем переменную-указатель, и помещаем в нее новый массив целых чисел (по структуре объявления думаю понятно что происходит). В квадратные скобки мы помещаем переменную a
, которую мы вводили с клавиатуры. Ну и теперь можем вручную задать значения для ячеек.
Теперь давай подумаем, что если нам нужен не просто ряд чисел, а, скажем, таблица. Например, если нам нужна таблица 5 на 5, то мы можем взять массив из 25 ячеек и считать что первые пять элементов это первый ряд, вторые 5 это второй и так далее. Ну, или мы можем создать массив из 5 ячеек, а в качестве значений поместить в каждую ячейку еще по одному массиву из 5 элементов. Такая структура называется двумерный массив (таких итераций может быть сколько угодно, но на практике больше чем две не делают). Сейчас разберем как это рабоатет.
int D[5][5];
cout << "D = " << D << endl;
cout << "D + 1 = " << D + 1 << endl;
cout << "*D = " << *D << endl;
cout << "*D + 1 = " << *D + 1 << endl;
cout << "**D = " << **D << endl;
Надеюсь ты еще помнишь, что массив хранит в себе адреса, по которым мы можем перемещаться, а для получения значения нам нужно провести разыменовывание этих адресов. Давай посмотрим что будет, если мы выведем первый адрес на который ссылается массив. Что ж, похоже это не сильно отличается от того, что мы видели при работе с одномерным массивом. Теперь посмотрим что выведет строка D + 1
. Опять адрес. Попробуй отнять от второго адреса первый. Сразу скажу что разница будет ровно 20 (нужно отнять в шестнадцатеричной системе, а потом перевести в десятичную), что значит, что одна ячейка массива D
занимает 20 байт. Вспоминаем что мы создавали целочисленный массив, одно целое число занимает 4 байта, делим 20 на 4, получаем 5, значит в первой ячейке массива D
содержится еще один массив из 5 ячеек. Попробуем до них добраться.
Ране мы говорили о том, как доставать значения из массива с помощью разыменовывания ссылок. Теперь попробуем сделать то же самое с массивом D
и выведем это в консоль. Мы опять получили ссылку, причем ту же самую что при выводе не разыменованного D
. Теперь прибавим 1 к тому что у нас получилось. Что-то новенькое. Теперь если мы от получившейся ссылки *D + 1
отнимем ссылку *D
, то у нас получиться 4. Отлично, теперь разыменуем ссылку *D
. Ура, мы докопались до числа, которое там лежит!
Думаю теперь примерно понятно как это выглядит и устроено. Посмотрим на более простой способ достать значение из многомерного массива.
int D[5][5];
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
D[i][j] = (i + 1)*(j + 1);
}
}
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 5; j++) {
cout << D[i][j] << " ";
}
cout << endl;
}
Тут мы можем заметить кое-что новое, вложенные циклы. То есть мы запустили один цикл, внутри него запустился еще один, и уже внутри второго мы что-то делаем с массивом. Когда внутренний цикл закончиться, внешний выйдет на второй круг и опять запустит второй цикл. Вложенность у циклов и условий может быть любого уровня и сложности.
Но вернемся к массиву. Чтобы получить доступ к ряду, нам нужно в первых скобках указать номер ряда, а для доступа уже к элементу ряда, или же можно назвать это колонкой, нам нужно указать число во вторых скобках.
Что считать рядом, а что столбиком решает сам программист, но зачастую за ряды принимается первый индекс.
Ну и напоследок, заканчивая тему двумерных массивов поговорим про динамические двумерные массивы.
int a;
cout << "Введи число, а после нажми Enter:" << endl;
cin >> a;
int **E = new int *[i];
for (int i = 0; i < a; i++){
E[i] = new int [a];
}
for (int i = 0; i < a; i++) {
for (int j = 0; j < a; j++) {
cout << E[i][j] << " ";
}
cout << endl;
}
Мы создаем переменную E
и указываем на двойную вложенность указателей.
Вообще, на самом деле, эти звездочки должны быть рядом с названием типа данных, вот так:
int*
,int**
и тп, так как указатель это отдельный тип данных (двумерный динамический массив, это указательный тип данных от указательного типа данных, отint
), но писать*
можно как рядом с названием типа данных, так и с названием переменной.
Но мы создали только внешний массив, а ведь в его ячейки нужно вложить другие динамические массивы. Сделаем это с помощью цикла.
Если мы на этом этапе установим для каждого вложенного массива свою индивидуальную длину, то такой массив будет называться зубчатым
К элементам динамических массивов так же можно обращаться с помощью ссылок и разыменовывания.
Теперь разберемся с таким понятием как функция. Вообще, все это время наш код запускался из функции которая называется main
. С этой функции начинается выполнение любой программы на C++.
Разберем синтаксис функций. Функция создается почти так же как и переменная. В начале тип данных который будет возвращать функция, после идет имя функции, которое, кстати, не может совпадать с именами переменных и других функций, количество аргументов которых одинаково. Ну и рядом с именем круглые скобки с этими самыми аргументами. Тело функции окружено фигурными скобками, что создает новую область видимости.
Страшно и сложно. Посмотрим на пример
int function(int a){
int b = a * a;
return b;
}
Теперь посмотрим как это использовать
int function(int a){
int b = a * a;
return b;
}
int main(){
int a = function(5);
cout << "5^2 = " << a << endl;
}
Что тут происходит? Мы создаем переменную a
и после выполняем функцию function
с аргументом 5. Это значит что внутри функции создалась переменная a
, которая, к слову, никак не связанна с той переменной a
которая находится в функции main
. И теперь, уже в функции function
, мы создаем переменную b
, которую сразу инициализируем значением a * a
(a
в нашем случае равно 5
). И в самом конце мы возвращаем переменную b
, которая уже равна 25
.
Теперь уже в главной функции мы записываем в переменную a
значение которое вернула function
, то есть 25.
Функция должна быть объявлена до ее использования. Это значит что все функции будут располагаться вверху файла.
Дополнительно
#include
- пишется в начале файла. Подключает так называемые библиотеки, которые содержать функции, написанные другими программистами. Для работы с консолью нужна библиотека iostream
, пишется все вместе вот так: #include <iostream>
<math.h>
- библиотека с математическими функциями, такими как степень, модуль, синус, косинус, корень, и другие. Полный список можно найти в интернете.
using namespace std
- Подключает стандартное пространство имен. Что это такое пока не важно, это из ООП. Важно то, что если не написать эту строку, то все придется писать с std::
в начале. Вот как будет выглядеть создание переменной и вывод в консоль:
int a = 0;
std::cout << a << std::endl;
<string>
- модуль со строковым типом данных, которого в "чистом" С++ нету.
Небольшой экскурс. Строки, это на самом деле массивы символов, и чтоб не возникало возни с этим, создали модуль, который упрощает работу с ними. Если интересно как это устроенно на программном уровне, погугли "С-строки".
#include <Windows.h>
- Модуль, который позволяет взаимодействовать с WinAPI, проще говоря позволяет управлять системой виндовс. В нашем случае его можно использовать для этих двух строк.
SetConsoleCP(CP_UTF8);
SetConsoleOutputCP(CP_UTF8);
Первая строка задает кодировку для вывода консоли, вторая для ввода. Проще говоря, позволяет печатать на других языках по мимо английского.
Файл с кодом, приведенным в этой статье, ты можеш найти по этой ссылке на гитхаб
Заключение
Это далеко не полное руководство, ни по С++, ни по программированию в целом. Это всего лишь небольшой обзор самых основ программирования, и структуры выполнения кода в компьютере. Очень надеюсь, что мне удалось заинетерсовать тебя в дальнейшем изучении программирования.
Для дальнейшего изучения рекомендую сайт metanit. А так же можешь отдельно погуглить что-то из того что описано в этой статье, для более глубокого понимания темы.
Удачи ?