06_C++指针与引用

📅 2026/7/8 1:26:55 👁️ 阅读次数 📝 编程学习
06_C++指针与引用

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修饰指针有三种情况:

    1. 指向常量的指针(const int* ptr):不能通过指针修改所指向的值,但可以改变指针本身的地址。
    2. 常量指针(int* const ptr):指针本身的地址不能改变,但可以通过指针修改所指向的值。
    3. 指向常量的常量指针(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 主要区别

  1. 是否必须初始化

    • 指针:可以先定义再赋值,也可以为nullptr
    • 引用:定义时必须立刻绑定到一个有效对象上,不能“悬空”。
  2. 是否允许指向/绑定为空

    • 指针:可以指向nullptr,用来表示“当前不指向任何对象”。
    • 引用:语义上不允许为空(尽管通过极端手段可以制造“空引用”,但会导致未定义行为),一般认为引用一定代表一个有效对象。
  3. 是否允许改变指向/绑定的目标

    • 指针:可以在生命周期内多次改变指向的地址。
    • 引用:一旦绑定后就不能再“改绑”,整个生命周期内都代表同一个对象。
  4. 使用语法是否需要解引用

    • 指针:访问所指向对象要使用解引用操作符*->
    • 引用:当作普通变量名使用即可,不需要额外的解引用语法。
  5. 内存占用与本质

    • 指针:是一个独立的变量,占用一个指针大小的内存(32位一般为4字节,64位一般为8字节)。
    • 引用:从语法层面看不占用独立内存,是被引用对象的“别名”;实际编译器实现时通常会用“指向该对象的指针常量”来完成。
  6. 使用场景建议

    • 指针:适合需要“可为空”、需要动态改变指向、需要进行指针运算(如遍历数组、手动管理动态内存)的场合。
    • 引用:适合用来做函数参数和返回值,更符合直观语义,代码更简洁安全。

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;}