C++ для начинающих

Вопросы написания собственного программного кода (на любых языках)

Модератор: Olej

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C++ для начинающих

Непрочитанное сообщение Olej » 15 авг 2015, 11:42

Очень много примеров и задач на C++ осталось в старой теме: примеры задач при изучении C++.
Там всё можно скачать и более-менее прокомментировано.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C++ для начинающих

Непрочитанное сообщение Olej » 15 авг 2015, 13:42

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

Рекомендую.

P.S. Относительно приматов, это совсем не сразу понятно из ресурса - это у них такой профессиональный юмор ... кондовый :lol: : приматы - ПРИкладныеМАТематикЫ ... факультет у них такой приматов ;-)

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C++ для начинающих

Непрочитанное сообщение Olej » 25 авг 2015, 12:27

Вот хорошая задача попрактиковаться:
- есть строка, в которой записано произвольное скобочное выражение (скобки (), [], или {})
- написать функцию check(), которая проверяет правильность этого скобочного выражения:

Код: Выделить всё

check("a(b)") -> true
check("[{}]") -> true
check("[(]") -> false
check("}{") -> false
check("z([{}-()]{a})") -> true
check("") -> true
Причём здесь интересны 2 варианта: когда строка задана в стиле C как char*, и когда это С++ тип string:

Код: Выделить всё

bool check( char* );
bool check( string );
Ну, наконец, здесь можно попрактиковаться в переопределённых функциях C++, и записать эту задачу единым тестовым файлом, что-то типа:

Код: Выделить всё

#include <iostream>
#include <cstring> 
using namespace std;

bool check( string str ) {
   bool result = true;
   // реализация: result = ...
   return result;
}

bool check( char* str ) {
   bool result = true;
   // реализация: result = ...
   return result;
}

int main( int argc, char **argv ) {
   string test[] = {
      "a(b)", "[{}]", "[(]", "}{", "z([{}-()]{a})", ""
   };
   for( int i = 0; i < sizeof( test ) / sizeof( test[ 0 ] ); i++ )
      cout << "'" << test[ i ] << "'" << " ==> "
           << ( check( test[ i ] ) ? "true" : "false" ) << " ... "
           << ( check( test[ i ].c_str() ) ? "true" : "false" )
           << endl;
   return 0;
}
Остаётся только вписать то, что закомментировано под "реализация" :lol: ...

Код: Выделить всё

olej@nvidia ~/tmp $ g++ lex.cc -o lex
olej@nvidia ~/tmp $ ./lex
'a(b)' ==> true ... true
'[{}]' ==> true ... true
'[(]' ==> true ... true
'}{' ==> true ... true
'z([{}-()]{a})' ==> true ... true
'' ==> true ... true
Ну и последним хорошим дополнением к задаче было бы: записать это как можно более кратким кодом.

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C++ для начинающих

Непрочитанное сообщение Olej » 25 авг 2015, 17:51

Olej писал(а): - написать функцию check(), которая проверяет правильность этого скобочного выражения:
Интерес этой задачи в том, что при ... более глубоких потребностях анализа скобочного выражения, его всё-равно придётся разбивать на вложенные токены, тем или иным способом.
Способов таких много ... поэтому задача (разных решений) остаётся.

Но если задачу сформулировать именно так, в усечённом виде, что нужно только проверить синтаксическую правильность, но не делать разбора получаемых токенов, то её можно и решить в таком же кастрированном стиле:
- заталкивать в стек открывающий разделитель текущего найденного токена;
- искать ближайший закрывающий разделитель текущего найденного токена...
- ... если он не совпадает с открывающим - это ошибка структуры
- ... если совпадает, то это завершение токена - выталкиваем из стека закрывающий символ-разделитель и продолжаем последовательный разбор строки.

Вот что-то такое:

Код: Выделить всё

#include <iostream>
#include <cstdint>
#include <cstring> 
#include <vector> 
using namespace std;

const char *delim[ 2 ] = { "([{", ")]}" };

bool check( const string& s ) {
   string stack = "";
   for( uint i = 0; i < s.size(); i++ ) {
      const char c = s[ i ];
      if( index( delim[ 0 ], c ) != NULL ) {
         stack.push_back( c );
         continue;
      }
      const char *z;
      if( NULL == ( z = index( delim[ 1 ], c ) ) )
         continue;
      if( stack.empty() ||
          stack.back() != delim[ 0 ][ z - delim[ 1 ] ] )
         return false;
      stack.pop_back();
   }
   return stack.empty();
}

bool check( const char* p ) {
   vector<char> stack( 0 );
   for( ; *p != '\0'; p++ ) {
      if( index( delim[ 0 ], *p ) != NULL ) {
         stack.push_back( *p );
         continue;
      }
      const char *z;
      if( NULL == ( z = index( delim[ 1 ], *p ) ) )
         continue;
      if( stack.empty() ||
          stack.back() != delim[ 0 ][ z - delim[ 1 ] ] )
         return false;
      stack.pop_back();
   }
   return stack.empty();
}

int main( int argc, char **argv ) {
   string test[] = { 
      "a(b)", "[{}]", "[(]",
      "}{", "z([{}-()]{a})", "",
      "a( b{c) d}e", "a(b{c[d]e}f)g", "a(c[c]{d(e)})f"
   };
   for( uint i = 0; i < sizeof( test ) / sizeof( test[ 0 ] ); i++ )
      cout << "'" << test[ i ] << "'" << " ==> " 
           << ( check( test[ i ] ) ? "true" : "false" ) << " ... "
           << ( check( test[ i ].c_str() ) ? "true" : "false" )
           << endl;
   return 0;
}

Код: Выделить всё

olej@nvidia ~/2015_WORK/in.WORK/SchoolCPP/brackets $ ./lexv 
'a(b)' ==> true ... true
'[{}]' ==> true ... true
'[(]' ==> false ... false
'}{' ==> false ... false
'z([{}-()]{a})' ==> true ... true
'' ==> true ... true
'a( b{c) d}e' ==> false ... false
'a(b{c[d]e}f)g' ==> true ... true
'a(c[c]{d(e)})f' ==> true ... true
Вложения
lexv.cc
(1.5 КБ) 328 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C++ для начинающих

Непрочитанное сообщение Olej » 26 авг 2015, 15:56

Olej писал(а): ... доктор Мазурок Игорь Евгеньевич и его студенты...
Если отбросить некоторую ... претенциозность названия сайта о приматах, то очень приличная подборка задач и решений.
Этот ресурс чем мне очень нравится (IMHO)?
Своей идеологией обучения языку C++ - Hello, World!
Изображение
В качестве базового языка программирования был выбран С++. Выбран не потому, что он наиболее подходит или наиболее удобен для начального изучения программирования. Просто я твердо убежден, что потенциальный профессиональный программист вынужден начинать именно с этого языка. Достаточно давно так сложилось, что без уверенного знания С++ практические невозможно стать серьёзным специалистом в области программирования и разработки программного обеспечения. Если бы у меня действительно был выбор, я скорее всего отклонился от верного курса и выбрал Java или Scala, или даже продемонстрировал для начала воздушный цирк Monty Python. Но выбора нет, нужно делать как следует делать…

Код: Выделить всё

#include <iostream>
int main() {
	printf("Hello, World!");
	return 0;
}
Этот суржик как раз и поясняет наши ближайшие планы – мы будем изучать язык программирования Си, но только в той его части, которая допустима и в С++ 11. При этом вполне допустимо (но не особенно поощряется) использование “настоящего” Си. Делается это в надежде принести всю возможную пользу от изучения языка программирования Си и создать как можно меньше “крепатур“, затрудняющих переход на объектные и почти функциональные рельсы С++.
Они ломают тот сложившийся везде идиотизм, когда лекторами в университетах по C++ являются вчерашние аспиранты, не представляющие языка C как базы C++, и излагающие C++ через задницу (через MS Visual Studio), как будто до этой задницы ничего в природе не существовало!

C++ нужно изучать через C!

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C++ для начинающих

Непрочитанное сообщение Olej » 31 авг 2015, 19:45

Olej писал(а):C++ нужно изучать через C!
... а C нужно изучать через GCC!
:lol:

Так что те, кто приступает к C++ через MS Visual Studio оказываются в сильно незавидном положении...

Но всё это лирика...
Olej писал(а): Но если задачу сформулировать именно так, в усечённом виде, что нужно только проверить синтаксическую правильность, но не делать разбора получаемых токенов, то её можно и решить в таком же кастрированном стиле:
Но вот если задачу сформулировать ... ближе к жизни, что нужно не только оценить синтаксическую правильность выражения но и выполнить само выражение, то тут таким хитрым трюком не обойтись.
Т.е. как в LISP требуется выполнить выражение:

Код: Выделить всё

( f1 1 2 ( f2 3 4 ) )
Или даже просто вычислить как в калькуляторе:

Код: Выделить всё

{ 1 + [ 2 / 3 ] - ( 4 - 5 ) }
Тут придётся делать оценивание значения выражения каждого уровня ... рекурсивно, т.е. рекурсивно здесь естественно и понятно (не, IMHO), наверняка это можно записать и итерационно, ... но я не очень представляю как.

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

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

Код: Выделить всё

( [a + b] ) - c * ( d + e ) ) => a . b + c . d . e + * -
Здесь я знаком . показывал проталкивание в стек ... можете его себе показывать любым символом (в принципе, если мы имеем фиксированный набор предопределённых символов операций, любой символ не попадающий в это число будет началом операнда, и тогда проталкивание в стек можно вообще никак не показывать: a b + c d e + * -).

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C++ для начинающих

Непрочитанное сообщение Olej » 08 сен 2015, 15:28

Мне тут задали задачку ;-) ...
P.S. Причём задали в совершенно реальных условиях:
- это тестирование, в несколько этапов, проводимое при отборе на работу кандидатов одной израильской фирмой. )
- я к ним напросился на вакансию ... хотя совершенно не уверен, что стану у них работать, даже при самом благоприятном стечении обстоятельств
- ... т.е. они думают, что тестируют меня на вакансию разработчика C++, а я думаю, что тестирую их на их методику тестирования и тестовые задачи ...
- вот так и живём :lol: ... уже 3 тура :-o

Но вся эта история имеет очень любопытное отношение к обучающимся (и обучающим ;-) ) программированию на C/C++.
Это целый класс задач для обучения и контроля ... точнее даже целых 2 класса:
1. улучшить (или изменить) предлагаемый код ... по каким-то признакам, чаще всего это упрощение и укорочение, причём для этих задач я имею виду формально улучшить код, особенно сильно не вникая в то, что он делает...
2. понять что делает предложенный фрагмент кода и попытаться его улучшить, или написать эквивалент в другой технике.

Это задачи на а). умение читать чужой код + б). глубокое понимание синтаксических конструкций (для улучшения).

А теперь оригинал задачи, предложенный из Израиля:
Simplify the implementation below as much as you can.
Even better if you can also improve performance as part of the simplification!
FYI: This code is over 35 lines and over 300 tokens, but it can be written in 5 lines and in less than 60 tokens.

Код: Выделить всё

char *func1(char *s, char a, char b)
{
    char *aptr;
    char *bptr;
    char *res;
    int i;
    if (s[0] == '\0')
    {
        if ((a == '\0') || (b == '\0'))
            return &(s[0]);
        else
            return NULL;
    }
    i = 0;
    aptr = NULL;
    bptr = NULL;
    while (aptr == NULL && bptr == NULL)
    {
        if (s[i] == a)
            aptr = s+i;
        if (s[i] == b)
            bptr = s+i;
        if (s[i] == '\0')
        {
            if ((aptr != s+i) && (bptr != s+i))
            return NULL;
        }
        i++;
    }
    if (aptr == NULL)
        res = bptr;    
    else if (bptr == NULL)
        res = aptr;        
    else if (aptr < bptr)
        res = aptr;
    else if (bptr < aptr)
        res = bptr;
    else
        res = aptr;
    return res;
}

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C++ для начинающих

Непрочитанное сообщение Olej » 08 сен 2015, 15:41

Olej писал(а): А теперь оригинал задачи, предложенный из Израиля:
Они ещё предлагают это делать на скорость: через 30 минут они отключают окно редактирования.
Но, по-моему, это как-раз глупость...

Модифицированный мой вариант (тот, который а). сделан в 30 минут и, поэтому, черновой и б). сделан по принципу "не особенно вникая что делает код", т.е. как выше названо класс 1):

Код: Выделить всё

char *func2(char *s, char a, char b) {
   char *aptr = strchr( s, a ),
        *bptr = strchr( s, b );
   return 0 == strlen( s ) ? ( ( ( a == 0 ) || ( b == 0 ) ) ? s : NULL ) :
          ( ( NULL == aptr ) && ( NULL == bptr ) ) ? NULL :
          aptr == NULL ? bptr :
          bptr == NULL ? aptr :
          bptr < aptr ? bptr : aptr;
}
И тестовая задача, проверяющая эквивалентность (результата func1() и func2() - исходной и модифицированной):

Код: Выделить всё

struct test {
   const char *str;
   char a, b;
   friend inline ostream& operator << ( ostream& stream, test& obj ) {
      stream << '<' << ( obj.str ? obj.str : "NULL" ) << "|'";
      if( isprint( obj.a ) ) stream << obj.a;
      else stream << '\\' << int( obj.a );
      stream << "'|'";
      if( isprint( obj.b ) ) stream << obj.b;
      else stream << '\\' << int( obj.b );
      stream << "'>";
      return stream;
   }
};
 
void strshow( char* s, const char* s1 ) {
   if( NULL == s ) cout << "NULL";
   else if( '\0' == *s ) cout << "\"\"";
   else cout << (char)*s << "[" << s - s1 << "]";
}
        
int main( int argc, char **argv ) {
   test at[] = {
      { "123456789", '7', '3' },
      { "123456789", '7', '8' },
      { "123455432", '7', '3' },
      { "123456789", '5', 'A' },
      { "123456789", 'B', '6' },
      { "123456789", 'x', 'y' },
      { "", 2, 3 },
      { "", 5, 0 },
   };
   for( uint i = 0; i < sizeof( at ) / sizeof( at[ 0 ] ); i++ ) {
      char *pf1 = func1( (char*)at[ i ].str,
                         at[ i ].a, at[ i ].b ),
           *pf2 = func2( (char*)at[ i ].str,
                         at[ i ].a, at[ i ].b );
      cout << i + 1 << " : " << at[ i ] << " => ";
      strshow( pf1, at[ i ].str );
      cout << " ... ";
      strshow( pf2, at[ i ].str );
      cout << endl;
   }
   return 0;
}
В массив тестов at[] вы можете вписать любые тестовые комбинации.
Выполнение:

Код: Выделить всё

olej@nvidia ~/2015_WORK/in.WORK/SchoolCPP/strsimpl $ ./strsimpl
1 : <123456789|'7'|'3'> => 3[2] ... 3[2]
2 : <123456789|'7'|'8'> => 7[6] ... 7[6]
3 : <123456789432|'7'|'3'> => 3[2] ... 3[2]
4 : <123456789|'5'|'A'> => 5[4] ... 5[4]
5 : <123456789|'B'|'6'> => 6[5] ... 6[5]
6 : <123456789|'x'|'y'> => NULL ... NULL
7 : <|'\2'|'\3'> => NULL ... NULL
8 : <|'\5'|'\0'> => "" ... ""
Отличная идея! для задач - модифицировать заданный код, а не писать собственную реализацию.
Умение работать (практика) с чужим кодом (да ещё написанном и форматированном в самых разных стилях) - это, порой, важнее, чем писать собственный код. Особенно это так в мире опенсорсных проектов, Linux, где в каждом крупном проекте приходится использовать десятки уже существующих публичных и опенсорсных.
Вложения
simplecod.tgz
(1.43 КБ) 283 скачивания

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C++ для начинающих

Непрочитанное сообщение Olej » 17 сен 2015, 19:58

Задача с того сайта ... для пыанэров, с обсуждения которого началась тема:
1. Распространённая задача: Дано четырехзначное число (к примеру 5678), вывести на экран в обратном порядке цифры из которых это число состоит. То есть мы должны увидеть на экране 8765.
Задача плёвая ... но как пианист для разминки играет гаммы, так и с такими задачами есть смысл поиграться.
Но только:
- придав им более обобщённую формулировку - почему 4-х разрядное число? ... положительное целое любой разрядности!
- и поставив целью своей разминки составить как можно более короткий код.

1-й вариант ;-) :

Код: Выделить всё

#include <iostream>
using namespace std;

int main() {
   unsigned long long e;
   while( true ) {
      cout << "Введите любое положительное целое : ";
      cin >> e;
      cout << "Введенные цифры в обратном порядке : ";
      for( ; e != 0; e /= 10 )
         cout << e % 10;
      cout << endl;
   }
   return 0;
}

Код: Выделить всё

olej@nvidia ~/2015_WORK/in.WORK/SchoolCPP/digrev $ ./digrev 
Введите любое положительное целое : 1235
Введенные цифры в обратном порядке : 5321
Введите любое положительное целое : 123456789
Введенные цифры в обратном порядке : 987654321
Введите любое положительное целое : 1
Введенные цифры в обратном порядке : 1
Введите любое положительное целое : 12
Введенные цифры в обратном порядке : 21
Введите любое положительное целое : 123
Введенные цифры в обратном порядке : 321
Введите любое положительное целое : ^C
Это уже действительно будет "для начинающих", как в заголовке темы.
Вложения
digrev.cc
(381 байт) 260 скачиваний

Аватара пользователя
Olej
Писатель
Сообщения: 21338
Зарегистрирован: 24 сен 2011, 14:22
Откуда: Харьков
Контактная информация:

Re: C++ для начинающих

Непрочитанное сообщение Olej » 17 сен 2015, 20:59

Olej писал(а): - и поставив целью своей разминки составить как можно более короткий код.
В той задаче можно пойти на обман :lol:
Обман состоит в том, что чтение из потока cin (или cin.get()) читает с терминала всё-равно текстовую строку, которую потом, из-за требования совместимости с типом результата, преобразовывает в целое (хотя приглашения ввода и убедительно предлагает ввести именно целое ;-) ):

Код: Выделить всё

#include <iostream>
using namespace std;

int main() {
   string e;
   while( true ) {
      cout << "Введите любое положительное целое : ";
      cin >> e;
      cout << "Введенные цифры в обратном порядке : ";
      for( string::const_reverse_iterator i = e.rbegin(); i != e.rend(); i++ )
         cout << *i;
      cout << endl;
   }
}

Код: Выделить всё

olej@nvidia ~/2015_WORK/in.WORK/SchoolCPP/digrev $ ./digrevs
Введите любое положительное целое : 1234567
Введенные цифры в обратном порядке : 7654321
Введите любое положительное целое : 678
Введенные цифры в обратном порядке : 876
Введите любое положительное целое : ^C
Вложения
digrevs.cc
(400 байт) 310 скачиваний
digrev.tgz
(740 байт) 259 скачиваний

Ответить

Вернуться в «Программирование»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 8 гостей