[C/C++]指针,各种指针,好多指针

7 篇文章 0 订阅

1指针的大小

对于任何指针来说,当我们使用操作符sizeof时,得到的是4字节(32位系统)或8字节(64位)。但会有一些特殊情况

#include<iostream>

void test(int *arr)
{
	std::cout<<sizeof(arr)<<std::endl;
	return;
}


int main()
{
	int arr[10] = {0};
	std::cout<<sizeof(arr)<<std::endl;
	test(arr);
	return 0;
}

在函数test中,我们传过去的实际上是数组的首元素地址,所以里面的sizeof获得的是首元素的地址,因此计算出来是4/8;而在main中,sizeof(arr),这里的arr传过去的是一个数组名,因此计算的是整个数组大小,因为是int类型,有10个元素,所以大小就是40字节。


2字符指针

字符指针需要注意的点有3个:

  1. char str[]=“abcde”,字符str是以"\0"为结尾的。
  2. char*p = str;得到的是首元素地址。
  3. char*p = “abcde”;这个会被编译器视为常量,为了不给自己犯浑,建议直接用
    const char*p = “abcde”;常量指针来定义(后面细说)

3指针与数组

3.1指针的数组,指针数组

接下来我将会以很长的篇幅介绍这种类似的套娃。

对于一个指针数组,指针是定语,数组才是主语,为了保证主语是完整的,我们先写一个数组,比如 int arr[10]={0};然后这个数组要保存指针,所以加个指针,变成int* arr[10];

这玩意儿是干吗的呢?对于int arr[10]来说,他保存的是10个int的数据;而对于int*arr[10]则是保存了10个int*的数据,比如:

int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	int *arr[3] = {&a,&b,&c};
	return 0;
}

实际上,这玩意儿可能在处理二维数组时更有用,对于一个二维数组arr[i][j]来说,实际上它是由i个含有j元素的数组组成的一个数组,而这i个数组实际上又是以他们首元素地址来定义的,因此可以这么来定义二维数组:

#include<iostream>
using namespace std;

int main()
{
    //int 4bits;int * 8bits;
    //sizeof(arr)提取整个数组的字节
    int arr1[] = {1, 2, 3};
    int arr2[] = {2, 3, 4};
    int arr3[] = {3, 4, 5};
    int *parr[] = {arr1, arr2, arr3};//数组名就是首元素地址,所以这里得用指针数组

    int col = sizeof(arr1) / sizeof(int);//col
    int row = sizeof(parr)/sizeof(int*);

    for (int i = 0; i < col; i++)
    {
        for (int j = 0; j < row; j++)
            cout << *(parr[i]+j) << ' ';
        cout << endl;
    }
    system("pause");
    return 0;
}

3.2数组的指针,数组指针

这次主语是指针所以我们先定义一个指针比如*parr,然后我们定义数组 int *parr[10];诶!这不是跟指针数组一样了吗!确实,因为搞错了,这实际上是一些运算符的优先级问题,*运算好像比[]要低,所以为了确保是指针应该是 (*parr),然后才是定义数组int (*parr)[10]。

int main()
{	
	int (*parr)[10];
	return 0;
}

对于这玩意儿需要注意的点是,他是数组的指针不是数组元素的指针! 请参考以下情况。

int main()
{
	int arr[5] = {0};
	int (*parr)[5] = arr;//这句是会报错的
	int(*parr)[5] = &arr;
	return 0;
}

由于arr是首元素地址,他不能给int (*parr)[5];赋值;而我们&arr后,获得的就是整个数组的地址,这个才能赋值给数组指针。

下面是一些使用的例子。

//打印一维数组
#include <iostream>
using namespace std;

int main()
{
    int arr[5] = {33, 32, 11, 52, 1};
    int(*parr)[5] = &arr;
    for (int i = 0; i < 5; i++)
    {
    //实际上(*parr)[i]等价于arr[i]
    //由于*的优先级比[]低,所以为了确保解引用需要用()
        cout << (*parr)[i] << endl;
    }
    system("pause");
    return 0;
}
//打印二维数组
#include <iostream>
using namespace std;

int main()
{
    int arr[5][3] = {{1, 2, 3},
                     {2, 3, 4},
                     {3, 4, 5},
                     {4, 5, 6},
                     {5, 6, 7}};

    //获取二维数组首元素地址,即{1,2,3}这个数组名的地址(不是1的地址)
    //你可以想象说arr[5][3]是一个指针数组
    int(*parr)[3] = &arr[0];
	//计算行列,也可以直接写5,3
    int col = sizeof(arr[0]) / sizeof(int);
    int row = sizeof(arr) / sizeof(arr[0]);

    for (int r = 0; r < row; r++)
    {
        for (int c = 0; c < col; c++)
        {	
        	//(parr+r)找的是数组1,数组2...的位置,第一次解引用后就是数组1,数组2...的首元素地址,
        	//然后在各自的数组内部找他们元素的地址,最后第二次解引用得到值。
        	//实际上可以直接写成parr[i][j]
            cout << *(*(parr + r) + c) << ' ';
        }
        cout << endl;
    }
    system("pause");
    return 0;
}

3.3 数组,指针与函数参数

3.3.1 一维数组

我们定义一个一维数组 int arr[10]={0}; ,他可以这样传到函数中

void test(int arr[]);//ok
void test(int arr[10]);//ok,10写不写无所谓
void test(int *arr);//ok

void test2(int *arr[]);//ok
void test2(int *arr[20]);//ok
void test2(int **arr);//ok


int main()
{
    int arr[10]={0};
    int *arr2[20] = {0};//指针数组,内部放的都是地址
    test(arr);
    test2(arr2);
    return 0;
}

注意!传入的其实只是首元素地址,arr中传入的只是&arr[0],arr2中,传入的也只是&arr2[0],你可以在这些函数的内部加入sizeof去验证。

3.3.2 二维数组

二维数组的情况可能会麻烦点

void test(int arr[][]);//error
void test(int arr[3][]);//error
void test(int arr[][5]);//ok
void test(int arr[3][5]);//ok

//main中arr传过来是一个首元素地址,那么用一个指针接受,
//但是该首元素地址指向的是一个数组名,而不是一维数组那种首元素,
//即实际上这里传了个数组地址过去
void test1(int *arr);//error
//二级指针存放的是一级指针,数组地址都放不进上面的一级指针,这里就造不出二级指针
void test1(int **arr)//error

//结合第一个分析,这是对的,我们要传的就是一个数组指针
void test1(int (*arr)[5]);

int main()
{
	int arr[3][5] = {0};
	test(arr);
	test1(arr);
	return 0;
}

4函数和指针

4.1指针的函数,指针函数

他首先是个函数,我们随便写个声明 void test(int a,int b);然后加上指针,变成
void* test(int a,int b)。这就是指针函数。与指针数组类似,指针数组说的是数组里面放的是指针,而这里说的是函数里面的返回值是一个指针。

4.2 函数的指针,函数指针

他首先得是一个指针,(*pf),然后才是一个函数,void(*pf)(int a,int b);比如:

int Add(int x,int y)
{
    int z =0;
    z = x+y;
    return z;
}

int main()
{
    int a =10;
    int b =20;
    cout<<Add(a,b);
    
    if(Add==&Add)
    {
        cout<<"都是函数的地址";
    }
    int (*pa)(int,int)=&Add//这就是函数指针
    cout<<(*pa)(a,b);
    //实际上直接用pa(a,b)是可以的,因为Add==&Add,但是不易于阅读
    return 0;
}

4.3 究极套娃,函数指针数组

他首先是个数组pfarr[5],然后是指针(*pfarr[5]),最后是函数 void(*pfarr[5])(int a,int b);在示例代码前,我简单分析一下,他本质就是个数组,但是里面的内容是一些指针,里面的指针存放的是函数的地址。然后因为数组内部是同类型元素,所以你可以预计到这些函数也是同类型的。而同类型的函数意思是你的输入类型,输出类型是一样的,比如下面这样:

#include <iostream>
using namespace std;
int ADD(int a, int b)
{
    return 0;
}
int SUB(int a, int b)
{
    return 1;
}
int MUL(int a, int b)
{
    return 2;
}

int main()
{
    int *arr[5];

    int (*pfun[3])(int, int) = {ADD, SUB, MUL};
    // ADD,SUB要写完整不能只有原型,只有输入和输出类型一致时才能这么操作

    for (int i = 0; i < 3; i++)
    {
        cout << (*pfun[i])(1, 2);
    }
    system("pause");
    return 0;
}

4.4究极娃中娃,函数指针数组指针

  1. 首先他是指针,(*ppfarr)
  2. 其次是数组,(*ppfarr)[5]//这里还不能放类型,因为要考虑函数的返回
  3. 然后又是指针, (*(*ppfarr)[5])
  4. 最后才是函数, int (*(*ppfarr)[5])(int a, int b);

所以首先他是一个指针,里面存放的是一个函数指针数组(4.3),回看4.3的代码,假设我们需要保存 int (*pfun[3])(int, int) = {ADD, SUB, MUL};这个数组的地址,我们就需要这样的指针

//请配合4.3食用
int main()
{
    int *arr[5];

    int (*pfun[3])(int, int) = {ADD, SUB, MUL};

    int (*(*ppfun)[3])(int, int) = &pfun;
    system("pause");
    return 0;
}

实际上,当你需要用到这种指针时,你可以在 int (*pfun[3])(int, int)的基础上修改即可,首先你得保证他是个指针int ((*pfun)[3])(int, int),然后在加个指针就可以了int (*(*pfun)[3])(int, int)。


5 常量和指针

5.1 指针是一个常量,指针常量

首先他是一个常量const a; 然后是个指针 int* const a;他的意思是:

  • a所对应的地址不能修改;即,int b =20; a = &b;是不允许的
  • a所对应的值可以修改;如*a =10;

5.1.1 指针常量和引用

引用,都指向同一块内存空间,里面的值可以修改,这和我们描述的指针常量很像,C++的引用本质上是指针常量的使用

int main()
{
	int a =10;
	//这一句实际上是:int* const ref = &a;
	//实际上要的是a所对应的内存空间,而不是把10赋给了&ref
	//比如int&ref = 10;是编译不了的
	int &ref = a;
	//当编译器知道ref是一个a的引用后,这里其实转变成*ref=20;
	ref = 20;
	return 0;
}

5.2 常量是一个指针,常量指针

首先他是指针 int * a,然后是个常量 const int*a

  • 指针的指向是可以更改的
  • 但是指针所指向的值是不可以被更改的

5.3 套娃,常量指针常量

首先是个常量 const a,然后是指针 int* const a,最后是常量,const int* const a=&tmp;
结合了指针常量和常量指针,他直接锁死了地址和值,啥都不能改。


6指针引用

额。。。体会以下代码吧。。。暂时没有啥想说的

int main()
{
	int a = 100;
	int *p = &a;
	int * &rp = p;
 
	cout << a << endl;
	cout << *p << endl;
	cout << *rp << endl; 
 
	cout << p << endl;
	cout << rp << endl;
 
	getchar();
	return 0;
}

7练习

1.请说出他们的各自类型

//这个代码是跑不动的
int add(int x,int y);
int sub(int x,int y);

int main()
{
    int arr[10];
    int* arr[10];
    int (*pa)[10]=&arr;
    int (*pf)(int,int) = &add;
    int (*pfArr[])(int,int) = {add,sub};
    int(*(*ppfArr[]))(int,int) = &pfArr;
    
    return 0;
}
void* blabla(int a, int b);//这个是什么函数

数组,指针数组,数组指针,函数指针,函数指针数组,函数指针数组指针,指针函数

2.请尝试自己解读以下两段莫名其妙代码(画图会比较好理解)

(*(void(*)())0)();
void (*signal(int,void(*)(int)))(int);

(由于文字很长,我不用删除线了)第一段中,void(*)(),这就是一个函数指针,他外面套的那个括号加上0,(void(*)())0,称之为强类型转换,他把0转化成一个函数指针,然后外面的*是解引用,使用0所在地址的函数;

第二段中,先从最里面的void(*)(int)开始,他是一个函数指针,他作为参数传入(关于这方面的应用我会在回调函数里面详细介绍),signal那里的*是解引用,引用的函数的输入是(int),返回是void。


8一些其他指针

8.1空指针

  1. 空指针用NULL来表示(C++也有NULL,但是建议用nullptr),int *p = NULL;
  2. 对于那些我们不知道应该赋什么值来初始化的指针,我们应该用NULL给他赋值,但是NULL所指向的空间,我们是没办法访问的!
  3. 空指针在概念上不同于未初始化的指针。空指针可以确保不指向任何对象或函数;而未初始化的指针则可能指向任何地方(这是野指针)
  4. 在内存管理中,我们在使用malloc和free后,必须把管理的指针置空!否则将会非法访问,造成野指针问题。

8.2野指针

这种指针指向的是我们不知道的内存空间,他可能会乱值,也可能指向一片已经被系统回收的地址,所以你得不到改地址原来的信息,具体成因:

  1. 指针变量未初始化,应该要int*p=NULL;
int *p;
  1. 指针释放后不置空,应该加上一句p=NULL;
int* p = (int*)malloc(sizeof(int)*10);
if(p==NULL)
{
	cout<<perror("Error:")<<endl;
	return 1;
}
free(p);
  1. 指针越界访问,通常报segment blabla就是这个问题
class A {
public:
  void Func(void){ cout << “Func of class A” << endl; }
};

class B {
public:
 A *p;
 void Test(void)
 {
    A a;
    p = &a; // 注意a的生命期 ,只在这个函数Test中,而不是整个class B
 }
 void Test1() {
  p->Func(); // p 是“野指针”
  }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要明白什么是指针,必须先要弄清楚数据在内存中是如何存储的,又是如何被读取的。 如果在程序中定义了一个变量,在对程序进行编译时,系统就会为这个变量分配内存单元。编译系统根据程序中定义的变量类型分配一定长度的空间。内存的基本单元是字节,一字节有8位。每字节都有一个编号,这个编号就是“地址”,它相当于旅馆的房间号。在地址所标示的内存单元中存放的数据,就相当于在该旅馆房间中居住的旅客。 大家一定要弄清楚“内存单元的地址”和“内存单元的内容”这两个概念的区别,即“房间号”和“房间内所住客人”的区别。在程序中一般是通过变量名来对内存单元进行存取操作的。其实程序经过编译以后已经将变量名转换为变量的地址,对变量值的存取都是通过地址进行的。这种按变量地址存取变量的方式称为直接访问方式。 还有一种间接访问的方式,即变量中存放的是另一个变量的地址。也就是说,变量中存放的不是数据,而是数据的地址。就跟寻宝一样,可能你按藏宝图千辛万苦找到的宝藏不是金银珠宝,而是另一张藏宝图。按C语言的规定,可以在程序中定义整型变量、实型变量、字符型变量,也可以定义这样一种特殊的变量,它是存放地址的。 由于通过地址能找到所需的变量单元,所以可以说,地址“指向”该变量单元。如同一个房间号指向某一个房间一样,只要告诉房间号就能找到房间的位置。因此在C语言中,将地址形象地称为“指针”,意思就是通过它能找到以它为地址的内存单元。 所以,一个变量的地址就称为该变量的指针指针就是地址,而地址就是内存单元的编号。它是一个从零开始的、操作受限的非负整数。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值