Теоретический материал (С++)
Сайт: | Информатикс |
Курс: | Темнов Андрей Владимирович |
Книга: | Теоретический материал (С++) |
Напечатано:: | Гость |
Дата: | Пятница, 27 Июнь 2025, 19:32 |
Введение
Историческая справка
С — это язык программирования, созданный в 70-х годах XX века для разработки системы UNIX и программного обеспечения для нее. В 80-х годах XX века на основе языка C был создан язык C++, являющийся объектно-ориентированным расширением языка C++. В настоящее время языки C и C++ являются наиболее распространенными языками для профессиональной разработки программного обеспечения для всех операционных систем. Синтаксис языка C и C++ не зависит от используемой системы и компилятора, однако набор доступных библиотек (например, для разработки графических приложений) является системно-зависимым и не стандартизирован.
В данных листках речь будет идти о языке C++. Многое из того, о чем пойдет ниже речь, верно и для языка C, но мы на этом останавливаться не будем. Мы будем использовать компилятор gcc для системы Linux, аналогичный ему компилятор MinGW для системы Windows и программу Dev-C++, называемую средой разработки, облегчающую процесс программирования. При этом все рассматриваемые примеры должны правильно компилироваться любым компилятором, соответствующим стандарту языка C++. Например, таким компилятором является MS Visual C++ последних версий (в то время, как широко распространенный в образовательных учреждениях компилятор Borland C++ 3.1 не соответствует стандарту и для него рассматриваемые программы работать не будут).
Hello, world
Язык C++ является компилируемым языком. Для того, чтобы написать программу, вам необходимо в любом текстовом редакторе набрать, например, следующий текст и сохранить его в файле, например, hello.cpp.
#include <iostream> using namespace std; int main() { cout<<"Hello, world!"<<endl; return 0; }
Будьте внимательны: язык C++ является чувствительным к регистру букв, то есть заменить main на Main или MAIN нельзя.
Весь текст (за исключением текстовой строки "Hello, world!"
) нужно набирать в нижнем регистре.
После этого вам нужно откомпилировать этот файл (создать из этого файла исполняемый машинный код) при помощи следующей команды (в системе Linux):
$ c++ hello.cpp
В среде разработки (вроде Dev-C++) для компиляции программы существует пункт меню, вызывающий компилятор.
Если ваша программа написана правильно, то компилятор не выдаст никаких сообщений
об ошибках и создаст исполняемый файл (a.out
в системе Linux или exe
-файл в системе Windows).
Этот файл содержит исполняемый двоичный машинный код.
Рассмотрим подробней текст этой программы.
В первой строчке мы подключаем к нашей программе файл с именем iostream,
в котором содержится описание стандартной библиотеки ввода-вывода языка C++.
В этом файле находится, в частности, определение
объектов cout
и endl
, который мы будем использовать позднее.
Вторая строка указывает компилятору на то, что мы будем использовать все функции, входящие в пространство
имен std
, то есть все функции, относящиеся к стандартной библиотеке C++.
Третья строка содержит объявление функции main
, не принимающей никаких аргументов и возвращающей значение int
.
Эта функция должна быть в каждой программе, именно эта функция получает управление при запуске программы.
Четвертая строка содержит открывающуюся фигурную скобку, что означает начало функции main
.
В пятой строке мы при помощи оператора <<
помещаем в объект cout
строку "Hello, world!"
, а потом специальный объект endl
, означающий символ перевода строки.
Это приводит к печати на экране этой строки и последующему переводу каретки.
В шестой строке мы даем инструкцию return
, завершающую выполнение функции main
и возвращающую нулевое значение. Седьмая строка содержит фигурную скобку, синтаксически закрывающую функцию main
.
Для начала можно считать, что все строки, кроме пятой, являются некоторым набором “заклинаний”, без которых программа не будет работать и которые обязательно нужно указать, а вот пятую строку можно заменить на другие строки с различными инструкциями.
Арифметика
Переменные
Все переменные в языке C++ должны быть объявлены перед использованием. При использовании в программе ранее не объявленной переменной компилятор выдаст сообщение об ошибке вроде
test.cpp: In function `int main()':
test.cpp:5: `c' undeclared (first use this function)
test.cpp:5: (Each undeclared identifier is reported only once
for each function it appears in.)
В этом сообщении указывается имя файла и номер строки, в которой обнаружена
ошибка (test.cpp:5
), а также описание ошибки (`c'
undeclared
— объект с именем c
не
объявлен).
Объявление переменной имеет следующий вид:
<тип переменой> <один или несколько
идентификаторов переменных через запятую>;
Например, переменные n
и m
типа int
можно объявить такой строкой:
int n, m;
Переменную x
типа double
можно объявить такой
строкой:
double x;
Значения переменных сразу после объявления не определены и являются произвольными. Предположение о том, что все переменные первоначально имеют нулевые значения, является ошибочным.
Идентификатор переменной — это ее имя, которое должно быть
последовательностью букв латинского алфавита, символа подчеркивания и цифр,
начинающейся с буквы. Примеры правильных идентификаторов: а
,
i
, Year
, school179
. Имена переменных
чувствительны к регистру букв, то есть Number
, number
,
NUMBER
и nUMbeR
— это четыре разных
переменных.
Типы данных
При объявлении переменной мы должны указать ее тип. Существует несколько стандартных типов, кроме того, программист может создавать собственные типы данных. Для представления целых чисел существуют следующие стандартные типы:
В языках С, C++ есть следующие стандартные типы данных для представления целых чисел.
Тип | Описание | Размер | Диапазон | Синоним |
int |
Целые числа | 4 байта | -231..231-1 | |
unsigned int |
Беззнаковые целые | 4 байта | 0..232-1 | unsigned |
long int |
Длинные целые | 4 байта | -231..231-1 | long |
unsigned long |
Беззнаковые длинные | 4 байта | 0..232-1 | |
short int |
Короткие целые | 2 байта | -215..215-1 | short |
unsigned short |
Беззнаковые короткие | 2 байта | 0..216-1 | |
При этом в стандарте языка C++ не оговаривается конкретный размер каждого из
вышеперечисленных типов, для каждого компилятора они могут быть своими.
Приведенные выше числа верны для компиляторов GCC, MS VC++, а
вот для Borland C++ версии 3.1 размер переменной типа int
— 2 байта.
Действительные числа можно записывать в виде десятичных дробей как с
фиксированной точкой (например, 3.1415926, 100.001, -10000000.0), так и с
плавающей точкой. В последнем случае число имеет вид
<f> e
<p>,
где <f> — дробное число (положительное или
отрицательное), называемое мантиссой, а <p> — целое число (положительное или
отрицательное), называемое порядком. Число, записанное таким образом,
равно f×10p . Фактически, порядок означает, на какое
число позиций нужно сдвинуть вправо десятичную точку в записи числа
<f>. Если же порядок меньше нуля, то сдвиг десятичной точки
осуществляется влево. Примеры записи чисел с плавающей точкой:
3.14e1
- означает 31.4
3.14e5
- означает 314000
3.14e-3
- означает 0.00314
-1e6
- означает -1000000
-1e-6
- означает -0.000001
Для представления действительных чисел существует два стандартных типа:
Имя типа | Размер |
float |
4 байта |
double |
8 байт |
Как правило, для хранения целых чисел следует использовать тип
int
, а для действительных чисел —
double
.
Арифметические операторы
Арифметическая инструкция — это некоторое выражение, состоящее из констант, идентификаторов переменных и арифметических операторов, которая завершается точкой с запятой. Самый главный арифметический оператор — это оператор присваивания ‘=’, который присваивает одной переменной, идентификатор которой указывается слева от оператора ‘=’ значение некоторого выражения, которое стоит справа. Например:
a=2;
b=a+1;
В последней строке встретился оператор сложения ‘+’. Кроме оператора сложения, есть еще операторы вычитания ‘-’, умножения ‘*’, деления ‘/’ и взятия остатка от деления целых чисел ‘%’.
Особого внимания заслуживает оператор деления. Если оба его аргумента имеют целочисленный тип (то есть один из типов, перечисленных в первой таблице или целочисленные константы), то этот оператор рассматривается, как оператор деления целых чисел с остатком. Если же хотя бы один из операторов будет иметь дробный тип, то оператор деления выполняется, как оператор деления десятичных дробей.
В арифметическом выражении сначала выполняются слева направо все операторы умножения и деления, затем слева направо все операторы сложения и вычитания, затем справа налево все операторы присваивания. При необходимости порядок действий можно изменить при помощи скобок.
Ввод-вывод
Для того, чтобы вывести на экран значение переменной или текстовой строки
нужно использовать объект ‘cout’ и оператор
‘<<’, который в данном случае следует
называть "Поместить в". cout
— объект,
связанный со стандартным выводом программы, как правило, это терминал. Для того,
чтобы перейти при печати на новую строку необходимо поместить в
cout
стандартный объект endl
.
Текстовые строки при выводе на экран необходимо заключать в двойные кавычки. Если хочется вывести на экран несколько объектов (переменных, текстовых строк и т.д.), то их нужно разделять между собой оператором ‘<<’.
Для того, чтобы считать значение переменной нужно использовать объект ‘cin’ и оператор ‘>>’, который надо называть "Извлечь из". При этом считывание данных будет производиться со стандартного ввода программы, как правило, являющегося клавиатурой. Если хочется за одну операцию считать несколько переменных, то их идентификаторы нужно разделять между собой оператором ‘>>’.
Более сложный пример
Обобщим все изложенное выше в более сложной программе, которая находит сумму двух введенных чисел.
#include <iostream>
using namespace std;
int main()
{
int a,b,s;
cout<<"Введите два числа: ";
cin>>a>>b;
s=a+b;
cout<<a<<"+"<<b<<"="<<s<<endl;
return 0;
}
Упражнения
- Напишите программу, содержащую объявление следующих переменных. Выведите
на экран значения всех объявленных переменных. Объясните, почему получился
именно такой результат.
int a = 13/5;
int b = 13%5;
int c = 13.0/5;
double d = 13/5;
double e = 13%5;
double f = 13.0/5;
double g = 13/5 + 2/5;
double h = 13.0/5 + 2.0/5;
int i = 13.0/5 + 2.0/5; - Дано действительное число x. Вычислите число x4. Какое наименьшее число операций умножения необходимо для этого?
- Дано число x. Вычислите число x6 при помощи трех операций умножения.
- Дано число x. Вычислите число x7 при помощи четырех операций умножения.
- Дано число x. Вычислите число x8 при помощи трех операций умножения.
- Дано число x. Вычислите число x13 при помощи пяти операций умножения.
- Дано число x. Вычислите число x21 при помощи шести операций умножения.
- Дано (пользователь ввел с клавиатуры) натуральное число. Найдите (выведите на экран) его последнюю цифру.
- Дано двузначное число. Найдите число десятков в нем.
- Дано натуральное число. Найдите число десятков в его десятичной записи (то есть вторую справа цифру его десятичной записи).
- Дано трехзначное число. Найдите сумму его цифр.
- Даны три целых числа: h, m, s. Определите угол (в градусах) между часовой стрелкой на циферблате часов в момент времени “h часов, m минут, s секунд” и между часовой стрелкой в полночь.
Битовые операции
Скалярными типами данных называются все типы, принимающие целочисленные значения: char
, short int
, int
, long int
, long long
, а также их signed
и unsigned
модификации.
Для хранения каждого из этих типов в памяти отводится определенное количество байт. Для того, чтобы узнать размер памяти, отводимый для хранения той или иной переменной можно использовать оператор sizeof
: например, sizeof(int)
возвращает количество байт, необходимых для хранения переменной типа int
, а sizeof(A)
, где A
– идентификатор переменной, возвращает количество байт, необходимой для хранения переменной A
.
Каждую переменную скалярного типа будем представлять в виде последовательности бит, нумеруя их от 0, биты будем записывать справа налево (то есть бит с номером 0 будет записан самым правым, а самый старший бит – самым левым).
Например, если переменная a
объявлена, как unsigned char
, то ее можно записать в виде последовательности из 8 бит:
unsigned char a;
a=0 ; // 00000000
a=1 ; // 00000001
a=2 ; // 00000010
a=10 ; // 00001010
a=255 ; // 11111111
Например, если a=10
, то в битовой записи a
биты с номерами 1 и 3 равны 1, а остальные биты равны 0.
Для двух переменных одинакового скалярного типа определены битовые операции:
&
битовое И (AND)
|
битовое ИЛИ (OR)
^
битовое ИСКЛЮЧАЮЩЕЕ ИЛИ (XOR)
~
битовое ОТРИЦАНИЕ (NOT) - унарный оператор
Битовые операторы работают следующим образом. Берутся два операнда, и к каждой паре соответствующих бит для левого и правого операнда применяется данная операция, результатом будет переменная того же типа, каждый бит которой есть результат применения соответствующей логической операции к соответствующим битам двух операндов. Рассмотрим пример:
unsigned char a, b, c, d, e, f;
a = 5 ; // 00000101
b = 6 ; // 00000110
c = a & b ; // 00000100 == 4
d = a | b ; // 00000111 == 7
e = a ^ b ; // 00000011 == 3
f = ~ a ; // 11111010 == 250
Битовое отрицание числа (величина f
в последнем примере) – это число, полученное из исходного заменой всех нулей на единицы и наоборот.
Будьте аккуратны, не путайте логические и битовые операции. Например, 2 && 1 == 1
, поскольку применение логического "И" к двум значениям 2 и 1, то есть к двум "истинам", это истина, но 2 & 1 == 0
!
Есть еще две операции, работающие с битами: это битовые сдвиги. Их две: сдвиг влево и вправо. Оператор a>>n
возвращает число, которое получается из a
сдвигом всех бит на n
позиций вправо, при этом самые правые n
бит отбрасываются. Например:
unsigned char a, b, c, d, e;
a = 43 ; // 00101011
b = a >> 1 ; // 00010101 == 21
c = a >> 2 ; // 00001010 == 10
d = a >> 3 ; // 00000101 == 5
e = a >> 5 ; // 00000001 == 1
Понятно, что для положительных чисел битовый сдвиг числа вправо на n
равносилен целочисленному делению на 2n.
Аналогично, битовый сдвиг влево на n
бит равносилен (для положительных чисел) умножению на 2n и осуществляется при помощи оператора <<
:
unsigned char a;
a = 5 ; // 00000101
b = a << 1 ; // 00001010 == 10
c = a << 2 ; // 00010100 == 20
d = 2 << 3 ; // 00101000 == 40
Упражнения
Во всех упражнениях нельзя использовать арифметические операторы сложения, умножения, вычитания, деления. Вместо них используем побитовые операторы &
, |
, ~
, ^
, <<
, >>
.
Входное число A имеет тип unsigned int
(за исключением последней задачи). Номера битов всегда задаются корректно, то есть принимают значения от 0 до 31.
- (A) Дано число n<32. Запишите число 2n, то есть число, у которого n-й бит равен 1, а остальные – нули.
- (B) Даны два неравных числа: n и m, не превосходящие 31. Вычислите 2n+2m.
- (C) Дано целое число A и натуральное число i. Обнулите у числа A его последние i бит и выведите результат.
- (D) Дано целое число A и натуральное число i. Выведите число, которое получается из числа A установкой значения i-го бита равному 1.
- (E) Дано целое число A и натуральное число i. Выведите число, которое получается из числа A инвертированием i-го бита.
- (F) Дано целое число A и натуральное число i. Выведите число, которое получается из числа A установкой значения i-го бита равному 0.
- (G) Дано целое число A и натуральное число n. Выведите число, которое состоит только из n последних бит числа A (то есть обнулите все биты числа A, кроме последних n).
- (H) Дано целое число A и натуральное число i. Выведите значение i-го бита числа A, то есть 0 или 1.
- (I) Дано число типа
unsigned char
, то есть от 0 до 255. Выведите его в битовой форме: 8 бит, старшие биты слева, младшие – справа.
Самостоятельная работа
Напишите функцию printbyte(unsigned char x)
, печатающую данный байт побитово (как в последней задаче). Теперь реализуйте шаблон template <typename T> print (T A)
, который печатает переменную A
данного типа T
побитно. В шаблоне print
объявим переменную p
типа unsigned char *
и сделаем так, чтобы она указывала на переменную A
, для чего потребуется сделать явное преобразование типов:
unsigned char * p = (unsigned char *) &A;
Теперь, p[0]
будет первым байтом переменной A
, p[1]
– следующим байтом и т.д. Значение каждого байта необходимо напечатать при помощи функции printbyte
. Ну а общее количество байт в переменной A
равно sizeof(A)
.
Теперь напишите функцию main
, которая будет для некоторого типа считывать значение переменной данного типа и выводить его на экран побайтно при помощи шаблона print
. Например, print( (short) 1)
должен вывести 00000001 00000000
, а print( (int) 1)
должен вывести 00000001 00000000 00000000 00000000
.
Теперь проведите эксперименты с различными типами данных с целью выяснения, как они хранятся в памяти. Начните с простых: unsigned char
, unsigned short
, unsigned int
. Как записываются числа 0, 1, 2, 3? Какое наибольшее число можно записать в этих типах?
Разобрались с беззнаковыми числами? Переходим ко знаковым: как храняться отрицательные значения в типах signed char
, signed short
, signed int
? Какие значения может принимать переменная этих типов? Попробуйте разобраться, как работают битовые сдвиги для отрицательных чисел.
Если удалось разобраться со знаковыми целыми – попробуйте перейти к действительным числам типов float
и double
. Это уже довольно трудная задача.