Bài giảng kỹ thuật lập trình Thầy Cường Học viên bưu chính viễn thông TP HCM
Trang 1• Từ khoá extern và asm
• Hàm chuyển kiểu
• Những khác biệt giữa C và C++
Trang 3I/ Hàm template
Hàm template (hàm mẫu) định nghiã một dãy tổng quát các tác vụ được dùng cho nhiều kiểu dữ liệu khác nhau Trong đó, kiểu dữ liệu được dùng sẽ được truyền đến hàm dưới dạng một tham số
Với cơ chế này, một thủ tục có thể được dùng cho nhiều kiểu dữ liệu khác nhau
Trong môn học Cấu trúc dữ liệu và thuật toán, nhiều giải thuật giống nhau về mặt luận lý, bất kể nó làm việc với kiểu dữ liệu gì Ví dụ giải thuật QuickSort là giống nhau, không cần biết nó sẽ áp dụng cho dãy số nguyên hay dãy số thực Vấn đề là ở chỗ dữ liệu được xử lý khác nhau
Bằng cách tạo ra hàm template, có thể định nghiã bản chất của giải thuật độc lập với kiểu dữ liệu mà nó xử lý Dựa vào hàm template, trình biên dịch sẽ tự động sinh ra mã chương trình để dùng cho một kiểu dữ liệu cụ thể nào đó khi thực thi chương
trình Thực chất là việc tạo ra một hàm template đồng nghiã với việc tạo ra một hàm mà nó có thể tự quá tải lên chính nó
Khai báo hàm template
template < class Ttype > ret_type func_name(parameter list)
{
// body of function }
Từ khoá template dùng để tạo ra một dạng mẫu mô tả hoạt động của hàm và
nhường cho trình biên dịch điền vào một cách chi tiết khi cần
Ttype là tên hình thức cho kiểu dữ liệu được dùng bên trong hàm, nó sẽ được thay thế bởi một kiểu dữ liệu cụ thể khi trình biên dịch sinh ra một phiên bản chi tiết của hàm
Quá trình tạo ra phiên bản chi tiết của hàm được gọi là quá trình sinh hàm Việc tạo
ra
bản chi tiết của hàm template được gọi là tạo ra ngay tức khắc (instantiating) một hàm Nói một cách khác là một hàm sinh ra phiên bản dùng trong chốc lát của hàm template
Trang 4Ví dụ 1.1 Hàm template trao đổi nội dung của hai biến
// Function template example
#include <iostream.h>
// This is a function template
template <class X> void swapargs(X &a, X &b)
cout << "Original i, j: " << i << ' ' << j << endl;
cout << "Original x, y: " << x << ' ' << y << endl;
swapargs(i, j); // swap integers
swapargs(x, y); // swap floats
cout << "Swapped i, j: " << i << ' ' << j << endl;
cout << "Swapped x, y: " << x << ' ' << y << endl;
return 0;
}
template <class X> void swapargs(X &a, X &b)
Dòng này thông báo với chương trình hai vấn đề :
- Đây là một hàm template
- Đây là điểm bắt đầu của phần định nghiã một hàm template
X có ý nghiã là kiểu dữ liệu của các biến cần trao đổi nội dung
Trang 5Hàm swapargs() được gọi hai lần trong hàm main() : một lần để trao đổi nội dung hai biến kiểu số nguyên, và lần sau cho hai biến kiểu số thực Do hàm swapargs() là
một hàm template, cho nên trình biên dịch sẽ tự động phát sinh ra hai phiên bản của
hàm swapargs() tương ứng với kiểu dữ liệu của đối số thực gởi vào
• Phần khai báo template của một hàm mẫu không bắt buộc phải viết trên cùng một dòng với tên hàm
void swapargs(X &a, X &b)
Trang 6• Trong hàm template, có thể sử dụng nhiều kiểu dữ liệu khác nhau bằng cách
đưa vào một danh sách các kiểu dữ liệu dùng trong hàm, trong đó tên hình thức của các kiểu được phân cách nhau bằng dấu phẩy
#include <iostream.h>
template <class type1, class type2>
void myfunc(type1 x, type2 y)
Trang 7// Overriding a template function
// This overrides the generic version of swapargs()
void swapargs(int a, int b)
cout << "Original i, j: " << i << ' ' << j << endl;
cout << "Original x, y: " << x << ' ' << y << endl;
Trang 8swapargs(i, j); // this calls the explicitly overloaded swapargs()
swapargs(x, y); // swap floats
cout << "Swapped i, j: " << i << ' ' << j << endl;
cout << "Swapped x, y: " << x << ' ' << y << endl;
return 0;
}
@ Việc quá tải lên một hàm template, cho phép lập trình viên thay đổi giải thuật của một phiên bản của hàm template để phù hợp với một tình huống đặc biệt Tuy nhiên, nếu muốn sử dụng những phiên bản hoàn toàn khác nhau của một hàm trong đó có nhiều kiểu dữ liệu, hãy dùng quá tải hàm thay vì hàm template
int bubblesort (X a[], int n);
với n xác định số lượng các phần tử của dãy a
3 Viết một chương trình, trong đó có hàm template tên là find(), thực hiện việc tìm kiếm một đối tượng trên một dãy Nếu tìm thấy, hàm trả về chỉ số của đối tượng tìm được, ngược lại hàm sẽ trả về trị là -1 Hàm có dạng
int find(int object, int *list, int size);
với size xác định số lượng các phần tử của dãy
Trang 9II/ Lớp template
Lớp template định nghiã tất cả các thuật toán được dùng bởi lớp đó, tuy nhiên kiểu dữ liệu thực sự mà lớp này xử lý sẽ được định rõ dưới dạng tham số lúc tạo ra các đối tượng thuộc lớp template đó
• Khai báo
template < class Ttype > class class_name {
// body of class };
Ttype là tên lớp hình thức và nó sẽ được xác định khi tạo ra một lớp Khi cần thiết, có thể định nghiã lớp template với nhiều kiểu dữ liệu template bằng cách dùng một danh sách các tên kiểu hình thức được phân cách nhau bằng dấu phẩy
• Sau khi tạo ra lớp template, có thể sinh ra một lớp cụ thể từ lớp template
class_name < type > object_name;
với type là tên thực của kiểu dữ liệu mà lớp đó xử lý
Các hàm thành phần của một lớp template cũng là các hàm template một cách tự động và không cần phải dùng từ khoá template để khai báo tường minh cho các hàm đó
Ví dụ 2.1 Tạo ra lớp template cho một danh sách liên kết, dùng để lưu các ký tự // A simple generic linked list
Trang 10next = 0;
}
list *getnext() { return next; }
data_t getdata() { return data; }
for(i=1; i<26; i++) {
p = new list<char> ('a' + i);
Trang 11@ Lưu ý dòng lệnh
list<char> start('a') ;
với kiểu dữ liệu được truyền sang cho lớp xuất hiện bên trong cặp dấu
ngoặc < >
Kết quả chương trình ?
@ Có thể dùng danh sách list để lưu trữ các phần tử kiểu số nguyên hay không ?
• Có thể sử dụng lớp template list ở trên cho một kiểu dữ liệu mới do người dùng định nghiã Ví dụ, về một cấu trúc lưu thông tin về danh bạ điện thoại
với structvar là biến có kiểu dữ liệu là addr
• Các hàm template và lớp template là một công cụ mạnh làm gia tăng hiệu quả
lập trình, bởi vì nó cho phép định nghiã một giải thuật ở dạng tổng quát và sử dụng được cho nhiều loại dữ liệu có kiểu khác nhau Ngoài ra, còn tiết kiệm được thời gian gõ chương trình vào máy tính, do không phải gõ lại nhiều đoạn mã chương trình giống nhau về mặt giải thuật nhưng khác nhau về kiểu dữ liệu mà nó xử lý
Ví dụ 2.2 Tạo ra lớp template cho stack, dùng để lưu các kiểu dữ liệu ký tự và
kiểu double
// This function demonstrates a generic stack
Trang 12#include <iostream.h>
#define SIZE 10
// Create a generic stack class
template <class StackType > class stack {
StackType stck[SIZE]; // holds the stack
public:
void init() { tos = 0; } // initialize stack
void push(StackType ch); // push object on stack
StackType pop(); // pop object from stack
cout << "Stack is empty.\n";
return 0; // return null on empty stack
}
tos ;
return stck[tos];
}
Trang 13int main()
{
// Demonstrate character stacks
stack<char> s1, s2; // create two stacks
for(i=0; i<3; i++) cout << "Pop s1: " << s1.pop() << "\n";
for(i=0; i<3; i++) cout << "Pop s2: " << s2.pop() << "\n";
// demonstrate double stacks
stack<double> ds1, ds2; // create two stacks
// initialize the stacks
for(i=0; i<3; i++) cout << "Pop ds1: " << ds1.pop() << "\n";
for(i=0; i<3; i++) cout << "Pop ds2: " << ds2.pop() << "\n";
Trang 14return 0;
}
Kết quả chương trình ?
• Một lớp template có thể chứa nhiều kiểu dữ liệu tổng quát ( generic data types)
Khi đó các tên kiểu dữ liệu template được khai báo trong phần template dưới dạng một danh sách và phân cách nhau bằng dấu phẩy
Ví dụ 2.3 Một lớp có hai kiểu dữ liệu tổng quát
// This example uses two generic data types in a class definition
myclass<int, double> ob1(10, 0.23); // a/
myclass<char, char *> ob2('X', "This is a test"); // b/
ob1.show(); // show int, double
ob2.show(); // show char, char *
Trang 15@ Trong cả hai trường hợp ở trên (a/, b/) , trình biên dịch sẽ tự động sinh ra các dữ liệu và hàm tương ứng với mệnh đề tạo ra lớp
Bài tập II
1 Viết chương trình tạo và biểu diễn một lớp template lưu trữ queue
2 Viết chương trình tạo một lớp template với tên input, khi hàm tạo của lớp này được gọi nó sẽ thực hiện các công việc sau :
- thông báo nhắc người dùng nhập dữ liệu : prompt message
- nhận dữ liệu từ bàn phím
- thông báo lỗi nếu dữ liệu không nằm trong vùng trị cho phép
giữa min_value và max_value
Đối tượng thuộc kiểu input được khai báo như sau
input ob("prompt message", min_value, max_value) ;
III/ Điều khiển ngoại lệ (exception handling)
C++ (theo chuẩn ANSI C++, 1994) cung cấp sẵn một cơ chế xử lý lỗi gọi là điều
khiển ngoại lệ Qua đó có thể khống chế và đáp ứng lại các lỗi xuất hiện lúc thực thi chương trình
1/ Điều khiển ngoại lệ của C++ gồm ba từ khoá : try, catch, throw
Một cách tổng quát, các mệnh đề dùng để giám sát các ngoại lệ được đặt bên trong
một khối try Khi một ngoại lệ (tức một lỗi) xuất hiện bên trong khối try, nó sẽ được ném ra (bằng từ khoá throw) Tiếp theo ngoại lệ này sẽ bị bắt lại để xử lý (bằng từ khoá catch)
Bất kỳ một mệnh đề nào thực hiện việc ném ra ngoài một ngoại lệ phải đặt ở bên trong khối try (hoặc bên trong một hàm được gọi đến từ một mệnh đề bên trong khối
try) Tất cả các ngoại lệ phải bị bắt lại bởi một mệnh đề catch đặt ngay sau khối try mà từ đó nó được ném ra
Trang 16Dạng khai báo tổng quát
try {
// try block
throw exception ; }
catch (type1 arg ) {
// catch block }
catch (type2 arg ) {
// catch block }
catch (typeN arg ) {
// catch block }
exception là giá trị được ném ra
type1, type2, , typeN kiểu dữ liệu của các đối số
Kiểu dữ liệu của ngoại lệ chính là cơ sở để quyết định xem ngoại lệ đó được xử lý bằng mệnh đề catch nào Nghiã là nếu kiểu dữ liệu xác định trong một catch nào đó trùng hợp với kiểu dữ liệu của ngoại lệ, thì mệnh đề catch đó được thực thi Tất cả các mệnh đề catch khác sẽ bị bỏ qua
Khi một ngoại lệ bị bắt lại, arg sẽ nhận giá trị của ngoại lệ này Giá trị ngoại lệ có thể là một kiểu dữ liệu bất kỳ, kể cả kiểu dữ liệu mới do người dùng tạo ra
Ví dụ 3.1 Cách điều khiển ngoại lệ của C++
// A simple exception handling example
#include <iostream.h>
int main()
{
cout << "start\n";
Trang 17try { // start a try block
cout << "Inside try block\n";
throw 10; // throw an error
cout << "This will not execute"; // not execution
}
catch (int i) { // catch an error
cout << "Caught One! Number is: ";
Inside try block
Caught One ! Numbers is : 10
• Kiểu của ngoại lệ phải giống với kiểu xác định trong mệnh đề catch
Ví dụ 3.2 Một chương trình kết thúc không bình thường do mệnh đề catch sẽ không bắt được ngoại lệ ném ra
// This example will not work
#include <iostream.h>
int main()
{
Trang 18cout << "start\n";
cout << "Inside try block\n";
throw 10; // throw an error
cout << "This will not execute";
}
catch (double i) { // Won't work for an int exception
cout << "Caught One! Number is: ";
inside try block
Abnormal program terminal
(Dạng thông báo lỗi này có thể khác nhau tuỳ thuộc vào mỗi compiler)
• Một ngoại lệ có thể được ném ra từ một mệnh đề nằm ngoài khối try chỉ khi nó được đặt trong một hàm, mà hàm này được gọi từ trong khối try
Ví dụ 3.3 Một chương trình kết thúc bình thường
// Throwing an exception from a function outside the try block
#include <iostream.h>
void Xtest(int test)
{
cout << "Inside Xtest, test is: " << test << "\n";
if(test) throw test;
}
Trang 19int main()
{
cout << "start\n";
cout << "Inside try block\n";
Xtest(0);
Xtest(1);
Xtest(2);
}
catch (int i) { // catch an error
cout << "Caught One! Number is: ";
Inside try block
Inside Xtest, test is: 0
Inside Xtest, test is: 1
Caught One! Number is: 1
end
• Một khối try có thể thuộc về một hàm khác hàm main() của chương trình Trong trường hợp này, cứ mỗi lần thực hiện hàm, bộ điều khiển ngoại lệ của hàm đó sẽ được lặp lại
Ví dụ 3.4 Một khối try/catch có thể ở bên trong một hàm khác hàm main()
#include <iostream.h>
// A try/catch can be inside a function other than main()
void Xhandler(int test)
{
try{
Trang 20if(test) throw test;
• Có thể tạo nhiều mệnh đề catch cho mỗi khối try Do mỗi mệnh đề catch có thể
bắt một kiểu ngoại lệ khác biệt với kiểu ở các catch khác
Ví dụ 3.5 Mỗi biểu thức catch được kiểm tra theo thứ tự xuất hiện và chỉ có một mệnh đề trùng kiểu mới được thực thi Tất cả các mệnh đề còn lại đều bị bỏ qua
#include <iostream.h>
// Different types of exceptions can be caught
void Xhandler(int test)
{
try{
if(test) throw test;
else throw "Value is zero";
Giải thích kết quả chương trình ?
start Caught One! Ex #: 1 Caught One! Ex #: 2 Caught One! Ex #: 3 end
Trang 21Dấu " " bắt được tất cả kiểu
• Khi một hàm được gọi từ bên trong khối block, có thể hạn chế kiểu dữ liệu nào của ngoại lệ mà hàm có thể ném được Để thực hiện các hạn chế, cần phải throw vào định nghiã hàm Dạng tổng quát
ret_type func_name(arg_list) throw(type_list) {
}
Giải thích kết quả chương trình ?
start Caught One! Ex #: 1 Caught One! Ex #: 2 Caught a string: Value is zero Caught One! Ex #: 3
end
Trang 22Chỉ có các ngoại lệ có kiểu trong danh sách type_list (phân cách bằng dâú phẩy) là
được ném ra từ hàm Ngược lại, việc ném ra các ngoại lệ thuộc kiểu khác là chương trình kết thúc bất thường
Lưu ý
Nếu đang dùng trình biên dịch tuân theo chuẩn ANSI C++, thì việc thử ném
ra một ngoại lệ mà không có mệnh đề catch nào bắt được sẽ làm gọi hàm
unexpected() Theo mặc định, hàm unexpected() sẽ gọi tiếp hàm abort() để
làm kết thúc chương trình một cách bất thường
Cũng có thể định lại để hàm unexpected() gọi đến một hàm xử lý kết thúc khác nếu
lập trình viên muốn Xem chi tiết tài liệu hướng dẫn di kèm trong trình biên dịch
• Nếu muốn ném một lần nữa một ngoại lệ từ bên trong bộ đều khiển ngoại lệ, hãy gọi một lần nữa từ khoá throw không cần kèm theo giá trị của ngoại lệ Cách này cho phép ngoại lệ hiện hành được truyền đến một đoạn try/catch khác
Ví dụ 3.6 Dùng catch( ) để bắt mọi ngoại lệ
// This example catches all exceptions
#include <iostream.h>
void Xhandler(int test)
{
try{
if(test==0) throw test; // throw int
if(test==1) throw 'a'; // throw char
if(test==2) throw 123.23; // throw double
}
catch( ) { // catch all exceptions
cout << "Caught One!\n";
}
}
int main()
{