Tải bản đầy đủ - 99 (trang)
Một số con trỏ đặc biệt

Một số con trỏ đặc biệt

Tải bản đầy đủ - 99trang

TopTaiLieu.Com | Chia Sẻ Tài Liệu Miễn Phí

© Dương Thiên Tứ



www.codeschool.vn



Không thể thực hiện số học con trỏ trên con trỏ kiểu void* vì không xác định được kích thước dữ liệu mà nó chỉ đến (nhưng

thực hiện được với con trỏ void**). Không thể deference con trỏ kiểu void* vì không xác định được kiểu dữ liệu của nó. Chỉ

có thể ép kiểu tường minh nó thành kiểu con trỏ khác.

Ép kiểu con trỏ có kiểu bất kỳ thành con trỏ có kiểu void* rồi truyền nó như đối số đến toán tử << sẽ trả về địa chỉ vùng nhớ

chứa trong con trỏ đó dưới dạng hex.

#include

using namespace std;

int main()

{

char* p = "OOP with C++";

void* q;

// con trỏ void* có thể chỉ đến dữ liệu có kiểu bất kỳ, ý tưởng:

// lưu trữ một con trỏ kiểu bất kỳ trong con trỏ void*

q = p;

// con trỏ void* q quản lý chuỗi "OOP with C++", có thể gán p bằng NULL

p = NULL;

// phải ép kiểu con trỏ void* để sử dụng được dữ liệu nơi nó chỉ đến

p = ( char* )q;

cout << p << endl;

// in chuỗi "OOP with C++"

cout << ( void* )p << endl;

// in địa chỉ chứa trong p

return 0;

}

Con trỏ void* dùng xây dựng các phương thức, template, nạp chồng toán tử new, … trong đó cần con trỏ chỉ đến một kiểu

chung do chưa xác định được kích thước kiểu dữ liệu chỉ đến.

c) Con trỏ hàm (function pointer - functor)

Khác với con trỏ thông thường dùng chỉ đến dữ liệu, con trỏ hàm là một loại con trỏ dùng chỉ đến code. Trong C++, tên một

hàm là con trỏ hằng chỉ đến hàm đó. Có nhiều cách dùng con trỏ hàm: dùng gọi hàm thay cho tên hàm, dùng như đối số truyền

đến hàm khác, lưu vào mảng để truy xuất theo chỉ số một hàm trong tập các hàm giống nhau.

#include

using namespace std;

int

{

int

{



add( int

return a

mul( int

return a



a, int b )

+ b; }

a, int b )

* b; }



// định nghĩa một số hàm có đặc điểm giống nhau

// nhận hai đối số int và trả về kiểu int



// khai báo con trỏ hàm nhận hai đối số int và trả về kiểu int

int ( *pFunc )( int, int );

// khai báo và gán một mảng các con trỏ hàm (các tên hàm)

int ( *apFunc[] )( int, int ) = { add, mul };

// khai báo một hàm nhận đối số là một con trỏ hàm (tên hàm)

int caller( int, int, int ( *p )( int, int ) );

int main()

{

// gán cho con trỏ hàm một địa chỉ hàm để nó ủy nhiệm đến khi gọi hàm

pFunc = &add;

// dereference một con trỏ hàm nghĩa là ủy nhiệm lời gọi đến hàm cần gọi, trong trường hợp này là hàm add()

cout << ( *pFunc )( 4, 5 ) << endl;

// nên dùng cú pháp sau, linh động và dễ sử dụng hơn, ủy nhiệm đến hàm mul(),

pFunc = mul;

// tên một hàm xem như là một con trỏ hàm!

// dùng con trỏ hàm như một hàm! (ủy nhiệm gọi hàm), tên con trỏ thay cho tên hàm

cout << pFunc( 4, 5 ) << endl;

// dùng một con trỏ hàm trong mảng các con trỏ hàm

cout << apFunc[0]( 4, 5 ) << endl;

// một phần tử của mảng con trỏ hàm cũng là một con trỏ!

cout << ( *( apFunc + 1 ) )( 4, 5 ) << endl;



}



// chuyển lời gọi hàm mul() đến hàm caller(), truyền con trỏ hàm như đối số

cout << caller( 4, 5, mul ) << endl;

return 0;



int caller( int a, int b, int ( *p )( int, int ) )

{

32



TopTaiLieu.Com | Chia Sẻ Tài Liệu Miễn Phí

© Dương Thiên Tứ



www.codeschool.vn



return p( a, b );



}

Dùng typedef sẽ làm cho chương trình dễ đọc hơn nhiều, xem lại ví dụ trên:

#include

using namespace std;

int add( int a, int b ) { return a + b; }

int mul( int a, int b ) { return a * b; }

typedef int ( *VPF )( int, int );

VPF v[] = { add, mul };

// mảng các con trỏ hàm

int caller( int, int, VPF );

// truyền con trỏ hàm như đối số

int main()

{

VPF pFunc = mul;

cout << pFunc( 4, 5 ) << endl;

cout << v[0]( 4, 5 ) << endl;

cout << caller( 4, 5, mul ) << endl;

return 0;

}



//

//

//

//



dùng tên hàm như con trỏ hàm

dùng con trỏ hàm giống như một hàm

dùng con trỏ hàm trong mảng các con trỏ hàm

gọi hàm với đối số là con trỏ hàm



int caller( int a, int b, VPF p )

{

return p( a, b );

}

Con trỏ chỉ đến phương thức thành viên (pointer to member function) cũng được sử dụng trong trường hợp lớp có nhiều phương

thức thành viên có đặc điểm giống nhau:

- Truy xuất con trỏ chỉ đến phương thức thành viên thông qua con trỏ chỉ đến đối tượng, dùng toán tử ->*

- Truy xuất con trỏ chỉ đến phương thức thành viên thông qua đối tượng, dùng toán tử .*

Khi truy xuất con trỏ chỉ đến dữ liệu thành viên cũng sử dụng hai toán tử trên theo cách tương tự.

#include

#include

using namespace std;

class Greetings {

public:

void hello( const string & name ) const

{ cout << "Hello, " << name << endl; }

void byebye( const string & name ) const

{ cout << "Byebye, " << name <
};

typedef void ( Greetings::*VPF )( string ) const;

void greet( const Greetings* p, string name, VPF pFunc = &Greetings::hello )

{

( p->*pFunc )( name );

}

int main()

{

Greetings* p = new Greetings();

greet( p, "Albert Einstein", &Greetings::byebye );

return 0;

}

Con trỏ hàm thường được dùng để thực hiện các hàm callback, cơ chế kết nối động (dynamic binding), các ứng dụng hướng sự

kiện, cơ chế ủy nhiệm (delegate), …

II. Tham chiếu (Reference)

1. Kiểu tham chiếu

a

iint a = 7;

aa

7

// khai báo tham chiếu, ký hiệu &

// không phải là toán tử lấy địa chỉ

chỉ là khai báo một "tên khác"

int& aa = a;

cout << aa << endl;

// đọc GIÁN TIẾP trị của a thông qua "tên khác" aa

aa = 5;

// a bây giờ là 5, bị thay đổi GIÁN TIẾP thông qua aa

aa là một tên khác của a, một bí danh (alias) của a (a gọi là referent của aa). Ta truy xuất đến aa cũng giống như truy xuất

đến a. Nhận xét: có thể truy xuất GIÁN TIẾP đến biến a thông qua bí danh của nó là biến tham chiếu aa.

Tham chiếu cũng truy xuất GIÁN TIẾP đến một biến như con trỏ nhưng cú pháp ít phức tạp hơn. C++ có khuynh hướng sử

33



TopTaiLieu.Com | Chia Sẻ Tài Liệu Miễn Phí

© Dương Thiên Tứ



www.codeschool.vn



dụng tham chiếu nhiều hơn con trỏ.

Tham chiếu không phải là một biến riêng biệt, biến tham chiếu aa thật sự đồng nhất với biến a. Áp dụng toán tử & lên tham

chiếu giống như áp dụng toán tử & lên biến mà nó tham chiếu đến, kết quả cho cùng một địa chỉ là địa chỉ của biến. Việc định

nghĩa một tham chiếu không tốn thêm bộ nhớ như con trỏ.

cout << &aa << ' ' << &a; // kết quả cho cùng một địa chỉ, ví dụ 0x25FDA8

Như vậy, tham chiếu chính là một tên khác của một biến đã tồn tại, nhưng dùng tham chiếu có nghĩa là ta đã truy xuất đến biến

đó một cách gián tiếp. Vì bản chất tham chiếu khác con trỏ nên tham chiếu có rất điểm khác con trỏ:

- Tham chiếu cần được khởi gán tại thời điểm nó được tạo ra, sau đó không thể thay đổi tham chiếu chỉ đến một đối tượng khác.

Nghĩa là không dùng một tham chiếu cho nhiều biến khác nhau, không gán lại tham chiếu.

- Không có biến tham chiếu NULL. Không có tham chiếu đến một con trỏ.

- Không có đặc tính cộng trừ như con trỏ. Không thể gán chỉ số cho tham chiếu.

- Không thể tạo một mảng các tham chiếu.

- Con trỏ được dereference bằng toán tử * hoặc -> một cách tường minh. Tham chiếu được dereference tự động, không dùng

toán tử. Vì vậy dùng tham chiếu có vẻ "tự nhiên" hơn, giống với dùng biến trực tiếp.

// con trỏ

// tham chiếu

// trực tiếp

int x;

int x;

int x;

int* p = &x;

int& y = x;

*p = 10;

y = 10;

x = 10;

cout << *p;

cout << y;

cout << x;

2. Chú ý khi dùng tham chiếu

Biến tham chiếu hoàn toàn không có ý nghĩa cho đến khi nó được khởi tạo bằng cách gắn (attach) với một referent nào đó. Gán

cho aa trị của b, nghĩa là gán cho a trị của b, không phải thay đổi tham chiếu aa cho nó chỉ (kết nối - bound) đến b.

int a = 2, b = 3;

int& aa = a;

// khởi gán tham chiếu aa là một alias của a

int c = aa;

// gán c với tham chiếu aa, tương đương gán c với a (mà aa là alias)

aa = b;

// thay đổi a với trị của b, không phải gán aa là alias của b

int& bb = 10;

// SAI. Tham chiếu phải là alias của biến hoặc đối tượng

Có thể tham chiếu đến một đối tượng cấp phát động.

#include

class Point

{

int x, y;

public:

Point( int a = 0, int b = 0 )

{ x = a; y = b; }

void show()

{ std::cout << "( " << x << ", " << y << " )\n"; }

};

int main()

{

// dùng tham chiếu của một đối tượng cấp phát động

Point& p = *new Point( 3, 4 );

p.show();

// dùng con trỏ chỉ đến đối tượng cấp phát động

Point* q = new Point( 5, 6 );

q->show();

return 0;

}

III. Truyền đối số cho một hàm

1. Truyền bằng trị (Pass By Value)

void DoubleIt( int x )

{ x *= 2; }

int main()

{

int a = 8;

DoubleIt( a );

cout << a << endl; // kết quả: 8, a không đổi sau khi gọi hàm DoubleIt

return 0;

}

Khi dùng truyền bằng trị biến a đến hàm DoubleIt(), đối số thực a sẽ được COPY đến đối số hình thức x: int x = a;

Hàm DoubleIt() sẽ thao tác trên biến cục bộ x này giống như thao tác trên một bản sao của a.

Hàm DoubleIt() không làm thay đổi được biến a do biến a ở tầm vực (scope) khác: thuộc về hàm main().

Truyền một mảng đến hàm tương đương với truyền con trỏ, hàm nhận đối số là mảng có thể thay đổi các phần tử của mảng.

Truyền con trỏ hằng (const con trỏ truyền) để bảo vệ mảng tránh thay đổi nếu cần.

34



TopTaiLieu.Com | Chia Sẻ Tài Liệu Miễn Phí

© Dương Thiên Tứ



www.codeschool.vn



2. Truyền bằng tham chiếu thông qua con trỏ (Pointer Based Pass By Reference)

Ta hiểu là truyền bằng trị một đối số kiểu con trỏ.

void DoubleIt( int* x )

{ *x *= 2; }

int main()

{

int a = 8;

DoubleIt( &a );

cout << a << endl; // kết quả: 16, a thay đổi sau khi gọi hàm DoubleIt

return 0;

}

Địa chỉ của đối số thực a được COPY đến đối số hình thức là con trỏ *x như sau: int* x = &a;

Như vậy, rõ ràng x là con trỏ chỉ đến biến a.

Hàm DoubleIt() không thể truy xuất TRỰC TIẾP biến a của hàm main(); nhưng vẫn có thể truy xuất GIÁN TIẾP và làm

thay đổi đối số thực a thông qua con trỏ x chỉ đến nó.

Ta nhận thấy cú pháp sử dụng phức tạp và dễ nhầm lẫn cho cả người gọi hàm lẫn người cài đặt hàm, do dùng nhiều toán tử *

và &. Tuy nhiên, đây là cách ngôn ngữ C dùng.

Ta thường dùng con trỏ để truy xuất đọc và ghi đến một đối tượng. Khi dùng chỉ với tác vụ đọc, ta có thể dùng từ khóa const

để gắn "nhãn chống ghi" cho đối tượng do con trỏ chỉ đến. Con trỏ trong trường hợp này gọi là con trỏ chỉ đọc (read-only pointer).

#include

using namespace std;

// mô phỏng các hàm xử lý chuỗi của C trả về số ký tự có trong chuỗi

size_t _strlen( const char *str )

{

const char* p = str;

while ( *p ) ++p;

return ( p - str );

}

// so sánh len ký tự của chuỗi s1 với s2

int _strncmp( const char *s1, const char *s2, size_t len )

{

if ( len > _strlen( s1 ) ) len = _strlen( s1 );

if ( len > _strlen( s2 ) ) len = _strlen( s2 );

for ( int i = 0; i < len; ++i )

if ( s1[i] != s2[i] ) return 1;

return 0;

}

// tìm chuỗi s2 trong chuỗi s1

char* _strstr( const char *s1, const char *s2 )

{

size_t len = _strlen( s2 );

for ( ; *s1 != '\0'; ++s1 )

if ( _strncmp( s1, s2, len ) == 0 )

return ( char * )s1;

return NULL;

}

// trả về vị trí các chuỗi s2 trong s1

int main()

{

char* s = "hom qua qua khong qua";

char* p = s;

while ( ( p = _strstr( p, "qua" ) ) != NULL )

{

std::cout << p - s << ' ';

p++;

}

return 0;

}

3. Truyền bằng tham chiếu (Pass By Reference)

Ta hiểu là truyền bằng trị một đối số kiểu tham chiếu.

void DoubleIt( int& x ){ x *= 2; }

int main()

{

int a = 8;

DoubleIt( a );

cout << a << endl; // kết quả: 16, a thay đổi sau khi gọi hàm DoubleIt

35



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

Một số con trỏ đặc biệt

Tải bản đầy đủ ngay(99 tr)

×