ä î < £ b Ø á k e 0 - vnuuet.vnu.edu.vn/~tqlong/2016thcs4/slide8_pointer_string.pdf · lqw...
TRANSCRIPT
Ts. Bùi Ngọc ThăngBộ môn KHMT, Trường Đại học CN
Nội dung
Khái niệm con trỏ & Khai báo Thao tác: Các toán tử “&”, “*”, “=”, “+” Nhắc lại về truyền tham số địa chỉ Nhắc lại về truyền tham số địa chỉ Con trỏ và mảng Cấp phát vùng nhớ động
Kiến trúc máy tính Bộ nhớ máy tính
Bộ nhớ RAM chứa rất nhiều ô nhớ, mỗi ô nhớ có kíchthước 1 byte.
RAM dùng để chứa một phần hệ điều hành, các lệnh RAM dùng để chứa một phần hệ điều hành, các lệnhchương trình, các dữ liệu…
Mỗi ô nhớ có địa chỉ duy nhất và địa chỉ này được đánhsố từ 0 trở đi.
Ví dụ RAM 512MB được đánh địa chỉ từ 0 đến 229 – 1 RAM 2GB được đánh địa chỉ từ 0 đến 231 – 1
Cấu trúc một CT C/C++ trong bộ nhớ Toàn bộ tập tin chương trình sẽ được nạp vào bộ
nhớ tại vùng nhớ còn trống, gồm 4 phần:
STACKSTACKLastLast--In FirstIn First--OutOut
STACKSTACKLastLast--In FirstIn First--OutOut
LLưưu u đốđối ti tượượng cục bộng cục bộKhi thực hiện hàmKhi thực hiện hàm
Vùng cấp phát tĩnhVùng cấp phát tĩnh(kích th(kích thướước cố c cố địđịnh)nh)
Vùng cấp phát Vùng cấp phát độđộngng(RAM trống và bộ nhớ ảo)(RAM trống và bộ nhớ ảo)
Gồm các lệnh và hằngGồm các lệnh và hằng(kích th(kích thướước cố c cố địđịnh)nh)
Vùng nhớ trốngVùng nhớ trống
HEAPHEAPĐối tĐối tượượng toàn cụcng toàn cục
& tĩnh& tĩnhĐối tĐối tượượng toàn cụcng toàn cục
& tĩnh& tĩnhMã chMã chươương trìnhng trình
Nhắc lại về biến Chúng ta đã biết các biến đều có kích thước, kiểu
dữ liệu xác định (biến tĩnh) chính là các ô nhớ mà chúng ta có thể truy xuất dưới các tên.
Các biến này được lưu trữ tại những chỗ cụ thể Các biến này được lưu trữ tại những chỗ cụ thể trong bộ nhớ, tồn tại trong suốt thời gian thực thi chương trình
Hạn chế của biến tĩnh: Cấp phát ô nhớ dư: Gây lãnh phí Cấp phát ô nhớ thiếu: Lỗi chương trình
Con trỏNgôn ngữ C cung cấp một kiểu biến gọi là con trỏ với những đặc điểm sau:
Chỉ phát sinh trong quá trình thực hiện chương trình Khi chạy chương trình, kích thước của biến, vùng nhớ và
địa chỉ vùng nhớ được cấp phát cho biến có thể thay đổiKhi chạy chương trình, kích thước của biến, vùng nhớ và địa chỉ vùng nhớ được cấp phát cho biến có thể thay đổi
Sau khi thực hiện xong có thể giải phóng để tiết kiệm chỗ trong bộ nhớ
Kích thước của biến con trỏ không phụ thuộc kiểu dữ liệu, nó luôn có kích thước cố định là 4 byte (tùy thuộc HĐH)
Con trỏ – Một số lý do nên sử dụng
Con trỏ là kiểu dữ liệu lưu trữ địa chỉ của các vùng dữ liệu trong bộ nhớ máy tính
Kiểu con trỏ cho phép: Truyền tham số kiểu địa chỉ Truyền tham số kiểu địa chỉ Biểu diễn các kiểu, cấu trúc dữ liệu động Lưu trữ dữ liệu trong vùng nhớ heap
Ví dụ con trỏ đã được sử dụng trong hàm scanf(…)
Con trỏ – Khai báo trong C Con trỏ đơn giản chỉ là địa chỉ của một vị trí bộ nhớ và
cung cấp cách gián tiếp để truy xuất dữ liệu trong bộ nhớ
Tên con trỏTên con trỏ
typetype **pointer_name;pointer_name;
• Kiểu dữ liệu được trỏ tới• Không phải là kiểu của con trỏ
Ý nghĩa: Khai báo một biến có tên là Khai báo một biến có tên là pointer_namepointer_name dùng để chứa địa chỉ dùng để chứa địa chỉ của các biến có kiểu là của các biến có kiểu là typetype
Con trỏ – Khai báo trong C Giá trị của một biến con trỏ là địa chỉ mà nó trỏ đến Nếu chưa muốn khai báo kiểu dữ liệu mà con trỏ chỉ
đến, ta có thể khai báo như sau:
void *<tên_con_trỏ>;
Ví dụint *number;char *character;float *greatnumber;
• Đây là ba khai báo của con trỏ• Mỗi biến trỏ tới một kiểu dữ liệu khác nhau
• Chúng đều chiếm một lượng bộ nhớ như nhau (kích thước của một biến con trỏ tùy thuộc vào hệ điều hành)
• Dữ liệu mà chúng trỏ tới không chiếm lượng bộ nhớ như nhau, một kiểu int, một kiểu char và cái còn lại kiểu float
int *pi;
long int *p;
float *pf;
Con trỏ – Khai báo trong C
char c, d, *pc; /* c và d kiểu charpc là con trỏ đến char */
double *pd, e, f; /* pd là con trỏ đến doublee and f are double */
char *start, *end;
Con trỏ- Toán tử lấy địa chỉ (&) Khai báo một biến thì nó phải được lưu trữ trong
một vị trí cụ thể trong bộ nhớ Chúng ta không quyết định nơi nào biến đó được
đặt Vậy: Hệ điều hànhHệ điều hành Vậy:
Ai đặt vị trí của biến? Chúng ta có thể biết biến đó được lưu trữ ở đâu ?
Hệ điều hànhHệ điều hành
Điều này có thể được thực hiện bằng cách đặt trước tên biến một dấu và (&), có nghĩa là "địa chỉ của".
Con trỏ - Toán tử lấy địa chỉ “&”
“&”: toán tử lấy địa chỉ của 1 biến Địa chỉ của tất cả các biến trong chương trình đều đã
được chỉ định từ khi khai báo
0x91A2
0x1132
char g = 'z';
int main(){
char c = 'a';char *p;p = &c;p = &g;return 0;
}
p c'a'
0x1132
p g
'z'0x91A2
Con trỏ - Toán tử “*” Có thể truy xuất trực tiếp đến giá trị được lưu trữ trong
biến được trỏ bởi con trỏ bằng cách đặt trước tên biến con trỏ một dấu sao (*) - ở đây được dịch là "giá trị được trỏ bởi"
*<tên_con_trỏ>;
• Vì vậy, nếu chúng ta viết: int beth = *ted;
• C thể đọc nó là: "beth bằng giá trị được trỏ bởi ted”
*<tên_con_trỏ>;
Con trỏ - Toán tử “*”
#include <stdio.h>char g = 'z';int main() a
0x1132
p c'a'
0x1132int main(){
char c = 'a';char *p;p = &c;printf("%c\n", *p);p = &g;printf("%c\n", *p);return 0;
}
az
xuất giá trị do p đang quản lý
0x91A2
0x1132
p g
'z'0x91A2
Phân biệt toán tử & và * Toán tử lấy địa chỉ (&)
Nó được dùng như là một tiền tố của biến và có thể được dịch là "địa chỉ của“&variable1 có thể được đọc là "địa chỉ của variable1“
Toán tử (*)Nó chỉ ra rằng cái cần được tính toán là nội dung được trỏ bởi biểu thức được coi như là một địa chỉ. Được dịch là "giá trị được trỏ bởi"..
*mypointer được đọc là "giá trị được trỏ bởi mypointer"
Con trỏ Con trỏ -- Truyền tham số địa chỉTruyền tham số địa chỉ
#include <stdio.h>void change(int *v);
int main(){
int var = 5;int var = 5;change(&var);printf("main: var = %i\n", var);return 0;
}
void change(int *v){
(*v) *= 100;printf("change: *v = %i\n", (*v));
}
Con trỏ NULLCon trỏ NULL Giá trị đặc biệt để chỉ rằng con trỏ không quản lý vùng
nào. Giá trị này thường được dùng để chỉ một con trỏ không hợp lệ.
#include <stdio.h>int main(){
int i = 13;short *p = NULL;if (p == NULL)
printf(“Con trỏ không hợp lệ!\n");else
printf(“Giá trị : %hi\n", *p);return 0;
}
0x15A4
0x15A0
Con trỏ - Toán tử gán “=” Có sự khác biệt rất quan trọng khi thực hiện các
phép gán:int i = 10, j = 14;int *p = &i;int *q = &j;
p i10
0x15A0q j
14
14
0x15A0
0x15A4*p = *q;
int i = 10, j = 14;int *p = &i;int *q = &j;
p = q;
và:0x15A4
14
p i10
0x15A0q j
0x15A40x15A4
14
0x15A4
Luyện tập – Điền vào ô trống
int main(void){
int i = 10, j = 14, k;int *p = &i;int *q = &j;
i0x2100
j0x2104
*p += 1;p = &k;*p = *q;p = q;*p = *q;
return 0;}
k0x1208
p0x120B
q0x1210
Con trỏ và Mảng Biến kiểu mảng là địa chỉ tĩnh của một vùng nhớ,
được xác định khi khai báo, không thay đổi trong suốt chu kỳ sống.
Biến con trỏ là địa chỉ động của một vùng nhớ, được xác định qua phép gán địa chỉ khi chương trình thực xác định qua phép gán địa chỉ khi chương trình thực thi. #include <stdio.h>
int main(){
int a[10] = {1, 3, 4, 2, 0};int *p;p = a; //a = p: saiprintf(“0x%04X %i 0x%04X %i\n“, );
a, a[0], p, *p);return 0;
}
3344
Con trỏ - Toán tử “+” với số nguyên
#include <stdio.h>int main(){
short a[10] = {1, 3, 5, 2, 0};short *p = a; printf(“0x%04X %i 0x%04X %i\n“, );
a, a[0], p, *p);
11
55
22
aa0x15A0
0x15A00x15A00x15A20x15A2
a, a[0], p, *p);p ++;printf(“0x%04X %i 0x%04X %i\n“, );
a, a[0], p, *p);(*p) ++;printf(“0x%04X %i 0x%04X %i\n“, );
a, a[0], p, *p);return 0;
}
22
00
……
pp0x16B2
Tăng (giảm) 1 ngôi trên biến con trỏ
Giả sử chúng ta có 3 con trỏ sau: char *mychar;short *myshort;long *mylong;
Nguyên nhân:Khi cộng thêm 1 vào một con trỏ thì nó sẽ trỏ tới phần tử tiếp theo có cùng kiểu mà nó đã được định nghĩa, vì vậy kích thước tính bằng byte long *mylong;
Chúng lần lượt trỏ tới ô nhớ 1000, 2000 và 3000. Nếu chúng ta viết
mychar++;myshort++;mylong++;
10011001
20012001
30013001
10011001
20022002
30043004
định nghĩa, vì vậy kích thước tính bằng byte của kiểu dữ liệu nó trỏ tới sẽ được cộng thêm vào biến con trỏ
Tăng (giảm) 1 ngôi trên biến con trỏ
Viết cách khác:Viết cách khác:mychar = mychar + 1;mychar = mychar + 1;myshort = myshort + 1;myshort = myshort + 1;mylong = mylong + 1;mylong = mylong + 1;
Con trỏ - Luyện tập
#include <stdio.h>int main(){
int a[10] = {2, 3, 5, 1, 4, 7, 0};int *p = a; printf(“%i %i\n“, a[0], *p);
2 23 11 91 3
printf(“%i %i\n“, a[0], *p);p ++;printf(“%i %i\n“, *p, p[2]);p ++; a[2] = 9;printf(“%i %i\n“, p[1], *p);p -= 2;printf(“%i %i\n”, p[3], p[1]);return 0;
}
Con trỏ - Cấp phát vùng nhớ động (1) Bộ nhớ tĩnh (stack)
Vùng nhớ được sử dụng để lưu trữ các biến toàn cục và lời gọi hàm
Tất cả những phần bộ nhớ chúng ta có thể sử Tất cả những phần bộ nhớ chúng ta có thể sử dụng là các biến các mảng và các đối tượng khác mà chúng ta đã khai báo. Kích cỡ của chúng là cố định và không thể thay đổi trong thời gian chương trình chạy
Con trỏ - Cấp phát vùng nhớ động (2) Bộ nhớ động (heap)
Bộ nhớ mà kích cỡ của nó chỉ có thể được xác định khi chương trình chạy
Có thể chỉ định vùng bộ nhớ động cho 1 con trỏ quản lý bằng các lệnh hàm malloc, calloc hoặc toán tử new của C++
Vùng nhớ do lập trình viên chỉ định phải được giải phóng bằng lệnh free (malloc, calloc) hoặc toán tử delete (new) trong C++
#include <stdio.h>#include <#include <malloc.hmalloc.h> > int main(){
int *p = (*int)malloc(10);p[0] = 1;p[3] = -7;free(p);return 0;
}
Một số lưu ý Một số lưu ý
Con trỏ là khái niệm quan trọng và khó nhất trong C/C++. Mức độ thành thạo C/C++ được đánh giá qua mức độ sử dụng con trỏ.mức độ sử dụng con trỏ.
Nắm rõ quy tắc sau, ví dụ int a, *pa = &a; *pa và a đều chỉ nội dung của biến a. pa và &a đều chỉ địa chỉ của biến a.
Không nên sử dụng con trỏ khi chưa được khởi tạo. Kết quả sẽ không lường trước được.
Tài liệu tham khảo [PVA] Chương 6 [K&R] Chapter 5
Chuỗi ký tự – Strings Một số qui tắc Nhập / xuất Con trỏ và chuỗi ký tự Một số hàm thư viện Một số hàm thư viện
Chuỗi ký tự Chuỗi ký tự -- Một số qui tắcMột số qui tắc
Chuỗi ký tự là mảng một chiều có mỗi thành phần là một số nguyên được kết thúc bởi số 0 (số không).
Ký tự kết thúc (0) ở cuối chuỗi ký tự thường được gọi là ký tự null (không giống con trỏ NULL). Có thể gọi là ký tự null (không giống con trỏ NULL). Có thể ghi là 0 hoặc ‘\0’ (không phải chữ o).
Được khai báo và truyền tham số như mảng một chiều.
char s[100];unsigned char s1[1000];
char first_name[5] = { 'J', 'o', 'h', 'n', '\0' };
char last_name[6] = "Minor";
char other[] = "Tony Blurt";
char characters[7] = "No null";
Chuỗi ký tự - Ví dụ
char characters[7] = "No null";
first_name
last_name
other
characters
'J' 'o' 'h' 'n' 0
'M' 'i' 'n' 'o' 'r' 0
'T' 'o' ‘n’
'y' 32 'B' 'l' 'u' 'r' 't' 0
'N' 'o' 32 'n' 'u' 'l' 'l' 0
Chuỗi ký tự Chuỗi ký tự -- Nhập / xuấtNhập / xuất Có thể nhập / xuất chuỗi ký tự s bằng cách nhập
từng ký tự của s Hoặc sử dụng các hàm scanf và printf với ký
tự định dạng “%s”
char other[] = "Tony Blurt";
Nhập chuỗi có khoảng trắng dùng hàm gets
char name[100];printf("Nhap mot chuoi ky tu %s: ");gets(name);
char other[] = "Tony Blurt";printf("%s\n", other);
Lưu ý: kết thúc chuỗi#include <stdio.h>
int main(){
char other[] = "Tony Blurt";
printf("%s\n", other);
"Blurt" sẽ không được in ra
'T' 'o' ‘n’
'y' 32 'B' 'l' 'u' 'r' 't' 0
printf("%s\n", other);
other[4] = '\0';
printf("%s\n", other);
return 0;}
Tony BlurtTony
other
Chuỗi ký tự – Một số hàm thư viện
#include<string.h> Lấy độ dài chuỗi
l = strlen(s); Đổi toàn bộ các ký tự của chuỗi thành IN Đổi toàn bộ các ký tự của chuỗi thành IN
HOAstrupr(s);
Đổi toàn bộ các ký tự của chuỗi thành in thườngstrlwr(s);
Chuỗi ký tự – Một số hàm thư viện
So sánh chuỗi: so sánh theo thứ tự từ điển
Phân biệt IN HOA – in thường:
int strcmp(const char *s1, const char *s2);int strcmp(const char *s1, const char *s2);
Không phân biệt IN HOA – in thường:int stricmp(const char *s1, const char *s2);
#include <stdio.h>
int main(){
char s1[] = "Minor";char s2[] = "Tony";int cmp = strcmp(s1, s2);
Chuỗi ký tự – ví dụ strcmp
Minor < Tony
int cmp = strcmp(s1, s2);if (cmp < 0)
printf("%s < %s", s1, s2);else
if (cmp == 0)printf("%s = %s", s1, s2);
elseprintf("%s > %s", s1, s2);
return 0;}
Chuỗi ký tự – Một số hàm thư viện
Gán nội dung chuỗi:o Chép toàn bộ chuỗi source sang chuỗi dest:int strcpy(char *dest, const char *src);
o Chép tối đa n ký tự từ source sang dest:int strncpy(char *dest,
const char *src, int n);
Tạo chuỗi mới từ chuỗi đã có:char *strdup(const char *src);
#include <stdio.h>
int main(){
char s[] = "Tony Blurt";char s2[100], *s3;
Chuỗi ký tự – ví dụ strcpy
Tony Blurt
To123Blurt
Blurt
strcpy(s2, s);printf("%s\n", s2);strncpy(s2 + 2, "12345", 3);printf("%s\n", s2);s3 = strdup(s + 5);printf("%s\n", s3);free(s3);return 0;
}
Chuỗi ký tự – Một số hàm thư viện
Nối chuỗi: char *strcat(char *dest,
const char *src);
Tách chuỗi được phân biệt bởi ký tự trong chuỗi Tách chuỗi được phân biệt bởi ký tự trong chuỗi sep:
char *strtok(char *s, const char *sep);
Trả về địa chỉ của đoạn đầu tiên. Muốn tách đoạn kế tiếp tham số thứ nhất sẽ là NULL
#include <stdio.h>
#define SEPARATOR "., "
int main(){
char s[]= "Thu strtok: 9,123.45";char *p;
Chuỗi ký tự – ví dụ strtok
Thu
strtok:
9
123
45char *p;
p = strtok(s, SEPARATOR);while (p != NULL){
printf("%s\n", p);p = strtok(NULL, SEPARATOR);
}return 0;
}
45
Chuỗi ký tự – Một số hàm thư viện
Tìm một ký tự trên chuỗi: char *strchr(const char *s, int c);
Tìm một đoạn ký tự trên chuỗi: char *strstr(const char *s1,
const char *s2);
#include <stdio.h>
int main(){
char s[]= "Thu tim kiem chuoi";char *p;
Chuỗi ký tự – ví dụ tìm kiếm
char *p;
p = strchr(s, 'm');printf("%s\n", p);p = strstr(s, "em");printf("%s\n", p);return 0;
}
m kiem chuoi
em chuoi
#include <stdio.h>
void StrIns(char *s, char *sub){
int len = strlen(sub);memmove(s + len, s, strlen(s)+1);strncpy(s, sub, len);
Chuỗi ký tự – chèn một đoạn ký tự
strncpy(s, sub, len);}int main(){
char s[]= "Thu chen";
StrIns(s, "123"); printf("%s\n", s);StrIns(s + 8, "45"); printf("%s\n", p);return 0;
}
123 Thu chen
123 Thu 45chen
#include <stdio.h>
void StrDel(char *s, int n){
memmove(s, s + n, strlen(s+n)+1);}int main()
Chuỗi ký tự – xóa một đoạn ký tự
xoa 12345int main(){
char s[]= "Thu xoa 12345";
StrDel(s, 4); printf("%s\n", s);StrDel(s + 4, 3); printf("%s\n", p);return 0;
}
xoa 12345
xoa 45
Tổng kếtTổng kết
Khai báo Nhập / xuất Con trỏ và chuỗi ký tự Con trỏ và chuỗi ký tự Một số hàm thư viện Chèn / loại bỏ một đoạn con
Tài liệu tham khảo
[K&R] Chapter 7