Константы и замена для препроцессора
В языке С квалификатор
const используется только для указания, что объект нельзя изменять, например,
параметр не меняется внутри функции:
char *strcpy(char *dst, const char *src);
Именованные константы в языке C определяются либо с помощью команды препроцессора
#define (в этом случае нельзя будет определить локально переменную с тем же именем), либо с помощью
enum (только для целых констант).
В С++ можно создавать типизированные
именованные константы с ограниченной областью видимости с помощью квалификатора
constexpr. Квалификатор
const также можно использовать для определения констант, но чаще нужен для указания, что значение объекта не должно меняться.
constexpr int size=100;
int a[size];
constexpr double pi=3.14159265358979323846;
Появилась специальная константа
nullptr, которая которая неявно может преобразована в указатель любого типа, но не в число. В языке С для определения нулевого указателя
NULL использовалась константа 0, что могло привести к путанице в C++.
Вместо константы
I языка С для определения комплексных чисел (
12.5+1.5e8*I) в C++ введен
суффикс
i:
12.5+1.5e8i. Кроме того введены суффиксы для промежутков времени:
2h, 15min, 9s, 11ms, 50us, 2ns. Программист может добавить свои суффиксы для констант.
Для записи двоичных чисел используется префикс 0b (
0b1010), разряды в длинных числах можно разделять апострофами (
12'345'671).
С помощью
enum class можно перечислить константы, которые являются типобезопасными и требуют явного преобразования в
int и обратно. Также для перечислений можно указать базовый целый тип (по умолчанию
int).
void f(int);
enum Switch: char {on, off};
enum class Color { green, red, blue, white };
Switch s1=off;
Color c1=Color::red;
f(s1); //
f(c1); //
f(int(c1)); //
Если перед функцией указать
constexpr, то при вызове такой функции с аргументами-константами, её значение будет вычислено во время компиляции.
constexpr int gcd(int a, int b){
return b ? gcd(b, a % b) : a;
}
int main()
{ int x=gcd(110,33); //
int a=110,b=33;
x=gcd(a,b); //
}
Функция, определенная с помощью спецификатора
inline становится
встраиваемой, т.е. её код подставляется в точку вызова. При этом устраняются все недостатки, свойственные макроопределениям. Время работы программы уменьшается за счет операций сохранения/восстановления регистров и передачи управления, но размер программы увеличивается. Спецификатор
inline носит рекомендательный характер, для больших функций компилятор генерирует обычный вызов. Тело встраиваемой функции определяется в заголовочном файле.
inline int max(int x, int y)
{ return x>y?x:y;
}
Макроопределение
#define max(x,y) ((x)>(y)?(x):(y)) работает для любых типов данных. Чтобы функция max работала так же, её нужно определить как шаблон:
template <typename T>
inline T max(T x, T y)
{ return x>y?x:y;
}
Для условной компиляции вместо #ifdef/#else/#endif во многих случаях можно использовать константы и условный оператор с квалификатором
constexpr. Компилятор не будет генерировать код для ложных условий.
constexpr bool DEBUG=false;
...
if constexpr(DEBUG)
{ cout<<"x="<<x<<endl;
}
Вместо комбинации команд препроцессора #if...#endif и #error, была добавлена возможность выполнять необходимые проверки компилятору:
static_assert(sizeof(int)!=sizeof(void *), "Cannot convert pointer to int.");
Таким образом препроцессор в C++ используется в основном для подключения заголовочных файлов, но в C++20 предложены модули как замена и для этих команд.
export module sample;
export {
constexpr int n=42;
int sqr(int x) { return x*x; }
}
Для подключения этого модуля и заголовочных файлов STL выглядит так:
import sample;
import <iostream>;
int main() {
cout<<sqr(n)<<'\n';
}
В MinGW C++ нужна предварительная компиляция заголовочных файлов, результаты которой сохраняются в папке проекта:
g++ -std=c++23 -c -fmodule-header=system -x c++-system-header iostream ...
В С++23 для подключения
всех стандартных заголовочных файлов C++ и C можно написать:
import std;
Также в C++20 появились спецификаторы
consteval для функций, проверяющий, что функция вызывается только с константными аргументами (constexpr-функция может вызываться не только для аргументов-констант) и
constinit для переменных, который проверяет, что переменная инициализируется константой. В C++23 добавлен оператор ветвления для constexpr-функций:
constexpr int fun(int n)
if consteval {
//
}
else {
//
}
}