Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Структура программы на С++. Консольный ввод и вывод.
Пространство имен.
Пример.
#include <iostream>
int main( )
{
std ::cout << "Hello World! \n";
return 0;
}
Построчно рассмотрим назначение инструкций программы.
#include <iostream>
При каждом запуске компилятора запускается препроцессор, он «читает» исходный текст программы, находит строки, начинающиеся с символа # и обрабатывает их перед началом компиляции программы. Файл iostream (Input-Output-Stream поток вода/вывода1) используется объектом cout, который обслуживает процесс вывода данных на экран.
Каждый поток (за исключением строковых) ассоциируется как взаимодействие операционной системы с определенным внешним устройством.
Команда include (подключить) также инструкция препроцессора, которая указывает, что далее следует имя файла, который необходимо найти и подключить. Угловые скобки, в которые заключается имя подключаемого файла, означают, что файл нужно искать во всех папках, отведенных для хранения подобных файлов.
int main( )
Основной код программы начинается с вызова функции main(), ее содержит каждая программа на языке С++. Функция main() имеет тип int, что значит, а это означает, что по окончании работы функция возвратит операционной системе целое число. В данном случае будет возвращено число «0» (инструкция return 0). Возвращение значения в ОС не столь важно и самой системой почти не используется, указанная строка всего лишь требование стандарта языка С++ к корректному объявлению функции main( ).
Все функции начинаются открывающей «{» и заканчиваются закрывающей «}» фигурной скобкой, между ними располагается тело функции.
std ::cout << "Hello World! \n";
Каждый оператор в С++ должен заканчиваться точкой с запятой, «;» иначе называется признаком конца оператора.
Объект cout используется для вывода сообщений либо данных на консоль (экран) Console Output. Этот объект содержится в стандартной библиотеке. Для указания же компилятору, что будет использован объект именно из нее, используется спецификация пространства имен std.
Пространство имен (namespace) это некая объявляемая область, необходимая для того, чтобы избежать конфликта имен идентификаторов, объектов. Существует вероятность приобрести аналогичные объекты с тем же именем от другого поставщика программных компонентов, и чтобы компилятор «понял», частью какого пространства является используемый объект, перед словом cout помещается спецификация std в сопровождении двух двоеточий:
std :: cout.
За словом cout следует оператор перенаправления потока вывода ( символ << 2), все, что следует за этим оператором (правый операнд) выводится на экран. Например, следующая инструкция выводит на экран компьютера число, хранящееся в переменной с:
cout << c ;
Два заключительных символа строки управляющие символы «\n», означают, что после строки текста необходимо выполнить переход на новую строку (обратный слэш «\» называется знаком перехода), т.е. все, что следует после символов «\n», будет показано с новой строки, сами символы на экран не выводятся.
Рассмотрим следующий фрагмент программного кода:
int a=5;
int b=16;
int c=a+b;
std::cout<< "There is variable c: "<<c<<"\n";
В последней строке объекту cout передаются три значения, и каждое отделяется оператором вывода: строка символов "There is variable c: "; значение переменной с; символы новой строки \n. Поскольку после первого значения нет символа начала новой строки, следующее значение выводится вслед за предыдущим:
There is variable c: 21_
К управляющим символам относятся так же:
{
std::cout<< "Hello!";
std::cout<< std::endl;
std::cout<<3+6<< std::endl;
}
Результат работы программы:
Hello!
9
Press any key to continue_
Типы данных в С++. Определение переменных. Особенности использования некоторых типов данных, переполнение регистров переменных.
Переменная место в оперативной памяти компьютера (RAM Random Access Memory), где можно размещать хранимое значение, а затем извлекать его для дальнейшего использования. Значение сохраняется в переменной до выключения компьютера.
Определяя переменную в С++, необходимо предоставить компилятору информацию о ее типе. Тогда компилятор будет знать, сколько место (какой объем оперативной памяти) нужно зарезервировать для хранения переменной, и какого рода значения будут в ней храниться.
В оперативной памяти любой тип занимает определенный объем, но у различных типов компьютеров он может оказаться разным. Тип integer (целое число) может иметь размер в два байта на одной машине и четыре на другой, но в пределах одного компьютера он будет одним и тем же. Размер целого числа определяется системой компьютера (16- или 32-разрядная) и используется компилятором.
Задача определения размеров переменных различных типов на конкретном компьютере может быть решена с использованием функции sizeof( ). Функция поддерживается каждым компилятором, и возвращает размер объекта, переданного ей в качестве параметра. Например запись вида sizeof(int) позволит получить размер переменной типа int.
Таблица
Тип данных |
Размер в байтах |
Диапазон |
char |
1 |
-128 - 127 (256 значений символов) |
unsigned char |
1 |
0 - 255 |
bool |
1 |
true или false |
unsigned short int (unsigned short) |
2 |
0 - 65 535 |
short int (short) |
2 |
-32 768 - 32 767 |
unsigned long int (unsigned long) |
4 |
0 - 4 294 967 295 |
long int (long) |
4 |
-2 147 483 648 - 2 147 483 647 |
unsigned int (unsigned) |
2 или 4 (16/32 байта) |
0 - 65 535 или 0 - 4 294 967 295 |
int |
2 или 4 (16/32 байта) |
-32 768 - 32 767 или -2 147 483 648 - 2 147 483 647 |
float |
4 |
1, 2е-38 - 3,4е38 |
double |
8 |
2, 2е-308 - 1,8е308 |
Представление целых чисел
Все целочисленные типы существуют в двух вариантах: signed (знаковые положительные и отрицательные) и unsigned (беззнаковые - положительные). Такое деление связано с тем, что отрицательные числа иногда нужны, а иногда нет.
Данные, имя типа которых включает модификатор signed, являются данными со знаком, имя типа которых содержит модификатор unsigned данными без знака. При отсутствии модификатора по умолчанию принимается модификатор signed.
Поскольку как для знаковых, так и для беззнаковых целых отводится одно и тоже число байтов (см. Таблицу 1), то максимальное число, которое можно хранить в беззнаковом целом, вдвое превышает максимальное положительное число, которое можно хранить в знаковом целом.
Например, переменные типа unsigned short int, как правило, имеют размер, равный двум байтам, и могут хранить значение, не превышающее 65 535. Диапазон же типа signed short int (short) поделен между положительными и отрицательными числами, поэтому их максимальное значение вдвое меньше, чем у беззнаковых (от -32 768 до 32 767).
Определение переменных
Чтобы создать или определить переменную, необходимо указать ее тип, за которым (после одно или нескольких пробелов) должно следовать ее имя, завершающееся точкой с запятой:
Тип ИмяПеременной;
Допустимо создание нескольких переменных с помощью одного оператора, указав их тип и перечень имен переменных, разделенных запятыми:
Тип ИмяПеременной_1, ИмяПеременной_2, …, ИмяПеременной_n;
Для присвоения значений переменным используется следующая запись:
Тип ИмяПеременной;
ИмяПеременной = значение;
Эти две строки можно объединить в одну и инициализировать переменную в процессе ее определения:
Тип ИмяПеременной = значение;
Инициализация переменных напоминает присвоение. Существенное отличие инициализации от присвоения состоит в том, что первая происходит в момент создания переменной. Подобно тому, как можно определять сразу несколько переменных, можно и инициализировать сразу несколько переменных. Например:
// создаем две переменных типа long и инициализируем их
long а = 5, b = 7;
Здесь переменная а типа long int инициализируется значением 5, а переменная b того же типа значением 7. Можно комбинировать определение и инициализацию:
int a = 42, b, c = 41;
Созданы три переменные, а инициализированы две первая и третья.
Особенности использования некоторых типов данных:
переполнение регистров переменных
Переменные имеют максимально допустимое значение. Что произойдет при превышении этого предела? Когда беззнаковое целое достигает своего максимального значения, то при очередном добавлении оно сбрасывается в ноль и отсчет начинается сначала.
Пример. Поместим слишком большое число в переменную типа unsigned short.
#include <iostream>
int main( )
{
using std::cout;
using std::endl;
unsigned short int smallNumb;
smallNumb=65535; // максимально 65 535
cout<<"Значение smallNumb:"<<smallNumb<<endl;
//значение переменной должно стать 65 536
smallNumb++;
//переменная сбрасывается в 0
cout<<" Значение smallNumb:"<<smallNumb<<endl;
//переманная увеличивается на 1 но уже с 0
smallNumb++;
cout<<" Значение smallNumb:"<<smallNumb<<endl;
return 0;
}
Результат:
Значение smallNumb: 65 535
Значение smallNumb:0
Значение smallNumb:1
Половина диапазона значений знаковых переменных отрицательные числа. Для понимания представления этого типа данных уместна аналогия циферблата, на котором числа увеличиваются при движении от полудня по часовой стрелке и уменьшаются при движении против. Один час от полудни является либо 1 (по часовой стрелке), либо -1 (против). Когда положительные числа будут исчерпаны, начнутся самые большие отрицательные.
Т.е. в случае приращения максимального положительного целого числа со знаком будет получено не нулевое значение (как в случае с беззнаковыми целыми), а максимальное отрицательное число.
Пример. Добавим единицу к максимально возможному числу, хранящемуся в переменной типа short int:
#include <iostream>
int main() {
using std::cout;
using std::endl;
short int Numb;
Numb=32767; // диапазон -32 768 до 32 767
cout<<"Значение Numb:"<<Numb<<endl;
//значение переменной увеличим на 1
Numb++;
//переменная имеет значение максимального отрицательного
cout<<" Значение Numb:"<<Numb<<endl;
Numb++;
cout<<" Новое значение Numb:"<<Numb<<endl;
return 0;
}
Результат:
Значение Numb: 32 767
Значение Numb: -32 768
Значение Numb: -32 767
Операторы С++. Базовые конструкции структурного программирования, условный оператор.
Объединение оператора присвоения и арифметических операторов. Любая инструкция типа Sum = Sum + I, смысл которой состоит в увеличении переменной Sum на величину i, и присвоении результата переменной Sum, может быть записана:
Sum + = i
Оператор присвоения с суммой («+ =») добавляет r-значение к l-значению, а затем присваивает результат l-значению. Если бы до начала выполнения выражения значение переменной Sum было равно 21, а i рано 1, то результат оказался бы равным 22.
Все арифметические операторы позволяют использовать такую же запись:
x = x * i x * = i
x = x / i x / = i
x = x % i x % = i
Инкремент и декремент. Если оператор «+ =» применить в инструкции увеличения переменной на 1 (инкремент) или уменьшения переменной на 1 (декремент), что в программах встречается очень часто, то можно записать вместо выражения i + = 1выражение i ++ , а вместо выражения i - = i --.
Пример. Рассмотрим программу:
#include <iostream>
using namespace std;
int main( ) {
int N=2000000;
int sum=0, i=1;
while (i<N) {
sum = sum + i;
i=i+1; }
cout<<"sum="<<sum<<endl;
return 0;}
Тело функции main( ) в этой программе может быть записано:
int main( ){
int N=2000000;
int sum=0, i=1;
while (i<N)
sum += i++;
cout<<"sum="<<sum<<endl;
return 0;}
Операторы инкремента и декремента существуют в двух вариантах: префиксном и постфиксном. Префиксный вариант записывается перед именем переменной (+=sum), а оператор увеличения значения вычисляется до присвоения. Постфиксный записывается после имени переменной (sum +=), оператор выполняется после присвоения.
Другими словами смысл префиксного оператора: изменить значение, а затем присвоить его. Смысл постфиксного: присвоить значение, а затем изменить его оригинал.
#include <iostream>
using namespace std;
int main( ){
int i=1, j=1;
int sumPr =++i;
int sumPst = j++;
cout<<" Prefics sum="<<sumPr<<endl;
cout<<" Postfics sum="<<sumPst<<endl;
return 0;}
Выражение int sumPr =++i; сообщает компилятору, что переменную i необходимо увеличить на 1, сделав ее равной 2, а затем присвоить это значение переменной sumPr. Выражение же int sumPst = j++; предписывает компилятору сначала присвоит переменной sumPst текущее значение переменной j (j=1) и только после этого увеличить j на единицу. Следовательно результат работы программы:
Prefics sum= 2
Postfics sum= 1
Логические операторы для программирования сложных условий. Для обозначения результатов логических выражений в стандарте ANSI применяется тип bool, имеющий только два возможных значения: true (истина для определения равенства двух значений) и false (ложь для определения неравенства двух значений). Тогда результатом любого логического выражения может быть либо истина, либо ложь. Выражение, содержащее операторы отношения (выражения сравнения) всегда возвращают значения true либо false. Математические выражения, возвращающие ноль, можно рассматривать как значение false, а любой другой результат как true.
Случается так, что программа выполнит какое-то действие только в случае соблюдения двух и большего числа условий. Например, для попадания в какой-то интервал переменная должна быть одновременно больше чего-то одного и меньше чего-то другого
0 <x<1. Поэтому в языке С++ есть специальные операторы, помогающие записывать сложные условия. Например, условие попадания числа x в указанный интервал запишется как x >9 && x < 10. В нем сначала проводятся сравнения x >9 и x < 10, потому, что приоритет операторов <>, выше, чем оператора &&, а затем с результатом сравнений работает оператор. Оператор && называется логическим (логическое AND). Кроме него существуют еще два логических оператора, они приведены в Таблице 3.
Таблица 3
Оператор |
Символ |
Пример |
AND (И) |
&& |
выражение1 && выражение2 |
OR (ИЛИ) |
| | |
выражение1 | | выражение2 |
NOT (НЕ) |
! |
! выражение |
Оператор AND (И) двухаргументный (оценивает два операнда). Результат оператора:
Например, логическое выражение в составе оператора условия
if ( (x = = 5) && (y = = 5) )
возвратит значение true только в том случае, если x и y равны числу 5 и false, если хотя бы одна из переменных не равна 5.
Оператор OR ( ИЛИ) двухаргументный. Результат оператора:
Например, логическое выражение в составе оператора условия
if ( (x = = 5) | | (y = = 5) )
возвратит значение true в том случае, если хотя бы одна их переменных x и y равна числу 5 или они обе равны 5, и false, если обе переменные не равны 5.
Оператор NOT является одноаргументным (оценивает только один операнд). Результат противоположен значению операнда:
Например, логическое выражение в составе оператора условия
if ( ! (x = = 5))
возвратит значение true только в том случае, если x не равен числу 5. Это выражение можно записать и по-другому:
if (x != 5)
Базовые конструкции структурного программирования
В теории программирования доказано, что программу для решения задачи любой сложности можно составить только из трех структур: линейной, разветвляющейся и циклической. Эти структуры называются базовыми конструкциями структурного программирования.
Линейной называется конструкция, представляющая собой последовательное соединение двух или более операторов. Ветвление задает выполнение одного из двух операторов, в зависимости от выполнения какого либо условия. Цикл задает многократное выполнение оператора.
Целью использования базовых конструкций является получение программы простой структуры. Такую программу легко читать, отлаживать и при необходимости вносить в нее изменения. Структурное программирование также называют программированием без go to, т. к. частое использование операторов перехода затрудняет понимание логики работы программы. Но иногда встречаются ситуации, в которых применение операторов перехода, наоборот, упрощает структуру программы.
Условный оператор IF
Как правило, программа выполняется строка за строкой в том порядке, в котором они записаны в программном коде. Современные программы отличаются тем, что их команды могут выполняться не последовательно (одна за другой), а по блокам, причем некоторые блоки могут запускаться на выполнение или нет в зависимости от выполнения определенного условия.
Оператор if позволяет проверить нужное условие и в зависимости от результата выполнить тот или иной участок кода.
Первая форма оператора If
if (выражение)
оператор;
следующий оператор;
Выражение в круглых скобках является условием и содержит, как правило, операторы отношения. Если это выражение возвращает false, то следующий за выражением в скобках оператор пропускается (игнорируется) и выполняется следующий оператор. Если же оно возвращает true, то оператор выполняется, а за ним выполняется следующий оператор.
Пример 1.
#include <iostream>
int main( ) {
using namespace std;
int a,b;
cout<<"Enter meaning variable a:";
cin>>a;
cout<<"Enter meaning variable b:";
cin>>b;
if (a>b)
a=a+b;
cout<<"Variable a is: "<<a<<endl;
cout<<"Variable b is: "<<b<<endl;
return 0; }
Здесь вводимые значения переменных a и b определяют возвращаемое выражением значение. Результаты работы программы при различных значениях переменных:
Enter meaning variable a: 5
Enter meaning variable b: 3
Variable a is: 8
Variable b is: 3
Enter meaning variable a: 5
Enter meaning variable b: 8
Variable a is: 5
Variable b is: 8
Вторая форма оператора If
Довольно часто в программах требуется, чтобы при выполнении условия (т.е., когда оно возвратит true) программа выполняла один блок кода, а при его невыполнении другой.
if (выражение)
оператор1;
else
оператор2;
следующий оператор;
Если выражение возвращает true, то выполняется оператор1, в противном случае выполняется оператор2, затем программа переходит к следующему оператору.
Пример 2.
#include <iostream>
int main( ) {
using namespace std;
int a,b;
cout<<"Enter meaning variable a:";
cin>>a;
cout<<"Enter meaning variable b:";
cin>>b;
if (a>b)
a=a+b;
else
a=a*b;
cout<<"Variable a is: "<<a<<endl;
return 0;}
Результаты работы программы:
Enter meaning variable a: 5
Enter meaning variable b: 3
Variable a is: 8
Enter meaning variable a: 5
Enter meaning variable b: 7
Variable a is: 35
В случае, когда число операторов, следующих за условием больше одного, используется блочная форма записи оператора if. Блоком называется составной оператор, начинающийся открывающей фигурной скобкой «{» и заканчивающийся закрывающей скобкой «}». Каждый оператор отделяется в блоке от другого точкой с запятой, сам же блок ею не заканчивается после закрывающей фигурной скобки точка с запятой не ставится:
{
оператор1;
оператор2;
…
операторN;
}
Т.е. вместо одиночного оператора в приведенных выше двух форматах записи условного оператора может стоять блок операторов.
Третья форма оператора If
В операторе If может находиться любой оператор, в том числе и If …Else. Тогда форма записи условного оператора примет вид:
if (выражение1)
{
if (выражение2)
оператор1;
else
{
if (выражение3)
оператор2;
else
оператор3;
}
}
else
оператор4;
Порядок работы этого оператора:
Пример. Решим систему уравнений:
#include <iostream>
int main( ) {
using namespace std;
float x, y;
cout<<"Enter number: \n";
cin>>x;
cout<<"\n";
if (x>1)
y=x*x+x*x*x;
else
{ if (x>=0 && x<=1)
y=x*x+2*x;
else
y=x*x+2*x*x; }
cout<<"y: "<<y<<endl;
return 0;}
Троичный условный оператор
Троичный условный оператор «? :» единственный оператор языка С++, который работает с тремя операндами, он получает три выражения и возвращает значение:
(выражение1) ? (выражение2) : (выражение3)
Смысл записи: если выражение1 истинно, возвратить значение выражения2, в противном случае возвратить значение выражения3.
Пример. Вот как будет выглядеть решение задачи о нахождении большего из двух значений.
#include <iostream>
int main( ){
using namespace std;
int x, y,z;
cout<<"Enter two number: ";
cin>>x;
cin>>y;
z =(x>y)? x : y;
cout<<"z: "<<z<<endl;
return 0;}
Ветвление процесса выполнения программ
Для решения многих задач требуется многократное повторение одних и тех же действий. На практике это реализуется либо с помощью рекурсии либо с помощью итерации. Итерация это повторение одних и тех же действий определенное количество раз. Основным методом итерации является цикл. Циклы в С++ могут быть трех видов:
Оператор цикла While
С помощью оператора цикла в программе создается цикл, который будет повторять последовательность операторов до тех пор, пока условие, определенное в начале цикла остается истинным.
Синтаксис оператора цикла с предусловием следующий:
while (выражение условия)
{
операторы тела цикла;
}
Выражение условия это любое выражения языка С++ , а оператор любой допустимый оператор или блок операторов. Если выражение истинно возвращает значение 1, выполняется тело цикла (блок операторов), после чего выражение условия проверяется снова. Так будет продолжаться до тех пор, пока выражение не станет ложным (0); тогда цикл while завершится, а выполнение программы продолжится со следующей строки.
Таким образом, в цикле с предусловием сначала выполняется проверка условия, а затем, в зависимости от результата проверки, выполняется или нет тело цикла.
Пример. В приведенном ниже листинге программы на экран выводятся все значения переменной х, начиная с начального (х=0), увеличиваясь на единицу с каждой итерацией, до тех пор, пока х < 5.
Сначала проверяется условие, и если оно истинно, то выполняется тело цикла. В данном случае условию удовлетворяют все х значения переменной х, меньшие 5. Если условие истинно, то значение переменной х увеличивается на 1 (выполняются операторы тела цикла), а следующая строка выводит это значение на экран. Как только значение счетчика достигает 5 (условие ложно), все тело цикла пропускается и программа переходит к строке 11.
Результат работы программы:
x= 1
x= 2
x= 3
x= 4
x= 5
Complete x: 5
Условие, проверяемое в цикле while может состоять из нескольких выражений, объединенных логическими операторами: &&, | |, ! . Например, приведенная ниже программа демонстрирует реализацию логической задачи. Вводятся два положительных числа одно меньше, другое больше, их значения размещаются соответственно в переменных small и large. Далее меньшее начинает увеличиваться на единицу, а большее уменьшаться на два, процесс продолжается до тех пор, пока значения чисел не «встретятся». Цель задачи определить эти числа. Для решения необходимо проверить три условия продолжения цикла:
- меньшее число меньше большего (small < large);
- большее больше нуля (0 < large);
- меньшее число меньше максимально допустимого.
Результат
Enter a small number: 2
Enter a large number: 100000
Small: 33335 Large: 33334
Оператор цикла do…while
Цикл с постусловием является противоположностью цикла с предусловием. Возможен вариант, когда тело цикла while вовсе не будет выполнено. Оператор цикла while проверяет выражение условия цикла до того, как приступит к выполнению операторов тела цикла, и если условие ложно с самого начала, то тело цикла будет пропущено.
// Пример игнорирования цикла при заведомо ложном условии
#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
char str[20];
int x;
CharToOem("Сколько приветов? : ", str);
cout<<str;
cin>>x;
while (x>0)
{
CharToOem("Привет!\n", str);
cout<<str;
x--;
}
return 0;
}
Тема: Указатели и ссылки
Вопрос №1 Понятие указателя
Адреса переменных. Оператор взятия адреса
Задача. Создать функцию, предназначенную для обмена значений двух однотипных переменных.
// Решение
#include <iostream>
using namespace std;
void Obmen(int x, int y)
{
int temp;
temp=x;
x=y;
y=temp;
// обмен выполнен
cout<<"В Obmen () x = "<<x<<endl; // здесь x = 21
cout<<"В Obmen () y = "<<y<<endl; // здесь y = 7
}
void main()
{
int a = 7, b=21;
cout<<"В main() до вызова Obmen ()… a = "<<a<<endl;
cout<<"В main() до вызова Obmen ()… b = "<<b<<endl;
Obmen (a,b); //вызов
cout<<"В main() после вызова Obmen () a = "<<a<<endl;
cout<<"В main() после вызова Obmen () b = "<<b<<endl;
}
Результат:
В main()до вызова Obmen () a = 7
В main()до вызова Obmen () b = 21
В Obmen () x = 21
В Obmen () y = 7
В main() после вызова Obmen () a = 7
В main() после вызова Obmen () b = 21
Задача не решена, функция Obmen () не влияет на значение переменных a и b в основной программе. Переменные a, b и параметры функции Obmen (int x, int y) «соприкасаются» лишь в момент ее вызова, и внутри функции x = 21, y = 7 (обмен выполнен). Но когда происходит возврат в основную программу, переменные x и y внутри функции пропадают, а переменные a и b основной программы остаются теми же.
Решение данной проблемы использование в качестве передаваемых параметров функции Obmen () аргументов не переменных a и b, а их адресов.
Оперативная память компьютера разделена на последовательно пронумерованные ячейки. Каждая переменная размещается в одной или нескольких последовательно расположенных отдельных ячейках памяти, а адрес первой ячейки называется адресом переменной. Получить реальный адрес переменной можно с помощью оператора обращения к адресу (&), который возвращает адрес объекта, расположенного в памяти. Значение адреса переменной специфично для каждого компьютера.
// Фрагмент программы.
// Использование оператора обращения к адресу переменной
unsigned short a = 5;
//получаем значение адресов переменных
cout<<"Adres a: "<<&a<<"\n";
Результат:
Adres a: 0012FF7C
Указатели
Для сохранения значения адреса используется специальный тип переменной указатель.
Указатель (pointer) это переменная, значением которой является адрес другой переменной, другого объекта или функции в памяти компьютера и позволяющая косвенно манипулировать ими.
Для объявления указателя используется символ звездочка:
тип_переменной * имя_указателя;
Здесь тип_переменной это тип, на который ссылается указатель. Например:
int * ptr ; //переменная ptr объявляется указателем на тип int
Эта строка означает, что ptr указатель, который будет содержать адрес переменной типа int.
тип_переменной * имя_указателя = инициализатор;
Существуют следующие способы инициализации указателя:
тип_переменной * имя_указателя = & имя_переменной;
Пример
// фрагмент кода
int a = 7;
cout<<"Znachenie a = "<<a <<endl;
int *ptr=&a; // объявляется указатель и инициализируется
// значением адреса переменной а
*ptr = 5; //значение переменной корректное значение указателя
cout<<" Znachenie a = "<<a <<endl;
Результат:
Znachenie a = 7
Znachenie a = 5
Связь между значением переменной и указателем
Обратите внимание, что как только объявляется указатель и в нем оказывается адрес переменной a, возникает «тайная тропа» к ней и после инструкции *ptr = 5; переменная становится равной 5.
Ссылка на значение, содержащееся в переменной, посредством указателя называется косвенной адресацией.
Когда компилятор обрабатывает оператор определения переменной, (например, int i=10;), то в памяти выделяется участок в соответствии с типом переменной (для int 4 байта) и записывает в этот участок указанное значение.
После чего все обращения к этой переменной компилятор заменит на адрес области памяти (адрес первой ячейки области памяти, отведенной под переменную), в которой хранится эта переменная.
Оператор разыменования
Чтобы возвратить значение по адресу, содержащемуся в указателе, перед его именем устанавливается оператор разыменования или косвенного обращения к объекту звездочка (*):
тип_переменной имя_переменной = *имя_указателя;
Т.е. с помощью указателя и оператора (*) можно получить значение переменной, адрес которой содержит указатель. Подобный способ доступа и называется косвенным.
// Пример_2. (фрагмент программы)
void main()
{
int a = 10; // определение переменной a
int *p=&a; // инициализация указателя
*ptr=*ptr+11; // косвенный доступ к а и неявное увеличение ее
// значения
cout<<"Znachenie a = "<< a <<endl;
}
Результат:
Znachenie a = 21
Как видно действие оператора *ptr=*ptr+11; аналогично оператору а =а+11;, а запись *ptr в контексте оператора *ptr=*ptr+11; по существу обозначает саму переменную a.
Важно понимать разницу между указателем, адресом, хранимым в указателе и значением переменной, расположенном по адресу, содержащемуся в указателе. Следующая схема иллюстрирует это.
Форматы использования оператора разыменования
В указателях оператор (*) имеет два разных назначения: как оператор объявления указателя и как оператор разыменования указателя для доступа к значению переменной, адрес которой хранит указатель. При этом выражение вида
*указатель
ведет себя по-разному, в зависимости от своего местоположения по отношению к оператору присваивания:
справа от оператора присваивания, что означает «взять значение переменной, адрес которой содержит указатель, и присвоить его какой-либо переменной». Например,
//Пример.
#include <iostream>
using namespace std;
int main()
{
int a,b,c;
int *ptr=NULL; //указатель инициализирован пустым значением
a=7;
b=21;
ptr=&a;
c=*ptr;
cout<<"Value c: "<<c<<endl<<endl; //c=7
//конструкция c = *ptr означает c = *(&a) или
//что то же самое c=a т.е. (*) и (&) взаимно стираются
c=*ptr+21;
cout<<"Value c: "<<c<<endl<<endl; //c=28
ptr=&b;
c=*ptr;
cout<<"Value c: "<<c<<endl<<endl; //c=21
return 0;
}
// Пример.
#include <iostream>
using namespace std;
int main()
{
int b;
int *ptr;
ptr=&b;
*ptr=21;
//конструкция *ptr = 21 означает *(&b) = 21 или
//что то же самое b=21 т.е. (&) и (*) взаимно стираются
cout<<"Value b: "<<b<<endl<<endl; //b=21
return 0;
}
Динамически распределяемая память
На практике использование указателей, так как это было показано выше, встречается редко примеры приведены для демонстрации механизма работы указателей.
Реально указатели применяются в следующих случаях:
Все переменные, объявленные в программе размещаются в одной непрерывной области памяти, которую называют сегментом данных. Такие переменные не меняют своего размера в ходе выполнения программы и называются статическими. Размера сегмента данных может быть недостаточно для размещения больших массивов информации. Выходом из этой ситуации является использование свободной или динамически распределяемой памяти (динамической памяти).
Динамическая память может быть представлена как огромный массив последовательно пронумерованных ячеек, предназначенных для хранения данных.
Ячейкам свободной памяти нельзя присвоить имя, можно лишь зарезервировать определенное количество ячеек и запомнить их адрес в указателе. С помощью указателей и осуществляется доступ к участкам динамической памяти.
Важным преимуществом динамической памяти является то, что выделенная в ней область не может использоваться в других целях до тех пор, пока не будет освобождена явно. Поэтому, если во время работы функции в динамической памяти выделяется область, ее можно использовать и по завершении работы функции. Другим преимуществом использования динамической памяти является то, что доступ к данным в ней можно получить только из тех функций, которые обладают доступом к указателю, хранящему нужный адрес. Это позволяет избегать нежелательного или случайного изменения данных.
Для выделения участка памяти в динамически распределяемой области используют ключевое слово new:
указатель = new выражение;
Аргументом для оператора new служит выражение, возвращающее число байтов, которые необходимо зарезервировать в области динамической памяти.
Например, чтобы создать в динамической памяти переменную типа unsigned short необходимо записать:
unsigned short int *ptr = new unsigned short int;
*ptr = 21;
можно прочитать: «разместить число 21 в той области динамически распределяемой памяти, на которую указывает ptr».
По завершении работы с выделенной областью памяти ее следует освободить. Сама она не освобождается автоматически при выходе из функции и остается занятой, а программа не сможет ею воспользоваться. Если функция часто вызывается, памяти будет оставаться все меньше и наступит момент, когда ее не останется вовсе, и программа аварийно завершиться. Чтобы этого не произошло, когда необходимость в выделенном участке памяти отпадает, его надо освободить используя оператор delete:
delete указатель;
Здесь указатель содержит адрес участка памяти, ранее выделенный с помощью операции new.
Например,
delete p;
где p ранее объявлен как указатель.
Так происходит освобождение участка динамической памяти, адрес которого находится в указателе. Сам указатель при этом сохраняется, как обычная переменная и ему может быть передан на хранение другой адрес.
Когда оператор delete применяется к указателю, происходит освобождение области динамической памяти, на которую этот указатель ссылается. Повторное применение оператора delete к этому же указателю приведет к зависанию программы. Рекомендуется при освобождении области динамической памяти присваивать связанному с ней указателю нулевое значение (0 либо NULL) вызов оператора delete для нулевого указателя не приведет к описанной выше проблеме.
Ниже приведен пример создания и удаления указателей.
//Пример_9
#include <iostream>
using namespace std;
int main()
{
int Var = 7; // объявление и инициализация переменной Var
int *ptrVar =&Var; // объявление указателя и присвоение ему адреса
int *ptrHeap=new int; // объявлен еще один указатель, а в динамической
// памяти выделено пространство для переменной типа int
*ptrHeap=21; // участку динамической памяти присвоено значение
cout<<"Value Var: "<<Var<<endl; // вывод содержимого переменной Var
cout<<"Value *ptrVar: "<<*ptrVar<<endl; // вывод значения,
//на которое указывает ptrVar
cout<<"Value *ptrHeap: "<<*ptrHeap<<endl; // вывод значения, на
// которое указывает ptrHeap
delete ptrHeap; // освобождение участка динамической памяти,
// указатель ptrHeap пуст и пригоден для записи
// адреса другого участка памяти
ptrHeap = new int; // в динамической памяти вновь выделено
// пространство для переменной типа int
*ptrHeap=3; // участку динамической памяти присвоено новое значение
cout<<"Value *ptrHeap: "<<*ptrHeap<<endl; // вывод значения, на
// которое указывает ptrHeap
delete ptrHeap; // освобождение участка динамической памяти
return 0;
}
Указатели на объекты
Создание и удаление объектов в динамической памяти
Если объявлен некоторый объект, то для работы с ним можно создать указатель, хранящий адрес объекта. Синтаксис резервирования места под объект в динамической памяти:
имя_класса *указатель = new тип_объекта;
Здесь имя_класса имя класса, адрес экземпляра которого, размещенного в динамической памяти будет хранить объявляемый указатель, а тип_объекта спецификатор типа объекта (экземпляра класса), для размещения которого выделяется память.
Приведенную запись можно рассматривать как создание объекта в динамической памяти, адрес которого сохраняется в указателе. Например:
Сar *ptrCar = new Car;
Следует помнить, что при создании объектов класса конструктор вызывается всегда, независимо от того размещается объект в стеке или в области динамической памяти.
При использовании оператора delete для указателя на объект вызывается деструктор соответствующего класса. Это происходит еще до освобождения участка памяти и возвращения его в область свободно динамической памяти.
Рассмотрим пример:
#include <iostream>
using namespace std;
//описание класса
class Car
{
public:
Car();
~Car();
private:
unsigned int itsYear;
};
Car::Car()
{
itsYear=5;
cout<< "Constructor called\n";
}
Car::~Car ( )
{
cout<< "Destructor called\n";
}
int main( )
{
cout<<"Static memory. Creation object…"<<endl;
Car myZaparozec; // в стеке создается объект myZaparozec
cout<<"Dynamic memory. Creation object…"<<endl;
Car *ptrCar=new Car; // в динамической памяти
//резервируется место под объект класса Car, адрес
// участка сохраняется в указателе ptrCar
cout<<"Delete object..."<<endl;
delete ptrCar; //освобождение указателя и вызов деструктора
cout<<"Function main() exit..."<<endl;
return 0;
}
Результат работы программы:
Static memory. Creation object…
Constructor called
Dynamic memory…Creation object
Constructor called
Delete object...
Destructor called
Function main() exit...
Destructor called
Доступ к членам объекта в динамической памяти
Для доступа к переменным-членам и функциям объекта, созданного в динамической памяти, необходимо использовать оператор косвенного доступа стрелка (- >).
#include <iostream>
class myclass
{
int a;
public:
myclass ( ) {a = 10;}
~myclass();
int Show ( );
};
int myclass:: Show ( )
{
return a;
}
int main ()
{
myclass *Object=new myclass; // объект в динамической памяти
std::cout<<"\nBy way of Pointer..."<<Object->Show ()<<"\n";
return 0;
}
Результат:
By way of Pointer...10
Перегрузка функций
Важной возможностью С++ является перегрузка функций (function overloading). Перегрузка функций не только обеспечивает механизм, посредством которого в С++ реализуется механизм, называемый полиморфизмом (многообразие форм), но и формирует то ядро, вокруг которого развивается вся среда программирования на С++. Под полиморфизмом функций понимают существование в программе нескольких перегруженных функций разного назначения. Перегруженные функции позволяют упростить программы, допуская обращение к одному имени для выполнения близких по смыслу действий.
Язык С++ позволяет создать несколько разных функций с одинаковым именем (функции-тезки), при этом функции отличаются:
Если две или более функций имеют одинаковое имя, говорят, что они перегружены.
Существует условие, которому обязаны удовлетворять функции-тезки функции должны отличаться друг от друга списком параметров, а именно:
Например, рассмотрим следующие объявления перегруженных функций:
Int myFunction (int, int);
Int myFunction (long, long);
Int myFunction (long);
Функция myFunction перегружена с тремя различными списками параметров: первая и вторая отличаются типами параметров, а третья типом и количеством.
Типы значений, возвращаемые перегруженными функциями, могут быть одинаковыми или разными. Важно помнить, что при создании двух функций с одинаковым списком параметров, но различными типами возвращаемых значений, возникнет ошибка компиляции.
Механизм перезагрузки функций необходим, например, тогда, когда требуется создать функцию, которая удваивает любое передаваемое ей значение. При этом необходимо иметь возможность передавать ей значения типа int, long, float или double. Без перегрузки функций пришлось бы создавать четыре различные функции:
int DblInt (int);
long DblLongt (long);
float DblInt (float);
double DblDouble (double);
С помощью же перегрузки функций достаточно использовать следующие объявления:
int Dbl (int); // объявление функции удвоения целых чисел
long Dbl (long); // объявление функции удвоения чисел типа long
float Dbl (float); // объявление функции удвоения чисел типа float
double Dbl (double); // объявление функции удвоения чисел типа double
Такую форму легче понять и еще легче использовать, поскольку не нужно думать какую именно функцию надо вызвать, достаточно передать ей переменную и автоматически будет вызвана нужная функция, поскольку требуемая функция определяется компилятором по совпадению используемых параметров.
Вышесказанное рассмотрим на примере. Здесь функция Dbl() перегружается для приема параметров четырех типов.
// полиморфизм функций
#include <iostream>
using namespace std;
// прототипы перегруженных функций
int Dbl (int);
long Dbl (long);
float Dbl (float);
double Dbl (double);
int main ()
{// инициализация переменных
int myInt = 6500;
long myLong=65000;
float myFloat=6.5F;
double myDouble=6.5e20;
// объявление переменных для хранения удвоенного значения
int doubledInt;
long doubledLong;
float doubldedFloat;
double doubledDouble;
// вывод на экран значений переменных
cout <<"myInt: "<<myInt<<"\n";
cout<<"myLong: "<<myLong<<"\n";
cout<<"myFloat: "<<myFloat<<"\n";
cout<<"myDouble: "<<myDouble<<"\n";
// вызов перегруженных функций и
// присвоение переменным возвращаемых ими значений
doubledInt = Dbl (myInt);
doubledLong = Dbl (myLong);
doubldedFloat = Dbl (myFloat);
doubledDouble = Dbl (myDouble);
//вывод значений
cout<<"doubleInt: "<<doubledInt<<"\n";
cout<<"doubleILong: "<<doubledLong<<"\n";
cout<<"doubleFloat: "<<doubldedFloat<<"\n";
cout<<"doubleDouble: "<<doubledDouble<<"\n";
return 0;
}
//определение функций
int Dbl (int original)
{
return 2*original;
}
long Dbl (long original)
{
return 2*original;
}
float Dbl (float original)
{
return 2*original;
}
double Dbl (double original)
{
return 2*original;
}
Результат:
myInt: 6500
myLong: 65000
myFloat: 6.5
myDouble: 6.5е+020
doubleInt: 13000
doubleILong: 130000
doubleFloat: 13
doubleDouble: 1.3е+021
Значения параметров функции, используемые по умолчанию
Правило: для каждого параметра, объявленного в прототипе и определении функции, при вызове функции должно быть предано соответствующее значение. Передаваемое значение должно иметь объявленный тип.
Из этого правила существует одно исключение, которое вступает в силу, если в объявлении функции для параметра объявляется значение по умолчанию. Значение по умолчанию это значение, которое используется в том случае, если при вызове функции для параметра не установлено значение.
Рассмотрим пример объявления функции:
long myFunc (int x = 21);
Запись можно понимать так: функция myFunc4 возвращает значение типа long и принимает целочисленный параметр, но если при ее вызове аргумент не будет представлен, компилятор использует вместо него 21.
Значение по умолчанию можно назначить любому параметру или всем параметрам функции. Но одно ограничение существует: если какой-то параметр не имеет значения по умолчанию, то ни один из предыдущих по отношению к нему параметров не может иметь значения по умолчанию. Например, прототип функции имеет вид:
long myFunc (int param1, int param2, int param3);
Параметру param2 может быть назначение значение по умолчанию только в том случае, если значение по умолчанию назначено и параметру param3. Параметру param1 когда установлены значения по умолчанию и param2 и param3.
Рассмотрим сказанное выше подробно.
// использование значений по умолчанию для параметров функций
Результат.
First: 10000
Second: 5000
Thrid: 2500
Анализ. В прототипе функции Cube() в строке 4 объявляется, что функция принимает три параметра, причем последние два имеют значения, устанавливаемые по умолчанию. Функция вычисляет объем параллелепипеда на основании передаваемых параметров: если значение ширины w не передано, то по умолчанию оно принимается равным 25 , а высоты h в том же случае 1. В строке13 в качестве параметров передаются функции Cube() переменные, инициализированные в строках 9-11 и в строке 16 выводится первое значение объема. В строке 19 функция вызывается вновь, но без параметра, содержащего высоту, и в этом случае используется значение по умолчанию(h=1). При третьем вызове функции Cube() нет w и h, поэтому уже два значения используются определенными по умолчанию.
Массив это упорядоченная последовательность переменных одного типа. Каждому элементу массива отводится одна ячейка памяти. Элементы одного массива занимают последовательно расположенные ячейки памяти. Все элементы имеют одно имя имя массива и отличаются индексами порядковыми номерами в массиве. Количество элементов в массиве называется его размером. Чтобы отвести в памяти нужное количество ячеек для размещения массива, надо заранее знать его размер. Резервирование памяти для массива выполняется на этапе компиляции программы.
Определение массива состоит из спецификатора типа, имени массива и размера в квадратных скобках:
тип_элемента имя_массива [размер]
Размер задает число элементов массива (не менее 1) и должен быть константным выражением.
Например.
int myArray [100]; // массив из 100 элементов целого типа
Операция sizeof(myArray), примененная к объявленному массиву даст результат 400, т. е. 100 элементов по 4 байта.
Небольшие массивы переменных встроенных типов (например, int или char) можно инициализировать при объявлении массива:
тип_элемента имя_массива [размер] = { список_значений}
здесь список_значений перечисление через запятую значений элементов массива
Например.
int a[10]={1,2,3,4,5,6,7,8,9,10} ; //массив из 10 целочисленных значений
или
int b[ ]={1,2,3,4,5,6,7,8,9,10} ;
Если размер массива не указан, но список значений присутствует, то будет создан и инициализирован массив достаточного размера, чтобы содержать все перечисленные значения. Таким образом, две вышеприведенные строки инициализации массивов a и b идентичны.
Рассмотрим пример объявления и заполнения массива.
#include <iostream>
int main()
{
int myArray[3]; //объявление массива
int i;
for (i=0; i<3; i++) //заполнение массива значениями
{
std::cout<<"Value for array [ "<< i <<" ]: "; //ввести значение
std::cin>>myArray[ i ]; //сохранение значения в соответствующем //элементе
}
for (i=0; i<3; i++)
std::cout<<i<<":"<<myArray[i]<<"\n"; // вывод массива
return 0;
}
Использование датчика случайных чисел для формирования массива
В С++ есть функция int rand() возвращающая псевдослучайное число из диапазона 0..RAND_MAX=32767, описание функции находится в файле <stdlib.h>.
В общем случае псевдослучайное n определяется по формуле:
n = a + rand ( ) % b
, где a величина сдвига (равна первому числу из требуемого диапазона последовательности целых чисел), b масштабирующий коэффициент (равен ширине требуемого диапазона).
Пример.
#include<iostream.h>
#include<stdlib.h>
void main()
{
int array[100];
int n;
cout<<"\nEnter the size of array:";
cin>>n;
for(int i=0;i<n;i++)
{
array[ i ]=rand()%100-50;// диапазон значений от -50 до 50
cout<<array[ i ]<<"\n";
}
}
В этой программе используется перебор массива по одному элементу слева направо с шагом 1.
Приведенная программа генерирует появление одной и той же случайной последовательности. Рандомизация достигается путем использования инструкции
srand(time(NULL));
, где функция srand(), содержащаяся в файле stdlib.h, принимает в качестве аргумента текущее календарное время в секундах, это значение преобразуется в беззнаковое целое и используется как начальное значение в генераторе случайных чисел. Прототип функции находится в файле time.h.
#include<iostream.h>
#include<stdlib.h>
#include<time.h>
void main()
{
int array[100];
int n;
cout<<"\nEnter the size of array:";
cin>>n;
srand(time(NULL));
for(int i=0;i<n;i++)
{
array[ i ]=-5+rand()%10; // диапазон значений от -5 до 5
cout<<array[ i ]<<"\n";
}
}
Тема: Классы в С++
Конструкторы и деструкторы
Переменные-члены класса не могут получить начальные значения в определении класса, но, как и обычные переменные должны быть инициализированы. Для этой цели и служит конструктор.
Для конструктора всегда выполняются следующие правила:
Объявив конструктор необходимо объявить и деструктор. Деструкторы удаляют из памяти отработавшие объекты и освобождают выделенную для них память. Правила деструктора:
#include <iostream>
class Car
{
public:
Car(unsigned int initialYear);
~Car();
unsigned int GetYear( );
void SetYear (unsigned int Year);
void Start( );
private:
unsigned int itsYear;
};
using namespace std;
Car::Car(unsigned int initialYear)
{
itsYear=initialYear;
}
Car::~Car ( ) { }
int unsigned Car::GetYear ( )
{
return itsYear;
}
void Car::SetYear (unsigned int Year)
{
itsYear=Year;
}
void Car::Start ()
{
cout<<"Forward!!!\n";
}
int main( )
{
unsigned int Old;
Car myZaparozec(19);
Old=myZaparozec.GetYear ( );
cout<<"The year back to my machine was "<<Old<<" years"<<endl;
myZaparozec.SetYear (20);
Old=myZaparozec.GetYear ( );
cout<<"Now to my machine "<<Old<<" years"<<endl;
myZaparozec.Start ();
return 0;
}
1 С++ не имеет встроенных средств ввода/вывода подобно другим языкам. Даже простейший исходный текст программы располагается в нескольких файлах: например, файл iostream является заголовочным файлом, в котором на языке С++ содержится описание стандартных средств ввода/вывода, в частности консольных потоков cin и cout. Эти потоки в терминах С++ являются объектами классов.
2 Символ << называют так же операцией помещения в поток.
3
4 В прототипе функции имена параметров не обязательны и имеет право существовать следующий вариант объявления: long myFunc (int = 21);