Ликбез по программированию
Нас читает много студентов… Действительно, и вопросов по программированию от них стало приходить немало. В основном спрашивают по С/С++, потому как эти языки наиболее часто преподаются в вузах. Многие вопросы являются простыми, но, как показывает практика, далеко не все могут быстро найти на них ответы. Поэтому постараемся восполнить эти пробелы.
Со своей стороны я бы выделил сначала топ-10 ошибок начинающих программистов, с которыми можно встретиться наиболее часто. Это из личного опыта: . Ошибка «одиннадцатого столба» (ошибка неучтенной единицы). То есть, сколько нужно столбов, чтобы сделать забор из десяти пролетов? Правильно, одиннадцать, хотя многие думают и считают, что десять. Кстати, этому способствует и то, что отправной точкой отсчета в большинстве языков программирования является 0, а не 1 (массивы и т.п.).
. Путаница со скобками и точками с запятой. Иногда это даже приводит к неожиданным результатам. Эта ошибка наиболее часто возникает, когда код пишется не в специализированных редакторах.
. Неправильное преобразование типов, а также непонимание некоторых типов.
. Проблемы работы с памятью.
. Переполнение стека.
. Ошибка выхода за диапазон. Несоответствие переменных и присваиваемых им значений.
. Целочисленное переполнение.
. Не(!)использование потоков там, где это нужно.
. Использование потоков там, где этого не нужно.
. Большая направленность на expression-oriented. Это даже не ошибка, а просто создание нечитабельного для других кода. Конечно, круто использовать тернарный «?» вместо конструкции if-then-else (по секрету сказать, он даже быстрее работает), но мало кто поймет такие записи. Я не против такого, но бывают случаи, когда программист «где-то увидел» и начал применять везде, где только можно.
Повторюсь, что это из личного опыта. Причем любой программист учится на ошибках, как правило, своих.
***
Итак, очень много вопросов пришло по преобразованию типов в С/С++, особенно в тех вариантах, когда такого отдельного типа, как string, нет, и есть невнятный CString (программирование под MFС).
Итак, даем код конвертации из CString в char* и обратно.
#include <atlbase.h>
USES_CONVERSION;
CString strData(_T("Some Data"));
char* lpszString = T2A((LPTSTR)(LPCTSTR)strData);
Обратное преобразование еще проще, у меня оно всегда работает так:):
strData=A2T(lpszString);
Если вы делаете подобные преобразования в разных зонах видимости кода, то наличие строки «USES_CONVERSION;» перед преобразованиями обязательно. То есть, если говорить простыми словами, T2A (text-to-array) и A2T (array-to-text) должны быть обоснованы. Не забудьте в заголовке файла поставить <atlbase.h>.
Вообще, немного странно, что многие букву «А» или «a» в данных случаях почему-то переводят как «ANSI». Это большое заблуждение, «а» обозначает array, то есть массив. В классическом С и в С++ нет такого типа, как string, а есть массив символов типа char.
***
Теперь перейдем к больному для многих вопросу — перехода от целых чисел к строковым массивам и обратно. Давайте напишем класс:
class str2int
{public:
explicit str2int(char const *s)
: m_value(atoi(s))
{}
//Операторы
public:
operator int() const {
return m_value;
}
//Члены
private:
int m_value;
};
Все, теперь вы можете смело набирать строку int x = str2int(char*…). Как видно в рамках представленного класса, ключевым пунктом является функция atoi(), которую можно перевести (array to integer). То есть, вы можете и просто пользоваться ею напрямую. Есть и обратная функция itoa(), которая преобразует целочисленную переменную в символьную строку. Записывается так:
char zBuff[1024];
char* z=itoa(int_value,zBuff,10);
То есть, int_value — это значение, которое нам необходимо перевести в строку, zBuff — буфер, через который мы это проделываем, последняя цифра — основание системы счисления. Все, если хотите, можете написать для этого специальный класс, назвав его как-нибудь оригинально, например, str2int.
***
Еще один часто задаваемый вопрос касается разделения строки на лексемы. Существует и используется несколько различных вариантов. Но в классических рекомендациях для C и C++ это делается с помощью функции strtok(). Выглядит это так… Допустим, вы пишите клиентскую или серверную программу и получаете строку szRecvBuff. Вам нужно ее «распарсить». Обычно в таких случаях сначала ставят команды через символьные разделители (самый частый случай — пробелы). Например:
«get from далее перечисление».
Итак:
char *p;
p=strtok(szRecvBuff," ");
komanda1=p;
p=strtok(NULL," ");
komanda2=p;
//и так далее…
//можете сделать цикл
//while(p);
Разделителем у нас является пробел, который, кстати, является символом (если кто не знает). В качестве символьных разделителей можно применять любые Unicode символы и их последовательности. Обратите внимание на то, что второй раз мы уже обращаемся к строке через NULL, поскольку сама строка содержится в указателе, а после p=strtok(szRecvBuff," "); мы сместились на следующую позицию. Стоит сказать, что разделители при формировании строк, если вы не умеете работать с очисткой памяти, нужно указывать явно, потому как строки могут заполняться всевозможным мусором.
. Объединение символьных массивов производится с помощью функции strcat(), копирование из одного массива в другой — с помощью strcpy().
***
Также часто спрашивают о формировании потоков, а именно — как их делать и как вызывать. Конечно, есть много специализированной литературы, но вспоминаем правило: «хуже отсутствия документации может быть только неправильная документация». В принципе, доступно написано о потоках в книге Тома Арчера и Эндрю Уайтчепела «Visual C++ .NET. Библия пользователя» (Диалектика, 2003), хотя сама книга довольно странная:), в общем, на любителя.
В принципе, опишу самую простую конструкцию потока.
Создаем, например, так:
DWORD WINAPI Thread1(LPVOID lpParam)
{
// «тело потока»
return 0;
}
Запускаем так:
HANDLE hThread1;
DWORD dwThread1Id;
hNetThread = CreateThread(NULL, 0, Thread1,
0, 0, &dwThread1Id);
Есть и другие варианты и методики. Тема обширна. Хотя лично моя рекомендация — обращайтесь с потоками осторожно, а лучше поищите и прочитайте дополнительную литературу. Например, я нашел много уникальных технологий (потому как потоки, особенно для С++ — очень важная тема), после чего некоторые книги и даже методические пособия для вузов выглядят просто теоретическими выкладками, которые на практике оказываются неэффективными.
***
Очень много вопросов по MFC, причем самых тривиальных, например, как создавать DDX переменные и т.п. Это во многом понятно, потому как в Visual Studio 2008 все несколько перевернуто, то есть старые книги не совсем подходят, а новых я на прилавке еще не видел, хотя… видел, но большинство из них посвящено С#. В общем, чтобы долго не расписывать, отошлю к вышеуказанной «библии пользователя», там все доступно описано и показано на примерах.
Когда многие программируют под MFC с диалоговыми окнами, то им часто необходим таймер, но его нет в Toolbox’е, когда вы переходите к форме. Ситуация решается просто. В h-файле в описании класса диалогового окна вводите строку:
afx_msg void OnTimer(UINT nIDEvent);
В *.cpp-файле в рамках BEGIN_MESSAGE_MAP вводите:
ON_WM_TIMER()
Потом в коде помещаете функцию, например:
void CMyDlg::OnTimer(UINT nIDEvent) {}
которая будет вызываться командой SetTimer(1, 1000, NULL), где 1000 — это интервал.
***
Как в C# повесить иконку в SysTray (там, где часы), и сворачивать туда окно программы?
В принципе, в рамках Visual Studio все делается практически идентично для всех языков программирования, используется класс NotifyIcon. В объявление класса формы вписываем:
public partial class Form1 : Form
{
private System.Windows.Forms.NotifyIcon notifyIcon1;
…
}
Далее в:
public Form1 {
…
//создаем окошко в трее
this.notifyIcon1 = new System.Windows.Forms.NotifyIcon(this.components);
//сюда загружаем иконку
notifyIcon1.Icon = new Icon("COLOR.ICO");
// называем окошко в трее и запускаем его
notifyIcon1.Text = "Наша программа";
notifyIcon1.Visible = true;
// Handle the DoubleClick event to activate the form
notifyIcon1.DoubleClick += new System.EventHandler(this.notifyIcon1_DoubleClick);
…
}
После этого пишем:
private void notifyIcon1_DoubleClick(object sender, EventArgs e)
{
//Показываем форму.
this.Show();
//Делаем окно "нормальным".
WindowState = FormWindowState.Normal;
}
private void Form1_Resize(object sender, EventArgs e)
{
if (FormWindowState.Minimized == WindowState)
{
this.Hide();
}
}
Все. Подробная информация по использованию класса NotifyIcon есть как в справке по Visual Studio, так и в MSDN.
Кристофер christopher@tut.by
Со своей стороны я бы выделил сначала топ-10 ошибок начинающих программистов, с которыми можно встретиться наиболее часто. Это из личного опыта: . Ошибка «одиннадцатого столба» (ошибка неучтенной единицы). То есть, сколько нужно столбов, чтобы сделать забор из десяти пролетов? Правильно, одиннадцать, хотя многие думают и считают, что десять. Кстати, этому способствует и то, что отправной точкой отсчета в большинстве языков программирования является 0, а не 1 (массивы и т.п.).
. Путаница со скобками и точками с запятой. Иногда это даже приводит к неожиданным результатам. Эта ошибка наиболее часто возникает, когда код пишется не в специализированных редакторах.
. Неправильное преобразование типов, а также непонимание некоторых типов.
. Проблемы работы с памятью.
. Переполнение стека.
. Ошибка выхода за диапазон. Несоответствие переменных и присваиваемых им значений.
. Целочисленное переполнение.
. Не(!)использование потоков там, где это нужно.
. Использование потоков там, где этого не нужно.
. Большая направленность на expression-oriented. Это даже не ошибка, а просто создание нечитабельного для других кода. Конечно, круто использовать тернарный «?» вместо конструкции if-then-else (по секрету сказать, он даже быстрее работает), но мало кто поймет такие записи. Я не против такого, но бывают случаи, когда программист «где-то увидел» и начал применять везде, где только можно.
Повторюсь, что это из личного опыта. Причем любой программист учится на ошибках, как правило, своих.
***
Итак, очень много вопросов пришло по преобразованию типов в С/С++, особенно в тех вариантах, когда такого отдельного типа, как string, нет, и есть невнятный CString (программирование под MFС).
Итак, даем код конвертации из CString в char* и обратно.
#include <atlbase.h>
USES_CONVERSION;
CString strData(_T("Some Data"));
char* lpszString = T2A((LPTSTR)(LPCTSTR)strData);
Обратное преобразование еще проще, у меня оно всегда работает так:):
strData=A2T(lpszString);
Если вы делаете подобные преобразования в разных зонах видимости кода, то наличие строки «USES_CONVERSION;» перед преобразованиями обязательно. То есть, если говорить простыми словами, T2A (text-to-array) и A2T (array-to-text) должны быть обоснованы. Не забудьте в заголовке файла поставить <atlbase.h>.
Вообще, немного странно, что многие букву «А» или «a» в данных случаях почему-то переводят как «ANSI». Это большое заблуждение, «а» обозначает array, то есть массив. В классическом С и в С++ нет такого типа, как string, а есть массив символов типа char.
***
Теперь перейдем к больному для многих вопросу — перехода от целых чисел к строковым массивам и обратно. Давайте напишем класс:
class str2int
{public:
explicit str2int(char const *s)
: m_value(atoi(s))
{}
//Операторы
public:
operator int() const {
return m_value;
}
//Члены
private:
int m_value;
};
Все, теперь вы можете смело набирать строку int x = str2int(char*…). Как видно в рамках представленного класса, ключевым пунктом является функция atoi(), которую можно перевести (array to integer). То есть, вы можете и просто пользоваться ею напрямую. Есть и обратная функция itoa(), которая преобразует целочисленную переменную в символьную строку. Записывается так:
char zBuff[1024];
char* z=itoa(int_value,zBuff,10);
То есть, int_value — это значение, которое нам необходимо перевести в строку, zBuff — буфер, через который мы это проделываем, последняя цифра — основание системы счисления. Все, если хотите, можете написать для этого специальный класс, назвав его как-нибудь оригинально, например, str2int.
***
Еще один часто задаваемый вопрос касается разделения строки на лексемы. Существует и используется несколько различных вариантов. Но в классических рекомендациях для C и C++ это делается с помощью функции strtok(). Выглядит это так… Допустим, вы пишите клиентскую или серверную программу и получаете строку szRecvBuff. Вам нужно ее «распарсить». Обычно в таких случаях сначала ставят команды через символьные разделители (самый частый случай — пробелы). Например:
«get from далее перечисление».
Итак:
char *p;
p=strtok(szRecvBuff," ");
komanda1=p;
p=strtok(NULL," ");
komanda2=p;
//и так далее…
//можете сделать цикл
//while(p);
Разделителем у нас является пробел, который, кстати, является символом (если кто не знает). В качестве символьных разделителей можно применять любые Unicode символы и их последовательности. Обратите внимание на то, что второй раз мы уже обращаемся к строке через NULL, поскольку сама строка содержится в указателе, а после p=strtok(szRecvBuff," "); мы сместились на следующую позицию. Стоит сказать, что разделители при формировании строк, если вы не умеете работать с очисткой памяти, нужно указывать явно, потому как строки могут заполняться всевозможным мусором.
. Объединение символьных массивов производится с помощью функции strcat(), копирование из одного массива в другой — с помощью strcpy().
***
Также часто спрашивают о формировании потоков, а именно — как их делать и как вызывать. Конечно, есть много специализированной литературы, но вспоминаем правило: «хуже отсутствия документации может быть только неправильная документация». В принципе, доступно написано о потоках в книге Тома Арчера и Эндрю Уайтчепела «Visual C++ .NET. Библия пользователя» (Диалектика, 2003), хотя сама книга довольно странная:), в общем, на любителя.
В принципе, опишу самую простую конструкцию потока.
Создаем, например, так:
DWORD WINAPI Thread1(LPVOID lpParam)
{
// «тело потока»
return 0;
}
Запускаем так:
HANDLE hThread1;
DWORD dwThread1Id;
hNetThread = CreateThread(NULL, 0, Thread1,
0, 0, &dwThread1Id);
Есть и другие варианты и методики. Тема обширна. Хотя лично моя рекомендация — обращайтесь с потоками осторожно, а лучше поищите и прочитайте дополнительную литературу. Например, я нашел много уникальных технологий (потому как потоки, особенно для С++ — очень важная тема), после чего некоторые книги и даже методические пособия для вузов выглядят просто теоретическими выкладками, которые на практике оказываются неэффективными.
***
Очень много вопросов по MFC, причем самых тривиальных, например, как создавать DDX переменные и т.п. Это во многом понятно, потому как в Visual Studio 2008 все несколько перевернуто, то есть старые книги не совсем подходят, а новых я на прилавке еще не видел, хотя… видел, но большинство из них посвящено С#. В общем, чтобы долго не расписывать, отошлю к вышеуказанной «библии пользователя», там все доступно описано и показано на примерах.
Когда многие программируют под MFC с диалоговыми окнами, то им часто необходим таймер, но его нет в Toolbox’е, когда вы переходите к форме. Ситуация решается просто. В h-файле в описании класса диалогового окна вводите строку:
afx_msg void OnTimer(UINT nIDEvent);
В *.cpp-файле в рамках BEGIN_MESSAGE_MAP вводите:
ON_WM_TIMER()
Потом в коде помещаете функцию, например:
void CMyDlg::OnTimer(UINT nIDEvent) {}
которая будет вызываться командой SetTimer(1, 1000, NULL), где 1000 — это интервал.
***
Как в C# повесить иконку в SysTray (там, где часы), и сворачивать туда окно программы?
В принципе, в рамках Visual Studio все делается практически идентично для всех языков программирования, используется класс NotifyIcon. В объявление класса формы вписываем:
public partial class Form1 : Form
{
private System.Windows.Forms.NotifyIcon notifyIcon1;
…
}
Далее в:
public Form1 {
…
//создаем окошко в трее
this.notifyIcon1 = new System.Windows.Forms.NotifyIcon(this.components);
//сюда загружаем иконку
notifyIcon1.Icon = new Icon("COLOR.ICO");
// называем окошко в трее и запускаем его
notifyIcon1.Text = "Наша программа";
notifyIcon1.Visible = true;
// Handle the DoubleClick event to activate the form
notifyIcon1.DoubleClick += new System.EventHandler(this.notifyIcon1_DoubleClick);
…
}
После этого пишем:
private void notifyIcon1_DoubleClick(object sender, EventArgs e)
{
//Показываем форму.
this.Show();
//Делаем окно "нормальным".
WindowState = FormWindowState.Normal;
}
private void Form1_Resize(object sender, EventArgs e)
{
if (FormWindowState.Minimized == WindowState)
{
this.Hide();
}
}
Все. Подробная информация по использованию класса NotifyIcon есть как в справке по Visual Studio, так и в MSDN.
Кристофер christopher@tut.by
Компьютерная газета. Статья была опубликована в номере 26 за 2009 год в рубрике программирование