由浅入深地学习指针(学习指针必看)

目录 

指针初阶

指针定义

指针和指针类型

c语言的整型指针解引用与整型变量的区别

内存

指针和指针类型

指针类型的意义

野指针

规避野指针

指针运算

指针和数组

二级指针

指针数组

指针进阶

指针的概念再提起

字符指针

《剑指offer》

字符串常量:

指针数组

数组指针

数组名是数组首元素地址

数组传参和指针传参

一维数组传参

二维数组传参

一级指针传参

二级指针传参

加深印象

函数指针

有趣的代码

推荐书籍《C陷阱与缺陷》

函数指针数组

模拟计算器

指向函数指针数组的指针

回调函数

冒泡排序

指针和数组结合的冒泡排序

模仿qsort实现冒泡排序通用算法

快速排序改良


指针初阶内容

指针定义

指针从根本上来看是一个值为内存地址的变量或者数据对象,利用地址,他的值直接指向(points to)存在电脑存储器中另一个地方的值。指针变量的值是地址。例如一个指针的变量名为p,那么我们可以通过 p = &a;将a的地址赋值给p,从而我们可以通过对p进行操作,对a进行修改等操作。地址形象地称为指针。

编号 - 地址 - 指针,they are same

要创建指针变量,首先要先生命指针变量的类型。例如:

int *a = NULL;//指针的类型是int *,指针所指向的是int类型的.
short *ps = NULL;
long *pc = NULL;
char *b = NULL;//指针的类型是char *,指针所指向的是char类型的.
double *c = NULL;//指针的类型是double *,指针所指向的是double类型的.
int **ptr;//指针的类型是int **,指针所指向的是int*类型的.
#include<stdio.h>
int main()
{
    int a = 10;//a占4个字节;
    int * pa = &a;//拿到的是a的4个字节中第一个字节的地址;
    *pa = 20;
    return 0;
}

指针是用来存放地址的,地址是唯一标示一块地址空间的。

指针的大小在32位平台是4个字节,在6位平台是8个字节。

指针和指针类型

#include<stdio.h>
int main()
{
    int* pa;
    char* pc;
    float * pf;
    printf("%d\n",sizeof(pa));
    printf("%d\n",sizeof(pc));
    printf("%d\n",sizeof(pf));
    return 0;
}
8
8
8

        指针类型决定了指针解引用的权限有多大

        引入:简单介绍一下整型变量,指针,引用符和解引用符

        定义一个整型变量a: int a;

        如果使用引用符号&,&a则是整型变量a的地址,也就是一个整型指针。

        定义一个整型指针b: int *b;b=&a;(这里把变量a的地址,也就是指针&a赋给了指针b)

        如果对一个指针使用解引用符号*,如b,便表示整型指针b存储的地址所指向的整型变量。

指针类型的意义

#include<stdio.h>
int main()
{
    int a = 0x11223344;
    char *pc = &a;
    //0 1 2 3 4 5 6 7 8 9 a b c d e f
    //11111111
    //    8421
    //int *pa = &a;
    //*pa = 0;
    //printf("%d\n",*pa);
    return 0;
}

c语言的整型指针解引用与整型变量的区别

#include<stdio.h>
int main()
{
    int a = 0;
    a++;
    printf("%d\n",a);
    int *b;
    b = &a;
    a=0;
    *b++;
    printf("%d\n",a);
    return 0;
}
输出:1
     0

        同样开始的时候赋予变量0值,直接对变量本身自增成功了。

        但是拿变量的指针的解引用来自增,结果变量并没有改变。

#include<stdio.h>
int main()
{
    int a;
    int *b;
    b = &a;
    a=0;
    (*b)++;
    printf("%d\n",a);
    return 0;
}
1

把代码中的指针的整型指针的解引用加上括号后再自增,结果自增成功了。

经过更多的实验,得出结论:内存分布

C中,,和++运算符同时存在时,后者比前者优先级更高,电脑会把*b++解读成 *(b++),即把b存的地址+1,再解应用,实际上解引用得到的不是本来指针所指的整型变量。

总结:在使用整型变量的指针时,(其实不止是对整型变量),要注意运算符的优先级。解引用时要注意解引用的指针是否发生了变化。

内存

内存是电脑上特别重要的存储器,计算机中所有程序的运行都是在内存中进行的。 所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。 为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。

0
1
2
3
4

                给每个格子编了号

                32位 - 32根地址线 - 物理线 - 通电 - 1/0

                64位 - 64根地址线 - 物理线 - 通电 - 1/0

                电信号转换成数字信号:0和1组成的二进制序列

                00000000000000000000000000000000---内存编号

                11111111111111111111111111111111---内存编号

                2^32个编号

                假设:一个内存单元是1 bit

                2^32 bit = 512 MB =0.5 GB

#include<stdio.h>
int main()
{
    int a = 10;//a在内存重要分配的空间 - 4个字节
    printf("%p\n",&a);
    int* pa = &a;//pa是用来存放地址的,在C语言中是指针变量
    //* 说明 pa是指针变量
    //int 说明pa执行的对象是int类型的
    printf("%p\n",pa);
    char ch = 'w';
    printf("%p\n",ch);
    char* pc = &ch;
    printf("%p\n",*pc);
    return 0;
}
000000000061FE0C
000000000061FE0C
0000000000000077
0000000000000077

        就可以输出a的内存地址,比如说我的a的地址就是000000000061FE1C(一串十六进制数)

int main()
{
    int a = 10;
    int* pa = &a;
    *pa = 20;//* 叫做解引用操作 *pa就是通过pa里边的地址,找到a
    //就是用*pa赋一个值改变原来的值
    printf("%d\n",a);
    return 0;
}
输出结果:20
int main()
{
    printf("%d\n",sizeof(char*));
    printf("%d\n",sizeof(short*));
    printf("%d\n",sizeof(int*));
    printf("%d\n",sizeof(long*));
    printf("%d\n",sizeof(long long*));
    return 0;
}
输出:
8
8
8
8
8

指针和指针类型

#include<stdio.h>
int main()
{
    int a = 0x11223344;
    int*pa = &a;
    printf("%p\n",a);
    printf("%p",*pa);
    *pa = 0;
    printf("%p",a);
    printf("%p",*pa);
    return 0;
}
0000000011223344
0000000011223344
0000000000000000
0000000000000000

                char* pc 不能来复制 int型 的地址

#include<stdio.h>
int main()
{
    int arr[10] = {0};
    int *p = arr;
    //char* pc = arr;//error,此处报错
    printf("%p\n",arr);
    printf("%p\n",p);
    printf("%p\n",p+1);
    //printf("%p\n",*pc);
    return 0;
}
#include<stdio.h>
int main()
{
    int arr[10] = {0};
    char ch[10] = {'i'};
    int *p = arr;
    char* pc = ch;
    printf("%p\n",arr);
    printf("%p\n",p);
    printf("%p\n",p+1);
    printf("%p\n",ch);
    printf("%p\n",pc);
    printf("%p",pc+1);
    return 0;
}
000000000061FDE0
000000000061FDE0
000000000061FDE4
000000000061FDD6
000000000061FDD6
000000000061FDD7

指针类型的意义

        1.指针类型决定:指针解引用的权限有多大

        2.指针类型决定:指针走一步,能走多长(步长)

        字符型指针加一,指针加一

        整型指针加一,指针加四

#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    int *p = arr;
    for(int i = 0;i < 10; i++)
    {
        *(p+i) = 1;//p+i 其实是下标为i的地址//而char类型则是一个字节一个字节访问
    }
    return 0;
}

野指针

        概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

        1.解释:指针指向的位置不可知

        2.三个成因:

         指针未初始化 ​           指针越界访问 ​           指针指向的空间释放(动态内存开辟)

        1.指针未初始化

#include<stdio.h>
int main()
{
    //这里的p就是一个野指针
    int *p;//p是一个局部的指针变量,局部变量不初始化的话,默认是随机值
    *p = 20;//非法访问内存
    return 0;
}

        2.指针越界

#include<stdio.h>
int main()
{
    int arr[10] = {0};
    int* p = arr;
    int i = 0;
    for(i = 0;i <= 10; i++)
    {
        *p = i;
        p++;//当i= 10,时,造成的第十一位出现,造成越界
    }
    return 0;
}

        3.指针指向的空间释放(动态内存开辟)

#include<stdio.h>
int* test()
{
    int a = 10;
    return &a;//使用后生命周期结束,归还操作系统
}
int main()
{
    int* p = test();
    *p = 20;//非法访问内存
    return 0;
}

        局部变量a出函数后销毁,本属于a的空间不是当前程序的,已经还给操作系统,可能会被分配储存其他内容

        访问一块已经被释放的空间,则会产生野指针

        像这种访问临时变量的地址,都存在问题

        除非出了范围不销毁,比如初始 static 变量

规避野指针

        1.指针初始化

        2.小心指针越界

        3.指针指向空间释放,及时置NULL

        4.指针使用之前检查有效性(NULL不能访问)

#include<stdio.h>
int main()
{
    //当前不知道p应该初始化为何地址的时候,直接初始化为NULL
    int* p = NULL;
    //明确知道初始化的值
    int a = 10;
    int* ptr = &a;
    //C语言本身是不会检查数据的越界行为的。
    int* p1 = NULL;
    if(p != NULL)
        *p1 = 10;
    return 0;
}

指针运算

        ·指针+-整数 ·指针-指针 ·指针的关系运算

 1.指针+-整数

#include<stdio.h>
#define N_VALUES 5
int main()
{
    float values[N_VALUES];
    float *vp;
    //指针+-整数:指针的关系运算
    for(vp = &values[0];vp < &values[N_VALUES];)
    {
        *vp++ = 0;
    }//成功循环一遍
    return 0;
}

        相应的,可以倒着进行循环。

#include<stdio.h>
#define N_VALUES 5
int main()
{
    float values[N_VALUES];
    float *vp;
    //指针+-整数:指针的关系运算
    for(vp = &values[N_VALUES - 1];vp >= &values[0];)
    {
        *--vp = 0;
    }
    return 0;
}
#include<stdio.h>
#define N_VALUES 5
int main()
{
    float values[N_VALUES];
    float *vp;
    //指针+-整数:指针的关系运算
    for(vp = &values[N_VALUES - 1];vp >= &values[0];vp--)
    {
        *vp = 0;
    }
    return 0;
}

        实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。

2.指针-指针

#include<stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    printf("%d\n",&arr[9] - &arr[0]);//指针-指针得到的是两个指针之间的元素个数
    return 0;
}
9
#include<stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    char c[5];
    printf("%d\n",&arr[9] - &c[0]);//error
    return 0;
}
        //虽然可能有值算出,但是因为指针类型不同,所以是无效的

        指针和指针相减的前提是两个指针指向同一块空间。

3.指针的关系运算

#include<stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = arr;
    int *pend = arr + 9;
    while(p <= pend)
    {
        printf("%d\n",*p);
        p++;
    }
    return 0;
}

4.strlen函数再回顾

4.1.库函数法

#include<stdio.h>
#include<string.h>
int main()
{
    char arr[] = {};
    scanf("%s",&arr);
    //比如说['a']['b']['c']['d']
    printf("%d\n",strlen(arr));
    return 0;
}//直接调用库函数

4.2.函数计数器法

#include<stdio.h>
#include<string.h>
int my_strlen(char* str)
{
    int count = 0;
    while(*str != '\0')
    {
        count++;
        str++;
    }
    return count;
}
int main()
{
    char arr[] = {};
    scanf("%s",&arr);
    printf("%d\n",my_strlen(arr));
    return 0;
}//计数器方法

4.3.递归法

#include<stdio.h>
int my_strlen(char* str)
{
    if(*str != '\0')
    {
        return 1 + my_strlen(str + 1 );
    }
    else return 0;
}
int main()
{
    char arr[] = {};
    scanf("%s",&arr);
    printf("%d\n",my_strlen(arr));
    return 0;
}//递归算法

4.4.指针-指针法

#include<stdio.h>
int my_strlen(char* str)
{
    char* start = str;
    while(*str != '\0')
    {
        str++;
    }
    return str - start;
}
int main()
{
    char arr[] = {};
    scanf("%s",&arr);
    printf("%d\n",my_strlen(arr));
    return 0;
}

指针和数组

#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    printf("%p\n",arr);//数组名是数组首元素的首地址
    printf("%p\n",&arr[0]);
    printf("%p",&arr[1]);
    return 0;
}
000000000061FDF0
000000000061FDF0
000000000061FDF4

                数组遍历

#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    int* p = arr;
    for(int i = 0;i < 10; i++)
    {
        printf("%p <==> %p\n",&arr[i],p + i);
    }
    return 0;
}
000000000061FDE0 <==> 000000000061FDE0
000000000061FDE4 <==> 000000000061FDE4
000000000061FDE8 <==> 000000000061FDE8
000000000061FDEC <==> 000000000061FDEC
000000000061FDF0 <==> 000000000061FDF0
000000000061FDF4 <==> 000000000061FDF4
000000000061FDF8 <==> 000000000061FDF8
000000000061FDFC <==> 000000000061FDFC
000000000061FE00 <==> 000000000061FE00
000000000061FE04 <==> 000000000061FE04
#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    int* p = arr;
    for(int i = 0;i < 10; i++)
    {
        *(p + i) = i;
    }
    for(int i = 0;i < 10; i++)
    {
        printf("%d ",*(p+i));
    }
    return 0;
}
0 1 2 3 4 5 6 7 8 9

                当然也可以通过如下方式进行操作

#include<stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = arr;//数组名
    printf("%d\n",arr[2]);
    printf("%d\n",p[2]);//p[2]-->*(p + 2)
    printf("%d\n",*(p+2));
    printf("%d\n",2[arr]);//其效果实际上就是arr[2]-->*(arr + 2)
    return 0;
}
3
3
3
3

二级指针

#include<stdio.h>
int main()
{
    int a = 10;
    int* pa = &a;//pa是指针变量,一级指针
    //ppa就是一个二级指针变量
    int** ppa = &pa;//pa也是个变量,&pa取出pa在内存中的起始地址
    int*** pppa = &ppa;
    printf("%p\n",a);
    printf("%p\n",*pa);
    printf("%p\n",**ppa);
    printf("%p\n",***pppa);
    return 0;
}
000000000000000A
000000000000000A
000000000000000A
000000000000000A

指针数组

        指针数组本质上是数组

#include<stdio.h>
int main()
{
    int arr[10];//整形数组 - 存放整型的数组就是整型数组
    char ch[5];//字符数组 - 存放字符的数组就是字符数组
    //指针数组 - 存放指针的数组
    int* parr[5];//整型指针的数组
    char* pch[5];//字符指针的数组
    return 0;
}

代码三种境界:

1.看代码就是代码

2.看代码就是内存

3.看代码还是代码

指针进阶

指针的概念再提起

1.指针就是一个变量,用来存放地址,地址唯一标识一块内存空间。

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

3.指针是有类型的,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。

4.指针的运算。

指针:通过地址能找到所需的变量单元,因此说,地址指向该变量单元,将地址形象化的称为指针;可理解为地址就是指针; ​ 指针变量:存放指针(地址)的变量; ​ 指针的作用:通过指针间接访问内存; ​ 指针变量的定义:类型名 * 指针变量名 ​ 一个变量的指针的含义包含两个方面,一是存储单元的纯地址,二是指向存储单元的数据类型,故定义指针变量时要指定基类型,用来指定此指针变量可以指向的变量的类型; 注释:* 取内容运算符,间接访问运算符 ,*p代表指针变量p所指向的对象;& 取地址运算符,&a是变量a的地址;

# include <stdio.h>
int main()
{
    int a;
    a = 10;
    int *p;
    p = &a;
    printf("a address is %p\n",p);//这里得到的地址是0x0060ff00,不同编译器得到的地址不一样
    int *p2 = (int *)0x0060ff00;//将指针变量p2指向这块内存地址
    *p2 =20;
    printf("在内存的%p的位置,存放值是%d\n",p2,*p2);
}//打印输出:在内存的0x0060ff00的位置,存放值是20

字符指针

#include <stdio.h>
int main()
{
    char ch = 'q';
    char* pc = &ch;
    *pc = 'p';
    printf("%c",ch);
    return 0;
}
输出结果:p

指针变量可以指向字符串

#include<stdio.h>
int main()
{
	char*ps = "hello bit.";//把字符串的首字符h的地址放入*ps之中
	printf("%c\n",*ps);
	return 0;
}
输出结果:h

 也可以使用指向字符串

#include<stdio.h>
int main()
{
	char*ps = "hello bit.";
	printf("%s\n",ps);
	return 0;
}
输出结果:hello bit.

 虽然我们此处的字符串内容是一致的,但是指针指向的字符串和数组的字符串是有区别的,区别在于其存储位置,地址是有差别的,这个在下面有详细说明。

#include<stdio.h>
int main()
{
    char* ps = "hello bit.";
    char arr[] = "hello bit.";
    printf("%s\n", ps);
    printf("%s\n", arr);
    printf("%p\n",ps);
    printf("%p\n",arr);
    return 0;
}
输出结果:hello bit.
        hello bit.
        0000000000404000
        000000000061FE0D//地址实际上不相同的

1.习题-判断字符串是否相等

#include<stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    char* str3 = "hello bit.";
    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 and str2 are not same
        str3 and str4 are same

题目解释:str1[]、str2[]是两个数组,地址是不一样的;

str3、str4是两个指针变量,两个里面放的都是同一个h(常量字符串不能改,所以两者共用一份字符 串,所以是一样的)

《剑指offer》

#include<stdio.h>
int main()
{
    char*ps = "hello bit.";
    char arr[] = "hello bit.";
    printf("%s\n", ps);
    printf("%s\n", arr);
    return 0;
}
代码无法输出 err

常量字符串是不能改的

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

const char常量字符串

字符串常量

字符常量: 由一对单引号括起来的单个字符,如'a', 'B';

字符串常量: 由一对双引号括起来的多个字符的序列,如"a",“I Love u”;

字符串常量: 由一对双引号括起来的多个字符的序列,如"a",“I Love u”

字符串常量是不能改的,所以只需要存储一份即可。

指针数组

#include<stdio.h>
int main()
{
    //指针数组
    //数组 - 数组中存放的是指针(地址)
    int* arr[4];
    int a = 10;
    int b = 20;
    int c = 30;
    int* arr[3] = { &a, &b, &c };//没有应用场景的
    int i = 0;
    for(i = 0;i < 3; i++)
    {
        printf("%d\n",*arr[i]);
    }
    return 0;
}
#include<stdio.h>
int main()
{
    int a[5] = { 1,2,3,4,5 };
    int b[] = { 2,3,4,5,6 };
    int c[] = { 3,4,5,6,7 };
    
    int* arr[3] = {a,b,c};//arr[]数组又涵盖了上面三个数组;把a,b,c三个数组的首元素地址放入arr[]中去了。
    int i = 0;
    for(i = 0;i < 3; i++)
    {
        int j = 0;
        for(j = 0;j < 5;j++)
        {
            printf("%d ",*(arr[i] + j));
        }
        printf("\n");
    }
    return 0;
}
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
#include<stdio.h>
int main()
{
    int a[5] = { 1,2,3,4,5 };
    int b[] = { 2,3,4,5,6 };
    int c[] = { 3,4,5,6,7 };
    
    int* arr[3] = {a,b,c};
    int i = 0;
    for(i = 0;i < 3; i++)
    {
        int j = 0;
        for(j = 0;j < 5;j++)
        {
            printf("%d ",(arr[i][j]));
        }
        printf("\n");
    }
    return 0;
}
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7

arr[i] [j]和arr[i]+j等价,模拟了二维数组,但不是真正的二维数组,因为二维数组是连续的

arr
int* 1 2 3 4 5 ——a
int* 2 3 4 5 6 ——b
int* 3 4 5 6 7 ——c

数组指针

数组指针是一种指针。

#include<stdio.h>
int main()
{
    int a = 10;
    int* pa = &a;//整型指针
    char ch = 'w';
    char* pc = &ch;//字符指针
    
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    //arr - 数组名是首元素的地址 - arr[0]的地址
    int (*parr)[10] = &arr;//取出整个数组的地址
    //parr就是一个数组指针
    
    double* d[5];
    double* (*pd)[5] = &d;//pd就是一个数组指针
    return 0;
}

1.指针数组和数组指针之间的区别

#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    int* p1 = arr;
    int (*p2)[10] = &arr;
    printf("%p\n",arr);
    printf("%p\n",&arr);//两者指向同一个地址,但是两者类型不同
    
    printf("%p\n",p1);
    printf("%p\n",p1+1);
    printf("%p\n",p2);
    printf("%p\n",p2+1);
    return 0;
}
000000000061FDE0
000000000061FDE0
000000000061FDE0
000000000061FDE4
000000000061FDE0
000000000061FE08

类似与下面的例子:

char c = 'a';//97
int c = 97;//两者值是一样的,但是类型不同

数组名是数组首元素地址

但是有两个例外

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

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

2.一维数组的数组指针应用

#include<stdio.h>
int main()
{
    int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };//实际上一维数组不适合取数组指针
    //int* pa = arr;
    //for(int i = 0;i < 10; i++)
    //{
        //printf("%d ",*pa+i);
    //}
    int (*pa)[10] = &arr;
    for(int i = 0;i < 10; i++)
    {
        printf("%d ",*((*pa) + i));//这样写实际上是非常别扭的
    }
    return 0;
}

3.二维数组的数组指针应用

原先的写法:

#include<stdio.h>
void print(int arr[3][5], int r, int c)
{
    for(int i = 0;i < r; i++)
    {
        for(int j = 0;j < c; j++)
        {
            printf("%d ",arr[i][j]);
        }
        printf("\n");
    }
}
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;
}

数组指针写法:

#include<stdio.h>
void print(int(*p)[5], int r, int c)
{
    int i = 0;
    int j = 0;
    for(i = 0;i < r; i++)
    {
        for(j = 0;j < c; j++)
        {
            printf("%d ",*(*(p+i) + j));
        }
        printf("\n");
    }
}
​
int main()
{                       arr[0]      arr[1]      arr[2]
    int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
    print(arr, 3, 5);//arr数组名,传参数组的首元素地址
    return 0;
}
//二维数组的数组名表示首元素的地址
//二维数组的首元素是:第一行
int arr[10];//整型数组
int *parr1[10];//整型指针的数组
int (*parr2)[10];//整型数组指针,该指针能够指向一个数组,数组10个元素,每个元素的类型是int
int (*parr3[10])[5];//整型数组指针的数组 parr3是一个存放数组指针的数组 该数组能够存放10个数组指针,每个数组指针能够指向一个数组,数组5个元素,每个元素是int类型

数组传参和指针传参

1.一维数组传参

#include<stdio.h>
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};//存放int*的数组
    test(arr);
    test2(arr2);
    return 0;
}

2.二维数组传参

#include<stdio.h>
void test(int arr[3][5])
{}
void test(int arr[][5])
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字
//因为对一个二维数组而言,可以不知道有多少行,但必须知道一行有几个元素
//这样才方便运算
void test(int (*parr)[5])
{}
int main()
{
    int arr[3][5];
    test(arr);
    return 0;
}

3.一级指针传参

#include<stdio.h>
void print(int* ptr,int sz)
{
    for(int i = 0;i < sz; i++)
    {
        printf("%d ",*(ptr + i));
    }
}
​
void test(char* p)
{
    printf("%c",*p);
}
​
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = arr;
    int sz = sizeof(arr) / sizeof(arr[0]);
    //p是一级指针
    print(p,sz);
    char ch = 'w';
    char* pch = &ch;
    test(&ch);
    test(pch);
    return 0;
}

4.二级指针传参

#include<stdio.h>
void test(int** ptr)
{
    **ptr = 20;
}
int main()
{
    int a = 10;
    int* pa = &a;//pa是一级指针
    int** ppa = &pa;//ppa是二级指针
    //把二级指针进行传参
    test(ppa);
    test(&pa);//传一级指针变量的地址
    int* arr[10] = {0};
    test(arr);//传存放一级指针的数组
    printf("%d\n",a);//输出20,
    return 0;
}
20

加深印象

一级指针:int* p;——整型指针,指向整型的指针 char* pc;——字符指针,指向字符的指针 void* pv;—— 无类型指针

二级指针:char** p; int** p;

数组指针:指向数组的指针 —— int(*p)[4];

数组:

一维数组 二维数组 指针数组——存放指针的数组

函数指针

函数指针:指向函数的指针,存放函数地址的指针

#include<stdio.h>
int Add(int x,int y)
{
    return x + y;
}
int main()
{
    int a = 10;
    int* pa = &a;
    char ch = 'w';
    char* pc = &ch;
    int arr[10] = {0};
    int (*parr)[10] = &arr;//取出数组的地址
    //parr是指向数组的指针 - 存放的是数组的地址
    
    //函数指针 - 存放函数地址的指针
    //&函数名 - 取到的就是函数的地址
    //pf就是一个函数指针变量
    int (*pf)(int,int) = &Add;
    printf("%p",&Add);
    printf("%p",Add);//两者相等
    return 0;
}
0000000000401550
0000000000401550

数组名 != &数组名,这两者不等价。数组名是数组首元素的地址,&数组名则是数组所有元素的地址。

函数名 == &函数名,这两者都是函数的地址名

#include<stdio.h>
void test(char* str)
{}
int main()
{
    void(*pt)(char*) = &test;
    return 0;
}

函数指针传参

#include<stdio.h>
int Add(int x,int y)
{
    return x + y;
}
int main()
{
    //函数指针 - 存放函数地址的指针
    //&函数名 - 取到的就是函数的地址
    //pf就是一个函数指针变量
    int (*pf)(int,int) = &Add;//int (*pf)(int,int) = Add;这样也是可以的
    //也就是说Add等价于pf
    //int ret = pf(3,5);等价于int ret = Add(3,5);这么写也是对的
    int ret = (*pf)(3,5);
    //int ret = *pf(3,5);这样是不行的
    printf("%d\n",ret);
    return 0;
}
8//说明程序正确
int ret = pf(3,5);
​
int ret = Add(3,5);
​
int ret = (*pf)(3,5);

以上三种写法都是等价的,正确的

int ret = *pf(3,5);这个是例外,是错误的

有趣的代码

//代码2
((*(void)(*)())0)();
//void(*)() - 函数指针类型
//(void(*)())0 - 对0进行强制类型转换,被解释为一个函数地址
//*(void(*)())0 - 对0地址处进行解引用操作
//(*(void(*)())0)() - 调用0地址处函数
//代码2
void (*signal(int ,void(*)(int)))(int);
//函数指针类型嵌套了一个函数指针类型
//signal和()先结合,说明signal是函数名
//signal函数的第一个参数类型是int,第二个参数类型是函数指针
//该函数指针,指向一个参数为int,返回类型是void的函数
//signal是一个函数的声明

代码1: 0前面的是一个函数指针,0被强制转换成函数指针类型。实质是调用0地址处放的函数。该函数无参,返回类型是void。

代码2:可以理解为void (*) (int) signal(int ,void ( *)(int));//但是代码不允许这么写,但是可以这么理解

typedef —— 对类型进行重定义、重命名

typedef void(* pfun_t)(int); //对void(*)(int)的函数指针类型重命名为pfun_t

//typedef unsigned int uint;

pfun_t signal(int, pfun_t);

推荐书籍《C陷阱与缺陷》

函数指针数组

整型指针数组 int* arr[5];

函数指针数组

#include<stdio.h>
int Add(int x,int y)
{
    return x + y;
}
int Sub(int x,int y)
{
    return x - y;
}
int main()
{
    int (*pf1)(int,int) = Add;
    int (*pf2)(int,int) = Sub;//返回类型参数是一样的
    int (*pfArr[2])(int,int) = {Add, Sub};//pfArr就是函数指针数组
    return 0;
}

1.模拟计算器

#include<stdio.h>
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 Div(int x,int y)
{
    return x / y;
}
void menu()
{
    printf("*******************************\n");
    printf("*********1.add     2.sub*******\n");
    printf("*********3.mul     4.div*******\n");
    printf("**************0.exit***********\n");
    printf("*******************************\n");
}
int main()
{
    int input = 0;
    //计算器-计算整型变量的加减乘除
    do
    {
        menu();
        int x;
        int y;
        int ret = 0;
        printf("请选择:>");
        scanf("%d",&input);
        switch(input)
        {
            case 1: 
                printf("请输入两个操作数:>\n");
                scanf("%d %d",&x,&y);
                printf("ret = %d\n",ret);
                ret = Add(x,y);
                break;
            case 2: 
                printf("请输入两个操作数:>\n");
                scanf("%d %d",&x,&y);
                printf("ret = %d\n",ret);
                ret = Sub(x,y);
                break;
            case 3: 
                printf("请输入两个操作数:>\n");
                scanf("%d %d",&x,&y);
                printf("ret = %d\n",ret);
                ret = Mul(x,y);
                break;
            case 4: 
                printf("请输入两个操作数:>\n");
                scanf("%d %d",&x,&y);
                printf("ret = %d\n",ret);
                ret = Div(x,y);
                break;
            case 0:
                printf("退出程序\n");
                break;
            default:
                printf("选择错误,重新选择");
                break;
        }
        
    }while(input);
    int (*pfArr[2])(int,int) = {Add, Sub};//pfArr就是函数指针数组
    return 0;
}

这时候可以用函数指针来进行模拟计算器

#include<stdio.h>
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 Div(int x,int y)
{
    return x / y;
}
void menu()
{
    printf("*******************************\n");
    printf("*********1.add     2.sub*******\n");
    printf("*********3.mul     4.div*******\n");
    printf("**************0.exit***********\n");
    printf("*******************************\n");
}
int main()
{
    int input = 0;
    //计算器-计算整型变量的加减乘除
    do
    {     
        menu();
        //pfArr就是函数指针数组
        //转移表 - 《C和指针》
        int (*pfArr[5])(int ,int) = {NULL,Add,Sub,Mul,Div};
        int x = 0;
        int y = 0;
        int ret = 0;
        printf("请选择:>");
        scanf("%d",&input);
        if(input >= 1&&input <= 4)
        {
            printf("请输入两个操作数:>\n");
            scanf("%d %d",&x,&y);
            ret = (pfArr[input])(x,y);
            printf("ret = %d",ret);
        }
        else if(input == 0)
        {
            printf("退出程序\n");
            break;
        }
        else
        {
            printf("选择错误,请重新选择\n");
        }
    }while(input);
    return 0;
}

指向函数指针数组的指针

函数指针数组本质是数组,取出的是函数指针数组的地址。

整型指针的数组

int* arr[5];
int* (*p2)[5] = &arr;
//p2是指向整型指针数组的指针
函数指针数组 &函数指针数组

int(*p)(int,int);//函数指针
int(* p2[4])(int,int);//函数指针数组
int(* (*p3)[4])(int,int) = &p2;//取出的是函数指针数组的地址
//p3就是一个指向函数指针数组的指针

理解以下的代码

void test(const char* str)
{
    printf("%s\n",str);
}
int main()
{
    //函数指针pfun
    void (*pfun)(const char*) = test;
    //函数指针的数组pfunArr
    void (*pfunArr[5])(const char* str);
    pfunArr[0] = test;
    //指向函数指针数组pfunArr的指针ppfunArr
    void (*(*ppfunArr)[10])(const char*) = &pfunArr;
    return 0;
}

以整型数组来类比理解

#include<stdio.h>
int main()
{
    int arr[10];
    //数组元素类型 - int
    //arr数组的类型是 int[10]
    return 0;
}

回调函数

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

1.冒泡排序

首先回顾一下之前的知识,我们熟悉的冒泡排序

#include<stdio.h>
void bubble_sort(int arr[],int sz)
{
    int t = 0;
    for(int i = 0;i < sz - 1; i++)
    {
        for(int j = 0;j < sz - 1 - i; j++)
        {
            if(arr[j] > arr[j+1])
            {
                t = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = t;
            }
        }
    }
}
void print_arr(int arr[],int sz)
{
    for(int i = 0;i < sz; i++)
    {
        printf("%d ",arr[i]);
    }
    printf("\n");
}
int main()
{
    int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    print_arr(arr,sz);
    bubble_sort(arr,sz);
    print_arr(arr,sz);
    return 0;
}

2.指针和数组结合的冒泡排序

#include<stdio.h>
#define N 100
int inputarr (int a[]);
void bubble_sort(int *arr, int length);
void outputarr (int *p,int n);
void swap(int *a,int *b);
​
int main()
{
    int a[N];
    int n=inputarr(a);
    bubble_sort(a, n); 
​
    outputarr (a, n);
    return 0;
}
int inputarr (int a[])
{   
    int n;
    int i = 0; 
​
    while(scanf("%d",&a[i]))
    {
        if(a[i]==0)
            break;
        i++;
    }
    n = i;
    return n;
​
}
void bubble_sort(int *arr, int n)
{    for(int i=0;i<n-1;i++)
    {
        for(int j=0;j<n-1-i;j++){
    
        if(arr[j]>arr[j+1])
        {
            swap(&arr[j+1],&arr[j]);
        }
     } 
​
                
    }
}         
​
void outputarr (int a[],int n)
{   int i;
    for(i=0;i<n;i++){
    printf("%d ",a[i]);
    }
​
}
void swap(int *a,int *b)
{
     int temp;
     temp=*a;
     *a=*b;
     *b=temp; 
}

3.模仿qsort实现冒泡排序通用算法

#include<stdio.h>
void bubble_sort(void* base,int sz,int width,int(*cmp)(const void*e1,const void*e2))
{
    for(int i = 0;i < sz - 1; i++)
    {
        for(int j = 0;j < sz - 1 - i; j++)
        {
            if(cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)
            {
                //交换
                Swap((char*)base+j*width,(char*)base+(j+1)*width,width);
            }
        }
    }
}
​
void Swap(char* buf1,char* buf2,int width)
{
    for(int i = 0;i < width; i++)
    {
        char tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        buf1++;
        buf2++;
    }
}
​
void print_arr(arr[],sz)
{
    for(int i = 0;i < sz; i++)
    {
        printf("%d ",arr[i]);
    }
    printf("\n");
}
​
int cmp_int(const void * p1,const void * p2)
{
    return (*(int *)p1 - *(int *)p2);//强制转换
}
​
void test()
{
    int arr[] = { 1,3,5,7,9,2,4,6,8,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);
    print_arr(arr,sz);
}
​
int main()
{
    test();
    return 0;
}

(char* )base + j * width//强制转换类型成char类型,从而指定相应的位置

之所以用char,原因是char类型占字节较小,比较好判断

4.快速排序改良

qsort函数适用于整型数据、字符串数据、结构体数据。而上面的冒泡函数只能排序规定好的数据。

演示一下qsort函数的使用:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
//qsort函数的使用者得实现一个比较函数
​
//void qsort(void* base,size_t num,size_t size,int (*compar)(const void*,const void*))
​
//base中存放的是待排序数据中第一个对象的地址
//size_t num元素的个数
//size_t size一个元素的大小
//int (*compar)(const void*,const void*)用来比较两待排序数据中的2个元素的函数
void printf_arr(int arr[],int sz)
{
    for(int i = 0;i < sz; i++)
    {
        printf("%d ",arr[i]);
    }
    printf("\n");
}
​
int cmp_int(const void * p1,const void * p2)
{
    return (*(int *)p1 - *(int *)p2);//强制转换
}
​
void test1()
{
    //整型数据的排序
    int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    //排序
    qsort(arr,sz,sizeof(arr[0]),cmp_int);
    //打印
    print_arr(arr,sz);
}
​
struct Stu
{
    char name[20];
    int age;
};
​
int sort_by_age(const void* e1,const void* e2)
{
    return ((struct Stu*)e1) ->age - ((struct Stu*)e2) ->age;//升序   降序只要把e1 e2位置交换
}
​
int sort_by_name(const void* e1,const void* e2)
{
    return strcmp(((struct Stu*)e1)->name , ((struct Stu*)e2)->name);//strcmp是比较内容的
}
​
void test2()
{
    //使用qsort函数排序结构体数据
    struct Stu s[] = {{"zhangsan",30},{"lisi",34},{"wangwu",20}};
    int sz = sizeof(s) / sizeof(s[0]);
    //按照年龄来排序
    qsort(s,sz,sizeof(s[0]),sort_by_age);
    //按照名字来排序
    qsort(s,sz,sizeof(s[0]),sort_by_name);
}
int main()
{
    //test1();
    test2();
    return 0;
}

A函数 B函数(A函数的地址)(B函数形参部分是A函数指针)

5.改造计算器

#include<stdio.h>
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 Div(int x,int y)
{
    return x / y;
}
void menu()
{
    printf("*******************************\n");
    printf("*********1.add     2.sub*******\n");
    printf("*********3.mul     4.div*******\n");
    printf("**************0.exit***********\n");
    printf("*******************************\n");
}
​
void Calc(int(*pf)(int,int))
{
    int x = 0;
    int y = 0;
    printf("请输入两个操作数:>\n");
    scanf("%d %d",&x,&y);
    return pf(x,y);
}
int main()
{
    int input = 0;
    //计算器-计算整型变量的加减乘除
    do
    {
        menu();
        int x;
        int y;
        int ret = 0;
        printf("请选择:>");
        scanf("%d",&input);
        switch(input)
        {
            case 1: 
                ret = Calc(Add);
                printf("ret = %d\n",ret);
                break;
            case 2: 
                ret = Calc(Sub);
                printf("ret = %d\n",ret);
                break;
            case 3: 
                ret = Calc(Mul);
                printf("ret = %d\n",ret);
                break;
            case 4: 
                ret = Calc(Div);
                printf("ret = %d\n",ret);
                break;
            case 0:
                printf("退出程序\n");
                break;
            default:
                printf("选择错误,重新选择");
                break;
        }
        
    }while(input);
    return 0;
}

刚入手学习C语言,希望大家能关注一下我~~~~

如果喜欢这篇文章的话,请您给个赞,也欢迎收藏啊啊啊~~~

同时如果有不对的地方,请您在下面对话框里留言,我会及时改正的,谢谢。

 

  • 29
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Williamtym

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

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

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

打赏作者

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

抵扣说明:

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

余额充值