Các mở rộng của C++ so với C
Trang 1Lê Thị Mỹ Hạnh
Khoa CNTT Đại học Bách khoa – Đại học Đà Nẵng
Trang 2 Các từ khóa mới này là :
asm catch class delete friend inline
new operator private protected public
template this throw try virtual
Trang 4Khai bỏo biến
Trong C tất cả các câu lệnh khai báo biến, mảng cục bộ
phải đặt tại đầu khối
vị trí khai báo và vị trí sử dụng của biến có thể ở cách khá xa
nhau, điều này gây khó khăn trong việc kiểm soát ch-ơng trình
C++ đã khắc phục nh-ợc điểm này bằng cách cho phép
các lệnh khai báo biến có thể đặt bất kỳ chỗ nào trong
ch-ơng trình tr-ớc khi các biến đ-ợc sử dụng
Phạm vi hoạt động của các biến kiểu này là khối trong đó
biến đ-ợc khai báo
Trang 5 PhÐp chuyÓn kiÓu nµy cã d¹ng nh- mét hµm sè chuyÓn
kiÓu ®ang ®-îc gäi C¸ch chuyÓn kiÓu nµy th-êng ®-îc sö dông trong thùc tÕ.
Trang 6Vào ra trong C++
Cú pháp: cout << biểu thức 1<< .<< biểu thức N;
Trong đó cout đ-ợc định nghĩa tr-ớc nh- một đối t-ợng biểu diễn cho thiết
bị xuất chuẩn của C++ là màn hình, cout đ-ợc sử dụng kết hợp với toán tử
chèn << để hiển thị giá trị các biểu thức 1, 2, , N ra màn hình.
Cú pháp: cin >>biến 1>> >>biến N;
Toán tử cin đ-ợc định nghĩa tr-ớc nh- một đối t-ợng biểu diễn cho thiết bị vào chuẩn của C++ là bàn phím, cin đ-ợc sử dụng kết hợp với toán tử trích
>> để nhập dữ liệu từ bàn phím cho các biến 1, 2, , N.
Để nhập một chuỗi không quá n ký tự và l-u vào mảng một chiều a (kiểu char) có thể dùng hàm cin.get nh- sau: cin.get(a,n);
Toán tử nhập cin>> sẽ để lại ký tự chuyển dòng ’\n’ trong bộ đệm Ký tự này
có thể làm trôi ph-ơng thức cin.get Để khắc phục tình trạng trên cần dùng ph-ơng thức cin.ignore(1) để bỏ qua một ký tự chuyển dòng.
Để sử dụng các loại toán tử và ph-ơng thức nói trên cần khai báo tập tin dẫn h-ớng iostream.h
Trang 7 Hàm này cần đặt trong toán tử xuất và nó chỉ có hiệu lực cho một giá trị đ-ợc
in gần nhất Các giá trị in ra tiếp theo sẽ có độ rộng tối thiểu mặc định là 0, nh- vậy câu lệnh:
cout<<setw(6)<<“Khoa”<<“CNTT”
sẽ in ra chuỗi “ KhoaCNTT”.
Trang 8Toá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
cout<< "Bien X ben trong = "<<X<<"\n";
cout<< "Bien X ben ngoai = "<<::X<<"\n";
return 0;
}
Trang 9Cấp phỏt và giải phúng bộ nhớ
Trong C để cấp phát bộ nhớ dùng: malloc(), calloc() và để giải phóng bộ nhớ đ-ợc cấp phát dùng hàm free().
C++ đ-a thêm một cách thức mới để thực hiện việc cấp
phát và giải phóng bộ nhớ bằng cách dùng hai toán tử new
và delete.
Trang 10Cấp phỏt và giải phúng bộ nhớ
Toán tử new để cấp phát bộ nhớ
new Tên kiểu ; hoặc new Tên kiểu(Giá trị khởi tạo);
Trong đó Tên kiểu là kiểu dữ liệu của biến con trỏ, nó có thể là: các kiểu dữ liệu chuẩn nh- int, float, double, char, hoặc các kiểu do ng-ời lập trình định nghĩa nh- mảng, cấu trúc, lớp,
Để cấp phát bộ nhớ cho mảng một chiều, dùng cú pháp nh- sau:
Biến con trỏ = new kiểu[n];
Trong đó n là số nguyên d-ơng xác định số phần tử của mảng.
Ví dụ: float *p = new float; //cấp phát bộ nhớ cho biến con trỏ p có kiểu int
int *a = new int[100]; //cấp phát bộ nhớ để l-u trữ mảng một chiều a gồm 100 phần tử
Khi sử dụng toán tử new để cấp phát bộ nhớ, nếu không đủ bộ nhớ để cấp phát, new sẽ trả lại giá trị NULL cho con trỏ Đoạn ch-ơng trình sau minh họa cách kiểm tra lỗi cấp phát bộ nhớ:
exit(0);
}
Trang 11Cấp phỏt và giải phúng bộ nhớ
Toán tử delete để giải phóng bộ nhớ
Toán tử delete thay cho hàm free() của C, nó có cú pháp nh-
Trang 12Hàm inline
Việc tổ chức ch-ơng trình thành các hàm có -u điểm ch-ơng trình
đ-ợc chia thành các đơn vị độc lập, điều này giảm đ-ợc kích th-ớc ch-ơng trình, vì mỗi đoạn ch-ong trình thực hiện nhiệm vụ của hàm
đ-ợc thay bằng lời gọi hàm
Tuy nhiên hàm cũng có nh-ợc điểm là làm là chậm tốc độ thực hiện ch-ơng trình vì phải thực hiện một số thao tác có tính thủ tục mỗi khi gọi hàm nh-: cấp phát vùng nhớ cho các đối số và biến cục bộ,
truyền dữ liệu của các tham số cho các đối, giải phóng vùng nhớ
tr-ớc khi thoát khỏi hàm.
C++ cho khả năng khắc phục đ-ợc nh-ợc điểm nói trên bằng cách dùng hàm nội tuyến Để biến một hàm thành hàm nội tuyến ta viết
thêm từ khóa inline vào tr-ớc khai báo nguyên mẫu hàm
Trang 13int s ;s=f(5,6);
cout<<s;
getch();
}
Trang 14Biến tham chiếu
Trong C có 2 loại biến là:
Biến giá trị dùng để chứa dữ liệu (nguyên, thực, ký tự, ) và biến con trỏdùng để chứa địa chỉ
Các biến này đều đ-ợc cung cấp bộ nhớ và có địa chỉ
C++ cho phép sử dụng loại biến thứ ba là biến tham chiếu
Biến tham chiếu là một tên khác (bí danh) cho biến đã định nghĩa tr-ớc
đó
Cú pháp khai báo biến tham chiếu nh- sau:
Kiểu &Biến tham chiếu = BiếnHằng;
Biến tham chiếu có đặc điểm là nó đ-ợc dùng làm bí danh cho một biến (kiểu giá trị) nào đó và sử dụng vùng nhớ của biến này
Trang 15Biến tham chiếu
Ví dụ: Với câu lệnh: int a, &tong=a; thì tong là bí danh của biến a và biến tong dùng
chung vùng nhớ của biến a Lúc này, trong mọi câu lệnh, viết a hay viết tong đều có ý
nghĩa nh- nhau, vì đều truy nhập đến cùng một vùng nhớ Mọi sự thay đổi đối với biến
tong đều ảnh h-ởng đối với biến a và ng-ợc lại.
Ví dụ: int a, &tong =a;
tong =1; //a=1 cout<< tong; //in ra số 1 tong++; //a=2 ++a; //a=3 cout<<tong; //in ra số 3
Chú ý:
Trong khai báo biến tham chiếu phải chỉ rõ tham chiếu đến biến nào
Biến tham chiếu có thể tham chiếu đến một phần tử mảng, nh-ng không cho phép khai báo mảng tham chiếu.
Biến tham chiếu có thể tham chiếu đến một hằng Khi đó nó sử dụng vùng nhớ của hằng và có thể làm thay đổi giá trị chứa trong vùng nhớ này.
Biến tham chiếu th-ờng đ-ợc sử dụng làm đối của hàm để cho phép hàm truy nhập đến các tham biến trong lời gọi hàm
Trang 16Hằng tham chiếu
Cú pháp khai báo hằng tham chiếu nh- sau:
const Kiểu dữ liệu &Hằng = Biến/Hằng;
const int &m = n;
const int &p = 123;
Hằng tham chiếu có thể tham chiếu đến một biến hoặc một hằng
Biến tham chiếu và hằng tham chiếu khác nhau ở chỗ: không cho phép dùng hằng tham chiếu để làm thay đổi giá trị của vùng nhớ mà nó tham chiếu.
Ví dụ: int y=12, z;
const int &p = y //Hằng tham chiếu p tham chiếu đến biến y
p = p + 1; //Sai, trình biên dịch sẽ thông báo lỗi
Hằng tham chiếu cho phép sử dụng giá trị chứa trong một vùng nhớ, nh-ng không cho phép thay đổi giá trị này.
Hằng tham chiếu th-ờng đ-ợc sử dụng làm đối số của hàm để cho phép sử dụng giá trị của các tham số trong lời gọi hàm, nh-ng tránh làm thay đổi giá trị tham số.
Trang 17Truyền tham số cho hàm theo tham chiếu
Trong C chỉ có một cách truyền dữ liệu cho hàm là truyền theo theo giá trị
Ch-ơng trình sẽ tạo ra các bản sao của các tham số thực sự trong lời gọi hàm và sẽ thao tác trên các bản sao này chứ không xử lý trực tiếp với các tham số thực sự
Cơ chế này rất tốt nếu khi thực hiện hàm trong ch-ơng trình
không cần làm thay đổi giá trị của biến gốc
Tuy nhiên, nhiều khi ta lại muốn những tham số đó thay đổi khi thực hiện hàm trong ch-ơng trình
C++ cung cấp thêm cách truyền dữ liệu cho hàm theo tham
chiếu bằng cách dùng đối là tham chiếu
Cách làm này có -u diểm là không cần tạo ra các bản sao của các tham số, do dó tiết kiệm bộ nhớ và thời gian chạy máy
Mặt khác, hàm này sẽ thao tác trực tiếp trên vùng nhớ của các tham số, do đó dễ dàng thay đổi giá trị các tham số khi cần.
Trang 18Truyền tham số theo tham chiếu – Ví dụ
void Hoanvi(double &x,double &y)
Trang 19 Trong tr-ờng hợp này biểu thức đ-ợc trả lại trong câu lệnh return phải là tên của một
biến xác định từ bên ngoài hàm, bởi vì khi đó mới có thể sử dụng đ-ợc giá trị của hàm
Khi ta trả về một tham chiếu đến một biến cục bộ khai báo bên trong hàm, biến cục
bộ này sẽ bị mất đi khi kết thúc thực hiện hàm Do vậy tham chiếu của hàm sẽ không còn ý nghĩa nữa Vì vậy, nếu hàm trả về là tham chiếu đến biến cục bộ thì biến cục
bộ này phải khai báo static.
Trang 20Hàm trả về là một tham chiếu
Khi giá trị trả về của hàm là tham chiếu, ta có thể gặp các câu lệnh gán hơi khác th-ờng, trong đó vế trái là một lời gọi hàm chứ không phải là tên của một biến
Trang 21Hàm với đối số mặc đinh
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
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
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
Trang 22Hàm với đối số mặc định
Quy tắc xây dựng hàm với đối số mặc định nh- sau:
Các đối có giá trị mặc định cần là các đối số cuối cùng tính từ trái qua phải
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
Nếu ch-ơng trình sử dụng khai báo nguyên mẫu hàm thì các đối số mặc định cần đ-ợc khởi gán trong nguyên mẫu hàm, không đ-ợc khởi gán khởi gán lại cho các đối mặc định trong dòng đầu của định nghĩa hàm
Khi xây dựng hàm, nếu không khai báo nguyên mẫu, thì các đối mặc định
đ-ợc khởi gán trong dòng đầu của định nghĩa hàm
Đối với các hàm có đối số mặc định thì lời gọi hàm cần viết theo quy định: Các tham số vắng mặt trong lời gọi hàm t-ơng ứng với các đối số mặc định cuối cùng (tính từ trái sang phải),
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
Trang 23Đ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
Có hai hình thức đa năng hóa:
Đa năng hóa hàm
Đa năng hóa toán tử
Trang 24Đa năng hóa hàm(Function 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
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 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
Như vậy, 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 Myabs(int i);
long Myabs(long l);
double Myabs(double d);
Trang 25Đa năng hóa hàm(Function Overloading)
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,
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 hàm đa năng hóa :
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)
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
Trang 26Đa năng hóa toán tử (Operators overloading)
Trong C, khi tạo ra một kiểu dữ liệu mới, để 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
Complex SetComplex(double R,double I);
Complex AddComplex(Complex C1,Complex C2);
Complex SubComplex(Complex C1,Complex C2);
=> C3 = AddComplex(C1,C2); //Hơi bất tiện !!!
C4 = SubComplex(C1,C2);
Điều này trở nên không thoải má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
Trang 27Đa năng hóa toán tử (Operators overloading)
Để 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ử
Ví dụ:
Complex operator + (Complex C1,Complex C2);
Complex operator - (Complex C1,Complex C2);
=> C3 = C1 + C2;
C4 = C1 - C2;
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;
Trang 28Đa năng hóa toán tử (Operators overloading)
operator_symbol: Ký hiệu của toán tử
parameters: Các tham số (nếu có).
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:
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.
Trang 29Đa năng hóa toán tử (Operators overloading)
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à :
:: 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á:
+ - * / % ^ ! = < > += -=
^= &= |= << >> <<= <= >= && || ++
() [] new delete & | ~ *= /= %= >>= == != , -> ->*