今天总结一下指针的知识点与指针的使用

指针及其应用

第一节 指针变量

指针变量的定义、赋值:
指针定义的一般形式:
类型说明符*变量名
其中,*表示这是一个指针变量,变量名及为指针的变量名,类型说明符表示该指针变量所指向的变量的数据类型。
例:int *p=NULL;
定义了一个指针变量p,p指向一个内存空间,里面存放的是一个内存地址。现在赋值为NULL(其实就是0,特殊的空地址)
给指针变量p赋值:
一、指针变量初始化的方法:
int a;int *p=&a;
二、赋值语句的方法:
int a;int *p;p=&a;
三、int *p=new(int);代表申请给p一个空间,*p内容不能确定。

不允许把一个数赋值给指针变量,故如下的赋值语句是错误的:int p=1000;(此句代表指针p指向内存地址为1000的数值,谁也无法确定将会是什么,随意修改或赋值可能会产生错误,甚至破坏程序或系统内存的值等,引发不可预测的错误)被赋值的指针变量前不能再加””说明符,故如下的赋值语句也是错误的:p=&a;。赋值之后,p就代表指针p指向的整型变量,而p中存放的就是a占用单元的起止地址。另外要注意的是:必须明确p指向的数据类型,类型不一样的不仅空间大小不相同,储存格式也不同。有的时候可能会使用无类型指针,对指针进行赋值后,最后在运算、输出等操作时必须明确p指向的数据类型,例如输出时:cout<<(int)p;
多重指针:指针是指向其他类型的,其实指针也是一种类型。C++允许使用递归地指针指向指针的指针——多重指针。
例:双重指针

int a=10;
int *p;
int * *pp;
p=&a;        //将p指向a
pp=&p;      //将pp指向p
printf(“%d=%d=%d\n”,a,*p,* *pp);

输出:10=10=10
多重指针除了可以多次“间接”访问数据,OI上主要应用动态的多维数组。

第二节 指针与数组

指针与数组的关系:
指向数组的指针变量称为数组指针变量。一个数组是一个连续的内存单元,数组名就是该单元的首地址。一个指针可以指向一个数组,也可以指向其中一个数组元素。假如指针要指向数组的第i号元素,可以把数组的第i元素的首地址赋予它,或把数组名加i赋予它。
指向数组的指针:
数组指针变量说明的一般形式:
类型说明符 指针变量名
其中类型说明符表示所指数组的类型。
当访问数组元素时,有以下两种形式:
一、下标法:即用p[i]形式访问a的数组元素
二、指针法:即采用
(p+i)形式,用间接访问的方法来访问数组元素。
指针也可以看作数组名:
指针可以动态申请空间,如果一次申请多个变量空间,系统给的地址是连续的,就可以当成数组使用,这就是动态数组的一种。
例:
int *a;
a=new int [n+1]; //向操作系统申请了连续的n+1个int型的空间
这样就可以利用已知的数据n规划内存空间(动态规划,n变内存空间变)
动态数组的优点:对于大数据可能超内存的情况是比较纠结的事,用小数组只得部分分,大数组可能爆空间(得0分)。使用“动态数组”,可以确保小数据没问题的前提下,尽量满足大数据的需求。
另外需要注意的指针运算问题:
例如指针p++的意思并不是和广义上的加1,不是p的值(地址)加1,而是根据类型int增加sizeof(int),即刚好跳过一个整数的空间,达到下一个整数,由此类推,p+i即p根据类型跳过sizeof(类型)个空间。

第三节 指针与字符串

在C++中我们访问字符串可以通过数组存放并输出的形式,还可以用字符指针指向一个字符串。
例:
char *str=“I love China!”;
printf(“%s\n”,str);
等价于:
char *str;
str=“I love China!”;
printf(“%s\n”,str);

这里并不是将字符串赋值给指针,指针的作用只是存放地址,不能存放字符串,这里只是将字符串第一个元素的地址(即存放字符串的字符数组的首地址)赋给str。

第四节 指针与函数

指针可以作为函数的参数。在我们把数字作为参数传到函数时,实际上就是利用了传递指针(即传递数组的首地址)的方法。通过首地址,我们能访问数组中任何一个元素。
一个函数可以返回整数型、字符型、实型等,也可以返回指针联系的数据(即地址)。
返回指针值的函数一般定义形式为:类型名 函数名(参数列表);例如:int a(int a,int b)其中a是函数名,调用它后得到一个指向整型数据的指针(地址)。x和y是函数a的形参,为整型。
注意:在
a的两侧没有括号,在a的两侧分别为
运算符和()运算符,由于()的优先级别高于*,因此a先于()结合。在函数前面有一个*,表示此函数是返回指针类型的函数。最前的int表示返回的指针指向整型变量。
另外,函数的入口地址也同样可以使用指针访问。但是,有些函数在编写时可能对要用的辅助函数尚未确定,在执行时才能根据情况为其传递辅助函数的地址。比如sort的调用:“sort(a,a+n,cmp);”其中的比较函数cmp是我们根据需要转给sort的(也可能是cmp1,cmp2等),其实就是传递了函数指针。
例:

#include<iostream>
using namespace std;
int t(int a)
{
    return a;
}
int main()
{
    cout<<t<<endl;        //显示函数地址
    int (*p)(int a);     //定义函数指针变量p
    p=t;                //将函数t的地址赋给函数指针p
    cout<<p(5)<<','<<(*p)(10)<<endl;
//输出p(5)是C++的写法,(*p)(10)是兼容C,这里使用p来调用函数的两种方法
return 0;
}

输出:
1
5,10

函数的基本操作
一、声明函数指针。
声明要指定函数的返回值类型以及函数的参数列表,和函数原型差不多。例如,函数的原型是:int test(int );相应的函数声明就是:int (*fp)(int );要注意的是,不能写成:int *fp(int );这样计算机编译程序会处理成声明一个fp(int )的函数,返回类型是int *。
二、获取函数的地址。
获取函数的地址只要使用函数名即可,例如:fp=test;这表明函数名和数组名一样,可以看作是指针。
三、使用函数指针来调用函数。
类似于普通变量指针,可以用(*fp)来间接调用函数指向的函数。但C++也允许像使用函数名一样使用fp(存在争议)。
另外函数指针还有typedef定义,定义格式如下:
typedef int (*LP)(int,int); //定义了一个函数指针变量类型LP
LP p=sum; //定义了一个LP类型的函数指针p,并赋值为sum
另外可以通过typedef定义函数指针来定义一个函数指针变量类型的函数指针数组,也可以同时为其初始化,这样可以应用于菜单功能区函数的调用,减少switch/case或if语句处理的复杂、冗长,并且这样有利于代码的维护,更加方便灵活。

第五节 结构体指针

结构体指针的定义与使用:
结构体指针变量定义的一般形式:
结构体名 *结构体指针变量名
引用结构体指针变量指向的结构体变量的成员的方法如下:
一、指针名->成员名
二、(*指针名).成员名
例:(*p).score与p->score是等价的。
自引用结构:
在一个结构体内部包含一个类型为该结构体本身的成员是不合法的,因为成员p是一个完整的结构,它将还包含自己的成员p,这样就会导致无限重复下去。例:

struct stu
{
   	char name[20];
 	 int age,score;
	 stu p;
 };

是非法的。
但是:

struct stu
{
 char name[20];
   int age,score;
	  stu *p;
};

是合法的。
区别就是现在的p是一个指针而不是结构体,因为编译器在结构体的长度确定之前就已经确定了指针的长度,所以这种类型的自引用是合法的。
当一个结构体中有一个或是多个成员是指针,它们所指向的类型就是本结构体类型时,通常这种结构称为“引用自身的结构体”,即“自引用结构”。这种自引用结构是实现其它一些结构的基础。
自引用结构在动态数据结构中有重要作用,甚至可以说,自引用结构是C/C++语言实现动态数据结构的基石。包括动态的链表、堆、栈、树,无不是自引用结构的具体实现。