vien thong

Màu nền
Font chữ
Font size
Chiều cao dòng

Tổng hợp ví dụ về class (C++)

The class keyword declares a class type or defines an object of a class type.

[template-spec] class [ms-decl-spec] [tag [: base-list ]]

{

member-list

} [declarators];

[ class ] tag declarators;

Parameters

template-spec

Optional template specifications. For more information, refer to Template Specifications.

class

The class keyword.

ms-decl-spec

Optional storage-class specification. For more information, refer to the __declspec keyword.

tag

The type name given to the class. The tag becomes a reserved word within the scope of the class. The tag is optional. If omitted, an anonymous class is defined. For more information, see Anonymous Class Types.

base-list

Optional list of classes or structures this class will derive its members from. See Base Classes for more information. Each base class or structure name can be preceded by an access specifier (public, private, protected) and the virtual keyword. See the member-access table in Controlling Access to Class Members for more information.

member-list

List of class members. Refer to Class Members for more information.

declarators

Declarator list specifying the names of the class. Declarator lists declare one or more instances of the class type. Declarators may include initializer lists if all data members of the class are public. This is more common in structures, whose data members are public by default, than in classes. See Overview of Declarators for more information.

Remarks

For more information on classes in general, refer to one of the following topics:

• struct

• union

• __multiple_inheritance

• __single_inheritance

• __virtual_inheritance

For information on managed classes and structs, see Classes and Structs

ví dụ 1

//class.cpp phân lớp trong c++ // compile with: /EHsc

// Example of the class keyword

// Exhibits polymorphism/virtual functions.

#include <iostream>

#include <string>

#define TRUE = 1

using namespace std;

class dog

{

public:

dog()

{

_legs = 4;

_bark = true;

}

void setDogSize(string dogSize)

{

_dogSize = dogSize;

}

virtual void setEars(string type) // virtual function

{

_earType = type;

}

private:

string _dogSize, _earType;

int _legs;

bool _bark;

};

class breed : public dog

{

public:

breed( string color, string size)

{

_color = color;

setDogSize(size);

}

string getColor()

{

return _color;

}

// virtual function redefined

void setEars(string length, string type)

{

_earLength = length;

_earType = type;

}

protected:

string _color, _earLength, _earType;

};

int main()

{

dog mongrel;

breed labrador("yellow", "large");

mongrel.setEars("pointy");

labrador.setEars("long", "floppy");

cout << "Cody is a " << labrador.getColor() << " labrador" << endl;

}

VÍ DỤ 2:

#include <iostream>

#include <string>

using namespace std;

class person

{

public:

string name;

int age;

};

int main ()

{

person a, b;

a.name = "Calvin";

b.name = "Hobbes";

a.age = 30;

b.age = 20;

cout << a.name << ": " << a.age << endl;

cout << b.name << ": " << b.age << endl;

return 0;

Set Class - Insert Function

0

#1

Apr 13th, 2009

I am working on a set class and I have successfully written an insert function, but I need the function to insert in an ordered fashion.

Help with Code Tags

C++ Syntax (Toggle Plain Text)

1. bool Set::insert( const EType & A )

2. {

3. bool Flag = false;

4. unsigned Position = 0;

5. Node * Prev = Head;

6. Node * Curr = Head->Succ;

7. Node * Temp;

8.

9. Temp = new Node;

10. if (Temp != NULL )

11. {

12. Temp->Item = A;

13. Temp->Succ = Curr;

14. Prev->Succ = Temp;

15. Num++;

16. Flag = true;

17. }

18. return Flag;

19. }

Help with Code Tags

C++ Syntax (Toggle Plain Text)

1. Set::Set()

2. {

3. Num = 0;

4. Head = new (nothrow) Node;

5. Head->Succ = NULL;

6. }

Help with Code Tags

C++ Syntax (Toggle Plain Text)

1. class Set

2. {

3. private:

4.

5. struct Node

6. {

7. EType Item; // User data item

8. Node * Succ; // Link to the node's successor

9. };

10.

11. unsigned Num; // Number of user data items in the set

12. Node * Head; // Link to the head of the chain

VÍ DỤ 3

/**************************************************************

Author: Arun Vishnu M V

Web: www.arunmvishnu.com

Description: C++ Program to implement a QUEUE using linked list.

***************************************************************/

#include<conio.h>

#include<iostream.h>

#include<process.h>

#include<malloc.h>

// Creating a NODE Structure

struct node

{

int data;

struct node *next;

};

// Creating a class QUEUE

class queue

{

struct node *frnt,*rear;

public:

queue() // constructure

{

frnt=rear=NULL;

}

void insert(); // to insert an element

void del(); // to delete an element

void show(); // to show the stack

};

// Insertion

void queue::insert()

{

int value;

struct node *ptr;

cout<<"

Insertion

";

cout<<"Enter a number to insert: ";

cin>>value;

ptr=new node;

ptr->data=value;

ptr->next=NULL;

if(frnt==NULL)

frnt=ptr;

else

rear->next=ptr;

rear=ptr;

cout<<"

New item is inserted to the Queue!!!";

getch();

}

// Deletion

void queue::del()

{

if(frnt==NULL)

{

cout<<"

Queue is empty!!";

getch();

return;

}

struct node *temp;

temp=frnt;

frnt=frnt->next;

cout<<"

Deletion Operation........

Deleted value is "<<temp->data;

delete temp;

getch();

}

// Show Queue

void queue::show()

{

struct node *ptr1=frnt;

if(frnt==NULL)

{

cout<<"The Queue is empty!!";

getch();

return;

}

cout<<"

The Queue is

";

while(ptr1!=NULL)

{

cout<<ptr1->data<<" ->";

ptr1=ptr1->next;

}

cout<<"END";

getch();

}

// Main function

int main()

{

clrscr();

queue q;

int choice;

while(1)

{

cout<<"

-----------------------------------------------------------";

cout<<"

\t\tQUEUE USING LINKED LIST

";

cout<<"1:INSERTION

2:DELETION

3:DISPLAY QUEUE

4:EXIT";

cout<<"

Enter your choice(1-4): ";

cin>>choice;

switch(choice)

{

case 1:

q.insert();

break;

case 2:

q.del();

break;

case 3:

q.show();

break;

case 4:

exit(0);

break;

default:

cout<<"Please enter correct choice(1-4)!!";

getch();

break;

}

}

return 0;

}

//---------------------- END--------------------

VÍ DỤ 4:

#include <iostream.h>

#include <stdlib.h>

enum{FALSE=0,TRUE=-1};

/////////////////////////////////

///// Implements Priority Queue

/////////////////////////////////

class PriorityQueue // Class Prioriry Queue

{

private:

struct Node // Node of Priority Queue

{

struct Node *Previous;

int Data;

struct Node *Next;

}Current;

struct Node *head; // Pointer to Head

struct Node *ptr;

// Pointer for travelling through Queue

static int NumOfNodes;

// Keeps track of Number of nodes

public:

PriorityQueue(void);

int Maximum(void);

int Minimum(void);

void Insert(int);

int Delete(int);

void Display(void);

int Search (int);

~PriorityQueue(void);

};

// First Nodes Created With Constructor

int PriorityQueue::NumOfNodes=1;

// Constructor

PriorityQueue::PriorityQueue(void)

{

Current.Previous=NULL;

cout<<"Enter First Element of Queue"<<endl;

cin>>Current.Data;

Current.Next=NULL;

head=&Current;

ptr=head;

}

// Function Finding Maximum Priority Element

int PriorityQueue::Maximum(void)

{

int Temp;

ptr=head;

Temp=ptr->Data;

while(ptr->Next!=NULL)

{

if(ptr->Data>Temp)

Temp=ptr->Data;

ptr=ptr->Next;

}

if(ptr->Next==NULL && ptr->Data>Temp)

Temp=ptr->Data;

return(Temp);

}

// Function Finding Minimum Priority Element

int PriorityQueue::Minimum(void)

{

int Temp;

ptr=head;

Temp=ptr->Data;

while(ptr->Next!=NULL)

{

if(ptr->Data<Temp)

Temp=ptr->Data;

ptr=ptr->Next;

}

if(ptr->Next==NULL && ptr->Data<Temp)

Temp=ptr->Data;

return(Temp);

}

// Function inserting element in Priority Queue

void PriorityQueue::Insert(int DT)

{

struct Node *newnode;

newnode=new Node;

newnode->Data=DT;

while(ptr->Next!=NULL)

ptr=ptr->Next;

if(ptr->Next==NULL)

{

newnode->Next=ptr->Next;

ptr->Next=newnode;

}

NumOfNodes++;

}

// Function deleting element in Priority Queue

int PriorityQueue::Delete(int DataDel)

{

struct Node *mynode,*temp;

ptr=head;

if(NumOfNodes==1)

{

cout<<"Cannot Delete the only Node"<<endl;

return FALSE;

}

if(ptr->Data==DataDel)

{

/*** Checking condition for deletion of first node ***/

temp=ptr;

ptr=ptr->Next;

ptr->Previous=NULL;

//delete temp;

head=ptr;

NumOfNodes--;

return(TRUE);

}

else

{

while(ptr->Next->Next!=NULL)

{

/*** Checking condition for deletion of ***/

/*** all nodes except first and last node ***/

if(ptr->Next->Data==DataDel)

{

mynode=ptr;

temp=ptr->Next;

mynode->Next=mynode->Next->Next;

mynode->Next->Previous=ptr;

delete temp;

NumOfNodes--;

return(TRUE);

}

ptr=ptr->Next;

}

if(ptr->Next->Next==NULL && ptr->Next->Data==DataDel)

{

/*** Checking condition for deletion of last node ***/

temp=ptr->Next;

delete temp;

ptr->Next=NULL;

NumOfNodes--;

return(TRUE);

}

}

return(FALSE);

}

// Function Searching element in Priority Queue

int PriorityQueue::Search(int DataSearch)

{

ptr=head;

while(ptr->Next!=NULL)

{

if(ptr->Data==DataSearch)

return ptr->Data;

ptr=ptr->Next;

}

if(ptr->Next==NULL && ptr->Data==DataSearch)

return ptr->Data;

return(FALSE);

}

// Function Displaying elements of Priority Queue

void PriorityQueue::Display(void)

{

ptr=head;

cout<<"Priority Queue is as Follows:-"<<endl;

while(ptr!=NULL)

{

cout<<ptr->Data<<endl;

ptr=ptr->Next;

}

}

// Destructor of Priority Queue

PriorityQueue::~PriorityQueue(void)

{

struct Node *temp; /* Temporary variable */

while(head->Next!=NULL)

{

temp=head->Next;

// delete head;

head=temp;

}

if(head->Next==NULL)

delete head;

}

//Main Function

void main()

{

PriorityQueue PQ;

int choice;

int DT;

while(1)

{

cout<<"Enter your choice"<<endl;

cout<<"1. Insert an element"<<endl;

cout<<"2. Display a priorty Queue"<<endl;

cout<<"3. Delete an element"<<endl;

cout<<"4. Search an element"<<endl;

cout<<"5. Exit"<<endl;

cin>>choice;

switch(choice)

{

case 1:

cout<<"Enter a Data to enter Queue"<<endl;

cin>>DT;

PQ.Insert(DT);

break;

case 2:

PQ.Display();

break;

case 3:

{

int choice;

cout<<"Enter your choice"<<endl;

cout<<"1. Maximum Priority Queue"<<endl;

cout<<"2. Minimum Priority Queue"<<endl;

cin>>choice;

switch(choice)

{

case 1:

PQ.Delete(PQ.Maximum());

break;

case 2:

PQ.Delete(PQ.Minimum());

break;

default:

cout<<"Sorry Not a correct choice"<<endl;

}

}

break;

case 4:

cout<<"Enter a Data to Search in Queue"<<endl;

cin>>DT;

if(PQ.Search(DT)!=FALSE)

cout<<DT<<" Is present in Queue"<<endl;

else

cout<<DT<<" is Not present in Queue"<<endl;

break;

case 5:

exit(0);

default:

cout<<"Cannot process your choice"<<endl;

}

}

}

Ví dụ hay nhất 5

Cũng tương tự như stack, Quêu là một cấu trúc có nhiều nút trải dài từ đầu hàng đợi đến cuối hàng đợi - với hàng đợi chúng ta chỉ được phép thêm nút vào cuối hàng đợi và xóa nút ở đầu hàng đợi nghĩa là việc truy xuất các nút trên hàng đợi xảy ra ở cả hai đầu. Vì nút thêm vào trước sẽ được lấy ra trước nên cấu trúc hàng đợi còn được gọi là cấu trúc FIFO (Firt In First Out). Ví dụ như bạn đi mua vé xem phim với người iu chẳng hạn thì người mua xếp hàng trước sẽ được mua vé trước còn người mua xếp hàng sau thì sẽ mua vé sau.

Và hàng đợi để làm gì ???: Hàng đợi để giải quyết những vấn đề có cơ chế FIFO như dịch vụ quản lý kho, dịch vụ ngân hàng ....

Và sau đây là mô hình Queue-ADT

1. Mô tả dữ liệu:

Có nhiều nút cùng kiểu dữ liệu.

Có đầu hàng đợi (front) và cuối hàng đợi (tail)

2. Mô tả các tác vụ trên hàng đợi:

* initialize

Chức năng: Khởi động hàng đợi.

Dữ liệu nhập: không.

Dữ liệu xuất: front và tail được gán giá trị phù hợp.

* empty:

Chức năng: Kiểm tra hàng đợi có bị rỗng không.

Dữ liệu nhập: không.

Dữ liệu xuất True or False. (true khi hàng đợi rỗng, false khi hàng đợi không rỗng)

* insert:

Chức năng: thêm nút mới vào cuối hàng đợi.

Dữ liệu nhập: nút mới.

Điều kiện: hàng đợi không bị đầy.

Dữ liệu xuất: không.

* remove

Chức năng: xóa nút ở đầu hàng đợi.

Dữ liệu nhập: không.

Điều kiện: hàng đợi không bị rỗng.

Dữ liệu xuất: nút bị xóa.

* queuefront:

Chức năng truy xuất nút ở đầu hàng đợi.

Dữ liệu nhập: không.

Điều kiện: hàng đợi không bị rỗng.

Dữ liệu xuất: nút tại đầu hàng đợi.

* queuetail:

Chức năng truy xuất nút tại cuối hàng đợi.

Dữ liệu nhập: không.

Điều kiện: hàng đợi không bị rỗng.

Dữ liệu xuất: nút tại cuối hàng đợi.

* queue size:

Chức năng: xác định số nút hiện có trong hàng đợi.

Dữ liệu nhập: không.

Dữ liệu xuất:số nút có trong hàng đợi.

* clearqueue:

Chức năng: xóa tất cả các nút có trong hàng đợi.

Dữ liệu nhập: không.

Dữ liệu xuất: front và tail với giá trị phù hợp.

Các phương pháp cài đặt Queue-ADT

Cũng như stack chúng ta cài đặt hàng đợi theo hai cách là cài đặt kiểu kế tiếp (dùng mảng 1 chiều hoặc dùng mảng vòng) và cài đặt kiểu liên kết (dùng DSLK đơn, vòng, kép, kép+vòng và giống như stack thì cài đặt bằng DSLK kép+vòng là hay nhất).

Để đơn giản và dễ hiểu nhất thì tớ sẽ cài đặt bằng mảng 1 chiều. Minh họa bằng ngôn ngữ C++

const Max = 100;

class queue

{

private:

int front,tail; //dau va cuoi hang doi

int nodes[Max];

public:

queue() // cau tu khoi dong hang doi

{

front = 0;

tail = -1;

}

int empty(); // kiem tra hang doi bi rong khong

int full(); // kiem tra hang doi co bi day chua

void insert(int x); // them mot nut vao cuoi hang doi

int remove(); // xoa nut o dau hang doi

int queuesize(); // xac dinh so nut co trong hang doi

};

int queue::empty()

{

return ((tail < front) ? true : false);

}

int queue::full()

{

return ((tail == Max-1) ? true : false);

}

void queue::insert(int x)

{

if(full())

cout<<"Hang doi bi day";

else

nodes[++tail] = x;

}

int queue::remove()

{

if(empty())

cout<<"hang doi bi rong";

else

return(nodes[front++]);

}

int queue::queuesize()

{

return(tail - front + 1);

}

VÍ DỤ 6:

// File: priorityQueue.cpp

// Author: Nuno Alves ([email protected])

//Purpose: To write a class priority

Queue. As private data your class should

//contain an array of queues. Each element of the array corresponds to a

//different priority.

//Your insert method puts an element into the queue corresponding to its

//priority; the remove method checks the queues one by one to find the first

//non-empty queue and then takes it from there.

//You may assume that your priority queue is storing integer data, although

//your design will be nicer if the type is parameterized--i.e. if you make

//your class a template class.

//Dịch by VANHOAN:

/ / File: priorityQueue.cpp

/ / Tác giả: Nuno Alves ([email protected])

/ /

/ / Mục đích: Để viết một ưu tiên lớp

Hàng đợi. Theo dữ liệu riêng lớp học của bạn nên

/ / chứa một mảng của hàng đợi. Mỗi phần tử của mảng tương ứng với một

/ / khác nhau ưu tiên.

/ / Phương thức chèn của bạn đặt một phần tử vào hàng đợi tương ứng với nó

/ / ưu tiên; các loại bỏ các phương pháp kiểm tra hàng đợi cái một để tìm kiếm đầu tiên

/ / không có sản phẩm nào xếp hàng và sau đó nhận nó từ đó.

/ / Bạn có thể cho rằng hàng đợi ưu tiên của bạn được lưu trữ dữ liệu số nguyên, mặc dù

/ / thiết kế của bạn sẽ đẹp hơn nếu gõ là tham số - tức là nếu bạn thực hiện

/ / lớp của bạn một mẫu lớp.

#include <iostream>

#include <cstdlib>

#include <ctime>

using namespace std;

//Node structure. This is the base of this program. This Node structure has

//a pointer to the next node (*next) and an integer value (data). If there

//is no next node, then the value of next is NULL.

struct Node {

Node(int d) : data(d), next(0) { }

Node *next;

int data;

};

//class queue

//===========

//this class will implement a simple queue.

//

//example of operation:

//

// queue myqueue; //initializes the queue object

// myqueue.insert(1); //inserts value 1 into the queue

// myqueue.insert(2); //inserts value 2 into the queue

// myqueue.display(); //displays the contents of the queue

// myqueue.remove(); //removes the foremost value from the queue (1)

// myqueue.display(); //displays the contents of the queue

//

class queue{

public:

queue(){size=0;}

void insert(int data);

int remove ();

void display();

int get_size(){return size;} //returns the current size of queue

private:

int size;

Node *pNode;

Node *pHead;

};

//inserts a node into the queue. Takes in a data value that will

//be used to store in the node.

void queue::insert(int data)

{

//everytime another node is inserted, the size of the queue is

//increased... this is just so we can keep track of its size

size++;

Node *newnode;

newnode = new Node(data);

//if there already is more than one node in the queue

if (size>1)

{

//adds the address of the the newly created node to the

//previous node

pNode->next=newnode;

//puts the newnode in the last position of the queue

pNode=pNode->next;

}

//if there is no nodes in the queue, then add the first node to the

//pHead and never touch it again... This is how the class know where

//the linkedlist starts

else

{

pHead=newnode;

//pNode will now know what the newnode is

pNode=newnode;

}

}

//this will remove the first member of the queue. Returns the value

//that was inside the node that was just removed

int queue::remove()

{

//if there is something to remove

if (size>0)

{

//stores the value of the first node in the queue in a temporary

//variable

int value=pHead->data;

Node *ptmp;

ptmp=pHead;

//changes the Head to the second node

pHead=pHead->next;

//reclaims some memory back to the os

delete ptmp;

size--; //decrease the size of the queue

//returns the value stored in the temporary variable

return(value);

}

else return(-1); //if the queue is empty

}

//this will display all the queue contents

void queue::display()

{

//if there exist a node, then proceed

if (size>0)

{

Node *ptmp;

ptmp=pHead;

//goes through every single node and outputs its contents

while(ptmp!=NULL)

{

cout<<" "<<ptmp->data;

ptmp=ptmp->next;

}

}

cout<<"

";

}

//class priorityQueue

//====================

//

//This class will implement a priority queue (various queues with different

//degrees of priority)

//

//example of operation:

//

//priorityQueue myqueue(3); //initialization of a priority queue with 3

// //levels of priority

// //priority queues: 0 is highest and 2 is the

// //lowest

//

//myqueue.insert_pq(2,13); //inserts the value 13, into queue with

// //priority 1

//

//myqueue.display_pq(); //displays all queues, contents and its

// //priorities

//

//myqueue.remove_pq(); //remove a node from the priority queue

class priorityQueue{

public:

priorityQueue(int n); //n is the number of queues

void insert_pq(int priority,int data);

void display_pq();

int remove_pq();

private:

int nqueues;

queue *pqueue;

};

//constructor. It will create an array of queues and store the number

//of queues in the nqueues variable

priorityQueue::priorityQueue(int n)

{

nqueues = n;

//allocating memory for the new array of queues

pqueue = new queue [nqueues];

}

//this will insert a value into the proper queue given its priority

void priorityQueue::insert_pq(int priority, int data)

{

//a bit of error checking against evil users

if ((priority>=nqueues)||(priority<0))

cout<<"There is no queue with such priority level "<<priority<<"

";

else

//inserting data in the queue the user specified

pqueue[priority].insert(data);

}

//this will display the contents of all priority queues

void priorityQueue::display_pq()

{

//scanning all queues while displaying its contents

for(int i=0;i<nqueues;i++){

cout<<"Displaying Queue with priority "<<i<<": ";

pqueue[i].display();

}

}

//this will remove one value from the priority queue. Since one value needs

//to be removed, it will start from the queue with highest priority. If it

//does not have any value to be removed, then it will go the the next queue

//lower in priority. It will keep doing this until an entry is removed or

//until all queues are scanned

int priorityQueue::remove_pq()

{

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

{

//if there are elements in queue of a a certain priority

if (pqueue[i].get_size()>0){

//delete the first value of the queue and

//return the value

cout<<"

From Queue with priority "<<i;

return(pqueue[i].remove());

}

}

return(-1); //returns -1 if there is no more elements to remove in

//the entire thing

}

void main()

{

srand(time(0));

int random_nbr=1+rand()%10;

cout<<"Random number of priority queues (from 1 to 10): "<<random_nbr;

//creating a number (random_nbr) of priority queues.

priorityQueue myqueue(random_nbr);

cout<<"

Priority queues:

0 has highest priority

"<<

random_nbr-1<<" has lowest priority

";

//inserting x random values into the priority queue (1 to 20 values),

//in random queues with different priorities. The values inserted will

//also be random (1 to 30)

//

//example of operation:

//myqueue.insert_pq(2,13); //inserts the value 13, into queue with

// //priority 1

int x=1+rand()%20;

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

myqueue.insert_pq(rand()%(random_nbr),1+rand()%30);

//displaying the all queues contents, its values and priorities

myqueue.display_pq();

//removing all contents of the priorityqueue. It will not stop until

//all elements have been removed

while (random_nbr!=-1)

{

random_nbr=myqueue.remove_pq();

if (random_nbr!=-1)

cout<<", "<<random_nbr<<" was removed";

else

cout<<"

No more elements to remove... priority queue is empty.

";

}

VÍ DỤ 7 tiếng việt

Queue_in_C++

#include <iostream.h>

class element

{

public:

int value;

element* next;

};//class element

class Queue

{

public:

int size;

element* head;

element* tail;

Queue()

{

size=0;

head=NULL;

tail=NULL;

}//default constructor

void Enqueue(int);

void Dequeue();

int isEmpty();

int getQueueSize();

void printQueueSize();

void printQueueElements();

void printQueueMenu();

};

void Queue::Enqueue(int ele)

{

if(head==NULL) // first element

{

head=new element;

tail=head; //head==tail if one element

head->value=ele;

head->next=NULL;

}

else

{

tail->next=new element;

tail->next->value=ele;

tail->next->next=NULL;

cout<<tail->next->value<<endl;

tail=tail->next;

}

size++;

//printQueueElements();

}

void Queue: equeue()

{

if(getQueueSize()==0)

return;

else if(head==tail)

{

head=NULL;

}

else

{

element *curr,*prev; //remove the first element inserted and

curr=head; //point the head to next element

head=curr->next;

curr=NULL;

}

size--;

}

int Queue::isEmpty()

{

if(getQueueSize()==0)

return 1;

return 0;

}

int Queue::getQueueSize()

{

return size;

}//returns size of the Queue

void Queue: rintQueueSize()

{

cout<<"

The Size of the Queue:"<<size<<"

";

}//print the Queue size

void Queue: rintQueueElements()

{

element* curr2;

curr2= head;

cout<<"

-----

";

cout<<"Queue

";

cout<<"-----

";

cout<<"size:"<<getQueueSize()<<endl;

while(curr2!=NULL)

{

cout<<" |"<<curr2->value<<"|";

curr2=curr2->next;

}

cout<<endl;

}// print the Queue

void Queue: rintQueueMenu()

{

cout<<"Welcome to Queue

";

cout<<"1.Enqueue an element

";

cout<<"2.Dequeue an element

";

cout<<"3.Display Queue

";

cout<<"4.Size Of Queue

";

cout<<"5.Exit

";

}

void main()

{

Queue qt;

qt.printQueueMenu();

char Option=0;

int val;

while(1)

{

qt.printQueueMenu();

cin>>Option;

switch(Option)

{

case '1':

cout<<"Enter a Number

";

cin>>val;

qt.Enqueue(val);

break;

case '2':

qt.Dequeue();

break;

case '3':

qt.printQueueElements();

break;

case '4':

qt.printQueueSize();

break;

case '5':

exit(0);

break;

}

}

}

Ví dụ 8:

Stack_inC++

#include <stdio.h>

#include <conio.h>

#include <iostream.h>

#include <stdlib.h>

class element

{

public:

int value;

element* next;

};//class element

class stack

{

public:

int size;

element* current;

stack()

{

size=0;

current=NULL;

}//default constructor

bool push(int,element*);

bool pop();

bool isEmpty();

int getStackSize();

void printStackSize();

void printStackElements(element*);

void printStackMenu();

};

bool stack: ush(int ele,element* temp)

{

temp=new element;

if(current==NULL)

{

temp->next=NULL;

}

else

{

temp->next=current;

}

temp->value=ele;

current=temp;

printf("%d inserted

",ele);

size++;

return false;

}

bool stack: op()

{

if(isEmpty())

{

cout<<"

Stack is Empty

";

return false;

}

else

{

cout<<"

Element To POP :"<<current->value;

cout<<"

Before POP";

printStackElements(current);

current=current->next;

cout<<"

After POP";

printStackElements(current);

size=size--;

}

return true;

}

bool stack::isEmpty()

{

if(getStackSize()==0)

return true;

return false;

}

int stack::getStackSize()

{

return size;

}//returns size of the stack

void stack: rintStackSize()

{

cout<<"

The Size of the Stack:"<<size<<"

";

}//print the stack size

void stack: rintStackElements(element* base)

{

element* curr2;

curr2= base;

cout<<"

-----

";

cout<<"STACK

";

cout<<"-----

";

while(curr2!=NULL)

{

cout<<" |"<<curr2->value<<"|

";

curr2=curr2->next;

}

}// print the stack

void stack: rintStackMenu()

{

cout<<"Welcome to Stack

";

cout<<"1.Push an element

";

cout<<"2.Pop an element

";

cout<<"3.Display Stack

";

cout<<"4.Size Of Stack

";

cout<<"5.Exit

";

}

void main()

{

stack st;

char Option=0;

int val;

while(1)

{

st.printStackMenu();

cin>>Option;

switch(Option)

{

case '1':

cout<<"Enter a Number

";

cin>>val;

st.push(val,st.current);

break;

case '2':

st.pop();

break;

case '3':

st.printStackElements(st.current);

break;

case '4':

st.printStackSize();

break;

case '5':

exit(0);

break;

}

}

}

VÍ DỤ 9:

Double Linked List _in C++

#include <iostream.h>

class node

{

public:

int value; //value stored in the node

node *next; //pointer to next node

node *prev; //pointer to previous node

};

class dlist

{

public:

node *front; //pointer to front of list

node *back; //pointer to back of list

dlist()

{

front=NULL;

back=NULL;

}

void insertFront(int value);

void insertBack(int value);

void removeFront();

void removeBack();

void insertBefore(int value,node *nodeB);

void insertAfter(int value,node *nodeA);

void removeBefore(node *nodeB);

void removeAfter(node *nodeA);

void removeNode(node *newNode);

void printDListFront();

void printDListBack();

};

//insert a node before nodeB

void dlist::insertBefore(int value,node *nodeB)

{

node *newNode;

newNode=new node();

newNode->prev=nodeB->prev;

newNode->next =nodeB;

newNode->value =value;

if(nodeB->prev==NULL)

{

this->front=newNode;

}

nodeB->prev=newNode;

removeNode(newNode);

}

//insert a node before the front node

void dlist::insertFront (int value)

{

node *newNode;

if(this->front==NULL)

{

newNode=new node();

this->front=newNode;

this->back =newNode;

newNode->prev=NULL;

newNode->next=NULL;

newNode->value=value;

}

else

{

insertBefore(value,this->front );

}

}

//insert a node after nodeB

void dlist::insertAfter(int value,node *nodeB)

{

node *newNode;

newNode=new node();

newNode->next= nodeB->next ;

newNode->prev =nodeB;

newNode->value =value;

if(nodeB->next==NULL)

{

cout<<"

"<< endl;

this->back =newNode;

}

nodeB->next=newNode;

cout<<"2"<<endl;

}

//insert a node after the last node

void dlist::insertBack (int value)

{

if(this->back==NULL)

{

cout<<"insert at back";

insertFront(value);

}

else

{

cout<<"insert at back";

insertAfter(value,this->back );

}

}

//remove the front node

void dlist::removeFront ()

{

removeNode(this->front);

}

//remove a back node

void dlist::removeBack ()

{

removeNode(this->back);

}

//remove before a node

void dlist::removeBefore(node *nodeB)

{

if(nodeB->prev==this->front)

{

this->front=nodeB;

this->front->prev=NULL;

}

else

{

removeNode(nodeB->prev);

}

}

//remove after a node

void dlist::removeAfter(node *nodeA)

{

if(nodeA->next==this->back)

{

this->back=nodeA;

this->back->next=NULL;

}

else

{

removeNode(nodeA->next);

}

}

//remove a perticular node

void dlist::removeNode(node *nodeToRemove)

{

if(nodeToRemove==this->front)

{

this->front=this->front->next;

this->front->prev=NULL;

}

else if (nodeToRemove==this->back)

{

this->back=this->back->prev;

this->back->next=NULL;

}

else

{

nodeToRemove->prev->next=nodeToRemove->next;

nodeToRemove->next->prev=nodeToRemove->prev;

}

}

//Print the list from front

void dlist: rintDListFront()

{

node* curr2;

curr2= this->front;

cout<<"

-----

";

cout<<"Queue

";

cout<<"-----

";

//cout<<"size:"<<getQueueSize()<<endl;

while(curr2!=NULL)

{

cout<<" |"<<curr2->value<<"|";

curr2=curr2->next;

}

cout<<endl;

}// print the Double Linked List from front

// print the Double Linked List from backwards

void dlist: rintDListBack()

{

node* curr2;

curr2= this->back;

cout<<"

-----

";

cout<<"Queue

";

cout<<"-----

";

//cout<<"size:"<<getQueueSize()<<endl;

while(curr2!=NULL)

{

cout<<" |"<<curr2->value<<"|";

curr2=curr2->prev;

}

cout<<endl;

}// print the Double Linked List from back

void main()

{

dlist *st ;

st= new dlist();

st->insertBack(8);

st->printDListFront ();

st->insertBack(5);

st->printDListFront ();

st->insertBack(6);

st->printDListFront ();

st->insertFront(1) ;

st->printDListFront ();

st->insertFront(3) ;

st->printDListFront ();

st->insertBack(7);

st->printDListFront ();

st->removeFront();

st->printDListFront ();

st->removeBack();

st->printDListFront ();

}

Giáo trtinhf c++

CHƯƠNG 7

LỚP VÀ ĐỐI TƯỢNG

Lập trình có cấu trúc và lập trình hướng đối tượng

Lớp và đối tượng

Đối của phương thức - Con trỏ this

Hàm tạo (contructor)

Hàm hủy (destructor)

Các hàm trực tuyến (inline)

LẬP TRÌNH CÓ CẤU TRÚC VÀ LẬP TRÌNH HƯỚNG ĐỐI TƯỢNG

Phương pháp lập trình cấu trúc

 Lập trình cấu trúc là tổ chức chương trình thành các chương trình con. Trong một số ngôn ngữ như PASCAL có 2 kiểu chương trình con là thủ tục và hàm, còn trong C++ chỉ có một loại chương trình con là hàm.

 Hàm là một đơn vị chương trình độc lập dùng để thực hiện một phần việc nào đó như: Nhập số liệu, in kết quả hay thực hiện một số công việc tính toán. Hàm cần có đối và các biến, mảng cục bộ dùng riêng cho hàm.

 Việc trao đổi dữ liệu giữa các hàm thực hiện thông qua các đối và các biến toàn cục.

 Một chương trình cấu trúc gồm các cấu trúc dữ liệu (như biến, mảng, bản ghi) và các hàm, thủ tục.

 Nhiệm vụ chính của việc tổ chức thiết kế chương trình cấu trúc là tổ chức chương trình thành các hàm, thủ tục.

Ví dụ, ta xét yêu cầu sau: Viết chương trình nhập toạ độ (x,y) của một dãy điểm, sau đó tìm một cặp điểm cách xa nhau nhất.

Trên tư tưởng của lập trình cấu trúc có thể tổ chức chương trình như sau:

• Sử dụng 2 mảng thực toàn bộ x và y để chứa toạ độ dãy điểm.

• Xây dựng 2 hàm:

Hàm nhapsl dùng để nhập toạ độ n điểm, hàm này có một đối là biến nguyên n và được khai báo như sau:

void nhapsl(int n);

Hàm do_dai dùng để tính độ dài đoạn thẳng đi qua 2 điểm có chỉ số là i và j, nó được khai báo như sau:

float do_dai(int i, int j);

Chương trình C của ví dụ trên được viết như sau:

#include <stdio.h>

#include <conio.h>

#include <math.h>

float x[100],y[100];

float do_dai(int i, int j)

{

return sqrt(pow(x[i]-x[j],2)+pow(y[i]-y[j],2));

}

void nhapsl(int n)

{

int i;

for (i=1; i<=n; ++i)

{

printf("

Nhap toa do x, y cua diem thu %d : ",i);

scanf(''%f%f",&x[i],&y[i]);

}

}

void main()

{

int n, i, j, imax,jmax;

float d, dmax;

printf(''

So diem N= '');

scanf(''%d'', &n);

nhapsl(n);

dmax=do_dai(1,2);imax=1; jmax=2;

for(i=1; i<=n-1; ++i)

for (j=i+1; j<=n; ++j)

{

d=do_dai(i,j);

if (d>dmax)

{

dmax=d;

imax=i; jmax=j;

}

}

printf(''

Doan thang lon nhat co do dai bang: %0.2f",dmax);

printf(''

Di qua 2 diem co chi so la %d va %d'',imax,jmax);

getch();

}

Phương pháp lập trình hướng đối tượng

Là lập trình có cấu trúc + trừu tượng hóa dữ liệu. Có nghĩa chương trình tổ chức dưới dạng cấu trúc. Tuy nhiên việc thiết kế chương trình sẽ xoay quanh dữ liệu, lấy dữ liệu làm trung tâm. Nghĩa là trả lời câu hỏi: Chương trình làm việc với những đối tượng dữ liệu nào, trên các đối tượng dữ liệu này cần thao tác, thực hiện những gì. Từ đó gắn với mỗi đối tượng dữ liệu một số thao tác thực hiên cố định riêng của đối tượng dữ liệu đó, điều này sẽ qui định chặt chẽ hơn những thao tác nào được thực hiện trên đối tượng dữ liệu nào. Khác với lập trình cấu trúc thuần túy, trong đó dữ liệu được khai báo riêng rẽ, tách rời với thao tác xử lý, do đó việc xử lý dữ liệu thường không thống nhất khi chương trình được xây dựng từ nhiều lập trình viên khác nhau.

Từ đó lập trình hướng đối tượng được xây dựng dựa trên đặc trưng chính là khái niệm đóng gói. Đóng gói là khái niệm trung tâm của phương pháp lập trình hướng đối tượng, trong đó dữ liệu và các thao tác xử lý nó sẽ được qui định trước và "đóng" thành một "gói" thống nhất, riêng biệt với các dữ liệu khác tạo thành kiểu dữ liệu với tên gọi là các lớp. Như vậy một lớp không chỉ chứa dữ liệu bình thường như các kiểu dữ liệu khác mà còn chứa các thao tác để xử lý dữ liệu này. Các thao tác được khai báo trong gói dữ liệu nào chỉ xử lý dữ liệu trong gói đó và ngược lại dữ liệu trong một gói chỉ bị tác động, xử lý bởi thao tác đã khai báo trong gói đó. Điều này tạo tính tập trung cao khi lập trình, mọi đối tượng trong một lớp sẽ chứa cùng loại dữ liệu được chỉ định và cùng được xử lý bởi các thao tác như nhau. Mọi lập trình viên khi làm việc với dữ liệu trong một gói đều sử dụng các thao tác như nhau để xử lý dữ liệu trong gói đó. C++ cung cấp cách thức để tạo một cấu trúc dữ liệu mới thể hiện các gói nói trên, cấu trúc dữ liệu này được gọi là lớp.

Để minh hoạ các khái niệm vừa nêu về kiêu dữ liệu lớp ta trở lại xét bài toán tìm độ dài lớn nhất đi qua 2 điểm. Trong bài toán này ta gặp một thực thể là dãy điểm. Các thành phần dữ liệu của lớp dãy điểm gồm:

• Biến nguyên n là số điểm của dãy

• Con trỏ x kiểu thực trỏ đến vùng nhớ chứa dãy hoành độ

• Con trỏ y kiểu thực trỏ đến vùng nhớ chứa dãy tung độ

Các phương thức cần đưa vào theo yêu cầu bài toán gồm:

• Nhập toạ độ một điểm

• Tính độ dài đoạn thẳng đi qua 2 điểm

Dưới đây là chương trình viết theo thiết kế hướng đối tượng.

#include <stdio.h>

#include <conio.h>

#include <math.h>

#include <alloc.h>

class daydiem

{

int n;

float *x,*y;

public:

float do_dai(int i, int j)

{

return sqrt(pow(x[i]-x[j],2)+pow(y[i]-y[j],2));

}

void nhapsl(void);

};

void daydiem::nhapsl(void)

{

int i;

printf(''

So diem N= '');

scanf("%d'',&n);

x = (float*)malloc((n+1)*sizeof(float));

y = (float*)malloc((n+1)*sizeof(float));

for (i=1; i<=n; ++i)

{

printf(''

Nhap toa do x, y cua diem thu %d : '',i);

scanf(''%f%f'',&x[i],&y[i]);

}

}

void main()

{

clrscr();

daydiem p;

p.nhapsl();

int n,i,j,imax,jmax;

float d,dmax;

n = p.n;

dmax=p.do_dai(1,2);imax=1; jmax=2;

for (i=1;i<=n-1;++i)

for (j=i+1;j<=n;++j)

{

d=p.do_dai(i,j);

if (d>dmax)

{

dmax=d;

imax=i; jmax=j;

}

}

printf(''

Doan thang lon nhat co do dai bang: %0.2f",dmax);

printf(''

Di qua 2 diem co chi so la %d va %d" , imax,jmax);

getch();

}

LỚP VÀ ĐỐI TƯỢNG

Trong lập trình hướng đối tượng, lớp (class) là một khái niệm rất quan trọng, nó cho phép giải quyết các vấn đề phức tạp của việc lập trình. Một lớp đơn (được định nghĩa như struct, union, hoặc class) bao gồm các hàm và dữ liệu có liên quan. Các hàm này là các hàm thành phần (member functon) hay còn là phương thức (method), thể hiện tác động của lớp có thể được thực hiện trên dữ liệu của chính lớp đó (data member).

Cũng giống như cấu trúc, lớp có thể xem như một kiểu dữ liệu. Vì vậy lớp còn gọi là kiểu đối tượng và lớp được dùng để khai báo các biến, mảng đối tượng (như thể dùng kiểu int để khai báo các biến mảng nguyên).

Như vậy từ một lớp có thể tạo ra (bằng cách khai báo) nhiều đối tượng (biến, mảng) khác nhau. Mỗi đối tượng có vùng nhớ riêng của mình và vì vậy ta cũng có thể quan niệm lớp chính là tập hợp các đối tượng cùng kiểu.

Khai báo lớp

Để khai báo một lớp, ta sử dụng từ khoá class như sau:

class tên_lớp

{

// Khai báo các thành phần dữ liệu (thuộc tính)

// Khai báo các phương thức (hàm)

};

Chú ý: Việc khai báo một lớp không chiếm giữ bộ nhớ, chỉcác đối tượng của lớp mới thực sự chiếm giữ bộ nhớ.

Thuộc tính của lớp có thể là các biến, mảng, con trỏ có kiểu chuẩn (int, float, char, char*, long,...) hoặc kiểu ngoài chuẩn đã định nghĩa trước (cấu trúc, hợp, lớp,...). Thuộc tính của lớp không thể có kiểu của chính lớp đó, nhưng có thể là con trỏ của lớp này, ví dụ:

class A

{

A x; //Không cho phép, vì x có kiểu lớp A

A* p ; //Cho phép , vì p là con trỏ kiểu lớp A

} ;

Khai báo các thành phần của lớp (thuộc tính và phương thức)

Các từ khóa private và public

Khi khai báo các thành phần dữ liệu và phương thức có thể dùng các từ khoá private và public để quy định phạm vi sử dụng của các thành phần này. Trong đó từ khóa private qui định các thành phần (được khai báo với từ khóa này) chỉ được sử dụng bên trong lớp (trong thân các phương thức của lớp). Các hàm bên ngoài lớp (không phải là phương thức của lớp) không được phép sử dụng các thành phần này. Đặc trưng này thể hiện tính che giấu thông tin trong nội bộ của lớp, để đến được các thông tin này cần phải thông qua chính các thành phần hàm của lớp đó. Do vậy thông tin có tính toàn vẹn cao và việc xử lý thông tin (dữ liệu) này mang tính thống nhất hơn và hầu như dữ liệu trong các lớp đều được khai báo với từ khóa này. Ngược lại với private, các thành phần được khai báo với từ khóa public được phép sử dụng ở cả bên trong và bên ngoài lớp, điều này cho phép trong chương trình có thể sử dụng các hàm này để truy nhập đến dữ liệu của lớp. Hiển nhiên nếu các thành phần dữ liệu đã khai báo là privte thì các thành phần hàm phải có ít nhất một vài hàm được khai báo dạng public để chương trình có thể truy cập được, nếu không toàn bộ lớp sẽ bị đóng kín và điều này không giúp gì cho chương trình. Do vậy cách khai báo lớp tương đối phổ biến là các thành phần dữ liệu được ở dạng private và thành phần hàm dưới dạng public. Nếu không quy định cụ thể (không dùng các từ khoá private và public) thì C++ hiểu đó là private.

Các thành phần dữ liệu (thuộc tính)

Được khai báo như khai báo các thành phần trong kiểu cấu trúc hay hợp. Bình thường các thành phần này được khai báo là private để bảo đảm tính giấu kín, bảo vệ an toàn dữ liệu của lớp không cho phép các hàm bên ngoài xâm nhập vào các dữ liệu này.

Các phương thức (hàm thành viên)

Thường khai báo là public để chúng có thể được gọi tới (sử dụng) từ các hàm khác trong chương trình.

Các phương thức có thể được khai báo và định nghĩa bên trong lớp hoặc chỉ khai báo bên trong còn định nghĩa cụ thể của phương thức có thể được viết bên ngoài. Thông thường, các phương thức ngắn được viết (định nghĩa) bên trong lớp, còn các phương thức dài thì viết bên ngoài lớp.

Một phương thức bất kỳ của một lớp, có thể sử dụng bất kỳ thành phần (thuộc tính và phương thức) nào của lớp đó và bất kỳ hàm nào khác trong chương trình (vì phạm vi sử dụng của hàm là toàn chương trình).

Giá trị trả về của phương thức có thể có kiểu bất kỳ (chuẩn và ngoài chuẩn)

Ví dụ sau sẽ minh hoạ các điều nói trên. Chúng ta sẽ định nghĩa lớp để mô tả và xử lý các điểm trên màn hình đồ hoạ. Lớp được đặt tên là DIEM.

• Các thuộc tính của lớp gồm:

int x ; // Hoành độ (cột)

int y ; // Tung độ (hàng)

int m ; // Mầu

• Các phương thức:

Nhập dữ liệu một điểm

Hiển thị một điểm

Ẩn một điểm

Lớp điểm được xây dựng như sau:

#include <iostream.h>

#include <graphics.h>

class DIEM

{

private:

int x, y, m ;

public:

void nhapsl() ;

void hien() ;

void an() { putpixel(x, y, getbkcolor());}

};

void DIEM::nhapsl()

{

cout <<"

Nhap hoanh do (cot) va tung do (hang) cua diem: '';

cin >> x >> y ;

cout << ''

Nhap ma mau cua diem: '';

cin >> m ;

}

void DIEM::hien()

{

int mau_ht ;

mau_ht = getcolor();

putpixel(x, y, m);

setcolor(mau_ht);

}

Qua ví dụ trên có thể rút ra một số chú ý sau:

+ Trong cả 3 phương thức (dù viết trong hay viết ngoài định nghĩa lớp) đều được phép truy nhập đến các thuộc tính x, y và m của lớp.

+ Các phương thức viết bên trong định nghĩa lớp (như phương thức an() ) được viết như một hàm thông thường.

+ Khi xây dựng các phương thức bên ngoài lớp, cần dùng thêm tên lớp và toán tử phạm vi :: đặt ngay trước tên phương phức để quy định rõ đây là phương thức của lớp nào.

Biến, mảng và con trỏ đối tượng

Như đã nói ở trên, một lớp (sau khi định nghĩa) có thể xem như một kiểu đối tượng và có thể dùng để khai báo các biến, mảng đối tượng. Cách khai báo biến, mảng đối tượng cũng giống như khai báo biến, mảng các kiểu khác (như int, float, cấu trúc, hợp,...), theo mẫu sau:

Tên_lớp danh sách đối ;

Tên_lớp danh sách mảng ;

Ví dụ sử dụng DIEM ở trên, ta có thể khai báo các biến, mảng DIEM như sau:

DIEM d1, d2, d3 ; // Khai báo 3 biến đối tượng d1, d2, d3

DIEM d[20] ; // Khai báo mảng đối tượng d gồm 20 phần tử

Mỗi đối tượng sau khi khai báo sẽ được cấp phát một vùng nhớ riêng để chứa các thuộc tính của nó. Chú ý rằng sẽ không có vùng nhớ riêng để chứa các phương thức cho mỗi đối tượng, các phương thức sẽ được sử dụng chung cho tất cả các đối tượng cùng lớp. Như vậy về bộ nhớ được cấp phát thì đối tượng giống cấu trúc.

Trong trường hợp này:

sizeof(d1) = sizeof(d2) = sizeof(d3) = 3*sizeof(int) = 6

sizeof(d) = 20*6 = 120

Thuộc tính của đối tượng

Trong ví dụ trên, mỗi đối tượng d1, d2, d3 và mỗi phần tử d[i] đều có 3 thuộc tính là x, y, m. Chú ý là mỗi thuộc tính đều thuộc về một đối tượng, vì vậy không thể viết tên thuộc tính một cách riêng rẽ mà bao giờ cũng phải có tên đối tượng đi kèm, giống như cách viết trong cấu trúc của C. Nói cách khác, cách viết thuộc tính của đối tượng như sau:

tên_đối_tượng.Tên_thuộc_tính

Với các đối tượng d1, d2, d3 và mảng d, có thể viết như sau:

d1.x; // Thuộc tính x của đối tượng d1

d2.x; // Thuộc tính x của đối tượng d2

d3.y; // Thuộc tính y của đối tượng d3

d[2].m; // Thuộc tính m của phần tử d[2]

d1.x = 100; // Gán 100 cho d1.x

d2.y =d1.x; // Gán d1.x cho d2.y

Sử dụng các phương thức

Cũng giống như hàm, một phương thức được sử dụng thông qua lời gọi. Tuy nhiên trong lời gọi phương thức bao giờ cũng phải có tên đối tượng để chỉ rõ phương thức thực hiện trên các thuộc tính của đối tượng nào.

Ví dụ lời gọi sau sẽ thực hiện nhập số liệu vào các thành phần d1.x, d1.y và d1.m: d1.nhapsl(); Câu lệnh sau sẽ thực hiện nhập số liệu vào các thành phần d[3].x, d[3].y và d[3].m: d[3].nhapsl() ;

Chúng ta sẽ minh họa các điều nói trên bằng một chương trình đơn giản sử dụng lớp DIEM để nhập 3 điểm, hiện rồi ẩn các điểm vừa nhập. Trong chương trình đưa vào hàm kd_do_hoa() dùng để khởi động hệ đồ hoạ.

#include <conio.h>

#include <iostream.h>

#include <graphics.h>

class DIEM

{

private:

int x, y, m ;

public:

void nhapsl();

void an() { putpixel(x,y,getbkcolor());}

void hien();

};

void DIEM::nhapsl()

{

cout << "

Nhap hoanh do (cot) va tung do (hang) cua DIEM: '' ;

cin>> x >> y ;

cout << "

Nhap ma tran cua diem: " ;

cin >> m ;

}

void DIEM::hien()

{

int mau_ht;

mau_ht = getcolor() ;

putpixel(x,y,m);

setcolor(mau_ht);

}

void kd_do_hoa()

{

int mh, mode ;

mh=mode=0;

initgraph(&mh, &mode, "C:\\TC\BGI");

}

void main()

{

DIEMd1, d2, d3 ;

d1.nhapsl(); d2.nhapsl(); d3.nhapsl();

kd_do_hoa();

setbkcolor(BLACK);

d1.hien(); d2.hien(); d3.hien();

getch();

d1.an(); d2.an(); d3.an();

getch();

closegraph();

}

Con trỏ đối tượng

Con trỏ đối tượng dùng để chứa địa chỉ của biến, mảng đối tượng. Nó được khai báo như sau:

Tên_lớp *con trỏ;

Ví dụ dùng lớp DIEM có thể khai báo:

DIEM *p1, *p2, *p3 ; // Khai báo 3 con trỏ p1, p2, p3

DIEM d1, d2 ; // Khai báo 2 đối tượng d1, d2

DIEM d[20] ; // Khai báo mảng đối tượng

và có thể thực hiện các câu lệnh:

p1= &d2 ; // p1 chứa địa chỉ của d2 , hay p1 trỏ tới d2

p2 = d ; // p2 trỏ tới đầu mảng d

p3 = new DIEM // Tạo một đt và chứa địa chỉ của nó vào p3

Để sử dụng thuộc tính của đối tượng thông qua con trỏ, ta viết như sau:

Tên_con_trỏ ® Tên_thuộc_tính

Chú ý: Nếu con trỏ chứa địa chỉ đầu của mảng, có thể dùng con trỏ như tên mảng.

Như vậy sau khi thực hiện các câu lệnh trên thì:

p1 ® x và d2.x là như nhau

p2[i].y và d[i].y là như nhau

Từ đó ta có quy tắc sử dụng thuộc tính: Để sử dụng một thuộc tính của đối tượng ta phải dùng phép . hoặc phép ®. Trong chương trình, không cho phép viết tên thuộc tính một cách đơn độc mà phải đi kèm tên đối tượng hoặc tên con trỏ theo các mẫu sau:

Tên_đối_tượng.Tên_thuộc_tính

Tên_con_trỏ ® Tên_thuộc_tính

Tên_mảng_đối_ tượng[chỉ_số].Tên_thuộc_tính

Tên_con_trỏ[chỉ_số].Tên_thuộc_tính

Chương trình dưới đây cũng sử dụng lớp DIEM để nhập một dãy điểm, hiển thị và ẩn các điểm vừa nhập. Chương trình dùng một con trỏ kiểu DIEM và dùng toán tử new để tạo ta một dãy đối tượng

#include <conio.h>

#include <iostream.h>

#include <graphics.h>

class DIEM

{

private:

int x, y, m ;

public:

void nhapsl();

void an() { putpixel(x,y,getbkcolor());}

void hien();

};

void DIEM::nhapsl()

{

cout <<"

Nhap hoanh do (cot) va tung do (hang) cua diem:'' ;

cin >> x >> y ;

cout << "

Nhap ma mau cua DIEM: '' ;

cin >> m ;

}

void DIEM::hien()

{

int mau_ht;

mau_ht = getcolor() ;

putpixel(x,y,m);

setcolor(mau_ht);

}

void kd_do_hoa()

{

int mh, mode ;

mh=mode=0;

initgraph(&mh, &mode, ''C:\\TC\BGI'');

}

void main()

{

DIEM *p;

int i, n;

cout << ''So diem: '' ;

cin >> n;

p = new DIEM[n+1];

for (i=1;i<=n;++i)

p[i].nhapsl();

kd_do_hoa();

for (i=1;i<=n;++i) p[i].hien();

getch();

for (i=1;i<=n;++i) p[i].an();

getch();

closegraph();

}

ĐỐI CỦA PHƯƠNG THỨC, CON TRỎ this

Con trỏ this là đối thứ nhất của phương thức

Chúng ta hãy xem lại phương thức nhapsl của lớp DIEM

void DIEM::nhapsl()

{

cout <<"

Nhap hoanh do (cot) va tung do (hang) cua diem:" ;

cin >> x >> y ;

cout <<''

Nhap ma mau cua diem: " ;

cin >> m ;

}

Trong phương thức này chúng ta sử dụng tên các thuộc tính x, y và m một cách đơn độc. Điều này có vẻ như mâu thuẫn với quy tắc sử dụng thuộc tính nêu trong mục trước. Thực tế C++ đã ngầm định sử dụng một con trỏ đặc biệt với tên gọi this trong các phương thức trên. Các thuộc tính viết trong phương thức được hiểu là thuộc một đối tượng do con trỏ this trỏ tới. Do đó, nếu tường minh hơn, phương thức nhapsl() có thể được viết dưới dạng tương đương như sau:

void DIEM::nhapsl()

{

cout << ''

Nhap hoanh do (cot) va tung do (hang) cua diem:'' ;

cin >> this ® x >> this ® y ;

cout << "

Nhap ma mau cua diem: '' ;

cin >>this ® m;

}

Như vậy có thể kết luận rằng: Phương thức bao giờ cũng có ít nhất một đối là con trỏ this và nó luôn luôn là đối đầu tiên của phương thức.

Tham số ứng với đối con trỏ this

Xét một lời gọi tới phương thức nhapsl() :

DIEM d1;

d1.nhapsl() ;

Trong trường hợp này tham số truyền cho con trỏ this chính là địa chỉ của d1:

this = &d1

Do đó:

this ® x chính là d1.x

this ® y chính là d1.y

this ® m chính là d1.m

Như vậy câu lệnh:d1.nhapsl() ;sẽ nhập dữ liệu cho các thuộc tính của đối tượng d1. Từ đó có thể rút ra kết luận sau:

Tham số truyền cho đối con trỏ this chính là địa chỉ của đối tượng đi kèm với phương thức trong lời gọi phương thức.

Các đối khác của phương thức

Ngoài đối đặc biệt this (đối này không xuất hiện một cách tường minh), phương thức còn có các đối khác được khai báo thư trong các hàm. Đối của phương thức có thể cókiểu bất kỳ (chuẩn và ngoài chuẩn).

Ví dụ để xây dựng phương thức vẽ đường thẳng qua 2 điểm ta cần đưa vào 3 đối: Hai đối là 2 biến kiểu DIEM, đối thứ ba kiểu nguyên xác định mã mầu. Vì đã có đối ngầm định this là đối thứ nhất, nên chỉ cần khai báo thêm 2 đối. Phương thức có thể viết như sau:

void DIEM::doan_thang(DIEM d2, int mau)

{

int mau_ht;

mau_ht = getcolor();

setcolor(mau);

line(this ® x, this ® y,d2.x,d2.y);

setcolor(mau_ht);

}

Chương trình sau minh hoạ các phương thức có nhiều đối. Ta vẫn dùng lớp DIEM nhưng có một số thay đổi:

• Bỏ thuộc tính m (mầu)

• Bỏ các phương thức hien và an

• Đưa vào 4 phương thức mới:

ve_doan_thang (Vẽ đoạn thẳng qua 2 điểm)

ve_tam_giac (Vẽ tam giác qua 3 điểm)

do_dai (Tính độ dài của đoạn thẳng qua 2 điểm)

chu_vi (Tính chu vi tam giác qua 3 điểm)

Chương trình còn minh hoạ:

• Việc phương thức này sử dụng phương thức khác (phương thức ve_tam_giac sử dụng phương thức ve_doan_thang, phương thức chu_vi sử dụng phương thức do_dai)

• Sử dụng con trỏ this trong thân các phương thức ve_tam_giac và chu_vi

Nội dung chương trình là nhập 3 điểm, vẽ tam giác có đỉnh là 3 điểm vừa nhập sau đó tính chu vi tam giác.

#include <conio.h>

#include <iostream.h>

#include <graphics.h>

#include <math.h>

#include <stdio.h>

class DIEM

{

private:

int x, y ;

public:

void nhapsl();

void ve_doan_thang(DIEM d2, int mau) ;

void ve_tam_giac(DIEM d2, DIEM d3,int mau) ;

double do_dai(DIEM d2)

{

DIEM d1 = *this ;

return sqrt(pow(d1.x - d2.x,2) + pow(d1.y - d2.y,2) ) ;

}

double chu_vi(DIEM d2, DIEM d3);

};

void DIEM::nhapsl()

{

cout <<''

Nhap hoanh do (cot) va tung do (hang) cua diem:'' ;

cin >> x >> y;

}

void kd_do_hoa()

{

int mh, mode ;

mh=mode=0;

initgraph(&mh, &mode, '''');

}

void DIEM::ve_doan_thang(DIEM d2, int mau)

{

setcolor(mau);

line(this ® x,this ® y,d2.x,d2.y);

}

void DIEM:: ve_tam_giac(DIEM d2, DIEM d3,int mau)

{

(*this).ve_doan_thang(d2,mau);

d2.ve_doan_thang(d3,mau);

d3.ve_doan_thang(*this,mau);

}

double DIEM:: chu_vi(DIEM d2, DIEM d3)

{

double s;

s=(*this).do_dai(d2)+ d2.do_dai(d3) + d3.do_dai(*this) ;

return s;

}

void main()

{

DIEM d1, d2, d3;

char tb_cv[20] ;

d1.nhapsl();

d2.nhapsl();

d3.nhapsl();

kd_do_hoa();

d1.ve_tam_giac(d2,d3,15);

double s = d1.chu_vi(d2,d3);

sprintf(tb_cv, "chu_vi = %0.2f", s);

outtextxy(10,10,tb_cv);

getch();

closegraph();

}

Một số nhận xét về đối của phương thức và lời gọi phương thức:

+ Quan sát nguyên mẫu phương thức:

void ve_doan_thang(DIEM d2, int mau) ;

sẽ thấy phương thức có 3 đối:

Đối thứ nhất là một đối tượng DIEM do this trỏ tới

Đối thứ hai là đối tượng DIEM d2

Đối thứ ba là biến nguyên mẫu

Nội dung phương thức là vẽ một đoạn thẳng đi qua các điểm *this và d2 theo mã mầu mau. Xem thân của phương sẽ thấy được nội dung này:

void DIEM::ve_doan_thang(DIEM d2, int mau) ;

{

setcolor(mau);

line(this ® x,this ® y,d2.x,d2.y);

}

Tuy nhiên trong trường hợp này, vai trò của this không cao lắm, vì nó được đưa vào chỉ cốt làm rõ đối thứ nhất. Trong thân phương thức có thể bỏ từ khóa this vẫn được.

+ Vai trò của this trở nên quan trọng trong phương thức ve_tam_giac:

voidve_tam_giac(DIEM d2, DIEM d3, int mau)

Phương thức này có 4 đối là:

this : trỏ tới một đối tượng kiểu DIEM

d2 : một đối tượng kiểu DIEM

d3 : một đối tượng kiểu DIEM

mau : một biến nguyên

Nội dung phương thức là vẽ 3 cạnh:

cạnh 1 đi qua *this và d2

cạnh 2 đi qua d2 và d3

cạnh 3 đi qua d3 và *this

Các cạnh trên đuợc vẽ nhờ sử dụng phương thức ve_doan_thang:

Vẽ cạnh 1 dùng lệnh: (*this).ve_doan_thang(d2,mau) ;

Vẽ cạnh 2 dùng lệnh: d2.ve_doan_thang(d3,mau);

Vẽ cạnh 3 dùng lệnh: d3.ve_doan_thang(*this,mau);

Trong trường này rõ ràng vai trò của this rất quan trọng. Nếu không dùng nó thì công việc trở nên khó khăn, dài dòng và khó hiểu hơn. Chúng ta hãy so sánh 2 phương án:

Phương án dùng this trong phương thức ve_tam_giac:

void DIEM::ve_tam_giac(DIEM d2, DIEM d3, int mau)

{

(*this).ve_doan_thang(d2,mau);

d2.ve_doan_thang(d3,mau); d3.ve_doan_thang(*this,mau);

}

phương án không dùng this trong phương thức ve_tam_giac:

void DIEM::ve_tam_giac(DIEM d2, DIEM d3, int mau)

{

DIEM d1;

d1.x = x; d1.y = y;

d1.ve_doan_thang(d2,mau);

d2.ve_doan_thang(d3,mau);

d3.ve_doan_thang(d1,mau);

}

HÀM TẠO (Constructor)

Hàm tạo (hàm thiết lập)

Hàm tạo cũng là một phương thức của lớp (nhưng là hàm đặc biệt) dùng để tạo dựng một đối tượng mới. Chương trình dịch sẽ cấp phát bộ nhớ cho đối tượng sau đó sẽ gọi đến hàm tạo. Hàm tạo sẽ khởi gán giá trị cho các thuộc tính của đối tượng và có thể thực hiện một số công việc khác nhằm chuẩn bị cho đối tượng mới.

Cách viết hàm tạo

i. Điểm khác của hàm tạo và các phương thức thông thường:

Khi viết hàm tạo cần để ý 3 sự khác biệt của hàm tạo so với các phương thức khác như sau:

• Tên của hàm tạo: Tên của hàm tạo bắt buộc phải trùng với tên của lớp.

• Không khai báo kiểu cho hàm tạo.

• Hàm tạo không có kết quả trả về.

ii. Sự giống nhau của hàm tạo và các phương thức thông thường

Ngoài 3 điểm khác biệt trên, hàm tạo được viết như các phương thức khác:

• Hàm tạo có thể được xây dựng bên trong hoặc bên ngoài định nghĩa lớp.

• Hàm tạo có thể có đối hoặc không có đối.

• Trong một lớp có thể có nhiều hàm tạo (cùng tên nhưng khác bộ đối).

Ví dụ sau định nghĩa lớp DIEM_DH (Điểm đồ họa) có 3 thuộc tính:

int x; // hoành độ (cột) của điểm

int y; // tung độ (hàng) của điểm

int m; // mầu của điểm

và đưa vào 2 hàm tạo để khởi gán cho các thuộc tính của lớp:

// Hàm tạo không đối: Dùng các giá trị cố định để khởi gán cho x, y, m

DIEM_DH() ;

// Hàm tạo có đối: Dùng các đối x1, y1, m1 để khởi gán cho x, y, m

DIEM_DH(int x1, int y1, int m1 = 15) ; // Đối m1 có giá trị mặc định 15

class DIEM_DH // (mầu trắng)

{

private:

int x, y, m ;

public:

// Hàm tạo không đối: khởi gán cho x = 0, y = 0, m = 1

// Hàm này viết bên trong định nghĩa lớp

DlEM_DH()

{

x = y = 0;

m = 1;

}

// Hàm tạo này xây dựng bên ngoài định nghĩa lớp

DIEM_DH(int x1, int y1, int m1 = 15) ;

// Các phương thức khác

};

// Xây dựng hàm tạo bên ngoài định nghĩa lớp

DIEM_DH:: DIEM_DH(int x1, int y1, int m1) ;

{

x = x1; y = y1; m = m1;

}

Dùng hàm tạo trong khai báo

+ Khi đã xây dựng các hàm tạo, ta có thể dùng chúng trong khai báo để tạo ra một đối tượng đồng thời khởi gán cho các thuộc tính của đối tượng được tạo. Dựa vào các tham số trong khai báo mà trình biên dịch sẽ biết cần gọi đến hàm tạo nào.

+ Khi khai báo một biến đối tượng có thể sử dụng các tham số để khởi gán cho các thuộc tính của biến đối tượng.

+ Khi khai báo mảng đối tượng không cho phép dùng các tham số để khởi gán.

+ Câu lệnh khai báo một biến đối tượng sẽ gọi tới hàm tạo 1 lần.

+ Câu lệnh khai báo một mảng n đối tượng sẽ gọi tới hàm tạo n lần.

Ví dụ:

DIEM_DH d; // Gọi tới hàm tạo không đối.

// Kết quả d.x = 0, d.y = 0, d.m = 1

DIEM_DH u(300, 100, 5); // Gọi tới hàm tạo có đối.

// Kết quả u.x = 300, u.y = 100, d.m = 5

DIEM_DH v(400, 200); // Gọi tới hàm tạo có đối.

// Kết quả v.x = 400, v.y = 200, d.m = 15

DIEM_DH p[20] ; // Gọi tới hàm tạo không đối 20 lần

Chú ý: Với các hàm có đối kiểu lớp, thì đối chỉ xem là các tham số hình thức, vì vậy khai báo đối (trong dòng đầu của hàm) sẽ không tạo ra đối tượng mới và do đó không gọi tới các hàm tạo.

Dùng hàm tạo trong cấp phát bộ nhớ

+ Khi cấp phát bộ nhớ cho một đối tượng có thể dùng các tham số để khởi gán cho các thuộc tính của đối tượng, ví dụ

DIEM_DH *q = new DIEM_DH(40, 20, 4); // Gọi tới hàm tạo có đối

// Kết quả q ® x = 40, q ® y = 20, q ® m = 4

DIEM_DH *r = new DIEM_DH ; //Gọi tới hàm tạo không đối

// Kết quả r ® x = 0, r ® y = 0, r ® m = 1

+ Khi cấp phát bộ nhớ cho một dãy đối tượng không cho phép dùng tham số để khởi gán, ví dụ:

int n = 30;

DIEM_DH *s = new DlEM_DH[n] ; // Gọi tới hàm tạo không đối 30 lần.

Dùng hàm tạo để biểu điền các đối tượng hằng

+ Như đã biết, sau khi định nghĩa lớp DIEM_DH thì có thể xem lớp này như một kiểu dữ liệu như int, double, char, ...

Với kiểu int chúng ta có các hằng int, như 253.

Với kiểu double chúng ta có các hằng double, như 75.42

Khái niệm hằng kiểu int, hằng kiểu double có thể mở rộng cho hằng kiểu DIEM_DH

+ Để biểu diễn một hằng đối tượng (hay còn gọi: Đối tượng hằng) chúng ta phải dùng tới hàm tạo. Mẫu viết như sau:

Tên_lớp(danh sách tham số) ;

Ví dụ đối với lớp DIEM_DH nói trên, có thể viết như sau:

DIEM_DH(234, l 23, 4) // Biểu thị một đối tượng kiểu DIEM_DH

// có các thuộc tính x = 234, y = 123, m = 4

Chú ý: Có thể sử dụng một hằng đối tượng như một đối tượng. Nói cách khác, có thể dùng hằng đối tượng để thực hiện một phương thức, ví dụ nếu viết:

DIEM_DH(234, l 23, 4).in();

thì có nghĩa là thực hiện phương thức in() đối với hằng đối tượng.

Ví dụ minh họa

Chương trình sau đây minh họa cách xây dựng hàm tạo và cách sử dùng hàm tạo trong khai báo, trong cấp phát bộ nhớ và trong việc biểu diễn các hằng đối tượng.

#include <conio.h>

#include <iostream.h>

#include <iomanip.h>

class DIEM_DH

{

private:

int x, y, m;

public:

// Hàm bạn dùng để in đối tượng DIEM_DH

friend void in(DIEM_DH d)

{

cout <<"

'' << d.x << '' ''<< d.y<<" " << d.m ;

}

// Phương thức dùng để in đối tượng DIEM_DH

void in()

{

cout <<''

'' << x << '' ''<< y<<" " << m ;

}

// Hàm tạo không đối

DIEM_DH()

{

x = y = 0;

m = 1;

}

// Hàm tạo có đối, đối m1 có giá trí mặc định là 15 (mầu trắng)

DIEM_DH(int x1, int y1, int m1 = 15);

};

// Xây dựng hàm tạo

DIEM_DH::DIEM_DH(int x1, int y1, int m1)

{

x = x1; y = y1; m = m1;

}

void main()

{

DIEM_DH d1; // Gọi tới hàm tạo không đối

DIEM_DH d2(200, 200, 10); // Gọi tới hàm tạo có đối

DIEM_DH*d;

d = new DIEM_DH(300, 300); // Gọi tới hàm tạo có đối

clrscr();

in(d1); //Gọi hàm bạn in()

d2.in(); //Gọi phương thức in()

in(*d); // Gọi hàm bạn in()

DIEM_DH(2, 2, 2).in(); // Gọi phương thức in()

DIEM_DH t[3]; // 3 lần gọi hàm tạo không đối

DIEM_DH*q; // Gọi hàm tạo không đối

int n;

cout << "

N = "; cin >> n;

q = new DIEM_DH[n+1]; // (n+1) lần gọi hàm tạo không đối

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

q[i] = DIEM_DH(300+i, 200+i, 8); //(n+1) lần gọi hàm tạo có đối

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

q[i].in(); // Gọi phương thức in()

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

DIEM_DH(300+i, 200+i, 8).in(); // Gọi phương thức in()

getch();

}

Lớp không có hàm tạo và hàm tạo mặc định

Nếu lớp không có hàm tạo

Chương trình dịch sẽ cung cấp một hàm tạo mặc định không đối (default), hàm này thực chất không làm gì cả. Như vậy một đối tượng tạo ra chỉ được cấp phát bộ nhớ, còn các thuộc tính của nó chưa được xác định. Chúng ta có thể kiểm chứng điều này, bằng cách chạy chương trình sau:

// Hàm tạo mặc định

#include <conio.h>

#include <iostream.h>

class DIEM_DH

{

private:

int x, y, m;

public:

// Phương thức

void in() { cout <<"

'' << x << '' ''<< y<<'' " << m ; }

};

void main()

{

DIEM_DH d;

d.in();

DIEM_DH *p;

p = new DIEM_DH[10];

clrscr();

d.in();

for (int i = 0; i<10; ++i) (p+i)->in();

getch();

}

Nếu trong lớp đã có ít nhất một hàm tạo

Khi đó hàm tạo mặc định sẽ không được phát sinh nữa và mọi câu lệnh xây dựng đối tượng mới đều sẽ gọi đến một hàm tạo của lớp. Nếu không tìm thấy hàm tạo cần gọi thì chương trình dịch sẽ báo lỗi. Điều này thường xẩy ra khi chúng ta không xây dựng hàm tạo không đối, nhưng lại sử dụng các khai báo không tham số như ví dụ sau:

#include <conio.h>

#include <iostream.h>

class DIEM_DH

{

private:

int x, y, m;

public:

// Phương thức dùng để in đối tượng DIEM_DH

void in()

{

cout <<"

'' << x << " "<< y<<" " << m ;

}

//Hàm tạo có đối

DIEM_DH::DIEM_DH(int x1, int y1 , int m1)

{

x = x1; y = y1 ; m = m1;

}

};

void main()

{

DIEM_DH d1(200, 200, 10); // Gọi tới hàm tạo có đối

DIEM_DH d2; // Gọi tới hàm tạo không đối

d2 = DIEM_DH(300, 300, 8); // Gọi tới hàm tạo có đối

d1.in();

d2.in();

getch();

}

Trong các câu lệnh trên, chỉ có câu lệnh thứ 2 trong hàm main() là bị báo lỗi. Câu lệnh này sẽ gọi tới hàm tạo không đối, mà hàm này chưa được xây dựng.

Giải pháp: có thể chọn một trong 2 giải pháp sau:

 Xây dựng thêm hàm tạo không đối.

 Gán giá trị mặc định cho tất cả các đối x1, y1 và m1 của hàm tạo đã xây dựng ở trên.

Theo phương án 2, chương trình có thể sửa như sau:

#include <conio.h>

#include <iostream.h>

class DIEM_DH

{

private:

int x, y, m;

public:

// Phương thức dùng để in đối tượng DIEM_DH

void in() { cout <<''

'' << x << " "<< y<<" " << m ; }

// Hàm tạo có đối , tất cả các đối đều có giá trị mặc định

DIEM_DH::DIEM_DH(int x1 = 0, int y1 = 0, int m1 = 15)

{

x = x1; y = y1; m = m1;

}

};

void main()

{

// Gọi tới hàm tạo, không dùng tham số mặc định

DIEM_DH d1(200, 200, 10);

// Gọi tới hàm tạo, dùng 3 tham số mặc định

DIEM_DH d2;

// Gọi tới hàm tạo, dùng 1 tham số mặc định

d2 = DIEM_DH(300, 300);

d1.in();

d2.in();

getch();

}

Hàm tạo sao chép (Copy Constructor)

Hàm tạo sao chép mặc định

Giả sử đã định nghĩa một lớp nào đó, ví dụ lớp PS (phân số). Khi đó:

+ Ta có thể dùng câu lệnh khai báo hoặc cấp phát bộ nhớ để tạo các đối tượng mới, ví dụ:

PS p1, p2 ;

PS *p = new PS ;

+ Ta cũng có thể dùng lệnh khai báo để tạo một đối tượng mới từ một đối tượng đã tồn tại, ví dụ:

PS u;

PS v(u) ; // Tạo v theo u

ý nghĩa của câu lệnh này như sau:

 Nếu trong lớp PS chưa xây dựng hàm tạo sao chép, thì câu lệnh này sẽ gọi tới một hàm tạo sao chép mặc định (của C++). Hàm này sẽ sao chép nội dung từng bit của u vào các bit tương ứng của v. Như vậy các vùng nhớ của u và v sẽ có nội dung như nhau. Rõ ràng trong đa số các trường hợp, nếu lớp không có các thuộc tính kiểu con trỏ hay tham chiếu, thì việc dùng các hàm tạo sao chép mặc định (để tạo ra một đối tượng mới có nội dung như một đối tượng cho trước) là đủ và không cần xây dựng một hàm tạo sao chép mới.

 Nếu trong lớp PS đã có hàm tạo sao chép (cách viết sẽ nói sau) thì câu lệnh: PS v(u); sẽ tạo ra đối tượng mới v, sau đó gọi tới hàm tạo sao chép để khởi gán v theo u.

Ví dụ sau sẽ minh họa cách dùng hàm tạo sao chép mặc định:

Trong chương trình đưa vào lớp PS (phân số):

+ Các thuộc tính gồm: t (tử số) và m (mẫu).

+ Trong lớp không có phương thức nào cả mà chỉ có 2 hàm bạn là các hàm toán tử nhập (>>) và xuất (<<).

+ Nội dung chương trình là: Dùng lệnh khai báo để tạo một đối tượng u (kiểu PS) có nội dung như đối tượng đã có d.

// Ham tao sao chep mac dinh

#include <conio.h>

#include <iostream.h>

class PS

{

private:

int t, m ;

public:

friend ostream& operator<< (ostream&os, const PS &p)

{

os << " = " << p.t << "/" << p.m;

return os;

}

friend istream& operator>> (istream& is, PS &p)

{

cout << "

Nhap tu va mau: " ;

is >> p.t >> p.m ;

return is;

}

} ;

void main()

{

PS d;

cout << "

Nhap PS d "; cin >> d;

cout << "

PS d " << d;

PS u(d);

cout << "

PS u "<< u;

getch();

}

Cách xây dựng hàm tạo sao chép

+ Hàm tạo sao chép sử dụng một đối kiểu tham chiếu đối tượng để khởi gán cho đối tượng mới. Hàm tạo sao chép được viết theo mẫu:

Tên_lớp (const Tên_lớp & dt)

{

// Các câu lệnh dùng các thuộc tính của đối tượng dt

// để khởi gán cho các thuộc tính của đối tượng mới

}

+ Ví dụ có thể xây dựng hàm tạo sao chép cho lớp PS như sau:

class PS

{

private:

int t, m ;

public:

PS (const PS &p)

{

this->t = p.t ;

this->m = p.m ;

}

...

} ;

Khi nào cần xây dựng hàm tạo sao chép

+ Nhận xét: Hàm tạo sao chép trong ví dụ trên không khác gì hàm tạo sao chép mặc định.

+ Khi lớp không có các thuộc tính kiểu con trỏ hoặc tham chiếu, thì dùng hàm tạo sao chép mặc định là đủ.

+ Khi lớp có các thuộc tính con trỏ hoặc tham chiếu, thì hàm tạo sao chép mặc định chưa đáp ứng được yêu cầu.

Ví dụ:

class DT

{

private:

int n; // Bac da thuc

double *a; // Tro toi vung nho chua cac he so da thuc a0, a1, ...

public:

DT() { this->n0; this->a = NULL; }

DT(int n1)

{

this->n = n1;

this->a = new double[n1+1];

}

friend ostream& operator << (ostream& os, const DT &d);

friend istream& operator>> (istream& is, DT &d);

...

} ;

Bây giờ chúng ta hãy theo dõi xem việc dùng hàm tạo mặc định trong đoạn chương trình sau sẽ dẫn đến sai lầm như thế nào:

DT d ; // Tạo đối tượng d kiểu DT

cin >> d ;

/* Nhập đối tượng d, gồm: nhập một số nguyên dương và gán cho d.n, cấp phát vùng nhớ cho d.a, nhập các hệ số của đa thức và chứa vào vùng nhớ được cấp phát */

DT u(d);

/* Dùng hàm tạo mặc định để xây dựng đối tượng u theo d. Kết quả: u.n = d.n và u.a = d.a. Như vậy 2 con trỏ u.a và d.a cùng trỏ đến một vùng nhớ */

Nhận xét: Mục đích là tạo ra một đối tượng u giống như d, nhưng độc lập với d. Nghĩa là khi d thay đổi thì u không bị ảnh hưởng gì. Thế nhưng mục tiêu này không đạt được, vì u và d có chung một vùng nhớ chứa hệ số của đa thức, nên khi sửa đổi các hệ số của đa thức trong d thì các hệ số của đa thức trong u cũng thay đổi theo. Còn một trường hợp nữa cũng dẫn đến lỗi là khi một trong 2 đối tượng u và d bị giải phóng (thu hồi vùng nhớ chứa đa thức) thì đối tượng còn lại cũng sẽ không còn vùng nhớ nữa.

Ví dụ sau sẽ minh họa nhận xét trên: Khi d thay đổi thì u cũng thay đổi và ngược lại khi u thay đổi thì d cũng thay đổi theo.

#include <conio.h>

#include <iostream.h>

#include <math.h>

class DT

{

private:

int n; // Bac da thuc

double *a; // Tro tơi vung nho chua cac he so da thuc a0, a1 , ...

public:

DT() { this->n = 0; this->a = NULL; }

DT(int n1)

{

this->n = n1 ;

this->a = new double[n1+1];

}

friend ostream& operator<< (ostream& os, const DT &d);

friend istream& operator>> (istream& is, DT &d);

} ;

ostream& operator<< (ostream& os, const DT &d)

{

os << " Cac he so (tu ao): ";

for (int i = 0 ; i< = d.n ; ++i)

os << d.a[i] <<" " ;

return os;

}

istream& operator >> (istream& is, DT &d)

{

if (d.a! = NULL) delete d.a;

cout << "

Bac da thuc: " ;

cin >> d.n;

d.a = new double[d.n+1];

cout << ''Nhap cac he so da thuc:

" ;

for (int i = 0 ; i< = d.n ; ++i)

{

cout << "He so bac "<< i << " = " ;

is >> d.a[i] ;

}

return is;

}

void main()

{

DT d;

clrscr();

cout <<"

Nhap da thuc d " ; cin >> d;

DT u(d);

cout << "

Da thuc d "<< d ;

cout << "

Da thuc u " << u ;

cout <<"

Nhap da thuc d " ; cin >> d;

cout << "

Da thuc d " << d;

cout <<"

Da thuc u " << u ;

cout <<"

Nhap da thuc u " ; cin >> u;

cout << "

Da thuc d "<< d ;

cout << "

Da thuc u " << u ;

getch();

}

Ví dụ về hàm tạo sao chép

Trong chương trình trên đã chỉ rõ: Hàm tạo sao chép mặc định là chưa thoả mãn đối với lớp DT. Vì vậy cần viết hàm tạo sao chép để xây dựng đối tượng mới (ví dụ u) từ một đối tượng đang tồn tại (ví dụ d) theo các yêu cầu sau:

+ Gán d.n cho u.n

+ Cấp phát một vùng nhớ cho u.a để có thể chứa được (d.n + 1) hệ số.

+ Gán các hệ số chứa trong vùng nhớ của d.a sang vùng nhớ của u.a

Như vậy chúng ta sẽ tạo được đối tượng u có nội dung ban đầu giống như d, nhưng độc lập với d.

Để đáp ứng các yêu cầu nêu trên, hàm tạo sao chép cần được xây dựng như sau:

DT::DT(const DT &d)

{

this ® n = d.n ;

this ® a = new double[d.n+1];

for (int i = 0; i< = d.n; ++i)

this ® a[i] = d.a[i];

}

Chương trình sau sẽ minh họa điều này: Sự thay đổi của d không làm ảnh hưởng đến u và ngược lại sự thay đổi của u không làm ảnh hưởng đến d.

// Viết hàm tạo sao chép cho lớp DT

#include <conio.h>

#include <iostream.h>

#include <math.h>

class DT

{

private:

int n; // Bac da thuc

double *a; // Tro toi vung nho chua cac he so da thuc a0, a1 , ...

public:

DT() { this ® n = 0; this ® a = NULL; }

DT(int n1)

{

this ® n = n1 ;

this ® a = new double[n1+1];

}

DT(const DT &d);

friend ostream& operator<< (ostream& os, const DT&d);

friend istream& operator>> (istream& is, DT&d);

};

DT::DT(const DT&d)

{

this ® n = d.n;

this ® a = new double[d.n+1];

for (int i = 0; i< = d.n; ++i)

this ® a[i] = d.a[i];

}

ostream& operator<< (ostream& os, const DT &d)

{

os << " Cac he so (tu ao): " ;

for (int i = 0 ; i< = d.n ; ++i) os << d.a[ i] <<" " ;

return os;

}

istream& operator>> (istream& is, DT &d)

{

if (d.a! = NULL) delete d.a;

cout << "

Bac da thuc: '' ;

cin >> d.n;

d.a = new double[d.n+1];

cout << ''Nhap cac he so da thuc:

'' ;

for (int i = 0 ; i< = d.n ; ++i)

{

cout << "He so bac " << i << " = " ;

is >> d.a[i] ;

}

return is;

}

void main()

{

DT d;

clrscr();

cout <<"

Nhap da thuc d " ; cin >> d;

DT u(d);

cout <<"

Da thuc d " << d ;

cout << "

Da thuc u " << u ;

cout <<"

Nhap da thuc d " ; cin >> d;

cout << "

Da thuc d "<< d ;

cout <<"

Da thuc u " << u ;

cout <<"

Nhap da thuc u " ; cin >> u;

cout << "

Da thuc d " << d ;

cout <<"

Da thuc u " << u ;

getch();

}

HÀM HỦY (Destructor)

Hàm hủy là một hàm thành viên của lớp (phương thức) có chức năng ngược với hàm tạo. Hàm hủy được gọi trước khi giải phóng (xoá bỏ) một đối tượng để thực hiện một số công việc có tính ''dọn dẹp'' trước khi đối tượng được hủy bỏ, ví dụ như giải phóng một vùng nhớ mà đối tượng đang quản lý, xoá đối tượng khỏi màn hình nếu như nó đang hiển thị, ...

Việc hủy bỏ một đối tượng thường xẩy ra trong 2 trường hợp sau:

+ Trong các toán tử và các hàm giải phóng bộ nhớ, như delete, free, ...

+ Giải phóng các biến, mảng cục bộ khi thoát khỏi hàm, phương thức.

Hàm hủy mặc định

Nếu trong lớp không định nghĩa hàm hủy, thì một hàm hủy mặc định không làm gì cả được phát sinh. Đối với nhiều lớp thì hàm hủy mặc định là đủ, và không cần đưa vào một hàm hủy mới.

Quy tắc viết hàm hủy

Mỗi lớp chỉ có một hàm hủy viết theo các quy tắc sau:

+ Kiểu của hàm: Hàm hủy cũng giống như hàm tạo là hàm không có kiểu, không có giá trị trả về.

+ Tên hàm: Tên của hàm hủy gồm một dấu ngã (đứng trước) và tên lớp:

~Tên_lớp

+ Đối: Hàm hủy không có đối

Ví dụ có thể xây dựng hàm hủy cho lớp DT (đa thức) như sau:

class DT

{

private:

int n; // Bac da thua

double *a; // Tro toi vung nho chua cac he so da thuc a0, a1 , ...

public:

~DT()

{

this ® n = 0;

delete this ® a;

}

...

};

Vai trò của hàm hủy trong lớp DT

Trong phần trước định nghĩa lớp DT (đa thức) khá đầy đủ gồm:

+ Các hàm tạo

+ Các toán tử nhập >>, xuất <<

+ Các hàm toán tử thực hiện các phép tính +, -, *, /

Tuy nhiên vẫn còn thiếu hàm hủy để giải phóng vùng nhớ mà đối tượng kiểu DT (cần hủy) đang quản lý.

Chúng ta hãy phân tích các khiếm khuyết của chương trình này:

+ Khi chương trình gọi tới một phương thức toán tử để thực hiện các phép tính cộng, trừ, nhân đa thức, thì một đối tượng trung gian được tạo ra. Một vùng nhớ được cấp phát và giao cho nó (đối tượng trung gian) quản lý.

+ Khi thực hiện xong phép tính sẽ ra khỏi phương thức. Đối tượng trung gian bị xoá, tuy nhiên chỉ vùng nhớ của các thuộc tính của đối tượng này được giải phóng. Còn vùng nhớ (chứa các hệ số của đa thức) mà đối tượng trung gian đang quản lý thì không hề bị giải phóng. Như vậy số vùng nhớ bị chiếm dụng vô ích sẽ tăng lên.

Nhược điểm trên dễ dàng khắc phục bằng cách đưa vào lớp DT hàm hủy trong mục 3 ở trên.

Ví dụ

Phần này chúng tôi trình bày một ví dụ tương đối hoàn chỉnh về lớp các hình tròn trong chế độ đồ họa. Chương trình gồm:

i. Lớp HT (hình tròn) với các thuộc tính:

int r; // Bán kính

int m; // Mầu hình tròn

int xhien, yhien; // Vị trí hiển thị hình tròn trên màn hình

char *pht; // Con trỏ trỏ tới vùng nhớ chứa ảnh hình tròn

int hienmh; // Trạng thái hiện (hienmh = 1), ẩn (hienmh = 0)

ii. Các phương thức

+ Hàm tạo không đối thực hiện việc gán giá trị bằng 0 cho các thuộc tính của lớp. HT();

+ Hàm tạo có đối. HT(int n, int m1 = 15);

Thực hiện các việc:

 Gán r1 cho r, m1 cho m

 Cấp phát bộ nhớ cho pht

 Vẽ hình tròn và lưu ảnh hình tròn vào vùng nhớ của pht

+ Hàm hủy: ~HT();

Thực hiện các việc:

 Xoá hình tròn khỏi màn hình (nếu đang hiển thị)

 Giải phóng bộ nhớ đã cấp cho pht

+ Phương thức: void hien(int x, int y);

Có nhiệm vụ hiển thị hình tròn tại (x, y)

+ Phương thức : void an()

Có nhiệm vụ làm ẩn hình tròn

iii. Các hàm độc lập:

void ktdh(); // Khởi tạo đồ họa

void ve_bau_troi(); // Vẽ bầu trời sao

void ht_di_dong_xuong(); // Vẽ một cặp 2 hình tròn di chuyển xuống

void ht_di_dong_len(); // Vẽ một cặp 2 hình tròn di chuyển lên trên

Nội dung chương trình là tạo ra các chuyển động xuống và lên của các hình tròn.

// Lop do hoa

// Ham huy

// Trong ham huy co the goi PT khac

#include <conio.h>

#include <iostream.h>

#include <math.h>

#include <stdlib.h>

#include <graphics.h>

#include <dos.h>

void ktdh(); // Khởi tạo đồ họa

void ve_bau_troi(); // Vẽ bầu trời sao

void ht_di_dong_xuong(); // Vẽ một cặp 2 hình tròn di chuyển xuống

void ht_di_dong_len(); // Vẽ một cặp 2 hình tròn di chuyển lên trên

int xmax, ymax;

class HT

{

private:

int r, m ;

int xhien, yhien;

char *pht;

int hienmh;

public:

HT();

HT(int n, int m1 = 15);

~HT();

void hien(int x, int y);

void an();

};

HT:: HT()

{

r = m = hienmh = 0;

xhien = yhien = 0;

pht = NULL;

}

HT::HT(int n, int m1)

{

r = n; m = m1; hienmh = 0;

xhien = yhien = 0;

if (r<0) r = 0;

if (r = = 0) pht = NULL;

else

{

int size; char *pmh;

size = imagesize(0, 0, r+r, r+r);

pmh = new char[size];

getimage(0, 0, r+r, r+r, pmh);

setcolor(m);

circle(r, r, r );

setfillstyle(1, m);

floodfill(r, r, m);

pht = new char[size];

getimage(0, 0, r+r, r+r, pht);

putimage(0, 0, pmh, COPY_PUT);

delete pmh;

pmh = NULL;

}

}

void HT::hien(int x, int y)

{

if (pmh! = NULL && !hienmh) // Chua hien

{

hienmh = 1;

xhien = x; yhien = y;

putimage(x, y, pht, XOR_PUT);

}

}

void HT::an()

{

if (hienmh) // Dang hien

{

hienmh = 0;

putimage(xhien, yhien, pht, XOR_PUT);

}

}

HT::~HT()

{

an();

if (pht! = NULL)

{

delete pht;

pht = NULL;

}

}

void ktdh()

{

int mh = 0, mode = 0;

initgraph(&mh, &mode, " ");

xmax = getmaxx();

ymax = getmaxy();

}

void ve_bau_troi()

{

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

putpixel(random(xmax), random(ymax), 1+random( 15));

}

void ht_di_dong_xuong()

{

HT h(50, 4);

HT u(60, 15);

h.hien(0, 0);

u.hien(40, 0);

for (int x = 0; x< = 340; x+ = 10)

{

h.an();

u.an();

h.hien(x, x);

delay(200);

u.hien(x+40, x);

delay(200);

}

}

void ht_di_dong_len()

{

HT h(50, 4);

HT u(60, 15);

h.hien(340, 340);

u.hien(380, 340);

for (int x = 340; x> = 0; x- = 10)

{

h.an();

u.an();

u.hien(x, x);

delay(200);

u.hien(x+40, x);

delay(200);

}

};

void main()

{

ktdh();

ve_bau_troi();

ht_di_dong_xuong();

ht_di_dong_len();

getch();

closegraph();

}

Các nhận xét:

+ Trong thân hàm hủy gọi tới phương thức an().

+ Điều gì xẩy ra khi bỏ đi hàm hủy:

• Khi gọi hàm ht_di_dong_xuong() thì có 2 đối tượng kiểu HT được tạo ra. Trong thân hàm sử dụng các đối tượng này để vẽ các hình tròn di chuyển xuống. Khi thoát khỏi hàm thì 2 đối tượng (tạo ra ở trên) được giải phóng. Vùng nhớ của các thuộc tính của chúng bị thu hồi, nhưng vùng nhớ cấp phát cho thuộc tính pht chưa được giải phóng và ảnh của 2 hình tròn (ở phía dưới màn hình) vẫn không được cất đi.

• Điều tương tự xẩy ra sau khi ra khỏi hàm ht_di_dong_len(): vùng nhớ cấp phát cho thuộc tính pht chưa được giải phóng và ảnh của 2 hình tròn (ở phía trên màn hình) vẫn không được thu dọn.

CÁC HÀM TRỰC TUYẾN (inline)

Một số mở rộng của C++ đối với C đã được trình bày trong các chương trước như biến tham chiếu, định nghĩa chồng hàm, hàm với đối mặc định ... Phần này ta xem một đặc trưng khác của C++ được gọi là hàm trực tuyến (inline).

Ưu nhược điểm của hàm

Việc tổ chức chương trình thành các hàm có 2 ưu điểm rõ rệt:

Thứ nhất là chia chương trình thành các đơn vị độc lập, làm cho chương trình được tổ chức một cách khoa học dễ kiểm soát, dễ phát hiện lỗi, dễ phát triển và mở rộng.

Thứ hai là giảm được kích thước chương trình, vì mỗi đoạn chương trình thực hiện nhiệm vụ của hàm được thay bằng một lời gọi hàm.

Tuy nhiên hàm cũng có nhược điểm là làm chậm tốc độ chương trình do phải thực hiện một số thao tác có tính thủ tục mỗi khi gọi hàm như: cấp phát vùng nhớ cho các đốivà biến cục bộ, truyền dữ liệu của các tham số cho các đối, giải phóng vùng nhớ trước khi thoát khỏi hàm.

Các hàm trực tuyến trong C++ có khả năng khắc phục được các nhược điểm nói trên.

Các hàm trực tuyến

Để biến một hàm thành trực tuyến ta viết thêm từ khoá inline vào trước khai báo nguyên mẫu hàm. Nếu không dùng nguyên mẫu thì viết từ khoá này trước dòng đầu tiên của định nghĩa hàm.

Ví dụ 1 :

inline float f(int n, float x);

float f(int n, float x)

{

// Các câu lệnh trong thân hàm

}

hoặc

inline float f(int n, float x)

{

// Các câu lệnh trong thân hàm

}

Chú ý: Trong mọi trường họp, từ khoá inline phải xuất hiện trước các lời gọi hàm thì trình biên dịch mới biết cần xử lý hàm theo kiểu inline.

Ví dụ hàm f trong chương trình sau sẽ không phải là hàm trực tuyến vì từ khoá inline viết sau lời gọi hàm:

#include <conio.h>

#include <iostream.h>

void main()

{

int s ;

s = f(5,6);

cout << s ;

getch();

}

inline int f(int a, int b)

{

return a*b;

}

Chú ý: Trong C++, nếu hàm được xây dựng sau lời gọi hàm thì bắt buộc phải khai báo nguyên mẫu hàm trước lời gọi. Trong ví dụ trên, trình biên dịch C++ sẽ bắt lỗi vì thiếu khai báo nguyên ngẫu hàm f .

Cách biên dịch và dùng hàm trực tuyến

Chương trình dịch xử lý các hàm inline như các macro (được định nghĩa trong lệnh #define), nghĩa là nó sẽ thay mỗi lời gọi hàm bằng một đoạn chương trình thực hiện nhiệm vụ của hàm. Cách này làm cho chương trình dài ra, nhưng tốc độ chương trình tăng lên do không phải thực hiện các thao tác có tính thủ tục khi gọi hàm.

Phương án dùng hàm trực tuyến rút ngắn được thời gian chạy máy nhưng lại làm tăng khối lượng bộ nhớ chương trình (nhất là đối với các hàm trực tuyến có nhiều câu lệnh). Vì vậy chỉ nên dùng phương án trực tuyến đối với các hàm nhỏ.

Sự hạn chế của trình biên dịch

Không phải khi gặp từ khoá inline là trình biên dịch nhất thiết phải xử lý hàm theo kiểu trực tuyến.

Có một số hàm mà các trình biên dịch thường không xử lý theo cách inline như các hàm chứa biến static, hàm chứa các lệnh chu trình hoặc lệnh goto hoặc lệnh switch, hàm đệ quy. Trong trường hợp này từ khoá inline lẽ dĩ nhiên bị bỏ qua.

Thậm chí từ khoá inline vẫn bị bỏ qua ngay cả đối với các hàm không có những hạn chế nêu trên nếu như trình biên dịch thấy cần thiết (ví dụ đã có quá nhiều hàm inline làm cho bộ nhớ chương trình quá lớn)

Ví dụ 2 : Chương trình sau sử dụng hàm inline tính chu vi và diện tích của hình chữ nhật:

Cách 1: Không khai báo nguyên mẫu. Khi đó hàm dtcvhcn phải đặt trước hàm main.

#include <conio.h>

#include <iostream.h>

inline void dtcvhcn(int a, int b, int &dt, int &cv)

{

dt=a*b;

cv=2*(a+b);

}

void main()

{

int a[20],b[20],cv[20],dt[20],n;

cout << "

So hinh chu nhat: '' ;

cin >> n;

for (int i=1; i<=n; ++i)

{

cout <<"

Nhap 2 canh cua hinh chu nhat thu " << i << ": ";

cin >> a[i] >> b[i];

dtcvhcn(a[i],b[i],dt[i], cv[i]);

}

clrscr();

for (i=1; i<=n; ++i)

{

cout << "

Hinh chu nhat thu "<< i << '' : '';

cout << "

Do dai 2 canh= '' << a[i] << '' va '' << b[i] ;

cout <<"

Dien tich= " << dt[i] ;

cout << "

Chu vi= '' << cv[i] ;

}

getch();

}

Cách 2:Sử dụng khai báo nguyên mẫu. Khi đó từ khoá inline đặt trước nguyên mẫu.

Chú ý: Không được đặt inline trước định nghĩa hàm. Trong chương trình dưới đây nếu đặt inline trước định nghĩa hàm thì hậu quả như sau: Chương trình vẫn dịch thông, nhưng khi chạy thì chương trình bị quẩn và không thoát đi được.

#include <conio.h>

#include <iostream.h>

inline void dtcvhcn(int a, int b, int &dt, int &cv);

void main()

{

int a[20],b[20],cv[20],dt[20],n;

cout << "

So hinh chu nhat: '' ;

cin >> n;

for (int i=1; i<=n; ++i)

{

cout <<"

Nhap 2 canh cua hinh chu nhat thu " << i << ": ";

cin >> a[i] >> b[i];

dtcvhcn(a[i],b[i],dt[i], cv[i]);

}

clrscr();

for (i=1; i<=n; ++i)

{

cout << "

Hinh chu nhat thu "<< i << '' : '';

cout << "

Do dai 2 canh= '' << a[i] << '' va '' << b[i] ;

cout <<"

Dien tich= " << dt[i] ;

cout << "

Chu vi= '' << cv[i] ;

}

getch();

}

void dtcvhcn(int a, int b, int&dt, int &cv)

{

dt=a*b;

cv=2*(a+b);

}

CHƯƠNG 9

CÁC DÒNG NHẬP/XUẤT VÀ FILE

Nhập/xuất với cin/cout

Định dạng

In ra máy in

Làm việc với File

Nhập/xuất nhị phân

Trong C++ có sẵn một số lớp chuẩn chứa dữ liệu và các phương thức phục vụ cho các thao tác nhập/xuất dữ liệu của NSD, thường được gọi chung là stream (dòng). Trong số các lớp này, lớp có tên ios là lớp cơ sở, chứa các thuộc tính để định dạng việc nhập/xuất và kiểm tra lỗi. Mở rộng (kế thừa) lớp này có các lớp istream, ostream cung cấp thêm các toán tử nhập/xuất như >>, << và các hàm get, getline, read, ignore, put, write, flush ... Một lớp rộng hơn có tên iostream là tổng hợp của 2 lớp trên. Bốn lớp nhập/xuất cơ bản này được khai báo trong các file tiêu đề có tên tương ứng (với đuôi *.h). Sơ đồ thừa kế của 4 lớp trên được thể hiện qua hình vẽ dưới đây.

Đối tượng của các lớp trên được gọi là các dòng dữ liệu. Một số đối tượng thuộc lớp iostream đã được khai báo sẵn (chuẩn) và được gắn với những thiết bị nhập/xuất cố định như các đối tượng cin, cout, cerr, clog gắn với bàn phím (cin) và màn hình (cout, cerr, clog). Điều này có nghĩa các toán tử >>, << và các hàm kể trên khi làm việc với các đối tượng này sẽ cho phép NSD nhập dữ liệu thông qua bàn phím hoặc xuất kết quả thông qua màn hình.

Để nhập/xuất thông qua các thiết bị khác (như máy in, file trên đĩa ...), C++ cung cấp thêm các lớp ifstream, ofstream, fstream cho phép NSD khai báo các đối tượng mới gắn với thiết bị và từ đó nhập/xuất thông qua các thiết bị này.

Trong chương này, chúng ta sẽ xét các đối tượng chuẩn cin, cout và một số toán tử, hàm nhập xuất đặc trưng của lớp iostream cũng như cách tạo và sử dụng các đối tượng thuộc các lớp ifstream, ofstream, fstream để làm việc với các thiết bị như máy in và file trên đĩa.

NHẬP/XUẤT VỚI CIN/COUT

Như đã nhắc ở trên, cin là dòng dữ liệu nhập (đối tượng) thuộc lớp istream. Các thao tác trên đối tượng này gồm có các toán tử và hàm phục vụ nhập dữ liệu vào cho biến từ bàn phím.

Toán tử nhập >>

Toán tử này cho phép nhập dữ liệu từ một dòng Input_stream nào đó vào cho một danh sách các biến. Cú pháp chung như sau:

Input_stream >> biến1 >> biến2 >> ...

trong đó Input_stream là đối tượng thuộc lớp istream. Trường hợp Input_stream là cin, câu lệnh nhập sẽ được viết:

cin >> biến1 >> biến2 >> ...

câu lệnh này cho phép nhập dữ liệu từ bàn phím cho các biến. Các biến này có thể thuộc các kiểu chuẩn như : kiểu nguyên, thực, ký tự, xâu kí tự. Chú ý 2 đặc điểm quan trọng của câu lệnh trên.

• Lệnh sẽ bỏ qua không gán các dấu trắng (dấu cách <>, dấu Tab, dấu xuống dòng ¿) vào cho các biến (kể cả biến xâu kí tự).

• Khi NSD nhập vào dãy byte nhiều hơn cần thiết để gán cho các biến thì số byte còn lại và kể cả dấu xuống dòng ¿ sẽ nằm lại trong cin. Các byte này sẽ tự động gán cho các biến trong lần nhập sau mà không chờ NSD gõ thêm dữ liệu vào từ bàn phím. Do vậy câu lệnh

cin >> a >> b >> c;

cũng có thể được viết thành

cin >> a;

cin >> b;

cin >> c;

và chỉ cần nhập dữ liệu vào từ bàn phím một lần chung cho cả 3 lệnh (mỗi dữ liệu nhập cho mỗi biến phải cách nhau ít nhất một dấu trắng)

Ví dụ 3 : Nhập dữ liệu cho các biến

int a;

float b;

char c;

char *s;

cin >> a >> b >> c >> s;

giả sử NSD nhập vào dãy dữ liệu : <><>12<>34.517ABC<>12E<>D ¿

khi đó các biến sẽ được nhận những giá trị cụ thể sau:

a = 12

b = 34.517

c = 'A'

s = "BC"

trong cin sẽ còn lại dãy dữ liệu : <>12E<>D ¿.

Nếu trong đoạn chương trình tiếp theo có câu lệnh cin >> s; thì s sẽ được tự động gán giá trị "12E" mà không cần NSD nhập thêm dữ liệu vào cho cin.

Qua ví dụ trên một lần nữa ta nhắc lại đặc điểm của toán tử nhập >> là các biến chỉ lấy dữ liệu vừa đủ cho kiểu của biến (ví dụ biến c chỉ lấy một kí tự 'A', b lấy giá trị 34.517) hoặc cho đến khi gặp dấu trắng đầu tiên (ví dụ a lấy giá trị 12, s lấy giá trị "BC" dù trong cin vẫn còn dữ liệu). Từ đó ta thấy toán tử >> là không phù hợp khi nhập dữ liệu cho các xâu kí tự có chứa dấu cách. C++ giải quyết trường hợp này bằng một số hàm (phương thức) nhập khác thay cho toán tử >>.

Các hàm nhập kí tự và xâu kí tự

Nhập kí tự

• cin.get() : Hàm trả lại một kí tự (kể cả dấu cách, dấu ¿).. Ví dụ:

char ch;

ch = cin.get();

 nếu nhập AB¿, ch nhận giá trị 'A', trong cin còn B¿.

 nếu nhập A¿, ch nhận giá trị 'A', trong cin còn ¿.

 nếu nhập ¿, ch nhận giá trị '¿', trong cin rỗng.

• cin.get(ch) : Hàm nhập kí tự cho ch và trả lại một tham chiếu tới cin. Do hàm trả lại tham chiếu tới cin nên có thể viết các phương thức nhập này liên tiếp trên một đối tượng cin. Ví dụ:

char c, d;

cin.get(c).get(d);

nếu nhập AB¿ thì c nhận giá trị 'A' và d nhận giá trị 'B'. Trong cin còn 'C¿'.

Nhập xâu kí tự

• cin.get(s, n, fchar) : Hàm nhập cho s dãy kí tự từ cin. Dãy được tính từ kí tự đầu tiên trong cin cho đến khi đã đủ n - 1 kí tự hoặc gặp kí tự kết thúc fchar. Kí tự kết thúc này được ngầm định là dấu xuống dòng nếu bị bỏ qua trong danh sách đối. Tức có thể viết câu lệnh trên dưới dạng cin.get(s, n) khi đó xâu s sẽ nhận dãy kí tự nhập cho đến khi đủ n-1 kí tự hoặc đến khi NSD kết thúc nhập (bằng dấu ¿).

Chú ý :

 Lệnh sẽ tự động gán dấu kết thúc xâu ('\0') vào cho xâu s sau khi nhập xong.

 Các lệnh có thể viết nối nhau, ví dụ: cin.get(s1, n1).get(s2,n2);

 Kí tự kết thúc fchar (hoặc ¿) vẫn nằm lại trong cin. Điều này có thể làm trôi các lệnh get() tiếp theo. Ví dụ:

struct Sinhvien {

char *ht; // họ tên

char *qq; // quê quán

};

void main()

{

int i;

for (i=1; i<=3; i++) {

cout << "Nhap ho ten sv thu " << i; cin.get(sv[i].ht, 25);

cout << "Nhap que quan sv thu "<< i; cin.get(sv[i].qq, 30);

}

...

}

Trong đoạn lệnh trên sau khi nhập họ tên của sinh viên thứ 1, do kí tự ¿ vẫn nằm trong bộ đệm nên khi nhập quê quán chương trình sẽ lấy kí tự ¿ này gán cho qq, do đó quê quán của sinh viên sẽ là xâu rỗng.

Để khắc phục tình trạng này chúng ta có thể sử dụng một trong các câu lệnh nhập kí tự để "nhấc" dấu enter còn "rơi vãi" ra khỏi bộ đệm. Có thể sử dụng các câu lệnh sau :

cin.get(); // đọc một kí tự trong bộ đệm

cin.ignore(n); //đọc n kí tự trong bộ đệm (với n=1)

như vậy để đoạn chương trình trên hoạt động tốt ta có thể tổ chức lại như sau:

void main()

{

int i;

for (i=1; i<=3; i++) {

cout << "Nhap ho ten sv thu " << i; cin.get(sv[i].ht, 25);

cin.get(); // nhấc 1 kí tự (enter)

cout << "Nhap que quan sv thu "<< i; cin.get(sv[i].qq, 30);

cin.get() // hoặc cin.ignore(1);

}

...

}

• cin.getline(s, n, fchar): Phương thức này hoạt động hoàn toàn tương tự phương thức cin.get(s, n, fchar), tuy nhiên nó có thể khắc phục "lỗi enter" của câu lệnh trên. Cụ thể hàm sau khi gán nội dung nhập cho biến s sẽ xóa kí tự enter khỏi bộ đệm và do vậy NSD không cần phải sử dụng thêm các câu lệnh phụ trợ (cin.get(), cin.ignore(1)) để loại enter ra khỏi bộ đệm.

• cin.ignore(n): Phương thức này của đối tượng cin dùng để đọc và loại bỏ n kí tự còn trong bộ đệm (dòng nhập cin).

Chú ý: Toán tử nhập >> cũng giống các phương thức nhập kí tự và xâu kí tự ở chỗ cũng để lại kí tự enter trong cin. Do vậy, chúng ta nên sử dụng các phương thức cin.get(), cin.ignore(n) để loại bỏ kí tự enter trước khi thực hiện lệnh nhập kí tự và xâu kí tự khác.

Tương tự dòng nhập cin, cout là dòng dữ liệu xuất thuộc lớp ostream. Điều này có nghĩa dữ liệu làm việc với các thao tác xuất (in) sẽ đưa kết quả ra cout mà đã được mặc định là màn hình. Do đó ta có thể sử dụng toán tử xuất << và các phương thức xuất trong các lớp ios (lớp cơ sở) và ostream.

Toán tử xuất <<

Toán tử này cho phép xuất giá trị của dãy các biểu thức đến một dòng Output_stream nào đó với cú pháp chung như sau:

Output_stream << bt_1 << bt_2 << ...

ở đây Output_stream là đối tượng thuộc lớp ostream. Trường hợp Output_stream là cout, câu lệnh xuất sẽ được viết:

cout << bt_1 << bt_2 << ...

câu lệnh này cho phép in kết quả của các biểu thức ra màn hình. Kiểu dữ liệu của các biểu thức có thể là số nguyên, thực, kí tự hoặc xâu kí tự.

ĐỊNH DẠNG

Các giá trị in ra màn hình có thể được trình bày dưới nhiều dạng khác nhau thông qua các công cụ định dạng như các phương thức, các cờ và các bộ phận khác được khai báo sẵn trong các lớp ios và ostream.

Các phương thức định dạng

Chỉ định độ rộng cần in

cout.width(n) ;

Số cột trên màn hình để in một giá trị được ngầm định bằng với độ rộng thực (số chữ số, chữ cái và kí tự khác trong giá tị được in). Để đặt lại độ rộng màn hình dành cho giá trị cần in (thông thường lớn hơn độ rộng thực) ta có thể sử dụng phương thức trên.

Phương thức này cho phép các giá trị in ra màn hình với độ rộng n. Nếu n bé hơn độ rộng thực sự của giá trị thì máy sẽ in giá trị với số cột màn hình bằng với độ rộng thực. Nếu n lớn hơn độ rộng thực, máy sẽ in giá trị căn theo lề phải, và để trống các cột thừa phía trước giá trị được in. Phương thức này chỉ có tác dụng với giá trị cần in ngay sau nó. Ví dụ:

int a = 12; b = 345; // độ rộng thực của a là 2, của b là 3

cout << a; // chiếm 2 cột màn hình

cout.width(7); // đặt độ rộng giá trị in tiếp theo là 7

cout << b; // b in trong 7 cột với 4 dấu cách đứng trước

Kết quả in ra sẽ là: 12<><><><>345

Chỉ định kí tự chèn vào khoảng trống trước giá trị cần in

cout.fill(ch) ;

Kí tự độn ngầm định là dấu cách, có nghĩa khi độ rộng của giá trị cần in bé hơn độ rộng chỉ định thì máy sẽ độn các dấu cách vào trước giá trị cần in cho đủ với độ rộng chỉ định. Có thể yêu cầu độn một kí tự ch bất kỳ thay cho dấu cách bằng phương thức trên. Ví dụ trong dãy lệnh trên, nếu ta thêm dòng lệnh cout.fill('*') trước khi in b chẳng hạn thì kết quả in ra sẽ là: 12****345.

Phương thức này có tác dụng với mọi câu lệnh in sau nó cho đến khi gặp một chỉ định mới.

Chỉ định độ chính xác (số số lẻ thập phân) cần in

cout.precision(n) ;

Phương thức này yêu cầu các số thực in ra sau đó sẽ có n chữ số lẻ. Các số thực trước khi in ra sẽ được làm tròn đến chữ số lẻ thứ n. Chỉ định này có tác dụng cho đến khi gặp một chỉ định mới. Ví dụ:

int a = 12.3; b = 345.678; // độ rộng thực của a là 4, của b là 7

cout << a; // chiếm 4 cột màn hình

cout.width(10); // đặt độ rộng giá trị in tiếp theo là 10

cout.precision(2); // đặt độ chính xác đến 2 số lẻ

cout << b; // b in trong 10 cột với 4 dấu cách đứng trước

Kết quả in ra sẽ là: 12.3<><><><>345.68

Các cờ định dạng

Một số các qui định về định dạng thường được gắn liền với các "cờ". Thông thường nếu định dạng này được sử dụng trong suốt quá trình chạy chương trình hoặc trong một khoảng thời gian dài trước khi gỡ bỏ thì ta "bật" các cờ tương ứng với nó. Các cờ được bật sẽ có tác dụng cho đến khi cờ với định dạng khác được bật. Các cờ được cho trong file tiêu đề iostream.h.

Để bật/tắt các cờ ta sử dụng các phương thức sau:

cout.setf(danh sách cờ); // Bật các cờ trong danh sách

cout.unsetf(danh sách cờ); // Tắt các cờ trong danh sách

Các cờ trong danh sách được viết cách nhau bởi phép toán hợp bit (|). Ví dụ lệnh cout.setf(ios::left | ios::scientific) sẽ bật các cờ ios::left và ios::scientific. Phương thức cout.unsetf(ios::right | ios::fixed) sẽ tắt các cờ ios::right | ios::fixed.

Dưới đây là danh sách các cờ cho trong iostream.h.

Nhóm căn lề

 ios::left : nếu bật thì giá trị in nằm bên trái vùng in ra (kí tự độn nằm sau).

 ios::right : giá trị in nằm bên phái vùng in ra (kí tự độn nằm trước), đây là trường hợp ngầm định nếu ta không sử dụng cờ cụ thể.

 ios::internal : giống cờ ios::right tuy nhiên dấu của giá trị in ra sẽ được in đầu tiên, sau đó mới đến kí tự độn và giá trị số.

Ví dụ:

int a = 12.3; b = -345.678; // độ rộng thực của a là 4, của b là 8

cout << a; // chiếm 4 cột màn hình

cout.width(10); // đặt độ rộng giá trị in tiếp theo là 10

cout.fill('*') ; // dấu * làm kí tự độn

cout.precision(2); // đặt độ chính xác đến 2 số lẻ

cout.setf(ios::left) ; // bật cờ ios::left

cout << b; // kết qủa: 12.3-345.68***

cout.setf(ios::right) ; // bật cờ ios::right

cout << b; // kết qủa: 12.3***-345.68

cout.setf(ios::internal) ; // bật cờ ios::internal

cout << b; // kết qủa: 12.3-***345.68

Nhóm định dạng số nguyên

 ios::dec : in số nguyên dưới dạng thập phân (ngầm định)

 ios::oct : in số nguyên dưới dạng cơ số 8

 ios::hex : in số nguyên dưới dạng cơ số 16

Nhóm định dạng số thực

 ios::fixed : in số thực dạng dấu phảy tĩnh (ngầm định)

 ios::scientific : in số thực dạng dấu phảy động

 ios::showpoint : in đủ n chữ số lẻ của phần thập phân, nếu tắt (ngầm định) thì không in các số 0 cuối của phần thập phân.

Ví dụ: giả sử độ chính xác được đặt với 3 số lẻ (bởi câu lệnh cout.precision(3))

 nếu fixed bật + showpoint bật :

123.2500 được in thành 123.250

123.2599 được in thành 123.260

123.2 được in thành 123.200

 nếu fixed bật + showpoint tắt :

123.2500 được in thành 123.25

123.2599 được in thành 123.26

123.2 được in thành 123.2

 nếu scientific bật + showpoint bật :

12.3 được in thành 1.230e+01

2.32599 được in thành 2.326e+00

324 được in thành 3.240e+02

 nếu scientific bật + showpoint tắt :

12.3 được in thành 1.23e+01

2.32599 được in thành 2.326e+00

324 được in thành 3.24e+02

Nhóm định dạng hiển thị

 ios::showpos : nếu tắt (ngầm định) thì không in dấu cộng (+) trước số dương. Nếu bật trước mỗi số dương sẽ in thêm dấu cộng.

 ios::showbase : nếu bật sẽ in số 0 trước các số nguyên hệ 8 và in 0x trước số hệ 16. Nếu tắt (ngầm định) sẽ không in 0 và 0x.

 ios::uppercase : nếu bật thì các kí tự biểu diễn số trong hệ 16 (A..F) sẽ viết hoa, nếu tắt (ngầm định) sẽ viết thường.

Các bộ và hàm định dạng

iostream.h cũng cung cấp một số bộ và hàm định dạng cho phép sử dụng tiện lợi hơn so với các cờ và các phương thức vì nó có thể được viết liên tiếp trên dòng lệnh xuất.

Các bộ định dạng

dec // tương tự ios::dec

oct // tương tự ios::dec

hex // tương tự ios::hex

endl // xuất kí tự xuống dòng ('

')

flush // đẩy toàn bộ dữ liệu ra dòng xuất

Ví dụ :

cout.setf(ios::showbase) ; // cho phép in các kí tự biểu thị cơ số

cout.setf(ios::uppercase) ; // dưới dạng chữ viết hoa

int a = 171; int b = 32 ;

cout << hex << a << endl << b ; // in 0xAB và 0x20

Các hàm định dạng (#include <iomanip.h>)

setw(n) // tương tự cout.width(n)

setprecision(n) // tương tự cout.precision(n)

setfill(c) // tương tự cout.fill(c)

setiosflags(l) // tương tự cout.setf(l)

resetiosflags(l) // tương tự cout.unsetf(l)

IN RA MÁY IN

Như trong phần đầu chương đã trình bày, để làm việc với các thiết bị khác với màn hình và đĩa ... chúng ta cần tạo ra các đối tượng (thuộc các lớp ifstream, ofstream và fstream) tức các dòng tin bằng các hàm tạo của lớp và gắn chúng với thiết bị bằng câu lệnh:

ofstream Tên_dòng(thiết bị) ;

Ví dụ để tạo một đối tượng mang tên Mayin và gắn với máy in, chúng ta dùng lệnh:

ofstream Mayin(4) ;

trong đó 4 là số hiệu của máy in.

Khi đó mọi câu lệnh dùng toán tử xuất << và cho ra Mayin sẽ đưa dữ liệu cần in vào một bộ đệm mặc định trong bộ nhớ. Nếu bộ đệm đầy, một số thông tin đưa vào trước sẽ tự động chuyển ra máy in. Để chủ động đưa tất cả dữ liệu còn lại trong bộ đệm ra máy in chúng ta cần sử dụng bộ định dạng flush (Mayin << flush << ...) hoặc phương thức flush (Mayin.flush(); ). Ví dụ:

Sau khi đã khai báo một đối tượng mang tên Mayin bằng câu lệnh như trên Để in chu vi và diện tích hình chữ nhật có cạnh cd và cr ta có thể viết:

Mayin << "Diện tích HCN = " << cd * cr << endl;

Mayin << "Chu vi HCN = " << 2*(cd + cr) << endl;

Mayin.flush();

hoặc :

Mayin << "Diện tích HCN = " << cd * cr << endl;

Mayin << "Chu vi HCN = " << 2*(cd + cr) << endl << flush;

khi chương trình kết thúc mọi dữ liệu còn lại trong các đối tượng sẽ được tự động chuyển ra thiết bị gắn với nó. Ví dụ máy in sẽ in tất cả mọi dữ liệu còn sót lại trong Mayin khi chương trình kết thúc.

LÀM VIỆC VỚI FILE

Làm việc với một file trên đĩa cũng được quan niệm như làm việc với các thiết bị khác của máy tính (ví dụ như làm việc với máy in với đối tượng Mayin trong phần trên hoặc làm việc với màn hình với đối tượng chuẩn cout). Các đối tượng này được khai báo thuộc lớp ifstream hay ofstream tùy thuộc ta muốn sử dụng file để đọc hay ghi.

Như vậy, để sử dụng một file dữ liệu đầu tiên chúng ta cần tạo đối tượng và gắn cho file này. Để tạo đối tượng có thể sử dụng các hàm tạo có sẵn trong hai lớp ifstream và ofstream. Đối tượng sẽ được gắn với tên file cụ thể trên đĩa ngay trong quá trình tạo đối tượng (tạo đối tượng với tham số là tên file) hoặc cũng có thể được gắn với tên file sau này bằng câu lệnh mở file. Sau khi đã gắn một đối tượng với file trên đĩa, có thể sử dụng đối tượng như đối với Mayin hoặc cin, cout. Điều này có nghĩa trong các câu lệnh in ra màn hình chỉ cần thay từ khóa cout bởi tên đối tượng mọi dữ liệu cần in trong câu lệnh sẽ được ghi lên file mà đối tượng đại diện. Cũng tương tự nếu thay cin bởi tên đối tượng, dữ liệu sẽ được đọc vào từ file thay cho từ bàn phím. Để tạo đối tượng dùng cho việc ghi ta khai báo chúng với lớp ofstream còn để dùng cho việc đọc ta khai báo chúng với lớp ifstream.

Tạo đối tượng gắn với file

Mỗi lớp ifstream và ofstream cung cấp 4 phương thức để tạo file. Ở đây chúng tôi chỉ trình bày 2 cách (2 phương thức) hay dùng.

+ Cách 1: <Lớp> đối_tượng;

đối_tượng.open(tên_file, chế_độ);

Lớp là một trong hai lớp ifstream và ofstream. Đối tượng là tên do NSD tự đặt. Chế độ là cách thức làm việc với file (xem dưới). Cách này cho phép tạo trước một đối tượng chưa gắn với file cụ thể nào. Sau đó dùng tiếp phương thức open để đồng thời mở file và gắn với đối tượng vừa tạo.

Ví dụ:

ifstream f; // tạo đối tượng có tên f để đọc hoặc

ofstream f; // tạo đối tượng có tên f để ghi

f.open("Baitap"); // mở file Baitap và gắn với f

+ Cách 2: <Lớp> đối_tượng(tên_file, chế_độ)

Cách này cho phép đồng thời mở file cụ thể và gắn file với tên đối tượng trong câu lệnh.

Ví dụ:

ifstream f("Baitap"); // mở file Baitap gắn với đối tượng f để

ofstream f("Baitap); // đọc hoặc ghi.

Sau khi mở file và gắn với đối tượng f, mọi thao tác trên f cũng chính là làm việc với file Baitap.

Trong các câu lệnh trên có các chế độ để qui định cách thức làm việc của file. Các chế độ này gồm có:

• ios::binary : quan niệm file theo kiểu nhị phân. Ngầm định là kiểu văn bản.

• ios::in : file để đọc (ngầm định với đối tượng trong ifstream).

• ios::out : file để ghi (ngầm định với đối tượng trong ofstream), nếu file đã có trên đĩa thì nội dung của nó sẽ bị ghi đè (bị xóa).ios::app : bổ sung vào cuối file

• ios::trunc : xóa nội dung file đã có

• ios::ate : chuyển con trỏ đến cuối file

• ios::nocreate : không làm gì nếu file chưa có

• ios::replace : không làm gì nếu file đã có

có thể chỉ định cùng lúc nhiều chế độ bằng cách ghi chúng liên tiếp nhau với toán tử hợp bit |. Ví dụ để mở file bài tập như một file nhị phân và ghi tiếp theo vào cuối file ta dùng câu lệnh:

ofstream f("Baitap", ios::binary | ios::app);

Đóng file và giải phóng đối tượng

Để đóng file được đại diện bởi f, sử dụng phương thức close như sau:

đối_tượng.close();

Sau khi đóng file (và giải phóng mối liên kết giữa đối tượng và file) có thể dùng đối tượng để gắn và làm việc với file khác bằng phương thức open như trên.

Ví dụ 4 : Đọc một dãy số từ bàn phím và ghi lên file. File được xem như file văn bản (ngầm định), các số được ghi cách nhau 1 dấu cách.

#include <iostream.h>

#include <fstream.h>

#include <conio.h>

void main()

{

ofstream f; // khai báo (tạo) đối tượng f

int x;

f.open("DAYSO"); // mở file DAYSO và gắn với f

for (int i = 1; i<=10; i++) {

cin >> x;

f << x << ' ';

}

f.close();

}

Ví dụ 5 : Chương trình sau nhập danh sách sinh viên, ghi vào file 1, đọc ra mảng, sắp xếp theo tuổi và in ra file 2. Dòng đầu tiên trong file ghi số sinh viên, các dòng tiếp theo ghi thông tin của sinh viên gồm họ tên với độ rộng 24 kí tự, tuổi với độ rộng 4 kí tự và điểm với độ rộng 8 kí tự.

#include <iostream.h>

#include <iomanip.h>

#include <fstream.h>

#include <stdlib.h>

#include <stdio.h>

#include <conio.h>

#include <ctype.h>

struct Sv {

char *hoten;

int tuoi;

double diem;

};

class Sinhvien {

int sosv ;

Sv *sv;

public:

Sinhvien() {

sosv = 0;

sv = NULL;

}

void nhap();

void sapxep();

void ghifile(char *fname);

};

void Sinhvien::nhap()

{

cout << "

Số sinh viên: "; cin >> sosv;

int n = sosv;

sv = new Sinhvien[n+1]; // Bỏ phần tử thứ 0

for (int i = 1; i <= n; i++) {

cout << "

Nhập sinh viên thứ: " << i << endl;

cout << "

Họ tên: "; cin.ignore(); cin.getline(sv[i].hoten);

cout << "

Tuổi: "; cin >> sv[i].tuoi;

cout << "

Điểm: "; cin >> sv[i].diem;

}

}

void Sinhvien::ghi(char fname)

{

ofstream f(fname) ;

f << sosv;

f << setprecision(1) << setiosflags(ios::showpoint) ;

for (int i=1; i<=sosv; i++) {

f << endl << setw(24) << sv[i].hoten << setw(4) << tuoi;

f << setw(8) << sv[i].diem;

}

f.close();

}

void Sinhvien::doc(char fname)

{

ifstream f(fname) ;

f >> sosv;

for (int i=1; i<=sosv; i++) {

f.getline(sv[i].hoten, 25);

f >> sv[i].tuoi >> sv[i].diem;

}

f.close();

}

void Sinhvien::sapxep()

{

int n = sosv;

for (int i = 1; i < n; i++) {

for (int j = j+1; j <= n; j++) {

if (sv[i].tuoi > sv[j].tuoi) {

Sinhvien t = sv[i]; sv[i] = sv[j]; sv[j] = t;

}

}

void main() {

clrscr();

Sinhvien x ;

x.nhap(); x.ghi("DSSV1");

x.doc("DSSV1"); x.sapxep(); x.ghi("DSSV2");

cout << "Đã xong";

getch();

}

Kiểm tra sự tồn tại của file, kiểm tra hết file

Việc mở một file chưa có để đọc sẽ gây nên lỗi và làm dừng chương trình. Khi xảy ra lỗi mở file, giá trị trả lại của phương thức bad là một số khác 0. Do vậy có thể sử dụng phương thức này để kiểm tra một file đã có trên đĩa hay chưa. Ví dụ:

ifstream f("Bai tap");

if (f.bad()) {

cout << "file Baitap chưa có";

exit(1);

}

Khi đọc hoặc ghi, con trỏ file sẽ chuyển dần về cuối file. Khi con trỏ ở cuối file, phương thức eof() sẽ trả lại giá trị khác không. Do đó có thể sử dụng phương thức này để kiểm tra đã hết file hay chưa.

Chương trình sau cho phép tính độ dài của file Baitap. File cần được mở theo kiểu nhị phân.

#include <iostream.h>

#include <fstream.h>

#include <stdlib.h>

#include <conio.h>

void main()

{

clrscr();

long dodai = 0;

char ch;

ifstream f("Baitap", ios::in | ios::binary) ;

if (f.bad()) {

cout << "File Baitap không có";

exit(1);

}

while (!f.eof()) {

f.get(ch));

dodai++;

}

cout << "Độ dài của file = " << dodai;

getch();

}

Đọc ghi đồng thời trên file

Để đọc ghi đồng thời, file phải được gắn với đối tượng của lớp fstream là lớp thừa kế của 2 lớp ifstream và ofstream. Khi đó chế độ phải được bao gồm chỉ định ios::in | ios::out. Ví dụ:

fstream f("Data", ios::in | ios::out) ;

hoặc

fstream f ;

f.open("Data", ios::in | ios::out) ;

Di chuyển con trỏ file

Các phương thức sau cho phép làm việc trên đối tượng của dòng xuất (ofstream).

 đối_tượng.seekp(n) ; Di chuyển con trỏ đến byte thứ n (các byte được tính từ 0)

 đối_tượng.seekp(n, vị trí xuất phát) ; Di chuyển đi n byte (có thể âm hoặc dương) từ vị trí xuất phát. Vị trí xuất phát gồm:

• ios::beg : từ đầu file

• ios::end : từ cuối file

• ios::cur : từ vị trí hiện tại của con trỏ.

 đối_tượng.tellp(n) ; Cho biết vị trí hiện tại của con trỏ.

Để làm việc với dòng nhập tên các phương thức trên được thay tương ứng bởi các tên : seekg và tellg. Đối với các dòng nhập lẫn xuất có thể sử dụng được cả 6 phương thức trên.

Ví dụ sau tính độ dài tệp đơn giản hơn ví dụ ở trên.

fstream f("Baitap");

f.seekg(0, ios::end);

cout << "Độ dài bằng = " << f.tellg();

Ví dụ 6 : Chương trình nhập và in danh sách sinh viên trên ghi/đọc đồng thời.

#include <iostream.h>

#include <iomanip.h>

#include <fstream.h>

#include <stdlib.h>

#include <stdio.h>

#include <conio.h>

#include <ctype.h>

void main() {

int stt ;

char *hoten, *fname, traloi;

int tuoi;

float diem;

fstream f;

cout << "Nhập tên file: "; cin >> fname;

f.open(fname, ios::in | ios::out | ios::noreplace) ;

if (f.bad()) {

cout << "Tệp đã có. Ghi đè (C/K)?" ;

cin.get(traloi) ;

if (toupper(traloi) == 'C') {

f.close() ;

f.open(fname, ios::in | ios::out | ios::trunc) ;

} else exit(1);

}

stt = 0;

f << setprecision(1) << setiosflags(ios::showpoint) ;

// nhập danh sách

while (1) {

stt++;

cout << "

Nhập sinh viên thứ " << stt ;

cout << "

Họ tên: "; cin.ignore() ; cin.getline(hoten, 25);

if (hoten[0] = 0) break;

cout << "

Tuổi: "; cin >> tuoi;

cout << "

Điểm: "; cin >> diem;

f << setw(24) << hoten << endl;

f << setw(4) << tuoi << set(8) << diem ;

}

// in danh sách

f.seekg(0) ; // quay về đầu danh sách

stt = 0;

clrscr();

cout << "Danh sách sinh viên đã nhập

" ;

cout << setprecision(1) << setiosflags(ios::showpoint) ;

while (1) {

f.getline(hoten,25);

if (f.eof()) break;

stt++;

f >> tuoi >> diem;

f.ignore();

cout << "

Sinh viên thứ " << stt ;

cout << "

Họ tên: " << hoten;

cout << "

Tuổi: " << setw(4) << tuoi;

cout << "

Điểm: " << setw(8) << diem;

}

f.close();

getch();

}

NHẬP/XUẤT NHỊ PHÂN

Khái niệm về 2 loại file: văn bản và nhị phân

File văn bản

Trong file văn bản mỗi byte được xem là một kí tự. Tuy nhiên nếu 2 byte 10 (LF), 13 (CR) đi liền nhau thì được xem là một kí tự và nó là kí tự xuống dòng. Như vậy file văn bản là một tập hợp các dòng kí tự với kí tự xuống dòng có mã là 10. Kí tự có mã 26 được xem là kí tự kết thúc file.

File nhị phân

Thông tin lưu trong file được xem như dãy byte bình thường. Mã kết thúc file được chọn là -1, được định nghĩa là EOF trong stdio.h. Các thao tác trên file nhị phân thường đọc ghi từng byte một, không quan tâm ý nghĩa của byte.

Một số các thao tác nhập/xuất sẽ có hiệu quả khác nhau khi mở file dưới các dạng khác nhau.

Ví dụ 1 : giả sử ch = 10, khi đó f << ch sẽ ghi 2 byte 10,13 lên file văn bản f, trong khi đó lệnh này chỉ khi 1 byte 10 lên file nhị phân.

Ngược lại, nếu f la file văn bản thì f.getc(ch) sẽ trả về chỉ 1 byte 10 khi đọc được 2 byte 10, 13 liên tiếp nhau.

Một file luôn ngầm định dưới dạng văn bản, do vậy để chỉ định file là nhị phân ta cần sử dụng cờ ios::binary.

Đọc, ghi kí tự

 put(c); // ghi kí tự ra file

 get(c); // đọc kí tự từ file

Ví dụ 2 : Sao chép file 1 sang file 2. Cần sao chép và ghi từng byte một do vậy để chính xác ta sẽ mở các file dưới dạng nhị phân.

#include <iostream.h>

#include <fstream.h>

#include <stdlib.h>

#include <conio.h>

void main()

{

clrscr();

fstream fnguon("DATA1", ios::in | ios::binary);

fstream fdich("DATA2", ios::out | ios::binary);

char ch;

while (!fnguon.eof()) {

fnguon.get(ch);

fdich.put(ch);

}

fnguon.close();

fdich.close();

}

Đọc, ghi dãy kí tự

 write(char *buf, int n); // ghi n kí tự trong buf ra dòng xuất

 read(char *buf, int n); // nhập n kí tự từ buf vào dòng nhập

 gcount(); // cho biết số kí tự read đọc được

Ví dụ 3 : Chương trình sao chép file ở trên có thể sử dụng các phương thức mới này như sau:

#include <iostream.h>

#include <fstream.h>

#include <stdlib.h>

#include <conio.h>

void main()

{

clrscr();

fstream fnguon("DATA1", ios::in | ios::binary);

fstream fdich("DATA2", ios::out | ios::binary);

char buf[2000] ;

int n = 2000;

while (n) {

fnguon.read(buf, 2000);

n = fnguon.gcount();

fdich.write(buf, n);

}

fnguon.close();

fdich.close();

}

Đọc ghi đồng thời

#include <iostream.h>

#include <iomanip.h>

#include <fstream.h>

#include <stdlib.h>

#include <stdio.h>

#include <conio.h>

#include <string.h>

#include <ctype.h>

struct Sv {

char *hoten;

int tuoi;

double diem;

};

class Sinhvien {

int sosv;

Sv x;

char fname[30];

static int size;

public:

Sinhvien(char *fn);

void tao();

void bosung();

void xemsua();

};

int Sinhvien::size = sizeof(Sv);

Sinhvien::Sinhvien(char *fn)

{

strcpy(fname, fn) ;

fstream f;

f.open(fname, ios::in | ios::ate | ios::binary);

if (!f.good) sosv = 0;

else {

sosv = f.tellg() / size;

}

}

void Sinhvien::tao()

{

fstream f;

f.open(fname, ios::out | ios::noreplace | ios::binary);

if (!f.good()) {

cout << "danh sach da co. Co tao lai (C/K) ?";

char traloi = getch();

if (toupper(traloi) == 'C') return;

else {

f.close() ;

f.open(fname, ios::out | ios::trunc | ios::binary);

}

}

sosv = 0

while (1) {

cout << "

Sinh viên thứ: " << sosv+1;

cout << "

Họ tên: "; cin.ignore(); cin.getline(x.hoten);

if (x.hoten[0] == 0) break;

cout << "

Tuổi: "; cin >> x.tuoi;

cout << "

Điểm: "; cin >> x.diem;

f.write((char*)(&x), size);

sosv++;

}

f.close();

}

void Sinhvien::bosung()

{

fstream f;

f.open(fname, ios::out | ios::app | ios::binary);

if (!f.good()) {

cout << "danh sach chua co. Tao moi (C/K) ?";

char traloi = getch();

if (toupper(traloi) == 'C') return;

else {

f.close() ;

f.open(fname, ios::out | ios::binary);

}

}

int stt = 0

while (1) {

cout << "

Bổ sung sinh viên thứ: " << stt+1;

cout << "

Họ tên: "; cin.ignore(); cin.getline(x.hoten);

if (x.hoten[0] == 0) break;

cout << "

Tuổi: "; cin >> x.tuoi;

cout << "

Điểm: "; cin >> x.diem;

f.write((char*)(&x), size);

stt++;

}

sosv += stt;

f.close();

}

void Sinhvien::xemsua()

{

fstream f;

int ch;

f.open(fname, ios::out | ios::app | ios::binary);

if (!f.good()) {

cout << "danh sach chua co";

getch(); return;

}

cout << "

Danh sách sinh viên" << endl;

int stt ;

while (1) {

cout << "

Cần xem (sua) sinh viên thứ (0: dừng): " ;

cin >> stt;

if (stt < 1 || stt > sosv) break;

f.seekg((stt-1) * size, ios::beg);

f.read((char*)(&x), size);

cout << "

Họ tên: " << x.hoten;

cout << "

Tuổi: " << x.tuoi;

cout << "

Điểm: " << x.diem;

cout << "Có sửa không (C/K) ?";

cin >> traloi;

if (toupper(traloi) == 'C') {

f.seekg(-size, ios::cur);

cout << "

Họ tên: "; cin.ignore(); cin.getline(x.hoten);

cout << "

Tuổi: "; cin >> x.tuoi;

cout << "

Điểm: "; cin >> x.diem;

f.write((char*)(&x), size);

}

}

f.close();

}

void main()

{

int chon;

Sinhvien SV("DSSV") ;

while (1) {

clrscr();

cout << "

1: Tạo danh sách sinh viên";

cout << "

2: Bổ sung danh sách";

cout << "

3: Xem - sửa danh sách";

cout << "

0: Kết thúc";

chon = getch();

chon = chon - 48;

clrscr();

if (chon == 1) SV.tao();

else if (chon == 2) SV.bosung();

else if (chon == 3) SV.xemsua();

else break;

}

}

BÀI TẬP

1. Viết chương trình đếm số dòng của một file văn bản.

2. Viết chương trình đọc in từng kí tự của file văn bản ra màn hình, mỗi màn hình 20 dòng.

3. Viết chương trình tìm xâu dài nhất trong một file văn bản.

4. Viết chương trình ghép một file văn bản thứ hai vào file văn bản thứ nhất, trong đó tất cả chữ cái của file văn bản thứ nhất phải đổi thành chữ in hoa.

5. Viết chương trình in nội dung file ra màn hình và cho biết tổng số chữ cái, tổng số chữ số đã xuất hiện trong file.

6. Cho 2 file số thực (đã được sắp tăng dần). In ra màn hình dãy số xếp tăng dần của cả 2 file. (Cần tạo cả 2 file dữ liệu này bằng Editor của C++).

7. Viết hàm nhập 10 số thực từ bàn phím vào file INPUT.DAT. Viết hàm đọc các số thực từ file trên và in tổng bình phương của chúng ra màn hình.

8. Viết hàm nhập 10 số nguyên từ bàn phím vào file văn bản tên INPUT.DAT. Viết hàm đọc các số nguyên từ file trên và ghi những số chẵn vào file EVEN.DAT còn các số lẻ vào file ODD.DAT.

9. Nhập bằng chương trình 2 ma trận số nguyên vào 2 file văn bản. Hãy tạo file văn bản thứ 3 chứa nội dung của ma trận tích của 2 ma trận trên.

10. Tổ chức quản lý file sinh viên (Họ tên, ngày sinh, giới tính, điểm) với các chức năng : Nhập, xem, xóa, sửa, tính điểm trung chung.

11. Thông tin về một nhân viên trong cơ quan bao gồm : họ và tên, nghề nghiệp, số điện thoại, địa chỉ nhà riêng. Viết hàm nhập từ bàn phím thông tin của 7 nhân viên và ghi vào file INPUT.DAT. Viết hàm tìm trong file INPUT.DAT và in ra thông tin của 1 nhân viên theo số điện thoại được nhập từ bàn phím.

CHƯƠNG 8

HÀM BẠN, ĐỊNH NGHĨA PHÉP TOÁN CHO LỚP

Hàm bạn

Định nghĩa phép toán cho lớp

HÀM BẠN (Friend function)

Hàm bạn

Để một hàm trở thành bạn của một lớp, có 2 cách viết:

Cách 1: Dùng từ khóa friend để khai báo hàm trong lớp và xây dựng hàm bên ngoài như các hàm thông thường (không dùng từ khóa friend). Mẫu viết như sau:

class A

{

private:

// Khai báo các thuộc tính

public:

...

// Khai báo các hàm bạn của lớp A

friend void f1(...);

friend double f2(...);

friend A f3(...) ;

...

} ;

// Xây dựng các hàm f1, f2, f3

void f1(...)

{

...

}

double f2(...)

{

...

}

A f3(...)

{

...

}

Cách 2: Dùng từ khóa friend để xây dựng hàm trong định nghĩa lớp. Mẫu viết như sau:

class A

{

private:

// Khai báo các thuộc tính

public:

// Xây dựng các hàm bạn của lớp A

void f1(...)

{

...

}

double f2(...)

{

...

}

A f3(...)

{

...

}

...

} ;

Tính chất của hàm bạn

Trong thân hàm bạn của một lớp có thể truy nhập tới các thuộc tính của các đối tượng thuộc lớp này. Đây là sự khác nhau duy nhất giữa hàm bạn và hàm thông thường.

Chú ý rằng hàm bạn không phải là phương thức của lớp. Phương thức có một đối ẩn (ứng với con trỏ this) và lời gọi của phương thức phải gắn với một đối tượng nào đó (địa chỉ đối tượng này được truyền cho con trỏ this). Lời gọi của hàm bạn giống như lời gọi của hàm thông thường.

Ví dụ sau sẽ so sánh phương thức, hàm bạn và hàm thông thường.

Xét lớp SP (số phức), hãy so sánh 3 phương án để thực hiện việc cộng 2 số phức:

Phương án 1: Dùng phương thức

class SP

{

private:

double a; // phần thực

double b; // Phần ảo

public:

SP cong(SP u2)

{

SP u:

u.a = this ® a + u2.a ;

u.b = this ® b + u2.b ;

return u;

}

};

Cách dùng:

SP u, u1, u2;

u = u1.cong(u2);

Phương án 2: Dùng hàm bạn

class SP

{

private:

double a; // Phần thực

double b; // Phần ảo

public:

friend SP cong(SP u1 , SP u2)

{

SP u:

u.a = u1.a + u2.a ;

u.b = u1.b + u2.b ;

return u;

}

};

Cách dùng

SP u, u1, u2;

u = cong(u1, u2);

Phương án 3: Dùng hàm thông thường

class SP

{

private:

double a; // phần thực

double b; // Phần ảo

public:

...

};

SP cong(SP u1, SP u2)

{

SP u:

u.a = u1.a + u2.a ;

u.b = u1.b + u2.b ;

return u;

}

Phương án này không được chấp nhận, trình biên dịch sẽ báo lỗi trong thân hàm không được quyền truy xuất đến các thuộc tính riêng (private) a, b của các đối tượng u, u1 và u2 thuộc lớp SP.

Hàm bạn của nhiều lớp

Khi một hàm là bạn của nhiều lớp, thì nó có quyền truy nhập tới tất cả các thuộc tính của các đối tượng trong các lớp này.

Để làm cho hàm f trở thành bạn của các lớp A, B và C ta sử dụng mẫu viết như sau:

class A; // Khai báo trước lớp A

class B; // Khai báo trước lớp B

class C; // Khai báo trước lớp C

// Định nghĩa lớp A

class A

{

// Khai báo f là bạn của A

friend void f(...) ;

} ;

// Định nghĩa lớp B

class B

{

// Khai báo f là bạn của B

friend void f(...) ;

} ;

// Định nghĩa lớp C

class C

{

// Khai báo f là bạn của C

friend void f(...) ;

} ;

// Xây dụng hàm f

void f(...)

{

...

}

Chương trình sau đây minh họa cách dùng hàm bạn (bạn của một lớp và bạn của nhiều lớp). Chương trình đưa vào 2 lớp VT (véc tơ), MT (ma trận) và 3 hàm bạn để thực hiện các thao tác trên 2 lớp này:

// Hàm bạn với lớp VT dùng để in một véc tơ

friend void in(const VT &x);

// Hàm bạn với lớp MT dùng để in một ma trận

friend void in(const MT &a);

// Hàm bạn với cả 2 lớp MT và VT dùng để nhân ma trận với véc tơ

friend VT tich(const MT &a, const VT &x);

Nội dung chương trình là nhập một ma trận vuông cấp n và một véc tơ cấp n, sau đó thực hiện phép nhân ma trận với véc tơ vừa nhập.

#include <conio.h>

#include <iostream.h>

#include <math.h>

class VT;

class MT;

class VT

{

private:

int n;

double x[20]; // Toa do cua diem

public:

void nhapsl();

friend void in(const VT &x);

friend VT tich(const MT &a, const VT &x) ;

};

class MT

{

private:

int n;

double a[20][20];

public:

friend VT tich(const MT &a, const VT &x);

friend void in(const MT &a);

void nhapsl();

} ;

void VT::nhapsl()

{

cout << "

Cap vec to = ";

cin >> n ;

for (int i = 1; i< = n ; ++i)

{

cout << "

Phan tu thu " << i <<" = " ;

cin >> x[i];

}

}

void MT::nhapsl()

{

cout <<"

Cap ma tran = ";

cin >> n ;

for (int i = 1; i< = n ; ++i)

for (int j = 1; j< = n; ++j)

{

cout << "

Phan tu thu: "<<i<< " hang "<< i << " cot " << j << " = ";

cin >> a[i][j];

}

}

VT tich(const MT &a, const VT &x)

{

VT y;

int n = a.n;

if (n! = x.n)

return x;

y.n = n;

for (int i = 1; i< = n; ++i)

{

y.x[i] = 0;

for (int j = 1; j< = n; ++j)

y.x[i] = a.a[i][j]*x.x[j];

}

return y;

}

void in(const VT &x)

{

cout << "

";

for (int i = 1; i< = x.n; ++i)

cout << x.x[i] << " ";

}

void in(const MT &a)

{

for (int i = 1; i< = a.n; ++i)

{

cout << "

" ;

for (int j = 1; j< = a.n; ++j)

cout << a.a[i][j] << " ";

}

}

void main()

{

MT a; VT x, y;

clrscr();

a.nhapsl();

x.nhapsl();

y = tich(a, x);

clrscr();

cout << "

Ma tran A:";

in(a);

cout << "

Vec to x: " ;

in(x);

cout << "

Vec to y = Ax: " ;

in(y);

getch();

}

ĐỊNH NGHĨA PHÉP TOÁN CHO LỚP

Đối với mỗi lớp ta có thể sử dụng lại các kí hiệu phép toán thông dụng (+, -, *, ...) để định nghĩa cho các phép toán của lớp. Sau khi được định nghĩa các kí hiệu này sẽ được dùng như các phép toán của lớp theo cách viết thông thường. Cách định nghĩa này được gọi là phép chồng toán tử (như khái niệm chồng hàm trong các chương trước).

Tên hàm toán tử

Gồm từ khoá operator và tên phép toán.

Ví dụ:

operator+(định nghĩa chồng phép +)

operator- (định nghĩa chồng phép -)

Các đối của hàm toán tử

 Với các phép toán có 2 toán hạng thì hàm toán tử cần có 2 đối. Đối thứ nhất ứng với toán hạng thứ nhất, đối thứ hai ứng với toán hạng thứ hai. Do vậy, với các phép toán không giao hoán (phép -) thì thứ tự đối là rất quan trọng.

Ví dụ: Các hàm toán tử cộng, trừ phân số được khai báo như sau:

struct PS

{

int a; //Tử số

int b; // Mẫu số

};

PS operator+(PS p1, PS p2); // p1 + p2

PS operator-(PS p1 , PS p2); // p1 - p2

PS operator*(PS p1, PS p2); // p1 *p2

PS operator/(PS p1, PS p2); // p1/p2

 Với các phép toán có một toán hạng, thì hàm toán tử có một đối. Ví dụ hàm toán tử đổi dấu ma trận (đổi dấu tất cả các phần tử của ma trận) được khai báo như sau:

struct MT

{

double a[20][20] ; // Mảng chứa các phần tử ma trận

int m ; // Số hàng ma trận

int n ; // Số cột ma trận

};

MT operator-(MT x) ;

Thân của hàm toán tử

Viết như thân của hàm thông thường. Ví dụ hàm đổi dấu ma trận có thể được định nghĩa như sau:

struct MT

{

double a[20][20] ; // Mảng chứa các phần tử ma trận

int m ; // Số hàng ma trận

int n ; // Số cột ma trận

};

MT operator-(MT x)

{

MT y;

for (int i=1 ;i<= y.m ; ++i)

for (int j =1 ;j<= y.n ; ++j)y.a[i][j] =- x.a[i][j];

return y;

}

Cách dùng hàm toán tử

Có 2 cách dùng:

Cách 1: Dùng như một hàm thông thường bằng cách viết lời gọi

Ví dụ:

PS p, q, u, v ;

u = operator+(p, q) ; // u = p + q

v = operator-(p, q) ; // v= p - q

Cách 2: Dùng như phép toán của C++

Ví dụ:

PS p, q, u, v ;

u = p + q ; // u = p + q

v = p - q ; //v = p - q

Chú ý: Khi dùng các hàm toán tử như phép toán của C++ ta có thể kết hơp nhiều phép toán để viết các công thức phức tạp. Cũng cho phép dùng dấu ngoặc tròn để quy định thứ tự thực hiện các phép tính. Thứ tự ưu tiên của các phép tính vẫn tuân theo các quy tắc ban đầu của C++. Chẳng hạn các phép * và / có thứ tự ưu tiên cao hơn so với các phép + và -

Các ví dụ về định nghĩa chồng toán tử

Ví dụ 4 : Trong ví dụ này ngoài việc sử dụng các hàm toán tử để thực hiện 4 phép tính trên phân số, còn định nghĩa chồng các phép toán << và >> để xuất và nhập phân số.

Hàm operator<< có 2 đối kiểu ostream& và PS (Phân số). Hàm trả về giá trị kiểu ostream& và được khai báo như sau:

ostream& operator<< (ostream& os, PS p);

Tượng tự hàm operator>> được khai báo như sau:

istream& operator>> (istream& is,PS &p);

Dưới đây sẽ chỉ ra cách xây dựng và sử dụng các hàm toán tử.

Chúng ta cũng sẽ thấy việc sử dụng các hàm toán tử rất tự nhiên, ngắn gọn và tiện lợi.

#include <conio.h>

#include <iostream.h>

#include <math.h>

typedef struct

{

int a,b;

} PS;

ostream& operator<< (ostream& os, PS p);

istream& operator>> (istream& is,PS &p);

int uscln(int x, int y);

PS rutgon(PS p);

PS operator+(PS p1, PS p2);

PS operator-(PS p1, PS p2);

PS operator*(PS p1, PS p2);

PS operator/(PS p1, PS p2);

ostream& operator<< (ostream& os, PS p)

{

os << p.a << '/' << p.b ;

return os;

}

istream& operator>> (istream& is,PS &p)

{

cout << "

Nhap tu va mau: '' ;

is >> p.a >> p.b ;

return is;

}

int uscln(int x, int y)

{

x=abs(x);y=abs(y);

if (x*y==0) return 1;

while (x!=y)

{

if (x>y) x-=y;

else y-=x;

}

return x;

}

PS rutgon(PS p)

{

PS q;

int x;

x=uscln(p.a,p.b);

q.a = p.a / x ;

q.b = p.b/ x ;

return q;

}

PS operator+(PS p1, PS p2)

{

PS q;

q.a = p1.a*p2.b + p2.a*p1.b;

q.b = p1 .b * p2.b ;

return rutgon(q);

}

PS operator-(PS p1, PS p2)

{

PS q;

q.a = p1.a*p2.b - p2.a*p1 .b;

q.b = p1.b * p2.b ;

return rutgon(q);

}

PS operator*(PS p1, PS p2)

{

PS q;

q.a = p1.a * p2.a ;

q.b = p1.b * p2.b ;

return rutgon(q);

}

PS operator/(PS p1 , PS p2)

{

PS q;

q.a = p1.a * p2.b ;

q.b = p1.b * p2.a ;

return rutgon(q);

}

void main()

{

PS p, q, z, u, v ;

PS s;

cout <<"

Nhap cac PS p, q, z, u, v: '' ;

cin >> p >> q >> z >> u >> v ;

s = (p - q*z) / (u + v) ;

cout << "

Phan so s = " << s;

getch();

}

Ví dụ 5 : Chương trình đưa vào các hàm toán tử:

operator- có một đối dùng để đảo dấu một đa thức

operator+ có 2 đối dùng để cộng 2 đa thức

operator- có 2 đối dùng để trừ 2 đa thức

operator* có 2 đối dùng để nhân 2 đa thức

operator^có 2 đối dùng để tính giá đa thức tại x

ơperator<< có 2 đối dùng để in đa thức

ơperator>> có 2 đối dùng để nhập đa thức

Chương trình sẽ nhập 4 đa thức: p, q, r, s. Sau đó tính đa thức: f = -(p+q)*(r-s)

Cuối cùng tính giá trị f(x), với x là một số thực nhập từ bàn phím.

#include <conio.h>

#include <iostream.h>

#include <math.h>

struct DT

{

double a[20];// Mang chua cac he so da thuc a0, a1,...

int n ;// Bac da thuc

};

ostream& operator<< (ostream& os, DT d);

istream& operator>> (istream& is, DT &d);

DT operator-(const DT& d);

DT operator+(DT d1, DT d2);

DT operator-(DT d1, DT d2);

DT operator*(DT d1, DT d2);

double operator^(DT d, double x);// Tinh gia tri da thuc

ostream& operator<< (ostream& os, DT d)

{

os << " Cac he so (tu ao): '' ;

for (int i=0 ;i<= d.n ;++i)

os << d.a[i] <<" " ;

return os;

}

istream& operator>> (istream& is, DT &d)

{

cout << " Bac da thuc: '' ;

cin >> d.n;

cout << ''Nhap cac he so da thuc:" ;

for (int i=0 ;i<=d.n ;++i)

{

cout << "

He so bac " << i <<" = '' ;

is >> d.a[i] ;

}

return is;

}

DT operator-(const DT& d)

{

DT p;

p.n = d.n;

for (int i=0 ;i<=d.n ;++i)

p.a[i] = -d.a[i];

return p;

}

DT operator+(DT d1, DT d2)

{

DT d;

int k,i;

k = d1.n > d2.n ? d1.n : d2.n ;

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

if (i<=d1.n && i<=d2.n) d.a[i] = d1.a[i] + d2.a[i];

else if (i<=d1.n) d.a[i] = d1.a[i];

else d.a[i] = d2.a[i];

i = k;

while (i>0 && d.a[i]==0.0) --i;

d.n=i;

return d ;

}

DT operator-(DT d1, DT d2)

{

return (d1 + (-d2));

}

DT operator*(DT d1 , DT d2)

{

DT d;

int k, i, j;

k = d.n = d1.n + d2.n ;

for (i=0;i<=k;++i) d.a[i] = 0;

for (i=0 ;i<= d1 .n ;++i)

for (j=0 ;j<= d2.n ;++j)

d.a[i+j] += d1 .a[i]*d2.a[j];

return d;

}

double operator^(DT d, double x)

{

double s=0.0 , t=1.0;

for (int i=0 ;i<= d.n ;++i)

{

s += d.a[i]*t;

t *= x;

}

return s;

}

void main()

{

DT p,q,r,s,f;

double x,g;

clrscr();

cout <<"

Nhap da thuc P '' ;cin >> p;

cout <<"

Nhap da thuc Q '' ;cin >> q;

cout <<"

Nhap da thuc R '' ;cin >> r;

cout <<"

Nhap da thuc S '' ;cin >> s;

cout << "

Nhap so thuc x: '' ;cin >> x;

f = -(p+q)*(r-s);

g = f^x;

cout << "

Da thuc f "<< f ;

cout << "

x = '' << x;

cout << "

f(x) = '' << g;

getch();

}

Bạn đang đọc truyện trên: Truyen2U.Pro