指针的进阶~c-pointer-improve

学完指针初阶之后,现在进行指针进阶

  • 1.字符指针
  • 2.数组指针
  • 3.指针数组
  • 4.数组传参和指针传参
  • 5.函数指针
  • 6.函数指针数组
  • 7.指向函数指针数组的指针
  • 8.回调函数
  • 9.指针和数组面试题的解析




在学之前我回忆一下指针的概念

指针:

指针是一个变量,存放变量地址,地址唯一标识一块空间(内存单元的编号就是地址,将地址存放到指针后指针就成了一个指针变量)

指针的大小:是固定的4/8个字节(32位平台/64位平台)

指针的类型:1.指针+-整数的步长

                      2.指针解引用操作的时候的权限

写文章-CSDN创作中心

具体的操作由俺的(初阶指针)这篇博客已经阐述。

了解完指针的概念之后,开始进行进阶指针




1.字符指针 char *

const char*pa="abcdeef"

上述的字符串是常量字符串,需要用const,如果*pa想改变字符串,是不可以的,因为这个字符串是常量字符串,所以用const来表明一下这个不可以修改的

如果不加const并且对pa进行修改,编译器不会报错,最终运行不出结果,极大程度上不容易debug

如果加上const并且对pa进行修改,编译器会直接报错,这样容易debug

-----不可以通过指针,改变常量字符串的任意字符。

 

总结:

1.一个指针是可以指向一个字符串(字符串可以理解为数组都是连续存放),存放的是首字母的地址(字符指针变量)

2.指针指向的字符串是常量字符串,在前面加上const更能说明,放在左边会表示这个是不可以更改的。 

一道笔试题可以展示出加const和不加const的区别

#include <stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char *str3 = "hello bit.";//常量字符串
    const char *str4 = "hello bit.";
    if(str1 ==str2)//比较的是首元素的地址
 printf("str1 and str2 are same\n");
    else
 printf("str1 and str2 are not same\n");//占用俩个内存块
       
    if(str3 ==str4)
 printf("str3 and str4 are same\n");常量表达式,占用同一个内存块
    else
 printf("str3 and str4 are not same\n");
       
    return 0;
}

char *pa="hello bit";
*pa='w';//error
常量字符串是无法改的

 

 str3和str4指向的是同一个常量字符串,c/c++会把常量的字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,它们实际上会指向同一个内存区域

const已经将字符串弄成不可修改的了,如果再进行const定义同一个字符串的话,都还是指向的是同一个内存区域。所以俩者是相同的。




ps:字符串打印

字符串打印直接打印即可,由于字符串有个结束标志是'\0',字符指针变量指向了这个字符串首地址即可,就可以将整个字符串全部打印出来,直到遇到'\0'结束,

但是字符串打印也是可以遍历打印,但是直接打印显得简单。

  • 字符指针里面存放的是常量字符串首字符的地址,可以通过地址找到字符串每个字符。
  • 整型数组需要从头遍历的打印,由于整型数组是没有结束标志的

 



2.指针数组

整型数组——存放整型的数组

字符数组-------存放字符的数组

指针数组-------存放指针(地址)的数组

强调的是数组

定义:存放地址(指针)的数组(多用于一维数组)这个数组的所有元素都是指针 ,int *a[10];

每个元素都是一个地址,不能是普通数据

<1>存放字符指针的数组

 

arr[0]  存放的是”abcdef“首字符的地址
arr[1]  存放的是”qwer“首字符的地址
arr[2]  存放的是”hello bit"首字符的地址
arr[3]  存放的是"hehe"首字符的地址

这个数组的每个字符的类型是char*类型

<2>存放整型指针的数组
int a=10,b=20,c=30;
int *p[3]={&a,&b,&c};
结果:p[0]=&a;p[1]=&b;p[2]=&c;
等价:*(p+0)=&a;*(p+1)=&b;*(p+2)=&c;

取值:*p[0]=10; *p[1]=20; *p[2]=30;
		等价于
		**(p+0)=10; **(p+1)=20; **(p+2)=30
int main()
{
	//存放整型指针的数组
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };
	int arr4[5] = { 0,0,0,0,0 };
 
 指针数组--存放指针(地址)的数组
 
	int* arr[4] = { arr1,arr2,arr3,arr4 };
	int i = 0;
	for (int i = 0; i <= 3; i++)
	{
		int j = 0;
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", *(arr[i] + j));//arr[i][j]
			//arr[i]是每一行元素的起始地址,+j之后是那一行往后元素的地址
			//然后对地址的解引用就是那一行的元素
		}
		printf("\n");
	}
	return 0;
}

以上的代码,存放整型指针的数组,存放四个指针数组,每个元素都是地址,arr1是第一个指针数组的首元素的地址。。。。,依次遍历。

这样我们对指针数组有了一个认识,那就是指针数组存放的都是地址,每个元素都是指针,将这些指针都存放再一个数组里,就拟化成一个二维数组的样子。



3.数组指针

能够指向数组的指针

字符指针--------存放字符地址的指针----指向字符的指针 char*

整型指针--------存放整型地址的指针----指向整型的指针 int *

数组指针--------存放数组地址的指针----指向数组的指针

数组指针类型是       ( int(*)[10] )

int main()
{
    int arr[10]={1,2,4,5};指向数组的指针
    int (*pa)[10]=&arr;
    解释:pa先和*结合---表示pa是指针,然后指向一个大小为10的数组。
    所以  pa是指针,指向一个数组,称之为数组指针------其中存放的是数组的地址
    return 0;
}
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

pa先与*结合来表示pa是指针,然后指向一个大小为10的整型数组

pa是指针指向了一个数组,称为数组指针,就是存放的是数组的地址

这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

void print1(int arr[3][5], int r, int c)
{
    int i = 0;
    int j = 0;
    for (int i = 0; i < r; i++)
    {
        for (int j = 0; j < c; j++)
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}
//数组指针--指向二维数组的指针
void print2(int(*p)[5], int r, int c)
{
    int i = 0;
    int j = 0;
    for (int i=0; i < r; i++)
    {
        for (int j=0; j < c; j++)
        {
            printf("%d ", *(*(p + i) + j));//*(p+i)是指某一行的第一个元素地址  
        }
        printf("\n");
    }
}
int main()
{
    int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,5},{3,4,5,6,7} };
    print1(arr, 3, 5);
    print2(arr, 3, 5);//arr数组名,表示数组首元素的地址
    //二维数组的数组名表示  首行的地址
    //二维数组的首元素是:第一行的元素
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    //可以数组指针来接收
    return 0;
}

二维数组的arr(数组名)指的是数组的第一行的地址, arr[0][0]才是第一行第一个元素的地址

一维数组的arr(数组名)指的是数组的第一个元素的地址

指向数组的指针(多用于二维数组)强调的是指针

这个指针存放着一个数组的首地址

int (*p)[10] 



 

4.&数组名VS数组名

数组名--数组首元素的地址

&数组名---数组的地址

数组首元素的地址和数组的地址从值得角度看是一样的,但是意义不一样

数组名是数组首元素的地址(俩个例外)

1.sizeof(数组名)-----数组名表示整个数组,计算的是整个数组的大小,单位是字节

2.&数组名---------------数组名表示整个数组,取出的是整个数组的地址

实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。

整型指针arr+1,arr+1相对于arr的差值是4;

数组的地址+1,跳过整个数组的大小(p2存的是数组的地址)

所以 &arr+1 相对于 &arr 的差值是40

(p2+1)与(p2)的差是40

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    printf("%p\n", arr);//int *
    printf("%p\n", arr+1);//4

    printf("%p\n", &arr[0]);//int*
    printf("%p\n", &arr[0]+1);//4

    printf("%p\n", &arr);//int (*)[10]指向数组的指针,加1会跳过整个数组
    printf("%p\n", &arr+1);//40
    return 0;
}

5. 复习

int arr[5];//整型数组
int *parr1[10];//整型指针的数组
int (*parr2)[10];//数组指针,该指针能够指向一个数组,数组10个元素,每个元素类型是int
int (*parr3[10])[5];//存放数组指针的数组,存放10个数组指针,每个数组指针都指向一个数组,该数组有5元素,每个元素是int类型
//解释:int (*         )[5];数组指针
     //int (*parr3[10])[5];存放10个数组指针



5. 数组参数、指针参数

<1>一维数组传参

void test(int arr[]);
void test(int arr[10]);
void test(int *arr);
int main()
{
    int arr[10]={0};
    test(arr);
}
void test2(int *arr2[20]);
void test2(int *arr2[]);//指针数组
void test2(int **arr2);存放int *数组
   //不能写成int *arr2//error
int main()
{
    int *arr2[20]={0};//指针数组
    test2(arr2);
}

<2>二维数组传参

void test(int arr[][]);//error
总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素
void test(int arr[][5]);
void test(int arr[3][5]);
void test(int (*arr)[5]);//数组指针//   每一行有5个元素
int main()
{
 int arr[3][5] = {0};
 test(arr);
 }

二维数组传参用二维数组接收,一维数组传参用一维数组传参.

数组指针适用于二维数组,所以在二维数组传参的时候可以用数组指针传参,int(*arr)[5],也关联了在传参的时候必须给出列。

指针数组适用于一维数组,所以在一维数组传参的时候可以用指针数组传参

<3>一级指针传参

id print(int *ptr,int sz)//用一级指针接收
{
    int i=0;
    for(int i=0;i<sz;i++)
    {
        printf("%d ",*(ptr+i));    
    }
}
int main()
{
    int arr[10]={0,1,2,3,4,5,6,7,8,9};
    int *p=arr;//数组名是数组首元素的地址,将首元素的地址给p,p存了arr数组首元素的地址
    //p是一级指针
    int sz=sizeof(arr)/sizeof(arr[0])
    print(p,sz);//指针变量传过去,只用将指针变量来接收即可
    //如果传&p,则需要传二级指针 int **p
    return 0;
}

int *p=arr,数组名是数组首元素的地址,将首元素的地址给p,p存了arr数组首元素的地址

p是一级指针,指针变量传过去,只用将指针变量来接收即可

如果传&p,则需要传二级指针int **p.

一维数组名,一级指针,变量的地址

<4>二级指针传参

void test(int** p2)
{
    **p2 = 20;
}
int main()
{
    int a = 10;
    int* pa = &a;//pa是一级指针
    int** ppa = &pa;//ppa是二级指针
    //把二级指针进行传参
    test(ppa);
    test(&pa);
    printf("%d\n", a);
    return  0;
}
传过去的是一级指针,就用一级指针接收
传过去的是二级指针,就用二级指针接收

二级指针,一级指针的地址,指针数组的数组名 (因为指针数组是存放指针的数组)


 6.函数指针


 函数指针--存放函数地址的指针

&函数名--取到的就是函数的地址

printf("%p\n", &ADD); printf("%p\n", ADD);

&数组名!=数组名

&函数名==函数名

int main()
{
    int (*pf)(int ,int) = &ADD;//pf是函数指针变量  ADD===pf
    //int(*p)[10] = &arr;//数组指针

    int ret=(*pf)(3, 5);
    int ret = pf(3, 5);
    int ret = ADD(3, 5);

    printf("%d\n", ret);
}

有趣的代码

(* (void (*)()) 0)();
//调用0地址处的函数
//该函数无参,返回类型是void
1.void(*)()-----函数指针类型
2.(void (*)()) 0----对0进行强制类型转换,转换成函数指针类型,0当作函数的地址,
参数是无参,返回类型的是void
3.* (void (*)()) 0 ----对0地址进行解引用操作
4.(* (void (*)()) 0)()----调用0地址处的函数
调用的时候要传参
该代码是一次函数调用,调用0地址处的一个函数
首先代码中将0强制类型转换为类型为void(*)()的函数指针
然后去调用0地址的函数
《c陷阱和缺陷》
void(*signal(int,void(*)(int)))(int)

 

对于指针重定义需要将重定义的名称放在*旁边

比如 上述例子

但是对于其他只用将要重定义的放在要改的后面即可typedef unsigned int uint; 

void (*signal   (  int , void(*)(int)  )    )(int);
简化代码
void(*) (int) signal(int ,void(*)(int));//语法不支持这样写,但这样很易懂


typedef--对类型进行重定义
简化代码如下:  
对指针重定义一般放在*的旁边
typedef void(* pfun_t)(int);//对void(*) (int) 函数指针类型重命名为pfun_t
pfun_t signal(int,pfun_t);

typedef unsigned int uint;//对unsigned int命名为unint


1.signal和()先结合,说明signal是个函数名
2.signal函数的第一个参数类型是int,第二个参数类型是函数指针
//该函数指针,指向一个参数为int,返回类型是void函数
3.signal函数的返回类型也是一个函数指针
//该函数指针,指向一个参数是int,返回类型是void函数
//signal是一个函数的声明

7. 函数指针数组

里面存放的是函数指针(地址)

整型指针 int *pa;
整型数组指针 int (*pa)[]=&arr;
整型指针数组 int *arr[5]; 数组的每个元素都是int*;

整型函数指针数组 int (*parr1[10])();
整型数组
int arr[5];
int (*p1)[5]=&arr;
整型指针数组
int* arr[5];
int* (*p2)[5]=&arr; p2是指向【整型指针数组】的指针
整型函数指针数组
int (*p)(int int);函数指针
int (*p2[4])(int int);//函数指针数组
int (*(*p3)[4]))(int int)=&p2函数指针数组的地址
p3就是指向【函数指针的数组】的指针

计算器

int Add(int x, int y)
{
    return x + y;
}
int Sub(int x, int y)
{
    return x - y;
}
int Mul(int x, int y)
{
    return x * y;
}
int Dev(int x, int y)
{
    return x / y;
}
menu()
{
    printf("****************************\n");
    printf("******1.Add      2.Sub******\n");
    printf("******3.Mul      4,Dev******\n");
    printf("******    5.Exit    ********\n");
    printf("****************************\n");
}
//利用函数指针
int main()
{
    int input = 0;
    do
    {
        int x = 0;
        int y = 0;
        menu();
        int (*p[5])(int, int) = { NULL,Add,Sub,Mul,Dev };

        printf("请选择:>");
        scanf("%d", &input);

        if (input >= 1 && input <= 4)
        {
            printf("请输入俩个操作数:>");
            scanf("%d %d", &x, &y);
            int ret = (p[input])(x, y);
                printf("%d\n", ret);
        }
        else if (input == 0)
        {
            printf("退出程序\n");
        }
        else
        {
            printf("选择错误\n");
        }
    } while (input);
    return 0;
}

8.指向函数指针数组的指针

指向函数指针数组的指针是一个指针

指针指向一个数组,数组的元素都是函数指针

p是函数指针数组
int (*p[5])(int ,int)
pp是指向函数指针数组的指针
int (*(*pp)[5])(int ,int)=&p
pp=&p
*pp=p
类型是 int(*(*)[5])(int,int)

目录

指针:指针是一个变量,存放变量地址,地址唯一标识一块空间

(内存单元的编号就是地址,将地址存放到指针后指针就成了一个指针变量)

1.字符指针 char *

一道笔试题可以展示出加const和不加const的区别

ps:字符串打印

2.指针数组

<1>存放字符指针的数组

<2>存放整型指针的数组

3.数组指针

指向数组的指针(多用于二维数组)强调的是指针

 

4.&数组名VS数组名

5. 复习

5. 数组参数、指针参数

<1>一维数组传参

<2>二维数组传参

<3>一级指针传参

<4>二级指针传参

 6.函数指针

有趣的代码

7. 函数指针数组

计算器

8.指向函数指针数组的指针

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值