精华内容
下载资源
问答
  • 指针 指针数组 指针数组的指针 数组指针 数组指针的数组 函数指针 函数指针数组 指向函数指针数组的指针

           指针是c语言的灵魂所在,然而因为其过于灵活,在使用的过程中难免出错。接下来我们简单分析一下常见的指针运用。

    指针:

         1  一级指针

             char *p1 =  “abcdef”;

          2  二级指针

             char **p2 = &p1;

    指针数组:

         1 char *arr[5] ;  (在数组中存放5个char* 指针)

    指针数组指针

         1 char* arr[5];

            char * (*parr)[5] = &arr;

           parr就是指向指针数组arr的指针 (即parr为指针数组的指针)

    数组指针:

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

              int  (*p)[5] = &arr ;          p即为指向数组的指针,又称数组指针

    数组指针的数组:

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

          int arr2[3] = {4,5,6};

          int (*parr[2])[3]={arr1,arr2};


    函数指针:

      先写一个简单的函数

            int add(int x, int y)

             {

                return x+y;

             }

        int  (*pfun)(int, int);

                pfun = add;

    这样一来  pfun就是指向函数add的指针(即函数add的指针)。

    下面是定义一个函数指针的方法:



    指向函数指针数组的指针:

               函数指针数组也是指针数组,在这个数组中存放的是函数的指针

        eg: 我们先写加减乘除四个子函数。

             

            int (*pfunarr[4])(int, int) = {add, sub, mul, div};//这个数组就是函数指针数组      pfunarr就是指向函数指针数组的指针

    展开全文
  • 指针数组 首先从名字就可以知道这是一个数组,是存放指针的数组。 先看几种指针数组: int * 数组指针 函数指针 函数指针数组 指向函数指针数组的指针

    指针数组

    首先从名字就可以知道这是一个数组,是存放指针的数组。
    先看几种指针数组:
    int *arr1[10];
    char *arr2[5];
    char **arr3[6];
    因为 [ ] 的优先级是高于 * 的,所以数组名会先于 [ ] 相结合组成数组。 再于 int * / char *类型结合,组成存放该类型的数组。



    指针数组因为存放的都是指针所以不管是 int *   char * 还是 char ** 其大小都是四个字节。

    数组指针

    从名字看就知道是指针,是指向一个数组的指针。

    int (*p)[10];
    char (*p)[10];
    由数组指针知道 [ ] 的优先级是高于 *  的,所以用()来提升指针先结合。

    int (*p)[10];  //  是一个指向10个整形的一维数组的指针
    char (*p)[10]; //  是一个指向10个字符型的一维数组的指针

    数组的存储方式



    因为指向的是一个数组,所以大小由数组决定


    函数指针

    指向函数的指针,通俗的说也就是函数的地址
    void(*pfun)();
    pfun 先和*结合,说明pfun是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void
    也就是说 其可以保存函数的地址



    函数指针数组

    int (*parr[3])();
    parr先与 [ ]结合,说明是一个数组,再与*结合,说明数组存放的是指针,指针都指向的是函数
    函数指针数组的使用(转移表)
    例子:计算器
    #include<stdio.h>
    
    int add( x, y)
    {
    	return x + y;
    }
    int sub(x,y)
    {
    	return x - y;
    }
    int mul(x,y)
    {
    	return x * y;
    }
    int div(x,y)
    {
    	return x / y;
    }
    int main()
    {
    	int x, y;
    	int input = 1;
    	int ret = 0;
    	int(*parr[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 > 0 && input < 5))
    		{
    			printf("输入你要计算的数:\n");
    			scanf("%d  %d", &x, &y);
    			ret = (*parr[input])(x, y);
    		}
    		else
    			printf("输入有误!\n");
    		printf("%d\n", ret);
    	}
    	return 0;
    }
    输出结果:


    指向函数指针数组的指针 

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

    定义方式:

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




    展开全文
  • 指针数组、数组指针——指针访问数组方法总结

    万次阅读 多人点赞 2019-01-14 09:51:11
    2.2.2 指向每一行的指针(指针数组方式) 2.2.3 指向整个数组的指针(数组指针方式) 3 总结 1.数组元素的访问 数组中的各元素在内存中是连续分布的,要想访问数组中某一元素,那么就必须知道其地址。 在一....

    目录

    1.数组元素的访问

    2.通过指针访问数组

    2.1 通过指针访问一维数组

    2.2 通过指针访问二维数组

    2.2.1 指向元素的指针

    2.2.2 指向每一行的指针(指针数组方式)

    2.2.3 指向整个数组的指针(数组指针方式)

    3 总结


    1.数组元素的访问

           数组中的各元素在内存中是连续分布的,要想访问数组中某一元素,那么就必须知道其地址

           在一维数组中,数组A的元素A[i]的地址&A[i]=A+L*i,其中A为数组的标识符(数组名),也可以用A来代表数组的首地址,L为数组A的数据类型,由此可见,对于一维数组,只需要知道数据类型大小和索引i,就可以知道A[i]的地址,从而就可以访问A[i]了,这也是为什么一维数组的定义可以不指定数组大小,也不会妨碍数组元素的访问

           二维数组,实际上也是一维数组,只不过这个一维数组的每个元素都是一个一维数组。因此,将二维数组的每一行看做一个元素,很容易可以知道二维数组中各元素在内存中是按行优先进行连续存储的,如定义数组A[3][4],那么它在内存中的存储情况如下:

                

            由此也可得到二维数组中元素A[i][j]的地址为&A[i][j]=A+L*(C*i+j),其中A为二维数组A的标识符(数组名),也就是数组的首地址,L为数组元素的数据类型,C为二维数组的列数。由此可见,要知道二维数组中某一元素的地址,就必须知道数据类型大小以及二维数组的列数,这样最终才能实现对二维数组元素的访问,这也是为什么二维数组的定义必须指定列数


    2.通过指针访问数组

           在理解访问数组的指针之前,我们不得不先理解另一个问题:如果定义一个数组A,按前面所说,A就是数组第一个元素的首地址,那么A+1是什么意思呢?我在第一次遇到这个问题的时候,第一反应是A既然表示的是地址,那么A+1自然就是地址+1了呀!然而事实并非如此,我们先来做个测试如图所示:

                                              

           根据测试可知,a+1并非就是a数值上加1,a+2也并非是a数值上加上2,他们实际上两两之间加的是4,即是sizeof(int),而*a=a[0]=1,*(a+1)=a[1]=2.....由此可以知道,a+i实际上就是a的第i个元素的地址,这与前面&A[i]的地址计算是相匹配的。

           而对于二维数组来说,道理其实是一样的,不过二维数组的元素A[0]表示第一行,A[1]表示第二行......因此,二维数组中A是数组的首地址,也就是第0行A[0]的行首地址,A+1就是第1行A[1]的行首地址,.....,A+i就是第i行A[i]的行首地址了....

          

    2.1 通过指针访问一维数组

     一维数组指针的定义方式如下:

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

           这里定义了一个指针变量p,它指向一个整型变量,而a实际上也就是a的第一个元素a[0]的地址,因此p就指向了数组的第一个元素a[0]。 那么p+1等于什么呢?实际上,p+1在数值上也就等于a+1,因此,p+1其实就是a[1]的地址,p+i就是a[i]的地址,这样,就可以通过*(p+i)来访问a[i]的值了。如图所示:

                                               

          由此可以得出,对于一维数组的数组指针p,数组名p实际上是指向数组第一个元素的指针,即p为int *类型,由于其指向int型数据,因此(p+i)就相当于在p的基础上偏移了i*sizeof(int)的地址大小,就等于数组第i个元素的地址(i=0,1,2....)。

    2.2 通过指针访问二维数组

    2.2.1 指向元素的指针

            这种办法可以说是最直接的办法了,定义方式如下:

    int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    int *p=&a[0][0];

            在这种定义方式下,对于数组元素的访问就需要一个个元素往后推,结合本文开头所说的,二维数组是按行优先存储的,第i行第j列元素实际上是整个二维数组中的第i*c+j个元素,其中c为二维数组的列数,相对于第一个元素,第i*c+j个元素的地址偏移量为4*(i*c+j),而由于p为int指针,p+x的地址偏移量为p+4*x,因此a[i][j]=*(p+i*c+j)

    2.2.2 指向每一行的指针(指针数组方式)

            这种方式是定义指针来指向二维数组的某一行,定义方式如下:

    int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    int *p=a[0];

           这里可能会有些迷惑了,为什么这里的指针变量p是指向的a[0]呢?而不能让p指向a呢?

           实际上,前面说过,二维数组实际上是一维数组中每个元素都为一个一维数组,那么此时定义了a[3][4],a代表什么呢?一定要知道,不管数组a是几维的,它实际上都是一维的,a一定是数组第一个元素的地址,这里的第一个元素是指的将n维数组看做一维数组后的第一个元素。因此,在a[3][4]中,将a看做一维数组后它的每一个元素实际上是每一行,因此如果让指针变量p指向a,就相当于让p指向第0行,而这里的第0行并不是整形变量,因此不能让p指向a;

           而如果让p指向a[0],实际上就相当于指向a[0]的第一个元素,也就是a[0][0]了,此时的a[0][0]是整形变量,因此让p指向a[0]是可以的,当然,也可以让p指向a[1]、a[2].....如果让p指向第i行,那么此时的p+j就相当于是一维数组中第j个元素的地址了,即p+j就指向了a[i][j]。

           这样看来,对于有r行的二维数组,就需要定义r个指针指向每一行,这样其实就可以定义一个装有r个指针的数组,其中每一个指针分别指向二维数组的每一行,定义方式如下:

    int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    int *p[3];
    
    for(int i=0;i<3;i++)p[i]=a[i];

           在这种定义方式下,p[i]即指向数组的第i行,也就是a[i][0],知道了a[i][0]的地址,要想访问a[i][j],地址即是a[i][0]+4*j,这里由于p[i]是int型的指针变量,因此刚好就等于p[i]+j,即a[i][j]=*(p[i]+j)。这就是一种定义指向二维数组的指针的方式,这里的p是以指针作为元素的数组,也就是指针数组

           值得注意的是,这种方法必须知道二维数组的行数

    2.2.3 指向整个数组的指针(数组指针方式)

           这种方式是定义指针来指向整个数组,定义方式如下:

    int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    int (*p)[4]=a;

           那么这里的p是什么意思呢?如果看不懂(*p)[4],那就把*p用b去替换掉,就成了b[4],这里的b就很明显了,就是一个有4个元素的数组的首地址,那么这里的p的含义也就不难得出了:指向一个有四个元素的数组的首地址,尤其需要注意的是,这里p是指向首地址,并非就是首地址,*p才是首地址,也就是指向第一个元素,因此这里的p也就是指向指针的指针。在int (*p)[4]=a中,p就是指向了第一个含四个元素的数组的首地址,也就是p指向a的首地址也就是a[0],*p指向a[0]的首地址也就是a[0][0],要访问a[0][0],就就是*(*p)了;

           既然p是指向一个有四个元素的数组首地址的指针,那么p+i呢?*p+i呢?要分析清楚这个问题,我们还是参考b[4],前面说过,这里的p是指向首地址的指针,因此p+i就对应了&b+i,因此p+i就指向了&b+i所在的位置;*p是b的首地址,指向b的第一个元素,*p+i就对应了b+i,因此*p+i就指向了b+i所在的位置。

           对于p+i,我们来看看&b+i和&b之间偏移了多少:

                                     

          可以看到,&b+1偏移了16个字节,&b+2偏移了32个字节,而16个字节刚好就是数组b的大小,因此&b+i实际上偏移的地址为i*sizeof(b)=i*c*sizeof(int)。也就是说p+i会跳过整个包含四个整型元素的数组指向下一个包含四个元素的数组,回到a[3][4]中,p指向的是a[0],那么p+1就指向a[1],p+i就指向a[i]了,因此,p+i偏移i个b数组大小的地址,也就是指向二维数组的第i行。

            而对于*p+i,就比较简单了,可以看做*p=b,那么*p+i也就是b+i,即是第i个元素的地址,因此也可得出*p+i指向当前有四个元素的数组的第i个元素

           好了,那么我们要是想访问数组中的a[i][j]怎么办呢?前面说过,要访问数组中的元素,必须先得到其地址,对于a[i][j],它是a数组中第i行的子数组中的第j个元素,综合上面分析的,p+i即指向了数组a的第i行,*(p+i)就是该行的首地址,也就是指向了a[i][0],而对于a[i]这个一维数组的第j个元素当然就是a[i]+j,也就是*(p+i)+j了,因此,a[i][j]的地址就是*(p+i)+j,a[i][j]=*(*(p+i)+j)

    3 总结

          综合以上分析,总结一下用指针访问数组元素的方法(以数组a为例):

     

     

    一维数组a[ ]

                                       二维数组a[R ][ C ]

    定义方式

    T *p=a;

    T *p=&a[m][n];

    T *p[R];

    p[m]=a[m];

    (m=0,1..R-1)

     T (*p)[C]=a;

    访问方式

    a[i]=*(p+i)

    a[i][j]=*(p+(i-m)*C+(j-n))

    a[i][j]=*(p[i]+j);

    a[i][j]=*(*(p+i)+j)

     

    展开全文
  • 指针 与 数组 ( 指针 | 数组 | 指针运算 | 数组访问方式 | 字符串 | 指针数组 | 数组指针 | 多维数组 | 多维指针 | 数组参数 | 函数指针 | 复杂指针解读)

    相关文章链接 :
    1.【嵌入式开发】C语言 指针数组 多维数组
    2.【嵌入式开发】C语言 命令行参数 函数指针 gdb调试
    3.【嵌入式开发】C语言 结构体相关 的 函数 指针 数组
    4.【嵌入式开发】gcc 学习笔记(一) - 编译C程序 及 编译过程
    5.【C语言】 C 语言 关键字分析 ( 属性关键字 | 常量关键字 | 结构体关键字 | 联合体关键字 | 枚举关键字 | 命名关键字 | 杂项关键字)
    6.【C 语言】编译过程 分析 ( 预处理 | 编译 | 汇编 | 链接 | 宏定义 | 条件编译 | 编译器指示字 )
    7.【C 语言】指针 与 数组 ( 指针 | 数组 | 指针运算 | 数组访问方式 | 字符串 | 指针数组 | 数组指针 | 多维数组 | 多维指针 | 数组参数 | 函数指针 | 复杂指针解读)


    文章目录



    注意 : 博客中出现的关于指针的计算方式, 如果在 32 位电脑中, 指针的地址类型是 unsigned int 类型 , 占 4 字节 , 在 64 位电脑中 指针地址的类型是 unsigned long int , 占 8 个字节 ;




    一. 指针



    1. 指针 简介


    ( 1 ) 指针 概念 ( 本质 | 占用内存 ① 32位 4字节 ② 64 位 8 字节 | * ① 声明指针 ② 获取指向的值 )


    指针简介 :

    • 1.指针本质 : 指针本质也是一个变量 ;
    • 2.占用内存 : 指针变量也要在内存中占用一定大小的空间, 不同 类型的指针占用的内存大小都是 相同的 ;

    32位系统 指针 占用内存大小 4 字节, 64位系统 指针 占用内存大小 8 字节;

    • 3.指针变量保存的值 : 指针变量中保存的是内存地址的值 ;

    符号简介 :

    • 1.声明指针 : 在 声明指针变量时, * 表示声明一个指定类型变量的指针 ;
    • 2.使用指针 : 使用指针的时候, * 表示指针变量地址指向的内存中的值, 可以读取该地址的实际数据值 或者 向地址中写入实际数据值 ;


    ( 2 ) 指针 简单示例 ( * 的读写内存作用 | 指针相关类型大小)


    指针简单示例 :

    • 1.代码示例 :
    #include <stdio.h>
    
    int main()
    {
    	//1. 指针简单使用, * 符号作用
    	int i = 666;
    	//声明 int 类型 指针, 使用 * 符号声明指针
    	int *p = &i;
    	//这里验证下 i 即存放在 p 地址的内容, * 用于读取 指针 p 地址中的数据
    	printf("%d, %x, %d\n", i, p, *p);
    	//等价于 i = 888, *p 代表指针指向的内容, p 是指针的地址, 之类 * 用于向 p 地址指向的内存中写入数据
    	*p = 888;
    	//改变一个变量的大小可以使用其地址来改变, 不一定必须使用变量名称
    	printf("%d, %x, %d\n", i, p, *p);
    	
    	
    	//2. 指针大小示例
    	//32位系统 指针 占用内存大小 4 字节, 64位系统 指针 占用内存大小 8 字节
    	int* p_int;
    	char* p_char;
    	//a. 打印 int* 类型指针 和 char* 类型指针的 指针变量本身大小
    	//b. 打印 指针指向的内容大小, int 指针指向 int 类型, 因此 sizeof(*p_int) 结果是 4, sizeof(*p_char) 结果是 1
    	printf("%ld, %ld, %ld, %ld\n", sizeof(p_int), sizeof(p_char), sizeof(*p_int), sizeof(*p_char));
    	//打印 int* 和 char* 类型大小, 打印 int 和 char 类型大小 
    	printf("%ld, %ld, %ld, %ld\n", sizeof(int*), sizeof(char*), sizeof(int), sizeof(char));
    	
    	return 0;
    }
    
    • 2.运行结果 :
      这里写图片描述



    2. 传值 和 传址 调用


    ( 1 ) 相关概念 ( 传值调用 复制实际值 | 传址调用 复制地址值 )


    传值调用 :

    • 1.产生复制情况 : 传值调用时 会发生 实参数据值 复制到 形参中 ;

    传址调用 :

    • 1.实现方式 : 将指针当做函数的参数, 因为指针也是变量, 可以当做参数使用 ;
    • 2.适用场景 : 如果需要在函数中修改实参的值, 并且执行函数完毕后保留下来, 这里就用到传址调用, 使用指针作为函数参数 ;
    • 3.适用场景2 : 参数数据类型较复杂, 如果参数很大, 传值调用需要实参到形参的复制, 会浪费性能 ;



    ( 2 ) 传址调用 ( 改变外部变量值 )


    代码示例1 :

    • 1.代码 :
    #include <stdio.h>
    
    //传值调用案例, 任意改变参数的值, 不影响传入的变量值
    int fun_1(int a, int b)
    {
    	a = 444;
    	b = 444;
    }
    
    //传址调用案例, 如果在函数中修改了地址指向的内存的值, 那么最终的值改变了
    int fun_2(int* a, int* b)
    {
    	*a = 444;
    	*b = 444;
    }
    
    int main()
    {
    	int x = 666, y = 888;
    	
    	//传值调用
    	fun_1(x, y);
    	printf("x = %d, y = %d\n", x, y);
    	
    	//传址调用
    	fun_2(&x, &y);
    	printf("x = %d, y = %d\n", x, y);
    
    	return 0;
    }
    
    • 2.执行结果 :
      这里写图片描述

    代码示例2 :

    • 1.代码 :
    #include <stdio.h>
    
    //传址调用, 替换传入的变量值
    int swap(int *a, int *b)
    {
    	int tmp = *a;
    	*a = *b;
    	*b = tmp;
    }
    
    int main()
    {
    	int x = 666, y = 888;
    	
    	printf ("x = %d, y = %d\n", x , y);
    	swap(&x, &y);
    	printf ("x = %d, y = %d\n", x , y);
    	
    
    	return 0;
    }
    
    • 2.执行结果 :
      这里写图片描述



    3. 常量 和 指针


    ( 1 ) 相关概念 ( 核心原则 左数右指 | 左数 ① const int* p ② int const* p 数据时常量 | 右指 int* const 指针是常量 )


    参考 : const 关键字 ;

    const 修饰指针 : 需要符合下面的规则 :

    声明 特征
    const int* p p指针地址可变 p指针指向的内容不可变 (const 在 * 左边, 数据不可变)
    int const* p p指针地址可变 p指针指向的内容不可变 (const 在 * 左边, 数据不可变)
    int* const p p指针地址不可变 p指针指向的内容不可变 (const 在 * 右边, 地址不可变)
    const int* const p p指针地址不可变 p指针指向的内容不可变 (const 在 * 左边 和 右边, 数据和地址都不可变)

    const 修饰指针规则 : ***左数 右指 (左边数据是常量, 右边指针是常量)***;
    左数 : const 出现在 * 左边时, 指针指向的数据为常量, 指向的数据不可改变;
    右指 : const 出现在 * 右边时, 指针地址本身是常量, 指针地址不可改变;



    ( 2 ) 验证 常量 指针 相关概念 ( 左数右指 )


    参考 : const 关键字 ;

    const 修饰指针规则 : 左数右指;
    左数 : const 出现在 * 左边时, 指针指向的数据为常量, 指向的数据不可改变;
    右指 : const 出现在 * 右边时, 指针地址本身是常量, 指针地址不可改变;


    const 关键字 代码示例 : 修饰指针

    • 1.代码示例1 : const 出现在 * 左边, const int* p = &i;
    #include <stdio.h>
    
    int main()
    {
    	//定义普通的变量, 用于取地址用
    	int i = 666;
    	//定义一个 const 在 * 左边的例子, 意义是 指针指向的内容是常量
    	//按照规则, 指针地址可改变, 指针指向的数据不可变
    	const int* p = &i; 
    	//指针指向的数据不可改变, 这里会报错
    	*p = 444;
    	 
    	return 0;
    }
    
    

    这里写图片描述

    • 2.代码示例2 : const 出现在 * 左边, int const* p = &i;
    #include <stdio.h>
    
    int main()
    {
    	//定义普通的变量, 用于取地址用
    	int i = 666;
    	//定义一个 const 在 * 左边的例子, 意义是 指针指向的内容是常量
    	//按照规则, 指针地址可改变, 指针指向的数据不可变
    	int const* p = &i;
    	//指针指向的数据不可改变, 这里会报错
    	*p = 444;
    	 
    	return 0;
    }
    

    这里写图片描述

    • 3.代码示例3 : const 出现在 * 右边, int* const p = &i;
    #include <stdio.h>
    
    int main()
    {
    	//定义普通的变量, 用于取地址用
    	int i = 666;
    	//定义一个 const 在 * 右边的例子, 意思是 地址是常量
    	//按照规则, 指针地址不可改变, 指针指向的内容可变
    	int* const p = &i;
    	//指针指向的数据不可改变, 这里会报错
    	p = NULL;
    	 
    	return 0;
    }
    

    这里写图片描述

    • 4.代码示例4 : const 同时出现在 * 左边 和 右边, const int* const p = &i;
    #include <stdio.h>
    
    int main()
    {
    	//定义普通的变量, 用于取地址用
    	int i = 666;
    	//定义 const 同时出现在 * 左边 和 右边, 则指针的地址 和 指向的数据都不可改变
    	const int* const p = &i;
    	//下面的两个操作, 一个是想修改指针地址, 一个是想修改指针值, 这两个都报错.
    	p = NULL;
    	*p = 444;
    	 
    	return 0;
    }
    

    这里写图片描述





    二. 数组



    1. 数组 简介



    ( 1 ) 数组 概念 ( 数组地址 | 数组大小 显示 隐式 声明 | 数组初始化 [ 效率比后期赋值高 ] )


    数组 简介 :

    • 1.概念 : 数组 是 相同类型 的 变量 的 有序集合 ;
    • 2.数组示例 :
     int array[6];
    

    定义数组 int array[6];
    意义 : 数组中包含 6 个 int 类型的数据 , 数组中每个元素都是 int 类型的 ;
    第一个元素地址 : array 是数组中第一个元素的起始地址;
    下标 : 可以通过下标来获取数组中指定位置的元素, array[0] 是第一个元素的位置, array[5] 是第六个元素的位置 ;


    数组大小 :

    • 1.数组定义时必须声明大小 : 数组在定义时, 必须显示 或 隐式 的声明数组的大小 ;
    • 2.显示声明数组大小 : 定义数组时, 在数组名称后的中括号中声明数组大小 ;
    int array[5]; 
    int array[5] = {1, 2, 3} ; //这个也是显示声明, 数组大小为 5, 但是只指定了 前三个元素的大小 ; 
    
    • 3.隐式声明数组大小 : 声明数组时, 不在中括号中声明数组大小, 只在初始化中初始化指定个数的元素, 那么元素的个数就是数组的大小 ;
    //隐式初始化, 该数组个数为 4
    int array[] = {0, 1, 2, 3};
    

    数组初始化 :

    • 1.完全初始化 : 数组大小为5, 将 5 个元素都在定义时指定位置 ;
    • 2.部分初始化 : 数组大小为5, 如果初始化前 1 ~ 4 个元素, 剩余的元素默认初始化为 0 ;
    • 3.初始化效率 : 初始化效率很高, 远远比依次赋值要高, 因此建议定义数组时最好初始化 ;
    • 4.最佳实践 :
    //这里只对数组的第一个元素进行初始化为0, 那么其余的元素默认也初始化为0, 初始化效率要远远高于依次赋值的效率
    int array[5] = {0}
    



    ( 2 ) 数组 示例 ( 定义 | 大小 | 初始化 )


    数组 大小 初始化 示例 :

    • 1.代码 :
    #include <stdio.h>
    
    //数组大小 和 初始化 示例
    //数组大小 : 
    //初始化 : 如果不初始化, 那么数组中就是随机值; 全部初始化, 部分初始化 : 其余默认为 0
    
    int main()
    {
    	//1. 显示声明数组大小, 其实际大小以中括号为准, 大小为 5, 5个元素只有 前3个初始化为 0, 1, 2
    	//初始化说明 : 
    	int array_1[5] = {0, 1, 2};
    	int array_3[5];
    	int array_4[5] = {0};
    	//2. 隐式声明数组大小, 其实际大小为 3, 三个元素全部初始化
    	int array_2[] = {0, 1, 2};
    	
    	printf("array_1 大小 : %ld, array_1 数组个数 : %ld\n", sizeof(array_1), sizeof(array_1)/sizeof(*array_1));
    	printf("array_2 大小 : %ld, array_2 数组个数 : %ld\n", sizeof(array_2), sizeof(array_2)/sizeof(*array_2));
    	
    	//打印 array_2 数组结果, 其中数组元素内容是 初始化值
    	printf("打印 int array_1[5] = {0, 1, 2}; 数组结果 : \n");
    	int i = 0;
    	for(i = 0; i < sizeof(array_1)/sizeof(*array_1); i ++)
    	{
    		printf("array_1[%d] = %d\n", i, array_1[i]);
    	}
    	
    	//打印 array_3 数组结果, 其中数组元素内容是随机值
    	printf("打印 int array_3[5]; 数组结果 : \n");
    	for(i = 0; i < sizeof(array_3)/sizeof(*array_3); i ++)
    	{
    		printf("array_3[%d] = %d\n", i, array_3[i]);
    	}
    	
    	//打印 array_4 数组结果, 其中数组元素内容是随机值
    	printf("打印 int array_4[5] = {0}; 数组结果 : \n");
    	for(i = 0; i < sizeof(array_4)/sizeof(*array_4); i ++)
    	{
    		printf("array_4[%d] = %d\n", i, array_4[i]);
    	}
    	return 0;
    }
    
    • 2.执行结果 :
      这里写图片描述



    2. 数组地址与名称 概念


    ( 1 ) 数组 概地址 ( 数组名 [ 数组首元素地址 ] 和 &数组名 [ 数组地址 ] | 数组名 类似于 常量指针 | 数组拷贝 )


    数组地址名称 简介 :

    • 1.数组名称 : 数组名称 等价于 数组 首元素 地址 ;
      • 注意 : 数组名 不是 数组的首地址 , &数组名 才是数组的首地址 , 但是这两个的值是相同的 ;
    • 2.数组地址 : 使用 & 取数组的地址, 才能获取数组的地址 ;
    • 3.值相同 : 数组的 首元素地址 与 数组地址是相同的 ;
    • 4.数组地址 与 数组首元素地址 : 这两个地址不是等价的, 其意义完全不同 ;

    数组名称 :

    • 1.数组名称的本质 : 数组名 类似于 常量指针, 数组名称 不能作为左值, 不能被赋值 ; 数组名 只能作为右值, 被赋值给别的指针 , 数组名在***大多数情况下可以当做常量指针理解***, 但是 数组名绝对不是真正的常量指针 ;
    • 2.数组名代表的地址 : 数组名称 指向 数组首元素的地址, 其绝对值 与 数组地址 相同;

    数组名称不作为常量指针的场合 : 数组名类似于常量, 但不是常量, 下面两种场合数组名与常量指针不同 ;

    • 1.sizeof 取大小时 : 使用 sizeof 操作符获取 array 数组大小时, sizeof 作用域常量指针获取的是指针的大小, sizeof 作用于数组名, 获取的是数组的大小 , 不是单个指针的大小;
    • 2.作为 & 参数时 : & 只能用于变量, 不能用于常量, 因此 &数组名 是取数组的地址, 这种用法 不符合常量指针的特点 ;

    数组拷贝禁用数组名直接赋值 :

    • 1.禁止使用的方式 : 数组拷贝不能 直接使用 数组名1 = 数组名2 的方式进行拷贝 或者 赋值 ;
    • 2.常量指针 : 数组名 类似于 常量指针, 其***不能作为赋值的左值, 只能做右值使用*** ;
    • 3.数组大小 : 数组还有一个隐含的大小属性, 如 sizeof(数组名) 就可以获取整个数组的大小, 单纯的数组名称只是一个地址, 如果使用地址进行互相赋值, 数组的大小属性无法体现, 因此 C 语言规范, 禁用数组名 作为左值 ;


    ( 2 ) 数组 示例 ( 数组名 | 地址 | 数组拷贝禁止情况 )


    数组代码示例 :

    • 1.代码示例 :
    #include <stdio.h>
    
    int main()
    {
    	int array_1[8] = {0};
    	int array_2[] = {0, 1, 2, 3};
    	
    	//array_1 的类型是 int *
    	//&array_1 的类型是 int*[8], 根据编译时的  warning 警告可以看到这两个类型
    	printf("array_1 : %x, &array_1 : %x \n", array_1, &array_1 );
    
    	//这种用法是错误的, array_1 类似于一个常量指针, 其不能当做左值
    	//数组除了地址信息之外, 还附带大小信息, 如果只是地址赋值, 大小信息无法带过去, 因此数组不能这样拷贝赋值
    	//C语言 不支持 这样的赋值
    	//array_1 = array_2; 
    	
    	return 0;
    }
    
    • 2.执行结果 :
      这里写图片描述



    3. 数组 与 指针 区别


    ( 1 ) 概念简介 ( ① 数组名就是首元素地址 不需要寻址 | ② 指针 中保存一个地址 指向首元素地址 需要寻址 | printf 打印 数组 或 指针 : 根据占位符自动判断打印地址还是打印内存中的具体内容 )


    printf 打印 数组 与 指针 变量 :

    • 1.处理数组 : 编译器不会寻址 , 直接将 数组名代表的内存空间地址对应的数据打印出来 , 因为数组名就代表了数组的首地址, 不需要再次寻址 ; 数组名代表的地址 就是 内容的首地址 , 不用去寻址查找内容 ;
    • 2.处理指针 : 编译器会寻址 , 查找 指针变量的四个字节的内容, 指针变量的四个字节的地址指向的内容 , 然后将指针指向的内容打印出来 , 指针的地址 与 实际内容的地址 不连续, 是断开的 ;

    下面这张图形象的说明了 指针 与 数组的 区别 :

    这里写图片描述


    指针的起始地址 和 数组的起始地址 :

    • 1.指针起始地址 : 这里要区分 指针保存的地址 和 指针起始地址,
      ( 1 )指针保存的地址 : 是指 指针变量 4 字节 (32位系统的, 64位 8 个字节) , 这四个或 8个字节中保存了一个地址 , 这个地址指向另外一段内存空间, 这个地址是指针保存的地址, 又叫指针指向的地址, 在下图中标注的 指针变量中保存(指向)的地址① , 这个地址还是 实际内容的起始地址① ;
      ( 2 )指针起始地址 : 是指指针变量所在的地址, 是 ***指针变量的四个字节的第一个字节所在内存的首地址 ***, 在下图中标注的 指针起始地址② ;
    • 2.数组起始地址 : 数组名就是数组的起始地址, 又是数组首元素地址 , int array[10], array 是一个地址, 在下图中标注的 数组首地址③, 这个地址还是数组 数组实际内容的首地址③ ;
    • 3.图示 :
      这里写图片描述

    printf 打印 数组 或 指针 的 内容 或 地址 : 针对 字符数组 和 字符指针, 根据占位符自动判断打印地址还是打印内存中的具体内容 ;

    • 1.打印字符串 : 如果想要打印出 数组或指针的 字符串, 那么使用 %s 作为占位符 ;
    • 2.打印地址 : 如果想要打印出 数组或指针的地址 , 那么使用 %x 作为占位符 ;
    • 3.智能判断 : printf 时, 方法中会自动判断 占位符 的类型, 来判断是否要寻址, 如果 %x 则只打印地址, 如果使用 %s, 则会自动根据对应的地址打印出其内容 ;
    • 4.代码示例 :
    #include <stdio.h>
    
    int main()
    {
    	
    	
    	char array[10] = {'H', 'e', 'l', 'l', 'o'};
    	char *str = "Hello";
    	
    	//1. 针对数组打印 
    	//   ( 1 ) 如果检测到 占位符 为 %s, 则会将组名首地址内存中的数据, 并一直到 \0 都打印出来(注意 不寻址)
    	//   ( 2 ) 如果检测到 占位符 为 %x, 则会自动将数组首地址打印出来
    	printf("array : %s\n", array);
    	printf("array : %x\n", array);
    	
    	//2. 针对指针打印 
    	//   ( 1 ) 如果检测到 占位符 为 %s, 则会寻址查找指针地址指向的内存, 将该内存中的字符串打印出来
    	//   ( 2 ) 如果检测到 占位符 为 %x, 则会将指针地址打印出来
    	printf("str : %s\n", str);
    	printf("str : %x\n", str);
    	
    	return 0;
    }
    
    
    
    
    • 5.执行结果 :
      这里写图片描述


    ( 2 ) 代码示例 ( 数组 | 指针 编译器处理上的区别 )


    代码示例 :

    • 1.代码1 : 文件 test_1.c 内容 ;
    #include <stdio.h>
    
    //编译器如何处理 数组 和 指针
    //1. 外部文件定义 : 在另外一个文件定义 char 指针 : char *p = "Hello";
    //		( 1 ) 编译器操作 : 在符号表中放置符号 p, 然后为符号 p 分配空间, 
    //                         查询到 p 是指针, 给 p 符号分配 4 字节, 4 字节存放地址, 指向 "Hello" 字符串 地址;
    //                         当打印 p 指针时, 编译器会按照指针地址寻址, 将指针指向的内存中取值并打印出来 ;
    //2. 本文件声明 : 在本文件声明 : extern char p[] ; 
    //      ( 2 ) 编译器操作 : 声明引用外部文件变量 p, 到符号表中查找 p, 但是编译器认为 p 是数组,
    //                         p 是数组名, 代表数组地址;
    //						   当打印 p 数组时, 编译器会直接将 p 当做数组地址 打印出来;
    
    //printf 打印变量规则 : 
    //( 1 ) 打印指针 : 编译器会寻址, 查找指针指向的内容, 然后将指针指向的内容打印出来 ;
    //( 2 ) 打印数组 : 编译器不会寻址, 直接将数组名代表的内存空间地址打印出来 ;
    
    //代码遵循原则 : 声明指针 数组, 在外部声明时类型要一致 ; 
    
    extern char p[];
    
    int main()
    {
    	//1. 此时 p 是数组, 直接打印 p, 会将数组地址打印出来, 以 %s 打印一个数组地址 会出乱码 
    	printf("*p = %s\n", p);
    	
    	//2. 正确使用数组 p 打印字符串的方法(模仿编译器行为手工寻址) : p 是指针, 指向 "Hello", 但是本文件中声明为类数组, 数组与指针打印时编译器会做不同处理;
    	// ( 1 ) 首先 p 是地址 ( 数组首地址 ), 
    	//			① 将 p 转为 unsigned int* 类型的指针 : (unsigned int*)p ;
    	//			② 说明 : 此处是将一个变量强制转为 指向 unsigned int 类型的 指针, 这个是一个二维指针, 是指向地址的指针
    	//					 为了获取 p 的地址(其地址是 unsigned int 类型的), 使用 * 即可提取 p 地址 ; 
    	// ( 2 ) 获取字符串地址 : 获取 (unsigned int*)p 指向的地址, 即 字符串的地址, 使用 *((unsigned int*)p) 获取字符串地址;
    	// ( 3 ) 将字符串地址强转为char*指针 : (char*) (*((unsigned int*)p)) 即指向字符串的指针, 打印这个指针会将字符串打印出来
    	printf("*p = %s\n", (char*) (*((unsigned int*)p)));
    	
    	return 0;
    }
    
    • 2.代码2 : 文件 test_2.c 中的内容 ;
    char *p = "Hello";
    
    • 3.执行结果 : 执行 gcc test_1.c test_2.c 命令进行编译 , 执行 编译后的可执行文件 ./a.out ;
      这里写图片描述




    三. 数组 指针 分析



    1. 指针 加减 运算方式


    ( 1 ) 指针 加减法 运算 ( 指针指向的位置在同一个数组中改变才有意义 )


    指针运算规则 :

    • 1.指针是变量 : 只要是变量就可以进行运算, 可以加减运算, 指针 + 1 运算如下 ;
    • 2.指针 + num 运算规则 : p + num(整数) , 反应到地址运算上 即 等价于 *(unsigned int)p + num * sizeof(p) , 其地址不是加1个字节, p 的地址的增量 是 所指向的数据类型的大小 乘以 被加数 的地址;

    指针指向数组元素规则 :
    前提 : 指针指向一个数组元素时有以下规则 :
    : 指针 + 1 指向数组的下一个元素 ;
    : 指针 - 1 指向数组的上一个元素 ;


    数组运算规则 :

    • 1.数组本质 : 数组的***元素存储空间是连续的***, 从数组首地址(数组元素首地址 | 数组名)开始 ;
    • 2.数组空间大小 : 数组的空间通过 sizeof(数组元素类型) * 数组大小 计算的, 这个数组元素类型是数组声明的时候指定的, 数组大小是数组声明或者初始化时指定的 ;
    • 3.数组名 : 数组名 是 数组首元素的地址, 又是***数组地址***, 即***数组所在内存空间的首地址*** ;
    • 4.数组名 看做 常量指针 : 数组名可以看做指向数组首元素的常量指针, 当数组名 + 1 时, 可以看做指针 进行了 加 1 运算, 其地址按照指针运算规则, 增加了 数组元素大小 * 1 ;

    这里写图片描述


    指针减法运算 :

    • 1.指针之间的运算 : 两个指针之间 只能进行 减法运算, 加法乘法除法不行, 并且 进行减法运算的两个指针的类型必须相同 ;
    • 2.指针减法运算的前提 : 进行减法运算的***两个指针类型必须是同一个类型*** ;
    • 3.指针减法运算的意义 : 指针减法运算时 两个指针指向同一个数组才有实际的意义, 计算结果是 同一个数组 两个指针指向位置的下标差 ;
    • 4.同类型无意义减法 : 如果两个指针指向相同类型的不同数组, 即使减法有结果, 这个结果也是没有任何意义的;

    指针减法的过程 : 指针1 - 指针2 = ( 指针1指向的地址 - 指针2指向的地址 ) / sizeof (指针1和指针2的相同类型)



    (2) 数组大小计算示例


    数组大小计算代码示例 :

    • 1.代码示例 :
    #include <stdio.h>
    
    int main()
    {
    	int array[10] = {0};
    	
    	//打印出数组整体占用的内存数, 以及数组元素个数 
    	printf("sizeof(array) = %ld, size = %ld \n", sizeof(array), sizeof(array)/sizeof(*array));
    	
    	return 0;
    }
    
    • 2.编译执行结果 :
      这里写图片描述


    ( 3 ) 指针 加法运算示例 ( 指针地址 + 4/8 * 被加数 )


    数组名 指针 加法示例 :

    • 1.代码示例 :
    #include <stdio.h>
    
    int main()
    {
    	int array[10] = {0};
    	
    	//打印出数组首元素地址, 打印出数组名 + 1 的值
    	printf("array 地址 : %x,  array + 1 地址 : %x\n", array, array + 1);
    	
    	return 0;
    }
    
    • 2.编译运行结果 : 示例中的 array + 1 比 array 的地址大 4 个字节 ;
      这里写图片描述

    指针运算 : int * p, p + 1 代表的地址是 p 的地址 加上 4, 即加上了 一个 int 类型大小的地址;



    ( 4 ) 指针 减法 运算示例


    指针减法代码示例 :

    • 1.代码示例 :
    #include <stdio.h>
    
    int main()
    {
    	int array_1[] = {0, 1, 2, 3, 4, 5};
    	int array_2[] = {6, 7, 8, 9};
    	
    	//1. 定义指针 p1_0 指向 array_1 数组中的第 0 个元素
    	int* p1_0 = array_1;
    	//2. 定义指针 p1_5 指向 array_1 数组中的第 5 个元素
    	int* p1_5 = &array_1[5];
    	//3. 定义指针 p2_0 指向 array_2 数组中的第 0 个元素
    	int* p2_0 = array_2;
    	
    	char c = 'c';
    	//4. 定义了一个 char 类型指针
    	char *p_c = &c;
    	
    	//1. 计算 p1_5 指针指向的元素 与 p1_0 指向的元素, 两个元素在数组中的下标差
    	printf("%d\n", p1_5 - p1_0);
    	//2. ( p1_5 - p1_0 ) 与 ( ( unsigned int ) p1_5 - ( unsigned int ) p1_0 ) / sizeof ( int ) ) 是等价的 ;
    	printf("%d\n", ( ( unsigned int ) p1_5 - ( unsigned int ) p1_0 ) / sizeof ( int ) );
    	
    	//3. 指针之间不支持加法, 这个操作在编译时就会报错, 这里注释掉 ; 
    	//printf("%d\n", p1_5 + p1_0);
    	
    	//4. 两个指针间的计算倒是没毛病, 但是两个指针分别指向两个数组, 这个计算的结果没有实际意义 ; 
    	printf("%d\n", p1_5 - p2_0);
    	
    	//5. 指针间进行运算的前提是 : 两个指针的类型必须相同, 这两个指针类型不同, 一个 int* 一个 char* , 编译时报错 ; 
    	//printf("%d\n", p1_5 - p_c);
    	
    	//6. 指针之间 不能进行乘法 和 除法, 编译时会报错
    	//printf("%d\n", p1_5 * p1_0);
    	
    	//7. 指针之间 不能进行乘法 和 除法, 编译时会报错
    	//printf("%d\n", p1_5 / p1_0);
    	
    	return 0;
    }
    
    • 2.编译运行结果 :
      这里写图片描述



    2. 指针 比较 运算方式


    ( 1 ) 指针 比较 运算 ( 大于 小于 大于等于 小于等于 运算的前提是 必须指向同一数组 中的元素 | 任意两指针只能进行 等于 不等于 的比较 )


    指针的比较运算 :

    • 1.同一数组的比较运算 : 对于 大于 ( > ) , 小于 ( < ) , 大于等于 ( >= ) , 小于等于 ( <= ) 四种类型运算, 指针之间进行这四种运算的前提示 两个指针 必须都指向同一个数组的元素 ;
    • 2.任意指针的比较运算 : 对于 等于 ( == ) , 不等于 ( != ) 两种比较运算, 指针之间进行这两种比较运算, 可以是任意指针, 指针指向不同数组也可进行这两种运算 ;


    ( 2 ) 指针 比较 运算代码示例 ( 用 指针 遍历数组 )


    使用指针遍历数组代码示例 :

    • 1.代码示例 :
    #include <stdio.h>
    
    int main()
    {
    	char array_str[] = {'H', 'e', 'l', 'l', 'o'};
    	
    	//1. 定义数组第一个元素的起始指针
    	char* p_start = array_str;
    	//2. 定义数组最后一个元素 之后的指针, 这个指针只是做比较用的, 不会真正的寻址
    	char* p_end = array_str + (sizeof(array_str) / sizeof(*array_str));
    	//3. 定义循环控制变量
    	char* p = NULL;
    	//4. 遍历数组
    	for(p = p_start; p < p_end; p ++)
    	{
    		printf("%c", *p);
    	}
    	
    	//5.此处换行
    	printf("\n");
    	
    	return 0;
    }
    
    • 2.编译执行结果 :
      这里写图片描述



    3. 数组访问方式


    ( 1 ) 下标 指针 访问 ( 推荐使用下标访问 )


    下标访问数组 和 指针访问数组 的示例 : 这两种访问数组的方式是等价的 ;

    • 1.下标访问数组 :
    int array[5] = {0};
    array[1] = 1;
    array[2] = 2;
    
    • 2.指针访问数组 :
    int array[5] = {0};
    *(array + 1) = 1;
    *(array + 2) = 2;
    

    下标访问 和 指针访问 对比 :

    • 1.可读性 : 使用下标访问数组, 数组的可读性会大大的提高, 指针访问数组不易理解 , 下标访问在可读性上优于指针访问数组 ;
    • 2.性能 : 当使用一个固定的增量访问数组时, 指针访问 的性能 优于 下标访问;

    推荐使用方式 : 现在的编译器编译出来的代码, 性能上 指针访问 与 下标访问基本相同, 出于代码可读性考虑, 推荐使用下标访问数组的方式 ;


    下标 指针访问数组性能分析 : 以 数组 中的元素互相赋值为例 ;

    • 1.下标访问 : 如访问 array[3] ( 数组第 4 个元素 ) , 其首地址地址是 array 首地址 加上 3 个元素地址 ( 第三个元素的尾地址就是第四个元素的首地址 ) , 其预算方式是这样的 : ( unsigned int ) array + sizeof(int) * 3 ;
    • 2.指针访问 : 如访问 array[3] , 以指针的形式, 如果每次递增 1 个下标, 那么运算方式是 ( unsigned int ) array + 4 即可, 这里每次只做加法, 下标访问每次都要用乘法, 乘法运算要比加法运算费时 ;


    ( 2 ) 下标 指针 访问 数组 性能 代码示例





    3. int array[]; array 和 &array 区别


    ( 1 ) int array[] 中 array 和 &array 意义 ( ① array 数组首元素地址 | ② &array 数组地址 )


    数组 int array[] 中 array 和 &array 意义 :

    • 1.数组元素首地址 : array 是数组首元素地址, sizeof ( *array ) 计算的是数组中单个元素的大小 ;
    • 2.数组地址 : & 是数组的首地址, 代表的是整个数组的地址 ;

    两种指针的运算 :

    • 1.array + 1 运算 : array + 1 的运算过程是 ( unsigned int ) array + sizeof (array) , 该运算相当于计算***数组中第二个元素的首地址** , 等价于 array[1] ;
    • 2.&array + 1 运算 : &array + 1 的运算过程是 ( unsigned int ) ( &array ) + sizeof(&array)***, 其中 &array 结果是 array, sizeof ( &array) 的结果 等价于 sizeof ( array ), 这是整个数组的大小, 因此 &array + 1 的*结果是数组的尾地址* ;


    ( 2 ) array 和 &array 计算 代码示例


    代码示例 :

    • 1.代码 :
    #include <stdio.h>
    
    //注意 : 在 64 位电脑上, 计算指针时需要将指针地址墙砖为 unsigned long int 类型
    
    int main()
    {
    	int array[5] = {0, 1, 2, 3, 4};
    	
    	//1. 计算 p1 指针指向 : 
    	//		( 1 ) &array 相当于 数组的地址, & array + 1 等价于 (unsigned long int) &array + sizeof ( *&array )
    	//				等价于 (unsigned long int) &array + sizeof ( array ) , sizeof ( array ) 计算的是整个数组的长度
    	// 		( 2 ) 经过上述计算, 该指针指向 数组的尾地址, 即最后一个元素的结尾处
    	int *p1 = ( int* )( &array + 1 );
    	
    	//2. 计算 p2 指针指向 : 
    	//		( 1 ) 单纯地址 : (unsigned long int)array 这个操作将一个有类型的指针 直接强转为单纯的地址;
    	//		( 2 ) 单纯地址增加 : (unsigned long int)array + 1 就是之前的地址单纯的加 1, 不再含有指针运算中 增加 数组元素 字节大小倍数 的意义;
    	//		( 3 ) 拆分int类型字节 : 这样如果计算 p2 指针指向的数据, 会将 4 个字节拆散计算, 可能从 元素1 中1取 3 个字节, 元素2 中取 1个字节
    	// 		( 4 ) 大小端方式选择 : 计算方式注意大端模式 和 小端模式, 一版的电脑都是小端模式, 
    	//		( 5 ) 小端模式计算方式 : 这里我们按照小端模式计算, 小端模式即 高地址存放高字节, 低地址存放低字节
    	int *p2 = ( int* )( (unsigned long int)array + 1 );
    	
    	//3. 计算 p3 指针指向
    	//		( 1 ) array + 1 等价于 ( unsigned long int ) array + sizeof ( *array ), 该地址等价于 array[1] 地址
    	//		( 2 ) 经过上述计算, p3 指针指向了 第二个元素的首地址
    	int *p3 = ( int* )( array + 1 );
    	
    	//1. p1[-1] : p1 指向数组的尾地址, 其 -1 下标 即指向了数组最后一个元素, 等价于 array[4];
    	//2. p2[0] : p2 指向了数组的第一个元素的 第二个字节地址, 
    	//		那么 p2[0] 的值是 数组第一个元素的 2 , 3, 4 三个字节, 再加上 第二个元素的 第一个字节;
    	//		小端地址策略 : 高位地址存高位, 低位地址存低位, 那么 第二元素第一字节是 高位, 其次是第一数组元素的 4, 3, 2 字节
    	//3. p3[1] : p3 指向了数组的第二个元素首地址, p3[1] 等价于 array[3]
    	printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]);
    	
    	return 0;
    }
    
    • 2.编译运算结果 :
      这里写图片描述
    • 3.图示 :
      这里写图片描述
    • 4.p2 指针计算过程 : 由上图可以看到 指针指向的位置开始取一个 int 类型, 地址由低到高 四个字节存储的数据为 0 0 0 1, 由于是小端模式, 高位地址存放在高位 其大小为 0x01 00 00 00 , 转为十进制是 16777216 ;
      这里写图片描述



    4. 数组参数


    ( 1 ) 数组参数 概念 ( 退化成指针 | 需要带上数组长度作为 附属参数 )


    数组参数相关概念 :

    • 1.数组作为参数时编译器行为 : 数组作为参数时, 编译器会将数组 退化成 指针, 此时这个指针就没有数组的长度信息了 ;

      示例 1 : ***void method(int array[]) 等价于 method(int p)**, 此时 *p 中是不包含数组的长度信息的 ;
      示例 2 : *void method(int array[100]) 等价于 method(int p), 此时 *p 中是不包含数组的长度信息的 ;

    • 2.数组作为参数时的最佳用法 : 数组作为参数时, 应该定义另一个 int 类型的参数, 作为数组的长度信息 ;



    ( 2 ) 数组参数 代码示例 ( 数组大小 | 数组参数大小 )


    代码示例 :

    • 1.代码 :
    #include <stdio.h>
    
    /*
    	编译器在编译时, 就将参数改为了 int* array 了
    	C 语言中不会有数组参数的, 即使有, 也在编译时被替换成指针了
    */
    void function(int array[100])
    {
    	printf("方法中数组参数 array 大小 : %ld\n", sizeof(array));
    }
    
    int main()
    {
    	int array[100] = {0};
    	
    	printf("main 方法中 array 数组大小 : array : %ld \n", sizeof(array));
    	
    	function(array);
    	
    	return 0;
    }
    
    • 2.编译执行结果 :
      这里写图片描述



    5. 数组 指针 对比 ( 内存分配 : ① 指针 分配 4 / 8 字节 ② 数组分配所有元素地址 | 作为参数 | 常量[ 数组 ] 变量[ 指针 ] 区别 )


    内存空间分配区别 :

    • 1.指针 ( 分配 4 或 8 字节 ) : 声明指针的时候 只分配了 容纳指针的 4字节 (32位系统) 或 8 字节 (64 位系统) ;
    • 2.数组 ( 分配连续内存 ) : 声明数组时 分配了一篇容纳数组所有元素的一片连续内存空间 ;

    参数上的区别 ( 等价 ) : 作为参数时, 数组 和 指针 参数时等价的, 数组会退化为指针, 丢失长度信息 ;


    指针 数组 的 性质 :

    • 1.数组 ( 常量 ) : 数组大部分情况下可以当做常量指针, 不能作为左值使用, 不能被赋值 ; (sizeof 和 & 作用域数组名时除外) ;
    • 2.指针 ( 变量 ) : 指针是变量, 变量中保存的值 是 内存中的一个地址 ;




    四. 字符串



    1. 字符串概念


    ( 1 ) 概念 ( 本质 是 char[] 数组 | ‘\0’ 结尾 | 存储位置 栈 堆 常量区 )


    字符串相关概念 :

    • 1.字符串本质 : C 语言中没有字符串这个数据类型, 使用 char[] 字符数组来模拟字符串 ;
    • 2.字符串要求 : 不是所有的字符数组都是字符串, 只有***以 ‘\0’ 结尾的字符数组***才是字符串 ;
    • 3.字符串存储位置 : 栈空间, 堆空间, 只读存储区 (常量区) ;


    ( 2 ) 示例代码 ( 字符串概念 | 字符串 )


    代码示例 :

    • 1.代码 (正确的版本) :
    #include <stdio.h>
    #include <malloc.h>
    
    int main()
    {
    	//1. s1 字符数组不是以 '\0' 结尾, 不是字符串
    	char s1[] = {'H', 'e', 'l', 'l', 'o'};
    	
    	//2. s2 是字符串, 其在 栈内存 中分配内存控件
    	char s2[] = {'H', 'e', 'l', 'l', 'o', '\0'};
    	
    	//3. s3 定义的是字符串, 在 只读存储区 分配内存空间 
    	//	 s3 指向的内容无法修改, 如果想要修改其中的数据, 会在执行时报段错误
    	char* s3 = "Hello";
    	//这个操作在执行时会报段错误, 因为 s3 指针指向只读存储区
    	//s3[0] = 'h';
    	
    	//4. s4 是以 '\0' 结尾, 是字符串, 在 堆空间 中分配内存
    	char* s4 = (char*)malloc(2*sizeof(char));
    	s4[0] = 'H';
    	s4[1] = '\0';
    	
    	return 0;
    }
    
    • 2.编译运行结果 ( 错误版本 报错提示 ) : 取消 s3[0] = ‘h’; 注释, 尝试修改 只读存储区的数据 , 运行时会报段错误 ;
      这里写图片描述



    2. 字符串 长度


    ( 1 ) 字符串长度计算 ( 不包括 ‘\0’ | 标准库中有该函数)


    字符串长度 :

    • 1.概念 : 字符串包含的字符个数, 不包含 ‘\0’ , 只包括有效字符 ;
    • 2.计算字符串长度 : 根据从字符串开始到 ‘\0’ 结束, 计算不包括 ‘\0’ 的字符个数 ;
    • 3.数组不完全使用 : 如果数组长度100, 在第50个元素位置出现了 ‘\0’, 那么这个字符串长度是 49, 数组长度是 100 ;

    针对 C 标准库已有的函数 :

    • 1.不要自己实现 C 标准库功能 : C 标准库是优化到极致, 个人修改的效果比库函数效果要差 ;
    • 2.复用库函数效率高 : 不要重复制造轮子 ;


    ( 2 ) 代码示例 ( 字符串长度计算示例 )


    代码示例 :

    • 1.代码 :
    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
    	//1. 字符串长度 : 以 '\0' 之前的个数为准, 不包括 '\0' , 字符串长度 5
    	//	 即使后面有有效的字符, 那么也不属于字符串 c 
    	//2. 数组长度 : 数组长度 666
    	char c[666] = {'H', 'e', 'l', 'l', 'o', '\0', 'w', 'o', 'r', 'l', 'd'};
    	
    	printf("字符串长度 : %ld, 字符数组长度 : %ld\n", strlen(c), sizeof(c));
    	
    	return 0;
    }
    
    • 2.编译运行结果 :
      这里写图片描述


    ( 3 ) 代码示例 ( 自己实现 strlen 方法 )


    实现 strlen 方法代码示例 ( 普通版本 ) :

    • 1.代码 :
    #include <stdio.h>
    #include <assert.h>
    
    size_t strlen(const char* s)
    {
    	size_t len = 0;
    	//1. 如果 s 为 NULL, 直接中断程序
    	assert(s);
    	//2. 指针先自增, 在取指针内指向的数据值, 看看是否为'\0'
    	//		如果指向的数据为 '\0', 那么循环中断执行下面的内容
    	while(* s++)
    	{
    		len ++;
    	}
    	return len;
    }
    
    int main()
    {
    	char * s1 = "0123";
    	char * s2 = NULL;
    	
    	//1. 测试 s1 的实际长度, 返回 4
    	printf("s1 长度 : %u\n", strlen(s1));
    	//2. 测试空字符串长度, 运行时会中断
    	printf("s2 长度 : %u\n", strlen(s2));
    	
    	return 0;
    }
    
    • 2.编译运行结果 :
      这里写图片描述

    实现 strlen 方法代码示例 ( 递归版本 ) :

    • 1.代码 :
    #include <stdio.h>
    #include <assert.h>
    
    size_t strlen(const char* s)
    {
    	//1. assert(s) 先验证是否为 NULL , 如果为 NULL 中断程序
    	//2. 递归退出条件 : *s 为 '\0' 时, 递归退出
    	//3. s + 1 即指向 字符串的 下一个 char 元素, 计算 下一个 char 到结尾的个数, 
    	//		当指向 '\0' 时, 之后的字符串个数为0, 然后依次退出递归
    	return ( assert(s), ( *s ? (strlen(s + 1) + 1) : 0 ) );
    }
    
    int main()
    {
    	char * s1 = "0123";
    	char * s2 = NULL;
    	
    	//1. 测试 s1 的实际长度, 返回 4
    	printf("s1 长度 : %u\n", strlen(s1));
    	//2. 测试空字符串长度, 运行时会中断
    	printf("s2 长度 : %u\n", strlen(s2));
    	
    	return 0;
    }
    
    • 2.编译执行结果 :
      这里写图片描述



    3. 字符串函数 长度不受限制 情况


    ( 1 ) 不受限制的字符串函数 ( 函数自动寻找 ‘\0’ 确定字符串大小 | stpcpy | strcat | strcmp )


    不受限制的字符串函数相关概念 :

    • 1.字符串常用方式 : 一般在函数中使用字符串时, 需要指明字符串的大小, 因为字符串数组 一旦当做函数参数时, 就退化成指针, 失去了大小信息 ;
    • 2.字符串相关的函数不需要大小信息 : 在 string.h 中的方法, 不需要传入大小信息, 函数中会自动寻找 ‘\0’ 来计算字符串的长度 ;
    • 3.参数不是字符串则出错 : 不受限制字符串函数如果传入的字符串没有 ‘\0’ , 则会出错 ;

    不受限制的字符串函数示例 :

           char *stpcpy(char *dest, const char *src);//字符串拷贝
    
           char *strcat(char *dest, const char *src);//字符串拼接
    
           int strcmp(const char *s1, const char *s2);//字符串比较
    

    不受限制字符串函数 的 相关注意事项 :

    • 1.字符串必须以 ‘\0’ 结尾 : 此类函数相关的字符串必须以 ‘\0’ 结尾, 因为字符串长度是根据找到的 ‘\0’ 来计算的, 如果没有 ‘\0’ 会报错 ;

    • 2.字符串长度改变相关 : strcpy ( 字符串拷贝 ) 和 strcat ( 字符串拼接 ) 必须保证 拷贝 或 拼接的 目标数组 有足够的空间来保存结果字符串 ;

    • 3.字符串比较函数 : strcmp 两个字符串比较, 如果返回 0 , 表示两个字符串相等 ;

      • 函数 : int strcmp(const char *s1, const char *s2);
      • ( 1 ) 返回值 等于 0 : 两个字符串相等 ;
      • ( 2 ) 返回值 大于 0 : 第一个字符串 大于 第二个字符串 ;
      • ( 3 ) 返回值 小于 0 : 第一个字符串 小于 第二个字符串 ;

      注意字符串要求 : strcmp 函数不会修改 s1 和 s2 字符串的值, 但是两个字符串必须符合要求 以 ‘\0’ 结尾 ;



    ( 2 ) 代码示例 ( 自己实现字符串拷贝函数 )


    实现拷贝字符串函数 :

    • 1.代码 :
    #include <stdio.h>
    #include <assert.h>
    
    //函数作用, 将 src 字符串 拷贝到 dst 指针指向的内存中, 同时将拷贝完的结果 dst 返回
    char* strcmp ( char* dst, const char* src )
    {
    	//1. 安全编程, 传入的两个值不能为 NULL
    	assert(dst && src);
    	
    	//2. 将 src 指针指向的 字符 赋值给 dst 指针指向的值, 然后两个指针自增 1
    	//		如果赋值的指针不等于 '\0' , 那么继续赋值, 如果赋值的值为 '\0' 就退出循环
    	while( (* dst++ = * src++) != '\0' );
    	
    	//3. 其返回值也是 dst 参数, 参数也可以当作返回值使用
    	return dst; 
    }
    
    int main()
    {
    	char dst[20];
    	printf("%s\n", strcpy(dst, "字符串拷贝"));
    	return 0;
    }
    
    • 2.编译运行结果 :
      这里写图片描述



    4. 字符串函数 长度受限制 情况


    ( 1 ) 受限制的字符串函数 ( 推荐使用 降低错误率 )


    长度受限制的字符串函数 :

    • 1.概念 : 长度受限制的字符串函数, 其 字符串参数 会 跟随一个字符串先关的长度测参数, 一般为 size_t 类型, 用于限定字符串的字符数 ;
    • 2.推荐使用 : 在函数调用的时候, 优先使用长度受限制的字符串函数, 这样会减少出现错误的几率 ;

    长度受限字符串函数 举例说明 :

    • 1.字符串拷贝 : char *strncpy(char *dest, const char *src, size_t n) ;
      • ( 1 ) 作用 : 拷贝 src 中 n 个字符 到 dest 目标字符串中 ;
      • ( 2 ) src 长度 小于 n : 使用 ‘\0’ 填充剩余空间 ;
      • ( 3 ) src 长度 大于 n : 只赋值 n 个字符, 并且不会使用 ‘\0’ 结束 , 因为已经复制了 n 个字符了 ;
    • 2.字符串拼接 : char *strncat(char *dest, const char *src, size_t n) ;
      • ( 1 ) 作用 : 从 src 字符串中赋值 n 个字符 到 dest 字符串中 ;
      • ( 2 ) 始终 ‘\0’ 结尾 : 函数始终在 dest 字符串之后添加 ‘\0’;
      • ( 3 ) 不填充剩余空间 : 对于拼接后剩余的数组空间, 不使用 ‘\0’ 填充 ;
    • 3.字符串比较 : int strncmp(const char *s1, const char *s2, size_t n) ;
      • ( 1 ) 作用 : 比较 src 和 dest 中前 n 个字符 是否相等 ;




    五. 指针数组 与 数组指针



    1. 数组指针


    ( 1 ) 数组类型介绍 ( 数组元素类型 | 数组大小 | 举例 int[8] )


    数组类型 :

    • 1.数组类型要求 : 数组的类型有两个决定要素, 分别是 ① 数组元素类型 和 ② 数组大小 ;
    • 2.数组类型示例 : int array[8] 的类型是 int[8] ;

    数组类型定义 :

    • 1.数组类型重命名 : 使用 typedef 实现 , typedef type(数组名)[数组大小] ;
    • 2.数组类型重命名示例 :
      • ( 1 ) 自定义一个 int[5] 类型的数组 : typedef int(ARRAY_INT_5)[5] ;
      • ( 2 ) 自定义一个 float[5] 类型的数组 : typedef float(ARRAY_FLOAT_5)[5] ;
    • 3.根据自定义的数组类型声明变量 :
      • ( 1 ) 使用自定义的 ARRAY_INT_5 声明变量 : ARRAY_INT_5 变量名 ;
      • ( 2 ) 使用自定义的 ARRAY_FLOAT_5 声明变量 : ARRAY_FLOAT_5 变量名 ;


    (2) 数组指针简介 ( 指向数组的 一个 指针 | 数组指针类型定义方式 : 数组元素类型 ( * 指针名称 ) [数组大小] )


    数组指针 : 本质是一个指针 ;

    • 1.数组指针作用 : 数组指针 用于 指向一个数组 ;
    • 2.数组名意义 : 数组名是数组首元素地址, 不是数组的首地址 , &数组名 是数组的首地址 ;
    • 3.数组首地址 : & 数组名 是数组首地址 , 数组首地址 不是 数组名( 数组首元素地址 ) ;
    • 4.数组指针定义 : 数组指针是通过 定义 数组类型 的指针;
      • ( 1 ) 数组类型 : 是之前说过的 包含 ① 数组元素类型 , ② 数组大小 两个要素, 定义数组类型 : typedef int(ARRAY_INT_5)[5] 定义一个 int[5] 类型的数组类型 ;
      • ( 2 ) 定义数组指针 : ARRAY_INT_5* 指针名称 ;
    • 4.数组指针定义的另外方式 : 类型 ( * 指针名称 ) [数组大小] ;
      • ( 1 ) 示例 : *int (p)[5] , 定义一个指针 p, 指向一个 int[5] 类型的指针 ;
      • ( 2 ) 不推荐此种写法 : 可读性很差 ;

    数组指针 和 数组首元素指针 大小打印 :

    • 1.代码示例 :
    #include <stdio.h>
    
    int main()
    {
    	//定义数组
    	int array[5] = {0};
    	
    	//1. 普通指针, 普通指针类型是 int, 指向数组首元素首地址, 其指向内容大小为数组的首元素
    	int *p = array;
    	
    	//2. 数组指针, 数组指针类型是 int[5], 指向数组首地址, 其指向的内容大小是整个数组 
    	typedef int(ARRAY_INT_5)[5];
    	ARRAY_INT_5 *p1 = &array;
    	
    	//3. 打印数字首元素指针 和 数组指针 指向的内存大小
    	//	 数组首元素指针 大小 为 4
    	//	 数组指针 大小 为 20
    	printf("%ld, %ld\n", sizeof(*p), sizeof(*p1));
    	
    	return 0;
    }
    
    • 2.编译运行结果 :
      这里写图片描述


    ( 3 ) 代码示例 ( 定义数组类型 | 数组指针用法 )


    代码示例 :

    • 1.代码 :
    #include <stdio.h>
    
    //1. 自定义数组类型 int[5] 类型为 ARRAY_INT_5, 包含信息 ①int 类型数组 ②数组包含5个元素
    typedef int(ARRAY_INT_5)[5];
    
    //2. 自定义数组类型 float[5] 类型为 ARRAY_FLOAT_5, 包含信息 ①float 类型数组 ②数组包含5个元素
    typedef float(ARRAY_FLOAT_5)[5];
    
    //3. 自定义数组类型 char[5] 类型为 ARRAY_CHAR_5, 包含信息 ①char 类型数组 ②数组包含5个元素
    typedef char(ARRAY_CHAR_5)[5];
    
    int main()
    {
    	//1. 使用自定义数组类型定义数组 : 定义一个 int 类型数组, 个数为 5个, 等价于 int array_1[5];
    	ARRAY_INT_5 array_1;
    	//( 1 ) sizeof(ARRAY_INT_5) 打印 ARRAY_INT_5 类型大小, 
    	//		该类型包含信息 ① int 类型数组 大小 5 个 ② 大小为 20 字节
    	//( 2 ) sizeof(array_1) 打印 array_1 数组大小
    	printf("%ld, %ld\n", sizeof(ARRAY_INT_5), sizeof(array_1));
    	
    	//2. 常规方法定义数组
    	float array_2[5];
    	//3. 数组指针 : 定义一个数组指针, 指向数组地址, 使用 &数组名 来获取数组地址, 其指向的内存内容大小为 20 字节
    	//	 注意区分数组首元素地址, array_2 是数组首元素地址, 指向内容大小为 4 字节
    	//   注意区分 float* p = array_2, 这个指针是指向数组首元素地址的指针
    	ARRAY_FLOAT_5* p = &array_2;
    	
    	//( 1 ) (*p)[i] 解析 : p 是数组指针, 其地址是数组地址 &array_2, 
    	//		*p 就是数组地址中存放的数组内容 *(&array_2), 即 (*p)[i] 等价于 array_2[i]
    	//		(*p)[i] = i 语句 等价于 array_2[i] = i ;
    	int i = 0;
    	for( i = 0; i < sizeof(array_2)/sizeof(*array_2); i ++)
    	{
    		(*p)[i] = i;
    	}
    	//( 2 ) 打印数组中每个值
    	for(i = 0; i < sizeof(array_2)/sizeof(*array_2); i ++)
    	{
    		printf("array_2[%d] = %f\n", i, array_2[i]);
    	}
    	
    	
    	
    	//4. 使用自定义数组类型定义数组
    	ARRAY_CHAR_5 array_3;
    	//5. 常规方法定义数组指针 : char(*p1)[5] 等价于 ARRAY_CHAR_5* p1; 
    	char(*p1)[5] = &array_3;
    	//6. 定义数组指针, 但是这个赋值过程中 左右两边类型不一致, 
    	//	 array_3 会被强转为 char[6] 类型的数组指针
    	char(*p2)[6] = array_3;
    	
    	//( 1 ) &array_3 : 是 array_3 数组的 数组地址, 绝对值 等于 其数组首元素地址
    	//( 2 ) p1 + 1 : p1 指针 是 array_3 的数组指针, 该指针指向一个数组, p1 + 1 增加的是一个数组的地址
    	//( 3 ) p2 + 1 : p2 指针 也是一个数组指针, 这个数组比 array_3 数组大一个, 因此 p2 + 1 地址可能比 p1 + 1 大1字节
    	printf("&array_3 地址值 : %x, p1 + 1 地址值 : %x, p2 + 1 地址值 : %x\n", &array_3, p1 + 1, p2 + 1);
    	
    	
    	return 0;
    }
    
    • 2.编译运行结果 :
      这里写图片描述



    2. 指针数组


    ( 1 ) 指针数组简介 ( 数组中存储的元素是指针 | 数组指针 int (array)[5] 本质是指针 | 指针数组 int array[5] 本质是数组 )


    指针数组 相关概念 :

    • 1.指针数组概念 : 指针数组是一个普通的数组, 其元素类型是 指针类型 ;
    • 2.指针数组定义 : 类型* 数组名称[数组大小] ;
      • ( 1 ) 指针数组 : int* array[5] ;
      • ( 2 ) 数组指针 : int (*array)[5] ;


    ( 2 ) 代码示例 ( 指针数组使用案例 )


    指针数组代码示例 :

    • 1.代码 :
    #include <stdio.h>
    
    /*
    	1. 函数作用 : 传入一个字符串, 和 一个字符串数组, 找出字符串在字符串数组中的索引位置, 从 0 开始计数
    	2. const char* key 参数分析 : 
    		( 1 ) 常量分析 : 左数右指(const 在 * 左边 数据是常量, const 在 * 右边 指针是常量), 这里数据是常量, 不可修改
    		( 2 ) 参数内容 : 字符串类型, 并且这个字符串内容不能修改
    	3. const char* month[] 参数分析 : 指针数组
    		( 1 ) 常量分析 : 左数右指, 指针指向的数组内容不能修改
    		( 2 ) 参数内容 : 指针数组, 每个指针指向一个字符数组, 这些字符数组都是字符串, 这些指针不可改变
    */
    int find_month_index(const char* key, const char* month[], const int month_size)
    {
    	int index = -1;
    	
    	//4. 遍历指针数组中指向的每个字符串, 与传入的 key 进行对比, 如果相等, 那么返回字符串在指针数组的索引
    	//		( 1 ) 对比函数 : 注意 strcmp 函数, 对比两个字符串, 如果相等 则 返回 0 ;
    	int i = 0;
    	for(i = 0; i < month_size; i ++)
    	{
    		if(strcmp(key, month[i]) == 0)
    		{
    			index = i;
    		}
    	}
    	return index;
    }
    
    int main()
    {
    	//1. 定义 指针数组, month 数组中, 每个元素都是一个指针, 每个指针指向字符串 
    	const char* month[] = {  
    		"January", "Febrary", "March", "April", 
    		"May", "June", "July", "August", 
    		"September", "October", "November", "December"  
    	}; 
    
    	printf("查询 July 字符串索引 : %d\n", find_month_index("July", month, sizeof(month)/sizeof(*month)));
    	printf("查询 HanShuliang 字符串索引 : %d\n", find_month_index("HanShuliang", month, sizeof(month)/sizeof(*month)));
    	return 0;
    }
    
    • 2.编译执行结果 :
      这里写图片描述



    3. main 函数参数 分析


    ( 1 ) main 函数简介


    main 函数分析 :

    • 1.main 函数 : main 函数是 ① 程序的 入口函数 , ② 操作系统调用的函数 ;
    • 2.main 函数示例 :
    int main()
    int main(int argc)
    int main(int argc, char* argv[])
    int main(int argc, char* argv[], char* env[])
    
    • main 函数参数说明 :
      • ( 1 ) int argc 参数 : 程序命令行参数个数 ;
      • ( 2 ) char argv[] 参数* : 程序命令行字符串参数数组, 这是一个数组指针, 数组中每个元素都是指向一个字符串的指针 ;
      • ( 3 ) char env[] 参数* : 环境变量数组, 这是一个数组指针, 数组中每个元素都是指向一个字符串的指针 ; 这个环境变量 在 Windows 中是配置的 环境变量, 在 Linux 中是配置在 /etc/profile ( 一种设置方式, 还有很多设置方式 ) 中定义的环境变量 ;


    (2) main 函数 代码示例


    main 函数代码示例 :

    • 1.代码示例 :
    #include <stdio.h>
    
    int main(int argc, char* argv[], char* env[])
    {
    	//1. 循环控制变量
    	int i = 0;
    	
    	//2. 打印 main 函数参数
    	printf("########参数个数%d 开始打印参数\n", argc);
    	for(i = 0; i < argc; i ++)
    	{
    		printf("%s\n", argv[i]);
    	}
    	printf("########参数打印完毕\n");
    	printf("\n");
    	printf("\n");
    	
    	//3. 打印环境变量
    	printf("########开始打印环境变量\n");
    	for(i = 0; env[i] != NULL; i ++)
    	{
    		printf("%s\n", env[i]);
    	}
    	printf("########环境变量打印完毕\n");
    	
    	return 0;
    }
    
    • 2.编译运行结果 : 环境变量打印出来的东西太多了, 就不一一截图查看 ;
      这里写图片描述

      这里写图片描述





    六. 多维数组 和 多维指针



    1. 二维指针 ( 指向指针的指针 )



    ( 1 ) 二维指针简介 ( 指向指针的指针 )


    指向 指针 的 指针 ( 二维指针 ) :

    • 1.指针变量 : 指针变量会占用 内存空间 , 很明显可以使用 & 获取指针变量的地址 ;
      • ( 1 ) 32 位系统 : 指针占 4 字节空间 ;
      • ( 2 ) 64 位系统 : 指针占 8 字节空间 ;
    • 2.指向 指针变量 的指针 : 定义一个指针, 这个指针 保存一个 指针变量 的地址 ( 不是 指针变量指向的地址, 是指针变量所在的本身的地址 ) ;

    指针变量 的 传值 和 传址 调用 :

    • 1.指针变量传值调用 ( 一维指针 ) : 直接将指针值传入, 修改的是 指针 指向的内存空间内容 ;

    如 : void fun ( char *p ) , 这是相对于指针的传值调用, 相对于 char 类型数据的传址调用, 用于修改 p 指针指向的内存中的值 ;

    • 2.指针变量传址调用 ( 二维指针 ) : 在函数内部 修改 函数外部的变量, 需要传入一个地址值, 如果要修改的是一个指针, 那么需要传入指针的地址, 即参数是一个指向指针的指针 ; 指针变量传址调用, 修改的是 指针 指向的 指针变量 ;

    如 : void fun(char ** pp) 该传址调用 即 传入的是 char* 指针的地址, 修改的是 pp 二维指针 指向的 char* 类型指针 ;

    • 3.函数中修改函数外部变量 : 只能使用指针 指向这个外部变量, 才可以修改这个外部变量 , 如果这个外部变量本身就是一个指针 , 那么就必须传入这个指针的地址, 那么传入的参数的内容就是一个二维指针 ;


    ( 2 ) 代码示例 ( 指针的传址调用 | 指向指针的指针 | 重置指针指向的空间 )


    代码示例 :

    • 1.代码 :
    #include <stdio.h>
    #include <malloc.h>
    
    /*
    	1. 方法作用 : 为参数 char **p 指向的 指针 重新分配内存空间
    	2. char **p 参数 : 需要在函数中修改函数外部的变量, 就需要传入一个 指向要修改的目标 的指针变量
    						需要修改的内容 是一个指针, 那么需要传入的参数就是 指向 指针变量 的指针
    						这样才能完成 传址调用, 用来修改函数外部的变量 
    	3. 
     */
    int reset_memory(char **p, int size, int new_size)
    {
    	//1. 定义函数中使用的变量
    	//( 1 ) 定义返回值
    	int ret = 0;
    	//( 2 ) 循环控制变量
    	int i = 0;
    	//( 3 ) 新空间的大小
    	int len = 0;
    	//( 4 ) 申请的新空间
    	char* p_new = NULL;
    	//( 5 ) 用于计算用的指向新空间的指针
    	char* p_new_tmp = NULL;
    	//( 6 ) 用于指向老空间指针, 使用 * 与 传入的二维指针 计算 得来
    	//		char** p 是指向 char* 指针 的 指针, 使用 *p 即可获得 指向 char* 的指针
    	char* p_old = *p;
    	
    	//2. 前置条件安全判定, 避免无意义崩溃
    	if(p == NULL || new_size <= 0)
    	{
    		return ret;
    	}
    	
    	//3. 重新分配空间, 并拷贝内存中的内容
    	//( 1 ) 重新分配内存空间
    	p_new = (char*)malloc(new_size);
    	//( 2 ) 为计算使用的指针赋值, 之后赋值是需要使用指针的自增, 为了不改变指针内容, 这里我们设置一个临时指针
    	p_new_tmp = p_new;
    	//( 3 ) 获取 新空间 和 老空间 内存大小的最小值, 将老空间的内容拷贝到新空间中
    	//		1> 新空间大于老空间 : 只拷贝所有老空间中的内容到新空间中
    	//		2> 新空间小于老空间 : 只拷贝能将新空间填满的内容, 字符串可能丢失 '\0'
    	len = (size < new_size) ? size : new_size;
    	//( 4 ) 将老空间中的内容拷贝到新空间中
    	for(i = 0; i < len; i ++)
    	{
    		*p_new_tmp++ = *p_old++ ;
    	}
    	
    	//4.释放原空间, 并修改传址调用的参数内容
    	free(*p);
    	*p = p_new;
    	ret = 1;
    	
    	return ret;
    }
    
    int main(int argc, char* argv[], char* env[])
    {
    	//1. 第一次为 char 类型指针分配 10 个字节空间
    	char* p = (char*)malloc(10);
    	// 		打印指针 p 指向的内存地址, 即分配的内存空间地址
    	printf("p 第一次分配空间后指向的地址 : %x\n", p);
    	
    	//2. 重置内存空间, 原来分配 10字节, 现在改为分配 8 字节
    	//		注意 : 在 reset_memory 函数中改变函数外部变量的值, 需要传址调用, 即将变量的地址传到函数中
    	reset_memory(&p, 10, 8);
    	//		打印重置空间后的指针指向的地址
    	printf("p 重置空间后指向的地址 : %x\n", p);
    	
    	return 0;
    }
    
    • 2.编译运行结果 :
      这里写图片描述



    2. 二维数组


    ( 1 ) 二维数组 ( 存放方式 | 数组名 | 首元素类型 | 数组名 类似 常量指针 | )


    二维数组 相关概念 : 二维数组 int array[5][5] ;

    • 1.二维数组存放方式 : 二维数组在内存中以 一维数组 方式排布 ;
    • 2.二维数组数组名 : 代表 二维 数组 首元素 地址, 其 首元素 是一个一维数组 , 即 array[0] ;
    • 3.二维数组首元素类型 : 数组名 array 指向二维数组首元素, 那么其类型是 数组指针, 数组类型 为 int[5] ( ① int 类型数组, ② 含有 5 个元素 ) ;
    • 4.数组名类似常量指针 : 二维数组的数组名可以看做常量指针, 除了两种情况 sizeof 计算大小 和 & 获取地址时 ;
    • 5.具体的数据值存放 : 二维数组第一维是 数组指针, 第二围才是具体的数据值 ;
    • 6.二维数组图示 :
      这里写图片描述

    一些注意点 :
    1.编译器没有二维数组概念 : C语言中没有二维数组改变, 编译器 都按照一维数组来处理, 数组的大小在编译时就确定了 ;
    2.二维数组由来 : C 语言中的数组元素可以是任何类型, 即可以是一维数组, 这样就产生了二维数组 ;
    3.首元素地址确定时间 : 在编译阶段确定的 除了 数组大小外, 数组的首元素也是在编译阶段确定的, 在程序运行阶段首元素地址不能被修改 (看做常量) ;



    (2) 代码示例 ( 以一维数组方式遍历二维数组 | 体现二维数组的数据排列 )


    代码示例 :

    • 1.代码 :
    #include <stdio.h>
    #include <malloc.h>
    
    /*
    	遍历一维数组
    	1. int *array 参数解析 : 传入的一维数组的首地址 
    	2. int size 参数解析 : 用于限制数组大小, 数组传入后也会退化为指针, 
    			数组是带有元素个数属性的, 因为数组类型是 int[9], 但是指针不包含元素个数 指针类型是 int*
    */
    void array_traverse (int *array, int size)
    {
    	int i = 0;
    	for(i = 0; i < size; i ++)
    	{
    		//使用数组递增的方式打印数组元素
    		printf("array[%d] = %d\n", i, *array++);
    	}
    }
    
    int main()
    {
    	//1. 定义二维数组
    	int array[2][2] = {{0, 1}, {2, 3}};
    	//2. 获取二维数组首地址, 将其赋值给 一维数组 int* p
    	int *p = &array[0][0];
    	
    	//3. 打印一维数组, 可以看到, 二维数组的数据排列
    	//		先将 {0, 1} 放入的内存, 然后紧接着存放 {2, 3} 数据
    	array_traverse(p, 4);
    	
    	return 0;
    }
    
    • 2.编译执行结果 :
      这里写图片描述

    代码分析 :
    将二维数组的首地址赋值给 类型相同 的一维数组, 遍历该一维数组, 并且该数组的大小为 二维数组所有值得大小 , 由此可以看出, 二维数组的数据排布是按照索引, 先放入二维数组的第一个数组元素, 在按照索引依次将数组放入内存中 ;




    3. 数组名


    ( 1 ) 数组名 简介 ( 数组首元素地址 | &数组名 是 数组地址 )


    数组名 相关概念 :

    • 1.数组名 : 数组名代表了 数组首元素地址 ;
    • 2.一维数组 数组名 : int array[2], array 指向数组首地址, 其指向了一个 int 类型首元素, array 类型为 int * ;
    • 3.二维数组 数组名 : int array[2][3] , array 指向数组首地址, 其指向了 类型 int[3] 数组的首元素, array 的类型是 int(*)[5] ;
      • ( 1 ) 类似常量指针 : 二维数组的数组名 可以看做为 常量指针 ;
      • ( 2 ) 看做一维数组 : 二维数组可以看做一维数组, 只是这个一维数组内的元素 是 一维数组 ;
      • ( 3 ) 二维数组元素 : 二维数组中每个元素都是 基础类型同类型的 一维数组,


    ( 2 ) 代码示例 ( 数组名指针指向的内容 | 二维指针数组名对应的指针运算 )


    代码示例 :

    • 1.代码 :
    #include <stdio.h>
    
    int main()
    {
    	//1. 定义二维数组, array 代表了 数组首地址
    	//		array 的本质是 指向 一个 int(*)[5] 类型的一维数组 的指针
    	int array[5][5];
    	//2. 定义数组指针, 指针 p 指向一个 int(*)[4] 一维数组 
    	int(*p)[4];
    	
    	//3. 注意了, 两个指针类型不同
    	//	 ( 1 ) array 指针 : 指向 int(*)[5] 类型的一维数组, array + 1 即内存中移动了 5 个 int 大小的内存
    	//			① 下标运算 : array[1] 即 array 指针 + 1 ;
    	//	 ( 2 ) p 指针 : 	指向 int(*)[4] 类型的一维数组, p + 1 即内存中移动了 4 个 int 大小的内存
    	p = array;
    	
    	/*
    		4. 指针计算过程 : 
    			( 1 ) &array[4][2] 计算 : array 指针指向 int(*)[5] 一维数组, 
    					array[4] 计算过程是 要加上 4 个 int(*)[5] 一维数组 , 
    					array[4] 指向 内存中(二维数组起始为0) 第 20 个元素, array[4][2] 指向第 22 个元素, 
    					&array[4][2] 是一个指向 array[4][2] 的int 类型指针
    					
    			( 2 ) &p[4][2] 计算 : p 指针指向 int(*)[4] 一维数组, 
    					p[4] 计算过程是 要加上 4 个 int(*)[4] 一维数组 , 
    					p[4] 指向 内存中(二维数组起始为0) 第 16 个元素, p[4][2] 指向第 18 个元素, 
    					&p[4][2] 是一个指向 p[4][2] 的int 类型指针
    					
    			( 3 ) 一个指向第 22 个元素, 一个指向第 18 个元素, 其起始地址是一样的, 因此结果是 -4
    	*/
    	printf("%ld\n", &p[4][2] - &array[4][2]);
    	
    	
    	return 0;
    }
    
    • 2.编译运行结果 :
      这里写图片描述
    • 3.图示 :
      这里写图片描述

    ( 3 ) 代码示例 ( 一维数组遍历 | 二维数组遍历 )


    代码示例 :

    • 1.代码 :
    #include <stdio.h>
    
    //1. 宏定义, 使用该宏 计算数组大小
    #define ARRAY_SIZE(a) (sizeof(a)/sizeof(*a))
    
    int main()
    {
    	//1. 遍历一维数组
    	int array_1d[5] = {0, 1, 2, 3, 4};
    	int i = 0;
    	for(i = 0; i < ARRAY_SIZE(array_1d); i++)
    	{
    		/*
    			*(array_1d + i) 执行过程 : 
    			( 1 ) array_1d + i : 首元素指针 + i, 即获取指向第 i 个元素的指针
    			( 2 ) *(array_1d + i) : 获取第 i 个元素指向的数据, 该表达式等价于 array_1d[i]
    		*/
    		printf("array_1d[%d] = %d\n", i, *(array_1d + i));
    	}
    	
    	
    	//2. 遍历二维数组
    	int array_2d[3][3] = { {0, 1, 2}, {3, 4, 5}, {6, 7, 8} };
    	int j = 0;
    	for(i = 0; i < ARRAY_SIZE(array_2d); i ++)
    	{
    		for(j = 0; j < ARRAY_SIZE(array_2d[0]); j ++)
    		{
    			/*
    				*(*(array_2d + i) + j) 计算过程 : 
    				( 1 ) array_2d : 是二维数组数组首元素, 本质是数组指针, 类型是 int(*)[3], 指向一个 int[3] 数组 ;
    				( 2 ) array_2d + i : 是指向数组 第 i 个元素, 其地址本质能上移动了 i 个 int[3] 数组所占的空间 ;
    				( 3 ) *(array_2d + i) : array_2d + i 是一个数组指针, 使用 * 即获得其指向的内容 一个 int[3] 数组;
    				( 4 ) *(array_2d + i) + j : 获取其指向的 int[3] 类型数组的 第 j 个指针 ;
    				( 5 ) *(*(array_2d + i) + j) : 即获取 int[3] 类型数组中 第 j 个指针指向的实际的 int 数据 ;
    			*/
    			printf("array_2d[%d][%d] = %d\n", i, j, *(*(array_2d + i) + j));
    		}
    	}
    	
    	return 0;
    }
    
    • 2.编译运行结果 :
      这里写图片描述

    ( 4 ) 代码示例 ( 为二维数组申请内存空间 )


    算法思想 : 为 int[3][3] 申请内存空间 ;

    • 1.申请空间 : 分别申请 数组指针空间 和 数据空间 ;
      • ( 1 ) 申请数组指针空间 : 申请三个 数组指针 空间, 只是三个普通的指针, 但是该指针类型是 int(*)[3], 即指向 int[3] 数组的指针 ;
      • ( 2 ) 申请数据空间 : 申请 能存放 9个 int 值的数据空间 ;
    • 2.分配指针 : 将申请的三个 数组指针 , 分别指向对应的 9 个int 值空间的对应位置 ;

    代码示例 :

    • 1.代码 :
    #include <stdio.h>
    #include <malloc.h>
    
    //1. 宏定义, 使用该宏 计算数组大小
    #define ARRAY_SIZE(a) (sizeof(a)/sizeof(*a))
    
    /*
    	为二维数组分配内存空间
    	1. 参数说明 : 
    	( 1 ) int row : 二维数组的行数, 即 有多少 一维数组; 
    	( 2 ) int column : 每个一维数组中的元素个数
    */
    int** malloc_array_2d(int row, int column)
    {
    	//1. 分配 数组指针 空间, 分配数组指针空间, 共有 row 个指针
    	int** ret = (int**)malloc(sizeof(int*) * row);
    	//2. 分配数据存放空间, 即 row * column 个 int 类型数据存放的空间
    	int * p = (int*)malloc(sizeof(int) * row * column);
    	
    	if(ret && p)
    	{
    		//3_1. ret 和 p 内存分配成功, 那么开始 将 ret 数组指针 与 p 内存空间 连接起来
    		int i = 0;
    		for(i = 0; i < row; i ++)
    		{
    			/*
    				说明 : 
    				( 1 ) ret 是分配的 数组指针 数组的 首元素, ret[i] 是第 i 个 数组指针 元素
    				( 2 ) p + i*column 是 具体到 int 类型元素的首地址
    				( 3 ) 这里将 int* 类型指针赋值给了 int(*)[column] 类型指针, 所幸是地址赋值, 将
    						int* 指针存放的地址 赋值给了了 int(*)[column] 类型指针的地址
    			*/
    			ret[i] = (p + i * column);
    		}
    	}
    	else
    	{
    		//3_2. 如果分配空间失败, 即 ret 或 p 有一个内存分配失败, 释放所有内存, 返回空
    		free(ret);
    		free(p);
    		ret = NULL;
    	}
    	
    	return ret;
    }
    
    //释放申请的二维数组空间
    void free_array_2d(int** array_2d)
    {
    	//array_2d[0] 代表了 第一个数组 的收个 int 类型元素地址, 该操作释放了int类型数据空间内存
    	free(array_2d[0]);
    	
    	//array_2d 代表了 数组指针 数组的 首元素地址, 该操作释放了数组指针空间的内存
    	free(array_2d);
    }
    
    int main()
    {
    	//1. 申请二维数组空间
    	int** array_2d = malloc_array_2d(3, 3);
    	int i = 0, j = 0;
    	//2. 为二维数组赋值
    	for(i = 0; i < 3; i ++)
    	{
    		for(j = 0; j < 3; j ++)
    		{
    			*(*(array_2d + i) + j) = i * 3 + j;
    		}
    	}
    	
    	//3. 打印二维数组内容
    	for(i = 0; i < 3; i ++)
    	{
    		for(j = 0; j < 3; j ++)
    		{
    			printf("array_2d[%d][%d] = %d\n", i, j, array_2d[i][j]);
    		}
    	}
    	
    	
    	return 0;
    }
    
    • 2.编译运行结果 :
      这里写图片描述
    • 3.图示 :
      这里写图片描述




    五. 数组参数 与 指针参数



    1. 数组参数退化为指针参数的意义


    ( 1 ) 数组参数退化的相关概念 ( 指针退化成数组 )


    一维数组参数退化为指针 :

    • 1.C语言中的拷贝方式 : C 语言中只会以 传值拷贝 的方式来传递参数 ;
      • ( 1 ) 传递指针也是传值 ( 修改指针指向的地址的内容是用户行为 ) : 只是传的是指针变量的值, 但是这个变量中存放着地址, 函数中可以改变这个地址的值 ;
    • 2.数组传递的方式 :
      • ( 1 ) 传递整个数组 : 如果将整个数组传递过去, 如果数组中元素很多, 需要将数组所有元素都要拷贝一份 ;
      • ( 2 ) 传递数组首元素地址 : 将数组名 ( 数组首元素的地址 ) 看做常量指针传入函数 ;
      • ( 3 ) C 语言针对数组参数的效率考虑 : 假如数组有 10000 个元素, 传递数组效率就非常低了, 如果传递数组首元素指针, 只用拷贝指针变量的值, 只拷贝 4 ( 32位系统 ) 或 8 ( 64位系统 ) 个字节, 这样效率能大大提高 ;
    • 3.数组参数退化的意义 : 数组参数退化为指针, 程序的执行效率能大大的提高 ;

    二维数组参数退化问题 :

    • 1.二维数组本质 : 二维数组也可以看做一维数组, 该一维数组中的每个数组元素都是一维数组 ;
    • 2.数组退化过程 :
      • ( 1 ) 一维数组参数退化过程 : void fun(int array[5]) <-> void fun(int array[]) <-> void fun(int* array) 以上的三种类型的参数都是等价的 ;
        • ① 第一次退化 : 数组的个数可以省略掉, 只需要表明数组元素类型即可, 数组元素类型 int[] 类型;
        • ② 第二次退化 : 只含有数组元素类型 不含数组个数的类型, 退化为 对应数组元素类型 的指针类型 ;
      • ( 2 ) 二维数组参数退化过程 : void fun(int array[3][3]) <-> void fun(int array[][3]) <-> void fun(int (*array)[3])
        • ① 第一次退化 : 数组的个数可以省略掉, 只需要表明数组元素类型即可, 数组元素类型 int[3] 类型;
        • ② 第二次退化 : 直接退化为指向 一维数组的 数组指针, 该数组指针类型为 int(*)[3] 类型;

    下面列举数组参数与指针参数一些等价关系 : 去中括号 ( [] ), 变星号 ( * ) , 放左边;

    数组参数 指针参数
    一维数组 int array[5] 指针 *int array
    一维指针数组 int array[5]* 指针 int* array*
    二维数组 int array[3][3] 指针 *int (array)[3]

    注意事项 :
    1.多维数组参数要求 : 传递多维数组参数时, 需要将除第一维之外的其它所有维度的大小都带上 , 否则无法确定数组大小 和 类型, 编译时会报错 ;
    2.数组参数限制 :
    ( 1 ) 一维数组 : 可以不带数组长度, 但是必须指定数组的大小 ;
    ( 2 ) 二维数组 : 数组 第一维 长度可以不带 ( 即 数组指针 元素个数可以省略 ) , 但是数组指针 指向的 数组类型大小必须指定 ( 第二维的大小必须指定 ) ;
    ( 3 ) 三维数组 : 数组 第一维 长度可不带, 但是第二维 和 第三维 长度 必须带上 ;



    ( 2 ) 代码示例 ( 二维数组参数 的指针退化 | 外层指针退化 | 内层数组指针没有退化 )


    代码分析 : 如 int array[3][3] ;

    • 1.二维数组参数退化部分 : 二维数组本身 array 数组大小退化, 其退化为 int (*)[3] 类型, 指向一组数组指针的首地址 ;
    • 2.二维数组参数没有退化部分 : array 数组中, array 作为首元素, 其类型为 int[3] 类型, 该类型 包含 ① 其指向的一维数组 中的元素类型 int 和 ② 一维数组大小 3;

    代码示例 :

    • 1.代码 :
    #include <stdio.h>
    
    void traverse_array_2d(int array_2d[][2], int row)
    {
    	/*
    		计算二维指针的列数
    		( 1 ) array_2d 是二维指针中的 数组指针 数组中的首元素
    		( 2 ) array_2d 是一个完整的数组指针, 该指针中包含着 其指向的数组的 类型 和 大小
    		( 3 ) 数组指针退化时, 退化的只是 array_2d 的 数组指针 数组 (最外层的一维数组) 大小, 
    				其每个元素都是一个 数组指针, 这个数组指针 包含 数组类型 和 大小, 没有退化
    	*/
    	int column = sizeof(*array_2d) / sizeof(*array_2d[0]);
    	int i = 0, j = 0;
    	
    	for(i = 0; i < row; i ++)
    	{
    		for(j = 0; j < column; j ++)
    		{
    			printf("array_2d[%d][%d] = %d\n", i, j, array_2d[i][j]);
    		}
    	}
    }
    
    int main()
    {
    	int array_2d[2][2] = {{0, 1}, {2, 3}};
    	traverse_array_2d(array_2d, 2);
    	return 0;
    }
    
    • 2.编译执行结果 :
      这里写图片描述




    六. 函数指针



    1. 函数类型 和 函数指针


    (1) 相关概念 ( 函数类型要素 ① 返回值, ② 参数类型, ③ 参数个数, ④ 隐含要素 : 参数顺序 | 函数指针类型 返回值类型 (*变量名) (参数列表) )


    函数类型 :

    • 1.函数类型引入 : 每个函数都有自己的类型 ;
    • 2.函数类型要素 : ① 返回值, ② 参数类型, ③ 参数个数, ④ 隐含要素 : 参数顺序 ;
      • 示例 : void fun(int a, float b) 函数的类型为 void( int, float ) ;
    • 3.函数重命名 : 使用 typedef 可以为函数重命名 , typedef 返回值类型 函数名称(参数列表) ;
      • 示例 : typedef void function(int, float), 就是将上面的 fun 函数重命名为 function ;

    函数指针 :

    • 1.函数指针概念 : 函数指针 指向一个 函数类型 变量 ;
    • 2.函数名 : 函数名指向了 函数体 的 入口地址 ;
    • 3.函数指针定义( 宏定义类型 ) : 函数类型* 变量名 ;
    • 4.函数指针类型( 简单类型 ) : 返回值类型 (*变量名) (参数列表) ;


    ( 2 ) 代码示例 ( 定义函数指针 : ①typedef int(FUN)(int); FUN* p; 或者 ② void(*p1)(); | 给 函数指针 赋值 , 右值 可以直接使用 ① 函数名 或 ② &函数名 | 调用函数指针方法 : ① 函数指针变量名(参数) ② (*函数指针变量名)(参数) | 函数名 和 &函数名 是等价的 | 函数指针变量名(参数) 和 (*函数指针变量名)(参数) 也是等价的 )


    代码示例 :

    • 1.代码 :
    #include <stdio.h>
    
    //1. 定义函数类型 FUN, 其类型为 int(int)
    typedef int(FUN)(int);
    
    //2. 定义一个 int(int) 类型的函数
    int fun_1(int i)
    {
    	return i * i * i;
    }
    
    //3. 定义一个 void() 类型的函数
    void fun_2()
    {
    	printf("调用 fun_2 函数\n");
    }
    
    int main()
    {
    	/*
    		1. 将 fun_1 函数赋值给 FUN 类型指针
    		( 1 ) FUN 是函数类型, 其类型是 int(int)
    		( 2 ) fun_1 函数名是函数体的入口地址, 可以直接赋值给指针 ; 
    	*/
    	FUN* p = fun_1;
    	
    	//2. 通过指针调用函数, 指针变量名(参数) 可以调用指针指向的函数 ; 
    	printf("调用 p 指针结果 : %d\n", p(10));
    	
    	/*
    		3. 定义 void() 类型的函数指针 p1
    		( 1 ) 方法返回值(*函数指针变量名)(参数列表) 是定义一个函数指针
    		( 2 ) &函数名 也可以获取函数的地址, 与 函数名 是等价的;
    				注意 : 这里与数组不同, 
    					   数组名 和 &数组名 是两种不同的概念
    					   函数名 和 &函数名 是等价的
    	*/
    	void(*p1)() = &fun_2;
    	
    	/*
    		4. 通过函数指针变量调用函数
    		( 1 ) 通过 函数指针变量名(参数) 和 (*函数指针变量名)(参数) 两种方法都可以调用函数指针变量指向的函数
    		( 2 ) 函数名 和 &函数名 是等价的, 
    			  函数指针变量名(参数) 和 (*函数指针变量名)(参数) 也是等价的
    	*/
    	p1();
    	(*p1)();
    	
    	
    	
    	return 0;
    }
    
    • 2.编译运行结果 :
      这里写图片描述



    2. 回调函数


    ( 1 ) 回调函数相关概念


    回调函数简介 :

    • 1.回调函数实现 : 回调通过 函数指针 调用函数实现 ;
    • 2.回调函数特点 : 调用者 和 被调用的函数 互不知情, 互不依赖 ;
      • ( 1 ) 调用者 : 调用者不知道具体的函数内容, 只知道函数的类型 ;
      • ( 2 ) 被调函数 : 被调用的函数不知道 调用者 什么时候调用该函数, 只知道要执行哪些内容 ;
      • ( 3 ) 调用方式 : 调用者 通过 函数指针 调用具体的函数 ;


    ( 2 ) 代码示例 ( 回调函数示例 )


    代码示例 :

    • 1.代码 :
    #include <stdio.h>
    
    //1. 定义函数类型 FUN, 其类型为 int(int)
    typedef int(FUN)(int);
    
    //2. 定义一个 int(int) 类型的函数
    int fun_1(int i)
    {
    	return i * i * i;
    }
    
    //3. 定义一个 int(int) 类型的函数
    int fun_2(int i)
    {
    	return i + i + i;
    }
    
    //4. 定义一个 int(int) 类型的函数
    int fun_3(int i)
    {
    	return i + i * i;
    }
    
    /*
    	5. 此处参数 FUN function 是一个函数类型, 
    		将 FUN 类型函数注册给 execute 函数
    */
    int execute(int i, FUN function)
    {
    	return i + function(i);
    }
    
    int main()
    {
    	//1. 给 execute 注册 fun_1 函数, 具体事件发生时调用 fun_1 函数
    	printf("int i = 5; i * fun_1(i) = %d\n", execute(5, fun_1));
    	//2. 给 execute 注册 fun_2 函数, 具体事件发生时调用 fun_2 函数
    	printf("int i = 8; i * fun_2(i) = %d\n", execute(8, fun_2));
    	//3. 给 execute 注册 fun_3 函数, 具体事件发生时调用 fun_3 函数
    	printf("int i = 2; i * fun_3(i) = %d\n", execute(2, fun_3));
    	
    	
    	return 0;
    }
    
    • 2.编译运行结果 :
      这里写图片描述



    3. 解读 复杂的 指针声明 ( 难点 重点 | ①找出中心标识符 ②先右 后左 看 确定类型 提取 ③ 继续分析 左右看 … )


    指针 定义 复杂性来源 :

    • 1.数组指针 : 数组指针类型为 int (*) [5] , 即 一个指向 int[5] 的指针, 其指针变量名称写在中间的括号中
    • 2.函数指针 : 函数指针类型为 int(*)(int, int), 即 一个指向 int(int, int) 类型函数的指针, 其指针变量名称写在中间的括号中 ;
    • 3.数组指针混合函数指针 : 如果出现了 数组指针 指向一个函数, 这个指针可读性很差, 理解需要一定的功力 ;

    复杂指针阅读技巧 ( 主要是 区分 函数指针 和 数组指针 ) 右左法则 :

    • 1.最里层标示符 : 先找到最里层的圆括号中的标示符;

      数组指针和函数指针的标示符 ( 指针变量名 ) 都在中间的圆括号中, 因此该步骤先找到指针变量名

    • 2.右左看 : 先往右看, 再往左看 ;

    • 3.确定类型 : 遇到 圆括号 “()” 或者 方括号 “[]” 确定部分类型, 调转方向 ; 遇到 * 说明是指针 , 每次确定完一个类型 , 将该类型提取出来 , 分析剩下的 ;

      一种可能性 :
      int (*) [5] , 遇到中括号说明是数组指针类型,
      int(*)(int, int) , 遇到圆括号 说明是函数指针类型 ;

    • 4.重复 2 , 3 步骤 : 一直重复, 直到 指针 阅读结束 ;


    指针阅读案例 :

    • 1.解读案例 1 :
    	/*
    		解读步骤 : 
    		1. 研究第一个标示符 p  
    			( 1 ) 先找最里层的圆括号中的 标示符 p
    			( 2 ) p 往右看, 是圆括号, 然后往左看, 是 * , 可以确定 p 是一个指针
    			( 3 ) 将 (*p) 拿出来, 然后看剩下的部分, 右看是 圆括号 (, 明显是个函数类型, int (int*, int (*f)(int*)) 很明显是一个 函数类型
    		2. 解读函数类型 int (int*, int (*f)(int*))
    			( 1 ) 函数类型 int (int*, int (*f)(int*)) 的返回值类型是 int 类型
    			( 2 ) 函数类型的第一个参数类型是 int* , 即 int 类型指针类型
    			( 3 ) 函数类型的 第二个参数是 int (*f)(int*) 也是一个函数类型指针
    		3. 解读 int (*f)(int*) 参数
    			( 1 ) 标示符是 f, 由看 是 圆括号, 坐看是 * , 因此 f 是一个指针;
    			( 2 ) 将(*f) 提取出来, int(int*) 是一个函数类型, 其返回值是 int 类型, 参数是 int* 指针类型
    		
    		总结 : 
    		指针 p 是一个指向 int(int*, int (*f)(int*)) 类型函数的指针, 
    			函数返回值是 int 类型, 参数是 int* 指针类型 和 int (*)(int*) 函数指针 类型
    		指针 f 是一个指向 int(int*) 类型函数的指针, 其返回值是 int 类型, 参数是 int* 指针类型
    	*/
    	int (*p) (int*, int (*f)(int*));
    

    这里写图片描述

    • 2.解读案例 2 :
    	/*
    		解读步骤 : 
    		1. 确定 p1 的类型
    			( 1 ) 找出最中心圆括号中的标示符, p1;
    			( 2 ) 数组类型确定 : 右看发现中括号, 说明 p1 是一个数组, 数组中有 3 个元素, 数组的类型目前还不知道
    			( 3 ) 数组内容确定 : 左看发现 *, 说明数组中存储的是 指针类型, 这里就知道了 ;
    					目前知道了 数组 p1 的要素 : ① 数组中有 3 个元素, ② 数组元素类型是指针;
    		2. 确定数组指针类型 : 上面确定了 p1 的数组个数 和 元素是指针, 但是指针指向什么不确定
    			( 1 ) 将 (*p1[3]) 提取出来, int(int*) 明显是一个函数类型, 返回值是 int 类型, 参数是 int* 类型
    			
    		总结 : p1 是一个数组, 数组中含有 3 个元素, 数组元素类型为 int(*)(int*) 函数指针, 即 指向 int(int*) 类型函数的指针 
    	*/
    	int (*p1[3])(int*);
    

    这里写图片描述

    • 3.解读案例 3 :
    	/*
    		解读步骤 : 
    		1. 确定 p2 类型 : 
    			( 1 ) 找出最中心的圆括号中的标示符, p4, 右看是圆括号 ), 掉头左看是 * , 说明 p2 是一个指针;
    			( 2 ) 将 (*p2) 提取出来, 分析int (*[5])(int*), 
    			( 3 ) 右看是 [, 说明指针指向了一个数组, 该数组有 5 个元素
    			( 4 ) 左看是 * , 说明数组中的元素是指针, 下面分析指针指向什么
    		2. 确定指针数组中指针指向什么 : 
    			( 1 ) 将 (*(*p2)[5]) 提取出来, 可以看到 指针指向 int(int*) 类型的函数
    			
    		总结 : p2 是一个数组指针, 指向一个数组, 该数组有 5 个元素, 每个元素都是一个指针, 
    			数组中的指针元素指向 int(int*) 类型的函数
    	*/
    	int (*(*p2)[5])(int*);
    

    这里写图片描述

    • 4.解读案例 4 :
    	/*
    		解读步骤 : 
    		1. 确定 p3 基本类型 : 
    			( 1 ) p3 右看是圆括号 ), 左看是 * , 说明p3 是指针
    			( 2 ) 将 (*p3) 提取出来, int (*(int*))[5], 右看是 圆括号 (, 说明指针指向一个函数
    			( 3 ) 函数的参数是 int*, 返回值是一个指针, 指向一个类型
    		2. 确定返回值的类型
    			( 1 ) 将 (*(*p3)(int*)) 提取出来, 右看是 [, 说明是数组类型, 
    					剩下 int[5] 类型, 返回值指针指向一个 int[5] 类型的数组, 
    					那么返回值类型是 int(*)[5] 数组指针
    					
    		总结 : p3 指向一个 函数, 函数的参数是 int* 指针, 返回值是 指向 int[5] 数组 的 数组指针
    	*/
    	int (*(*p3)(int*))[5];
    

    这里写图片描述


    展开全文
  • 理解 指针数组 数组指针 函数指针 函数指针数组 指向函数指针数组的指针
  • 数组指针和指针数组

    万次阅读 多人点赞 2019-09-17 16:39:06
    首先,理解一下数组指针和指针数组这两个名词: “数组指针”和“指针数组”,只要在名词中间加上“的”字,就知道中心了—— 数组的指针:是一个指针,什么样的指针呢?指向数组的指针。 指针的数组:是一个数组...
  • 数组指针知道指针数组什么之后,那么数组指针是什么呢?数组指针实际上他是一个指针,我们常见的整型指针:int *p 这是一个能够指向整型数据的指针,浮点型指针:float *p 这是一个能够指向浮点型数据的指针,那么...
  • 数组:一组数据的集合...当然数组中除了存储一般常见的数据类型外,也可以用来存放指针,此时的数组就叫指针数组指针数组指针数组的元素全为指针。它的声明方法是:数据类型 * 数组名[数组长度];例如int arr[5];
  • 指针数组:正如可以创建整型数组,也可以创建指针数组指针数组是数组,是一个存放指针的数组。请看下列例子:int *arr1[10];char **arr2[10];两个都是指针数组。数组指针:数组也是一种类型,也可以创建数组指针。...
  • C语言中,我们知道,指针变量一种特殊的变量,专门用来...指针数组、数组指针、函数指针、函数指针数组、指向函数指针数组的指针 1.指针数组  指针数组强调的是数组,并且数组元素是指针类型 例:int *arr[5]//ar
  • 指针数组与数组指针详解

    万次阅读 多人点赞 2016-09-28 21:21:20
    什么指针数组和数组指针? 指针数组指针数组可以说成是”指针的数组”,首先这个变量是一个数组,其次,”指针”修饰这个数组,意思是说这个数组的所有元素都是指针类型,在32位系统中,指针占四个字节。 数组...
  • 野指针,数组指针,指针数组

    千次阅读 多人点赞 2020-12-17 16:31:40
    指针数组和数组指针(1)指针数组(2)数组指针(3)指针数组和数组指针+1的区别 1.野指针 (1)什么是野指针? 野指针就是指针指向的位置是不可知的(随机的,不正确的,没有明确限制的) (2)野指针的成因 a.指针初始化 ...
  • 数组和指针,数组指针,指针数组1.数组和指针关系及基本使用通过指针进行...如果当前声明的int类型数组,数组类型3,这样我可以用一个int类型的指针,指向一个首地址,这样就可以通过指针操作数组了 #include&...
  • 指针数组 int a=10,b=20; int *arr[2]={&a,&b}; //指针数组,数组  printf("%p %p\n\n",arr[0],&a); // 000000000062FE3C 000000000062FE3C 打印结果两个一样的地址 数组指针 int (*parr)[2]; parr=&arr; /...
  • 指针数组和数组指针是两个截然不同的概念,指针数组是一种数组,该数组存放的是一组变量的地址。数组指针是一个指针,表示该指针是指向数组的指针。 1.指向数组元素的指针 int a[5]={1,2,3,4,5}; int *p=a; //int...
  • 指针数组–数组元素指针的数组 #include<stdio.h> //本质是一个数组,数组的元素全是指针 int main() { int a[]={1,2,3}; int * p[3]; //取数组的长度 = 数组名/首元素 int n = sizeof(p)/sizeof(p[0]);...
  • 1.指针数组 指针数组是一个数组,里面每个元素是指针。 初始化如下 2.数组指针 指向数组的指针 形式如下int (*p)[5] 因为[ ]比*优先级高,因此表示一个指针必须给*p带上括号 赋初值如下 3....
  • 数组指针 指针数组

    2013-05-03 17:36:07
    而声明数组并使某个指针指向其值指向某个数组的地址(不一定是首地址),指针取值可以改变。  二:数组指针:是指向数组的一个指针,如int (*p)[10] 表示一个指向10个int元素的数组的一个针。  int (*p)[2]; ...
  • 开门见山,今天的主要内容是:指针数组,数组指针,函数指针,函数指针数组,函数指针数组指针。 看见题目是不是有点晕晕的感觉,没关系等看完这篇博客你会觉得他们其实也很好理解的! 接下来我一个一个介绍。 一....
  • 指针数组与数组指针 指针数组:储存指针的数组 首先它是一个数组,数组的元素都是指针,数组占多少字节由数组本身决定。 例如: int *arr1[5]; //”[ ]”的优先级高于”*”,首先它是一个数组,数组名arr,...
  • C++中的指针、数组指针与指针数组、函数指针与指针函数 本文从初学者的角度,深入浅出地详解什么是指针、如何使用指针、如何定义指针、如何定义数组指针和函数指针,并给出对应的实例演示;接着,区别了数组...
  • 数组指针与指针数组

    多人点赞 2017-07-04 19:10:35
    一、数组指针与指针数组 1、数组指针与指针数组的定义 2、数组指针与数组元素对应关系 3、使用数组指针遍历二维数组
  • 文章目录指针数组数组的指针指针数组的指针关于CLion编辑器本地变量定义小技巧代码 C++数组的指针、指针数组指针数组的指针的声明有点绕,防止后面绕晕记不住所以记录下。 指针数组 int *ap[3]; 啥[3]要放在...
  • 指针数组表达式:int *p[5] 理解:下标运算符[ ]的优先级是要高于指针运算符*,因此p先和下标运算符结合,决定了p首先是个数组,其类型int *,表明数组的元素都是都是指针。而数组占多少个字节由数组本身决定。...
  • 一、指针数组 二、数组指针 三、函数指针 四、函数指针数组 五、指向函数指针数组的指针 目录 1.指针数组 2.数组指针 3.函数指针 4.函数指针数组 5.函数指针数组的指针 一、指针数组 1.是一个...
  • 1、指针数组: 在C语言和C++...指针数组中的元素亦可以表示“*(*(ptr_array+i))”。又因为“()”的优先级较“*”高,且“*”是右结合的,因此可以写作**(ptr_array+i)。由于数组元素均指针,因此ptr_array...
  • 指针数组是数组元素指针的数组(例如 int *p[3],定义了p[0],p[1],p[2]三个指针),其本质为数组。 2、代码:#include int main() { int *p1[3];//指针数组,本质:数组 int (*p2)[3];//数组指针,本质:
  • 第一眼看到结尾“数组”两个字,那么就第一反应就得数组来进行对待。 现在,来讲一讲数组指针,指针数组,函数指针,函数指针数组的概念。 数组指针:指向数组的指针。 指针数组:里面存指针的数...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 839,816
精华内容 335,926
关键字:

为什么可以把指针当数组用