Последнее обновление:2026-10-12
C++ указатели и ссылки¶
Об авторе:¶
Личный официальный аккаунт:

1.Ссылки и указатели¶
Общая картина:
| Ссылка | Указатель |
|---|---|
| Должна быть инициализирована | Может не инициализироваться |
| Не может быть пустой | Может быть пустым |
| Не может сменить цель | Может сменить цель |
Ссылка должна быть инициализирована, а указатель может не инициализироваться.
При определении ссылки мы обязаны задать ей начальное значение, а указатель — нет.
Ссылка не может быть пустой, а указатель может.
Поскольку ссылка не может быть пустой, при работе со ссылкой нам не нужно проверять её корректность, тогда как при работе с указателем сначала нужно проверить, не является ли он нулевым, иначе возможно аварийное завершение программы.
void test_p(int* p)
{
if(p != null_ptr) //对p所指对象赋值时需先判断p是否为空指针
*p = 3;
return;
}
void test_r(int& r)
{
r = 3; //由于引用不能为空,所以此处无需判断r的有效性就可以对r直接赋值
return;
}
Ссылка не может сменить цель
Указатель может в любой момент изменить своё направление, а ссылка может указывать только на объект, выбранный при инициализации, и не может это изменить.
int a = 1;
int b = 2;
int &r = a; //初始化引用r指向变量a
int *p = &a; //初始化指针p指向变量a
p = &b; //指针p指向了变量b
r = b; //引用r依然指向a,但a的值变成了b
2.Ссылки¶
Lvalue-ссылки¶
Обычные ссылки; как правило, выражают идентичность объекта.
Rvalue-ссылки¶
Rvalue-ссылка — это ссылка, которая обязана быть привязана к rvalue (временному объекту, объекту, который вот-вот будет уничтожен); как правило, она выражает значение объекта.
Rvalue-ссылки позволяют реализовать семантику перемещения (Move Semantics) и точную передачу (Perfect Forwarding); их главные цели двояки:
- Устранить ненужное копирование объектов при их взаимодействии, сэкономить вычислительные ресурсы и ресурсы хранения, повысить эффективность.
- Дать возможность более лаконично и ясно определять обобщённые функции.
Схлопывание ссылок¶
X& &,X& &&,X&& &схлопываются вX&X&& &&схлопывается вX&&
Ссылки в C++, уменьшая свободу программиста, повышают безопасность операций с памятью и красоту семантики. Например, обязательная инициализация ссылки позволяет при её использовании не проверять ссылку на пустоту, делает код более лаконичным и изящным и избавляет от повсеместного разгула указателей. Помимо этого сценария ссылки применяются ещё в двух случаях:
Параметры-ссылки
Обычно мы используем параметр const reference как входной параметр только для чтения; в этом случае можно избежать копирования параметра и одновременно получить такой же способ вызова, как при передаче по значению.
void test(const vector<int> &data)
{
//...
}
int main()
{
vector<int> data{1,2,3,4,5,6,7,8};
test(data);
}
Возвращаемые значения-ссылки
C++ предоставляет возможность перегрузки операторов; при перегрузке некоторых операторов использование возвращаемого значения-ссылки даёт такой же синтаксис вызова, как у исходного оператора, сохраняя согласованность его семантики. Один из примеров — оператор operator []: этот оператор, как правило, должен возвращать объект-ссылку, чтобы его можно было корректно изменять.
vector<int> v(10);
v[5] = 10; //[]操作符返回引用,然后vector对应元素才能被修改
//如果[]操作符不返回引用而是指针的话,赋值语句则需要这样写
*v[5] = 10; //这种书写方式,完全不符合我们对[]调用的认知,容易产生误解
3.Разница в производительности между указателями и ссылками¶
Есть ли разница в производительности между указателями и ссылками? Чтобы ответить на этот вопрос, нужно спуститься на уровень ассемблера. Сначала напишем функцию test1 с передачей параметра через указатель:
Ассемблерный код, соответствующий этому фрагменту, таков:
(gdb) disassemble
Dump of assembler code for function test1(int*):
0x0000000000400886 <+0>: push %rbp
0x0000000000400887 <+1>: mov %rsp,%rbp
0x000000000040088a <+4>: mov %rdi,-0x8(%rbp)
=> 0x000000000040088e <+8>: mov -0x8(%rbp),%rax
0x0000000000400892 <+12>: movl $0x3,(%rax)
0x0000000000400898 <+18>: nop
0x0000000000400899 <+19>: pop %rbp
0x000000000040089a <+20>: retq
End of assembler dump.
В приведённом коде строки 1 и 2 — операции сохранения контекста при вызове параметров; строка 3 — передача параметра: первый аргумент вызова функции обычно помещается в регистр rdi, и эта строка записывает значение регистра rdi (значение указателя p) в стек; строка 4 записывает значение p из стека в регистр rax; строка 5 записывает непосредственное значение 3 в память, на которую указывает значение регистра rax; здесь обратите внимание на скобки вокруг (%rax) — они вовсе не необязательны: (%rax) и %rax — совершенно разные вещи; (%rax) обозначает память по адресу, хранящемуся в регистре rax, что эквивалентно *p в коде C++, а %rax обозначает сам регистр rax, что эквивалентно значению p в коде C++; поэтому в ассемблере здесь используется (%rax), а не %rax.
Теперь напишем фрагмент кода C++ test2 с передачей параметра по ссылке:
Ассемблерный код, соответствующий этому фрагменту:
(gdb) disassemble
Dump of assembler code for function test2(int&):
0x000000000040089b <+0>: push %rbp
0x000000000040089c <+1>: mov %rsp,%rbp
0x000000000040089f <+4>: mov %rdi,-0x8(%rbp)
=> 0x00000000004008a3 <+8>: mov -0x8(%rbp),%rax
0x00000000004008a7 <+12>: movl $0x3,(%rax)
0x00000000004008ad <+18>: nop
0x00000000004008ae <+19>: pop %rbp
0x00000000004008af <+20>: retq
End of assembler dump.
Мы обнаруживаем, что ассемблерный код test2 полностью совпадает с ассемблерным кодом test1; это показывает, что компилятор C++ при компиляции программы превращает указатели и ссылки в совершенно одинаковый машинный код. Поэтому ссылка в C++ — лишь «синтаксический сахар» над операциями с указателями: на нижнем уровне компилятор C++ реализует обе операции совершенно одинаково.
3.Резюме¶
В C++ введена операция ссылки: наложив на использование ссылок больше ограничений, язык обеспечивает их безопасность и удобство, а также сохраняет элегантность кода. Используйте подходящую операцию в подходящей ситуации: применение ссылок в определённой мере позволяет избежать «повсеместного разгула указателей» и положительно сказывается на стабильности программы. Наконец, указатели и ссылки реализованы на нижнем уровне одинаково — не беспокойтесь о разнице в их производительности.
Часть материала взята из: http://irootlee.com/juicer_pointer_reference/#
Похожие темы¶
- C++ чисто виртуальные функции и абстрактные классы | C++那些事
- C++ утверждения assert | C++那些事
- C++ битовые поля | C++那些事
- Реализация полиморфизма C++ на языке C | C++那些事
- C++ константы const и его модификация | C++那些事
- C++ всё семейство const | C++那些事
- C++ оператор разыменования * | C++那些事
- C++ инициализация и присваивание | C++那些事
- C++ возврат нескольких значений | C++那些事
- C++ ключевое слово mutable | C++那些事