Работая в IT сфере мне не один раз приходилось «наступать на грабли» в программировании. Наступить на них один раз не страшно, но когда это делаешь методически то всерьез начинаешь беспокоится о своем здоровье и карьерных перспективах…:) С определённого момента я стал записывать некоторые вещи для себя (как оказалось позже — не только для себя) в текстовый файл. На данный момент мне захотелось поделится с читателем своей подборкой… Да что темнить? Хочется не только поделиться с неблагодарным читателем:), но и в свою очередь, получить от него полезные советы!!! Учится ведь никогда не поздно?!

Странности использования квадратных скобок при индексации массива

Согласно стандарту:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
    const int NUM_OF_ELEMENTS = 5;
    int array[NUM_OF_ELEMENTS];
 
    int index = 4;
 
 
    // запись 1
    array[index] = 100;
    // запись 2
    index[array] = 100;
 
    return 0; 
}

Запись 1 равнозначна записи 2. Т. е. внутри скобок может быть сам индекс массива или же само название массива.

Что за конструктор копии?

Вызывается во время создания копии, например

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class C
{
public:
    C(const C &copy) {;};
 
public:
    C();
    ~C();
};
 
void func(C temp)
{
;
}
 
int main()
{
    C temp;
 
    func(temp);
 
    return 0;
}

Зачем делать приватным конструктор копии?

Данный финт не позволяет передавать инстанс класса по копии (например, как аргумент для метода)

1
2
3
4
5
6
7
8
9
class C
{
public:
    C(const C &notused) {;};
 
public:
    C();
    ~C();
};

Как правильно проверять на null?

Позволяет осуществлять проверку возвращаемого значения на null

new(nothrow)

Без nothrow проверять на null нельзя.
Тут можно только проверять методом try-catch.

Для чего нужны const методы и что такое mutable?

const функции (методы) не позволяют менять содержимое объекта, частью которого они являются. Для того чтобы иметь возможность изменить что-то из этого объекта, нужно это что-то объявить с идентификатором mutable.

Что делает explicit с конструктором класа?

explicit для конструктора позволяет использовать запись вида myclass ob(10). В то же самое время запись myclass ob = 10 является недопустимой.

Можно ли для одного и того же указателя одновременно использовать, например, new и free?

Нельзя смешивать new/delete с malloc/free.

Как проверить, нормально ли был удален участок памяти?

Удобно проверять нормально ли был удален участок выделенной ранее памяти следующим образом:

assert(_CrtCheckMemory());

Действует только из-под дебага!

int _CrtCheckMemory(void);

Возвращаемое значение:
В случае успешного завершения проверки возвращает значение TRUE. В противном случае возвращает значение FALSE.

Описание
Данная функция производит проверку целостности блока памяти в куче отладки (действует только в отладочной версии приложения). При обнаружении ошибок в целостности кучи отладки или в ее заголовке, а также записи информации за пределами выделенных буферов, функция _CrtCheckMemory выдает отладочное сообщение с указанием выявленной ошибки. Если символ _DEBUG не определен, вызов данной функции удаляется препроцессором. Поведением функции _CrtCheckMemory можно управлять, устанавливая различные поля во флаге _crtDbgFlag с использование функции _CrtSetDbgFlag. Установка флага _CRTDBG_CHECK_ALWAYS_DF приводит к тому, что функция _CrtCheckMemory вызывается при каждом запросе на выделение памяти. Хотя это и замедляет выполнение приложения, зато ускоряет процесс выявления ошибок. Сброс флага _CRTDBG_ALLOC_MEM_DF отменяет проверку кучи и заставляет функцию _CrtCheckMemory всегда возвращать значение TRUE. Поскольку данная функция возвращает логическое значение, ее вызов может быть помещен в макрос _ASSERT, что позволит быстро локализовать возникшие ошибки. Описание данной функции содержится в файле заголовка crtdbg.h.

Делить на 0 можно?!

Делить на 0.0 можно, если пользоваться числами с плавающей точкой. Кроме того, можно делить как на +0.0 так и на -0.0 и результаты будут разными.

Основы многозадачности (псевдомногопоточности).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include "stdafx.h"
 
// количество потоков (задач)
#define NUM_TASKS 2
 
// количество раз (только для теста. В реальной жизни там чаще всего стоит while(1))
#define NUM_TIMES 5
 
// выделяем масив указателей на функцию (неоходимо для потоков)
void (*task[NUM_TASKS])();
 
// тут необходимо установить приоритеты для каждого из потоков
int priorities[NUM_TASKS];
 
// первая задача
void task1()
{
    printf("task1...\n");
}
 
// вторая задача
void task2()
{
    printf("task2...\n");
}
 
// главная точка входа программы
int _tmain(int argc, _TCHAR* argv[])
{
    // указываем указатель на первую задачу-поток
    task[0] = task1;
    // указываем приоритет
    priorities[0] = 5;
 
    // указываем указатель на вторую задачу-поток
    task[1] = task2;
    // указываем приоритет
    priorities[1] = 2;
 
    // while(1)
    for(int t=0; t<NUM_TIMES; t++)
    {
        // для каждого потока (задачи)
        for(int i=0; i<NUM_TASKS; i++)
        {
            // повторяем неоходимое количество раз (указанное в приоритетах)
            for(int p=0; p<priorities[i]; p++)
                task[i]();
        }
    }
 
    getchar();
 
    return 0;
}

Еще чуть-чуть о нативной многопоточности (многозадачности).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <avr\io.h>
#include <avr\interrupt.h>
#include <avr\signal.h>
 
unsigned char current_task = 0;
 
float t0 = 0;
float t1 = 0;
 
void TimerInit()
{
cli();
 
   TCNT1 = 0xFF00;
 
   TIMSK |= (1 << TOIE1);   // Enable overflow interrupt
   sei();                   // Enable global interrupts
 
   TCCR1B |= (1 << CS00);   // Start timer at Fcpu
 
sei();
}
 
void IOInit()
{
    DDRB = 0xFF;
    PORTB = 0x00;
}
 
int main(void)
{
    TimerInit();
    IOInit();
 
    while(1)
    {
       while(current_task == 0)
       {
          t0++;
          PORTB ^= (1<<PB0);
       }
 
       while(current_task == 1)
       {
          t1++;
          PORTB ^= (1<<PB1);
       }
    }
}
 
ISR(TIMER1_OVF_vect)
{
    if( (++current_task) > 1 )
        current_task = 0;
 
    TCNT1 = 0xFF00;
}

P.S. Пример написан под компилятор gcc для процессора Atmega8.

Как использовать указатель на функцию?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void f1()
{
    printf("f1\n");
}
 
void f2()
{
    printf("f2\n");
}
 
// указатель на функцию
void (*p)();
 
int _tmain(int argc, _TCHAR* argv[])
{
    // присваивание адреса функции f1
    p = f1;
 
    // вызов функции f1
    p();
 
    // присваивание адреса функции f2
    p = f2;
 
    // вызов функции f2
    p();
 
    getchar();
 
    return 0;
}

Как сделать динамический массив указателей на функцию?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void f1()
{
    printf("f1\n");
}
 
int _tmain(int argc, _TCHAR* argv[])
{
    const int FUNC_COUNT = 10;
 
    typedef void (*pointer_on_function_t) (void);
 
    //pointer_on_function_t *fArray = (pointer_on_function_t *)malloc (sizeof (pointer_on_function_t *) * FUNC_COUNT);
    pointer_on_function_t *fArray = new pointer_on_function_t[FUNC_COUNT];
 
    // присваивание адреса функции f1
    fArray[0] = f1;
 
    // вызов функции f1
    fArray[0]();
 
    delete [] fArray;
 
    getchar();
 
    return 0;
}

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

1
2
3
4
5
6
void re(char *a, char *b)
{
    *a ^= *b;
    *b ^= *a;
    *a ^= *b;
}

Для чего нужен виртуальный деструктор?

Предисловие: «Основное правило: если у вас в классе присутствует хотя бы одна виртуальная функция, деструктор также следует сделать виртуальным. При этом не следует забывать, что деструктор по умолчанию виртуальным не будет, поэтому следует объявить его явно. Если этого не сделать, у вас в программе почти наверняка будут утечки памяти (memory leaks).» В следующем примере:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <stdio.h>
 
class IShape
{
public:
    virtual float GetArea() = 0;
 
    virtual ~IShape() 
    {
        printf("Shape Destructor called!\n");
    };
};
 
class Circle : public IShape
{
public:
    Circle() : r(0) 
    {
        ;
    };
 
    Circle(float r)
    {
        this->r = r;
    }
 
    float GetArea()
    {
        return 3.14*r*r;
    }
 
    ~Circle() 
    {
        printf("Circle Destructor called!\n");
    };
 
private:
    float r;
};
 
int main(int argc, char* argv[])
{
    IShape *shape = new Circle();
 
    delete shape;
 
    return 0;
}

класс IShape — это базовый класс. Circle унаследован от интерфейса и являет собой класс-наследник. В коде, представленном выше, создан указатель базового класса и ему присвоим указатель на объект, унаследованный от него:

IShape *shape = new Circle();

При удалении указателя

delete shape;

сначала вызывается деструктор наследника ~Circle(), а уже потом деструктор базового класса ~IShape().

Если бы деструктор базового класса был объявлен как:

1
2
3
~IShape()
{
}

то в этом случае вызвался бы лишь деструктор базового класса ~IShape().

Теперь представим, что деструктор наследника отвечает за освобождение занимаемой им памяти. Получается, могут возникнуть проблемы с утечкой памяти…

Как пользоваться функций с переменным числом аргументов?

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <stdarg.h> 
#include <stdio.h>
 
typedef double (*pFunction) (int num,...);
 
double sum(int num,...) 
{
    va_list valist; 
    double sum = 0.0; 
    int i; 
 
    /* initialize valist for num number of arguments */ 
    va_start(valist, num); 
 
    /* access all the arguments assigned to valist */ 
    for (i = 0; i < num; i++) 
    { 
       sum += va_arg(valist, int); 
    } 
    /* clean memory reserved for valist */ 
    va_end(valist); 
 
    return sum; 
} 
 
double average(int num,...) 
{
    va_list valist; 
    double sum = 0.0; 
    int i; 
 
    /* initialize valist for num number of arguments */ 
    va_start(valist, num); 
 
    /* access all the arguments assigned to valist */ 
    for (i = 0; i < num; i++) 
    { 
       sum += va_arg(valist, int); 
    } 
    /* clean memory reserved for valist */ 
    va_end(valist); 
 
    return sum/num; 
} 
 
int main() 
{ 
    pFunction fun = average;
 
    printf("Average of 2, 3, 4, 5 = %f\n", fun(4, 2,3,4,5)); 
    printf("Average of 5, 10, 15 = %f\n", fun(3, 5,10,15)); 
 
    fun = sum;
 
    printf("Sum of 2, 3, 4, 5 = %f\n", fun(4, 2,3,4,5)); 
    printf("Sum of 5, 10, 15 = %f\n", fun(3, 5,10,15)); 
 
    return 0;
}

Инициализация статической переменной, которая является приватным членом класса в заголовочном файле.

Имеем объявление класса в «.h» файле:

1
2
3
4
5
class foo
{
    private:
        static int i;
};

Для того, чтобы инициализировать статическую переменную нужно в файле имплементации «.cpp», «.c»… прописать следующее:

int foo::i = 0;

Если же нам необходимо инициализировать переменную в заголовочном файле, тогда можна использовать следующую запись (но только для продукции VisualStudio):

1
2
3
4
5
6
7
class foo
{
    private:
        static int i;
};
 
__declspec(selectany) int foo::i = 0;

P.S. Для GNU компиляторов это будет выглядеть следующим образом:

__attribute__ ((selectany)) int foo::i = 0;

Использование const идентификатора для переменных и указателей.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "stdafx.h"
 
int _tmain(int argc, _TCHAR* argv[])
{
// 1) константная переменная
    const char const_data_variable = 'a';
 
    // cann't
    // const_data_variable = 'b';
 
// 2) константный указатель
    char * const const_pointer_value = new char();
 
    // can
    *const_pointer_value = 'b';
 
    // cann't
    // const_pointer_value = new char();
 
    delete const_pointer_value;
 
// 3) константное значение для укзателя
    const char * const_pointer_data_value = new char();
 
    // cann't
    // *const_pointer_data_value = 'b';
 
    delete const_pointer_data_value;
 
    // can
    const_pointer_data_value = new char();
 
    delete const_pointer_data_value;
 
// 4) константный указатель и его данные
    const char * const const_pointer_value_and_data_value = new char();
 
    // cann't
    // *const_pointer_value_and_data_value = 'b';
 
    // cann't
    // const_pointer_value_and_data_value = new char();
 
    delete const_pointer_value_and_data_value;
 
    return 0;

Как использовать const в целях «перегрузки» метода?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include "stdafx.h"
 
class ConstOverloading
{
public:
    ConstOverloading(){};
    ~ConstOverloading(){};
 
    void PrintMethod()
    {
        printf("PrintFromRegularMethod\n");
    }
 
    void PrintMethod() const
    {
        printf("PrintFromConstOverloadedMethod\n");
    }
};
 
int _tmain(int argc, _TCHAR* argv[])
{
    ConstOverloading coRegular;
 
    coRegular.PrintMethod();
 
    ConstOverloading const coConstOverloaded;
 
    coConstOverloaded.PrintMethod();
 
    getchar();
 
    return 0;
}

Имеем 2 метода с одинаковым названием, но с разным наполнением (реализацией)… Пользуясь данной техникой можно вызывать ту или иную функцию.