Страницы

Поиск по вопросам

Показаны сообщения с ярлыком объявление. Показать все сообщения
Показаны сообщения с ярлыком объявление. Показать все сообщения

четверг, 13 февраля 2020 г.

C++. Предварительное объявление членов классов за их пределами

#cpp #классы #объявление


У меня сложилась такая ситуация, что есть два класса, каждый из которых нуждается
в конструкторе другого. Для этого нужно, чтобы они видели не только объявления (прототипы),
но и определения друг друга одновременно, что невозможно. Значит, каждый класс должен
видеть сверху по крайней мере прототип конструктора другого.

Вот мой код:

#include 

class Euros;
Euros::Euros(double euros = 0);

class Dollars
{
    private:
        double m_dollars;

    public:
        Dollars(double dollars = 0) : m_dollars(dollars)
        {
        }

        operator double() const { return m_dollars; }
        operator Euros() const { return Euros(m_dollars * 0.89); }

        double getDollars() const { return m_dollars; }
        void setDollars(double dollars) { m_dollars = dollars; }
};

class Euros
{
    private:
        double m_euros;
    public:
        Euros(double euros) : m_euros(euros)
        {
        }

        operator double() const { return m_euros; }
        operator Dollars() const { return Dollars(m_euros * 1.12); }

        double getEuros() const { return m_euros; }
        void setEuros(double euros) { m_euros = euros; }
};


Здесь для каждого класса перегружается операция преобразования типов друг в друга,
для чего требуется возвращать вновь созданный с помощью конструктора экземпляр противоположного
класса. С классом Euros всё нормально, т.к. он видит полное определение класса Dollars,
а значит видит и конструктор. С классом Dollars всё наоборот: он вообще не видит Euros.
В 3 строке я объявил прототип Euros, но этого недостаточно, т.к. для создания объекта
нужен ещё и конструктор. 

Я знаю, что методы класса достаточно только объявить внутри него, а определение может
быть уже после за его пределами. Но я не знаю, можно ли сделать наоборот: определить
внутри класса, а объявить перед ним. В 4 строке я попытался это сделать, но получил
ошибку "invalid use of incomlete type 'class Euros'". Естественно, были и другие ошибки,
вытекающие из этой, но данная ошибка источник всех проблем.

Попытки гуглить привели к осознанию того, что я не способен сформулировать вопрос
на языке, понятном для поисковика. Как можно решить этот замкнутый круг?
    


Ответы

Ответ 1



Простое вынесите реализации "перекрёстных" методов (одного из них) наружу из определения класса и поместите ниже - после определения второго класса class Euros; class Dollars { ... operator Euros() const; ... }; class Euros { ... operator Dollars() const { return Dollars(m_euros * 1.12); } ... }; inline Dollars::operator Euros() const { return Euros(m_dollars * 0.89); } Вы и сами написали об этом в вопросе: "Я знаю, что методы класса достаточно только объявить внутри него, а определение может быть уже после за его пределами." Только применять это знание вам надо было обычным способом к вашим операторам преобразования, а не каким-то "вывернутым" образом к конструкторам.

пятница, 31 января 2020 г.

Спецификатор noexcept

#cpp #функции #исключения #cpp11 #объявление


class Math { 
    public: 
        static bool __fastcall      IsPowerOfTwo( int x ) noexcept;
};

__forceinline bool Math::IsPowerOfTwo( int x ) { // хз что делает :D
    return ( x & ( x - 1 ) ) == 0 && x > 0;
}


noexcept надо тоже писать в определении?
    


Ответы

Ответ 1



поясню про выражение x & (x-1) == 0 && x > 0 оно проверяет положительное число x на степень двойки тоесть это числа вида x = 2^i, где i позиция единственного единичного бита в двоичном представлении имеют следующий вид: 0000...100000 = 2^i = x а числа x - 1 имеют вид 0000...011111 = 1*2^i-1 + 1*2^i-2 + ... + 1*2^0 = x-1 тоесть все разряды начиная с i-1 ого единичные, отсюда если побитово применить AND тоесть x & (x-1) получим 0 что означает что, число x это степень двойки, в противном случае, соотвественно не является степенью двойки

Ответ 2



Согласно стандарту C++ (15.4 Exception specification) 5 If any declaration of a function has an exception-specification that is not a noexcept-specification allowing all exceptions, all declarations, including the definition and any explicit specialization, of that function shall have a compatible exception-specification.... Это значит, что определение функции IsPowerOfTwo также должно содержать спецификацию исключения noexcept или noexcept( true ) Что касается самой функции, то она определяет, является ли заданное положительное целое число степенью 2. ( x & ( x - 1 ) ) == 0 && x > 0; Выражение ( x & ( x - 1 ) ) означает следующее. Все целые числа, являющиеся степенью 2 имеют следующий вид (на примере одного байта) 0000 0001 0000 0010 0000 0100 0000 1000 0001 0000 0010 0000 0100 0000 и т.д. для остальных байтов. То есть в числе присутствует лишь одна битовая 1. Если для какого-нибудь из перечисленных чисел вычесть 1, то получим 0000 0100 - 1 == 0000 0011 Теперь если сделать битовую операцию & (AND) с полученными числами, то результат будет равен 0. 0000 0100 0000 0011 ========= 0000 0000 Если бы исходное число не являлось степенью 2, как, например, 0000 1100 то есть если оно содержит более одной битовой 1, то после вычитания 1 в старших разрядах сохранятся другие единицы, если они присутствуют в числе 0000 1100 - 1 == 0000 1011 ^ ^ Поэтому побитовая операция & (AND) не даст 0. 0000 1100 0000 1011 ========= 0000 1000

среда, 29 января 2020 г.

Объявление глобальной переменной внутри функции

#c #объявление #global_variables


Возможно ли объявить глобальную переменную в функции? Если да, то как?
    


Ответы

Ответ 1



Объявить глобальную переменную в функции, то есть переменную с внешним или внутренним связыванием, можно. А, вот, определить такую переменную нельзя, так как глобальная переменная - это переменная определенная вне функции. Например, #include int x; void f( int i ) { extern int x; x = i; } int main(void) { printf( "x = %d\n", x ); f( 10 ); printf( "x = %d\n", x ); return 0; } Вывод программы на консоль x = 0 x = 10 В этой программе в функции f объявляется глобальная переменная x extern int x;

Ответ 2



Объявить или определить? Возможно, я несколько путаюсь в терминологии, тогда пусть меня поправят, но если я правильно понимаю, то int x = 5; // Определение глобальной переменной int main(int argc, const char * argv[]) { extern int x; // Ее объявление, локальное для функции main() printf("%d\n",x); } Более того, они могут находиться в разных файлах.

Ответ 3



Нет, невозможно. Всё, что объявлено в функции, видно только в ней. Это основной принцип структурного программирования, называющийся «область видимости». Возможностью межпроцедурного доступа обладают только глобальные переменные. Крайне желательно использовать static-переменные, уровня .c-файла, но в крайнем случае допустимо использовать и действительно глобальные переменные, уровня всей программы. Разумеется, можно: Приписать к локальной переменной модификатор static, тогда она переживёт выход из функции в силу особого смысла этого ключевого слова при локальных переменных; Вернуть из функции указатель на эту переменную. Передать этот указатель внутрь другой функции, которая сохранит его внутри своей статической локальной переменной. И это будет работать, так как статические локальные переменные хранятся не на стеке. Однако лучше переместить эту переменную в структуру, как бы хранящую состояние между вызовами, и передавать эту структуру обеим функциям по указателю. Тогда: Первая функция будет записывать данные в экземпляр структуры (инициализировать её); Вторая функция будет просто читать и писать в эту структуру. Чтобы посторонние не могли вмешаться в массив, воспользуйтесь шаблоном проектирования «прозрачный указатель»: вынесите обе функции и определение структуры в отдельный .c-файл, а остальной программе предоставьте прототипы функций и краткого определения структуры, достаточного для объявления указателей на неё (typedef struct MyStruct;). Заметьте, в любом случае придётся что-то передавать в качестве параметра второй функции.

Ответ 4



Читаем АВТОРОВ языка: Объекты, объявляемые вне всех блоков на одном уровне с определениями функций, всегда статические, и они становятся глобальными для всей программы. "Язык программирования Си" - Брайан В. Керниган, Деннис М. Ритчи

вторник, 28 января 2020 г.

Вызов конструктора без аргументов?

#cpp #классы #конструктор #объявление


Здравствуйте. Решаю 2 задачу 9 главы из книги Р. Лафоре. Наткнулся на проблему.
Задача:
Вспомните пример STRCONV из главы 8. Класс String в этом примере имеет дефект: у
него нет защиты на тот случай, если его объекты будут инициализированы слишком длинной
строкой (константа size имеет значение 15). Например: String s = "Эта строка имеет
очень большую длину и мы можем быть уверены, что она не уместится в отведенный буфер,
что приведет к непредсказуемым последствиям."; будет причиной переполнения массива
str строкой s с непредсказуемыми последствиями вплоть до краха системы. Создадим класс
Pstring, производный от класса String, в котором предотвратим возможность переполнения
буфера при определении слишком длинной строковой константы. Новый конструктор производного
класса будет копировать в str только size-1 символов, если строка окажется слишком
длинной, и будет копировать строку полностью, если она будет иметь длину меньшую, чем
size. Напишите функцию main() программы для проверки ее работы со строками разной длины.

#include 
#include 

const int size = 15;

class String {
protected:
    char str[size];
public:
    String () {
        str[0] = '\x0';
    }
    String (const char* s) {
        strcpy(str, s);
        std::cout << "Скопировано: " << str << std::endl;
    }
    void display () const {
        std::cout << str << std::endl;
    }
    operator char* () {
        return str;
    }
};

class Pstring : public String {
public:
    Pstring (char* s) {
        if (strlen(s) >= size) {
            for (int index = 0; index < size - 1; ++index) {
                str[index] = s[index];
            }
            str[strlen(str)] = '\x0';
        }
        else {
            String(s); // почему не копируется строка, а вызывается конструктор без
аргументов?
            }
    }
};

int main () {
Pstring p1 = "pointers is evil!!!";
p1.display ();
Pstring p2 = "char * is bad";
p2.display();

return 0;
}


При определении переменных класса Pstring вначале вызывается конструктор по умолчанию
String::String(); до выполнения блока. Далее, идёт проверка на длину строки. Вопрос:
Почему, если строка меньшего размера, то  при вызове конструктора String(s); в ветви
else не копируется значение строки? Почему на выводе пустая строка.
    


Ответы

Ответ 1



У вас имеется несколько проблем с определением конструкторов в классе Pstring. Во-первых, желательно, чтобы он вел себя как класс String, что означает, что он также должен иметь конструктор по умолчанию. Pstring() {} Во-вторых, вами определенный конструктор с параметром полностью неверный. В этом предложении конструктора str[strlen(str)] = '\x0'; присутствуют сразу же две ошибки. Строка str не имеет пока еще завершающего нуля, поэтому вызов strlen( str ) ведет к неопределенному поведению. К тому же вы должны установить в 0 символ в позиции size - 1 В этом предложении String(s); У вас создается временный объект, который сразу же удаляется в конце предложения. Он никак не связан с тем объектом, в конструкторе которого вызывается это предложение. Имейте в виду, что когда вы имеете дело со строками, а тем более со стандартными C функциями, предназначенными для работы со строками, то лучше в качестве индекса в строках использовать тип size_t вместо типа int. Также в качестве параметра конструктора следует задать параметр с типом const char *, так как, как по крайней мере следует из вашей программы, конструктор может иметь дело со строковыми литералами в качестве аргументов, а строковые литералы в C++ в выражениях преобразуются (за редким исключением) к типу const char *. Далее длина члена класса str - это внутреннее свойство самого класса. Поэтому его длина не должна быть глобальной переменной. И, наконец, желательно, чтобы деструктор был виртуальным. Я бы определили данные классы следующим образом class String { protected: static const size_t size = 15; char str[size]; public: String () { str[0] = '\x0'; } String (const char* s) { std::strcpy(str, s); std::cout << "Скопировано: " << str << std::endl; } virtual ~String() {} void display () const { std::cout << str << std::endl; } operator const char* () const { return str; } }; class Pstring : public String { public: Pstring() {} Pstring ( const char* s) { if ( std::strlen(s) < size) { std::strcpy( str, s ); } else { size_t i = 0; for ( ; i < size - 1; ++i) { str[i] = s[index]; } str[i] = '\x0'; // или просто //std::strncpy( str, s, size - 1 ); // str[size-1] = '\0'; } } };

Ответ 2



У вас здесь String(s); просто объявление переменной s типа String. Естественно, для нее вызывается конструктор по умолчанию. Вы не можете просто взять и вызвать конструктор посреди другой функции, как обычную функцию. Перепишите свой конструктор как Pstring(const char* s) { if (s) { strncpy(str,s,size-1); str[size-1] = 0; } } При s==nullptr я ничего не делаю, потому что конструктор базового класса по умолчанию уже зануляет первый символ. Кстати, рекомендовал бы и в String делать соответствующую проверку.

пятница, 10 января 2020 г.

Какой тип данных используется для хранения строки?

#cpp #строки #cpp11 #объявление #строковый_литерал


Вот пример кода:

auto size = "Hello, world";
std::cout << sizeof(size);


В консоль выводится 4. Не могу понять, какой тип данных использует компилятор для
хранения этой строки?
    


Ответы

Ответ 1



Строковые литералы в C++ имеют типы константных символьных массивов. Данный литерал "Hello, world" имеет тип const char[13] Используемый в качестве выражения инициализации он неявно преобразуется к указателю на свой первый элемент, который имеет тип const char * Соответственно переменная size имеет тип const char *. Вы можете убедиться в этом, запустив на выполнение следующий фрагмент кода auto size = "Hello, world"; std::cout << typeid(size).name() << std::endl; Если бы вы написали так decltype(auto) size = "Hello, world"; std::cout << sizeof(size) << std::endl; то переменная size была бы ссылкой на строковый литерал и имела тип const char ( & )[13], а оператор sizeof вернул бы значение 13.

Ответ 2



С абстрактно-наивной точки зрения в данном случае приемлемыми типами для левой части инициализации являются const char (&)[13], [const] char[13] и const char *. Поведение, соответственно, будет зависеть о того, как правила языка описывают дедукцию типа для спецификатора auto в такой декларации. Эти правила совпадают со старыми-добрыми классическими правилами дедукции типов шаблонных аргументов для шаблонных функций, которые говорят, что в данном случае должен дедуцироваться тип "указатель" (а не тип "массив" или тип "ссылка"). Т.е. произойти в данном случае должно то же самое, что и примере template void foo(T) { std::cout << sizeof(T) << std::endl; } int main() { foo("Hello, world"); // специализируется как `foo` } При желании, как в вашем случае, так и в примере с функцией выше, вы можете направить дедукцию в другом направлении auto &size = "Hello, world"; и получить size типа const char (&)[13] и sizeof, возвращающий 13. Заметьте, что ни в первом, ни во втором случае не происходит создания копии исходного строкового литерала, т.е. никакого дополнительного "хранения строки" при этом не возникает. "Хранящаяся строка" в этих случаях - это лишь исходный строковый литерал и хранится он так, как он хранился с начала времен: как статический неизменяемый объект.

Ответ 3



Указатель на char sizeof(char *)

четверг, 2 января 2020 г.

Переменная = empty

#cpp #инициализация #объявление


Можно ли в c++ новосозданной переменной присвоить значение empty?
    


Ответы

Ответ 1



В C++ 17 имеется класс std::optional - обертка вокруг других объектов, который позволяет определять, есть ли значение у переменной или нет. Однако многие компиляторы еще не поддерживают этот класс. Поэтому для фундаментальных типов нет такого значения, как empty. Вы можете переменные этих типов инициализировать нулем, как это делает компилятор для статических переменных. Тем не менее нельзя сказать, что такие значения являются значением empty.

Ответ 2



В дополнение к ответу выше, имеется еще boost::optional и boost::tribool. Простенькая реализация аналога optional пилится самостоятельно очень быстро.

вторник, 31 декабря 2019 г.

Скобки () {} при инициализации внутри класса

#cpp #классы #конструктор #инициализация #объявление


Подскажите пожалуйста, а есть ли разница между использованием круглых и фигурных
скобок при инициализации конструктора внутри класса. Оба варианта работают корректно.

class A
{
public:
    char c;
    int d;

    A(char ch)
        :c(ch) {}
    A(char ch, int i)
        :c{ch}, d{i} {}
};

int main()
{
    A first = A('a');
    A second = A('b', 1);

    cout << "First: "  << first.c << endl;
    cout << "Second: " << second.c  << ' ' << second.d << endl;

    return 0;
}

    


Ответы

Ответ 1



Начнем с того, что имеются случаи, когда нельзя применять инициализацию одного из видов к членам данных класса. Например, если у вас имеется член класса, который является агрегатом (структурой или массивом), то возможные виды инициализации ограничены. Рассмотрим несколько примеров. В данном примере внутри структуры A объявляется агрегатный член данных B, имеющий тип структуры. Тогда данное объявление конструктора будет некорректныым struct A { A(int x) : b( x ) {} struct B { int x; } b; }; int main() { A a( 10 ); } Компилятор выдаст сообщение об ошибке, говорящее о том, что он не может преобразовать объект типа int в объект типа struct B. Однако если вы замените круглые скобки на фигурные, struct A { A(int x) : b{ x } {} struct B { int x; } b; }; int main() { A a( 10 ); } то код будет успешно компилироваться, так как объект b будет инициализирован как агрегат. Теперь если в конструкторе заменить тип у параметра с int на A::B, то ситуация изменится. Данная программа будет успешно компилироваться struct A { struct B; A(B x) : b( x ) {} struct B { int x; } b; }; int main() { A::B b = { 10 }; A a( b ); } так как структуры, которые являются агрегатами, имеют конструктор копирования, создаваемый компилятором неявно. Иная ситуация складывается, когда членом данных, который представляет собой агрегат, является массив. Как и в случае со структурой, данная программа не будет компилироваться struct A { struct B; A(int x) : b( x ) {} int b[1]; }; int main() { int b = 10; A a( b ); } так как нет преобразования из целочисленного типа в массив. Однако если заменить параметр на массив, не важно, является он ссылкой или нет, как показано ниже struct A { struct B; A(int x[1]) : b( x ) {} int b[1]; }; int main() { int b[1] = { 10 }; A a( b ); } или struct A { struct B; A(int ( &x )[1]) : b( x ) {} int b[1]; }; int main() { int b[1] = { 10 }; A a( b ); } то программа не будет компилироваться, так как в первом случае нет преобразования из указателя в массив, а во втором случае, когда параметр объявлен как ссылка, массивы не имеют конструктора копирования. Для инициализации члена данных, который является массивом, можно использовать следующую запись struct A { struct B; A(int x) : b{ x } {} int b[1]; }; int main() { int b = 10; A a( b ); } Данная программа успешно скомпилируется. Однако вы не можете запись с фигурными скобками заключить еще в круглые собкки, как показано ниже struct A { struct B; A(int x) : b({ x }) {} int b[1]; }; int main() { int b = 10; A a( b ); } Компилятор выдаст сообщение об ошибке, так как, опять-таки, для массивов нет конструктора копирования. Однако для структур такая запись инициализации будет успешно воспринята компилятором, так как структуры, как агрегаты, имеют неявно объявленный компилятором конструктор копирования. struct A { A(int x) : b({ x }) {} struct B { int x; } b; }; int main() { int b = 10; A a( b ); } Данная программа успешно скомпилируется. Для арифметических типов инициализация с фигурными скобками не разрешает "сужение" значения, то есть использовать в качестве инициализатора значение, которое потенциально не может разместиться в инициализируемом объекте. Поэтому следующая программа не будет компилироваться struct A { A(int x) : b{ x } {} short b; }; int main() { int b = 10; A a( b ); } Причиной ошибки будет то, что объект типа short не в состоянии разместить все значения объекта типа int, то есть будет иметь место "сужение" инициализирующего значения. Однако если заменить фигурные скобки на круглые, то программа успешно скомпилируется struct A { A(int x) : b( x ) {} short b; }; int main() { int b = 10; A a( b ); } Когда инициализируемый член класса является определенный пользователем тип, то в дело вступают конструкторы, которые имеют параметр типа std::initializer_list Например, в приведенной ниже программе, когда нет такого конструктора, можно инициализировать член класса как b( x ), или как b{ x }, или даже как b( { x } ) struct A { A(int x) : b({ x }) {} struct B { B(int x) { std::cout << "B( int )" << std::endl; } } b; }; int main() { int b = 10; A a(b); } Однако если в классе присутствует конструктор с параметром типа std::initializer list, то для инициализаций вида b{ x } и b( { x } ) будет вызван именно он. А для инициализации вида b( x ) будет вызван другой конструктор. Например, для этой программы struct A { A(int x) : b( x ) {} struct B { B(int x) { std::cout << "B( int )" << std::endl; } B(std::initializer_list) { std::cout << "B( std::initializer_list )" << std::endl; } } b; }; int main() { int b = 10; A a(b); } будет выведено сообзение B( int ) А для этой программы struct A { A(int x) : b{ x } {} struct B { B(int x) { std::cout << "B( int )" << std::endl; } B(std::initializer_list) { std::cout << "B( std::initializer_list )" << std::endl; } } b; }; int main() { int b = 10; A a(b); } будет выведено сообщение B( std::initializer_list ) Вообще, эта тема достаточно обширная. О некоторых причудах инициализации я написал на своем сайте в конце темы Шутка - ложь, но в ней намек, добрым молодцам урок.

Ответ 2



Ну, например, напишите int main(int argc, const char * argv[]) { A a(300); A b{300}; } Вам сразу сообщат, что второе объявление не годится - поскольку 300 в char не поместится. Инициализация с фигурными скобками не разрешает сужающее преобразование. Есть и другие отличия - например, при фигурных скобках компилятор будет искать конструктор от initializer_list. Например, если в вашем классе будет такое: class A { vector v; ... A(int x):v{x} {} A(int x):v(x) {} то последние две строчки означают совершенно разные вещи - первая - вектор с элементом x, а вторая - вектор с x элементами...

воскресенье, 8 декабря 2019 г.

C++, более одного объявления в выражении инициализации цикла for

#cpp #for #инициализация #объявление #выражение


Существует ли в C++ способ объявить в выражении инициализации цикла for несколько
переменных разного типа?

Например:

for (size_t s = 0, float f = 0.f; ; ) {}

    


Ответы

Ответ 1



Начиная с С++17 у вас есть вариант с structured binding for (auto [s, f] = std::make_tuple(0, 0.0); ; ) { // Работаем с `s` и `f` } А до появления structured binding были лишь трюки вроде for (struct { size_t s = 0; float f = 0.f; } sf; ; ) { // Работаем с `sf.s` и `sf.f` } или for (struct { size_t s; float f; } sf = { 0 }; ; ) { // Работаем с `sf.s` и `sf.f` } Такой трюк не используется особенно широко, но тем не менее доступен со времен С.

среда, 4 декабря 2019 г.

Что означает данная запись

#cpp #массивы #указатели #объявление


#include 
using namespace std;


int main()
{
    int b[10];
    int ( *a )[ 10 ];       // эта
    a = b;                  // error
    a[ 0 ] = new int;       // error
    a = new int;            // error

}


Пытаюсь методом подбора понять что это и как это используется.
    


Ответы

Ответ 1



Чтобы проще было понять запись int ( *a )[ 10 ]; можно ввести определение алиаса для массива typedef int T[10]; T *a; то есть объекты, которые может адресовать указатель, представляют собой целочисленные массивы из десяти элементов. Если вы хотите, например, чтобы этот указатель адресовал такой массив, вы можете написать typedef int T[10]; T b; T *a; a = &b; Тогда выражение *a или a[0] представляет собой массив b Например, вы можете написать typedef int T[10]; T b; T *a; a = &b; for ( int i = 0; i < 10; i++ ) a[0][i] = i; Если хотите динамически выделить массив, то вам следует записать typedef int T[10]; T *a; a = new T[1]; Это эквивалентно следующему фрагменту кода int ( *a )[10]; a = new int[1][10]; То есть такой указатель используется обычно при работе с двумерными массивами. Например, #include int main() { const size_t M = 2; const size_t N = 10; int b[M][N] = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 } }; for ( int ( *a )[N] = b; a != b + M; ++a ) { for ( int x : *a ) std::cout << x << ' '; std::cout << std::endl; } } Вывод на консоль 0 1 2 3 4 5 6 7 8 9 9 8 7 6 5 4 3 2 1 0

Ответ 2



Формально это - указатель на массив из 10 int. Так что присваивать нужно как a = &b; Если хотите использовать для присваивания элемента - то (*a)[0] = 12; // Присваивание b[0] = 12 Если же вы имеете в виду массив массивов по 10 элементов, то a[0][3] = 15; // Присваивание третьему элементу первого массива // по адресу a значение 15 - то же, что и b[3] = 15; Ну, а последнее... например, так: using arr = int[10]; a = new arr[1];

пятница, 29 ноября 2019 г.

Анонимные классы в c++

#c++ #классы #c++11 #объявление


Этот код вполне себе нормально компилируется.

class 
{ 
   public:
   //...
} anonymous;


Зачем и для каких целей применяются неименованные классы?
    


Ответы

Ответ 1



Для начала хочу обратить ваше внимание, что в C++ нет анонимных классов с ключевыми словами class или struct. Вы путаете понятия безымянного класса и анонимного класса. Эти понятия различны и имеют разный семантический смысл. В своем вопросе вы ведете речь именно про безымянные классы. Определения именованных классов определяют именованные типы. Используя это имя вы явно можете объявлять члены такого класса, в объявлении которых присутствует спецификатор типа класса, объекты этого типа или, например, параметры функций. Кроме того, если имя имеет внешнее связывание, то оно будет означать одну и ту же сущность в разных единицах компиляции, если включить объявление этого имени в эти единицы компиляции. В безымянном классе невозможно, например, объявить конструкторы, деструктор, операторы, которые имеют тип возвращаемого значения или типы параметров, включающие в себя имя класса. Вы также, например, не можете объявлять члены данных класса, которые являются указателями или ссылками на объекты класса. В безымянном классе не разрешается объявлять статические члены данных класса. Из стандарта C++ (9.4.2 Static data members) 4 [ Note: There shall be exactly one definition of a static data member that is odr-used (3.2) in a program; no diagnostic is required. —end note ] Unnamed classes and classes contained directly or indirectly within unnamed classes shall not contain static data members. Например, как объявить узел списка, если вы не можете в определении этого узла объявить член данных, являющийся указателем на следующий или предыдущий узел? Так что возможности безымянных классов очень ограничены. Безымянные классы обычно используются как вложенные классы других классов или как локальные классы, когда введенный таким образом безымянный тип класса не требуется за пределами той области видимости, где этот безымянный класс определен.

Ответ 2



Эта возможность оставлена ради совместимости со старым (C без плюсов кодом). В C безымянные и именованные структуры почти равноценны, поэтому в большом объеме кода применяется: tyedef struct { int a; int b; } my_type; (чтобы можно было писать my_type вместо struct my_type в точке использования типа. В C++ безымянные классы не имеют практического применения.

среда, 27 ноября 2019 г.

Что означает const*?

#c++ #c #указатели #объявление


Именно со звездой - демонические конструкции могут принимать вид 

const char* const* blah_blah, 

char const* blah 

int const* const integer, 

int* const* const и так далее в самых разных комбинациях. Как расшифровать сии начертания
и зачем вообще может кому-то понадобиться оборачиваться в const, будто шелкопряду?
    


Ответы

Ответ 1



Такие записи проще всего рассматривать справа налево. Например, Данное объявление const char * const * blah_blah; объявляет указатель с именем blah_blah, который указывает на константный указатель (так как квалификатор const предшествует *, если смотреть справа налево), который указывает на константный объект типа char. Вот пример простой программы, который облегчает понимание этого объявления. #include int main(void) { const char * const * blah_blah; const char *p = "Hello"; blah_blah = &p; puts( *blah_blah ); } На консоль будет выведено Hello Можно ввести typedef имена для наглядности typedef const char CONST_CHAR_TYPE; typedef CONST_CHAR_TYPE *POINTER_TO_CONST_CHAR_TYPE; const POINTER_TO_CONST_CHAR_TYPE *blah_blah; в этом случае переменная blah_blah является указателем на константный указатель на константный объект типа char. Я думаю будет легче понять эти объявления, если следовать грамматике определения указателя. Указатель в стандарте C определяется следующим образом pointer: * type-qualifier-listopt * type-qualifier-listopt pointer то есть за знаком * может следовать список квалификации, который к нему относится. Поэтому предыдущее объявление можно записать как const char ( * const ( *blah_blah ) ); Можно сделать и сам указатель blah_blah константным. Но тогда он должен быть явно инициализирован при объявлении, если имеет автоматическую продолжительность памяти. Например const char ( * const ( * const blah_blah ) ) = /* некоторое значение */; или const char * const * const blah_blah = /* некоторое значение */; Вот демонстрационная программа, в которой сам указатель blah_blah также объявлен как константный. #include int main(void) { const char *p = "Hello"; const char ( * const ( * const blah_blah ) ) = &p; puts( *blah_blah ); } Объявление этого указателя читается как константный указатель на константный указатель на константный объект типа char. В объявления функций, имеющих прототип, можно опускать идентификатор параметра. Ниже представлена такая программа #include void display( const char ( * const ( * const ) ) ); void display( const char * const * const ); void display( const char ( * const ( * ) ) ); void display( const char * const * ); int main(void) { const char *p = "Hello"; display( &p ); } void display( const char * const * const blah_blah ) { puts( *blah_blah ); } Так как объявлений одной и той же функции (без ее определения) может быть несколько, то все выше указанные функции объявляют одну и ту же функцию. Так называемый квалификатор верхнего уровня, который относится непосредственно к параметру, можно убрать из объявления функции. То есть (еще один пример) эти пары функций объявляют одни и те же функции void f( const int x ); void f( int x ); и void g( int * const p ); void g( int * p );

Ответ 2



В языках С и С++ нередко используется [неформальная/полуформальная] концепция пути доступа (access path) к объекту. А именно: указатель (или ссылка) на объект является путем доступа к этому объекту. Пути доступа бывают константные и неконстантные. Через константные пути доступа объект модифицировать нельзя, через неконстантные - можно (при условии, что сам объект является модифицируемым). К одному и тому же объекту могу вести как константные так и неконстантные пути доступа. int a = 42; int *pa = &a; const int *ca = &a; // `*pa` - неконстантный путь доступа к `a` // `*ca` - константный путь доступа к `a` Приведенные вами примеры использования модификатора const на более глубоких уровнях вложенности в объявлениях указателей (т.е. слева от *) как раз управляют константностью пути доступа, то есть разрешают или запрещают модификацию указуемого объекта через данный путь доступа. (Из ряда вот выходит только ваш последний пример, где крайний правый const задает константность самого указателя integer, а не пути доступа к указуемому объекту.) Если один и то же объект виден вам как через константный, так и через неконстантный путь доступа, то модификация этого объекта, сделанная через неконстантный путь доступа, будет тут же видна через константный путь доступа int a = 42; int *pa = &a; const int *ca = &a; *pa = 5; // Теперь значение `*ca` равно 5 То есть ваши модификаторы const не говорят о том, что указуемый объект не может изменяться. Они лишь не дают вам модифицировать указуемый объект именно через этот путь доступа. Таким образом управление константностью пути доступа является в основном средством самодисциплины. Однако в коде, с самого начала грамотно и активно использующем константность объектов, обойтись без использования этого средства невозможно. Правила константной корректности языка С и С++ позволяют вам свободно превращать неконстантные пути доступа в константные int a = 42; int *pa = &a; // `*pa` - неконстантный путь доступа к `a` const int *ca = pa; // `*ca` - константный путь доступа к `a` но обратное превращение невозможно. (Путем использования const_cast или C-style cast можно "грубой силой" превращать константные пути доступа в неконстантные, но это уже другая история.) Можно дополнительно заметить, что правила константной корректности языков С и С++ несколько отличаются, что приводит к ряду сохраняющихся по сей день "нелогичностей" в языке С, давно устраненных в языке С++ int **p = 0; const int *const *ccp = p; // Ошибка в С, разрешено в С++ int a[10] = { 0 }; const int (*сpa)[10] = &a; // Ошибка в С, разрешено в С++ Это временами мешает полноценно пользоваться константными путями доступа в С.

Ответ 3



Ну смотрите, например: int j; // Переменная типа int, в нее можно писать, что угодно. int * pi; // Указатель на переменную int, в которую можно писать: *pi = 5; const int * cpi; // Указатель на константную переменную int, // которую можно только читать: о = *cpi; // Сам указатель можно менять: cpi = &j; const int * const ccpi; // Константный указатель на константную переменную. // И переменную, на которую он указывает, можно только читать, // и сам указатель нельзя изменять... Так более-менее понятно? Более сложные варианты могут включать, например, константный указатель на вот такой константный указатель, как ccpi выше :)

Зачем typedef объвлять с одним и тем же типом

#c #структуры #объявление #typedef


Наверно какие-то C-шные ухищрения. Навроде их классов. Вроде бы и объявление тут
уже есть. Может поэтому? Вот такая строка например (из OpenCV):

typedef struct CvFileStorage    CvFileStorage;


Зачем же так писать? Не нашел никак, сходу, ответ на это. Когда-то (несколько лет
назад), помню что читал об этом. Тогда чистый C был в моде наверное, а сейчас такое
позабыто получается. Еще подобное видел в коде от Windows CE6 и др. Это запомнил:),
а для чего нужно не помню никак. Если еще какие-то доп. куски кода надо привести, то
скажите. Эта строка стоит перед структурой, которая у себя где-то в поле использует
тип из этого typedef-а. Но зачем так писать, а не просто объявить его? Спасибо.
    


Ответы

Ответ 1



Имеется по крайней мере две веские причины объявить этот typedef. typedef struct CvFileStorage CvFileStorage; Первая причина заключается в том, что в C программах вы должны указывать ключевые слова struct или enum перед именем структуры или перечисления. Это выглядит обременительно при вводе кода. Очень часто программисты забывают указать эти слова, что приводит к появлению ошибки компиляции. Поэтому этот typedef упрощает жизнь программистам, позволяя им не писать эти ключевые слова перед именем структуры или перечисления. Вторая причина состоит в том, что имена структур и другие идентификаторы находятся в различных пространствах имен. Поэтому одно и то же имя можно использовать для объявления структуры и обычной переменной. Например, следующий фрагмент кода является корректным struct CvFileStorage { //... }; int CvFileStorage; В этом фрагменте кода объявляется структура с именем CvFileStorage и переменная типа int с тем же самым именем. Эти объявления не конфликтует друг с другом, так как, как уже было написано, перед именем структуры обязательно должно следовать ключевое слово struct . В C вы можете записать, к примеру struct CvFileStorage { int CvFileStorage; } CvFileStorage; Это объявление корректно, так как эти три совпадающих идентификаторs находятся в различных пространствах имен. Однако это может вводить в заблуждение читающих код, так как если программист по ошибке опустит ключевое слово struct перед именем структуры, то может оказаться, что код по-прежнему с точки зрения синтаксиса языка будет корректным, хотя на самом деле имелась в виду структура, а не переменная с таким же именем. Например, в данном выражении программист по невнимательности забыл указать ключевое слово struct, и тем не менее получил корректное выражение, так как имеется переменная с таким же именем sizeof( CvFileStorage ) Чтобы избежать такой путаницы также целесообразно резервировать это имя без ключевого слова struct для имени структуры, используя typedef.. В С++ ключевое слово struct можно опускать при обращении к структуре. Тогда возникает вопрос: а как быть с тем, что в C можно объявлять переменную или функцию с таким же имеенем как имя структуры? Этот вопрос решается следующим образом: имя переменной или имя функции скрывает объявление структуры с тем же самым именем. Поэтому при обращении к структуре надо указывать уточненное имя. Например, struct CvFileStorage { //... }; void CvFileStorage(); В этом фрагменте кода объявление функции скрывает объявление одноименной структуры. Поэтому если, например, вы хотите объявить объект этой структуры, то надо будет указывать уточненное имя структуры. struct CvFileStorage obj; Или, например, можно написать такие объявления struct CvFileStorage { //... }; void CvFileStorage( struct CvFileStorage ); Эти имена не будут конфликтовать друг с другом, так как для структуры используется ее уточненное имя.

Ответ 2



В с++ если нужно объявить переменную типа структуры, нужно просто написать имя типа и переменную. В чистом си это не так. И нужно всегда писать struct. Это все потому, что типы для структур и остальные типы как бы находятся в разных областях видимости. Но так как программисты существа ленивые, то лучше один наз написать typedef struct CvFileStorage CvFileStorage; и потом не задумываться, почему оно не компилируется (потому что забыли struct) или почему оно работает как то странно (потому что забыли struct, а кто то объявил свой тип не структуру с таким же именем). Но часто пишут ещё интереснее: typedef struct { // тут объявление структры } struct_name; В этом случае создается сразу все так, как привычно в обычном с++.

Ответ 3



Без этого typedef при любом упоминании структуры CvFileStorage надо писать полностью: struct CvFileStorage cv; void func(struct CvFileStorage* cv); и так далее. При наличии объявления typedef слово struct можно выбрасывать: CvFileStorage cv; void func(CvFileStorage* cv);

вторник, 2 апреля 2019 г.

Спецификатор noexcept

class Math { public: static bool __fastcall IsPowerOfTwo( int x ) noexcept; };
__forceinline bool Math::IsPowerOfTwo( int x ) { // хз что делает :D return ( x & ( x - 1 ) ) == 0 && x > 0; }
noexcept надо тоже писать в определении?


Ответ

поясню про выражение
x & (x-1) == 0 && x > 0
оно проверяет положительное число x на степень двойки
тоесть это числа вида x = 2^i, где i позиция единственного единичного бита
в двоичном представлении имеют следующий вид:
0000...100000 = 2^i = x
а числа x - 1 имеют вид
0000...011111 = 1*2^i-1 + 1*2^i-2 + ... + 1*2^0 = x-1
тоесть все разряды начиная с i-1 ого единичные,
отсюда если побитово применить AND тоесть x & (x-1) получим 0 что означает что, число x это степень двойки, в противном случае, соотвественно не является степенью двойки

пятница, 29 марта 2019 г.

Объявление глобальной переменной внутри функции

Возможно ли объявить глобальную переменную в функции? Если да, то как?


Ответ

Объявить глобальную переменную в функции, то есть переменную с внешним или внутренним связыванием, можно. А, вот, определить такую переменную нельзя, так как глобальная переменная - это переменная определенная вне функции.
Например,
#include
int x;
void f( int i ) { extern int x; x = i; }
int main(void) { printf( "x = %d
", x ); f( 10 ); printf( "x = %d
", x );
return 0; }
Вывод программы на консоль
x = 0 x = 10
В этой программе в функции f объявляется глобальная переменная x
extern int x;

четверг, 28 марта 2019 г.

Вызов конструктора без аргументов?

Здравствуйте. Решаю 2 задачу 9 главы из книги Р. Лафоре. Наткнулся на проблему. Задача: Вспомните пример STRCONV из главы 8. Класс String в этом примере имеет дефект: у него нет защиты на тот случай, если его объекты будут инициализированы слишком длинной строкой (константа size имеет значение 15). Например: String s = "Эта строка имеет очень большую длину и мы можем быть уверены, что она не уместится в отведенный буфер, что приведет к непредсказуемым последствиям."; будет причиной переполнения массива str строкой s с непредсказуемыми последствиями вплоть до краха системы. Создадим класс Pstring, производный от класса String, в котором предотвратим возможность переполнения буфера при определении слишком длинной строковой константы. Новый конструктор производного класса будет копировать в str только size-1 символов, если строка окажется слишком длинной, и будет копировать строку полностью, если она будет иметь длину меньшую, чем size. Напишите функцию main() программы для проверки ее работы со строками разной длины.
#include #include
const int size = 15;
class String { protected: char str[size]; public: String () { str[0] = '\x0'; } String (const char* s) { strcpy(str, s); std::cout << "Скопировано: " << str << std::endl; } void display () const { std::cout << str << std::endl; } operator char* () { return str; } };
class Pstring : public String { public: Pstring (char* s) { if (strlen(s) >= size) { for (int index = 0; index < size - 1; ++index) { str[index] = s[index]; } str[strlen(str)] = '\x0'; } else { String(s); // почему не копируется строка, а вызывается конструктор без аргументов? } } };
int main () { Pstring p1 = "pointers is evil!!!"; p1.display (); Pstring p2 = "char * is bad"; p2.display();
return 0; }
При определении переменных класса Pstring вначале вызывается конструктор по умолчанию String::String(); до выполнения блока. Далее, идёт проверка на длину строки. Вопрос: Почему, если строка меньшего размера, то при вызове конструктора String(s); в ветви else не копируется значение строки? Почему на выводе пустая строка.


Ответ

У вас имеется несколько проблем с определением конструкторов в классе Pstring. Во-первых, желательно, чтобы он вел себя как класс String, что означает, что он также должен иметь конструктор по умолчанию.
Pstring() {}
Во-вторых, вами определенный конструктор с параметром полностью неверный.
В этом предложении конструктора
str[strlen(str)] = '\x0';
присутствуют сразу же две ошибки. Строка str не имеет пока еще завершающего нуля, поэтому вызов strlen( str ) ведет к неопределенному поведению. К тому же вы должны установить в 0 символ в позиции size - 1
В этом предложении
String(s);
У вас создается временный объект, который сразу же удаляется в конце предложения. Он никак не связан с тем объектом, в конструкторе которого вызывается это предложение.
Имейте в виду, что когда вы имеете дело со строками, а тем более со стандартными C функциями, предназначенными для работы со строками, то лучше в качестве индекса в строках использовать тип size_t вместо типа int
Также в качестве параметра конструктора следует задать параметр с типом const char *, так как, как по крайней мере следует из вашей программы, конструктор может иметь дело со строковыми литералами в качестве аргументов, а строковые литералы в C++ в выражениях преобразуются (за редким исключением) к типу const char *
Далее длина члена класса str - это внутреннее свойство самого класса. Поэтому его длина не должна быть глобальной переменной.
И, наконец, желательно, чтобы деструктор был виртуальным.
Я бы определили данные классы следующим образом
class String { protected: static const size_t size = 15; char str[size];
public: String () { str[0] = '\x0'; } String (const char* s) { std::strcpy(str, s); std::cout << "Скопировано: " << str << std::endl; } virtual ~String() {} void display () const { std::cout << str << std::endl; } operator const char* () const { return str; } };
class Pstring : public String { public: Pstring() {} Pstring ( const char* s) { if ( std::strlen(s) < size) { std::strcpy( str, s ); } else { size_t i = 0; for ( ; i < size - 1; ++i) { str[i] = s[index]; } str[i] = '\x0'; // или просто //std::strncpy( str, s, size - 1 ); // str[size-1] = '\0'; } } };

среда, 20 февраля 2019 г.

Какой тип данных используется для хранения строки?

Вот пример кода:
auto size = "Hello, world"; std::cout << sizeof(size);
В консоль выводится 4. Не могу понять, какой тип данных использует компилятор для хранения этой строки?


Ответ

Строковые литералы в C++ имеют типы константных символьных массивов. Данный литерал "Hello, world" имеет тип const char[13]
Используемый в качестве выражения инициализации он неявно преобразуется к указателю на свой первый элемент, который имеет тип const char *
Соответственно переменная size имеет тип const char *
Вы можете убедиться в этом, запустив на выполнение следующий фрагмент кода
auto size = "Hello, world"; std::cout << typeid(size).name() << std::endl;
Если бы вы написали так
decltype(auto) size = "Hello, world"; std::cout << sizeof(size) << std::endl;
то переменная size была бы ссылкой на строковый литерал и имела тип const char ( & )[13], а оператор sizeof вернул бы значение 13

понедельник, 17 декабря 2018 г.

Скобки () {} при инициализации внутри класса

Подскажите пожалуйста, а есть ли разница между использованием круглых и фигурных скобок при инициализации конструктора внутри класса. Оба варианта работают корректно.
class A { public: char c; int d;
A(char ch) :c(ch) {} A(char ch, int i) :c{ch}, d{i} {} };
int main() { A first = A('a'); A second = A('b', 1);
cout << "First: " << first.c << endl; cout << "Second: " << second.c << ' ' << second.d << endl;
return 0; }


Ответ

Начнем с того, что имеются случаи, когда нельзя применять инициализацию одного из видов к членам данных класса.
Например, если у вас имеется член класса, который является агрегатом (структурой или массивом), то возможные виды инициализации ограничены.
Рассмотрим несколько примеров.
В данном примере внутри структуры A объявляется агрегатный член данных B, имеющий тип структуры. Тогда данное объявление конструктора будет некорректныым
struct A { A(int x) : b( x ) {}
struct B { int x; } b; };
int main() { A a( 10 ); }
Компилятор выдаст сообщение об ошибке, говорящее о том, что он не может преобразовать объект типа int в объект типа struct B. Однако если вы замените круглые скобки на фигурные,
struct A { A(int x) : b{ x } {}
struct B { int x; } b; };
int main() { A a( 10 ); }
то код будет успешно компилироваться, так как объект b будет инициализирован как агрегат.
Теперь если в конструкторе заменить тип у параметра с int на A::B, то ситуация изменится.
Данная программа будет успешно компилироваться
struct A { struct B;
A(B x) : b( x ) {}
struct B { int x; } b; };
int main() { A::B b = { 10 }; A a( b ); }
так как структуры, которые являются агрегатами, имеют конструктор копирования, создаваемый компилятором неявно.
Иная ситуация складывается, когда членом данных, который представляет собой агрегат, является массив. Как и в случае со структурой, данная программа не будет компилироваться
struct A { struct B;
A(int x) : b( x ) {}
int b[1]; };
int main() { int b = 10; A a( b ); }
так как нет преобразования из целочисленного типа в массив.
Однако если заменить параметр на массив, не важно, является он ссылкой или нет, как показано ниже
struct A { struct B;
A(int x[1]) : b( x ) {}
int b[1]; };
int main() { int b[1] = { 10 }; A a( b ); }
или
struct A { struct B;
A(int ( &x )[1]) : b( x ) {}
int b[1]; };
int main() { int b[1] = { 10 }; A a( b ); }
то программа не будет компилироваться, так как в первом случае нет преобразования из указателя в массив, а во втором случае, когда параметр объявлен как ссылка, массивы не имеют конструктора копирования.
Для инициализации члена данных, который является массивом, можно использовать следующую запись
struct A { struct B;
A(int x) : b{ x } {}
int b[1]; };
int main() { int b = 10; A a( b ); }
Данная программа успешно скомпилируется. Однако вы не можете запись с фигурными скобками заключить еще в круглые собкки, как показано ниже
struct A { struct B;
A(int x) : b({ x }) {}
int b[1]; };
int main() { int b = 10; A a( b ); }
Компилятор выдаст сообщение об ошибке, так как, опять-таки, для массивов нет конструктора копирования. Однако для структур такая запись инициализации будет успешно воспринята компилятором, так как структуры, как агрегаты, имеют неявно объявленный компилятором конструктор копирования.
struct A { A(int x) : b({ x }) {}
struct B { int x; } b; };
int main() { int b = 10; A a( b ); }
Данная программа успешно скомпилируется.
Для арифметических типов инициализация с фигурными скобками не разрешает "сужение" значения, то есть использовать в качестве инициализатора значение, которое потенциально не может разместиться в инициализируемом объекте. Поэтому следующая программа не будет компилироваться
struct A { A(int x) : b{ x } {} short b; };
int main() { int b = 10; A a( b ); }
Причиной ошибки будет то, что объект типа short не в состоянии разместить все значения объекта типа int, то есть будет иметь место "сужение" инициализирующего значения.
Однако если заменить фигурные скобки на круглые, то программа успешно скомпилируется
struct A { A(int x) : b( x ) {} short b; };
int main() { int b = 10; A a( b ); }
Когда инициализируемый член класса является определенный пользователем тип, то в дело вступают конструкторы, которые имеют параметр типа std::initializer_list
Например, в приведенной ниже программе, когда нет такого конструктора, можно инициализировать член класса как b( x ), или как b{ x }, или даже как b( { x } )
struct A { A(int x) : b({ x }) {}
struct B { B(int x) { std::cout << "B( int )" << std::endl; } } b; };
int main() { int b = 10; A a(b); }
Однако если в классе присутствует конструктор с параметром типа std::initializer list, то для инициализаций вида b{ x } и b( { x } ) будет вызван именно он. А для инициализации вида b( x ) будет вызван другой конструктор.
Например, для этой программы
struct A { A(int x) : b( x ) {}
struct B { B(int x) { std::cout << "B( int )" << std::endl; } B(std::initializer_list) { std::cout << "B( std::initializer_list )" << std::endl; } } b; };
int main() { int b = 10; A a(b); }
будет выведено сообзение
B( int )
А для этой программы
struct A { A(int x) : b{ x } {}
struct B { B(int x) { std::cout << "B( int )" << std::endl; } B(std::initializer_list) { std::cout << "B( std::initializer_list )" << std::endl; } } b; };
int main() { int b = 10; A a(b); }
будет выведено сообщение
B( std::initializer_list )
Вообще, эта тема достаточно обширная.
О некоторых причудах инициализации я написал на своем сайте в конце темы Шутка - ложь, но в ней намек, добрым молодцам урок.

понедельник, 8 октября 2018 г.

Что означает данная запись

#include using namespace std;
int main() { int b[10]; int ( *a )[ 10 ]; // эта a = b; // error a[ 0 ] = new int; // error a = new int; // error
}
Пытаюсь методом подбора понять что это и как это используется


Ответ

Чтобы проще было понять запись
int ( *a )[ 10 ];
можно ввести определение алиаса для массива
typedef int T[10];
T *a;
то есть объекты, которые может адресовать указатель, представляют собой целочисленные массивы из десяти элементов.
Если вы хотите, например, чтобы этот указатель адресовал такой массив, вы можете написать
typedef int T[10];
T b;
T *a; a = &b;
Тогда выражение *a или a[0] представляет собой массив b
Например, вы можете написать
typedef int T[10];
T b;
T *a; a = &b;
for ( int i = 0; i < 10; i++ ) a[0][i] = i;
Если хотите динамически выделить массив, то вам следует записать
typedef int T[10];
T *a; a = new T[1];
Это эквивалентно следующему фрагменту кода
int ( *a )[10];
a = new int[1][10];
То есть такой указатель используется обычно при работе с двумерными массивами. Например,
#include
int main() { const size_t M = 2; const size_t N = 10;
int b[M][N] = { { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 } };
for ( int ( *a )[N] = b; a != b + M; ++a ) { for ( int x : *a ) std::cout << x << ' '; std::cout << std::endl; } }
Вывод на консоль
0 1 2 3 4 5 6 7 8 9 9 8 7 6 5 4 3 2 1 0

пятница, 5 октября 2018 г.

анонимные классы в c++

этот код вполне себе нормально компилируется.
class { public:
//...
} anonymous;
Зачем и для каких целей применяются не именованные классы?


Ответ

Для начала хочу обратить ваше внимание, что в C++ нет анонимных классов с ключевыми словами class или struct. Вы путаете понятия безымянного класса и анонимного класса. Эти понятия различны и имеют разный семантический смысл. В своем вопросе вы ведете речь именно про безымянные классы
Определения именованных классов определяют именованные типы. Используя это имя вы явно можете объявлять члены такого класса, в объявлении которых присутствует спецификатор типа класса, объекты этого типа или, например, параметры функций. Кроме того, если имя имеет внешнее связывание, то оно будет означать одну и ту же сущность в разных единицах компиляции, если включить объявление этого имени в эти единицы компиляции.
В безымянном классе невозможно, например, объявить конструкторы, деструктор, операторы, которые имеют тип возвращаемого значения или типы параметров, включающие в себя имя класса.
Вы также, например, не можете объявлять члены данных класса, которые являются указателями или ссылками на объекты класса.
В безымянном классе не разрешается объявлять статические члены данных класса.
Из стандарта C++ (9.4.2 Static data members)
4 [ Note: There shall be exactly one definition of a static data member that is odr-used (3.2) in a program; no diagnostic is required. —end note ] Unnamed classes and classes contained directly or indirectly within unnamed classes shall not contain static data members.
Например, как объявить узел списка, если вы не можете в определении этого узла объявить член данных, являющийся указателем на следующий или предыдущий узел?
Так что возможности безымянных классов очень ограничены.
Безымянные классы обычно используются как вложенные классы других классов или как локальные классы, когда введенный таким образом безымянный тип класса не требуется за пределами той области видимости, где этот безымянный класс определен.

среда, 3 октября 2018 г.

Что означает const*?

Именно со звездой - демонические конструкции могут принимать вид
const char* const* blah_blah,
char const* blah
int const* const integer,
int* const* const и так далее в самых разных комбинациях. Как расшифровать сии начертания и зачем вообще может кому-то понадобиться оборачиваться в const, будто шелкопряду?


Ответ

Такие записи проще всего рассматривать справа налево. Например,
Данное объявление
const char * const * blah_blah;
объявляет указатель с именем blah_blah, который указывает на константный указатель (так как квалификатор const предшествует *, если смотреть справа налево), который указывает на константный объект типа char.
Вот пример простой программы, который облегчает понимание этого объявления.
#include
int main(void) { const char * const * blah_blah; const char *p = "Hello"; blah_blah = &p; puts( *blah_blah ); }
На консоль будет выведено
Hello
Можно ввести typedef имена для наглядности
typedef const char CONST_CHAR_TYPE; typedef CONST_CHAR_TYPE *POINTER_TO_CONST_CHAR_TYPE;
const POINTER_TO_CONST_CHAR_TYPE *blah_blah;
в этом случае переменная blah_blah является указателем на константный указатель на константный объект типа char
Я думаю будет легче понять эти объявления, если следовать грамматике определения указателя.
Указатель в стандарте C определяется следующим образом
pointer: * type-qualifier-listopt * type-qualifier-listopt pointer
то есть за знаком * может следовать список квалификации, который к нему относится.
Поэтому предыдущее объявление можно записать как
const char ( * const ( *blah_blah ) );
Можно сделать и сам указатель blah_blah константным. Но тогда он должен быть явно инициализирован при объявлении, если имеет автоматическую продолжительность памяти. Например
const char ( * const ( * const blah_blah ) ) = /* некоторое значение */;
или
const char * const * const blah_blah = /* некоторое значение */;
Вот демонстрационная программа, в которой сам указатель blah_blah также объявлен как константный.
#include
int main(void) { const char *p = "Hello"; const char ( * const ( * const blah_blah ) ) = &p; puts( *blah_blah ); }
Объявление этого указателя читается как константный указатель на константный указатель на константный объект типа char
В объявления функций, имеющих прототип, можно опускать идентификатор параметра.
Ниже представлена такая программа
#include
void display( const char ( * const ( * const ) ) ); void display( const char * const * const ); void display( const char ( * const ( * ) ) ); void display( const char * const * );
int main(void) { const char *p = "Hello"; display( &p ); }
void display( const char * const * const blah_blah ) { puts( *blah_blah ); }
Так как объявлений одной и той же функции (без ее определения) может быть несколько, то все выше указанные функции объявляют одну и ту же функцию. Так называемый квалификатор верхнего уровня, который относится непосредственно к параметру, можно убрать из объявления функции. То есть (еще один пример) эти пары функций объявляют одни и те же функции
void f( const int x ); void f( int x );
и
void g( int * const p ); void g( int * p );