К основным типам переменных в C/C++ относятся следующие типы:
1.целочисленные:
Тип
Описание
Пример объявления
char
Символьный тип (один символ)
char ch='q';
short или short int
Короткое целое. Тип больше или равен типу char
Short a=28;
int
Целое число. Тип больше или равен типу short
int a=20;
long
Целое длинное число. Тип больше или равен типу int
long s=3;
2.вещественные:
Тип
Описание
Пример объявления
float
Вещественный тип
Float a, b, c=0.93;
double
Вещественный тип двойной точности. Тип больше или равен типу float
double d=56.078;
Обратите внимание, что символьный тип - это один из целочисленных типов. Это, в частности, означает, что переменную такого типа можно превратить в любой другой целый тип. Также обратите внимание, что один символ мы записываем в одинарные кавычки. Сразу заметим, что несколько символов (строку) мы должны записывать в двойные кавычки.
В C/C++ есть еще множество различных типов. Их мы рассмотрим в последующих уроках.
C/C++ Урок 2. Массивы
Массив представляет из себя неcколько переменных с одним именем и одного типа. К конкретному элементу массива можно обращаться по номеру. Вот пример:
//Объявление массива из 3 элементов. int z[3]; //Инициализируем элементы массива. z[0]=4; z[1]=-4; z[2]=13;
Обратите внимание, что при объявлении массива мы пишем в квадратных скобках именно столько элементов, сколько нам надо. Если надо, чтобы в массиве было 10 элементов, значит, пишем в квадратных скобках 10.
Нумерация элементов массива идет всегда с нуля. Другой возможности нет. Это в чаcтности означает, что последний элемент массива будет иметь номер на 1 меньше, чем количество элементов массива. Т. е. если в массиве 10 элементов, то последний имеет номер 9.
Массивы могут быть многомерные. Вот пример:
//Объявление массива из 6 элементов. int z[3][2]; //Заполняем элементы массива. z[0][0]=4; z[1][0]=-4;
Если внимательно посмотреть на объявление двумерного массива, то видно, что фактически он представляет из себя массив массивов.
Немного забегая вперед, скажем, что имя массива представляет из себя адрес начала массива (т. е. адрес начального элемента массива).
Элементы массива располагаются в адресном пространстве подряд. В частности для быстродействия это означает следующее - для доступа к любому элементу массива достаточно к адресу начала массива прибавить номер этого элемента, умноженный на размер, нужный для одного элемента. Т. е. для доступа к произвольному элементу массива не надо перебирать все предыдущие. В этом массивы отличаются от, например, списков - там для доступа к определенному элементу надо перебрать все предыдущие, что, разумеется, отражается на быстродействии.
При объявлении массива его размер должен быть известен заранее. Т. е. нельзя, например, сначала запросить у пользователя число n, а потом объявить массив:
int z[n]; //Неверно!
Но если n объявлена перед этим как константа, то такое объявление массива вполне законно.
Эта программа после компиляции и запуска выведет на экран надпись "Hello World!".
Разберем текст программы. Начинаем ее со строчки
#include <iostream.h>
Это так называемая директива препроцессора. Все директивы препроцессора начинаются с символа #. Конкретно директива include нужна для включения некоторого файла в вашу программу. Тут мы включаем файл iostream.h. Он нужен для того, чтобы мы в нашей программе могли что-нибудь вводить с клавиатуры или выводить на экран. Этот include пишется почти всегда. После директив препроцессора точка с запятой не ставится.
Далее в программе идет функция main. Функция с таким именем всегда должна присутствовать в любой консольной программе на C/C++ (причем только одна). Именно с нее (а не с первой строчки) начинается выполнение программы. Перед именем функции мы пишем тип возвращаемого значения (void означает, что функция ничего не возвращает - это аналог процедур в других языках программирования). После имени функции обязательно пишем круглые скобки. В них мы пишем парметры (если они есть). В нашем случае параметров нет.
Тело функции пишем в фигурных скобках. В нашем примере мы в теле пишем
... cout<<"Hello World!\n";...
cout - это экран монитора (точнее, стандартное устройство для консольного вывода (первая буква c - от слова console, out - вывод)). На консоль (экран) мы отправляем строку "Hello World!". Обратите внимание, куда передается наше строка (<< - так как она передается влево, на консоль). Не путайте - так как символ >> тоже существует и используется для консольного ввода. В строке внутри кавычек мы видим символы \n. Это переход на новую строку. Сами символы \n печататься не будут.
Строки в C/C++ заключаются в двойные кавычки (в отличие от переменных типа char, значения для которых заключаем в одинарные кавычки).
В C/C++ любой оператор заканчивается на точку с запятой. Точка с запятой - это часть оператора, а не разделитель между операторами, как, например, в Delphi.
C/C++ Урок 4. Циклы for
В любом языке программирования есть, как правило, 2 типа циклов: те, про которые известно, сколько раз они должны выполниться и те, про которые заранее неизвестно, сколько раз они должны выполниться. Пример первого - это запрос у пользователя, например, 10 чисел (ясно, что мы должны повторить это действие 10 раз). Пример второго - запрос пароля у пользователя (неясно заранее, сколько попыток потребуется пользователю на ввод правильного пароля).
Циклы for относятся к первой категории. Они выполняются заранее известное число раз. Вот пример:
... int z[3]; //Заполняем элементы массива. for(int i=0; i<3; i++){ z[i]=i; } В этом примере мы заполняем элементы массива соответствующими числами.
После ключевого слова for мы пишем в круглых скобках параметры для цикла (обратите внимание, что после for обязательно должны идти общие круглые скобки). Параметры для цикла делятся на три части, которые мы разделяем точкой с запятой. Первая часть - это задание начального значения для переменной цикла. В нашем примере мы переменной i присваиваем значение 0 (но можно и любое другое). Вторая часть - это условие продолжения цикла. Если оно истинно, то цикл продолжает выполнение. Как только оно станет ложным, цикл прекратится. Третья часть - это изменение переменной цикла. Чаще всего переменная цикла изменяется на 1. i++ как раз и увеличивает i на 1 (можно было записать и так: i=i+1 - только так писать дольше).
Обратите внимание, что переменную i для цикла мы объявили прямо внутри цикла:
... for(int i=0;...){... Так часто и делают. Но, разумеется, i можно объявить и заранее:
int i;... for(i=0;...){... Циклы for часто используются для перебора всех элементов массива. Вот так можно перебрать элементы двумерного массива:
int z[3][2]; //Заполняем элементы массива. for(int i=0; i<3; i++){ for(int j=0; j<2; j++){ z[i][j]=0; } } В цикле может быть и несколько переменных цикла. Вот пример:
int n=0; for(int i=0, int j=0; i<3, j<2; i++, j++){ n++; } Этот цикл выполнится 2 раза и n по выходе из цикла будет равно 2. Синтаксис у такого цикла такой - имеются те же три части, между которыми пишем точку с запятой. Внутри каждой части в качестве разделителя пишем запятую. Цикл прекращается, если хотя бы одно из условий второй части (у нас это i<3, j<2;) окажется ложным.
C/C++ Урок 5. Циклы while
Циклы while, в отличие от циклов for, предназначены для ситуаций, когда заранее неизвестно, сколько раз цикл должен выполнится.
Циклы while делятся на 2 типа: while и do-while. Принципиальная разница между ними такая - while может не выполнится ни одного раза, do-while выполняется по крайней мере 1 раз. Так что надо смотреть по задаче - если наше действие точно должно один раз выполнится (например при запросе у пользователя пароля), то выбираем do-while.
А вот и конкретный синтаксис:
while(a>0){...//что-то делаем};
Этот цикл будет выполятся до тех пор, пока a>0. Как только условие в крулых скобках станет ложным, цикл прекратит свою работу. Если же a будет сразу меньше или равно нулю, то цикл вообще ни разу не выполнится.
Пример цикла do-while:
int password;do{ cout<<"\nEnter password"; cin>>password; //Ввод пользователем пароля.}while(password!=123);
Этот цикл будет выполнятся до тех пор, пока пользователь не введет 123 в качестве пароля. Т. е. его условие продолжения такое же, как у while - пока условие в скобках истинно, цикл продолжает работу.
Обратите внимание, что после слова while условие продолжения цикла мы пишем в общих круглых скобках.
Также обратите внимание, что переменные, влияющие на истинность условия, как-то внутри цикла должны меняться - иначе мы можем оказаться в бесконечном цикле:
int a = 5;while(a>0){ cout<<"*";//Бесконечный цикл.}
Здесь всегда a больше 0 и из цикла мы никогда не выйдем.
C/C++ Урок 6. Директивы препроцессора
Мы уже сталкивались с одной директивой препроцессора в уроке 3. Сейчас мы обсудим, что это такое.
До компиляции (т. е. превращение в машинные коды) вашей программы происходят некоторые действия –
а именно сначала выполняются так называемые директивы препроцессора, а уже потом – непосредственно
компиляция. Директивы препроцессора могут, например, включать в файл вашей программы другие
файлы (это мы делаем с помощью директивы #include), определять некоторые константы
(директива #define) и многое другое.
Важно понимать, что все директивы препроцессора выполняются до этапа компиляции. После выполнения директив
препроцессора текст вашей программы будет изменен (не в исходном файле, конечно - там все как было, так и
останется).
Перед любой директивой препроцессора пишется знак #.
Давайте немного приоткроем покров над тайной директив препроцессора на маленьком примере. Этот пример будет
специфичен для Visual C++, но это не должно нас особенно тревожить - сейчас нам важно просто понять,
как это все работает.
Итак, создайте на Visual C++ новый проект для консольного приложения.
Для файла *.cpp задайте имя test.cpp. В качестве текста программы введите
#include "test2.h"#define pi 3.14void main(){ float z=2*pi;} Пока текст нас не слишком должен волновать. Если не все понимаете в тексте - не беда.
Принцип здесь такой - #include влючает указанный файл в наш файл.
У нас это файл test2.h. Создайте его (например в Блокноте) в той же папке, где находятся все остальные файлы
вашего проекта. В нем напечатайте
следующий текст (на самом деле можно напечатать все что угодно - это не принципиально):
struct v{ int x, y;}; Еще одна директива препроцессора в нашей программе - это #define. Она просто определяет константу pi, которую
мы парой строчек ниже используем.
Теперь для просмотра того, что получается после выполнения директив препроцессора, измените настройки нашего
проекта.
Нажимайте Alt+F7 (или меню Project и далее Settings) и переходим на вкладку C/C++. В ней меняем содержимое
окошка Project Options:
Из этого окошка все удалите, и напечатайте там
/MLd /Fo"Debug/" /P Пока мы это обсуждать не будем, отметим только, что с такими параметрами вы получите в папке проекта файл с
результатом работы препроцессора. Закройте окно настроек, нажав на OK.
Компилируем программу (клавиша F7). В папке с файлами проекта появится файл test.i со следующим текстом:
#line 1 "D:\\_programming\\cpp\\test\\test.cpp"#line 1 "D:\\_programming\\cpp\\test\\test2.h"struct v{ int x, y;};#line 2 "D:\\_programming\\cpp\\test\\test.cpp" void main(){ float z=2*3.14;} Как вы видите, препроцессор поработал - вставил файл test2.h в наш файл и заменил pi на значение константы:
... float z=2*3.14; Так что работу препроцессора мы немного посмотрели.
Рассылки нашего сайта на Subscribe.Ru
Visual C++, MFC C# и.NET VB.NET ASP.NET new! Win API C/C++ Delphi Java HTML, PHP, mySQL, WEB-дизайн Flash MX C++ Builder Ассемблер SQL Server DirectX Обзор книг Обзор программ Новости сайта progs.biz
window.google_render_ad();
C/C++ Урок 7. Константы
Константа - это понятное обозначение для некоторой величины, которая не должна меняться в программе.
Например, вместо 3.1415926 в программе удобнее писать что-то типа pi.
Есть 2 способа объявления констант - через директиву препроцессора #define и через ключевое слово const.
Вот пример использования в программе константы, объявленной через директиву #define:
#include <iostream.h>#define SIZE 10void main(){ int z[SIZE]; for(int i=0; i<SIZE; i++) { z[i]=i; } int sum=0; for(i=0; i<SIZE; i++) { sum+=z[i]; } cout<<sum<<"\n";}
А вот пример использования в программе константы, объявленной с помощью ключевого слова const:
#include <iostream.h>const int size = 10;void main(){ int z[size]; for(int i=0; i<size; i++) { z[i]=i; } int sum=0; for(i=0; i<size; i++) { sum+=z[i]; } cout<<sum<<"\n";} Использование констант сильно упрощает процесс изменения в программе. Если вам надо увеличить размер массива
в приведенных примерах, то вы просто изменяете константу.
Обратите внимание, что при объявлении массива таким способом:
... int z[size];... size должна быть объявлена именно как константа. Если вы просто объявите size как целое, то компилятор выдаст
ошибку.
Рассылки нашего сайта на Subscribe.Ru
Visual C++, MFC C# и.NET VB.NET ASP.NET new! Win API C/C++ Delphi Java HTML, PHP, mySQL, WEB-дизайн Flash MX C++ Builder Ассемблер SQL Server DirectX Обзор книг Обзор программ Новости сайта progs.biz
window.google_render_ad();
C/C++ Урок 8. Структуры
Структуры предназначены для хранения в одном месте различных данных, которые относятся к одному понятию.
Скажем, данные для книги могут быть следующие: автор, название, количество страниц и т. п.
И эти данные удобнее хранить не отдельно, а в одном месте.
Вот пример объвления и использования структур:
#include <iostream.h>struct vector{ //Координаты x и y вектора. float x; float y;};void main(){ //Использование структуры vector. vector d; d.x=2; d.y=-2.5;} Как вы видите, для объявления структуры используется ключевое слово struct.
Для доступа к полям внутри структуры используется оператор точка (.):
... d.x=2; Обратите внимание, что после закрывающей фигурной скобки при объявлении структуры надо обязательно
ставить точку с запятой:
struct vector{...}; Без нее компилятор выдаст сообщение об ошибке.
Внутри структур могут быть функции. Вот так может выглядеть наша структура с функциями:
struct vector{ float x; float y; //Функция для записи x и y в вектор. void SetVector(float x1, float y1){ x=x1; y=y1; } //Функция для вывода вектора на консоль. void ShowVector(){ cout<<"x="<<x<<", y="<<y<<"\n"; }}; А вот так функции можно использовать:
vector d;... d.SetVector(5, 3); d.ShowVector(); //Выведется "x=5, y=3". Напоследок отметим, что развитием структур являются классы.
C/C++ Урок 9. Функции
Функция представляет из себя отдельный кусок кода, который вы можете многократно использовать. Каждая функция
имеет имя и может возвращать значение. Написав функцию один раз, вы можете использовать ее столько раз, сколько
надо - для это мы вызываем ее по имени.
Вот пример:
//Объявление функции. int sum(int a, int b){ return a+b;} void main(){ //Вызов функции. cout<<sum(3, 5);}
Указанный фрагмент выведет, естественно, 8.
В нашем фрагменте мы объявили функцию sum, которая принимает два параметра целого типа - a и b.
Обратите внимание, как мы пишем параметры. Во-первых, в круглых скобках и через запятую:
int sum(int a, int b)...
Круглые скобки тут надо писать, даже если у функции нет вообще параметров:
int sum ()...
и во-вторых, тип мы пишем перед каждым параметром - скажем такое объявление функции будет неправильным:
int sum(int a, b)...
Значение, которая функция возвращает, мы пишем после return:
int sum(int a, int b){... return a+b;}
Тип возвращаемого значения должен совпадать с типом функции - раз перед именем функции мы написали int,
то и после return надо написать нечто типа int.
C/C++ Урок 10. Рекурсивные функции
Функция может вызывать сама себя. Т. е. внутри функции мы используем ее саму.
Вот классический пример - вычисление факториала (факториал n (обозначается как n!) - это произведение целых чисел
При компиляции тело функции не повторяется - т. е. при первом приближении оно имеется только в одном месте
программы, и при вызове функции происходит переход на тело функции, которая и выполняется. При этом есть как
плюсы, так и минусы. Плюс тот, что размер exe-файла получается не такой большой (функция-то занимает место только
один раз), а минус - то, что программа работает несколько медленнее, так как тратится дополнительное время на
переход к телу функции.
Встраиваемые (inline) функции как раз и решают эту проблему. Оформляется такая функция как и обычная, только с
ключевым словом inline. Но при компиляции тело функции подставляется во все места ее вызова. Минусы и плюсы тут
меняются местами по сравнению с обычной функцией: вызов такой функции идет быстрее, а место exe-файл занимает
больше. Вот пример:
...//Встраиваемая функция. inline int f(int a, int b){ return a*b;}void main(){ //Вызов функции. cout<<f(23, 45)<<"\n"; cout<<f(-23, 77)<<"\n";}
Надо сказать, что разные компиляторы по-разному работают с inline-функциями. Если компилятор не может сделать
функцию встраиваемой, то он делает ее обычной. Например, такое может случиться с рекурсивной функцией.
C/C++ Урок 13. Ссылки
Когда мы передаем в функцию параметр, то он передается по значению. Это означает, что мы передаем в функцию
копию и что изменения этого параметра внутри функции никак не повлияют на сам оригинал. Вот поясняющий пример:
#include <iostream.h>void f(int a){ a++; //Увеличиваем a на 1.}void main(){ int k=20; f(k); cout<<k<<"\n"; //Выведется 20.}
Тут мы внутри функции изменяем параметр на 1, но в функции main переменная k как имела значение 20, так и будет
иметь.
Параметры можно передавать и по ссылке. Это означает, что мы передаем в функцию сам оригинал переменной.
Вот пример:
...void f(int & a){ a++; //Увеличиваем a на 1.}void main(){ int k=20; f(k); cout<<k<<"\n"; //Выведется 21.}
В этом примере в функцию f передается оригинал переменной, и в функции main переменная k будет изменена.
Это мы достигаем передачей ссылки на переменную:
...void f(int & a){...
Как вы видите, для передачи по ссылке мы используем знак амперсанда (&).
C/C++ Урок 14. Адрес переменной
Операционная система Windows выделяет каждой программе 4 Гб адресного пространства. Каждая программа искренне
думает, что у нее есть столько места.
Каждый байт адресного пространства имеет свой адрес. Адрес - это просто некоторое число. В Windows адрес выглядит
приблизительно так: 0x0D56FF23. Тут префикс 0x означает что это число в шестнадцатеричном виде.
Цифры шестнадцатеричного числа - это обычные числа от 0 до 9 и буквы A, B, C, D, E, F. Обратите внимание, что в
адресе 8 знаков (так и должно быть, так как у каждой программы 4 Гб адресного пространства).
Рассмотрим подробнее, что происходит, когда вы пишете в вашей программе что-то вроде
int z;
В этом случае в адресном пространстве вашей программы несколько байт (а именно столько, сколько надо на хранение
одного целого - скорей всего это будет 4 байта) получают имя z. И это очень удобно - запись в эти ячейки идет не по их
адресу (который выглядит как-то вроде 0x00FD240A) а по удобному имени z. Эти байты выделяются в определенном
месте адресного пространства. Адрес первого байта нашего числа z будет начальным адресом все переменной z.
Адрес можно получить непосредственно. Для этого служит оператор взятия адреса & (называется амперсанд).
Вот пример:
int z;cout<< &z; //Выводим на экран адрес переменной z.
Указаный фрагмент выведет что-то вроде 0x0012FF7C.
Вот еще пример:
int z[3]; //Массивcout<<z; //Адрес начального элемента массива.
Тут выведется адрес начала массива (т. е. адрес его нулевого элемента). Таким образом имя массива - это адрес его
начала.
C/C++ Урок 15. Указатели
Указатель - одно из важных понятий языка C/C++. Начинающие программисты поначалу путаются, но со временем
все должно встать на свои места.
Итак, каждая переменная имеет свой адрес в адресном пространстве. Так вот, указатель - это переменная, которая
хранит адрес другой переменной. Т. е. если в переменной обычного типа хранится, например, целое число, то в
переменной-указателе может храниться адрес переменной, в которой хранится целое число. Все это справедливо,
разумеется, не только для целого типа, но и любого другого.
Указатели различаются по типам. Например, указатель на целое может хранить адрес переменной целого типа, а
указатель на тип float - адрес переменной типа float.
Указатель на определенный тип объявляем так: сначала пишем этот тип, потом пишем звездочку и затем произвольное
имя переменной. Пример:
int * p1; // Указатель на целое.bool * p2; // Указатель на тип bool. Так же как и для переменных обычного типа для переменных типа указатель мы при объявлении имеем в такой
переменной мусор - т. е. некотрый непонятный адрес. Существует два способа записи инициализации указателя –
записать в него адрес существующей переменной соответствующего типа или выделить новый участок памяти через
ключевое слово new.
Вот пример на первый способ:
int z=20; int *p=&z; Тут мы в указатель p записали адрес переменной z.
А вот пример на второй способ инициализации указателя - через ключевое слово new:
int *p=new int; В этом случае мы говорим, что память у нас выделена динамически. Синоним этого - память выделена в куче (heap).
В противоположность этому память для переменных обычного типа (не указателей) выделяется в стеке.
И в первом, и во втором способах в переменной p будет храниться указатель (адрес) целого типа. Для того, чтобы через
этот адрес работать с тем местом, на которое он указывает (т. е. в нашем случае с некоторым целым числом), мы
используем так называемое разыменование указателя. Вот пример:
...*p=123; //Записываем 123 в место, на которое указывает указатель p.cout<<p<<"\n"; //Выведется некий адрес (например, 0x0012FF7C)cout<<*p<<"\n"; //Выведется 123; Т. е. для доступа к тому месту, на которое указывает указатель, мы перед его именем добавляем звездочку.
Таким образом, по сути разыменование указателя - это доступ к тому месту на которое указатель указывает.
Для указателей существует некий аналог нуля - это значение NULL. Вот пример:
int *p=NULL; На самом деле NULL определена как 0, поэтому в большинстве способов можно писать 0 вместо NULL.
Пока это было краткое знакомство с указателями. Подробности - в следующих уроках.
Рассылки нашего сайта на Subscribe.Ru
Visual C++, MFC C# и.NET VB.NET ASP.NET new! Win API C/C++ Delphi Java HTML, PHP, mySQL, WEB-дизайн Flash MX C++ Builder Ассемблер SQL Server DirectX Обзор книг Обзор программ Новости сайта progs.biz
window.google_render_ad();
C/C++ Урок 16. Оператор sizeof
Оператор sizeof позволяет выяснить, сколько байт занимает тот или иной тип. В качестве параметра он принимает или
название типа, или переменную соответствующего типа. Вот пример:
int z; cout<<sizeof(int)<<"\n"; //Выведется 4. cout<<sizeof(z)<<"\n"; //Выведется 4.
Параметр может быть не только встроенным типом. Пример со структурой:
Тут первый параметр: argc - это общее количество параметров. При простом запуске программы (например, простым
щелчком на exe-файле) он будет равен 1. Второй параметр представляет из себя массив типа char*. Тип char* - это
указатели на символ, который интерпретируется как строка. Нулевой элемент в этом массиве строк - это имя самого
exe-файла, первый - это первый передаваемый параметр, второй - второй передаваемый параметр (разумеется, если эти
параметры вообще есть).
Откомпилируем и запустим нашу программу из командной строки с параметрами. Результат будет приблизительно
такой:
В средах разработки часто существует возможность задать параметры командной строки в самой IDE.
Вот так, например, это делается в Visual C++.NET. Из контекстного меню для проекта выбираем Properties:
затем в появившемся окне Property Pages в разделе Debugging задаем Command Arguments:
C/C++ Урок 19. Преобразование типов
Преобразование типов - это приведение (превращение) одного типа в другой. Такое преобразование возможно далеко
не всегда. А иногда в нем вообще нет особой необходимости - например, когда вы присваиваете переменной типа float
значение переменной типа int - тут нет никакой потери точности, так что компилятор даже не выдаст вам
предупреждения. А вот, например, обратный пример:
... int a; float b=2.78;... a=b; //Потеря точности!
Тут мы в переменную типа int записываем значение переменной типа float. Синтаксической ошибки не будет, но скорей
всего компилятор выдаст предупреждение о возможной потери точности.
Для того, чтобы компилятор не предупреждал, мы сделаем преобразование типа float к типу int. Вот так:
... a= (int) b;...
Этот случай достаточно очевидный - мы преобразуем один числовой тип к другому. А вот так, например, можно
преобразовать тип char к типу int или наоборот:
... char ch='A'; //Выведется 65 - код символа 'A'. cout<<(int)ch; int a=7; //Раздастся звонок (код 7). cout<<(char)a;...
На самом деле такое преобразование будет возможно, так как char - это тоже целочисленный тип как и int.
C/C++ Урок 20. Работаем со строками
Вообще говоря, в C/C++ нет строкового типа. Его роль выполняет либо указатель на char, либо массив char'ов.
Для работы с такими строками в C++ есть несколько функций, имена которых начинаются на str.
Вот пример их использования:
#include <iostream.h>#include <string.h>void main(){ char* ch="Roman"; char ch1[30]; //Показывем длину строки cout<<strlen(ch)<<"\n"; //Копируем строку strcpy(ch1, ch); cout<<ch1<<"\n"; //Длина скопированной строки такая же, как и у старой cout<<strlen(ch1)<<"\n"; //Сравнение строк if(strcmp(ch, "Roma")==0) { cout<<"Strings are equal\n"; } else { cout<<"Strings are not equal\n"; } //Конкатенация (сложение) строк strcpy(ch1, "Roman"); strcat(ch1, " Alexeev"); //Выведется "Roman Alexeev" cout<<ch1<<"\n";}
Для использования таких функций мы должны написать соответствующий include:
...#include <string.h>...
Пару слов следует сказать о функции strcmp. Она возвращает ноль, если строки равны; отрицательное число,
если строка, задаваемая первым параметром, расположена в алфавитном порядке раньше строки, задаваемой вторым
параметром; и положительное число, если первая строка расположена в алфавитном порядке дальше, чем вторая.
C/C++ Урок 21. Передача массива как параметра функции
Массивы в качестве параметра функции передаются по ссылке. Это означает, что если вы внутри такой функции
изменяете элементы массива, то эти изменения будут внесены и в оригинал массива.
Вот пример:
#include <iostream.h>void f(int arr[2]){ // Изменяем внутри функции массив. arr[0]=11; arr[1]=12;}void main(){ int a[2]={1, -1}; // Вызов функции. f(a); // Выводим элементы массива. cout<<a[0]<<"\n"; cout<<a[1]<<"\n";}
Указанный фрагмент выведет числа 11 и 12.
Обратите внимание, что для массивов, в отличие от отдельных переменных (т. е. тех, которые не являются массивами),
не надо писать знак амперсанда (&).
Вообще говоря, в таком поведении массивов ничего удивительного нет - как вы знаете, имя массива (а именно оно и
передается в функцию) есть адрес начала массива. Так что мы передаем адрес массива, т. е. его оригинал.
C/C++ Урок 22. Шаблоны функций
Шаблоны служат для ситуации, когда у нас есть разные типы данных, с которыми мы должны работать по одному,
не зависящему от этих типов данных, алгоритму. Например, метод сортировки массива не зависит от типов данных
- такой алгоритм будет одинаков и для, например, чисел типа int и для чисел типа float.
Приведем пример использования шаблона.
Вот код для простейшего щаблона функции, которая из двух передаваемых в нее параметров возвращает максимальный:
#include <iostream.h>//Объявление шаблона функции.template <class T>T max(T a, T b){ if(a>b) { return a; } else { return b; }}void main(){ //Использование шаблона функции для целых. int x = 45, y = 32; cout<<max(x, y)<<"\n"; //Выведется 45. //Использование шаблона функции для вещественных. float s = 4.18, t = 34.08; cout<<max(s, t)<<"\n"; //Выведется 34.08. }
Обратите внимание на синтаксис. Сначала мы пишем ключевое слово template и задаем формальный тип T
(T - это просто произвольное имя):
template <class T>...
Далее мы пишем непосредственно функцию, причем в качестве типа параметров и возвращаемого значения пишем
введенный ранее формальный тип T:
T max(T a, T b)...
Разумеется, количество параметров может быть любое и не все из них должны иметь тип T. То же относится и к типу
возвращаемого значения - в нашем случае это тот же тип T, но в принципе тип может быть любой, хоть void.
Также обратите внимание, что заданием формального параметра и объявлением класса не должно быть никаких
операторов.
Теперь пара слов об использовании шаблона. При использовании мы просто пишем имя функции (в нашем случае это
max) с параметрами конкретных типов (int и float в нашем примере). Компилятор в этом месте сам сгенерирует на
основании этого шаблона функцию с параметрами конкретных типов.
C/C++ Урок 23. Пример шаблона функции
Вот еще конкретный пример на шаблон функции. Этот шаблон ищет в массиве определенный элемент и если такой
элемент есть, то шаблон функции возвращает номер этого элемента, а если такой элемент не найден, то возвращается -1.
#include <iostream.h>//Объявление шаблона функции.template <class T>int find(T *arr, T a, int n){ for(int i=0; i<n; i++) { if(arr[i]==a) { return i; } } return -1;}void main(){ //Использование шаблона функции для целых. int z[4] = {5, 7, -2, 66}; int res; res = find(z, 66, 4); cout<<res<<"\n"; //Выведется 3. //Использование шаблона функции для булевских. bool b[3] = {true, true, true}; res = find(b, false, 3); cout<<res<<"\n"; //Выведется -1.}
У нашего шаблона 3 параметра: первый - это массив, в котором мы ищем наш элемент (помните, что имя массива –
это указатель на нулевой элемент массива, поэтому мы и пишем T *arr), второй параметр шаблона - это искомый
элемент, и третий - количество элементов в массиве.
C/C++ Урок 24. Введение в классы
Мы с вами рассматривали структуры. Классы чем-то напоминают структуры - у них также внутри есть переменные
разных типов. Но наряду с этими переменными у класса есть и несколько отличий, которые мы сейчас и рассмотрим
на примере.
Вот пример класса:
#include <iostream.h>//Объявление класса прямоугольника.class CRect{ float m_a, m_b; //Стороны.public: //Методы класса. //Методы по чтению и записи сторон. void SetA(float a); void SetB(float b); float GetA(); float GetB(); float GetSquare(); //Площадь. float GetPerim(); //Периметр. bool IsSquare(); //Не является ли прямоугольник квадратом.}; //Точка с запятой обязательна!void main(){ //Использование класса. CRect r; r.SetA(5); r.SetB(3); cout<<"Perimeter = "<<r.GetPerim()<<"\n"; cout<<"Square = "<<r.GetSquare()<<"\n"; if(r.IsSquare()) { cout<<"Square\n"; //Квадрат. } else { cout<<"Not a quare\n"; //Не квадрат. }}//Реализация методов класса.//Методы по чтению и записи сторон.void CRect::SetA(float a){ if(a>0) { m_a = a; } else { m_a = 1; }}void CRect::SetB(float b){ if(b>0) { m_b = b; } else { m_b = 1; }}float CRect::GetA(){ return m_a;}float CRect::GetB(){ return m_b;}//Площадь.float CRect::GetSquare(){ return m_a*m_b;}//Периметр.float CRect::GetPerim(){ return (m_a+m_b)*2;}//Не является ли прямоугольник квадратом.bool CRect::IsSquare(){ return (m_a==m_b);} После запуска программы выведет, что периметр равен 16, площадь - 15 и что это - не квадрат. Этого, собственно,
и следовало ожидать.
Как вы видите, у класса есть несколько особенностей. Во-первых, у него есть функции (вообще говоря, те функции,
которые принадлежат классу, называются методами). Во-вторых, его части имеют разный тип доступа (у нас это
public и private (последний тип доступа является типом доступа по умолчанию и слово private можно не писать,
что мы и
сделали для переменных m_a и m_b)).
В методе main мы создали так называемый экземпляр класса (с именем r) - можно считать, что это конкретный
прямоугольник. Экземпляр класса объявляется аналогично объявлению обычной переменной. Потом мы в main
вызываем функции (методы) класса для нашего экземпляра r.
Дальнейший разбор класса мы произведем на следующем уроке.
Рассылки нашего сайта на Subscribe.Ru
Visual C++, MFC C# и.NET VB.NET ASP.NET new! Win API C/C++ Delphi Java HTML, PHP, mySQL, WEB-дизайн Flash MX C++ Builder Ассемблер SQL Server DirectX Обзор книг Обзор программ Новости сайта progs.biz
window.google_render_ad();
C/C++ Урок 25. Разбор первого класса
На этом уроке мы с вами разберем наш первый класс, написанный на прошлом уроке.
Итак, для объявления класса мы используем следующую конструкцию:
class MyClass{ //Внутренность класса.};
Тут вместо MyClass мы можем написать, разумеется, любое имя. Обратите так же внимание на точку с запятой в конце
класса - она обязательна.
Объявление класса - это как бы конструирование нового типа данных. В C/C++ есть встроенные типы данных - int, char,
float и другие. Но для реальной задачи удобнее создать свои типы, которые будет лучше моделировать поставленную
задачу. Классы как раз для этого и предназначены.
Теперь несколько слов о том, что может находиться внутри класса. А именно, там могут находиться переменные разных
типов и функции (они же методы) класса. Переменные могут быть самых разных типов - в том числе и экземпляры
других классов (и даже экземпляры того же самого класса).
Вообще внутренность класса делится на 3 части - public (доступна всем), private (доступна только самому классу) и
protected (доступна классу и его потомкам (об этом мы будем говорить на следующих уроках)).
Слово private можно не писать - оно действует по умолчанию. Т. е. наши переменные m_a и m_b нахадятся в private
части класса:
class CRect{ float m_a, m_b; //Стороны....
Зачем мы поместили m_a и m_b в private часть класса? Так как на их значения существуют ограничения - а именно они
не могут быть отрицательные. Поэтому для доступа к этим переменным мы добавили по две функции GetA/B – для
Обратите внимание, что функции мы вызываем не сами по себе, а именно для нашего экземпляра класса r.
Синтаксис тут такой: имя экземпляра, точка, имя функции:
r.SetA (5);
Можно в программе объявить несколько экземпляров класса или даже массив:
CRect w, v;CRect z[5];
Все такие экземпляры будут независимы друг от друга.
C/C++ Урок 26. Конструкторы и деструкторы
Конструкторы и деструкторы - это специальные методы класса. Это надо понять в первую очередь. Разумеется, эти
методы обладают целым рядом особенностей (именно по этому они и выделены в специальную группу).
Сейчас мы об этих особенностях и поговорим.
Первое. В отличие от других методов они должны называться особым образом. Если обычные методы могут называться
как угодно, то имя констуктора должно совпадать с именем класса, а имя деструктора - с именем класса с приписанной в
начале тильдой. Например, если класс называется CRect, то конструктор этого класса обязан называться тоже CRect, а
деструктор - ~CRect.
Второе. В отличии от других методов конструктор и деструктор вызываются сами (а другие методы мы вызываем
явным образом). Конструктор вызывается в момент создания экземпляра класса, а деструктор - в момент уничтожения.
Т. е. их не надо вызывать явным образом - они вызываются сами. Именно поэтому конструкторы обычно используются
для задания некоторых начальных значений для переменных класса, а деструкторы - для освобождения памяти
(в случае если у вас есть внутри класса переменные-указатели).
Третье. Ни конструктор, ни деструктор не возвращают никакого значения (даже типа void). Это означает, в частности,
что при объявлении конструктора и деструктора в классе мы перед ними не пишем ни какой тип.
Четвертое. В классе может быть несколько конструкторов (и они должны различаться параметрами), и только один
деструктор (у него параметров вообще быть не может).
Вот пример на все вышесказанное:
CRect{ float m_a, m_b; //Стороны.public: //Конструкторы и деструктор. CRect(); // Конструктор без параметров. CRect(float a. float b); // Конструктор с двумя параметрами. ~CRect(); // Деструктор....};...// Реализация конструкторов и деструкторов.CRect::CRect(){ // Задание стандартных (нулевых) значений. m_a = 0; m_b = 0;}CRect::CRect(float a. float b){ // Задание значений, задаваемых параметрами. m_a = a; m_b = b;}CRect::~CRect(){ // Просто вывод некоторой надписи. cout<<"Destructor\n";}
А вот так можно использовать класс в функции main:
void main(){ //Использование класса. CRect r; // Вызов конструктора без параметров. cout<<"Perimeter = "<<r.GetPerim()<<"\n"; // Выведется 0. cout<<"Square = "<<r.GetSquare()<<"\n"; // Выведется 0. CRect r1(2, 3); // Вызов конструктора с параметрами. cout<<"Perimeter = "<<r1.GetPerim()<<"\n"; // Выведется 10. cout<<"Square = "<<r1.GetSquare()<<"\n"; // Выведется 6. // В этом месте вызовутся 2 деструктора - для переменных r и r1. // Соответственно, на консоль выведется два раза слово "Destructor".}
В приведенном примере методы GetPerim и GetSquare мы берем из урока 24.
C/C++ Урок 27. Классы и указатели
На прошлых уроках мы видели только один способ объявления экземпляров класса - а именно мы писали что-то вроде:
CMyClass z;
Вообще же говоря, экземпляры класса лучше во многих случаях заводить не в стеке, а в куче (at heap - подробности см.
в уроке 15. Это, в частности, связано с тем, что стек - вещь довольно-таки ограниченная по объему, классы же часто
представляют из себя довольно-таки большие и сложные объекты, и если вы будете размещать в стеке что-то вроде
массива экземпляров класса, то объема стека просто может не хватить (хотя, конечно, для учебных программ это
случится навряд ли).
Так же как и для встроенных типов данных для размещения экземпляра класса в куче используется оператор new.
Именно он и занимается выделением памяти. Вот пример:
CMyClass * z;z = new CMyClass; // Непосредственное выделение памяти.
Конструктор класса при этом вызовется на второй строке, т. е. в операторе new.
Эти два оператора можно объединить:
CMyClass * z = new CMyClass; // Непосредственное выделение памяти.
Если в классе есть конструктор с параметрами, то он вызывается примерно так:
// Вызов конструтора с одним параметром.CMyClass * z = new CMyClass(22);// Вызов конструтора с двумя параметрами.CMyClass * z = new CMyClass(22, 44);
Еще одно отличие между двумя способами создания экземпляра класса (в стеке или в куче) - это вызов методов класса.
Если при создании экземпляра в стеке между именем экземпляра и именем метода используется точка, то при создании
экземпляра класса в куче (т. е. через оператор new) используется стрелочка. Вот пример:
CMyClass * z = new CMyClass;z->SomeMethod();
Урок 28. Практика: список - добавление элементов
С этого урока мы с вами применим полученные знания на практике - а именно, мы создадим динамический список. В него можно будет добавлять элементы (экземпляры некоторого класса CData), показывать все элементы списка, удалять элементы, сортировать, выяснять, есть ли определенный элемент в списке или нет и делать другие стандартные для списка операции.
Итак, начинаем. Сначала напишем класс CData:
// Класс данных.class CData{public: // Собственно данные класса. int a; int b; // Указатель на следующий экземпляр класса CData. CData * m_pNext; // Конструкторы класса. CData() { a = 0; b = 0; m_pNext = NULL; } CData(int a1, int b1) { a = a1; b = b1; m_pNext = NULL; }}; Как вы видите, класс небольшой - в нем всего пара переменных для данных (a и b) и указатель m_pNext типа CData * - он показывает на следующий элемент в нашем списке. Т. е. первые данные в списке (а данные в нашем случае - это тоже самое, что и экземпляр класса CData) указывают на следующие, те - на следующие и т. п. Последние ни на что показывать не будут - у них в поле m_pNext записан NULL (это мы делаем в конструкторе класса).
Теперь начинаем писать класс списка. Назовем его естественным образом - CList.
На этом уроке мы напишем только метод по добавлению элемента в наш список. Вот так пока будет выглядеть наш класс:
//Класс списка.class CList{public: // Указатель на первый элемент списка. CData * m_pFirst; // Конструктор. CList(); // Добавление данных в конец списка. void Add(CData * pNext); // Поиск последнего элемента в списке. CData * GetLast();}; Обратите внимание, что мы в наш класс ввели переменную m_pFirst типа CData *. Это указатель на первый элемент списка. Он же внутри себя (см. класс CData) содержит указатель на следующий и т. д.
А вот реализация конструктора и методов класса CList:
// Реализация методов класса списка.CList::CList(){ m_pFirst = NULL;} void CList::Add(CData * pNext){ // Ищем последний элемент в списке // и приписываем pNext к нему. // Если список не пуст. if(m_pFirst!=NULL) { GetLast()->m_pNext = pNext; } else { // Если список пуст, то // добавлем данные в начало списка. m_pFirst = pNext; }} CData * CList::GetLast(){ // Указатель на очередные данные в списке. CData * pCurr = m_pFirst; // Если список пуст, то возвращаем NULL. if(pCurr == NULL) { return NULL; } // Пока есть следующий элемент списка. while(pCurr->m_pNext!=NULL) { pCurr = pCurr->m_pNext; } return pCurr;} Как вы видите, в конструкторе мы просто обнуляем указатель на первый элемент списка (m_pFirst = NULL). Далее мы пишем метод Add, который добавляет передаваемые в параметре данные к списку. Добавляем мы новые данные в конец списка, который получаем методом GetLast, тело которого тоже приводится. Также обратите внимание на то, что мы особым образом перед добавлением или перед поиском последнего элемента в списке обрабатываем ситуацию, когда в списке вообще нет пока элементов.
Еще одно отличие между двумя способами создания экземпляра класса (в стеке или в куче) - это вызов методов класса. Если при создании экземпляра в стеке между именем экземпляра и именем метода используется точка, то при создании экземпляра класса в куче (т. е. через оператор new) используется стрелочка. Вот пример:
CMyClass * z = new CMyClass;z->SomeMethod();
C/C++ Урок 29. Показ элементов списка
Напишем метод класса CList, который будет выводить на консоль все элементы нашего списка. Для этого
воспользуемся циклом while, так как мы не знаем, сколько всего элементов нам надо вывести.
Добавьте в класс CList объявление метода ShowAll:
class CList{public:... // Показ всех элементов списка. void ShowAll();};
Реализация у метода будет такая:
void CList::ShowAll(){ // Указатель на очередные данные в списке. CData * pCurr = m_pFirst; // Если список пуст, то это и напишем. if(pCurr == NULL) { cout<<"List is empty.\n"; } // Пока есть следующий элемент списка. do{ // Выводим на консоль очередной элемент. cout<<"a="<<pCurr->a<<", b="<<pCurr->b<<"\n"; // Переводим pCurr на следующий элемент в списке. // Если он равен NULL, то заканчиваем. }while((pCurr = pCurr->m_pNext)!=NULL);}
Теперь можно испытать наш класс списка в работе. Например, вот так:
void main(){ CList list; CData *pData; int a, b; for(int i = 0; i<3; i++) { cout<<"Enter a:"; cin>>a; cout<<"\n"; cout<<"Enter b:"; cin>>b; cout<<"\n"; pData = new CData(a, b); list.Add(pData); list.ShowAll(); }}
Теперь можно запускать программу. Три раза мы будем вводить данные a и b для каждого нового элемента,
добавляемого в список, и каждый раз после этого весь список будет выводиться на экран.
C/C++ Урок 30. Ищем элемент в списке
Продолжаем писать наш класс. В этом уроке мы с вами напишем метод FindData нашего класса, который находит
некоторый элемент в нашем списке. Элемент будет задаваться параметрами - так как в каждом элементе списка два
параметра (a и b), то и у метода FindData будет два параметра.
Вот тело метода:
CData * CList::FindData(int a, int b){ // Указатель на очередные данные в списке. CData * pCurr = m_pFirst; // Если список пуст, то возвращаем NULL. if(pCurr == NULL) { return NULL; } // Обходим все элементы списка. do{ // Если нашли искомый элемент, то его и возвращаем. if(pCurr->a == a && pCurr->b == b) { return pCurr; } // Переводим pCurr на следующий элемент в списке. // Если он равен NULL, то заканчиваем. }while((pCurr = pCurr->m_pNext)!=NULL); // Если ничего не нашли, то возвращаем NULL. return NULL;}
Алгоритм метода ясен из комментариев. Если искомый элемент найден, то возвращается указатель на него, если нет,
то возвращается нулевой указатель NULL.
C/C++ Урок 31. Пара вспомогательных методов для списка
Сейчас мы с вами напишем пару вспомогательных методов для нашего класса списка.
Внесите в заголовочный файл класса списка объявления наших методов:
//Класс списка.class CList{public: // Не пустой ли наш список? bool IsEmpty(); // Предыдущий элемент в списке. CData * GetPrev(CData * p);... Первый метод короткий - с него и начнем. Он просто отвечает на вопрос, не пуст ли наш список. Вот его код:
bool CList::IsEmpty(){ return m_pFirst==NULL;} Второй метод возвращет по указателю на передаваемый в параметре элемент списка предыдущий для него (такой метод может оказаться весьма полезным, так как наш класс CData содержит только указатель на следующий элемент, а на предыдущий - нет). Вот его код:
CData * CList::GetPrev(CData *p){ // Если предыдущего элемента списка нет // (т. е. наш элемент совпадает с первым) // то возвращем NULL. if(p==m_pFirst) return NULL; // Если предыдущий элемент есть. CData * pCurr = m_pFirst; do{ // Если для очередного элемента в списке // следующий для него элемент есть // передаваемый нами в параметре, if(pCurr->m_pNext==p){ // то и возвращаем этот очередной элемент. return pCurr; } // Переводим pCurr на следующий элемент в списке. }while((pCurr = pCurr->m_pNext)!=NULL); return NULL;} Этими методами мы воспользуемся на следующих уроках.
C/C++ Урок 32. Удаление элемента из списка
Рассмотрим стандартную задачу - удаление элемента из списка.
Алгоритмически мы должны рассмотреть два случая - когда удаляем самы первый элемент из списка и когда удаляем элемент из середины или конца списка.
Вот код для нашего метода (не забудьте внести объявление этого метода в сам класс):
bool CList::RemoveData(int a, int b){ // Если список пуст if(IsEmpty()) // то выходим. return false; // Получаем элемент для удаления. CData * pData = FindData(a, b); // Если такого элемента в списке нет if(pData == NULL) // то выходим. return false; // Если удаляемый элемент - первый в списке. if(pData == m_pFirst){ // Делаем первым следующий за ним элемент списка. m_pFirst = pData->m_pNext; // Освобождаем память, которую занимал удаляемый элемент. delete pData; // И выходим. return true; } // Если удаляемый элемент - не первый в списке. // То делаем так, чтобы элемент, находящийся в списке // перед удаляемым, показывал на следующий за удаляемым элемент. GetPrev(pData)->m_pNext = pData->m_pNext; // Освобождаем память, которую занимал удалаемый элемент. delete pData; // И выходим. return true;}
Код должен быть ясен из комментария. Если удаление произошло, то возвращяем true, если нет, то false. В этом методе мы использовали другие методы (GetPrev, FindData), написанные на прошлых уроках.
C/C++ Урок 33. Функция с переменным числом параметров
Вот пример функции, принимающий переменное число параметров.
Пример классический - а именно наша функция возвращает сумму своих параметров. Обратите внимание, что первым параметром мы передаем число чисел для суммирования (т. е. сам первый параметр суммироваться не будет, он говорит только, сколько всего параметров будут суммироваться (это все оставшиеся параметры)).
#include <iostream.h>// Задаем функцию с переменным числом параметров.int sum(int n,...) { // Получаем адрес первого параметра. int *p = &n; // Переводим указатель на второй параметр. p++; // Объявляем переменную для суммы // и присваиваем ей ноль. int res = 0; // Сумирование оставшихся параметров. for(int i=0; i<n; i++){ // Добавление к сумме очередного параметра. res+=(*p); // Первод указателя на следующий параметр. p++; } // Возврат суммы. return res;} void main(){ int r = 0; // Суммируем 5 чисел. r = sum(5, 1, 2, 3, 4, 500); cout<<"Sum = "<<r<<"\n";} Переменное число параметров в функции обозначаается посредством многоточия (...). В нашем объявлении функции мы указываем, что обязотельно должен присутствовать первый параметр (типа int), после которого может быть любое число параметров любого типа.
Внутри функция устроена так - мы получаем адрес в адресном пространстве, по которому расположены передаваемые в функцию параметры. Это адрес первого параметра:
... int *p = &n;... Далее мы перебираем все параметры (а всего их n) через указатель - он постоянно переводится на следующий параметр посредством строки:
... p++;... Результатом выполнения указанного фрагмента будет 510.
C/C++ Урок 34. Считаем элементы в списке
На этом и следующем уроках мы добавим еще пару вспомогательных методов для нашего списка - которыми мы воспользуемся на следующем уроке.
Это будут методы GetCount() - он возвратит общее число элементов нашего списка и Change(CData * p) - этот метод поменяет 2 элемента списка - а именно тот, который мы передадим в него в качестве параметра и следующий за ним.
Сначала добавьте объявление метода GetCount в класс:
class CList{public:... // Общее число элементов. int GetCount();...
Напишите реализацию этого метода:
int CList::GetCount(){ // Указатель на очередные данные в списке. CData * pCurr = m_pFirst; // Если список пуст, то возвращаем 0. if(pCurr == NULL) { return 0; } int count = 1; //Число элементов в списке. // Пока есть следующий элемент списка. while(pCurr->m_pNext!=NULL) { // Переходим на следуюющего. pCurr = pCurr->m_pNext; // Увеличиваем счетчик.