Tái định nghĩa hàm và toán tử trong C++ - Kỹ thuật Overloading cho lập trình viên

Người đăng

Ẩn danh

Thể loại

Giáo trình
160
0
0

Phí lưu trữ

45 Point

Tóm tắt

I. Tổng quan về giáo trình C và ngôn ngữ lập trình C

Giáo trình C là tài liệu học thuật quan trọng dành cho sinh viên công nghệ thông tin và những người muốn nắm vững ngôn ngữ lập trình C/C++. Ngôn ngữ C được phát triển từ đầu thập niên 1970 bởi Dennis Ritchie tại Bell Labs. Sau đó, Bjarne Stroustrup mở rộng thành C++ với các tính năng hướng đối tượng. Giáo trình C thường bắt đầu từ cú pháp cơ bản, cấu trúc dữ liệu, con trỏ, rồi tiến đến các khái niệm nâng cao như tái định nghĩa hàm và toán tử. Chương trình C++ kế thừa toàn bộ ưu điểm của C đồng thời bổ sung khả năng tái định nghĩa, kế thừa và đóng gói. Việc học qua giáo trình giúp người học hệ thống hóa kiến thức từ cơ bản đến nâng cao. Giáo trình thường đi kèm bài tập thực hành, giúp củng cố lý thuyết. Nền tảng vững chắc từ giáo trình C là bước đệm quan trọng để tiếp cận các ngôn ngữ lập trình hiện đại khác.

1.1. Lịch sử phát triển ngôn ngữ C và C

Ngôn ngữ C ra đời năm 1972 tại phòng thí nghiệm Bell Labs. Dennis Ritchie thiết kế C để phát triển hệ điều hành Unix. Năm 1979, Bjarne Stroustrup bắt đầu phát triển C++ như một phần mở rộng của C. C++ bổ sung các tính năng lập trình hướng đối tượng gồm lớp, kế thừa và đa hình. Năm 1998, tiêu chuẩn ISO đầu tiên cho C++ được công bố. Sự tiến hóa liên tục qua các phiên bản C++11, C++14, C++17 và C++20 mang đến nhiều tính năng hiện đại. Giáo trình C hiện nay thường kết hợp cả hai ngôn ngữ này.

1.2. Vai trò của giáo trình C trong đào tạo lập trình

Giáo trình C đóng vai trò nền tảng trong chương trình đào tạo công nghệ thông tin tại các trường đại học. Sinh viên tiếp cận giáo trình để hiểu cấu trúc chương trình, biến, kiểu dữ liệu và thuật toán. Giáo trình cung cấp lộ trình học tập có hệ thống, từ đơn giản đến phức tạp. Các ví dụ minh họa trong giáo trình giúp người học nắm bắt lý thuyết một cách trực quan. Ngoài ra, giáo trình còn hướng dẫn cách biên dịch và chạy chương trình trên nhiều nền tảng khác nhau. Kiến thức từ giáo trình C áp dụng rộng rãi trong phát triển phần mềm hệ thống và ứng dụng.

II. Phân tích khái niệm tái định nghĩa hàm trong giáo trình C

Tái định nghĩa hàm là một trong những khái niệm cốt lõi được trình bày trong giáo trình C++. Thuật ngữ tái định nghĩa nghĩa là cung cấp nhiều định nghĩa khác nhau cho cùng một tên hàm. Mỗi định nghĩa phải có dấu hiệu duy nhất, tức là khác biệt về số lượng hoặc kiểu tham số. Ví dụ, hàm GetTime có thể trả về số giây dưới dạng long hoặc trả về giờ, phút, giây qua tham chiếu. Khi hàm được gọi, trình biên dịch so sánh đối số với các định nghĩa có sẵn để chọn phiên bản phù hợp. Tính năng này mang lại sự tiện lợi lớn cho lập trình viên. Thay vì đặt nhiều tên khác nhau cho các hàm cùng chức năng, chỉ cần một tên duy nhất. Các hàm thành viên trong lớp cũng có thể tái định nghĩa. Giáo trình nhấn mạnh rằng mỗi định nghĩa phải có chữ ký riêng biệt để tránh xung đột. Tái định nghĩa hàm giúp mã nguồn gọn gàng, dễ đọc và dễ bảo trì hơn.

2.1. Nguyên tắc hoạt động của hàm tái định nghĩa

Khi một hàm được tái định nghĩa, trình biên dịch sử dụng cơ chế phân giải quá tải. Quá trình này so sánh số lượng và kiểu dữ liệu của đối số trong lời gọi với các định nghĩa có sẵn. Nếu khớp chính xác, định nghĩa đó được chọn. Nếu không khớp chính xác, trình biên dịch thực hiện chuyển đổi kiểu ngầm định. Thứ tự ưu tiên là khớp chính xác, chuyển đổi mở rộng, chuyển đổi thu hẹp. Nếu không có định nghĩa nào phù hợp, lỗi biên dịch sẽ xảy ra. Nguyên tắc này đảm bảo tính chính xác và ổn định của chương trình.

2.2. Ví dụ thực tế về hàm Max tái định nghĩa

Giáo trình thường minh họa tái định nghĩa hàm qua hàm Max. Hàm Max được viết lại để so sánh hai số nguyên, hai số thực hoặc hai chuỗi ký tự. Mỗi phiên bản có cùng tên Max nhưng khác kiểu tham số. Với số nguyên, hàm so sánh trực tiếp giá trị. Với số thực, hàm xử lý số dấu phẩy động. Với chuỗi, hàm sử dụng phép so sánh từ điển. Cách tiếp cận này thể hiện rõ lợi ích của tái định nghĩa. Người gọi hàm không cần nhớ nhiều tên khác nhau, chỉ cần gọi Max với đối số phù hợp.

III. Phương pháp tái định nghĩa toán tử trong giáo trình C

Tái định nghĩa toán tử là nội dung nâng cao trong giáo trình C++. Giống như hàm, các toán tử nhận toán hạng và trả về giá trị. Phần lớn toán tử C++ đã được tái định nghĩa sẵn cho các kiểu dữ liệu cơ bản. Ví dụ, toán tử cộng cộng được dùng cho số nguyên, số thực và địa chỉ. Tuy nhiên, các định nghĩa sẵn chỉ giới hạn trên kiểu dữ liệu có sẵn. Lập trình viên có thể cung cấp thêm định nghĩa mới để toán tử thao tác trên kiểu người dùng tự định nghĩa. Mỗi định nghĩa thêm được cài đặt thông qua một hàm đặc biệt. Giáo trình trình bày cách tái định nghĩa toán tử nhập xuất, toán tử mảng và toán tử con trỏ. Việc khởi tạo và gán tự động cũng được thảo luận chi tiết. Giáo trình nhấn mạnh tầm quan trọng của việc cài đặt chính xác khi lớp sử dụng bộ nhớ động. Quy tắc chuyển kiểu giúp rút gọn số lượng tái định nghĩa cần thiết cho cùng một toán tử.

3.1. Tái định nghĩa toán tử nhập xuất và toán tử mảng

Toán tử nhập xuất được tái định nghĩa rộng rãi trong giáo trình C++. Toán tử << dùng cho xuất dữ liệu và >> dùng cho nhập dữ liệu. Việc tái định nghĩa cho phép áp dụng trực tiếp với kiểu dữ liệu tự định nghĩa. Toán tử [] cũng thường được tái định nghĩa cho các lớp chứa dữ liệu. Ví dụ, lớp mảng có thể dùng toán tử [] để truy cập phần tử theo chỉ số. Cách tiếp cận này tạo ra cú pháp tự nhiên và trực quan. Người học cần nắm vững cú pháp khai báo hàm bạn để tái định nghĩa toán tử nhập xuất đúng cách.

3.2. Tái định nghĩa toán tử tăng giảm và toán tử gán

Toán tử tăng tăng và giảm giảm có hai phiên bản tiền tố và hậu tố. Giáo trình hướng dẫn cách tái định nghĩa cả hai phiên bản này. Phiên bản tiền tố trả về giá trị sau khi tăng. Phiên bản hậu tố trả về giá trị trước khi tăng và nhận một đối số giả để phân biệt. Toán tử gán cũng cần được tái định nghĩa khi lớp chứa dữ liệu động. Việc cài đặt đúng toán tử gán tránh rò rỉ bộ nhớ và đảm bảo sao chép sâu. Giáo trình thường dùng lớp Binary để minh họa các toán tử này.

IV. Kết luận và ứng dụng thực tế của giáo trình C trong lập trình

Giáo trình C cung cấp kiến thức toàn diện từ cơ bản đến nâng cao cho người học lập trình. Các khái niệm như tái định nghĩa hàm và toán tử mở ra khả năng viết mã linh hoạt và tái sử dụng. Trong thực tế, kỹ năng lập trình C/C++ áp dụng rộng rãi trong phát triển hệ điều hành, phần mềm nhúng và ứng dụng hiệu suất cao. Quá trình biên dịch chương trình C++ bao gồm nhiều bước từ tiền xử lý đến liên kết. Giáo trình hướng dẫn cách sử dụng trình biên dịch trên cả hệ điều hành Unix và Windows. Các lệnh biên dịch như cc với tùy chọn -o giúp người học kiểm soát quá trình tạo file thực thi. Bài tập cuối chương trong giáo trình cung cấp cơ hội thực hành quý giá. Người học nên xây dựng nền tảng vững chắc qua giáo trình trước khi tiếp cận framework và thư viện hiện đại. Kiến thức từ giáo trình C là tài sản không thể thay thế trong sự nghiệp lập trình.

4.1. Quy trình biên dịch và chạy chương trình C

Biên dịch chương trình C++ gồm nhiều bước trong suốt với người dùng. Đầu tiên, bộ tiền xử lý xử lý các chỉ thị như include. Tiếp theo, trình biên dịch chuyển mã nguồn thành mã đối tượng. Cuối cùng, trình liên kết kết hợp mã đối tượng với thư viện để tạo file thực thi. Trên Unix, lệnh cc được sử dụng với tùy chọn -o để đặt tên file đầu ra. Trên Windows, trình biên dịch cung cấp giao diện đồ họa thân thiện. Tên file nguồn C++ thường có phần mở rộng .cpp. Hiểu quy trình biên dịch giúp người học gỡ lỗi hiệu quả.

4.2. Ứng dụng thực tế của kiến thức từ giáo trình C

Kiến thức từ giáo trình C áp dụng trong nhiều lĩnh vực công nghệ. Phát triển hệ điều hành như Linux sử dụng C làm ngôn ngữ chính. Phần mềm nhúng trong thiết bị IoT và vi điều khiển thường viết bằng C. Các thư viện hiệu suất cao như OpenGL và CUDA dựa trên C/C++. Lập trình game sử dụng C++ với engine như Unreal Engine. Hệ thống cơ sở dữ liệu như MySQL được phát triển bằng C. Nghiên cứu khoa học tính toán sử dụng C++ cho tốc độ xử lý. Giáo trình C là nền tảng không thể thiếu cho mọi lập trình viên chuyên nghiệp.

21/04/2026

Trích đoạn nội dung tài liệu

Tái định nghĩa Chương này thảo luận về tái định nghĩa hàm và toán tử trong C++. Thuật ngữ tái định nghĩa (overloading) nghĩa là ‘cung cấp nhiều định nghĩa’. Tái định nghĩa hàm liên quan đến việc định nghĩa các hàm riêng biệt chia sẻ cùng tên, mỗi hàm có một dấu hiệu duy nhất. Tái định nghĩa hàm thích hợp cho: • Định nghĩa các hàm về bản chất là làm cùng công việc nhưng thao tác trên các kiểu dữ liệu khác nhau. • Cung cấp các giao diện tới cùng hàm. Tái định nghĩa hàm (function overloading) là một tiện lợi trong lập trình. Giống như các hàm, các toán tử nhận các toán hạng (các đối số) và trả về một giá trị. Phần lớn các toán tử C++ có sẵn đã được tái định nghĩa rồi. Ví dụ, toán tử + có thể được sử dụng để cộng hai số nguyên, hai số thực, hoặc hai địa chỉ. Vì thế, nó có nhiều định nghĩa khác nhau. Các định nghĩa xây dựng sẵn cho các toán tử được giới hạn trên những kiểu có sẵn. Các định nghĩa thêm vào có thể được cung cấp bởi các lập trình viên sao cho chúng cũng có thể thao tác trên các kiểu người dùng định nghĩa. Mỗi định nghĩa thêm vào được cài đặt bởi một hàm. Tái định nghĩa các toán tử sẽ được minh họa bằng cách sử dụng một số lớp đơn giản. Chúng ta sẽ thảo luận các qui luật chuyển kiểu có thể được sử dụng như thế nào để rút gọn nhu cầu cho nhiều tái định nghĩa của cùng toán tử. Chúng ta sẽ trình bày các ví dụ của tái định nghĩa một số toán tử phổ biến gồm << và >> cho xuất nhập, [] và () cho các lớp chứa, và các toán tử con trỏ. Chúng ta cũng sẽ thảo luận việc khởi tạo và gán tự động, tầm quan trọng của việc cài đặt chính xác chúng trong các lớp sử dụng các thành viên dữ liệu được cấp phát động. Không giống như các hàm và các toán tử, các lớp không thể được tái định nghĩa; mỗi lớp phải có một tên duy nhất. Tuy nhiên, như chúng ta sẽ thấy trong chương 8, các lớp có thể được sửa đổi và mở rộng thông qua khả năng thừa kế (inheritance). Chương 8: Tái định nghĩa 122 8. Tái định nghĩa hàm Xem xét một hàm, GetTime, trả về thời gian hiện tại của ngày theo các tham số của nó, và giả sử rằng cần có hai biến thể của hàm này: một trả về thời gian theo giây tính từ nửa đêm, và một trả về thời gian theo giờ, phút, giây. Rõ ràng các hàm này phục vụ cùng mục đích nên không có lý do gì lại để cho chúng có những cái tên khác nhau. C++ cho phép các hàm được tái định nghĩa, nghĩa là cùng hàm có thể có hơn một định nghĩa: long GetTime (void); // số giây tính từ nửa đêm void GetTime (int &hours, int &minutes, int &seconds); Khi hàm GetTime được gọi, trình biên dịch so sánh số lượng và kiểu các đối số trong lời gọi với các định nghĩa của hàm GetTime và chọn một cái khớp với lời gọi. Ví dụ: int h, m, s; long t = GetTime(); // khớp với GetTime(void) GetTime(h, m, s); // khớp với GetTime(int&, int&, int&); Để tránh nhầm lẫn thì mỗi định nghĩa của một hàm được tái định nghĩa phải có một dấu hiệu duy nhất. Các hàm thành viên của một lớp cũng có thể được tái định nghĩa: class Time { //. long GetTime (void); // số giây tính từ nửa đêm void GetTime (int &hours, int &minutes, int &seconds); }; Tái định nghĩa hàm giúp ta thu được nhiều phiên bản đa dạng của hàm mà không thể có được bằng cách sử dụng đơn độc các đối số mặc định. Các hàm được tái định nghĩa cũng có thể có các đối số mặc định: void Error (int errCode, char *errMsg = ""); void Error (char *errMsg); 8. Tái định nghĩa toán tử C++ cho phép lập trình viên định nghĩa các ý nghĩa thêm vào cho các toán tử xác định trước của nó bằng cách tái định nghĩa chúng. Ví dụ, chúng ta có thể tái định nghĩa các toán tử + và – để cộng và trừ các đối tượng Point: class Point { public: Point (int x, int y) {Point::x = x; Point::y = y;} Chương 8: Tái định nghĩa 123 Point operator + (Point &p) {return Point(x + p.y);} Point operator - (Point &p) {return Point(x - p.y);} private: int x, y; }; Sau định nghĩa này thì + và – có thể được sử dụng để cộng và trừ các điểm giống như là chúng được sử dụng để cộng và trừ các số: Point p1(10,20), p2(10,20); Point p3 = p1 + p2; Point p4 = p1 - p2; Việc tái định nghĩa các toán tử + và – như trên sử dụng các hàm thành viên. Một khả năng khác là một toán tử có thể được tái định nghĩa toàn cục: class Point { public: Point (int x, int y) {Point::x = x; Point::y = y;} friend Point operator + (Point &p, Point &q) {return Point(p.y);} friend Point operator - (Point &p, Point &q) {return Point(p.y);} private: int x, y; }; Sử dụng một toán tử đã tái định nghĩa tương đương với một lời gọi rõ ràng tới hàm thi công nó. Ví dụ: operator+(p1, p2) // tương đương với: p1 + p2 Thông thường, để định nghĩa một toán tử λ xác định trước thì chúng ta định nghĩa một hàm tên operator λ . Nếu λ là một toán tử nhị hạng: • operator λ phải nhận chính xác một đối số nếu được định nghĩa như một thành viên của lớp, hoặc hai đối số nếu được định nghĩa toàn cục. Tuy nhiên, nếu λ là một toán tử đơn hạng: • operator λ phải nhận không đối số nếu được định nghĩa như một thành viên của lớp, hoặc một đối số nếu được định nghĩa toàn cục.1 tổng kết các toán tử C++ có thể được tái định nghĩa. Năm toán tử còn lại không được tái định nghĩa là: .* :: ?: sizeof Chương 8: Tái định nghĩa 124 Bảng 8.1 Các toán tử có thể tái định nghĩa. + - * ! ~ & ++ -- () -> ->* Đơn hạng new delete + - * / % & | ^ << >> = += -= /= %= &= |= ^= << >> Nhị hạng = = == != < > <= >= & || [] () , & Toán tử đơn hạng (ví dụ ~) không thể được tái định nghĩa như nhị hạng hoặc toán tử nhị hạng (ví dụ =) không thể được tái định nghĩa như toán tử đơn hạng. C++ không hỗ trợ định nghĩa toán tử new bởi vì điều này có thể dẫn đến sự mơ hồ. Hơn nữa, luật ưu tiên cho các toán tử xác định trước cố định và không thể được sửa đổi. Ví dụ, dù cho bạn tái định nghĩa toán tử * như thế nào thì nó sẽ luôn có độ ưu tiên cao hơn toán tử +. Các toán tử ++ và –- có thể được tái định nghĩa như là tiền tố cũng như là hậu tố. Các luật tương đương không được áp dụng cho các toán tử đã tái định nghĩa. Ví dụ, tái định nghĩa + không ảnh hưởng tới += trừ phi toán tử += cũng được tái định nghĩa rõ ràng. Các toán tử ->, =, [], và () chỉ có thể được tái định nghĩa như các hàm thành viên, và không như toàn cục. Để tránh sao chép các đối tượng lớn khi truyền chúng tới các toán tử đã tái định nghĩa thì các tham chiếu nên được sử dụng. Các con trỏ thì không thích hợp cho mục đích này bởi vì một toán tử đã được tái định nghĩa không thể thao tác toàn bộ trên con trỏ. Ví dụ: Các toán tử trên tập hợp Lớp Set được giới thiệu trong chương 6. Phần lớn các hàm thành viên của Set được định nghĩa như là các toán tử tái định nghĩa tốt hơn. Chương 8: Tái định nghĩa 125 Danh sách 8.h> 2 const maxCard = 100; 3 enum Bool {false, true}; 4 class Set { 5 public: 6 Set(void) { card = 0; } 7 friend Bool operator & (const int, Set&); // thanh vien 8 friend Bool operator == (Set&, Set&); // bang 9 friend Bool operator != (Set&, Set&); // khong bang 10 friend Set operator * (Set&, Set&); // giao 11 friend Set operator + (Set&, Set&); // hop 12 //. 13 void AddElem(const int elem); 14 void Copy (Set &set); 15 void Print (void); 16 private: 17 int elems[maxCard]; // cac phan tu cua tap hop 18 int card; // so phan tu cua tap hop 19 }; Ở đây, chúng ta phải quyết định định nghĩa các hàm thành viên toán tử như là bạn toàn cục. Chúng có thể được định nghĩa một cách dễ dàng như là hàm thành viên. Việc thi công các hàm này là như sau. Bool operator & (const int elem, Set &set) { for (register i = 0; i < set.card; ++i) if (elem == set.elems[i]) return true; return false; } Bool operator == (Set &set1, Set &set2) { if (set1.card) return false; for (register i = 0; i < set1.card; ++i) if (!(set1.elems[i] & set2)) // sử dụng & đã tái định nghĩa return false; return true; } Bool operator != (Set &set1, Set &set2) { return !(set1 == set2); // sử dụng == đã tái định nghĩa } Set operator * (Set &set1, Set &set2) { Set res; for (register i = 0; i < set1.card; ++i) if (set1.elems[i] & set2) // sử dụng & đã tái định nghĩa res.elems[i]; Chương 8: Tái định nghĩa 126 return res; } Set operator + (Set &set1, Set &set2) { Set res; set1.Copy(res); for (register i = 0; i < set2.Print(); if (s1 != s2) cout << "s1 /= s2\n"; return 0; } Khi chạy chương trình sẽ cho kết quả sau: s1 = {10,20,30,40} s2 = {30,50,10,60} 20 thuoc s1 s1 giao s2 = {10,30} s1 hop s2 = {10,20,30,40,50,60} s1 /= s2 8. Chuyển kiểu Các luật chuyển kiểu thông thường có sẵn của ngôn ngữ cũng áp dụng tới các hàm và các toán tử đã tái định nghĩa. Ví dụ, trong if ('a' & set) //. toán hạng đầu của & (nghĩa là 'a') được chuyển kiểu ẩn từ char sang int, bởi vì toán tử & đã tái định nghĩa mong đợi toán hạng đầu của nó thuộc kiểu int. Chương 8: Tái định nghĩa 127 Bất kỳ sự chuyển kiểu nào khác thêm vào phải được định nghĩa bởi lập trình viên. Ví dụ, giả sử chúng ta muốn tái định nghĩa toán tử + cho kiểu Point sao cho nó có thể được sử dụng để cộng hai điểm hoặc cộng một số nguyên tới cả hai tọa độ của một điểm: class Point //.

Nội dung được bảo vệ bản quyền — Tải xuống đầy đủ