C语言基础10——指针进阶。字符指针、指针数组、数组指针、函数指针、函数指针数组、回调函数、数组名详解、杨氏矩阵、字符串旋转

目录

字符指针

指针数组

数组指针

数组传参、指针参数

函数指针

函数指针数组

指向函数指针数组的指针

回调函数

练习

数组名的意义

指针笔试题


字符指针

  • 字符指针的另一种使用方式

    #include 
    
    int main()
    {
        //字符指针的使用
        char ch = 'q';
        char * pc = &ch;
    
        //本质上是把这个字符串的首字符地址存储在了指针变量ps中
        const char * ps = "hello world";
        //数组存储字符串时,是把整个字符串都存储在其中。
        char arr[] = "hello world";
    
        printf("%c\n",*ps);//h
        printf("%s\n",ps);//hello world   %s打印,遇到字符串结束符\0停止
        printf("%s\n",arr);//hello world
    
        return 0;
    }
    
  • 因为常量字符串不可以被修改,所以相同的常量字符串,在内存中只会存储一份。

    #include 
    
    int main()
    {
        //每个数组初始化都会开辟空间,即使两个数组中保存的是相同的数据,也会另外开辟空间,其内存地址不同。
        char str1[] = "hello world.";
        char str2[] = "hello world.";
    
    //    char * str3 = "hello world.";
    //    char * str4 = "hello world.";
        //这里的"hello world."是一个常量字符串。不能使用*解引用操作修改。
        //因为不能使用*修改,所以一般要使用const修饰,是用于修饰*str3或*str4,所以const要写在*的左边
        const char * str3 = "hello world.";
        const char * str4 = "hello world.";
        //*str3 = "hehe";  程序会挂掉,不能正常执行。
        //因为常量字符串是不可以被修改的,而这两个字符串都一样,所以内存中只存储一个"hello world."字符串。
        //str3和str4中保存的都是这个字符串首字符的地址。他们指向的是同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针,指向同一个字符串的时候,他们实际会指向同一块内存。所以str3==str4
    
        if(str1 == str2)
        {
            printf("str1 与 str2 相同\n");
        } else
        {
            printf("str1 与 str2 不相同\n");
        }
        if(str3 == str4)
        {
            printf("str3 与 str4 相同\n");
        } else
        {
            printf("str3 与 str4 不相同\n");
        }
       
        //str1 与 str2 不相同
    	//str3 与 str4 相同
        return 0;
    }
    

指针数组

int main()
{
    int arr[10];  //整型数组:存放整型数据的数组
    char ch[5];   //字符数组:存放字符的数组

    //指针数组:存放指针的数组
    int* parr[5]; //存放整型指针的数组
    char* pch[5]; //存放字符型指针的数组
    
    char** arrr[3]; //存放二级字符指针的数组
}

指针数组的使用

#include 

int main()
{

    //这种使用方式很少见,没什么太大意义。
//    int a = 10;
//    int b = 20;
//    int c = 30;
//    int * arr[3] = {&a,&b,&c};
//    int i;
//    for(i=0 ; i<3 ; i++)
//    {
//        printf("%d ",*(arr[i]));//10 20 30
//    }

    int a[] = {1,2,3,4,5};
    int b[] = {1,3,5,7,9};
    int c[] = {2,4,6,8,10};

    //数组名存储的是数组首元素的地址。
    int * arr[3] = {a,b,c};
    int i,j;
    for(i=0 ; i<3 ; i++)
    {
        for(j=0 ; j<5 ; j++)
        {
            //解引用的方式
            printf("%d ",*(arr[i]+j));
            //arr[i]取出数组,然后[j]是数组中元素的下标。
            //printf("%d ",arr[i][j]);
        }
        printf("\n");
    }

    return 0;
}

数组指针

  • 数组指针如何定义

    /*
     * 数组指针,是一种指向数组的指针。
     * 数组指针的定义:指向的数组类型 (*数组指针变量名)[指向的数组中元素个数] = &数组名;
     */
    
    int main()
    {
        //整型指针,指向整型的指针;
        int i = 10;
        int *pi = &i;
        // 字符指针,是指向字符的指针。
        char c = 'c';
        char * pc = &c;
    
        //数组指针
        int arr[10] = {0};
    
        //数组名是数组首元素的地址,arr存储数组首元素地址;&arr,取出的是整个数组的地址。
        //因为[]下标引用符的优先级比*解引用操作符的优先级高,所以这样写就成了指针数组:
        //int * parr[10] = &arr;
    
        //所以要先让*与parr结合,这样就是指针了,结合之后再与[10]结合,就叫做数组指针
        //parr就是一个数组指针,其中存放的是一个数组的地址。int表示指向的数组是int类型的,[10]表示指向的数组中存储了10个元素
        int (*parr)[10] = &arr;
    
        //d是一个指针数组,其中存储的是一级浮点型指针
        double * d[5];
        //取d的地址,是取出d这个数组。d这个数组是double*类型的 ,有5个元素。
        //又因为是存储数组的地址,所以pd是一个数组指针。加()先与*结合,再与[]结合
        double* (*pd)[5] = &d;
    
        return 0;
    }
    
  • 数组名 与 &数组名 的区别

    /*
     * 数组名与&数组名的区别:
     * 数组名是数组首元素的地址
     * 但是有两个例外:
     * - sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组大小,单位是字节。
     * - &数组名,数组名表示整个数组,取出的是整个数组的地址。
     */
    
    #include 
    
    int main()
    {
    
        int arr[9] = {0};
        //arr和&arr分别是什么?
        int * p1 = arr;
        int (*p2)[9] = &arr;
    
        //数组名arr表示的是数组首元素的地址,arr+1,加了4个字节,也就是加一个int类型大小。
        printf("%p\n",p1);//0000009cbf1ff780
        printf("%p\n",p1+1);//0000009cbf1ff784
    
        //&arr,取出的是整个数组,&arr+1,加的是0x24,十进制就是36个字节,加的是整个数组大小。
        printf("%p\n",p2);//0000009cbf1ff780
        printf("%p\n",p2+1);//0000009cbf1ff7a4
        
        return 0;
    }
    
  • 数组指针使用示例,并不建议这样使用。

    #include 
    
    int main()
    {
        int arr[10] = {0,1,2,3,4,5,6,7,8,9};
    
        int (*pa)[10] = &arr;
        int i;
        for(i=0 ; i<10 ; i++)
        {
            //*(*pa)解引用,找到pa指向的数组arr,相当于直接使用数组名,+i再解引用,找到对应元素
            //但是一般不这么使用,不如直接使用数组名进行操作。
            printf("%d ",(*(*pa) + i));
            //printf("%d ",*(arr + i));
        }
        return 0;
    }
    
  • 数组指针的使用

    #include 
    
    print1(int arr[3][5])
    {
        int i,j;
        for(i=0 ; i<3 ; i++)
        {
            for(j=0 ; j<5 ; j++)
            {
                printf("%d ",arr[i][j]);
            }
            printf("\n");
        }
    }
    
    
    //传过来的是二维数组的首元素地址,这个地址可以说是指向一个一维数组,所以用数组指针来接收然后进行操作。
    //p是一个数组指针,指向的数组是int类型的,并且有5个元素。
    print2(int (*p)[5],int m)
    {
        int i,j;
        for(i=0 ; i
  • 存储数组指针的数组

    #include 
    
    int main()
    {
        //整型数组
        //int arr[5];
    
        //因为[]的优先级比*高,所以parr1先与[]结合,再与*结合。这是一个指针数组,其中存储int *类型的指针。整型指针数组。
        int *parr1[10];
    
        //(*parr2)中parr2先与*结合,也就是说这是一个指针,再与[10]结合。
        //也就是说,parr2是一个数组指针,该指针指向一个int类型数组,这个数组中存储10个元素。
        int (*parr2)[10];
    
        //(*parr3[10])中parr3先与[10]集合,是一个数组。
        //去掉数组名[],就是数组中元素的类型。剩下的是int(*)[5],所以说其中的每个元素是int * [5]类型的。
        // 也就是说parr3是一个存储数组指针的数组,这个数组可以存储10个数组指针。每个指针指向一个5元素的int类型数组。
        int (*parr3[10])[5];
    
        //parr4先与*结合,说明是一个指针。再与[10]结合。
        //parr4是一个数组指针,该指针指向一个int类型数组,这个数组中存储了5个元素。
        int (*parr4)[5];
    
        int arr[2] = {1,2};
        //p是一个数组指针,指向arr数组
        int (*p)[2] = &arr;
        //pfun是一个存放数组指针的数组。
        int (*pfun[2])[2] = {p};
        //*pf是一个指向数组指针数组的指针,指向存放数组指针的数组pfun
        int (*(*pf)[2])[2] = &pfun;
        //因为[]的优先级比*高,pf[0]可以找到pfun数组。pfun[0]存储的是p,p是一个数组指针,指向arr的地址。p[0]就是arr。
        // 对arr进行解引用,找到的是数组首元素。
        printf("%d\n",*pf[0][0][0]);
        //数组首元素地址+1就是数组arr下标为1的地址,我们就可以通过*(arr+i)来访问数组。
        printf("%d\n",*(pf[0][0][0]+1));
    
        return 0;
    }
    

数组传参、指针参数

  • 一维数组传参

    //元素个数可以省略,写了也没什么意义。写不写都可以
    //void test(int arr[]){}
    //void test(int arr[10]){}
    //可以写成指针。数组名传参,传来的是首元素的地址,而这里是int数组,所以用int类型指针接收即可。
    void test(int * arr){}
    
    //arr2是整型指针数组,所以用整型指针数组类型来接收。20可以省略
    //void test2(int *arr[20]){}
    //用指针接收。因为是指针数组,所以首元素是一个指针,也就是说我们可以定义一个二级指针来保存这个指针的地址。
    void test2(int * *arr){}
    int main()
    {
        int arr[10]= {0};
        int * arr2[20] = {0};
        test(arr);
        test2(arr2);
        return 0;
    }
    
  • 二维数组传参

    //二维数组传参,只能省略第一个[]中的数组,不能省略第二个[]里面的数字。
    // 对于一个二维数组,可以不知道有多少行,但是必须知道一行有多少元素。然后通过元素的个数,再通过一行中的元素个数,就可以计算出有几行。
    //void test(int arr[3][5]){}
    //void test(int arr[][]){} //这个写法错误
    //void test(int arr[][5]){}
    
    //二维数组的首元素地址,是第一行(一维数组)的地址。也就是说这里应该是一个数组指针。因为一行有五个,所以应该是一个指向5元素数组的指针。
    void test(int (*arr)[5]){}
    
    int main()
    {
        int arr[3][5] = {0};
        test(arr);
    }
    
  • 一级指针传参

    #include 
    
    void print(int * ptr, int sz)
    {
        int i ;
        for(i=0 ; i
  • 二级指针传参

    #include 
    
    void test(int** p)
    {
        //*p解引用找到其存储的一级指针,再*解引用就可以找到一级指针指向的变量。
        **p = 20;
    
    }
    int main()
    {
        int a = 10;
    
        int* pa = &a; // 一级指针
        int** ppa = &pa; //二级指针
    
        //把二级指针进行传参
        test(ppa);
        printf("%d\n",a);
        //二级指针中保存的是一级指针的地址,所以可以取一级指针的地址传递进去。
        test(&pa);
        printf("%d\n",a);
    
        //arr是一个指针数组。其中存储一级指针,是int*类型的。
        int* arr[10] = {0};
        //arr是数组名,存储的是数组首元素的地址,而这是一个指针数组。也就是说arr中存储的是一级指针的地址。
        //所以如果函数的参数是一个二级指针,我们也可以传入指针数组的数组名。
        //但这里,因为方法内执行逻辑不是为数组准备的,所以运行有错。
        //test(arr);
    
        return 0;
    }
    
  • 学过的指针与数组

    /*
     * 一级指针
     * - int * p;  整型指针,指向整形的指针
     * - char * pc; 字符指针,指向字符的指针
     * - void * pv; 五类型的指针
     *
     * 二级指针
     * - char** p;
     * - int** p;
     *
     * 数组指针:指向数组的指针
     * - int (*p)[4]  p指向一个4元素的int数组。
     *
     * 数组
     * - 一维数组
     * - 二维数组
     * - 指针数组:存放指针的数组
     */
    

函数指针

  • 函数指针语法

    //函数返回类型 (*函数指针变量名)(函数参数[,函数参数......]) = 函数名/&函数名
    #include 
    
    int Add(int x,int y)
    {
        return x+y;
    }
    
    void test(char* str){}
    
    int main()
    {
        //函数指针 —— 存放函数地址的指针。
        //&函数名:取出函数的地址。
        printf("%p\n",&Add);//00007ff6562617a1
        //函数名,表示的也是函数的地址。 函数名==&函数名
        printf("%p\n",Add);//00007ff6562617a1
    
        //pf就是一个函数指针变量。
        //因为()优先级要比*高,所以要写成(*pf),意思是pf是一个指针,指向参数为(int,int),返回类型为int的函数。
        //int (*pf)(int,int) = &Add;
        //printf("%p\n",*pf);//00007ff6562617a1
    
        //test函数指针
        void (*pt)(char*) = &test;
    
        //通过指针调用函数:
        //先解引用(*pf)找到这个函数然后传递参数(3,5)。然后使用变量接收返回值。
        //int ret = (*pf)(3,5);
        //printf("%d",ret);//8
    
        //因为&函数名与函数名等效。也就是说Add的地址直接放到了pf中
        //函数Add保存的是函数本身的地址,pf保存的是Add函数的地址。所以Add === pf
        int (*pf)(int,int) = Add;
    
        //平时我们调用函数:
        int ret = Add(3,9);
        printf("%d\n",ret);//12
    
        //使用指针解引用调用
        ret = (*pf)(10,33);
        printf("%d\n",ret);//43
    
        //因为Add===pf,所以我们也可以这样调用:
        ret = pf(22,44);
        printf("%d\n",ret);//66
    
        //所以调用时,(*pf)===pf===Add
    
        return 0;
    }
    
  • 《C陷阱和缺陷》中的两段代码解读

    /*
     * 调用0地址处的函数:(*(void (*)())0)();  改函数没有参数,返回类型是void
     * - void (*)()是函数指针类型。
     *   void (*p)()是函数指针变量,p是变量名,指向的函数返回类型是void类型,函数没有参数。去掉变量名p就是函数指针类型。
     * - (void (*)())0 ,对0进行强制类型转换,0就成为函数指针类型
     *   想要调用地址为0位置的函数,就需要把0这个数字转换为指针类型。但事实上地址都是由编译器分配的,地址的分配不是固定的。
     *   编写这个语句的程序员是为了模拟开机启动的情形,所以将这里的地址耦合为0,但如果要使用这种形式,那么这里的地址最好是动态的,不能是一个常量,因为可能这个地址没有被开辟出来。
     * - (*(void (*)())0) ,0被转换为指针类型之后,就可以解引用,找到0所在位置的函数。
     * - (*(void (*)())0)(),找到0位置的函数之后,()进行函数调用。
     *
     *
     */
    
    /*
     * void (*signal(int,void(*)(int)))(int);
     * - void(*)(int)是一个函数指针类型,指向参数为int,返回类型为void的函数。作为signal函数的参数。
     * - signal(int,void(*)(int),因为()优先级高,所以signal先与()结合,就是一个函数。剩下void(*)(int)是一个函数指针类型
     * - 所以signal函数的返回类型也是一个函数指针。该指针指向参数为int,返回类型为void的函数
     * - signal是一个函数声明,既不是定义也不是调用。这里有函数的名字、函数的参数、还有函数的返回类型:void(*)(int)。
     *
     * 函数声明语法:返回返回类型 函数名(函数参数[,函数参数...]);
     * - 该函数声明本来应该是:void(*)(int) signal(int,void(*)(int));  但是语法规定不可以这样写。
     *   语法规定:函数的返回类型如果是一个指针,那么*必须与函数名连接在一起。所以就成为了:void (*signal(int,void(*)(int)))(int);
     *
     * - 如何简化呢?
     */
    int main()
    {
        //使用typedef对类型进行重定义
        //typedef void(*)(int) pfun_t;  指针类型的*要与名字写在一起,所以就成为:
        typedef void(*pfun_t)(int);  //将void(*)(int)函数指针类型,重命名为pfun_t
        pfun_t signal(int,pfun_t);
        return 0;
    }
    

函数指针数组

  • 函数指针数组的定义

    //函数指针数组:存放函数指针的数组
    //函数指针数组定义语法:指向的函数的返回类型 (*函数指针数组变量名[函数指针数组元素个数])(函数参数);
    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;
    
        //函数指针数组,也是一个数组,所以直接在变量名后面加[2]。pfArr[2]就表示是一个数组
        //剩下的int (*)(int,int)是数组元素类型。也就是说每个元素都指向一个参数为(int,int),返回类型为int的函数。
        int(*pfArr[2])(int,int) = {Add, Sub};
    
        return 0;
    }
    
  • 函数指针数组的应用:计算器

    //计算器,计算整型变量的加减乘除。
    #include 
    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("------输入菜单序号执行对应功能---------\n");
        printf("------------- 0.退出 ----------------\n");
        printf("------------- 1.加法 ----------------\n");
        printf("------------- 2.减法 ----------------\n");
        printf("------------- 3.乘法 ----------------\n");
        printf("------------- 4.除法 ----------------\n");
        printf("-------------------------------------\n");
    }
    
    int main()
    {
    
        //条件
        int input = 1;
        //函数指针数组
        int(*pfArr[5])(int,int) = {0,Add,Sub,Mul,Div};
        //循环
        while(input)
        {
            menu();
            printf("请选择:");
            scanf("%d",&input);
    
            if(input >0 && input<5)
            {
                int x,y,ret;
                printf("输入要进行操作的两个数:");
                scanf("%d %d",&x,&y);
                //ret = (*pfArr[input])(x,y);  //*可以省略。
                ret = pfArr[input](x,y);
                printf("ret=%d\n",ret);
            }
            else if (input == 0)
            {
                printf("退出程序");
            }
            else
            {
                printf("输入有误请重新输入\n");
            }
        }
    
        return 0;
    }
    

指向函数指针数组的指针

/*
 * - 整型数组: int arr[5]
 *   将这个整型数组存储到指针中,就叫做整型数组指针:int (*p)[5] = &arr;
 *
 * - 整型指针数组:int* arr[5]
 *   存储整型指针数组的指针,就叫做:指向整型指针数组的指针。int *(*p)[5] = &arr,p2是一个数组指针,指向的数组类型是int * [5]。
 *
 * - 函数指针: int (*p)(int,int);
 *   函数指针数组:int (*p2[5])(int,int);  p2数组中存储了5个指针,指针类型是int (*)(int,int),每个指针都指向参数为(int,int),返回类型为int的函数。
 * - &p2,取出的是函数指针数组的地址。 int (*(*p3)[5])(int,int) = &p2;
 *   p3先与*结合,表示p3是一个指针,再与[5]结合,表示指向的是一个数组。
 *   剩下的int(*)(int,int)是函数指针类型,表示该数组中存储的是函数指针。也就是说p3是指向函数指针数组的指针。
 */
//一个数组,如 int arr[10]
//去掉数组名,是数组类型:int [10]  ; 去掉数组名[],是数组中的元素类型:int

#include 

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

回调函数

  • 回调函数是什么?

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

    #include 
    int Add(int a, int b)
    {
        return a + b;
    }
    int Sub(int a, int b)
    {
        return a - b;
    }
    int Mul(int a, int b)
    {
        return a*b;
    }
    int Div(int a, int b)
    {
        return a / b;
    }
    void menu()
    {
        printf("------------------------------------\n");
        printf("------输入菜单序号执行对应功能---------\n");
        printf("------------- 0.退出 ----------------\n");
        printf("------------- 1.加法 ----------------\n");
        printf("------------- 2.减法 ----------------\n");
        printf("------------- 3.乘法 ----------------\n");
        printf("------------- 4.除法 ----------------\n");
        printf("-------------------------------------\n");
    }
    int main()
    {
        int x, y;
        int input = 1;
        int ret = 0;
        do
        {
            menu();
            printf( "请选择:" );
            scanf( "%d", &input);
            switch (input)
            {
                case 1:
                    printf( "输入操作数:" );
                    scanf( "%d %d", &x, &y);
                    ret = Add(x, y);
                    printf( "ret = %d\n", ret);
                    break;
                case 2:
                    printf( "输入操作数:" );
                    scanf( "%d %d", &x, &y);
                    ret = Sub(x, y);
                    printf( "ret = %d\n", ret);
                    break;
                case 3:
                    printf( "输入操作数:" );
                    scanf( "%d %d", &x, &y);
                    ret = Mul(x, y);
                    printf( "ret = %d\n", ret);
                    break;
                case 4:
                    printf( "输入操作数:" );
                    scanf( "%d %d", &x, &y);
                    ret = Div(x, y);
                    printf( "ret = %d\n", ret);
                    break;
                case 0:
                    printf("退出程序\n");
                    break;
                default:
                    printf( "选择错误\n" );
                    break;
            }
        } while (input);
    
        return 0;
    }
    

    改进

    #include 
    int Add(int a, int b)
    {
        return a + b;
    }
    int Sub(int a, int b)
    {
        return a - b;
    }
    int Mul(int a, int b)
    {
        return a*b;
    }
    int Div(int a, int b)
    {
        return a / b;
    }
    void menu()
    {
        printf("------------------------------------\n");
        printf("------输入菜单序号执行对应功能---------\n");
        printf("------------- 0.退出 ----------------\n");
        printf("------------- 1.加法 ----------------\n");
        printf("------------- 2.减法 ----------------\n");
        printf("------------- 3.乘法 ----------------\n");
        printf("------------- 4.除法 ----------------\n");
        printf("-------------------------------------\n");
    }
    
    void calc(int (*pf)(int,int ))
    {
        int x, y,ret;
        printf( "输入操作数:" );
        scanf( "%d %d", &x, &y);
        ret = pf(x, y);
        printf( "ret = %d\n", ret);
    }
    
    int main()
    {
        int input = 1;
        do
        {
            menu();
            printf( "请选择:" );
            scanf( "%d", &input);
            switch (input)
            {
                case 1:
                    calc(Add);
                    break;
                case 2:
                    calc(Sub);
                    break;
                case 3:
                    calc(Mul);
                    break;
                case 4:
                    calc(Div);
                    break;
                case 0:
                    printf("退出程序\n");
                    break;
                default:
                    printf( "选择错误\n" );
                    break;
            }
        } while (input);
    
        return 0;
    }
    
  • 复习:冒泡排序的实现

    #include 
    
    void bubble_sort(int arr[], int sz)
    {
        int i,j;
        //冒泡排序的趟数:有10个元素,就要排序9趟,剩下最后一个元素不用再与别的元素比较。趟数=元素个数-1
        for(i=0 ; i arr[j+1])
                {
                    int tmp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = tmp;
                }
            }
        }
    }
    
    int main()
    {
        //升序
        int arr[10] = {9,8,7,6,5,4,3,2,1,0};
        int sz = sizeof(arr)/sizeof(arr[0]);
        bubble_sort(arr,sz);
        int i;
        for(i=0 ; i
  • qsort排序函数的使用

    /*
     * qsort函数()
     * - 函数原型:void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*));
     * - 作用:对数组元素进行排序。
     *   对base指向的数组的num个元素进行排序,每个元素大小为字节。使用比较函数确定顺序。
     *   此函数使用的排序算法通过调用指定的比较函数并将指向他们的指针作为参数来比较元素。
     *   该函数不返回任何值,但会修改由base指向的数组的内容,按照compar定义对其元素重新排序。
     *
     * 参数:
     * - base:指向要排序的数组的第一个对象的指针。并将其转换为void *类型。
     *   void *,表示无具体类型,什么类型都可以传入。
     * - num:base指向的数组中的元素个数。size_t是无符号整数类型
     * - size:数组中每个元素的大小(以字节为单位)。size_t是无符号整数类型
     * - compar:指向比较两个元素的函数的指针。qsort会反复调用这个函数比较两个元素。
     *   该函数原型:int compar (const void* p1, const void* p2);
     *   将两个指针作为参数(都转换为const void *类型),该函数通过返回值定义元素顺序:
     *   - 返回值<0 , p1指向的元素在p2指向的元素之前 ,p10 , p1指向的元素在p2指向的元素之后 , p1>p2
     *
     */
    #include 
    #include 
    #include 
    
    void print(int arr[],int sz)
    {
        int i;
        for(i=0 ; ip2;如果小于,返回的是一个小于0的数,就表示p1name,(s+i)->age);
        }
    }
    
    int cmp_age(const void* p1,const void* p2)
    {
        return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
    }
    
    int cmp_name(const void* p1,const void* p2)
    {
        /*
         * strcmp函数返回值:
         * - str1 < str2,返回值小于0
         * - str1 = str2,返回值等于0 
         * - str1 > str2,返回值大于0
         *   比较这两个字符串相同位置的字符的ASCII码,一旦出现不匹配的,就比较这两个字符。ASCII码低的就小,高的就大。
         *   如abcq与adc进行比较,两个字符串第一个字符相同,比较第二个字符,d比b大,所以adc>abcq。
         *   比较的是对应位置的字符的大小,而不是比较字符串长度。
         * - 正好与qsort()函数的比较函数规定发返回值判断相符。
         */
        return strcmp(((struct Stu*)p1)->name,((struct Stu*)p2)->name);
    }
    
    void qsort_stu()
    {
        //使用qsort函数为结构体数据排序
        struct Stu s[] = {{"zhangsan",30},{"lisi",34},{"wangwu",20}};
        int sz = sizeof(s)/sizeof(s[0]);
    
        printf("排序前:\n");
        print_stu(s,sz);
        //按照年龄排序
        //qsort(s,sz, sizeof(s[0]),cmp_age);
        //按照名字排序
        qsort(s,sz,sizeof(s[0]),cmp_name);
    
        printf("排序后:\n");
        print_stu(s,sz);
    }
    
    int main()
    {
        //整型数据的排序
        //qsort_int();
    
        //结构体数据的排序
        qsort_stu();
    }
    
  • 模拟qsort实现一个冒泡排序算法

    #include 
    #include 
    
    Swap(char* buf1,char* buf2,int size)
    {
        int i;
        for(i=0 ; i0)
                {
                    //交换
                    //交换的是两个指针指向的内容。但是我们只是知道地址,而不知道其是什么类型的数据。
                    //解决:不管其是什么类型的数据,我们把他们的每个字节的内容都进行交换,这样也相当于交换了内容。
                    Swap((char*)base+j*size,(char*)base+(j+1)*size,size);
                }
            }
        }
    }
    
    int cmp_int(const void* p1,const void* p2)
    {
        //因为p1与p2都是void* 无类型指针。没有办法比较。
        //而我们知道传入的数据是int类型的,所以我们可以将其先强制类型转换为int*类型的指针(int*)p1、(int*)p2
        //然后再解引用,就可以进行比较了。*(int*)p1 - *(int*)p2
        //如果p1大于p2,就会将这个值返回,返回的是一个大于0的数,就表示p1>p2;如果小于,返回的是一个小于0的数,就表示p1name,(s+i)->age);
        }
    }
    
    int cmp_age(const void* p1,const void* p2)
    {
        return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
    }
    
    int cmp_name(const void* p1,const void* p2)
    {
        /*
         * strcmp函数返回值:
         * - str1 < str2,返回值小于0 ;相等,则返回值等于0 ;str1 > str2,返回值大于0。
         *   比较这两个字符串相同位置的字符的ASCII码,一旦出现不匹配的,就比较这两个字符。ASCII码低的就小,高的就大。
         *   如abcq与adc进行比较,两个字符串第一个字符相同,比较第二个字符,d比b大,所以adc>abcq。
         *   比较的是对应位置的字符的大小,而不是比较字符串长度。
         * - 正好与qsort()函数的比较函数规定发返回值判断相符。
         */
        return strcmp(((struct Stu*)p1)->name,((struct Stu*)p2)->name);
    }
    
    void qsort_stu()
    {
        //使用qsort函数为结构体数据排序
        struct Stu s[] = {{"zhangsan",30},{"lisi",34},{"wangwu",20}};
        int sz = sizeof(s)/sizeof(s[0]);
    
        printf("排序前:\n");
        print_stu(s,sz);
        //按照年龄排序
        //bubble_sort(s,sz, sizeof(s[0]),cmp_age);
        //按照名字排序
        bubble_sort(s,sz,sizeof(s[0]),cmp_name);
    
        printf("排序后:\n");
        print_stu(s,sz);
    }
    
    int main()
    {
        //test();
        qsort_stu();
        return 0;
    }
    
  • void *类型

    #include 
    
    int main()
    {
        int a = 10;
        char ch = 'w';
        //void为无具体类型,void*就是无具体类型指针。其指针变量中,什么类型的变量地址都可以存储。
        void* p = &a;
        p = &ch;
        //但是在引用的时候不能直接引用,因为void*是无具体类型的,所以解引用或进行指针运算,编译器不知道访问几个字节。
        //*p解引用或p++,这样的用法都是没有意义的。必须是我们将其强制转换为具体的类型才可以使用。
        printf("%c",*(char*)p); //w
        return 0;
    }
    

练习

数组名的意义

- sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
- &数组名,这里的数组名表示整个数组,取出的是整个数组的大小
- 除此之外的所有数组名都表示首元素的地址
  • int数组中关于sizeof()求大小

    /*
     * 除了这两种情况,剩下的数组名都表示数组首元素地址
     * - sizeof(数组名),这里的数组名,代表整个数组。计算的是整个数组的大小
     * - &数组名,数组名表示整个数组,取出的是整个数组的地址。
     */
    #include 
    
    int main()
    {
        int a[] = {1,2,3,4};
    
        //sizeof(数组名),计算整个数组的大小。int类型数组中有4个元素。4*4=16
        printf("%d\n",sizeof(a));//16
    
        //a+0,a中存储数组首元素地址,首元素地址+0:代表的还是首元素地址。sizeof(a+0)计算地址的大小
        //在32位平台下,地址大小是4个字节;在64位平台下,地址是8个字节。
        printf("%d\n",sizeof(a+0));//4/8
    
        //数组名a中存储的是数组首元素的地址,解引用找到的是数组首元素:1。1是int类型数据,所以应该是4
        printf("%d\n",sizeof(*a));//4
    
        //a+1,a中存储数组首元素地址,首元素地址+1:代表的数组中第二个元素的地址。sizeof(a+1)计算地址的大小
        printf("%d\n",sizeof(a+1));//4/8
    
        //计算数组中第二个元素的大小。
        printf("%d\n",sizeof(a[1]));//4
    
    
        //&a,取出整个数组地址。这是一个指向数组的指针。sizeof(地址) 4/8
        printf("%d\n",sizeof(&a));//8
    
        //&a,取出整个数组地址。解引用找到数组。也就是计算整个数组大小
        printf("%d\n",sizeof(*&a));//16
    
        //&a,取出整个数组地址。数组地址+1,跳过整个数组之后的一块地址。还是一个地址,所以这里是计算地址的大小。4/8
        printf("%d\n",sizeof(&a+1));//8
    
        //a[0]是数组首元素,&a[0]取出数组首元素地址。是一个地址,4/8
        printf("%d\n",sizeof(&a[0]));//8
    
        //a[0]是数组首元素,&a[0]取出数组首元素地址,&a[0]+1,数组首元素地址+1,就是数组第二个元素地址。是一个地址,4/8
        printf("%d\n",sizeof(&a[0]+1));//8
    
        return 0;
    }
    
  • 字符数组(不存放字符串结束符),sizeof以及strlen的计算

    /*
     * 除了这两种情况,剩下的数组名都表示数组首元素地址
     * - sizeof(数组名),这里的数组名,代表整个数组。计算的是整个数组的大小
     * - &数组名,数组名表示整个数组,取出的是整个数组的地址。
     */
    #include 
    #include 
    
    int main()
    {
        char arr[] = {'a','b','c','d','e','f'};
    
        /*
        //计算数组大小。char类型占一个字节,arr中有6个元素。
        printf("%d\n",sizeof(arr));//6
        //arr是数组首元素地址;arr+0,还是首元素地址。地址的大小:4/8
        printf("%d\n",sizeof(arr+0));//8
        //arr是数组首元素地址,*arr找到数组首元素。也就是计算char类型大小
        printf("%d\n",sizeof(*arr));//1
        //arr[1]是数组首元素,计算数组首元素大小,也就是计算char类型大小
        printf("%d\n",sizeof(arr[1]));//1
        //&arr,取出整个数组地址。整个数组地址,也是一个地址,地址大小:4/8
        printf("%d\n",sizeof(&arr));//8
        //&arr,取出整个数组地址。&arr+1,跳过整个数组之后的一块地址。还是地址,计算地址的大小。4/8
        printf("%d\n",sizeof(&arr+1));//8
        //arr[0]是数组首元素,&arr[0]取出数组首元素地址,&arr[0]+1,数组首元素地址+1,就是数组第二个元素地址。是一个地址,4/8
        printf("%d\n",sizeof(&arr[0]+1));//8
        */
    
        //计算字符串长度,strlen计算字符串长度时,直到遇见字符串结束符'\0'才结束
        //arr是首元素地址,会一直往后取。直到碰到\0才结束。所以这里是个随机值
        printf("\n%d\n",strlen(arr));//随机值
        //首元素地址+0,还是首元素地址。然后往后取,直到碰到\0才结束。与以上相同,是个随机值。
        printf("%d\n",strlen(arr+0));//随机值
    
        //strlen函数的原型:size_t __cdecl strlen(const char *_Str);
        //其参数是一个指针,也就是说参数应该是一个指针,或者直接传地址过去。
    
        //&arr,取出的是整个数组地址,该地址是char (*)[6]类型。然后放入strlen函数,被强制类型转换为char*类型。
        //也会从数组首元素开始取,直到碰到字符串结束符结束
        printf("%d\n",strlen(&arr));//随机值
        //&arr+1,紧接着数组地址后的一块地址。该地址也是char (*)[6]类型。然后放入strlen函数,被强制类型转换为char*类型。
        //会从数组后第一个元素开始取,直到碰到字符串结束符结束。也就是说要比以上随机值少6个字符。
        printf("%d\n",strlen(&arr+1));//随机值-6
        //arr[0]是数组首元素,&arr[0]取出数组首元素地址,&arr[0]+1,数组首元素地址+1,就是数组第二个元素地址。
        //从数组第二个元素取直到取到字符串结束符\0,少了数组首元素。所以应该是随机值-1
        printf("%d\n",strlen(&arr[0]+1));//随机值-1
    
        //*arr解引用找到数组首元素'a',a的ASCII码是97。97传入strlen函数,该函数需要一个地址。
        //可能被解析为:是要找97所在位置的数据,但是我们并不知道97位置有没有数据,所以这里是报错的。
        printf("%d\n",strlen(*arr));// 报错
        //arr[1]是数组首元素'b',b的ASCII码是98。找98位置数据,同样报错。
        printf("%d\n",strlen(arr[1]));//报错
    
        return 0;
    }
    

    字符数组(存放字符串结束符),sizeof以及strlen的计算

    #include 
    #include 
    
    int main()
    {
        //因为这个字符串自带一个字符串结束符。所以arr数组中存储的元素实际上是7个:a b c d e f \0
        char arr[] = "abcdef";
    
        /*
        //计算数组大小。char类型占一个字节,arr中有7个元素。
        printf("%d\n",sizeof(arr));//7
        //arr是数组首元素地址;arr+0,还是首元素地址。地址的大小:4/8
        printf("%d\n",sizeof(arr+0));//8
        //arr是数组首元素地址,*arr找到数组首元素。也就是计算char类型大小
        printf("%d\n",sizeof(*arr));//1
        //arr[1]是数组首元素,计算数组首元素大小,也就是计算char类型大小
        printf("%d\n",sizeof(arr[1]));//1
        //&arr,取出整个数组地址。地址大小:4/8
        printf("%d\n",sizeof(&arr));//8
        //&arr,取出整个数组地址。&arr+1,跳过整个数组之后的一块地址。还是地址,计算地址的大小。4/8
        printf("%d\n",sizeof(&arr+1));//8
        //arr[0]是数组首元素,&arr[0]取出数组首元素地址,&arr[0]+1,数组首元素地址+1,就是数组第二个元素地址。是一个地址,4/8
        printf("%d\n",sizeof(&arr[0]+1));//8
        */
    
        //注意:字符串长度,不计算最后的字符串结束符\0
        //计算字符串长度,strlen计算字符串长度时,直到遇见字符串结束符'\0'才结束
        //arr是首元素地址,会一直往后取。直到碰到\0才结束。这里是6
        printf("\n%d\n",strlen(arr));//6
        //首元素地址+0,还是首元素地址。然后往后取,直到碰到\0才结束。与以上相同,是6
        printf("%d\n",strlen(arr+0));//6
    
        //strlen函数的原型:size_t __cdecl strlen(const char *_Str);
        //其参数是一个指针,也就是说参数应该是一个指针,或者直接传地址过去。
        //&arr,取出的是整个数组地址,该地址是char (*)[6]类型。然后放入strlen函数,被强制类型转换为char*类型。
        //也会从数组首元素开始取,直到碰到字符串结束符结束
        printf("%d\n",strlen(&arr));//6
        //&arr+1,紧接着数组地址后的一块地址。该地址也是char (*)[6]类型。然后放入strlen函数,被强制类型转换为char*类型。
        //会从数组后第一个元素开始取,直到碰到字符串结束符结束。也就是说是个随机值
        printf("%d\n",strlen(&arr+1));//随机值
        //arr[0]是数组首元素,&arr[0]取出数组首元素地址,&arr[0]+1,数组首元素地址+1,就是数组第二个元素地址。
        //从数组第二个元素取直到取到字符串结束符\0,少了数组首元素。所以应该是6-1=5
        printf("%d\n",strlen(&arr[0]+1));//5
    
        //*arr解引用找到数组首元素'a',a的ASCII码是97。97传入strlen函数,该函数需要一个地址。
        //可能被解析为:是要找97所在位置的数据,但是我们并不知道97位置有没有数据,所以这里是报错的。
        printf("%d\n",strlen(*arr));// 报错
        //arr[1]是数组首元素'b',b的ASCII码是98。找98位置数据,同样报错。
        printf("%d\n",strlen(arr[1]));//报错
    
        return 0;
    }
    
  • 常量字符串存储到指针中

    #include 
    #include 
    
    int main()
    {
        //p指针变量中存储的a的地址
        char* p = "abcdefgh";
        /*
        //p是一个指针。在32位平台下,指针占4个字节;64位平台下,指针占8个字节。 4/8
        printf("%d\n",sizeof(p));//8
        //p+1,指针+1,指向的是b。但是p+1是个地址,地址大小:4/8
        printf("%d\n",sizeof(p+1));//8
        //*p解引用找到a,a是字符类型,占一个字节大小
        printf("%d\n",sizeof(*p));//1
        //p[0] == *(p+0),可以找到a,char类型。
        printf("%d\n",sizeof(p[0]));//1
        //&p,取出p的地址。地址大小:4/8
        printf("%d\n",sizeof(&p));//8
        //&p+1,是一个地址。地址大小:4/8
        printf("%d\n",sizeof(&p+1));//8
        //p[0]找到a,取出其地址,&p[0]+1,是一个地址。地址大小:4/8
        printf("%d\n",sizeof(&p[0]+1));//8
         */
    
        //strlen函数的原型:size_t __cdecl strlen(const char *_Str);
        // 其参数是一个指针,也就是说参数应该是一个指针,或者直接传地址过去。
    
        //p是一个指针,指向a。一直取到字符串结束,是6
        printf("%d\n",strlen(p));//6
        //p是指向a的char类型指针,p+1,跳过1个字节,就是指向b。从b开始取到字符串结束,6-1=5
        printf("%d\n",strlen(p+1));//5
    
        //&p,取出的是p的地址。&p是一个char**类型,放入strlen函数,被强制转换为char*类型。
        //&p是一个地址,占8个字节。 地址的每个字节都被当作一个字符。直到取到\0(或者是十六进制的00)才会结束,其后面不知道什么时候才能取到字符串结束符,所以这里是一个随机值。
        printf("&p:%d\n",strlen(&p));//随机值
        //&p,取出的是p的地址。&p+1,是&p后面紧接着的那个地址。其后面不知道什么时候才能取到字符串结束符,所以这里是一个随机值
        printf("&p+1:%d\n",strlen(&p+1));//随机值
    
        //这两个地址都是随机值,有没有什么联系呢? //可能有联系、可能没有联系
        //没有联系的情况:
        //p变量的地址(&p):0x 00 00 7f f7 60 31 a0 10
        //因为内存中采用小端存储形式,也就是低位低地址。所以&p存储到内存中是:10 a0 31 60 f7 7f 00 00。
        //本来使用指针来取,则是倒着取出来。但是传入strlen函数,被强制转换为char*类型
        // 每次取出一个字节,10,a0,31,60,f7,7f,然后遇到字符串结束符00。00也是字符串结束符,所以这里是6。
        //紧接着10 a0 31 60 f7 7f 00 00后的8个字节就是&p+1的中存储的内容,从开头开始取,直到取到字符串结束符。不管什么时候取到字符串结束符,也与&p的长度没有关系
        //此时&p与&p+1就没有联系
    
        //有联系的情况:
        //p变量的地址(&p):0x11 22 7f f7 60 31 a0 10
        //因为内存中采用小端存储形式,也就是低位低地址。所以&p存储到内存中是:10 a0 31 60 f7 7f 22 11。
        //本来使用指针来取,则是倒着取出来。但是传入strlen函数,被强制转换为char*类型
        //从开头开始取,每次取出一个字节,10,a0,31,60,f7,7f,22,11,没有遇到字符串结束符。
        //紧接着10 a0 31 60 f7 7f 22 11后的8个字节就是&p+1的中存储的内容,继续取,直到取到字符串结束符。
        //不管什么时候遇到结束符,此时生成的随机值关系:&p的随机值减去8就是&p+1的随机值。
        //因为是在64位平台下,所以是减去8。如果是32平台下,那么地址的长度是4个字节,就是减去4。
    
    
        //p[0]找到a,&p[0]取出a的地址,&p[0]+1,也就是b所在的地址,然后取到字符串结尾。应该是6-1=5
        printf("%d\n",strlen(&p[0]+1));//5
    
        //*p解引用找到数组首元素'a',a的ASCII码是97。97传入strlen函数,该函数需要一个地址。
        //可能被解析为:是要找97所在位置的数据,但是我们并不知道97位置有没有数据,所以这里是报错的。
        printf("%d\n",strlen(*p));//报错
        //p[0]==*(p+0),还是首元素a,与以上原理相同
        printf("%d\n",strlen(p[0]));//报错
        return 0;
    }
    
  • 二维数组

    #include 
    
    int main()
    {
        int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
        //printf("%d\n",*(a[0]+1));
    
        //a是数组名,在这里表示整个数组。4*3*4=48
        printf("%d\n",sizeof(a));//48
        //a[0][0]表示二维数组中首元素的首元素,是一个int类型元素。
        printf("%d\n",sizeof(a[0][0]));//4
    
        //a[0]==*(a+0)是二维数组中的首元素。二维数组的首元素,也就是首行。a[0]就可以理解位第一行的数组名。
        //数组名单独放在sizeof中,代表这个数组。第一行有4个元素,每个元素占4个字节。4*4=16
        printf("%d\n",sizeof(a[0]));//16
        //a[0]是二维数组首行地址。a[0]单独使用时是代表首行四个元素。
        //a[0]+1,此时a[0]不再代表首行地址,而是首行的首个元素地址。a[0]+1,是指针运算,就表示首行的第二个元素的地址。地址大小:4/8
        printf("%d\n",sizeof(a[0]+1));//8
        //a[0]+1是首行的第二个元素。所以其大小是:4
        printf("%d\n",sizeof(*(a[0]+1)));//4
    
        //a作为二维数组数组名,没有&,也没有单独放在sizeof内部。
        //所以这里a代表二维数组第一行,a+1是二维数组第二行的地址。地址大小:4/8
        printf("%d\n",sizeof(a+1));//8
        //a+1是二维数组第二行的地址。解引用可以找到二维数组第二行。
        printf("%d\n",sizeof(*(a+1)));//16
    
        //a[0]是二维数组第一行的数组名,&a[0],取出二维数组第一行地址。&a[0]+1,就表示二维数组第二行的地址。地址大小:4/8
        printf("%d\n",sizeof(&a[0]+1));//8
        //&a[0]+1,二维数组第二行的地址。*(&a[0]+1),解引用找到二维数组第二行。这一行大小:16
        printf("%d\n",sizeof(*(&a[0]+1)));//16
        //a是二维数组数组名,其中存储的是首行地址。*a解引用找到第一行。第一行大小:16
        printf("%d\n",sizeof(*a));//16
    
        //我们定义的二维数组只有三行,而a[3]是数组的第四行了,也就是说数组访问越界了。
        //但是在C语言中,编译器不检查数组越界。而我们有说过一个表达式有两种属性,一种是值属性、一种是类型属性。
        //而sizeof()中的表达式在编译阶段就执行了,其中的表达式并不会真正计算,不是真正去访问a[3],从而也不会出现数组越界的情况。
        //所以其运用的就是表达式的类型属性。a[3]是int [4]类型的,所以4*4=16,a[3]的大小是:16。
        //计算了其类型大小之后,这个表达式在运行阶段也不会再执行。
        printf("%d\n",sizeof(a[3]));//16
    
        //复习
        short s = 5;
        int i = 4;
        //这里最后的运算结果是放在s中,所以是计算s的大小,s是short类型,占两个字节。
        //而其中的表达式在编译阶段,只是计算大小,不会执行其表达式。
        // 并且在运行阶段,这里的sizeof(s=a+6)就已经被计算好的大小2替换掉了,也就是说其中的表达式并不会执行。所以s的值并没有改变
        printf("\n%d\n",sizeof(s=a+6));//2
        printf("%d\n",s);//5
    
        return 0;
    }
    

指针笔试题

你可能感兴趣的:(C语言,c语言,矩阵,c++)