Вторник, 09 Сентября 2025, 23:10

Приветствую Вас Гость

[ Новые сообщения · Игроделы · Правила · Поиск ]
Результаты поиска
nilremДата: Суббота, 23 Января 2010, 17:18 | Сообщение # 821 | Тема: Курс : "Основы С++ для начинающих программистов игр."
Просветленный разум
Сейчас нет на сайте
Урок 4. Функции и указатели.


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

Массивы и указатели.

Я уже раньше намекал, что имя массива является также и константным указателем на массив, а также на первый его элемент. Убедится в этом можно из следующего примера:
Code

#include <iostream>
using namespace std;

void main()
{
    int array[10];
    cout<<'\n'<< array;  // отображение адреса массива
    cout<<'\n'<<&array [0];    // отображение адреса первого элемента
    cin.get();
}


Оба напечатанных адреса будут идентичными.
Напомню, что для отображения адреса первого элемента использовался оператор взятия адреса &, ведь первый элемент это число, а не указатель.
Что же нам это дает? Да ничего особенного, если не знать, что к указателям также можно применять операторы инкременты и декременты. При этом адрес изменяется не на единицу, как обычно, а на размер переменной в байтах.
В результате доступ к элементам массива можно выполнять еще одним способом. Его вы видите в примере:
Code

#include <iostream>
using namespace std;

void main()
{
    int a[10];
    for(int i=0;i<10;i++)
    {
     a[i]=i;
     cout<<a[i]<<' ';
    }
    for(int i=0;i<10;i++)
    {
     cout<<*(a++)<<' ';
    }
    cin.get();
}


Вот только работать этот пример не будет. Ведь а это константный указатель, то есть изменить его нельзя. Чтобы пример заработал необходимо создать указатель обычный и передать ему адрес из константного:
Code

#include <iostream>
using namespace std;

void main()
{
    int a[10];
    for(int i=0;i<10;i++)
    {
     a[i]=i;
     cout<<a[i]<<' ';
    }
    cout<<'\n';
    int *pa=a;
    for(int i=0;i<10;i++)
    {
     cout<<*(pa++)<<' ';
    }
    cin.get();
}

Опять напомню что в записи *(pa++) звездочка это оператор разыменования указателя, с помощью которого получаем хранящееся по адресу значение. Скобки здесь используются, чтобы не запутаться с приоритетами операторов.
При доступе к элементам массива с использованием указателя и инкременты(декременты) остерегайтесь выхода за пределы массива.
Code

#include <iostream>
using namespace std;

void main()
{
    int a[10];
    for(int i=0;i<10;i++)
    {
     a[i]=i;
     cout<<a[i]<<' ';
    }
    cout<<'\n';
    int *pa=a;
    for(int i=0;i<11;i++)
    {
     cout<<*(pa++)<<' ';
    }
    cin.get();
}

В этом примере происходит доступ к несуществующему 11 элементу(с индексом 10, ведь как вы помните отсчет начинается с 0). В результате программа выдает нам какое-то, непонятно откуда взявшееся число.

Динамическое распределение памяти.


А сейчас настало время познакомится с одним из самых распространенных способов использования указателей.
Когда ваша программа начинает свою работу, операционная система выделяет для нее место в памяти. Называется это место стек. Размер стека не безграничный, поэтому иногда возникает проблема, когда ваши данные, например большой массив, в него попросту не помещаются. Эта проблема получила название – переполнение стека, и приводит к краху работы программы.
Обидно получается. В компьютере вон сколько памяти, а попытка создать парумегабайтный массивчик делает программу неработоспособной. Но выход есть.
Ко всей этой неиспользуемой, находящейся за пределами стека памяти, можно получить доступ. Память эта, кстати, называется динамической(точнее динамически распределяемой), то есть непостоянной, в противовес постоянному стеку.
Для того, чтобы разместить данные в динамической памяти используется указатель и специальный оператор - new.
Работает он очень просто. По запросу программы он резервирует где-то в динамической памяти свободный кусочек, и возвращает его адрес.

Синтаксис оператора следующий:
Code

указатель  =  new  тип_данных;

Пример:
Code

int *pi = new int;

согласно этой записи оператор new выделит в области динамического распределения некий, соответствующий размеру типа данных, объем памяти, и передаст указателю pi его адрес. Как мы помним, размер целочисленного типа данных 4 байта, столько и будет выделено.
В дальнейшем доступ к этой памяти производится через указатель.
Code

#include <iostream>
using namespace std;

void main()
{
    int *pi = new int;
    *pi=10;
    cout<<*pi;
    cin.get();
}


Ключевой особенностью использования динамически распределенной памяти является ее независимость от области видимости. Будучи созданной один раз она доступна везде, куда можно передать ее адрес:
Code

#include <iostream>
using namespace std;

int* func()
{
    int* pa=new int;    // динамическое выделение памяти
    *pa=123123;
    return pa; // возврат адреса
}

void main()
{
    int* pi;
    pi=func(); // получение адреса на распределенную память
    cout<<*pi;
    cin.get();
}

Здесь функция func() возвращает адрес на динамически выделенную память. Теоретически после завершения работы функции все созданные в ней переменные должны быть утрачены. Но это не относится к динамическому распределению памяти. Что и подтверждает строчка cout<<*pi;, печатающая ранее присвоенное в функции и никуда не подевавшееся корректное значение.
Динамическое распределение памяти настолько мощное средство, что даже по завершению программы выделенная в ней память остается недоступной для других программ.(Было это во времена ДОСа и первых Windows, сейчас же операционные системы работают с памятью более грамотно). Так вот, если все время выделять память, рано или поздно она кончится. Поэтому когда память под переменную становится не нужной, ее нужно освободить. Для этих целей используется оператор delete.

Синтаксис следующий:
Code

delete указатель;

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

#include <iostream>
using namespace std;

int* func()
{
    int* pa=new int;    
    *pa=123123;
    delete pa;  // освобождение ранее выделенной памяти.
    return pa;    
}

void main()
{
    int* pi;
    pi=func();    
    cout<<*pi;
    cin.get();
}

Теперь все отлично, память мы освободили. Вот только программа стала неработоспособной. Ведь освободив память мы уничтожили хранящееся там значение.
Как же тогда быть. Ведь если не использовать delete внутри функции, по ее завершении указатель прекратит свое существование, а значит и освободить память будет невозможно. А вот и нет. При использовании delete это не обязательно должен быть тот самый указатель, но это обязательно должен быть тот самый адрес.

Code

#include <iostream>
using namespace std;

int* func()
{
    int* pa=new int;    
    *pa=123123;
    return pa;    
}

void main()
{
    int* pi;
    pi=func();    
    cout<<*pi;
    delete pi; // освобождение ранее выделенной памяти.
    cout<<*pi;
    cin.get();
}


Предупреждаю сразу – это был плохой пример. Посмотрите его и забудьте. Если где-то в функции выделяется память, то она в этой функции и должна освобождаться. Лучше по новому выделить в main память и передать туда значение, а не разбрасываться вот так вот памятью, а то в конце концов можно окончательно запутаться, и превратить свой код в кучу мусора. Тем не менее таким способом пользуются довольно часто, особо отмечая, что потом память нужно освобождать, например создавая для этого специальные функции и указывая в документации, что их вызов по окончании работы обязателен.

Иногда программист забывает где-то освободить память, и если этот код выполняется неоднократно, можно выделить довольно много памяти в никуда. Такую ситуацию называю утечкой памяти, и это одна из довольно распространенных ошибок, которые в будущем попортят вам нервы.

С выделением памяти под обычные переменные разобрались, теперь рассмотрим как это делается с массивами.
Code

указатель  =  new  тип_данных[размер массива];

В принципе разница небольшая. Необходимо всего лишь указать после типа данных размер массива. Для того чтобы освободить выделенную таким образом память используется следующая запись.
Code

delete [] указатель;    

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

Что то мой разговор об указателях затягивается, пора возвращаться к функциям.

Параметры-адреса


Давайте рассмотрим такой простой пример:
Code

#include <iostream>
using namespace std;

void print(int a)
{
    a = 32;
    cout<<a<<'\n';
}

void main()
{
    int i=10;
    print(i);
    cout<<i<<'\n';
    cin.get();
}


Здесь у нас есть функция print, которой передается переменная i. Внутри функции этой переменной присваивается новое значение. Но потом, после функции оказывается что значение переменной i осталось неизменным. То есть изменить значение переменной, которая передавалась функции как аргумент, нельзя.
Так происходит потому, что функции на самом деле передается не оригинальная переменная, а всего лишь хранящееся в ней значение. Поэтому такой способ передачи называют передачей по значению. Любые манипуляции с этим значением внутри функции оригинальную переменную не затрагивают.
Получается, что бы не делала функция результат ее работы за пределы функции не выйдет. В таком случае зачем они вообще нужны. Ах, да. Функция же может возвращать значение. Вот только всего одно. А если нам нужно больше?
Тут на помощь опять приходят указатели. Функция работает с указателями так же, как и с обычными переменными, то есть передает их значение. А что у нас хранится в указателе? Правильно, адрес ячейки памяти, в которой хранится некое значение. А значит мы можем в полной мере воздействовать на это значение. Это проиллюстрировано следующим примером:
Code

#include <iostream>
using namespace std;

void print(int* a)
{
    *a = 32;
    cout<<*a<<'\n';
}

void main()
{
    int i=10;
    int *pi = &i;
    print(pi);
    cout<<i<<'\n';
    cin.get();
}

Здесь в функции объявляется переменная i, которой присваивается число 10. Затем эта переменная передается функции print. Причем передается не сама переменная, а указатель на нее, как того требует прототип функции:
Code

void print(int* a);

аргументом функции является указатель, то есть попросту говоря адрес. Внутри функции с помощью оператора разыменования * по адресу присваивается новое значение. По завершении работы функции оказывается, что теперь значение переменной i изменилось, и равно тому, что было присвоено в функции. То есть нам удалось изменить значение переданной функции переменной. Что и требовалось. Вот такие вот полезные указатели. Такой способ передачи аргументов называется передачей по указателю.
Добавлю, что запись
Code

int *pi = &i;
    print(pi);

можно сократить так:
Code

    print(&i);

здесь адрес берется непосредственно при передаче переменной в функцию.

Кроме передачи по значению и передачи по указателю есть еще и третий способ, называемый передача аргументов по ссылке.
Он не требует от программиста непосредственной работы с указателями, хотя тоже работает с адресами переменных. Но вся эта работа скрыта от программиста.
Вот предыдущий пример, измененный так, чтобы продемонстрировать передачу по ссылке.
Code

#include <iostream>
using namespace std;

void print(int& a)
{
    a = 32;
    cout<<a<<'\n';
}

void main()
{
    int i=10;

    print(i);
    cout<<i<<'\n';
    cin.get();
}

Здесь ключевую роль играет запись аргументов в объявлении функции.
Code

void print(int& a)


int& a означает что будет производится передача по ссылке(можно сказать что переменная а это новый тип данных, который называется ссылка, и является синонимом адреса). В коде примера нет ни одного указателя. Но функция print(i)получает не значение переменной, а ее адрес, и записывает 32 по этому адресу. То есть результат тот же, что и при использовании указателей.
По-моему, намного проще.


Windmill 2

WindMill 2D Game Engine
nilremДата: Суббота, 23 Января 2010, 16:47 | Сообщение # 822 | Тема: Курс : "Основы С++ для начинающих программистов игр."
Просветленный разум
Сейчас нет на сайте
Урок 3. Обширный функционал


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

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


Идя по свежим следам, вновь вернемся к именам. Именам функций.
Имя, которое используем мы и имя, которым функция представлена в программе, различны. В отличии от нас, программа также учитывает количество и тип аргументов а также тип возвращаемого значения. Все это вместе называют полным именем функции, ее сигнатурой.
Пример:
Code

int func();
char func();  // ошибка
int func(int);
int func(char);
char func(int,int);
char func(int,float);

Все это разные функции.
Возможность создавать функции с одинаковыми именами называется перегрузкой функций.
И обратите внимание на вторую строку, возле которой приписано "ошибка". От первой она отличается возвращаемым значением. Но на этапе компиляции компилятор не сможет предугадать, какой тип данных будет возвращен, и какую же из перегруженных функций нужно использовать. Поэтому такая перегрузка недопустима и порождает ошибку.
Может возникнуть вопрос, зачем же эта перегрузка нужна. Зачем сознательно вносить такую путаницу в программу. Оказывается для того, чтобы эти самые программы упростить. Понять это поможет следующий пример:
Code

int sum(int a, int b)
{
   return a+b;
}
float sum(float a, float b)
{
   return a+b;
}

void main()
{
   cout<<sum(10,20);
   cout<<sum(10.5,20.7);
}

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

Необходимо отметить, что указанный выше пример работать в Microsoft Visual Studio 2008 не будет. Компиляция завершится следующей ошибкой:

error C2668: sum: неоднозначный вызов перегруженной функции

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

cout<<sum(10.5f,20.7f);


или так:
Code

cout<<sum((float)10.5,(float)20.7);


Значение аргументов по умолчанию.


И еще немного об именах.
В С++ есть возможность присваивать используемым в функциях аргументам значение по умолчанию. Делается это очень просто. Достаточно выполнить присваивание в момент объявления функции:
Code

int func(int i=50);


Здесь аргументу, представленному переменной int i, по умолчанию присвоено 50. Оно будет использоваться, если при вызове не передавать функции никакого значения.
Пример:
Code

#include <iostream>
using namespace std;

void func(int i=50);    // обьявление с заданием аргумента по умолчанию

void main()
{
   func();    // первый вызов
   func(111);    // второй вызов
   cin.get();
}

void func(int i)
{
   cout<<i<<"\n";
}

Здесь при первом вызове функции не передается никакое значение, поэтому используется значение по умолчанию. При втором вызове используется 111.

Конечно же у функции может быть больше одного аргумента.
Code

void func(int arg1, int arg2, int arg3);


Присвоить значение по умолчанию аргументу arg2, можно только в том случае, если значение по умолчанию уже есть у arg3.
Так делать нельзя:
Code

void func(int arg1, int arg2 = 125, int arg3);

Можно только так:
Code

void func(int arg1, int arg2 = 125, int arg3 = 0);

Если объявление функции не используется, то аргументы по умолчанию задаются в ее реализации:
Code

#include <iostream>
using namespace std;

void func(int i=50)    //  задание аргумента по умолчанию
{
   cout<<i<<"\n";
}

void main()
{
   func();    // первый вызов
   func(111);    // второй вызов
   cin.get();
}


Встраиваемые функции


Использование функций имеет один маленький недостаток. На вызов функции тратится некоторое время. Происходит это потому, что под код функции резервируется отдельное место в памяти. По ходу выполнения программы, при вызове функции программе приходится переходить к этому месту, а затем возвращаться обратно. Естественно что этот переход не происходит мгновенно, и хорошо если она вызывается всего раз, а вот если 100, значит и переходов будет столько же, и потраченного времени в сто раз больше.
Получается что получить большую скорость работы программы можно, вписывая код функции в место вызова. Это еще бы было нормально, если бы функция вызывалась всего пару раз, а вот если опять сотню. Это ж получится такой объем кода, что сам черт в нем не разберется.
Чтобы обойти все эти проблемы, можно использовать оператор inline.
Синтаксис следующий:
Code

inline Тип_возвращаемого_значения  имя_функции(список агрументов);

Пример:
Code

inline int sum();

Этот оператор заставляет компилятор встраивать код функции в место ее вызова. Как говорится, и волки сыты и овцы целы, то есть и код небольшой и легко читаем, и программа работает быстро.
Но все же есть одно но. Если такая функция вызывается 100 раз, то при компиляцию в программу будет вставлено 100 ее экземпляров. За увеличение скорости придется заплатить увеличением программного кода и занимаем местом в памяти. В результате никакого выигрыша может и не быть. Поэтому нужно руководствоваться следующим правилом: Если функция содержит всего две-три строки кода, то ее можно, и даже нужно, делать встраиваемой. Если же функция большая, да к тому же используется много раз, то делать ее встраиваемой нельзя.

Статические переменные.


Допустим, что нам зачем-то нужно посчитать, сколько раз конкретная функция вызывалась в программе. Первый пришедший на ум способ сделать это – создать отдельную глобальную переменную и в коде функции увеличивать ее на единицу.
Вот так:
Code

#include <iostream>
using namespace std;

int r=0;    // глобальная переменная-счетчик

inline int sum(int a, int b)
{
   r++;    //использование глобальной переменной
   return a+b;
}

void main()
{
   for(int i =0;i<50;i++)
   {
    sum(10,20);
   }
   cout<<r;    // результат подсчета
   cin.get();
}

Есть у подобного способа один недостаток. Допустим, вы скопировали вашу функцию в другую программу, или вообще дали своему другу. А в этой другой программе, ну или у друга, нет специально объявленной глобальной переменной. Программа работать не будет и хорошо, если вы вспомните почему.
Естественно, способ решить подобную проблему есть. Это оператор - static.
Объявив с его помощью переменную внутри функции
Code

int sum(int a, int b)
{
   static int r=0;    // статическая переменная-счетчик
   r++;   
   return a+b;
}

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

Рекурсивные функции


Может быть то, что я сейчас скажу, покажется вам бредом, но знайте, функция способна вызывать сама себя.
Code

int func()
{
func();
}

Называется эта возможность рекурсией. Рекурсия может быть прямая и косвенная.
Прямая – когда функция вызывает сама себя, косвенная – когда функция вызывает другую функцию, которая в свою очередь вызывает первую. Кстати, это уже знакомая ситуация:
Code

void b();

void a()
{
   b();
}
...
void b()
{
   a();
}


Применяется рекурсия в разных случаях, когда например нужно выполнить ее для какого то значения, а затем эта же функция применяется к полученному результату.
Для рекурсивных функций является обязательным наличие условия, способного прекратить рекурсию.
Небольшое откровение. Я не люблю рекурсию, особенно прямую. Нет такой ситуации, когда бы без нее нельзя было обойтись. К тому же множественный вызов функций, передача аргументов расходуют дополнительное время и память. Но все же есть одно преимущество - использование рекурсии позволяет сделать код более компактным.
Специально приводить пример рекурсии я не буду.


Windmill 2

WindMill 2D Game Engine
nilremДата: Суббота, 23 Января 2010, 16:32 | Сообщение # 823 | Тема: Курс : "Основы С++ для начинающих программистов игр."
Просветленный разум
Сейчас нет на сайте
Урок 2. Область видимости имен

Тема, которую мы рассмотрим на этом уроке, касается не только функций, но и переменных. Поэтому для упрощения буду дальше говорить в основном о них.
Область видимости, это часть программы в которой можно использовать указанную переменную. Бывает она всего двух видов: глобальная и локальная.
Глобальная область это весь наш файл с исходным кодом. Локальная же область, это область, принадлежащая определенному блоку кода, например функции. Локальные области могут быть вложенными друг в друга. Вне своей области локальная переменная невидима, то есть ее нельзя использовать, тогда как глобальная переменная может использоваться в любой объявленной в ней локальной. Локальная область видимости ограничивается фигурными скобками.
Давайте рассмотрим простой пример.

Code

int i;

void main()
{
  i=20;
}

void func()
{
  i = 321;
}


Это весь код, никаких инклюдов, ничего.

Переменная i, объявленная вне главной функции main(), является глобальной. Она видима и внутри main и внутри вообще любой другой функции, например func.
Запомните, глобальная область предназначена только для объявлений или определений. Никакие другие действия в ней не работают. Если вы захотите выполнить, например, присваивание:

Code

int i;

i=123; // ошибка
void main()
{
  i=20;
}


то компилятор укажет на ошибку. Он решит, что вы пытаетесь заново объявить переменную i, да еще и неправильно, забыв указать тип. Если нужно задать глобальной переменной начальное значение, то делается это сразу при объявлении вот так:
Code

int i=123; // все ОК
void main()
{
  i=20;
}

Теперь слегка изменим пример:
Code

void func();

void main()
{
  int s;
  s = 10; // все ОК
}

void func()
{
  s = 321; // ошибка
}

Здесь внутри функции main объявлена переменная с, внутри самой функции с ней можно делать все что угодно. Но попытка использовать ее в функции func вызовет ошибку, поскольку func не принадлежит к области видимости функции main.
Казалось бы, чисто теоретически, можно было бы сделать вот так.
Code

void func();

void main()
{
  int s;
  s = 10; // все ОК
  void func()
  {
   s = 321;
  }
}

Но, не судьба. Запомните, объявлять и писать реализацию функции можно только в глобальной области. Поэтому попытка проделать такое вызовет ошибку.
Следующий пример:
Code

int s;

void main()
{
  s = 10; // все ОК
  while()
  {
   s = 321; // тоже все ОК
   int w;  
   w = 128; // все ОК
  }
  w = 512; // А вот здесь ошибка!
}

Что и где доступно, видно из комментариев. Здесь у цикла есть собственная область видимости. Создать такую область можно, просто выделив что-то фигурными скобками.
Code

int s;

void main()
{
  s = 10; // все ОК
  {
   s = 321; // тоже все ОК
   int w;  
   w = 128; // все ОК
  }
  w = 512; // А вот здесь ошибка!
}

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

Ранее, описывая требования для имен переменных, я говорил, что имя должно быть уникальным. Теперь я хочу это правило дополнить. Имя должно быть уникальным в пределах одной области видимости.

То есть:

Code

void func();

void main()
{
  int s;
}

void func()
{
  int s;
}

Не вызовет никаких проблем. Переменная s является для каждой из функций локальной, посему в другой области она невидима и такая запись не приводит к возникновению ошибки.
Хорошо, теперь вот такой пример:
Code

int s;
  void func();

void main()
{
  int s;
  s = 10;
}

void func()
{
  s = 50;
}

Здесь переменная s объявлена также и в глобальной области. Казалось бы, она должна быть видимой и внутри других функций и подобная запись должна приводить к ошибке. Но нет!
Ранее озвученное правило продолжает действовать. Просто если имя объявлено в глобальной области, то везде используется оно, но если в локальной области объявлена переменная с аналогичным именем, то используется локальная.
Поэтому в приведенном выше примере

Присваивание s = 10; никак не затрагивает s глобальную. Тогда как в функции func присваивание производится как раз глобальной переменной. Так что можно даже сказать, что это две разные переменные. И следующая программа это продемонстрирует:

Code

#include <iostream>
using namespace std;

int s;
void func();

void main()
{
  setlocale(0,"");
  int s=30;
  cout<<"Локальная s ="<<s;
  func();
  cin.get();
}

void func()
{
  s = 10;
  cout<<"\nГлобальная s ="<<s;
}

Результат ее работы наглядно иллюстрирует, что изменение локальной переменной никак не влияет на ее глобального тезку.
Отлично. Все это хорошо. А вот что тогда делать, если нам зачем-то нужно обратится к этой самой глобальной одноименной переменной.
Для этого есть специальный оператор - ::. С его помощью можно запросто обратится к глобальной области. Как им пользоваться проиллюстрировано в примере ниже:

Code

#include <iostream>
using namespace std;

int s=10;

void main()
{
  setlocale(0,"");
  int s=30;
  cout << "Локальная s =" << s;
  cout << "\nГлобальная s =" << ::s; // Вот здесь вот оно, это двойное двоеточие
  cin.get();
}


Все, если вы внимательно читали и рассматривали примеры, то должны все понять. Теперь можно вернутся к функциям, и поговорить о том, зачем же нужно их объявление.
Допустим где то в программе, глубоко в областях видимости, хотя это и не важно, у нас есть две функции, использующие для работы друг друга:
Code

void a()
{
  b();
}
...
void b()
{
  a();
}

Если объявления нет, то как же первая функция узнает о существовании второй?
Вот такой вариант не пройдет:
Code

void b()
{
  a();
}
...
void a()
{
  b();
}
...
void b()
{
  a();
}

Компилятор не допустит существования двух функций с одинаковыми именами. Да и не эффективно это, ведь функция может содержать несколько сотен строк кода. Поэтому, дабы избежать подобных недоразумений, и используется предварительное объявление:
Code

void b();

void a()
{
  b();
}
...
void b()
{
  a();
}

Проблемы области видимости.

А теперь поговорим о некоторых проблемах для локальных областей видимости. Есть у них один недостаток. Объявленная внутри локальной области переменная существует до тех пор, пока эта область активна. Рассмотрим следующий пример:

Code

int* func()
{
  int* pi;
  int i=55;
  pi = &i;
  return pi;
}

void main()
{
  int* pС;
  pС = func();

}

Сейчас нам необходимо вспомнить об указателях. Функции способны работать с ними так же, как и с любым другим типом данных. В приведенном примере func()
возвращает указатель на переменную i. Вот только сама переменная, по завершении работы функции, перестает существовать. В результате наш указатель указывает на все, что угодно, то есть по старому адресу, только не на существовавшую ранее переменную, а ее значение (55) окажется потерянным.
Хуже всего то, что компилятор никак не сможет помочь вам избежать таких вот ситуаций. Тут уж все в руках программиста.

На этом с видимостью завязываем, и вновь возвращаемся непосредственно к функциям. Ведь узнать нам придется еще очень много.


Windmill 2

WindMill 2D Game Engine
nilremДата: Суббота, 23 Января 2010, 14:52 | Сообщение # 824 | Тема: Вопрос-Ответ - мини вопросы по созданию игр
Просветленный разум
Сейчас нет на сайте
vc, используй указатели.
http://www.rsdn.ru/article/cpp/fastdelegate.xml


Windmill 2

WindMill 2D Game Engine
nilremДата: Суббота, 23 Января 2010, 13:12 | Сообщение # 825 | Тема: Конкурс на GcUp.ru #4
Просветленный разум
Сейчас нет на сайте
Quote (TovTripleZ)
Вопрос конечно глупый, но... разве был третий конкурс?

Конкурс "Средневековый дом" - http://www.gcup.ru/forum/16-1377-1

За оффтоп в теме – бан. Не забывайте, данный раздел жестко модерируется.

Так что... отдыхай 7 дней.


Windmill 2

WindMill 2D Game Engine
nilremДата: Суббота, 23 Января 2010, 12:57 | Сообщение # 826 | Тема: Предлагаю услуги сценариста
Просветленный разум
Сейчас нет на сайте
Почитал Le Grand Secret. Поначалу хотел написать рецензию, но поленился.
Поэтому просто вставил комментарии в текст.
В приложении первая глава с моими комментариями.
И воспринимай их не как критику, а как совет.
Прикрепления: LGS1g.rar (10.0 Kb)


Windmill 2

WindMill 2D Game Engine
nilremДата: Суббота, 16 Января 2010, 17:49 | Сообщение # 827 | Тема: Как делать игры на с++, превратись из новичка в профи
Просветленный разум
Сейчас нет на сайте
Quote (Naigelgog)
На мой вопрос так никто и не ответил.

karuy ответил, и я с ним согласен. Для новичка книга подходящая.


Windmill 2

WindMill 2D Game Engine
nilremДата: Суббота, 16 Января 2010, 15:51 | Сообщение # 828 | Тема: Как делать игры на с++, превратись из новичка в профи
Просветленный разум
Сейчас нет на сайте
Vinchensoo,

Quote (Vinchensoo)
От BOOM: Подробная книга о DX9, необходимы знания C++ скачать книгу

это и есть Горнаков - DirectX 9 уроки программирования на C++


Windmill 2

WindMill 2D Game Engine
nilremДата: Пятница, 15 Января 2010, 23:05 | Сообщение # 829 | Тема: Предложения по улучшению
Просветленный разум
Сейчас нет на сайте
Файлообменники генерируют ссылку для конкретного пользователя под конкретный ip. Так что просьба невыполнимая.

Windmill 2

WindMill 2D Game Engine
nilremДата: Четверг, 14 Января 2010, 23:35 | Сообщение # 830 | Тема: Текстуры на заказ
Просветленный разум
Сейчас нет на сайте
ЖiR@F, некрофил.

Windmill 2

WindMill 2D Game Engine
nilremДата: Четверг, 14 Января 2010, 16:16 | Сообщение # 831 | Тема: Нужна помощь в создании игры. Crystal of Surius.
Просветленный разум
Сейчас нет на сайте
Отличная графика! Красивая, яркая.
Мне бы такую для игр на кпк.

Если тебе не надоест учить гм, то получится хорошая(как минимум в плане графики) игра.


Windmill 2

WindMill 2D Game Engine
nilremДата: Четверг, 14 Января 2010, 15:43 | Сообщение # 832 | Тема: Уроки по созданию игр на Delphi
Просветленный разум
Сейчас нет на сайте
Quote (TrueIfrit)
А я книгу писать собирался

А ты все равно пиши, или с Sharom кооперируйся.

Quote (Sharom)
Если можешь укажи какие ошибки

На ошибки и недостатки тебе укажут ученики, например, skorpi уже написал.


Windmill 2

WindMill 2D Game Engine
nilremДата: Четверг, 14 Января 2010, 14:55 | Сообщение # 833 | Тема: Visual Studio Express Edition какую выбрать!!! O_o
Просветленный разум
Сейчас нет на сайте
Quote (Bender1911)
Я тоже эту версию щас качаю с лекарством, осталось найти visual c++ с лекарством

Microsoft Visual Studio 2008 - это среда разработки, т.е. попросту говоря программа в которой можно писать на нескольких языках, в том числе на С++.
То есть отдельно искать visual c++ тебе не нужно.

"Джесс Либерти - Освой самостоятельно С++ за 21 день" поначалу ничем тебе не поможет, поскольку там совершенно не описывается как работать со средой Microsoft Visual Studio.
Нужто что то вроде - "Пахомов. С/С++ и Ms Visual С++ 2008 для начинающих"


Windmill 2

WindMill 2D Game Engine
nilremДата: Четверг, 14 Января 2010, 14:38 | Сообщение # 834 | Тема: Уроки по созданию игр на Delphi
Просветленный разум
Сейчас нет на сайте
Уроки, честно говоря, слабые.
В процессе чтения возникают элементарные вопросы, которые автор должен был сам объяснить по ходу урока.
Это значит, что, либо автор сам достаточно не разбирается в теме, либо он просто не смыслит, как нужно «выдавать» материал для обучения. (Например, совершенно не поясняются используемые термины, что для начинающего критично.)
Надеюсь что второе.

В общем, начинание похвальное.
Продолжай.
Эту тему закрепляю.


Windmill 2

WindMill 2D Game Engine
nilremДата: Воскресенье, 10 Января 2010, 23:04 | Сообщение # 835 | Тема: Оцените сценарий Про 4 стихии
Просветленный разум
Сейчас нет на сайте
За сценарий - 0.
Так как его здесь нет.
А есть просто слабое описание игрового процеса.
Я так обычно набросок сюжета(квестов) делаю. Чтобы потом его охудожествить, прикинуть, как сделать разбивку на диалоги.
Продумать - что по чем.
Так что 10 тебе за жел


Windmill 2

WindMill 2D Game Engine
nilremДата: Четверг, 31 Декабря 2009, 17:24 | Сообщение # 836 | Тема: лучшая стратегия года
Просветленный разум
Сейчас нет на сайте
TrueIfrit,
Ты в Disciples III играл?


Windmill 2

WindMill 2D Game Engine
nilremДата: Четверг, 31 Декабря 2009, 10:37 | Сообщение # 837 | Тема: лучшая игра года
Просветленный разум
Сейчас нет на сайте
Dragon Age

ps: А худшая - Disciples 3


Windmill 2

WindMill 2D Game Engine
nilremДата: Среда, 30 Декабря 2009, 22:27 | Сообщение # 838 | Тема: Disciples 3
Просветленный разум
Сейчас нет на сайте
Спешу поделиться хорошей новостью. В игру уже можно играть не покупая), ибо платить за бета-тест даже 1 доллар слишком много. А просили аж 15.
Нехорошие разработчики, решившие устроить платный тест, теперь получили облом, а старфосщики, утверждавшие, что защиту не сломают минимум три месяца, круто попали.


Windmill 2

WindMill 2D Game Engine
nilremДата: Среда, 30 Декабря 2009, 08:55 | Сообщение # 839 | Тема: Вымышленный мир... Какой самый любимый?
Просветленный разум
Сейчас нет на сайте
Забытые Королевства ака Forgotten Realms.
По этому сеттингу в далеком 1974 году вышла настольная ролевая игра Dungeons & Dragons.
Кроме того именно в Забытых королевствах происходят действия хитовых серий компьютерных игр: Baldur's Gate, Neverwinter Nights и Icewind Dale.

http://ru.wikipedia.org/wiki/Forgotten_Realms


Windmill 2

WindMill 2D Game Engine
nilremДата: Среда, 30 Декабря 2009, 00:02 | Сообщение # 840 | Тема: Украинская игровая индустрия
Просветленный разум
Сейчас нет на сайте
Quote (FRANKENSTEIN)
Я только одну игру украинскую занаю

Ты и вторую должен знать - S.T.A.L.K.E.R. называется.


Windmill 2

WindMill 2D Game Engine
Поиск:

Все права сохранены. GcUp.ru © 2008-2025 Рейтинг