Giáo trình Ngôn ngữ lập trình máy tính C++

Tóm tắt Giáo trình Ngôn ngữ lập trình máy tính C++: ...ne type name ( arguments ... ) { instructions ... } lời gọi hàm cũng như bất kì một hàm nào khác. Không cần thiết phải đặt từ khoá inline trong lệnh gọi, chỉ cần trong lời khai báo hàm là đủ. Đệ qui. Các hàm có thể gọi chính nó. Điều này có thể có ích với một số tác vụ như là một số phương pháp ... (&c,sizeof(c)); cout << (int) a << ", " << b << ", " << c; return 0; } 6, 10, 13 sizeof là một toán tử của ngôn ngữ C++, nó trả về một giá trị hằng là kích thước tính bằng byte của tham số truyền cho nó, ví dụ sizeof(char) bằng 1 vì kích thước của char là 1 byte... bản. Dưới đây là danh sách tất cả các toán tử có thể được quá tải: + - * / = += -= *= /= > >= == != = ++ -- % & ^ ! | ~ &= ^= |= && || %= [] () new delete Để làm quá tải một toán tử chúng ta chỉ cần viết một hàm thành viên của lớp có tên operator theo sau là toán tử chúng ta muốn làm quá...

doc114 trang | Chia sẻ: havih72 | Lượt xem: 122 | Lượt tải: 0download
Nội dung tài liệu Giáo trình Ngôn ngữ lập trình máy tính C++, để tải tài liệu về máy bạn click vào nút DOWNLOAD ở trên
late 
int pair::module() {
 return value1%value2;
}
int main () {
 pair myints (100,75);
 pair myfloats (100.0,75.0);
 cout << myints.module() << '\n';
 cout << myfloats.module() << '\n';
 return 0;
}
25
0
Như bạn có thể thấy sự chuyên môn hoá được định nghĩa theo cách sau: 
template class class_name 
Sự chuyên môn hoá này là một phần của một mẫu, vì vậy chúng ta phải bắt đầu phần khai báo với template . Và rõ ràng rằng đó là sự chuyên môn hoá cho một kiểu dữ liệu cụ thể nên chúng ta không thể dùng một kiểu dữ liệu tổng quát, cặp ngoặc nhọn cũng phải để trống. Sau phần tên lớp chúng ta phải viết thêm tên kiểu dữ liệu muốn dùng ở giữa cặp ngoặc nhọn . 
Khi chúng ta chuyên biệt hoá một kiểu dữ liệu cho một mẫu chúng ta cũng phải định nghĩa tất cả các thành viên tương xứng với sự chuyên môn hoá đó (nếu bạn thấy chưa rõ lắm, hãy xem lại ví dụ trên trong đó chúng ta đã phải viết lại constructor cho chính nó mặc dù cái này hoàn toàn giống như constructor ở trong lớp tổng quát. Nguyên nhân là do không có thành viên nào được "thừa kế" từ lớp tổng quát cho lớp chuyên môn hoá. 
Các giá trị tham số cho mẫu
Bên cạnh các tham số của mẫu là kiểu dữ liệu (được đứng trước bởi từ khoá class hay typename), các hàm mẫu và các lớp mẫu còn có thể có các tham số khác không phải là kiểu mà chúng có thể là các giá trị hằng. Trong ví dụ dưới đây lớp mẫu được dùng để lưu trữ mảng:
// array template
#include 
template 
class array {
 T memblock [N];
 public:
 setmember (int x, T value);
 T getmember (int x);
};
template 
array::setmember (int x, T value) {
 memblock[x]=value;
}
template 
T array::getmember (int x) {
 return memblock[x];
}
int main () {
 array myints;
 array myfloats;
 myints.setmember (0,100);
 myfloats.setmember (3.0,3.1416);
 cout << myints.getmember(0) << '\n';
 cout << myfloats.getmember(3) << '\n';
 return 0;
}
100
3.1416
Chúng ta cũng có thể thiết lập các giá trị mặc định cho bất kì tham số mẫu nào giống như với các tham số của hàm bình thường. 
Ví dụ:
template // Trường hợp phổ biến nhất: một tham số lớp.
template // Hai tham số lớp.
template // Một lớp và một giá trị nguyên.
template // Một tham số lớp với giá trị mặc định.
template // Tham số là một hàm.
Mẫu và các dự án
Theo quan điểm của trình biên dịch, các mẫu không phải là các hàm hay lớp thông thường. Chúng được dịch theo nhu cầu. Điều đó có nghĩa là mã của một hàm mẫu không được dịch cho đến khi cần dùng đến. Với các trình dịch ở thời điểm thực hiện bài viết, trình dịch sẽ tạo ra từ mẫu một hàm cụ thể cho kiểu dữ liệu được yêu cầu. 
Khi các dự án phát triển lên, nó thường chi mã của một chương trình thành nhiều file khác nhau. Trong những trường hợp đó, nói chúng phần giao tiếp và phần thực hiện là tách biệt. Lấy ví dụ một thư viện hàm nào đó, phần giao tiếp thường gồm tất cả những khai báo mẫu của các hàm có thể được gọi, chúng thường được khai báo trong một "header file"với phần mở rộng .h, và phần thực hiện (phần định nghĩa cả các hàm đó) được đặt trong các file mã C++ riêng. 
Đối với mẫu, chúng ta có một điều bắt buộc khi thực hiện các dự án nhiều file: phần thực hiện (phần định nghĩa) của một lớp mẫu hay hàm mẫu phải nằm trong cùng một file với phần khai báo. Điều này có nghĩa là chúng ta không thể tách phần giao tiếp thành một file riêng mà phải gộp cả phần giao tiếp và phần thực thi vào bất kì fiel nào sử dụng mẫu. 
Trở lại với trường hợp thư viện hàm, nếu chúng ta muốn tạo một thư viện hàm mẫu, thay vì việc tạo một header file (.h) chúng ta nên tạo một "template file" chứa cả phần giao tiếp và phần thực thi của các hàm mẫu (không có một quy định nào về phần mở rộng của những file này, bạn thích dùng thế nào cũng được hoặc cứ để là .h cho tiện). Một điều cuối cùng nữa là việc một "template file" được dùng nhiều lần trong một dự án sẽ không gây ra lỗi liên kết vì chúng được dịch theo nhu cầu và những trình biên dịch hỗ trợ templates sẽ biết phải làm thế nào để không sinh mã lặp trong các trường hợp đó. 
Bài 5.1	Namespaces
Namespaces cho phép chúng ta gộp một nhóm các lớp, các đối tượng toàn cục và các hàm dưới một cái tên. Nói một cách cụ thể hơn, chúng dùng để chia phạm vi toàn cụ thành những phạm vi nhỏ hơn với tên gọi namespaces. 
Khuông mẫu để sử dụng namespaces là: 
namespace identifier
{
  namespace-body
}
Trong đó identifier là bất kì một tên hợp lệ nào và namespace-body là một tập hợp những lớp, đối tượng và hàm được gộp trong namespace. Ví dụ: 
namespace general
{
 int a, b;
}
Trong trường hợp này, a và b là những biến bình thường được tích hợp bên trong namespace general. Để có thể truy xuất vào các biến này từ bên ngoài namespace chúng ta phải sử dụng toán tử ::. Ví dụ, để truy xuất vào các biến đó chúng ta viết: 
general::a
general::b
Namespace đặc biệt hữu dụng trong trường hợp có thể có một đối tượng toàn cục hoặc một hàm có cùng tên với một cái khác, gây ra lỗi định nghĩa lại. Ví dụ:
// namespaces
#include 
namespace first
{
 int var = 5;
}
namespace second
{
 double var = 3.1416;
}
int main () {
 cout << first::var << endl;
 cout << second::var << endl;
 return 0;
}
5
3.1416
Trong ví dụ này có hai biến toàn cục cùng có tên var, một được định nghĩa trong namespace first và cái còn lại nằm trong second. Chương trình vẫn chạy ngon, cám ơn namespaces. 
using namespace
Chỉ thị using theo sau là namespace dùng để kết hợp mức truy xuất hiện thời với một namespace cụ thể để các đối tượng và hàm thuộc namespace có thể được truy xuất trực tiếp như thể chúng được khai báo toàn cục. Cách sử dụng như sau: 
using namespace identifier;
Ví dụ:
// using namespace example
#include 
namespace first
{
 int var = 5;
}
namespace second
{
 double var = 3.1416;
}
int main () {
 using namespace second;
 cout << var << endl;
 cout << (var*2) << endl;
 return 0;
}
3.1416
6.2832
Trong trường hợp này chúng ta có thể sử dụng var mà không phải đặt trước nó bất kì toán tử phạm vi nào. 
Bạn phải để ý một điều rằng câu lệnh using namespace chỉ có tác dụng trong khối lệnh mà nó được khai báo hoặc trong toàn bộ chương trình nếu nó được dùng trong phạm vi toàn cục. Ví dụ, nếu chúng ta định đầu tiên sử dụng một đối tượng thuộc một namespace và sau đó sử dụng một đối tượng thuộc một namespace khác chúng ta có thể làm như sau:
// using namespace example
#include 
namespace first
{
 int var = 5;
}
namespace second
{
 double var = 3.1416;
}
int main () {
 {
 using namespace first;
 cout << var << endl;
 }
 {
 using namespace second;
 cout << var << endl;
 }
 return 0;
}
5
3.1416
Định nghĩa bí danh
Chúng ta cũng có thể định nghĩa những tên thay thế cho các namespaces đã được khai báo. Cách thức để làm việc này như sau: 
namespace new_name = current_name ;
Namespace std
Một trong những ví dụ tốt nhất mà chúng ta có thể tìm thấy về namespaces chính là bản thân thư viện chuẩn của C++. Theo chuẩn ANSI C++, tất cả định nghĩa của các lớp, đối tượng và hàm của thư viện chuẩn đều được định nghĩa trong namespace std. 
Bạn có thể thấy rằng chúng ta đã bỏ qua luật này trong suốt tutorial này. Tôi đã quyết định làm vậy vì luật này cũng mới như chuẩn ANSI (1997) và nhiều trình biên dịch cũ không tương thích với nó. 
Hầu hết các trình biên dịch, thậm chí cả những cái tuân theo chuẩn ANSI, cho phép sử dụng các file header truyền thống (như là iostream.h, stdlib.h), những cái mà chúng ta trong suốt tutorial này. Tuy nhiên, chuẩn ANSI đã hoàn toàn thiết kế lại những thư viện này để tận dụng lợi thế của tính năng templates và để tuân theo luật phải khai báo tất cả các hàm và biến trong namespace std. 
Chuẩn ANSI đã chỉ định những tên mới cho những file "header" này, cơ bản là dùng cùng tên với các file của chuẩn C++ nhưng không có phần mở rộng .h. Ví dụ, iostream.h trở thành iostream. 
Nếu chúng ta sử dụng các file include của chuẩn ANSI-C++ chúng ta phải luôn nhớ rằng tất cả các hàm, lớp và đối tượng sẽ được khai báo trong std. Ví dụ:
// ANSI-C++ compliant hello world
#include 
int main () {
 std::cout << "Hello world in ANSI-C++\n";
 return 0;
}
Hello world in ANSI-C++
Mặc dù vậy chúng ta nên sử dụng using namespace để khỏi phải viết toán tử :: khi tam chiếu đến các đối tượng chuẩn:
// ANSI-C++ compliant hello world (II)
#include 
using namespace std;
int main () {
 cout << "Hello world in ANSI-C++\n";
 return 0;
}
Hello world in ANSI-C++
Bài 5.1 	Exception handling
Trong suốt quá trình phát triển một chương trình, có thể có một số trường hợp mà một số đoạn mã chạy sai do truy xuất đến những tài nguyên không tồn tại hay vượt ra ngoài khoảng mong muốn... 
Những loại tình huống bất thường này được nằm trong cái được gọi là exceptions và C++ đã vừa tích hợp ba toán tử mới để xử lý những tình huống này: try, throw và catch. 
Dạng thức sử dụng như sau: 
try {
 // đoạn mã cần thử
 throw exception;
}
catch (type  exception)
{
 // đoạn được thực hiện trong trường hợp có lỗi
}
Nguyên tắc hoạt động:
- Đoạn mã nằm trong khối try được thực hiện một cách bình thường. Trong trường hợp có lỗi xảy ra, đoạn mã này phải sử dụng từ khoá throw và một tham số để báo lỗi. Kiểu tham số này mô tả chi tiết hoá lỗi và có thể là bất kì kiểu hợp lệ nào.
- Nếu có lỗi xảy ra, nếu lệnh throw đã được thực hiện bên trong khối try, khối catch sẽ được thực hiện và nhận tham số được truyền bởi throw. 
Ví dụ:
// exceptions
#include 
int main () {
 char myarray[10];
 try
 {
 for (int n=0; n<=10; n++)
 {
 if (n>9) throw "Out of range";
 myarray[n]='z';
 }
 }
 catch (char * str)
 {
 cout << "Exception: " << str << endl;
 }
 return 0;
}
Exception: Out of range
Trong ví dụ này, nếu bên trong vòng lặp mà n lớn hơn 9 thì một lỗi sẽ được thông báo vì  myarray[n] trong trường hợp đó có thể trỏ đến địa chỉ ô nhớ không tin cậy. Khi throw được thực hiện, khối try ngay lập tức kết thúc và mọi đối tượng được tạo bên trong khối try bị phá huỷ. Sau đó, quyền điều khiển được chuyển cho khối catch tương ứng (chỉ được thực hiện trong những tình huống như thế này). Cuối cùng chương trình tiếp tục ngay sau khối, trong trường hợp này: return 0;. 
Cú pháp được sử dụng bởi throw tương tự với return: Chỉ có một tham số và không cần đặt nó nằm trong cặp ngoặc đơn. 
Khối catch phải nằm ngay sau khối try mà không được có đoạn mã nào nằm giữa chúng. Tham số mà catch chấp nhận có thể là bất kì kiểu dữ liệu hợp lệ nào. Hơn nữa, catch có thể được quá tải để có thể chấp nhận nhiều kiểu dữ liệu khác nhau. Trong trường hợp này khối catch được thực hiện là khối phù hợp với kiểu của tham số được gửi đến bởi throw:
// exceptions: multiple catch blocks
#include 
int main () {
 try
 {
 char * mystring;
 mystring = new char [10];
 if (mystring == NULL) throw "Allocation failure";
 for (int n=0; n<=100; n++)
 {
 if (n>9) throw n;
 mystring[n]='z';
 }
 }
 catch (int i)
 {
 cout << "Exception: ";
 cout << "index " << i << " is out of range" << endl;
 }
 catch (char * str)
 {
 cout << "Exception: " << str << endl;
 }
 return 0;
}
Exception: index 10 is out of range
Ở đây có thể có hai trường hợp xảy ra: 
Khối dữ liệu 10 kí tự không thể được cấp phát (gần như là chẳng bao giờ xảy ra nhưng không có nghĩa là không thể): lỗi này sẽ bị chặn bởi catch (to char * str).
Chỉ số cực đại của mystring đã bị vượt quá: lỗi này sẽ bị chặn bởi catch (int i), since parameter is an integer number. 
Chúng ta có thể định nghĩa một khối catch để chặn tất cả các exceptions mà không phụ thuộc vào kiểu được dùng để gọi throw. Để làm việc này chúng ta phải viết dấu ba chấm thay vì kiểu và tên số tham số: 
try {
 // code here
}
catch (...) {
 cout << "Exception occurred";
}
Còn có thể lồng các khối try-catch vào các khối try khác. Trong trường hợp này, một khối catch bên trong có thể chuyển tiếp exception nhận được cho khối bên ngoài, để làm việc này chúng ta sử dụng biểu thức  throw; không có tham số. Ví dụ: 
try {
 try {
 // code here
 }
 catch (int n) {
 throw;
 }
}
catch (...) {
 cout << "Exception occurred";
}
Exception không bị chặn
Nếu một exception không bị chặn bởi bất kì lệnh catch nào vì không có lệnh nào có kiểu phù hợp, hàm đặc biệt terminate sẽ được gọi. 
Hàm này đã được định nghĩa sẵn để chấm dứt chương trình ngay lập tức và hiển thịc thông báo lỗi "Abnormal termination". Dạng thức của nó như sau: 
void terminate(); 
Những exceptions chuẩn
Một số hàm thuộc thư viện C++ chuẩn gửi các exceptions mà chúng ta có thể chặn nếu chúng ta sử dụng một khối try. Những exceptions này được gửi đi với kiểu tham số là một lớp thừa kế từ std::exception. Lớp này (std::exception) được định nghĩa trong file header C++ chuẩn và được dùng làm mẫu cho hệ thống phân cấp các exception chuẩn:
exception
bad_alloc
(gửi bởi new)
bad_cast
(gửi bởi dynamic_cast khi thất bại với một kiểu tham chiếu)
bad_exception
(được gửi khi một exception không phù hợp với lệnh catch nào)
bad_typeid
(gửi bởi typeid)
logic_error
domain_error
invalid_argument
length_error
out_of_range
runtime_error
overflow_error
range_error
underflow_error
ios_base::failure
(gửi bởi ios::clear)
Bởi vì đây là một hệ thống phân lớp có thứ bậc, nếu bạn sử dụng một khối catch để chặn bất kì một exception nào nằm trong hệ thông này bằng cách sử dụng tham số biến (thêm một dấu & vào phía trước tên của tham số) bạn sẽ chặn được tất cả các exception thừa kế (luật thừa kế trong C++) 
Ví dụ dưới đây chặn một exception có kiểu bad_typeid (được thừa kế từ exception), lỗi này được tạo ra khi muốn biết kiểu của một con trỏ null.
// Những exception chuẩn
#include 
#include 
#include 
class A {virtual f() {}; };
int main () {
 try {
 A * a = NULL;
 typeid (*a);
 }
 catch (std::exception& e)
 {
 cout << "Exception: " << e.what();
 }
 return 0;
}
Exception: Attempted typeid of NULL pointer
Bạn có thể sử dụng các lớp của hệ thống phân cấp các exception chuẩn này báo những lỗi của mình hoặc thừa kế những lớp mới từ chúng. 
Bài 5.4 	Chuyển đổi kiểu nâng cao
Cho đến nay, để có thể chuyển đổi kiểu một đối tượng đơn giản sang kiểu khác chúng ta đã sử dụng toán tử chuyển đổi kiểu truyền thống. Ví dụ, để chuyển một số dấu phẩy động có kiểu double sang dạng số nguyên có kiểu int chúng ta sử dụng: 
int i;
double d;
i = (int) d; 
hoặc 
i = int (d); 
Điều này làm việc tốt đối với các kiểu cơ bản đã có định nghĩa các cacchs chuyển đổi cơ bản, tuy nhiên những toán tử này cũng có thể được áp dụng bừa bãi với các lớp và con trỏ tới các lớp. Bởi vậy, hoàn toàn hợp lệ khi viết như sau: 
// class type-casting
#include 
class CDummy {
 int i;
};
class CAddition {
	int x,y;
 public:
	CAddition (int a, int b) { x=a; y=b; }
	int result() { return x+y;}
};
int main () {
 CDummy d;
 CAddition * padd;
 padd = (CAddition*) &d;
 cout result();
 return 0;
}
Mặc dù chương trình trên là hợp lệ trong C++ (thực tế là nó sẽ được dịch mà không có bất kì một lỗi hay warning nào đối với hầu hết các trình dịch) nhưng nó là một đoạn mã không tốt lắm vì chúng ta sử dụng hàm result, đó là một thành viên của CAddition, padd không phải là một đối tượng, nó chỉ là một con trỏ được chúng ta gán cho địa chỉ của một đối tượng không có quan hệ. Khi truy xuất đến thành viên result, chương trình sẽ tạo ra lỗi run-time hoặc chỉ là một kết quả không mong muốn. 
Để có thể điều khiển việc chuyển đổi kiểu giữa các lớp, chuẩn ANSI-C++ đã định nghĩa bốn toán tử chuyển đổi kiểu mới: reinterpret_cast, static_cast, dynamic_cast và const_cast. Tất cả chúng đều có cùng dạng thức khi sử dụng: 
reinterpret_cast (expression)
 dynamic_cast (expression)
 static_cast (expression)
 const_cast (expression)
Trong đó new_type kiểu mà expression phải được chuyển đổi thành. Để tạo ra sự tương tự dễ hiểu với các toán tử chuyển đổi truyền thống các biểu thức này có nghĩa là: 
(new_type) expression
new_type (expression) 
reinterpret_cast
reinterpret_cast chuyển đổi một con trỏ sang bất kì kiểu con trỏ nào khác. Nó cũng cho phép chuyển đổi từ con trỏ sang dạng số nguyên và ngược lại. 
Toán tử này có thể chuyển đổi con trỏ giữa các lớp không có quan hệ với nhau. Kết quả của toán tử này là một bản copy giá trị của con trỏ này sang con trỏ kia. Dữ liệu được trỏ đến không hề được kiểm tra hay chuyển đổi. 
Trong trường hợp chuyển đổi giữa con trỏ và số nguyên, cách chuyển nội dung của nó phụ thuộc vào hệ thống.  
class A {};
class B {};
A * a = new A;
B * b = reinterpret_cast(a); 
reinterpret_cast đối xử với tất cả các con trỏ giống như các toán tử chuyển đổi truyền thống. 
static_cast
static_cast cho phép thực hiện bất kì phép chuyển đổi nào 
static_cast allows to perform any casting that can be implicitly performed as well as also the inverse cast (even if this is not allowed implicitly).
Applied to pointers to classes, that is to say that it allows to cast a pointer of a derived class to its base class (this is a valid conversion that can be implicitly performed) and can also perform the inverse: cast a base class to its derivated class. 
In this last case the base class that is being casted is not checked to determine wether this is a complete class of the destination type or not. 
class Base {};
class Derived: public Base {};
Base * a = new Base;
Derived * b = static_cast(a);
static_cast, aside from manipulating pointers to classes, can also be used to perform conversions explicitly defined in classes, as well as to perform standard conversions between fundamental types: 
double d=3.14159265;
int i = static_cast(d); 
dynamic_cast
dynamic_cast is exclusively used with pointers and references to objects. It allows any type-casting that can be implicitly performed as well as the inverse one when used with polymorphic classes, however, unlike static_cast, dynamic_cast checks, in this last case, if the operation is valid. That is to say, it checks if the casting is going to return a valid complete object of the requested type. 
Checking is performed during run-time execution. If the pointer being casted is not a pointer to a valid complete object of the requested type, the value returned is a NULL pointer. 
class Base { virtual dummy(){}; };
class Derived : public Base { };
Base* b1 = new Derived;
Base* b2 = new Base;
Derived* d1 = dynamic_cast(b1);   // succeeds
Derived* d2 = dynamic_cast(b2);   // fails: returns NULL
If the type-casting is performed to a reference type and this casting is not possible an exception of type bad_cast is thrown: 
class Base { virtual dummy(){}; };
class Derived : public Base { };
Base* b1 = new Derived;
Base* b2 = new Base;
Derived d1 = dynamic_cast(b1);   // succeeds
Derived d2 = dynamic_cast(b2);   // fails: exception thrown 
const_cast
This type of casting manipulates the const attribute of the passed object, either to be set or removed: 
class C {};
const C * a = new C;
C * b = const_cast (a); 
Neither of the other three new cast operators can modify the constness of an object. 
typeid
ANSI-C++ also defines a new operator called typeid that allows to check the type of an expression: 
typeid (expression)
this operator returns a refernece to a constant object of type type_info that is defined in standard header file . This returned value can be compared with another using operators == and != or can serve to obtain a string of characters representing the data type or class name by using its name() method.
// typeid, typeinfo
#include 
#include 
class CDummy { };
int main () {
 CDummy* a,b;
 if (typeid(a) != typeid(b))
 {
 cout << "a and b are of different types:\n";
 cout << "a is: " << typeid(a).name() << '\n';
 cout << "b is: " << typeid(b).name() << '\n';
 }
 return 0;
}
a and b are of different types:
a is: class CDummy *
b is: class CDummy 
MỤC LỤC

File đính kèm:

  • docgiao_trinh_ngon_ngu_lap_trinh_may_tinh_c.doc