C语言(指针详解)重点笔记:指针易错点,都是精华

简介: C语言(指针详解)重点笔记:指针易错点,都是精华

1.字符指针

#include<stdio.h>
int fun(int arr[])//本质上是一个指针
{
  return sizeof(arr)/sizeof(arr[0]);
//指针大小/整型元素的大小,32位系统,指针大小4,得到结果为1
//64位系统指针大小8个字节,得到结果为2
} 
int main()
{
  int arr[10];
  fun(arr);
  printf("%d",fun(arr));
  return 0; 
}

将一个字符串存到数组中,数组名赋给pc,pc相当于指向了字符数组(arr)


int main()
{
    char arr[]="abcdef";
    char* pc=arr;
    printf("%s\n",arr);
    printf("%s\n",pc);
   
    return 0;
}

结果相同

abcdef

abcdef

注:在32位系统中,指针只有四个字节,无法存放”abcded\0“这7个字节,所以”abcdef“放到p中这样的理解是错误的,如何理解


p中存放的是a的地址(把字符串首字符的地址赋值到p),即*p=a;


int main()
{
    char* p="abcdef";//常量字符串
    printf("%c\n",*p);//输出结果为a
    printf("%s\n",p);//输出结果为abcdef
    return 0;
}

a的地址是0x0012ff44,p中存放这个地址,就能找到这个常量字符串,通过这个指针能找到字符串的空间,因为字符串的结尾是\0,打印时遇到后则停止打印。



"abcdef"是常量字符串,既然是常量字符串,就不能被改动,就会报错(segmentfault)


int main()
{
    const char* p="abcdef";//这里最准确的写法是加上const
    *p='W';
    printf("%s\n",p);
    return 0;
}

面试题


int main()
{
    char arr1[]="abcdef";
    char arr2[]="abcdef";
    const char* p1="abcdef";
    const char* p2="abcdef";
    
/*
    if(arr1==arr2)
        printf("1\n");
    else
        printf("2");
*/
 
    if(p1==p2)
        printf("1\n");
    else
        printf("2");
    
    return 0;
}

arr1!=arr2:两个不同的数组,占有不同的空间


p1==p2:为了节省空间,p1和p2都指向同一个存储空间的起始位置


注p1和p2还是两个独立的空间,所以p1改变,p2不会受到影响



2.指针数组和数组指针

(1)指针数组


int main()
{
    int arr1[]={1,2,3,4,5};
    int arr2[]={2,3,4,5,6};
    int arr3[]={3,4,5,6,7};
       
    int* parr[]={arr1,arr2,arr3};
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<5;j++)
        {
            printf("%d ",*(parr[i]+j));//parr[i]:是数组的地址,就是该数组首元素的地址
                                       //i表示第几行,j表示改行的偏移值
        }
        printf("\n");
    }
    return 0;
}


注意:指向的是每个数组首元素的地址



(2)数组指针



数组名绝大多数情况下指的是首元素地址


取地址数组名(&arr)取出的是数组的地址


arr+1和&arr+1


#include<stdio.h>
int main()
{
  int arr[10]={0};
  printf("arr=%p\n",arr);
  printf("&arr=%p\n",&arr);
  printf("arr+1=%p\n",arr+1);
  printf("arr+1=%p\n",&arr+1);
  return 0;
}

结果



一个数组名加1,跳过一个元素,取地址数组名+1跳过一个数组


指针的运用


int main()
{
  int arr[10]={1,2,3,4,5,6,7,8,9,10};
    int *p=arr;    //指向数组的首元素
    for(int i=0;i<10;i++)
    {
        printf("%d",*(p+i));
    }
    /*
  int (*pa)[10]=&arr;
  int i=0;
  for(i=0;i<10;i++)
  {
  printf("%d",*(*pa+i));//*pa=arr,arr相当于数组名,数组名相当于首元素地址
  }
    */
  /*
  for(i=0;i<10;i++)
  {
  printf("%d",(*pa)[i]);//pa表示整个数组的地址,*pa表示整个数组的值,(*pa)[i]表示数组的第几个元素
 
  }
  */
  return 0;
}
数组指针一般用在二维数组比较方便
//参数是数组类型 
void print1(int arr[3][5],int x,int y)
{
  int i=0;
  int j=0;
  for(i=0;i<x;i++)
  {
  for(j=0;j<y;j++)
  {
    printf("%d ",a[i][j]);
  }
  printf("\n");
  }
}
//参数是指针类型 
void print2(int (*p)[5], int x,int y)//指向的是一行,就是一维数组
{
  for(int i=0;i<x;i++)
  {
  for(int j=0;j<y;j++)
  {
    printf("%d ",*(*(p+i)+j));//j表示每一行数组的偏移值*(*(p+i)+j) 
            //printf("%d ",(*(p+i))[j]);//与上面代码效果相同
           //printf("%d ",*(p[i]+j));
          //printf("%d ",p[i][j]);
           
  }
  printf("\n");
  }
}
int main()
{
  int arr[3][5]={{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
  print1(arr,3,5);
//数组名传参是整个数组 ,arr--数组名--数组的首元素地址,对于二维数组而言,
在讨论二维数组首元素时,要把其想像为一维数组,就是把第一行当作第一个元素,第二行第二个元素...,
所以二维数组中数组名就表示首元素的地址,就是第一行的地址,第一行是一个一维数组,
那么数组地址要放到数组指针当中,指针中放的是一个含有5个元素的一维数组,类型是int
  print2(arr,3,5);
  return 0;
} 
 p指向的数组为5个元素的话,所以p+1,就跳过一行,所以对*(p+i)进行遍历,*(p+i)=该行的数组名,即该行首元素,再用 j 代表偏移值,找到某行的某一元素
void print2(int (*p)[5], int x,int y)//指向的是一行,就是一维数组
{
  for(int i=0;i<x;i++)
  {
  for(int j=0;j<y;j++)
  {
    printf("%d ",*(*(p+i)+j));//j表示每一行数组的偏移值*(*(p+i)+j) 
            //printf("%d ",(*(p+i))[j]);//与上面代码效果相同
           //printf("%d ",*(p[i]+j));
          //printf("%d ",p[i][j]);
           
  }
  printf("\n");
  }
}
#include<stdio.h>
int main()
{
  int arr[10]={1,2,3,4,5,6,7,8,9,10};
  int i=0; 
  int* p = arr;
  for(int i=0;i<10;i++)
  {
  printf("%d ",*(p+i));//int* p,指向数组首元素地址,*(p+i)表示遍历各个元素
  printf("%d ",*(arr+i));//arr表示数组首元素地址,(arr+i)表示遍历各个元素,*(arr+i)表示取各个元素的值
  printf("%d ",p[i]);
  printf("%d ",arr[i]);//arr[i]==*(arr+i)=*(p+i)==p[i]...等价 
  }
  return 0;
}


回顾字符指针,数组指针和指针数组



3. 数组参数和指针参数

(1)一维数组传参

(2)二维数组传参

void test(int arr[3][5])
{}
void test2(int arr[][5])//可以把行省略 
{}
void test3(int arr[3][])//不可以把列省略
//必须知道一行有多少列才方便计算
//错误的 
void test4(int *arr)//二维数组的数组名,表示首元素的话,首元素应该是第一行,第一行不应该放在整型指针里面,整型指针里面应该放整型地址 
{}
void test4(int **arr)//一维数组的地址不能放到二级指针当中,一级指针的地址才能放到二级指针当中
{}
void test4(int *p[5])//这是一个指针数组,但是二维数组传过来的是第一行的地址,指针里面才能存放地址 
//正确的 
void test4(int (*p)[5])//指针指向的是一维数组,数组中有5个元素,每个元素是int类型 
int main()
{
  int arr[3][5]={0};
  test(arr);//二维数组传参 
  test2(arr);
  test3(arr); 
  test4(arr); 
  return 0; 
}


对于void test4(int **arr){}这里特别说明一下


一级指针,指向的是数组的元素,一级指针才能指向数组的首元素,不能是二级指针


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


int **s0, *s1, **s2;


s0 = &a;


s1 = a;//首元素地址


s1 = &a[2];


s2 = &s1;


s0 是二级指针,它指向一维数组


s1 是一级指针,它指向一维数组的元素


s2 是二级指针,它指向一级指针


下面这几种情况才能被二级指针接受,下面指针传参也会提到


void test(int **p)
{}
int main()
{
  int *ptr;
  int** pp=&ptr;
  int* arr[10];
  test(arr);//这个数组名代表的是int *的地址,一级指针的地址可以用二级指针接收 
  test(&ptr);
  test(pp);
  return 0;
}

(3)一级指针传参(指针传参,指针接受)

#include<stdio.h>
void print(int *p,int sz)
{
  int i=0;
  for(i=0;i<sz;i++)
  {
  printf("%d\n",*(p+i));
  }
}
int main()
{
  int arr[10]={1,2,3,4,5,6,7,8,9};
  int *p=arr;
  int sz=sizeof(arr)/sizeof(arr[0]);
  //一级指针p,传给函数
  printf(p,sz);
  return 0; 
}


思考


当一个函数的参数部分为一级指针的时候,函数能接收什么参数


例如

(4)二级指针传参(参数接受一级指针变量的地址或者是二级指针,也可以传存放一级指针的数组(char* arr[])的数组名)

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

补充


void test(int **p)
{}
int main()
{
  int *ptr;
  int** pp=&ptr;
  int* arr[10];
  test(arr);//这个数组名代表的是int *的地址,一级指针的地址可以用二级指针接收 
  test(&ptr);
  test(pp);
  return 0;
}

4.函数指针

数组指针---指向数组的指针


函数指针---指向函数的指针---存放函数地址的一个指针


int Add(int x,int y)
{
  int z=0;
  z=x+y;
  return z; 
} 
#include<stdio.h>
int main()
{
  int a=10;
  int b=20;
    //printf("%d\n",Add(a,b));
  //Add(a,b);
    printf("%p\n",&Add);//取地址Add,就是函数的地址
    printf("%p\n",Add);//与上面的结果一样
//在数组中&arr,表示整个数组的地址,arr表示首元素的地址,而在函数中&Add和Add是相同的,因为没有函数首地址这样的概念
  return 0;
}


结果相同



int Add(int x,int y)
{
  int z=0;
  z=x+y;
  return z; 
} 
#include<stdio.h>
int main()
{
  int a=10;
  int b=20;
    //printf("%d\n",Add(a,b));
  //Add(a,b);
    /*
    printf("%p\n",&Add);//取地址Add,就是函数的地址
    printf("%p\n",Add);//与上面的结果一样
//在数组中&arr,表示整个数组的地址,arr表示首元素的地址,而在函数中&Add和Add是相同的,因为没有函数首地址这样的概念
    */
    int *pa(int,int)=Add;//这里的pa是一个函数名而已,不是指针,不能存放函数地址
//pa是一个函数名,后面的(int,int)是参数类型,前面的int *是返回类型,这是不正确的,我们希望pa是一个指针,指针才能存放地址,正确的写法为
    int (*pa)(int,int)=Add;//这是一个函数指针,最前面的int表示函数的返回类型,(int,int)表示参数类型
    (*pa)(2,3);//调用这个函数,再将(2,3)传入这个函数
    //结果为5
    pa=Add;
  return 0;
}


再来看一个例子


void Print(char *str)
{
  printf("%s\n",str);
}
int main()
{
  void (*p)(char*)=Print;
  (*p)("hello");//调用这个函数,并传入一个char类型元素,或者说让char* 指向char类型的元素 
  return 0;
}

两段有趣的代码


代码1


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


void(*p)(char *)=Print;//去掉指针变量名p后,就是指针类型void (*p)(char *),这里一段一段分析


void(*)():这是一个指针类型,指向的是一个(),即一个函数,返回类型是void
所以
void(*) ():函数指针类型
那么
(  void(*) () ):就是强制类型转换
((void(*)())0):对0进行强制类型转换,即把0作为某函数的地址
(*(void(*)())0):通过*找到这个函数
(*(void(*)())0)():调用这个函数,这个函数的参数是(),即无参
这个代码是一次调用,调用0地址处的参数为无参,返回类型为void的函数

代码2:


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


signal(int,void(*)(int))
函数有两个参数,一个是int类型,一个是函数指针类型,函数的参数是int,返回值是void
与void Add(int,int)相同,将Add(int,int)去掉,剩下的就是函数返回类型
所以signal函数的返回类型是void(*)(int):函数指针类型,参数是int,返回类型是void
这是一次函数申明,signal是一个函数,函数的第一个参数是整型,第二个参数是函数指针(void(*)(int)),函数的返回类型也是一个函数指针(void(*)(int))
注意不能这样写
void(*)(int)  signal(   int, void(*)(int)  )
*号一定要和函数指针变量名signal在一起


怎么写的更加简洁?


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


typedef void(*pfun_t)(int);//pfun_t要放在*旁边


可以写为


pfun_t signal(int,pfun_t);


int main()
{
    int a=10;
    int b=20;
   
    int (*pa)(int,int)=Add;
    printf("%d\n",pa(2,3));//pa是Add这个函数的地址,调用这个地址,指向这个函数
    printf("%d\n",Add(2,3));
    printf("%d\n",(*pa)(2,3));//指针解引用找到对象
    printf("%d\n",(**pa)(2,3));
    printf("%d\n",(***pa)(2,3));
//*就没有太大的价值,这些输出都相同,并且如果加上*则必须用括号括起来,如(*pa),否则无法编译
    return 0;
}

5.函数指针数组

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;
}
int main()
{
    //指针数组
    int *arr[5];
    int (*pa)(int,int)=Add;
//因为Add,Sub,Div,Mul这几个函数的函数类型和返回值都相同,所以可以表示这几个函数的地址,那么怎么区分呢
//需要一个数组,这个数组可以存放四个函数的地址--函数指针的数组
    int(*pa[4])(int,int)={Add,Sub,Mul,Div};//pa首先跟[]结果成为一个数组,数组有4个元素,每个元素的类型是函数指针
    int i=0;
    for(i=0;i<4;i++)
    {
        printf("%d\n",parr[i](2,3));
        //parr[i]可以不写解引用(*),直接传参
        //依次调用这几个函数5,-1,6,0
    }
    return 0;
}


举例


//计算器
void menu()
{
  printf("*******************\n");
  printf("**  1.add   2.sub**\n");
  printf("**  3.add   4.div**\n");
  printf("**  0.exit       **\n");
  printf("*******************\n");
} 
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;
}
int main()
{
  int x=0;
  int y=0;
  do
  {
  menu();
  printf("请选择:>");
  scanf("%d",&input);
 
  switch(input)
  {
  case 1:
            printf("请输入两个操作数:>")
      scanf("%d %d",&x,&y); 
    printf("%d\n",Add(x,y));
    break;
  case 2:
            printf("请输入两个操作数:>")
      scanf("%d %d",&x,&y); 
    printf("%d\n",Sub(x,y));
    break;
  case 3:
            printf("请输入两个操作数:>")
      scanf("%d %d",&x,&y); 
    printf("%d\n",Mul(x,y));
    break;
  case 4:
            printf("请输入两个操作数:>")
      scanf("%d %d",&x,&y); 
    printf("%d\n",Div(x,y));
    break;
  case 0:
    printf("退出\n");
    break;
  default:
    printf("选择错误!\n");
    break;
  }
  }while(input);
}


以上的代码冗余过多,改进后可写为


/计算器
void menu()
{
  printf("*******************\n");
  printf("**  1.add   2.sub**\n");
  printf("**  3.add   4.div**\n");
  printf("**  0.exit       **\n");
  printf("*******************\n");
} 
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;
}
int main()
{
  int x=0;
  int y=0;
    //pf是一个函数指针数组----转移表
    int (*pf[5])(int,int)={0,Add,Sub,Mul,Div};//Add的下标刚好为1,Sub的下标刚好为2....
  do
  {
  menu();
  printf("请选择:>");
  scanf("%d",&input);
        if(input>=1 && input<=4)
        {
            printf("请输入两个操作数:>")
         scanf("%d %d",&x,&y); 
            int ret=pf[input](x,y);//接受返回值
            printf("%d\n",ret);
        }
        else if(input==0)
        {
            printf("退出\n");
        }
        else
        {
            printf("选择错误\n");
        }
    }while(input);
} 
 6.指向函数指针数组的指针
int Add(int x,int y)
{
    return x+y;
}
int main()
{
    int arr[10]={0};
    int (*p)[10]=&arr;//取出数组的地址
    int (*pf)(int,int);//函数指针
    int (*pf[4])(int,int);//pf是一个数组-函数指针的数组
    //ppf是一个指向[函数指针数组]的指针
    int (*(*ppf)[4])(int,int)=&pf;//ppf是一个数组指针,指针指向的数组有4个元素
                                  //指向的数组的每一个元素的类型是一个函数指针int (*)(int,int)
    return 0;
}


7.回调函数

不断调用Calc(int(*pf)(int,int))


接收函数的地址,通过收到的地址,调用相应的函数,那么Calc函数可以实现不同的功能


//计算器
void menu()
{
  printf("*******************\n");
  printf("**  1.add   2.sub**\n");
  printf("**  3.add   4.div**\n");
  printf("**  0.exit       **\n");
  printf("*******************\n");
} 
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 Calc(int (*pf)(int,int))//传函数名,就是函数地址(指针)
{   
    int x=0;
    int y=0;
    printf("请输入两个操作数:>")
    scanf("%d %d",&x,&y); 
  printf("%d\n",pf(x,y));
}
int main()
{
  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:
            printf("请输入两个操作数:>")
      scanf("%d %d",&x,&y); 
    printf("%d\n",Div(x,y));
    break;
  case 0:
    printf("退出\n");
    break;
  default:
    printf("选择错误!\n");
    break;
  }
  }while(input);
}


回调函数最简单的示例


void print(char *str)
{
  printf("hehe:%s",str);  
} 
void test(void (*p)(char *))
{
  printf("test\n");
  p("bit"); //char类型的指针指向char类型的变量
} 
int main()
{
  test(print);
  return 0;
}


输出结果


test


hehe:bit


回调函数示例:冒泡排序



经典冒泡排序的代码不够通用


void bubble_sort(int arr[],int sz)
{
    int i=0;
    for(i=0;i<sz-1;i++)
    {
        int j=0;
        for(j=0;j<sz-i-1;j++)
        {
            if(arr[j]>arr[j+1])
            {
                int tmp=arr[j];
                arr[j]=arr[i];
                arr[j+1]=tmp;
            }
        }
    }
}
struct Stu
{
    char name[20];
    int age;
};
int main()
{
    int arr[10]={9,8,7,6,5,4,3,2,1,0};
    int sz=sizeof(arr)/sizeof(arr[0]);
    struct Stu s[3]={{"张三",20}{"李四",30}{"王五",10}};
   
    bubble_sort(arr,sz);//不能用简单的冒泡排序进行比较
   
    int i=0;
    for(i=0;i<sz;i++)
    {
        printf("%d",arr[i]);
    }
    return 0;
}


需要运用qsort函数处理更为复杂的冒泡排序


void qsort(void *base,//待排序数组的首元素


               size_t num,//待排序数组的元素个数


               size_t width,//待排序数组的每个元素的大小,单位是字节


               int(*cmp)(const void *e1,const void *e2)//函数指针,比较两个元素的方法的函数的地址,这个函数需要自己实现


               函数指针的两个参数,是待比较两个元素的地址


);


补充

void*:能够接受任意类型元素的指针


int main()
{
    int a=10;
    int* pa=&a;
    char* pa=&a;//发出警报
    void* p=&a;//正确,不发出警报
    *p =0;//非法的间接寻址,void *指针是不能进行解引用操作的,因为不知道他是是什么类型的指针,不知道访问多少个字节
    p++;//也报错,不知道向后迈几个字节
   
    return 0;
}

● 当第一个元素小于第二个元素时,返回一个小于0的数字


●当第一个元素等于第二个元素时,返回0


●当第一个元素大于第二个元素时,返回一个大于0的数字



运用上述知识,得到最终代码为:


#include<stdio.h>
#include<stdlib.h>//qsort函数的头文件
#include<string.h>
void bubble_sort(int arr[],int sz)
{
    int i=0;
    for(i=0;i<sz-1;i++)
    {
        int j=0;
        for(j=0;j<sz-i-1;j++)
        {
            if(arr[j]>arr[j+1])
            {
                int tmp=arr[j];
                arr[j]=arr[i];
                arr[j+1]=tmp;
            }
        }
    }
}
struct Stu
{
    char name[20];
    int age;
};
/*
void qsort(void *base,
                size_t num,
                size_t width,
                int(*cmp)(const void *e1,const void *e2)//比较
);
*/
//*(int*)e1 强制类型转换为int *,进行解引用
int cmp_int(const void *e1,const void *e2)//比较两个整数值,e1和e2是要比较的两个参数的地址
{
    return *(int*)e1-*(int*)e2;
}
void test1()
{
    int arr[10]={9,8,7,6,5,4,3,2,1,0};
    int sz=sizeof(arr)/sizeof(arr[0]);
    qsort(arr,sz,sizeof(arr[0]),cmp_int);
    int i=0;
    for(i=0;i<sz;i++)
    {
        printf("%d",arr[i]);
    }
}
int cmp_float(const float* e1,const float* e2)
{
    if(*(float*)e1==*(float*)e2)
        return 0;
    else if(*(float*)e1>*(float*)e2)
        return 1;   
    else
        return -1;
/*
也可以直接进行强制类型转换
    return ((int)(*(float*)e1-*(float*)e2));
*/
}
void test2()
{
     float f[]={9.0,8.0,7.0,6.0,5.0,4.0};
     int sz=sizeof(f)/sizeof(f[0]);
     qsort(f,sz,sizeof(f[0]),cmp_float);
     int j=0;
     for(j=0;j<sz;j++)
     {  
          printf("%f",f[j]);
     }
}
//按照年龄排序
cmp_stu_by_age(const void* e1,const void* e2)
{
    return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//强制转换为结构体指针   
}
com_stu_by_name(const void* e1,const void* e2)
{
//比较字符串,不能直接使用大于小于等于符号,应该用strcmp函数
     return strcmp(   ((struct Stu*)e1)->name  ((struct Stu*)e2)->name    );
//strcmp函数:第一个大于第二个,返回大于0数字,第一个小于第二个返回小于0数字,等于则返回0
//和qsort函数规则相同,可直接返回    
}
void test3()
{
    struct Stu s[3]={{"张三",20}{"李四",30}{"王五",10}};
    int sz=sizeof(s)/sizeof(s[0]);
    qsort(s,sz,sizeof(s[0]),cmp_stu_by_age);
}
void test4()
{
    struct Stu s[3]={{"张三",20}{"李四",30}{"王五",10}};
    int sz=sizeof(s)/sizeof(s[0]);
    qsort(s,sz,sizeof(s[0]),cmp_stu_by_name);
}
int main()
{
 
    test1();
    test2();
    test3();
    test4();
    //bubble_sort(arr,sz);//不能用简单的冒泡排序进行比较
    return 0;
}
改造冒泡排序的数组
#include<stdio.h>
Swap(char* buf1,char* buf2,int width)//交换width,由buf1和buf2指向的字符,交换每一对字节,两两对应
{
    int i=0;
    for(i=0;i<width;i++)
    {
        char tmp=*buf1;
        *buf1=*buf2;
        *buf2=tmp;
        buf1++;
        buf2++;
    }
}
//可以接收任意想要比较的两个元素
void bubble_sort(void* base,int sz,int width,int (*cmp)(void *e1,void *e2))
{
    int i=0;
    //趟数
    for(int i=0;i<sz-1;i++)
    {    
        //每一趟比较的对数
        int j=0;
        for(j=0;i<sz-i-1;j++)
        {
            //两个元素的比较
            if(cmp((char*)base+width*j,(char*)base+width*(j+1))>0)
            //cmp(base,base+1):错误,base是void*类型
            //cmp((char*)base+width*j,(char*)base+width*(j+1)):char是一个字节,+width表示跳过宽度个字节
            Swap((char*)base+width*j,(char*)base+width*(j+1),width);
        }
    }
}
int cmp_int(const void *e1,const void *e2)//比较两个整数值,e1和e2是要比较的两个参数的地址
{
    return *(int*)e1-*(int*)e2;
}
com_stu_by_name(const void* e1,const void* e2)
{
//比较字符串,不能直接使用大于小于等于符号,应该用strcmp函数
     return strcmp(   ((struct Stu*)e1)->name  ((struct Stu*)e2)->name    );
//strcmp函数:第一个大于第二个,返回大于0数字,第一个小于第二个返回小于0数字,等于则返回0
//和qsort函数规则相同,可直接返回    
}
void test()
{
    int arr[10]={9,8,7,6,5,4,3,2,1,0};
    int sz=sizeof(arr)/sizeof(arr[0]);
//使用这个函数的程序员一定知道自己排序的是什么数据
//就应该知道如何排序待排序的元素
    bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);//根据地址调用相应的比较函数
}
void test2()
{
    struct Stu s[3]={{"张三",20}{"李四",30}{"王五",10}};
    int sz=sizeof(s)/sizeof(s[0]);
    bubble_sort(s,sz,sizeof(s[0]),cmp_stu_by_name);
}
int main()
{
    test();
    test2();
    return 0;
}
目录
相关文章
|
2月前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
58 0
|
21天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
74 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
21天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
47 9
|
21天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
41 7
|
1月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
109 13
|
25天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
25天前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
84 3
|
26天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
1月前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
54 11
|
25天前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
36 1