Популярные услуги

Все письменные КМ под ключ за 3 суток! (КМ-6 + КМ-7 + КМ-8 + КМ-9 + КМ-10)
КМ-6. Динамические массивы. Семинар - выполню любой вариант!
КМ-2. Разработка простейших консольных программ с использованием ООП + КМ-4. Более сложные элементы ООП - под ключ!
Любая задача на C/C++
Одно любое задание в mYsql
Сделаю ваше задание: Лабораторная работа на Pascal / Lazarus
Любой тест по базам данных максимально быстро на хорошую оценку - или верну деньги!
Любой реферат по объектно-ориентированному программированию (ООП)
Повышение уникальности твоей работе
Оба семинара по программированию под ключ! КМ-2. Разработка циклических алгоритмов + КМ-3. Функции и многофайловые программы в Си

Лекция 10

2021-03-09СтудИзба

                                                Лекция 10

По настоящему переменный набор параметров поддерживают только C/C++ и С#.

Пример:

                        printf(const char *,…);

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


                                   va_list

                                   va_end

Рекомендуемые материалы

                                   va_start

           

            Общая идея: все параметры, которые загружаются в переменную list совместимы с целочисленными ТД или с указателями, а интерпретация остаётся на совести программиста, например, в printf(); параметры выбираются из стека в зависимости от информации, которая находится в процедуре в строке.

            Это решение сразу стало критиковаться в связи с ненадёжностью, так как в этом случае ни компилятор, ни кто-то ещё не в состоянии произвести ни динамический, ни статический контроль. Зачем такая конструкция была введена (В С++ понятно из-за концепции совместимости)? Её наличием легко была решена проблема, связанная с вводом-выводом.

Даже в языках, не поддерживающих переменный список параметров, есть процедуры, которые реально поддерживают переменный список. Например, стандартный Паскаль: write(…); - это как бы псевдопроцедура (процедура, которую нельзя написать на самом языке), про которую компилятор знает достаточно много, отсюда у неё и получается переменный список параметров. Здесь же два подхода: вопрос удобства программирования (вывод разнородной информации) или языковая концептуальная целостность, упрощения языка и повышения надёжности. Такой подход принят в семействе языков, опирающихся на язык Паскаль: Модула 2, Оберон и так далее – нет процедур с переменным числом параметров. Надо вывести конструкцию вида: строка-int-строка. Оберон:

            IMPORT InOut;

Это стандартный библиотечный модуль, содержащий все процедуры ввода-вывода.

            InOut.WriteString(“count = “);

            InOut.WriteInt(cnt);

            InOut.WriteCh(‘.’);

            InOut.WriteLn;

А на Си: printf(“count = %d.n”, cnt); и если мы забудем указать переменную cnt, то формально ошибки не будет, но будет напечатано какое-то число из стека (оно будет проинтерпретировано как целое число, загружающая программа загружает параметры в стек и их выгружает и, формально, никакого слома программы не произойдёт), чего при описанной выше системе ввода-вывода в принципе не может быть (мы либо просто забываем cnt, либо никаких ошибок быть не может). Но с другой стороны здесь язык становится несколько проще, и программировать становится удобней.

В С++ у нас есть класс CString, и его метод S.Format(“…”,…); реализует sprintf();. Многие программисты продолжают придерживаться этого подхода, следовательно, остаётся ненадёжность.

Один из подходов – статически параметризуемые ТД. Мы уже говорили о статическом перекрытии операций. Это некоторые процедуры с одинаковыми именами, но разным типом и числом (ограниченным, т.е. псевдобесконечного списка параметров написать нельзя) параметров. В Аде таким образом реализована процедура PUT(    ,…).

В С++ богатый набора встроенных операций, которые можно переопределять (в том числе и стандартные знаки операций).

Надёжная библиотека ввода-вывода:

            iostream.h

(“<<”, “>>” операции сдвига вправо, влево)


                 << e           -           вывод объекта в канал

            ch >> obj        -           операция ввода из канала

Есть ТД iostream (поток ввода-вывода). Каждая операция возвращает:

iostream& operator << (iostream& int, T x);

iostream& operator >> (iostream& int, T x);

И для каждого ТД, для которого хотим определить ввод-вывод, переопределяем эти операции (как правило, на базе введённых в iostream ТД). А возвращаемое значение – это ссылка на первый аргумент. Выполняется слево-направо. Получается мощная и красивая схема:

            cout << “count = “ << cnt << ‘.’ <<endl;

Надёжно и читабельно. Также существуют объекты специального типа, которые служат модификаторами ввода-вывода, они позволяют управлять форматом, например, endl, модификаторы, позволяющие изменять ширину поля и т.д.. Вся мощь библиотеки stdio.h сохранена и никаких проблем с надёжностью нет. Но это достигнуто за счёт переопределения стандартных знаков операций, и это наглядно. Но язык усложняется, за счёт введения достаточно нетривиальных вещей.

Альтернативный подход: концепция динамической идентификации типов в C#. Наблюдение за процедурами с переменным числом параметров показывает, что есть некий список параметров, который мы можем проинициализировать. А макросы va_start, va_end позволяют нам итерировать по этому списку. Проблема Си/С++ в том, что мы не знаем, что же на самом деле подставляется в эти параметры и из-за этого соответствующая ненадёжность.

Если у нас язык таков, что Object – базовый ТД, из которого происходят элементы всех классов (все объекты), к нему можно свести любой тип, с помощью механизма обёрток (boxing). В Delphi - это TObject, в Java – Object, а в C# - object. К тому же в этих ЯП поддерживается динамическая идентификация типа (проверка является ли этот объект объектом данного типа, иначе генерируется исключение).

ТД ParamArray – представляет переменный список параметров в качестве массива, каждый из элементов которого - объект, типа Object, который на самом деле есть ссылка на объект класса или обёртка (псевдообъект) структуры или простого значения. У обрабатывающей процедуры последний параметр ParamArray и она работает с ним как с массивом, и вывод осуществляется в зависимости от того, что за элементы в нём. Тут нет никакого статического контроля, но есть динамический. Но кто упрятывает параметры в обёртки. Если мы делаем это сами, то это можно реализовать на любом ЯП самим описав ТД ParamArray и т.д.. Это неудобно и мы теряем все преимущества переменного списка параметров. Очевидно, что это должен делать не программист. В C# это встроенный ТД (ParamArray).

Общее правило - компилятор ищет точное соответствие:

            void f(T1 x);

            void f(T1 x, T2 x);

void f(T1 x, ParamArray objc);

T1 a, T2 b, T1 c, T3 d

f(a);

f(a, b);

f(a, d);

Пусть T1, T2 и T3 никак не связаны (С наследованием чуть сложнее). Следовательно, точное соответствие не было найдено и он выбирает третий вариант функции, автоматически формируя массив из одного элемента.

f(c, a, b, d);

В данном случае формируется массив из трёх элементов. Таким образом построена библиотека ввода-вывода (Паскаль: WriteLn(форматная строка, ParamArray);), с контролем типов объектов.

            В Java более простые правила. Ввод-вывод строк: у каждого объекта (он происходит от ТД Object) есть метод toString, который и применяется по необходимости (компилятор смотрит на контекст):

                        String s;

                        int i;

                        s+i;

            Где ”+” – операция конкатенации для строк. i завертывается в оболочку класса Int (который происходит от ТД Object), вызывается метод toString и к строке s добавляется символьное представление i. Следовательно, получаем одну процедуру вывода: вывод строки, а её аргумент – это совокупность объектов, для которых вызывается метод toString. Для нового класса переопределяем метод toString. Похоже на прошлый подход.

            ParamArray и прочее в C# появилось, чтобы программисты с С++ легче переходили на С#, а во-вторых в некоторых случаях (например, когда мы пишем процедуры обработчики событий) у нас приходит переменный список параметров и априори мы не можем предсказать какой он будет. Но оно надёжно за счёт динамической идентификации типов. В С++ это тоже есть, но там нет того, что каждый объект происходит от общего и поэтому там ограниченные возможности подобного рода решений.

                                               Глава 5

Определение новых ТД

П.1. Концепция уникальности ТД                            (Основные проблемы)

            Более точная формулировка понятий, строгая и сильная типизация. Их постоянно мешают и непонятно, то ли Си со строгой, то ли с сильной типизацией. Мы определим концепцию уникальности типов, и язык имеет тем боле сильную или строгую типизацию, чем он ближе к этой концепции. Лучше всех удовлетворяет язык Ада. 4 пункта:

1)Каждый объект данных имеет ТД, и этот тип единственен и статически определим.

Типы данных это как бы классы эквивалентности, на которые разбиваются все объекты. То есть не может быть ни константы, ни переменной без ТД. Говоря «статически определим» имеем в виду, что, глядя на текст программы, мы всегда можем определить к какому ТД он принадлежит (Паскаль: по виду константы определяли её тип), иначе – сообщение об ошибке. В Аде у нас есть уточнитель типа данных (например, когда константа принадлежит к нескольким перечислимым ТД): T’e и компилятор всегда знает всё о ТД. Такая концепция во всех традиционных ЯП, и именно из-за этого они с одной стороны являются достаточно эффективными (компилятор может оптимизировать). Но недостатком такого подхода является то, что все ТД разбиваются на непересекающиеся классы, и теряется гибкость.

2)Типы эквивалентны тогда и только тогда, когда их имена совпадают.

            Это именная эквивалентность типов, все традиционные современные ЯП поддерживают её (с небольшими отклонениями). Существует ещё и структурная эквивалентность (в первых ЯП, когда типизации уделяли мало внимания речь шла именно о ней – PL|I): ТД совпадают, если их структуры совпадают. От структурной эквивалентности отказались (так как при нём тип – это набор значений), сейчас главное в типе – набор операций (даже на основе одной и той же структуры). Так же, если мы рассмотрим сложные ЯП, структурная эквивалентность двух рекурсивных типов может быть алгоритмически неразрешима. Но иногда отступают от именной эквивалентности: в большинстве старых ЯП есть синонимия типов, например, в Паскале:

                        Type T = integer;

            Типы T и integer теперь синонимы. Данные одного типа могут появляться везде, где появляются данные другого типа и наоборот. В Си и C++ то же (typedef). Здесь типы эквивалентны, если имена совпадают, либо они синонимичны. Это было введено из соображений лучшей документируемости. С точки зрения надёжности, такая конструкция даёт мало. Например, пусть у нас есть три типа данный, синонима integer: Length, Width, Square. Но мы можем к площади прибавить ширину или длине присвоить отрицательное значение.

            В Аде (производные типы, введём с помощью специальной концепции нового ТД, который наследует всё множество значений и всё множество операций, эти величины):

                        type Length is new Integer;

                                Width  is new Integer;

                                Square is new Integer;

            Это совершенно разные типы и их нельзя смешивать.

3)Каждый ТД имеет свой набор операций с фиксированным набором параметров.

            Имеется в виду, что фиксированы их число и типы. Переменный список параметров – отклонение от этой концепции.

4)Неэквивалентные ТД несовместимы по операциям.

            Тип данных int имеет один набор операций (+, -, *). В Аде у типов Length, Width и Square одни и те же операции, но так как они не эквивалентны из-за разных имён, то их смешивать нельзя. Но мы можем переопределить операцию *:

                        Length*Width=Square

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

            ЯП, полностью поддерживающий 1)-4) – надёжный. Эта концепция по языку Ада, а другие ЯП ослабляют свойства этой типизации и менее надёжны. В Аде нет синонимии типов, но мы можем переопределять операции, также полностью запрещено неявное (вставляемое компилятором) приведение типов (нарушение 4)):

                        v:=e

            Типы v и e должны совпадать. А компилятор может вставлять или не вставлять контроль. Но между подтипами неявное преобразование возможно, но оно может сопровождаться контролем:

                       

                        A: Tarr(1..10);

                        B: Tarr(2..11);

                        A := B;

           

            Присвоение корректно, так как совпадают динамические атрибуты A’Length и B’Length. Если компилятор не может определить их длины (А и В – формальные параметры) он там вставляет проверку на динамический атрибут «длина». В других ЯП неявные преобразования, которые ухудшают надёжность.

            Иногда нарушается именная эквивалентность типов. В Си неявно пролезает структурная эквивалентность типов, если используем одинаковые структурные ТД в разных модулях:


m1.c                struct S { int a; int b; }

                        void f(struct S x);

m2.c                struct S1 { int x; int y; }

                        extern void f(struct S1 y);

                        …

                        struct S1 a;

                        f(a);

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

            В С++ эта проблема решена за счёт кодирования имён. Страуструп предложил приём: для каждой внешней процедуры f вместо соответствующего имени, которое дал пользователь, на самом деле ей присваивается закодированное имя, которое кодирует и имена параметров и возвращаемый тип функции, возможно, не имеющее никакой лексической нагрузки, что-то вроде f@#_…, но гарантируется уникальность индификатора. И в данном примере ошибка будет найдена, не на стадии трансляции (так как она раздельная и независимая), а на этапе сборки программы.

Концепция типов ориентирована на надёжность и эффективность. Эффективность концепции в том, что поведение ТД определяется строго операциями (и, как следствие, контроль и оптимизация компилятором), но традиционные ЯП страдают с точки зрения гибкости.

            Тут возникают две проблемы:

1)Множественность ролей с точки зрения ТД (Янус-проблема).

            Тип характеризует содержательную роль объекта ЯП, но этих содержательных ролей может быть много, а мы вынуждены одну из них выделять как главную и использовать её как единственную роль. Традиционные ЯП не знают адекватных средств решения проблемы. Единственная возможность – использование объединений:

                                               T1

                                               .

                        union  T          .

                                               .

                                               TN

            Но это тоже не адекватное средство как с точки зрения надёжности, так и с точки зрения удобства программирования.

            Адекватное решение только в ООЯП.

2)Полиморфизм (Множественность форм).

            Тесно связано с 1) – это множественность форм с точки зрения набора операций. Одной операции может соответствует, вообще говоря, множество форм. Например, операция сложения «+»: целое сложение, вещественное сложение, конкатенация двух строк, сложение двух векторов или двух матриц. Эта проблема разрешается с помощью статического перекрытия операций. Или метод Draw(); у объекта, но своя форма для каждого ТД (вспомним курс машинной графики). Есть: статически полиморфные операции (на этапе трансляции знаем множество ТД и для каждого ТД известно какую форму приобретает операция «+») статически полиморфные языки в некотором смысле средство решения этой проблемы, но в некоторых случаях не можем предвидеть, какого типа будет объект. Например, графическая библиотека использует зафиксированный на этапе трансляции набор объектов. Компилятор выберет нужный Draw();. Если мы хотим добавить в библиотеку новый объект, то мы должны добавить новый ТД, видимо, с помощью соответствующего объединения и изменить обращение к процедуре Draw(); во всей библиотеке, следовательно, серьёзные изменения в исходном коде и перетрансляция всей библиотеки. В традиционных ЯП только статический полиморфизм, основанный на понятии объединения и перекрытия имён во время трансляции. По настоящему решается в ООЯП, где есть динамическое связывание методов (виртуальный метод С++, все методы в Java и C#).

П.2. Логический модуль

            ТД = множество операций + множество значений

            С современной точки зрения ТД – это прежде всего множество операций. Множество операций – это набор процедур и функций (играет основополагающую роль), а множество значений – это структура данных или совокупность структур данных (например на основе базисных ТД или понятия наследования). Следовательно, для определения ТД, мы должны ввести нобор структур данных типа и множество операций, присвоив этому одно имя.

            Новые ТД можно определять и на Паскале и на Си, но там нет конструкторов, подчёркивающих дуализм.

            С этой точки зрения ЯП разделяются на:

Модульные языки: вводится понятие контейнера, объединяющего множество операций и множество значений. Модуль не является ТД. Модульные языки: Ада, Модула 2, Оберон.

            Языки с классами: класс – это сущность, содержащая данные и обладающая набором операций. Класс как бы является универсальной обёрткой, в отличие от модуля, который является более общей конструкцией, и его основное предназначение - определять ТД. Но и может использоваться как модуль. В языки с классами понятие модуля пролезает, но оно уже несколько отлично: это понятие пакета и пространства имён. Языки с классами: C++, Java, C#.

            Delphi похож на Модулу 2 (по модульной структуре), но и является языком с классами, в этом проявляется его двойственная природа (он был создан на основе Турбо Паскаля, но в него были добавлены и другие концепции).

            Модульные ЯП.

            Есть понятие unit. В Delphi:

unit имя;

interface

      [набор объявлений]

implementation

                  реализация

end имя.

            Доступно другим модулям с помощью конструкции uses. Это набор объявлений типов, переменных и процедур. Все такие процедуры должны быть реализованы в implementation.. Каждый модуль представляет сервис – набор услуг, описанных в интерфейсе. А вспомогательные процедуры и ТД - описываются в реализации. Модуль может описывать 0 (набор констант), 1 или несколько ТД. Модуль более общее понятие, чем ТД, но, в частности, может служить для описания ТД.

            Надо иметь сервис для доступа к этому модулю. У нас есть клиентские модули, которые используют сервис и сервисные модули, которые его предоставляют. Все клиентские модули используют конструкцию: uses список имён модулей. Это значит, что все имена, описанные в интерфейсной части становятся непосредственно видимыми в клиентском модуле. Два базовых понятия, связанных с понятием логического модуля: unit и пространство имён.

            Модули структурируют пространство имён:


                        M1      M2      M3      …

            Имена: описанные в интерфейсе и  реализационные. Глобальное пространство имён состоит из имён описанных в интерфейсе. Но непосредственно видимыми (мы их можем употреблять без каких-то пояснений) являются имена модулей. В Delphi программи состоит из набора юнитов и хменанепосредственно видимы. Конструкция uses, .которую мы можем употреблять либо в начале интерфейсной части, либо в начале реализационной, делает все имена из соответствующего модуля непосредственно видимыми. Но имена могут конфликтовать. И помнить все имена из всех  модулей практически невозможно.  Поэтому  говорим,,чоменавдмытолько потенциально. Для решения этой проблемы в ЯП было введено понятие уточнения (квалификации) имени – это конструкция, указывающая к какому модулю относится имя.

            Qualifying в Delphi: имя_модуля.имя. Имена модулей должны быть разные. Как следствие, на Delphi супербольших проектов не пишут.

Есть клиентские и сервисные модули. Клиентские являются и сервисными, за исключением главной программы., которая является только клиентским модулем и её вид несколько иной:


            program имя;

                        uses     …;

                                   объявления

                        begin

                                   …

                        end имя.

В Модуле 2 сделано гибче. В Delphi одна физическая единица unit (один файл) разбивается на две логические части, а тут и интерфейс и реализация – это отдельные модули (файлы):


            DEFINITION MODULE имя;

                        набор объявлений

            END имя.

Для него может существовать модуль реализаций (должны быть реализованы все процедуры из модуля объявлений и тут могут быть вспомагательные ТД и процедуры):


            IMPLEMENTATION MODULE имя;

                        реализация

            END имя.

Есть три вещи: определение (или интерфейсная часть), реализация и использование (сокращённо - OPU). В традиционных ЯП эти три вещи предельно физически разделены:

            stacks.MOD   -           реализация

            stacks.DEF     -           определение

            import             -           использование

Модула 2:

1)         IMPORT список имён модулей

В Delphi надо было уточнать только конфликтующие имена. А в Модуле 2 все эти имена становятся потенциально видимыми и их надо уточнять.

            IMPORT InOut;

            …

            InOut.Writeln             -           и так с любыми именами из модуля InOut.

Иначе компилятор скажет, что не знает имени Writeln.

Есть альтернативная форма:

2) FROM имя модуля IMPORT список имён

Эти имена становятся непосредственно видимыми:

            FROM InOut IMPORT writeln, writestring;

            …

            writeln;

            writestring(s);

При конфликте имён надо их квалифицировать.

Это разделение в Модуле 2 сделано для удобства программистов: мы пишём один раз, а читаем много. Все стандартные имена описаны по крайней мере в help, но имена из модулей сторонних поставщиков нигде не описаны. Когда у нас есть модуль описаний, намного удобнее читать, потому, что если описание перемешано с реализацией, нам приходится пролистывать огромные тексты процедур. Именно по этой причине Borland поставлял все версии Паскаля с утилитой типа grep, которая как раз и позволяла выбирать интересующие нас вещи. В Модуле 2 имя – либо стандартное (их мало так как описание языка всего 40 страниц), либо описано в этом модуле, либо опиисано в конструкции FROM.

            Оберон: Запрещена конструкция FROM…

IMPORT список имён модулей

И  все имена из других модулей надо уточнять: тяжело писать, но легко читать.

Все эти понятия модульности сформировались ещё в 70-е годы.

Разделение OPU – это хорошо и содержательно, так как программист не обращает внимания на реализацию и её детали, а на определение и то, что ему нужно от каждого конкретного сервиса. Но современные ЯП отходят от разделения O и P. OP и U разделены – это аксиома. Но O и P в современных ЯП слиты воедино.

Оберон (наследник Модулы):

            MODULE имя;

                        IMPORT

                                   …

                                   объявления

                        begin

                                   операторы

                        end имя.

Просто в Обероне все имена которые попадают в интерфейсную часть должны сопровождаться *.

Пример:

            MODULE Stacks;

                        TYPE Stack *=

                                   record

                                               …

                                   end;

                        procedure Push*(var S:Stack; X:INTEGER);

            END Stacks.

Это неудобно: описание и тела процедур в одном месте, а нас могут интересовать всего лишь параметры. Но тот же подход в Java и C#, то есть там тоже слиты воедино O и P (В С++ могут быть разделены, а здесть требуется, что они должны быть объединены). А концепция, чтобы ничего не было потеряно с точки зрения удобства пользователя, здесь не пострадала, так как эти ЯП уже часть системы программирования.

Оберон: Тут есть простейшая утилита, автоматически генерирующая интерфейс (так называемый псевдомодуль), расписывающий все публичные имена , являющиеся частью системы программирования:

Информация в лекции "9 Оператор присваивания" поможет Вам.

            DEFINITION имя;

                        type stack = …;

                        procedure push(…);

                        …

            END имя;

В Java есть утилита Javadoc, генерирующая интерфейсы класов (плюс вставляет ссылки на документации классов из других модулей,  на классы которых  ссылаюся  параметры данных классов).

Свежие статьи
Популярно сейчас
Почему делать на заказ в разы дороже, чем купить готовую учебную работу на СтудИзбе? Наши учебные работы продаются каждый год, тогда как большинство заказов выполняются с нуля. Найдите подходящий учебный материал на СтудИзбе!
Ответы на популярные вопросы
Да! Наши авторы собирают и выкладывают те работы, которые сдаются в Вашем учебном заведении ежегодно и уже проверены преподавателями.
Да! У нас любой человек может выложить любую учебную работу и зарабатывать на её продажах! Но каждый учебный материал публикуется только после тщательной проверки администрацией.
Вернём деньги! А если быть более точными, то автору даётся немного времени на исправление, а если не исправит или выйдет время, то вернём деньги в полном объёме!
Да! На равне с готовыми студенческими работами у нас продаются услуги. Цены на услуги видны сразу, то есть Вам нужно только указать параметры и сразу можно оплачивать.
Отзывы студентов
Ставлю 10/10
Все нравится, очень удобный сайт, помогает в учебе. Кроме этого, можно заработать самому, выставляя готовые учебные материалы на продажу здесь. Рейтинги и отзывы на преподавателей очень помогают сориентироваться в начале нового семестра. Спасибо за такую функцию. Ставлю максимальную оценку.
Лучшая платформа для успешной сдачи сессии
Познакомился со СтудИзбой благодаря своему другу, очень нравится интерфейс, количество доступных файлов, цена, в общем, все прекрасно. Даже сам продаю какие-то свои работы.
Студизба ван лав ❤
Очень офигенный сайт для студентов. Много полезных учебных материалов. Пользуюсь студизбой с октября 2021 года. Серьёзных нареканий нет. Хотелось бы, что бы ввели подписочную модель и сделали материалы дешевле 300 рублей в рамках подписки бесплатными.
Отличный сайт
Лично меня всё устраивает - и покупка, и продажа; и цены, и возможность предпросмотра куска файла, и обилие бесплатных файлов (в подборках по авторам, читай, ВУЗам и факультетам). Есть определённые баги, но всё решаемо, да и администраторы реагируют в течение суток.
Маленький отзыв о большом помощнике!
Студизба спасает в те моменты, когда сроки горят, а работ накопилось достаточно. Довольно удобный сайт с простой навигацией и огромным количеством материалов.
Студ. Изба как крупнейший сборник работ для студентов
Тут дофига бывает всего полезного. Печально, что бывают предметы по которым даже одного бесплатного решения нет, но это скорее вопрос к студентам. В остальном всё здорово.
Спасательный островок
Если уже не успеваешь разобраться или застрял на каком-то задание поможет тебе быстро и недорого решить твою проблему.
Всё и так отлично
Всё очень удобно. Особенно круто, что есть система бонусов и можно выводить остатки денег. Очень много качественных бесплатных файлов.
Отзыв о системе "Студизба"
Отличная платформа для распространения работ, востребованных студентами. Хорошо налаженная и качественная работа сайта, огромная база заданий и аудитория.
Отличный помощник
Отличный сайт с кучей полезных файлов, позволяющий найти много методичек / учебников / отзывов о вузах и преподователях.
Отлично помогает студентам в любой момент для решения трудных и незамедлительных задач
Хотелось бы больше конкретной информации о преподавателях. А так в принципе хороший сайт, всегда им пользуюсь и ни разу не было желания прекратить. Хороший сайт для помощи студентам, удобный и приятный интерфейс. Из недостатков можно выделить только отсутствия небольшого количества файлов.
Спасибо за шикарный сайт
Великолепный сайт на котором студент за не большие деньги может найти помощь с дз, проектами курсовыми, лабораторными, а также узнать отзывы на преподавателей и бесплатно скачать пособия.
Популярные преподаватели
Добавляйте материалы
и зарабатывайте!
Продажи идут автоматически
5155
Авторов
на СтудИзбе
439
Средний доход
с одного платного файла
Обучение Подробнее