Bài giảng Kỹ thuật lập trình - Chương 3: Các kỹ thuật xây dựng chương trình phần mềm (Phần 3) - Vũ Thị Hương Giang
a. Hàm định nghĩa sẵn
Được định nghĩa trong các thư viện
• Cần khai báo thư viện ở đầu chương trình để có thể dùng các hàm này
Ví dụ: trong thư viện cmath, hàm sqrt tính căn bậc hai của một số
the_root = sqrt(9.0);
-
9.0 : tham số, cũng có thể là một biến hoặc là một biểu thức
the_root : biến lưu kết quả trả về (3.0)
sqrt(9.0) : lời gọi hàm (kích hoạt việc thực hiện hàm sqrt), cũng có thể được sử dụng như một biểu thức
bonus = sqrt(sales) / 10;
cout
<< “Cạnh của hình vuông có diện tích “
<< area
<< “ là
<< sqrt(area);
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Kỹ thuật lập trình - Chương 3: Các kỹ thuật xây dựng chương trình phần mềm (Phần 3) - Vũ Thị Hương Giang", để tải tài liệu gốc về máy hãy click vào nút Download ở trên.
File đính kèm:
- bai_giang_ky_thuat_lap_trinh_chuong_3_cac_ky_thuat_xay_dung.pdf
Nội dung text: Bài giảng Kỹ thuật lập trình - Chương 3: Các kỹ thuật xây dựng chương trình phần mềm (Phần 3) - Vũ Thị Hương Giang
- CHƯƠNG III. CÁC KỸ THUẬT XÂY DỰNG CHƯƠNG TRÌNH PHẦN MỀM I. Mở đầu II. Làm việc với biến III. Viết mã chương trình hiệu quả IV. Thiết kế chương trình V. Xây dựng hàm/thủ tục
- 1. Một số khái niệm thường gặp • Biểu thức (expression): tính toán giá trị đích dựa trên giá trị nguồn • Lệnh gán (assigment): lưu trữ giá trị của biểu thức hoặc của biến nguồn vào trong 1 biến đích
- b. Hàm do LTV định nghĩa • Khai báo hàm: – Chỉ ra cách thức gọi hàm – Phải khai báo trước khi gọi hàm – Cú pháp Kiểu_trả_về Tên_hàm (Kiểu_1 tên_tham_số_1, , Kiểu_n tên_tham_số_n); Kiểu_trả_về Tên_hàm (Kiểu_1, , Kiểu_n); //Chú thích: hàm dùng để làm gì • Ví dụ: khai báo hàm cho phép tính tổng chi phí theo công thức: tổng chi phí = số lượng hàng * giá mỗi mặt hàng + 5% thuế giá trị gia tăng double total_cost(int number_par, double price_par); double total_cost(int, double);
- c. Gọi hàm • Tên_hàm(tham_số_1, , tham_số_n) • Giá trị được truyền vào các tham số của hàm sẽ được sử dụng trong phần thân hàm. – Pass by value: giá trị truyền vào là bản sao của giá trị lưu trữ trong biến đóng vai trò tham số đầu vào • Giá trị tham số không thay đổi khi được sử dụng trong thân hàm • Gọi hàm tham trị loại bỏ các thay đổi ngoài ý muốn lên các tham số – Pass by reference: giá trị truyền vào là địa chỉ của tham số đầu vào • Giá trị tham số có thể thay đổi khi được sử dụng trong thân hàm, do truyền biến gốc chứ không phải bản sao • Thay đổi giá trị của đối số trong hàm sẽ ảnh hưởng hoặc thay đổi trực tiếp lên biến gốc
- Viết hàm để hoán đổi giá trị hai biến? void trao_doi(int so1, int so2) { int temp; temp = so1; so1 = so2; so2 = temp; } void sap_xep_mang(int *a, int n) { int i, j, temp; for (i=0; i<n-1; i++) for (j=i+1; j<n; j++) if (a[i] < a[j]) trao_doi(a[i], a[j]); }
- d. Biến tham chiếu trong C++ • Cú pháp: – kieuDL &ten_bien; • Bí danh của biến khác – Thay đổi biến tham chiếu (bí danh) sẽ làm thay đổi giá trị của biến được tham chiếu • Ví dụ: – int count = 1; – int &ref = count; //ref là bí danh của count – ++ref; //tăng count lên 1,sử dụng bí danh ref
- Ví dụ - Hàm tham chiếu #include int x = 4; int & myFunc() { return x; } int main() { cout<<"X="<<X<<endl; cout<<"X="<<MyFunc()<<endl; myFunc() = 20; //nghĩa là x = 20 cout<<"X="<<X<<endl; return 0; }
- 2. Nguyên tắc chung • Tuân thủ các quy tắc đặt ra. – Chỉ nên vi phạm 1 quy tắc nếu được đền bù bằng thứ đáng để mạo hiểm • Phong cách lập trình cần phải nhất quán. – Nếu bạn chấp nhận một quy tắc như cách thức đặt tên hàm hay biến, hằng thì hãy tuân thủ nó trong toàn bộ chương trình. • Mỗi chương trình con (CTC) phải có một nhiệm vụ rõ ràng. – Có 2 loại CTC: functions và procedures. Functions chỉ nên tác động tới duy nhất 1 giá trị - giá trị trả về của hàm – Một CTC phải đủ ngắn để người đọc có thể nắm bắt được chức năng của nó: tính toán, đánh giá hay biến đổi dữ liệu – Tối thiểu hóa số các tham số của CT con. • > 6 tham số cho 1 CTC là quá nhiều
- a. Đặt tên • Tên hàm / thủ tục: – Phải ngắn gọn và có tính chất gợi nhớ – Phải là động từ hoặc cụm động từ – Bắt đầu bằng chữ in hoa/in thường một cách thống nhất • Tên tham số: bất kỳ tên hợp lệ nào – Không nên trùng với tên của các biến trong CT
- c. Che giấu thông tin • Thiết kế các hàm/thủ tục dưới dạng các hộp đen chính là ví dụ của việc che giấu thông tin – Hàm được sử dụng mà không cần biết nó được viết như thế nào – Thân hàm được giấu đi • Điều này cho phép LTV – Thay đổi hoặc nâng cao hiệu quả của hàm bằng cách viết lại phần định nghĩa hàm – Đọc phần khai báo hàm và các chú thích tương ứng để biết cách sử dụng hàm
- Các biện pháp tăng tốc độ • Có thể tăng tốc độ bằng cách sử dụng thêm bộ nhớ ( mảng ). • Dùng thêm các dữ liệu có cấu trúc: – Thời gian cho các phép toán thông dụng có thể giảm bằng cách sử dụng thêm các cấu trúc dữ liệu với các dữ liệu bổ xung hoặc bằng cách thay đổi các dữ liệu trong cấu trúc sao cho dễ tiếp cận hơn. • Lưu các kết quả được tính trước: – Thời gian tính toán lại các hàm có thể giảm bớt bằng cách tính toán hàm chỉ 1 lần và lưu kết quả, những yêu cầu sau này sẽ được xử lý bằng cách tìm kiếm từ mảng hay danh sách kết quả thay vì tính lại hàm. • Caching: – Dữ liệu thường dùng cần phải dễ tiếp cận nhất, luôn hiện hữu. • Lazy Evaluation: – Không bao giờ tính 1 phần tử cho đến khi cần để tránh những sự tính toán không cần thiết.
- 3.2. Loại bỏ những biểu thức thông thường • Đừng tính cùng một biểu thức nhiều lần! • Một số compilers có thể nhận biết và xử lý. for (i = 1; i<=10; i++) x += strlen(str); Y = 15 + strlen(str); len = strlen(str); for (i = 1; i<=10; i++) x += len; Y = 15 + len;
- 3.4. Sử dụng các vòng lặp hợp lý • Những điểm nóng - Hot spots trong phần lớn các chương trình đến từ các vòng lặp. • Đưa code ra khỏi các vòng lặp: – Thay vì thực hiện việc tính toán trong mỗi lần lặp, tốt nhất thực hiện nó chỉ một lần bên ngoài vòng lặp- nếu được. • Kết hợp các vòng lặp – Loop fusion: – Nếu 2 vòng lặp gần nhau cùng thao tác trên cùng 1 tập hợp các phần tử thì cần kết hợp chung vào 1 vòng lặp. • Kết hợp các phép thử - Combining tests: – Trong vòng lặp càng ít kiểm tra càng tốt và tốt nhất chỉ một phép thử. LTV có thể phải thay đổi điều kiện kết thúc vòng lặp. “Lính gác” hay “Vệ sĩ” là một ví dụ cho quy tắc này. • Loại bỏ các vòng lặp: – Với những vòng lặp ngắn thì cần loại bỏ vòng lặp, tránh phải thay đổi và kiểm tra điều kiện lặp
- Sử dụng “lính canh” . • Ý tưởng chung – Đặt giá trị cần tìm vào cuối xâu – Luôn tìm thấy ! – Nhưng nếu vị trí > size => không tìm thấy size = strlen(s); strcat(s, searchValue); pos = 0; while (s[pos] != searchValue) do pos++; if (pos >= size) tim =0 else tim = 1; Có thể làm tương tự với mảng, danh sách
- Không dùng các vòng lặp ngắn for (i =j; i<= j+3;i++) sum += q*i -i*7; i = j; sum += q*i -i*7; i ++; sum += q*i -i*7; i ++; sum += q*i-i*7;
- Inline functions #include #include using namespace std; inline double hypothenuse (double a, double b) { return sqrt (a * a + b * b); } int main () { double k = 6, m = 9; // 2 dòng sau thực hiện như nhau: cout << hypothenuse (k, m) << endl; cout << sqrt (k * k + m * m) << endl; return 0; }
- Ví dụ: Giảm thời gian tính toán • Trong mô phỏng Neural Network người ta thường dùng hàm có tên sigmoid • Với x dương lớn ta có sigmoid(x) 1 • Với x âm “lớn” sigmoid (x) 0 1 sigmoid(x) 1 e kx
- Tính Sigmoid – Giải pháp • Thay vì tính hàm mọi lúc x sigmoid(x ) – Tính hàm tại N điểm và xây dựng 0 0 1 mảng. x1 sigmoid(x0) – Chọn số các điểm (N = 1000, x2 sigmoid(x0) 10000, v.v.) tùy theo độ chính xác x3 sigmoid(x0) mong muốn x4 sigmoid(x0) • Tốn kém thêm không gian bộ nhớ x sigmoid(x ) cho mỗi điểm là 2 float hay double 5 0 tức là 8 – 16 bytes/ điểm x6 sigmoid(x0) . – Khởi tạo giá trị cho mảng khi bắt . đầu thực hiện . x99 sigmoid(x99)
- Kết quả • Nếu dùng exp(x) : – Mỗi lần gọi mất khoảng 300 nanoseconds với 1 máy Pentium 4 tốc độ 2 Ghz. • Dùng tìm kiếm trên mảng và nội suy tuyến tính : – Mỗi lần gọi mất khoảng 30 nanoseconds • Tốc độ tăng gấp 10 lần – Đổi lại phải tốn kếm thêm từ 64K to 640 K bộ nhớ.
- 4. Chồng/đa năng hóa hàm/toán tử • Với C++ – chúng ta có thể đa năng hóa/chồng các hàm và các toán tử – Đ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. • Với C, tên hàm phải là duy nhất
- Ví dụ #include #include int myAbs(int X) { return abs(X); } long myAbs(long X) { return labs(X); } double myAbs(double X) { return fabs(X); } int main() { int X = -7; long Y = 200000l; double Z = -35.678; cout<<"Tri tuyet doi cua so nguyen "<<X <<" la " <<myAbs(X)<<endl; cout<<"Tri tuyet doi cua so nguyen "<<Y <<" la " <<myAbs(Y)<<endl; cout<<"Tri tuyet doi cua so thuc "<<Z <<" la " <<myAbs(Z)<<endl; return 0; }
- Ví dụ trong C #include struct SP {double THUC; double AO; } ; SP setSP(double R,double I); SP addSP(SP C1,SP C2); SP subSP(SP C1,SP C2); void displaySP(SP C); int main(void) { SP C1,C2,C3,C4; C1 = SetSP(1.0,2.0); C2 = SetSP(-3.0,4.0); cout <<"\nSo phuc thu nhat:"; displaySP(C1); cout << "\nSo phuc thu hai:"; displaySP(C2); C3 = AddSP(C1,C2); C4 = SubSP(C1,C2); cout <<"\nTong hai so phuc nay:"; displaySP(C3); cout << "\nHieu hai so phuc nay:"; displaySP(C4); return 0; }
- Đa năng hóa toán tử trong C++ • Trong ví dụ C trên, ta dùng hàm để cài đặt các phép toán cộng và trừ hai số phức phức tạp, không thoải mái, tự nhiên khi sử dụng, 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. • 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ử • Cú pháp định nghĩa toán tử: kieuDL operator kyHieuToanTu(danhSachThamSo) { }
- Ví dụ - Bài toán số phức trong C++ (2) setSP(double r, double i) { SP tmp; tmp.THUC = r; tmp.AO = i; return tmp; } //Cong hai so phuc SP operator +(SP C1, SP C2) { SP tmp; tmp.THUC = C1.THUC+C2.THUC; tmp.AO = C1.AO+C2.AO; return tmp; } //Tru hai so phuc SP operator -(SP C1, SP C2) { SP tmp; tmp.THUC = C1.THUC-C2.THUC; tmp.AO = C1.AO-C2.AO; return tmp; } //Hien thi so phuc void displaySP(SP C) { cout<<"("<<C.THUC<<","<<C.AO<<")"; }