Tôi biết tài liệu tham khảo là cú pháp đường, vì vậy mã dễ đọc và viết hơn.
Nhưng sự khác biệt là gì?
int &x = *(int*)0;
trên gcc. Tham khảo thực sự có thể trỏ đến NULL.
Tôi biết tài liệu tham khảo là cú pháp đường, vì vậy mã dễ đọc và viết hơn.
Nhưng sự khác biệt là gì?
int &x = *(int*)0;
trên gcc. Tham khảo thực sự có thể trỏ đến NULL.
Câu trả lời:
Một con trỏ có thể được gán lại:
int x = 5;
int y = 6;
int *p;
p = &x;
p = &y;
*p = 10;
assert(x == 5);
assert(y == 10);
Một tham chiếu không thể, và phải được chỉ định khi khởi tạo:
int x = 5;
int y = 6;
int &r = x;
Một con trỏ có địa chỉ bộ nhớ và kích thước của nó trên ngăn xếp (4 byte trên x86), trong khi một tham chiếu chia sẻ cùng một địa chỉ bộ nhớ (với biến ban đầu) nhưng cũng chiếm một khoảng trống trên ngăn xếp. Vì một tham chiếu có cùng địa chỉ với chính biến ban đầu, nên có thể nghĩ rằng một tham chiếu là một tên khác cho cùng một biến. Lưu ý: Những gì một con trỏ trỏ đến có thể trên ngăn xếp hoặc đống. Ditto một tài liệu tham khảo. Yêu cầu của tôi trong tuyên bố này không phải là một con trỏ phải trỏ đến ngăn xếp. Một con trỏ chỉ là một biến chứa một địa chỉ bộ nhớ. Biến này là trên ngăn xếp. Vì một tham chiếu có không gian riêng trên ngăn xếp và vì địa chỉ giống với biến mà nó tham chiếu. Thêm vào stack vs heap. Điều này ngụ ý rằng có một địa chỉ thực sự của một tham chiếu mà trình biên dịch sẽ không cho bạn biết.
int x = 0;
int &r = x;
int *p = &x;
int *p2 = &r;
assert(p == p2);
Bạn có thể có con trỏ tới con trỏ tới con trỏ cung cấp thêm mức độ gián tiếp. Trong khi các tài liệu tham khảo chỉ cung cấp một mức độ gián tiếp.
int x = 0;
int y = 0;
int *p = &x;
int *q = &y;
int **pp = &p;
pp = &q;//*pp = q
**pp = 4;
assert(y == 4);
assert(x == 0);
Một con trỏ có thể được chỉ định nullptr
trực tiếp, trong khi tham chiếu không thể. Nếu bạn cố gắng đủ, và bạn biết làm thế nào, bạn có thể tạo địa chỉ của một tài liệu tham khảo nullptr
. Tương tự như vậy, nếu bạn cố gắng hết sức, bạn có thể có một tham chiếu đến một con trỏ và sau đó tham chiếu đó có thể chứa nullptr
.
int *p = nullptr;
int &r = nullptr; <--- compiling error
int &r = *p; <--- likely no compiling error, especially if the nullptr is hidden behind a function call, yet it refers to a non-existent int at address 0
Con trỏ có thể lặp qua một mảng; bạn có thể sử dụng ++
để đi đến mục tiếp theo mà con trỏ đang trỏ tới và + 4
đi đến phần tử thứ 5. Đây không phải là vấn đề kích thước của đối tượng mà con trỏ trỏ tới.
Một con trỏ cần được hủy *
đăng ký để truy cập vào vị trí bộ nhớ mà nó trỏ tới, trong khi tham chiếu có thể được sử dụng trực tiếp. Một con trỏ tới một lớp / struct sử dụng ->
để truy cập các thành viên của nó trong khi một tham chiếu sử dụng a .
.
Tài liệu tham khảo không thể được nhồi vào một mảng, trong khi con trỏ có thể được (Được đề cập bởi người dùng @litb)
Tài liệu tham khảo Const có thể được ràng buộc với thời gian. Con trỏ không thể (không phải không có một số điều hướng):
const int &x = int(12); //legal C++
int *y = &int(12); //illegal to dereference a temporary.
Điều này làm cho const&
an toàn hơn để sử dụng trong danh sách đối số và vv.
Một tham chiếu có thể được coi là một con trỏ không đổi (không bị nhầm lẫn với một con trỏ đến một giá trị không đổi!) Với tính năng tự động, tức là trình biên dịch sẽ áp dụng *
toán tử cho bạn.
Tất cả các tham chiếu phải được khởi tạo với giá trị không null hoặc quá trình biên dịch sẽ thất bại. Không thể lấy địa chỉ của tham chiếu - toán tử địa chỉ sẽ trả về địa chỉ của giá trị được tham chiếu thay vào đó - cũng không thể thực hiện các phép đo đối với các tham chiếu.
Các lập trình viên C có thể không thích các tham chiếu C ++ vì nó sẽ không còn rõ ràng khi xảy ra tình trạng gián tiếp hoặc nếu một đối số được truyền bằng giá trị hoặc bằng con trỏ mà không nhìn vào chữ ký hàm.
Các lập trình viên C ++ có thể không thích sử dụng các con trỏ vì chúng được coi là không an toàn - mặc dù các tài liệu tham khảo không thực sự an toàn hơn các con trỏ liên tục ngoại trừ trong các trường hợp tầm thường nhất - thiếu sự tiện lợi của việc tự động chuyển hướng và mang một ý nghĩa ngữ nghĩa khác.
Hãy xem xét các tuyên bố sau từ C ++ FAQ :
Mặc dù một tham chiếu thường được thực hiện sử dụng một địa chỉ trong ngôn ngữ lắp ráp cơ bản, xin vui lòng không nghĩ đến một tài liệu tham khảo như một con trỏ tìm hài hước đến một đối tượng. Một tài liệu tham khảo là đối tượng. Nó không phải là một con trỏ tới đối tượng, cũng không phải là bản sao của đối tượng. Nó là đối tượng.
Nhưng nếu một tài liệu tham khảo thực sự là đối tượng, làm thế nào có thể có các tài liệu tham khảo lơ lửng? Trong các ngôn ngữ không được quản lý, các tham chiếu không thể 'an toàn' hơn con trỏ - nói chung không có cách nào đáng tin cậy các giá trị bí danh trên các ranh giới phạm vi!
Xuất thân từ một nền C, C ++ tài liệu tham khảo có thể trông giống như một khái niệm hơi ngớ ngẩn, nhưng chúng ta vẫn nên sử dụng chúng thay vì con trỏ nếu có thể: Tự động gián tiếp là thuận tiện, và tài liệu tham khảo trở nên đặc biệt hữu ích khi giao dịch với RAII - nhưng không phải vì bất kỳ an toàn nhận thức lợi thế, nhưng thay vì họ làm cho việc viết mã thành ngữ bớt khó xử.
RAII là một trong những khái niệm trung tâm của C ++, nhưng nó tương tác không tầm thường với ngữ nghĩa sao chép. Vượt qua các đối tượng bằng cách tham chiếu sẽ tránh được các vấn đề này vì không liên quan đến sao chép. Nếu các tài liệu tham khảo không có trong ngôn ngữ, thay vào đó, bạn phải sử dụng các con trỏ, điều này sẽ khó sử dụng hơn, do đó vi phạm nguyên tắc thiết kế ngôn ngữ rằng giải pháp thực hành tốt nhất nên dễ dàng hơn các giải pháp thay thế.
Nếu bạn muốn thực sự phạm tội, có một điều bạn có thể làm với một tham chiếu mà bạn không thể làm với một con trỏ: kéo dài tuổi thọ của một đối tượng tạm thời. Trong C ++ nếu bạn liên kết một tham chiếu const với một đối tượng tạm thời, thời gian tồn tại của đối tượng đó sẽ trở thành thời gian tồn tại của tham chiếu.
std::string s1 = "123";
std::string s2 = "456";
std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;
Trong ví dụ này, s3_copy sao chép đối tượng tạm thời là kết quả của phép nối. Trong khi đó s3 numference về bản chất trở thành đối tượng tạm thời. Nó thực sự là một tham chiếu đến một đối tượng tạm thời hiện có cùng thời gian với tham chiếu.
Nếu bạn thử điều này mà không có const
nó sẽ không biên dịch được. Bạn không thể liên kết một tham chiếu không phải là một đối tượng tạm thời, và bạn cũng không thể lấy địa chỉ của nó cho vấn đề đó.
const &
ràng buộc và chỉ khi tham chiếu vượt ra khỏi phạm vi thì hàm hủy của loại tham chiếu thực tế (so với loại tham chiếu, có thể là cơ sở) mới được gọi. Vì nó là một tài liệu tham khảo, không có lát cắt sẽ diễn ra ở giữa.
Animal x = fast ? getHare() : getTortoise()
thì x
sẽ phải đối mặt với vấn đề cắt cổ điển, trong khi Animal& x = ...
sẽ làm việc một cách chính xác.
Ngoài đường cú pháp, một tham chiếu là một const
con trỏ ( không phải con trỏ đến a const
). Bạn phải thiết lập những gì nó đề cập đến khi bạn khai báo biến tham chiếu và bạn không thể thay đổi nó sau này.
Cập nhật: bây giờ tôi nghĩ về nó nhiều hơn, có một sự khác biệt quan trọng.
Mục tiêu của con trỏ const có thể được thay thế bằng cách lấy địa chỉ của nó và sử dụng cast const.
Mục tiêu của tài liệu tham khảo không thể được thay thế bằng bất kỳ cách nào thiếu UB.
Điều này sẽ cho phép trình biên dịch thực hiện tối ưu hóa nhiều hơn trên một tài liệu tham khảo.
T* const
loại đường cú pháp khác nhau (điều đó xảy ra để loại bỏ rất nhiều * và & từ mã của bạn).
int i; int const *pci = &i; /* implicit conv to const int* */ int *pi = const_cast<int*>(pci);
là OK.
Trái với ý kiến phổ biến, có thể có một tài liệu tham khảo đó là NULL.
int * p = NULL;
int & r = *p;
r = 1; // crash! (if you're lucky)
Cấp, nó khó hơn nhiều để làm với một tài liệu tham khảo - nhưng nếu bạn quản lý nó, bạn sẽ xé tóc ra để cố gắng tìm nó. Tài liệu tham khảo vốn không an toàn trong C ++!
Về mặt kỹ thuật, đây là một tài liệu tham khảo không hợp lệ , không phải là tài liệu tham khảo null. C ++ không hỗ trợ các tham chiếu null dưới dạng khái niệm như bạn có thể tìm thấy trong các ngôn ngữ khác. Có nhiều loại tài liệu tham khảo không hợp lệ là tốt. Bất kỳ tham chiếu không hợp lệ nào cũng làm tăng bóng ma của hành vi không xác định , giống như sử dụng một con trỏ không hợp lệ.
Lỗi thực tế nằm ở phần hội thảo của con trỏ NULL, trước khi gán cho tham chiếu. Nhưng tôi không biết về bất kỳ trình biên dịch nào sẽ tạo ra bất kỳ lỗi nào trong điều kiện đó - lỗi sẽ lan truyền đến một điểm xa hơn trong mã. Đó là những gì làm cho vấn đề này rất ngấm ngầm. Hầu hết thời gian, nếu bạn bỏ qua một con trỏ NULL, bạn sẽ gặp sự cố ngay tại vị trí đó và không cần phải gỡ lỗi nhiều để tìm ra nó.
Ví dụ của tôi ở trên là ngắn và có kế hoạch. Đây là một ví dụ thực tế hơn.
class MyClass
{
...
virtual void DoSomething(int,int,int,int,int);
};
void Foo(const MyClass & bar)
{
...
bar.DoSomething(i1,i2,i3,i4,i5); // crash occurs here due to memory access violation - obvious why?
}
MyClass * GetInstance()
{
if (somecondition)
return NULL;
...
}
MyClass * p = GetInstance();
Foo(*p);
Tôi muốn nhắc lại rằng cách duy nhất để có được một tham chiếu null là thông qua mã không đúng định dạng và một khi bạn có nó, bạn sẽ nhận được hành vi không xác định. Nó không bao giờ có ý nghĩa để kiểm tra một tài liệu tham khảo null; ví dụ bạn có thể thử if(&bar==NULL)...
nhưng trình biên dịch có thể tối ưu hóa câu lệnh tồn tại! Một tham chiếu hợp lệ không bao giờ có thể là NULL vì vậy theo quan điểm của người biên dịch, so sánh luôn luôn sai và có thể loại bỏ if
mệnh đề dưới dạng mã chết - đây là bản chất của hành vi không xác định.
Cách thích hợp để tránh xa rắc rối là tránh hội thảo con trỏ NULL để tạo tham chiếu. Đây là một cách tự động để thực hiện điều này.
template<typename T>
T& deref(T* p)
{
if (p == NULL)
throw std::invalid_argument(std::string("NULL reference"));
return *p;
}
MyClass * p = GetInstance();
Foo(deref(p));
Để có cái nhìn sâu sắc hơn về vấn đề này từ một người có kỹ năng viết tốt hơn, hãy xem Tài liệu tham khảo Null từ Jim Hyslop và Herb Sutter.
Đối với một ví dụ khác về sự nguy hiểm của việc hủy bỏ hội thảo, một con trỏ null xem Phơi bày hành vi không xác định khi cố gắng chuyển mã sang nền tảng khác của Raymond Chen.
Bạn đã quên phần quan trọng nhất:
truy cập thành viên với con trỏ sử dụng ->
quyền truy cập thành viên với tham chiếu sử dụng.
foo.bar
rõ ràng là vượt trội so với foo->bar
cùng một cách mà vi rõ ràng là vượt trội so với Emacs :-)
->
cung cấp cho các tham chiếu đến các con trỏ, giống như với chính con trỏ.
.
và ->
có liên quan đến vi và emacs :)
.
tốt hơn so với sử dụng ->
, nhưng cũng giống như vi vs emacs, nó hoàn toàn chủ quan và bạn không thể chứng minh bất cứ điều gì
Tài liệu tham khảo rất giống với con trỏ, nhưng chúng được chế tạo đặc biệt để hữu ích cho việc tối ưu hóa trình biên dịch.
Ví dụ:
void maybeModify(int& x); // may modify x in some way
void hurtTheCompilersOptimizer(short size, int array[])
{
// This function is designed to do something particularly troublesome
// for optimizers. It will constantly call maybeModify on array[0] while
// adding array[1] to array[2]..array[size-1]. There's no real reason to
// do this, other than to demonstrate the power of references.
for (int i = 2; i < (int)size; i++) {
maybeModify(array[0]);
array[i] += array[1];
}
}
Một trình biên dịch tối ưu hóa có thể nhận ra rằng chúng ta đang truy cập [0] và [1] khá nhiều. Rất thích tối ưu hóa thuật toán để:
void hurtTheCompilersOptimizer(short size, int array[])
{
// Do the same thing as above, but instead of accessing array[1]
// all the time, access it once and store the result in a register,
// which is much faster to do arithmetic with.
register int a0 = a[0];
register int a1 = a[1]; // access a[1] once
for (int i = 2; i < (int)size; i++) {
maybeModify(a0); // Give maybeModify a reference to a register
array[i] += a1; // Use the saved register value over and over
}
a[0] = a0; // Store the modified a[0] back into the array
}
Để thực hiện tối ưu hóa như vậy, cần phải chứng minh rằng không có gì có thể thay đổi mảng [1] trong suốt cuộc gọi. Điều này là khá dễ dàng để làm. i không bao giờ nhỏ hơn 2, vì vậy mảng [i] không bao giờ có thể tham chiếu đến mảng [1]. mayModify () được cho a0 làm tham chiếu (mảng răng cưa [0]). Do không có số học "tham chiếu", trình biên dịch chỉ cần chứng minh rằng mayModify không bao giờ lấy địa chỉ của x và nó đã chứng minh rằng không có gì thay đổi mảng [1].
Nó cũng phải chứng minh rằng không có cách nào một cuộc gọi trong tương lai có thể đọc / ghi [0] trong khi chúng tôi có một bản sao đăng ký tạm thời của nó trong a0. Điều này thường không quan trọng để chứng minh, bởi vì trong nhiều trường hợp, rõ ràng là tài liệu tham khảo không bao giờ được lưu trữ trong một cấu trúc vĩnh viễn như một thể hiện của lớp.
Bây giờ làm điều tương tự với con trỏ
void maybeModify(int* x); // May modify x in some way
void hurtTheCompilersOptimizer(short size, int array[])
{
// Same operation, only now with pointers, making the
// optimization trickier.
for (int i = 2; i < (int)size; i++) {
maybeModify(&(array[0]));
array[i] += array[1];
}
}
Hành vi là như nhau; chỉ bây giờ khó khăn hơn nhiều để chứng minh rằng mayModify không bao giờ sửa đổi mảng [1], bởi vì chúng tôi đã cho nó một con trỏ; Con mèo ra khỏi cái túi xách. Bây giờ nó phải thực hiện một bằng chứng khó khăn hơn nhiều: một phân tích tĩnh về mayModify để chứng minh rằng nó không bao giờ ghi vào & x + 1. Nó cũng phải chứng minh rằng nó không bao giờ lưu một con trỏ có thể tham chiếu đến mảng [0], chỉ là như là khó khăn
Các trình biên dịch hiện đại ngày càng tốt hơn trong phân tích tĩnh, nhưng thật tuyệt khi giúp chúng ra và sử dụng các tài liệu tham khảo.
Tất nhiên, việc tối ưu hóa thông minh như vậy, trình biên dịch thực sự sẽ biến các tham chiếu thành con trỏ khi cần.
EDIT: Năm năm sau khi đăng câu trả lời này, tôi đã tìm thấy một sự khác biệt kỹ thuật thực tế nơi các tài liệu tham khảo khác với chỉ một cách nhìn khác nhau về cùng một khái niệm địa chỉ. Tài liệu tham khảo có thể sửa đổi tuổi thọ của các đối tượng tạm thời theo cách mà con trỏ không thể.
F createF(int argument);
void extending()
{
const F& ref = createF(5);
std::cout << ref.getArgument() << std::endl;
};
Thông thường các đối tượng tạm thời như đối tượng được tạo bởi lệnh gọi createF(5)
sẽ bị hủy ở cuối biểu thức. Tuy nhiên, bằng cách ràng buộc đối tượng đó với một tham chiếu, ref
C ++ sẽ kéo dài tuổi thọ của đối tượng tạm thời đó cho đến khi ref
vượt ra khỏi phạm vi.
maybeModify
không lấy địa chỉ của bất kỳ thứ gì liên quan đến x
thực chất dễ dàng hơn việc chứng minh rằng một loạt các số học con trỏ không xảy ra.
void maybeModify(int& x) { 1[&x]++; }
mà các ý kiến khác ở trên đang thảo luận
Trên thực tế, một tài liệu tham khảo không thực sự giống như một con trỏ.
Trình biên dịch giữ "tham chiếu" đến các biến, liên kết tên với địa chỉ bộ nhớ; đó là công việc của nó để dịch bất kỳ tên biến thành địa chỉ bộ nhớ khi biên dịch.
Khi bạn tạo một tham chiếu, bạn chỉ nói với trình biên dịch rằng bạn gán tên khác cho biến con trỏ; đó là lý do tại sao các tham chiếu không thể "trỏ đến null", bởi vì một biến không thể và không thể.
Con trỏ là các biến; chúng chứa địa chỉ của một số biến khác hoặc có thể là null. Điều quan trọng là một con trỏ có một giá trị, trong khi một tham chiếu chỉ có một biến mà nó đang tham chiếu.
Bây giờ một số giải thích về mã thực:
int a = 0;
int& b = a;
Ở đây bạn không tạo ra một biến khác trỏ đến a
; bạn chỉ cần thêm một tên khác vào nội dung bộ nhớ giữ giá trị của a
. Bộ nhớ này hiện có hai tên, a
và b
, nó có thể được xử lý bằng tên này.
void increment(int& n)
{
n = n + 1;
}
int a;
increment(a);
Khi gọi một hàm, trình biên dịch thường tạo các không gian bộ nhớ cho các đối số được sao chép vào. Chữ ký hàm xác định các khoảng trắng sẽ được tạo và đặt tên nên được sử dụng cho các khoảng trắng này. Khai báo một tham số làm tham chiếu chỉ cho trình biên dịch sử dụng không gian bộ nhớ biến đầu vào thay vì phân bổ một không gian bộ nhớ mới trong khi gọi phương thức. Có vẻ lạ khi nói rằng hàm của bạn sẽ trực tiếp thao tác một biến được khai báo trong phạm vi gọi, nhưng hãy nhớ rằng khi thực thi mã được biên dịch, không còn phạm vi nữa; chỉ có bộ nhớ phẳng đơn giản và mã chức năng của bạn có thể thao tác bất kỳ biến nào.
Bây giờ có thể có một số trường hợp trình biên dịch của bạn không thể biết được tham chiếu khi biên dịch, như khi sử dụng biến ngoài. Vì vậy, một tham chiếu có thể hoặc không thể được thực hiện như một con trỏ trong mã bên dưới. Nhưng trong các ví dụ tôi đã đưa cho bạn, rất có thể nó sẽ không được thực hiện bằng một con trỏ.
Một tài liệu tham khảo không bao giờ có thể được NULL
.
void Foo::bar() { virtual_baz(); }
segfaults đó. Nếu bạn không biết rằng các tài liệu tham khảo có thể là null, bạn không thể theo dõi null trở lại nguồn gốc của nó.
int &r=*p;
là hành vi không xác định. Vào thời điểm đó, bạn không có một "tài liệu tham khảo trỏ đến NULL," bạn có một chương trình mà không còn có thể được lập luận về ở tất cả .
Trong khi cả tham chiếu và con trỏ được sử dụng để gián tiếp truy cập một giá trị khác, có hai điểm khác biệt quan trọng giữa tham chiếu và con trỏ. Đầu tiên là một tham chiếu luôn đề cập đến một đối tượng: Đó là lỗi khi xác định tham chiếu mà không khởi tạo nó. Hành vi của sự phân công là sự khác biệt quan trọng thứ hai: Việc gán cho một tham chiếu thay đổi đối tượng mà tham chiếu bị ràng buộc; nó không rebind tham chiếu đến đối tượng khác. Sau khi khởi tạo, một tham chiếu luôn đề cập đến cùng một đối tượng cơ bản.
Hãy xem xét hai đoạn chương trình này. Đầu tiên, chúng ta gán một con trỏ cho một con trỏ khác:
int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2; // pi now points to ival2
Sau khi gán, ival, đối tượng được giải quyết bằng pi vẫn không thay đổi. Việc gán thay đổi giá trị của pi, làm cho nó trỏ đến một đối tượng khác. Bây giờ hãy xem xét một chương trình tương tự gán hai tham chiếu:
int &ri = ival, &ri2 = ival2;
ri = ri2; // assigns ival2 to ival
Nhiệm vụ này thay đổi ival, giá trị được tham chiếu bởi ri và không phải là chính tham chiếu. Sau khi gán, hai tham chiếu vẫn tham chiếu đến các đối tượng ban đầu của chúng và giá trị của các đối tượng đó cũng giống nhau.
Có một sự khác biệt về ngữ nghĩa có thể xuất hiện bí truyền nếu bạn không quen với việc học ngôn ngữ máy tính theo kiểu trừu tượng hoặc thậm chí học thuật.
Ở cấp độ cao nhất, ý tưởng của các tài liệu tham khảo là chúng là các "bí danh" trong suốt. Máy tính của bạn có thể sử dụng một địa chỉ để làm cho chúng hoạt động, nhưng bạn không cần phải lo lắng về điều đó: bạn phải nghĩ chúng là "chỉ một tên khác" cho một đối tượng hiện có và cú pháp phản ánh điều đó. Chúng chặt chẽ hơn con trỏ để trình biên dịch của bạn có thể cảnh báo bạn một cách đáng tin cậy hơn khi bạn sắp tạo một tham chiếu lơ lửng, hơn là khi bạn sắp tạo một con trỏ lơ lửng.
Ngoài ra, tất nhiên có một số khác biệt thực tế giữa con trỏ và tài liệu tham khảo. Cú pháp để sử dụng chúng rõ ràng là khác nhau và bạn không thể "ngồi lại" các tham chiếu, có các tham chiếu đến hư vô hoặc có các con trỏ tới các tham chiếu.
Tham chiếu là bí danh cho một biến khác trong khi con trỏ giữ địa chỉ bộ nhớ của biến. Các tham chiếu thường được sử dụng làm tham số hàm để đối tượng được truyền không phải là bản sao mà là chính đối tượng.
void fun(int &a, int &b); // A common usage of references.
int a = 0;
int &b = a; // b is an alias for a. Not so common to use.
Không quan trọng là nó chiếm bao nhiêu dung lượng vì bạn thực sự không thể thấy bất kỳ tác dụng phụ nào (không thực thi mã) của bất kỳ không gian nào nó sẽ chiếm.
Mặt khác, một sự khác biệt chính giữa các tham chiếu và các con trỏ là các tạm thời được gán cho các tham chiếu const sống cho đến khi tham chiếu const vượt quá phạm vi.
Ví dụ:
class scope_test
{
public:
~scope_test() { printf("scope_test done!\n"); }
};
...
{
const scope_test &test= scope_test();
printf("in scope\n");
}
sẽ in:
in scope
scope_test done!
Đây là cơ chế ngôn ngữ cho phép ScopeGuard hoạt động.
Điều này dựa trên hướng dẫn . Những gì được viết làm cho nó rõ ràng hơn:
>>> The address that locates a variable within memory is
what we call a reference to that variable. (5th paragraph at page 63)
>>> The variable that stores the reference to another
variable is what we call a pointer. (3rd paragraph at page 64)
Đơn giản chỉ cần nhớ rằng,
>>> reference stands for memory location
>>> pointer is a reference container (Maybe because we will use it for
several times, it is better to remember that reference.)
Hơn nữa, như chúng ta có thể đề cập đến hầu hết mọi hướng dẫn về con trỏ, con trỏ là một đối tượng được hỗ trợ bởi số học con trỏ, làm cho con trỏ giống với một mảng.
Nhìn vào tuyên bố sau đây,
int Tom(0);
int & alias_Tom = Tom;
alias_Tom
có thể được hiểu là một alias of a variable
(khác với typedef
, đó là alias of a type
) Tom
. Bạn cũng có thể quên thuật ngữ của tuyên bố đó là tạo tài liệu tham khảo Tom
.
nullptr
không? Bạn đã thực sự đọc bất kỳ phần nào khác của chủ đề này, hoặc ...?
Một tham chiếu không phải là một tên khác được đặt cho một số bộ nhớ. Đó là một con trỏ bất biến được tự động tham chiếu khi sử dụng. Về cơ bản, nó sôi sùng sục xuống:
int& j = i;
Nó trở thành nội bộ
int* const j = &i;
const
con trỏ. Tính linh hoạt đó không chứng minh rằng có sự khác biệt giữa tham chiếu và con trỏ.
Một tài liệu tham khảo trong C ++ là gì? Một số trường hợp cụ thể của loại không phải là loại đối tượng .
Một con trỏ trong C ++ là gì? Một số trường hợp cụ thể của loại đó là một loại đối tượng .
Từ định nghĩa ISO C ++ của loại đối tượng :
Một loại đối tượng là một (có thể cv- đủ tiêu chuẩn) không phải là một loại chức năng, không phải là một loại tham chiếu và không cv void.
Có thể cần biết, loại đối tượng là một loại cấp cao nhất của vũ trụ loại trong C ++. Tham khảo cũng là một thể loại cấp cao nhất. Nhưng con trỏ thì không.
Con trỏ và tài liệu tham khảo được đề cập cùng nhau trong bối cảnh của loại hợp chất . Điều này về cơ bản là do bản chất của cú pháp khai báo được kế thừa từ (và mở rộng) C, không có tham chiếu. (Bên cạnh đó, có nhiều hơn một loại khai báo tham chiếu kể từ C ++ 11, trong khi các con trỏ vẫn "unityped":&
+&&
vs *
..) Vì vậy, việc phác thảo một ngôn ngữ cụ thể bằng "phần mở rộng" với kiểu C tương tự trong ngữ cảnh này là hơi hợp lý . (Tôi vẫn sẽ lập luận rằng cú pháp của người khai báo làm lãng phí tính biểu cảm cú pháp rất nhiều , khiến cả người dùng và người thực hiện đều bực bội. Vì vậy, tất cả đều không đủ điều kiện để hợpthiết kế ngôn ngữ mới. Đây là một chủ đề hoàn toàn khác về thiết kế PL. , Tuy nhiên.)
Mặt khác, điều quan trọng là các con trỏ có thể đủ điều kiện là một loại cụ thể với các tham chiếu cùng nhau. Họ chỉ đơn giản là chia sẻ quá ít thuộc tính chung bên cạnh tính tương tự cú pháp, do đó không cần phải đặt chúng cùng nhau trong hầu hết các trường hợp.
Lưu ý các câu trên chỉ đề cập đến "con trỏ" và "tham chiếu" dưới dạng các loại. Có một số câu hỏi quan tâm về trường hợp của họ (như các biến). Cũng có quá nhiều quan niệm sai lầm.
Sự khác biệt của các danh mục cấp cao nhất có thể tiết lộ nhiều khác biệt cụ thể không liên quan trực tiếp đến con trỏ:
cv
vòng loại cấp cao nhất . Tài liệu tham khảo không thể.Một vài quy tắc đặc biệt hơn về tài liệu tham khảo:
&&
các tham số (như "tham chiếu chuyển tiếp") dựa trên thu gọn tham chiếu trong khi khấu trừ tham số mẫu cho phép "chuyển tiếp hoàn hảo" các tham số.std::initializer_list
một số quy tắc tương tự kéo dài tuổi thọ tham chiếu. Nó là một con giun khác.Tôi biết tài liệu tham khảo là cú pháp đường, vì vậy mã dễ đọc và viết hơn.
Về mặt kỹ thuật, điều này hoàn toàn sai. Tài liệu tham khảo không phải là cú pháp của bất kỳ tính năng nào khác trong C ++, bởi vì chúng không thể được thay thế chính xác bằng các tính năng khác mà không có bất kỳ sự khác biệt về ngữ nghĩa.
(Tương tự như vậy, lambda-biểu s là không cú pháp đường của bất kỳ tính năng khác trong C ++ vì nó không thể được mô phỏng một cách chính xác với tính chất "không xác định" như thứ tự khai báo các biến bị bắt , có thể là quan trọng bởi vì trình tự khởi tạo các biến như vậy có thể có ý nghĩa.)
C ++ chỉ có một vài loại đường cú pháp theo nghĩa chặt chẽ này. Một trường hợp là (được kế thừa từ C) toán tử tích hợp (không quá tải) []
, được xác định chính xác có cùng thuộc tính ngữ nghĩa của các dạng kết hợp cụ thể so với toán tử đơn *
và toán tử tích hợp+
.
Vì vậy, cả một con trỏ và một tham chiếu đều sử dụng cùng một lượng bộ nhớ.
Các tuyên bố trên chỉ đơn giản là sai. Để tránh những quan niệm sai lầm như vậy, thay vào đó, hãy xem các quy tắc ISO C ++:
Từ [intro.object] / 1 :
... Một vật thể chiếm một vùng lưu trữ trong thời kỳ xây dựng, trong suốt vòng đời của nó và trong thời kỳ hủy diệt. ...
Từ [dcl.ref] / 4 :
Không xác định được liệu tham chiếu có yêu cầu lưu trữ hay không.
Lưu ý đây là ngữ nghĩa thuộc tính .
Ngay cả những con trỏ không đủ điều kiện để được đặt cùng với các tham chiếu theo nghĩa của thiết kế ngôn ngữ, vẫn có một số đối số khiến nó trở nên gây tranh cãi khi đưa ra lựa chọn giữa chúng trong một số bối cảnh khác, ví dụ, khi đưa ra lựa chọn về các loại tham số.
Nhưng đây không phải là toàn bộ câu chuyện. Ý tôi là, có nhiều thứ hơn con trỏ so với tài liệu tham khảo mà bạn phải xem xét.
Nếu bạn không phải dính vào những lựa chọn quá cụ thể như vậy, trong hầu hết các trường hợp, câu trả lời rất ngắn gọn: bạn không cần phải sử dụng con trỏ, vì vậy bạn không cần . Con trỏ thường đủ tệ vì chúng ngụ ý quá nhiều điều bạn không mong đợi và chúng sẽ dựa vào quá nhiều giả định ngầm làm suy yếu khả năng duy trì và (thậm chí) tính di động của mã. Không cần thiết phải dựa vào con trỏ chắc chắn là một phong cách xấu và nên tránh theo nghĩa của C ++ hiện đại. Xem xét lại mục đích của bạn và cuối cùng bạn sẽ thấy rằng con trỏ là tính năng của các loại cuối cùng trong hầu hết các trường hợp.
&
tham chiếu cụ thể làm kiểu tham số thứ nhất. (Và thường thì nênconst
đủ tiêu chuẩn.)&&
tham chiếu cụ thể làm kiểu tham số thứ nhất. (Và thường không nên có vòng loại.)operator=
vì các hàm thành viên đặc biệt yêu cầu các loại tham chiếu tương tự như tham số thứ nhất của các hàm tạo sao chép / di chuyển.++
yêu cầu giả int
.unique_ptr
và shared_ptr
(hoặc thậm chí với các tay cầm homebrew nếu bạn yêu cầu chúng mờ đục ), thay vì con trỏ thô.std::optional
, thay vì con trỏ thô.observer_ptr
trong Thư viện cơ bản TS.Các ngoại lệ duy nhất không thể được xử lý trong ngôn ngữ hiện tại:
operator new
. (Tuy nhiên, cv - void*
vẫn khá khác biệt và an toàn hơn so với các con trỏ đối tượng thông thường vì nó loại trừ các trường hợp con trỏ bất ngờ trừ khi bạn đang dựa vào một số phần mở rộng không tuân thủ void*
như của GNU.)Vì vậy, trong thực tế, câu trả lời rất rõ ràng: khi nghi ngờ, hãy tránh con trỏ . Bạn phải sử dụng con trỏ chỉ khi có những lý do rất rõ ràng rằng không có gì khác phù hợp hơn. Ngoại trừ một vài trường hợp đặc biệt được đề cập ở trên, các lựa chọn như vậy hầu như không hoàn toàn là C ++ - cụ thể (nhưng có khả năng là ngôn ngữ cụ thể - cụ thể). Những trường hợp như vậy có thể là:
Nếu bạn đến để xem câu hỏi thông qua một số kết quả tìm kiếm của Google (không cụ thể đối với C ++) , đây rất có thể là địa điểm sai.
Tài liệu tham khảo trong C ++ là khá "kỳ quặc", vì nó về cơ bản không phải là hạng nhất: họ sẽ được đối xử như các đối tượng hoặc các chức năng được gọi để họ không có cơ hội để hỗ trợ một số hoạt động hạng nhất như là toán hạng trái của các toán tử truy cập thành viên độc lập với loại đối tượng được đề cập. Các ngôn ngữ khác có thể có hoặc không có các hạn chế tương tự đối với tài liệu tham khảo của họ.
Tài liệu tham khảo trong C ++ có thể sẽ không bảo tồn ý nghĩa trên các ngôn ngữ khác nhau. Ví dụ, các tài liệu tham khảo nói chung không bao hàm các thuộc tính không hoàn chỉnh trên các giá trị như trong C ++, do đó, các giả định như vậy có thể không hoạt động trong một số ngôn ngữ khác (và bạn sẽ tìm thấy các bản mẫu tương đối dễ dàng, ví dụ Java, C #, ...).
Vẫn có thể có một số thuộc tính chung trong số các tài liệu tham khảo trong các ngôn ngữ lập trình khác nhau nói chung, nhưng hãy để nó cho một số câu hỏi khác trong SO.
(Một lưu ý phụ: câu hỏi có thể sớm hơn đáng kể so với bất kỳ ngôn ngữ "giống như C" nào có liên quan, như ALGOL 68 so với PL / I. )
Có thể tham chiếu đến một con trỏ trong C ++, nhưng điều ngược lại là không thể có nghĩa là một con trỏ tới một tham chiếu là không thể. Tham chiếu đến một con trỏ cung cấp một cú pháp sạch hơn để sửa đổi con trỏ. Nhìn vào ví dụ này:
#include<iostream>
using namespace std;
void swap(char * &str1, char * &str2)
{
char *temp = str1;
str1 = str2;
str2 = temp;
}
int main()
{
char *str1 = "Hi";
char *str2 = "Hello";
swap(str1, str2);
cout<<"str1 is "<<str1<<endl;
cout<<"str2 is "<<str2<<endl;
return 0;
}
Và xem xét phiên bản C của chương trình trên. Trong C, bạn phải sử dụng con trỏ tới con trỏ (nhiều cảm ứng) và điều này dẫn đến sự nhầm lẫn và chương trình có thể trông phức tạp.
#include<stdio.h>
/* Swaps strings by swapping pointers */
void swap1(char **str1_ptr, char **str2_ptr)
{
char *temp = *str1_ptr;
*str1_ptr = *str2_ptr;
*str2_ptr = temp;
}
int main()
{
char *str1 = "Hi";
char *str2 = "Hello";
swap1(&str1, &str2);
printf("str1 is %s, str2 is %s", str1, str2);
return 0;
}
Truy cập vào phần sau để biết thêm thông tin về tham chiếu đến con trỏ:
Như tôi đã nói, một con trỏ đến một tham chiếu là không thể. Hãy thử chương trình sau:
#include <iostream>
using namespace std;
int main()
{
int x = 10;
int *ptr = &x;
int &*ptr1 = ptr;
}
Tôi sử dụng tài liệu tham khảo trừ khi tôi cần một trong hai thứ sau:
Con trỏ Null có thể được sử dụng như một giá trị sentinel, thường là một cách rẻ tiền để tránh quá tải chức năng hoặc sử dụng bool.
Bạn có thể làm số học trên một con trỏ. Ví dụ,p += offset;
&r + offset
nơi r
được tuyên bố là tham chiếu
Có một sự khác biệt cơ bản giữa các con trỏ và các tham chiếu mà tôi không thấy ai đã đề cập: các tham chiếu cho phép ngữ nghĩa thông qua tham chiếu trong các đối số hàm. Con trỏ, mặc dù ban đầu nó không hiển thị: chúng chỉ cung cấp ngữ nghĩa truyền qua giá trị. Điều này đã được mô tả rất độc đáo trong bài viết này .
Trân trọng, & rzej
Có nguy cơ thêm vào sự nhầm lẫn, tôi muốn đưa vào một số đầu vào, tôi chắc chắn rằng nó chủ yếu phụ thuộc vào cách trình biên dịch thực hiện các tham chiếu, nhưng trong trường hợp gcc, ý tưởng rằng một tham chiếu chỉ có thể trỏ đến một biến trên ngăn xếp không thực sự chính xác, lấy ví dụ này:
#include <iostream>
int main(int argc, char** argv) {
// Create a string on the heap
std::string *str_ptr = new std::string("THIS IS A STRING");
// Dereference the string on the heap, and assign it to the reference
std::string &str_ref = *str_ptr;
// Not even a compiler warning! At least with gcc
// Now lets try to print it's value!
std::cout << str_ref << std::endl;
// It works! Now lets print and compare actual memory addresses
std::cout << str_ptr << " : " << &str_ref << std::endl;
// Exactly the same, now remember to free the memory on the heap
delete str_ptr;
}
Mà kết quả này:
THIS IS A STRING
0xbb2070 : 0xbb2070
Nếu bạn nhận thấy ngay cả các địa chỉ bộ nhớ hoàn toàn giống nhau, có nghĩa là tham chiếu được trỏ thành công vào một biến trên heap! Bây giờ nếu bạn thực sự muốn trở nên kỳ dị, điều này cũng hoạt động:
int main(int argc, char** argv) {
// In the actual new declaration let immediately de-reference and assign it to the reference
std::string &str_ref = *(new std::string("THIS IS A STRING"));
// Once again, it works! (at least in gcc)
std::cout << str_ref;
// Once again it prints fine, however we have no pointer to the heap allocation, right? So how do we free the space we just ignorantly created?
delete &str_ref;
/*And, it works, because we are taking the memory address that the reference is
storing, and deleting it, which is all a pointer is doing, just we have to specify
the address with '&' whereas a pointer does that implicitly, this is sort of like
calling delete &(*str_ptr); (which also compiles and runs fine).*/
}
Mà kết quả này:
THIS IS A STRING
Do đó, một tham chiếu IS là một con trỏ bên dưới mui xe, cả hai chỉ lưu trữ một địa chỉ bộ nhớ, trong đó địa chỉ được trỏ đến là không liên quan, bạn nghĩ điều gì sẽ xảy ra nếu tôi gọi std :: cout << str numf; SAU khi gọi xóa & str numf? Chà, rõ ràng là nó biên dịch tốt, nhưng gây ra lỗi phân đoạn khi chạy vì nó không còn trỏ đến một biến hợp lệ, về cơ bản chúng ta có một tham chiếu bị hỏng vẫn còn tồn tại (cho đến khi nó nằm ngoài phạm vi), nhưng vô dụng.
Nói cách khác, một tham chiếu không là gì ngoài một con trỏ có cơ chế con trỏ được trừu tượng hóa, làm cho nó an toàn hơn và dễ sử dụng hơn (không có toán học con trỏ ngẫu nhiên, không trộn lẫn '.' Và '->', v.v.), giả sử bạn đừng thử bất kỳ điều gì vô nghĩa như ví dụ của tôi ở trên;)
Bây giờ bất kể trình biên dịch xử lý các tham chiếu như thế nào, nó sẽ luôn có một loại con trỏ bên dưới mui xe, bởi vì một tham chiếu phải tham chiếu đến một biến cụ thể tại một địa chỉ bộ nhớ cụ thể để nó hoạt động như mong đợi, do đó không có xung quanh điều này (do đó thuật ngữ 'tham khảo').
Quy tắc chính duy nhất quan trọng cần nhớ với các tham chiếu là chúng phải được xác định tại thời điểm khai báo (ngoại trừ tham chiếu trong tiêu đề, trong trường hợp đó phải được xác định trong hàm tạo, sau khi đối tượng chứa trong đó là xây dựng đã quá muộn để định nghĩa nó).
Hãy nhớ rằng, các ví dụ của tôi ở trên chỉ là, các ví dụ minh họa một tham chiếu là gì, bạn sẽ không bao giờ muốn sử dụng một tài liệu tham khảo theo những cách đó! Để sử dụng đúng cách một tài liệu tham khảo, có rất nhiều câu trả lời ở đây đã đánh vào đầu
Một sự khác biệt nữa là bạn có thể có các con trỏ tới một kiểu void (và nó có nghĩa là con trỏ tới bất cứ thứ gì) nhưng các tham chiếu đến void bị cấm.
int a;
void * p = &a; // ok
void & p = a; // forbidden
Tôi không thể nói rằng tôi thực sự hài lòng với sự khác biệt đặc biệt này. Tôi rất thích nó sẽ được cho phép với tham chiếu ý nghĩa cho bất cứ điều gì có địa chỉ và hành vi tương tự cho các tài liệu tham khảo. Nó sẽ cho phép định nghĩa một số hàm thư viện C tương đương như memcpy bằng cách sử dụng các tham chiếu.
Ngoài ra, một tham chiếu là tham số cho hàm được nội tuyến có thể được xử lý khác với con trỏ.
void increment(int *ptrint) { (*ptrint)++; }
void increment(int &refint) { refint++; }
void incptrtest()
{
int testptr=0;
increment(&testptr);
}
void increftest()
{
int testref=0;
increment(testref);
}
Nhiều trình biên dịch khi nội tuyến phiên bản con trỏ sẽ thực sự buộc ghi vào bộ nhớ (chúng tôi đang lấy địa chỉ một cách rõ ràng). Tuy nhiên, họ sẽ để lại tham chiếu trong một thanh ghi tối ưu hơn.
Tất nhiên, đối với các hàm không được trỏ vào con trỏ và tham chiếu sẽ tạo cùng một mã và sẽ tốt hơn là truyền nội tại theo giá trị so với tham chiếu nếu chúng không được sửa đổi và trả về bởi hàm.
Một cách sử dụng tham chiếu thú vị khác là cung cấp một đối số mặc định của loại do người dùng xác định:
class UDT
{
public:
UDT() : val_d(33) {};
UDT(int val) : val_d(val) {};
virtual ~UDT() {};
private:
int val_d;
};
class UDT_Derived : public UDT
{
public:
UDT_Derived() : UDT() {};
virtual ~UDT_Derived() {};
};
class Behavior
{
public:
Behavior(
const UDT &udt = UDT()
) {};
};
int main()
{
Behavior b; // take default
UDT u(88);
Behavior c(u);
UDT_Derived ud;
Behavior d(ud);
return 1;
}
Hương vị mặc định sử dụng tham chiếu 'bind const cho khía cạnh tạm thời' của các tham chiếu.
Chương trình này có thể giúp hiểu được câu trả lời của câu hỏi. Đây là một chương trình đơn giản của một tham chiếu "j" và một con trỏ "ptr" trỏ đến biến "x".
#include<iostream>
using namespace std;
int main()
{
int *ptr=0, x=9; // pointer and variable declaration
ptr=&x; // pointer to variable "x"
int & j=x; // reference declaration; reference to variable "x"
cout << "x=" << x << endl;
cout << "&x=" << &x << endl;
cout << "j=" << j << endl;
cout << "&j=" << &j << endl;
cout << "*ptr=" << *ptr << endl;
cout << "ptr=" << ptr << endl;
cout << "&ptr=" << &ptr << endl;
getch();
}
Chạy chương trình và xem đầu ra và bạn sẽ hiểu.
Ngoài ra, hãy dành 10 phút và xem video này: https://www.youtube.com/watch?v=rlJrrGV0iOg
Tôi cảm thấy như có một điểm khác chưa được đề cập ở đây.
Không giống như các con trỏ, các tham chiếu tương đương về mặt cú pháp với đối tượng mà chúng tham chiếu, tức là bất kỳ thao tác nào có thể được áp dụng cho một đối tượng đều hoạt động để tham chiếu và với cú pháp chính xác (tất nhiên ngoại lệ là khởi tạo).
Mặc dù điều này có vẻ bề ngoài, tôi tin rằng tài sản này rất quan trọng đối với một số tính năng của C ++, ví dụ:
Mẫu . Do các tham số mẫu được gõ vịt, nên các thuộc tính cú pháp là một vấn đề quan trọng, do đó, thường thì cùng một mẫu có thể được sử dụng với cả T
và T&
.
(hoặc std::reference_wrapper<T>
vẫn dựa vào một diễn viên ngầm định T&
)
Các mẫu bao gồm cả hai T&
và T&&
thậm chí còn phổ biến hơn.
Giá trị . Xem xét câu lệnh str[0] = 'X';
Không có tham chiếu, nó sẽ chỉ hoạt động cho chuỗi c ( char* str
). Trả về ký tự theo tham chiếu cho phép các lớp do người dùng định nghĩa có cùng ký hiệu.
Sao chép các nhà xây dựng . Về mặt cú pháp, việc truyền các đối tượng để sao chép các hàm tạo và không trỏ đến các đối tượng là hợp lý. Nhưng không có cách nào để một hàm tạo sao chép lấy một đối tượng theo giá trị - nó sẽ dẫn đến một cuộc gọi đệ quy đến cùng một hàm tạo sao chép. Điều này để lại tài liệu tham khảo là lựa chọn duy nhất ở đây.
Vận hành quá tải . Với các tài liệu tham khảo, có thể giới thiệu sự gián tiếp cho một cuộc gọi toán tử - giả sử, operator+(const T& a, const T& b)
trong khi vẫn giữ nguyên ký hiệu infix. Điều này cũng hoạt động cho các chức năng quá tải thường xuyên.
Những điểm này trao quyền cho một phần đáng kể của C ++ và thư viện chuẩn, vì vậy đây là một tài sản chính của tài liệu tham khảo.
Có một sự khác biệt phi kỹ thuật rất quan trọng giữa con trỏ và tham chiếu: Một đối số được truyền cho hàm bằng con trỏ hiển thị rõ hơn nhiều so với đối số được truyền cho hàm bởi tham chiếu không phải là const. Ví dụ:
void fn1(std::string s);
void fn2(const std::string& s);
void fn3(std::string& s);
void fn4(std::string* s);
void bar() {
std::string x;
fn1(x); // Cannot modify x
fn2(x); // Cannot modify x (without const_cast)
fn3(x); // CAN modify x!
fn4(&x); // Can modify x (but is obvious about it)
}
Quay lại C, một cuộc gọi trông giống như fn(x)
chỉ có thể được chuyển qua giá trị, vì vậy nó chắc chắn không thể sửa đổi x
; để sửa đổi một đối số bạn sẽ cần phải vượt qua một con trỏ fn(&x)
. Vì vậy, nếu một đối số không có trước một &
bạn biết thì nó sẽ không được sửa đổi. (Điều ngược lại, &
có nghĩa là đã sửa đổi, là không đúng vì đôi khi bạn sẽ phải chuyển các cấu trúc chỉ đọc lớn bằng const
con trỏ.)
Một số ý kiến cho rằng đây là một tính năng hữu ích khi đọc mã, các tham số con trỏ phải luôn được sử dụng cho các tham số có thể sửa đổi thay vì không const
tham chiếu, ngay cả khi hàm không bao giờ mong đợi a nullptr
. Đó là, những người cho rằng chữ ký chức năng như fn3()
trên không nên được cho phép. Hướng dẫn về phong cách C ++ của Google là một ví dụ về điều này.
Có thể một số ẩn dụ sẽ giúp; Trong ngữ cảnh của không gian màn hình máy tính để bàn của bạn -
Một con trỏ có thể được khởi tạo thành 0 và tham chiếu thì không. Trong thực tế, một tham chiếu cũng phải tham chiếu đến một đối tượng, nhưng một con trỏ có thể là con trỏ null:
int* p = 0;
Nhưng chúng ta không thể có int& p = 0;
và cũng có int& p=5 ;
.
Trong thực tế để làm điều đó đúng, chúng ta phải khai báo và định nghĩa một đối tượng trước tiên sau đó chúng ta có thể tạo một tham chiếu đến đối tượng đó, vì vậy việc thực hiện đúng mã trước đó sẽ là:
Int x = 0;
Int y = 5;
Int& p = x;
Int& p1 = y;
Một điểm quan trọng khác là chúng ta có thể thực hiện khai báo con trỏ mà không cần khởi tạo tuy nhiên không có điều gì có thể được thực hiện trong trường hợp tham chiếu phải tạo tham chiếu luôn cho biến hoặc đối tượng. Tuy nhiên, việc sử dụng một con trỏ như vậy có rủi ro vì vậy nhìn chung chúng tôi kiểm tra xem con trỏ có thực sự đang trỏ đến một cái gì đó hay không. Trong trường hợp tham chiếu, không cần kiểm tra như vậy, bởi vì chúng tôi đã biết rằng việc tham chiếu đến một đối tượng trong khi khai báo là bắt buộc.
Một điểm khác biệt nữa là con trỏ có thể trỏ đến một đối tượng khác tuy nhiên tham chiếu luôn được tham chiếu đến cùng một đối tượng, hãy lấy ví dụ này:
Int a = 6, b = 5;
Int& rf = a;
Cout << rf << endl; // The result we will get is 6, because rf is referencing to the value of a.
rf = b;
cout << a << endl; // The result will be 5 because the value of b now will be stored into the address of a so the former value of a will be erased
Một điểm khác: Khi chúng ta có một mẫu như mẫu STL, mẫu lớp đó sẽ luôn trả về một tham chiếu, không phải là một con trỏ, để dễ đọc hoặc gán giá trị mới bằng toán tử []:
Std ::vector<int>v(10); // Initialize a vector with 10 elements
V[5] = 5; // Writing the value 5 into the 6 element of our vector, so if the returned type of operator [] was a pointer and not a reference we should write this *v[5]=5, by making a reference we overwrite the element by using the assignment "="
const int& i = 0
.
Sự khác biệt là biến con trỏ không hằng (không bị nhầm lẫn với con trỏ thành hằng số) có thể bị thay đổi tại một thời điểm trong khi thực hiện chương trình, yêu cầu các toán tử con trỏ được sử dụng (&, *), trong khi các tham chiếu có thể được đặt khi khởi tạo chỉ (đó là lý do tại sao bạn chỉ có thể đặt chúng trong danh sách trình khởi tạo của hàm tạo, chứ không phải bằng cách nào khác) và sử dụng ngữ nghĩa truy cập giá trị thông thường. Về cơ bản các tài liệu tham khảo đã được giới thiệu để cho phép hỗ trợ cho các nhà khai thác quá tải như tôi đã đọc trong một số cuốn sách rất cũ. Như ai đó đã nêu trong chủ đề này - con trỏ có thể được đặt thành 0 hoặc bất kỳ giá trị nào bạn muốn. 0 (NULL, nullptr) có nghĩa là con trỏ được khởi tạo không có gì. Đó là một lỗi để hủy bỏ con trỏ null. Nhưng trên thực tế, con trỏ có thể chứa một giá trị không trỏ đến một vị trí bộ nhớ chính xác. Đến lượt mình, các tham chiếu cố gắng không cho phép người dùng khởi tạo một tham chiếu đến một cái gì đó không thể được tham chiếu do thực tế là bạn luôn cung cấp giá trị của loại chính xác cho nó. Mặc dù có rất nhiều cách để biến tham chiếu được khởi tạo thành một vị trí bộ nhớ sai - tốt hơn là bạn không nên đào sâu điều này vào chi tiết. Ở cấp độ máy, cả con trỏ và tham chiếu đều hoạt động đồng đều - thông qua con trỏ. Hãy nói trong tài liệu tham khảo thiết yếu là cú pháp đường. tham chiếu rvalue khác với điều này - chúng là các đối tượng stack / heap tự nhiên. Mặc dù có rất nhiều cách để biến tham chiếu được khởi tạo thành một vị trí bộ nhớ sai - tốt hơn là bạn không nên đào sâu điều này vào chi tiết. Ở cấp độ máy, cả con trỏ và tham chiếu đều hoạt động đồng đều - thông qua con trỏ. Hãy nói trong tài liệu tham khảo thiết yếu là cú pháp đường. tham chiếu rvalue khác với điều này - chúng là các đối tượng stack / heap tự nhiên. Mặc dù có rất nhiều cách để biến tham chiếu được khởi tạo thành một vị trí bộ nhớ sai - tốt hơn là bạn không nên đào sâu điều này vào chi tiết. Ở cấp độ máy, cả con trỏ và tham chiếu đều hoạt động đồng đều - thông qua con trỏ. Hãy nói trong tài liệu tham khảo thiết yếu là cú pháp đường. tham chiếu rvalue khác với điều này - chúng là các đối tượng stack / heap tự nhiên.
nói một cách đơn giản, chúng ta có thể nói một tham chiếu là một tên thay thế cho một biến trong khi đó, một con trỏ là một biến chứa địa chỉ của một biến khác. ví dụ
int a = 20;
int &r = a;
r = 40; /* now the value of a is changed to 40 */
int b =20;
int *ptr;
ptr = &b; /*assigns address of b to ptr not the value */