Hướng dẫn phương pháp kiểm tra số nguyên tố trong C++. Bạn sẽ học được cách tạo hàm kiểm tra số nguyên tố trong C++ cũng như cách liệt kê tất cả các số nguyên tố nhỏ hơn n bằng C++ sau bài học này.
1. Số Nguyên Tố Là Gì?
Chúng ta đều biết số nguyên tố là số tự nhiên lớn hơn 1 không phải là tích của hai số tự nhiên nhỏ hơn. Nói cách khác, số nguyên tố là những số chỉ có đúng hai ước số là 1 và chính nó.
Ví dụ, chúng ta xem xét các số 2, 3, 6 như sau:
Số 2 chỉ có đúng hai ước số là 1 và chính nó nên là số nguyên tố
Số 3 chỉ có đúng hai ước số là 1 và chính nó nên là số nguyên tố
Số 6 có 4 ước số là 1, 2 , 3 và 6, do đó nó không phải là số nguyên tố.
Số Nguyên Tố Là Gì?
2. Kiểm Tra Số Nguyên Tố Trong C Dùng While
+ Cấu trúc vòng lặp do while
Cấu trúc vòng lặp do-while là: do { <khối lệnh> } while (<điều kiện>)
Vậy nên khi bạn chuyển từ vòng lặp for sang vòng lặp do while thì chỉ cần thay thế:
Khối lệnh trong do là những gì thực hiện trong vòng lặp for:
if(n%i==0) demuoc++; i++;
Và đừng quên theo sau nó vẫn còn biến i++ nữa nhé, mỗi lần lặp nó sẽ tăng lên 1 đơn vị.
Điều kiện trong while là: điều kiện trong for: i<=n
+ CODE SỐ NGUYÊN TỐ DO WHILE TRONG C++
#include <iostream>
using namespace std;
int main()
{
int i=1,n,demuoc=0;
cout<<"nhap n = ";cin>>n;
do
{
if(n%i==0) demuoc++; i++;
}
while (i<=n);
if (demuoc==2) cout<<"la so nguyen to";
else cout<<"khong la so nguyen to";
}
Kết quả khi chạy chương trình:
nhap n = 11
la so nguyen to
Dãy số nguyên tố <100: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97.
Kiểm Tra Số Nguyên Tố Trong C Dùng While
3. Dùng Đệ Quy Kiểm Tra Số Nguyên Tố
Dưới đây là một chương trình minh họa sử dụng đệ quy trong C. Bạn chú ý, trong thân hàm recurse() có lời gọi hàm tới chính nó => đó là hàm đệ quy.
Vậy 1 chương trình sẽ chạy như thế nào nếu có hàm đệ quy? Bạn hãy xem hình ảnh dưới đây:
Dùng Đệ Quy Kiểm Tra Số Nguyên Tố
Như các bạn có thể thấy, khi một hàm đệ quy được gọi (ở ví dụ trên là hàm main gọi) thì thay vì hàm đó chỉ được thực thi 1 lần thì ở đây bản thân hàm gọi lại chính nó => Nó có thể tự chạy lại chính mình số lần bất kỳ.
Cùng tìm hiểu về kiểu dữ liệu string trong C++. Bạn sẽ biết khái niệm string là gì trong C++, cách khai báo string trong C++, cách khởi tạo string trong C++, cách gán string trong C++ cũng như cách truy xuất ký tự của string. Bạn cũng sẽ biết sự khác biệt giữa mảng ký tự và string trong C++ sau bài học này.
I. String Trong C++ Là Gì
Trong ngôn ngữ C++, có một kiểu dữ liệu gọi là kiểu char (kiểu ký tự) ( char là viết tắt của character). Đây là kiểu dữ liệu có kích thước bộ nhớ là 1 byte. Vì 1 byte là đơn vị nhỏ nhất nên có thể nói char là loại có kích thước bộ nhớ nhỏ nhất.
Khi chúng ta nối những ký tự (char) trong C++ lại với nhau thì sẽ được 1 chuỗi ký tự. Ví dụ như “Hello” là 1 chuỗi ký tự được tạo bởi các ký tự đơn chẳng hạn.
Và chúng ta gọi loại dữ liệu được tạo thành bởi tập hợp những ký tự đơn như vậy là kiểu dữ liệu string trong C++ hay còn gọi là chuỗi string trong C++.
Để xử lý chuỗi string trong C++, chúng ta có thể dùng đến mảng ký tự được kế thừa từ ngôn ngữ C, hoặc là sử dụng tới std::string là một trình bao bọc để xử lý chuỗi thuận tiện mới được thêm vào C++. Về phương pháp sử dụng string trong c++ bằng mảng ký tự thì Kiyoshi đã phân tích rất kỹ trong loạt bài tại chuyên đề Chuỗi trong C rồi.
String Trong C++ Là Gì
II. Nhập String Trong C++
Hướng dẫn cách nhập xuất string trong C++. Bạn sẽ học được cách sử dụng lệnh cin, cout cũng như hàm getline() để nhập xuất string trong C++ sau bài học này.
Nhập xuất string trong C++*
Giống như việc nhập các loại dữ liệu khác, chúng ta sử dụng lệnh cin để nhập vào một string trong C++ với cú pháp sau đây:
string str;
cin >> str
Trong đó dòng đầu tiên dùng để khai báo biến str có kiểu string, và dòng thứ 2 dùng để gán dữ liệu nhập từ bàn phím vào biến str.
Tương tự, chúng ta sử dụng lệnh cout để xuất vào một string trong C++ với cú pháp sau đây:
cout >> str
Ví dụ, chúng ta viết chương trình nhập xuất string trong C++ như sau:
#include <iostream>
#include <cstring>
using namespace std;
int main() {
cout << "Nhap chuoi ky tu: ";
string str;
cin >> str;
cout <<"Chuoi ky tu vua nhap: "<< str;
return 0;
}
Khi đó màn hình nhập dữ liệu sẽ như sau:
Nhap chuoi ky tu: abcdd
Chuoi ky tu vua nhap: abcdd
Nhập nhiều string trong C++
Để nhập nhiều string trong C++ cách nhau bởi dấu cách, chúng ta viết các string nhập vào cách nhau bởi toán tử >>, khi dùng lệnh cin với cú pháp sau đây:
Khi đó màn hình nhập dữ liệu sẽ hiện ra. Chúng ta nhập từng string cách nhau bởi dấu cách từ bàn phím như sau:
Nhap cac chuoi: ab cd123 ef8
Cac chuoi vua nhap: ab cd123 ef8
Nhập String Trong C++
III. Các Hàm String Trong C++
Thư viện <string> rất nhiều hàm cho phép xử lý chuỗi. Ví dụ: tìm chiều dài chuỗi, so sánh hai chuỗi, tìm kiếm/rút trích chuỗi con, nối chuỗi,…
Hàm s.length()
Trả về số lượng ký tự trong string s.
string s1 = "introduction to programming";
cout<<"length of s1 = "<<s1.length();//27
Hàm s.substr(x, y)
Rút trích một chuỗi con với chiều dài y bắt đầu tại vị trí x. Nếu không có y, một chuỗi con từ vị trí x tới cuối chuỗi sẽ được rút trích.
string s1 = "introduction to programming";
cout<<"substring has 10 characters of s1 start at 5:"<<s1.substr(5, 10);
//Kết quả: substring has 10 characters of s1 start at 5:duction to
Hàm s.find(r)
Kiểm tra chuỗi string r có xuất hiện trong chuỗi s hay không. Nếu có thì trả về vị trí bắt đầu xuất hiện chuỗi r trong chuỗi s.
string s1 = "introduction to programming";
cout<<"position of 'duc' string in s1:"<<s1.find("duc");//5
Hàm s.erase(x, n)
Xóa n ký tự bắt đầu tại vị trí x.
string s1 = "introduction to programming";
s1.erase(5, 15);
cout<<"s1 string after erase:"<<s1;//s1 string after erase:introramming
Hàm s.replace(x, n, str)
Thay thế n ký tự tại vị trí bắt đầu là x bằng chuỗi str. Lưu ý: chiều dài của str có thể lớn hơn n.
string s1 = "introduction to programming";
s1.replace(5, 10, "123123123");
cout<<"s1 string after replace:"<<s1;//s1 string after replace:intro123123123 programming
Hàm s1.compare(s2)
So sánh chuỗi s1 với s2. Giá trị trả về là -1 nếu s1 < s2, bằng 0 nếu s1 == s2, là 1 nếu s1 > s2.
string s1 = "introduction to programming";
string s2 = "c++ programming language";
s1.swap(s2);
cout<<"s1 after swap:"<<s1<<endl;
cout<<"s2 after swap:"<<s2;
//Kết quả:
//s1 after swap:c++ programming language
//s2 after swap:introduction to programming
Hàm s1.insert(index, s2)
Thêm chuỗi s2 vào s1 sau vị trí index.
string s1 = "introduction to programming";
string s2 = "c++ programming language";
s1.insert(10, s2);
cout<<"s1 after insert s2:"<<s1;
//Kết quả:
//s1 after insert s2:introductic++ programming languageon to programming
Các Hàm String Trong C++
IV. Tính Độ Dài String Trong C++
Hướng dẫn cách lấy độ dài string trong C++. Bạn sẽ học được cách dùng hàm size và hàm length để lấy độ dài string trong C++ sau bài học này.
Độ dài string trong C++ là gì
Độ dài string trong C++ chính là số ký tự chứa trong string đó. Độ dài string trong C++ có thể lấy được bằng cách chia kích thước của string cho kích thước 1 phần tử trong string đó.
Độ dài string (số phần tử) = [Kích thước string] / [Kích thước kiểu char]
Do phần tử tạo nên string là các ký tự thuộc dạng char với kích thước là 1 byte, nên thông thường, chúng ta coi độ dài string trong C++ chính bằng kích thước của string đó.
Lấy độ dài string trong C++ bằng hàm size và hàm length
Do thông thường độ dài string trong C++ chính bằng kích thước của string đó, nên bằng phương pháp tính ra kích thước string thì chúng ta có thể thu về ngay độ dài của string đó.
Và để tính kích thước string trong C++, chúng ta sẽ sử dụng một trong hai hàm đã học tại bài Lấy kích thước string trong C++.
Cách lấy độ dài string trong C++ như các ví dụ sau đây:
Ví dụ 1: lấy độ dài string trong C++ bằng hàm size
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string str("Hello world");
cout<<"Do dai string = kich thuoc string = ",str.size()<<endl;
return 0;
}//Do dai string = kich thuoc string = 11
Ví dụ 1: lấy độ dài string trong C++ bằng hàm length
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string str("Hello world");
cout<<"Do dai string = kich thuoc string = "<<str.length()<<endl;
return 0;
}//Do dai string = kich thuoc string = 11
Trường hợp độ dài string khác với số ký tự có trong string
Ở phần trên chúng ta đã biết với những chuỗi được tạo thành bởi các ký tự được biểu diễn bởi 1 mã ký tự, do kích thước của các ký tự như vậy cũng là 1 byte, nên kết quả tính độ dài string bằng 2 hàm size và length cũng chính là số ký tự có trong string đó.
Tuy nhiên trong trường hợp chuỗi được tạo ra bởi các ký tự được biểu diễn bởi 2 mã ký tự như là tiếng Việt có dấu, hay kanji tiếng Nhật chẳng hạn, thì độ dài string sẽ lớn hơn số ký tự có trong string. Ví dụ:
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string str("Hà Nội");
cout<<str.size()<<endl;
string str2("東京");
cout<<str2.size()<<endl;
return 0;
}
Kết quả:
9
6
Có thể thấy rõ độ dài chuỗi trong các trường hợp này sẽ lớn hơn số ký tự có trong chuỗi đó.
Bởi vậy lúc dùng 2 hàm này để lấy số ký tự có trong string, cần hết sức chú ý trong trường hợp tồn tại ký tự được tạo thành bởi 2 mã ký tự như trên.
Tính Độ Dài String Trong C++
V. Cắt String Trong C++
Hướng dẫn phương pháp cắt string trong C++. Bạn sẽ học được cách sử dụng 3 hàm cơ bản như front(), back() và substr() để cắt string trong C++ sau bài học này.
Chúng ta có 3 phương pháp cơ bản để cắt string trong C++ như sau:
Hàm front(): Cắt ký tự trước tiên từ string
Hàm back(): Cắt ký tự cuối cùng từ string
Hàm substr(): Cắt một phạm vi từ string
Cắt ký tự trước tiên từ string trong C++ bằng hàm front
Hàm front là 1 hàm thành viên trong class std:string, có tác dụng tham chiếu tới ký tự đầu tiên trong chuỗi string.
Bằng cách ứng dụng hàm front(), chúng ta không những có thể cắt ra giá trị của ký tự đầu tiên trong string, mà còn có thể thay đổi giá trị của ký tự đầu tiên đó.
Chúng ta sử dụng hàm front trong C++ với cú pháp sau đây:
str.front();
Trong đó str là chuỗi string cần cắt ra ký tự ban đầu. Hàm front() sẽ trả về tham chiếu tới ký tự đầu tiên của chuỗi string, qua đó chúng ta có thể cắt lấy giá trị của ký tự đầu tiên này, hoặc là gán một ký tự khác để thay đổi nó.
Lưu ý chúng ta không dùng hàm front() cho chuỗi string trống nhé.
Ví dụ cụ thể:
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello World!";
//Tạo tham chiếu tới vị trí ký tự đầu tiên của chuỗi string
char frontstr = str.front();
cout << frontstr << endl;
//Thay đổi ký tự đầu tiên
str.front() = 'K';
cout << str << endl;
return 0;
}
Kết quả:
H
Kello World!
Cắt ký tự cuối cùng từ string trong C++ bằng hàm back
Ngược lại với hàm front() chính là hàm back().
Hàm back là 1 hàm thành viên trong class std:string, có tác dụng tham chiếu đến ký tự cuối cùng trong chuỗi string.
Bằng cách ứng dụng hàm back(), chúng ta không những có thể cắt ra giá trị của ký tự đầu tiên trong string, mà còn có thể thay đổi giá trị của ký tự cuối cùng đó.
Chúng ta dùng hàm back trong C++ với cú pháp sau đây:
str.back();
Trong đó str là chuỗi string cần cắt ra ký tự cuối cùng. Hàm back() sẽ trả về tham chiếu tới ký tự cuối cùng của chuỗi string, qua đó chúng ta có thể cắt lấy giá trị của ký tự cuối cùng này, hoặc là gán một ký tự khác để thay đổi nó.
Lưu ý chúng ta không dùng hàm back() cho chuỗi string trống nhé.
Ví dụ cụ thể:
#include <iostream>
#include <string>
using namespace std;
int main() {
string str = "Hello World!";
//Tạo tham chiếu tới vị trí ký tự cuối cùng của chuỗi string
char backstr = str.back();
cout << backstr << endl;
//Thay đổi ký tự cuối cùng.
str.back() = '?';
cout << str << endl;
return 0;
}
Kết quả:
!
Hello World?
Cắt một phạm vi từ string bằng hàm substr()
Chúng ta sử dụng hàm substr() để cắt một phạm vi chỉ định từ trong string ban đầu. Ví dụ cụ thể:
Kết quả, các chuỗi string được cắt trong phạm vi chỉ định như sau:
ell
ello W
Cắt String Trong C++
VI. Nối Chuỗi String Trong C++
Bài tập C++: Nối chuỗi trong C++
Đề bài: Viết chương trình C++ để nối 2 chuỗi trong C.
Bài tập C++ này có nhiều cách gọi: trộn hai chuỗi, nối hai chuỗi, ghép hai chuỗi hay cộng hai chuỗi, … Dù có nhiều cách gọi khác nhau đi chăng nữa thì đây cũng chỉ là một bài tập để nối ghép các ký tự của hai chuỗi để thành một chuỗi duy nhất.
Lời giải (không sử dụng hàm)
Để giải bài tập C++ này, bạn sử dụng hàm strlen() để tìm độ dài chuỗi. Sử dụng vòng lặp để lặp qua tất cả ký tự của chuỗi.
Dưới đây là chương trình C++ để giải bài tập nối chuỗi trong C++:
#include <stdio.h>
#include <string.h>
int main() {
char s1[10] = "VietTuts";
char s2[] = ".Vn";
int i, j, n1, n2;
n1 = strlen(s1);
n2 = strlen(s2);
j=0;
for(i = n1; i<n1+n2; i++ ) {
s1[i] = s2[j];
j++;
}
s1[i] = '\0';
printf("\nKet qua sau khi noi chuoi la:\n");
printf("%s", s1);
return 0;
}
Chạy chương trình C++ trên cho kết quả như sau:
Nối Chuỗi String Trong C++
Lời giải (sử dụng hàm)
Bạn có thể thực hiện nối hai chuỗi thành một chuỗi bởi sử dụng các hàm strcpy() và strcat() trong C.
Giả sử bạn cần nối hai chuỗi là str1 và str2. Bạn cần khai báo thêm một chuỗi str3 có độ dài bằng tổng độ dài của str1 và str2. Sau đó:
Sử dụng hàm strcpy(str3, str1) để sao chép chuỗi str1 vào str3.
Sử dụng hàm strcat(str3, str2) để nối chuỗi str2 với str1 vừa được sao chép.
Dưới đây là chương trình C++ để giải bài tập nối chuỗi trong C++:
Đề bài: Viết chương trình C++ để sa sánh hai chuỗi.
Yêu cầu bài tập C++ này là so sánh xem hai chuỗi có đồng nhất (giống nhau) hay không. Để giải bài tập C++ này bạn cần sử dụng vòng lặp để duyệt qua tất cả các ký tự của hai chuỗi và kiểm tra từng ký tự tương ứng với chỉ mục có giống nhau không.
Lời giải
Dưới đây là chương trình C++ để giải bài tập so sánh 2 chuỗi trong C++:
#include <stdio.h>
int main() {
char s1[50];
char s2[50];
int n = 0;
short flag = 1;
printf("Nhap chuoi s1: ");
gets(s1);
printf("Nhap chuoi s2: ");
gets(s2);
// so sanh chuoi s1 voi s2
while (s1[n] != '\0') {
if(s1[n] != s2[n]) {
flag = 0;
break;
}
n++;
}
if(flag == 1) {
printf("Chuoi %s va chuoi %s la giong nhau.", s1, s2);
}else {
printf("Chuoi %s va chuoi %s la khac nhau.", s1, s2);
}
return 0;
}
So Sánh 2 String Trong C++
VIII. Xóa 1 Ký Tự Trong String C++
Hướng dẫn cách xóa string trong C++. Bạn sẽ học được cách sử dụng các hàm như hàm pop_back, hàm erase để xóa string trong C++ sau bài học này.
Chúng ta có 3 phương pháp để xóa string trong C++ như sau:
Hàm pop_back : Xóa ký tự cuối cùng trong string
Hàm erase: Xóa ký tự tại vị trí chỉ định
Hàm erase(first, last): Xóa các ký tự trong phạm vi chỉ định
Xóa ký tự cuối cùng trong string C++ bằng pop_back
Hàm pop_back là một hàm thành viên trong class std:string, có tác dụng xóa ký tự cuối cùng trong string cũng như giảm độ dài của nó đi một đơn vị.
Cú pháp sử dụng hàm pop_back để xóa ký tự cuối cùng trong string như sau:
str.pop_back();
Trong đó str là tên string cần xóa ký tự cuối cùng. Lưu ý là ký tự cuối cùng ở đây không bao gồm ký tự kết thúc chuỗi \0.
Hàm pop_back thuộc kiểu void, do đó nó sẽ không trả giá trị. Bởi vậy nó sẽ xóa ký tự cuối cùng trong string chứ không trả về ký tự đó. Trong trường hợp muốn lấy ký tự cuối cùng trong string, hãy sử dụng tới hàm back để thay thế.
Có thể thấy ký tự cuối cùng trong chuỗi là ký tự d đã bị xóa khỏi chuỗi ban đầu.
Xóa 1 ký tự trong string bằng hàm erase
Hàm erase là một hàm thành viên trong class std:string, có tác dụng xóa một hoặc nhiều ký tự trong string C++ cũng như làm giảm độ dài tương ứng của nó.
Để xóa 1 ký tự tại vị trí chỉ định trong string C++ bằng hàm erase chúng ta sử dụng cú pháp sau đây:
str.erase(p);
Ở đây trình lặp (iterator) là một vòng lặp có tác dụng giống như con trỏ, giúp truy cập đến các ký tự ở vị trí cụ thể trong string.
Trong trường hợp cần chỉ đến vị trí index thứ n trong string, chúng ta sẽ viết trình lặp p như sau:
str.begin() + i
Trong đó str.begin() chỉ đến vị trí đầu tiên trong string, và i là index của vị trí ký tự cần chỉ đến.
Kết hợp lại thì công thức để xóa 1 ký tự vào vị trí chỉ định trong string C++ bằng hàm erase sẽ như sau:
str.erase(str.begin() + i);
Ví dụ cụ thể:
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string str("Hello World");
str.erase(str.begin() + 2);//Xóa ký tự ở vị trí thứ 2
cout << str<<endl;
str.erase(str.begin() + 6);//Xóa ký tự ở vị trí thứ 6
cout << str;
return 0;
}
Kết quả:
Helo World
Helo Wrld
Xóa các ký tự trong một phạm vi chỉ định trong string C++
Chúng ta sử dụng hàm erase để xóa các ký tự trong một phạm vi chỉ định trong string C++ với cú pháp sau đây:
str.erase( iterator_first, iterator_last);
Trong đó str là chuỗi ban đầu, iterator_first và iterator_last là phạm vi cần xóa được chỉ định trong trình lặp trỏ đến vị trí cần xóa trong string.
Tương tự như khi xóa 1 ký tự thì cú pháp sử dụng thực tế để xóa các ký tự trong một phạm vi chỉ định trong string C++ bằng hàm erase sẽ là:
Trong đó start và end là vị trí index của phạm vi xóa trong string.
Ví dụ cụ thể:
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string str("Hello World");
//Xóa ký tự trong phạm vi index từ 1 đến 3
str.erase(str.begin() + 1, str.begin() + 3);
cout << str<<endl;
//Xóa ký tự trong phạm vi index từ 2 đến 5
str.erase(str.begin() + 2, str.begin() +5);
cout << str;
return 0;
}
Kết quả:
Hlo World
Hlorld
Xóa 1 Ký Tự Trong String C++
IX. Bài Tập Về String Trong C++
C++ cung cấp hai kiểu biểu diễn chuỗi như sau:
Chuỗi theo phong cách của ngôn ngữ C (C-style),
Lớp Chuỗi (String) được giới thiệu trong C/C++ chuẩn.
Chuỗi theo phong cách C
Dạng chuỗi này bắt nguồn từ ngôn ngữ C và tiếp tục được hỗ trợ trong C/C++. Chuỗi trong ngôn ngữ lập trình C thực chất là mảng một chiều của các ký tự mà kết thúc bởi một ký tự null ‘\0’.
Phần khai báo và khởi tạo dưới đây tạo ra một chuỗi bao gồm một từ “Hello”. Để giữ các giá trị null tại cuối của mảng, cỡ của mảng các ký tự bao gồm một chuỗi phải nhiều hơn số lượng các ký tự trong từ khóa “Hello”.
Nếu bạn theo quy tắc khởi tạo các chuỗi, bạn có thể viết lệnh như sau:
char loiChao[] = "Hello";
Dưới đây là phần biểu diễn ô nhớ cho đoạn chuỗi trên trong ngôn ngữ C/C++:
Bài Tập Về String Trong C++
Thực tế, bạn không đặt ký tự null tại vị trí cuối cùng của biến hằng số. Bộ biên dịch C tự động thêm ‘\0’ tại ví trí cuối cùng của chuỗi khi nó khởi tạo chuỗi. Cùng thử ví dụ in ra chuỗi sau đây:
#include <iostream>
using namespace std;
int main ()
{
char loiChao[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
cout << "Khi gap nhau, chung ta noi: ";
cout << loiChao << endl;
return 0;
}
Khi đoạn code trên được biên dịch và thực hiện, kết quả in ra sẽ như sau:
Khi gap nhau, chung ta noi: Hello
Ngôn ngữ C/C++ hỗ trợ nhiều hàm đa dạng để thao tác các chuỗi kết thúc là null:
STT
Hàm & Mục đích
1
strcpy(s1, s2);Sao chép chuỗi s2 cho chuỗi s1.
2
strcat(s1, s2);Nối chuỗi s2 vào cuối chuỗi s1.
3
strlen(s1);Trả về độ dài của chuỗi s1.
4
strcmp(s1, s2);Trả về 0 nếu s1 và s2 là như nhau; nhỏ hơn 0 nếu s1<s2; lớn hơn 0 nếu s1>s2.
5
strchr(s1, ch);Trả về con trỏ tới vị trí đầu tiên của ch trong s1.
6
strstr(s1, s2);Trả về con trỏ tới vị trí đầu tiên của chuỗi s2 trong chuỗi s1.
Dưới đây là ví dụ cho việc sử dụng một vài hàm bên trên:
#include <iostream>
#include <cstring>
using namespace std;
int main ()
{
char chuoi1[10] = "Hello";
char chuoi2[10] = "Christmas";
char chuoi3[10];
int len ;
// sao chep chuoi1 vao trong chuoi3
strcpy( chuoi3, chuoi1);
cout << "strcpy( chuoi3, chuoi1) : " << chuoi3 << endl;
// noi hai chuoi: chuoi1 va chuoi2
strcat( chuoi1, chuoi2);
cout << "strcat( chuoi1, chuoi2): " << chuoi1 << endl;
// tong do dai cua chuoi1 mot sau khi thuc hien noi chuoi
len = strlen(chuoi1);
cout << "Dung ham strlen(chuoi1) de tinh do dai chuoi1: " << len << endl;
return 0;
}
Chạy chương trình C/C++ trên sẽ cho kết quả như hình sau:
Bài Tập Về String Trong C++
Lớp String trong C/C++
Thư viện chuẩn C/C++ cung cấp một kiểu lớp String mà hỗ trợ tất cả hoạt động liên quan tới chuỗi đã đề cập ở trên, và bổ sung thêm nhiều tính năng nữa. Chúng ta sẽ học lớp này trong Thư viện chuẩn C/C++ (C++ Standard Library), nhưng lúc này, chúng ta xem xét ví dụ sau:
Lúc này, có thể bạn không hiểu ví dụ này, bởi vì chúng ta chưa bàn luận về Lớp và Đối tượng trong C/C++. Vì thế, bạn quan sát và ghi nhớ chúng tới khi bạn đã hiểu các khái niệm về Hướng đối tượng được trình bày ở chương sau đó.
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string chuoi1 = "Hello";
string chuoi2 = "Christmas";
string chuoi3;
int len ;
// sao chep chuoi1 vao trong chuoi3
chuoi3 = chuoi1;
cout << "Bay gio chuoi3 la: " << chuoi3 << endl;
// noi hai chuoi: chuoi1 va chuoi2
chuoi3 = chuoi1 + chuoi2;
cout << "chuoi1 + chuoi2 co ket qua la: " << chuoi3 << endl;
// tong do dai cua chuoi3 mot sau khi thuc hien noi chuoi
len = chuoi3.size();
cout << "Tinh do dai voi ham chuoi3.size() : " << len << endl;
return 0;
}
Chạy chương trình C/C++ trên sẽ cho kết quả như hình sau:
Bài Tập Về String Trong C++
X. Chuẩn Hóa String Trong C++
Chuẩn hóa chuỗi trong C
Ý tưởng chuẩn hóa chuỗi trong C như sau:
Bỏ các ký tự khoảng trắng thừa ở đầu chuỗi
Bỏ các ký tự khoảng trắng thừa ở cuối chuỗi
Viết hoa chữ cái đầu tiên của chuỗi
Bỏ qua các khoảng trắng thừa ở giữa
Đưa tất cả về lowercase
Viết hoa chữ cái bắt đầu từ theo ý tưởng đếm từ phía trên
void Correct(char str[], int length)
{
int first = 0, last = length - 1;
// Xóa dấu trắng đầu chuỗi
while (first < last && str[first] == ' ')
first++;
// Xóa dấu trắng cuối chuỗi
while (last > first && str[last] == ' ')
last--;
// Viết hoa chữ cái đầu tiên
if (str[first] >= 'a' && str[first] <= 'z')
str[first] -= 32;
for (int i = first + 1; i <= last; i++)
{
// Đưa hết về chữ thường
if (str[i] >= 'A' && str[i] <= 'Z'){
str[i] += 32;
}
if (i + 1 <= last)
{
// Bỏ dấu trắng thừa ở giữa
if (str[i] == str[i + 1] && str[i] == ' ')
{
continue;
}
// Nếu là chữ cái đầu mỗi từ thì viết hoa
if (str[i] == ' ' && str[i + 1] >= 'a' && str[i + 1] <= 'z')
{
str[i + 1] -= 32;
}
}
printf("%c", str[i]);
}
}
Lời giải chuẩn hóa chuỗi và đếm số từ trong C
#include <stdio.h>
#include <string.h>
int WordCount(char str[], int length)
{
int word = (str[0] != ' ');
for (int i = 0; i < length - 1; i++)
{
if (str[i] == ' ' && str[i + 1] != ' ')
{
word++;
}
}
return word;
}
void Correct(char str[], int length)
{
int first = 0, last = length - 1;
// Xóa dấu trắng đầu chuỗi
while (first < last && str[first] == ' ')
first++;
// Xóa dấu trắng cuối chuỗi
while (last > first && str[last] == ' ')
last--;
// Viết hoa chữ cái đầu tiên
if (str[first] >= 'a' && str[first] <= 'z')
str[first] -= 32;
for (int i = first + 1; i <= last; i++)
{
// Đưa hết về chữ thường
if (str[i] >= 'A' && str[i] <= 'Z'){
str[i] += 32;
}
if (i + 1 <= last)
{
// Bỏ dấu trắng thừa ở giữa
if (str[i] == str[i + 1] && str[i] == ' ')
{
continue;
}
// Nếu là chữ cái đầu mỗi từ thì viết hoa
if (str[i] == ' ' && str[i + 1] >= 'a' && str[i + 1] <= 'z')
{
str[i + 1] -= 32;
}
}
printf("%c", str[i]);
}
}
int main()
{
char str[100];
// Nhập chuỗi
printf("\nNhap chuoi: ");
fgets(str, sizeof str, stdin);
// Do sử dụng fgets nên chuỗi của chúng ta sẽ đọc cả ký tự '\n'.
// Xóa bỏ nó đi.
int length = strlen(str) - 1;
str[length] = '\0';
printf("So tu cua \"%s\" la %d", str, WordCount(str, length));
printf("\nXau chuan hoa la: ");
Correct(str, length);
}
Kết quả chạy thử:
PS G:\c_cources\day_61> g++ .\ChuanHoa.cpp -o .\ChuanHoa
PS G:\c_cources\day_61> .\ChuanHoa.exe
Nhap chuoi: LAP trINH khong KHO!
So tu cua " LAP trINH khong KHO!" la 4
Xau chuan hoa la: Lap trinh khong kho
Đệ quy trong C++ là 1 phương thức vô cùng quan trọng và là cơ sở của rất rất đa dạng thuật toán. Vì vậy, hiểu được đệ quy sẽ giúp bạn dễ dàng tiếp cận và học hỏi thêm nhiều kiến thức khác về lập trình. Trong bài viết ngày này, Techacademy sẽ chia sẻ với các bạn tất tần tật mọi thứ về đệ quy cùng với các bài tập đệ quy có lời giải chi tiết để giúp bạn hiểu rõ hơn về nó nữa đấy!
I. Đệ Quy Trong C++ Là Gì
Đệ quy trong C++ là quá trình trong đó một phương thức gọi lại chính nó một cách liên tiếp. Một hàm trong C++ gọi lại chính nó được gọi là phương thức đệ quy. Trong một hàm đệ quy sẽ gồm có điều kiện dừng và lời gọi hàm đệ quy, cú pháp cụ thể như sau:
Kiểu_trả_về Tên_hàm (Các_tham_số)
{
Điều_kiện_dừng;
return Tên_hàm (Các_tham_số_mới) ;
// hoặc một biểu thức có chứa lời gọi hàm.
}
Để giúp bạn dễ hình dung hơn thì dưới đây sẽ là ví dụ về hàm đệ quy giúp tính giai thừa của một số tự nhiên:
long long Giaithua(int n)
{
if (n==0 || n==1)
return 1;
else
return Giaithua(n-1) * n;
}
Giải thích thuật toán: Ở đây, điều kiện dừng chính là n=0 hoặc là n=1 thì sẽ trả về giá trị là 1 ( Do 0!=1!=1). Ngược lại, nếu n>1, hàm sẽ trả về n*Giaithua(n-1). Chẳng hạn ta cho n nhận giá trị là 3, chương trình sẽ thực thi như sau:
Vậy mục đích của hàm đệ quy là chia vấn đề thành những vấn đề nhỏ hơn cho đến lúc đạt được điều kiện cơ bản. Lưu ý quan trọng khi dùng đệ quy là bắt buộc phải có điều kiện dừng, nếu không có thì sẽ làm hàm gọi hàm liên tục không có điểm dừng và dẫn đến chương trình không thể kết thúc được.
Đệ Quy Trong C++ Là Gì
II. Cách Sử Dụng Đệ Quy Trong C++
Trong C++, một hàm gọi chính nó ta gọi đó là hàm đệ quy, nghĩa là trong thân hàm sẽ gọi đến chính tên hàm hiện tại và truyền đúng những tham số mà hàm đã khai báo.
Cú pháp: Cú pháp của hàm đệ quy trong C++ như sau:
HamDeQuy(){
HamDeQuy(); //goi lai chinh no
}
Ví dụ: Chúng ta cùng xem một ví dụ đơn giản về hàm đệ quy trong C++ đó là tính giai thừa của một số nguyên.
Trước khi giải bài toán tính giai thừa của một số nguyên trong C++ chúng ta cùng nhớ lại công thức tính giai thừa trong toán học trước đã nhé.
Theo định nghĩa giai thừa ta có:
0! = 1
n! = 1 * 2 * 3 * … * n
Vậy là ta đã có công thức tính giai thừa của một số nguyên rồi. Nếu n = 0 thì giai thừa bằng 1. Nếu n > 0 thì giai thừa sẽ là tích từ 1 đến n. Và không có giai thừa của số âm.
Giải bằng vòng lặp For
Trước khi đi vào giải bài toán trên bằng hàm đệ quy, mình sẽ giải bằng vòng lặp for trong C++ trước nhé.
Ví dụ
#include<iostream>
using namespace std;
int main()
{
int n;
while(true) {
int giaithua = 1;
cout << "Nhap so n: ";
cin >> n;
//Nhap n nho hon 0 de thoat khoi vong lap
if(n < 0) {
cout << " So am khong co giai thua" << endl;
break;
}
if ( n > 0) {
for(int i = 1; i <=n; i++) {
giaithua = giaithua * i;
}
}
cout << " Giai thua cua " << n << " la: " << giaithua << endl;
}
return 0;
}
Và kết quả sau khi thực thi chương trình trên như sau:
Cách Sử Dụng Đệ Quy Trong C++
Như vậy, để giải quyết bài toán giai thừa của một số bằng vòng lặp for trong C++ rất đơn giản phải không các bạn? Bây giờ mình sẽ giải bài toán giai thừa trên bằng hàm đệ quy trong C++.
Giải bằng đệ quy C++
Các bạn để ý kỹ thì thấy thuật toán tính giai thừa sẽ như sau: Giả sử cần tính 5!, lúc này quy luật của nó sẽ là.
5! = 4! * 5
4! = 3! * 4
3! = 2! * 3
2! = 1! * 2
1! = 1
Thay các vế vào ta sẽ được quy luật: 5! = 1 * 2 * 3 * 4 * 5.
Giả sử ta có hàm tính gian thừa của một số tên là GT, lúc này thay vào công thức trên ta được như sau:
GT(5) = GT(4) * 5
GT(4) = GT(3) * 4
GT(3) = GT(2) * 3
GT(2) = GT(1) * 2
GT(1) = 1
Vậy, ta có thể áp dụng giải thuật đệ quy C++ để giải quyết, bằng cách bên trong thân hàm sẽ gọi đến chính hàm đó.
Ví dụ
#include<iostream>
using namespace std;
int GiaiThua(int n) {
// Trường hợp người dùng nhập
if (n == 1)
return 1;
else
return (n * GiaiThua(n - 1));
}
int main()
{
int n;
while(true) {
cout << "Nhap so n: ";
cin >> n;
//Nhap n nho hon 0 de thoat khoi vong lap
if(n < 0) {
cout << " So am khong co giai thua" << endl;
break;
}
cout << " Giai thua cua " << n << " la: " << GiaiThua(n) << endl;
}
return 0;
}
Và kết quả sau khi thực thi chương trình trên như sau:
Cách Sử Dụng Đệ Quy Trong C++
Đối với các bạn mới bắt đầu học lập trình thì cách giải bài toán giai thừa trên bằng vòng lặp for sẽ dễ hiểu hơn rất nhiều so với việc giải bằng hàm đệ quy. Tuy nhiên, các bạn đừng quá lo lắng, mình sẽ giải thích đoạn code cho các bạn bạn dễ hiểu.
Giả sử mình nhập n = 5 thì chương trình trên sẽ chạy như sau:
Mục đích của hàm đệ quy là chia vấn đề thành các vấn đề nhỏ hơn cho đến khi đạt được điều kiện cơ bản.
Ví dụ trong chương trình giai thừa ở trên, chúng ta đang giải quyết hàm giai thừa GiaiThua(n) bằng cách gọi hàm giai thừa nhỏ hơn GiaiThua(n-1), điều này được lặp lại liên tục cho đến khi giá trị n đạt đến điều kiện cơ sở (GiaiThua(1) = 1).
III. Đệ Quy Quay Lui Trong C++
Quay lui là 1 kĩ thuật thiết kế giải thuật dựa trên đệ quy. Ý tưởng của quay lui là tìm lời giải từng bước, mỗi bước chọn một trong số các lựa chọn khả dĩ và đệ quy. Người đầu tiên đề ra thuật ngữ này (backtrack) là nhà toán học người Mỹ D. H. Lehmer vào những năm 1950.
Tư tưởng
Dùng để giải bài toán liệt kê những cấu hình. Mỗi cấu hình được xây dựng bằng từng phần tử. Mỗi phần tử lại được chọn bằng cách thử toàn bộ các khả năng.
Các bước trong việc liệt kê cấu hình dạng X[1…n]:
Xét tất cả các giá trị X[1] có thể nhận, thử X[1] nhận những giá trị đó. Với mỗi giá trị của X[1] ta sẽ:
Xét đa số giá trị X[2] có thể nhận, lại thử X[2] cho các giá trị đó. Với mỗi giá trị X[2] lại xét khả năng giá trị của X[3]…tiếp tục như vậy cho đến bước:
…
…
Xét tất cả giá trị X[n] có thể nhận, thử cho X[n] nhận lần lượt giá trị đó.
Thông báo cấu hình tìm được.
Bản chất của quay lui là một quá trình tìm kiếm theo chiều sâu(Depth-First Search).
Mô hình thuật toán
Mã giả cho thuật toán quay lui.
Backtracking(k) {
for([Mỗi phương án chọn i(thuộc tập D)]) {
if ([Chấp nhận i]) {
[Chọn i cho X[k]];
if ([Thành công]) {
[Đưa ra kết quả];
} else {
Backtracking(k+1);
[Bỏ chọn i cho X[k]];
}
}
}
}
Ví dụ: Trò chơi Sudoku
Sudoku là một trò chơi khá phổ biến và chắc ai cũng biết. Trò chơi như sau: có một hình vuông được chia thành 9×9 ô vuông con. Mỗi ô vuông con có giá trị trong khoảng từ 1 đến 9. Ban đầu hình vuông có một số ô vuông con cho trước (có điền sẵn số) và còn lại là trống. Hãy điền các số từ 1-9 vào các ô con lại sao cho: hàng ngang là các số khác nhau từ 1 đến 9, hàng dọc là các số khác nhau từ 1 đến 9, và mỗi khối 3×3 chính là các số khác nhau từ 1 đến 9. Sau đây là 1 ví dụ về đề bài và lời giải:
Đệ Quy Quay Lui Trong C++
Áp dụng quay lui để giải bài toán sudoku. Ý tưởng: Mỗi bước tìm tập các giá trị khả dĩ để điền vào ô trống, và sau đó đệ quy để điền ô tiếp theo. Giả mã của thuật toán (ở đây chú ý mảng chỉ có kích thước 9×9×9)
void solveSudoku(int S[][9], int x, int y){
if(y == 9){
if(x == 8){
printSolution(S);
exit(0);
} else {
solveSudoku(S, x+1,0);
}
} else if(S[x][y] == 0){
for (int k = 1; k <=9; k++){
if(checkValid(S,x,y,k)){
S[x][y] = k;
solveSudoku(S, x, y+1);
S[x][y] = 0;
}
}
} else {
solveSudoku(S,x,y+1);
}
}
boolean checkValid(int S[][9], int x, int y, int k){
for(int i = 0; i <9 ; i++){
if(S[x][i] == k) return false;
}
for(int i = 0; i <9 ; i++){
if(S[i][y] == k) return false;
}
int a = x/3, b = y/3;
for(int i = 3*a; i < 3*a+3; i++){
for(int j = 3*b; j < 3*b+3; j++){
if(S[i][j] == k) return false;
}
}
return true;
}
Nhận xét
Ưu điểm: Việc quay lui là thử tất cả các tổ hợp để tìm được một lời giải. Thế mạnh của phương pháp này là nhiều cài đặt tránh được việc phải thử nhiều trường hợp chưa hoàn chỉnh, nhờ đó giảm thời gian chạy.
Nhược điểm: Trong trường hợp xấu nhất độ phức tạp của quay lui vẫn là cấp số mũ. Vì nó mắc phải các nhược điểm sau:
Rơi vào tình trạng “thrashing”: qúa trình tìm kiếm cứ gặp phải bế tắc với cùng một nguyên nhân.
Thực hiện các công việc dư thừa: Mỗi lần chúng ta quay lui, chúng ta cần phải đánh giá lại lời giải trong khi đôi lúc điều đó không cần thiết.
Không sớm phát hiện được các khả năng bị bế tắc trong tương lai. Quay lui chuẩn, không có cơ chế nhìn về tương lai để nhận biết đc nhánh tìm kiếm sẽ đi vào bế tắc.
IV. Bài Tập Đệ Quy Quay Lui Trong C++
+ Tìm Ước Chung Lớn Nhất Và Bội Chung Nhỏ Nhất Bằng Đệ Quy
Hàm đệ quy là những hàm gọi lại chính nó. Nó hữu dụng trong các tác vụ như sắp xếp hoặc tính toán các số giai thừa… Hàm đệ quy tương ứng với khái niệm quy nạp trong toán học.
Bài tập 1. Thuật toán Euclide tìm ước chung lớn nhất
Viết chương trình tìm ước chung lớn nhất của 2 số nguyên dương a, b bằng thuật toán. Euclide được định nghĩa đệ quy như sau:
[Cài đặt:]
#include <conio.h>
#include <iostream.h>
int UCLN(int a, int b) {
if(a==b)
return a;
else if(a>b)
return UCLN(a-b,b);
else
return UCLN(a,b-a);
}
void main() {
clrscr();
int a,b;
cout<<"Nhap a = ";
cin>>a;
cout<<"Nhap b = ";
cin>>b;
cout<<"Uoc chung lon nhat cua "<<a<<" va "<<b<<" la "<<UCLN(a,b);
getch();
}
Bài tập 2. Tìm ước chung lớn nhất của n số nguyên
Viết chương trình tìm ước chung lớn nhất của n số nguyên dương 0 1 ,…, n a a được định nghĩa đệ quy như sau:
[Cài đặt:]
#include <conio.h>
#include <iostream.h>
/*Ham tra ve uoc chung lon nhat cua a va b*/
int UCLN(int a, int b) {
if(a==b)
return a;
else if(a>b)
return UCLN(a-b,b);
else
return UCLN(a,b-a);
}
/*Ham tra ve uoc chung lon nhat cua n phan tu duoc luu tru trong mang 1 chieu a*/
int UC(int a[], int n) {
if(n==1)
return a[0];
else
return UCLN(a[n-1],UC(a,n-1));
}
void main() {
clrscr();
int *a,n;
cout<<"Nhap n = ";
cin>>n;
a = new int[n];
cout<<"Nhap vao "<<n<<" phan tu\n";
for(int i=0; i<n ; i++){
cout<<"a["<<i<<"] = ";
cin>>a[i];
}
cout<<"UCLN cua "<<n<<" phan tu vua nhap la "<<UC(a,n);
getch();
}
+ Đệ Quy Fibonacci Trong C++
Chắc các bạn cũng đã biết dãy Fibonacci là gì rồi. Đó là dãy số mà số tiếp theo là tổng của hai số liền trước, ví dụ: 1, 1, 2, 3, 5, 8, 13, …. Bài viết này sẽ hướng dẫn cho các bạn cách tính số fibonacci bằng phương pháp dùng đệ quy và không dùng đệ quy.
Dùng đệ quy để tính số fibonacci
Công thức truy hồi của dãy fibonacci có dạng: f(n) = f(n-1) + f(n-2) .
Với f(1) = 1; f(2) =1;
Cách tính số Fibonacci trong C
#include <stdio.h>
#include <conio.h>
int Fibonacci(int n)
{
if (n == 1 || n == 2)
return 1;
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
int main()
{
int n;
printf("nhap n: ");
scanf("%d", &n);
printf("So Fibonacci thu %d la: %d", n, Fibonacci(n));
return 0;
}
nhap n: 6
So Fibonacci thu 6 la: 8
Cách tính số Fibonacci trong C++
#include <iostream>
using namespace std;
int Fibonacci(int n)
{
if (n == 1 || n == 2)
return 1;
return Fibonacci(n - 1) + Fibonacci(n - 2);
}
int main()
{
int n;
cout << "nhap n: ";
cin >> n;
cout << "So Fibonacci thu " << n << " la: " << Fibonacci(n);
return 0;
}
nhap n: 6
So Fibonacci thu 6 la: 8
Khi đã có hệ thức truy hồi thì việc viết hàm đệ quy rất đơn giản phải không nào ? Nhưng liệu bạn có thử nhập n lớn ( cỡ 30 – 40 ) không ạ. Nếu thử rồi thì chắc các bạn cũng thấy nó chậm hơn rất nhiều. Nguyên nhân là khi tính số fibonacci thứ 5 chương trình sẽ yêu cầu tính hai số fibonacci thứ 4 và thứ 3. Nó lại tiếp tục như vậy đến khi tính được số fibonacci thứ 2 hoặc thứ 1 mới dừng lại.
Đệ Quy Fibonacci Trong C++
Vậy nếu muốn chương trình của chúng ta chạy nhanh hơn thì chúng ta phải khử đệ quy. Cùng làm nhé !
Cách tính số Fibonacci không dùng đệ quy
Ý tưởng cách này là chúng ta sẽ dùng một vòng lặp để tính số Fibonacci .
Nếu n = 1 hoặc n = 2 thì chúng ta return 1
Sau đó tạo một biến i có giá trị bằng 3
Trong vòng while chúng ta tính a = a1 + a2
Sau đó gán a1 = a2 và a2 = a cứ chạy đến khi nào i = n thì dừng
#include <stdio.h>
int Fibonacci(int n)
{
int a1 = 1, a2 = 1;
if (n == 1 || n == 2)
return 1;
int i = 3, a;
while (i <= n)
{
a = a1 + a2;
a1 = a2;
a2 = a;
i++;
}
return a;
}
int main()
{
int n;
printf("nhap n: ");
scanf("%d", &n);
printf("So Fibonacci thu %d la: %d", n, Fibonacci(n));
return 1;
}
nhap n: 40
So Fibonacci thu 40 la: 102334155
Cách tính số Fibonacci trong C++
#include <iostream>
using namespace std;
int Fibonacci(int n)
{
int a1 = 1, a2 = 1;
if (n == 1 || n == 2)
return 1;
int i = 3, a;
while (i <= n)
{
a = a1 + a2;
a1 = a2;
a2 = a;
i++;
}
return a;
}
int main()
{
int n;
cout << "nhap n: ";
cin >> n;
cout << "So Fibonacci thu " << n << " la: " << Fibonacci(n);
return 1;
}
nhap n: 40
So Fibonacci thu 40 la: 102334155
Tìm 1000 số Fibonacci đầu tiên
Với code trên bạn tìm đến số Fibo thứ 50 là bị tràn số rồi. Code với số nguyên lớn dưới đây sẽ giúp bạn tính được số Fibo thứ 1000 hoặc hơn thế nữa. Có thể bạn sẽ cần đọc bài viết dưới đây trước khi tham khảo code này.
Lời giải cho chương trình in ra 1000 số Fibo đầu tiên.
#include <bits/stdc++.h>
using namespace std;
const int base = 1000000000;
const int base_digits = 9;
struct bigint
{
vector<int> a;
int sign;
bigint() : sign(1)
{
}
bigint(long long v)
{
*this = v;
}
bigint(const string &s)
{
read(s);
}
void operator=(const bigint &v)
{
sign = v.sign;
a = v.a;
}
void operator=(long long v)
{
sign = 1;
if (v < 0)
sign = -1, v = -v;
for (; v > 0; v = v / base)
a.push_back(v % base);
}
bigint operator+(const bigint &v) const
{
if (sign == v.sign)
{
bigint res = v;
for (int i = 0, carry = 0; i < (int)max(a.size(), v.a.size()) || carry; ++i)
{
if (i == (int)res.a.size())
res.a.push_back(0);
res.a[i] += carry + (i < (int)a.size() ? a[i] : 0);
carry = res.a[i] >= base;
if (carry)
res.a[i] -= base;
}
return res;
}
return *this - (-v);
}
bigint operator-(const bigint &v) const
{
if (sign == v.sign)
{
if (abs() >= v.abs())
{
bigint res = *this;
for (int i = 0, carry = 0; i < (int)v.a.size() || carry; ++i)
{
res.a[i] -= carry + (i < (int)v.a.size() ? v.a[i] : 0);
carry = res.a[i] < 0;
if (carry)
res.a[i] += base;
}
res.trim();
return res;
}
return -(v - *this);
}
return *this + (-v);
}
void operator*=(int v)
{
if (v < 0)
sign = -sign, v = -v;
for (int i = 0, carry = 0; i < (int)a.size() || carry; ++i)
{
if (i == (int)a.size())
a.push_back(0);
long long cur = a[i] * (long long)v + carry;
carry = (int)(cur / base);
a[i] = (int)(cur % base);
//asm("divl %%ecx" : "=a"(carry), "=d"(a[i]) : "A"(cur), "c"(base));
}
trim();
}
bigint operator*(int v) const
{
bigint res = *this;
res *= v;
return res;
}
friend pair<bigint, bigint> divmod(const bigint &a1, const bigint &b1)
{
int norm = base / (b1.a.back() + 1);
bigint a = a1.abs() * norm;
bigint b = b1.abs() * norm;
bigint q, r;
q.a.resize(a.a.size());
for (int i = a.a.size() - 1; i >= 0; i--)
{
r *= base;
r += a.a[i];
int s1 = r.a.size() <= b.a.size() ? 0 : r.a[b.a.size()];
int s2 = r.a.size() <= b.a.size() - 1 ? 0 : r.a[b.a.size() - 1];
int d = ((long long)base * s1 + s2) / b.a.back();
r -= b * d;
while (r < 0)
r += b, --d;
q.a[i] = d;
}
q.sign = a1.sign * b1.sign;
r.sign = a1.sign;
q.trim();
r.trim();
return make_pair(q, r / norm);
}
bigint operator/(const bigint &v) const
{
return divmod(*this, v).first;
}
bigint operator%(const bigint &v) const
{
return divmod(*this, v).second;
}
void operator/=(int v)
{
if (v < 0)
sign = -sign, v = -v;
for (int i = (int)a.size() - 1, rem = 0; i >= 0; --i)
{
long long cur = a[i] + rem * (long long)base;
a[i] = (int)(cur / v);
rem = (int)(cur % v);
}
trim();
}
bigint operator/(int v) const
{
bigint res = *this;
res /= v;
return res;
}
int operator%(int v) const
{
if (v < 0)
v = -v;
int m = 0;
for (int i = a.size() - 1; i >= 0; --i)
m = (a[i] + m * (long long)base) % v;
return m * sign;
}
void operator+=(const bigint &v)
{
*this = *this + v;
}
void operator-=(const bigint &v)
{
*this = *this - v;
}
void operator*=(const bigint &v)
{
*this = *this * v;
}
void operator/=(const bigint &v)
{
*this = *this / v;
}
bool operator<(const bigint &v) const
{
if (sign != v.sign)
return sign < v.sign;
if (a.size() != v.a.size())
return a.size() * sign < v.a.size() * v.sign;
for (int i = a.size() - 1; i >= 0; i--)
if (a[i] != v.a[i])
return a[i] * sign < v.a[i] * sign;
return false;
}
bool operator>(const bigint &v) const
{
return v < *this;
}
bool operator<=(const bigint &v) const
{
return !(v < *this);
}
bool operator>=(const bigint &v) const
{
return !(*this < v);
}
bool operator==(const bigint &v) const
{
return !(*this < v) && !(v < *this);
}
bool operator!=(const bigint &v) const
{
return *this < v || v < *this;
}
void trim()
{
while (!a.empty() && !a.back())
a.pop_back();
if (a.empty())
sign = 1;
}
bool isZero() const
{
return a.empty() || (a.size() == 1 && !a[0]);
}
bigint operator-() const
{
bigint res = *this;
res.sign = -sign;
return res;
}
bigint abs() const
{
bigint res = *this;
res.sign *= res.sign;
return res;
}
long long longValue() const
{
long long res = 0;
for (int i = a.size() - 1; i >= 0; i--)
res = res * base + a[i];
return res * sign;
}
friend bigint gcd(const bigint &a, const bigint &b)
{
return b.isZero() ? a : gcd(b, a % b);
}
friend bigint lcm(const bigint &a, const bigint &b)
{
return a / gcd(a, b) * b;
}
void read(const string &s)
{
sign = 1;
a.clear();
int pos = 0;
while (pos < (int)s.size() && (s[pos] == '-' || s[pos] == '+'))
{
if (s[pos] == '-')
sign = -sign;
++pos;
}
for (int i = s.size() - 1; i >= pos; i -= base_digits)
{
int x = 0;
for (int j = max(pos, i - base_digits + 1); j <= i; j++)
x = x * 10 + s[j] - '0';
a.push_back(x);
}
trim();
}
friend istream &operator>>(istream &stream, bigint &v)
{
string s;
stream >> s;
v.read(s);
return stream;
}
friend ostream &operator<<(ostream &stream, const bigint &v)
{
if (v.sign == -1)
stream << '-';
stream << (v.a.empty() ? 0 : v.a.back());
for (int i = (int)v.a.size() - 2; i >= 0; --i)
stream << setw(base_digits) << setfill('0') << v.a[i];
return stream;
}
static vector<int> convert_base(const vector<int> &a, int old_digits, int new_digits)
{
vector<long long> p(max(old_digits, new_digits) + 1);
p[0] = 1;
for (int i = 1; i < (int)p.size(); i++)
p[i] = p[i - 1] * 10;
vector<int> res;
long long cur = 0;
int cur_digits = 0;
for (int i = 0; i < (int)a.size(); i++)
{
cur += a[i] * p[cur_digits];
cur_digits += old_digits;
while (cur_digits >= new_digits)
{
res.push_back(int(cur % p[new_digits]));
cur /= p[new_digits];
cur_digits -= new_digits;
}
}
res.push_back((int)cur);
while (!res.empty() && !res.back())
res.pop_back();
return res;
}
typedef vector<long long> vll;
static vll karatsubaMultiply(const vll &a, const vll &b)
{
int n = a.size();
vll res(n + n);
if (n <= 32)
{
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
res[i + j] += a[i] * b[j];
return res;
}
int k = n >> 1;
vll a1(a.begin(), a.begin() + k);
vll a2(a.begin() + k, a.end());
vll b1(b.begin(), b.begin() + k);
vll b2(b.begin() + k, b.end());
vll a1b1 = karatsubaMultiply(a1, b1);
vll a2b2 = karatsubaMultiply(a2, b2);
for (int i = 0; i < k; i++)
a2[i] += a1[i];
for (int i = 0; i < k; i++)
b2[i] += b1[i];
vll r = karatsubaMultiply(a2, b2);
for (int i = 0; i < (int)a1b1.size(); i++)
r[i] -= a1b1[i];
for (int i = 0; i < (int)a2b2.size(); i++)
r[i] -= a2b2[i];
for (int i = 0; i < (int)r.size(); i++)
res[i + k] += r[i];
for (int i = 0; i < (int)a1b1.size(); i++)
res[i] += a1b1[i];
for (int i = 0; i < (int)a2b2.size(); i++)
res[i + n] += a2b2[i];
return res;
}
bigint operator*(const bigint &v) const
{
vector<int> a6 = convert_base(this->a, base_digits, 6);
vector<int> b6 = convert_base(v.a, base_digits, 6);
vll a(a6.begin(), a6.end());
vll b(b6.begin(), b6.end());
while (a.size() < b.size())
a.push_back(0);
while (b.size() < a.size())
b.push_back(0);
while (a.size() & (a.size() - 1))
a.push_back(0), b.push_back(0);
vll c = karatsubaMultiply(a, b);
bigint res;
res.sign = sign * v.sign;
for (int i = 0, carry = 0; i < (int)c.size(); i++)
{
long long cur = c[i] + carry;
res.a.push_back((int)(cur % 1000000));
carry = (int)(cur / 1000000);
}
res.a = convert_base(res.a, 6, base_digits);
res.trim();
return res;
}
};
int main()
{
bigint first, second, temp;
first = 1;
second = 1;
int i = 3;
cout << 1 << " " << first << "\n";
cout << 2 << " " << second << "\n";
while (i < 1000)
{
i++;
temp = first + second;
cout << i << " " << temp << "\n";
first = second;
second = temp;
}
}
Hướng dẫn cách tìm số đảo ngược trong C. Bạn sẽ học được cách tạo hàm tìm số đảo ngược trong C sau bài học này.
Bài toán tìm số đảo ngược trong C
Tìm số đảo ngược trong C là bài toán nhập vào một số nguyên dương n từ bàn phím. In ra số đảo ngược của số n vừa nhập.
Ví dụ chúng ta nhập số 1234 thì sẽ thu về số 4321 chẳng hạn.
Mặc dù không không có ý nghĩa về mặt ứng dụng nhưng đây là một bài toán căn bản vô cùng hay giúp chúng ta rèn luyện lập trình bằng ngôn ngữ C.
Tìm số đảo ngược trong C
Cách tìm số đảo ngược trong C rất đơn giản, chúng ta viết lại từng hàng trong số theo thứ tự ngược lại là xong.
Vậy thì chúng ta sẽ viết ngược như thế nào?
Giả sử số đã cho là n = 1234. Chúng ta có thể biểu diễn số này dưới dạng tổng các lũy thừa của 10 như sau:
n = 1x104 + 2x103 + 3x102 + 4x101
Để biểu diễn số này theo cách ngược lại, chúng ta sẽ giữ nguyên phần lũy thừa của 10 và viết ngược lại từng chữ số trong tổng như sau:
n = 4x104 + 3x103 + 2x102 + 1x101
Để tìm ra được từng chữ số trong hàng thập phân như trên, chúng ta sẽ chia lần lượt số đã cho cho các luỹ thừa của 10 để tìm phần dư là xong.
Chúng ta sẽ sử dụng vòng lặp while và viết hàm để thực hiện xử lý đảo ngược số ở trên như sau:
/*Hàm tìm số đảo ngược trong C*/
int reverse_num(int n){
int reverse = 0;
while (n > 0) {
reverse = reverse * 10 + n % 10;
n /= 10;
}
return reverse;
}
Chúng ta có thể gọi hàm này và sử dụng trong chương trình nhập vào một số nguyên dương n từ bàn phím. In ra số đảo ngược của số n vừa nhập trong C như sau:
#include <stdio.h>
/*Hàm tìm số đảo ngược trong C*/
int reverse_num(int n){
int reverse = 0;
while (n > 0) {
reverse = reverse * 10 + n % 10;
n /= 10;
}
return reverse;
}
int main(void){
int n;
printf(">> nhap mot so nguyen duong: ");
scanf("%d",&n);
int result = reverse_num(n);
printf("So dao nguoc: %d\n",result );
return 0;
}
Màn hình nhập liệu và kết quả tìm số đảo ngược trong C sẽ như sau:
>> nhap mot so nguyen duong: 1234
So dao nguoc: 4321
>> nhap mot so nguyen duong: 23456789
So dao nguoc: 98765432
+ In Ra Dạng Nhị Phân Của Số Nguyên Dương N
Chắc hẳn ai học về giải thuật cũng đã từng nghe qua và làm về bài toán đưa ra chuỗi nhị phân độ dài N rồi, nếu bạn mới bắt đầu học hoặc đã bỏ lỡ qua bài toán thú vị này thì cũng đừng lo, vì trong bài viết này mình sẽ giới thiệu cho tất cả các bạn về bài toán này nhé.
Chuỗi nhị phân là gì?
Khái niệm về chuỗi nhị phân
Hệ nhị phân (hay hệ đếm cơ số hai hoặc mã nhị phân) là một hệ đếm dùng hai ký tự để biểu đạt một giá trị số, bằng tổng số các lũy thừa của 2. Hai ký tự đó thường là 0 và 1, chúng thường được dùng để biểu đạt hai giá trị hiệu điện thế tương ứng (có hiệu điện thế, hoặc hiệu điện thế cao là 1 và không có, hoặc thấp là 0). Do có ưu điểm tính toán đơn giản, dễ dàng thực hiện về mặt vật lý, chẳng hạn như trên các mạch điện tử, hệ nhị phân trở thành một phần kiến tạo căn bản trong các máy tính đương thời.
Ví dụ 0, 1, 0000, 0001, 010101, 00011100 là các chuỗi nhị phân
Bài toán đưa ra chuỗi nhị phân độ dài N
Chắc hẳn ai học về giải thuật cũng đã từng nghe qua và làm về bài toán đưa ra chuỗi nhị phân độ dài N rồi, nếu bạn mới bắt đầu học hoặc đã bỏ lỡ qua bài toán thú vị này thì cũng đừng lo, vì ngay bây giờ mình sẽ giới thiệu về nó nhé.
Bài toán cụ thể như sau:
Nhập vào một số nguyên dương N (1 ≤ N ≤ 20) hãy đưa ra tất cả các chuỗi nhị phân độ dài N, một chuỗi ghi trên một dòng, các chuỗi được sắp xếp từ bé đến lớn theo thứ tự từ điển,
Ví dụ 1:
Input
Output
1
0
1
Ví dụ 2:
Input
Output
2
00
01
10
11
Ví dụ 3:
Input
Output
3
000
001
010
011
100
101
110
111
Bài toán khá là thú vị đúng không nào, đã có ai có ý tưởng làm bài này chưa? hãy thử làm nó nhé, nếu bạn đã làm xong hoặc chưa biết làm thì cũng xem thử mình đã xử lý bài toán này bằng cách nào nhé.
Một số thuật toán đưa ra chuỗi nhị phân độ dài N
Biến đổi số thành chuỗi nhị phân
Thực chất các chuỗi nhị phân độ dài N lần lượt là biểu diễn nhị phân của các số thập phân từ 0 đến 2N-1.
Ví dụ 0 = 0(2), 1 = 1(2), 2 = 10(2), 7 = 111(2).
Việc cần làm của chúng ta rất đơn giản đó là chỉ cần chuyển đổi các số tự nhiên từ 0 đến 2N-1 sang chuỗi nhị phân là được (chú ý nhớ chèn các ký tự ‘0’ vào các chuỗi nhị phân để độ dài của chuỗi nhị phân đủ N).
Để chuyển một số tự nhiên sang chuỗi nhị phân ta có thể làm như sau:
In ra dạng nhị phân của số nguyên dương n
string decToBin(int n){
string ans = "";
while (n > 0) {
ans = char (n % 2 + '0') + ans;
n /= 2;
}
while (ans.length() < N)
ans = "0" + ans;
return ans;
}
Ta sẽ chia N cho 2 cho đến khi kết quả bằng 0, mỗi lần chia như vậy ta lưu lại số dư của N cho 2, chuỗi nhị phân của N chính là chuỗ số dư được đọc ngược.
Source code:
#include <iostream>
#include <math.h>
using namespace std;
int N;
string decToBin(int n){
string ans = "";
while (n > 0) {
ans = char (n % 2 + '0') + ans;
n /= 2;
}
while (ans.length() < N)
ans = "0" + ans;
return ans;
}
int main(){
cin >> N;
int N_2 = pow(2, N);
for (int i = 0; i < N_2; i++)
cout << decToBin(i) << endl;
}
Đệ quy quay lui
Với bài này ta có thể dùng phương pháp đệ quy, hiểu đơn giản là chúng ta cần dùng N vòng for lồng nhau, mỗi vòng for biến chạy sẽ chạy từ 0 đến 1.
phương pháp này để các bạn luyện tập đệ quy rất tốt, nhưng mình không khuyến khích các bạn dùng đệ quy trong khi nó có thể làm theo cách khác nha.
Source code:
#include <iostream>
using namespace std;
int N;
int x[100];
void in(int x[]){
for (int i = 1; i <= N; i++)
cout << x[i];
cout << endl;
}
void deQuy(int i){
for (int j = 0; j <= 1; j++){
x[i] = j;
if (i == N)
in(x);
else
deQuy(i + 1);
}
}
int main(){
cin >> N;
deQuy(1);
}
Phương pháp sinh
Ta thấy rằng nếu lấy lần lượt các chuỗi nhị phân độ dài N – 1, sau đó thêm ký tự 0 hoặc 1 vào cuối chuỗi đó, ta sẽ được 2 chuỗi nhị phân độ dài N.
Ví dụ như các chuỗi nhị phân độ dài 2 có các chuỗi là “00”, “01”, “10”, “11”.
Với chuỗi “00” khi ta thêm vào cuối nó ký tự ‘0’ hoặc ‘1’ ta có chuỗi “000” và “001”.
Tương tự với chuỗi “01” ta sẽ có chuỗi “010” và “011”.
Tương tự với chuỗi “10” ta sẽ có chuỗi “100” và “101”.
Tương tự với chuỗi “11” ta sẽ có chuỗi “110” và “111”.
Như vậy chỉ cần 2 ký tự “0” và “1” ta có thể sinh ra các chuỗi nhị phân độ dài N.
Source code:
#include <iostream>
#include <math.h>
using namespace std;
int N;
string a[100009];
int main(){
cin >> N;
int n = 2;
a[0] = "0";
a[1] = "1";
int k = 0;
while (a[k].length() < N){
a[n++] = a[k] + "0";
a[n++] = a[k] + "1";
k++;
}
for (int i = k; i < n; i++)
cout << a[i] << endl;
}
Tìm chuỗi nhị phân tiếp theo
Với phương pháp này ta sẽ tìm chuỗi nhị phân tiếp theo khi biết được chuỗi nhị phân trước đó, ví dụ tiếp theo của chuỗi “101” là “110”, tiếp theo của “111” là “1000”, vậy là sao làm được như vậy.
Cách làm sẽ là với chuỗi nhị phân S, để tìm chuỗi nhị phân tiếp theo của S ta sẽ làm như sau.
– Tìm vị trí index là vị trí của bit khác ‘0’ cuối cùng của S.
– Thay thì bit thứ index từ ‘0’ thành ‘1’.
– Biến đổi tất cả bit ‘1’ thành ‘0’ từ vì trí index + 1 đến hết chuỗi.
Ví dụ:
Với chuỗi S = “10011”, thì ta có bit bằng ‘0’ cuối cùng là bit thứ 3, ta biến đổi bit 3 thành 1 và bit 4, bit 5 thành 0, ta sẽ được chuỗi nhị phân tiếp theo của S là “10100”.
Source code:
#include <iostream>
#include <math.h>
using namespace std;
int N;
string next(string s){
for (int i = s.length() - 1; i >= 0; i--)
if (s[i] == '0'){
s[i] = '1';
return s;
} else
s[i] = '0';
return "";
}
int main(){
cin >> N;
string s = "";
for (int i = 0; i < N; i++)
s = "0" + s;
for (int i = 0; i < pow(2, N); i++){
cout << s << endl;
s = next(s);
}
}
+ Tính N Giai Thừa Trong C/C++ Bằng Đệ Quy Và Khử Đệ Quy
Bài viết chia sẻ thuật toán và cách tính n giai thừa trong C/C+ sử dụng hai phương pháp đệ quy và khử đệ quy. Một bài toán hay dành cho các bạn học lập trình.
Giới thiệu bài toán
Giai thừa là một bài toán kinh điển trong lập trình, nó là một bài toán mà mình tin là bất kì bạn nào mới học đều phải trải qua. Bài toán này sẽ giúp bạn hiểu được thuật toán đệ quy hoặc sử dụng thành thạo vòng lặp.
Đề bài đại loại có thể tóm tắt lại như sau: Tính n giai thừa và in kết quả ra màn hình, n nhập vào từ bàn phím.
Trước khi giải quyết bài toán, chúng ta cần hiểu định nghĩa về n! (n là một số nguyên dương): n giai thừa là tích của n số nguyên dương đầu tiên.
Công thức tổng quát: n! = n*(n-1)!
Trường hợp đặc biệt: 0! = 1
Tính N giai thừa trong CC++ bằng đệ quy và khử đệ quy
Tính giai thừa sử dụng vòng lặp
Cách tính đầu tiên này sẽ đơn giản hơn cách sử dụng đệ quy. Và nó được gọi là cách khử đệ quy bởi vì nó tránh được việc phải dùng đến đệ quy. Tùy từng trường hợp mà đệ quy và khử đệ quy có ưu điểm khác nhau.
Tư tưởng giải quyết:
Khai báo một biến để lưu giá trị và gán nó bằng 1: giai_thua = 1
Sử dụng vòng lặp chạy i từ 1 đến n sau đó gán: giai_thua = giai_thua*i
Code C/C++:
// giai thua su dung vong lap
int giaithualap(int n){
int giai_thua = 1;
for (int i = 1; i <= n; i++)
giai_thua = giai_thua * i;
return giai_thua;
}
Tính giai thừa sử dụng đệ quy
Để hiểu rõ hơn thuật toán này trước tiên bạn nên tìm hiểu thuật toán đệ quy.
Ở bài này, ta có công thức tổng quát n giai thừa là : n!=n*(n-1)!
Chính vì thế, ta cũng sử dụng lệnh truy hồi dựa trên công thức này.
Điều kiện dừng ở đây là khi n =1 (vì ta tính tích các số bắt đầu từ 1)
Code C/C++:
// tinh giai thua su dung de quy
int factorial(int n){
if(n==1)
return 1;
return(n*factorial(n-1));
}
Đánh giá cả 2 cách: Cách sử dụng đệ quy để tính giai thừa có vẻ chuyên nghiệp hơn. Tuy nhiên cách sử dụng vòng lặp có tốc độ nhanh không kém đệ quy, thậm trí là nhanh hơn.
Trong cách bài toán thực tế, nếu để lựa chọn thì các lập trình viên sẽ sử dụng cách 1 để hạn chế ít nhất việc sử dụng đệ quy.
Chú ý: Ở đây kiểu dữ liệu của hàm mình để là kiểu int, chính vì thế chỉ có thể chạy khi n <= 19 (nếu quá thì sẽ vượt kích thước của kiểu int dẫn đến sai kết quả). Nếu bạn muốn chạy được số lớn hơn thì nên để kiểu double (max n 170).
Code full bài toán nhập N và tính đệ quy:
/* Bai toan tinh N giai thua trong C++
By: https://duongdinh24.com/
github: https://github.com/duongdinh24/
*/
#include<bits/stdc++.h>
using namespace std;
// n! su dung de quy
int factorial(int n){
if(n==1)
return 1;
return(n*factorial(n-1));
}
// nn! Khu de quy su dung vong lap
int giaithualap(int n){
int giai_thua = 1;
for (int i = 1; i <= n; i++)
giai_thua = giai_thua * i;
return giai_thua;
}
int main(){
int n;
cout<<"Nhap n: "; cin>>n;
cout<<"Ket qua "<<n<<"!: "<<factorial(n); // De quy
// cout<<"Ket qua "<<n<<"!: "<<giaithualap(n); // Khu de quy
}
Ép kiểu trong C++ là việc gán giá trị của một biến có kiểu dữ liệu này tới biến khác có kiểu dữ liệu khác. Trong bài viết ngày hôm nay, chúng ta sẽ tìm hiểu kĩ hơn về ép kiểu trong C++ qua bai viết dưới đây nhé.
I. Ép Kiểu Trong C++ Là Gì
Toán tử ép kiểu (một cast) trong C++ là 1 toán tử đặc biệt mà làm một kiểu dữ liệu này biến đổi thành kiểu dữ liệu khác. Toán tử ép kiểu là một toán tử 1 ngôi và có cùng độ ưu tiên như bất kỳ toán tử một ngôi nào khác trong C++.
Cú pháp được dùng thường xuyên của toán tử ép kiểu trong C++ là:
(kieu_du_lieu) bieu_thuc
Ở đây, kieu_du_lieu là kiểu dữ liệu bạn muốn. Dưới đây là một số toán tử ép kiểu được hỗ trợ bởi C++:
const_cast<kieu_du_lieu> (bieu_thuc): Toán tử const_cast được sử dụng để ghi đè const và/hoặc volatile. Kiểu dữ liệu bạn muốn buộc phải giống như kiểu dữ liệu nguồn ngoại trừ sự sửa đổi của những thuộc tính const hoặc volatile trong một cast. Dạng ép kiểu này thao tác thuộc tính const của đối tượng đã truyền: hoặc được thiết lập hoặc gỡ bỏ.
dynamic_cast<kieu_du_lieu> (bieu_thuc): Toán tử dynamic_cast trong C++ thực hiện một ép kiểu tại runtime mà thẩm tra tính hợp lệ của cast. Nếu cast không thể được tạo ra, cast này thất bại và biểu thức ước lượng là null. Một toán tử dynamic_cast thực hiện các cast trên các kiểu đa hình và có thể ép một con trỏ A* thành một con trỏ B* chỉ khi đối tượng đang được trỏ tới thực sự là một đối tượng B.
reinterpret_cast<kieu_du_lieu> (bieu_thuc): Toán tử reinterpret_cast trong C++ thay đổi 1 con trỏ tới bất kỳ kiểu con trỏ khác. Nó cũng cho phép ép kiểu từ con trỏ tới một kiểu integer và ngược lại.
static_cast<kieu_du_lieu> (bieu_thuc): Toán tử static_cast trong C++ thực hiện một cast không có tính đa hình. Ví dụ, nó có thể được dùng để ép kiểu một con trỏ lớp cơ sở thành một con trỏ lớp kế thừa.
Tất cả toán tử ép kiểu trên sẽ được sử dụng trong lúc làm việc với lớp và đối tượng. Bây giờ, bạn thử ví dụ sau để hiểu toán tử ép kiểu đơn giản trong C++. Copy và paste chương trình C++ sau trong tệp test.cpp, sau đó biên dịch và chạy chương trình:
#include <iostream>
using namespace std;
main()
{
double a = 15.65653;
float b = 9.02;
int c ;
c = (int) a;
cout << "Dong 1: Gia tri cua (int)a la: " << c << endl ;
c = (int) b;
cout << "Dong 1: Gia tri cua (int)b la: " << c << endl ;
return 0;
}
Chạy chương trình C++ trên sẽ cho kết quả như hình sau:
Ép Kiểu Trong C++ Là Gì
II. Cách Ép Kiểu Dữ Liệu Trong C++
III. Ép Kiểu Class Trong C++
Ép kiểu (Casting) là quá trình convert từ kiểu dữ liệu A sang kiểu dữ liệu B. C++ có 2 kiểu casting.
Implicit conversion: việc ép kiểu được thực hiện tự động bởi compiler.
int iVar = 10;
float fVar = iVar; //fVar = 10.00
Explicit conversion: ép kiểu được thực hiện bởi lập trình viên.
int iVar = 20;
float fVar = (float)iVar / 10;
Ngôn ngữ C++ support 5 toán tử ép kiểu.
– static_cast
– const_cast
– reinterpret_cast
– dynamic_cast
static_cast
Ví dụ 1:
// static_cast_Operator.cpp
// compile with: /LD
class B {};
class D : public B {};
void f(B* pb, D* pd)
{
D* pd2 = static_cast<D*>(pb); // Not safe, D can have fields
// and methods that are not in B.
B* pb2 = static_cast<B*>(pd); // Safe conversion, D always
// contains all of B.
}
Trong ví dụ 1
– Line B* pb2 = static_cast<B*>(pd); an toàn vì các thuộc tính và method của class B đều thuộc class D.
– Line D* pd2 = static_cast<D*>(pb); không an toàn vì class D có các thuộc tính và method mà class B không có. Tuy nhiên, câu lệnh này không báo lỗi khi chạy runtime vì toán tử static_cast không có check runtime (khác với toán tử dynamic_cast). Điều thực sự nguy hiểm khi sử dụng *pd2 truy cập tới thuộc tinh và method chỉ thuộc lớp D và không thuộc lớp B –> Chương trình crash do ACCESS VIOLATION.
Ví dụ 2:
// static_cast_Operator_2.cpp
// compile with: /LD /GR
class B
{
public:
virtual void Test(){}
};
class D : public B {};
void f(B* pb)
{
D* pd1 = dynamic_cast<D*>(pb);
D* pd2 = static_cast<D*>(pb);
}
Trong ví dụ 2
– Nếu pb thực sự trỏ tới đối tượng class D, thì pd1 và pd2 sẽ có cùng giá trị, pd1 và pd2 sẽ có cùng giá trị nếu pd == 0 (NULL).
– Nếu pb trỏ tới đối tượng class B, thì dynamic_static sẽ trả về 0, nhưng static_cast không thể detect được vấn đề này. Do vậy, trong trường hợp sử dụng static_cast, lập trình viên phải kiểm tra nếu pb trỏ tới đối tượng class D thì mới trả về con trỏ trỏ tới class D.
Ví dụ 3:
// static_cast_Operator_3.cpp
// compile with: /LD /GR
typedef unsigned char BYTE;
void f()
{
char ch;
int i = 300;
float f = 2.5;
double dbl;
ch = static_cast<char>(i); // int to char
dbl = static_cast<double>(f); // float to double
i = static_cast<BYTE>(ch);
}
Trong ví dụ 3,
– Toán tửstatic_cast được sử dụng để ép kiểu cho các kiểu dữ basic. Từ int –> char, float –> double, char –> int.
– Câu lệnh “ch = static_cast(i); // int to char” gây ra mất dữ liệu vì kiểu char (1 byte) không đủ chứa kiểu int (4 bytes). Do vậy, chỉ ép kiểu từ kiểu dữ liệu NHỎ –> kiểu dữ liệu TO để tránh convert sai.
const_cast
const_cast < type-id > ( expression )
// expre_const_cast_Operator.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;
class CCTest
{
public:
void setNumber( int );
void printNumber() const;
private:
int number;
};
void CCTest::setNumber( int num ) { number = num; }
void CCTest::printNumber() const
{
cout << "\nBefore: " << number;
const_cast< CCTest * >( this )->number--;
cout << "\nAfter: " << number;
}
int main()
{
CCTest X;
X.setNumber( 8 );
X.printNumber();
system("pause");
}
Ép Kiểu Class Trong C++
IV. Ép Kiểu Tường Minh Trong C++
Ép kiểu tường minh (Explicit type conversion) là quá trình chuyển đổi kiểu dữ liệu một cách tường minh (rõ ràng) bởi lập trình viên, sử dụng toán tử ép kiểu (casting operator) để thực hiện việc chuyển đổi.
Trong C++, có 5 cách ép kiểu tường minh:
C-style casts
Static casts
Const casts
Dynamic casts
Reinterpret casts
Phạm vi bài học này sẽ nói về C-style casts và Static casts, 2 cách phổ biến nhất trong C++.
Dynamic casts, Const casts và Reinterpret casts cần những kiến thức chuyên sâu hơn, nên sẽ được bỏ qua trong bài học này.
C-style casts
Trong ngôn ngữ C chuẩn, ép kiểu được thực hiện thông qua toán tử (), và tên kiểu dữ liệu cần chuyển được đặt bên trong.
Ví dụ:
#include <iostream>
using namespace std;
int main()
{
int n1 = 3;
int n2 = 2;
double d = (double)n1 / n2;
cout << d << endl;
return 0;
}
Trong chương trình trên, trình biên dịch (compiler) chuyển đổi biến n1 từ kiểu int sang kiểu double thông qua ép kiểu tường minh C-style. Sau đó biểu thức có 2 toán hạng có kiểu double và kiểu int, nên toán hạng kiểu int sẽ được chuyển đổi ngầm định sang kiểu double. Vì vậy, kết quả của biểu thức là phép chia giữa 2 số chấm động kiểu double.
Ngôn ngữ C++ cho phép thực hiện ép kiểu tường minh C-style với cú pháp như một lời gọi hàm:
int n1 = 3;
int n2 = 2;
double d = double(n1) / n2;
Ép kiểu tường minh C-style không được trình biên dịch (compiler) kiểm tra tại thời điểm biên dịch (compile time), nên trình biên dịch sẽ không đưa ra những cảnh báo trong những trường hợp chuyển đổi không đúng
Chú ý: Tránh sử dụng ép kiểu tường minh C-style.
static_cast
Ngôn ngữ C++ có 1 toán tử ép kiểu gọi là static_cast. Bạn đã biết đến nó trong bài KIỂU KÝ TỰ TRONG C++ (Character):
#include <iostream>
#include <iomanip> // for std::setprecision()
using namespace std;
int main()
{
int n{ 75 };
cout << static_cast<char>(n) << endl; // in ký tự với mã ASCII 75
char ch{ 'K' };
cout << static_cast<int>(ch) << endl; // in mã ASCII của ký tự 'K'
return 0;
}
Chương trình trên sử dụng toán tử ép kiểu static cast trong C++ để in một ký tự từ một số nguyên và ngược lại.
Sử dụng static cast trong biểu thức:
Ví dụ:
#include <iostream>
using namespace std;
int main()
{
int n1 = 3;
int n2 = 2;
double d = static_cast<double>(n1) / n2;
cout << d << endl;
return 0;
}
Ưu điểm của toán tử static_cast là nó yêu cầu compiler kiểm tra kiểu dữ liệu tại thời điểm biên dịch chương trình, hạn chế được những lỗi ngoài ý muốn.
Chú ý: Nên sử dụng toán tử static_cast thay vì ép kiểu C-style.
Ép Kiểu Tường Minh Trong C++
V. Ép Kiểu Con Trỏ Trong C++
VI. Ép Kiểu Float Trong C
Chắc hẳn khi mới bắt đầu tập tành với C/C++ ai cũng gặp trường hợp phải ép kiểu int sang float và ngược lại. Vậy bài viết này mình sẽ hướng dẫn các bạn cách ép kiểu nguyên sang kiểu thực trong C/C++.
Ép từ kiểu nguyên sang kiểu thực
Ép kiểu int sang float
Có nhiều trường hợp chúng ta phải ép kiểu từ int sang float như ví dụ dưới đây
Bài toán: Nhập vào hai số nguyên dương a và b. Hãy xuất ra kết quả a/b.
#include<iostream>
using namespace std;
int main()
{
int a, b;
float kq;
cout << "Nhap a: "; cin >> a;
cout << "Nhap b: "; cin >> b;
if (!b) {// Hoặc if(b==0)
cout << "Mau phai khac 0 !";
return 0;
}
kq = a / b;
cout << "Ket qua la: " << kq << endl;
return 0;
}
Nhap a: 2
Nhap b: 3
Ket qua la: 0
Trong trường hợp trên ta bắt buộc phải ép kiểu int sang float. Cú pháp ép kiểu như sau:
kq = (float)a / (float)b;
kq = float(a) / float(b);
Ở trên có hai cách ép kiểu nhưng mình khuyên các bạn nên dùng cách dưới, nhìn dễ hiểu hơn.
Cùng xem lại đoạn code ở trên nhé
#include<iostream>
using namespace std;
int main()
{
int a, b;
float kq;
cout << "Nhap a: "; cin >> a;
cout << "Nhap b: "; cin >> b;
if (!b) {// Hoặc if(b==0)
cout << "Mau phai khac 0 !";
return 0;
}
//kq = (float)a / (float)b;
kq = float(a) / float(b);
cout << "Ket qua la: " << kq << endl;
return 0;
}
Nhap a: 2
Nhap b: 3
Ket qua la: 0.666667
Ép kiểu int sang double
Cách làm cũng tương tự như trên mà thôi.
Ép từ kiểu thực sang kiểu nguyên
Ép kiểu float sang int
Ta có thể ép kiểu dữ liệu như sau:
#include<iostream>
using namespace std;
int main()
{
float a = 3.5f;
int b = int(a);
cout << "Sau khi ep kieu: " << b << endl;
return 0;
}
Sau khi ep kieu: 3
Lưu ý: Khi ép kiểu từ int sang float thì chỉ nhận được các chữ số trước dấu phẩy mà thôi. Chứ không làm tròn lên nhé.
Ví dụ: int a = float(8.9) thì a sẽ nhận giá trị 8 mà thôi.
Ép kiểu double sang int
Tương tự như ép kểu float sang int.
Ép Kiểu Float Trong C
VII. Ép Kiểu Int Sang String Trong C++
Hướng dẫn cách chuyển int sang string trong C++. Bạn sẽ học được cách sử dụng hàm to_string() trong C++ sau bài học này.
Chúng ta có 2 phương pháp để chuyển int sang string trong C++ như sau:
Sử dụng hàm template (trước C++11)
Sử dụng hàm to_string (từ C++11)
Chuyển float và int sang string trong C++ bằng hàm template
Phương pháp đầu tiên cũng để chuyển int sang string trong C++ đó chính là sử dụng hàm template sau đây:
Hàm template trên có tác dụng chuyển đổi kiểu số bao gồm cả int lẫn float sang kiểu string. Sau khi đã khai báo template này, chúng ta có thể sử dụng nó nhiều lần trong chương trình.
Ví dụ cụ thể, chúng ta có thể chuyển float và int sang string như chương trình sau. Lưu ý chúng ta sử dụng tới hàm typeid.name() để kiểm tra kiểu của dữ liệu trước và sau lúc đổi thay bằng hàm template.
#include <iostream>
#include <sstream>
#include <typeinfo>
using namespace std;
template <typename T> string tostr(const T& t)
{
ostringstream os; os<<t; return os.str();
}
int main() {
int a = 123;
float b = 3.45;
cout << "Kieu cua a: "<< typeid(a).name() <<endl;
cout << "Kieu cua b: "<<typeid(b).name() <<endl;
//Kiểu sau khi thay đổi
cout << "Kieu thay doi cua a: "<< typeid(tostr(123)).name() <<endl;
cout << "Kieu thay doi cua b: "<<typeid(tostr(3.1415)).name() <<endl;
return 0;
}
Kết quả, hai biến a và b với kiểu int và float đã được chuyển sang kiểu string bằng hàm tostr như sau:
Kieu cua a: i
Kieu cua b: f
Kieu thay doi cua a: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Kieu thay doi cua b: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Chuyển int sang string trong C++ bằng hàm to_string
Để dùng hàm template ở trên, chúng ta đều cần khai báo template ở đầu mỗi chương trình, và chuyện này thật là rườm rà phải không nào?
Đó là lý do mà từ phiên bản C++11 trở đi, song hành cùng với hàm template ở trên thì các nhà phát triển C++ đã thêm vào 1 hàm mới có chức năng tương tự nhưng lại có thể sử dụng dễ dàng hơn, đó chính là hàm to_string.
Hàm to_string là 1 hàm được thêm vào từ phiên bản C++11, có tác dụng chuyển số sang string trong C++. Hàm to_string có thể chuyển tất cả các định dạng kiểu số như int, float, double sang string trong C++ với cú pháp đơn giản và ngắn gọn.
Chúng ta sử dụng hàm to_string() trong C++ với cú pháp sau đây:
to_string(num);
Trong đó num là số cần chuyển sang kiểu chuỗi string, và num có thể ở bất kiểu số nào, ví dụ như int, float, double hay short chẳng hạn.
Ví dụ cụ thể, chúng ta có thể chuyển int sang string trong C++ bằng hàm to_string và kiểm tra kiểu trước và sau khi chuyển đổi bằng hàm typeid.name() như sau:
#include <iostream>
#include <typeinfo>
using namespace std;
int main() {
int a = 123;
float b = 3.45;
cout << "Kieu cua a: "<< typeid(a).name() <<endl;
cout << "Kieu cua b: "<<typeid(b).name() <<endl;
//Kiểu sau khi thay đổi
cout << "Kieu thay doi cua a: "<< typeid(to_string(123)).name() <<endl;
cout << "Kieu thay doi cua b: "<<typeid(to_string(3.1415)).name() <<endl;
return 0;
}
Kết quả, hai biến a và b với kiểu int và float đã được chuyển sang kiểu string bằng hàm to_string như sau:
Kieu cua a: i
Kieu cua b: f
Kieu thay doi cua a: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Kieu thay doi cua b: NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE
Ép Kiểu Int Sang String Trong C++
VIII. Ép Kiểu String Sang Char Trong C+
IX. Ép Kiểu Int Sang Char Trong C++
X. Phân Loại Ép Kiểu Trong C++
Phân loại ép kiểu trong C++
Trong C++, có hai loại ép kiểu dữ liệu:
Nới rộng (widening): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước nhỏ hơn sang kiểu có kích thước lớn hơn. Kiểu biến đổi này không làm mất thông tin.
Thu hẹp (narrowwing): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước lớn hơn sang kiểu có kích thước nhỏ hơn. Kiểu biến đổi này có thể làm mất thông tin
1. Nới rộng (widening)
Phân Loại Ép Kiểu Trong C++
Nới rộng (widening): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước nhỏ hơn sang kiểu có kích thước lớn hơn. Kiểu biến đổi này không làm mất thông tin. Ví dụ chuyển từ int sang float. Chuyển kiểu loại này có thế được thực hiện ngầm định bởi trình biên dịch.
Ví dụ:
#include <iostream>
using namespace std;
int main() {
int i = 100;
long l = i; // khong yeu cau chi dinh ep kieu
float f = l; // khong yeu cau chi dinh ep kieu
cout << "Gia tri Int: " << i << endl;
cout << "Gia tri Long: " << l << endl;
cout << "Gia tri Float: " << f << endl;
return 0;
}
Kết quả:
Giá trị Int: 100
Giá trị Long: 100
Giá trị Float: 100
2.Thu hẹp (narrowwing)
Phân Loại Ép Kiểu Trong C++
Thu hẹp (narrowwing): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước lớn hơn sang kiểu có kích thước nhỏ hơn. Kiểu biến đổi này có thể làm mất thông tin như ví dụ ở trên. Chuyển kiểu loại này không thể thực hiện ngầm định bởi trình biên dịch, người dùng phải thực hiện chuyển kiểu tường minh.
Ví dụ:
#include <iostream>
using namespace std;
int main() {
double d = 100.04;
long l = (long) d; // yeu cau chi dinh kieu du lieu (long)
int i = (int) l; // yeu cau chi dinh kieu du lieu (int)
cout << "Gia tri Double: " << d << endl;
cout << "Gia tri Long: " << l << endl;
cout << "Gia tri Int: " << i << endl;
return 0;
}
Kết quả:
Giá trị Double: 100.04
Giá trị Long: 100
Giá trị Int: 100
Trong bài viết này chúng ta sẽ tìm hiểu về hàm sqrt() trong C++. Đây là hàm được sử dụng rất nhiều trong các ngôn ngữ lập trình khi giải các bài toán.
I. Căn Bậc 2 Trong C++ Là Gì ?
Hàm sqrt() là hàm có sẵn trong thư viện math, vì vậy trước khi sử dụng nó hãy khai báo thư viện đã nhé: #include<math.h>
Hàm sqrt() là hàm được sử dụng để tính căn bậc hai của một số. Tham số truyền vào nó là một số bất kỳ và giá trị trả về là căn bậc hai của tham số đó.
Cú pháp:
sqrt(x)
Trong đó x là một số truyền vào bất kỳ, có thể là số nguyên hoặc số thực.
Căn Bậc 2 Trong C++ Là Gì ?
II. Hàm Tính Căn Bậc 2 Trong C++
Hôm nay Techacademy quay lại cùng với một bài toán đơn giản mà phức tạp. Chắc hẳn khi học lập trình bạn nào cũng đã từng tính căn bậc hai của một số. Chắc không ai quên được tên hàm nó chính là hàm sqrt(). Nhưng các bạn đã bao giờ tự hỏi làm sao để có thể tự viết hàm sqrt() này chưa? Hãy cũng mình đi tìm giải pháp trong bài viết này nhé.
Tính căn bậc hai sử dụng hàm trong C/C++
// Code from https://techacademy.edu.vn
#include <stdio.h>
#include <math.h>
int main()
{
int x;
printf("Input x: ");
scanf("%d", &x);
printf("Sqrt of %d = %f\n", x, sqrt(x));
}
Chạy demo:
Input x: 5
Sqrt of 5 = 2.236068
Tìm căn bậc hai của một số không dùng hàm thì sao?
Ý tưởng tìm căn bậc hai:
Khai báo 1 epsilon đặt sai số chấp nhận, vì căn bậc hai của một số có thể là số thập phân vô hạn
Khởi tạo kết quả bằng 1.0
Nếu kết quả có sai số cao hơn epsilon, cập nhật lại kết quả theo công thức
result = (number/result - result) / 2 + result;
Mình sẽ thu hẹp dần giới hạn trên và giới hạn dưới của kết quả, lấy trung bình hiệu khoảng cách giới hạn đó để cập nhật kết quả. Điều này luôn đảm bảo rằng giới hạn trên dưới sẽ bị thu hẹp nhưng sẽ luôn bao bọc đáp án.
Nếu kết quả có sai số nhỏ hơn EPSILON thì dừng lại và lấy kết quả đó làm đáp án.
Ví dụ: Bạn cần tính căn bậc 2 của 5.
Ta khởi tạo kết quả là 1.0. Kết quả này dĩ nhiên không đúng rồi, nên đáp số sẽ nằm trong khoảng 1.0 và 5/1.0 = 5.0.
Lấy một nửa hiệu khoảng 1.0 đến 5.0 là 1.0 + (5.0 – 1.0)/2 được 3.0. Nhưng 3.0 lớn hơn kết quả thực(bình phương là biết, trong code thì sai số để check),
Lại lấy nửa hiệu khoảng từ 5/3.0 đến 3.0 cộng vào kết quả hiện tại(3.0) = 3.0 + (5/3.0 – 3.0) = 2.33…
Cứ làm tiếp tục như vậy cho tới khi sai số nhỏ hơn EPSILON
Code đầy đủ cho ý tưởng này là
// Code from https://techacademy.edu.vn
#include <stdio.h>
#include <math.h>
#define EPSILON 0.0001f
double mySqrt(int number)
{
double result = 1.0f;
while (fabs(result * result - number) / number >= EPSILON)
result = (number / result - result) / 2 + result;
return result;
}
int main()
{
int x;
printf("Input x: ");
scanf("%d", &x);
printf("Sqrt of %d = %1.9f\n", x, mySqrt(x));
return 0;
}
Và chạy thử xem sao
Input x: 5
Sqrt of 5 = 2.236069
Hàm Tính Căn Bậc 2 Trong C++
III. Ví Dụ Về Căn Bậc 2 Trong C++
Trong phần này mình sẽ thực hiện hai ví dụ sử dụng hàm sqrt() để các bạn có thể so sánh kết quả nhé.
Ví dụ 1: Ở ví dụ này mình sẽ khai báo và khởi tạo giá trị x = 9, sau đó gọi hàm sqrt() để tính căn bậc hai của x.
#include <iostream>
#include <math.h>
using namespace std;
int main()
{
//khai báo và khởi tạo giá trị x = 9
double x = 9, result;
//gọi hàm sqrt() để tính căn bậc hai của x
result = sqrt(x);
//hiển thị kết quả ra màn hình
cout << "Căn bậc hai của " << x << " là " << result << endl;
cout<<"\n--------------------------------------------\n";
cout<<"Chương trình này được đăng tại techacademy.edu.vn";
}
Kết quả:
Ví Dụ Về Căn Bậc 2 Trong C++
Ví dụ 2: Ở ví dụ này mình sẽ thay đổi giá trị của x thành 125, cùng xem kết quả nhé.
#include <iostream>
#include <math.h>
using namespace std;
int main()
{
//khai báo và khởi tạo giá trị x = 125
double x = 125, result;
//gọi hàm sqrt() để tính căn bậc hai của x
result = sqrt(x);
//hiển thị kết quả ra màn hình
cout << "Căn bậc hai của " << x << " là " << result << endl;
cout<<"\n--------------------------------------------\n";
cout<<"Chương trình này được đăng tại Freetuts.net";
}
Kết quả:
Ví Dụ Về Căn Bậc 2 Trong C++
IV. Căn Bậc N Trong C
Bài này mình sẽ hướng dẫn các bạn mới học lập trình cách để tính căn bậc n với n là một số bất kỳ nhé, nhưng n >= 2 nha. Chỉ là chỉ lại giúp các bạn nhớ lại toán học thôi ^^.
Một số thắc mắc của các bạn sinh viên:
Tình hình là em đang bí trong cách tính căn bậc n của 1 số. Thực tình em mới học môn này được 2 tuần nên thực tình ko biết giải quyết thế nào, ai biết phép toán của nó ra sao thì giúp em với?
Trong C chỉ có hàm sqrt để tính căn bậc 2, vậy nếu em muốn tính căn bậc 3 hay căn bậc n của một số thì làm thế nào ạ?
Tính căn bậc 3 trong C/C++
Để tính căn bậc 3 trong C/C++, bạn sử dụng công thức toán học sau. Công thức này đúng với căn bậc n luôn nhé. Bạn có thể áp dụng để tính căn bậc n của số bất kỳ:
Như vậy, với một số x = 8 chẳng hạn, bạn muốn tính căn bậc 3 của 8. Khi đó, trong công thức trên x = 8, n = 3, m = 1. Vậy ta sẽ phải tính x1/3
Để tính ax trong C/C++, chúng ta có thể sử dụng hàm pow() có cú pháp như sau:
double pow (double base, double exponent);
Ví dụ dưới đây sử dụng C/C++ để tính căn bậc 3 của 8 theo cách trên. Các bạn lưu ý em kiểu cho số mũ nhé. Vì 2 số nguyên chia cho nhau sẽ chỉ ra giá trị nguyên(1/3 = 0).
#include <stdio.h>
#include <math.h>
int main ()
{
int a = 8;
printf("%f", pow(a, 1.0/3));
}
Kết quả: 2.000000
Như vậy, để tính căn bậc n của số a, bạn sẽ dùng hàm pow để tính kết quả của phép tính a1/n
Tính căn bậc 3 không dùng hàm pow
Để tính căn bậc 3 của 1 số dương a ( tổnq quát căn bậc n ), ta phải xây dựng một chuỗi ( toán học ) mà chuỗi này hội tụ về căn bậc 3 của a. Sau đó lặp hữu hạn 1 số lần để được kết quả với độ chính xác nào đó chấp nhận được. Sau day la chuong trinh trong C de thuật tính căn bậc 3 cua 2 voi độ chính xác 0.00000001 chỉ bằng các phép toán +, – , *, / mà thôi.
Dưới đây là lời giải tính căn bậc 3 của 2 của một thành viên trong Cộng đồng C Việt:
////////////////////////////////////////////////////////////////////////
// Cube root of 2 by Nguyen Van Noi - DHTG
// Email : nvnoi76@yahoo.com
///////////////////////////////////////////////////////////////////////
#include <stdio.h>
double myabs(double x)
{
return ((x>=0)?x:(-x));
}
void main()
{
double a=2.0, xo, xn=1, e=1e-8;
do
{
xo=xn;
xn=(a/xo/xo+2.0*xo)/3.0;
}
while (myabs(xn-xo)>e);
printf("Can bac 3 cua 2 = %1.8f\n",xn);
}
Một lời giải khác do mình tổng hợp:
Ý tưởng: Tìm phần nguyên trước, sau đó tìm phần thập phân.
Với cách làm này, bạn chỉ thu được kết quả xấp xỉ với đáp án chính xác. Độ chính xác phụ thuộc vào giá trị precision mà bạn mong muốn.
#include <stdio.h>
double cubeRoot(double n) {
double i, precision = 0.000001;
for(i = 1; (i*i*i) <= n; ++i); //tim phan nguyen
for(--i; (i*i*i) < n; i += precision); //tim phan thap phan
return i;
}
int main() {
int n = 125;
printf("Can bac ba cua %d = %lf", n, cubeRoot(n));
return 0;
}
Chạy thử:
Can bac 3 cua 125 = 5.000000
Căn Bậc N Trong C
V. Dấu Căn Bậc 2 Trong C++
Hàm sqrt() trong C
Hàm double sqrt(double x) trong Thư viện C trả về căn bậc hai của x.
Khai báo hàm sqrt() trong C
Dưới đây là phần khai báo cho hàm sqrt() trong C:
double sqrt(double x)
Tham số
x − Đây là giá trị số thực dấu chấm động.
Trả về giá trị
Hàm này trả về căn bậc hai của x.
Ví dụ
Chương trình C sau minh họa cách sử dụng của hàm sqrt() trong C:
#include <stdio.h>
#include <math.h>
int main ()
{
printf("Can bac hai cua %lf bang %lf\n", 4.0, sqrt(4.0) );
printf("Can bac hai cua %lf bang %lf\n", 5.0, sqrt(5.0) );
return(0);
}
Biên dịch và chạy chương trình C trên sẽ cho kết quả:
Dấu Căn Bậc 2 Trong C++
VI. Hàm Căn Bậc 3 Trong C++
Bài tập C: Tìm căn bậc ba của một số
Tương tự như cách tìm căn bậc hai (không sử dụng hàm sqrt()), bài tập C này có thể được giải theo hai bước:
Tìm phân nguyên
Tìm phần thập phân
Đây chỉ là cách giải cho kết quả xấp xỉ, và độ chính xác phụ thuộc vào số các số sau dấu thập phân.
Bạn theo dõi phần code dưới đây để hiểu cách tìm căn bậc ba của một số bất kỳ trong C.
Chương trình C
Dưới đây là chương trình C để giải bài tập tìm căn bậc ba của một số trong C:
#include <stdio.h>
double cubeRoot(double n) {
double i, precision = 0.000001;
for(i = 1; (i*i*i) <= n; ++i); //tim phan nguyen
for(--i; (i*i*i) < n; i += precision); //tim phan thap phan
return i;
}
int main() {
int n = 125;
printf("Can bac ba cua %d = %lf", n, cubeRoot(n));
return 0;
}
Biên dịch chương trình C trên sẽ cho kết quả:
Hàm Căn Bậc 3 Trong C++
VII. Bài Tập Về Căn Bậc 2 Trong C++
Đề bài
Viết chương trình giải phương trình bậc 2 trong C++. Phương trình bậc 2 có dạng:
Bài Tập Về Căn Bậc 2 Trong C++
Lời giải
Lời giải của chúng tôi sử dụng phương thức sqrt() để tính căn bậc 2 của một số trong C++.
#include <iostream>
#include <cmath>
using namespace std;
/**
* giai phuong trinh bac 2: ax2 + bx + c = 0
*
* @param a: he so bac 2
* @param b: he so bac 1
* @param c: so hang tu do
*/
void giaiPTBac2(float a, float b, float c) {
// kiem tra cac he so
if (a == 0) {
if (b == 0) {
printf("Phuong trinh vo nghiem!");
} else {
printf("Phuong trinh co mot nghiem: x = %f", (-c / b));
}
return;
}
// tinh delta
float delta = b*b - 4*a*c;
float x1;
float x2;
// tinh nghiem
if (delta > 0) {
x1 = (float) ((-b + sqrt(delta)) / (2*a));
x2 = (float) ((-b - sqrt(delta)) / (2*a));
printf("Phuong trinh co 2 nghiem la: x1 = %f va x2 = %f", x1, x2);
} else if (delta == 0) {
x1 = (-b / (2 * a));
printf("Phong trinh co nghiem kep: x1 = x2 = %f", x1);
} else {
printf("Phuong trinh vo nghiem!");
}
}
/**
* ham main
*/
int main() {
float a, b, c;
cout << "Nhap he so bac 2, a = ";
cin >> a;
cout << "Nhap he so bac 1, b = ";
cin >> b;
cout << "Nhap so hang tu do, c = ";
cin >> c;
giaiPTBac2(a, b, c);
return 1;
}
Kết quả:
Nhap he so bac 2, a = 2
Nhap he so bac 1, b = 3
Nhap so hang tu do, c = 1
Phuong trinh co 2 nghiem la: x1 = -0.500000 va x2 = -1.000000
VIII. Tìm Căn Bậc Hai Không Dùng Sqrt
Tìm căn bậc hai không sử dụng hàm sqrt? Bạn nghĩ sao? Hôm nay Techacademy quay lại cùng với một bài toán đơn giản mà phức tạp. Chắc hẳn khi học lập trình bạn nào cũng đã từng tính căn bậc hai của một số. Chắc không ai quên được tên hàm nó chính là hàm sqrt(). Nhưng các bạn đã bao giờ tự hỏi làm sao để có thể tự viết hàm sqrt() này chưa? Hãy cũng mình đi tìm giải pháp trong bài viết này nhé.
Tìm Căn Bậc Hai Không Dùng Sqrt
Tìm căn bậc hai của một số không dùng hàm thì sao?
Ý tưởng tìm căn bậc hai:
Khai báo 1 epsilon đặt sai số chấp nhận, vì căn bậc hai của một số có thể là số thập phân vô hạn.
Khởi tạo kết quả bằng 1.0
Nếu kết quả có sai số cao hơn epsilon, cập nhật lại kết quả theo công thức
result = (number/result – result) / 2 + result;
Mình sẽ thu hẹp dần giới hạn trên và giới hạn dưới của kết quả, lấy trung bình hiệu khoảng cách giới hạn đó để cập nhật kết quả. Điều này luôn đảm bảo rằng giới hạn trên dưới sẽ bị thu hẹp nhưng sẽ luôn bao bọc đáp án.
Nếu kết quả có sai số nhỏ hơn EPSILON thì dừng lại và lấy kết quả đó làm đáp án.
Ví dụ: Bạn cần tính căn bậc 2 của 5.
Ta khởi tạo kết quả là 1.0. Kết quả này dĩ nhiên không đúng rồi, nên đáp số sẽ nằm trong khoảng 1.0 và 5/1.0 = 5.0.
Lấy một nửa hiệu khoảng 1.0 đến 5.0 là 1.0 + (5.0 – 1.0)/2 được 3.0. Nhưng 3.0 lớn hơn kết quả thực(bình phương là biết, trong code thì sai số để check),
Lại lấy nửa hiệu khoảng từ 5/3.0 đến 3.0 cộng vào kết quả hiện tại(3.0) = 3.0 + (5/3.0 – 3.0) = 2.33…
Cứ làm tiếp tục như vậy cho tới khi sai số nhỏ hơn EPSILON
Code đầy đủ cho ý tưởng này là
// Code from https://nguyenvanhieu.vn
#include <stdio.h>
#include <math.h>
#define EPSILON 0.0001f
double mySqrt(int number)
{
double result = 1.0f;
while (fabs(result * result – number) / number >= EPSILON)
result = (number / result – result) / 2 + result;
return result;
}
int main()
{
int x;
printf(“Input x: “);
scanf(“%d”, &x);
printf(“Sqrt of %d = %1.9fn”, x, mySqrt(x));
return 0;
}
Con trỏ trong lập trình là 1 định nghĩa hơi khó nhằn đối với các bạn mới học về C++. Không ngoa khi nói rằng C++ khó vì có con trỏ. Tuy nhiên ví như làm chủ được con trỏ, bạn có thể hiểu và thao tác với dữ liệu trong bộ nhớ máy tính, và những kiến thức liên quan mà bạn học được thông qua con trỏ cũng rất hữu ích cho việc học các ngôn ngữ hướng đối tượng sau này như Java chẳng hạn.
Hãy cùng tìm hiểu con trỏ trong C++ là gì, quan hệ giữa con trỏ và địa chỉ trong C++, cấu trúc, vai trò và cách sử dụng con trỏ sau bài học này nhé.
I. Khai Báo Con Trỏ Trong C++
Để khai báo con trỏ trong C++, chung ta sử dụng với cấu trúc ngữ pháp sau đây:
type *p;
Trong đó type là kiểu dữ liệu của con trỏ, và p là tên con trỏ. Lưu ý là kiểu dữ liệu của con trỏ phải giống với kiểu dữ liệu của dữ liệu cần lưu địa chỉ trong con trỏ.
Ví dụ, chúng ta khai báo con trỏ tên p với kiểu int như sau:
int *p;
Lưu ý là các cách viết sau đây cũng được chấp nhận khi khai báo con trỏ trong C++:
int* p;
int * p;
Khai Báo Con Trỏ Trong C++
II. Cấp Phát Bộ Nhớ Cho Con Trỏ Trong C++
Ngôn ngữ C++ hỗ trợ ba loại cấp phát bộ nhớ cơ bản, hai loại trong số đó bạn đã được học ở các bài học trước:
1. Cấp phát bộ nhớ tĩnh (Static memory allocation):
Xảy ra trên các biến tĩnh và biến toàn cục.
Vùng nhớ của các loại biến này được cấp phát một lần khi chương trình bắt đầu chạy và vẫn tồn tại trong suốt thời gian tồn tại của chương trình.
Kích thước của biến/mảng nên được biết tại thời điểm biên dịch chương trình.
2. Cấp phát bộ nhớ tự động (Automatic memory allocation):
Xảy ra trên các tham số hàm và biến cục bộ.
Vùng nhớ của các loại biến này được cấp phát khi chương trình đi vào khối lệnh và được giải phóng khi khối lệnh bị thoát.
Kích thước của biến/mảng phải được biết tại thời điểm biên dịch chương trình.
3. Cấp phát bộ nhớ động (Dynamic memory allocation) sẽ được kể tới trong bài học này.
Trong tất cả những trường hợp, cấp phát bộ nhớ tĩnh và tự động có thể đáp ứng tốt các đề nghị của chương trình. Tuy nhiên, ta cùng xem ví dụ bên dưới:
Ví dụ: Chúng ta cần sử dụng một chuỗi để lưu tên của người dùng, nhưng chúng ta không biết tên của họ dài bao nhiêu cho đến khi họ nhập tên. Hoặc chúng ta cần lưu trữ danh sách nhân viên trong một công ty, nhưng chúng ta không biết trước được công ty đó sẽ có bao nhiêu nhân viên.
Đối với cấp phát bộ nhớ tĩnh và tự động, kích thước của biến/mảng phải được biết tại thời điểm biên dịch chương trình. Vì vậy, điều tốt nhất chúng ta có thể làm là cố gắng đoán một kích thước tối đa của các biến đó:
char name[30]; // tên không quá 30 ký tự
Staff staff[500]; // công ty không quá 500 nhân viên
Khuyết điểm của cách khai báo trên:
+ Gây lãng phí bộ nhớ nếu các biến không thực sự sử dụng hết kích thước khi khai báo. Ví dụ: nếu công ty chỉ có 100 nhân viên, chúng ta có 400 vùng nhớ nhân viên không được sử dụng tới.
+ Thứ hai, hầu hết các biến thông thường (bao gồm mảng tĩnh) được cấp phát trong một phần bộ nhớ gọi là ngăn xếp (stack). Kích thước bộ nhớ stack cho một chương trình khá nhỏ (khoảng 1Mb với Visual Studio), nếu yêu cầu cấp phát vùng nhớ vượt quá con số này, chương trình sẽ bị đóng bởi hệ điều hành với lỗi stack overflow.
char byte[1000000 * 2]; // khoảng 2Mb bộ nhớ => lỗi stack overflow
+ Thứ ba, điều gì xảy ra nếu công ty có 600 nhân viên, trong khi mảng staff chỉ có 500 phần tử. Lúc này, chương trình sẽ bị giới hạn bởi kích thước được khai báo ban đầu.
Để giải quyết những hạn chế trên, cấp phát bộ nhớ động được ra đời.
Cấp Phát Bộ Nhớ Cho Con Trỏ Trong C++
III. Con Trỏ Trong C++ Dùng Để Làm Gì
Tác dụng của con trỏ trong c chính là để lưu giữ địa chỉ của dữ liệu trong bộ nhớ máy tính, và bằng cách truy cập vào địa chỉ này, chúng ta có thể lấy được giá trị của dữ liệu tại đó.
Ngoài ra thì giá trị của con trỏ cũng là một số, nên chúng ta cũng có thể thực hiện các phép tính toán với con trỏ, ví dụ như cộng thêm hoặc hoặc trừ đi một số lượng đơn vị.
Do đó, con trỏ trong C sẽ được dùng để làm 1 trong 2 công việc sau đây trong chương trình:
Thao tác với địa chỉ bằng các phép tính toán với số được lưu trong nó
Thao tác với giá trị tại địa chỉ mà nó lưu mà thôi.
Con Trỏ Trong C++ Dùng Để Làm Gì
IV. Mảng Con Trỏ Trong C++
Trước khi chúng ta hiểu về khái niệm mảng các con trỏ, chúng ta xem xét ví dụ sau, mà sử dụng một mảng gồm 3 số integer:
#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
int var[MAX] = {10, 100, 200};
for (int i = 0; i < MAX; i++)
{
cout << "Gia tri cua var[" << i << "] = ";
cout << var[i] << endl;
}
return 0;
}
Khi code trên được biên dịch và thực thi, nó cho kết quả sau:
Gia tri cua var[0] = 10
Gia tri cua var[1] = 100
Gia tri cua var[2] = 200
Có một tình huống khi chúng ta muốn duy trì một mảng, mà có thể lưu giữ các con trỏ tới một kiểu dữ liệu int hoặc char hoặc bất kỳ kiểu nào khác. Sau đây là khai báo một mảng của các con trỏ tới một integer:
int *contro[MAX];
Nó khai báo contro như là một mảng các con trỏ MAX kiểu integer. Vì thế, mỗi phần tử trong contro, bây giờ giữ một con trỏ tới một giá trị int. Ví dụ sau sử dụng 3 số integer, mà sẽ được lưu giữ trong một mảng các con trỏ như sau:
#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
int var[MAX] = {10, 100, 200};
int *contro[MAX];
for (int i = 0; i < MAX; i++)
{
contro[i] = &var[i]; // gan dia chi cua so nguyen.
}
for (int i = 0; i < MAX; i++)
{
cout << "Gia tri cua var[" << i << "] = ";
cout << *contro[i] << endl;
}
return 0;
}
Khi code trên được biên dịch và thực thi, nó cho kết quả sau:
Gia tri cua var[0] = 10
Gia tri cua var[1] = 100
Gia tri cua var[2] = 200
Bạn có thể sử dụng một mảng các con trỏ tới ký tự để lưu giữ một danh sách các chuỗi như sau:
#include <iostream>
using namespace std;
const int MAX = 4;
int main ()
{
char *tensv[MAX] = {
"Nguyen Thanh Tung",
"Tran Minh Chinh",
"Ho Ngoc Ha",
"Hoang Minh Hang",
};
for (int i = 0; i < MAX; i++)
{
cout << "Gia tri cua tensv[" << i << "] = ";
cout << tensv[i] << endl;
}
return 0;
}
Chạy chương trình C++ trên sẽ cho kết quả như hình sau:
Mảng Con Trỏ Trong C++
V. Con Trỏ Hàm Trong C++
Con trỏ hàm là một biến lưu trữ địa chỉ của một hàm, thông qua biến đó, ta có thể gọi hàm mà nó trỏ tới.
Cú pháp khai báo con trỏ hàm:
<kiểu trả về> (*<tên con trỏ>)(<danh sách tham số>);
int(*fcnPtr)(int); // con trỏ hàm nhận vào 1 biến kiểu int và trả về kiểu int
void(*fcnPtr)(int, int); // con trỏ hàm nhận vào 2 biến kiểu int và trả về kiểu void
Chú ý: Dấu ngoặc () quanh *fcnPtr là bắt buộc.
VI. Con Trỏ Trong Class C++
Một con trỏ tới một lớp trong C++ được thực hiện theo cách giống hệt như một con trỏ tới một cấu trúc; và để truy cập các thành viên của một con trỏ tới một lớp bạn sử dụng toán tử truy cập thành viên trong C++ là toán tử ->, như khi bạn thực hiện với các con trỏ tới cấu trúc. Cũng như với tất cả con trỏ, bạn phải khai báo con trỏ trước khi sử dụng nó.
Bạn thử ví dụ sau để hiểu khái niệm con trỏ tới một lớp trong C++:
#include <iostream>
using namespace std;
class Box
{
public:
// phan dinh nghia Constructor
Box(double dai=1.0, double rong=1.0, double cao=1.0)
{
cout <<"Constructor duoc goi." << endl;
chieudai = dai;
chieurong = rong;
chieucao = cao;
}
double theTich()
{
return chieudai * chieurong * chieucao;
}
private:
double chieudai; // chieu dai cua mot box
double chieurong; // chieu rong cua mot box
double chieucao; // chieu cao cua mot box
};
int main(void)
{
Box Box1(2.4, 4.2, 2.2); // khai bao box1
Box Box2(4.5, 2.0, 3.2); // khai bao box2
Box *controBox; // khai bao con tro toi mot class.
// luu giu dia chi cua doi tuong dau tien
controBox = &Box1;
// bay gio thu truy cap mot thanh vien boi su dung toan tu truy cap thanh vien
cout << "The tich cua Box1 la: " << controBox->theTich() << endl;
// luu giu dia chi cua doi tuong thu hai
controBox = &Box2;
// bay gio thu truy cap mot thanh vien boi su dung toan tu truy cap thanh vien
cout << "The tich cua Box2 la: " << controBox->theTich() << endl;
return 0;
}
Biên dịch và chạy chương trình C++ trên sẽ cho kết quả sau:
Con Trỏ Trong Class C++
VII. Con Trỏ Trong Struct C++
Chào các bạn đang theo dõi khóa học lập trình trực tuyến ngôn ngữ C++.
Chúng ta cùng tiếp tục tìm hiểu về kiểu dữ liệu tự định nghĩa thông qua từ khóa struct mà ngôn ngữ C++ hỗ trợ. Trong bài học này, mình sẽ trình bày về kiểu struct khi sử dụng kết hợp với con trỏ.
Như các bạn đã học trong bài trước, sau khi chúng ta tự định nghĩa một struct, compiler sẽ coi tên gọi của struct đó như là một kiểu dữ liệu. Điều này có nghĩa khi chúng ta sử dụng các kiểu dữ liệu built-in để tạo ra các biến, tham chiếu hoặc con trỏ thì chúng ta cũng có thể sử dụng kiểu struct để tạo ra biến struct, tham chiếu struct và con trỏ kiểu struct (Pointer to struct).
Pointer to struct
Đầu tiên, chúng ta cùng định nghĩa một kiểu dữ liệu theo ý muốn. Dưới đây, mình định nghĩa một kiểu dữ liệu có tên là Letter:
struct Letter
{
};
Trong struct Letter mình chưa định nghĩa các trường dữ liệu, lúc này Letter là một kiểu dữ liệu rỗng. Nhưng ngôn ngữ C++ vẫn đặt kích thước của kiểu Letter này là 1 bytes.
Mục đích là để đảm bảo địa chỉ của 2 biến được tạo ra sẽ có địa chỉ khác nhau. Tuy nhiên, định nghĩa ra một struct rỗng không có tác dụng gì trong chương trình, chúng ta cùng thêm vào một số trường dữ liệu cho Letter:
struct Letter
{
char from[50];
char to[50];
};
Một lá thư sẽ có thông tin về người gửi và người nhận, nên mình thêm vào 2 trường dữ liệu kiểu C-style string dùng để lưu thông tin mà người dùng điền vào một lá thư.
Mình vừa định nghĩa xong một kiểu dữ liệu mới để phục vụ cho chương trình của mình. Bây giờ chúng ta cùng tạo ra một đơn vị từ kiểu dữ liệu trên (mình thao tác luôn trong hàm main):
int main()
{
Letter myLetter;
return 0;
}
Với mỗi biến kiểu Letter được tạo ra, chương trình sẽ yêu cầu cấp phát 100 bytes (50 bytes cho trường dữ liệu from và 50 bytes cho trường dữ liệu to), và chắc chắn rồi, biến đó sẽ có một địa chỉ xác định được thông qua toán tử address-of.
int main()
{
Letter myLetter;
std::cout << "Address of myLetter: " << &myLetter << std::endl;
std::cout << "Address of from field: " << &myLetter.from << std::endl;
return 0;
}
Ở đoạn chương trình trên, mình in ra địa chỉ của biến myLetter, đồng thời in ra luôn địa chỉ của trường dữ liệu from của biến myLetter. Kết quả cho thấy 2 địa chỉ được in ra có giá trị hoàn toàn giống nhau. Điều này có nghĩa địa chỉ của trường dữ liệu đầu tiên trong một biến struct cũng là địa chỉ của biến struct đó.
Các bạn có thể liên hệ struct với mảng một chiều trong C/C++, khi mảng một chiều mà tập hợp các phần tử có cùng kiểu dữ liệu được bao bọc bởi tên mảng một chiều và địa chỉ của mảng một chiều cũng là địa chỉ của phần tử đầu tiên trong mảng, một biến struct sẽ bao gồm tập hợp các trường dữ liệu mà địa chỉ của biến struct sẽ là địa chỉ của trường dữ liệu được khai báo đầu tiên trong struct.
Và như các bạn cũng đã học về con trỏ (Pointer), kiểu dữ liệu của con trỏ dùng để xác định kiểu dữ liệu của vùng nhớ mà con trỏ có thể trỏ đến. Vậy thì để cho con trỏ trỏ đến một địa chỉ của biến kiểu struct, chúng ta cần có một con trỏ cùng kiểu struct với biến được trỏ đến.
Letter myLetter;
Letter *pLetter = &myLetter;
Dù kích thước của kiểu dữ liệu struct có lớn bao nhiêu, biến con trỏ cũng chỉ có kích thước 4 bytes trên hệ điều hành 32 bits và kích thước 8 bytes trên hệ điều hành 64 bits (đủ để trỏ đến toàn bộ địa chỉ trên bộ nhớ ảo).
Access struct members
Trong bài học trước, các bạn đã biết cách truy cập đến các trường dữ liệu của các biến struct thông qua member selection operator (dấu chấm). Nhưng khi sử dụng Pointer to struct, member selection operator được sử dụng dưới cách viết khác. Để phân biệt sự khác nhau khi sử dụng member selection operator cho biến struct thông thường và một Pointer to struct, các bạn cùng xem ví dụ bên dưới:
struct BankAccount
{
__int64 accountNumber;
__int64 balance;
};
int main()
{
BankAccount myAccount = { 123456789, 50 }; // $50
BankAccount *pAccount = &myAccount;
std::cout << "My bank account number: " << myAccount.accountNumber << std::endl;
std::cout << "My bank account number: " << pAccount->accountNumber << std::endl;
std::cout << "My balance: " << myAccount.balance << std::endl;
std::cout << "My balance: " << pAccount->balance << std::endl;
return 0;
}
Như các bạn thấy, kết quả của việc truy xuất giá trị thông qua tên biến struct và con trỏ kiểu struct là hoàn toàn giống nhau, và chúng đều dùng toán tử member selection. Tuy nhiên, để phân biệt biến con trỏ và biến thông thường, biến con trỏ kiểu struct sẽ truy cập đến các trường dữ liệu trong vùng nhớ bằng toán tử (->). Hai toán tử này cùng tên, chỉ khác nhau về cách biểu diễn.
Một số nhầm lần khi sử dụng struct và Pointer to struct
Khi mới tìm hiểu về Pointer to struct, các bạn có thể bị nhầm lẫn giữa cách khởi tạo hoặc gán giá trị cho biến struct thông thường và biến con trỏ struct.
Đoạn chương trình trên báo lỗi vì biến con trỏ chỉ nhận giá trị là địa chỉ. Tuy nhiên, lỗi này có thể thấy dễ dàng vì Visual studio đưa ra thông báo lỗi ngay. Dưới đây là cách gán giá trị đúng khi mình sử dụng toán tử dereference cho biến con trỏ struct để thay đổi giá trị bên trong vùng nhớ:
Và các bạn lưu ý khi sử dụng biến kiểu con trỏ struct thì chúng ta sử dụng toán tử member selection này (->). Có một số bạn nhầm lẫn giữa biến con trỏ struct và trường dữ liệu kiểu con trỏ. Ví dụ:
Mình thêm vào struct một trường dữ liệu kiểu con trỏ char nhưng việc truy xuất đến trường dữ liệu này không có gì thay đổi khi mình sử dụng biến struct thông thường.
Như các bạn thấy, từ biến con trỏ pAccount truy xuất vào các trường dữ liệu bên trong thì mình dùng toán tử (->), nhưng trường dữ liệu Date trong struct BankAccount là biến thông thường, nên mình dùng dấu chấm để truy xuất dữ liệu ngày đăng kí.
Con Trỏ Trong Struct C++
VIII. Con Trỏ Null Trong C++
Con trỏ NULL trong C++ là một hằng với một giá trị là 0 được định nghĩa trong một vài thư viện chuẩn, gồm iostream.
#include <iostream>
using namespace std;
int main () {
int *ptr = NULL;
cout << "Gia tri cua contro la " << ptr ;
return 0;
}
Kết quả:
Gia tri cua contro la 0
Trên hầu hết các hệ điều hành, các chương trình không được phép truy cập bộ nhớ tại địa chỉ 0, vì bộ nhớ đó được dự trữ bởi hệ điều hành. Tuy nhiên, địa chỉ bộ nhớ 0 có ý nghĩa đặc biệt, nó chỉ ra rằng con trỏ không được trỏ tới một vị trí ô nhớ có thể truy cập. Nhưng theo qui ước, nếu một con trỏ chứa giá trị 0, nó được xem như là không trỏ tới bất cứ thứ gì.
Để kiểm tra một con trỏ null trong C++, bạn có thể sử dụng lệnh if như sau:
if(contro) // true neu contro khong la NULL
if(!contro) // true neu contro la NULL
Con Trỏ Null Trong C++
IX. Nhập Xuất Mảng Bằng Con Trỏ Trong C++
Bằng cách sử dụng con trỏ mảng, chúng ta có thể chỉ định vị trí các phần tử trong mảng, cũng như là truy cập và lấy giá trị của các phần tử đó.
Ứng dụng điều này, chúng ta cũng có thể nhập xuất mảng bằng con trỏ trong C++ như sau.
Nhập mảng bằng con trỏ trong C++
Trong bài Nhập xuất mảng trong C++ chúng ta đã biết cách tạo hàm nhập trực tiếp các giá trị từ bàn phím vào mảng như sau:
/*Tạo hàm nhập mảng 1 chiều trong C++*/
void input_array(int array[], int length){
//array: tên mảng
//length: độ dài mảng
for (short i = 0; i < length; i++) cin >> array[i];
}
Trong đó array và length lần lượt là tên và độ dài (số phần tử) của mảng cần nhập.
Để nhập mảng bằng con trỏ trong C++ chúng ta chỉ cần sử dụng giá trị con trỏ thay cho mảng, và thay vì dùng index để chỉ định vị trí nhập dữ liệu, thì chúng ta sẽ sử dụng trực tiếp giá trị con trỏ để chỉ định vị trí cần nhập. Chúng ta viết hàm nhập mảng bằng con trỏ trong C++ như sau:
/*Tạo hàm nhập mảng bằng con trỏ trong C++*/
void input_array(int array[], int length){
//array: tên mảng
//length: độ dài mảng
for (short i = 0; i < length; i++) cin >> *(array + i);
}
Lưu ý tại đây chúng ta sử dụng lệnh cin để nhập dữ liệu vào mảng, nên cần chỉ định bản thân dữ liệu mà con trỏ chỉ đến bằng cách thêm dấu hoa thị * trước con trỏ.
Tuy nhiên nếu sử dụng hàm scanf được kế thừa từ ngôn ngữ C để nhập mảng bằng con trỏ trong C++, thay vì chỉ định dữ liệu thì chúng ta cần chỉ định địa chỉ của dữ liệu đã được lưu vào con trỏ như sau:
/*Tạo hàm nhập mảng bằng con trỏ trong C*/
void input_array(int *array, int length){
//array: tên mảng
//length: độ dài mảng
for (short i = 0; i < length; i++) scanf("%d", (array + i));
}
Xuất mảng bằng con trỏ trong C++
Trong bài xuất xuất mảng trong C++ chúng ta đã biết cách tạo hàm xuất trực tiếp các giá trị của mảng như sau:
/*Tạo hàm xuất mảng 1 chiều trong C++*/
void show_array(int array[], int length){
//array: tên mảng
//length: độ dài mảng
for(short i = 0; i < length; i++) cout << array[i] <<' ';
cout << endl;
}
Trong đó array và length lần lượt là tên và độ dài (số phần tử) của mảng cần xuất.
Để xuất mảng bằng con trỏ trong C++ chúng ta chỉ cần sử dụng con trỏ thay cho mảng, và thay vì truy xuất giá trị các phần tử của mảng bằng index thì chúng ta sẽ dùng tên con trỏ và dấu hoa thị để xuất giá trị đó. Chúng ta viết hàm xuất mảng bằng con trỏ trong C++ như sau:
/*Tạo hàm xuất mảng bằng con trỏ trong C++*/
void show_array(int array[], int length){
//array: tên mảng
//length: độ dài mảng
for(short i = 0; i < length; i++) cout << *(array + i) <<' ';
cout << endl;
}
Chương trình mẫu nhập xuất mảng bằng con trỏ trong C++
Dưới đây là chương trình mẫu sử dụng các hàm trên để nhập xuất mảng bằng con trỏ trong C++:
#include <iostream>
using namespace std;
/*Tạo hàm nhập mảng bằng con trỏ trong C++*/
void input_array(int array[], int length){
//array: tên mảng
//length: độ dài mảng
for (short i = 0; i < length; i++) cin >> *(array + i);
}
/*Tạo hàm xuất mảng bằng con trỏ trong C++*/
void show_array(int array[], int length){
//array: tên mảng
//length: độ dài mảng
for(short i = 0; i < length; i++) cout << *(array + i) <<' ';
cout << endl;
}
int main(){
/*Nhập mảng bằng con trỏ trong C++*/
int n;
cout << ">>Nhap so phan tu: ";
cin >> n;
int array[n], *p;
cout << (">>Nhap phan tu:\n");
input_array(array, n);
/*Xuất mảng bằng con trỏ trong C++*/
cout << (">>Mang vua nhap:\n");
show_array(array, n);
}
C và C++ hỗ trợ con trỏ cái mà khác với hầu hết các ngôn ngữ lập trình khác. Các ngôn ngữ khác bao gồm C ++, Java, Python, Ruby, Perl và PHP đều hỗ trợ tham chiếu.
Nhìn bề ngoài, cả hai tham chiếu và con trỏ đều rất giống nhau, cả hai đều được sử dụng để có một biến cung cấp quyền truy cập cho một biến khác. Với việc cả hai đều cung cấp nhiều khả năng giống nhau, người ta thường không rõ điều gì khác biệt giữa các cơ chế khác nhau này. Trong bài viết này, tôi sẽ cố gắng minh họa sự khác biệt giữa con trỏ và tham chiếu.
Con trỏ: Con trỏ là một biến chứa địa chỉ bộ nhớ của một biến khác. Một con trỏ cần được deference với toán tử * để truy cập vào vị trí bộ nhớ mà nó trỏ tới.
Tham chiếu: Một biến tham chiếu là một bí danh, tức là một tên khác của một biến đã tồn tại. Một tham chiếu, giống như một con trỏ, cũng được hiện thựcbằng cách lưu trữ địa chỉ của một đối tượng.
Một tham chiếu có thể được coi là một con trỏ hằng (đừng nhầm với một con trỏ đến một giá trị không đổi!) với tính năng tự động chuyển hướng, tức là trình biên dịch sẽ áp dụng toán tử * cho bạn.
int i = 3;
// Một con trỏ đến biến i
// (lưu trữ địa chỉ của i)
int *ptr = &i;
// Một tham chiếu (bí danh) của i.
int &ref = i;
Sự khác nhau giữa Con trỏ và Tham chiếu
Khởi tạo
Một con trỏ có thế được khai báo và khởi tạo đồng thời hoặc trên nhiều dòng.
C++
int a = 10;
int *p = &a;
hay
int *p;
p = &a;
Trong khi tham chiếu phải được khởi tạo cùng lúc với khai báo.
int a=10;
int &p=a; //OK
nhưng
int &p;
p=a; //NOK
Chú ý: Sự khác biệt này có thể thay đổi từ trình biên dịch này sang trình biên dịch khác. Sự khác biệt trên là đối với Turbo IDE.
Gán lại giá trị
Một con trỏ có thể được gán lại. Thuộc tính này hữu ích cho việc triển khai các cấu trúc dữ liệu như danh sách liên kết, cây, v.v. Xem ví dụ sau:
int a = 5;
int b = 6;
int *p;
p = &a;
p = &b;
Trong khi đó, tham chiếu không gán lại mà phải được gán ngay lúc khởi tạo.
int a = 5;
int b = 6;
int &p = a;
int &p = b; //Tại dòng này sẽ hiện lỗi là "không được khai báo nhiều lần"
//Tuy nhiên, lệnh như dưới đây thì ok.
int &q=p;
Địa chỉ bộ nhớ
Một con trỏ có địa chỉ bộ nhớ và kích thước riêng của nó trên ngăn xếp trong khi một tham chiếu chia sẻ cùng địa chỉ bộ nhớ (với biến gốc) nhưng cũng chiếm một số không gian trên ngăn xếp.
int &p = a;
cout << &p << endl << &a; // Địa chỉ của p và a là như nhau
Giá trị NULL
Con trỏ có thể được gán NULL trực tiếp, trong khi tham chiếu không thể. Các ràng buộc liên quan đến tham chiếu (không NULL, không gán lại) đảm bảo rằng các hoạt động cơ bản không rơi vào trường hợp ngoại lệ.
Con trỏ đến con trỏ
Bạn có thể có các con trỏ đến con trỏ cung cấp nhiều cấp độ chuyển hướng bổ sung. Trong khi đó, các tham chiếu chỉ cung cấp một mức chuyển hướng.
//Con trỏ,
int a = 10;
int *p;
int **q; //Con trỏ đến con trỏ.
p = &a;
q = &p;
//Đối với tham chiếu,
int &p = a;
int &&q = p; //Lỗi, không có tham chiếu của tham chiếu.
Các phép tính toán học
Các phép toán số học khác nhau có thể được thực hiện trên con trỏ trong khi không có thứ gọi là Số học tham chiếu. (Nhưng bạn có thể lấy địa chỉ của một đối tượng được trỏ bởi một tham chiếu và thực hiện số học con trỏ trên đó như &obj + 5)
Khi nào sử dụng cái nào
Hiệu suất hoàn toàn giống nhau, vì các tham chiếu được triển khai bên trong dưới dạng con trỏ. Tuy nhiên, bạn vẫn có thể ghi nhớ một số điểm để quyết định khi nào sử dụng cái gì:
Sử dụng tham chiếu:
Trong các tham số hàm và kiểu trả về.
Sử dụng con trỏ:
Sử dụng con trỏ nếu cấn tính toán con trỏ số học hoặc NULL. Ví dụ đối với mảng (Lưu ý rằng truy cập mảng được thực hiện bằng cách sử dụng số học con trỏ).
Để triển khai các cấu trúc dữ liệu như danh sách liên kết, cây, v.v. và các thuật toán của chúng vì để trỏ ô khác nhau, chúng ta phải sử dụng khái niệm con trỏ.
Con Trỏ Và Tham Chiếu Trong C++
XI. Ý Nghĩa Con Trỏ This Trong C++
Con trỏ this trong C++
This là một con trỏ đặc biệt dùng để trỏ đến địa chỉ của đối tượng hiện tại. Như vậy để truy cập đến các thuộc tính, phương thức của đối tượng hiện tại thì ta sẽ sử dụng con trỏ this. Hãy xem ví dụ dưới đây.
Ví dụ
#include <iostream>
using namespace std;
class NhanVien {
int msnv;
string ten;
int tuoi;
public:
void setData(int msnv, string ten, int tuoi) {
this->msnv = msnv;
this->ten = ten;
this->tuoi = tuoi;
}
void showData() {
cout << "Ten nhan vien: " << this->ten << endl;
cout << "Ma so nhan vien: " << this->msnv << endl;
cout << "Tuoi: " << this->tuoi << endl;
}
};
Trong ví dụ này mình đã tạo ra ba thuộc tính để lưu trữ thông tin của một nhân viên đó là: manv, ten, tuoi. Ngoài ra mình có tạo thêm phương thức setData() dùng để gán dữ liệu cho sinh viên, và showData() dùng để hiển thị dữ liệu.
Trong phương thức setData() mình đã sử dụng từ khóa this->ten_thuoc_tinh để thực hiện phép gán dữ liệu cho các thuộc tính, còn ở phương thức showData() mình cũng sử dụng cú pháp tương tự để hiển thị dữ liệu của các thuộc tính. Như vậy công dụng của từ khóa this chính là một con trỏ và trỏ đến địa chỉ của đối tượng hiện tại.
Câu hỏi đặt ra là đối tượng hiện tại tại là gì? Để hiểu rõ hơn thì hãy xem đoạn code sử dụng class trên như sau:
Ví dụ
int main() {
// Nhan vien 1
NhanVien n1 = NhanVien();
n1.setData(111231, "Nguyen Van A", 24);
n1.showData();
// Nhan vien 2
NhanVien n2 = NhanVien();
n2.setData(111232, "Nguyen Van B", 25);
n2.showData();
return 0;
}
Trong ví dụ này mình đã tạo ra hai đối tượng sinh viên đó là n1 và n2, và con trỏ this của n1 sẽ trỏ đến chính đối tượng n1, con trỏ this của n2 sẽ trỏ đến chính đối tượng n2, đây ta gọi là đối tượng hiện tại.
Lưu ý: Trong các phương thức bình thường (không phải hàm khởi tạo) nếu bạn sử dụng tên của biến thì sẽ có hai trường hợp xảy ra.
Nếu biến đó không tôn tại trong phương thức mà nó lại trùng với tên thuộc tính thì mặc nhiên nó sẽ hiểu đó là thuộc tính.
Nếu biến đó có khai báo trong phương thức thì ta sẽ hiểu đó là biến bình thường, không phải là thuộc tính.
Một ví dụ khác về con trỏ this
Bạn hãy xem ví dụ dưới đây, đây là một ví dụ mình viết lại ở phần 1 và có một chút thay đổi.
Ví dụ
#include <iostream>
using namespace std;
class NhanVien {
int msnv;
string ten;
int tuoi;
public:
NhanVien(int msnv, string ten, int tuoi) {
cout << "Trong ham xay dung: " << endl;
cout << " msnv: " << msnv << endl;
cout << " ten: " << ten << endl;
cout << " Tuoi: " << tuoi << endl;
msnv = msnv;
ten = ten;
tuoi = tuoi;
}
void HienThi() {
cout << "Ham in thong tin cua doi tuong nhan vien: " << endl;
cout << ten << endl;
cout << " Ma so nhan vien: " << msnv << endl;
cout << " Tuoi: " << tuoi << endl;
}
};
int main() {
NhanVien n1 = NhanVien(111231, "Nguyen Van A", 25);
n1.HienThi();
return 0;
}
Ý Nghĩa Con Trỏ This Trong C++
XII. Con Trỏ Void Trong C++
Để tìm hiều về con trỏ void trong C++, trước hết bạn cần phải nắm vững các kiến thức cơ bản về con trỏ void. Đừng lo lắng vì Techacademy đã chuẩn bị cho bạn trong các bài viết sau đây:
Con trỏ void trong C++ là gì
Trong số các kiểu con trỏ, bạn có thể xác định một con trỏ hơi khác thường được gọi là con trỏ void.
Giống như các loại con trỏ khác trong C++ thì con trỏ void cũng được sử dụng để lưu trữ địa chỉ của một dữ liệu trong bộ nhớ máy tính.
Tuy nhiên điều đặc biệt ở đây là, với kiểu dữ liệu mà con trỏ void lưu giữ địa chỉ thì chương trình có thể truy cập đến địa chỉ của dữ liệu đó, nhưng không xác định được kiểu của nó. Nói cách khác thì con trỏ void được sử dụng để lưu giữ địa chỉ của các kiểu dữ liệu không tồn tại kiểu dữ liệu.
Khai báo con trỏ void trong C++
Cách khai báo con trỏ void trong C++ cũng tương tự như với các loại con trỏ khác, chúng ta viết kiểu void, rồi dấu hoa thị *, và cuối cùng là tên con trỏ void như sau:
void * pdata;
Cách viết này cũng tương tự như với các kiểu con trỏ int hay char chẳng hạn:
char * pCharData; // Con trỏ kiểu char
int * pIntData; // Con trỏ kiểu int
Chúng ta cũng có thể thực hiện các thao tác như gán địa chỉ vào con trỏ, hoặc là in địa chỉ được gán vào con trỏ void tương tự như các loại con trỏ khác trong C++. Ví dụ:
#include <iostream>
using namespace std;
int main()
{
char data = 'A'; //Khai báo biến data
void * pdata = &data; // Khai báo con trỏ void và gán địa chỉ của biến data vào con trỏ
cout << "pointer: "<< pdata;//In địa chỉ mà con trỏ lưu giữ
return 0;
}
// pointer: 0x7ffe529fddbf
Tuy nhiên, không giống như với các kiểu con trỏ khác thì chúng ta lại không thể thực hiện các thao tác với giá trị của biến mà con trỏ void trỏ đến, và vì thế cũng không thể biết được kiểu của dữ liệu đó là gì. Ví dụ, chúng ta không thể đọc được giá trị của biến thông qua con trỏ, vì lỗi sau đây sẽ xảy ra:
#include <iostream>
using namespace std;
int main()
{
char data = 'A'; //Khai báo biến data
void * pdata = &data;// Khai báo con trỏ void và gán địa chỉ của biến data vào con trỏ
cout << "data: "<< *pdata;//In giá trị tại địa chỉ mà con trỏ lưu giữ
return 0;
}
Vậy chẳng phải con trỏ void trong C++ rất là vô dụng hay sao? Tất nhiên là không phải rồi, vì chúng ta sẽ cần tới con trỏ void trong các trường hợp như dưới đây:
Sử dụng con trỏ void trong C++
Con trỏ void trong C++ sẽ được sử dụng trong các trường hợp đặc biệt sau đây:
Con trỏ vạn năng giúp lưu giữ tất cả các loại giữ liệu trong C++
Một điều dễ hiểu là do con trỏ void không tồn tại kiểu của dữ liệu mà nó đang chỉ đến, nên nó có khả năng chấp nhận và lưu giữ địa chỉ của tất cả các loại giữ liệu khác nhau trong C++. Đây là điều mà các con trỏ khác trong C++ không làm được. Ví dụ như con trỏ kiểu int thì chỉ chấp nhận lưu địa chỉ của dữ liệu kiểu int, còn con trỏ kiểu char thì cũng chỉ có thể chấp nhận lưu giữ địa chỉ của kiểu giữ liệu char. Nhưng với con trỏ void, void chấp hết ^_ .
Ví dụ cụ thể, con trỏ void trong C++ dưới đây có thể lưu giữ địa chỉ của tất cả các loại giữ liệu mà không sợ lỗi xảy ra trong chương trình.
#include <iostream>
using namespace std;
int main(){
//Khai báo biến
char data1;
short data2;
long data3;
double data4;
// Khai báo con trỏ void
void * pdata;
//Gán địa chỉ của các loại dữ liệu với nhiều kiểu vào con trỏ void
pdata = &data1; // pdata --> data1
pdata = &data2; // pdata --> data2
pdata = &data3; // pdata --> data3
pdata = &data4; // pdata --> data4
return 0;
}
Con trỏ giúp cố ý ẩn kiểu dữ liệu
Vì con trỏ C++ không cho phép chúng ta đọc kiểu dữ liệu cũng như truy cập vào dữ liệu tại địa chỉ mà nó lưu giữ, nên con trỏ void có vai trò vô cùng quan trong khi chúng ta muốn ẩn kiểu dữ liệu nào đó trong chương trình.
Đây là một thuật toán vô cùng phức tạp đòi hỏi lượng kiến thức khá cao, chỉ dành cho các bạn thực sự pro và muốn tìm hiểu sâu về C++ mà thôi.
Con Trỏ Void Trong C++
XIII. Bài Tập Về Con Trỏ Trong C++
Trong chủ đề này, chúng ta cùng làm một số bài tập về Con trỏ trong C++.
Bài tập 1
Sử dụng con trỏ trong C++, bạn hãy viết một chương trình C++ để nhận dữ liệu từ người dùng và tìm giá trị lớn nhất của một tập dữ liệu nội bộ.
Lời giải
Dưới đây là chương trình C++ để giải bài tập trên. Mình sử dụng một hàm mà nhận mảng các giá trị dữ liệu và kích cỡ của nó. Hàm này trả về con trỏ mà trỏ tới giá trị lớn nhất.
#include<iostream>
#include<conio.h>
using namespace std;
int *findMax(int arr[],int n);
int main(){
int n,i,*p;
cout<<"Nhap so du lieu: ";
cin>>n;
int arr[n];
for(i=0;i<n;i++) {
cout<<"Nhap gia tri thu "<<i+1<<" la :";
cin>>arr[i];
}
p=findMax(arr,n);
cout<<"Gia tri lon nhat la: "<<*p;
getch();
return 0;
}
int *findMax(int data[],int n){
int *max=data;
int i;
for(i=1;i<n;i++){
if(*max<*(max+i)) *max=*(max+i);
}
return max;
}
Chạy chương trình C++ trên sẽ cho kết quả như hình sau:
Bài Tập Về Con Trỏ Trong C++
Bài tập 2
Viết một chương trình C++ để nhận 5 giá trị nguyên từ bàn phím. 5 giá trị này sẽ được lưu trữ trong một mảng bởi sử dụng một con trỏ. Sau đó, in các phần tử của mảng trên màn hình.
Lời giải
Dưới đây là chương trình C++ để giải bài tập trên.
#include<iostream>
#include<conio.h>
using namespace std;
int main()
{
int arr[5],i;
int *p=arr;
cout<<"Nhap 5 so:";
cin>>*p>>*(p+1)>>*(p+2)>>*(p+3)>>*(p+4);
cout<<"Cac so ban vua nhap la:\n";
for(i=0;i<5;i++)
cout<<arr[i]<<endl;
return 0;
}
Chạy chương trình C++ trên sẽ cho kết quả như hình sau:
Bài Tập Về Con Trỏ Trong C++
Sửa đổi lời giải trên để in các phần tử của mảng theo thứ tự đảo ngược bởi sử dụng một con trỏ.
#include<iostream>
#include<conio.h>
using namespace std;
int main()
{
int arr[5],i;
int *p=arr;
cout<<"Nhap 5 so:";
cin>>*p>>*(p+1)>>*(p+2)>>*(p+3)>>*(p+4);
cout<<"Cac so ban vua nhap theo thu tu dao nguoc la:\n";
for(i=4;i>=0;i--)
cout<<*(p+i)<<endl;
return 0;
}
Chạy chương trình C++ trên sẽ cho kết quả như hình sau:
Bài Tập Về Con Trỏ Trong C++
XIV. Ép Kiểu Con Trỏ Trong C++
Ép kiểu trong C++ là việc gán giá trị của một biến có kiểu dữ liệu này tới biến khác có kiểu dữ liệu khác.
Cú pháp:
(type) value;
Ví dụ:
float c = 35.8f;
int b = (int)c + 1;
Trong ví dụ trên, đầu tiên giá trị dấu phảy động c được đổi thành giá trị nguyên 35. Sau đó nó được cộng với 1 và kết quả là giá trị 36 được lưu vào b.
Phân loại ép kiểu trong C++
Trong C++, có hai loại ép kiểu dữ liệu:
Nới rộng (widening): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước nhỏ hơn sang kiểu có kích thước lớn hơn. Kiểu biến đổi này không làm mất thông tin.
Thu hẹp (narrowwing): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước lớn hơn sang kiểu có kích thước nhỏ hơn. Kiểu biến đổi này có thể làm mất thông tin
1. Nới rộng (widening)
Ép Kiểu Con Trỏ Trong C++
Nới rộng (widening): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước nhỏ hơn sang kiểu có kích thước lớn hơn. Kiểu biến đổi này không làm mất thông tin. Ví dụ chuyển từ int sang float. Chuyển kiểu loại này có thế được thực hiện ngầm định bởi trình biên dịch.
Ví dụ:
#include <iostream>
using namespace std;
int main() {
int i = 100;
long l = i; // khong yeu cau chi dinh ep kieu
float f = l; // khong yeu cau chi dinh ep kieu
cout << "Gia tri Int: " << i << endl;
cout << "Gia tri Long: " << l << endl;
cout << "Gia tri Float: " << f << endl;
return 0;
}
Kết quả:
Giá trị Int: 100
Giá trị Long: 100
Giá trị Float: 100
2.Thu hẹp (narrowwing)
Ép Kiểu Con Trỏ Trong C++
Thu hẹp (narrowwing): Là quá trình làm tròn số từ kiểu dữ liệu có kích thước lớn hơn sang kiểu có kích thước nhỏ hơn. Kiểu biến đổi này có thể làm mất thông tin như ví dụ ở trên. Chuyển kiểu loại này không thể thực hiện ngầm định bởi trình biên dịch, người dùng phải thực hiện chuyển kiểu tường minh.
Ví dụ:
#include <iostream>
using namespace std;
int main() {
double d = 100.04;
long l = (long) d; // yeu cau chi dinh kieu du lieu (long)
int i = (int) l; // yeu cau chi dinh kieu du lieu (int)
cout << "Gia tri Double: " << d << endl;
cout << "Gia tri Long: " << l << endl;
cout << "Gia tri Int: " << i << endl;
return 0;
}
Kết quả:
Giá trị Double: 100.04
Giá trị Long: 100
Giá trị Int: 100
Chúng ta có thể định nghĩa các thành viên lớp là static bởi dùng từ khóa static trong C++. Khi chúng ta khai báo 1 thành viên của 1 lớp là static, lúc đó dù cho có bao nhiêu đối tượng của lớp được tạo, thì sẽ chỉ mang 1 bản sao của thành viên static.
Một thành viên static chỉ buộc phải khởi tạo 1 lần và được chia sẻ cho tất cả đối tượng của lớp. Tất cả dữ liệu static được khởi tạo về 0 khi đối tượng đầu tiên được tạo.
Chúng ta không thể khởi tạo thành viên static trong định nghĩa lớp, nhưng nó có thể được định nghĩa bên ngoài lớp đó bởi việc khai báo lại biến static như trong ví dụ sau, sử dụng toán tử phân giải phạm vi :: để nhận diện lớp nào sở hữu nó. Hãy cùng Techacademy tìm hiểu chi tiết qua bài viết dưới đây nhé.
I. Định Nghĩa Static C++
Static trong c++ là dữ liệu của lớp không phải là dữ liệu của đối tượng. Static trong c++ tồn tại như 1 biến toàn cục. Hay nói cách khác dữ liệu static xuất hiện trước lúc bạn khởi tạo đối tượng của lớp, và nó chỉ tồn tại duy nhất.
Các thành viên static có thể là public, private hoặc protected.
Static ngoài khai báo biến còn có thể khai báo hàm.
Đối với class, static sử dụng để khai báo thành viên dữ liệu dùng chung cho mọi thể hiện của lớp:
Một bản duy nhất tồn tại trong suốt quá trình chạy của chương trình.
Dùng chung cho tất cả các thể hiện của lớp.
Bất kể lớp đó có bao nhiêu thể hiện.
Định Nghĩa Static C++
II. Biến Static Trong Hướng Đối Tượng C++
Ví dụ mình sẽ khai báo một biến static, int count, biến này dùng để đếm số lượng các hình chữ nhật đã được tạo ra.
class Rectangle {
private: int width;
int length;
static int count;
public: void set(int w, int l);
int area();
Rectangle() {
count++;
}
Rectangle(int x, int y) {
count++;
set(x, y);
}
}
class Rectangle {
private:
int width;
int length;
static int count;
public:
void set(int w, int l);
int area();
Rectangle() {
count++;
}
Rectangle(int x, int y) {
count++;
set(x, y);
}
}
Như vậy khi một đối tượng được tạo chúng ta sẽ tăng count lên để thực hiện đúng ý đồ đếm hình chữ nhật.
Tuy nhiên chúng ta không thể gán trước giá trị ban đầu của static trong class mà chúng ta phải gán giá trị cho nó ở ngoài class.
ví dụ
int main() {
int Rectangle::count = 0; // Sau khi khởi tạo giá trị đầu mình tạo minh họa 3 hình chữ nhật Rectangle r1(2,4); Rectangle r2(1,2); Rectangle r3;}
int main() {
int Rectangle::count = 0;
// Sau khi khởi tạo giá trị đầu mình tạo minh họa 3 hình chữ nhật
Rectangle r1(2, 4);
Rectangle r2(1, 2);
Rectangle r3;
}
Biến Static Trong Hướng Đối Tượng C++
Ví dụ về static trong c++
Ví dụ bên trên chỉ rõ biến static count không phải là dữ liệu của đối tượng như width và length mà nó là dữ liệu của class và chỉ tồn tại duy nhất.
Để sử dụng, gán hoặc lấy giá trị của static bạn sử dụng phạm vi truy xuất để gọi nó
//Ví dụ khởi tạo ban đầu
int Rectangle::count = 0;
//Gọi biến static:
Rectangle::count
Lưu ý thêm ở phần khai báo mình để static count ở private, nên bên ngoài class không thể dùng phạm vi truy xuất đến truy cập (Như cách gọi Rectangle::count ) Nhưng việc khai báo và gán giá trị ban đầu như int Rectangle::count = 0; là được phép.
Để dùng Rectangle::count bạn có thể khai báo biến static này ở public hoặc viết thêm hàm static get giá trị count này.
III. Hàm Static Trong Hướng Đối Tượng C++
Hàm static có vai trò cũng như biến static. Nghĩa là khi bạn đã khai báo class nhưng chưa tạo ra đối tượng như Rectangle r1(2,4) thì chúng ta vẫn truy cập được biến count như ví dụ trên. Vậy ở phần hàm static cũng có vai trò như vậy, hàm này sẽ là tồn tại duy nhất của class.
Mình sẽ khai báo thêm static int getCount() ở thuộc tính public
a. Code cụ thể về static
class Rectangle
{
private:
int width;
int length;
static int count;
public:
void set(int w, int l);
static int getCount()
{
return count;
}
int area();
Rectangle()
{
count++;
}
Rectangle(int x, int y)
{
count++;
set(x,y);
}
}
b. Gọi hàm static
Bạn có thể gọi hàm static bằng cách sau:
Gọi Rectangle::getCount() ở ngoài class hoặc bên trong class
Gọi getCount() Bên trong class.
Hàm Static Trong Hướng Đối Tượng C++
IV. Thành Viên Static Của Class
Hàm thành viên static trong C++
Bằng việc khai báo 1 hàm thành viên là static trong C++, bạn làm nó độc lập với bất kỳ đối tượng cụ thể nào của lớp. Một hàm thành viên static có thể được gọi ngay cả lúc không có đối tượng nào của lớp tồn tại và những hàm static được truy cập chỉ bởi dùng tên lớp và toán tử phân giải phạm vi :: trong C++.
Một hàm thành viên chỉ có thể truy cập thành viên dữ liệu static, những hàm thành viên static khác và bất kỳ hàm khác từ bên ngoài lớp đó.
Các hàm thành viên static có một phạm vi lớp và chúng không có sự truy cập đến con trỏ this của lớp trong C++. Bạn có thể sử dụng 1 hàm thành viên để xác định có hay không một số đối tượng của lớp đã được tạo.
Bạn thử ví dụ sau để hiểu khái niệm của hàm thành viên static trong C++:
#include <iostream>
using namespace std;
class Box
{
public:
static int biendemDT;
// phan dinh nghia Constructor
Box(double dai=1.0, double rong=1.0, double cao=1.0)
{
cout <<"Constructor duoc goi." << endl;
chieudai = dai;
chieurong = rong;
chieucao = cao;
// gia tri cua biendemDT tang len moi khi doi tuong duoc tao
biendemDT++;
}
double theTich()
{
return chieudai * chieurong * chieucao;
}
static int layBienDem()
{
return biendemDT;
}
private:
double chieudai; // Chieu dai cua mot box
double chieurong; // Chieu rong cua mot box
double chieucao; // Chieu cao cua mot box
};
// khoi tao thanh vien static cua lop Box
int Box::biendemDT = 0;
int main(void)
{
// in tong so doi tuong duoc tao truoc khi tao cac doi tuong.
cout << "So doi tuong ban dau la: " << Box::layBienDem() << endl;
Box Box1(2.4, 4.2, 2.2); // khai bao box1
Box Box2(4.5, 2.0, 3.2); // khai bao box2
// in tong so doi tuong duoc tao sau khi tao cac doi tuong.
cout << "Tong so doi tuong sau khi tao la: " << Box::layBienDem() << endl;
return 0;
}
Biên dịch và chạy chương trình C++ trên sẽ cho kết quả sau:
Hàm Static Trong Hướng Đối Tượng C++
V. Khi Nào Nên Sử Dụng Biến Static
Biến static được dùng lúc chỉ cần một bản sao của biến. vì vậy trường hợp bạn khai báo biến bên trong phương thức thì không dùng biến như vậy, nó sẽ trở thành cục bộ chỉ hoạt động ..
Các biến được khai báo static thường được chia sẻ trên tất cả các trường hợp của một lớp.
Các biến được khai báo static thường được chia sẻ trên tất cả các trường hợp của một lớp. Khi bạn tạo đa dạng phiên bản của lớp VariableTest Biến này vĩnh viễn được chia sẻ trên tất cả chúng. Do đó, tại bất kỳ thời điểm nhất định nào, sẽ chỉ có 1 giá trị chuỗi được chứa trong biến vĩnh viễn.
Vì chỉ có 1 bản sao của biến có sẵn cho tất cả các trường hợp, mã this.permament sẽ dẫn đến lỗi biên dịch vì có thể nhớ lại rằng this.variablename tham chiếu đến tên biến cá thể. Do đó, những biến tĩnh phải được truy cập trực tiếp, như được chỉ ra trong mã.
Khi Nào Nên Sử Dụng Biến Static
VI. Const Static’ Có Nghĩa Là Gì Trong C Và C ++?
Const static’ có nghĩa là gì trong C và C ++
Tôi đã thấy điều này trong 1 số mã ở đây trên StackOverflow và tôi không thể tìm ra nó làm gì. Sau đó, tôi thấy 1 số câu trả lời nhầm lẫn trên các diễn đàn khác. Tôi đoán tốt nhất là nó được dùng trong C để ẩn hằng số fookhỏi những mô-đun khác. Điều này có đúng không? Nếu vậy, tại sao mọi người sẽ dùng nó trong bối cảnh C ++ nơi bạn có thể tạo ra nó private?
Câu trả lời:
+ Nó có dùng trong cả C và C ++.
Như bạn đã đoán, staticphần giới hạn phạm vi của nó cho đơn vị biên dịch đó. Nó cũng cung cấp cho khởi tạo tĩnh. constchỉ nói với trình biên dịch để không cho phép bất cứ ai sửa đổi nó. Biến này được đặt trong phân đoạn dữ liệu hoặc bss tùy thuộc vào kiến trúc và có thể nằm trong bộ nhớ được đánh dấu chỉ đọc.
Tất cả đó là cách C xử lý các biến này (hoặc phương pháp C ++ xử lý các biến không gian tên). Trong C ++, một thành viên được đánh dấu staticđược chia sẻ bởi tất cả các phiên bản của một lớp nhất định. Cho dù đó là riêng tư hay không không ảnh hưởng đến thực tế là một biến được chia sẻ bởi nhiều trường hợp. Có consttrên đó sẽ cảnh báo bạn nếu có bất kỳ mã nào sẽ tìm mọi cách sửa đổi điều đó.
Nếu nó hoàn toàn riêng tư, thì mỗi phiên bản của lớp sẽ có phiên bản riêng (mặc dù trình tối ưu hóa).
+ Rất nhiều người đã đưa ra câu trả lời cơ bản nhưng không ai chỉ ra rằng trong C ++ constmặc định staticở namespace cấp độ (và một số đã cung cấp thông tin sai). Xem phần tiêu chuẩn C ++ 98 3.5.3.
Đầu tiên một số nền tảng:
Đơn vị dịch: Một tệp nguồn sau bộ xử lý trước (đệ quy) bao gồm tất cả các tệp bao gồm.
Liên kết tĩnh: Một biểu tượng chỉ có sẵn trong đơn vị dịch thuật của nó.
Liên kết ngoài: Một biểu tượng có sẵn từ các đơn vị dịch thuật khác.
– Ở cấp độ chức năng
staticcó nghĩa là giá trị được duy trì giữa các lệnh gọi hàm.
Các ngữ nghĩa của các staticbiến chức năng tương tự như các biến toàn cục ở chỗ chúng nằm trong phân đoạn dữ liệu của chương trình (chứ không phải ngăn xếp hoặc heap), xem câu hỏi này để biết thêm chi tiết về statictuổi thọ của biến.
– Ở classcấp độ
staticcó nghĩa là giá trị được chia sẻ giữa tất cả các thể hiện của lớp và constcó nghĩa là nó không thay đổi.
Const Static’ Có Nghĩa Là Gì Trong C Và C ++
VII. Bài Tập C++ OOP Giá Trị Của Biến Static
Biến static (static variables)
Biến static được tạo ra bên trong một khối lệnh có khả năng lưu giữ giá trị của nó cho dù chương trình đã chạy ra bên ngoài khối lệnh chứa nó.
Biến static chỉ cần được khai báo một lần duy nhất, và tiếp tục được duy trì sự tồn tại xuyên suốt cho đến khi chương trình kết thúc.
Dưới đây là một ví dụ đơn giản cho thấy sự khác biệt giữa việc sử dụng biến cục bộ và biến static:
#include <iostream>
void incrementAndPrint()
{
int value = 1; // automatic duration by default
++value;
std::cout << value << '\n';
} // value is destroyed here
int main()
{
incrementAndPrint();
incrementAndPrint();
incrementAndPrint();
return 0;
}
Mỗi khi hàm incrementAndPrint được gọi, một biến có tên gọi “value” được tạo ra và được gán giá trị 1. Hàm incrementAndPrint sẽ tăng giá trị của biến “value” lên 1 đơn vị, giá trị in ra màn hình là 2. Khi hàm incrementAndPrint thực hiện xong tác vụ, biến “value” bị hủy. Cứ thực hiện liên tiếp như vậy, kết quả in ra màn hình là:
2
2
2
Bây giờ, chúng ta thêm từ khóa static trước nơi biến “value” được khai báo:
#include <iostream>
void incrementAndPrint()
{
static int s_value = 1; // static duration variable
++s_value;
std::cout << s_value << '\n';
} // s_value is not destroyed here, but became inaccessible
int main()
{
incrementAndPrint();
incrementAndPrint();
incrementAndPrint();
return 0;
}
Lần này, vì biến “s_value” được khai báo với từ khóa static, nó sẽ chỉ được khởi tạo một lần duy nhất, dù hàm incrementAndPrint được gọi nhiều lần. Mỗi lần hàm incrementAndPrint được gọi, giá trị của biến “s_value” được tăng thêm 1, và còn được lưu giữ lại đó cho đến khi chương trình kết thúc. Kết quả in ra như sau:
2
3
4
Hàm static (static functions)
Từ khóa static cũng có thể được sử dụng cho các hàm, thường là những hàm được đặt bên trong các struct hoặc class (gọi là phương thức). Trong bài học này, mình lấy một ví dụ về việc sử dụng hàm static bên trong một struct mẫu như sau:
Trong bài viết này chúng ta sẽ tìm hiểu về hàm memset() trong C++. Đây là một hàm được sử dụng sao chép ký tự trong một chuỗi.
I. Memset Trong C++ Là Gì
Hàm void *memset(void *str, int c, size_t n) sao chép ký tự c (một unsigned char) tới n ký tự đầu tiên của chuỗi được trỏ tới bởi tham số str.
Memset Trong C++ Là Gì
II. Cú Pháp Memset Trong C++
Hàm memset() trong C++ được sử dụng để sao chép một ký tự đơn lẻ trong một khoảng nhất định vào một đối tượng.
Cú pháp:
void* memset( void* dest, int ch, size_t count );
Trong đó:
dest: Con trỏ tới đối tượng để sao chép ký tự.
ch: Ký tự cần sao chép.
count: Số lần sao chép.
Hàm trả về một chuỗi đã được sao chép và lưu vào con trỏ dest.
Cú Pháp Memset Trong C++
III. Ví Dụ Về Hàm Memset Trong C++
Hàm memset() trong C++ được sử dụng để sao chép một ký tự đơn lẻ trong một khoảng nhất định vào một đối tượng.
Trong phần này mình sẽ thực hiện một ví dụ để mình họa cho hàm memset() trong C++.
Cụ thể mình sẽ tạo một biến dest với độ dài 50 ký tự để lưu các ký tự đã được sao chép. Sau đó sử dụng hàm memset() để sao chép ký tự ‘a’ với số lần là 5.
#include <iostream>
#include <cstring>
using namespace std;
int main() {
//khai báo biến dest có độ dài 50 ký tự, được dùng để lưu các ký tự lặp lại
char dest[50];
//biến ch là biến được sử dụng để lặp lại
char ch = 'a';
//gọi hàm memset để lặp lại ký tự a 5 lần rồi gán vào biến dest
memset(dest, ch, 5);
cout << "Sau khi gọi hàm memset để lặp lại" << endl;
cout << "\tBiến dest = " << dest;
cout<<"\n-------------------------------\n";
cout<<"Chương trình này được đăng tại Techcademy.edu.vn";
}
Kết quả:
Ví Dụ Về Hàm Memset Trong C++
IV. Memcpy() Trong C++
Hàm memcpy() trong C
Hàm void *memcpy(void *str1, const void *str2, size_t n) sao chép n ký tự từ str2 tới str1.
Trong bài viết này chúng ta sẽ tìm hiểu về hàm gets() trong C / C++. Đây là một hàm được sử dụng để đọc các ký tự từ stdin.
I. Hàm Gets() Trong C++ Là Gì
Hàm gets() là hàm có sẵn trong thư viện cstdio, vì vậy trước khi sử dụng nó các bạn nhớ khai báo thư viện đã nhé: #include<cstdio>.
Hàm char *gets(char *str) trong Thư viện C chuẩn đọc một dòng từ stdin và lưu trữ nó bên trong chuỗi được trỏ bởi str. Nó dừng khi bắt gặp end-of-file hoặc ký tự newline (dòng mới) được đọc.
Hàm gets() khác hàm scanf() ở chỗ là hàm này chấp nhận các chuỗi có khoảng trống.
Hàm Gets() Trong C++ Là Gì
II. Cú Pháp Hàm Gets() Trong C++
Hàm gets() được sử dụng để đọc các ký tự từ stdin và lưu trữ chúng cho đến khi tìm thấy ký tự dòng mới. Hàm get () không cung cấp hỗ trợ để ngăn chặn tràn bộ đệm nếu chuỗi đầu vào lớn.
Cú pháp:
char* gets(char* str);
Trong đó:
str: Con trỏ đến một mảng ký tự lưu trữ các ký tự từ stdin.
Hàm trả về:
Khi thành công, hàm gets() trả về str.
Khi thất bại, nó trả về null.
Ví dụ: khi chúng ta muốn yêu cầu người dùng nhập vào họ và tên thì có thể sử dụng hàm gets() kết hợp với cout.
char str[100];
cout << "Nhập vào họ tên của bạn: ";
gets(str);
Như vậy là khi chạy thì chương trình sẽ yêu cầu bạn nhập vào từ bàn phím.
Cú Pháp Hàm Gets() Trong C++
III. Ví Dụ Hàm Gets() Trong C++
Chương trình C sau minh họa cách sử dụng của hàm gets() trong C. Bạn sẽ thấy rằng hàm gets() chấp nhận chuỗi có chứa khoảng trống, không giống như hàm scanf():
#include <stdio.h>
int main()
{
char str[50];
printf("Nhap mot chuoi: ");
gets(str);
printf("Ban vua nhap chuoi: %s", str);
return(0);
}
Biên dịch và chạy chương trình C trên để xem kết quả.
Ví Dụ Hàm Gets() Trong C++
IV. Hướng Dẫn Xử Lý Lỗi Bỏ Qua Khi Dùng Hàm Gets Và Cin.Getline Trong C & C++
Chắc các bạn sẽ thấy rằng sau lúc nhập sinh viên đầu tiên nó bỏ qua bước nhập tên sinh viên 2 mà nhảy tới ngày sinh luôn
Hướng Dẫn Xử Lý Lỗi Bỏ Qua Khi Dùng Hàm Gets Và Cin.Getline Trong C & C++
Điều này đc giải thích như sau:
Khi bạn nhập một ký tự từ bàn phím thì ký tự ấy sẽ được chuyển vào 1 trong 2 nơi : bộ đệm bàn phím hoặc tệp stdin .
Làm sao biết lúc ta nhập 1 ký tự bất kỳ thì nó sẽ gửi vào đâu. Câu trả lời là trường hợp ta nhập ký tự trong lúc máy dừng chờ trước những hàm scanf , gets , getchar thì máy sẽ gửi ký tự vào tệp stdin. Nếu ko phải 3 hàm ấy thì là vào bộ đệm bàn phím . Tệp stdin có tính chất như sau :
+ Nếu trên stdin có dư dữ liệu ( tức là bạn bấm nhiều phím bất kỳ ) thì các hàm trên sẽ nhận được 1 phần dữ liệu mà chúng yêu cầu . Các ký tự còn lại vẫn nằm trên stdin .
+ lúc stdin chưa đủ dữ liệu thì máy sẽ chờ cho đến khi người dùng nhập đủ dữ liệu vào .
khi bạn nhập như sau : 45 và bấm ENTER cho hàm cin thì ký tự 45 sẽ được gán cho thuộc tính cân nặng . Nhưng ký tự ENTER vẫn còn lưu lại trên stdin hoặc bộ đệm và hàm cin>> (scanf)sẽ nhận nó bởi vậy sẽ làm trôi hàm cin.getline (gets) đi . Dù bạn rất muốn nhập cái gì đó cho chuỗi hoten nhưng cũng bó tay .
Thế thì cách giải quyết chính là làm sạch tệp stdin và bộ đệm buffer đi
Đối với thư viện stdio.h bị lỗi với gets thì ta chèn
PHP Code:
fflush(stdin);
Sau hàm nhập số liền kề với gets sau nó
Đối với thư viện iostrem.h bị lỗi cin.getline thì ta chèn
PHP Code:
cin.ignore();
Sau hàm nhập số trước cin.getline() liền kề sau nó để xóa bộ đệm đi là ok;
Chương trình C sau minh họa cách sử dụng của hàm gets() trong C. Bạn sẽ thấy rằng hàm gets() chấp nhận chuỗi có chứa khoảng trống, không giống như hàm scanf():
#include <stdio.h>
int main()
{
char str[50];
printf("Nhap mot chuoi: ");
gets(str);
printf("Ban vua nhap chuoi: %s", str);
return(0);
}
Trong bài học hôm nay chúng ta tiếp tục tìm hiểu về hàm hủy (Destructor) trong C++. Mục đích của hàm hủy trong C++ là gì? Cách sử dụng hàm hủy như thế nào? Chúng ta sẽ cùng tìm hiểu trong nội dung sau đây.
I. Hàm Destructor Trong C+Là Gì
Hàm hủy (Destructor) trong C++ ngược lại với hàm xây dựng, trong khi hàm xây dựng dùng để khởi tạo giá trị cho đối tượng thì hàm hủy dùng để hủy đối tượng.
Chỉ có duy nhất một hàm hủy trong 1 lớp. Hàm hủy tự động được gọi. Nếu như chúng ta không định nghĩa hàm hủy thì mặc định trình biên dịch sẽ tự tạo ra 1 hàm hủy mặc nhiên
Cũng giống như hàm xây dựng, hàm hủy được định nghĩa có cùng tên với tên lớp, khôn có bất cứ kiểu gì trả về kể cẳ kiểu void, tuy nhiên phải có dấu ~ trước tên của hàm hủy.
Lưu ý: Hàm hủy (Destructor) không có bất cứ tham số nào
Cú pháp
Cú pháp của hàm hủy (Destructor) trong C++ như sau:
Cú pháp
~TenLop() { };
Ví dụ cụ thể là lớp nhân viên, thì chúng ta sẽ tạo hàm hủy cho lớp nhân viên như sau:
Ví dụ
class NhanVien {
public:
~NhanVien(){};
};
Ví dụ
Chúng ta cùng xem xét một ví dụ đơn giản nhất về hàm hủy trong C++ như sau:
Hàm hủy (Destructor) trong C++ được gọi tự động lúc đối tượng đi ra khỏi phạm vi:
Kết thúc hàm
Kết thúc chương trình
Kết thúc 1 block
Toán tử delete được gọi
Có hai hạn chế lúc dùng hàm hủy đó là:
Chúng ta không thể lấy địa chỉ của nó
Lớp con không có thừa kế hàm hủy từ lớp cha của nó
Khi Nào Hàm Destructor Được Gọi
III. So Sánh Hàm Destructor Với Hàm Constructor
+ Giống Nhau:
Hàm tạo và hàm hủy là những hàm thành viên có cùng tên với lớp của chúng. Loại cũ constructor giúp khởi tạo một đối tượng. Ngược lại, a người phá hủy khác với hàm tạo sẽ xóa hàm tạo đã tạo khi nó không được sử dụng.
Đôi khi nó được yêu cầu khởi tạo 1 số phần của một đối tượng trước lúc nó có thể được sử dụng. Ví dụ, chúng ta đang thao tác trên ngăn xếp, trước khi chúng ta thực hiện bất kỳ hành động nào, đỉnh của ngăn xếp phải luôn được đặt bằng 0. Tính năng khởi tạo tự động này được thực hiện thông qua ‘Constructor’.
Giống như, trường hợp 1 đối tượng cần thực thi 1 số mã trước lúc nó bị phá hủy. Ví dụ: nếu một đối tượng cần đóng một tệp mà nó đã mở, trước khi nó bị phá hủy. Nó có thể được thực hiện với sự trợ giúp của ‘Destructor’. Bây giờ, hãy tổng quan về một số điểm khác biệt cơ bản giữa hàm tạo và hàm hủy với sự trợ giúp của biểu đồ so sánh
+ Khác Nhau:
– Biểu đồ so sánh:
Cơ sở để so sánh
Constructor
Kẻ hủy diệt
Mục đích
Nó cấp phát bộ nhớ cho 1 đối tượng.
Nó phân bổ bộ nhớ của một đối tượng.
Tờ khai
class_name (các đối số nếu có) {};
~ class_name (không có đối số) {};
Tranh luận
Hàm tạo chấp nhận đối số
Trình hủy không chấp nhận bất kỳ đối số nào.
Kêu gọi
Hàm tạo được gọi tự động, trong lúc đối tượng được tạo.
Bộ hủy được gọi tự động, lúc khối được thoát hoặc chương trình kết thúc.
Đang làm việc
Constructor cho phép một đối tượng khởi tạo một số giá trị của nó trước đó, nó được sử dụng.
Destructor cho phép một đối tượng thực thi một số mã tại thời điểm nó bị phá hủy.
Thứ tự thực hiện
Hàm tạo được gọi theo thứ tự liên tiếp.
Hàm hủy được gọi theo thứ tự ngược lại của hàm tạo.
Bằng số
Có thể có nhiều hàm tạo trong một lớp.
Luôn có một hàm hủy duy nhất trong lớp.
Copy Constructor
Copy constructor cho phép một constructor khai báo và khởi tạo một đối tượng từ một đối tượng khác.
Không có khái niệm như vậy.
Quá tải
Các trình xây dựng có thể bị quá tải.
Bộ hủy không thể bị quá tải.
– Định nghĩa của Constructor
A constructor về căn bản là 1 hàm thành viên của lớp, nó khởi tạo đối tượng và cấp phát bộ nhớ cho nó. Các hàm tạo có thể được xác định dễ dàng vì chúng được khai báo và định nghĩa cùng tên với tên của lớp. Một phương thức khởi tạo không có bất kỳ kiểu trả về nào; vì vậy, chúng không trả lại bất cứ thứ gì, thậm chí không phải là ‘void’. Một Constructor luôn được định nghĩa trong phần public của 1 lớp.
Có thể có nhiều hàm tạo trong 1 lớp; chúng có thể được phân biệt dựa trên số lượng và loại đối số được truyền vào. Nếu có nhiều hàm tạo trong một lớp; phương thức khởi tạo ngầm định (hàm tạo không làm gì) phải được định nghĩa cùng với chúng; nó không làm gì ngoài, đáp ứng trình biên dịch.
Các hàm tạo cũng có thể được định nghĩa với những đối số mặc định. Trong khi đó, họ cũng khởi tạo đối tượng “động”. Các hàm tạo không thể được kế thừa, cũng không thể là ảo, nhưng chúng có thể bị quá tải. Họ không thể được giới thiệu đến địa chỉ của họ.
– Các loại cấu tạo
Về cơ bản có ba loại cấu trúc – Cấu trúc mặc định, Tham số và Sao chép.
Nhà xây dựng mặc định: Nó là một hàm tạo mà không có đối số nào được đưa ra cho hàm tạo. Hàm tạo mặc định không có tham số, nhưng các giá trị cho hàm tạo mặc định có thể được truyền theo mặc định (động).
Trình tạo tham số: Kiểu hàm tạo này nhận các đối số; chúng ta có thể chuyển các giá trị khác nhau cho các thành viên dữ liệu làm đối số.
Copy Constructor: Copy constructor khác với các loại constructor khác vì nó chấp nhận địa chỉ của đối tượng khác làm đối số.
– Thực hiện hàm tạo
lớp Const {int a, b; public: Const () // hàm tạo không có tham số {a = 0; b = 0; } Const (int c, int d) {// hàm tạo với tham số a = c; c = d; }}; int main () {Const C1; C2 (10,20); // câu lệnh này gọi hàm tạo}
Khi C1 được tạo, một hàm tạo không có tham số nào được thực thi, vì C1 không truyền bất kỳ tham số nào. Trong khi, khi C2 được tạo, một hàm tạo có tham số sẽ được thực thi, vì nó đang truyền hai số nguyên cho hàm tạo.
– Định nghĩa của Destructor
A Kẻ hủy diệt cũng là một hàm thành viên của một lớp, nó sẽ phân bổ bộ nhớ được cấp phát cho một đối tượng. Nó được định nghĩa cùng tên với tên của một lớp, đứng trước dấu ngã (~) Biểu tượng. Các hàm hủy luôn được gọi theo thứ tự ngược lại của các hàm tạo.
Luôn có một hàm hủy duy nhất trong một lớp, vì nó không chấp nhận bất kỳ đối số nào. Các đối tượng địa phương bị phá hủy ngay sau khi quyền kiểm soát của việc thực hiện thúc đẩy khối; mặt khác, các đối tượng toàn cục bị phá hủy khi toàn bộ chương trình kết thúc.
Một trình hủy được gọi ngầm bởi một trình biên dịch. Nếu các lớp được kế thừa và một lớp được dẫn xuất từ lớp cha và cả lớp con và lớp cha đều có hàm hủy; sau đó, hàm hủy của lớp dẫn xuất được gọi đầu tiên, tiếp theo là hàm hủy của lớp cha.
– Triển khai Trình hủy
lớp Const {int a, b; public: Const (int c, int d) // hàm tạo có tham số. {a = c; c = d; cout “giá trị của a và b là” ab ” n”; } ~ Const () // hàm hủy đang được gọi. {cout “đối tượng C1 bị phá hủy” ” n”; }}; int main () {Const C1 (10,20); }
Khi đối tượng C1 được tạo, một hàm tạo có hai tham số kiểu số nguyên được gọi và thành viên “a, b” được khởi tạo và giá trị của “a, b” được in ra. Sau khi trình hủy đó được gọi và in ra thông báo “đối tượng C1 bị phá hủy”.
Need of Destructor
Việc tạo hàm tạo sẽ tiêu tốn một lượng không gian bộ nhớ, vì nó cuối cùng sẽ cấp phát bộ nhớ cho các đối tượng. Bộ nhớ được cấp phát này phải được phân bổ trước khi hủy các đối tượng để giải phóng tài nguyên cho các tác vụ khác. Bộ hủy cực kỳ hữu ích cho mục đích đã định, nó phá hủy hiệu quả các đối tượng và thực hiện các nhiệm vụ dọn dẹp để giải phóng bộ nhớ.
So Sánh Hàm Destructor Với Hàm Constructor
IV. Bài Tập Hàm Destructor Trong C++
Chúng ta cùng xem một class đơn giản có sử dụng hàm destructor:
/**
* Techacademy.edu.vn - Kênh thông tin IT hàng đầu Việt Nam
*
* @author cafedevn
* Contact: cafedevn@gmail.com
* Fanpage: https://www.facebook.com/cafedevn
* Instagram: https://instagram.com/cafedevn
* Twitter: https://twitter.com/CafedeVn
* Linkedin: https://www.linkedin.com/in/cafe-dev-407054199/
*/
#include <iostream>
#include <cassert>
class IntArray
{
private:
int *m_array;
int m_length;
public:
IntArray(int length) // constructor
{
assert(length > 0);
m_array = new int[length]{};
m_length = length;
}
~IntArray() // destructor
{
// Dynamically delete the array we allocated earlier
delete[] m_array;
}
void setValue(int index, int value) { m_array[index] = value; }
int getValue(int index) { return m_array[index]; }
int getLength() { return m_length; }
};
int main()
{
IntArray ar(10); // allocate 10 integers
for (int count{ 0 }; count < ar.getLength(); ++count)
ar.setValue(count, count+1);
std::cout << "The value of element 5 is: " << ar.getValue(5) << '\n';
return 0;
} // ar is destroyed here, so the ~IntArray() destructor function is called here
Mẹo nhỏ
Nếu bạn thử biên dịch đoạn code trên và bị lỗi sau:
If you compile the above example and get the following error:
error: 'class IntArray' has pointer data members [-Werror=effc++]|
error: but does not override 'IntArray(const IntArray&)' [-Werror=effc++]|
error: or 'operator=(const IntArray&)' [-Werror=effc++]|
Để khắc phúc lỗi này, bạn có thể loại bỏ cờ “-Weffc++” khỏi compile settings (những thiết lập về biên dịch) cho ví dụ này, hoặc là bạn có thể thêm hai dòng code sau vào trong class IntArray:
Chúng ta sẽ thảo luận về chức năng của hai câu lệnh này trong chương sau.
Đoạn chương trình trên sẽ in ra:
The value of element 5 is: 6
Trên dòng đầu tiên, chúng ta đã khởi tạo 1 đối tượng mới của class IntArray, được gọi là ar, và truyền vào độ dài mảng là 10. Việc khởi tạo này sẽ gọi tới hàm constructor nhằm cấp phát bộ nhớ động cho những biến thành viên của class IntArray.
Chúng ta bắt buộc sử dụng cấp phát động tại đây bởi vì chúng ta không thể biết được tại thời điểm biên dịch (compile time) thì độ dài của mảng là bao nhiêu (caller – đối tượng gọi hàm sẽ quyết định điều đó).
Khi hàm main() kết thúc, lúc này đối tượng ar đã nằm ngoài phạm vi đoạn code mà chương trình đang chạy trên đấy (tức là ar đã goes out of scope). Điều này sẽ làm cho hàm destructor ~IntArray() được gọi, để xóa đi mảng mà chúng ta đã cấp phát bên trong phần thân hàm của constructor!