Lập trình tân binh | 1.7. Chia nhỏ chương trình thành các hàm

Trong bài học trước, chúng ta đã nhắc đến các lệnh cấu trúc chia nhánh (lệnh điều kiện) và lặp (vòng lặp) cho phép chúng ta quản lý tiến trình của chương trình. Trước đó, chúng ta cũng đã nói đến các biến. Đấy đều là những khái niệm cơ bản của tin học mà bạn có thể bắt gặp trong bất cứ ngôn ngữ lập trình nào. Khái niệm mà tôi sẽ giới thiệu với các bạn tiếp theo đây cũng vậy. Chủ đề của bài học này sẽ là : hàm.

Tất cả những chương trình C + + đều sử dụng những hàm. Kể cả những bạn cũng đã từng sử dụng chúng, dù hoàn toàn có thể là bạn cũng không ý thức được .
Mục đích của việc tạo ra những hàm là chia nhỏ chương trình ra thành nhiều phần mà mỗi phần hoàn toàn có thể được sử dụng lại trong những trường hợp khác nhau. Hình dung chúng kiểu như những viên gạch vậy, bạn hoàn toàn có thể dùng cho những mục tiêu khác nhau để xây tường, xây nhà, xây kho. Sau khi làm xong những ” viên gạch ” thì việc những lập trình viên phải làm chỉ là gắn chúng lại với nhau để tạo ra chương trình của mình .

Nào, hãy bắt đầu bằng việc “đúc gạch” !

Tạo ra và sử dụng các hàm

Từ đầu của giáo trình này, chúng ta đã bắt đầu sử dụng các hàm. Đến bây giờ vẫn vậy. Các bạn hẳn sẽ không quên main() chứ ? Điểm khởi đầu của mọi chương trình C++, đó là nơi mà mọi thứ bắt đầu.

#include 
using namespace std;

int main(){ //Bat dau cua ham main() va cung cua chuong trinh
  cout << "Xin chao, Tan Binh !" << endl;
  return 0;
} //Ket thuc cua ham main() va cung cua chuong trinh

Chương trình khởi đầu ở dòng thứ 4 và kết thúc ở dòng thứ 7. Điều đó có nghĩa là hàng loạt chương trình nằm trong 1 và chi 1 hàm. Chương trình không ra khỏi hàm đó. Trong cả tiến trình chỉ có 1 đoạn mã được chạy tuần tự từ trên xuống dưới .
Nếu tôi nói thế với những bạn thì là vì tất cả chúng ta hoàn toàn có thể viết thêm những hàm khác nữa và chương trình sẽ được chia nhỏ ra thành những phần độc lập nhau .

? Tại sao lại phải làm như thế ?

Đúng thật là chúng ta có thể nhét tất cả mã của chương trình vào trong hàm main(). Thế nhưng đây không phải 1 thói quen tốt.

Hãy nghĩ về siêu cấp game show 3D mà bạn sẽ tạo ra. Bởi vì nó khá phức tạp, mã nguồn hoàn toàn có thể lên tới vài chục nghìn dòng ! Nếu xếp toàn bộ chỗ đó vào cùng 1 hàm thì sẽ rất khó để theo dõi được những thứ. Sẽ đơn thuần hơn rất nhiều nếu ở 1 góc tất cả chúng ta để 1 đoạn mã chuyên giải quyết và xử lý hoạt động của nhân vật rồi góc khác đoạn giải quyết và xử lý đổi khác cảnh vật mỗi khi nhân vật lên bàn, vv … Chia nhỏ chương trình sẽ giúp tất cả chúng ta tổ chức triển khai việc làm hiệu suất cao hơn .
Ngoài ra, nếu trong nhóm của bạn có nhiều người cùng tham gia viết mã, sẽ rất tiện để chia việc làm theo kiểu mỗi người đảm nhiệm 1 số ít hàm khác nhau .

Không chỉ có thể! Lấy ví dụ về hàm lấy căn mà chúng ta đã sử dụng lúc trước. Nếu bạn tạo ra 1 chương trình bao gồm các công thức toán thì lý do là vì bạn cần dùng chúng ở những chỗ khác nhau để thực hiện các phép tính toán. Hàm sqrt() sẽ giúp ta tránh được việc lặp lại 1 đoạn mã nhiều lần ở những vị trí khác nhau. Chúng ta có thể tái sử dụng các hàm, đây mới là lý do chính.

Giới thiệu về các hàm

1 hàm là một đoạn mã thực thi một việc làm nhất định. Nó nhận vào những tài liệu để giải quyết và xử lý, thực thi 1 số hành vi rồi trả về cho tất cả chúng ta một giá trị .

Những giá trị mà chúng ta đưa vào được gọi là thông số (argument) và giá trị mà ta nhận ở đầu ra của hàm là giá trị trả về (return value). Một hình vẽ minh họa :D.

Các bạn vẫn còn nhớ hàm pow() chứ ? Đó là hàm cho phép bạn hiện phép lũy thừa. Sử dụng những từ vựng mà chúng ta vừa làm quen thì hàm này :

  1. Nhận vào 2 thông số
  2. Thực hiện 1 phép tính
  3. Trả về kết quả của phép tính

Nếu sử dụng mẫu hình vẽ bên trên

Các bạn đã tự mình thử nghiềm việc có thể dùng lại hàm pow() mỗi khi cần để tính lũy thừa mà không cần chép lại đoạn mã thực thi trong hàm này.

Định nghĩa 1 hàm

Giờ là đến lúc lúc thao tác cụ thể hàm. Tôi đã có thể để các bạn vật lộn với hàm main() và tự đưa ra tổng kết. Thế nhưng tôi lại rất tốt bụng :D, vì thế tôi sẽ đưa ra 1 số chỉ dẫn. Sẵn sàng chưa? … Chúng ta bắt đầu nhé!

Tất cả những hàm đều có dạng :

Kiểu_dữ_liệu tên_hàm(danh_sách_thông_số){
  //Đoạn lệnh thực thi bên trong hàm
}

Ta hoàn toàn có thể xác lập được 1 số yếu tố :

  • Kiểu dữ liệu trả về : cung cấp thông tin về kiểu dữ liệu của giá trị trả về. Nếu hàm trả về 1 thông điệp thì kiểu trả về phải là string còn nếu là phép tính thì phải là kiểu số như int, double
  • Tên của hàm : các bạn đã thấy 1 số ví dụ như main(), pow() hay sqrt(). Quan trọng là tên của hảm phải nói lên được nhiệm vụ của hàm đó, giống như tên biến vậy.
  • Danh sách thông số : cung cấp những dữ liệu đầu vào mà hàm sẽ xử lý. Hàm có thể có 0 thông số giống như main(), 1 thông số như sqrt() hay nhiều thông số như pow().
  • Dấu {} : cho biết chỗ bắt đầu và kết thúc của hàm. Tất cả lệnh được thực thi phải nằm trong đấy.

! Chúng ta có quyền tạo ra nhiều hàm trùng tên với nhau nhưng điều kiện bắt buộc là danh sách thông số phải khác nhau (chú ý khi xét sự giống nhau của danh sách thông số, chúng ta chỉ quan tâm đến kiểu dữ liệu và thứ tự của chúng trong đó chứ không quan tâm đến tên biến). Việc này gọi là khai báo chồng. Ví dụ trong cùng 1 chương trình, chúng ta có thể có 2 hàm int cong(int a, int b)double cong(double a, double b). 2 hàm này cùng tên nhưng 1 làm việc với số nguyên còn cái còn lại làm việc với số thực.

Một hàm đơn giản

Hãy khởi đầu với 1 hàm cơ bản : cộng thêm 2 vào giá trị của 1 số và trả về tác dụng !

int tangHai(int so){
    int ketQua(so + 2);
    //Ta tao ra 1 o nho
    //Nhan vao thong so va cong vao gia tri do 2 don vi
    //Luu vao trong o nho vua tao bo nho

    return ketQua;
    //Tra ve gia tri duoc luu trong 'ketQua'
}

!Không có dấu ; sau khi khai báo cũng như sau dấu {}

Với tất cả những gì mà tôi đã giải thích với các bạn ở bên trên, tôi cá là các bạn sẽ hiểu dòng đầu tiên. Chúng ta khai báo 1 hàm tên là tangHai. Hàm này nhận vào thông số là một số nguyên và sau khi kết thúc, trả về kết quả là 1 số nguyên.

Tất cả những dòng sau đó đều khá quen thuộc, chỉ trừ dòng return ketQua;. Nếu bạn có đặt câu hỏi về dòng này, xin mời trước hết đọc lại bài học về bộ nhớ. Câu lệnh return gửi trả về kết quả đầu ra của hàm mà trong trường hợp này là giá trị của biến ketQua.

Gọi hàm

Bạn nghĩ sao nếu tôi nói là những bạn đã biết thừa cách gọi hàm rồi ? Có nhớ bài học kinh nghiệm của tất cả chúng ta về những hàm trong toán học chứ ?

#include 
using namespace std;

int tangHai(int so){
    int ketQua(so + 2);

    return ketQua;
}

int main(){
    int a(2),b(2);

    cout << "Gia tri cua a : " << a << endl;
    cout << "Gia tri cua b : " << b << endl;
    b = tangHai(a); //Goi ham
    cout << "Gia tri cua a : " << a << endl;
    cout << "Gia tri cua b : " << b << endl;

    return 0;
}

Chúng ta thấy cú pháp = (). Quá dễ đúng không !

Đây là hiệu quả khi chạy đoạn mã trên .

Sau khi gọi hàm, giá trị của biến b đã đổi khác. Tất cả hoạt động giải trí như Dự kiến .

Hàm nhiều thông số

 Chúng ta đã thấy là 1 hàm có khả năng nhận vào nhiều thông số như getline() hay pow(). Để truyền nhiều thông số cho hàm, các thông số trong danh sách phải cách nhau bằng dấu phẩy.

int cong(int a, int b){
    return a+b;
}

double nhan(double a, double b, double c){
    return a*b*c;
}

Hàm tiên phong trả về hiệu quả là tổng của 2 thông số kỹ thuật còn hàm thứ 2 thì hiệu quả lại là tích của 3 thông số kỹ thuật .
! Đương nhiên là bạn cũng hoàn toàn có thể khai báo hàm với những biến mang những kiểu tài liệu khác nhau .

Hàm không có thông số

Trái ngược với những hàm nhiều thông số kỹ thuật, có những hàm không cần có tài liệu nguồn vào, đồng nghĩa tương quan với không có thông số kỹ thuật. Trong trường hợp đó, tất cả chúng ta chỉ cần để trống bên trong dấu ngoặc là được

? Những hàm như thế có tác dụng gì?

Hãy nghĩ đến 1 hàm có công dụng là hỏi tên của người dùng, vây thì nó không cần phải có thông số kỹ thuật ;

string hoiTen(){    
     cout << "Ten cua ban la : ";
     string ten;
     cin >> ten;
     return ten;
}

Dù là kiểu hàm này cũng khá hiếm nhưng tôi nghĩ những bạn sẽ không khó khăn vất vả nếu muốn tìm 1 ví dụ đâu .

Hàm không trả về kết quả

Tất cả những hàm mà tôi trình làng nãy giờ đều nhận những thông số kỹ thuật và trả về tác dụng. Nhưng như vậy không có nghĩa là không sống sót những hàm không trả về gì cả ( hoặc nói đúng chuẩn hơn là gần như không gì cả ) .

Không có gì được trả về nhưng khi bạn khai báo hàm, vẫn phải ghi 1 kiểu dữ liệu đầu ra. Đấy là lý do chúng ta dùng kiểu dữ liệu void, từ tiếng Anh mang nghĩa là trống rỗng.

Và vì thế, nó sẽ mang ý nghĩa là hàm này không trả về gì cả .

void noiXinChao(){
    cout << "Xin chao, Tan Binh!" << endl;
    //Khong tra ve ket qua nao nen khong co lenh return
}

int main(){
    noiXinChao();
    //Vi ham khong tra ve ket qua
    //Nen khong can luu gia tri trả ve trong o nho
    
    return 0;
}

Cần ghi nhớ là hàm không hề trả về nhiều hơn 1 giá trị. 1 hàm chỉ có tối đa 1 hiệu quả .
Đến đây là tất cả chúng ta đã lướt qua hết phần triết lý. Phần tiếp theo là 1 số ví dụ và sơ đồ tóm tắt .

1 vài ví dụ

Hàm bình phương

Chúng ta sẽ khởi đầu với 1 ví dụ đơn thuần : tính bình phương của 1 số. Hàm này nhận vào thông số kỹ thuật là 1 số x và tính giá trị bình phương của x .

#include 
using namespace std;

double binhPhuong(double x){
    double ketQua;
    ketQua = x*x;
    return ketQua;
}

int main(){
    double so, soBinhPhuong;
    cout << "Hay nhap vao 1 so : ";
    cin >> so;

    soBinhPhuong = binhPhuong(so); //Su dung ham ben tren

    cout << "Binh phuong cua " << so << " la " << soBinhPhuong << endl;
    return 0;
}

Tôi đã vẽ giúp những bạn 1 sơ đồ lý giải tiến trình của chương trình .

  1. Chương trình bắt đầu ở đầu hàm main().
  2. Thực hiện 3 dòng đâu tiên như bình thường.
  3. Gặp 1 lệnh gọi hàm.
  4. Kiểm tra thông số. Giá trị của thông số là giá trị của biến so. Giá trị này sẽ được chép vào ô nhớ x.
  5. Chương trình chuyển lên dòng đầu tiên của hàm binhPhuong(), chạy mã của hàm này giống như bình thường.
  6. Đến cuối hàm binhPhuong, chép giá trị trong biến ketQua vào ô nhớ soBinhPhuong.
  7. Quay tở về hàm main() và thực hiện nốt dòng lệnh cuối.

Một điều tuyệt đối phải nhớ là : giá trị của biến được cung cấp cho hàm được chép vào 1 ô nhớ mới. Những xử lý của hàm binhPhuong() không ảnh hưởng gì tới các biến được khai báo trong hàm main() mà nó chỉ sử dụng những ô nhớ của riêng nó. Chỉ có khi lệnh return được tiến hành thì mới thay đổi biến soBinhPhuong của hàm main(). Biến so không thay đổi trong quá trình gọi hàm.

Sử dụng lại hàm có sẵn

Lợi ích của sử dụng hàm ở đây là giúp tất cả chúng ta hoàn toàn có thể tính bình phương của nhiều số khác nhau, ví dụ những số từ 1 tới 20 .

#include 
using namespace std;

double binhPhuong(double x){
    double ketQua;
    ketQua = x*x;
    return ketQua;
}

int main(){
    for(int i(1); i <= 20 ; i++){
        cout << "Binh phuong cua " << i << " la : " << binhPhuong(i) << endl;
    }
    return 0;
}

Chúng ta chỉ cần viết 1 lần công thức của hàm tính bình phương và sử dụng 20 lần " viên gạch " này. Ở đây, phép tính khá đơn thuần nhưng có những trường hợp, tất cả chúng ta sẽ rút ngắng được tương đối đoạn mã .

Các biến cùng tên

Trong bài học kinh nghiệm trước, tất cả chúng ta đã nói là mỗi biến phải có 1 cái tên độc nhất. Điều này trọn vẹn đúng nhưng chỉ được vận dụng trong khoanh vùng phạm vi bên trong của cùng 1 hàm. Chúng ta được phép khai báo 2 biến có tên trùng nhau trong 2 hàm khác nhau .

#include 
using namespace std;

double binhPhuong(double x){
    double so;
    so = x*x;
    return so;
}

int main(){
    double so, soBinhPhuong;
    cout << "Hay nhap vao 1 so : ";
    cin >> so;

    soBinhPhuong = binhPhuong(so); //Su dung ham ben tren

    cout << "Binh phuong cua " << so << " la " << soBinhPhuong << endl;
    return 0;
}

Như các bạn đã thấy, có 2 biến so tồn tại trong 2 hàm là main()binhPhuong(). Trình biên dịch không phàn nàn gì và chương trình thực hiện đúng như đoạn mã trước. Không có sự nhầm lẫn ở đây vì mỗi lần, trình biên dịch chỉ chú ý đên 1 hàm và không nhận ra là có 2 biến trùng tên.

? Nhưng vì sao phải làm thế?

Hãy nhớ lại là tên biến phải đặc trưng cho tài liệu mà nó tàng trữ. Rất hiển nhiên là có những trường hợp mà những biến khác nhau mang những vai trò tương tự như nhau và do đó có tên giống. Ngoài ra tất cả chúng ta cũng tránh đặt những cái tên quá dài và phức tạp chỉ với mục tiêu để nó trở thành độc nhất trong chương trình. Chắc sẽ cần đến một trí tưởng tượng khác thường nếu phải ngồi nghĩ ra khoảng chừng vài trăm cái tên khác nhau cho một đoạn mã vài nghìn dòng .

Hàm có 2 thông số

Trước khi kết thúc pần này, đây là 1 ví dụ ở đầu cuối. Lần này, tôi ý kiến đề nghị là một hàm có 2 thông số kỹ thuật. Chúng ta sẽ vẽ 1 hình chữ nhật từ những dấu " * ". Hàm này cần 2 thông số kỹ thuật là chiều dài và chiều rộng của hình chữ nhật .

#include 
using namespace std;

void veHinhChuNhat(int dai, int rong){
    for(int dong(0); dong < rong; dong++){
        for(int cot(0); cot < dai; cot++){
            cout << "*";
        }
        cout << endl;
    } 
}

int main(){
    int dai, rong;
    cout << "Chieu dai cua hinh chu nhat : ";
    cin >> dai;
    cout << "Chieu rong cua hinh chu nhat : ";
    cin >> rong;
    
    veHinhChuNhat(dai, rong);
    return 0;
}

Đây là tác dụng đạt được .

Hàm này chỉ gồm lệnh hiển thị thông điệp nên nó không trả về giá trị nào. Vì thế phải khai báo với kiểu void .
Chúng ta hoàn toàn có thể thuận tiện đổi khác để nó trả về diện tích quy hoạnh hình chữ nhât. Trong trường hợp đó, cần cho hàm trả về kiểu int .
Hãy thử đổi khác 1 số thứ của hàm này xem ! Sau đây là vài ý tưởng sáng tạo :

  • Hiển thị thông báo lỗi nếu chiều dài nhỏ hơn 0
  • Thêm vào 1 thông số cho biết ký tự sẽ được dùng thay thế "*" để vẽ hình

Chúc những bạn vui tươi. Cái chính là cần làm chủ được những khái niệm .

Tham trị và tham chiếu

Phần cuối bài học kinh nghiệm dành cho những khái niệm nâng cao. Các bạn hoàn toàn có thể xem lại mỗi khi có vướng mắc trong phần sau của giáo trình .

Tham trị

Khái niệm nâng cao tiên phong là cách mà máy tính quản trị bộ nhớ khi thực thi những hàm .
Quay lại với hàm đơn thuần ở đầu bài học kinh nghiệm : tăng 2 đơn vị chức năng vào giá trị thông số kỹ thuật. Do những bạn đã khởi đầu hiểu được mọi thứ đang xảy ra, tôi sẽ biến hóa đi 1 tí tẹo .

int tangHai(int a){
    a += 2;
    return a;
}

! Tôi cố tình sử dụng dấu +=. Các bạn sẽ biết ngay thôi.

Hãy thử đoạn mã sau. Hoàn toàn không có gì mới để học trong đoạn mã này .

#include 
using namespace std;

int tangHai(int a){
    a+=2;
    return a;
}

int main(){
    int so(4), ketQua;
    ketQua = tangHai(so);
    
    cout << "Gia tri goc : " << so << endl;
    cout << "Ket qua : " << ketQua << endl;
    return 0;
}

Kết quả trả về không có gì giật mình .

Công đoạn đặc sắc nàm ở chỗ là điều gì đã xảy ra khi dòng lênh ketQua = tangHai(so); được thực thi. Các bạn còn nhớ cái sơ đồ lúc nãy chứ? Hãy lôi ra xem nào.

Khi gọi hàm, rất nhiều thứ đã xảy ra .

  1. Chương trình xem xét giá trị của biến so. Kết quả là 4.
  2. Mượn 1 ô nhớ và lưu vào đó giá trị 4. Ô nhớ đó dán nhãn là a, tên biến của hàm.
  3. Thực thi hàm.
  4. Tăng thêm 2 đơn vị vào biến a.
  5. Giá trị của a sau đó được ghi vào biến ketQua, bây giờ nó có giá trị là 6.
  6. Thoát khỏi hàm

Chuyện quan trọng là giá trị của biến so đã được chép vào trong 1 ô nhớ mới. Ta nói là thông số đã được truyền bằng tham trị. Khi chương trình ở bên trong hàm, mọi thứ diễn ra như trong hình sau.

 

Chúng ta có 3 ô nhớ trong bộ nhớ và quan trọng hơn là giá trok của biển so không hề thay đổi.

Tham chiếu

Các bạn vẫn còn nhớ về những tham chiếu chứ ? Đúng thể, chính là cái khái niệm khó hiểu mà tôi nói với những bạn trong bài học kinh nghiệm trước. Nếu bạn không chắc, đừng ngại đọc để nhớ lại nhé. Giờ là lúc tất cả chúng ta xem tham chiếu có công dụng gì .

Thay vì sao chép giá trị của so vào trong a, chúng ta có thể thêm vao 1 cái nhãn thứ 2 cho ô nhớ so ở bên trong hàm. Và đương nhiên là chúng ta sử dụng tham chiếu làm thông số cho hàm để làm điều này.

int tangHai(int& a){ //Chu y den dau & !!! 
  a += 2;
  return a;
}

Khi chúng ta gọi hàm theo cách này thì không có bản sao nào đước tạo ra cả. Chương trình đơn giản là thêm 1 cách gọi khác cho ô nhớ chứa biến so. Hãy nhìn hình vẽ sau đây.

Lần này thì biến so và biến a trùng nhau. Chúng ta gọi việc này là truyền thông số sử dụng tham chiếu.

? Lợi ích của sử dụng tham chiếu là gì?

Việc này cho phép hàm tangHai() thay đổi giá trị các thông số của nó. Vì thế nó cũng dẫn theo ảnh hưởng ít nhiều đến phần còn lại của chương trình. Vẫn chương trình lúc trước nhưng lần này chúng ta sử dụng tham chiếu thì sẽ nhận được kết quả như sau :

Chuyện gì đã xảy ra ? Để lý giải thì vừa đơn thuần vừa phức tạp .

Bởi vì aso chỉ đơn giản là 2 cái nhãn của cùng một ô nhớ, câu lệnh a += 2 đã thay đổi giá trị của biến so.

Vì thế việc sử dụng tham chiếu hoàn toàn có thể trở nên rất nguy hại. Chỉ làm thế khi nào bạn thật sự cần .

? Hãy cho tôi 1 ví dụ thực tiễn !

Ví dụ điển hình luôn được sử dụng cho khái niệm này chính là hàm doiCho() cho phép tráo đổi giá trị của 2 thông số ta đưa vào trong chương trình.

void doiCho(double& a, double& b){
 double trungGian(a); //Luu gia tri cua 'a' trong bien 'trungGian'
 a = b; //Ghi vao trong 'a' gia tri cua 'b'
 b = trungGian; //Ghi gia tri goc cua 'a' vào 'b'
}

int main(){
 double a(1.2), b(4.5);

 cout << "a = " << a << " va b = " << b << endl;

 doiCho(a,b); //Goi ham

 cout << "a = " << a << " va b = " << b << endl;
 return 0;
}

Kết quả khi chạy chương trình này .

Giá trị của 2 biến đã được tráo đổi cho nhau .
Nếu tất cả chúng ta không sử dụng tham chiếu mà dùng tham trị, hàm sẽ thực thi giải quyết và xử lý việc đổi chỗ của 2 bản sao của những thông số kỹ thuật. Các thông số kỹ thuật không biến hóa gì cả và việc này trở thành vô dụng .
Tôi đề xuất những bạn chạy thử hàm này dùng 2 cách tham chiếu và tham trị. Bạn sẽ thấy rõ hơn chuyện gì đang diễn ra .
Có thể giờ đây những bạn vẫn hơi sầm uất và thấy tham chiếu rắc rối. Tuy nhiên trong phần sau của giáo trình, tất cả chúng ta sẽ sử dụng nó khá liên tục. Bạn luôn hoàn toàn có thể quay lại đọc lại bài học kinh nghiệm này bất kỳ khi nào để hoàn toàn có thể hiểu rõ hơn .

Nâng cao : tham chiếu hằng

Bởi vì tất cả chúng ta đã nói đến tham chiếu, tôi sẽ nhân tiện nhắc đến một ứng dụng thực tiễn của nó. Khải niệm này đặc biệt quan trọng hữu dụng trong những chương sau nhưng tất cả chúng ta cũng hoàn toàn có thể nhắc đến nó sớm 1 chút .
Sử dụng tham chiếu có 1 ưu điểm lớn so với sử dụng tham trị, đó là không cần phải tạo ra thêm 1 bản sao của biến dùng làm thông số kỹ thuật. Hãy thử tưởng tượng thông số kỹ thuật là một string. Nếu chuỗi ký tự đó của bạn chứa một đoạn văn thật dài ( ví dụ nội dung 1 quyển tiểu thuyết ! ), vậy thì việc tạo ra 1 bản sao của nó trong bộ nhớ sẽ tốn rất nhiều thời hạn cũng như ngốn 1 đống bộ nhớ của bạn. Bản sao này trọn vẹn vô dụng và việc vô hiệu được nó sẽ giúp cải tổ hiệu suất của chương trình của bạn .
Và đây là lúc có bạn sẽ nói với tôi là vậy thì tất cả chúng ta hãy sử dụng tham chiếu. 1 ý tưởng sáng tạo hay ! Nếu dùng tham chiếu thì không có bản sao nào được tạo ra cả. Nhược điểm của việc này là nó được cho phép đổi khác thông số kỹ thuật của bạn. Đương nhiên rồi, đấy chính là nguyên do mà tham chiếu sống sót .

void f1(string vanBan);{ //Dan den viec sao chep 'vanBan' lam giam hieu suat 

}

void f2(string& vanBan);{ //Dan den viec 'vanBan' co the bi thay doi

}

Giải pháp đưa ra là sử dụng tham chiếu hằng. Chúng ta tránh được việc tao ra bản sao vì sử dụng tham chiếu và ngăn cản việc có thể thay đổi thông số đó khi khai báo rằng nó là 1 hằng số.

void f1(string const& vanBan);{ //Khong sao chep va cung khong the thay doi 

}

Lại thêm 1 thứ hơi mờ mịt và vô dụng? Trong chương 2, lúc chúng ta cùng thảo luận về lập trình hướng đối tượng, chúng ta sẽ thường xuyên dùng kỹ năng này. Và nếu cần, quyền tự do khi cho phép ban quay lại để đọc chương này bất cứ khi nào bạn muốn cool.

Sử dụng nhiều tệp mã nguồn

Trong phần trình làng, tôi đã nói là những hàm giống như là những viên gạch được tạo ra chuẩn bị sẵn sàng để sử dụng cho nhiều mục tiêu khác nhau .
Cho đến lúc này thì tất cả chúng ta mới chỉ tạo ra nó và đặt bên cạnh hàm main ( ). Chúng ta vẫn chưa thật sự sử dụng lại được nó ở chỗ nào khác .
Ngôn ngữ C + + cho phép ta tách mã nguồn ra và chứa trong nhiều tệp ( file ) mã nguồn khác nhau. Mỗi tệp hoàn toàn có thể chứa 1 hay nhiều hàm. Chúng ta hoàn toàn có thể thiết kế xây dựng 1 tệp bao ( include ) gồm 1 tệp khác có sẵn qua đó hoàn toàn có thể sử dụng lại những hàm trong nhiều dự án Bất Động Sản khác nhau. Đấy mới chính thức là giống những viên gạch hoàn toàn có thể dùng vào nhiều mục đich .

Những tệp cần thiết

Để mọi thứ được rõ ràng, tất cả chúng ta không chỉ cần 1 mà đến 2 loại tệp :

  • Tệp nguồn : thường có phần mở rộng là .cpp, chứa mã nguồn thực tế
  • Tệp tiêu đề : thường có phần mở rộng là .h hoặc .hpp, chứa nguyên mẫu của hàm.

Hãy tạo ra 2 tệp cho hàm tangHai ( ) của tất cả chúng ta .

int tangHai(int so){
 int ketQua(so + 2);

 return ketQua;
}

Tệp nguồn

Trên thanh công cụ, chọn File > New > File, sau đó chọn C / C + + source như trong hình .

Ấn Go. Giống như khi khởi tạo dự án Bất Động Sản, máy tính sẽ hỏi bạn muốn thao tác với C hay C + +, đương nhiên là chọn C + + .

Cuối cùng, máy tính yêu cầu bạn nhập 1 tên cho tệp này. Như mọi khi, hãy chon 1 tên có ý nghĩa cho các tệp để tiện cho việc quản lý. Với hàm tangHai(), tôi chọn tên math.cpp (thú thật là khi không được sử dụng dấu thì tên tiếng Anh khó bị nhầm hơn tên tiếng Việt nên nếu đã học lập trình, bạn cũng nên biết 1 ít tiếng Anh) và để nó trong cùng thư mục với main.cpp.

Chọn tổng thể những tùy chọn như trong hình .

Ấn Finish để kết thúc và tệp của tất cả chúng ta đã được tạo ra. Hãy thực thi tạo tệp tiêu đề .

Tệp tiêu đề

Phần đầu khá giống nhau, bạn chũng chọn File > New > File rồi sau đó chọn C / C + + header như trong hình .

Trong hành lang cửa số tiếp theo, bạn cần cho tệp 1 cái tên. Tốt nhất là hãy đặt cùng tên với tệp nguồn, chỉ khác là phần lan rộng ra là. h chứ không phải. cpp. Trong trường hợp này, tất cả chúng ta có math. h. Đặt nó ở cùng chỗ với 2 tệp trước .
Đừng đổi khác gì phần bên để điền dưới và đừng quên chọn mọi tùy chọn .

Ấn Finish và thế là xong .
Một khi 2 tệp này đã được tạo ra, những bạn sẽ thấy chúng Open trong ô bên trái của Code :: Block như trong hình .

Khai báo hàm trong các tệp

Bây giờ sau khi đã tạo xong những tệp, cần phải thêm nội dung cho chúng

Tệp nguồn

Tôi đã nói với các bạn là trong tệp nguồn chứa nội dung của hàm. Đó chỉ là một bộ phận. Phần còn lại thì có đôi chút khó hiểu hơn. Trình biên dịch cần biết là có 1 sự liên quan giữa 2 tệp .h.cpp. Vậy nên ta cần thêm dòng lệnh sau :

#include "math.h"

Các bạn hẳn là nhận ra dòng này. Nó cho biết là chúng ta muốn sử dụng những thứ có trong tệp math.h.

! Cần chú ý là ở đây phải sử dụng dấu ngoặc kép "" thay vì dấu <> như các bạn vẫn hay dùng.

Tệp math.cpp hoàn hảo trông sẽ như sau .

#include "math.h"

int tangHai(int so){
 int ketQua(so + 2);

 return ketQua;
}

Tệp tiêu đề

Nếu những bạn quan sát nội dung của tệp được tạo ra, bạn sẽ thấy nó không hề trống. Có 3 dòng lệnh huyền bí :

#ifndef MATH_H_INCLUDED
#define MATH_H_INCLUDED

#endif // MATH_H_INCLUDED

Những dòng này để tránh việc trình biên dịch gồm có nhiều lần tệp này. Trình biên dịch đôi khi không được mưu trí lắm và dễ rơi vào vòng luẩn quẩn nếu phải gồm có nhiều lần 1 tệp. 3 dòng lệnh được thêm vào để tránh cho tất cả chúng ta rơi vào trường hợp này. Không nên động vào và biến hóa những dòng này và phải viết hàng loạt mã nguồn ở giữa dòng lệnh thứ 2 và thứ 3 .
! Phần chữ in hoa sẽ khác nhau tùy theo từng tệp. Đó chính là những chữ đã Open trong phần để điền mà tôi khuyên những bạn không nên đổi khác khi khởi tạo tệp này. Nếu bạn không sử dụng Code :: Block, hoàn toàn có thể những dòng này sẽ không được tự động hóa thêm vào những tệp. Trong trường hợp đó, những bạn phải tự tay thêm chúng vào. Phần chữ in hoa phải giống nhau trong cả 3 dòng và mỗi tệp phải dùng những chữ khác nhau .

Trong tệp này chứa cái mà chúng ta gọi là nguyên mẫu của hàm. Trong dòng đầu tiên của hàm, chúng ta chép tất cả những chữ trước dấu { rồi thêm vào cuối dấu .

Rất ngắn gọn, đây là những gì có được sau đó

#ifndef MATH_H_INCLUDED
#define MATH_H_INCLUDED

int tangHai(int so);

#endif // MATH_H_INCLUDED

! Đừng quên dấu

Trong trường hợp đơn giản nhất thì thế là đủ. Nếu các bạn dùng những thông số phức tạp hơn như kiểu string hay kiểu mảng (chúng ta sẽ nói trong bài sau), cần phải thêm dòng lệnh bao gồm kiểu như #include trong nguyên mẫu. Ta sẽ có :

#ifndef MATH_H_INCLUDED
#define MATH_H_INCLUDED

#include 

void inThongDiep(std::string thongDiep);

#endif // MATH_H_INCLUDED

 Cần chứ ý 1 yếu tố quan trọng nữa là thêm std:: trước string. Đây là một không gian tên, khái niệm sẽ được nhắc đến trong phần sau của giáo trình. Nếu các bạn để ý kỹ, các bạn sẽ nhận ra là std xuất hiện trong tất cả các tệp nguồn của chúng ta trong dòng lệnh using namespace std. Bởi vì trong trường hợp này không có dòng lệnh đó (và lời khuyên là không nên sử dụng dòng lệnh đó trong tệp tiêu đề), chúng ta phải viết tên đầy đủ của kiểu dữ liệu string đó là std::string. Các bạn sẽ bắt gặp những ví dụ khác với tên phức trong phần sau của giáo trình. Trong lúc này thì std::string trường hợp đặc biệt duy nhất sealed.

Việc cuối cùng chúng tq phải làm là bao gồm tất cả những tệp này bên trong tệp main.cpp. Nếu không làm như vậy, trình biên dịch sẽ không biết là cần phải tìm hàm tangHai() ở đâu. Vậy nên chúng ta cần thêm 1 dòng lệnh

#include "math.h"

vào đầu chương trình. Kết quả nhận được là

#include 
#include "math.h"
using namespace std;

int main(){
    int a(2),b(2);
    cout << "Gia tri cua a : " << a << endl;
    cout << "Gia tri cua b : " << b << endl;
    b = tangHai(a);      //Goi ham
    cout << "Gia tri cua a : " << a << endl;
    cout << "Gia tri cua b : " << b << endl;

    return 0;
}

!Luôn luôn chỉ bao gồm tệp tiêu đề .h chứ không bao giờ bao gồm tệp nguồn .cpp.

Cuối cùng chúng ta cũng thật sự sử dụng được các hàm giống như những viên gạch. Nếu trong dự án khác mà ta cần dùng hàm tangHai() thì chỉ việc chép 2 tệp math.hmath.cpp vào trong dự án đó.

! Chúng ta cũng hoàn toàn có thể định nghĩa nhiều hàm trong cùng 1 tệp. Thường thì những hàm này được phân loại theo công dụng. Ví dụ tất cả chúng ta sẽ đưa toàn bộ những hàm giám sát vào 1 tệp và những hàm dùng để tạo hoạt động cho nhân vật vào 1 tệp khác. Lập trình cũng là học cách tổ chức triển khai những thứ .

Giải trình mã nguồn

Trước khi khởi đầu phần này, tôi muốn nhắc lại 1 yếu tố tưởng như thể vô ích. Ngay từ đầu, tôi đã nói với những bạn là nên thêm những phản hồi vào trong mã nguồn của mình để hiểu chương trình sẽ làm gì. Điều này càng đúng trong tường hợp những hàm chính do tất cả chúng ta sẽ sử dụng lại những hàm được viết bởi lập trình viên khác cũng như họ sẽ dùng mã của tất cả chúng ta. Tất cả chỉ chăm sóc đến tính năng của hàm chứ không quan trong việc biết xem hàm đấy có chính sách hoạt động giải trí như thế nào ( do tại lập trình viên rất lười ; D ) .
Bởi vì tệp tiêu đề thường có dung tích rất nhỏ, tất cả chúng ta tận dụng để thêm những phản hồi lý giải công dụng những hàm vào trong đó. Thường sẽ có 3 yếu tố trong những phản hồi đó ,

  • Chức năng của hàm
  • Danh sách các thông số nhận vào và ý nghĩa của chúng
  • Kết quả trả về

Thay vì viết 1 bài thuyết trình, trong phần phản hồi lý giải tính năng của hàm tangHai ( ), ta hoàn toàn có thể viết

#ifndef MATH_H_INCLUDED
#define MATH_H_INCLUDED

/*
 * Ham tang 2 don vi vao gia tri cua thong so nhan vao
 * so : thong so ma ta muon tang gia tri
 * Ket qua tra ve : so + 2
 */
int tangHai(int so);

#endif // MATH_H_INCLUDED

Trong trường hợp này thì phần miêu tả khá là đơn thuần. Cái chính là tất cả chúng ta cần tạo được thói quen làm điều này. Các bạn nên nhớ là việc thêm những phản hồi vào tệp tiêu đề thông dụng đến mức là có những chương trình được viết ra để đọc chúng để tạo nên những website tư liệu về những hàm .

Ví dụ như chương trình doxygen sử dụng cấu trúc sau

/*
 * \brief Ham tang 2 don vi vao gia tri cua thong so nhan vao
 * \param so Thong so ma ta muon tang gia tri
 * \return so + 2
 */
int tangHai(int so);

Bạn sẽ thấy trong chương sau này việc có mã nguồn được báo cáo giải trình đơn cử là rất quan trọng .

Giá trị mặc định cho các thông số

Các bạn chắc đã quen dần với những thông số kỹ thuật. Từ đầu của bài học kinh nghiệm, tất cả chúng ta đã nói đến chúng, 1 hàm yêu cấu 3 thông số kỹ thuật thì cần phải cung ứng cho hàm đó 3 gía trị thì nó mới hoàn toàn có thể hoạt động giải trí được. Tuy nhiên, trong phần này, bạn sẽ thấy là moi thứ không phải khi nào cũng như thế .
Hãy xem xét hàm sau

int soGiay(int gio, int phut, int giay)
{
    int tongSo(0);

    tongSo = gio * 60 * 60;
    tongSo += phut * 60;
    tongSo += giay;

    return tongSoo;
}

Hàm này tính tổng số giaay dựa trên số giờ, phút, giây mà chúng ta cung cấp cho nó. Không có gì là khó hiểu ở đây. Các biến gio, phut, giay là thông số mà hàm soGiay() nhận vào để tiến hành xử lý.

Giá trị mặc định

Cái mới ở đây là tất cả chúng ta hoàn toàn có thể đưa ra những giá trị mặc định cho những thông số kỹ thuật và không cần phải cung ứng giá trị của toàn bộ những thông số kỹ thuật cho hàm .
Để mở màn, tất cả chúng ta sẽ sử dụng 1 đoạn mã hoàn hảo mà tất cả chúng ta cùng chạy thử trong IDE .

#include 

using namespace std;

// Nguyen mau cua ham
int soGiay(int gio, int phut, int giay);

// Main
int main(){
    cout << soGiay(1, 10, 25) << endl;

    return 0;
}

// Dinh nghia ham
int soGiay(int gio, int phut, int giay){
    int tongSo(0);

    tongSo = gio * 60 * 60;
    tongSo += phut * 60;
    tongSo += giay;

    return tongSo;
}

Kết quả nhận được là

Nếu bạn vẫn còn hoài nghi thì 1 giờ gồm 3600 giây, 10 phút là 600 giây, 25 giây là ... 25 giây và 3600 + 600 + 25 = 4225 .
Tất cả mọi thứ hoạt động giải trí khá ổn .
Bây giờ, tất cả chúng ta muốn rằng 1 số thông số kỹ thuật là không bắt buộc, ví dụ chính do tất cả chúng ta thường dùng số giờ hơn là những đại lượng phút và giây .
Chúng ta sẽ phải biến hóa nguyên mẫu của hàm ( chú ý quan tâm, là nguyên mẫu chứ không phải là định nghĩa của hàm ). Trong nguyên mẫu, cần đổi khác để thêm vào những giá trị mặc định mà ta muốn đưa cho những thông số kỹ thuật không bắt buộc

int soGiiay(int gio, int phut = 0, int giay = 0);

Trong ví dụ này, chỉ có thông số gio là bắt buộc. Nếu người dùng không nhập vào số phút và số giây thì các thông số này sẽ mang giá trị là 0 ở trong xử lý của hàm.

Đây là đoạn mã sau khi đã biến hóa

#include 

using namespace std;

// Nguyen mau cua ham
int soGiay(int gio, int phut = 0, int giay = 0);

// Main
int main(){
    cout << soGiay(1, 10, 25) << endl;

    return 0;
}

// Dinh nghia ham
int soGiay(int gio, int phut, int giay){
    int tongSo(0);

    tongSo = gio * 60 * 60;
    tongSo += phut * 60;
    tongSo += giay;

    return tongSo;
}

! Nếu những bạn đọc kỹ đoạn mã thì những bạn sẽ thấy là những giá trị mặc định chỉ được nhắc đến ở trong nguyên mẫu mà không phải trong đoạn mã định nghĩa của hàm. Nếu mã nguồn của bạn được chia ra thành nhiều tệp thì nó sẽ nằm trong tệp tiêu đề. Đừng lo, nếu những bạn nhầm lẫn thì trình biên dịch sẽ hiển thị cho những bạn 1 lỗi nằm ở dòng lệnh đầu của định nghĩa hàm .
Đoạn mã không biến hóa gì nhiều lắm và hiệu quả tất cả chúng ta nhận được vẫn giống như lúc trước. Khác biệt chỉ hoàn toàn có thể thấy là giờ đây ở trong hàm main ( ), tất cả chúng ta hoàn toàn có thể gọi hàm chỉ mà bớt đi 1 hoặc 2 thông số kỹ thuật. Ví dụ

cout << soGiay(1) << endl;

Trình biên dịch sẽ xét từ trái qua phải. Bởi vì chỉ có 1 thong số và thông số kỹ thuật cho giờ là bắt buộc nên giá trị 1 sẽ được truyền cho số giờ .
Kết quả nhận được là

Bạn cũng hoàn toàn có thể thêm giá trị cho số phút nếu bạn muốn

cout << soGiay(1, 10) << endl;

Khi mà những bạn còn nhập đủ những thông số kỹ thuật bắt buộc, mọi thứ còn đều hoạt động giải trí thông thường .

Các trường hợp đặc biệt, chú ý nguy hiểm

Vẫn có 1 số ít quan tâm cần biết về việc sử dụng những giá trị mặc định và những thông số kỹ thuật không bắt buộc. Chúng ta sẽ làm 1 list hỏi / đáp những trường hợp hay gặp .

? Nếu tôi muốn đưa giá trị số giờ và giây nhưng không đưa ra giá trị số phút thì sao?

Thực ra thì, chuyện này là không hề. Bởi vì trình biên dịch đọc từ trái sang phải, vậy nên giá trị thứ nhất phải là số giờ, giá trị thứ 2 là số phút và ở đầu cuối là số giây .
Các bạn KHÔNG thể viết :

cout << soGiay(1,,25) << endl;

Trong C + +, việc này bị cấm. Điều này có nghĩa là bạn không hề bỏ lỡ 1 thông số kỹ thuật ở giữa nếu bạn muốn đưa ra giá trị cho thông số kỹ thuật đầu và cuối. Bạn bắt buộc phải viết

cout << soGiay(1, 0, 25) << endl;

? Liệu có thể thiết lập cho thông số giờ là không bắt buộc và phút với giây là bắt buộc không?

Nếu nguyên mẫu định nghĩa theo thứ tự tất cả chúng ta đã viết bên trên thì không. Tất cả những giá trị không bắt buộc phải nằm ở cuối .
Trình biên dịch sẽ không đồng ý đoạn mã sau

int soGiay(int gio = 0, int phut, int giay);

Giải pháp cho trường hợp này là đổi chỗ những thông số kỹ thuật .

int soGiay(int giay, int phut, int gio = 0);

? Tất cả các thông số đều không bắt buộc thì sao?

Không có yếu tố gì với việc này

int soGiay(int gio = 0, int phut = 0, int giay = 0);

Lúc đó, khi gọi hàm bạn hoàn toàn có thể viết

cout << soGiay() << endl;

Kết quả trả về sẽ là 0 trong trường hợp thử vừa qua .

Những quy tắc cần nhớ

Tổng kết lại thì có 2 quy tắc cần nhớ cho những giá trị mặc định :

  • Các giá trị mặc định chỉ được viết trong nguyên mẫu
  • Các thông số không bắt buộc phải nằm ở cuối của danh sách thông số (bên phải)

Tóm tắt bài hoc :

  • Hàm là một đoạn mã thực hiện 1 xử lý công việc nhất định.
  • Tất cả các chương trình đều có hàm main(). Đó là điểm bắt đầu của chương trình.
  • Chia chương trình thành các hàm khác nhau xử lý các công việc khác nhau cho phép việc sắp xếp công việc dễ hơn.
  • 1 hàm có thể được gọi nhiều lần trong chương trình.
  • 1 hàm có thể nhận các dữ liệu đầu vào (thông số) và trả về 1 giá trị nhờ lệnh return.
  • Các hàm nhận vào các thông số qua tham chiếu có thể thay đổi trực tiếp nội dung ô nhớ của thông số đó.
  • Khi mà chương trình lớn, khuyến khích chia nhỏ ra thành các tệp chứa các nhóm hàm. Các tệp .cpp chứa định nghĩa của các hàm và các tệp .h chứa nguyên mẫu của chúng.Các tệp .h cho phép thông báo sự tồn tại của các hàm cho những tệp khác của chương trình.