Skip to content Skip to navigation Skip to collection information

OpenStax_CNX

You are here: Home » Content » Lập trình hướng đối tượng » Các mở rộng của c++

Navigation

Lenses

What is a lens?

Definition of a lens

Lenses

A lens is a custom view of the content in the repository. You can think of it as a fancy kind of list that will let you see content through the eyes of organizations and people you trust.

What is in a lens?

Lens makers point to materials (modules and collections), creating a guide that includes their own comments and descriptive tags about the content.

Who can create a lens?

Any individual member, a community, or a respected organization.

What are tags? tag icon

Tags are descriptors added by lens makers to help label content, attaching a vocabulary that is meaningful in the context of the lens.

This content is ...

Affiliated with (What does "Affiliated with" mean?)

This content is either by members of the organizations listed or about topics related to the organizations listed. Click each link to see a list of all content affiliated with the organization.
  • VOCW

    This module and collection are included inLens: Vietnam OpenCourseWare's Lens
    By: Vietnam OpenCourseWare

    Click the "VOCW" link to see all content affiliated with them.

Recently Viewed

This feature requires Javascript to be enabled.
 

Các mở rộng của c++

Module by: Lê Thị Mỹ Hạnh. E-mail the author

Summary: Phần này trình bày về các mở rộng của c++

LỊCH SỬ CỦA C++

Vào những năm đầu thập niên 1980, người dùng biết C++ với tên gọi "C with Classes" được mô tả trong hai bài báo của Bjarne Stroustrup (thuộc AT&T Bell Laboratories) với nhan đề "Classes: An Abstract Data Type Facility for the C Language" và "Adding Classes to C : AnExercise in Language Evolution". Trong công trình này, tác giả đã đề xuất khái niệm lớp, bổ sung việc kiểm tra kiểu tham số của hàm, các chuyển đổi kiểu và một số mở rộng khác vào ngôn ngữ C. Bjarne Stroustrup nghiên cứu mở rộng ngôn ngữ C nhằm đạt đến một ngôn ngữ mô phỏng (simulation language) với những tính năng hướng đối tượng.

Trong năm 1983, 1984, ngôn ngữ "C with Classes" được thiết kế lại, mở rộng hơn rồi một trình biên dịch ra đời. Và chính từ đó, xuất hiện tên gọi "C++". Bjarne Stroustrup mô tả ngôn ngữ C++ lần đầu tiên trong bài báo có nhan đề "Data Abstraction in C". Sau một vài hiệu chỉnh C++ được công bố rộng rãi trong quyển "The C++ Programming Language" của Bjarne Stroustrup xuất hiện đánh dấu sự hiện diện thực sự của C++, người lập tình chuyên nghiệp từ đây đã có một ngôn ngữ đủ mạnh cho các dữ án thực tiễn của mình.

Về thực chất C++ giống như C nhưng bổ sung thêm một số mở rộng quan trọng, đặc biệt là ý tưởng về đối tượng, lập trình định hướng đối tượng.Thật ra các ý tưởng về cấu trúc trong C++ đã xuất phát vào các năm 1970 từ Simula 70 và Algol 68. Các ngôn ngữ này đã đưa ra các khái niệm về lớp và đơn thể. Ada là một ngôn ngữ phát triển từ đó, nhưng C++ đã khẳng định vai trò thực sự của mình.

CÁC MỞ RỘNG CỦA C++

Các từ khóa mới của C++

Để bổ sung các tính năng mới vào C, một số từ khóa (keyword) mới đã được đưa vào C++ ngoài các từ khóa có trong C. Các chương trình bằng C nào sử dụng các tên trùng với các từ khóa cần phải thay đổi trước khi chương trình được dịch lại bằng C++. Các từ khóa mới này là :

Bảng 1
asm catch class delete friend inline
new operator private protected public template
this throw try virtual    

Cách ghi chú thích

 C++ chấp nhận hai kiểu chú thích. Các lập trình viên bằng C đã quen với cách chú thích bằng /*…*/. Trình biên dịch sẽ bỏ qua mọi thứ nằm giữa /*…*/.

Ví dụ 2.1: Trong chương trình sau :

#include <iostream.h>

int main()

{

int I;

for(I = 0; I < 10 ; ++ I) // 0 - 9

cout<<I<<"\n";// In ra

return 0;

}

Mọi thứ nằm giữa /*…*/ từ dòng 1 đến dòng 3 đều được chương trình bỏ qua. Chương trình này còn minh họa cách chú thích thứ hai. Đó là cách chú thích bắt đầu bằng // ở dòng 8 và dòng 9. Chúng ta chạy ví dụ 2.1, kết quả ở hình 2.1.

Hình 1
Hình 1 (graphics1.png)

Hình 2.1: Kết quả của ví dụ 2.1

Nói chung, kiểu chú thích /*…*/ được dùng cho các khối chú thích lớn gồm nhiều dòng, còn kiểu // được dùng cho các chú thích một dòng.

Dòng nhập/xuất chuẩn

Trong chương trình C, chúng ta thường sử dụng các hàm nhập/xuất dữ liệu là printf() và scanf(). Trong C++ chúng ta có thể dùng dòng nhập/xuất chuẩn (standard input/output stream) để nhập/xuất dữ liệu thông qua hai biến đối tượng của dòng (stream object) là cout và cin.

Ví dụ 2.2: Chương trình nhập vào hai số. Tính tổng và hiệu của hai số vừa nhập.

//Chuong trinh 2.2

#include <iostream.h>

int main()

{

int X, Y;

cout<< "Nhap vao mot so X:";

cin>>X;

cout<< "Nhap vao mot so Y:";

cin>>Y;

cout<<"Tong cua chung:"<<X+Y<<"\n";

cout<<"Hieu cua chung:"<<X-Y<<"\n";

return 0;

}

Để thực hiện dòng xuất chúng ta sử dụng biến cout (console output) kết hợp với toán tử chèn (insertion operator) << như ở các dòng 5, 7, 9 và 10. Còn dòng nhập chúng ta sử dụng biến cin (console input) kết hợp với toán tử trích (extraction operator) >> như ở các dòng 6 và 8. Khi sử dụng cout hay cin, chúng ta phải kéo file iostream.h như dòng 1. Chúng ta sẽ tìm hiểu kỹ về dòng nhập/xuất ở chương 8. Chúng ta chạy ví dụ 2.2 , kết quả ở hình 2.2.

Hình 2
Hình 2 (graphics2.png)

Hình 2.2: Kết quả của ví dụ 2.2

Hình 3
Hình 3 (graphics3.png)

Hình 2.3: Dòng nhập/xuất dữ liệu

Cách chuyển đổi kiểu dữ liệu

Hình thức chuyển đổi kiểu trong C tương đối tối nghĩa, vì vậy C++ trang bị thêm một cách chuyển đổi kiểu giống như một lệnh gọi hàm.

Ví dụ 2.3:

#include <iostream.h>

int main()

{

int X = 200;

long Y = (long) X; //Chuyen doi kieu theo cach cua C

long Z = long(X); //Chuyen doi kieu theo cach moi cua C++

cout<< "X = "<<X<<"\n";

cout<< "Y = "<<Y<<"\n";

cout<< "Z = "<<Z<<"\n";

return 0;

}

Chúng ta chạy ví dụ 2.3 , kết quả ở hình 2.4.

Hình 4
Hình 4 (graphics4.png)

Hình 2.4: Kết quả của ví dụ 2.3

Vị trí khai báo biến

Trong chương trình C đòi hỏi tất cả các khai báo bên trong một phạm vi cho trước phải được đặt ở ngay đầu của phạm vi đó. Điều này có nghĩa là tất cả các khai báo toàn cục phải đặt trước tất cả các hàm và các khai báo cục bộ phải được tiến hành trước tất cả các lệnh thực hiện. Ngược lại C++ cho phép chúng ta khai báo linh hoạt bất kỳ vị trí nào trong một phạm vi cho trước (không nhất thiết phải ngay đầu của phạm vi), chúng ta xen kẽ việc khai báo dữ liệu với các câu lệnh thực hiện.

Ví dụ 2.4: Chương trình mô phỏng một máy tính đơn giản

1: #include <iostream.h>2: int main()3: {4:    int X;5:    cout<< "Nhap vao so thu nhat:";6:    cin>>X;7:    int Y;

8:    cout<< "Nhap vao so thu hai:";9:    cin>>Y;10:    char Op;11:    cout<<"Nhap vao toan tu (+-*/):";12:    cin>>Op;13:    switch(Op)14:    {15:       case ‘+’:

16:          cout<<"Ket qua:"<<X+Y<<"\n";

17:          break;

18:       case ‘-’:

19:          cout<<"Ket qua:"<<X-Y<<"\n";

20:          break;

21:       case ‘*’:

22:          cout<<"Ket qua:"<<long(X)*Y<<"\n";

23:          break;

24:       case ‘/’:

25:          if (Y)

26:             cout<<"Ket qua:"<<float(X)/Y<<"\n";

27:          else

28:             cout<<"Khong the chia duoc!" <<"\n"; 9; 9;

29:          break;

30:       default :

31:          cout<<"Khong hieu toan tu nay!"<<"\n";

32:    }

33:    return 0;

34: }

Trong chương trình chúng ta xen kẻ khai báo biến với lệnh thực hiện ở dòng 4 đến dòng 12. Chúng ta chạy ví dụ 2.4, kết quả ở hình 2.5.

Hình 5
Hình 5 (graphics5.png)

Hình 2.5: Kết quả của ví dụ 2.4

Khi khai báo một biến trong chương trình, biến đó sẽ có hiệu lực trong phạm vi của chương trình đó kể từ vị trí nó xuất hiện. Vì vậy chúng ta không thể sử dụng một biến được khai báo bên dưới nó.

Các biến const

Trong ANSI C, muốn định nghĩa một hằng có kiểu nhất định thì chúng ta dùng biến const (vì nếu dùng #define thì tạo ra các hằng không có chứa thông tin về kiểu). Trong C++, các biến const linh hoạt hơn một cách đáng kể:

C++ xem const cũng như #define nếu như chúng ta muốn dùng hằng có tên trong chương trình. Chính vì vậy chúng ta có thể dùng const để quy định kích thước của một mảng như đoạn mã sau:

const int ArraySize = 100;

int X[ArraySize];

Khi khai báo một biến const trong C++ thì chúng ta phải khởi tạo một giá trị ban đầu nhưng đối với ANSI C thì không nhất thiết phải làm như vậy (vì trình biên dịch ANSI C tự động gán trị zero cho biến const nếu chúng ta không khởi tạo giá trị ban đầu cho nó).

Phạm vi của các biến const giữa ANSI C và C++ khác nhau. Trong ANSI C, các biến const được khai báo ở bên ngoài mọi hàm thì chúng có phạm vi toàn cục, điều này nghĩa là chúng có thể nhìn thấy cả ở bên ngoài file mà chúng được định nghĩa, trừ khi chúng được khai báo là static. Nhưng trong C++, các biến const được hiểu mặc định là static.

Về struct, union và enum

Trong C++, các struct và union thực sự các các kiểu class. Tuy nhiên có sự thay đổi đối với C++. Đó là tên của struct và union được xem luôn là tên kiểu giống như khai báo bằng lệnh typedef vậy.

Bảng 2
Trong C, chúng ta có thể có đoạn mã sau :struct Complex{float Real;float Imaginary;};…………………..struct Complex C; Trong C++, vấn đề trở nên đơn giản hơn:struct Complex{float Real;float Imaginary;};…………………..Complex C;

Quy định này cũng áp dụng cho cả union và enum. Tuy nhiên để tương thích với C, C++ vẫn chấp nhận cú pháp cũ.

Một kiểu union đặc biệt được thêm vào C++ gọi là union nặc danh (anonymous union). Nó chỉ khai báo một loạt các trường(field) dùng chung một vùng địa chỉ bộ nhớ. Một union nặc danh không có tên tag, các trường có thể được truy xuất trực tiếp bằng tên của chúng. Chẳng hạn như đoạn mã sau:

union

{

int Num;

float Value;

};

Cả hai Num và Value đều dùng chung một vị trí và không gian bộ nhớ. Tuy nhiên không giống như kiểu union có tên, các trường của union nặc danh thì được truy xuất trực tiếp, chẳng hạn như sau:

Num = 12;

Value = 30.56;

Toán tử định phạm vi

Toán tử định phạm vi (scope resolution operator) ký hiệu là ::, nó được dùng truy xuất một phần tử bị che bởi phạm vi hiện thời.

Ví dụ 2.5 :

1: #include <iostream.h>

2: int X = 5;

3: int main()

4: {

5:    int X = 16;

6:    cout<< "Bien X ben trong = "<<X<<"\n";

7:    cout<< "Bien X ben ngoai = "<<::X<<"\n";

8:    return 0;

9: }

Chúng ta chạy ví dụ 2.5, kết quả ở hình 2.6

Hình 6
Hình 6 (graphics6.png)

Hình 2.6: Kết quả của ví dụ 2.5

Toán tử định phạm vi còn được dùng trong các định nghĩa hàm của các phương thức trong các lớp, để khai báo lớp chủ của các phương thức đang được định nghĩa đó. Toán tử định phạm vi còn có thể được dùng để phân biệt các thành phần trùng tên của các lớp cơ sở khác nhau.

Toán tử new và delete

Trong các chương trình C, tất cả các cấp phát động bộ nhớ đều được xử lý thông qua các hàm thư viện như malloc(), calloc() và free(). C++ định nghĩa một phương thức mới để thực hiện việc cấp phát động bộ nhớ bằng cách dùng hai toán tử new và delete. Sử dụng hai toán tử này sẽ linh hoạt hơn rất nhiều so với các hàm thư viện của C.

Bảng 3
Đoạn chương trình sau dùng để cấp phát vùng nhớ động theo lối cổ điển của C.int *P;P = malloc(sizeof(int));if (P==NULL)printf("Khong con du bo nho de cap phat\n");else{*P = 290;printf("%d\n", *P);free(P);} Trong C++, chúng ta có thể viết lại đoạn chương trình trên như sau:int *P;P = new int;if (P==NULL)cout<<"Khong con du bo nho de cap phat\n";else{*P = 290;cout<<*P<<"\n";delete P;}

Chúng ta nhận thấy rằng, cách viết của C++ sáng sủa và dễ sử dụng hơn nhiều. Toán tử new thay thế cho hàm malloc() hay calloc() của C có cú pháp như sau :

new type_name

new ( type_name )

new type_name initializer

new ( type_name ) initializer

Trong đó :

type_name: Mô tả kiểu dữ liệu được cấp phát. Nếu kiểu dữ liệu mô tả phức tạp, nó có thể được đặt bên trong các dấu ngoặc.

initializer: Giá trị khởi động của vùng nhớ được cấp phát.

Nếu toán tử new cấp phát không thành công thì nó sẽ trả về giá trị NULL.

Còn toán tử delete thay thế hàm free() của C, nó có cú pháp như sau :

delete pointer

delete [] pointer

Chúng ta có thể vừa cấp phát vừa khởi động như sau :

int *P;

P = new int(100);

if (P!=NULL)

{

cout<<*P<<"\n";

delete P;

}

else

cout<<"Khong con du bo nho de cap phat\n";

Để cấp phát một mảng, chúng ta làm như sau :

int *P;

P = new int[10]; //Cấp phát mảng 10 số nguyên

if (P!=NULL)

{

for(int I = 0;I<10;++)

P[I]= I;

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

cout<<P[I]<<"\n";

delete []P;

}else cout<<"Khong con du bo nho de cap phat\n";

Chú ý: Đối với việc cấp phát mảng chúng ta không thể vừa cấp phát vừa khởi động giá trị cho chúng, chẳng hạn đoạn chương trình sau là sai :

int *P;

P = new (int[10])(3); //Sai !!!

Ví dụ 2.6: Chương trình tạo một mảng động, khởi động mảng này với các giá trị ngẫu nhiên và sắp xếp chúng.

1: #include <iostream.h>

2: #include <time.h>

3: #include <stdlib.h>

4: int main()

5: {

6:    int N;

7:    cout<<"Nhap vao so phan tu cua mang:";

8:    cin>>N;

9:    int *P=new int[N];

10:    if (P==NULL)

11:    {

12:       cout<<"Khong con bo nho de cap phat\n";

13:       return 1;

14:    }

15:    srand((unsigned)time(NULL));

16:    for(int I=0;I<N;++I)

17:      P[I]=rand()%100; //Tạo các số ngẫu nhiên từ 0 đến 99

18:    cout<<"Mang truoc khi sap xep\n";

19:    for(I=0;I<N;++I)

20:       cout<<P[I]<<" ";

21:    for(I=0;I<N-1;++I)

22:       for(int J=I+1;J<N;++J)

23:          if (P[I]>P[J])

24:          {

25:             int Temp=P[I];

26:             P[I]=P[J];

27:             P[J]=Temp;

28:          }

29:    cout<<"\nMang sau khi sap xep\n";

30:    for(I=0;I<N;++I)

31:       cout<<P[I]<<" ";

32:    delete []P;

33:    return 0;

34:  }

Chúng ta chạy ví dụ 2.6, kết quả ở hình 2.7

Hình 7
Hình 7 (graphics7.png)

Hình 2.7: Kết quả của ví dụ 2.6

Ví dụ 2.7: Chương trình cộng hai ma trận trong đó mỗi ma trận được cấp phát động.

Chúng ta có thể xem mảng hai chiều như mảng một chiều như hình 2.8

Hình 8
Hình 8 (graphics8.png)

Hình 2.8: Mảng hai chiều có thể xem như mảng một chiều.

Gọi X là mảng hai chiều có kích thước m dòng và n cột.

A là mảng một chiều tương ứng.

Nếu X[i][j] chính là A[k] thì k = i*n + j

Chúng ta có chương trình như sau :

1: #include <iostream.h>

2: #include <conio.h>

3: //prototype

4: void AddMatrix(int * A,int *B,int*C,int M,int N);

5: int AllocMatrix(int **A,int M,int N);

6: void FreeMatrix(int *A);

7: void InputMatrix(int *A,int M,int N,char Symbol);

8: void DisplayMatrix(int *A,int M,int N);

9:

10: int main()

11: {

12:     int M,N;

13:     int *A = NULL,*B = NULL,*C = NULL;

14:

15:     clrscr();

16:     cout<<"Nhap so dong cua ma tran:";

17:     cin>>M;

18:     cout<<"Nhap so cot cua ma tran:";

19:     cin>>N;

20:     //Cấp phát vùng nhớ cho ma trận A

21:     if (!AllocMatrix(&A,M,N))

22:     { //endl: Xuất ra kí tự xuống dòng (‘\n’)

23:         cout<<"Khong con du bo nho!"<<endl;

24:         return 1;

25:     }

26:     //Cấp phát vùng nhớ cho ma trận B

27:     if (!AllocMatrix(&B,M,N))

28:     {

29:         cout<<"Khong con du bo nho!"<<endl;

30:         FreeMatrix(A);//Giải phóng vùng nhớ A

31:         return 1;

32:     }

33:     //Cấp phát vùng nhớ cho ma trận C

34:     if (!AllocMatrix(&C,M,N))

35:     {

36:         cout<<"Khong con du bo nho!"<<endl;

37:         FreeMatrix(A);//Giải phóng vùng nhớ A

38:         FreeMatrix(B);//Giải phóng vùng nhớ B

39:         return 1;

40:     }

41:     cout<<"Nhap ma tran thu 1"<<endl;

42:     InputMatrix(A,M,N,'A');

43:     cout<<"Nhap ma tran thu 2"<<endl;

44:     InputMatrix(B,M,N,'B');

45:     clrscr();

46:     cout<<"Ma tran thu 1"<<endl;

47:     DisplayMatrix(A,M,N);

48:     cout<<"Ma tran thu 2"<<endl;

49:     DisplayMatrix(B,M,N);

50:     AddMatrix(A,B,C,M,N);

51:     cout<<"Tong hai ma tran"<<endl;

52:     DisplayMatrix(C,M,N);

53:     FreeMatrix(A);//Giải phóng vùng nhớ A

54:     FreeMatrix(B);//Giải phóng vùng nhớ B

55:     FreeMatrix(C);//Giải phóng vùng nhớ C

56:     return 0;

57: }

68: //Cộng hai ma trận

69: void AddMatrix(int *A,int *B,int*C,int M,int N)

70: {

71:     for(int I=0;I<M*N;++I)

72:     C[I] = A[I] + B[I];

73: }

74: //Cấp phát vùng nhớ cho ma trận

75: int AllocMatrix(int **A,int M,int N)

76: {

77:     *A = new int [M*N];

78:     if (*A == NULL)

79:         return 0;

80:     return 1;

81: }

82: //Giải phóng vùng nhớ

83: void FreeMatrix(int *A)

84: {

85:     if (A!=NULL)

86:         delete [] A;

87: }

88: //Nhập các giá trị của ma trận

89: void InputMatrix(int *A,int M,int N,char Symbol)

90: {

91:     for(int I=0;I<M;++I)

92:     for(int J=0;J<N;++J)

93     {

94:         cout<<Symbol<<"["<<I<<"]["<<J<<"]=";

95:         cin>>A[I*N+J];

96:     }

97: }

100: //Hiển thị ma trận

101: void DisplayMatrix(int *A,int M,int N)

102: {

103:    for(int I=0;I<M;++I)

104:    {

105:      for(int J=0;J<N;++J)

106:     {

107:      out.width(7);//canh le phai voi chieu dai 7 ky tu

108:      cout<<A[I*N+J];

109:     }

110:    cout<<endl;

111:    }

112: }

Chúng ta chạy ví du 2.7 , kết quả ở hình 2.9

Hình 9
Hình 9 (graphics9.png)

Hình 2.9: Kết quả của ví dụ 2.7

Một cách khác để cấp phát mảng hai chiều A gồm M dòng và N cột như sau:

int ** A = new int *[M];

int * Tmp = new int[M*N];

for(int I=0;I<M;++I)

{

A[I]=Tmp;

Tmp+=N;

}

//Thao tác trên mảng hai chiều A

…………………..

delete [] *A;

delete [] A;

Toán tử new còn có một thuận lợi khác, đó là tất cả các lỗi cấp phát động đều có thể bắt được bằng một hàm xử lý lỗi do người dùng tự định nghĩa. C++ có định nghĩa một con trỏ (pointer) trỏ đến hàm đặc biệt. Khi toán tử new được sử dụng để cấp phát động và một lỗi xảy ra do cấp phát, C++ tự gọi đến hàm được chỉ bởi con trỏ này. Định nghĩa của con trỏ này như sau:

typedef void (*pvf)();

pvf _new_handler(pvf p);

Điều này có nghĩa là con trỏ _new_handler là con trỏ trỏ đến hàm không có tham số và không trả về giá trị. Sau khi chúng ta định nghĩa hàm như vậy và gán địa chỉ của nó cho _new_handler chúng ta có thể bắt được tất cả các lỗi do cấp phát động.

Ví dụ 2.8:

1: #include <iostream.h>

2: #include <stdlib.h>

3: #include <new.h>

4:

5: void MyHandler();

6:

7: unsigned long I = 0; 9;

8: void main()

9: {

10:    int *A;

11:    _new_handler = MyHandler;

12:    for( ; ; ++I)

13:       A = new int;

14:

15: }

16:

17: void MyHandler()

18: {

19:    cout<<"Lan cap phat thu "<<I<<endl;

20:    cout<<"Khong con du bo nho!"<<endl;

21:    exit(1);

22: }

Sử dụng con trỏ _new_handler chúng ta phải include file new.h như ở dòng 3. Chúng ta chạy ví dụ 2.8, kết quả ở hình 2.10.

Hình 10
Hình 10 (graphics10.png)

Hình 2.10: Kết quả của ví dụ 2.8

Thư viện cũng còn có một hàm được định nghĩa trong new.h là hàm có prototype sau :

void ( * set_new_handler(void (* my_handler)() ))();

Hàm set_new_handler() dùng để gán một hàm cho _new_handler.

Ví dụ 2.9:

1: #include <iostream.h>

2: #include <new.h>

3: #include <stdlib.h>

4:

5: void MyHandler();

6:

7: int main(void)

8: {

9:

10:    char *Ptr;

11:

12:    set_new_handler(MyHandler);

13:    Ptr = new char[64000u];

14:    set_new_handler(0); //Thiết lập lại giá trị mặc định

15:    return 0;

16: }

17:

18: void MyHandler()

19: {

20:    cout <<endl<<"Khong con du bo nho";

21:    exit(1);

22 }

Chúng ta chạy ví dụ 2.9, kết quả ở hình 2.11

Hình 11
Hình 11 (graphics11.png)

Hình 2.11: Kết quả của ví dụ 2.9

Hàm inline

Một chương trình có cấu trúc tốt sử dụng các hàm để chia chương trình thành các đơn vị độc lập có logic riêng. Tuy nhiên, các hàm thường phải chứa một loạt các xử lý điểm vào (entry point): tham số phải được đẩy vào stack, một lệnh gọi phải được thực hiện và sau đó việc quay trở về cũng phải được thực hiện bằng cách giải phóng các tham số ra khỏi stack. Khi các xử lý điểm vào chậm chạp thường các lập trình viên C phải sử dụng cách chép lập lại các đoạn chương trình nếu muốn tăng hiệu quả.

Để tránh khỏi phải xử lý điểm vào, C++ trang bị thêm từ khóa inline để loại việc gọi hàm. Khi đó trình biên dịch sẽ không biên dịch hàm này như một đoạn chương trình riêng biệt mà nó sẽ được chèn thẳng vào các chỗ mà hàm này được gọi. Điều này làm giảm việc xử lý điểm vào mà vẫn cho phép một chương trình được tổ chức dưới dạng có cấu trúc. Cú pháp của hàm inline như sau :

inline data_type function_name ( parameters )

{

……………………………..

}

Trong đó:data_type: Kiểu trả về của hàm.

Function_name:Tên của hàm.

Parameters: Các tham số của hàm.

Ví dụ 2.10: Tính thể tích của hình lập phương

1: #include <iostream.h>

2: inline float Cube(float S)

3: {

4: return S*S*S;

5: }

6:

7: int main()

8: {

9:     cout<<"Nhap vao chieu dai canh cua hinh lap phuong:";

10:    float Side;

11:    cin>>Side;

12:    cout<<"The tich cua hinh lap phuong = "<<Cube(Side);

13:    return 0;

14: }

Chúng ta chạy ví dụ 2.10, kết quả ở hình 2.12

Hình 12
Hình 12 (graphics12.png)

Hình 2.12: Kết quả của ví dụ 2.10

Hình 13
Hình 13 (graphics13.png)
Chú ý:

Hình 14
Hình 14 (graphics14.png)
Sử dụng hàm inline sẽ làm cho chương trình lớn lên vì trình biên dịch chèn đoạn chương trình vào các chỗ mà hàm này được gọi. Do đó thường các hàm inline thường là các hàm nhỏ, ít phức tạp.

Hình 15
Hình 15 (graphics15.png)
Các hàm inline phải được định nghĩa trước khi sử dụng. Ở ví dụ 2.10 chúng ta sửa lại như sau thì chương trình sẽ bị báo lỗi:

#include <iostream.h>

float Cube(float S);

int main()

{

    cout<<"Nhap vao chieu dai canh cua hinh lap phuong:";

    float Side;

    cin>>Side;

    cout<<"The tich cua hinh lap phuong = "<<Cube(Side);

    return 0;

}

inline float Cube(float S)

{

   return S*S*S;

}

graphics16.pngCác hàm đệ quy không được là hàm inline.

Các giá trị tham số mặc định

Một trong các đặc tính nổi bật nhất của C++ là khả năng định nghĩa các giá trị tham số mặc định cho các hàm. Bình thường khi gọi một hàm, chúng ta cần gởi một giá trị cho mỗi tham số đã được định nghĩa trong hàm đó, chẳng hạn chúng ta có đoạn chương trình sau:

void MyDelay(long Loops); //prototype

………………………………..

void MyDelay(long Loops)

{

for(int I = 0; I < Loops; ++I)

;

}

Mỗi khi hàm MyDelay() được gọi chúng ta phải gởi cho nó một giá trị cho tham số Loops. Tuy nhiên, trong nhiều trường hợp chúng ta có thể nhận thấy rằng chúng ta luôn luôn gọi hàm MyDelay() với cùng một giá trị Loops nào đó. Muốn vậy chúng ta sẽ dùng giá trị mặc định cho tham số Loops, giả sử chúng ta muốn giá trị mặc định cho tham số Loops là 1000. Khi đó đoạn mã trên được viết lại như sau :

void MyDelay(long Loops = 1000); //prototype

………………………………..

void MyDelay(long Loops)

{

for(int I = 0; I < Loops; ++I)

;

}

Mỗi khi gọi hàm MyDelay() mà không gởi một tham số tương ứng thì trình biên dịch sẽ tự động gán cho tham số Loops giá trị 1000.

MyDelay(); // Loops có giá trị là 1000

MyDelay(5000); // Loops có giá trị là 5000

Giá trị mặc định cho tham số có thể là một hằng, một hàm, một biến hay một biểu thức.

Ví dụ 2.11: Tính thể tích của hình hộp

1: #include <iostream.h>

2: int BoxVolume(int Length = 1, int Width = 1, int Height = 1);

3:

4: int main()

5: {

6:   cout << "The tich hinh hop mac dinh: "

7:   << BoxVolume() << endl << endl

8:   << "The tich hinh hop voi chieu dai=10,do rong=1,chieu cao=1:"

9:   << BoxVolume(10) << endl << endl

10:  << "The tich hinh hop voi chieu dai=10,do rong=5,chieu cao=1:"

11:  << BoxVolume(10, 5) << endl << endl

12:  << "The tich hinh hop voi chieu dai=10,do rong=5,chieu cao=2:"

13:  << BoxVolume(10, 5, 2)<< endl;

14:    return 0;

15: }

16: //Tính thể tích của hình hộp

17: int BoxVolume(int Length, int Width, int Height)

18: {

19:    return Length * Width * Height;

20: }

Chúng ta chạy ví dụ 2.11, kết quả ở hình 2.13

Hình 16
Hình 16 (graphics17.png)

Hình 2.13: Kết quả của ví dụ 2.11

Hình 17
Hình 17 (graphics18.png)
Chú ý:

Hình 18
Hình 18 (graphics19.png)
Các tham số có giá trị mặc định chỉ được cho trong prototype của hàm và không được lặp lại trong định nghĩa hàm (Vì trình biên dịch sẽ dùng các thông tin trong prototype chứ không phải trong định nghĩa hàm để tạo một lệnh gọi).

Hình 19
Hình 19 (graphics20.png)
Một hàm có thể có nhiều tham số có giá trị mặc định. Các tham số có giá trị mặc định cần phải được nhóm lại vào các tham số cuối cùng (hoặc duy nhất) của một hàm. Khi gọi hàm có nhiều tham số có giá trị mặc định, chúng ta chỉ có thể bỏ bớt các tham số theo thứ tự từ phải sang trái và phải bỏ liên tiếp nhau, chẳng hạn chúng ta có đoạn chương trình như sau:

int MyFunc(int a= 1, int b , int c = 3, int d = 4); //prototype sai!!!

int MyFunc(int a, int b = 2 , int c = 3, int d = 4); //prototype đúng

………………………..

MyFunc(); // Lỗi do tham số a không có giá trị mặc định

MyFunc(1);// OK, các tham số b, c và d lấy giá trị mặc định

MyFunc(5, 7); // OK, các tham số c và d lấy giá trị mặc định

MyFunc(5, 7, , 8); // Lỗi do các tham số bị bỏ phải liên tiếp nhau

Phép tham chiếu

Trong C, hàm nhận tham số là con trỏ đòi hỏi chúng ta phải thận trọng khi gọi hàm. Chúng ta cần viết hàm hoán đổi giá trị giữa hai số như sau:

void Swap(int *X, int *Y);

{

int Temp = *X;

*X = *Y;

*Y = *Temp;

}

Để hoán đổi giá trị hai biến A và B thì chúng ta gọi hàm như sau:

Swap(&A, &B);

Rõ ràng cách viết này không được thuận tiện lắm. Trong trường hợp này, C++ đưa ra một kiểu biến rất đặc biệt gọi là biến tham chiếu (reference variable). Một biến tham chiếu giống như là một bí danh của biến khác. Biến tham chiếu sẽ làm cho các hàm có thay đổi nội dung các tham số của nó được viết một cách thanh thoát hơn. Khi đó hàm Swap() được viết như sau:

void Swap(int &X, int &Y);

{

int Temp = X;

X = Y;

Y = Temp ;

}

Chúng ta gọi hàm như sau :

Swap(A, B);

Với cách gọi hàm này, C++ tự gởi địa chỉ của A và B làm tham số cho hàm Swap(). Cách dùng biến tham chiếu cho tham số của C++ tương tự như các tham số được khai báo là Var trong ngôn ngữ Pascal. Tham số này được gọi là tham số kiểu tham chiếu (reference parameter). Như vậy biến tham chiếu có cú pháp như sau :

data_type & variable_name;

Trong đó:

data_type: Kiểu dữ liệu của biến.

variable_name: Tên của biến

Khi dùng biến tham chiếu cho tham số chỉ có địa chỉ của nó được gởi đi chứ không phải là toàn bộ cấu trúc hay đối tượng đó như hình 2.14, điều này rất hữu dụng khi chúng ta gởi cấu trúc và đối tượng lớn cho một hàm.

Hình 20
Hình 20 (graphics21.png)

Hình 2.14: Một tham số kiểu tham chiếu nhận một tham chiếu tới một biến được chuyển cho tham số của hàm.

Ví dụ 2.12: Chương trình hoán đổi giá trị của hai biến.

#include <iostream.h>

//prototype

void Swap(int &X,int &Y);

int main()

{

   int X = 10, Y = 5;

   cout<<"Truoc khi hoan doi: X = "<<X<<",Y = "<<Y<<endl;

   Swap(X,Y);

 cout<<"Sau khi hoan doi: X = "<<X<<",Y = "<<Y<<endl;

   return 0;

}

void Swap(int &X,int &Y)

{

    int Temp=X;

    X=Y;

    Y=Temp;

}

Chúng ta chạy ví dụ 2.12, kết quả ở hình 2.15

Hình 21
Hình 21 (graphics22.png)

Hình 2.15: Kết quả của ví dụ 2.12

Đôi khi chúng ta muốn gởi một tham số nào đó bằng biến tham chiếu cho hiệu quả, mặc dù chúng ta không muốn giá trị của nó bị thay đổi thì chúng ta dùng thêm từ khóa const như sau :

int MyFunc(const int & X);

Hàm MyFunc() sẽ chấp nhận một tham số X gởi bằng tham chiếu nhưng const xác định rằng X không thể bị thay đổi.

Biến tham chiếu có thể sử dụng như một bí danh của biến khác (bí danh đơn giản như một tên khác của biến gốc), chẳng hạn như đoạn mã sau :

int Count = 1;

int & Ref = Count;  //Tạo biến Ref như là một bí danh của biến Count

++Ref; //Tăng biến Count lên 1 (sử dụng bí danh của biến Count)

Các biến tham chiếu phải được khởi động trong phần khai báo của chúng và chúng ta không thể gán lại một bí danh của biến khác cho chúng. Chẳng hạn đoạn mã sau là sai:

int X = 1;

int & Y; //Lỗi: Y phải được khởi động.

Khi một tham chiếu được khai báo như một bí danh của biến khác, mọi thao tác thực hiện trên bí danh chính là thực hiện trên biến gốc của nó. Chúng ta có thể lấy địa chỉ của biến tham chiếu và có thể so sánh các biến tham chiếu với nhau (phải tương thích về kiểu tham chiếu).

Ví dụ 2.13: Mọi thao tác trên trên bí danh chính là thao tác trên biến gốc của nó.

#include <iostream.h>

int main()

{

   int X = 3;

   int &Y = X; //Y la bí danh của X

   int Z = 100;

   cout<<"X="<<X<<endl<<"Y="<<Y<<endl;

   Y *= 3;

   cout<<"X="<<X<<endl<<"Y="<<Y<<endl;

   Y = Z;

   cout<<"X="<<X<<endl<<"Y="<<Y<<endl;

   return 0;

}

Chúng ta chạy ví dụ 2.13, kết quả ở hình 2.16

Hình 22
Hình 22 (graphics23.png)

Hình 2.16: Kết quả của ví dụ 2.13

Ví dụ 2.14: Lấy địa chỉ của biến tham chiếu

#include <iostream.h>

int main()

{

   int X = 3;

   int &Y = X; //Y la bí danh của X

   cout<<"Dia chi cua X = "<<&X<<endl;

   cout<<"Dia chi cua bi danh Y= "<<&Y<<endl;

   return 0;

}

Chúng ta chạy ví dụ 2.14, kết quả ở hình 2.17

Hình 23
Hình 23 (graphics24.png)

Hình 2.17: Kết quả của ví dụ 2.14

Chúng ta có thể tạo ra biến tham chiếu với việc khởi động là một hằng, chẳng hạn như đoạn mã sau :

int & Ref = 45;

Trong trường hợp này, trình biên dịch tạo ra một biến tạm thời chứa trị hằng và biến tham chiếu chính là bí danh của biến tạm thời này. Điều này gọi là tham chiếu độc lập (independent reference).

Các hàm có thể trả về một tham chiếu, nhưng điều này rất nguy hiểm. Khi hàm trả về một tham chiếu tới một biến cục bộ của hàm thì biến này phải được khai báo là static, ngược lại tham chiếu tới nó thì khi hàm kết thúc biến cục bộ này sẽ bị bỏ qua. Chẳng hạn như đoạn chương trình sau:

int & MyFunc()

{

static int X = 200; //Nếu không khai báo là static thì điều này rất nguy hiểm.

return X;

}

Khi một hàm trả về một tham chiếu, chúng ta có thể gọi hàm ở phía bên trái của một phép gán.

Ví dụ 2.15:

1: #include <iostream.h>

2:

3: int X = 4;

4: //prototype

5: int & MyFunc();

6:

7: int main()

8: {

9:     cout<<"X="<<X<<endl;

10:    cout<<"X="<<MyFunc()<<endl;

11:    MyFunc() = 20; //Nghĩa là X = 20

12:    cout<<"X="<<X<<endl;

13:    return 0;

14: }

15:

16: int & MyFunc()

17: {

18:    return X;

19: }

Chúng ta chạy ví dụ 2.15, kết quả ở hình 2.18

Hình 24
Hình 24 (graphics25.png)

Hình 2.18: Kết quả của ví dụ 2.15

Hình 25
Hình 25 (graphics26.png)
Chú ý:

Hình 26
Hình 26 (graphics27.png)
Mặc dù biến tham chiếu trông giống như là biến con trỏ nhưng chúng không thể là biến con trỏ do đó chúng không thể được dùng cấp phát động.

Hình 27
Hình 27 (graphics28.png)
Chúng ta không thể khai báo một biến tham chiếu chỉ đến biến tham chiếu hoặc biến con trỏ chỉ đến biến tham chiếu. Tuy nhiên chúng ta có thể khai báo một biến tham chiếu về biến con trỏ như đoạn mã sau:

int X;

int *P = &X;

int * & Ref = P;

Phép đa năng hóa (Overloading)

Với ngôn ngữ C++, chúng ta có thể đa năng hóa các hàm và các toán tử (operator). Đa năng hóa là phương pháp cung cấp nhiều hơn một định nghĩa cho tên hàm đã cho trong cùng một phạm vi. Trình biên dịch sẽ lựa chọn phiên bản thích hợp của hàm hay toán tử dựa trên các tham số mà nó được gọi.

Đa năng hóa các hàm (Functions overloading)

Trong ngôn ngữ C cũng như mọi ngôn ngữ máy tính khác, mỗi hàm đều phải có một tên phân biệt. Đôi khi đây là một điều phiền toái. Chẳng hạn như trong ngôn ngữ C, có rất nhiều hàm trả về trị tuyệt đối của một tham số là số, vì cần thiết phải có tên phân biệt nên C phải có hàm riêng cho mỗi kiểu dữ liệu số, do vậy chúng ta có tới ba hàm khác nhau để trả về trị tuyệt đối của một tham số:

int abs(int i);

long labs(long l);

double fabs(double d);

Tất cả các hàm này đều cùng thực hiện một chứa năng nên chúng ta thấy điều này nghịch lý khi phải có ba tên khác nhau. C++ giải quyết điều này bằng cách cho phép chúng ta tạo ra các hàm khác nhau có cùng một tên. Đây chính là đa năng hóa hàm. Do đó trong C++ chúng ta có thể định nghĩa lại các hàm trả về trị tuyệt đối để thay thế các hàm trên như sau :

int abs(int i);

long abs(long l);

double abs(double d);

Ví dụ 2.16:

1: #include <iostream.h>

2: #include <math.h>

3:

4: int MyAbs(int X);

5: long MyAbs(long X);

6: double MyAbs(double X);

7:

8: int main()

9: {

10:    int X = -7;

11:    long Y = 200000l;

12:    double Z = -35.678;

13:    cout<<"Tri tuyet doi cua so nguyen (int) "<<X<<" la "

14:        <<MyAbs(X)<<endl;

15:    cout<<"Tri tuyet doi cua so nguyen (long int) "<<Y<<" la "

16:        <<MyAbs(Y)<<endl;

17:    cout<<"Tri tuyet doi cua so thuc "<<Z<<" la "

18:        <<MyAbs(Z)<<endl;

19:    return 0;

20: }

21:

22: int MyAbs(int X)

23: {

24:    return abs(X);

25: }

26:

27: long MyAbs(long X)

28: {

29:    return labs(X);

30: }

31:

32: double MyAbs(double X)

33: {

34:    return fabs(X);

35: }

Chúng ta chạy ví dụ 2.16 , kết quả ở hình 2.19

Hình 28
Hình 28 (graphics29.png)

Hình 2.19: Kết quả của ví dụ 2.16

Trình biên dịch dựa vào sự khác nhau về số các tham số, kiểu của các tham số để có thể xác định chính xác phiên bản cài đặt nào của hàm MyAbs() thích hợp với một lệnh gọi hàm được cho, chẳng hạn như:

MyAbs(-7); //Gọi hàm int MyAbs(int)

MyAbs(-7l); //Gọi hàm long MyAbs(long)

MyAbs(-7.5); //Gọi hàm double MyAbs(double)

Quá trình tìm được hàm được đa năng hóa cũng là quá trình được dùng để giải quyết các trường hợp nhập nhằng của C++. Chẳng hạn như nếu tìm thấy một phiên bản định nghĩa nào đó của một hàm được đa năng hóa mà có kiểu dữ liệu các tham số của nó trùng với kiểu các tham số đã gởi tới trong lệnh gọi hàm thì phiên bản hàm đó sẽ được gọi. Nếu không trình biên dịch C++ sẽ gọi đến phiên bản nào cho phép chuyển kiểu dễ dàng nhất.

MyAbs(‘c’); //Gọi int MyAbs(int)

MyAbs(2.34f); //Gọi double MyAbs(double)

Các phép chuyển kiểu có sẵn sẽ được ưu tiên hơn các phép chuyển kiểu mà chúng ta tạo ra (chúng ta sẽ xem xét các phép chuyển kiểu tự tạo ở chương 3).

Chúng ta cũng có thể lấy địa chỉ của một hàm đã được đa năng hóa sao cho bằng một cách nào đó chúng ta có thể làm cho trình biên dịch C++ biết được chúng ta cần lấy địa chỉ của phiên bản hàm nào có trong định nghĩa. Chẳng hạn như:

int (*pf1)(int);

long (*pf2)(long);

int (*pf3)(double);

pf1 = MyAbs; //Trỏ đến hàm int MyAbs(int)

pf2 = MyAbs; //Trỏ đến hàm long MyAbs(long)

pf3 = MyAbs; //Lỗi!!! (không có phiên bản hàm nào để đối sánh)

Hình 29
Hình 29 (graphics30.png)
Các giới hạn của việc đa năng hóa các hàm:

Bất kỳ hai hàm nào trong tập các hàm đã đa năng phải có các tham số khác nhau.

Các hàm đa năng hóa với danh sách các tham số cùng kiểu chỉ dựa trên kiểu trả về của hàm thì trình biên dịch báo lỗi. Chẳng hạn như, các khai báo sau là không hợp lệ:

void Print(int X);

int Print(int X);

Không có cách nào để trình biên dịch nhận biết phiên bản nào được gọi nếu giá trị trả về bị bỏ qua. Như vậy các phiên bản trong việc đa năng hóa phải có sự khác nhau ít nhất về kiểu hoặc số tham số mà chúng nhận được.

Các khai báo bằng lệnh typedef không định nghĩa kiểu mới. Chúng chỉ thay đổi tên gọi của kiểu đã có. Chúng không ảnh hưởng tới cơ chế đa năng hóa hàm. Chúng ta hãy xem xét đoạn mã sau:

typedef char * PSTR;

void Print(char * Mess);

void Print(PSTR Mess);

Hai hàm này có cùng danh sách các tham số, do đó đoạn mã trên sẽ phát sinh lỗi.

Đối với kiểu mảng và con trỏ được xem như đồng nhất đối với sự phân biệt khác nhau giữa các phiên bản hàm trong việc đa năng hóa hàm. Chẳng hạn như đoạn mã sau se phát sinh lỗi:

void Print(char * Mess);

void Print(char Mess[]);

Tuy nhiên, đối với mảng nhiều chiều thì có sự phân biệt giữa các phiên bản hàm trong việc đa năng hóa hàm, chẳng hạn như đoạn mã sau hợp lệ:

void Print(char Mess[]);

void Print(char Mess[][7]);

void Print(char Mess[][9][42]);

const và các con trỏ (hay các tham chiếu) có thể dùng để phân biệt, chẳng hạn như đoạn mã sau hợp lệ:

void Print(char *Mess);

void Print(const char *Mess);

Đa năng hóa các toán tử (Operators overloading) :

Trong ngôn ngữ C, khi chúng ta tự tạo ra một kiểu dữ liệu mới, chúng ta thực hiện các thao tác liên quan đến kiểu dữ liệu đó thường thông qua các hàm, điều này trở nên không thoải mái.

Ví dụ 2.17: Chương trình cài đặt các phép toán cộng và trừ số phức

1: #include <stdio.h>

2: /* Định nghĩa số phức */

3: typedef struct

4: {

5:     double Real;

6:     double Imaginary;

7: }Complex;

8:

9:  Complex SetComplex(double R,double I);

10: Complex AddComplex(Complex C1,Complex C2);

11: Complex SubComplex(Complex C1,Complex C2);

12: void DisplayComplex(Complex C);

13:

14: int main(void)

15: {

16:    Complex C1,C2,C3,C4;

17:

18:    C1 = SetComplex(1.0,2.0);

19:    C2 = SetComplex(-3.0,4.0);

20:    printf("\nSo phuc thu nhat:");

21:    DisplayComplex(C1);

22:    printf("\nSo phuc thu hai:");

23:    DisplayComplex(C2);

24:    C3 = AddComplex(C1,C2); //Hơi bất tiện !!!

25:    C4 = SubComplex(C1,C2);

26:    printf("\nTong hai so phuc nay:");

27:    DisplayComplex(C3);

28:    printf("\nHieu hai so phuc nay:");

29:    DisplayComplex(C4);

30:    return 0;

31: }

32:

33: /* Đặt giá trị cho một số phức */

34: Complex SetComplex(double R,double I)

35: {

36:    Complex Tmp;

37:

38:    Tmp.Real = R;

39:    Tmp.Imaginary = I;

40:    return Tmp;

41: }

42: /* Cộng hai số phức */

43: Complex AddComplex(Complex C1,Complex C2)

44: {

45:    Complex Tmp;

46:

47:    Tmp.Real = C1.Real+C2.Real;

48:    Tmp.Imaginary = C1.Imaginary+C2.Imaginary;

49:    return Tmp;

50: }

51:

52: /* Trừ hai số phức */

53: Complex SubComplex(Complex C1,Complex C2)

54: {

55:    Complex Tmp;

56:

57:    Tmp.Real = C1.Real-C2.Real;

58:    Tmp.Imaginary = C1.Imaginary-C2.Imaginary;

59:    return Tmp;

60: }

61:

62: /* Hiển thị số phức */

63: void DisplayComplex(Complex C)

64: {

65:    printf("(%.1lf,%.1lf)",C.Real,C.Imaginary);

66: }

Chúng ta chạy ví dụ 2.17, kết quả ở hình 2.20

Hình 30
Hình 30 (graphics31.png)

Hình 2.20: Kết quả của ví dụ 2.17

Trong chương trình ở ví dụ 2.17, chúng ta nhận thấy với các hàm vừa cài đặt dùng để cộng và trừ hai số phức 1+2i và –3+4i; người lập trình hoàn toàn không thoải mái khi sử dụng bởi vì thực chất thao tác cộng và trừ là các toán tử chứ không phải là hàm. Để khắc phục yếu điểm này, trong C++ cho phép chúng ta có thể định nghĩa lại chức năng của các toán tử đã có sẵn một cách tiện lợi và tự nhiên hơn rất nhiều. Điều này gọi là đa năng hóa toán tử. Khi đó chương trình ở ví dụ 2.17 được viết như sau:

Ví dụ 2.18:

1: #include <iostream.h>

2: // Định nghĩa số phức

3: typedef struct

4: {

5:     double Real;

6:     double Imaginary;

7: }Complex;

8:

9:  Complex SetComplex(double R,double I);

10: void DisplayComplex(Complex C);

11: Complex operator + (Complex C1,Complex C2);

12: Complex operator - (Complex C1,Complex C2);

13:

14: int main(void)

15: {

16:    Complex C1,C2,C3,C4;

17:

18:    C1 = SetComplex(1.0,2.0);

19:    C2 = SetComplex(-3.0,4.0);

20:    cout<<"\nSo phuc thu nhat:";

21:    DisplayComplex(C1);

22:    cout<<"\nSo phuc thu hai:";

23:    DisplayComplex(C2);

24:    C3 = C1 + C2;

25:    C4 = C1 - C2;

26:    cout<<"\nTong hai so phuc nay:";

27:    DisplayComplex(C3);

28:    cout<<"\nHieu hai so phuc nay:";

29:    DisplayComplex(C4);

30:    return 0;

31: }

32:

33: //Đặt giá trị cho một số phức

34: Complex SetComplex(double R,double I)

35: {

36:    Complex Tmp;

37:

38:    Tmp.Real = R;

39:    Tmp.Imaginary = I;

40:    return Tmp;

41: }

42:

43: //Cộng hai số phức

44: Complex operator + (Complex C1,Complex C2)

45: {

46:    Complex Tmp;

47:

48:    Tmp.Real = C1.Real+C2.Real;

49:    Tmp.Imaginary = C1.Imaginary+C2.Imaginary;

50:    return Tmp;

51: }

52:

53: //Trừ hai số phức

54: Complex operator - (Complex C1,Complex C2)

55: {

56:    Complex Tmp;

57:

58:    Tmp.Real = C1.Real-C2.Real;

59:    Tmp.Imaginary = C1.Imaginary-C2.Imaginary;

60:    return Tmp;

61: }

62:

63: //Hiển thị số phức

64: void DisplayComplex(Complex C)

65: {

66:    cout<<"("<<C.Real<<","<<C.Imaginary<<")";

67: }

Chúng ta chạy ví dụ 2.18, kết quả ở hình 2.21

Hình 31
Hình 31 (graphics32.png)

Hình 2.21: Kết quả của ví dụ 2.18

Như vậy trong C++, các phép toán trên các giá trị kiểu số phức được thực hiện bằng các toán tử toán học chuẩn chứ không phải bằng các tên hàm như trong C. Chẳng hạn chúng ta có lệnh sau:

C4 = AddComplex(C3, SubComplex(C1,C2));

thì ở trong C++, chúng ta có lệnh tương ứng như sau:

C4 = C3 + C1 - C2;

Chúng ta nhận thấy rằng cả hai lệnh đều cho cùng kết quả nhưng lệnh của C++ thì dễ hiểu hơn. C++ làm được điều này bằng cách tạo ra các hàm định nghĩa cách thực hiện của một toán tử cho các kiểu dữ liệu tự định nghĩa. Một hàm định nghĩa một toán tử có cú pháp sau:

data_type operator operator_symbol ( parameters )

{

………………………………

}

Trong đó: data_type: Kiểu trả về.

operator_symbol: Ký hiệu của toán tử.

parameters: Các tham số (nếu có).

Trong chương trình ví dụ 2.18, toán tử + là toán tử gồm hai toán hạng (gọi là toán tử hai ngôi; toán tử một ngôi là toán tử chỉ có một toán hạng) và trình biên dịch biết tham số đầu tiên là ở bên trái toán tử, còn tham số thứ hai thì ở bên phải của toán tử. Trong trường hợp lập trình viên quen thuộc với cách gọi hàm, C++ vẫn cho phép bằng cách viết như sau:

C3 = operator + (C1,C2);

C4 = operator - (C1,C2);

Các toán tử được đa năng hóa sẽ được lựa chọn bởi trình biên dịch cũng theo cách thức tương tự như việc chọn lựa giữa các hàm được đa năng hóa là khi gặp một toán tử làm việc trên các kiểu không phải là kiểu có sẵn, trình biên dịch sẽ tìm một hàm định nghĩa của toán tử nào đó có các tham số đối sánh với các toán hạng để dùng. Chúng ta sẽ tìm hiểu kỹ về việc đa năng hóa các toán tử trong chương 4.

Các giới hạn của đa năng hóa toán tử:

Chúng ta không thể định nghĩa các toán tử mới.

Hầu hết các toán tử của C++ đều có thể được đa năng hóa. Các toán tử sau không được đa năng hóa là :

Bảng 4
Toán tử Ý nghĩa
:: Toán tử định phạm vi.
.* Truy cập đến con trỏ là trường của struct hay thành viên của class.
. Truy cập đến trường của struct hay thành viên của class.
?: Toán tử điều kiện
sizeof  

và chúng ta cũng không thể đa năng hóa bất kỳ ký hiệu tiền xử lý nào.

Chúng ta không thể thay đổi thứ tự ưu tiên của một toán tử hay không thể thay đổi số các toán hạng của nó.

Chúng ta không thể thay đổi ý nghĩa của các toán tử khi áp dụng cho các kiểu có sẵn.

Đa năng hóa các toán tử không thể có các tham số có giá trị mặc định.

Các toán tử có thể đa năng hoá:

Bảng 5
+ - * / % ^
! = < > += -=
^= &= |= << >> <<=
<= >= && || ++ --
() [] new delete & |
~ *= /= %= >>= ==
!= , -> ->*    

Các toán tử được phân loại như sau :

Các toán tử một ngôi : * & ~ ! ++ -- sizeof (data_type)

Các toán tử này được định nghĩa chỉ có một tham số và phải trả về một giá trị cùng kiểu với tham số của chúng. Đối với toán tử sizeof phải trả về một giá trị kiểu size_t (định nghĩa trong stddef.h)

Toán tử (data_type) được dùng để chuyển đổi kiểu, nó phải trả về một giá trị có kiểu là data_type.

Các toán tử hai ngôi: * / % + - >> << > <

>= <= == != & | ^ && ||

Các toán tử này được định nghĩa có hai tham số.

Các phép gán: = += -= *= /= %= >>= <<= ^= |=

Các toán tử gán được định nghĩa chỉ có một tham số. Không có giới hạn về kiểu của tham số và kiểu trả về của phép gán.

Toán tử lấy thành viên : ->

Toán tử lấy phần tử theo chỉ số: []

Toán tử gọi hàm: ()

BÀI TẬP

graphics33.pngBài 1: Hãy viết lại chương trình sau bằng cách sử dụng lại các dòng nhập/xuất trong C++.

/* Chương trình tìm mẫu chung nhỏ nhất */

#include <stdio.h>

int main()

{

int a,b,i,min;

printf("Nhap vao hai so:");

scanf("%d%d",&a,&b);

min=a>b?b:a;

for(i = 2;i<min;++i)

if (((a%i)==0)&&((b%i)==0)) break;

if(i==min) {

printf("Khong co mau chung nho nhat");

return 0;

}

printf("Mau chung nho nhat la %d\n",i);

return 0;

}

graphics34.pngBài 2: Viết chương trình nhập vào số nguyên dương h (2<h<23), sau đó in ra các tam giác có chiều cao là h như các hình sau:

Hình 32
Hình 32 (graphics35.png)
 
Hình 33
Hình 33 (graphics36.png)

graphics37.pngBài 3: Một tam giác vuông có thể có tất cả các cạnh là các số nguyên. Tập của ba số nguyên của các cạnh của một tam giác vuông được gọi là bộ ba Pitago. Đó là tổng bình phương của hai cạnh bằng bình phương của cạnh huyền, chẳng hạn bộ ba Pitago (3, 4, 5). Viết chương trình tìm tất cả các bộ ba Pitago như thế sao cho tất cả các cạnh không quá 500.

graphics38.pngBài 4: Viết chương trình in bảng của các số từ 1 đến 256 dưới dạng nhị phân, bát phân và thập lục phân tương ứng.

graphics39.pngBài 5: Viết chương trình nhập vào một số nguyên dương n. Kiểm tra xem số nguyên n có thuộc dãy Fibonacci không?

graphics40.pngBài 6: Viết chương trình nhân hai ma trân Amxn và Bnxp. Mỗi ma trận được cấp phát động và các giá trị của chúng phát sinh ngẫu nhiên (Với m, n và p nhập từ bàn phím).

graphics41.pngBài 7: Viết chương trình tạo một mảng một chiều động có kích thước là n (n nhập từ bàn phím). Các giá trị của mảng này được phát sinh ngẫu nhiên trên đoạn [a, b] với a và b đều nhập từ bàn phím. Hãy tìm số dương nhỏ nhất và số âm lớn nhất trong mảng; nếu không có số dương nhỏ nhất hoặc số âm lớn nhất thì xuất thông báo "không có số dương nhỏ nhất" hoặc "không có số âm lớn nhất".

graphics42.pngBài 8: Anh (chị) hãy viết một hàm tính bình phương của một số. Hàm sẽ trả về giá trị bình phương của tham số và có kiểu cùng kiểu với tham số.

graphics43.pngBài 9: Trong ngôn ngữ C, chúng ta có hàm chuyển đổi một chuỗi sang số, tùy thuộc vào dạng của chuỗi chúng ta có các hàm chuyển đổi sau :

Hình 34
Hình 34 (graphics44.png)
int atoi(const char *s);

Chuyển đổi một chuỗi s thành số nguyên kiểu int.

Hình 35
Hình 35 (graphics45.png)
long atol(const char *s);

Chuyển đổi một chuỗi s thành số nguyên kiểu long.

Hình 36
Hình 36 (graphics46.png)
double atof(const char *s);

Chuyển đổi một chuỗi s thành số thực kiểu double.

Anh (chị) hãy viết một hàm có tên là aton (ascii to number) để chuyển đổi chuỗi sang các dạng số tương ứng.

graphics47.pngBài 10: Anh chị hãy viết các hàm sau:

Hình 37
Hình 37 (graphics48.png)
Hàm ComputeCircle() để tính diện tích s và chu vi c của một đường tròn bán kính r. Hàm này có prototype như sau:

void ComputeCircle(float & s, float &c, float r = 1.0);

Hình 38
Hình 38 (graphics49.png)
Hàm ComputeRectangle() để tính diện tích s và chu vi p của một hình chữ nhật có chiều cao h và chiều rộng w. Hàm này có prototype như sau:

void ComputeRectangle(float & s, float &p, float h = 1.0, float w = 1.0);

Hình 39
Hình 39 (graphics50.png)
Hàm ComputeTriangle() để tính diện tích s và chu vi p của một tam giác có ba cạnh a,b và c. Hàm này có prototype như sau:

void ComputeTriangle(float & s, float &p, float a = 1.0, float b = 1.0, float c = 1.0);

Hình 40
Hình 40 (graphics51.png)
Hàm ComputeSphere() để tính thể tích v và diện tích bề mặt s của một hình cầu có bán kính r. Hàm này có prototype như sau:

void ComputeSphere(float & v, float &s, float r = 1.0);

Hình 41
Hình 41 (graphics52.png)
Hàm ComputeCylinder() để tính thể tích v và diện tích bề mặt s của một hình trụ có bán kính r và chiều cao h. Hàm này có prototype như sau:

void ComputeCylinder(float & v, float &s, float r = 1.0 , float h = 1.0);

graphics53.pngBài 11: Anh (chị) hãy viết thêm hai toán tử nhân và chia hai số phức ở ví dụ 2.18 của chương 2.

graphics54.pngBài 12: Một cấu trúc Date chứa ngày, tháng và năm như sau:

struct Date

{

int Day; //Có giá trị từ 1  31

int Month; //Có giá trị từ 1  12

int Year; //Biểu diễn bằng 4 chữ số.

};

Anh (chị) hãy viết các hàm định nghĩa các toán tử : + - > >= < <= == != trên cấu trúc Date này.

graphics55.pngBài 13: Một cấu trúc Point3D biểu diễn tọa độ của một điểm trong không gian ba chiều như sau:

struct Point3D

{

float X;

float Y;

float Z;

};

Anh (chị) hãy viết các hàm định nghĩa các toán tử : + - == != trên cấu trúc Point3D này.

graphics56.pngBài 14: Một cấu trúc Fraction dùng để chứa một phân số như sau:

struct Fraction

{

int Numerator; //Tử số

int Denominator; //Mẫu số

};

Anh (chị) hãy viết các hàm định nghĩa các toán tử :

+ - * / > >= < <= == !=

trên cấu trúc Fraction này.graphics57.png

Collection Navigation

Content actions

Download:

Collection as:

EPUB (?)

What is an EPUB file?

EPUB is an electronic book format that can be read on a variety of mobile devices.

Downloading to a reading device

For detailed instructions on how to download this content's EPUB to your specific device, click the "(?)" link.

| More downloads ...

Module as:

PDF | More downloads ...

Add:

Collection to:

My Favorites (?)

'My Favorites' is a special kind of lens which you can use to bookmark modules and collections. 'My Favorites' can only be seen by you, and collections saved in 'My Favorites' can remember the last module you were on. You need an account to use 'My Favorites'.

| A lens I own (?)

Definition of a lens

Lenses

A lens is a custom view of the content in the repository. You can think of it as a fancy kind of list that will let you see content through the eyes of organizations and people you trust.

What is in a lens?

Lens makers point to materials (modules and collections), creating a guide that includes their own comments and descriptive tags about the content.

Who can create a lens?

Any individual member, a community, or a respected organization.

What are tags? tag icon

Tags are descriptors added by lens makers to help label content, attaching a vocabulary that is meaningful in the context of the lens.

| External bookmarks

Module to:

My Favorites (?)

'My Favorites' is a special kind of lens which you can use to bookmark modules and collections. 'My Favorites' can only be seen by you, and collections saved in 'My Favorites' can remember the last module you were on. You need an account to use 'My Favorites'.

| A lens I own (?)

Definition of a lens

Lenses

A lens is a custom view of the content in the repository. You can think of it as a fancy kind of list that will let you see content through the eyes of organizations and people you trust.

What is in a lens?

Lens makers point to materials (modules and collections), creating a guide that includes their own comments and descriptive tags about the content.

Who can create a lens?

Any individual member, a community, or a respected organization.

What are tags? tag icon

Tags are descriptors added by lens makers to help label content, attaching a vocabulary that is meaningful in the context of the lens.

| External bookmarks