{return FNames[i];
}
Void Famille::SetName(int i,const AnsiString s)
{FNames[i]=s;
}
Int main()
{
Famille C;
C.Names[0]="Steve"; //calls Famille::SetName()
C.Names[1]="Amy";
C.Names[2]="Sarah";
C.Names[3]="Andrew";
for (int i = 0; i <= 3; i++)
{
//calls Famille::GetName()
puts(C.Names[i].c_str()); //вывод на экран
}
}
4.9. Перегрузка операторов
В С++ кроме перегрузки функций есть возможность перегрузки операторов. Напомним, что перегрузка это не замена одних свойств на другие, а добавление новых свойств. В данном случае новые свойства появляются у операторов.
Необходимость в создании перегруженных опереаторов обычно возникает после создания новых типов переменных. Например, представьте себе, что объектами некого класса являются геометрические фигуры. Пусть человек, который использует эти объекты, из отдельных фигур хочет создать картину. По-существу картина это новая фигура, которая является объектом того же класса, но с другой стороны она является суммой отдельных фигур. Поэтому естественно перегрузить знак +, т.е. сделать его пригодным для суммирования геометрических фигур. Это позволит создавать картины из готовых элементов.
Перегруженная операция представляет собой функцию со специальным именем, в которой описывается переопределение оператора. Необходимость в перегрузке операторов обычно возникает тогда, когда от оператора требуется чтобы он работал с классами также как с числами. Например, оператор + можно использовать для того, чтобы указать, что строки нужно соединить вместе. Рассмотрим как можно сделать перегрузку операторов.
Пусть @ некоторый оператор языка, кроме следующих:
. – доступ к элементу класса (точка);
.* - доступ к указателю (точка со зведочкой);
:: - разрешение области видимости (двойное двоеточие);
|
?: - трехместная условная операция, (вопросительный знак с двоеточием, то же самое, что if - else);
# - указание препроцессору.
Тогда достаточно определить функцию с именем operator@ требуемым числом и типом аргументов, так, чтобы эта функция выполняла необходимые действия. То есть
Тип_результата operator @ (список_параметров)
{
//тело функции
}
Но в том случае, когда перегрузка производится в классе, т.е. функция-оператор является членом класса перегрузка имеет вид
Тип_результата имя_класса::operator @ (список_параметров)
{
//тело функции
}
То есть перегрузка оператора это построение функции с именем из специальных символов без передачи аргументов в явном виде. Перегрузка осуществляется с помощью кдючевого слова operator. Хотя функцию operator @ (список_параметров) можно использовать как обычную функцию. Оба способа будут показана в примере, который мы рассмотрим чуть позже.
При перегрузке операторов следует руководствоваться правилом:
- Перегрузка не может изменять старшинство и ассоциативность операций;
- Нельзя изменять число операндов операций;
- операция присваивания = может быть использована с любым классом без явной перегрузки.
- По крайней мере один аргумент перегруженного оператора должен принадлежать типу который является классом
- Невозможно создать новый оператор. Все, что можно сделать, - это перегузить существующий оператор.
- Нельзя изменять значение оператора по отношению к встроенным типам, т.е int, double, bool, и т.д.
- Функция-операция (т.е. operator @) должна быть либо членом класса (структуры), либо воспринимать один или несколько аргументов, имеющих тип класса или структуры.
|
Рассмотрим пример перегрузки операторов в задаче сложения комплексных чисел. В программе перегружены операторы + и +=. Причем показано, что перегрузка возможна для выбранных типов переменных.
//Сложение 2-х комплексных чисел
#include <iostream>
#include <windows>
using namespace std;
struct Complex{double real, imag;}; /* Объявление структуры в
*в которой хранятся вещественная и мнимая части комлексного числа */
//--------------------------------------------------------
//Перегрузка оператора +
/*объявляем функцию аргументами которой являются адреса 2-х структур типа Complex*/
Complex operator+(Complex &a, Complex &b){
Complex c;
c.real=a.real+b.real;
c.imag=a.imag+b.imag;
return c;}
//--------------------------------------------------------
//Перегрузка унарного оператора += для комплексных чисел
void operator +=(Complex &a, Complex &b){
/*эта запись говорит о том, что если встретится инструкция a+=b то нужно сделать следующее */
a.real=a.real+b.real;
a.imag=a.imag+b.imag;
}
//------------------------------------------------------
/* Перегрузка унарного оператора += для действительных чисел
* типа double */
void operator +=(Complex &a, double b){
a.real +=b;
}
//-------------------------------------------------------
//определение функции для вывода комплексных чисел
void showComplex (const Complex &x){
if(x.imag>=0)cout<<x.real<<"+j"<<x.imag<<endl;
else cout<<x.real<<"-j"<<-x.imag<<endl;
}
//--------------------------------------------------------
int main(){
Complex x,y,z1,z2;
x.real=20; x.imag=40;
y.real=30; y.imag=50;
SetConsoleOutputCP(1251);
cout<<"Первое число:"; showComplex(x); //Вывод числа x
cout<<"Второе число:"; showComplex(y); //Вывод числа y
z1=operator+(x,y); //здесь перегрузка используется как функция
cout<<"Определение суммы с помощью функции:"; showComplex(z1);
|
z2=x+y; //перегруженный оператор используется в операции
cout<<"Определение суммы с помошью оператора:"; showComplex(z2);
z1 +=x; //то же, что operator+=(z1,x);
cout<<"Если добавить первое число к сумме, то получится:"; showComplex(z1);
z2 +=30.0; //то же, что operator+=(z2,30.0);
cout<<"Если добавить число 30 к сумме, то получится:"; showComplex(z2);
int i; cin>>i;
return 0;
}
Вот результат работы программы
В этой программе одна и таже функция перегрузки
Complex operator+(const Complex &a,const Complex &b)
используется по-разному. Один раз как обычная функция, которая вызывается из функции main() с помощью ее имени с указанием аргументов
z1=operator+(x,y);
Второй раз как перегруженный оператор
z2=x+y;
Вызов функции в качестве оператора сложения не вызывает никаких трудностей. Напротив, второй вариант, вероятно, не совсем понятен. Сделаем некоторые пояснения. Встретив знак + для небазового типа Complex компилятор проверяет, не является ли этот оператор перегруженным. С этой целью он ищет функцию, у которой имя содержит ключевое слово operator и указанный знак операции. Если эта функция найдена, то он проверяет соответствие её параметров числу и типам операндов. После этого обеспечивает выполнение написанного кода.
В качестве примера перегрузки в классе рассмотрим перегрузку опрератора массива или, лучше сказать, оператора работы с индексами [ ]. В качестве примера рассмотрим класс предназначенный для хранения строки и числа. Чтобы привлечь ваше внимание пусть это будет имя шпиона и его поядковый идентификационный номер (ПИН). Программа подготовлена для случая когда агент по имени Bond забыл свой номер. Введя свое имя, а точнее фамилию он узнает номер.
#include <iostream>
#include <string>
using namespace std;
const int MAX_LEN=100;
/* Класс для хранения имени шпиона и его ПИНа
* имя хранится в массиве strcpy, а ПИН в переменной типа
* int decoderKey */
class SecretInfo{
int decoderKey;
char codeName[MAX_LEN];
public:
SecretInfo(char *spyName, int spyKey); /*Объявляем конструктор
* с двумя аргументами */
int operator [](const char *spyName); //Перегрузка [] вклассе
}; //Конец класса
//Определение конструктора
/* Данный конструктор осуществляет перезапись аргументов
* в переменные объявленные в классе */
SecretInfo::SecretInfo(char *spyName, int spyKey){
strcpy(codeName, spyName); /* встроенная функция strcpy
* копирует вторую строку в первую */
decoderKey=spyKey;
}
/* Определение перегружаемого оператора */
int SecretInfo::operator[](const char *spyName){
if(!strcmp(codeName, spyName)) /* strcmp функция сравнения строк
* возвращает 0 если строки совпадают
* следует напомнить, что в языке С
* вместо false используется ноль*/
return decoderKey;
else
return 0;
} //конец функции-оператора
/*теперь оператор [] означает ПИН агента*/
//===========================================
int main(void){
SecretInfo agent("Bond",7);
char temp[MAX_LEN];
cout<<"Input Your code name:";
cin>>temp;
if(agent[temp])
cout<<"Your Kode Name"<<agent.operator [](temp);
else
cout<<"Sorry Bond! False Name Code"<<endl;
char z;
cin>>z;
return 0;
}
Пример
#include <iostream>
#include <vector>
#include <string>
#include <cstdlib>
#define EOS -1 //Конец строки(возвращаеться функцией get_word)
using namespace std;
class dict_el{ //Элемент словаря (слово и его позиции)
public:
dict_el():Word(""){}; //Конструсторы
dict_el(string wd):Word(wd){};
~dict_el(){}; //Деструктор
void add_pos(const int i){ //Добавить позицию
this->pos.push_back(i);
};
bool operator==(const string& str){
/* сравнение со строкой для алгоритма find */
return (this->Word == str)? true:false;
}
friend ostream& operator<<(ostream& cout, dict_el& rhs);
/*Вывод слова и его позиций */
private:
string Word; //Хранимое слово
vector<int> pos; //Позиции для хранимого слова
};
ostream& operator<<(ostream& cout, dict_el& rhs){
/*Вывод слова и его позиций*/
cout << rhs.Word << ": ";
vector<int>::iterator i;
for (i = rhs.pos.begin(); i!= rhs.pos.end(); ++i)
cout << *i << " "; //Выводит слово: и его позиции
return cout;
}
int get_word(string& str,string& res){ //получить следующее слово
static int current=0; //текущая позиция
int i;
while(isspace(str[current])) {
++current; //пропустить все знаки пробел,
} //переход строки и т.д.
if (str[current] == '\0') return EOS; //достигнут конец строки
i = current; //начало нового слова
res="";
while(!isspace(str[current]) && str[current]!= '\0'){//считывать слово до конца
res+=tolower(str[current++]); //и записывать его в res
}
return i; //вернуть позицию слова
}
void parse_str(string& str, vector<dict_el>& dict){
/* Разбор строки и формирование словаря */
string temp; //следующее слово
int position; // и его позиция
vector<dict_el>::iterator i;
while((position=get_word(str,temp))!= EOS){ /*Пока не дошли до конца строки получить следующее слово */
i=find(dict.begin(),dict.end(),temp); //искать это слово в словаре
if (i == dict.end()){ //если такого слова нет
dict_el A(temp); //создать его
A.add_pos(position); //ввести первую позицию
dict.push_back(A); //и добать в словарь
}
else{ //если слово уже есть
(*i).add_pos(position); //добавть к нему новую позицию
}
}
}
//Начало программы
int main(){
string str="Testing my Testing my program\0"; //Строка для разбора
vector<dict_el> dict; //Словарь
parse_str(str,dict); //Создать словарь
vector<dict_el>::iterator i;
for(i = dict.begin(); i!= dict.end(); ++i){ //Вывести словарь
cout << *i << endl;
}
return 0;
}
Использование "умных" указателей
Принципы использования "умных" указателей известны каждому программисту на C++. Идея предельно проста: вместо того, что бы пользоваться объектами некоторого класса, указателями на эти объекты или ссылками, определяется новый тип для которого переопределен селектор ->, что позволяет использовать объекты такого типа в качестве ссылок на реальные объекты. На всякий случай, приведу следующий пример:
class A {
public: void method();
}
class APtr {
protected: A* a;
public: APtr();
~APtr();
A* operator->();
};
inline APtr::APtr(): a(new A) { }
inline APtr::~APtr() { delete a; }
inline A* APtr::operator->() { return a; }
Теперь для объекта, определенного как APtr aptr; можно использовать следующую форму доступа к члену a:
aptr->method();
Тонкости того, почему operator->() возвращает именно указатель A* (у которого есть свой селектор), а не, например, ссылку A& и все равно все компилируется таким образом, что выполнение доходит до метода A::method(), я пропущу за ненадобностью --- я не собираюсь рассказывать о том, как работает этот механизм и какие приемы применяются при его использовании --- это очень подробно написано в книге Джеффа Элджера "C++ for real programmers" (комментарий к этой замечательной книге обязательно появится в соответствующем разделе моей странички), в которой вообще буквально половина книги посвящена "умным" указателям.
Преимущества такого подхода, в принципе, очевидны: появляется возможность контроля за доступом к объектам; немного простых телодвижений и получается указатель, который сам считает количество используемых ссылок и при обнулении автоматически уничтожает свой объект, что позволяет не заботиться об этом самостоятельно... не важно? Почему же: самые трудно отлавливаемые ошибки это ошибки в использовании динамически выделенных объектов. Сплошь и рядом можно встретить: попытка использования указателя на удаленный объект, двойное удаление объекта по одному и тому же адресу, неудаление объекта. При этом последняя ошибка, в принципе, самая безобидная: программа, в которой не удаляются объекты (следовательно, теряется память, которая могла бы быть использована повторно) может вполне спокойно работать в течение некоторого времени (причем это время может спокойно колебаться от нескольких часов до нескольких дней), чего вполне хватает для решения некоторых задач. При этом заметить такую ошибку достаточно просто: достаточно наблюдать динамику использования памяти программой; кроме того, существуют специальные средства для отслеживания подобных казусов, например BoundsChecker.
Первая ошибка в этом списке тоже, в принципе, достаточно простая: использование после удаления скорее всего приведет к тому, что операционная система скажет соответствующее системное сообщение. Хуже становится тогда, когда подобного сообщения не появляется (т.е., данные достаточно правдоподобны или область памяти уже занята чем-либо другим), тогда программа может повести себя каким угодно образом.
Вторая ошибка может дать самое большое количество неприятностей. Все дело в том, что, хотя на первый взгляд она ничем особенным не отличается от первой, тем не менее на практике повторное удаление объекта приводит к тому, что менеджер кучи удаляет что-то совсем невообразимое. Вообще, что значит "удаляет"? Это значит, что помечает память как пустую (готовую к использованию). Обычно, менеджер кучи, для того что бы знать, сколько памяти удалить, в блок выделяемой памяти вставляет его размер. Так вот, если память уже была занята чем-то другим, то по "неверному" указателю находится неправильное значение размера блока, таким образом менеджер кучи удалит некоторый случайный размер используемой памяти. Это даст следующее: при следующих выделениях памяти (рано или поздно) менеджер кучи отдаст эту "неиспользуемую" память под другой запрос и... на одном клочке пространства будут ютиться два разных объекта. Крах программы произойдет почти обязательно, это лучшее что может случится. Значительно хуже, если программа останется работать и будет выдавать правдоподобные результаты. Одна из самых оригинальных ошибок, с которой я столкнулся и которая, скорее всего, была вызвана именно повторным удалением одного и того же указателя, было то, что программа, работающая несколько часов, рано или поздно "падала" в... функции malloc(). Причем проработать она должна была именно несколько часов, иначе эта ситуация не повторялась.
Таким образом, автоматическое удаление при гарантированном неиспользовании указателя, это явный плюс. В принципе, можно позавидовать программистам на Java, у которых подобных проблем не возникает; зато, у них возникают другие проблемы;)
Я еще не убедил вас в полезности использования "умных" указателей? Странно. Тогда я приведу примеры реального использования в своих проектах. Вот, например, объявление того самого "умного" указателя с подсчетом ссылок, о котором я говорил:
template<class T>
Class MPtr
{
public:
MPtr();
MPtr(const MPtr<T>& p);
~MPtr();
MPtr(T* p);
T* operator->() const;
operator T*() const;
MPtr<T>& operator=(const MPtr<T>& p);
protected:
Struct RealPtr
{
T* pointer;
unsigned int count;
RealPtr(T* p = 0);
~RealPtr();
};
RealPtr* pointer;
private:
};
4.8. Полиморфизм
Полиморфизм это еще одно интересное свойство классов. В наиболее общем понимании полиморфизм означает способность с одним именем функции несколько ее реализаций. Мы уже сталкивались с перегрузкой имен, которая является частным случаем полиморфизма. Кроме того только что у нас были два класса с одинаковыми именами. Иначе говоря незаметно для себя мы уже познакомились с этим свойством, но теперь рассмотрим более внимательно все возможности которые оно открывает.
Иногда создавая код программист пытаясь создать некий универсальный продукт попадает в ситуацию связанную с неопределенностью. Например, мы собираемся написать программу для учета всех продаж магазина. Казалось бы чего проще. Трудность вносит то обстоятельство, что типов продаж может быть довольно много. Кроме обычных продаж по указанной цене могут быть продажи со скидкой, продажи с доставкой на дом посыльным, продажи по почте. Спустя какое то время потребуются еще некоторые типы продаж, например, выигрыши по лотереи и т.п. Каждому типу продажи соответствует свой вид счета. К концу какого-либо отчетного периода, например, к концу дня, на основании выписанных счетов, необходимо вычислить общий объем продаж, среднюю величину продажи, а также минимальную и максимальную величину продажи.
Главное меню — компонент MainMenu
Компонент Главное меню - MainMenu – расположен на закладке Standard.
Это невизуальный компонент. Это значит, что его расположение на форме не играет никакого значения, его можно расположить в любом месте формы.
Основное свойство компонента — Items. Его можно найти в окне Инспектора Объектов.
После щелчка по полю (Menu) появится кнопка с многоточием. Нажатие этой кнопки (или двойной щелчок по пиктограмме компонента на форме) вызывает появление окна
Конструктор меню | Инспектор объектов |
которое является моделью, или конструктором меню. Одновременно в инспекторе Объектов появится новый объект без имени <Unnamed> с открытым окном Caption. В окно следует внести имя меню, например Файл. Если теперь нажать кнопку Enter, то это имя появится в конструкторе меню, а в Инспекторе объектов появится имя N1 и бледный текст подсказки TMenuItem
Конструктор меню | Инспектор объектов |
Кроме того, в конструкторе меню, появится место для создания нового раздела, которое выделено рамкой из точек. Щелчок по этой рамке приведет кпоявлению новонго безымянного раздела меню. Если потребуется разделы меню поменять местами, то это можно сделать перетащив его на нужное место с помощью мыши.
Щелчок по имени созданного раздела приводит к появлению рамки для создания пункта меню и к появлению нового безымянного объекта в инспекторе объектов. Создание пунктов меню ничем не отличается от создания разделов. Пункты меню можно перетаскивать.
Если потребуется поместить новый пункт меню между уже созданными пунктами то это можно сделать с помощью контекстного меню, которое появляется после щелчка правой кнопкой мыши. Щелчок по Insert добавляет новый пункт, соответственно щелчок по Delete удаляет его. Если это не получается, то еще раз воспользуйтесь свойством Items.
Контекстное меню для создания нового пункта | Меню после щелчка по Insert |
Важной командой является CreateSubmenu, которая позволяет создать подменю. Однако для этого еужно сначала установить свойство Default равным true.
Описание основных свойств приведено ниже.
Свойство | Описание |
Caption | Определяет надпись раздела. Символ амперсанд позволяет обозначить клавиш быстрого доступа. Если ввести символ минус "-", то вместо раздела в меню появится разделитель в виде черты |
Shortcut | Определяет клавиши быстрого доступа к разделу меню горячие клавиши, с помощью которых пользователь, не заходя в меню, может в любой момент вызвать выполнение процедуры, связанной с данным разделом. Чтобы определить клавиши быстрого доступа, надо открыть выпадающий списоксвойства Shortcut в окне Инспектора Объектов и выбрать из него нужную комбинацию клавиш. |
Name | Определяет имя объекта, соответствующего разделу меню. Рекомендуется давать объектам осмысленные имена. Имена в виде номера быстро приводят к путанице. |
Default | Указывает, имеет ли данный пункт по умолчанию свое подменю. умолчанию, выделяемый жирным шрифтом |
Break | Используется в длинных меню, чтобы разбить список разделов на несколько столбцов. Возможные значение Break: mbNone — отсутствие разбиения меню (это значение принято по умолчанию), mbBarBreak и mbBreak - в меню вводится новый столбец разделов, отделенный от предыдущего полосой (mbBarBreak) или пробелами (mbBreak). |
Checked | указывает, что в разделе меню будет отображаться маркер флажка, показывающий, что данный раздел выбран |
AutoCheck | Если его установить в true, то при каждом выборе пользователем данного раздела маркер будет автоматически переключаться,указывая то на выбранное.состояние, то на отсутствие выбора |
Radioltem | true означает, что данный раздел должен работать в режиме радиокнопки совместно с другими разделами, имеющими то же значение свойства Grouplndex. По умолчанию значение GroupIndex равно 0. |
Enabled | Означает доступносить.Еслиfalse,то будет изображаться серая надпись и пункт меню не будет реагировать на щелчок пользователя |
Visible | Видимость. Используются для изменения состава доступных пользователю разделов. |
Bitmap | Позволяет ввести изображение в раздел, выбрав его из указанного файла |
Imagelndex | Позволяет указать индекс изображения, хранящегося во внешнем компоненте ImageList |
Основное событие раздела — OnClick, возникающее при щелчке пользователя на разделе или при нажатии «горячих№ клавиш быстрого доступа.
Свойство — Action..
Сославшись на ранее описанное действие, вы избавляетесь от необходимости задавать большинство из указанных выше свойств, так как они наследуются от объекта действия. Вам не требуется также писать обработчик события OnClick, так как он оже наследуется от действия.
Диалоги
В приложениях часто приходится выполнять действия связанные с выбором и подтверждением или отрицаемем некоторых действий. Например, нужно выбрать файл из предлагаемрго списка. Для этого используются компоненты, размещенные на вкладке Dialogs. Но не всегда нас могут устроить стандартные диалоги. Несмотря на то, что в них предусмотрены достаточно широкие возможности настройки, специфика наших приложений может требовать каких-то дополнительных функций, которые невозможно реализовать в стандартном диалоге. Поэтому в C++Builder имеются компоненты, которые можно рассматривать как фрагменты диалоговых окон. Из них можно создавать собственные диалоги.
Пиктограмма | Компонент | Описание |
OpenDialog | Создает окно типа «Открыть файл» | |
SaveDialog | Создает окно типа «Сохранить файл» | |
OpenPictureDialog | Создает окно типа «Открыть рисунок» | |
SavePictureDialog | Создает окно типа «Сохранить рисунок» | |
FontDialog | Создает окно типа «Выбрать шрифт» | |
ColorDialog | Создает окно типа «Выбрать цвет» | |
PrintDialog | Создает окно типа «Печать» | |
PrinterSetupDialog | Создает окно типа «Установка принтера» | |
FindDialog | Создает окно типа «Найти» | |
ReplaceDialog | Создает окно типа «Заменить» |
На вкладке Additional есть еще два диалога
Пиктограмма | Компонент | Описание |
ColorBox | Создает список для выбора цвета | |
CustomizeDlg | Создает настраиваемый диалог, связанный со стандартными действиями. |
Есть еще несколько диалогов исполненных в стилеWindows 3.1, однако в настоящее время они практически не используются.
Все свойства этих компонентов одинаковы. Основное свойство, в котором возвращается в виде строки выбранный пользователем файл, — FileName. Значение этогосвойства можно задать и перед обращением к диалогу. Тогда оно появится в диалоге как значение по умолчанию в окне Имя файла.
Все диалоги являются невизуальными компонентами, так что место их размещения на форме не имеет значения. При обращении к этим компонентам вызываются стандартные диалоги, вид которых зависит от версии Windows и настройки системы. Так что при запуске одного и того же приложения на компьютерах с разными системами диалоги будут выглядеть по-разному. Например, при русифицированной версии Windows все их надписи будут русскими, а при англоязычной версии надписи будут на английском языке.
В качестве иллюстрации некоторых возможностей диалоговых компонентов создадим форму, в которой меню позволяет вызвать некоторые диалоги. Для этого поместим на форму компоненты Edit, MainMenu, OpenDialog и SaveDialog.
Создадим простое меню
Чтобы избежать путаницы в числовых именах разделовтменю их следует переименовать. В дальнейшем это будут File, Open, Save, Exit.
В модуле формы создадим глобальную переменную
AnsiString MyFileName="";
Обработчик OnClick раздела Открыть запишем так:
void __fastcall TForm1::OpenClick(TObject *Sender)
{
if(OpenDialog1->Execute()){
MyFileName=OpenDialog1->FileName;
Edit1->Text=MyFileName;
}
}
Поясним этот код.
Основной метод, которым производится обращение к любому диалогу, - Execute. Эта функция открывает диалоговое окно и, если пользователь произвел в нем какой-то выбор, то функция возвращает true. При этом в свойствах компонента — диалога запоминается выбор пользователя, который можно прочитать и использовать в дальнейших операциях. Если пользователь в диалоге нажал кнопку Отмена или клавишу Esc, то функция Execute возвращает false. Поэтому стандартное обращение к диалогу имеет вид:
if (<имя компонента - диалога> -> Execute())
<оператор, использующий выбор пользователя>;
Часто требуется выводит в диалог не все файлы, а только те, которые имеют заданное расширение, например, exe, doc, txt. Для этого существует окно «Тип файлов».
Типы искомых файлов, появляющиеся в диалоге в выпадающем списке. Тип файла задаются свойством Filter. Для этого щелчком по установленному на форме компоненту, в данном случае это OpenDialog, выделитьнужный компонет. В Инспекторе Объектов щелкнуть по окошку Filtr, и нажать появившуюся кнопку с многоточием.
После этого открывается окно редактора с таблицей из двух колонок. В левую половину записывается описание файлов, т.е. текст который увидит пользователь в выпадающем списке, а в правую, через точку с запятой, их расширения.
В данном случае задано три фильтра:
◦ фильтр текстовых файлов с расширениями .doc,.txt,.rtf,
◦ фильтр исполняемых файлов с расширениями .exe,.com,
◦ любых файлов с шаблоном "*.*".
После выхода из окна редактирования фильтров заданные шаблоны появятся в свойстве Filter в виде строки вида:
Текст (doc, txt, rtf)|*.doc; *.txt; *.rtf|Исполняемые|*exe; *.com|Все файлы|*.*
В этой строке тексты и шаблоны разделяются вертикальными линиями. В аналогичном виде, если требуется, можно задавать свойство Filter программно во время выполнения приложения.
Свойство | Описание |
Filterlndex | определяет номер фильтра, который будет по умолчанию показан пользователю в момент открытия диалога. Например, значение FilterIndex=1 задает по умолчанию первый фильтр. |
InitialDir | определяет начальный каталог, который будет открыт в момент начала работы пользователя с диалогом. Если значение этого свойства не задано, то открывается текущий каталог или тот, который был открыт при последнем обращении пользователя к соответствующему диалогу в процессе выполнения данного приложения. |
DefaultExt | определяет значение расширения файла по умолчанию. Если значение этого свойства не задано, пользователь должен указать в диалоге полное имя файла с расширением. Если же задать значение DefaultExt,то пользователь может писать в диалоге имя без расширения. В этом случае будет принято заданное расширение. |
Title | позволяет задать заголовок диалогового окна. Если это свойство не задано, окно открывается с заголовком, определенным в системе (например, ≪Сохранить как≫). Но можно задать и свой заголовок, подсказывающий пользователю ожидаемые действия. Например, ≪Укажите имя и тип сохраняемого файла |
Options | определяет условия выбора файла. |
Множество опций, которые можно установить программно или во время проектирования, включает:
ofAllowMuItiSelect | Позволяет пользователю выбирать несколько файлов. |
ofCreatePrompt | В случае, если пользователь написал имя несуществующего файла, появляется замечание и запрос, надо ли создать файл с заданным именем. |
ofEnablelncludeNotify | Разрешает посылать в диалог сообщения. |
ofEnableSizing | Разрешает изменять размер окна |
ofExtensionDifferent | Этот флаг, который можно прочитать после выполнения диалога, показывает, что расширение файла, выбранного пользователем, отличается от DefaultExt. |
ofFileMustExist | В случае, если пользователь написал имя несуществующего файла, появляется сообщение об ошибке. |
ofРideReadOnly | Удаляет из диалога индикатор Открыть только для чтения. |
ofNoChangeDir | После щелчка пользователя на кнопке ОК восстанавливает текущий каталог, независимо от того, какой каталог был открыть при поиске файла. |
ofNoDereferenceLinks | Запрещает переназначать клавиши быстрого доступа в диалоговом окне. |
ofNoLongNames | Отображаются только не более 8 символов имени и трех символов расширения. |
ofNoNetworkButton | Убирает из диалогового окна кнопку поиска в сети. Действует только если флаг ofOldStyleDialog включен. |
ofNoReadOnlyReturn | Если пользователь выбрал файл только для чтения, то генерируется сообщение об ошибке. |
ofNoTestFileCreate | Запрещает выбор в сети защищенных файлов и недоступных дисков при сохранении файла. |
ofNoValidate | Не позволяет писать в именах файловнеразрешенные символы, но не мешает выбирать файлы с неразрешенными символами. |
ofOldStyleDialog | Создает диалог выбора файла в старом стиле |
ofOverwritePrompt | В случае, если при сохранении файла пользователь написал имя существующего файла, появляется замечание, что файл с таким именем существует, и запрашивается желание пользователя переписать существующий файл. |
ofPathMustExist | Генерирует сообщение об ошибке, если пользователь указал в имени файла несуществующий каталог. |
ofReadOnly | По умолчанию включает индикатор Открыть только для чтения при открытии диалога. |
ofShareAware | Игнорирует ошибки нарушения условий коллективного доступа и разрешает, несмотря на них, производить выбор файла. |
ofShowHelp | Отображает в диалоговом окне кнопку Справка. |
В компонентах диалогов открытия и сохранения файлов предусмотрена возможность обработки ряда событий. Такая обработка может потребоваться, если рассмотренных опций, несмотря на их количество, не хватает, чтобы установить все диктуемые конкретным приложением ограничения на выбор файлов. Событие OnCanClose возникает при нормальном закрытии пользователем диалогового окна после выбора файла. При отказе пользователя от диалога — нажатии кнопки Отмена, клавиши Esc и т.д. событие OnCanClose не наступает. В обработке события OnCanClose можно произвести дополнительные проверки выбранного пользователем файла и, если по условиям вашей задачи этот выбор недопустим, можно известить об этом пользователя и задать значение false передаваемому в обработчик параметру CanClose. Это не позволит пользователю закрыть диалоговое окно.
Можно также написать обработчики событий OnFolderChange — изменение каталога, OnSelectionChange — изменение имени файла, OnTypeChange — изменение типа файла. В этих обработчиках вы можете предусмотреть какие-то сообщения пользователю.
Файлы и потоки
В языке С++ операторов ввода-вывода нет. Поэтому для передачи данных с одного устройства на другое используются, так называемые, потоки. В программировании слово поток перегружено смысловыми значениями. В данном случае слово поток это вспомогательная программа (объект класса), которая обеспечивает направленное движение данных. Хотя часто под потоком понимают само побайтное перемещениедвижение данных. Такое двусмысленное значение слова поток, обычно, не вызывает противоречий.
Если движение данных направлено в программу, то поток называют входным потоком, если из программы то выходным потоком. Кстати говоря, использованные нами «операторы» cin и cout организуют соответственно входной и выходной потоки. Причем cin (console input) это входной поток с клавиатуры, точнее стандартный поток ввода операционной системы, а cout (cosole output) это выходной поток данных на экран или стандартный поток вывода операционной системы. Директива #include <iostream> позволяет получить доступ к библиотеке программ, которая автоматически создает эти потоки. Кстати говоря, стандартные потоки могут быть перенаправлены на другие устройства или в файлы.