高级C语言-指针

计算机的内存长什么样子:

1、计算机的内存就像是一叠非常厚的"便签",一张便签就相当于一个字节的内存,一个字节有8个二进制位。

2、每一张"便签"的都有自然排序形成的一个编号,计算机根据便签的编号访问、使用"便签"。

3、CPU会有若干个金手指,每根金手指能感知高低两种电流,低电流当作二进制的0,高电流当作二进制的1,我们所说的32位的CPU,指的是CPU有32个金手指用于感知便签的编号:

便签的最小编号 00000000000000000000000000000000 = 0
便签的最大编号 11111111111111111111111111111111 = 4294967295
所以32位的CPU最多能使用 4294967296byte->4194304kb->4096mb->4gb

4、便签的编号也就是内存的地址,是一种无符号整数。(unsigned int\long)

什么是指针:

1、指针(pointer)是一种特殊的数据类型,使用它可以定义指针变量,简称指针。

2、指针变量中存储的是内存的地址,是一种无符号的整数。

3、通过指针变量中记录的内存地址,我们可以读取内存中所存储的数据,也可以向内存中写入数据。

4、一般使用%p以十六进制格式显示内存地址。

如何使用指针:
定义指针变量:

类型* 指针变量名;

int* xxx_p;

1、指针变量中只记录了内存中某个字节的内存地址,当我们把它当做一块连续内存块的首地址使用时,当使用指针变量访问内存时可以连续往后访问多个字节,具体连续访问多少个字节由定义该指针变量时它的类型决定,并无法修改

char* p;    //  访问1字节
int* p;     //  访问4字节
long* p1;   //  访问4/8字节
*(char*)p1  //  访问1字节
*p1         //  依然访问4/8字节

2、普通变量与指针变量的用法有所不同,为了避免混用,一般指针变量以p结尾

3、指针变量不能连续定义,一个*只能定义一个指针变量

int n1,n2,n3;   //都是int类型
int *p1,p2,p3;  //p1是int* 类型 p2 p3是int
int *p1,*p2,*p3;//都是int*类型

4、与普通变量相同的是,指针变量的默认值是随机的(野指针),为了安全起见,要给指针变量赋初始值,如果不知道该赋什么值,那么可以初始化为NULL(空指针)

int* p = NULL;

5、指针变量占用的内存是 4(32位系统)|8(64位系统)字节

给指针变量赋值:

指针变量 = 内存地址。

所谓的给指针变量赋值,就是给其存储一个内存地址

必须给指针变量赋一个有效地址,如果赋值的是非法地址,可能导致后续对该内存解引用时发生段错误

//  存储栈、data、bss内存段的地址
int num = 10;
int* p = #  //  &运算符 计算出变量的地址
//  注意:类型要匹配
​
//  存储堆内存地址
int* p = malloc(4); //  把堆内存申请出来的地址赋值给p

指针变量解引用:

*指针变量

给指针变量赋值就是让指针变量引用某块内存,解引用就是根据指针变量中存储的内存地址,访问对应的内存中的数据,具体连续访问多少个字节由定义指针变量时的类型决定

 int num = 88; 
​
    //  定义指针变量
    int* p = NULL;
​
    //  给指针变量赋值
    p = #
​
    *p = 99; 
​
    num = 77; 
    //  对指针变量解引用                                               
    printf("%d %d %p %p\n",*p,num,p,&num);
​
    return 0;
   

说明指针变量中存储的就是无符号整型
#include <stdio.h>
​
void func(unsigned long addr)
{
    *(int*)addr = 99; 
}
​
int main(int argc,const char* argv[])
{
    int num = 88; 
        
    func(&num);                                                       
    printf("%d\n",num);
    return 0;
}
​

为什么要使用指针:
1、函数之间需要共享普通变量

函数之间的命名空间是相互独立的,普通变量的传参是单向值传递(拷贝),所以通过传参无法解决普通变量共享的问题

全局变量可以在函数之间共享,但是过多地使用全局变量容易造成命名冲突和内存浪费

可以使用数组能够“址传递”,可以共享,但是使用麻烦并且额外传递数组的长度

综上原因,可以使用指针解决函数之间普通变量共享的问题,虽然函数之间命名空间是独立的,但是使用的内存地址编号是同一套

int Num = 0;
void func(int num)
{
    printf("func:%d\n",num);
    num = 88;
    printf("func:%d\n",num);
}
​
void func1(int arr[],int len)
{
    Num = 77;                                                                                                             
    arr[0] = 88;
}
​
void func2(int* p)
{
    printf("func2:%p\n",p);
    *p = 44;
}
​
int main(int argc,const char* argv[])
{
    int num = 10;
    printf("main:%d %p\n",num,&num);
    func2(&num);
    printf("main:%d\n",num);
​
    /*
    //func(num);
    int arr[1] = {10};
    printf("main:%d\n",arr[0]);
    func1(arr,1);
    printf("main:%d\n",arr[0]);
    */
           
​
//  p输出型参数
int func3(int* p)
{
    *p = 99; 
    return 88; 
}
int main(int argc,const char* argv[])
{
    int num = 10; 
    printf("main:%d %p\n",num,&num);
    func2(&num);
    printf("main:%d\n",num);
​
    int ret1 = 0;
    int ret2 = func3(&ret1);
    printf("ret1=%d ret2=%d\n",ret1,ret2);
​

2、使用指针变量可以提高函数的传参效率

函数是以赋值的方式来传参的,就是内存的拷贝,当把一个变量传参给另一个函数时,如果该变量的字节数比较大时,传参效率就比较低,而如果传递该变量的地址,只需要拷贝4|8字节即可,可以提高传参效率

#include <stdio.h>                                                     
​
//void func(long double d)
void func(long double* d)
{
​
}
​
int main(int argc,const char* argv[])
{
    long double d = 3.14;
    for(int i=0; i<1000000000; i++)
    {   
        func(&d);
    }   
    return 0;
}
 
3、使用堆内存时必须与指针变量配合

堆内存无法取名字,标准库、操作系统提供的内存分配接口函数的返回值都是内存地址,因此只能通过指针才能使用堆内存

int* p = malloc(4);
realloc()\calloc()\free()
注意:由于指针变量具有一定的危险性,使用不当容易发生段错误,所以除了以上三种情况外,都不要轻易使用指针

使用指针要注意的问题:
空指针:

如果指针变量中存储的是NULL,该指针称为空指针,如果对空指针解引用必定段错误

作用:

1、用于指针初始化

2、如果函数的返回值类型是指针类型时,当函数执行错误时,可以通过发返NULL表示函数执行错误,作为错误标志存在

int* func(void)
{
    if(条件)
    {
        return NULL;    //执行错误
    }
}
如何避免空指针产生的段错误?

对来历不明的指针在解引用之前,先判断是否是空指针,再解引用:

1、当你写的函数的参数是指针类型,需要先判断再使用,因为别人调用该函数时,可能会传递空指针

2、当你使用别人写的函数,该函数的返回值是指针类型时,可能会返回空指针,所以使用前也需要先判断

int* p = malloc(4);
if(NULL == p)
{
    printf("malloc error\n");
    return 0 ;
}
*p = 88;
if(NULL == p)   //  正确写法
if(p == NULL)   //  容易变成赋值语句
if(!p)          //  在大多数系统中NULL就是0,在极个别系统中NULL是1  
NULL是定义在stdio.h中的一个宏,要使用NULL的话必须导入stdio.h

野指针:

当指针变量中存储的地址,无法确定具体是哪个合法内存时,该指针就称为野指针

int num;
int* p = &num;
p = NULL;
int* p1;    //野指针
对野指针解引用的后果:

1、一切正常,野指针刚好指向空闲的有效的内存

2、段错误,指向非法内存

3、脏数据,存储的是其它数据的内存

野指针的危害比空指针要更大

1、一旦产生野指针,就无法判断出来,但是空指针可以被判断出来

2、野指针解引用的后果,问题可能是隐藏型的,有可能后面才会暴露

如何避免野指针的危害?

所有的野指针都是程序员制造的,如果严格遵循规则,不制造野指针就不会有危害

如何避免产生野指针?

1、定义指针变量时一定要初始化,要么给合法地址、要么给NULL

2、不要返回局部变量、块变量的地址,当函数执行结束后,局部变量、块变量就被销毁了,所以返回他们的地址,去访问该内存没有任何意义

#include <stdio.h>                                                     

int* func(void)
{
    int num = 10;
    int* p = &num;
    return p;
}

int main(int argc,const char* argv[])
{
    int* p = func();
    printf("%d\n",*p);
    printf("heheheh\n");
    printf("%d\n",*p);
    return 0;
}

3、当使用堆内存时,如果指向的堆内存已经被释放了,该指针要及时置空

int* p = malloc(4);
*p = 100;
free(p);    // 释放了4个字节的堆内存 p的指向没有改变
p = NULL;

练习:
1、实现一个函数,用于交换两个int变量的值。并调用它实现一个数组排序函数
void swap(int* p1,int* p2)
{
    int temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}
​
void sort(int arr[],int len)
{
    for(int i=0; i<len-1; i++)
    {
        for(int j=i+1; j<len; j++)
        {
            if(arr[i] > arr[j])
            {
                swap(&arr[i],&arr[j]);
            }
        }
    }
}
​
int main(int argc,const char* argv[])
{
    int n1 = 10,n2 = 20;
    swap(&n1,&n2);
    printf("%d %d\n",n1,n2);
​
    int arr[] = {4,6,23,45,6,7,2,45,7,8};
    sort(arr,sizeof(arr)/sizeof(arr[0]));
    for(int i=0; i<sizeof(arr)/sizeof(arr[0]); i++)
    {
        printf("%d ",arr[i]);
    }
    return 0;
}

2、实现一个函数,用于计算两整数最大公约数和最小公倍数。
#include <stdio.h>                                              
int max_min_num(int n1,int n2,int* min)
{
    //  计算最大公约数
    int max = 1; 
    for(int i=2; i<=n1; i++)
    {   
        if(0 == n1%i && 0 == n2%i)
        {   
            max = i;
        }
    }
    
    //  计算最小公倍数
    for(int i=n1*n2; i>=n1; i--)
    {   
        if(0 == i%n1 && 0 == i%n2)
        {   
            *min = i;
        }
    }
​
    return max;
}
int main(int argc,const char* argv[])
{
    int n1,n2;
    scanf("%d %d",&n1,&n2);
    int min = 0;
    int max = max_min_num(n1,n2,&min);
    printf("max:%d min:%d\n",max,min);
    return 0;
}
​

指针的进步值与指针的运算:

指针变量里面存储的是整数,代表着内存的编号(每个整数都对应着一字节的内存)。

指针的进步值:

指针变量中存储的其实是一个内存块的首地址,内存块的具体大小由指针变量的类型决定,当使用指针变量解引用访问内存时,实际访问的内存字节数叫做指针变量的进步值,也就是指针变量+1后的内存地址的变化。

int main(int argc,const char* argv[])
{
    char* p1 = NULL;
    short* p2 = NULL;
    int* p3 = NULL;
    long long* p4 = NULL;
    long double* p5 = NULL;
​
    printf("%p %p %p %p %p\n",p1,p2,p3,p4,p5);
//  printf("%lu %lu %lu %lu %d\n",p1,p2,p3,p4,p5);
    printf("%p %p %p %p %p\n",p1+1,p2+1,p3+2,p4+3,p5+1);             
    return 0;
}
​

指针的运算:

指针变量存储就是是整数,理论上整数能使用的运算符,指针变量都可以使用,但只有以下运算才有意义:

指针+n = 指针所代表的整数+进步值*n       
指针-n = 指针所代表的整数-进步值*n
指针1-指针2 = (指针1所代表的整数-指针2所代表的整数)/进步值 

指针加减整数,就相当于以指针变量的进步值为单位前后移动,指针-指针可以计算出两个指针变量之间相隔多少个元素。

注意:指针+-n之后得到的依然是指针,指针-指针得到整数

#include <stdio.h>
​
int main(int argc,const char* argv[])
{
    int num = 10; 
​
    int* p = &num;
    printf("%p %p %p\n",p,p+3,p-1);
    p += 10;                                                           
    printf("%p\n",p);
    int* p1 = &num;
    printf("%d\n",p - p1);
    
    int arr[10] = {1,2,3,40,5,6,7,8,9,10};
    int* p3 = &arr[2];
    int* p4 = &arr[7];
    printf("%d\n",*(p3+4));                                            
    printf("%d\n",p4-p3);
​
    return 0;
}
​

注意:

指针-指针运算,它们的类型必须相同,否则编译器会报错。

数组名与指针:
数组名就是指针:

1、数组名就是数组内存块的首地址,它是个常量地址(特殊的指针),所以它作函数的参数时,才能蜕变成指针变量。

#include <stdio.h>
​
void show_arr(int* arr,int len)
{
    for(int i=0; i<len; i++)
    {                                                                  
        printf("%d ",arr[i]);
        //printf("%d ",*(arr+i));
    }   
}
​
int main(int argc,const char* argv[])
{
    int arr[10] = {1,2,3,4,5,6,7,8,8,1};
    printf("%p %p\n",arr,&arr[0]);
​
    show_arr(arr,10);
    return 0;
}

2、指针变量可以使用[]解引用,数组名也可以*遍历,它们是等价的。

 int arr[10] = {1,2,3,4,5,6,7,8,8,1};
    printf("%p %p\n",arr,&arr[0]);
​
    int* p = arr;
​
    show_arr(arr,10);
    printf("\n");
    for(int i=0; i<10; i++)
    {   
    //  printf("%d ",*(arr+i));
    //  printf("%d ",*(p+i));
        printf("%d ",p[i]);
    }   
    return 0;
​

注意:如果定义<TYPE> arr[n]数组,数组名arr 就是 TYPE*类型的地址。

int arr1[10];   //  arr1是int*类型
double arr2[10];    //  arr2是double*类型
char arr3[10];  //  arr3是char*类型
​

数组名与指针的相同点:

1、它们都是地址

2、它们都使用[],*去访问一块连续的内存

数组名与指针的不同点:

1、数组名是常量,而指针是变量

2、指针变量有它自己的存储空间,而数组名就是地址,它没有存储地址的内存。

3、指针变量与它的目标内存是指向关系,而数组名与它的目标内存是映射关系。

通用指针:(万能指针)

一些具备通用性的操作函数,它们的参数可能是任意类型的指针,但编译器规定不同类型的指针不能进行赋值,为了兼容各种类型的指针,C语言中设计了void类型的指针,它能与任意类型的指针互相转换,它能解决不同类型的指针参数的兼容性问题。

void* p1 = NULL;
// void* 可以给任意类型的指针变量赋值
int* p2 = p1;
// 任意类型的指针可以给void*类型的指针赋值
void* p3 = p2;

通用操作的函数:

void bzero(void *s, size_t n);
功能:把内存块s的n个字节,赋值为0。
    
void *memset(void *s, int c, size_t n);
功能:把内存块s的n个字节,赋值为c(0~255)
返回值:就是s,为了链式调用
    
void *memcpy(void *dest, const void *src, size_t n);
功能:从src内存块拷贝n个字节的内容到dest内存块
返回值:就是dest,为了链式调用
    
int memcmp(const void *s1, const void *s2, size_t n);
功能:比较s1和s2内存块的n个字节,每个字节比较,一旦比较出结果立即返回
    s1 > s2 返回1
    s1 < s2 返回-1
    s1 == s2 返回0
注意:
void类型的指针变量的进步值是1。
void类型的指针变量不能解引用 ,必须转换成其它类型的指针才能解引用。
int main(int argc,const char* argv[])
{
    char arr[10] = {2,4,5,76,4,32,34,5,6};
    char arr1[10] = {1,1,1,1,1,1,1,1,1,1};
    void* p = arr;
    printf("%d\n",*(char*)p);
​
    //bzero(arr,sizeof(double)*3);
    memset(arr,1,10);
    printf("memcmp:%d\n",memcmp(arr,arr1,10));
    memcpy(arr,arr1,10);
    for(int i=0; i<10; i++)
    {   
        printf("%hhd ",arr[i]);
    }   
    return 0;
} 

练习3:实现一个 交换任意类型的两个变量的 函数。

#include <string.h>
​
void swap(void* p1,void* p2,size_t n)
{
    if(NULL == p1 || NULL == p2 || 0 == n)
        return;
    char temp[n];                                                   
    memcpy(temp,p1,n);
    memcpy(p1,p2,n);
    memcpy(p2,temp,n);
}
​
int main(int argc,const char* argv[])
{
    double n1 = 10,n2 = 20; 
    swap(&n1,&n2,sizeof(n1));
    printf("%lf %lf\n",n1,n2);
    return 0;
}

练习4:实现自定义的bzero、memset、memcpy、memcmp函数

#include <stdio.h>                                                                    
void my_bzero(void* s,size_t n)
{
    if(NULL == s || 0 == n)
        return;
​
    for(int i=0; i<n; i++)
    {
        //s[i] = 0;
        *((char*)s+i) = 0;
    }
}
void* my_memset(void* s,int c,size_t n)
{
    if(NULL == s || 0 == n) return NULL;
​
    for(int i=0; i<n; i++)
    {
        *((char*)s+i) = c;
    }
    return s;
}
​
void* my_memcpy(void* dest,const void* src,size_t n)
{
    if(NULL == dest || NULL == src || 0 == n)
        return NULL;
​
    for(int i=0; i<n; i++)
    {
        *(char*)(dest+i) = *(char*)(src+i);
    }
    return dest;
}
​
int my_memcmp(const void* s1,const void* s2,size_t n)
{
    if(NULL == s1 || NULL == s2 || 0 == n)
        return 0xffffffff;
​
    char* p1 = (char*)s1;
    char* p2 = (char*)s2;
    for(int i=0; i<n; i++)
    {
        if(p1[i] > p2[i])   return 1;
        if(p1[i] < p2[i])   return -1;
    }
    return 0;
}
​
int main(int argc,const char* argv[])
{
    int n1 = 10,n2 = 20,n3 = 100;
    my_memset(&n1,1,4);
    printf("%d\n",n1);
    my_memcpy(&n2,&n1,4);
    printf("%d\n",n2);
    printf("%d\n",my_memcmp(&n1,&n3,4));
    return 0;
}
​
​
int main(int argc,const char* argv[])
{
    double arr[10] = {24,3,6,6,34,5,6,45,43};
    my_bzero(arr,80);
    for(int i=0; i<10; i++)
    {
        printf("%lf ",arr[i]);
    }
    return 0;
}
​

const与指针:

就近原则 只需要关注const右边紧跟着的是* 还 p

const int* p;
功能:保护指针变量所指向的内存不被*p修改,或者说不能对 *p 赋值
​
int const *p;
功能:同上
    
int* p1 = p; // 编译时会有警告

由于指针的使用存在一定的风险,所以函数的参数只要是指针,并且函数没有修改指针所指向的内存的需求,我们就应该给指针变量加上const 类型* 指针变量。

void func(const double* d)
{   
    //*d = 10;  //  防止d的内存被修改导致死循环
}
​
int main()
{
    for(double i = 0; i<100000000; i++)
    {
        func(&i);
    }
    return 0;
}


int * const p;
功能:保护指针变量p不被修改
    
const int * const p;
功能:既保存指针变量p不被修改,也保护指针变量指向的内存*p不被修改
    
int const * const p;
功能:同上
    
    
    
    
const int num;
​
// 指向const修饰的变量时,指针变量要用const修改,否则编译会有警告
const int* p = &num;

当使用数组作函数的参数时,数组就蜕变成了指针变量,为了防止指针改变指向导致数组无法使用,理论上我们应该使用 类型* const 指针变量 防止指针变量改变指向。

void show_arr(int* const arr,int len)                                  
{
    printf("%p\n",arr);
    arr = NULL;
    printf("%p\n",arr);
}
​
int main(int argc,const char* argv[])
{
    int arr[10] = {}; 
    printf("main:%p\n",arr);
    show_arr(arr,10);
    printf("main:%p\n",arr);
}

与堆内存配合的指针变量也应该从一而终,这样定义 类型* const 指针变量 防止指针变量改指向,从面导致堆内存无法释放(防止产生内存泄漏)。

int* const p = malloc(4);
int num = 10;
//p = &num; 报错
free(p)

二级指针:
什么是二级指针:

一级指针存储的是普通变量的内存地址,二级指针存储的是指针变量内存地址。

int num;
int* p = &num;
定义二级指针:

类型* 一级指针;

类型** 二级指针;

注意:二级指针在使用方法上与一组指针不同,所以一般以pp结尾,让使用者从变量名上就能区别一级指针与二级指针。

二级指针的赋值:

二级指针 = &一级指针;

注意:给二级指针赋值的一级指针,它们的类型必须相同,否则编译时就会报错。

二级指针解引用:

二级指针 = &一级指针;

*二级指针 此时它等价于一级指针

**二级指针 此时它等价于 *一级指针

  int num = 10; 
    int* p = &num;
​
    int** pp = &p; 
    //*pp == p == &num
    //**pp == *p == num
    **pp = 88; 
    printf("%d %d %d\n",num,*p,**pp);
    printf("%p %p %p",*pp,p,&num);                                     
    return 0;
二级指针的用处:

只有一个情况适合使用二级指针,那就是跨函数共享一级指针变量。

#include <stdio.h>
​
int Num = 100;
​
void func(int** pp) 
{
    printf("%p %p\n",&Num,*pp);
    *pp = &Num;                                                        
    printf("%p %p\n",&Num,*pp);
}
​
int main(int argc,const char* argv[])
{
    int* p = NULL;
    func(&p);
    printf("%p %p\n",&Num,p);
​
    return 0;
}
练习5:实现一个函数,能够交换两个指针变量的指向
int n1 = 10,n2 = 20;
int* p1 = &n1, *p2 = &n2;
swap(xxx);
p1->n2, p2->n1
n1 n2值不能变,只能变p1 p2的指向
​
void swap_p(int** pp1,int** pp2)
{
    int* temp = *pp1;
    *pp1 = *pp2;
    *pp2 = temp;
}
​
int main(int argc,const char* argv[])
{
    int n1 = 10,n2 = 20; 
    int *p1 = &n1, *p2 = &n2;
    printf("%p %p %d %d\n",p1,p2,*p1,*p2);
​
    swap_p(&p1,&p2);                                                   
    printf("%p %p %d %d\n",p1,p2,*p1,*p2);
​
    return 0;
}

指针数组与数组指针:
什么是指针数组:

由指针变量构成的数组,也可以说它的身份是数组,成员是类型相同的指针变量。

定义指针数组:

类型* 数组名[n];

就相当于定义了n个类型相同的指针变量。

int* arr[10];   //  定义了由10个int*类型指针变量组成的数组
//  10个野指针
int* arr[10] = {};  //  10个NULL指针

指针数组的用处:

1、构建不规则二维数组。

int main(int argc,const char* argv[])
{
    int arr1[] = {4,1,2,3,4};
    int arr2[] = {7,2,2,3,4,5,6,7};
    int arr3[] = {1,8};
    int arr4[] = {5,9,2,3,4,5};
​
    int* arr[4] = {arr1,arr2,arr3,arr4};
  
    printf("size:%d\n",sizeof(arr[2]));
​
    for(int i=0; i<4; i++)
    {   
        for(int j=1; j<arr[i][0]+1; j++)                               
        {
            printf("%d ",arr[i][j]);
        }
        printf("\n");
    }   
    return 0;
}

2、构建字符串数组。

什么是数组指针:

专门指向数组的指针变量,它的进步值是整个数组的字节数。

定义数组指针:

类型 (*指针变量名) [n];

类型和n决定了 数组指针 指向的是什么样的数组。

int (*arrp)[10];//专门指向长度为10类型为int的数组的数组指针
数组指针的用处:
#include <stdio.h>
// 使用数组指针可以把一块连续的内存当作二维数组使用,特别是与堆内存配合效果更佳
int main()
{
    int arr[20] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
    int (*p)[5] = (void*)arr;                                                   
    for(int j=0; j<4; j++)
    {
        for(int i=0; i<5; i++)
        {
            //printf("%d ",*(*(p+j)+i));
            printf("%d ",p[j][i]);
        }
        printf("\n");
    }
}
#include <stdio.h>
​
int main(int argc,const char* argv[])
{
    int arr[5] = {1,2,3,4,58};      
    int (*p)[5] = &arr;
    printf("%d\n",*((int*)(p+1)-1));
    //  结果是58  
    // p+1  指向了58的后面
    //(int*)(p+1)-1 往后退4字节 访问58
    
    return 0;
}
数组指针可以用于函数之间传递二维数组:
函数之间传递二维数组的方式:
1、void func(int arr[行数][列数])        
    //行列数要固定
2、void func(int arr[][列数],int x)        
    //列数不能省略
3、void func(int (*arr)[列数],int x)   
    //一样缺乏泛用性
4、void func(int* arr,int x,int y)
{
    printf(“%d ”,*(arr+i*y+j)); //使用麻烦
    arr+1*5+3 == *(arr+8)
    int arr[3][5];
    func((int*)arr,3,5);
}   
5、void func(int x,int y,int arr[x][y])  //  建议
  

数组名、指针、数组指针:
int arr[n];
arr             int*
*arr            int
&arr[0]         int*
&arr            int (*)[n]  
    int (*p)[n] = &arr;
        *p <=> arr
        arr[i] <=> *(arr+i)
​
int arr2[r][c];
arr2            int (*)[c]
*arr2           int*
**arr2          int
arr2[i][j]      int
&arr2[0]        int (*)[c]
&arr2[0][1]     int*
​

函数指针:
函数名是什么:

函数就是一段具有某项功能的代码,它会被编译器编译成二进制指令存储在text内存段,函数名就是它在text内存段的首地址。编译器认为函数名就是一个地址(整数)

什么是函数指针:

专门存储函数地址的指针变量叫函数指针。

定义函数指针:

1、先确定指向的函数的格式(函数声明)。

2、照抄函数声明。

3、用小括号包含函数名。

4、在函数名前加*

5、在函数名末尾加_fp,防止命名冲突。

, 6、用函数名给函数指针赋值后,函数指针就可以当作函数调用了。

#include <stdio.h>
​
void func(void)
{
    printf("我是函数func,我被调用了...\n");
}
​
int main()
{
    void (*func_fp)(void) = func;
    func_fp();  
}
函数指针的用处:

函数指针可以让函数像数据一样在函数之间传递。

当我们实现一个数组的排序函数时,那么排序函数内部需要调用数组元素的比较函数,由于我们不知道待排序的数组是什么类型,也就无法自己实现数组元素的比较函数,那么我们可以在排序函数的参数列表中预留一个函数指针,当有人调我们的排序函数时,他就需要提供一个数组元素比较函数供我们调用,排序函数就可以为它的数组进行排序。

函数的这种调用模式就叫回调模式。

void qsort(void *base, 
           size_t nmemb, 
           size_t size,
           int (*compar)(const void *, const void *));
功能:为数组进行排序
base:数组的首地址
nmemb:数组的长度
size:数组成员的字节数
compar:调用者需要提供的数组元素的比较函数 回调函数
#include <stdio.h>                                                                                           
#include <stdlib.h>
​
int cmp_int(const void* p1, const void* p2)
{
    int n1 = *(int*)p1;
    int n2 = *(int*)p2;
    if(n1 > n2) return -1;
    if(n1 < n2) return 1;
    return 0;
}
​
int cmp_double(const void* p1, const void* p2)
{
    return *(double*)p1 - *(double*)p2;
}
int main(int argc,const char* argv[])
{
    int arr[10] = {5,5,2,46,7,-100,3,5,67,99};
    double arr_d[] = {3.11,7.232,2.11};
​
    qsort(arr,10,sizeof(arr[0]),cmp_int);
    qsort(arr_d,3,8,cmp_double);
​
    for(int i=0; i<10; i++)
    {
        printf("%d ",arr[i]);
    }
    for(int i=0; i<3; i++)
    {
        printf("%lf ",arr_d[i]);
    }
    return 0;
 
  • 8
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言是一种广泛应用于系统编程和嵌入式开发高级编程语言。函数指针C语言中的一个重要概念,它可以指向函数,并且可以通过函数指针来调用函数。快速排序算法是一种常用的排序算法,它的核心思想是通过分治的策略将一个大问题分解为多个小问题,并通过递归的方式解决这些小问题。 下面是C语言中使用函数指针实现快速排序算法的示例代码: ```c #include <stdio.h> // 交换两个元素的值 void swap(int* a, int* b) { int temp = *a; *a = *b; *b = temp; } // 快速排序的分区函数 int partition(int arr[], int low, int high) { int pivot = arr[high]; // 选取最后一个元素作为基准 int i = (low - 1); // 定义一个指针,用于指向小于基准的元素 for (int j = low; j <= high - 1; j++) { if (arr[j] < pivot) { i++; swap(&arr[i], &arr[j]); } } swap(&arr[i + 1], &arr[high]); return (i + 1); } // 快速排序函数 void quickSort(int arr[], int low, int high) { if (low < high) { int pi = partition(arr, low, high); // 将数组分区,并获取分区点的位置 quickSort(arr, low, pi - 1); // 对分区点左边的子数组进行快速排序 quickSort(arr, pi + 1, high); // 对分区点右边的子数组进行快速排序 } } // 打印数组元素 void printArray(int arr[], int size) { for (int i = 0; i < size; i++) { printf("%d ", arr[i]); } printf("\n"); } int main() { int arr[] = {10, 7, 8, 9, 1, 5}; int n = sizeof(arr) / sizeof(arr[0]); printf("原始数组:"); printArray(arr, n); quickSort(arr, 0, n - 1); printf("排序后的数组:"); printArray(arr, n); return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值