Лекции по структури от данни и програмиране


графите са нелинейни структури от данни; делят се на два вида: неориентиран граф



страница3/4
Дата25.07.2016
Размер0.95 Mb.
#6569
ТипЛекции
1   2   3   4

графите са нелинейни структури от данни; делят се на два вида:

  • неориентиран граф – разглежда се като G = < A, P >, където A е множество от върхове и P е множество от ненаредени двойки върхове, наречени ребра;

  • ориентиран граф – разглежда се като D = < A, R >, където A е множество от върхове и R е множество от наредени двойки върхове, наречени дъги;

ще разгледаме по-подробно крайните ориентирани графи, т.е. графите D = < A, R > при които A е крайно множество;

всъщност теориите на ориентираните и неориентираните графи са независими една от друга и разглеждат различни обекти;
нека е даден ориентиран граф D = < A, R >;

нека A1  A, R1  R; графът D1 = < A1, R1 > се нарича част от D;

ако A1 = A, казваме че D1 е субграф на D;

нека A2  A, R2  R, така че R2 съдържа всички дъги с краища върхове от A2 и само тях; графът D2 = < A2, R2 > се нарича подграф на D; ако D2  D, казваме че D2 е истински подграф на D;


отмерен ориентиран граф е такъв, при който всички дъги имат присвоена величина, която наричаме тегло;
пример:

нека графът D се състои от всички кръстовища и свързващите ги улици в даден град;

ако D1 съдържа всички кръстовища и всички улици, които ги свързват в даден квартал на града, то D1 е подграф на D;

ако D2 съдържа всички кръстовища в града и свързващите ги асфалтирани улици, то D2 е субграф на D;

ако D3 съдържа всички кръстовища и улици по които се движи градския транспорт, то D2 е част от D;
ако (a, b)  R и a = b, дъгата (a, b) наричаме примка;
нека X  A и (a, b)  R;

ако a  X, b  X, казваме че дъгата (a, b) влиза в X;

ако a  X, b  X, казваме че дъгата (a, b) излиза от X;

дъгите, които влизат в X и излизат от X се наричат инцидентни с X;

ще означаваме с RX+  R множеството от всички дъги, които излизат от X; броят на елементите на RX+ се нарича полустепен на изхода

на X, означаваме od (X);

ще означаваме с RX-  R множеството от всички дъги, които излизат от X; броят на елементите на RX- се нарича полустепен на входа

на X, означаваме id (X);

с td (X) означаваме броят на дъгите, инцидентни с X, т.е.

td (X) = id (X) + od (X);


пример:


5


1

3

4

2

това е изображение на следния граф:

D = < { 1, 2, 3, 4, 5}, { (1, 2), (2, 1), (1, 1), (2, 3), (4, 3) } >;

нека X = { 2, 4};

имаме RX+ = { (2, 3), (4, 3), (2, 1) } и od (X) = 3,

RX- = { (1, 2)} и id (X) = 1, td (X) = id (X) + od (X) = 3 + 1 = 4;


лесно се вижда, че сумата на полустепените на входа и на изхода на всички върхове на графа D дава броят на всички дъги в D, които не са примки;
последователността (a1, a2), (a2, a3), …, (an-1, an) от дъги наричаме път от a1 до an; ако a1 = an, казваме че пътят е цикъл;

ако ai  aj при i  j, казваме че пътят е прост;

ако върховете a1, …, an-1 са два по два различни и a1 = an, казваме че пътят е прост цикъл;

прост път, който съдържа всички върхове на графа се нарича Хамилтонов път;

прост цикъл, който съдържа всички върхове на графа се нарича Хамилтонов цикъл;

например в горния граф (1, 2), (2, 3) е прост път,

(1, 2), (2, 1) е прост цикъл; в него не съществуват Хамилтонови пътища и цикли;
граф, който съдържа поне един цикъл се нарича цикличен,

ако не съдържа нито един цикъл, казваме че графът е ацикличен;


върхът b е достижим от върха a, ако съществува път от a до b;

върхът a наричаме изолиран връх, ако той не е достижим от никой връх и никой връх не е достижим от него; в горния граф върхът 5 е изолиран връх, 3 е достижим от 1, 4 не е достижим от 3;


нека J  A; J наричаме минимално пораждащо подмножество

на A, ако:



  • всеки връх на A е достижим от някой връх от J;

  • J е минимално по включване с това свойство;

например минималното пораждащо подмножество на ацикличен ориентиран граф се определя като множеството от всички върхове с полустепен на входа 0;
ориентиран граф D = < A, R > с върхове a1, a2, …, an може еднозначно да бъде определен с квадратна матрица от ред n; тази матрица се нарича матрица на съседство; ще я означаваме с X; тя се дефинира по следния начин X = (xij), xij = 1, ако (ai, aj)  R и xij = 0, ако (ai, aj)  R;

за отмерен граф се използва матрица на теглата W = (wij),

wij = теглото на дъгата (ai, aj), ако (ai, aj)  R и wij = 0, ако (ai, aj)  R;

за графа D дефинираме матрица на пътищата P по следния начин: P = (pij), pij = 1, ако има път от ai до aj и pij = 0, ако няма път от ai до aj;

ще отбележим, че матрицата P не определя еднозначно графа;
Теорема: Нека Y = Xh, h  N; тогава yij дава броя на различните пътища с дължина h от ai до aj;

Следствие: Ако Xh = O за някое h, то графът D е ацикличен;


ориентираният граф D = < A, R > може да се представи и чрез списъци по следния начин: списък от върховете и за всеки връх списък от дъгите с начало в този връх; първият списък може да се замени с масив;
основни операции с графи:

  • създаване на граф;

  • проверка за празен граф;

  • търсене на връх или дъга в граф;

  • добавяне на връх или дъга;

  • премахване на връх или дъга;

  • намиране на път между два върха с точно определена дължина, в частност с минимална или максимална дължина;

  • намиране на циклите, в които участва даден връх на графа;

  • обхождане на граф; под обхождане на граф разбираме посещаване на всеки връх точно веднъж и евентуално извършване на допълнителни действия свързани с конкретната задача; съществуват два основни вида обхождане:

    • обхождане в дълбочина – ако обхождаме връх u, то непосредствено след това обхождаме един от наследниците му v и останалите наследници на u се обхождат когато се обходят всички наследници на v;

    • обхождане в широчина – ако обхождаме връх v, то непосредствено след това обхождаме всичките му наследници и тогава преминаваме към техните наследници;


27. Дървета.
ацикличен ориентиран граф, в който само един връх, наречен корен има полустепен на входа 0 и всички останали върхове имат полустепен на входа 1 се нарича ориентирано дърво; върховете с полустепен на изхода 0 се наричат листа на дървото; дължината на пътя, измерена в брой дъги, от корена до даден връх се нарича ниво на този връх – този път при дърветата е единствен, тъй като всеки връх има единствен предшественик;

два върха наричаме братя, ако са наследници на един и същи връх;

пример:


1

2

3

4

5

6

7

8













в това дърво 1 е корен, на първо ниво са 2, 3, 4, на второ ниво са

5, 6, 7 и на трето ниво е 8; листата са 2, 6, 7, 8; 2, 3, 4 са братя, 6 и 7 не са братя, въпреки че са на едно ниво;


всеки вътрешен връх в едно дърво (който не корен и не е листо) може да се разглежда като корен на дърво, което се състои от неговите наследници, от наследниците на техните наследници и т.н.;

например за горното дърво върхът 3 е корен на поддърво, което съдържа върховете 3, 5, 6, 8;

този факт дава основание да се даде алтернативна рекурсивна дефиниция за понятието дърво:

дървото е крайно множество T от върхове, такива че:



  • съществува точно един избран връх, наречен корен;

  • всички останали върхове се съдържат в k  0 на брой две по две непресичащи се подмножества T1, T2, …, Tk, които се наричат поддървета на корена на T; корените на тези поддървета наричаме наследници на корена на T;

ако в дървото е въведена наредба на върховете по всяко ниво, дървото се нарича наредено;


дърво в което всеки връх има полустепен на изхода 0, 1 или 2 се нарича двоично (бинарно) дърво;

ще дадем аналогична на горната рекурсивна дефиниция за двоично дърво: двоично дърво е крайно множество от върхове, което е или празно, или се състои от един връх, наречен корен и две непресичащи се двоични дървета, които се наричат ляво и дясно поддърво на корена на дървото;


произволно дърво може да се представи чрез двоично по следния рекурсивен алгоритъм:

  • коренът на двоичното дърво е коренът на изходното дърво;

  • ляво поддърво на корена на двоичното дърво е преобразуваното най-ляво поддърво на корена на изходното дърво;

  • дясно поддърво на корена на двоичното дърво е преобразуваното дърво на десния брат на корена;

ще използваме рекурсивната дефиниция за двоично дърво, за да опишем структура за елемент на дърво;


template < class KeyType >

struct btree {

KeyType key;

btree < KeyType> *left, *right;

} ;
по горния алгоритъм елемент на произволно дърво може да се опише по следния начин:
template < class KeyType >

struct tree {

KeyType key;

tree < KeyType > *first_left, *next_right;

} ;
рекурсивната дефиниция на двоично дърво предполага лесно използване на рекурсивни функции за операции с двоични дървета;

пример е обхождането на дърво, за което съществуват три основни метода:



  • смесен ред на обхождане:

    • обхожда се лявото поддърво;

    • посещава се корена, т.е. извършват се действия свързани с конкретната задача;

    • обхожда се дясното поддърво;

  • низходящ ред на обхождане:

    • посещава се корена;

    • обхожда се лявото поддърво;

    • обхожда се дясното поддърво;

  • възходящ ред на обхождане:

    • обхожда се лявото поддърво;

    • обхожда се дясното поддърво;

    • посещава се корена;

ще реализираме три функции, които реализират трите вида обхождане; функцията p (btree < KeyType > *t) реализира посещаване на корена на дървото, сочено от t;


функция, която реализира смесен ред на обхождане на двоично дърво;
template < class KeyType >

void inorder (btree *t)

{ if (t != NULL)

{ inorder (t -> left);

p (t);

inorder (t -> right);



}

}
функция, която реализира низходящ ред на обхождане на двоично дърво;


template < class KeyType >

void preorder (btree *t)

{ if (t != NULL)

{ p (t);


inorder (t -> left);

inorder (t -> right);

}

}
функция, която реализира възходящ ред на обхождане на двоично дърво;


template < class KeyType >

void postorder (btree *t)

{ if (t != NULL)

{ inorder (t -> left);

inorder (t -> right);

p (t);


}

}

аритметичните изрази могат да се представят по следния начин:



  • операциите са в нелиста;

  • операндите са в листа;

  • ако една операция се съдържа във връх v, то v има толкова наследника, колкото са аргументите на операцията и тя се прилага върху изразите, които отговарят на поддърветата на v;

например изразът a – b + c*d се представя по следния начин:




+



*

d

c

b

a

нека p (t) извежда ключа в съответния връх на дървото;



  • при смесено обхождане получаваме изразът във възприетия инфиксен запис: a – b + c*d;

  • при низходящо обхождане получаваме изразът в така наречения префиксен запис: + - a b * c d;

  • при възходящо обхождане получаваме изразът в постфиксен запис или още обратен полски запис;

обратният полски запис се използва от компилаторите за пресмятане на изрази; ще опишем алгоритъм за пресмятане на израз, който е в обратен полски запис: (разполагаме със стек, който в началото е празен)

  1. i = 1;

  2. прочитаме i-тия символ от израза;

ако той е операнд, го включваме в стека;

ако той е операция, изключваме двата операнда във върха на стека, прилагаме за тях операцията и полученото го включваме в стека; i = i + 1; ако има непрочетени символи,

премини към 2., в противен случай премини към 3.;


  1. резултатът е във върха на стека; край;

основни операции с дърво:



  • създаване на дърво;

  • проверка за празно дърво;

  • достъп до корена на дървото;

  • намиране на броя на поддърветата на даден връх;

  • включване на поддърво в дадено дърво;

  • изключване на поддърво от дърво;

  • унищожаване на дърво;

  • обхождане на дърво;

  • включване на връх в двоично дърво за търсене;

  • изключване на връх от двоично дърво за търсене;


28. Създаване на двоично дърво и включване на връх в в двоично дърво за търсене.
ще използваме следната структура за елемент на дървото:
struct btree {

int key;


btree *left, *right;

} ;
функция за създаване:


void create (btree *&t)

{ int x; char ch;

cout << “Ключ на корен: “;

cin >> x;

if ( (t = new btree) == NULL)

{ cout << “Няма достатъчно памет!\n” << endl;

exit (1);

}

t -> left = t -> right = NULL; t -> key = x;



cout << “Ляво поддърво с ключ на корена “ << x

<< “ Y/N? “;

ch = cin.get ();

if ( (ch = cin.get ()) == ‘Y’ || ch == ‘y’ )

create (t -> left);

cout << “Дясно поддърво с ключ на корена “ << x

<< “ Y/N? “;

ch = cin.get ();

if ( (ch = cin.get ()) == ‘Y’ || ch == ‘y’ )

create (t -> right);

}
за реализиране на конкретно дърво може да се използва следната главна функция:

void main ()

{ btree *tree;

create (tree);

}
под двоично дърво за търсене разбираме двоично дърво за всеки връх на което стойностите на всички ключове в лявото поддърво са по-малки от стойността на ключа във върха x и стойностите на всички ключове в дясното поддърво са не по-малки от стойността на ключа във върха x; такова дърво още наричаме двоично дърво за търсене с повторения, тъй като в него се допускат върхове с еднакви ключове; ако изискването за стойностите на ключовете на върховете в дясното поддърво на върха x е да са по-големи от стойността на ключа във върха x, тогава двоичното дърво за търсене е без повторения;


ще опишем функция за добавяне на връх в двоично дърво за търсене с повторения;
template < class KeyType >

struct btree {

KeyType key;

btree < KeyType > *left, *right;

} ;

template < class KeyType >



void insert ( KeyType x, btree < KeyType > *t)

{ if (t == NULL)

{ if ( ( t = new btree < KeyType >) == NULL)

{ cout << “Няма достатъчно памет!” << endl;

exit (1);

}

t -> left = t -> right = NULL; t -> key = x;



return;

}

if ( x < t -> key ) insert (x, t -> left);



else insert (x, t -> right);

}
за реализиране на конкретно дърво за търсене с повторения може да използваме следната главна функция:

void main ()

{ btree *tree = NULL;

insert (5, tree);

insert (3, tree);

insert (4, tree);

}


ще отбележим, че смесеното обхождане на дърво за търсене (с или без повторение) посещава върховете, сортирани във възходящ ред;
29. Обектно-ориентирана реализация на двоично дърво за търсене.
ще опишем клас за двоично дърво за търсене без повторения;

първо ще опишем клас за елемент от дървото;


файл treenode.h
#ifndef TREENODE_H

#define TREENODE_H

template < class NodeType > class Tree;

template < class NodeType >

class TreeNode {

friend class Tree < NodeType >;

public:

TreeNode (const NodeType &d) :



leftPtr (0), rightPtr (0), data (d) { }

NodeType getData () const

{ return data; }

private:


NodeType data;

TreeNode < NodeType > *leftPtr, *rightPtr;

} ;

#endif
сега ще опишем клас за самото дърво; той ще съдържа три функции за обхождане – в смесен, низходящ и възходящ ред; при обхождането стойностите на ключовете във върховете се извежат на екрана;



двоичното търсещо дърво, което реализираме може да се използва за сортиране на данни – достатъчно е да се добавят всички елементи и след това да се извърши смесено обхождане;
файл tree.h
#ifndef TREE_H

#define TREE_H

#include

#include “treenode.h”

template < class NodeType >

class Tree {

public:

Tree ();


void insertNode (const NodeType &);

void preOrderTraversal () const;

void inOrderTraversal () const;

void postOrderTraversal () const;

private:

TreeNode < NodeType > *rootPtr;

void insertNodeHelper (TreeNode *&, const NodeType &);

void preOrderHelper (TreeNode *) const;

void inOrderHelper (TreeNode *) const;

void postOrderHelper (TreeNode *) const;

} ;
template < class NodeType >

Tree < NodeType > :: Tree ()

{ rootPtr = 0; }

template < class NodeType >

void Tree < NodeType > :: insertNode ( const NodeType &value )

{ insertNodeHelper (rootPtr, value); }

template < class NodeType >

void Tree < NodeType > :: insertNodeHelper

( TreeNode < NodeType > *& ptr, const NodeType &value )

{ if (ptr == NULL)

{ ptr = new TreeNode (value);

assert (ptr != NULL);

}

else if (value < ptr -> data)



insertNodeHelper ( ptr -> leftPtr, value );

else if (value > ptr -> data)

insertNodeHelper ( ptr -> rightPtr, value );

else cout << value << “ е дубликат!” << endl;

}

template < class NodeType >



void Tree < NodeType > :: preOrderTraversal () const

{ preOrderHelper (rootPtr); }

template < class NodeType >

void Tree < NodeType > :: preOrderHelper

(TreeNode *ptr) const

{ if (ptr != 0)

{ cout << ptr -> data << ‘ ‘;

preOrderHelper (ptr -> leftPtr);

preOrderHelper (ptr -> rightPtr);

}

}



template < class NodeType >

void Tree < NodeType > :: inOrderTraversal () const

{ inOrderHelper (rootPtr); }

template < class NodeType >

void Tree < NodeType > :: inOrderHelper

(TreeNode *ptr) const

{ if (ptr != 0)

{ inOrderHelper (ptr -> leftPtr);

cout << ptr -> data << ‘ ‘;

inOrderHelper (ptr -> rightPtr);

}

}

template < class NodeType >



void Tree < NodeType > :: postOrderTraversal () const

{ postOrderHelper (rootPtr); }

template < class NodeType >

void Tree < NodeType > :: postOrderHelper

(TreeNode *ptr) const

{ if (ptr != 0)

{ postOrderHelper (ptr -> leftPtr);

postOrderHelper (ptr -> rightPtr);

cout << ptr -> data << ‘ ‘;

}

}



#endif
ще опишем програма, която използва гореописаните класове;
файл treetest.cpp
#include

#include

#include “tree.h”

using std::cout;

using std::cin;

using std::endl;

using std::setiosflags;

using std::ios;

using std::setprecision;

void main ()

{ Tree < int > intTree;

int intValue, i;

cout << “Въведете 10 int – стойности:\n”;

for (i = 0; i < 10; i++)

{ cin >> intValue;

intTree.insertNode (intValue);

}

cout << “Низходящо обхождане:\n“;



intTree.preOrderTraversal ();

cout << endl;

cout << “Смесено обхождане:\n“;

intTree.inOrderTraversal ();

cout << endl;

cout << “Възходящо обхождане:\n“;

intTree.postOrderTraversal ();

cout << endl;

Tree < double > doubleTree;

double doubleValue;

cout << “Въведете 10 double стойности:\n”

<< setiosflags (ios::fixed | ios::showpoint)

<< setprecision (1);

for (i = 0; i < 10; i++)

{ cin >> doubleValue;

doubleTree.insertNode (doubleValue);

}

cout << “Низходящо обхождане:\n“;



doubleTree.preOrderTraversal ();

cout << endl;

cout << “Смесено обхождане:\n“;

doubleTree.inOrderTraversal ();

cout << endl;

cout << “Възходящо обхождане:\n“;

doubleTree.postOrderTraversal ();

cout << endl;

}
ще отбележим, че конкретният вид на двоичното дърво за търсене съществено зависи от реда по който се подават данните;

търсенето на елемент в дървото в средния случай се осъществява с

log2n сравнения; това означава, че в дърво с 1000 върха съществуването на елемент с даден ключ може да се провери средно с около 10 сравнения, при дърво с 1000000 върха този брой е 20;

съществува, обаче, най-лош случай, в който дървото се изражда в списък – когато елементите се подават в сортиран ред; тогава сложността за търсене е линейна, т.е. от порядъка на броя на върховете на дървото;


30. Обработка на файлове с последователен достъп.
в C++ всеки файл се разглежда като последователност от n байта с номера от 0 до n-1, като всеки файл завършва със символа EOF;

за да се извърши вход и изход с файлове в C++ в програмата трябва да се включат заглавните файлове ifstream, ofstream; заглавният файл fstream включва и двата файла и някои други допълнителни функции; връзката между файловете и програмата се осъществява от потоците (обекти от посочените класове);


файловете се отварят чрез създаване на обекти от класовете ifstream, ofstream, fstream или чрез обръщение към вече създадени обекти;

за всеки файл, който предстои да бъде отворен, се задава режим за отваряне, като някои от възможностите са следните:



  • ios::app - за присъединяване на данни в края на файла без да се променят данните, които се намират вече в него;

  • ios::in – отваряне на файл за въвеждане;

  • ios::out – отваряне на файл за извеждане;

  • ios::binary – отваряне на файл за двоичен вход или изход; използва се с другите режими чрез използване на операция побитово или (‘|’);

отварянето на файл за извеждане става чрез създаване на обект от класа ofstream, като при създаването се предават два параметъра на съответния конструктор на класа – името на файла и режима за отваряне;


за обект от класа ofstream режима за отваряне може да бъде ios::out или ios::app; в режима ios::out съществуващите файлове се унищожават и създават наново, а несъществуващите файлове се създават; аргументът режим за отваряне по премълчаване е ios::out; обект от класа ofstream може да бъде създаден без да се отваря файл – за целта не се подават аргументи на конструктора; чрез този обект по-късно може да бъде отворен файл чрез функцията елемент open, която има същите параметри като конструктора за отваряне;
операцията отрицание (‘!’) е предефинирана в класа ios и се използва върху обекти от този клас; операцията връща ненулева стойност (true) точно когато при отварянето на файла с помощта на обекта са се установили флаговете failbit или badbit; някои възможни причини за това са отваряне на несъществуващ файл за четене, опит за създаване на файл когато на диска няма свободно място и т.н.;

операцията за преобразуване към тип void * също е предефинирана в класа ios; тя преобразува обект от клас ios към указател, който получава стойност 0, ако за обекта са установени флаговете failbit или badbit; например в условието на цикъла while (cin >> c) { …} , обектът cin автоматично се преобразува към void *; това условие е истина докато за cin не е установен някой от флаговете failbit или badbit; въвеждането на признак за край на файла (в DOS и в Windows с клавишна комбинация Ctrl+Z) води до установяване на failbit за cin; в този случай условието не е истина и цикълът ще приключи;


при излизане от областта на действие на обект, свързан с файл автоматично се извиква неговия деструктор който освен че унищожава обекта, затваря файла свързан с него (ако има такъв отворен файл); затварянето на файл може да става и явно чрез функцията елемент close без параметри; препоръчва се явно затваряне, тъй като това води до икономия на ресурси и до

по-голяма яснота в програмата;


ще разгледаме програма за създаване на файл с последователен достъп, който съдържа записи съставени от номер на сметка, баланс и име на клиента; предполага се, че файлът се създава и обработва в сортиран вид;
#include

using std::cout;

using std::cin;

using std::ios;

using std::cerr;

using std::endl;

#include

using std::ofstream;

#include

void main ()

{ ofstream outclientFile ("clients.dat", ios::out);

if (!outclientFile)

{ cerr << "Файлът не може да бъде отворен!" << endl;

exit (1);

}

cout << "Въведете записи от номер " <<



"на сметката, име и баланс; Ctrl+Z за край:\n? ";

int account; char name[30]; double balance;

while (cin >> account >> name >> balance)

{

outclientFile << account << ' ' << name << ' ' << balance << endl;



cout << "? ";

}

outclientFile.close ();



}
31. Четене на данни от файл с последователен достъп.
отварянето на файл за въвеждане става чрез създаване на обект от класа ifstream, като при създаването се предават два параметъра на съответния конструктор на класа – името на файла и режима за отваряне; по премълчаване режимът за отваряне е ios::in, т.е. режим за въвеждане; подобно на случая с обекти от класа ofstream, обекти от класа ifstream могат да бъдат създавани без да се отваря файл едновременно с това; по-късно в програмата чрез тези обекти

могат да се отварят файлове с помощта на функцията елемент open;

обекти от класа ifstream могат да се използват като условие в цикъл, например: while (обект_от_клас_ifstream >> c) { …} ; при компилиране на условието се използва операцията за преобразуване на обект от клас ios към указател от тип void *, който приема нулева стойност, ако за обекта са установени флагове failbit или badbit; достигането до символа за край на файла включва флага failbit, така че цикълът ще приключи, ако се достигне до края на файла;
при последователно търсене на данни във файл често се налага последователна обработка на файла, като на всяка стъпка се започва от началото на файла; за целта всеки обект от класа istream има така наречения указател get на позицията за въвеждане от файла и всеки обект от класа ostream има указател put на позицията за извеждане във файла; стойността на тези указатели е поредният номер на байт във файла; класовете istream и ostream съдържат функции елементи за задаване на стойност на указателя на позицията във файла;

тези функции са следните:



  • seekg – за позициониране при въвеждане от файл

(от класа istream);

  • seekp – за позициониране при извеждане във файл

(от класа ostream);
всяка от тези функции има по два аргумента; първият аргумент е от тип long int и задава новата стойност за указателя на позицията във файла, а вторият аргумент може да приема една от следните стойности:

  • ios::beg – позициониране относно началото на файла;

  • ios::end – позициониране относно края на файла;

  • ios::cur – позициониране относно текущата позиция;

по премълчаване стойността на този аргумент е ios::beg;
примери:

обект_от_клас_istream.seekg (0) задава стойност 0 на указателя на позицията във файла, който е свързан с обекта, т.е. това води до позициониране в началото на файла;

обект_от_клас_istream.seekg (n) води до позициониране на байт с номер n;

обект_от_клас_istream.seekg (n, ios::cur) води до позициониране с n байта напред, т.е. спрямо текущата позиция;

обект_от_клас_istream.seekg (n, ios::end) води до позициониране с n байта преди края на файла;

обект_от_клас_istream.seekg (0, ios::end) води до позициониране в края на файла;

аналогични примери могат да се направят с обект от клас ostream и функцията елемент seekp;
функциите елемент tellg от класа istream и tellp от класа ostream връщат текущите стойности на указателите get и put; те връщат стойност от тип long int;
текстовите файлове с последователен достъп съдържат записи с неограничена дължина, за това обновяване на място не може да се извършва, т.е. възможно е единствено презаписване на целия файл или присъединяване на данни в края на файла;
примерна програма:
#include

using std::cin;

using std::cout;

using std::endl;

using std::cerr;

using std::ios;

#include

using std::ifstream;

#include

#include

using std::setiosflags;

using std::setw;

using std::setprecision;

void main ()

{ ifstream inclientFile (“clients.dat”, ios::in);

if (!inclientFile)

{ cerr << “Файлът не може да бъде отворен!” << endl;

exit (1);

}

int account;



char name[30];

double balance;

cout << setiosflags (ios::left) << setw (10) << “Сметка” << setw (30)

<< “Име” << “Баланс” << endl;

while (inclientFile >> account >> name >> balance)

cout << setiosflags (ios::left) << setw (10) << account << setw (30)

<< name << setprecision (2) << balance << endl;

}

32. Обработка на файлове с пряк достъп.


основно средство при обработване на файлове с пряк достъп е използването на записи с фиксирана дължина; в този случай, програмата може бързо да определи точното местоположение на даден запис спрямо началото на файла;

при обработка на файлове с пряк достъп записването, обновяването и изключването на данни може да стават без да се разрушават другите данни или да се презаписва целия файл;


функцията елемент write от класа ostream извежда фиксиран брой байтове в зададен поток, започвайки от дадено място в оперативната памет; когато потокът е свързан с файл, данните се извеждат започвайки от позицията, определена с помощта на указателя put;

първият аргумент на write е от тип const char * и определя мястото в оперативната памет, откъдето ще се извежда; вторият аргумент е от цял тип и определя броя на байтовете за извеждане;


функцията елемент read от класа istream въвежда фиксиран брой байтове от зададен поток в област от оперативната памет;

ако потокът е свързан с файл, байтовете се въвеждат започвайки от позицията, определена с помощта на указателя get;

първият аргумент на read е от тип char * и определя мястото в оперативната памет, където ще се въвеждат байтовете; вторият аргумент е от цял тип и определя броя на байтовете за въвеждане;
ако се планира програма, която чете неформатирани данни, записани с write, то е необходимо тя да бъде компилирана и изпълнена в операционна система, съвместима с програмата, която е записала данните;
ще опишем програма за обработка на сметки, която може да съхрани до 100 записа с фиксирана дължина за компания с до 100 клиенти;

програмата ще обновява сметки, ще унищожава сметки, ще вмъква нови сметки и ще извежда списък на всички сметки във форматиран текстов файл;


ще опишем заглавен файл, който определя типа на структурата;
файл clientdata.h
#ifndef CLIENTDATA_H

#define CLIENTDATA_H

struct clientData {

int account;

char firstName[15], lastName[15];

double balance;

} ;

#endif
ще опишем програма за инициализиране на файла със записите;


файл clientinit.cpp
#include

using std::cerr;

using std::endl;

using std::ios;

#include

using std::ofstream;

#include

#include “clientdata.h”

void main ()

{ ofstream outCredit ( “credit.dat”, ios::binary);

if (!outCredit)

{ cerr << “Файлът не може да бъде отворен!\n”;

exit (1);

}

clientData blankClient = { 0, “”, “”, 0.0 };



for (int i = 0; i < 100; i++)

outCredit.write (reinterpret_cast < const char *> (&blankClient),

sizeof (clientData));

}
всеки незапълнен запис от типа на структурата съдържа номер 0, празни низове за име и фамилия и баланс 0; във файла първоначално се задават 100 незапълнени записаи по-нататък програмата лесно ще може да определи дали някой от записите е запълнен или не;

в извикването на функцията елемент write се извършва преобразуване с операцията reinterpret_cast, тъй като аргументът

(&blankClient) е от тип clientData* и така извикването се компилира без синтактична грешка;


ще опишем програма за директно записване във файла с пряк достъп;
файл clientadd.cpp
#include

using std::cerr;

using std::cin;

using std::cout;

using std::endl;

using std::ios;

#include

using std::ofstream;

#include

#include “clientdata.h”

void main ()

{ ofstream outCredit ( “credit.dat”, ios::binary);

if (!outCredit)

{ cerr << “Файлът не може да бъде отворен!\n”;

exit (1);

}

cout << “Въведете номер на сметка от 1 до 100, 0 за край: “;



clientData client;

cin >> client.account;

while (client.account > 0 && client.account <= 100)

{ cout << “Въведете името и баланса: “;

cin >> client.firstName >> client.lastName >> client.balance;

outCredit.seekp ( (client.account-1)*sizeof (clientData));

outCredit.write (reinterpret_cast < const char *> (&client),

sizeof (clientData));

cout << “Въведете номер на сметка от 1 до 100, 0 за край: “;

cin >> client.account;

}

}
програмата записва данни във файла чрез комбинация от функциите seekp и write – функцията seekp установява указателя put на зададена позиция във файла и след това данните се извеждат с write;



от номера на сметката се изважда 1, тъй като номерацията на байтовете във файла започва от 0;
накрая ще опишем програма, която извежда непразните записи;
файл clientprint.cpp
#include

using std::cout;

using std::endl;

using std::ios;

using std::cerr;

#include

using std::setprecision;

using std::setiosflags;

using std::setw;

#include

using std::ifstream;

using std::ostream;

#include

#include “clientdata.h”

void outputLine (ostream &, const clientData &);

void main ()

{ ifstream inCredit (“credit.dat”, ios::in);

if (!inCredit)

{ cerr << “Файлът не може да бъде отворен!”;

exit (1);

}

cout << setw (10) << “Сметка:“



<< setw (16) << “Име:“ << setw (16) << “Фамилия:”

<< setw (10) << “Баланс:” << endl;

clientData client;

inCredit.read (reinterpret_cast < char * > (&client), sizeof (clientData));

while (inCredit && !inCredit.eof ())

{ if (client.account != 0)

outputLine (cout, client);

inCredit.read (reinterpret_cast (&client), sizeof (clientData));

}

}



void outputLine (ostream &output, const clientData &c)

{ output << setw (10) << c.account



<< setw (16) << c.firstName << setw (16) << c.lastName

<< setprecision (2) << setiosflags (ios::fixed | ios::showpoint)

<< setw (10) << c.balance << endl;

}
програмата последователно чете записи от файла и извежда тези от тях, които са непразни; четенето с функцията read приключва, ако се достигне края на файла (тогава !inCredit.eof () става лъжа) или ако възникне грешка по време на четене (тогава inCredit става лъжа);

записите се извеждат във възходящ ред по номера на сметката – това е следствие от съхраняването на записите във файл с пряк достъп;
обработката на файлове с пряк достъп е по-бърза, но изисква повече памет, тъй като се налага да се съхраняват незапълнени записи;
33. Макроопределения. Включване на файлове.




Сподели с приятели:
1   2   3   4




©obuch.info 2024
отнасят до администрацията

    Начална страница