
http://ips.ifmo.ru/courses/cpp/topic1/l2/
Концепция типа данных
Основная цель любой программы состоит в обработке данных. Данные различного типа хранятся и обрабатываются по-разному. В любом алгоритмическом языке каждая константа, переменная, результат вычисления выражения или функции должны иметь определенный тип.
Тип данных определяет:
внутреннее представление данных в памяти компьютера;
множество значений, которые могут принимать величины этого типа;
операции и функции, которые можно применять к величинам этого типа.
Исходя из этих характеристик, программист выбирает тип каждой величины, используемой в программе для представления реальных объектов. Обязательное описание типа позволяет компилятору производить проверку допустимости различных конструкций программы. От выбора типа величины зависит последовательность машинных команд, построенная компилятором.
Все типы языка С++ можно разделить на простые (скалярные), составные (агрегатные) и функциональные. Простые типы могут быть стандартными и определенными программистом.
В языке С++ определено шесть стандартных простых типов данных для представления целых, вещественных, символьных и логических величин. На основе этих типов, а также массивов и указателей (указатель не является самостоятельным типом, он всегда связан с каким-либо другим конкретным типом), программист может вводить описание собственных простых или структурированных типов. К структурированным типам относятся перечисления, функции, структуры, объединения и классы.
Простые типы данных
Простые типы делятся на целочисленные типы и типы с плавающей точкой. Для описания стандартных типов определены следующие ключевые слова:
int (целый);
char (символьный);
wchar_t (расширенный символьный);
bool (логический);
float (вещественный);
double (вещественный с двойной точностью).
Существует четыре спецификатора типа, уточняющих внутреннее представление и диапазон значений стандартных типов:
short (короткий);
long (длинный);
signed (со знаком);
unsigned (без знака).
Целый тип (int)
Размер типа int стандартом ANSI не определяется. Он зависит от реализации. Для 16-разрядного процессора под величины этого типа отводится 2 байта, для 32-разрядного — 4 байта.
Спецификатор short перед именем типа указывает компилятору, что под число требуется отвести 2 байта. Спецификатор longозначает, что целая величина будет занимать 4 байта.
Внутреннее представление величины целого типа — целое число в двоичном коде. При использовании спецификатора signedстарший бит числа интерпретируется как знаковый (0 — положительное число, 1 — отрицательное). Спецификатор unsignedпозволяет представлять только положительные числа. Диапазоны значений величин целого типа с различными спецификаторами для IBM PC-совместимых компьютеров приведены в таблице 1.4.
По умолчанию все целочисленные типы считаются знаковыми.
Константам, встречающимся в программе, приписывается тип в соответствии с их видом. Программист может явно указать требуемый тип с помощью суффиксов L, l (long) и U, u (unsigned). Например, константа 32L имеет тип long и занимает 4 байта.
ПРИМЕЧАНИЕ
Типы short int, long int, signed int и unsigned int можно сокращать до short, long, signed и unsigned соответственно.
Символьный тип (char)
Под величину символьного типа отводится количество байт, достаточное для размещения любого символа из набора символов для данного компьютера. Как правило, это 1 байт.
Тип char, как и другие целые типы, может быть со знаком или без знака.
В величинах со знаком можно хранить значения в диапазоне от –128 до 127. При использовании спецификатора unsignedзначения могут находиться в пределах от 0 до 255. Величины типа char применяются также для хранения целых чисел, не превышающих границы указанных диапазонов.
Расширенный символьный тип (wchar_t)
Тип wchar_t предназначен для работы с набором символов, для кодировки которых недостаточно 1 байта, например, Unicode. Размер этого типа зависит от реализации; как правило, он соответствует типу short.
Логический тип (bool)
Величины логического типа могут принимать только значения true и false. Внутренняя форма представления значения false— 0 (нуль). Любое другое значение интерпретируется как true. При преобразовании к целому типу true имеет значение 1.
Типы с плавающей точкой (float, double и longdouble)
Стандарт С++ определяет три типа данных для хранения вещественных значений: float, double и longdouble.
Внутреннее представление вещественного числа состоит из мантиссы и порядка. Длина мантиссы определяет точность числа, а длина порядка — его диапазон.
Константы с плавающей точкой имеют по умолчанию тип double. Можно явно указать тип константы с помощью суффиксов F, f(float) и L, l (long). Например, константа 2E+6L будет иметь тип long double.
Таблица 1.3. Диапазоны значений простых типов данных для IBM PC
Тип |
Диапазон значений |
Размер (байт) |
bool |
true и false |
1 |
signed char |
–128 .. 127 |
1 |
unsigned char |
0 .. 255 |
1 |
signed short int |
–32 768 .. 32 767 |
2 |
unsigned short int |
0 .. 65 535 |
2 |
signed long int |
–2 147 483 648 .. 2 147 483 647 |
4 |
unsigned long int |
0 .. 4 294 967 295 |
4 |
float |
3.4e–38 .. 3.4e+38 |
4 |
double |
1.7e–308 .. 1.7e+308 |
8 |
long double |
3.4e–4932 .. 3.4e+4932 |
10 |
Для вещественных типов в таблице приведены абсолютные величины минимальных и максимальных значений.
Тип void
Тип void используется для определения функций, которые не возвращают значения, для указания пустого списка аргументов функции, как базовый тип для указателей и в операции приведения типов.
http://cppstudio.com/post/423/
Указатель – переменная, значением которой является адрес ячейки памяти. То есть указатель ссылается на блок данных из области памяти, причём на самое его начало. Указатель может ссылаться на переменную или функцию. Для этого нужно знать адрес переменной или функции. Так вот, чтобы узнать адрес конкретной переменной в С++ существует унарная операция взятия адреса &. Такая операция извлекает адрес объявленных переменных, для того, чтобы его присвоить указателю.
Указатели используются для передачи по ссылке данных, что намного ускоряет процесс обработки этих данных (в том случае, если объём данных большой), так как их не надо копировать, как при передаче по значению, то есть, используя имя переменной. В основном указатели используются для организации динамического распределения памяти, например при объявлении массива, не надо будет его ограничивать в размере. Ведь программист заранее не может знать, какого размера нужен массив тому или иному пользователю, в таком случае используется динамическое выделение памяти под массив. Любой указатель необходимо объявить перед использованием, как и любую переменную.
1 2 |
//объявление указателя /*тип данных*/ * /*имя указателя*/; |
Принцип объявления указателей такой же, как и принцип объявления переменных. Отличие заключается только в том, что перед именем ставится символ звёздочки *. Визуально указатели отличаются от переменных только одним символом. При объявлении указателей компилятор выделяет несколько байт памяти, в зависимости от типа данных отводимых для хранения некоторой информации в памяти. Чтобы получить значение, записанное в некоторой области, на которое ссылается указатель нужно воспользоваться операцией разыменования указателя *. Необходимо поставить звёздочку перед именем и получим доступ к значению указателя. Разработаем программу, которая будет использовать указатели.
MVS
Code::Blocks
Dev-C++
QtCreator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// pointer1.cpp: определяет точку входа для консольного приложения.
#include "stdafx.h" #include <iostream> using namespace std;
int main(int argc, char* argv[]) { int var = 123; // инициализация переменной var числом 123 int *ptrvar = &var; // указатель на переменную var (присвоили адрес переменной указателю) cout << "&var = " << &var << endl;// адрес переменной var содержащийся в памяти, извлечённый операцией взятия адреса cout << "ptrvar = " << ptrvar << endl;// адрес переменной var, является значением указателя ptrvar cout << "var = " << var << endl; // значение в переменной var cout << "*ptrvar = " << *ptrvar << endl; // вывод значения содержащегося в переменной var через указатель, операцией разименования указателя system("pause"); return 0; } |
В строке 10 объявлен и инициализирован адресом переменной var указатель ptrvar. Можно было сначала просто объявить указатель, а потом его инициализировать, тогда были бы две строки:
1 2 |
int *ptrvar; // объявление указателя ptrvar = &var; // инициализация указателя |
В программировании принято добавлять к имени указателя приставку ptr, таким образом, получится осмысленное имя указателя, и уже с обычной переменной такой указатель не спутаешь. Результат работы программы (см. Рисунок 1).
CppStudio.com
&var = 0x22ff08
ptrvar = 0x22ff08
var = 123
*ptrvar = 123
Для продолжения нажмите любую клавишу . . .
Рисунок 1 — Указатели в С++
Итак, программа показала, что строки 11 и 12 выводят идентичный адрес, то есть адрес переменной var, который содержится в указателе ptrvar. Тогда как операция разыменования указателя *ptrvar обеспечивает доступ к значению, на которое ссылается указатель.
Указатели можно сравнивать, причём не, только на равенство или неравенство, ведь адреса могут быть меньше или больше относительно друг друга. Разработаем программу, которая будет сравнивать адреса указателей.
MVS
Code::Blocks
Dev-C++
QtCreator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// pointer.cpp: определяет точку входа для консольного приложения.
#include "stdafx.h" #include <iostream> using namespace std;
int main(int argc, char* argv[]) { int var1 = 123; // инициализация переменной var1 числом 123 int var2 = 99; // инициализация переменной var2 числом 99 int *ptrvar1 = &var1; // указатель на переменную var1 int *ptrvar2 = &var2; // указатель на переменную var2 cout << "var1 = " << var1 << endl; cout << "var2 = " << var2 << endl; cout << "ptrvar1 = " << ptrvar1 << endl; cout << "ptrvar2 = " << ptrvar2 << endl; if (ptrvar1 > ptrvar2) // сравниваем значения указателей, то есть адреса переменных cout << "ptrvar1 > ptrvar2" << endl; if (*ptrvar1 > *ptrvar2) // сравниваем значения переменных, на которые ссылаются указатели cout << "*ptrvar1 > *ptrvar2" << endl; system("pause"); return 0; } |
Результат работы программы показан на рисунке 2.
CppStudio.com
var1 = 123
var2 = 99
ptrvar1 = 0x22ff04
ptrvar2 = 0x22ff00
ptrvar1 > ptrvar2
*ptrvar1 > *ptrvar2
Для продолжения нажмите любую клавишу . . .
Рисунок 2 — Указатели в С++
В первом случае, мы сравнивали адреса переменных, и, причём адрес второй переменной, всегда меньше адреса первой переменной. При каждом запуске программы адреса выделяются разные. Во втором случае мы сравнивали значения этих переменных используя операцию разыменования указателя.
Из арифметических операций, чаще всего используются операции сложения, вычитания, инкремент и декремент, так как с помощью этих операций, например в массивах, вычисляется адрес следующего элемента.
Указатели на указатели
Указатели могут ссылаться на другие указатели. При этом в ячейках памяти, на которые будут ссылаться первые указатели, будут содержаться не значения, а адреса вторых указателей. Число символов * при объявлении указателя показывает порядок указателя. Чтобы получить доступ к значению, на которое ссылается указатель его необходимо разыменовывать соответствующее количество раз. Разработаем программу, которая будет выполнять некоторые операции с указателями порядка выше первого.
MVS
Code::Blocks
Dev-C++
QtCreator
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// pointer.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include <iostream> using namespace std;
int _tmain(int argc, _TCHAR* argv[]) { int var = 123; // инициализация переменной var числом 123 int *ptrvar = &var; // указатель на переменную var int **ptr_ptrvar = &ptrvar; // указатель на указатель на переменную var int ***ptr_ptr_ptrvar = &ptr_ptrvar; cout << " var\t\t= " << var << endl; cout << " *ptrvar\t= " << *ptrvar << endl; cout << " **ptr_ptrvar = " << **ptr_ptrvar << endl; // два раза разименовываем указатель, так как он второго порядка cout << " ***ptr_ptrvar = " << ***ptr_ptr_ptrvar << endl; // указатель третьего порядка cout << "\n ***ptr_ptr_ptrvar -> **ptr_ptrvar -> *ptrvar -> var -> "<< var << endl; cout << "\t " << &ptr_ptr_ptrvar<< " -> " << " " << &ptr_ptrvar << " ->" << &ptrvar << " -> " << &var << " -> " << var << endl; system("pause"); return 0; } |
На рисунке 3 показан результат работы программы.
CppStudio.com
var = 123
*ptrvar = 123
**ptr_ptrvar = 123
***ptr_ptrvar = 123
***ptr_ptr_ptrvar -> **ptr_ptrvar -> *ptrvar -> var -> 123
0x22ff00 -> 0x22ff04 ->0x22ff08 -> 0x22ff0c -> 123
Для продолжения нажмите любую клавишу . . .
Рисунок 3 — Указатели в С++
Данная программа доказывает тот факт, что для получения значения количество разыменований указателя должно совпадать с его порядком. Логика n-кратного разыменования заключается в том, что программа последовательно перебирает адреса всех указателей вплоть до переменной, в которой содержится значение. В программе показана реализация указателя третьего порядка. И если, используя такой указатель (третьего порядка) необходимо получить значение, на которое он ссылается, делается 4 шага:
по значению указателя третьего порядка получить адрес указателя второго порядка;
по значению указателя второго порядка получить адрес указателя первого порядка;
по значению указателя первого порядка получить адрес переменной;
по адресу переменной получить доступ к её значению.
Данные четыре действия показаны на рисунке 3 (две предпоследние строки). Верхняя строка показывает имена указателей, а нижняя строка их адреса.
На рисунке 4 показана схема разыменовывания указателя третьего порядка из верхней программы. Суть в том, что указатели связаны друг с другом через свои адреса. Причём, например, для указателя ptr_ptrvar данное число 0015FDB4 является адресом, а для указателя ptr_ptr_ptrvar это же число является значением.
Рисунок 4 — Указатели в С++
Именно таким образом выполняется связка указателей. Вообще говоря, указатели порядка выше первого используются редко, но данный материал поможет понять механизм работы указателей.