C++. Указатели на функции, массивы указателей на функции

Указатели сами по себе очень полезны в работе, но в то же время коваврны, потому что с ними нужно быть очень аккуратным, иначе не избежать утечки памяти. Ниже рассмотрено как можно применять указатели к функциям. Предположим, что у нас есть декларированные функции:

int f1(int); int f2(int); int f3(int);

Тогда можно объявить указатель:

int (*p) (int);

Скобки здесь обязательны так как тогда компилятор воспримет это выражение так:

int* p(int);

Ну а это объявление функции с возвращаемым типом типа указатель. Продолжим.
Теперь, ему можно присвоить ссылку на одну из функций:
 
p = f1;

Если прописать так,

p = f1();

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

(*p) (16); // p (16); Так тоже работает

Стоит отметить, что при объявлении указателей на функцию, необходимо соблюдать согласованность типов между указателями и функциями, например так будет некорректно:
 
int (*p) (); // Это равнозначно вырыжению int (*p) (void);

А это уже несогласованность!
Есть еще один нюанс. Предположим, что функция декларирована с аргументами по-умолчанию
 
int f4(int n = 0);

И казалось можно написать так

int (*p) (int);
p = f4;
p ();

И я получаю ошибку компиляции

error C2198: 'int (__cdecl *)(int)' : too few arguments for call

Поэтому нужно будет передать конкретное значение

p (16);

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

template <class T>
T getme(T a, T b) {

       return (a>b?a:b);
}


Тогда при объявлении указателя будет опрелен тип функции getme()
 
char (*tpl) (char, char);
tpl = getme;
cout << "from template function getme() " << tpl ('a', 'b') << endl;


Для удобства работы с указателями можно определить синоним указателей на функцию, например

typedef int (*F) (int); // Сие эквивалентно выражению int (*) int;

Таким образом мы получили синоним указателей - F. Далее с ним работаем вот так

F p; // Сие эквивалентно выражению int (*p) int;

Так мы объявили указатель на функции, ну а дальше по аналогии

p = f1;
p (16);

Теперь рассмотрим массив указателей на функции. Здесь ничего сложного, итак, объявим сие массив:

int (*pm[]) (int) = {f1, f2, f3};

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

(*pm[0]) (16);

На этом закончу. Если что, то исходник можно скачать здесь.

C++. struct like a class

Есть простые типы данных, есть и сложные, определяемые пользователем - пользовательский тип данных. В чистом C - это struct id {}, union id {} и enum id {}. В C++ - class id {}, причем понятие класса здесь настолько широкое, что оно включает в себя и struct id {}, и union id {}, и enum id {}. Поэтому структура может иметь свои методы, идентификаторы доступа (public:, private:, protected:), в общем мы имеем дело с объектной ориентацией. Например, такое в C не прокатит:

#include <iostream>
using namespace std;

struct first {
private:
   int a;
};

struct test {
   int n;
} tpl;

struct sec: first {
   int s;
   sec() {
     cout << "***" << endl;
     a = 16;
 }
 void f(int param = 0) {
    cout << "in sec struct function" << endl;
    p.s = param;
    cout << "s = " << p.s << endl;
    (p.s)++;
    cout << "s++ = " << p.s << endl;
 };
 void show() {
    cout << "a = " << a << endl;
 }
} p;

void testing() {
   cout << "in testing function" << endl;
}

test func(int n = 0) {
   tpl.n = n;
   return tpl;
}

int main() {
 testing();
 func();
 cout << "func(0). tpl.n = " << tpl.n << endl;
 func(16);
 cout << "func(16). tpl.n = " << tpl.n << endl;

 p.f(16);
 cout << "Outline. p->s = " << p.s << endl;
 p.show();

 return 0;
}


Стоит только отметить, по-умолчанию ( если явно не указывать ) тип доступа в struct id {}; - public:, а в class id {}; - private:



C++. Convert from 'std::string' to 'const wchar_t *'. Нюанс

При автоматической генерации выходных файлов, мало ли какие на сие есть потребности, нужно предусмотреть автоматическое именование. Для этого удобней всего работать со стандартным классом string. Есть один нюанс. Если при открытии файлового потока (создание файла) в качестве параметра передать переменную строкового типа string, можно столкнуться с тем самым нюансом. Компилятор выдаст ошибку - cannot convert parameter 1 from 'std::string' to 'const wchar_t *'. М-да, видимо для этого случая перегрузка метода не предусмотрена. Что делать? Для этого предусмотрен метод класса string - c_str().

#include <iostream>
#include <string>
#include <fstream>

using namespace std;

void main(void)
{
    char temp[16];
    string nameFile = "test.txt";
    ofstream out;
 
    // Место нюанса. Без соответствующего метода компилятор ловит ошибку несоответствия
    // out.open(nameFile, ios::out);
    // Если так, то компиляция проходит успешно
    out.open(nameFile.c_str(), ios::out);
    // При необходимости записи строки типа string нужно также использовать сие метод
    out.write("Hello, world", 12);
    out.close();

    // waiting your action...
    cin >> temp;

}

C++. Массив из структурных элементов. Альтернативные способы обращения к полям структур

Часто для решения определенных задач приходится создавать массивы, элементами которого являются структуры. Дело несложное. Ниже приведены два способа обращения к полям таких элементов-структур. Авось кому сгодится. Ниже приведен листинг (скачать)

 

#include <iostream>
using namespace std;
void main(void) { // Описываем структуру test для хранения четных (even) и нечетных (odd) чисел struct test { int even; int odd; };
// Указатель для создания динамического массива четных и нечетных чисел test *p; int n; char temp[16];
cout << "n = "; cin >> n;
// Выделяем память под массив чисел p = new test[n]; // Приведенные способы справедливы и для статических массивов // test p[100];
// Заполняем соответствующими значениями элементы массива p основным // и альтернативным способом for(int i = 0; i < n; i++) { // Основной способ p[i].even = 2*i; // Альтернативный способ (p+i)->odd = 2*i + 1; }
// Выводим содержимое массива p основным и альтернативным способом for(int i = 0; i < n; i++) { cout << "p[" << i << "].even" << "=" << p[i].even << "\t"; cout << "(p+" << i << ")->odd" << "=" << (p+i)->odd << endl; }
// Освобождаем память delete(p);
// One moment please... cin >> temp;
}







Сие справедливо как для динамических, так и для статических массивов

C++. Using dll in a cli application

На примере подключения библиотеки winmm.dll обеспечивающей воспроизведение wav-файлов (windows multimedia api); 1. После объявления используемых пространств имен, прописываем нужную dll-библиотеку (с объявлением внешних методов необходимых для работы):


[System::Runtime::InteropServices::DllImport("winmm.dll")]
extern
          bool PlaySound(String^ lpszName, int hModule, int dwFlags);


2. Вызываем нужные методы:


private: System::Void button1_Click(System::Object^ sender, System::EventArgs^  e) {
           PlaySound(Application::StartupPath + "\\ring.wav", 0, 1);
}
 

C++. Using sqlite in a managed c cli application

В предыдущей теме "C++. How use SQLite in Windows Forms" по-сути были одни голые слова, что-то где-то прочитал, где-то додумал, где-то слышал. Но на деле так и не получалось продвинуться, не было ясности, и вот на глаза попался блог разработчика Дэвида Креви - http://dcravey.wordpress.com, тема топика - "using-sqlite-in-a-managed-c-cli-application". Вот то, что мне нужно было. This helped me to understand some things and pushed to the decision. Thank you David Cravey :) Здесь я попытаюсь расширить его рецептик: от начала и до конца.

Начну с того, что указанный ресурс http://sqlite.phxsoftware.com/ (не смотря на официальность) для получения SQLite .NET версии поддерживался до апреля 2010 года (SQLite-1.0.66.0-setup.exe). Не совсем хорошо в своих разработках использовать "несвежачок". Для получения последних релизов нужно обратиться на другой официальный ресурс - http://system.data.sqlite.org. Итак, первый шаг, обращаемся к этому ресурсу в раздел "Download" и качаем нужный установочный пакет. Нужность определяется вашей средой разработки, в моем случае - Microsoft Visual C++ 2008 Express Edition, поэтому я скачал sqlite-netFx35-setup-bundle-x86-2008-1.0.82.0.exe. Почему? Потому что там же сказано:

This setup package features the mixed-mode assembly and will install all the necessary runtime components and dependencies for the x86 version of the System.Data.SQLite 1.0.83.0 (3.7.15.1) package. The Visual C++ 2008 SP1 runtime for x86 is included. The .NET Framework 3.5 SP1 is required.
This setup package is capable of installing the design-time components for Visual Studio 2008.

С самого начала я создал проект testDB (Templates - Windows Form Application) и все последующие действия буду сопровождать скриншотами, чтобы была наглядность.


Создание проекта testDB
Свойства проекта


Свойства проекта. Расширенное
До запуска установки пакета не поленитесь заглянуть в свойства проекта, там где определяются имена ссылок целевого .NET framework, в моем случае - .NET framework 3.5. Имена ссылок определяют пространство имен, которое используется при кодинге, расширяя наши возможности. Немного отвлеклись. Если Вы заглянули, то можете видеть отсутствие каких-либо намеков на SQLite. Вот теперь, шаг второй - запускаем установку нашего пакета.

Установка пакета
Все по-умолчанию и проставляем все флажки. Вуаля, пакет установлен.
Шаг третий. После окончания установки нужно пройти в свойства проекта и добавить новые ссылки (References -> Add New References -> Вкладка .NET -> System.Data.SQLite.Core и другие).
Данный пакет расширит имена ссылок целевой платформы .NET framework 3.5 (расширение пространства имен System.Data).


Список активных ссылок


Добавление новых ссылок на System.Data.SQLite


Если Вы воспользовались версией SQLite-1.0.66.0, название ссылок может немного отличаться.
После установки пакета в установочной директории (если Вы ничего не меняли) - C:\Program Files\System.Data.SQLite в поддиректории \2008\bin будут располагаться те самые динамические библиотеки - System.Data.SQLite.dll и System.Data.SQLite.Linq.dll.
Для версии SQLite-1.0.66.0 немного иначе - C:\Program Files\SQLite.NET и поддиректория \bin.
Данные библиотеки уже готовы к использованию, в отличие от "C"шной SQLite библиотеки, где нужно проводить "линковку", например при разработке консольных приложений. Об этом можно почитать здесь - Связывание с динамической библиотекой в Visual C++, Разработка консольного приложения для работы с базой данных SQLite.
Каждая ссылка ссылается (поэтому она и называется ссылкой (Reference)) на соответствующую динамическую библиотеку (*.dll). Если использовать свежие релизы, то при установке пакета для удобства можно включить так называемый Caching metadata (флажок выставлен по-умолчанию), в результате вам не нужно будет располагать библиотеки в директории проекта, в отличии от SQLite-1.0.66.0, где такой возможности нет, а значит нужно будет ручками дополнительно ко всем манипуляциям скопипастить из \bin директории в директорию Вашего проекта соответствующие библиотеки.
Итак, мы готовы к кодингу. Для последующей самопроверки используя консольный движок sqlite3.exe (command-line shell) я создал простеньую базу данных sqlitedb.db состоящая из одной таблицы ipaddress. Далее в две колонки добавил записей. Созданную базу данных рассположил в директории проекта.
Что касается кодинга, для начала в месте объявлений используемых пространств имен прописываем:

 

 
//SQLite
 using namespace System::Data::SQLite;
 using namespace System::Text;
 

 

Изначально на форме были размещены такие активные компоненты как button и textbox, ими и воспользуемся:

 
private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
    // Create The Connection Object
    SQLiteConnection ^db = gcnew SQLiteConnection();
    try {
      // Open Database Connection
      MessageBox::Show("Opening Database Connection To sqlitedb.db ...");
      db->ConnectionString = "Data Source=sqlitedb.db";
      db->Open();
      MessageBox::Show("Database Connection To sqlitedb.db Opened.");

      // Display Table
      try {
        MessageBox::Show("Displaying Table ...");
        SQLiteCommand ^cmdSelect = db->CreateCommand();
        cmdSelect->CommandText = "SELECT * FROM ipaddress;";
        SQLiteDataReader ^reader = cmdSelect->ExecuteReader();
        StringBuilder ^sb = gcnew StringBuilder();
        for (int colCtr = 0; colCtr < reader->FieldCount; ++colCtr) {
         
         // Add Seperator (If After First Column)
         if (colCtr > 0) sb->Append("|");

         // Add Column Name
         sb->Append(reader->GetName(colCtr));
        }
        sb->AppendLine();
        sb->Append("~~~~~~~~~~~~");
        sb->AppendLine();
        while (reader->Read()) {
         for (int colCtr = 0; colCtr < reader->FieldCount; ++colCtr) {
          // Add Seperator (If After First Column)
          if (colCtr > 0) sb->Append("|");

          // Add Column Text
          sb->Append(reader->GetValue(colCtr)->ToString());
         }
         sb->AppendLine();
        }
        // Browse result
        //  ...by use textBox1
        this->textBox1->AppendText(sb->ToString());
        // ...or like this method
        // MessageBox::Show(sb->ToString(), "SQLite MyTable");
       } catch (Exception ^e) {
         MessageBox::Show("Error Executing SQL: " + e->ToString(), "Exception While
                                                            Displaying MyTable ...");
       }
      // Close Database Connection
      MessageBox::Show("Closing Database Connection To sqlitedb.db ...");
      db->Close();
      MessageBox::Show("Database Connection To sqlitedb.db Closed.");
     } finally {
       // Dispose Database Connection
       delete (IDisposable^)db;
     }
    }
 


Запускаем компиляцию. Вуаля, ни единой ошибки. Как итог, работающее приложение, для сравнения и упоминавшейся самопроверки сравним результаты:


Приложение in action. Результат на лицо

Листинги, базу данных и "солюшн" под Visual Studio C++ 2008 EE можно скачать по ссылке - download.
Немного затрону тему инсталляции и распространения Вашего готового релиза.
Во-первых, нужно помнить, что в состав готового дистрибутива нужно в обязательном порядке включить используемые при разработке динамические библиотеки;
Во-вторых, так как приложение ориентировано на .NET framework платформу, то нужно обеспечить предустановку соответствующего фреймворка, в моем случае - .NET framework 3.5;  
Для небольшого тестинга я попробывал перенести таким образом дистрибуив на другую машину. Изначально разработка велась под Windows 7 SP1 (иногда под Windows Vista HB), переносил и тестировал на Windows XP SP3.


Пойманное исключение. Отсутствие динамической библиотеки
Приложение in action
Изначально во время запуска было поймано исключение, которое я вызвал искусственно - не добавил динамическую библиотеку. Как только ее размещаем, приложение отрабатывает на должном уровне.

Еще раз по поводу предустановки .NET framework, для заметки. Работоспосбность приложения сохраняется независимо от версии фреймворка, но немного снижается быстрота отклика (чисто по наблюдениям, серьезно не воспринимать, все-таки нужны численные сравнения), при полном отстствии .NET фреймворков на запуск приложения система ругается меседжем:
The application failed to initialize properly (0xc0000135).


Итог отсутствия ".NET framework"-ов

--------------------------------------------------------------------------------------------------------------

Почему столько много внимания и времени я уделил SQLite библиотеке? Конечно, есть другие технологии работы с базами данных MySQL, Microsoft SQL (также есть легковесная версия - microsoft sql server compact edition), другие. SQLite привлек меня тем, что он - 1) относится к независимым разработкам; - 2) действительно легковесный; - 3) возможность создания базы данных в памяти (виртуальная БД), можно использовать для расширения внутренней области памяти приложения или можно подключить к основной базе данных и использовать ее для кэширования; - 4) можно в любой момент к открытому подключению добавить до 10 баз данных; - 5) ну и все те возможности, присущие другим базам данных - журналы транзакции, логирование, режимы бэкапа и многое другое.