Cấu chúc chung của hàmcần viết tên tham số ▪ Vẫn phải viết kiểu trả về và tên hàm.. Quy tắc▪ Mô tả đủ thông tin để có thể phát lời gọi hàm ▪ Phải viết trước bất kỳ lời gọi hàm nào ▪ Phải
Trang 1LẬP TRÌNH NÂNG CAO
Bài 1: Giới thiệu môn học và nhắc lại
kiến thức C/C++
Trang 2Nội dung chính
Trang 3Giới thiệu môn học
Phần 1
Trang 4Giáo trình & Giờ học
▪ Visual Studio Code
Trang 5Nội dung giảng dạy
3 Xâu ký tự và các phép toán trên xâu
Trang 6Nội dung giảng dạy
Trang 7Nội dung giảng dạy
Trang 8Mục tiêu của môn học
niệm nền tảng của lập trình
Trang 9Tại sao phải học môn này?
các môn lập trình khác
Trang 10Thi & Tính điểm
▪ Điểm quá trình (50%):
• Điểm chuyên cần
• Điểm kiểm tra giữa kỳ (2 đầu điểm)
▪ Điểm kiểm tra cuối kì (50%, thi thực hành, máy chấm tự động)
thực hành trên máy, chú trọng vào viết chương trình,
không có lý thuyết học thuộc
truongxuannam@gmail.com
Trang 11Một vài chú ý khác
hành)
https://txnam.net mục BÀI GIẢNG
ghi chép nhiều trong giờ lý thuyết
Trang 12Nhắc lại kiến thức C/C++
Phần 2
Trang 13Ôn luyện kiến thức C/C++
▪ Biến
▪ Phép toán (+, -, *, /, %, &, |, ^, !, &&, ||, ~, <<, >>, )
Trang 14Ôn luyện kiến thức C/C++
Trang 15Ôn luyện kiến thức C/C++
Trang 16Ôn lại kiến thức
Trang 17Bài tập
Phần 3
Trang 18Bài tập
Trang 19LẬP TRÌNH NÂNG CAO
Bài 2+3: Hàm trong C/C++
Trang 20Nội dung chính
8 Bài tập
Trang 21Cấu chúc chung của hàm
Phần 1
Trang 22Cấu chúc chung của hàm
▪ Thông qua tên
Trang 23Cấu chúc chung của hàm
của file hoặc tách riêng thành một file (gọi là file header)
Trang 24Cấu chúc chung của hàm
cần viết tên tham số
▪ Vẫn phải viết kiểu trả về và tên hàm Riêng phần tham
số chỉ cần viết kiểu và bỏ qua phần tên
Trang 25Cấu chúc chung của hàm
cần viết tên tham số
trên viết một đằng ở dưới viết một nẻo vẫn được chấp nhận
Trang 26Cấu trúc của một chương trình C/C++
Trang 27Quy tắc
▪ Mô tả đủ thông tin để có thể phát lời gọi hàm
▪ Phải viết trước bất kỳ lời gọi hàm nào
▪ Phải có kiểu trả về của hàm
▪ Phải có kiểu của từng tham số
▪ Không nhất thiết phải có tên tham số
▪ double mu_x (int, double);
▪ double mu_x (int a, double d);
▪ d = mu_x (3, 0.5);
▪ Những giá trị thực sự được dùng trong lời gọi hàm được gọi là đối số (argument) hoặc tham số thực (actual parameter)
Trang 28▪ Còn gọi là các tham số hình thức (formal parameter)
▪ Trả về kết quả thông qua lệnh return
màn hình), thì khai báo kiểu void và không cần return nữa
▪ double mu_x (int a, double d) {
double k = 1 ;
k *= d;
return k }
Trang 29Thảo luận
▪ Tái sử dụng : Mã được viết một lần, sử dụng nhiều lần
▪ Giảm chi phí : Sửa lỗi, nâng cấp ở một đoạn mã
▪ Dễ phát triển : Chia chương trình phức tạp thành nhiều đơn
thể, giảm độ phức tạp khi viết các khối mã
▪ Phát triển song song
(functional abstraction)
Trang 30Hiểu về cách hàm hoạt động
Phần 2
Trang 32Các hàm có sẵn
Phần 3
Trang 33Các hàm có sẵn
cấp cho chúng ta sử dụng
▪ Thư viện thường gồm 2 loại file:
• File header: chỉ chứa các khai báo hàm (.h, hpp hoặc không đuôi)
• File source: chứa phần thân hàm (.c, cpp)
▪ Khai báo thư viện thông qua phát biểu #include
▪ Phát biểu #include phải chỉ ra file header sẽ sử dụng
▪ #include <iostream> ← tìm file trong thư mục chuẩn
▪ #include "mylib" ← tìm file trong thư mục hiện tại
Trang 34Các hàm có sẵn
Trang 35Các hàm có sẵn
Trang 36Tạo số ngẫu nhiên
▪ Tạo các tình huống ngẫu nhiên trong chương trình, trò chơi
▪ Tạo các biến ngẫu nhiên trong tính toán khoa học
▪ Chỉ là giả-ngẫu-nhiên
▪ rand () : trả về giá trị nguyên giữa 0 & RAND_MAX
• RAND_MAX tùy thuộc vào từng thư viện và trình biên dịch
• Trả về số ngẫu nhiên giữa 0 & 5
• Trả về số ngẫu nhiên giữa k & 5+k
▪ Khởi tạo nhân cho việc tạo số ngẫu nhiên: srand ( time ( 0 ))
Trang 37Phạm vi của biến và của hàm
Phần 4
Trang 38Quy tắc
nào) thì chỉ được truy cập bên trong khối đó
▪ Biến không nằm trong bất kỳ cặp ngoặc nào: biến toàn cục
(global variable)
▪ Biến nằm trong hàm: biến cục bộ (local variable)
▪ Có thể truy cập từ bất kỳ đâu trong chương trình
▪ Chú ý: một chương trình có thể gồm nhiều file
▪ Có thể truy cập biến ở trong file khác: từ khóa extern
▪ Rất cẩn thận khi sử dụng
Trang 39Từ khóa static
biến cục bộ
trong phạm vi của file hiện tại
truy cập trong phạm vi của file hiện tại
không bị hủy đi khi kết thúc hàm
▪ Chỉ khởi tạo một lần
▪ Hàm lần sau sử dụng giá trị còn tồn lại từ lần gọi trước
Trang 40// hàm module, chỉ gọi được từ đoạn mã cùng file
static void dosmt ( bool a ) {
bool b = true ; // biến cục bộ, phạm vị hàm
// hàm toàn cục, định nghĩa hàm được viết ở file khấc
extern int somefuction ( int n );
Trang 41Ví dụ về biến static, hãy chạy thử xem nào!
Trang 42Truyền tham số trong hàm
Phần 5
Trang 43③Khai báo biến k = 1
⑤Trả về kết quả qua lệnh
return và thoát khỏi hàm
Vấn đề: Biến a và d là biến của hàm, việc thay đổi giá trị chỉ có tác dụng nội bộ
Trang 45Cách giải quyết: tham chiếu (&)
▪ Alias (nickname) của một biến khác
▪ Khai báo như biến, nhưng thêm dấu & vào trước tên biến
Trang 46Ví dụ viết lại bằng tham chiếu
Trang 47Tham chiếu có ưu điểm và có điểm bất tiện
tham chiếu luôn có kích thước 4 byte – tùy OS)
biến, không làm việc với
dữ liệu trực trị (giá trị viết trực tiếp vào đối số)
biến cục bộ có thể gây những lỗi bộ nhớ
Trang 48Quy tắc chung
▪ Muốn thay đổi giá trị đối số thì hãy sử dụng tham chiếu (thêm dấu & vào trước tên biến)
thể sử dụng tham chiếu
▪ Muốn ngăn chặn việc vô ý thay đổi dữ liệu tham chiếu thì thêm
từ khóa const vào trước tham chiếu
// dùng khi cần thay đổi d
int change (string & d )
// dùng khi cần hàm nhanh hơn
int change (string & d )
// dùng khi cần hàm nhanh hơn và không thay đổi d
int change ( const string & d )
Trang 49Nạp chồng hàm
Phần 6
Trang 50Khái niệm
nhiều hàm cùng tên nhau trong một chương trình
trung bình cộng của 2, 3 hoặc 4 số thực
double trungbinh ( double a , double b ) {
Trang 51Khái niệm
trungbinh2, trungbinh3 và trungbinh4
▪ Làm mất ý nghĩa của tên hàm: trungbinh2 có thể hiểu là trung bình bình phương ?
hàm hợp lý nhất trong số các hàm trùng tên
▪ Dựa trên kiểu dữ liệu của các tham số của hàm
▪ Không dựa trên kết quả trả về của hàm
▪ Cơ chế này gọi là tự động phân giải nạp chồng (automatic
overload resolution)
Trang 52Nạp chồng hàm: tình huống đơn giản
#include <iostream>
using namespace std;
void print ( int a , int b ) { cout << "0" << endl; }
void print ( int a , double b ) { cout << "1" << endl; }
void print ( double a , int b ) { cout << "2" << endl; }
int main () {
print ( 10 , 20 ); // print(int, int)
print ( 0.5 , 100 ); // print(double, int)
print ( 5 , 0.5 ); // print(int, double)
print ( 1.5 , 0.5 ); // Lỗi
}
Trang 53Nạp chồng hàm: tình huống chuyển đổi kiểu
#include <iostream>
using namespace std;
void print ( long a , long b ) { cout << "0" << endl; }
void print ( long a , double b ) { cout << "1" << endl; }
void print ( double a , long b ) { cout << "2" << endl; }
void print ( double a , double b ) { cout << "3" << endl; }
int main () {
print ( 10 , 20 ); // Lỗi
print ( 0.5 , 100L ); // print(double, long)
print ( 5L , 0.5 ); // print(long, double)
print ( 1.5 , 0.5 ); // print(double, double)
}
Trang 54Nạp chồng hàm: tham số mặc định
#include <iostream>
using namespace std;
double area ( double dai , double rong = 1 ) {
return dai * rong;
}
int main () {
cout << area ( 10 , 20 ) << endl; // dai = 10, rong = 20
cout << area ( 10 ) << endl; // dai = 10, rong = 1
}
Trang 55Nạp chồng hàm giúp thiết kế linh hoạt hơn
Trang 56Hàm đệ quy
Phần 7
Trang 57Khái niệm đệ quy
▪ Đệ quy trực tiếp: gọi lại chính nó ngay trong thân hàm
▪ Đệ quy gián tiếp: gọi lại chính nó thực hiện trong các hàm con
phổ biến trong toán học, tin học, vật lý,
▪ Giải bài toán với trường hợp nhỏ nhất
▪ Giải bài toán lớn dựa trên lời giải từ bài toán con
Trang 59Hàm đệ quy thực hiện như thế nào?
Trang 60Hàm đệ quy thực hiện như thế nào?
Trang 61Bài tập
Phần 8
Trang 62Bài tập
Trang 63Bài tập
Trang 64Bài tập
𝐶𝑛𝑘
Trang 65LẬP TRÌNH NÂNG CAO
Bài 4+5+6 : Kiểu dữ liệu mảng và xâu ký
tự trong C/C++
Trang 662 Các phép toán trên xâu kí tự
3 Các bài toán cơ bản với kiểu xâu kí tự
4 Xâu kí tự vs Chuỗi (string)
Trang 67Kiểu dữ liệu mảng
Phần 1
Trang 681.1 Khái niệm và khai báo
▪ Chỉ số là số tự nhiên, luôn bắt đầu từ 0
▪ Là giải pháp cho phép lưu trữ một dãy các biến tương đương, thay vì phải chỉ ra từng biến một
Trang 691.1 Khái niệm và khai báo
▪ Kích cỡ (số phần tử) được xác định ngay khi khai báo (thường phải là hằng số)*
▪ Kích cỡ không thể thay đổi
▪ Sẽ là một khối nhớ liên tục chứa các biến
Trang 701.1 Khái niệm và khai báo: một số lỗi hay gặp
▪ int a[]; => int a[ 100 ];
▪ int n1 = 10 ; int a[n1]; => int a[ 10 ];
▪ const int n2 = 10 ; int a[n2]; => int a[ 10 ];
Trang 71// mảng số nguyên hai chiều 3 hàng x 4 cột
// chú ý cách khởi tạo dữ liệu
int mang22 [ 3 ][ 4 ] = {
{ 1 , 2 , 3 , 4 }, { 5 , 6 , 7 }, { 8 , 9 , 10 } };
Trang 721.2 Mảng nhiều chiều
biến nằm liên tiếp thành một khối trong bộ nhớ
int mang22 [ 3 ][ 4 ] = { 1 , 2 , 3 , 4 , 5 , 6 };
Trang 73▪ Không an toàn khi sử dụng
trong hàm (thay đổi giá trị)
Trang 741.4 Hàm với tham số kiểu mảng
#include <iostream>
#include <vector>
using namespace std;
typedef int Mang [ 100 ]; // định nghĩa kiểu mảng
void change (Mang x ) {
Trang 751.5 Vòng lặp phạm vi
mới bằng option sau: menu => Tools => Compiler Options
“-static-libgcc -std=c++11”
Trang 761.5 Vòng lặp phạm vi
for ( auto & x : mang21) {
for ( auto & y : x) cout << y << " ";
cout << endl;
}
Trang 771.6 Các bài toán cơ bản với kiểu mảng
▪ Danh sách điểm số, sinh viên, => mảng 1 chiều
▪ Âm thanh số hóa => mảng 1 chiều
▪ Hình ảnh số hóa => mảng 2 chiều
▪ Dữ liệu tài nguyên, đất đai, không gian, => mảng 3 chiều
Trang 781.6 Các bài toán cơ bản với kiểu mảng
các vấn đề cơ bản
▪ sort : sắp xếp một dãy
▪ find : tìm kiếm trong dãy
▪ binary_search : kiểm tra xem có phần tử trong đoạn tăng dần hay không
▪ lower_bound : trả về vị trí của phần tử đầu tiên không bé hơn phần tử cần tìm
▪ upper_bound : trả về vị trí của phần tử đầu tiên lớn hơn phần
tử cần tìm
Trang 79Kiểu xâu kí tự
Phần 2
Trang 802.1 Khái niệm và khai báo
▪ 1 byte, char (- 128 127) hoặc unsigned char (0 255)
▪ Kiểu dữ liệu số nguyên
▪ Viết trong cặp ngoặc đơn: 'a' 'x' '&’
▪ Hoặc viết giá trị mã của chữ
▪ Tức là hai cách viết dưới đây hoàn toàn như nhau:
Trang 812.1 Khái niệm và khai báo
Trang 822.1 Khái niệm và khai báo
char str [ 4 ] = "C++";
char str[] = {'C','+','+',' \0 '};
char str [ 4 ] = {'C','+','+',' \0 ’ };
dùng để biểu diễn kích cỡ khi làm việc với bộ nhớ và kích
cỡ các biến
▪ X có thể là một biểu thức
Trang 832.2 Các phép toán trên xâu kí tự
▪ Thư viện <cstring> (string.h): nhiều hàm kiểu xâu kí tự
Trang 842.3 Các bài toán cơ bản với kiểu xâu kí tự
Trang 85▪ Kiểu dữ liệu sẵn có của C/C++
▪ Không cần thư viện ngoài
▪ Hàm bổ trợ dùng con trỏ
▪ Không an toàn khi sử dụng
trong hàm (thay đổi giá trị)
▪ Phải gán giá trị từng phần tử
khi sao chép mảng
Chuỗi
▪ Kiểu dữ liệu của thư viện std
Xâu kí tự
Trang 86Bài tập
Phần 3
Trang 87Bài tập về mảng
Trang 89Bài tập về xâu kí tự
Trang 90LẬP TRÌNH NÂNG CAO
Bài 7+8+9 : Con trỏ và bộ nhớ trong
C/C++
Trang 92Bộ nhớ máy tính
Phần 1
Trang 93Các kiểu lưu trữ thông tin trên máy tính
Trang 94▪ Một dãy các byte liên tiếp (một mảng byte khổng lồ)
▪ Có thể biết chính xác “địa chỉ” của chúng?
▪ Có thể “tóm” được chúng và đọc / ghi giá trị?
Trang 95Bộ nhớ vật lý và bộ nhớ bảo vệ
Trang 96Bộ nhớ của chương trình C/C++
Trang 97Biến và địa chỉ của biến
Phần 2
Trang 98Biến và địa chỉ của biến
nào đó, vị trí này gọi là địa chỉ (address) của biến
▪ Phép toán địa chỉ: &
▪ Trả về địa chỉ của biến
▪ Thường là một số 32 bit (tùy vào CPU, OS và kiểu chương trình)
int a[] = { 1 , 3 , 2 , 4 , 2 };
cout << &a << endl;
cout << & a [ 0 ] << endl;
cout << & a [ 1 ] << endl;
cout << ( long ) & a [ 2 ] << endl;
▪ Có lấy được địa chỉ của thứ khác trong bộ nhớ không?
Trang 99Biến con trỏ
Phần 3
Trang 100Biến con trỏ
▪ Có, sử dụng biến có kiểu “con trỏ”
int a = 10 ;
int *pa = &a; // con trỏ tới biến a
cout << "A = " << a << endl;
cout << "PA (con tro) = " << pa << endl;
cout << "PA (int) = " << ( int ) pa << endl;
int a, *b, c, **d;
Trang 101Khai báo và khởi tạo con trỏ
int *p1; // con trỏ đến giá trị int
double *p2; // con trỏ đến giá trị thực
bool *p3; // con trỏ đến giá trị logic
int **p4; // con trỏ đến con trỏ kiểu nguyên
int n;
int *p1 = &n; // con trỏ đến n
double *p2; // con trỏ đến đâu???
bool *p3 = NULL ; // con trỏ NULL
▪ NULL là một giá trị đặc biệt, bằng 0 ( nullptr từ C++11)
Trang 102Sử dụng con trỏ
con trỏ sẽ có tốc độ cao hơn do dễ dàng dịch thành các mã máy tương ứng (lý do ngôn ngữ lập trình C/C++ chạy nhanh)
▪ Biết địa chỉ của biến, biết biến đó nằm ở đâu trong bộ nhớ
▪ Thông qua con trỏ, có thể truy cập vào biến để đọc/ghi giá trị
Trang 103Con trỏ làm tham số của hàm: có gì đặc biệt?
Trang 104Quy tắc sử dụng con trỏ
qua khả năng hiểu và sử dụng con trỏ của ứng viên
▪ Hai phép toán đối lập: & và *
▪ Phép & trả về địa chỉ của biến
▪ Phép * trả về biến từ địa chỉ
▪ *pa và a đều chỉ nội dung của biến a
• *pa còn được gọi là truy cập gián tiếp vào a
▪ pa và &a đều là địa chỉ của biến a
Trang 105Phép toán trên con trỏ
▪ Hai con trỏ bằng nhau, trỏ đến cùng một chỗ
Trang 106Phép toán trên con trỏ
Trang 107Phép toán trên con trỏ
▪ Đối ngẫu với phép cộng con trỏ với số nguyên
int *pa = &a, *pb = &b;
cout << pb-pa << endl;
short *ppa = ( short *) pa;
short *ppb = ( short *) pb;
cout << ppb-ppa << endl;
Trang 108Phép toán trên con trỏ
Trang 109Mảng và con trỏ
Phần 4
Trang 110Mảng và con trỏ
điểm giống nhau về cách sử dụng, thậm chí sử dụng có phần lẫn lộn
▪ Vì lý do đó nên một số tài liệu xem mảng là hằng con trỏ (tức là một con trỏ nhưng trỏ đến một vị trí cố định trong bộ nhớ),
điều này không hoàn toàn chính xác
▪ Cách tốt nhất là hãy phân biệt rạch ròi giữa mảng và con trỏ, cho dù chúng có nhiều đặc điểm chung
int a [ 5 ], *p1, *p2;
p1 = a; // p trỏ đến đầu của a, tức a[0]
p2 = & a [ 0 ]; // p trỏ đến a[0]
cout << p1 << endl; // in ra địa chỉ của a
in ra địa chỉ của a[0], giống p1
Trang 111Mảng và con trỏ
int a [ 5 ] = { 1 , 2 , 3 , 4 , 5 }, *p = a;
cout << a [ 2 ] << endl; // 3, bình thường
cout << *(a+ 2 ) << endl; // 3, dùng a như con trỏ
cout << *(p+ 2 ) << endl; // 3, bình thường
cout << p [ 2 ] << endl; // 3, dùng p như mảng
cout << *( 2 +p) << endl; // 3, lạ chưa?
int a [ 5 ], *p = a;
cout << sizeof (a) << endl; // 20
cout << sizeof (p) << endl; // 4
int * a [ 5 ];
int ** p ;
Trang 112Mảng và con trỏ
chính nó:
▪ Bạn có lời giải thích nào không?
void print ( int a [], int n ) {
for ( int i = 0 ; i < n; i++)
for ( int i = 0 ; i < n; i++)
cout << *(a++) << " "; // lỗi
Trang 113Bộ nhớ động
Phần 5
Trang 114Bộ nhớ động
▪ Vùng code : chứa mã thực thi
▪ Vùng data (static memory):
chứa các dữ liệu được khởi
tạo từ ban đầu, thường là
các biến global
▪ Vùng stack : chứa các biến địa
phương
• Vùng này sẽ tăng giảm theo
độ sâu gọi hàm (call stack)
▪ Vùng heap : chứa các biến sẽ
được “cấp phát động”
• Chẳng hạn như dữ liệu của vector, có thể lúc ít phần tử, lúc khác lại chứa rất nhiều phần tử
Trang 115Bộ nhớ động
động” – khi nào cần mới yêu cầu cấp
nào để hiển thị và soạn thảo một file?
▪ Không thể biết trước được, có những file rất ít dữ liệu, có
những file cực nhiều dữ liệu
động, xin bộ nhớ theo nhu cầu của phần mềm
▪ Một số tài liệu nói C cấp phát ở “heap” còn C++ cấp phát ở
“free store”, thực chất hai vùng nhớ này là một
Trang 116Cấp phát động kiểu C: cấp theo khối nhớ
▪ Sử dụng thư viện: <stdlib.h>
▪ malloc (N) – cấp một khối nhớ cỡ N byte
▪ calloc (N, S) – cấp một khối nhớ cỡ N x S byte, điền số 0 vào mọi
ô dữ liệu được cấp phát
▪ free (p) – hủy khối nhớ được cấp cho con trỏ p
▪ realloc (p, S) – chỉnh kích cỡ khối nhớ được cấp bởi con trỏ p thành cỡ S byte, giữ lại dữ liệu cũ đã được khởi tạo
▪ Các hàm cấp phát trả về con trỏ void (void *)
▪ Cấp phát không thành công sẽ trả về con trỏ nullptr