понедельник, 29 ноября 2010 г.

Указатели, функции и указатели на функции (С, Си)

Итак, в уроке №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;
}

Это как эволюция понимания, потому рассмотрим последний код построчно.
  1. Первая строка - говорим что нужно создать переменную n и инициализировать ее нулем.

  2. Вторая строка - цикл for - пропускаем блок инициализации (первый после круглой скобки) сразу ставим точку с запятой, второй блок - условия - в нем проверяем является ли указатель на символ 0 - пустым символом, означающим конец строки, третий блок перемещаем указатель в памяти (указатель, а не его значение)

  3. Третья строка - проверяем, а текущий указатель указывает на букву А? Если так увеличиваем счетчик 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.

Для сложных функций, передаваемых в качестве параметра, лучше использовать заменитель typedef
typedef 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));
}


понедельник, 22 ноября 2010 г.

Второй урок - Типы переменных. (С, Си)

Данный урок, продолжение первого урока.
Раберем типы переменных в C.
Их не много, это символ, он же байт - char принимает значения от 0..255 (или -127 .. +127)
Целочисленное - int - значения целые числа
С плавающей точкой - float
С двойной точностью - double
Тип данных float практически не используется в современном программировании.

Существуют несколько префиксов к типам данных, например знаковость это signed и unsgined.
По умолчанию переменные знаковые (signed), хотя такая запись не будет ошибочной signed int a

В чем различия? В диапазоне принимаемых значений в случае знака это от - до +, в беззнаковом случае(unsigned) от 0 до максимального значения.

Максимальное значение для char это 255 (емкость - 28) , а для int (емкость 232) - в 32 битных системах, в 64 - соотвественно 264, а я помню еще времена когда в int можно было загнать только до 216.

Размер памяти выделяемый для каждой переменной, естественно кратный ее максимальному значению (или наоборот - тут спор как про яйцо и курицу), для char - 1 байт, для int (32) - 4 байта.

Для float - 4 байта, для double - 8 байт.

Также есть префиксы изменяющие размер данных - это short и long
Но не все варианты возможны, скажем для char и float нельзя указывать такой префикс.
Можно только для int и double. Т.к. long float это и есть double, а long char - это наверное int.

Так вот short int - это слово (два байта), т.е. максимальное значение 216
Для long int в bcc и gcc - размер равен что и просто int - двойное слово (четыре байта).
Однако для некоторых компиляторов - long int это int64 - 8 байт.

Вариантам short double тоже несуществует, однако есть long double.
Кстати в разных компиляторах это разные размеры данных. В bcc - 10 байт, а в gcc - 12 байт (на 32битной платформе).

По поводу signed/unsigned - тут стоит заметить что переполнение емкости данных, ведет к разным значениям в зависимости от signed.
Например unsined char a = 255; a+=2, "a" будет равна 1. А char a = 127; a+=2, "a" будет равна -127.

Эта нехитрая ошибка приводит к большому количеству багов и глюков множества программ.

А как же строки, спросит тот редкий читатель, что увидел этот текст.
Все просто - строка это массив символов. Размерность массива указывается в квадратных скобках после имени переменной
Например int a[50]; - целочисленный массив, с 50 элементами. Для доступа к конкретному элементу в квадратных скобках указывается его индекс int b= a[5];.
Массивы можно инициализировать сразу при описании, например int a[5] = {1,2,3};, значения указывается через запятую в фигурных скобках. Значений может быть меньше чем размерность массива, но не больше!

Также и строки char str[10] = {'H','e','l','l','o','!',\0};.
Символы указывается в апострофах, и в ковычках может быть только один символ. char a='aa';//ошибка!!!

Но гораздо легче и проще инициализировать строки (а также использовать их для других целей) через строку заключенную в двойные кавычки char str[10] = "Hello!";.
Следует заметить, что 0-символ ставится автоматически.
Т.е. char str[6]="Hello!"; - не сработает, скажет что размер массива меньше чем количество инициализируемых элементов.
Т.е. "Hello!" автоматически преобразуется в {'H','e','l','l','o','!',\0}


вторник, 16 ноября 2010 г.

Первый урок - Hello world на C(си)

В языке C очень малый набор ключевых слов, т.к. только несколько типов переменных, слова управления циклами и функциями.
Еше необходимо заметить, что C язык компилируемый, т.е. для его выполнения необходимо его скопилировать - преобразовать из текста языка в машинный код.

При выполнении программы, написаной на C необходимо описать главную функцию main, которая и будет вызываться при запуске.
Функция main является такой-же обычной функцией как и все остальные, в целом у нее есть два несколько прототипов описания.
void main(), int main(), int main(int,char **) и void main(int,char **).

Первое слово перед main это тип функции - тип значения которое она может веруть int - целочисленное число, void (пусто) ничего.
Затем идет имя функции (в нашем случае main), а далее в круглых скобках - принимаемые параметры, также сначала тип, а затем название переменной.
Причем как в описании, так и в самой функции названия могут отсутствовать.
Отсутствие название переменных и для чего это - обсудим потом


Тут нужно учесть, что компилятор gcc считает что функция main должна обязательно иметь тип int, для того чтобы вернуть код выполнения программы, а вот скажем bcc от Borland допускает и тип void, хотя в этом случае на самом деле возвращает 0.
Чтобы поддерживать общие стандарты лучше описывать функцию main все-же через тип int

Функция main отличается от других библиотечных функций тем, что она противоположная по смыслу, в остальных функциях вы знаете их прототип для вызова, но не знаете/видете код функции, а для main все как раз наоброт - компилятор знает ее описание, но вот код - должны написать вы.

И так самый примитивный рабочий код на C!
int main()
{
return 0;
}

Данный код можно скомпилировать в программу и даже выполнить - правда, он ничего не выполнит, потому как единственная строка кода в нем return 0; - выйти из функции и вернуть значение 0.
Ну да в C - разделитель комманд точка с запятой ';'


Для того. чтобы что-нибудь вывести на экран нужно вызвать функцию для вывода, впрочем большинство стандартных функций уже давно написаны. Например для вывода на экран(консоль) есть специальная библиотека stdio, которая содержит тьму функций.
Но нам интересна пока только одна - printf - форматированный вывод на экран.
Чтобы подключить библиотеку нужно указать директиву include с указанием имени библиотеки, имя библиотеки можно написать в угловых скобках (тогда компилятор библиотеку будет искать в стандартной папке, либо в двойных ковычках тогда он сначала ищет в текущем каталоге, а потом в стандартом)
Все директивы - это команды к препроцессору, т.е. процессу вызываемому до компиляции, это облегчает жизнь программистам, в противном случае им пришлось бы самим писать все прототипы функций вручную.
Ну и директивы пишутся через знак #, всего их не очень много основные: include, define, ifdef, else, error

Для нашего примера это #include <stdio.h> - т.е. включить заголовки(описания прототипов) от библиотеки stdio (расширение .h - как раз указывает что это заголовки от англ. header)

Функция printf поддерживает множество аргуметов, но пока нас интересует только первый - а именно базовая строка, которая будет выводится на экран.
Cамый примитивный рабочий код на C с выводом на экран!
#include <stdio.h>
int main()
{
printf("Hello, World!\n");
return 0;
}



Т.к. в качестве первого аргумента функции printf является строка, то приходится квотировать некоторые символы, например \n - означает перевод строки, а \" - двойную кавычку


Теперь скомпилировав и выполнив это программу, увидем на экране Hello, World

Если, вы конечно, запускаете ее из консоли (в винде через cmd), а в линуксе через терминал.

Ну и чтобы скомпилировать программу - команда gcc hello.c -ohello.exe - это если у вас gcc

понедельник, 15 ноября 2010 г.

Подумал тут

И решил сделаю я курс обучения C/C++ (сначала основы, а потом классы и полиморфизм ;) )
Я все потому, что прочитал я в блоге Шелвина, что это интересует массы.

А прочитал я блог, потому что автор устраивает марафон на 60 дней для набора подписчиков.
Обещает горячие темы и жаркие посты, так что думаю можно и подписаться.

УТКИ НА СТАРТ!

четверг, 11 ноября 2010 г.

Авторизация на ya.ru (через passport.yandex.ru)

Попросили меня (за практически спасибо) написать модуль для авторизации и постинга постов на ярушку (ya.ru).

Быстренько накидал через свои компоненты, а что сложного-то? GET my.ya.ru далее получаем форму для авторизации через passport.yandex.ru заполняем поля и далее через n редиректов в идеале попадаем на my.ya.ru уже авторизованными.

В чем сложность №1. Понятно что авторизация не совсем обычная (не через тривиальные куки) - и понятно что яндекс(домен) свои куки не отдает даже домену ya.ru.

Первый и быстро написаный код не работал - вернее он явно авторизовал на passport.yandex.ru (видно было по выдаче), но на my.ya.ru был редирект без кук и сессий.

Ломал над этим голову долго, причем этот код замечательно работал для mail.yandex.ru и webmaster.yandex.ru - на это и понятно - это один и тот-же домен.

Перебрал все, установил сниффер - вижу что при первичной загрузке my.ya.ru в браузере срабатывает редирерт, на my.ya.ru/pass где получают куки и возвращают опять же редиректом на my.ya.ru.
С точки зрения пользователя ничего не происходит он вбивает my.ya.ru и видит страницу авторизации.

Но в моем случае этого не было! Было просто загрузка формы логина, без редиректа и без принятия кук.

Оказалось что я не передавал в хедере HTTP тэг accept = html/text и прочее.
И именно из-за этого my.ya.ru не делал редирект и не подсовывал кук.
Как только начал указывать - все получилось.

В кратце:
GET my.ya.ru -> my.ya.ru/pass -> my.ya.ru
Заполняем форму
POST passport.ya.ru -> ... -> pass.yandex.ru -> my.ya.ru -> pass.yandex.ru -> my.ya.ru
Мы авторизованы! (В предпоследнем ya.ru передается сессия, а в последнем уже авторизованные куки для ya.ru)