С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 17
Текст из файла (страница 17)
Замечено, что программисты, привыкшие кОбъектноОриентированномуПодходу предпочитают выделять слова заглавнымибуквами, в то время как те_кто_много_писал_на_С используют символ подчеркивания.Какой из двух способов лучше – вопрос вкуса.3.2.3. Определение объектаВ самом простом случае оператор определения объекта состоит из спецификатора типа иdouble salary;double wage;int month;int day;int year;имени объекта и заканчивается точкой с запятой. Например:unsigned long distance;В одном операторе можно определить несколько объектов одного типа. В этом случае ихимена перечисляются через запятую:double salary, wage;int month,day, year;unsigned long distance;Простое определение переменной не задает ее начального значения.
Если объектопределен как глобальный, спецификация С++ гарантирует, что он будетинициализирован нулевым значением. Если же переменная локальная либо динамическиразмещаемая (с помощью оператора new), ее начальное значение не определено, то естьона может содержать некоторое случайное значение.Использование подобных переменных – очень распространенная ошибка, которую к томуже трудно обнаружить.
Рекомендуется явно указывать начальное значение объекта, покрайней мере в тех случаях, когда неизвестно, может ли объект инициализировать самсебя. Механизм классов вводит понятие конструктора по умолчанию, который служитдля присвоения значений по умолчанию. (Мы уже сказали об этом в разделе 2.3. Разговоро конструкторах по умолчанию будет продолжен немного позже, в разделах 3.11 и 3.15,int main() {где мы будем разбирать классы string и complex из стандартной библиотеки.)// неинициализированный локальный объектint ival;// объект типа string инициализирован// конструктором по умолчаниюstring project;83С++ для начинающих// ...}Начальное значение может быть задано прямо в операторе определения переменной.
ВС++ допустимы две формы инициализации переменной – явная, с использованиемint ival = 1024;оператора присваивания:string project = "Fantasia 2000";int ival( 1024 );и неявная, с заданием начального значения в скобках:string project( "Fantasia 2000" );Оба варианта эквивалентны и задают начальные значения для целой переменной ivalкак 1024 и для строки project как "Fantasia 2000".double salary = 9999.99, wage = salary + 0.01;int month = 08;Явную инициализацию можно применять и при определении переменных списком:day = 07, year = 1955;Переменная становится видимой (и допустимой в программе) сразу после ее определения,поэтому мы могли проинициализировать переменную wage суммой только чтоопределенной переменной salary с некоторой константой.
Таким образом, определение:// корректно, но бессмысленноint bizarre = bizarre;является синтаксически допустимым, хотя и бессмысленным.// ival получает значение 0, а dval - 0.0Встроенные типы данных имеют специальный синтаксис для задания нулевого значения:int ival = int();double dval = double();// int() применяется к каждому из 10 элементовВ следующем определении:vector< int > ivec( 10 );84С++ для начинающихк каждому из десяти элементов вектора применяется инициализация с помощью int().(Мы уже говорили о классе vector в разделе 2.8. Более подробно об этом см.
в разделе3.10 и главе 6.)Переменная может быть инициализирована выражением любой сложности, включая#include <cmath>вызовы функций. Например:#include <string>double price = 109.99, discount = 0.16;double sale_price( price * discount );string pet( "wrinkles" );extern int get_value();int val = get_value();unsigned abs_val = abs( val );abs() – стандартная функция, возвращающая абсолютное значение параметра.get_value() – некоторая пользовательская функция, возвращающая целое значение.Упражнение 3.3(a)(b)(c)(d)int car = 1024, auto = 2048;int ival = ival;int ival( int() );double salary = wage = 9999.99;Какие из приведенных ниже определений переменных содержат синтаксические ошибки?(e) cin >> int input_value;Упражнение 3.4Объясните разницу между l-значением и r-значением. Приведите примеры.Упражнение 3.5Найдите отличия в использовании переменных name и student в первой и второй(a) extern string name;string name( "exercise 3.5a" );(b) extern vector<string> students;строчках каждого примера:vector<string> students;Упражнение 3.6Какие имена объектов недопустимы в С++? Измените их так, чтобы они сталисинтаксически правильными:85С++ для начинающих86(a) int double = 3.14159; (b) vector< int > _;(c) string namespase;(d) string catch-22;(e) char 1_or_2 = '1';(f) float Float = 3.14f;Упражнение 3.7В чем разница между следующими глобальными и локальными определениямиstring global_class;int global_int;переменных?int main() {int local_int;string local_class;// ...}3.3.
УказателиУказатели и динамическое выделение памяти были вкратце представлены в разделе 2.2.Указатель – это объект, содержащий адрес другого объекта и позволяющий косвенноманипулировать этим объектом. Обычно указатели используются для работы сдинамически созданными объектами, для построения связанных структур данных, таких,как связанные списки и иерархические деревья, и для передачи в функции большихобъектов – массивов и объектов классов – в качестве параметров.Каждый указатель ассоциируется с некоторым типом данных, причем их внутреннеепредставление не зависит от внутреннего типа: и размер памяти, занимаемый объектомтипа указатель, и диапазон значений у них одинаков5. Разница состоит в том, каккомпилятор воспринимает адресуемый объект.
Указатели на разные типы могут иметьодно и то же значение, но область памяти, где размещаются соответствующие типы,может быть различной:•указатель на int, содержащий значение адреса 1000, направлен на областьпамяти 1000-1003 (в 32-битной системе);•указатель на double, содержащий значение адреса 1000, направлен на областьпамяти 1000-1007 (в 32-битной системе).intcomplex<double>stringvector<int>*ip1, *ip2;*cp;*pstring;*pvec;Вот несколько примеров:5 На самом деле для указателей на функции это не совсем так: они отличаются отуказателей на данные (см. раздел 7.9).С++ для начинающихdouble*dp;Указатель обозначается звездочкой перед именем. В определении переменных спискомзвездочка должна стоять перед каждым указателем (см.
выше: ip1 и ip2). В примерениже lp – указатель на объект типа long, а lp2 – объект типа long:long *lp, lp2;В следующем случае fp интерпретируется как объект типа float, а fp2 – указатель нанего:float fp, *fp2;Оператор разыменования (*) может отделяться пробелами от имени и даженепосредственно примыкать к ключевому слову типа. Поэтому приведенные определенияstring *ps;синтаксически правильны и совершенно эквивалентны:string* ps;Однако рекомендуется использовать первый вариант написания: второй способен ввестив заблуждение, если добавить к нему определение еще одной переменной через запятую://внимание: ps2 не указатель на строку!string* ps, ps2;Можно предположить, что и ps, и ps2 являются указателями, хотя указатель – толькопервый из них.Если значение указателя равно 0, значит, он не содержит никакого адреса объекта.Пусть задана переменная типа int:int ival = 1024;//pi инициализирован нулевым адресомint *pi = 0;// pi2 инициализирован адресом ivalint *pi2 = &ival;// правильно: pi и pi2 содержат адрес ivalpi = pi2;// pi2 содержит нулевой адресНиже приводятся примеры определения и использования указателей на int pi и pi2:pi2 = 0;Указателю не может быть присвоена величина, не являющаяся адресом:87С++ для начинающих// ошибка: pi не может принимать значение intpi = ivalТочно так же нельзя присвоить указателю одного типа значение, являющееся адресомdouble dval;объекта другого типа.
Если определены следующие переменные:double *ps = &dval;// ошибки компиляциито оба выражения присваивания, приведенные ниже, вызовут ошибку компиляции:// недопустимое присваивание типов данных: int* <== double*pi = pdpi = &dval;Дело не в том, что переменная pi не может содержать адреса объекта dval – адресаобъектов разных типов имеют одну и ту же длину. Такие операции смешения адресовзапрещены сознательно, потому что интерпретация объектов компилятором зависит оттипа указателя на них.Конечно, бывают случаи, когда нас интересует само значение адреса, а не объект, накоторый он указывает (допустим, мы хотим сравнить этот адрес с каким-то другим).
Дляразрешения таких ситуаций введен специальный указатель void, который может// правильно: void* может содержатьуказывать на любой тип данных, и следующие выражения будут правильны:// адреса любого типаvoid *pv = pi;pv = pd;Тип объекта, на который указывает void*, неизвестен, и мы не можем манипулироватьэтим объектом. Все, что мы можем сделать с таким указателем, – присвоить его значениедругому указателю или сравнить с какой-либо адресной величиной.
(Более подробно мырасскажем об указателе типа void в разделе 4.14.)Для того чтобы обратиться к объекту, имея его адрес, нужно применить операциюразыменования, или косвенную адресацию, обозначаемую звездочкой (*). Имеяint ival = 1024;, ival2 = 2048;следующие определения переменных:int *pi = &ival;мы можем читать и сохранять значение ival, применяя операцию разыменования куказателю pi:88С++ для начинающих// косвенное присваивание переменной ival значения ival2*pi = ival2;// косвенное использование переменной ival как rvalue и lvalue*pi = abs(*pi); // ival = abs(ival);*pi = *pi + 1;// ival = ival + 1;Когда мы применяем операцию взятия адреса (&) к объекту типа int, то получаемрезультат типа int*int *pi = &ival;Если ту же операцию применить к объекту типа int* (указатель на int), мы получимуказатель на указатель на int, т.е.
int**. int** – это адрес объекта, который содержитадрес объекта типа int. Разыменовывая ppi, мы получаем объект типа int*,содержащий адрес ival. Чтобы получить сам объект ival, операцию разыменования кint **ppi = πint *pi2 = *ppi;cout <<<<<<<<"Значение ival\n""явное значение: " << ival << "\n""косвенная адресация: " << *pi << "\n""дважды косвенная адресация: " << **ppi << "\n"ppi необходимо применить дважды.<< endl;Указатели могут быть использованы в арифметических выражениях. Обратите вниманиена следующий пример, где два выражения производят совершенно различные действия:int i, j, k;int *pi = &i;// i = i + 2*pi = *pi + 2;// увеличение адреса, содержащегося в pi, на 2pi = pi + 2;К указателю можно прибавлять целое значение, можно также вычитать из него.Прибавление к указателю 1 увеличивает содержащееся в нем значение на размер областипамяти, отводимой объекту соответствующего типа.