1. Const-корректность придумал Бьярне Строуструп, и только ≈1999 её внесли в Си без крестов. А поскольку память, занятую строковым литералом, изменять запрещается (а в современных ОС литералы сидят в особом неизменяемом сегменте), понятное дело, новым типом для них стал const char*. Поскольку Си99 для совместимости позволял писать
char* text = "foo";
, а разработка Си(++) застопорилась на десятилетие, учебники по Си очень долго писали такую нерекомендуемую строчку.
3. Для маленьких объектов это одно и то же, но для больших немалая разница. Эта программа компилируется начиная с Си++17.
#include <iostream>
class WrapInt
{
public:
int value = 0;
WrapInt(int x) : value(x) {}
WrapInt(WrapInt&) = delete;
private:
};
int main()
{
WrapInt x = 42;
}
int zero = 0;
до C++14 — создать временный объект, вызвать конструктор перемещения, а остальное делает оптимизатор. С Си++17 — вызов конструктора, если он не explicit. Мне казалось, что в очень старых версиях DJGPP был задействован op=, но ХЕЗ, MinGW и Си++03 вызывают именно конструктор.
int zero = int(0);
— что удивительно, то же самое, только конструктор любой! Хотя выглядит как явное создание временного объекта.
int zero(0);
— вызов конструктора во всех версиях Си++.
int zero{0};
— универсальный инициализатор Си++11, также вызов конструктора.
4.
int array[] = { 1, 2, 3};
— все версии Си и Си++.
int array[] { 1, 2, 3};
— универсальный инициализатор Си++11.
6. Эдсгер Дейкстра
рассказал, почему массивы должны нумероваться с нуля, а целые диапазоны — задаваться в формате [a,b). Итак, у нас есть диапазон [a,b) и массив с запасом. Как сдвинуть b и добавить в диапазон один элемент? —
array[b] = newElem; ++b;
. Керниган и Ритчи решили объединить это чудо в одну строку
array[b++] = newElem;
, к тому же в процессорах действительно бывают пред- и постинкрементные операции.
7. void — это псевдотип, чьи элементы создавать нельзя. Указатели можно передвигать с шагом, который равен размеру типа — то есть прибавляем 1, реально прибавляется 1, 2, 4, 8 или сколько там. Для void*, который является простым указателем на память, шаг 1 байт. Или 1 слово, если машина оперирует более крупными словами — такие были, но из-за переусложнённой работы с текстом от них отказались. Но инкременты-декременты void*, если и возможны, то нежелательны, и вот почему.
Чтобы «соединить ежа с ужом» (длинное слово и доступ по байтам), x86 говорит: работа с невыровненными данными (например, 4 байта по адресу 21) будет происходить медленно и по неопределённому алгоритму. Itanium вообще, ЕМНИП, к невыровненным данным не обращается, как это устроено в компиляторах: char* → int* говорит, что обращаться нужно медленно и программно, void* → int* — быстро и аппаратно.
8. libc можно и не подключать, хоть это и сложно. А return из main НЕ вызывает std::exit. Линкер создаёт код обвязки, который открывает все глобальные объекты, вызывает main), закрывает все глобальные объекты и выходит из программы по-системному.