C++学习之路:指针基础

目录

指针一般在C/C++语言学习的后期接触,这样就导致指针给新手一种高深莫测、难以掌握的刻板印象。但实际上指针的使用很简单,并且还能够极大的提高程序的灵活性,帮助我们轻松实现复杂的功能。

指针介绍与基本用法

指针是什么?和上一章将的变量一样,指针也是一种变量罢了。与普通变量不同的是,指针变量存储的内容是地址数据,像什么0xffee之类的十六进制数据,并且它也有自己的地址,可以通过取地址符号&进行取址。下面这个代码定义了一个空指针a并且输出a的地址:

cpp 复制代码
#include<iostream>
#include <cstring>
using namespace std;

int main(){
char* a = NULL; 
cout << &a <<endl; //输出0xe3c49ff878
return 0;
}

指针也是一个变量,就像int类型里面存储整数,float存储小数一样,指针也有自己的使命:存储其他变量的地址。因此,不能给指针赋数字或者字符,指针内只能装载其它变量的地址数据。

指针的语法为:type * ptr_name例如int * a;float *b等啊,前面的type表明了这个指针存储的地址值对应的变量的类型。int类型就只能存储int类型变量的地址,float类型就只能存储float类型变量的地址。

cpp 复制代码
#include<iostream>
#include <cstring>
using namespace std;

int main(){
    int a = 1;
    float b = 0.01;
    int * i_Ptr = &a;
    float * f_Ptr = &b;
    char* c_Ptr = &a ; //报错:"int *" 类型的值不能用于初始化 "char *" 类型的实体C/C++(144)
return 0;
}

指针使用的精髓与最强大的地方在于取值符*,通过*实现了根据地址找到指针指向的变量。这个功能真的很振奋人心,是int,float等其他变量类型所没有的功能。看下面这个例子,*ptr = variable

cpp 复制代码
#include<iostream>
#include <cstring>
using namespace std;

int main(){
    int a = 1;
    float b = 0.01;
    int * i_Ptr = &a;
    float * f_Ptr = &b;
    //通过*,找到了对应地址的变量a,b
    cout << *i_Ptr <<endl;  //输出:1
    cout << *f_Ptr <<endl;  //输出 0.01
    //再通过地址来修改对应变量的值,此时*i_Ptr = a,*f_Ptr = b
    *i_Ptr = 2; 
    *f_Ptr = 0.02;
    cout <<a <<endl; //输出2
    cout <<b<<endl; //输出0.02
return 0;
}

为了进一步说明取值符*的作用,绘图一张如下:揭示了指针的指向变量的功能

双重指针

上面这张图最后出现了一个指向指针的指针,我们也称这种指针叫双重指针(另外还有三重、四重等,学会了双重指针其余的都是一样的)。双指针即指针内存储的值(指向的变量的地址)是另一个指针的地址。例如上图的d,有d=&p,则*d = p。

双指针的作用在于,通过双指针(d)可以在函数中调用以及修改指针(p)指向的变量(通过*d = p这个方法可以修改指针p的值从而改变指向对象,以及**d = *p =a,从而修改指针p指向的变量a的值)

下面这个是通过**d修改双重指针指向的指针(b)所指向的对象(a)的值,把a的值改成了10

cpp 复制代码
 #include<iostream>
using namespace std;
void func1(float** d){
    **d = 10; //通过**d改变指针指向对象的值
}
int main(){
    float a = 1;
    float *b = &a;
    func1(&b); 
    cout << a<<endl; //输出a=10,即通过**d = a实现修改指向指针的值
    return 0;
}

下面这个是通过*d修改双重指针指向的指针(b)所指向的对象(a),把指向的对象从a转成了b

cpp 复制代码
#include<iostream>
using namespace std;
float c = 2;
void func1(float** d){
    *d = &c; //通过*d改变指针指向的对象
}
int main(){
    float a = 1;
    float *b = &a; //此时 b指向a
    func1(&b);  //函数更改了b指向的对象
    cout << *b <<endl; //输出2,即通过*d = b,实现修改指向指针所指向的对象;
    return 0;
}

函数指针

函数指针顾名思义就是指向一个函数的指针,通过函数指针可以调用函数。事实上,函数名本身就是一个函数指针,其存储着函数的入口地址,写一段小试验打印函数指针地址内容如下:

cpp 复制代码
#include<iostream>
using namespace std;

void func1(int a,int b)
{

    cout << (a+b) <<endl;
}

int main()
{
    cout << (void*)func1 <<endl; //输出0x7ff701251450
    return 0;
}

函数指针主要由函数返回值类型以及函数参数类型来定义。语法为返回值类型(*指针名)(参数1类型,参数2类型...)

例如指向前面代码片段的函数指针可以被定义为void(*p)(int,int)。函数指针存储函数入口地址,最大的功能就是将函数作为参数传入另一个函数,示例如下:

cpp 复制代码
#include<iostream>
using namespace std;

void func1(int a,int b)
{

    cout << (a+b) <<endl;
}

void func2(void (*f)(int,int),int c)
{
    f(1,3);
    cout << c <<endl;
}

int main()
{
    void(*p)(int,int);
    p = func1;
    func2(p,3);  //输出 4 3
    //直接func2(func1,3)也行
    return 0;
}

空指针与野指针

空指针是指不指向任何变量的指针,C++中常用NULL宏来对其进行定义。空指针的作用就是初始化指针,例如在进行内存分配前,我们可能需要先把指针给定义出来,此时空指针不指向任何对象,被初始化为0。来看这样一个例子:

cpp 复制代码
#include<iostream>
using namespace std;
int main(){
    int* a; //野指针
    cout<< a <<endl; //输出0x7f6de082ba50
    cout<< *a <<endl; //输出 2
    
    int* b = NULL; //空指针
    cout<<b<<endl; //输出0
    cout<<*b <<endl; //输出Segmentation fault (core dumped)

    return 0;
}

我们把未初始化,或者指向无效对象的指针叫做野指针,例如上面例子中的指针a。a指向了一个完全随机的内存位置,这样就有可能造成越界访问的问题,进而导致程序崩溃。反观指针b被定义为空指针,其值为0,并且如果此时访问空指针,操作系统会报错,对内存数据进行保护,减小了程序崩溃的风险。

函数参数的指针传递

指针的一个重要的应用就是函数参数的指针传递,即传入函数的参数不是变量本身而是变量的地址。如果传入的是变量那么这种传参方式叫值传递,编译器会拷贝一份参数值到函数内部。这样一来是无法修改对应的变量,因为只传入了值,没有对应地址;二来拷贝需要占用内存空间。

通过指针传递的方法,可以在不使用返回值的前提下(使用返回值的方法会产生数据拷贝增大函数内存开销),通过取值操作符*来直接改变变量的值。并且由于传入的是变量地址,并不会函数本身不会占用太多的内存空间。二者对比的测试函数如下:

cpp 复制代码
#include<iostream>
using namespace std;
float c = 2;

//值传递
void func1(float d){
    d = 10; //实际上只是函数内部创建了一个局部变量float d;
}
//指针传递
void func2(float* d){
    *d = 10; //通过*d改变指针指向的对象
}

int main(){
    float a = 1;
    float *b = &a; //此时 b指向a
    func1(a);
    cout << a <<endl; //输出 a = 1,并没有改变a的值。
    func2(&a);  //函数可以直接改变a的值
    cout << a <<endl; //输出10,即修改指向指针所指向的对象的值;
    return 0;
}

双重指针的作用也是类似,可以实现在函数中改变传入的指针变量,例如改变指向对象,改变内存分配等。上一节双重指针的两个实例可以仔细研读一下。这里给一个通过双重指针实现自定义内存分配的例子:

cpp 复制代码
#include<iostream>
using namespace std;
float c = 2;

//函数功能:为指针分配内存空间
void func1(float **d){
    *d = new float[2]; //*d相当于传入的指针a
}

int main(){
    float *a = NULL;//定义了一个空指针
    cout <<a[1]<<endl; //越界访问,程序出错
    func1(&a); 
    cout << a[1] <<endl; //输出一个随机值
    return 0;
}

最后

最后想说一下,关于指针上面的内容只是基础,后续还有关于指针数组和数组指针的一些内容,我将会放在数组那一章再介绍。

相关推荐
云上艺旅1 小时前
K8S学习之基础七十四:部署在线书店bookinfo
学习·云原生·容器·kubernetes
懒羊羊大王&1 小时前
模版进阶(沉淀中)
c++
你觉得2051 小时前
哈尔滨工业大学DeepSeek公开课:探索大模型原理、技术与应用从GPT到DeepSeek|附视频与讲义下载方法
大数据·人工智能·python·gpt·学习·机器学习·aigc
owde2 小时前
顺序容器 -list双向链表
数据结构·c++·链表·list
GalaxyPokemon2 小时前
Muduo网络库实现 [九] - EventLoopThread模块
linux·服务器·c++
W_chuanqi2 小时前
安装 Microsoft Visual C++ Build Tools
开发语言·c++·microsoft
A旧城以西3 小时前
数据结构(JAVA)单向,双向链表
java·开发语言·数据结构·学习·链表·intellij-idea·idea
无所谓จุ๊บ3 小时前
VTK知识学习(50)- 交互与Widget(一)
学习·vtk
FAREWELL000753 小时前
C#核心学习(七)面向对象--封装(6)C#中的拓展方法与运算符重载: 让代码更“聪明”的魔法
学习·c#·面向对象·运算符重载·oop·拓展方法
tadus_zeng3 小时前
Windows C++ 排查死锁
c++·windows