C/С++: быстрый старт. И.А. Волков, Т.В. Гурьянова

Объектно-ориентированное программирование на C/С++: быстрый старт

Книга 4, часть III
И.А. Волков, Т.В. Гурьянова

Книга в *.pdf доступна по ссылке: https://vk.com/doc-86363774_371000814 и https://drive.google.com/folderview?id=0B4icNOfw8DwUeVlFX0pJTUNySHc&usp=sharing

Предисловие

Эта брошюра— краткое введение к обширной литературе по С/C++ и объектно-ориентированному программированию. В нее вошли теория и задания 30-часового курса по основам С++, который читался Т.В. Гурьяновой школьникам 6-8 классов «Юни-центра XXI» ФПМИ БГУ во втором полугодии 2013/2014 года, а также выдержки из курса «ЭВМ и программирование», который И.А. Волков вел несколько лет на 2 курсе ФПМИ начиная с 1997/98 учебного года, и курса по основам С\С++, который читался Т.В. Гурьяновой экспериментальной группе отобранной из студентов 1 курса БГУ и БНТУ на базе ОИПИ АН РБ в 2001 году.

Брошюра может быть использована преподавателями на факультативных занятиях по информатике, а также учащимися, умеющими писать простые программы на языке высокого уровня (например, Паскаль). Если вы не написали в своей жизни ни одной программы, но сильно хотите начать изучение С++, попробуйте тогда несколько дней поразобраться со средой scratch (визуальная объектно-ориентированная среда программирования для обучения школьников). Написать там несколько своих программок и изучить написанные другими. Инсталлятор можно скачать здесь:http://info.scratch.mit.edu/ru/Scratch_1.4_Download. Вводный урок можно посмотреть тут: https://www.youtube.com/watch?v=lqwuxs1lHhQ

Если Вам доступен электронный вариант книги, то, отлаживая соответствующие программы, часть кода можно не набирать, а копировать прямо из текста и вставлять в *.cpp-файл. Нужно учитывать при этом, что электронный вариант кавычек c/с++-кода может не совпадать с полиграфическим: кавычки в скопированном из книге коде нужно заменять при отладке. Пример отладки см: http://youtu.be/YZrOq6VSjUg

Введение

Стандарт С/C++ включает очень мало операторов. Синтаксис С/C++ схож с синтаксисом языков JavaScript и Java, но даже в том случае, если вы изучали Паскаль, вам не трудно будет перестроиться, особенно, если вы начнете с чтения книги Криса Паппаса и Уильяма Мюррея «С/C++. Руководство программиста. Книга I» [1]. Там очень доступно и последовательно даются основы языка. Мы же не будем останавливаться на деталях, а сразу дадим самое, на наш взгляд, важное и интересное.

Каждая программа состоит из функций и объявлений. Они могут находиться в различных файлах. Программа начинает выполняться с функции main(). Для выполнения ввода/вывода С++ опирается на внешнюю стандартную библиотеку iostream. В библиотеках находятся уже готовые к использованию функции. Чтобы получить доступ к этой библиотеке, надо в программу вставить команду:

#include ”название_библиотеки.h”.

Файлы с расширением *.h называют «хидер»-файлами. Библиотеки и h-файлы может создавать и сам пользователь.

Функции не могут быть вложенными.

Вот пример простейшей программы на С++ (в некоторых версиях нужно писать #include «iostream»):

a.cpp

#include ”iostream.h

using namespace std;

void main(){

cout<<”This is my first C++ application”;

}

Многое из синтаксиса станет понятным несколько позже.

Переменные

Переменные объявляются перед использованием так:

тип_переменной имя_переменной;

Локальные переменные определяются внутри блока ({ — начало блока, } — конец блока), и видны внутри этого блока.

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

a.cpp

extern int zw; //указание на то, что эта переменная есть где-то в проекте;

b.cpp

int zw=3;

Те, кто на данном этапе слабо владеет основами С/C++, могут пропускать не совсем понятные вещи. Другими словами: если что-то не понятно, читайте дальше, не зацикливайтесь. Для зануд, как уже говорилось, есть [1] : — )

Типы. Приведение типов

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

Операции различных типов приводятся к старшему (более длинному типу). Например, к long double

приводятся типы double, float, unsigned float, long int, unsigned, intи char.

При присваивании результат приводится к типу переменной слева.

Преобразования

short—>int, int—>unsigned int, long int —> unsigned long

int—>long, int—>unsigned long — не меняют двоичный код, а происходит размножение знака.

Например,

unsigned int y=1;

int x=-2;

if(y<x) cout<<”y<x”; //программа выведет y<x!!!

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

if ((int)y<x) cout<<”y<x”;

Старшие типы преобразуются в младшие отбрасыванием старших байт:

long int ff=0x1234; int i;

i=ff //0x34

Операция— явное приведение типа:

(type) имя_переменной

int=8,b=5, float v1, v2;

v1=a/b //1.0

v2=(float)a/(float)b //1.6

Макрос define

Define используется в программе для замены первого аргумента, указанного после ключевого слова #define на второй. Например, #define i 5 везде в тексте программы заменит вхождение слова «i» на 5.

Что делает следующая команда?

#define square(x) (x)*(x) //x берем в скобки «( )», а в конце «;» не ставится!

Если поставить в конце этой строки «;», то при обработке процессора, если square(x) находится в середине выражения (square(x)+1), то компилятор поставит «(x)*(x);+1»— ошибка. (И найти эту ошибку будет очень трудно.) Если же (x)*(x) написать без скобок:

#define square(x) x*x

square(i+3) даст i+3*i+3 – произойдет чисто механическая замена, а поскольку приоритет умножения выше сложения, то мы получим не i+3 в квадрате, а i+3i+3.

Упражнение. 1. Напишите и отладьте программу, использующую приведенный выше макрос.

2. В одном cpp-файле напишите простую программу, поместите оператор #define в «хидер»-файл:

d.h

#define i 5

Массивы

Массив— пронумерованный набор переменных одинакового типа. См. подробнее в [2].

Описание:

Тип имя_массива[количество элементов].

Например, int a[3] — массив из трех целочисленных переменных: a[0], a[1], a[2].

Каждому элементу массива соответствует a[индекс]. Индекс— целое число, начиная с 0. Пример:

int a[10]={1,2,3}//массив a содержит числа 1, 2, 3 и 7 нулей.

Также можно объявить: int b[]={1,2,3} — количество элементов будет подсчитываться автоматически.

Указатели

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

int b=3;//объявление целочисленной переменной b;

cout<<b;//вывод на экран значения b — числа 3;

cout<<&b;//вывод на экран адреса переменной b. По этому адресу будет размещаться число 3.

Указатель на переменную любого типа можно задать так:

тип * имя_указателя;

Чтобы по указателю получить значение переменной, на которую он указывает, нужно перед именем указателя поставить *, то есть, * имя_указателя— это значение переменной, хранящееся по этому адресу. Более подробно об указателях см. [1].

Пример:

int house;//объявляем целочисленную переменную house

int* address_of_house;//объявляем указатель на целочисленную переменную address_of_house

house=3;// в переменную house помещаем целое число 3;

address_of_house=&house;//в переменную address_of_house помещаем адрес переменной house (пусть, это будет, например, 1111), по которому находится значение переменной house — число 3; при этом говорят, что address_of_house указывает на house.

Есть два способа обратиться к одному и тому же месту памяти: первый— использовать переменную house, второй— использовать указатель address_of_house. И чтобы, например, вывести это значение на экран, нужно написать:

cout<<house;

либо

cout<<*address_of_house;//если не использовать оператор *, то на экран выведется не число 3, а адрес, //по которому это число находится в памяти компьютера (1111).

Упражнение.Проверьте (напишите и отладьте соответствующую программу).

Вернемся к массивам. Пусть у нас объявлен массив int a[10]. Имя любого массива— это указатель на первый элемент массива. В данном случае а==&a[0].Если мы передаем имя массива в качестве параметра функции, то тем самым мы передаем адрес начала массива.

Следующая программа демонстрирует передачу массива в качестве параметра функции.

#include <iostream>

#define length 8

using namespace std;

void twice_each_element(int a[]); // это описание заголовка функции, сама функция описана ниже

void main(){

int a[length]={1,2,3,4,5,6,7,8};

cout<<”Наш массив до вызова функции:\n”;

for (int i=0; i<length; ++i)cout<<a[i]<<endl;

twice_each_element(a);

cout<<Наш массив после вызова функции:\n”;

for (int i=0; i<length; ++i)cout<<a[i]<<endl;

}

void twice_each_element(int a[]){

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

a[i]=a[i]+a[i];

}

Двумерные массивы в памяти представляются, как одномерные (длинная строка), поэтому компилятору необходимо знать размер массива, за исключением, возможно, первого: a[][5] (размещение в памяти идет по строкам, поэтому первый индекс можно пропустить).

Строки

Функции работы со строками описаны в библиотеке string (cstring). Все функции описаны в [1].

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

#include <iostream>

#include <string>

using namespace std;

void main(){

char *p_palindrome = “a roza upala na lapu Azora”; //инициализация строки

int i;

for(i=strlen(p_palindrome)-1;i>=0;i—) //цикл выполняется от конца строки к началу

cout<<p_palindrome[i]; // вывод посимвольно (по одной букве)

cout<<” ”;

cout<<p_palindrome<<endl; // выводится первоначальная строка целиком

}

В указателе p_palindrome сохраняется адрес первого символа строки. Строка начинается с этого символа и продолжается до тех пор, пока не встретится символ ‘\0‘.

Функция strlen(), объявленная в файле string.h, в качестве аргумента принимает указатель на строку, заканчивающуюся нулевым символом ‘\0’, и возвращает число символов в строке, не считая последнего. В нашем примере строка состоит из 26 символов; счетчик цикла for инициализируется значением 25, поскольку строка интерпретируется как массив, содержащий элементы от нулевого до 25. На первый взгляд, может показаться странной взаимосвязь между строковыми указателями и массивами символов, но если мы вспомним, что имя массива по сути своей является указателем на первый элемент массива, то станет понятным, почему переход от имени указателя к имени массива в программе не вызвал возражений компилятора.

Получить i-й символ строки можно, как p_palindrome[i], и как *(p_palindrome +i).

Подумайте, почему.

Поиск первого вхождения подстроки в строку с помощью функции str()

#include <iostream>

#include <string.h>

using namespace std;

char* search_string= “The quick brown dog jumps over the lazy fox”;

char* string_to_find= “lazy”;

void main(){

char *ptr_to_matching_first_char;

int result;

ptr_to_matching_first_char=strstr(search_string, string_to_find);

result=ptr_to_matching_first_char — search_string+1;

if(ptr_to_matching_first_char!=NULL){

cout<<”found at position”<<result;

}

else cout<<”not found”;

}

Упражнение.Отладьте соответствующую программу (или напишите свою).

Если подстрока встречается в строке несколько раз, и нам надо подсчитать количество этих вхождений, поможет следующая программа:

#include <iostream>

#include <string.h>

using namespace std;

void main() {

char string[] = “Abra Cadabra”, str[] = “ra”;

char* ptr_сh = string;

int counter = 0, smth = 32;

while (ptr_ch!=NULL){

ptr_ch = strstr(ptr_ch, str);

ptr_ch = strnset(ptr_ch, smth, 2); // функция char* strnset(char *s, int ch, size_t n)

//заполняет первые n символов строки кодом ch;

counter++;

if (ptr_ch==” ”) ptr_ch=NULL; //NULL — это “нулевой указатель“;

}

cout<<counter<<endl;

}

Эта программа подсчитывает количество вхождений подстроки str в строку string.

Упражнение.Отладьте ее, обращая внимание на изменение ptr_ch=NULL; а затем перепишите, используя только ptr_ch==” ” или ptr_ch=NULL, а также убрав лишние проверки, и операторы.

Упражнение. Напишите простую программу на С++, которая выводит на экран значения переменных, описанных так:

char a[6]=”hello”,

b[6]={‘h’,’e’,’l’,’l’,’o’,’\0’},

c[]= ”hello”,

d[]= {”hello”},

*sp=” hello”;

a) посимвольно;

b) полностью всю строку.

Учтите, что записи char*s и char s[] не эквивалентны!

Следующее описание не верно: char s[6]; s=”hello” ; (так как нельзя присваивать массивы)

char d[]=”hello”, s[6]; s=d; (т.к. d — константа).

Упражнение.Проверьте!

Имеется в виду: напишите программу отладьте ее. Так, например, если бы вас попросили проверить корректность описания переменных предыдущего упражнения, вы бы написали и отладили такую программу:

#include<iostream>

using namespace std;

int main ();

{

char a[6]=”hello”;

char b[6]={‘h’,’e’,’l’,’l’,’o’,’\0’};

char c[6]=”hello”;

char d[6]={“hello”}

for(int i;i<=6;++i)

{

cout<<a[i]<<b[i]<<c[i]<<d[i]<<endl;

}

cout<<a<<b<<c<<d<<endl;

}

Примеры работы со строками s и d:

while (*d++=*s++) — копирование строк

if(strlen(s)>n) s[n]=’\0’ — усечение строки до n символов

Упражнение.Проверьте!

Модификаторы классов памяти (auto, register, static, extern)

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

Статическая переменная (static) позволяет локальной переменной сохранять свое значение при по-вторном входе в блок. Их обычно применяют, например, для подсчета количества вызовов функции:

void f(){

static int i=0; // это присваивание (инициализация) выполняется только один раз

if (i++==1000) exit(1);

}

Здесь i=0 только при первом обращении. Каждый раз, когда вызывается функция f, значение i увеличивается на 1. При i=1000 происходит выход из программы.

Упражнение.Проверьте.

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

b.cpp

#include <iostream>

using namespace std;

extern int n;

extern char s[];

void f(){

cout<<n<<endl;

s[n] = ‘+’;

}

a.cpp

#include <iostream>

using namespace std;

void f();

int n=2;

char s[] = “012345678”;

void main(){

f();

cout<<s;

}

s[n] — указатель на элемент строки по адресу s+n.

Упражнение.1. Убедитесь в этом: напишите программу и отладьте ее.

2. В файле a.cpp oпишите константу pi: a) const double pi=3.1415; b) extern const double pi=3.1415, а также функцию double circle(double radius), возвращающую значение длины окружности (2*pi*radius). В файл b.cpp поместите функцию main(), в которой будете вводить значение радиуса окружности и выводить на экран ее длину.

Рассмотрим примеры написание функции, которая меняет местами значение двух переменных.

1) void swap (int*x, int*y){ // * — передаются адреса переменных x и y

int tmp;

tmp=*x; //значение x поместить в tmp

*x=*y;

*y=tmp;

}

swap(&a,&b); // передача адреса.

В С++ есть 3 типа аргументов функции: переменные, указатели и ссылки. Тип ссылки задает положение, но не требует оператора ссылки (*).

2) void swap (int &x, int &y){ //& — передаются данные через адреса,

// при этом не требуется оператора разыменования— *

int tmp=x;

x=y;

y=tmp;

}

swap(a,b);

Упражнение.Напишите программы и отладьте их.

int i; int &j=i // j — альтернативное имя для i (синоним). Изменение i равносильно изменению j

int *p; int *&q=p // аналогично для указателей.

Важно: ссылки при объявлении должны быть проинициализированы!

const char & new_line=’\n’;

Ограничения на использование оператора &

Оператор взятия адреса (&) можно применять далеко не с каждым выражением. Ниже иллюстрируются ситуации, когда оператор & используется неправильно:

/*с константами*/

pivariable = &48;

/*в выражениях с арифметическими операторами*/

int iresult = 5;

pivariable = &(iresult + 15);

/* с переменными класса памяти register*/

register int registerl; pivariable = &registerl;

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

Во втором случае программа пытается найти адрес выражения iresult+15. Поскольку результатом этого выражения является число, находящееся в программном стеке (во временной памяти), его адрес не может быть получен.

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

Упражнение.Рассмотрите содержимое двух файлов:

b.cpp

#include<iostream>

using namespace std;

extern const double pi = 3.1415;

extern double radius, k;

double circle(double radius){

k = 2* radius * pi;

return k;

}

a.cpp

#include<iostream>

using namespace std;

double circle(double radius );

double radius,k;

void main(){

double sum;

cout<<”vvedi radius”<<endl;

cin>> radius; // считывается значение с клавиатуры и заносится в переменную radius

cout<< circle(radius);}

Есть ли избыточные описания в этих программах? Перепишите их, переделав внешнюю функцию: пусть она вместо длины окружности вычисляет ее площадь: s=pi*radius*radius.

Области видимости

Напомним, что область видимости переменной — та часть программы, где переменная видна. Локальная переменная видна внутри функции, где она описана. Переменные с файловой областью видимости определяются вне отдельных функций и видны в пределах файла (являются глобальными для файла). По умолчанию переменные с файловой областью видимости невидимы в других файлах при совместной компиляции.

При определении файловой переменной ее может “перекрывать” локальная переменная, поскольку у нее больший приоритет:

int i=2;

void f(){

int i=5;

cout<<i*i; // i*i=25

}

В С++ есть возможность разрешения области видимости ::

Когда в программе встречается этот символ, вместо локальной переменной используется глобальная (переменная с файловой областью видимости):

int x=7;

int f(void){

int x=5;

return x*::x; // x*::x=5*7=35

}

Упражнение.Отладьте программу иллюстрирующую уточнение области видимости (знака “::”).

Видимость информации в других файлах

Видимость информации в других файлах может осуществляться следующими способами:

1. Передача как аргумент функции (возвращение аргумента функции).

2. Пусть в a.cpp есть функция int f(){..}. Для того, чтобы ее вызвать из другого файла проекта b.cpp, нужно описать прототип функции int f(); в файле b.cpp либо в заголовочном файле a.h (в b.cpp при этом добавить #include ”a.h” (двойные кавычки означают, что поиск файла a.h будет осуществляться в текущей директории, и только после этого, если файл не найден, среди библиотек).

b.cpp

#include <iostream>

#include “a.h”

using namespace std;

void main(){

cout<<f()<<endl;

}

3. Использование модификатора extern (extern int a, где значение int a инициализируется в другом файле.

Упражнение.Отладьте программы, иллюстрирующие эти три пункта.

Операторы манипулирования свободной памятью new и delete

С помощью операторов new и delete можно управлять выделением памяти под переменную или объект: new — создаем объект, delete — удаляем (см. также книгу 4 часть I). Этими операторами удобнее пользоваться, чем функциями malloc, calloc и free.

У оператора new несколько форм записи:

1) new имя_типа; //возвращается адрес памяти, выделенной под переменную

2) new имя_типа [кол-во]; //выделяется массив из соответствующего количества переменных

3) new имя_типа (значение — какое-то выражение) ;// выделяется память под один единственный элемент и в него заносится значение

Что при этом происходит:

1. Распределяется память.

2. Возвращается адрес объекта типа (имя_типа*). Например,

int *addr= new int;

Если память не может быть выделена, возвращается NULL.

Оператор delete ничего не возвращает (void). Он уничтожает объект, созданный при помощи new и возвращает память. Формы записи:

1) delete pointer;// возвращает память, на которую указывает указатель. Если в этом случае pointer указывал на массив, то освобождается память лишь под первый элемент.

2) delete [] pointer;// возвращает память, выделенную под массив.

Например:

int *ptr_i=new int;

int *a=new int[10];

int * ptr_j;

ptr_j=new int(5); // или, что эквивалентно int *ptr_j=new int(5);

delete ptr_i;

delete [] a;

delete ptr_j;

Если мы дважды пытаемся освободить одну и ту же память, то ошибка: Access violation. Если память не возвращается (есть new, нет соответствующего delete ), то она занята до конца работы приложения. При этом работа программы замедляется и может “зависнуть”.

void main(){

int N;

cin>>N;// длина массива определяется пользователем, и лучше проверить N>0?

int *p=new int[N]// далее с этим динамическим массивом можно работать также, как и со статическим

for (int i=0; i<N;++i) p[i]=i;// или (альтернативный способ):

for (i=0; i<N;i++) cout<<*(p+i);//переменную i второй раз как int объявлять не надо: действует до

delete [] p; // конца блока

Упражнение.Отладьте программу, иллюстрирующую использование операторов new и delete, и объясните, что она выводит

#include <iostream>

using namespace std;

void main(){

int *ptr = new int(17);

cout<<&ptr<<endl;

cout<<*ptr <<endl;

cout<<”удалить значение?(0 | 1)”<<endl;

int check;

cin>>check;

if(check == 0)

{

delete ptr;

}

cout<<&ptr <<endl;

cout<<*ptr <<endl;

}

Чтение данных из текстового файла и запись данных в текстовый файл

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

#include <iostream>

#include <fstream>

#include <iomanip>

#include <stdlib.h>

using namespace std;

#define Mesto_pod_nulevoi_simvol_okonchaniya_str 1

#define Maksimalnaya_dlina_familii 35

int main( ){

char customer_name[Maksimalnaya_dlina_familii + Mesto_pod_nulevoi_simvol_okonchaniya_str];

float total_sale;

int percent_disc;

ifstream fin(“C:\\temp\\stream\\file_in.txt”);

ofstream fout(“C:\\temp\\stream\\file_out.txt”);

fin >> customer_name >> total_sale >> percent_disc;

while( !fin.eof() ){

fout << setiosflags(ios::fixed); // для округления. Чтобы лучше понять— закомментируйте

fout << “Ваша покупка \t\t$” << setprecision(2) << setw(8) << total_sale << ‘\n’;

fout << “Скидка!! $” << setprecision(2) << setw(8) << total_sale * ((100 — percent_disc) * 0.01) << ‘\n’;

fin >> customer_name >> total_sale >> percent_disc;

}

fin.close();

fout.close();

return ( EXIT_SUCCESS );

}

Упражнения.Отладьте соответствующую программу. Разберитесь с тем, что она делает.

Напишите программу, считывающую текстовый файл с записью программы «викторина» на языке высокого уровня, создающую новый файл, в котором записана только текстовая информация (все записи, касающиеся синтекса языка требуется убрать). Например,

//входной файл:

//часть программы, написанной Капитановым Никитой (9 класс)

#include<iostream>

using namespace std;

int main()

{

setlocale(LC_ALL,”russian”); //делает распознаваемой русскую кодировку в ряде сред разработки

int a,b,c,a1,b1,c1;

cout<<” МИР ЖИВОТНЫХ “<<endl;

cout<<” викторина “<<endl;

cout<<”Готовы начать?”<<endl;

cout<<”1. да”<<endl;

cout<<”2. нет”<<endl;

int d,e,y;

int k;

k = 0;

cin>>d;

if (d == 1)

{

cout<<”Тогда начинаем!!!”<<endl;

}

if (d != 1)

{

cout<<”Не хотите как хотите!.”<<endl;

return 1;

}

cout<<”С какой скоростью могут ‘бегать’ морские котики? Введите ответ:”<<endl;

cout<<”1. 3 км/ч”<<endl;

cout<<”2. 10 км/ч”<<endl;

cout<<”3. 20 км/ч”<<endl;

cout<<”Ваш ответ: “;

cin>>a;

if (a == 2)

{

cout<<”Правильно!”<<endl;

k=k+1;

}

else

{

cout<<”Неправильно”<<endl;

}

cout<<”Поют ли хищные птицы?”<<endl;

cout<<”1. да”<<endl;

cout<<”2. нет”<<endl;

cout<<”Ваш ответ: “;

cin>>b;

if (b == 1)

{

cout<<”Правильно!”<<endl;

k=k+1;

}

else

{

cout<<”Неправильно”<<endl;

}

if (k == 2)

{

cout<<”Молодец! Хорошо учил(а). Все правильно!”<<endl;

}

else

{

cout<<”Плохо учил(а). Есть ошибки.”<<endl;

}

int t;

cout<<”Введите какое-нибудь число чтобы закончить программу”<<endl;

cin>>t;

return 0;

}

// файл, который требуется получить:

МИР ЖИВОТНЫХ

викторина

Готовы начать?

1. да

2. нет

Тогда начинаем!!!

Не хотите как хотите!

С какой скоростью могут ‘бегать’ морские котики? Введите ответ

1. 3 км/ч

2. 10 км/ч

3. 20 км/ч

Ваш ответ:

Правильно

Неправильно

Поют ли хищные птицы?

1. да

2. нет

Ваш ответ:

Правильно!

Неправильно

Молодец! Хорошо учил(а). Все правильно!

Плохо учил(а). Есть ошибки

Введите какое-нибудь число чтобы закончить программу

Можно ли модернизировать программу так, чтобы она в выходном файле викторины отмечала бы правильные ответы (например, ставила бы + напротив правильного ответа)?

Передача данных функции main() из командной строки

Мы создаем exe-файл и хотим передать параметры из командной строки (recode.exe -a w text.txt) во внутрь программы. Для этого:

#include “stdafx.h”

#include “stdlib.h”

#include <iostream>

using namespace std;

int _tmain(int argc, char* argv[]) {//argc есть количество параметров в командной строке +1;

//второй аргумент char* argv[] — массив строк-параметров.

if (argc<3){

cout<<”Try smth like a.exe param1, param2”;

exit(EXIT_FAILURE);} // описание в stdlib.h

for (int i; i<argc;i++){

cout<<argv[i];// распечатываем параметры

int j=atoi(argv[1]); }// если, например, один из параметров (argv[1]) нужно получить в

//виде числа. Функция atoi() делает из символа число, описана в

//stdlib.h

}

Упражнение.Отладьте программу, попробуйте запустить ее из командной строки с параметрами

Структуры

Структура позволяет объединить компоненты разных типов в одиночную именованную переменную. Составляющие структуры имеют индивидуальные имена. Описание struct перед определением структуры в C обязательно, в С++ — нет.

Пример.

struct complex{

double re, int i

}; //обязательна ; после описания структуры!

void assign(complex &с, double rel, double image=0){

c.re=real;

c.im=image;

}

complex add(complex &c1, complex &c2){

complex t;

t.re=c1.re+c2.re;

t.im=c1.im+c2.im;

return t;

}

c=add(a,b);// удобнее было бы записать “с=a+b”. На С так нельзя, на С++ можно (см. главу Перегрузка операторов).

Что изменится, если записать complex &add? Получим ошибку времени выполнения — при сложении будут получаться не те результаты.

Упражнение. 1.Отладьте соответствующую программу. Проверьте последнее утверждение. 2. Просмотрите содержимое файла complex.h (библиотеки C++ ).

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

#include<iostream>

using namespace std;

struct complex {

double re;

double im;

};

void assign(complex &c, double real, double image = 0){

c.re = real;

c.im = image;

}

complex add(complex &c1, complex &c2){

complex t;

t.re = c1.re + c2.re;

t.im = c1.im + c2.im;

return t;

}

void main(){

complex c,a,b;

assign(a, 2, 3);

assign(b, 5, 6);

c = add(a,b);

cout<<c.re<<” ”<<c.im<<endl;}

Перегрузка операторов

Если мы складываем два комплексных числа c1 и с2, то гораздо удобнее написать c1+c2, чем вызывать ранне описанную функцию add(c1,c2). В С++ такое возможно (ключевое слово operator, затем значок оператора, который хотим перегрузить):

complex::complex operator+(const complex c1, const complex c2){

complex tmp;//tmp:=c1+c2;

tmp.re=c1.re+ c2.re;

tmp.im=c1.im+c2.im;

return tmp; }

void main(){

complex c,a,b;

assign(a,2,3);

assign(b,5,6);

c=a+b;

cout<<c.re<<” “<<c.im<<endl;

}

Упражнение.Добавьте описание умножения и деления комплексных чисел, перегрузив операторы * и /.

Перегрузка функций

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

int sum(int* a, int N){

int s=0;

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

s+=a[i];

return s;}

Пусть a — массив целых чисел, b — действительных, тогда функция sum(a,N) вызовет первую из описанных функций, а sum(b,N) — вторую.

Это есть по сути полиморфизм.

Шаблоны

Забегая вперед, сделаем еще одно важное замечание. В С++ с помощью шаблонов (templates)можно вводить функции с одинаковыми именами, которые будут оперировать с разными типами данных. Например, можно определить функцию определения минимального из двух чисел left и right вне зависимости от их типа следующим образом:

template<typename_T>

T_min(const typename_T&left,const typename_T&right){

return(left<right)?left:right;

}

Класс

Класс— это форма структуры, у которой спецификация доступа по умолчанию private, а у структу- ры — public. Это единственное различие между структурой и классом.

class complex{

int re, im; // private по умолчанию

public:

void assign(int, int);

};

struct complex{

void assign(int, int);//public по умолчанию

private:

int re, im;

};

Public-интерфейс класса — то, с чем будет работать пользователь. То, что описано в privateпользователь не видит. Сокрытие данных называется инкапсуляцией.

К секции private можно обращаться только из функции этого же класса или из friend-функции.

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

Класс — шаблон для объекта — тут такая же связь, как между типом данных и переменной. Грубо говоря, класс можно рассмотреть, как тип объекта, функции класса описывают поведение объекта.

Суть ООП (объектно-ориентированного программирования) — это способ группировки функций и данных + инкапсуляция.

Все имена структуры (класса) обрабатываются в собственном пространстве имен. В том случае, когда класс (структура) описана внутри функции, область видимости его (ее) ограничена этой функцией. И наоборот: область видимости функций, описанных в классе, ограничивается этим классом. Когда создается класс, образуются минимум два файла: *.cpp (реализация этого класса) и *.h (класс интерфейса). Взаимоотношения между классом и экземпляром класса такие же, как между типом и именем переменной (класс определяет свойства объекта).

Конструктор— это функция, которая вызывается (автоматически) системой при создании объекта класса. Имя конструктора совпадает с именем класса. Конструктор ничего не возвращает, но может принимать параметры. Обычно при этом производится инициализация членов класса и создание подобъектов (например, динамический массив, размер которого зависит от параметра, вводимого при инициализации). В том случае, если конструктор не принимает параметров, он называется конструктором по умолчанию. Другими словами, конструктор — это функция, которая ничего не возвращает, но может принимать параметры, и создает объект класса. В рамках класса может быть несколько перегруженных конструкторов. См. [3]

Пример.

#include<iostream>

using namespace std;

class DataClass{

private:

int m_private;

public:

int m_public;

DataClass(int i);// конструктор

int GetValue(){ return m_private;}

~DataClass(); };// деструктор класса — его имя совпадает с именем класса + “~“ (тильда) вначале.

//Если конструктор описывается вне класса, то нужно указать полный путь:

DataClass::DataClass(int i){m_private=i;} //Деструктор используется в обратных целях.

DataClass::~DataClass( ){cout<<”Delete”<<endl;}

void main(){

DataClass a=DataClass(3);

cin>>a.m_public;

}

Имя деструктора совпадает с именем класса, перед которым ставится тильда. Обычно в рамках деструктора уничтожаются подобъекты. Деструктор не может иметь параметр, ничего не возвращает, его нельзя перегружать. Если конструктор не указан, система создает его по умолчанию. Аналогично дело обстоит с деструктором. Обычно деструктор явно не вызывается. Так, например, экземпляр класса “виден” в пределах блока описания, но как только выходим за его пределы, то деструктор вызывается автоматически. Если мы решим вызвать его сами, то когда система дойдет до конца видимости этого экземпляра класса, она попытается освободить уже освобожденное нами место — возникнит ошибка.

Инициализация списков:

DataClass::DataClass(int i)://”:” — только для конструкторов

m_private(i), m_public();// если больше одного присваивания, то через “,“

{…} //дальнейшая инициализация

Если мы после такого описания объекта DataClass попробуем вызвать конструктор DataClass obj, компилятор выдаст сообщение об ощибке “конструктор по умолчанию не найден” (наш конструктор был описан с параметрами).

А вот так писать можно:

DataClass obj(12);

obj.m_public=23;

cout<<obj.m_public<<obj.GetValue();//вызываем private через GetValue;

} // после закрывающейся скобки вызывается деструктор.

Мы можем создать массив объектов типа DataClass. Но это будет ошибкой, поскольку при создании массива, когда создается каждый из элементов, вызывается конструктор по умолчанию без параметров.

Пример класса “Справочник городских телефонов и адресов”

Опишем класс Address (адрес) на основе массивов символов с фиксированными размерами (описание поместим в Address.h):

class Address{

public:

Address();

const char*lastname()const{return lastname_;}

void lastname(const char*);

const char*firstname() const {return firstname_;}

void firstname(const char*);

const char*phone() const {return phone_;}

void phone(const char*);

const char*address() const {return address_;}

void address(const char*);

private:

enum{namelen = 16 , phonelen = 16,addrlen =100};

char firstname_[namelen];

char lastname_[namelen];

char phone_[phonelen];

char address_[addrlen];

};

Если этот класс входит в справочник городских телефонов и адресов, то естественно запретить пользователю изменять данные справочника (описать их, как private:). Но в то же время пользоваться информацией разрешено всем: в соответствующем месте примера стоит модификатор доступа public.

#include<cstring> //изменение в стандартной библиотеке string: комитет по стандартизации ISO в свое

//время принял решение упразнить расширения .h, чтобы унифицировать

//использование расширений в файлах заголовков С++ (одни изготовители //компиляторов использовали .h, другие .hxx и .hpp). см. [4]

#include “Address.h”

using namespace std;

Address::Address()

{

lastname_[0]=firstname_[0]=phone_[0]=address_[0]=’\0’;

}

void Address::lastname(const char*s){

strcpy(lastname_,s);

}

void Address::firstname(const char*s){

strcpy(firstname_,s);

}

void Address::phone(const char*s){

strcpy(phone_,s);

}

void Address::address(const char*s){

strcpy(address_,s);

}

А вот программа, тестирования класса Address:

#include<iostream>

#include “Address.h”

using namespace std;

void dump(const Address&a)

{

cout<< a.firstname() <<” “<<a.lastname() <<’\n’<<a.address() <<’\n’ << a.phone() << ‘\n’<<endl;

}

int main(){

Address a;

a.firstname(“Petrov”);

a.lastname(“Pavel”);

a.address(“The individual business”);

a.phone(“+37529********”);

a.phone(“+37529*ext.8”);

dump(a);

return 0;

}

Пример.

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

#include<iostream>

using namespace std;

class SchoolClass{

int* ClassData;//указатель на данные о баллах

int ClassDataIndex;//положение относительно начала массива при добавлении ученика

public:

SchoolClass(int NumberOfStudents);//выделение памяти в конструкторе

~SchoolClass(){delete ClassData;}

void AddScore(int Score){ClassData[ClassDataIndex++]=Score;}

int GetScore(int Index){return(ClassDataIndex>=Index)?ClassData[Index]:-1;}

float AverageScore(void);

};

SchoolClass::SchoolClass(int NumberOfStudents){

ClassData=new int[NumberOfStudents];

ClassDataIndex=0;

}

//Для записи информации в класс необходимо предусмотреть функцию занесения информации

// работы с нею.

float SchoolClass::AverageScore(void){

float sum=0;

if(ClassDataIndex==0)return-1;

for(int LoopIndex=0; LoopIndex<ClassDataIndex; LoopIndex++)

sum+=(float)ClassData[LoopIndex];

return sum/(float)ClassDataIndex;

}

void main(){

int n, Score;

cin>>n;

SchoolClass a=SchoolClass(n);

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

cin>>Score;

a.AddScore(Score);

}

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

cout<<a.GetScore(i);

}

cout<<” “<<a.AverageScore();

}

Пример создания одномерного динамического массива произвольной вместимости

#include<iostream>

using namespace std;

void main(){

int* data;

int size;

cout<<”Enter array size\n”;

cin>>size;

data=new int[size];

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

cout<<(data[i]=i)<<’\t’;

delete[] data;

}

Распределение памяти

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

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

Имя двумерной матрицы — это указатель (pointer) на одномерный массив, элементы которого — указатели на строки матрицы.

Мы можем обращаться к элементам массива p[i][j] (а вот p[i,j] будет тождественно обращению p[j]).

Пусть i порядковый номер строки матрицы, а j — столбца. Тогда i-я строка будет находиться в ячейке памяти по адресу p+i; j-й элемент в этой строке *(p+i) можем извлечь, дойдя по ней до j-го элемента: *(p+i)+j и взяв его содержимое: *(*(p+i)+j) — элемент p[i][j]. Если матрица трехмерная, добавится еще один оператор разыменования.

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

struct twodim{

double** base;

int raw_size, column_size;

};

void allocate(int r, int s, twodim & m){

m.base=new double*[s];

for (int i=0;i<r;++i) m.base[i]= new double[r];

m.raw_size=r;

m.column_size=s;

}

void deallocate(twodim & m){

for (int i=0;i<m.raw_size;++i) delete []m.base[i];

delete [] m.base;

}

Static-члены

Члены-данные могут быть описаны с модификатором памяти static. Эти члены совместно используются всеми переменными этого класса и хранятся в одном месте уникально. Поэтому возможная форма обращения к такому члену — имя_класса::идентификатор — обеспечивается тем, что он имеет видимость public. Статический член глобального класса должен явно определяться в контектсе файла.

Например:

class str{char s[100];

public:

static bool read_only;

static int count_strings;

void print();

void assign(const char*);

};

int str::count_strings;

boolean str::read_only=false;

Вложенные внутрь функции классы не могут иметь статические члены.

Классы могут быть вложенными. Вложенные классы (class), в отличае от struct, невидимы извне.

Пример класса “Стек”

Описать стек на языке высокого уровня можно, как массив данных идентичного типа, если наложить на него некоторые ограничения: добавлять элементы массива, начиная с нулевого, плюс, запретить произвольный доступ к внутренним элементам массива, дабы не нарушить абстракцию LIFO (Last-In-First-Out — “последним пришел — первым вышел”) для стека. Необходимо сделать доступным только текущий элемент, а доступ к следующему элементу разрешить только в строгой очерндности (нельзя “перескакивать” через элемент). Сначала стек должен быть пуст. Потом в него добавляем один элемент, затем другой — получается новый стек — затем третий и так далее. Доступным всегда оставлять лишь последний добавленный элемент: его можно считывать, удалять и только после этого переходить к чтению предыдущего — и так, пока не достигнем элемента, добавленного самым первым. Организация стека предполагает создание интерфейса для пользователя стека: например, описать функции добавления элемента, извлечения элемента,… Необходимо обеспечить возможность доступа к данным стека через пользовательский интерфейс, а также гарантировать, что стек будет инициализирован (создан) перед первым использованием.

#include<iostream>

using namespace std;

class item{public: int info; item* pred;};

class stack{

item* top;

public:

stack(){top=0;};

~stack(void);

void push(int);

int pop(void);

int peek(void);

int empty(void){return (top==0);}

};

void stack::push(int elem)

{item* tmp=new item;

tmp->pred=top;

tmp->info=elem;

top=tmp;}

int stack::pop(void)

{if (empty()) return(-1);

else {int tmp=top->info;

top=top->pred;

return tmp;}

}

stack::~stack(void){

while(top!=0){pop();

cout<<”Destructor for item”<<endl;}

};

int stack::peek(void)

{

if (empty())

return(-1);

return top->info;

}

void main()

{

stack s;

cout<<s.peek()<<” “;

s.push(1);

s.push(2);

s.push(3);

cout<<s.peek()<<” “<<s.pop()<<” “<<s.pop()<<endl;

}

Упражнение.Отладьте программу, допишите перегрузку метода pop(int n)

Объявление структуры включает в себя объявление функций. Используя термнологию ООП, методы— это функции-члены, а вызов функций — это передача сообщений.

Те методы, которые определены в теле структуры, являются inline — они встраиваются в тело программы. Вызов функции заменяется на встраивание ее кода (аналог дерективы #define). Если тело функции большое, то в struct описывается прототип функции, а тело — вне:

int stack::pop(){return s[top—]}

// Можно объявить еще один вариант функции pop

int stack::pop(int n){

while(n— >1) top—; return s(top—);}

// Можно записать: void push(int ) без имени переменной-параметра функции

class stack{

friend int count(void);

};

int count (void){

int cnt=0; stack* p=top;

while(p!=0){cnt++;p=p->pred;}

return cnt;

}

Вот еще пример описание класса стек

struct itrm{

int info;

itrm *pred;}

class stack{

itrm* top;// указатель на верх стека.

public:

stack();

~stack();

void push(int);

int pop();

bool isEmpty(){return top==NULL};

//Cтек первоначально имеет смысл создавать пустым и помещать туда NULL:

stack::stack(): top(NULL){}

или (эквивалентная запись:)

stack::stack(): {top=NULL}

void stack::push(int i){

item*tmp=new item;

tmp->info=i;

tmp->pred=top;

top=tmp;

}

Эквивалентный вариант записи:

item tmp1;

tmp1.info=i; tmp.pred=top; top=&tmp;

int stack:pop(){if(!isEmpty){int t=top->info;//доставать элементы из стека, если он не пуст

item* tmp=top;

top=top->pred;

delete tmp;

return t;}; else return-1;}

В случае, когда мы удаляем верхушку стека, затем переходим к следующему элементу, может возникнуть ошибка. Лучше вызывать деструктор:

stack::~stack(){

while(!isEpmty()) pop(); //освобождаем оставшуюся память

}

В программе мы не сможем обратиться ни к одному методу этого класса, не сможем создать ни одного объекта, если не поставим модификатор доступа public. Есть два способа создания экземпляра класса, когда конструктор private:

void main(){

stack s;

s.push(1);

s.push(2);

cout<<s.pop();

}

На этом этапе у нас в стеку остался один элемент. Если не напишем деструктор, память останется помеченной, как занятая, что в последствии может привести к ошибке. Чтобы этого избежать, для объектов, память на которые объявлена динамически, надо создать свой метод копирования. (см. например, копирование в классе Address)

friend-функции

К секции private можно обращаться только из функции этого же класса или из friend-функции. freind-функции класса позволяют использовать частичную информацию класса, не являясь членами этого класса. friend-функции могут работать с теми же ресурсами, что и члены класса. Их достоинство заключается в том, что они являются внешними по отношению к описанию класса.

Пример.

class stack{

friend int count(stack &s);// параметр сам стек

… };

int count (stack &s){

item *t=s.top;

int cnt=0;

while(t){cnt++; t=t->pred;}

return cnt; }

Функция count описана вне класса, но имеет доступ ко всем элементам класса. Обычно используется для перегрузки функций ввода-вывода класса.

Функция может быть дружественной к нескольким классам.

Например, умножение массива на вектор может использовать функции set() и get(), но вызов функции— трудоемкое занятие, следовательно умножаться матрица на вектор будет долго. Но если мы опишем функцию умножения матрицы на вектор, как friend и к классу матрица и к классу вектор, то умножение будет осуществляться быстрее.

Использование friend-функций некоторым образом нарушает правила объектно-ориентированного программирования. В Java friend-функций нет (есть отражение— reflection — способность программы анализировать саму себя). Но без использования friend-функций в С++ некоторые функции перегрузить нельзя.

static-члены класса

При процедурном подходе возникает необходимость пересылать информацию от одной функции другой. Это решается с помощью глобальных переменных или параметров функции. При объектно-ориентированном подходе создается общее поле видимости. В том случае, если члены-данные описаны, как static, они используются совместно всеми экземплярами этого класса и хранятся в одном месте уникально. Обращение к static-переменным осуществляется так:

имя_класса::имя_static-переменной, либо

экземпляр_класса.имя_static-переменной

Если мы хотим, чтобы public static-переменная была видна вне класса, мы ее должны явно описать в контексте файла.

Пример. (класс строка)

class str{

char s[maxlen];

public:

static bool read_only;

static int str_count;

void print( ) const;

void assign(char*);

};

Если в каком-нибудь экземпляре класса флажок read_only будет иметь значение “false”, то он будет иметь такое значение (false) и во всех экземплярах класса.

Переменная str_count создается для учета того, сколько экземпляров класса создано.

Атрибут static вне класса не пишется

bool str::read_only=true;

int str::str_count=1;// можем менять значение извне — в любой точке программы.

В отличие от Delphi, где read-write — атрибуты — и если мы хотим заблокировать доступ к какому-либо члену класса — только через соответствующую функцию.

Указатель this

Иногда необходимо из класса вернуть экземпляр (в Delphi, object Pascal — указатель self). В С++ с этой целью используется указатель this — неявный указатель на сам объект. Имеет смысл только в нестатическом методе (функции-члене класса).

Синтаксис:

this->имя_поля(члена); // указывает на поле объекта;

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

*this//представляет собой сам объект

либо

count<<this;// выведет базовый адрес объекта (адрес памяти)

(*this).имя_поля //приоритет ( ) выше . — поэтому и ставим.

Пример.

#include<iostream>

using namespace std;

class clock{

private:

int total,sec,hour,min,day;

public:

clock(int i);

void print();

void tick();

сlock operator+(const clock & c2){ clock tmp(total+c2.total); return tmp;}

//френд-функция не может быть объявлена const

friend сlock operator-(const clock & c1, const clock & c2);//код такой же, как и предыдущий, но с “-“

//если мы функцию описываем вне класса, то:

friend сlock operator-(const clock & c1, const clock & c2){ clock tmp(c1.total-c2.total); return tmp;}

clock & operator ++ ();

clock operator ++ (int);

friend clock & operator++(clock &c);

};

clock::clock(int c){

total = c;

sec = total % 60;

hour = (total/3600)% 60;

min = (total/60)% 60;

day=total/86400;

}

void clock::print()

{

cout<<sec<<” “<<min<<” “<<hour<<” “<<day;

}

void clock::tick()

{

clock tmp (total+1);

*this=tmp;

}

clock & clock ::operator++()

{

this ->tick;

return *this;

}

clock & clock:: operator ++ (int)

{

clock tmp= *this ;

this -> tick ();

return tmp;

}

clock &operator++ (clock &c)

{

c.tick();

return c;

}

void main()

{

clock p, с(100), i(10);

p=i+c;

++p.print();// альтернативная запись: p++.print();

}

Функции-члены класса: static и const

Реализация функций-членов static и const объясняется в терминах this.

Имя_функции (аргументы-параметры) — когда обращаемся к методу (функции) класса, мы имеем доступ к указателю this, а через него к членам данного класса, даже если они не передаются в качестве параметров.

Обычная функция-член: object.f(i,j,k) — в скобках указан явный список параметров — плюс, неявный

список параметров, к которым осуществляется доступ через this.

static-функция не может обращаться к членам через this, а только к static-членам. const-функция не может менять неявные параметры.

int comp()const{}

static void reset(){}

Пусть мы добавили модификатор static:

static void set_Access(bool);

В этом случае мы будем иметь доступ к параметрам функции (передаваемым в нее аргументам) и к static-членам класса.

static-функции используются для получения значений static-переменных.

this имеет значение вне static-функций. В static-функциях this не имеет значения: static-функции и static-переменные лежат отдельно от класса. И даже если не создан и один экземпляр объекта, у нас есть доступ к static-переменным и к static-функциям. Но мы можем получить через static доступ к private-конструктору. Если у переменной модификатор const, она не может через this менять члены класса.

Например, функция print( ) в классе Строка. Эта функция должна печатать члены класса, но не должна менять их содержимое. При попытке изменить появляется сообщение об ошибке.

Примеры.

#include<iostream>

using namespace std;

class s_pair{

int c1,c2;

public:

void init(char b){

c2=b;c1=1+b;}

s_pair&operator++(){c1++; c2++;

return(*this);// альтернативный в-т: s_pair&increment(){c1++; c2++; return *this;}

}

s_pair&where_am _I(){return(*this);}

friend ostream&operator<<(ostream& ,s_pair);

void print()const{cout<<”c1=”<<c1<<”c2=”<<c2<<endl;}

};

ostream&operator<<(ostream&s,s_pair a){return s<<a.c1<<”,”<<a.c2;}

void main()

{

s_pair a;

a.init(‘y’);

a.print();

cout<<a.where_am _I()<<endl;

a++.print();// альтернативный в-т: a.increment().print(); — популярен в JavaScript

a++;

cout<<a;

}

this — немодифицируемый указатель c_pair* const this.

Работаем со временем

Договоримся вводить время в следующем формате: “чч:мм.сс “ и опишем класс:

#include<iostrem>

#include<string>

class time_class{

int hours minutes, seconds;

public:

void assign(char*);

};

void time_class::assign(char* str0{

char *h,*m,*s;

h=strtok(str,:);//char* strtok(char*s1,const char*s2) ищет в s1 первый фрагмент с ограничителями s2

m=strtok(NULL,”.”);

s=strtok(NULL,” “);

hours=atoi(h);

minutes=atoi(m);

seconds=atoi(s);

}

Пример применения часто используемых функций даты и времени из time.h

Вывод на экран даты, дня недели, числа, месяца, года, часов, минут и секунд:

#include <iostream>

#include <time.h>

using namespace std;

void main(){

struct tm *time_d;

time_t timer;

time(&timer);

time_d = localtime(&timer);

cout<<”vremya “<<asctime(time_d); }

#include <iostream>

#include <time.h>

using namespace std;

void main(){

struct tm *time_d;

time_t timer;

time(&timer);

time_d = gmtime(&timer);

cout<<”vremya “<<asctime(time_d);

}

#include <iostream>

#include <time.h>

#define MAXCHAR 1000

using namespace std;

void main(){

//функция позволяет использовать в консольном выводе русскую кодировку в ряде сред разработки:

setlocale(LC_ALL , “.1251”);

struct tm *time_d;

time_t timer;

char c_b[MAXCHAR];

time(&timer);

time_d = localtime(&timer);

strftime(c_b , MAXCHAR , “Сейчас %H:%M:%S %p %Y года, часовой пояс %Z , день недели %w, номер недели %W”, time_d);

cout << c_b << endl;

}

Упражнение.Разберитесь детально в том, что выполняют данные программы.

Visual C++ (Windows Forms)

Лабораторная I

1. Запускаем Visual C++, создаем новый проект: выбираем Приложение Windows Forms.

2. Перетаскиваем на форму кнопку Button из Панели элементов (пиктограмма с перекрещенным молоточком и гаечным ключом Ctr+Alt+X) и label

3. В обработчик щелчка кнопки (двойной клик на кнопку) вставляем код: this->label1->Text=”this is my first programm”;

4. В окне свойста (Alt+Enter) выбираем цвет формы, кнопки,label1. Можно также поменять надписи, расположение, размер…

5.Нажимаем на зеленый треугольничек (F5-”начать отладку”) — тестируем приложение.

Перед тем, как начать работу над второй лабораторной, желательно разобраться, как использовать функцию fcvt() из stdlib.h для преобразования числа с плавающей точной в строку. Эта функция возвращает информацию о знаке и точности полученной строки:

#include <iostream>
#include <stdlib>
using namespace std;

void main(){
char* ConvertedFloatToString;
int float_sign,decimal_placement, precision=3;
float a=3.14, b=-2.12;
ConvertedFloatToString=fcvt(a, precision,&decimal_placement,&float_sign);
cout<<”a=”a<<” ConvertedFloatToString=”<< ConvertedFloatToString<<”\t The decimal placement is”<< decimal_placement<<”\n The sign of the number is:”<<((float_sign ? “-“:”+”)<<endl;
}

Лабораторная II

(заготовка к написанию программы “калькулятор”)

1. Запускаем Visual C++, создаем новый проект: выбираем Приложение Windows Forms.

2. Перетаскиваем на форму кнопку Button из Панели элементов, textBox1, textBox2 и label

3. В обработчик щелчка кнопки вставляем код:

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

float a=Convert::ToDouble(textBox1->Text),
b=Convert::ToDouble(textBox2->Text);
a=a+b;

// объявляем переменную-строку, куда будем заносить результат сложения, переведенный из

//числа в строку, заносим эту строку в label1->Text:

char* ConvertedFloatToString;
int float_sign,decimal_placement;
ConvertedFloatToString=fcvt(a,3,&decimal_placement,&float_sign);
label1->Text=gcnew System::String(ConvertedFloatToString);

Полезно после этого переписать на С++ все лабораторные по Delphi. приведенные в [5].

Лабораторная III

Мы надемся, что вы уже догадываетесь, что все свойства объектов, которые можно менять и устанавливать во вкладке “Properties”, можно менять через код программы. Так, например, при нажатии на кнопку в первой лабораторной мы меняли свойство Text компонента lebel1. Это же свойство можно поменять изменив значение lebel1, набрав текст во вкладке “Properties” напротив подписи “Text”. Единственное отдичие, что при каждом запуске программы это свойство будет каждый раз инициироваться, поменять его значение можно только в коде программы (при каком-то условии if, например, задавать другое значение).

Цвет кнопки компонента button можно задать через свойство BackColor во вкладке “Properties”, выбрав нужный вариант из предлагаемых.

Подобно тому, как при нажатии на кнопку можно поменять на ней надпись

button1->Caption=”Нужный текст”;

чтобы поменять ее цвет, естественно написать

button1->BackColor=”Нужный цвет”;

Так оно и есть, за тем лишь исключением, что нужный цвет “спрятан” и его нужно “доставать” через череду разыменований.

Если мы хотим, что бы при нажатии кнопка стала зеленой, то в обработчик щелчка кнопки нужно вставить код:

button1->BackColor=System::Drawing::Color::Green;

либо

button1->BackColor= Color::Green;

если вы предварительно задали нужный namespace:

using namespace System::Drawing;

Задание. Напишите программу, которая бы меняла “случайным образом” цвет кнопки при нажатии.

Литература

1. Криса Паппас и Уильям Мюррей. С/C++. Руководство программиста. Книга I. ZD-Press Emeryville, California USA, СК-Пресс, Москва-Россия.

2. Т. Гурьянова. Программирование: быстрый старт. Язык программирования Паскаль. Книга 3 часть I. Мн.: БГУ ФПМИ «Юни-центр XXI», 2013.

3. Т. Гурьянова. Объектно-ориентированное программирование: быстрый старт. Книга 4 часть I.

4. П. Халперн. Стандартная библиотека С++ на примерах. М-СПб-Киев: Издательский дом «Вильямс», 2001.

5. Т. Гурьянова. Объектно-ориентированное программирование: быстрый старт. Лабораторные по Delphi. Книга 4 часть II. Мн.: БГУ ФПМИ «Юни-центр XXI», 2014.

6. Криса Паппас и Уильям Мюррей. С/C++. Руководство программиста. Книга II. ZD-Press Emeryville, California USA, СК-Пресс, Москва-Россия.

7. Г. Буч. Объектно-ориентированное программирование на С++.

8. Б. Страуструп. Язык программирования С++. М: Бином, С-Пб.: Невский диалект. 2000.

9. Н. Вирт. Структуры и алгоритмы

Содержание

Предисловие 1

Введение 2

Переменные 2

Типы. Приведение типов 3

Макрос define 3

Массивы 4

Указатели 4

Строки 5

Поиск первого вхождения подстроки в строку с помощью функции str() 6

Модификаторы классов памяти (auto, register, static, extern) 7

Ограничения на использование оператора & 9

Области видимости 10

Видимость информации в других файлах 10

Операторы манипулирования свободной памятью new и delete 11

Чтение данных из текстового файла и запись данных в текстовый файл 12

Передача данных функции main() из командной строки 14

Структуры 15

Перегрузка операторов 16

Перегрузка функций 16

Шаблоны 17

Класс 17

Пример класса “Справочник городских телефонов и адресов” 19

Распределение памяти 21

Пример создания двумерного динамического массива произвольной вместимости 22

Static-члены 22

Пример класса “Стек” 22

friend-функции 25

static-члены класса 26

Указатель this 26

Функции-члены класса: static и const 28

Работаем со временем 29

Пример применения часто используемых функций даты и времени из time.h 29

Visual C++ (Windows Forms). Лабораторная I 30

Лабораторная II. (Заготовка к написанию программы “калькулятор”) 30

Литература 31

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

методические материалы по информатике