пятница, 2 декабря 2011 г.

Заметки о С++

[статья в разработке]

Арифметические операторы
высший,           низший   приоритеты
++  --  -  *  /  %  +  -
------  -  -------  ----
  1     2     3      4
        |
        V
     Унарный 
      минус





Операторы отношений
==  !=  >  <  >=  <=

Логические операторы
!  &&  ||

Приоритет операторов отношений и логических операторов
высший,                  низший   приоритеты
!  >  >=  <  <=  ==  !=  &&  ||
-  ------------  ------  ------
1       2          3       4

Операторы декремента и инкремента

бывают как префиксные, так и постфиксные.
Основное отличие:
постфиксная запись возвращает исходное (не изменённое) значение объекта;
префиксная запись возвращает новое(изменённое) значение объекта;
Пример:
int a = 1;
int b = a++;    // b == 1;
int c = ++b;    // c == 2;
где a++ - постфиксная форма записи, а ++a - префиксная форма записи инкремента.
С оператором декремента всё аналогично, за исключением того, что он не увеличивает, а уменьшает значение переменной.

Переменные

Переменные бывают константные и не константные

Константные переменные не могут быть изменены в рамках программы (Константная переменная может быть изменена только либо внешней программой, либо внешним устройством, и в этом случае также необходимо добавлять слово volatile)
Не константные переменные могут быть изменены в рамках программы.
int a = 5;         // НЕ константная переменная
const int b = 7;   // константная переменная
a = a + 1;         // допустимо
b = b + 1;         // будет ошибка

Указатели

Указатель - содержит адрес переменной, на которую он указывает.
Указатель может быть разыменован для получения значения переменной на которую он указывает.
int a = 5;    
int *b = &a;   // записать в указатель b адрес переменной a
int c = *b;    // разыменовать указатель b и записать значение в c

Указатели также могут быть константными и не константными

Константные указатели не могут изменить своего значения, т.е. они не могут начать указывать на другие данные.
Не константные указатели могут менять своё значение, т.е. они могут изменить адрес, на который они указывают.
int a = 1;
int *b = &a;        // объявить НЕ константный указатель b
int *const c = &a;  // объявить константный указатель c

int x = 10;
b = &x;             // изменить указатель b, теперь он указывает на x
c = &x;             // а здесь будет ошибка! 
              // Так как константный указатель не может быть изменён.
Сразу добавлю, что имя массива - это константный указатель, и поэтому нет возможности изменить его значение, чтобы он начал указывать на другие данные
int a[] = {1,2,3,4,5,6,7};        // объявить массив a,
                                  // где a - константный указатель.
int *b = a;                       // допустимо.
int c = 100;
int d[] = {10,20,30,40,50,60,70}; // объявить массив d
a = &c;                           // Ошибка!
a = d;                            // Тоже ошибка!

Указатели могут указывать как на константные данные, так и на не константные данные

Обратите внимание, что указатель на константные данные не может изменить эти данные, даже если в действительности он указывает на не константные данные.
int a = 1;
int *b = &a;        // указатель на НЕ константные данные
const int *c = &a;  // указатель на константные данные

*b = *b + 1;        // переменная a будет изменена
*c = *c + 1;        // а здесь будет ошибка!

Ещё один пример с указателями

Такая программа будет работать
#include <iostream>
using namespace std;
int main()
{
    int x = 5;
    int *p1 = &x;
    int **p2 = &p1;
    const int *const *const *const p3 = &p2;  // ok
    cout << "***p3 = " << ***p3 << endl;
    return 0;
}

Функции

Функции-члены и функции-не члены

Функции-член - получают указатель this на объект их вызвавший. Функции-не члены - НЕ получают указатель this.

Работа с функциями

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

Передача параметров

возможна несколькими способами:
  • по значению
  • по указателю
  • по ссылке
При передаче по значению в функцию копируются значения передаваемых переменных. (Изменения полученных значений не повлияет на изменение значения передаваемого объекта)
#include <iostream>
using namespace std;

// Передача по значению
int fun(int a)
{
    a = a + 1;
    return a;
}

int main()
{
    int x = 0;
    int y = fun(x);   // передача по значению
    cout << "x = " << x << endl;
    cout << "y = " << y << endl;
    return 0;
}
На выходе:
x = 0
y = 1
При передаче по указателю в функцию копируются не значения переменных, а указатели на них. (После операции разыменования, можно изменять значение переданного параметра)
#include <iostream>
using namespace std;

// Передача по указателю
int fun(int *a)
{
    *a = *a + 1;
    return *a;
}

int main()
{
    int x = 0;
    int y = fun(&x);  // & - возвращает адрес переменной
                      //     для указателя в функции
    cout << "x = " << x << endl;
    cout << "y = " << y << endl;
    return 0;
}
На выходе:
x = 1
y = 1
При передаче по ссылке в функцию напрямую передаётся адрес на переменную, с которой вызывается функция. (Это позволяет беспрепятственно изменять значение переданного объекта. Визуально работа с ссылкой ни чем не отличается от работы с обычной переменной (Помните: переменные-ссылки имеют ряд ограничений перед простыми переменными))
#include <iostream>
using namespace std;

// Передача по ссылке (обратите внимание на знак &)
int fun(int &a)
{
    a = a + 1;       // работа с переменной не отличается
    return a;        // от варианта с передачей по значению
}

int main()
{
    int x = 0;
    int y = fun(x);  // вызов функции с передачей по ссылке
                     // не отличается от вызова функции с
                     // передачей по значению.
    cout << "x = " << x << endl;
    cout << "y = " << y << endl;
    return 0;
}
На выходе:
x = 1
y = 1

Возвращение значения, указателя и ссылки из функции

ДОПИСАТЬ РАЗДЕЛ

Перегрузка функции

Сигнатура функции в С++ складывается из имени функции и типов, порядка и кол-ва её аргументов. Возвращаемое функцией значение не входит в сигнатуру функции. Поэтому следующую функцию нельзя перегрузить таким образом:
// Ошибка перегрузки!
int  fun(int *a) { return *a; }
int *fun(int *a) { return  a; }
ни даже таким способом:
// Ошибка перегрузки!
int    fun(int *a) { return *a; }
float *fun(int *a) { static float b=1.0; return &b; }
Но функция успешно перегрузится, если мы сменим тип входного аргумента:
// Правильная перегрузка
int  fun(int   *a) { return *a; }
int *fun(float *a) { static int b=1; return &b; }
И такая перегрузка тоже верна (изменён тип возвращаемого значения во второй функции):
// Правильная перегрузка
int fun(int   *a) { return *a; }
int fun(float *a) { static int b=1; return b; }

Неявная ошибка при перегрузке функций

Возможны ситуации, при которых компилятор позволяет перегрузить функцию, но ошибка возникает позднее, при попытке обратиться к функции. Например, следующие функции будут успешно перегружены компилятором:
#include <iostream>
using namespace std;

// Функция передачи по значению
int fun(int a)
{
    cout << "int fun(int a); " << a << endl;
    return a;
}
// Перегрузка функции с передачей по ссылке
int fun(int &a)
{
    cout << "int fun(int &a); " << a << endl;
    return a;
}
Но ошибка возникнет сразу, как только мы попытаемся передать в функцию переменную:
int main()
{
    int x = 1;
    cout << fun(x);   // Ошибка! Неоднозначность вызова функции
    return 0;
}
Ошибка возникает по причине неоднозначности какую именно функцию необходимо вызвать. Переменная может вернуть как свою ссылку, так и своё значение. Следующий код, в отличии от предыдущего примера, не вернёт ошибки и будет работать:
int main()
{
    cout << fun(2);   // Верно.  Передача по значению
    return 0;
}
Будет выведено:
int fun(int a); 2
Так как литерал не может вернуть на себя ссылку, компилятор однозначно выбирает функцию с передачей по значению. Но пример с передачей переменной в функцию может быть разрешён следующим способом вызова:
int main()
{
    cout << fun( (int)x );   // Верно.  Передача по значению
    return 0;
}
В связи с этим перегрузка функций таким образом не целесообразна - пропадает возможность передачи переменной по ссылке в функцию, а передача её по значению несколько усложняется в записи.

Способы объявления функции с передачей по указателю

На основе все описанного выше, подведём итог по всем способам объявления функции с одной переменной (! Проверить, можно ли переопределить все эти функции? )
int fun(int a);
int fun(int *a);
int fun(int &a);

int *fun(int a);
int *fun(int *a);
int *fun(int &a);

int &fun(int a);
int &fun(int *a);
int &fun(int &a);


int fun(const int a);
int fun(const int *a);
int fun(int *const a);
int fun(const int *const a);
int fun(const int &a);

int *fun(int a);
int *fun(const int *a);
int *fun(int *const a);
int *fun(const int *const a);
int *fun(int &a);

int &fun(int a);
int &fun(const int *a);
int &fun(int *const a);
int &fun(const int *const a);
int &fun(int &a);




const int fun(int a);
const int fun(int *a);
const int fun(int &a);

const int *fun(int a);
const int *fun(int *a);
const int *fun(int &a);

const int &fun(int a);
const int &fun(int *a);
const int &fun(int &a);


const int fun(const int a);
const int fun(const int *a);
const int fun(int *const a);
const int fun(const int *const a);
const int fun(const int &a);

const int *fun(int a);
const int *fun(const int *a);
const int *fun(int *const a);
const int *fun(const int *const a);
const int *fun(int &a);

const int &fun(int a);
const int &fun(const int *a);
const int &fun(int *const a);
const int &fun(const int *const a);
const int &fun(int &a);