指针入门级指南

开篇语

似乎大家在OI中都并不是很喜欢使用指针,因为这个东西关于玄学,还会莫名RE
但是好多高手很喜欢用指针啊
如果您想学习指针的入门级使用方法,这篇文章可能比较适合你
笔者会把他在OI中使用指针的经验都写在这里(笔者水平 普及<<31????)

什么是指针

在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。[1] 在高级语言中,指针有效地取代了在低级语言,如汇编语言与机器码,直接使用通用暂存器的地方,但它可能只适用于合法地址之中。指针参考了存储器中某个地址,通过被称为反参考指针的动作,可以取出在那个地址中存储的值。作个比喻,假设将电脑存储器当成一本书,一张内容记录了某个页码加上行号的便利贴,可以被当成是一个指向特定页面的指针;根据便利粘贴面的页码与行号,翻到那个页面,把那个页面的那一行文字读出来,就相当于是对这个指针进行反参考的动作。[2]

我抄了一段百度百科,为了显示我的解释都是人话
说白了,指针就是一个指向变量地址的变量
那怎么用呢?

#include<cstdio>
using namespace std;
int main()
{
    int *p;
}

这就是定义了一个int 类型的指针
为什么地址还有类型呢?
C++规定一个类型的指针只能指向同一个类型的变量,注意,是变量,常数1,2什么的都不可以
怎么指向一个变量呢?

#include<cstdio>
using namespace std;
int main()
{
    int a;
    int *p=&a;
}

就是这样,其中&是取地址符,表示把a的地址传给了指针p
为了表现他是一个地址,我们可以尝试输出一下p

#include<cstdio>
using namespace std;
int main()
{
    int a=1;
    int *p=&a;
    printf("%d\n",p);
}

结果这里写图片描述
这就是a所在地址的编号
那么我们怎么调取p指向的变量呢?
我们只需要加上*即可

#include<cstdio>
using namespace std;
int main()
{
    int a=1;
    int *p=&a;
    printf("%d\n",*p);
}

结果这里写图片描述
如果这里你都还明白就比较妙了

空指针

通俗的理解,就是指向空地址的指针,它不指向任何的对象或者函数
在OI中,我们常常使用C++自带的宏NULL(任何类型都可以使用NULL)来 作为空指针常量
编译器自动将其间过程的细节屏蔽,我们不需要关注这个空指针究竟指在哪里,以及NULL是如何定义的
但比较重要的一点我们要知道
访问空指针是不合法的操作,系统在发现你访问空指针时,会自动强制终止程序
一切万恶的RE几乎都来自空指针

#include<cstdio>
using namespace std;
int main()
{
    int *p=NULL;
    printf("%d\n",*p);
    printf("test succeed\n");
}

希望读者自己写一下这个代码,亲自测试一下,你会发现不会输出test succeed语句,因为程序在访问空指针时终止了
要记住,在你操作指针前,要运用一些手段(根据空指针特征来判断)这是不是空指针,否则您的程序会死无葬身之地

野指针

野指针没什么好讲的
记住在调取指针前确保它已经被赋过值就好
否则您会莫名TLE,RE等等奇异操作

指针与结构体

下面介绍指针与结构体奇妙的关系
首先是结构体成员的取用
最显然的方法是加个*

#include<iostream>
using namespace std;
struct QAQ
{
    string name;
};
int main()
{
    QAQ *starria=new QAQ;//申请一个QAQ类型的地址,new函数的返回类型为QAQ *
    (*starria).name="AK KING";
    cout<<(*starria).name<<endl;
}

但是这样写有什么弊端呢?
这个傻瓜(*)写起来太麻烦,尤其一层套一层,如果没有自动补全真的太恶心
机制的C++开发者已经想到怎么办了
他们定义了一种新的运算符
“->”

#include<iostream>
using namespace std;
struct QAQ
{
    string name;
};
int main()
{
    QAQ *starria=new QAQ;//申请一个QAQ类型的地址,new函数的返回类型为QAQ *
    starria->name="OUR LIBERATOR";
    cout<<starria->name<<endl;
}

我们这样就可以避免该死的括号套括号了
而是用优美的->
真是很妙
下面介绍递归定义
C++允许在结构体里定义同一类型的指针(不是指针不行)
这将方便链表的构建
比如

#include<iostream>
using namespace std;
struct QAQ
{
    string name;
    QAQ *son;
};
int main()
{
    QAQ *starria=new QAQ;//申请一个QAQ类型的地址,new函数的返回类型为QAQ *
    starria->name="OUR LIBERATOR";
    cout<<starria->name<<endl;
}

我们就可以快乐的构建链表啦

内存池

我们使用new函数非常方便,但是也令人头大
因为new函数的时间代价有点大,不是那么令人满意
我们用内存池来代替new函数
什么是内存池?
我们先定义我们需要的变量,到时候把他们的地址直接取用即可
比如经典的线段树代码

struct node
{
  int val,l,r;
  node *ls,*rs;
  inline void push_up()
  {
    if(l==r)return  ;
    val=max(ls->val,rs->val);
  }
}*root,t[MAXN<<1],*tail=t;//数组是一段连续的地址,数组名是首地址
node *build(int left,int right)
{
  node *temp=tail++;//内存池的取用
  temp->l=left,temp->r=right;
  temp->val=INT_MIN;
  if(left==right)
  {
    temp->ls=temp->rs=NULL;
    return temp;
  }
  int mid=(left+right)>>1;
  temp->ls=build(left,mid),temp->rs=build(mid+1,right);
  return temp;
}

构造函数

这根不算指针的内容,但是和一会要讲的知识点关系密切
什么是构造函数?

构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载。

我们在定义一个变量时,我们可以提前写好一个函数,让他在定义时就已经赋好初值,方便我们使用
那这个东西怎么玩呢

#include<iostream>
using namespace std;
struct QAQ
{
    string name;
    QAQ()//不用写类型,无返回值
    {
        name="AK KING";
    }
    QAQ(string s)//重载,在不同情况下调用不同的函数
    {
        name=s;
    }
};
int main()
{
    QAQ *starria=new QAQ;
    cout<<starria->name<<endl;
    QAQ *assass_cannotin=new QAQ("JURUO");
    cout<<assass_cannotin->name<<endl;
}

结果
这里写图片描述
很妙很妙
但是和内存池搭配使用效果不佳

空指针防止翻车进阶

线段树防止RE:判断l是否等于r即是否是叶子结点
平衡树防止RE:构造空指针,不用傻逼NULL

struct node
{
  node* fa,*son[2];
  int val,size,cnt;
  node(int x);
  inline int cmp(int x)
  {
    return (x^val)?x>val:-1;
  }
  inline void push_up()
  {
    size=son[0]->size+son[1]->size+cnt;
    return ;
  }
  inline int dir()
  {
    if(fa->son[1]==this)return 1;
    if(fa->son[0]==this)return 0;
    return -1;
  }
}*root,*nil,*tmp;
node::node(int x)
{
  val=x;
  size=cnt=1;
  son[0]=son[1]=fa=nil;
}
inline void init()
{
  nil=new node(-1);
  nil->size=nil->cnt=0;
  nil->son[0]=nil->son[1]=nil->fa=nil;
  root=nil;
}

大概就是这样,把nil当做NULL就好了
这样构造一个空指针,永不翻车
蒟蒻没什么可写了,就先退下了

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值