#8指针进阶#

1.字符指针

#include<stdio.h>
int main()
{
    char ch = 'w';
    char *pc = &ch;

    *pc = 'w';
    return 0;
}

#include<stdio.h>
int main()
{
    const char* pstr = "hello bit.";
    printf("%s\n", pstr);
    return 0;
}

这里是把一个字符串放到pstr指针变量里了吗?

上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。

存放的是首字符的地址

小练

#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;
}

相同的常量字符串初始化数组时会开辟出不同的内存块

几个指针指向同一个字符串时 几个指针会指向同一块内存

所以

str1 str2 not same

str3 str4 same

2.指针数组

int* arr1[10]; //整形指针的数组

char *arr2[4]; //一级字符指针的数组

char **arr3[5];//二级字符指针的数组

3.数组指针

int *p1[10];   指针数组

int (*p2)[10];数组指针

解释:

p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针

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

写法:

p2是指针加上*和()

后面是数组元素个数

前面是数组元素类型

=后面是数组的地址

&arr vs arr

#include<stdio.h>
int main()
{
	int arr[10]={0};
	printf("%p\n",arr);//地址 首元素的地址
	printf("%p\n",arr+1);//加4 
	printf("%p\n",&arr[0]);//首元素地址 
	printf("%p\n",&arr[0]+1);//加4 
	printf("%p\n",&arr);//整个数组的地址
	printf("%p\n",&arr+1);//跳过一个数组 加sz*4 
	//&arr-&数组名 数组名不是首元素的地址 数组名表示整个数组 
    //-&数组名 取出的是整个数组的地址 
	return 0;
}

&arr加减以数组为单位

arr  加减以元素为单位 

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
    return 0;
}

小练

int (*parr3[10])[5];

这个代码是什么意思?

//parr3[10]是一个数组

//类型是int(*)[5](去掉parr3[10])

//该数组的10个元素都是数组指针

//类型是int [5](去掉*)

//该数组指针指向的数组是5个int类型的元素

4.数组参数 指针参数

(1)一维数组传参

#include <stdio.h>
void test(int arr[])//1 
{}
void test(int arr[10])//2 
{}
void test(int *arr)//3 
{}
void test2(int *arr[20])//4 
{}
void test2(int **arr)//5 
{}
int main()
{
	int arr[10] = {0};
	int *arr2[20] = {0};
	test(arr);
	test2(arr2);
}

1:传过来数组 用数组接收 可以

2:同1 加不加元素个数都行 可以

3:可以看作传过来的是首元素地址 用指针接收 可以

4:传过来数组 用数组接收 可以

5:看成一级指针的地址传过去 用二级指针接收 可以 

(2)二维数组传参

void test(int arr[3][5])//1
{}
void test(int arr[][5])//2
{}
void test(int arr[3][])//3
{}
void test(int arr[][])//4
{}
void test(int *arr)//5
{}
void test(int* arr[5])//6 
{}
void test(int (*arr)[5])//7 
{}
void test(int **arr)//8 
{}
int main()
{
	int arr[3][5] = {0};
	test(arr);
}

1:二维数组传参 传过去保持完全一致 可以 
2:省略行是可以的 可以 
3:省略列不可以   不可以
4:全部省略       不可以
//只能省略行
5:看成地址传过去 二维数组的数组名表示成地址是第一行的地址 一维数组地址不能对应整形指针的地址 不可以
6:指针数组 是一维数组 不对应 不可以 
7:指向某一行5个int类型的元素 可以 
8:数组的地址 不是一级指针的地址 不对应 不可以 

(3)一级指针传参

对应就可以

void print(int *p)

int arr[]={1,2,3};

int *p=arr;

反过来

void test(int *p)

{}

//test函数能接收什么参数?

1类型int &a变量的地址           地址 与一级指针对应

2存放地址的一级指针                     与一级指针对应

(4)二级指针传参

#include <stdio.h>
void test(int** ptr)
{
    printf("num = %d\n", **ptr); 
}
int main()
{
    int n = 10;
    int*p = &n;
    int **pp = &p;
    test(pp);
    test(&p);
    return 0;
}

传过去一级指针的地址或者二级指针

反过来

void test(char **p)

{ }

//test函数能接收什么参数?

1一级指针变量的地址

2二级指针

3指针数组的数组名也可以

也就是一级指针变量的地址 

5.函数指针

函数是有地址的

&test和test地址是一样的(&test和test都是函数的地址)

写法:

int (*pa)(int,int)=Add;

pa是指针 加上*和()

后面是参数类型

前面是函数返回类型

=后面是函数名字

和数组指针写法类似

需要调用函数

(*pa)(参数)

//几个*都一样 *在这里没有意义 其实不写*也可以

//但是加上一个*便于代码理解

小练

(*(void (*)())0)();

代码含义是什么?

把0强制类型转换成:void(*)()函数指针类型

0就是一个函数的地址

再解引用调用0地址处的这个函数

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

代码含义是什么?

void (*signal(int , void(*)(int)))(int);(正确写法 不好理解)

可以写成

void(*)(int) signal(int,void(*)(int))(这种写法不对 *要靠近函数名 但更好理解)

signal是一个函数声明

signal函数参数有两个

第一个是int

第二个是void(*)(int)函数指针

该函数指针指向的函数类型是int 返回类型是void

signal函数的返回类型是void(*)(int)函数指针

该函数指针指向的函数类型是int 返回类型是void

第二个参数类型和返回类型一样 可以进行简化

typedef void(*pfun_t)(int);(正确写法 不好理解)

可以写成

typedef void(*)(int) pfun_t(这种写法不对 *要靠近函数名 但更好理解)

把void(*)(int)变成pfun_t

最后简化成

pfun_t signal(int,pfun_t)

6.函数指针数组

int (*parr1[10])(int,int);

parr1[10]是数组

该数组的10个元素是函数指针

该函数指针指向的函数的参数有两个

参数类型都是int

该函数指针指向的函数的返回类型是int

写法:

parr1[10]是数组

去掉数组后就为数组元素的类型

再加上函数指针类型int (*)(int,int);就可以

int (*)(int,int);

int (*parr1[10])(int,int);

小练

char* my_strcpy(char* dest,const char* src);

1写一个函数指针pf 能够指向my_strcpy

2写一个函数指针数组pfArr 能够存放4个my_strcpy函数的地址

1:char* (*pf)(char* dest,const char* src)=my_strcpy;

pf是指针

加上*和括号

后面是函数参数

前面是函数返回类型

=后面是函数名

2:char* (*pfArr[4])(char* dest,const char* src)=my_strcpy;

pfArr[4]是数组

元素类型是函数指针

加上*和括号

后面是函数参数

前面是函数返回类型

=后面是函数名

计算器

#include <stdio.h>
int add(int a, int b)
{
           return a + b;
}
int sub(int a, int b)
{
           return a - b;
}
int mul(int a, int b)
{
           return a*b;
}
int div(int a, int b)
{
           return a / b;
}
int main()
{
     int x, y;
     int input = 1;
     int ret = 0;
     int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
     while (input)
     {
          printf( "*************************\n" );
          printf( " 1:add           2:sub \n" );
          printf( " 3:mul           4:div \n" );
          printf( "*************************\n" );
          printf( "请选择:" );
          scanf( "%d", &input);
          if ((input <= 4 && input >= 1))
          {
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = (*p[input])(x, y);
          }
          else
              printf( "输入有误\n" );
          printf( "ret = %d\n", ret);
     }
     return 0;
}

解引用操作和通过下标来调用函数指针数组所对应的元素

就可以执行对应的函数功能

相当于几个类似功能函数的一个集合

也就是几个类似功能函数的一个数组

函数指针数组

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

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

指针指向一个 数组

数组的元素都是 函数指针 ;

1函数指针pfun

void (*pfun)(const char*) = test;

2函数指针的数组pfunArr

void (*pfunArr[5])(const char* str);

3指向函数指针数组pfunArr的指针ppfunArr

void (*(*ppfunArr)[5])(const char*) = &pfunArr;

 

这里ppfunArr解引用得到pfunArr

也就可以变为2的形式

8.回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

qsort快速排序(冒泡排序)

#include <stdio.h>
int int_cmp(const void * p1, const void * p2)
//比较函数 比较两个元素的地址 void*可以接收任意类型的地址 
{
	return (*( int *)p1 - *(int *) p2);
 //先强制转化成int*(比较什么类型强制转化成什么*) 再解引用操作 
}//p1<p2 返回<0的数 p1=p2返回0 p1>p2返回>0的数 
void _swap(void *p1, void * p2, int size)
{
    int i = 0;
    for (i = 0; i< size; i++)
    {
        char tmp = *((char *)p1 + i);
        *(( char *)p1 + i) = *((char *) p2 + i);
        *(( char *)p2 + i) = tmp;//强制类型转化后解引用 
    }
}
void bubble(void *base, int count , int size, int(*cmp )(void *, void *))
{
    int i = 0;
    int j = 0;
    for (i = 0; i< count - 1; i++)
    {
        for (j = 0; j<count-i-1; j++)
        {
            if (cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0)//返回后 
            {
               _swap(( char *)base + j*size, (char *)base + (j + 1)*size, size);//比较 
            }
        }
    }
}
int main()
{
    int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
    //char *arr[] = {"aaaa","dddd","cccc","bbbb"};
    int i = 0;
    bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);
    //第一个参数 待排序数组的首元素地址
	//第二个参数 待排序数组的元素个数
	//第三个参数 待排序数组的每个元素的大小 单位是字节
	//第四个参数 是函数指针 比较两个元素的所用函数的地址 这个函数使用者自己实现
	//           函数指针的两个参数是 待比较的两个元素的地址
    for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
    {
       printf( "%d ", arr[i]);
    }
    printf("\n");
    return 0;
}
    

9.指针题目练习

(1)

    int a[] = {1,2,3,4};
    printf("%d\n",sizeof(a));             //16  计算数组总大小 
    printf("%d\n",sizeof(a+0));         //4   计算首元素地址  
    printf("%d\n",sizeof(*a));            //4/8 首元素地址解引用=首元素 
    printf("%d\n",sizeof(a+1));         //4/8 第二个元素的地址 
    printf("%d\n",sizeof(a[1]));        //4   第二个元素大小 
    printf("%d\n",sizeof(&a));          //4/8 &a取出的是数组的地址 还是4/8 
    printf("%d\n",sizeof(*&a));         //16  取地址a是数组地址 数组地址解引用访问整个数组  
    printf("%d\n",sizeof(&a+1));      //4/8 取地址a是数组地址 +1跳过1个数组 还是地址 
    printf("%d\n",sizeof(&a[0]));      //4/8 第一个元素地址 
    printf("%d\n",sizeof(&a[0]+1));  //4/8 第二个元素地址


    char arr[] = {'a','b','c','d','e','f'};
    printf("%d\n", sizeof(arr));            //6        整个char 
    printf("%d\n", sizeof(arr+0));        //4/8      首元素地址 
    printf("%d\n", sizeof(*arr));          //1        arr是首元素地址 *arr就是首元素 
    printf("%d\n", sizeof(arr[1]));       //1    
    printf("%d\n", sizeof(&arr));         //4/8      数组的地址 
    printf("%d\n", sizeof(&arr+1));     //4/8      跳过一个数组的地址 
    printf("%d\n", sizeof(&arr[0]+1)); //4/8    
    printf("%d\n", strlen(arr));            //随机值   从首元素开始往后走直到碰到\0 
    printf("%d\n", strlen(arr+0));        //随机值       
    printf("%d\n", strlen(*arr));           //报错     相当于97访问 
    printf("%d\n", strlen(arr[1]));        //报错     相当于98访问 
    printf("%d\n", strlen(&arr));          //随机值     
    printf("%d\n", strlen(&arr+1));      //随机值-6 跳过了一个数组 
    printf("%d\n", strlen(&arr[0]+1));  //随机值-1 跳过了一个元素


    char arr[] = "abcdef";
    printf("%d\n", sizeof(arr));            //7 
    printf("%d\n", sizeof(arr+0));       //4/8 
    printf("%d\n", sizeof(*arr));          //1 
    printf("%d\n", sizeof(arr[1]));       //1 
    printf("%d\n", sizeof(&arr));         //4/8 
    printf("%d\n", sizeof(&arr+1));     //4/8 
    printf("%d\n", sizeof(&arr[0]+1)); //4/8 
    printf("%d\n", strlen(arr));             //6 
    printf("%d\n", strlen(arr+0));         //6 
    printf("%d\n", strlen(*arr));            //报错 strlen只能接收字符地址 
    printf("%d\n", strlen(arr[1]));         //报错 
    printf("%d\n", strlen(&arr));           //6 
    printf("%d\n", strlen(&arr+1));       //随机值 跳过一个数组 跳过了\0 
    printf("%d\n", strlen(&arr[0]+1));  //5


    char *p = "abcdef";
    printf("%d\n", sizeof(p));               //4/8     计算指针变量p的大萧 
    printf("%d\n", sizeof(p+1));           //4/8     p+1得到的是字符b的地址 
    printf("%d\n", sizeof(*p));              //1      

    p是首个字符的地址 *p就是字符串的第一个字符 


    printf("%d\n", sizeof(p[0]));           //1      

    int arr[10];arr[0]==*(arr+0)  p[0]==*(p+0)=='a'  


    printf("%d\n", sizeof(&p));           //4/8                                              
    printf("%d\n", sizeof(&p+1));       //4/8 
    printf("%d\n", sizeof(&p[0]+1));   //4/8     都是地址 
    printf("%d\n", strlen(p));              //6       从a开始走  
    printf("%d\n", strlen(p+1));          //5       从b开始走 
    printf("%d\n", strlen(*p));             //报错    *p是a 会变成ASCII码值 非法访问     
    printf("%d\n", strlen(p[0]));          //报错    p[0]=*(p+0) 同上一个 
    printf("%d\n", strlen(&p));           //随机值  将a的地址存起来 不确定  
    printf("%d\n", strlen(&p+1));       //随机值  将a的地址存起来 跳过一个地址 不确定          
    printf("%d\n", strlen(&p[0]+1));   //5  从b开始往后数


    int a[3][4] = {0};
    printf("%d\n",sizeof(a));                 //48 
    printf("%d\n",sizeof(a[0][0]));        //4    就是一个整形元素大小 不是地址 
    printf("%d\n",sizeof(a[0]));            //16   a[0]第一行作为一维数组的数组名  4*4    
    printf("%d\n",sizeof(a[0]+1));        //4/8  

    a[0]是第一行的数组名 数组名代表首元素地址 所以a[0]是第一行首元素的地址 a[0]+1是        第一行第二个元素的地址    

                                
    printf("%d\n",sizeof(*(a[0]+1)));     //4    

    第一行第二个元素的地址 解引用 就是第二个元素 


    printf("%d\n",sizeof(a+1));             //4/8  

    a是二维数组的数组名 没有sizeof 也没有& 所有a是首元素地址 而二维数组的首元素是第        一行 a是第一行的地址 第二行地址 


    printf("%d\n",sizeof(*(a+1)));         //16   a+1是第二行地址 解引用 
    printf("%d\n",sizeof(&a[0]+1));      //4/8  

    第二行的地址 取第一行地址 加1就是第二行地址

 
    printf("%d\n",sizeof(*(&a[0]+1)));  //16   第二行的地址 解引用 
    printf("%d\n",sizeof(*a));                //16  

    a是整个数组数组名 数组名表示首元素地址 也就是第一行的地址 解引用后就为第一行 
    printf("%d\n",sizeof(a[3]));             //16   不会真的访问第一行 与a[0]相同 

(2)

int main()

{    

        int a[5] = { 1, 2, 3, 4, 5 };    

        int *ptr = (int *)(&a + 1);    

        printf( "%d,%d", *(a + 1), *(ptr - 1));    

        return 0;

} //程序的结果是什么?

2 5

*(a+1)表示第二个元素

&a+1直接跳过一个数组

*(ptr-1)表示最后一个数组

(3)

struct Test

{

        int Num;

        char *pcName;

        short sDate;

        char cha[2];

        short sBa[4];

}*p;

//假设p 的值为0x100000。 如下表表达式的值分别为多少?

//已知,结构体Test类型的变量大小是20个字节

int main()

{

        printf("%p\n", p + 0x1);//1

        printf("%p\n", (unsigned long)p + 0x1);//2

        printf("%p\n", (unsigned int*)p + 0x1);//3

        return 0;

}

1 p是整个结构体 加1直接跳20个字节 //0x100014//16进制

2 p是整数            加1就是加1              //0x100001

3 p是整型指针     加1跳过4个字节       //0x100004

(4)

#include<stdio.h> 
int main()
{
    int a[4] = { 1, 2, 3, 4 };
    int *ptr1 = (int *)(&a + 1);
    int *ptr2 = (int *)((int)a + 1);
    printf( "%x,%x", ptr1[-1], *ptr2);
    return 0;
}

ptr1[-1]==*(ptr+(-1))==*(ptr-1)打印4
比如说地址是0x000005
强制转化成int 变为5 5+1=6 6再取地址0x000006
但是0x00005和0x000006实际上差1个字节
01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00//每两个数一个字节 
ptr2会指向01后面 然后读取4个字节00 00 00 02

再倒过来(倒过来存进去的) 02 00 00 00 02前面的0会省略
因此打印4,2000000

(5)

#include <stdio.h>
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int *p;
    p = a[0];
    printf( "%d", p[0]);
    return 0;
}

逗号表达式
1 3 5
1 0
3 0

5 0
p存的1的地址

p[0]==*(p+0)就是1

(6)

#include <stdio.h>
int main()
{
    int a[5][5];
    int(*p)[4];
    p = a;
    printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    return 0;
}
    a[0]       a[1]       a[2]      a[3]        a[4]
[][][][][] [][][][][] [][][][][] [][][][][] [][][][][]
a的值赋给p
p指向首元素地址
p整型 有4个元素 加1跳4个元素
p[4][2]==*(*(p+4)+2)
*(p+4)从第1个元素开始跳16个元素
解引用实际上拿到了4个元素
加2再解引用 1+16+2=19 实际上得到了第19个元素
&p[4][2]第19个元素  &a[4][2]第23个元素 
地址相减为中间元素个数
-4
10000000000000000000000000000100 -4的原码
11111111111111111111111111111011 -4的反码 符号位不变 按位取反
11111111111111111111111111111100 -4的补码 补码==反码+1
4个1是一个f 1100是12 是c
最后打印0xFF FF FF FC 和 -4 

(7)

#include <stdio.h>
int main()
{
    int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int *ptr1 = (int *)(&aa + 1);
    int *ptr2 = (int *)(*(aa + 1));
    printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
    return 0;
}

&aa+1跳过整个二维数组
-1解引用后就是访问最后一个元素 10 
*(aa+1)==aa[1]第2行元素 
-1解引用就是第一最后一个元素 5
最后打印10 5

(8)

#include <stdio.h>
int main()
{
    char *a[] = {"work","at","alibaba"};
    char**pa = a;
    pa++;
    printf("%s\n", *pa);
    return 0;
}

*(pa+1)直接访问*a的第2个元素

打印at

(9)

int main()
{
    char *c[] = {"ENTER","NEW","POINT","FIRST"};
    char**cp[] = {c+3,c+2,c+1,c};
    char***cpp = cp;
    printf("%s\n", **++cpp);
    printf("%s\n", *--*++cpp+3);
    printf("%s\n", *cpp[-2]+3);
    printf("%s\n", cpp[-1][-1]+1);
    return 0;
}

c

[ ENTER ] [ NEW ] [ POINT ] [ FIRST ] 

cp

[ c+3 ] [ c+2 ] [ c+1 ] [ c ]

char***cpp指向c+3的前面

1:**++cpp

++cpp cpp到c+2的前面

解引用得到c+2

c+2是POINT首字符P的地址

再解引用 以字符串打印得到POINT

2:*--*++cpp+3

++cpp cpp到c+1的前面

解引用得到c+1

--后得到c

c是ENTER首字符E的地址

解引用得到E

+3就访问第二个E

字符串打印ER

3:*cpp[-2]+3

cpp[-2]==*(cpp-2)

*cpp[-2]+3==**(cpp-2)+3

cpp-2到c+3的前面

解引用得到c+3

c+3是字符串FIRST首字符F的地址

再解引用得到F

最后加三得到S

字符串打印得到ST

4:cpp[-1][-1]+1

cpp[-1][-1]==*(*(cpp-1)-1)

cpp-1到c+2的前面

解引用得到c+2

*(c+2-1)==*(c+1)

c+1是字符串NEW首字符N的地址

解引用得到N

最后加1得到E

字符串打印得到W

最后打印结果:

POINT

ER
ST
EW

 #8指针进阶#完

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的小恒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值