Итак, в
уроке №2 мы разобрали какие бывают переменные и как они хранят свое содержимое.
Всё в мире байты - это как эпиграф.
А байты хранятся в памяти, в виде цепочки - друг-за-другом, чтобы узнать где именно - можно получить указатель на адрес переменной.
Вообще разница между адресами и указателями очень тонка, и отличия возникают все же больше в C++, нежели в простом C. Но об этом позже
В языке C, получить адрес переменной - знак амперсанда -
&.
Допустим:
int a=5;int *b=&a;
Переменная b - на самом деле указатель на целочисленный тип (int). Чтобы точнее это понять стоит писать
int* b, но все пишут (и я тоже) как
int *b, потому что в пробелы и их отсутствие - это не суть.
Так вот, переменная b указывает на адрес памяти переменной a или если кратко - b указатель на a.
Для понимания можно выполнить следующий код
*b = 6;
, звездочка перед b означает действие разыменование, и реально
именно переменная a станет равной 6!Если же выполнить конструкцию просто
b = 6;
то в результате переменная
b будет содержать на область памяти с смещением 6. Само по себе это ничего не даст (и ошибок тоже), но если после этого сделать
*b = 6; 99% что ваша программа закроется с ошибкой. Потому что вы меняете значения памяти не глядя, а там может располагаться исполняемые код или другие данные.
Это тоже одна из плавающих ошибок, которую трудно найти.
Функции
Функции это самостоятельный код, который объеденен в группу - функцию.
Функция может принимать любое количество, любых типов переменных и возвращать любой тип переменных. А может и не возвращать - в некоторых языках делят на функции и процедуры - мол процедуры не возвращают ничего, в C - функции могут возвращать тип void - ничего/пусто.
Синтаксис очень просто перед именем функции пишут тип возвращаемых данных, например int, затем имя функции, а в круглых скобках перечисления через запятую типы аргументов и их имена.
Например
int my_func(int a,int b,double num)
Тело функции пишутся после объявления функции в фигурных скобках, например
int my_func(int a,int b,double num)
{
int c = a + b;
c = c + (int)num;
return c;
}
Ключевое слово
return говорит что в данном месте нужно прекратить выполнение функции и вернуть значение, которое у него указано (в примере - переменная
c).
Если функция типа
void - то можно конструкция упрощается просто
return;.
Теперь про большие проекты, функции используются еще для того чтобы разнести код по его смыслу на разные модули.
Чтобы в другом файле(модуле) использовать функцию другого файла(модуля) нужно написать(указать) ее прототип.
Например в нашем случае
int my_func(int a,int b,double num);
- описания прототипа заканчивается точкой запятой, а описание функции - телом функции в фигурных скобках.
В прототипе функции(описании) можно не указывать названия переменных или (если хочется) писать любые имена - все равно комплиятор их не смотрит
int my_func(int,int,double);
- это тоже прототип функции.
Забегая вперед скажу, что в C++ можно не указывать имя переменной и в описания функции - это означает, что в данной функции вы не используете эту переменную, а ее синтаксис сохранен для совпадения с чем-то. В этом случае компилятор не выдает предупреждение(warning) Parameter 'a' is never used in function
Допустим мы в функции
my_func изменем содержание переменной a, например
a = 5;
, то в сама переменная указаная в функции при вызове -
не изменится.
Например:
int my_func(int a,int b,double num)
{
a = 6;
int c = a + b;
return c + (int)num;
}
int main()
{
int i=5,j=6,n;
double k=1;
n=my_func(i,j,k);
printf("n=%d, i=%d\n",n,i);
}
Выдаст
n=13, i=5, потому что
в функцию передаются значения переменных, а не их адресаЧтобы изменять значения переменной нужно в описании функции указывать не просто переменную, а указатель на нее.
void my_func2(int *a,int b)
{
*a = b + 5;
}
int main()
{
int i=5,j=6;
my_func2(&i,j);
printf("i=%d\n",i);
}
Код вернет
i=11, т.к. в функции мы используем указатель на переменную, и пусть она называется не так как в основной функции (там i, а в myfunc2 - a) - имена не играют никакой роли, все равно в область памяти переменной указанной в качестве первого аргумента, будет записано значение переменной второго аргумента + 5.
С переменными более, менее ясно - теперь - как передавать массивы, в частности строки?
Допустим мы хотим написать функцию, которая будет определять сколько букв 'а', в заданной фразе.
Нам нужно передать в функцию строку - массив символов, так как это сделать?
Очень просто, в описании функции нужно написать, что ожидается указатель на элемент массива (в нашем случае - символ).
Например:
int getSymA(char *src);
В функции, чтобы получить элемент массива, можно также просто писать str[1] или *(str+1) - это два идентичных кода вернут второй символ в строке.
В целом наша функция будет выглядеть так:
int getSymA(char *src)
{
int i,n=0;
for(i=0;src[i];i++)
{
if (src[i]=='а') n++;
}
return n;
}
кстати, фигурные скобки после циклов, если код состоит из одной строки можно не указывать, например:
int getSymA(char *src)
{
int i,n=0;
for(i=0;src[i];i++)
if (src[i]=='а') n++;
return n;
}
или вообще не использовать лишние переменные:
int getSymA(char *src)
{
int n=0;
for(;*src;src++)
if (*src=='а') n++;
return n;
}
Это как эволюция понимания, потому рассмотрим последний код построчно.
- Первая строка - говорим что нужно создать переменную n и инициализировать ее нулем.
- Вторая строка - цикл for - пропускаем блок инициализации (первый после круглой скобки) сразу ставим точку с запятой, второй блок - условия - в нем проверяем является ли указатель на символ 0 - пустым символом, означающим конец строки, третий блок перемещаем указатель в памяти (указатель, а не его значение)
- Третья строка - проверяем, а текущий указатель указывает на букву А? Если так увеличиваем счетчик n на один, если нет - то ничего не делаем (нет блока else)
Сделаем вызов, скажем следующего кода:
char s[10]="Hello!";
printf("%d\n",getSymA(s));
Первое - при передаче массива, не надо указывать его адрес - нужно указывать саму переменную, т.к. она и есть уже указатель на переменную.
Второе -
АААА! Мы изменяли указатель! Программа сломается и нельзя использовать переменную дальше! - Вовсе нет, мы передали указатель, который может менять значение переменной, но мы его не модифицировали, а просто изменяли его адрес.
Чтобы модифицировать сам указатель нужно использовать указатель на указатель :)
Код:
void hhh(int **a,int *b)
{
*a = b;
}
int main()
{
int i=5,j=6,*n=&i;
hhh(&n,&j);
printf("i=%d,j=%d,n=%d\n",i,j,*n);
}
результат:
i=5,j=6,n=6, в функцию мы передали указатель на указатель переменной i, но в самой функции сказали, что теперь он равен указателю на переменную b.
Сами переменные (i и j) не изменились, а вот указатель n, ранее указывающий на i, стал указывать на переменную j.
И отвечая на ваш вопрос скажу -
ДА! Есть указатели на указатели указателей, особенно это любит использовать корпорация MicrosoftНо в реальной жизни, хватает и указателей на указатели. А некоторым и просто указателей.
Указатели на функции
Раз в функции можно передать указатель на область памяти, то почему не передать на ту область где находится функция?
Конечно можно и не так сложно, код:
#include
int pw(int n,int t)
{
int i,r=1;
for(i=0;i>t;i++)
r*=n;
return r;
}
int hhh(int (nn(int,int)),int z)
{
return nn(z,2);
}
int main()
{
printf("%d\n",hhh(pw,3));
}
Вызываем функцию hhh, передавая ей в качестве аргументов адрес на функцию и число.
В функции hhh вызываем функцию указанную в качестве параметра и получаем в результате цифру 9.
Для сложных функций, передаваемых в качестве параметра, лучше использовать заменитель
typedeftypedef int (*load_func)(int,int);
int hh(load_func f,int z)
{
if (f) return f(z,2);
return 0;
}
int main()
{
printf("%d\n",hhh(&pw,3));
}