Программирование. Принципы и практика использования C++ Исправленное издание - Бьёрн Страуструп
Шрифт:
Интервал:
Закладка:
int i;
for (i = 0; i<max; ++i) x[i] = y[i];
struct S* p;
while (p = next(q)) {
/* ... */
}
void f(int i)
{
if (i< 0 || max<=i) error("Ошибка диапазона");
{
int a[max];
/* ... */
}
}
В языке С++ неинициализированное объявление считается определением; в языке С оно считается простым объявлением, поэтому его можно дублировать.
int x;
int x; /* определяет или объявляет одну целочисленную переменную
с именем x в программе на языке C; ошибка в языке C++ */
В языке С++ сущность должна быть определена только один раз. Ситуация становится интереснее, если эти две переменные типа int с одинаковыми именами находятся в разных модулях компиляции.
/* в файле x.c: */
int x;
/* в файле y.c: */
int x;
Ни компилятор языка С, ни компилятор языка С++ не найдет никаких ошибок в файлах x.c или y.c. Но если файлы x.c и y.c скомпилировать как файлы на языке С++, то редактор связей выдаст сообщение об ошибке, связанной с двойным определением. Если же файлы x.c и y.c скомпилировать на языке C, то редактор связей не выдаст сообщений об ошибке и (в полном соответствии с правилами языка C) будет считать, что речь идет об одной и той же переменной x, совместно используемой в файлах x.c и y.c. Если хотите, чтобы в программе всеми модулями совместно использовалась одна глобальная переменная x, то сделайте это явно, как показано ниже.
/* в файле x.c: */
int x = 0; /* определение */
/* в файле y.c: */
extern int x; /* объявление, но не определение */
Впрочем, лучше используйте заголовочный файл.
/* в файле x.h: */
extern int x; /* объявление, но не определение */
/* в файле x.c: */
#include "x.h"
int x = 0; /* определение */
/* в файле y.c: */
#include "x.h"
/* объявление переменной x находится в заголовочном файле */
А еще лучше: избегайте глобальных переменных.
27.3.4. Приведение типов в стиле языка С
В языке C (и в языке C++) можете явно привести переменную v к типу T, используя минимальные обозначения.
(T)v
Это так называемое “приведение в стиле языка С”, или “приведение в старом стиле”. Его любят люди, не умеющие набирать тексты (за лаконичность) и ленивые (потому что они не обязаны знать, что нужно для того, чтобы из переменной v получилась переменная типа T). С другой стороны, этот стиль яростно отвергают программисты, занимающиеся сопровождением программ, поскольку такие преобразования остаются практически незаметными и никак не привлекают к себе внимания. Приведения в языке С++ (приведения в новом стиле (new-style casts), или приведения в шаблонном стиле (template-style casts); см. раздел А.5.7) осуществляют явное преобразование типов, которое легко заметить. В языке С у вас нет выбора.
int* p = (int*)7; /* интерпретирует битовую комбинацию:
reinterpret_cast<int*>(7) */
int x = (int)7.5; /* усекает переменную типа: static_cast<int>(7.5) */
typedef struct S1 { /* ... */ } S1;
typedef struct S2 { /* ... */ } S2;
S1 a;
const S2 b; /* в языке С допускаются неинициализированные
/* константы */
S1* p = (S2*)&a; /* интерпретирует битовую комбинацию:
reinterpret_cast<S1*>(&a) */
S2* q = (S2*)&b; /* отбрасывает спецификатор const:
const_cast<S2*>(&b) */
S1* r = (S1*)&b; /* удаляет спецификатор const и изменяет тип;
похоже на ошибку */
Мы не рекомендуем использовать макросы даже в программах на языке C (раздел 27.8), но, возможно, описанные выше идеи можно было бы выразить следующим образом:
#define REINTERPRET_CAST(T,v) ((T)(v))
#define CONST_CAST(T,v) ((T)(v))
S1* p = REINTERPRET_CAST (S1*,&a);
S2* q = CONST_CAST(S2*,&b);
Это не обеспечит проверку типов при выполнении операторов reinterpret_cast и const_cast, но сделает эти ужасные операции заметными и привлечет внимание программиста.
27.3.5. Преобразование указателей типа void*
В языке указатель типа void* можно использовать как в правой части оператора присваивания, так и для инициализации указателей любого типа; в языке C++ это невозможно. Рассмотрим пример.
void* alloc(size_t x); /* выделяет x байтов */
void f (int n)
{
int* p = alloc(n*sizeof(int)); /* OK в языке C;
ошибка в языке C++ */
/* ... */
}
Здесь указатель типа void* возвращается как результат функции alloc() и неявно преобразовывается в указатель типа int*. В языке C++ мы могли бы переписать эту строку следующим образом:
int* p = (int*)alloc(n*sizeof(int)); /* OK и в языке C,
и в языке C++ */
Мы использовали приведение в стиле языка C (раздел 27.3.4), чтобы оно оказалось допустимым как в программах на языке C, так и в программах на языке C++.
Почему неявное преобразование void* в T* является недопустимым в языке С++? Потому, что такие преобразования могут быть небезопасными.
void f()
{
char i = 0;
char j = 0;
char* p = &i;
void* q = p;
int* pp = q; /* небезопасно; разрешено в языке C,
ошибка в языке C++ */
*pp = –1; /* перезаписываем память, начиная с адреса &i */
В данном случае мы даже не уверены, какой фрагмент памяти будет перезаписан: переменная j или часть памяти, на которую ссылается указатель p? А может быть, память, использованная для управлении вызовом функции f() (стек функции f)? Какие бы данные ни были перезаписаны, вызов функции f() приведет к печальным последствиям.
Обратите внимание на то, что (обратное)