06_C++指针与引用
6.1 指针
6.1.1 指针的概念
- 指针是C++中的一种变量类型,用于存储另一个变量的内存地址。
- 指针变量通过解引用操作符(
*)可以访问或修改其所指向的变量的值。 - 指针的使用可以提高程序的效率,尤其是在处理大型数据结构时。
- 指针和普通变量的区别:
- 普通变量直接存储数据值,而指针变量存储的是另一个变量的地址。
- 指针变量需要使用地址操作符(
&)来获取变量的地址。 - 指针变量可以通过" * "操作符,操作指针变量指向的内存空间,这个过程称为解引用
6.1.2 指针的定义和使用
- 指针的定义语法格式:
数据类型*指针变量名;- 示例:
#include<iostream>usingnamespacestd;intmain(){intvar=42;// 定义一个整数变量int*ptr=&var;// 定义一个指针变量并初始化为var的地址cout<<"变量var的值: "<<var<<endl;// 输出变量var的值cout<<"指针ptr指向的值: "<<*ptr<<endl;// 通过指针访问var的值*ptr=100;// 通过指针修改var的值cout<<"修改后变量var的值: "<<var<<endl;// 输出修改后的var的值return0;}6.1.3 指针所占用的内存空间
- 指针变量本身也占用内存空间,通常在32位系统中占4字节,在64位系统中占8字节。
- 示例:
#include<iostream>usingnamespacestd;intmain(){intvar=42;int*ptr=&var;cout<<"变量var的地址: "<<&var<<endl;// 输出变量var的地址cout<<"指针ptr的值(var的地址): "<<ptr<<endl;// 输出指针ptr的值cout<<"指针ptr所占用的内存大小: "<<sizeof(ptr)<<" 字节"<<endl;// 输出指针大小return0;}6.1.4 空指针和野指针
空指针(nullptr)是指不指向任何有效内存地址的指针(通常为0),使用空指针可以避免意外访问无效内存。
野指针是指指向已经释放或未分配内存的指针,使用野指针会导致未定义行为。
空指针示例:
#include<iostream>usingnamespacestd;intmain(){int*ptr=nullptr;// 定义一个空指针if(ptr==nullptr){cout<<"指针ptr是空指针"<<endl;}// 注意:不要解引用空指针或野指针,否则会导致程序崩溃return0;}- 野指针示例:
#include<iostream>usingnamespacestd;intmain(){int*ptr=(int*)0x1234;// 未初始化的指针,可能是野指针// 注意:不要解引用野指针,否则会导致程序崩溃// cout << *ptr << endl; // 这行代码是危险的return0;}6.1.5 const修饰指针
const修饰指针有三种情况:
- 指向常量的指针(
const int* ptr):不能通过指针修改所指向的值,但可以改变指针本身的地址。 - 常量指针(
int* const ptr):指针本身的地址不能改变,但可以通过指针修改所指向的值。 - 指向常量的常量指针(
const int* const ptr):既不能改变指针本身的地址,也不能通过指针修改所指向的值。
- 指向常量的指针(
示例:
#include<iostream>usingnamespacestd;intmain(){intvar=42;constint*ptr1=&var;// 指向常量的指针// *ptr1 = 100; // 错误,不能通过ptr1修改var的值intnewVar=100;ptr1=&newVar;// 可以改变ptr1的地址int*constptr2=&var;// 常量指针*ptr2=100;// 可以通过ptr2修改var的值// ptr2 = &newVar; // 错误,不能改变ptr2的地址constint*constptr3=&var;// 指向常量的常量指针// *ptr3 = 200; // 错误,不能通过ptr3修改var的值// ptr3 = &newVar; // 错误,不能改变ptr3的地址cout<<"var的值: "<<var<<endl;// 输出:var的值: 100return0;}技巧:就近原则,即
const修饰符靠近它所修饰的对象。
6.1.6 指针和数组
指针和数组有密切的关系,数组名实际上是一个指向数组首元素的指针常量。
可以通过指针来遍历和操作数组元素。
示例:
#include<iostream>usingnamespacestd;intmain(){intarr[]={10,20,30,40,50};int*ptr=arr;// 数组名作为指向首元素的指针for(inti=0;i<5;++i){cout<<"arr["<<i<<"] = "<<*(ptr+i)<<endl;// 通过指针访问数组元素}return0;}6.1.7 指针运算
- 指针可以进行算术运算,如加法、减法、递增和递减。
- 指针运算会根据指针所指向的数据类型自动调整步长。
- 示例:
#include<iostream>usingnamespacestd;intmain(){intarr[]={10,20,30,40,50};int*ptr=arr;// 指向数组首元素cout<<"指针初始值: "<<ptr<<", 指向的值: "<<*ptr<<endl;ptr++;// 指针递增,指向下一个元素cout<<"指针递增后: "<<ptr<<", 指向的值: "<<*ptr<<endl;ptr+=2;// 指针加2,跳过一个元素cout<<"指针加2后: "<<ptr<<", 指向的值: "<<*ptr<<endl;ptr--;// 指针递减,指向上一个元素cout<<"指针递减后: "<<ptr<<", 指向的值: "<<*ptr<<endl;return0;}6.2 引用
6.2.1 引用的基本使用
- 引用是C++中的一种别名机制,用于为变量创建一个新的名字。
- 引用必须在定义时初始化,并且一旦绑定到一个变量,就不能再绑定到其他变量。
- 示例:
#include<iostream>usingnamespacestd;intmain(){intvar=42;int&ref=var;// 定义引用ref,绑定到varcout<<"变量var的值: "<<var<<endl;// 输出变量var的值cout<<"引用ref的值: "<<ref<<endl;// 输出引用ref的值ref=100;// 通过引用修改var的值cout<<"修改后变量var的值: "<<var<<endl;// 输出修改后的var的值return0;}6.2.2 常量引用
- 常量引用(
const引用)是指引用所绑定的变量不能通过该引用进行修改。 - 常量引用可以绑定到临时变量或不同类型的变量,常用于函数参数传递。
- 示例:
#include<iostream>usingnamespacestd;voidprintValue(constint&ref){cout<<"引用的值: "<<ref<<endl;// ref = 100; // 错误,不能通过常量引用修改值}intmain(){intvar=42;printValue(var);// 传递变量var的引用printValue(100);// 传递临时变量的引用return0;}6.2.3 引用的本质
- 引用在底层实现上通常被编译器处理为指针常量,但引用的语法更简洁,使用起来更安全。
- 引用不能为null,必须绑定到一个有效的变量。
- 示例:
//发现是引用,转换为 int* const ref = &a; void func(int& ref){ ref = 100; // ref是引用,转换为*ref = 100 } int main(){ int a = 10; //自动转换为 int* const ref = &a; 指针常量是指针指向不可改,也说明为什么引用不可更改 int& ref = a; ref = 20; //内部发现ref是引用,自动帮我们转换为: *ref = 20; cout << "a:" << a << endl; cout << "ref:" << ref << endl; func(a); return 0; }6.3 指针与引用的比较
6.3.1 相同点
- 都可以间接访问同一个变量或对象,支持通过“别名”来修改原始数据。
- 都可以作为函数参数,将实参传入函数体内并允许在函数中修改调用者的数据(所谓“按地址/引用传递”)。
- 在底层实现上,引用通常会被编译器转换为指针来处理,因此两者在生成的机器代码层面很多时候非常接近。
6.3.2 主要区别
是否必须初始化
- 指针:可以先定义再赋值,也可以为
nullptr。 - 引用:定义时必须立刻绑定到一个有效对象上,不能“悬空”。
- 指针:可以先定义再赋值,也可以为
是否允许指向/绑定为空
- 指针:可以指向
nullptr,用来表示“当前不指向任何对象”。 - 引用:语义上不允许为空(尽管通过极端手段可以制造“空引用”,但会导致未定义行为),一般认为引用一定代表一个有效对象。
- 指针:可以指向
是否允许改变指向/绑定的目标
- 指针:可以在生命周期内多次改变指向的地址。
- 引用:一旦绑定后就不能再“改绑”,整个生命周期内都代表同一个对象。
使用语法是否需要解引用
- 指针:访问所指向对象要使用解引用操作符
*或->。 - 引用:当作普通变量名使用即可,不需要额外的解引用语法。
- 指针:访问所指向对象要使用解引用操作符
内存占用与本质
- 指针:是一个独立的变量,占用一个指针大小的内存(32位一般为4字节,64位一般为8字节)。
- 引用:从语法层面看不占用独立内存,是被引用对象的“别名”;实际编译器实现时通常会用“指向该对象的指针常量”来完成。
使用场景建议
- 指针:适合需要“可为空”、需要动态改变指向、需要进行指针运算(如遍历数组、手动管理动态内存)的场合。
- 引用:适合用来做函数参数和返回值,更符合直观语义,代码更简洁安全。
6.3.3 简单示例对比
#include<iostream>usingnamespacestd;voidaddByPointer(int*p){// 使用指针if(p){*p+=10;// 需要解引用}}voidaddByReference(int&r){// 使用引用r+=10;// 直接当变量用}intmain(){inta=10;int*p=&a;// 指针可以为nullptr,也可以随时改指向addByPointer(p);cout<<"通过指针修改后的 a = "<<a<<endl;int&ref=a;// 引用必须初始化,并且终身绑定 aaddByReference(ref);cout<<"通过引用修改后的 a = "<<a<<endl;return0;}