高级指针总结
1、指针总览
1.1 指针的作用
- 使程序简洁、紧凑、高校
- 有效地表示复杂的数据结构
- 动态分配内存
- 得到多于一个的函数返回值
1.2 指针的种类
指针的种类:函数指针,数组指针,一级指针,二级指针,指向不同类对象的指针
2、字符串指针
2.1 指针指向字符串常量
const char* p; //此处若不加const会报错,因为指向常量的指针必须是常量指针。
p = "Hello World";//此处"Hello World"是一个字符串常量。
*p = "xxx";//此处会报错,因为字符串常量不可修改。
2.2 指针指向字符串变量
char ch[] = "Hello World";
char* p;
p = ch;
cout << p[0] << endl;
cout << *(ch+0) << endl;
cout << ch[0] << endl;
cout << *p << endl;
以上四种方式均可以访问到字符串ch的第一个字母
3、指针与数组
3.1 指针数组
- <存储类型><数据类型>* <指针数组名> [<大小>]
- 指针数组名表示该指针数组的起始地址
指针数组可以和二维数组结合使用
#include<iostream>
using namespace std;
int main()
{
int a[2][3] = {{1,4,6},{12,9,7}};
int* p[2];
p[0] = a[0];
p[1] = a[1];
//三种取值方式
cout << a[0][1] << endl;
cout << *(a[0]+1) << endl;
cout << *(p[0]+1) << endl;
system("pause");
return 0;
}
输出结果:
配合二级指针可以实现如下骚操作:
int** pp = p;//定义一个二级指针指向指针数组的首地址
//取出所有数据
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++){
cout << *(*(pp+i)+j) << “\t”;
}
cout <<endl;
}
输出结果:
3.2 数组指针
- <存储类型><数据类型>(* <指针数组名>) [<大小>]
- 指针执行加一操作,地址增加<大小>×数据类型对应的大小
3.2.1一维数组指针
一维数组指针就是普通的数组指针,这样的指针加一,地址增加整个一行数组的大小,一般用于指向一个二维数组,这样指针加一,则将指针的指向直接跳过一维,即切换到数组的下一行。
这里需要注意的是一个一维数组指针相当于一个二级指针,只不过指针执行加一操作时,所跳跃的地址大小不同。如果想要通过指针得到数组中的值,我们需要对指针进行两次解引用操作。
//定义一个二维数组
int b[2][3] = {
{0, 1, 2},
{3, 4, 5}
};
//一维数组指针指向二维数组b
int(*arrayp)[3] = b;
cout << **arrayp << endl;
arrayp++;
cout << **arrayp << endl;
输出结果:
3.2.2二维数组指针
二维数组指针可以通过类比一维数组指针来进行解读,首先二维数组指针一般用于指向一个三维数组,二维数组指针加一,指针的指向直接跳过一块(如下定义的三维数组c,我们认为这个三位数组有4块,每一块中有2行,每一行有3列)
同理,我们可以推断出一个二维数组指针相当于一个三级指针,只不过指针执行加一操作时,所跳跃的地址大小不同。如果想要通过指针得到数组中的值,我们需要对指针进行三次解引用操作。
//定义一个三维数组
int c[4][2][3] = {
{ { 0, 1, 2}, { 3, 4, 5} },
{ {10, 11, 12}, {13, 14, 15} },
{ {20, 21, 22}, {23, 24, 25} },
{ {30, 31, 32}, {33, 34, 35} }
};
//二维数组指针
int(*d2Arrayp)[2][3] = c;
cout << ***d2Arrayp << endl;
d2Arrayp++;
cout << ***d2Arrayp << endl;
d2Arrayp++;
cout << ***d2Arrayp << endl;
d2Arrayp++;
cout << ***d2Arrayp << endl;
输出结果:
3.2.3N维数组指针
同理我们可以推断到N维数组指针,三维数组指针一般指向四维数组,四维数组指针指向五维数组…不过一般没人会用到那么复杂的东西了。
总结:
你有没有发现,对于指针数组和数组指针,他们之间似乎有着某种联系,下面我们来进行探究一下。
指针数组是一个数组,一个用于存放同类型指针的数组。
数组指针是一个指针,一个指向固定大小固定维数数组的指针。
假设我们在一个指针数组中放了若干个函数指针,如果我们想要调用这些函数,首先我们想到的是通过数组索引的方式分别找到每一个函数来对他们进行调用,如果我们现在的需求是用一个指针来调用这些函数,那么我们是不是就需要再创建一个二级指针,来指向我们的指针数组,通过这个二级指针就可以实现对每一个函数的调用,这个时候的二级指针,是不是就和我们的数组指针的作用有一些相似。
4、指针与函数
4.1 指针函数
指针函数的定义格式如下:
int* pfunc(int a, int b){}
按照以往的习惯,我们按照顺序读,“*”称为指针,pfunc(int a, int b)
为函数,所以这是一个指针函数,从语文的角度来看,指针函数的主体是函数,所以这是一个函数,只不过这个函数的返回值是一个指针。
当然了,既然这个函数的返回值是一个指针,那么我们指针的格式可是多种多样的,所以这个指针函数的返回值也就变得多种多样了。
4.1.1指针函数返回一级指针
int a = 5;
int* p = &a;
int* pfunc1(int a, int b) {
return p;
}
4.1.2指针函数返回二级指针
int a = 5;
int* p = &a;
int** pp = &p;
int** pfunc2(int a, int b) {
return pp;
}
4.1.3指针函数返回数组指针
这只是一种定义方法,还有使用typedef来定义的方法,下个专题讲。
int(*arrayp)[3];//定义一个大小为3个int值的数组指针
int(* pfunc3(int a, int b))[3]{
return arrayp;//返回这个数组指针
}
4.1.4指针函数返回函数指针
char (*fp)(char a, char b);//定义一个函数指针,为了便于区分,该函数指针指向的函数输入输出均设为char类型
char (*pfunc4(int a, int b))(char a, char b) {
return fp;//返回这个函数指针
}
4.1.5指针函数返回类对象指针
class Animal //定义Animal类
{
public:
Animal(string name, int age) {
this->name = name;
this->age = age;
}
string name;
int age;
};
//定义指针函数,返回值为Animal类的对象的地址,这里的输入参数也是一个指针,为了方便输出
Animal* pfunc5(Animal* animal){
return animal;
}
int main()
{
Animal cat("mimi", 5);
Animal* pAnimal;//定义一个Animal类型的指针
pAnimal = pfunc5(&cat);
cout << pAnimal->name <<"\t"<<pAnimal->age<< endl;
system("pause");
return 0;
}
输出结果:
4.2 函数指针
函数指针的定义格式如下:
int (*funcp)(int a, int b);
按照以往的习惯,我们按照顺序读,“*”称为指针,funcp(int a, int b)
为函数,但是此时的定义式中的“*”和funcp被括号括住了,所以“*”的优先级提高,funcp将会先和“*”结合,再和“()”结合,所以这是一个函数指针,从语文的角度来看,函数指针的主体是指针,所以这是一个指针,只不过这个指针的指向是一个函数。
下面给出一个小例子
int (*funcp)(int a, int b); //定义一个函数指针
int testfunc(int a, int b) { //定义一个普通函数
return a + b;
}
int main()
{
funcp = testfunc; //函数指针指向函数testfunc
cout << funcp(5, 6) << endl; //通过指针调用函数并进行输出
system("pause");
return 0;
}
输出结果:
从上面的例子中可以看出来,好像函数指针没有什么用处,仅仅是给函数换了一个名字一样,确实是这样,所以说函数指针一般要和数组进行结合才会发挥出它的威力,下面我们先介绍函数指针数组。
5、指针与常量
指针常量、常量指针,你真的分得清吗?
首先,分辨这两种指针的方法就是观察在定义的时候const与“*”的前后位置关系,然后按照顺序读出来就好,const读作常量,“*”读作指针。
5.1 指针常量
下面这是一个例子中“*”在const的前面,所以这是一个指针常量,指针常量是一个专一的指针,这个指针这一生只能指向一个变量,而且在出生的时候就必须被安排好了,但是对于它指向的变量没有任何要求,可以随意更改变量中的值。
int* const p; //error! 指针常量初始化未赋值
5.2 常量指针
比如下面这个例子中const在“*”的前面,所以这是一个常量指针,常量指针是一个只能指向常量的指针,常量指针可以指向变量,只不过这个变量的值不可以通过这个常量指针来改变。(常量指针定义时const与int可以交换位置)
int a =100;
const int* p = &a; //正确! 常量指针可以指向一个变量,并非只能指向常量。
(*p)++; //错误,无法通过常量指针修改变量的值。
a++; //正确,a是一个变量,可以进行运算。
5.3 指针常量的指针常量
以上两种指针常量与常量指针的结合体,叫做只能指向常量的指针常量,这个指针可以指向一个变量,不过不可以通过指针对这个变量值进行修改,这个指针一旦指向了一个变量或者常量,他就不可以再切换指向,指向其他变量或常量了,而且这个指针在定义的时候就必须初始化指针的指向,也就是说,这样一个指针结合了指针常量与常量指针的所有特点,所以我在这里暂且称他为:不能修改指向值的指针常量。
const char * const p = &a;
6、指针、数组与函数
6.1 函数指针数组
函数指针数组的定义格式如下:
int(*parray[3])(int a, int b);
按照以往的习惯,我们按照顺序读,“*”称为指针,parray[3]为数组,按照我们之前的推论,是不是会推导出函数指针数组的定义应该是int*(int a, int b) parray[3];
但是如果这样定义的话,我们的编译器在看到前面的int*(int a, int b)
就会将其认为是一个函数(先不管是什么函数),所以后面的parray[3]
编译器就不认识了,所以这样是不行的,为了避免这样的情况,我们的解决方法是将(int a, int b)
往后放,于是就变成了int(*parray[3])(int a, int b)
,这样编译器就认识了(哈哈,其实我也不确定到底是不是这样,总之这算是我的一种记忆方法吧,分享给大家)
现在我们根据语文的角度来分析函数指针数组这个名词,指针数组是主体,函数用于修饰指针,所以这是一个放指针的数组,而数组中的每一个指针都指向了一个函数,这样的数组我们将其称为函数指针数组。
6.2 指向函数指针数组的指针
指向函数指针数组的指针的定义格式如下:
int (*(*ppfarray)[3])(int a, int b);
这一部分的用处其实并不大,也就不做过多的解释,欢迎收藏本文,以后用到的时候前来查阅即可。
int func(int a, int b) {
return a + b;
}
int func2(int a, int b) {
return a - b;
}
int func3(int a, int b) {
return a * b;
}
int main()
{
int(*fparray[3])(int a, int b); //创建函数指针数组
fparray[0] = func; //函数指针数组放入函数
fparray[1] = func2;
fparray[2] = func3;
int (*(*ppfarray)[3])(int a, int b); //创建指向函数指针数组的指针
ppfarray = &fparray; //指针指向函数指针数组
//输出函数地址
cout << "函数一的地址" << func << endl;
//输出指针指向的函数的地址
cout << "通过指针获得函数一的地址" << *ppfarray[0][0] << endl;
cout << "函数二的地址" <<func2 << endl;
cout << "通过指针获得函数二的地址" << *ppfarray[0][1] << endl;
//通过指针调用函数,方式一
cout << "函数调用方式一" << endl;
cout << (*ppfarray[0][0])(5, 3)<< endl;
cout << (*ppfarray[0][1])(5, 3) << endl;
cout << (*ppfarray[0][2])(5, 3) << endl;
//通过指针调用函数,方式二
cout << "函数调用方式二\n"<<(**ppfarray)(5, 3) << endl;
//这种方式调用函数的话只能调用第一个函数,
//无法通过指针加一的方式使指针指向下一个函数,
//通过我的测试,这里的ppfarray指针似乎有了数组指针的属性,
//指针加一,则直接跳过了三个函数。
system("pause");
return 0;
}
输出结果:
6.3 指针与函数、数组结合的小demo
#include<iostream>
using namespace std;
//函数指针数组
int(*parray[3])(int a, int b);
//定义三个函数
int func1(int a, int b) {
return(a + b);
}
int func2(int a, int b) {
return(a - b);
}
int func3(int a, int b) {
return(a * b);
}
int calculate(int a,int b,int mod) {
return parray[mod](a, b);
}
int main()
{
parray[0] = func1;
parray[1] = func2;
parray[2] = func3;
int a = 5, b = 2;
cout << calculate(a, b, 0) << endl;
cout << calculate(a, b, 1) << endl;
cout << calculate(a, b, 2) << endl;
system("pause");
return 0;
}
输出结果:
在上面这个小demo中,我们仅仅通过修改calculate函数的mod参数值即可指定运行不同的函数,而不用像传统的那样使用switch-case语句来进行分类,这算是函数指针数组的一个应用。
下面是通过二级函数指针来实现相同的功能,你们可以来感受一下,输出结果完全一样。
//函数指针数组
int(*parray[3])(int a, int b);
//定义一个二级函数指针
int(**fpp)(int a, int b);
//定义三个函数
int func1(int a, int b) {
return(a + b);
}
int func2(int a, int b) {
return(a - b);
}
int func3(int a, int b) {
return(a * b);
}
int main()
{
parray[0] = func1;
parray[1] = func2;
parray[2] = func3;
int a = 5, b = 2;
fpp = parray;
cout << (*fpp++)(a, b) << endl;
cout << (*fpp++)(a, b) << endl;
cout << (*fpp++)(a, b) << endl;
system("pause");
return 0;
}
7、多级指针
我们把指向指针变量的指针变量,称为多级指针。
首先最常用的多级指针是二级指针,再往上三级四级指针用的就比较少了,但是总体的用法可以类推,二级指针变量指向一级指针变量,同理三级指针变量指向二级指针变量,四级指针变量指向三级指针变量。。。
同样地,多级指针作为一个指针,他也可以指向各种类型的指针,比如多积函数指针,多级数组函数以及多级类对象指针。
7.1 二级指针
7.1.1二级函数指针
int(**fpp)(int a, int b); //这是一个二级函数指针
7.1.2二级数组指针
//定义一个二维数组
int b[2][3] = {
{0, 1, 2},
{3, 4, 5}
};
//一维数组指针
int(*arrayp)[3] = b;
//一维数组二级指针(二级数组指针)
int(**arraypp)[3] = &arrayp;
//我想了想,这玩意儿好像没啥用,但是这样的定义与赋值操作编译器是可以通过的。
8、void指针
void指针是一种不确定数据类型的指针变量,他可以通过强制类型转换让该变量指向任何数据类型的变量。
一般形式为:void* <指针变量名称>;
对于void指针,在没有强制类型转换之前,不能进行任何指针的算术运算。
9、关于指针的一些概念
9.1 野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的。
一般在程序中我们应该避免野指针的出现,为了避免野指针,我们通常将不用的指针置为空指针:
p == NULL;
9.2 空指针
对于空指针值,一般的文档中倾向于用 NULL 表示,而没有直接说成 0。但是我们应该清楚:对于指针类型来说,返回 NULL 和 返回 0 是完全等价的,因为 NULL 和 0 都表示 “null pointer”(空指针)。一句话, 空指针是什么,就是一个被赋值为0的指针,在没有被具体初始化之前,其值为0.(来源于百度百科)
可以使用以下三种方法判断一个指针是不是空指针
int* p;
if ( p == NULL ) /* 使用 NULL 必须包含相应的标准库的头文件 */
if ( NULL == p )
if ( !p )
未完待续。。。