指针进阶(其一)

这一节指针进阶重点内容
  1. 字符指针
  2. 指针数组
  3. 数组指针
  4. 数组传参与指针传参
  5. 函数指针
指针的主题,我们在初识了解指针的时候已经知道了指针的概念
  1. 指针是一个变量,用来存放地址,地址唯一表示一块内存空间
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)
  3. 指针是有类型的,指针的类型决定了指针+-整数的步长,指针解引用的时候的权限
  4. 指针运算
1、字符指针
在指针的类型中,我们知道有一种指针类型为字符指针char*  
一般使用
int main ()
{
    char  ch = 'w';
    char* pc = &ch;
    *pc = 'b';
    printf("%c\n",ch);
    return 0;
}
我们在中间对这个指针进行了修改,最后打印出来的是字符b
那么这个字符指针既然能保存一个字符,那他好像是不是也可以存一个字符串啊?
char* p="abcdef";
printf("%s\n",p);
我们发现这个也成功打印出了我们想要的结果
那这个指针里面是不是就是放了一个字符串呢,这个讲法是不对的
我们付给p的其实是一个首字符的地址,我们想,abcdef,再加一个\0,这其实是7个字节,但是指针变量怎么能储存七个字节的数据呢?这里其实是把“a”的地址,赋予了p,打印的时候,你只需要告诉我从哪里开始的就可以了,p只是提供了一个地址,%s就开始打印,遇到\0就停止打印
我们用数组储存字符串的时候,char arr[]="abcdef";  相当于把这个数组进行了初始化,数组名也指向了字符串的第一个地址。
char* p="abcdef";在我们写这个代码的时候,有的编译器可能会报警告,会说这个指针是不安全的,我们的“abcdef”是一个常量字符串,但是p是一个变量,是可以被改变的,非要强行改,会发生写入权限冲突,需要对p进行限制const char* p="abcdef";  限制*p无法改动
 之前有过这样一道笔试题
int main()
{
    const char* p1 = "abcdef";
    const char* p2 = "abcdef";
    char arr1[]="abcdef";
    char arr2[]="abcdef";
    if(p1==p2)
        printf("p1==p2\n");
    else
        printf("p1!=p2\n");
    if(arr1 == arr2)
        printf("arr1==arr2\n");
    else
        printf("arr1!=arr2\n");
}
结果是这样的
结果是第一个等于,第二个不等于
p1等于p2,说明p1与p2,指向了同一个字符串
因为这是个常量字符串,放在内存的只读数据区域,只能用,不能改变它的值,所以两个相同的常量字符串,没有必要存两个,反正你也不能改。所以p1与p2指向的是同一块儿内存
然而arr1与arr2不相等,因为,这两个abcdef储存了两个,两者都是可以改变的,所以不会使用相同的一块儿空间。
2、指针数组
指针数组,在指针初阶中我们学过,指针数组是一个数组,是用来存放指针的数组
int arr[10]; //整型数组
char arr[10]; //字符数组
int*  arr[10]; // 存放整形指针的数组
char * arr[10]; // 存放字符指针的数组
3、数组指针
数组指针是一个指针,他是一个指向数组的指针
int*  p1[10];
int  (*p2)[10];
第一个,p1与[]先结合,说明p1是数组,数组的元素是指针,p1是数组指针
第二个,p2与*先结合,说明p2是指针,指向了一个有10个int元素的数组,p2是一个数组的指针, 指针也是有类型的,这里的这个指针的类型就是 int [10],所以我们还得 加上[10]。
我们之前就讲过数组与数组名的关系
int main()
{
int arr[10]={0};
printf("%p\n",arr);
printf("%p\n",&arr[0]);
}
这个程序打印的两个地址是一样的,有人会说这个数组名是首元素地址,但是
int sz = sizeof(arr);   地址应该是四个字节或者八个字节,那这个结果应该是4或者8啊,但是打印的结果是40,这理由不是首元素地址了,所以我们要注意,数组名通常是数组首元素的地址,但是当 sizeof(arr)    与    &arr的时候是代表整个数组,其余的时候都是指数组首元素地址。
printf("%p\n",&arr);
我们在执行这个代码的时候,表示的依然是整个数组,取出的是整个数组的地址,但与数组首元素的地址是一样的,数组的地址就是首元素的地址,但其实是有区别的。
当我们让&arr+1,的时候,会·体哦爱国整个数组的地址,转到数组之外的地址上,但是当我们让&arr[0]+1的时候,会转到数组的下一个元素上
那当我们要存放数组的地址的时候,也就是我们上面讲的&arr
我们要用一个p,来存放数组的地址,p=&arr;我们还希望这是一个指针所以我们加上一个(*p),指向谁呢?指向一个大小为10的数组,int   (*p)[10] = &arr;
指针也是有类型的,这里的这个指针的类型就是 int [10],所以我们加上了[10],
那这个数组指针到底应该在什么时候使用呢?如果在一些简单的情况,比如打印数组的时候,那使用数组指针还不如直接使用数组。
数组指针的使用应该在二维或者三维数组中,才会相对便利一些,
比如说我要打印一个二维数组
void print(int arr[3][5],int row,int col)
{
    for(int i=0;i<row;i++)
    {
        for(int j=0;j<col;j++)
        {
            cout<<arr[i][j];
        }
        cout<<endl;
    }
}
int main()
{
    int arr[3][5]={1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};
    print(arr,3,5);
    
    return 0;
}
那我怎样使用数组指针呢?
注意,二维数组的首元素是他的第一行,整个一行是他的首元素,是一个一维数组的地址
所以这个二维数组的数组指针应该写为 int (*p)[5]
void print(int (*p)[5],int row,int col)
{
    for(int i=0;i<row;i++)
    {
        for(int j=0;j<col;j++)
        {
            cout<<*(*(p+i)+j); //p+i将某一行的元素地址取出,再解引用相当于拿到了这一行的地址
        }
        cout<<endl;
    }
}
int main()
{
    int arr[3][5]={1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};
    print(arr,3,5);
    
    return 0;
}
对于 *(*(p+i)+j)的解析
二维数组里放的也都是一维数组名,因为访问整个数组没有意义,就像一维数组也是首元素地址, *(p+i)拿到的是某一行,访问整个数组第一行没有必要,数组名能代表这一行,所以访问的是数组名
如果觉得这种方法太绕,那么*(p+i),相当于p[i],那么*(*(p+i)+j)就相当于*(p[i]+j),那么*(p[i]+j)其实又相当于p[i][j],  cout<<p[i][j];
4、数组传参与指针传参
写代码的时候,难免要把数组或者指针传给函数,那函数的参数应该怎么样设计呢
4.1一维数组的传参
当我要把一个一维数组传参的时候,我应该怎么样设计它的参数?
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}
void test2(int *arr[20])
{}
void test2(int **arr)
{}
int main()
{
    int arr[10]={0};
    int* arr2[20]={0};
    test(arr);
    test2(arr2);
}
显然第一种,第二种与第三种都是可以的,传参,可以传一个数组,这个一维数组可以是规定好的,也可以不写大小,也可以传一个指针
这时arr,那arr2怎么传参?这是一个20个元素的数组,每一个元素的类型时int*
这就是第四个
int *arr[20],没有任何问题,20依然可以省略掉,这个地方也是可以写数组,可以写指针,那指针应该怎么写?
这个数组的每个元素都是int* 他的首元素是一个int*,那这个数组名,是int*的地址,所以放入一个二级指针没有问题,二级指针本来就是要指向一个一级指针的地址,所以第五个依然没有问题
4.2 二维数组的传参
void test(int arr[3][5])
{}
void test2(int arr[][])
{}
void test3(int arr[][5])
{}
void test4(int *arr)
{}
void test5(int *arr[5])
{}
void test6(int (*arr)[5])
{}
void test7(int **arr)
{}
int main()
{
    int arr[3][5]={0};    
}
第一个传参二维数组可行没有争议
但是第二个,二维数组传参行可以省略,列不能省略,第二个不可行
所以第三个也可以
下面就是指针区域了
第四个写成这样,二维数组的数组名,是第一行的地址,但是第一行是一个一维数组,一维数组的地址无法放入一个一级指针
第五个也是不可行的,第五行的int *arr[5],arr先与[5]结合,是一个数组,每一个元素是int*,是在一个指针数组
所以第六行可行,第六行先与*结合,是一个类型为int [5]的指针
那写成二级指针的话,二级指针是存放一级指针变量的地址,这里显然不是二级指针,数组名是第一行数组的地址,无法放入一个二级指针里,*arr是第一行数组的数组名,**arr第一行数组的第一个元素,无法对应
4.3一级指针传参
void print(int *p,int sz)
{
    for(int i =0;i<sz ;i++)
    {
        printf("%d\n",*(p+i));
    }
}
int main()
{
    int arr[10]={1,2,3,4,5,5,6,7,8,9};
    int *p=arr;
    int sz =sizeof(arr)/sizeof(arr[0]);
    print(p,sz);
    return 0;
}
如果我们传指针,对p进行传参,如果函数的参数部分是指针
void print(int*  p)
{}
那我应该怎么样对这个函数传参呢?
int a=10;
print(&a);
这个肯定是可以的,p是个地址,传a的地址肯定是可以的
int*  ptr=&a;
print(ptr);
这个也是可以的,一级指针变量用一级指针接收
int arr[10];
print(arr);
数组名是数组首元素的地址,这个也是可以的
只要传过去的本质上是一个整形指针,就是可以传的
4.4 二级指针传参
第一个,二级指针变量,二级指针接收,没问题。
把一级指针变量的地址传参也没问题。
如果函数的形式参数是二级指针,调用函数的时候,可以传什么参数呢?
test (int** p)
{}
int *p1;
int ** p2;
test (p2);   //传二级指针没问题
int*  arr[10];  //指针数组的数组名可以传过去
5、函数指针
我们可以将函数指针与数组指针来类比
数组指针就是指向数组的指针
那么函数指针就是指向函数的指针
取地址数组名就能拿到一个数组的指针
int arr[10]={0};
int (*p)[5]=&arr;
这个p就是数组的元素
那是不是取地址函数就能取出函数的地址呢?
我们进行一个测试
我们发现我们还真打印了一个地址,这个地址其实就是函数的地址,每一个函数都有地址
我们联想到数组名也是个地址,那函数名是不是地址呢,我们再进行一个测试
打印的结果果然是一模一样的,这两个没有区别,对于函数来说,取地址,函数名都是函数的地址
那函数的地址怎么样储存呢?
首先,他得是个指针吧  (*pf) =&Add;
其次,这个指针的返回类型是int吧  int  (*pf) =&Add;
最后,这个函数的参数类型是int int ;
那我们写上这个函数的参数的类型就是这个函数的指针了 int  (*pf)(int,int) =&Add;
那这个函数指针在什么情况下用呢?
当我一个指针变量存了一个变量的值的时候,我可以通过解引用来对这个值进行操作,函数也一样,
我们在上面已经把这个函数的地址存到pf里了,那对这个pf解引用就能找到这个函数(*pf)
一个普通的整形函数解引用可以改变它的值,一个函数指针解引用,也可以对它进行操作,来改变函数的参数
其实这个cout<<(*pf)(2,3)<<endl;里的*可写可不写
这是为了理解起来好一点,其实我们平时调用函数的时候就是不用*的
int num=Add(2,3);
int num=pf(2,3);
其实是一个道理
  • 34
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值