指针使用详解

目录

一、什么是指针

1.内存

2.地址的生成

3.数据的储存

4、指针变量

5.解引用操作符*

二、指针变量类型

1.指针加减整数

2.指针的解引用

三、野指针

1.野指针的成因

2.避免野指针的方法

四、指针运算

1.指针加减整数

2.指针减指针

3.指针的关系运算

五、指针和数组

六、二级指针

七、字符指针

1.字符指针的使用

2.笔试题

八、指针数组

九、数组指针

1.数组指针的定义和创建

2.数组名和&数组名

3.数组指针的使用

十、数组与指针传参

1.一维数组传参

2.二维数组传参

3.一级指针传参

4.二级指针传参

十一、函数指针

1.函数指针

2.函数指针实现计算器

十二、函数指针数组

1.函数指针数组的定义

2.函数指针数组简化代码

十三、指向函数指针数组的指针

十四、回调函数

1.qsort函数

 2.利用冒泡排序思想模拟实现qsort函数


一、什么是指针

1.内存

程序的运行需要储存信息,而信息储存在内存中,我们为了有效地使用内存,就需要将内存划分为一个个小的内存单元,每一个单元的大小是一个字节。(一个字节比较合理,这个内存单元太小也不好,太大也不好)为了能够有效地使用每个内存单元,我们给每一个单元都定了一个编号,这个编号就叫做这个内存单元的地址。假如内存是一幢楼房,就像楼中的门牌号,通过门牌号我们可以找到对应的房间。同样在计算机中使用这样的方式,我们可以轻松地找到对应的字节位置,而不需要一个一个去比对。

内存

编号(取十六进制数)

1byte

0x00000001(十进制:1)

1byte

0x00000002(十进制:2)

1byte

0x00000003(十进制:3)

1byte

0x00000004(十进制:4)

1byte

0x00000005(十进制:5)

1byte

0x00000006(十进制:6)

1byte

......

2.地址的生成

我们的电脑中都有硬件电路,用于生成地址的电线叫地址线。当电路中有电路通过时,会产生正负脉冲,从而表示0与1.此处我们以三十二位电脑为例,它在生成地址时32根地址线同时产生电信号表示1或0,当每一个地址线组合起来时就有了许许多多的不同的排列组合方式

我可以三十二个全为0:00000000000000000000000000000000——对应0

我也可以前三十一个全为0:00000000000000000000000000000001——对应1

......

这样的排序方式一共有2的32次方种,也就是说它有是4294967296种排序,内存中就一共有这么多个字节的空间。

但是这个数字不是很直观,我们先对它除以1024得到4194304个KB,再除1024得到4096个MB,再除以1024得到4GB,也就是说在早期的三十二位电脑内存中一共有4GB的内存空间。

3.数据的储存

这里我们打开VS2022,输入以下代码,可以通过调试找到a的地址

#include <stdio.h>
int main()
{
    int a = 10;
    &a;//取出num的地址,&为取地址符号
    //这里的num共有4个字节,每个字节都有地址,但我们取出的是第一个字节的地址(较小的地址)
    printf("%p\n", a);//打印地址,%p是以地址的形式打印
    return 0; 
}

具象化的话,就用以下内容表示:

内存

编号

1byte

0xFFFFFFFF

1byte

0xFFFFFFFE

1byte

......

a

0x0012FF47

0x0012FF46

0x0012FF45

0x0012FF44

1byte

......

1byte

0x0000002

1byte

0x0000001

我们实际上取出的只有0x0012FF47这个地址

我们在VS上的内存窗口上可以看到三行,包含以下内容:

地址

内存中的数据(补码)

内存数据的文本解析

0x0012FF47

a0 00 00 00(小端存储)

????(内容不定,没什么用处)

我们a的值为10,用二进制表示即为:0000 0000 0000 0000 0000 0000 0000 1010(二进制的数字表达最后一位表示2的0次方,倒数第二位就表示2的1次方,以此类推,十就是2的3次方加2的一次方也就是1010),在这个时候我们以每个四位为一组,就可以得到数据的表示方法:00 00 00 0a(在16进制数中,a表示10,b表示11,c表示12,d表示13,e表示14,f表示15)

0000 0000 0000 0000 0000 0000 0000 1010

0 0 0 0 0 0 0 a

4、指针变量

请看以下代码:

#include<stdio.h>
int main()
{
    int a = 10;
    int* p = &a;
    //我们把a这个变量的地址储存在这个变量p中,这个p就叫做指针变量,类型为int*
    return 0;
}

编号代表地址,而地址也可以成为指针,有一定的指向作用,所以叫做指针变量。简单地说指针就是地址。

那我们如何理解这个int* p = &a;呢?

(1)中间的*表示p是个指针变量,注意指针变量是p,而不是*p

(2)int表示指针指向的对象是整形

(3)p为指针变量,接受&a的内容,也就是变量的首地址

在了解这些后,我们也可以创建其它类型的指针变量,比如:

#include <stdio.h>
int main()
{
    char ch = 'w';
    char* pc = &ch;//字符型变量的指针
    *pc = 'q';
    printf("%c\n", ch);
    return 0; 
}
//输出:q

5.解引用操作符*

int main()
{
	int a = 10;
	int* p = &a;
	printf("%d", *p);//这里的*p表示对指针变量p解引用,找到其对应的内容
	return 0;
}
//输出:10

注意:int*p = &a;中的*不表示解引用,通过解引用符号,我们可以轻易地找到指针地址的对应值

6.指针变量的大小

#include <stdio.h>
int main()
{
    printf("%d\n", sizeof(char *));
    printf("%d\n", sizeof(short *));
    printf("%d\n", sizeof(int *));
    printf("%d\n", sizeof(double *));
    return 0; 
}

你可能认为输出结果是:1 2 4 8

但实际上是:4\8 4\8 4\8 4\8(4或8)

原因:指针变量储存的是地址,也就是说指针变量的大小取决于存放一个地址需要的空间的大小,32位平台下地址是32个bit位(即4个字节),而64位平台下地址是64个bit位(即8个字节),所以指针变量的大小就是4或8.

结论:指针大小在32位平台是4个字节,64位平台是8个字节。

二、指针变量类型

1.指针加减整数

#include <stdio.h>
int main()
{
    int n = 10;
    char *pc = (char*)&n;
    int *pi = &n;
    printf("%p\n", &n);
    printf("%p\n", pc);
    printf("%p\n", pc+1);
    printf("%p\n", pi);
    printf("%p\n", pi+1);
    return  0; 
}
//结果:
//007FFC20
//007FFC20
//007FFC21
//007FFC20
//007FFC24

在这里我们可以看到,不管打印int*还是char*指针对应的地址,它们的结果都是一样的。

那么我们为什么不能将所有的指针统一为一个数据类型呢?

然而,当我们观察两种类型的指针加一后的地址时,不难发现,int*类型的地址向后移动了4字节,char*类型的地址向后移动了1字节.

(1)总结:指针的类型决定了指针向前或者向后走一步有多大

2.指针的解引用

#include <stdio.h>
int main()
{
    int n = 0x11223344;
    char* pc = (char*)&n;
    int* pi = &n;
    *pc = 0;
    printf("%x\n", n);
    *pi = 0; 
    printf("%x\n", n);
    return 0;
}
//结果:
//11223300
//0

在内存中,0x11223344这个数字以小端字节序存储(44 33 22 11),先用char* 的指针解引用只能访问一个字节,所以会把第一个字节改为0,也就会打印11223300;而 int* 的指针解引用能访问四个字节,所以会把第四个字节都改为0,也就会打印0

(2)总结:指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

三、野指针

野指针就是指针指向的位置是不可知的,就像一条没有拴住的恶犬,接近它是会受伤的。

1.野指针的成因

(1)指针没有初始化

#include<stdio.h>
int main()
{
    int* p;//没有初始化
    *p = 20;//不清楚地址在哪里,为野指针
    return 0;
}

(2)指针的越界访问

#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(i=0; i<=10; i++)
    {
        printf("%d ",*(p+i));    
        //可以读取到arr[10],越界访问,为野指针
    }
    return 0;
}

(3)指针指向的空间被释放

#include<stdio.h>
int* add(int x,int y)
{
    int d = x+y;
    int* p = &d;
    return p;//返回和的地址
}
int main()
{
    int a = 10;
    int b = 20;
    int* c = add(a,b);
    //因为退出了函数,原来p指针这个地址也就不在程序的作用域内了,c就成为了野指针
    printf("%d ",*c);
    //这个解引用虽然数值是正确的,但由于这个空间不在程序的作用域内,
    //我们无法确定是否会有其他的操作会改变它的值
    return 0;
}

2.避免野指针的方法

(1)指针初始化

(2)小心指针越界

(3)指针指向空间释放即使置NULL

(4)避免返回局部变量的地址

(5)指针使用之前检查有效性

可以在使用指针时加上下面的代码:

#include <stdio.h>
int main()
{
    int a = 10;
    p = &a;
    //使用的指针一定要有准确的地址
    int *p = NULL;
    //不用的指针记得置空
    if(p != NULL)
    {
        *p = 20;
    }
    //空指针不能解引用,加上判断
    return 0; 
}

四、指针运算

1.指针加减整数

#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
//这里虽然下标为五的元素属于越界访问,但是并没有读取它的内容,不算越界访问
{
     *vp++ = 0; 
     //vp先解引用,然后再++
}

指针加减整数可以让地址向后或向前移动对应的字节数。

2.指针减指针

int my_strlen(char *s) 
{
    char *p = s;
    while(*p != '\0' )
        p++;
    return p-s; 
}

指针减指针可以求出中间所差的类型对应字节数的个数

3.指针的关系运算

for(vp = &values[N_VALUES]; vp > &values[0];)
{
    *--vp = 0; 
}

指针的比较实际上就是十六进制数字的比较

五、指针和数组

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    printf("%p\n", arr);
    printf("%p\n", &arr[0]);
    int i = 0;
    for(i=0; i<10; i++)
    {
        printf("&arr[%d] = 0x%p",i,&arr[i])    
    }
    return 0; 
}
//结果:
//00DBFD88
//00DBFD88
//&arr[0] = 0x00DBFD88 = p+0
//&arr[1] = 0x00DBFD8C = p+1
//&arr[2] = 0x00DBFD90 = p+2
//&arr[3] = 0x00DBFD94 = p+3
//&arr[4] = 0x00DBFD98 = p+4
//&arr[5] = 0x00DBFD9C = p+5
//&arr[6] = 0x00DBFDA0 = p+6
//&arr[7] = 0x00DBFDA4 = p+7
//&arr[8] = 0x00DBFDA8 = p+8
//&arr[9] = 0x00DBFDAC = p+9

数组名为数组首元素地址:arr = 00DBFD88,arr[0] = 00DBFD88

&arr[0] = 0x00DBFD88 = p+0

&arr[1] = 0x00DBFD8C = p+1

&arr[2] = 0x00DBFD90 = p+2

&arr[3] = 0x00DBFD94 = p+3

&arr[4] = 0x00DBFD98 = p+4

&arr[5] = 0x00DBFD9C = p+5

&arr[6] = 0x00DBFDA0 = p+6

&arr[7] = 0x00DBFDA4 = p+7

&arr[8] = 0x00DBFDA8 = p+8

&arr[9] = 0x00DBFDAC = p+9

从这些结果我们得到arr[i]等同于*(p+i),这也就解释了数组中元素的访问其实使用了指针的思想,也让我们了解到数组元素的访问可以使用指针。

六、二级指针

1.指针变量也是变量,是变量就有地址,那指针变量的地址就可以储存在二级指针内 。

2.二级指针解引用需要两次才能找到变量

#include<stdio.h>
int main()
{
    int a = 0;
    int* p1 = &a;
    int** p2 = &p;
    //二级指针,存放指针变量的地址
    //可以看作int* *p2,前面的int*表示指向的对象为int*类型,后面的*表示p2为指针变量
    printf("%d",**p2);//p2解引用一次得到指针变量p1,再解引用得到a
    return 0;
}

七、字符指针

1.字符指针的使用

#include<stdio.h>
int main()
{
    char a = 'w';
    char* p = &a;
    printf("%c",*p);
    //这是最简单的使用方法
    const char* pstr = "hello bit.";
    //这里指针保存了这个字符串的首字符地址而不是整个字符串
    printf("%s\n", pstr);
    //按字符串打印内存中的数据会打印到\0终止
    return 0;
}

2.笔试题

#include <stdio.h>
int main()
{
    char str1[] = "hello world.";
    char str2[] = "hello world.";
    const char *str3 = "hello world.";
    const char *str4 = "hello world.";
    if(str1 ==str2)
        printf("str1 and str2 are the same\n");
    else
        printf("str1 and str2 are not the same\n");       
    if(str3 ==str4)
        printf("str3 and str4 are the same\n");
    else
        printf("str3 and str4 are not the same\n");       
    return 0; 
}
//结果:
//str1 and str2 are not same
//str3 and str4 are same

这段代码中的str1与str2是两个不同的数组,元素的内容都是hello world.,而str3与str4是两个指针变量,内容都是hello world.的首字符地址。

数组储存在栈区,每创建一个新的数组都需要占用内存空间存储,所以两个地址不同;字符串常量储存在静态区,不需要储存多个这样的字符串,所以两个指针变量都指向了同一个地址。

八、指针数组

指针数组也是数组,只是存放的元素是指针。

#include<stdio.h>
int main()
{
    int a = 0;
    int b = 0;
    int c = 0;
    int* arr[3]={&a,&b,&c};
    //这就是一个简单的指针数组
    return 0;
}

九、数组指针

1.数组指针的定义和创建

定义:指向数组的指针

#include<stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    //int arr[10]是一个数组,我们首先去掉数组名
    //int [10]在中间写上指针变量名p,再写上*表示p为指针变量
    //最后为了防止被解析为指针数组再加上括号:int (*p)[10],这就是一个指向数组的指针
    //[10]表示指向的数组有是个元素,前面的int表示数组的元素为int类型
    return 0;
}

2.数组名和&数组名

#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; 
}
//结果:
//arr = 00EFFCDC
//&arr= 00EFFCDC
//arr+1 = 00EFFCE0
//&arr+1= 00EFFD04

arr = 00EFFCDC,&arr= 00EFFCDC二者虽然在内容上是一样的,但是arr+1 = 00EFFCE0跳过了4字节,&arr+1= 00EFFD04跳过了整个数组的40字节,二者加一跳过的字节数不同。

3.数组指针的使用

重要思想:一个二维数组可以看作多个一维数组的组合,但二维数组在内存中是连续存放的。

#include<stdio.h>
void print1(int arr[3][5], int r, int c)//二维数组传参可以直接写数组
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0;j < c;j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
void print2(int (*arr)[5], int r, int c)//但是在本质上,用数组指针接收会更好
//由于arr[1]=*(p+1),我们用指针的思想改变代码
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0;j < c;j++)
		{
                        //printf("%d ",arr[i][j]);
                        //printf("%d ",*((arr[i])+j));
			printf("%d ", *(*(arr+i)+j));
                        //上面三行代码效果是一样的                          
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
	printf("print1\n");
	print1(arr, 3, 5);//二维数组的数组名也是首元素地址,但是这个首元素是首个一位数组的地址
	printf("print2\n");
	print2(arr, 3, 5);
	return 0;
}
//结果:
//print1
//1 2 3 4 5
//2 3 4 5 6
//3 4 5 6 7
//print2
//1 2 3 4 5
//2 3 4 5 6
//3 4 5 6 7

请看下面的代码,注意理解数据的类型

#include<stdio.h>
int main()
{
    //当去掉变量名时剩下的就是数据的类型
    int parr[5];//整形数组,共有五个元素
    int* parr1[10];//整型指针的数组,共有十个元素
    int(*parr2)[10];//数组指针,指向的数组有十个整型元素,指针的类型为int(*)[10]
    int(*parr3[10])[5];//指针数组,包含十个数组指针,指向的是个数组都是有五个整型元素
    return 0;
}

十、数组与指针传参

1.一维数组传参

#include <stdio.h>
void test(int arr[10])//可以直接写整形数组
{}
void test(int arr[])//数组的元素个数可以省略
{}
void test(int* arr)//本质上数组名是指针
{}
void test2(int* arr[20])//这是个整型指针数组,不符合
{}
void test2(int** arr)//这是个二级指针,也不符合
{}
int main()
{
    int arr[10] = { 0 };
    test(arr);
    test2(arr);
}

2.二维数组传参

void test(int arr[3][5])
{}
void test(int arr[][])
{}
void test(int arr[][5])
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素才方便运算。



//二维数组的首元素地址是第一行一维数组的地址
void test(int* arr)//这是一个整型指针,不符合
{}
void test(int* arr[5])//这是一个整型指针数组,不符合
{}
void test(int(*arr)[5])//这是一个整型数组指针,符合
{}
void test(int** arr)//这是一个二级指针,不符合
{}
int main()
{
    int arr[3][5] = { 0 };
    test(arr);
}

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,参数为对应的指针类型
    print(p, sz);
    return 0; 
}

4.二级指针传参

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

十一、函数指针

1.函数指针

函数指针是指向函数的指针

#include<stdio.h>
void print(int n)
{
}
int main()
{
    //首先函数的定义去掉函数名:void (int n)
    //在中间加上(*)表明它是指针变量:void (*)(int)
    //写上变量名,也可以删去内部参数的变量名:void (*p1)(int)

    void (*p1)(int) = print;//初始化
    void (*p2)(int) = &print;
    printf("0x%p\n", p1);
    printf("0x%p\n", p2);
    return 0;
}
//结果:
//0x009013F2
//0x009013F2

(1)函数名与&函数名都是函数的地址

(2)这个函数地址储存在函数区里,所以函数的地址与它是否被调用无关

2.函数指针实现计算器

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
//定义加减乘除
void menu()
{
	printf("**************************\n");
	printf("*** 1.Add ****** 2.Sub ***\n");
	printf("*** 3.Mul ****** 4.Div ***\n");
	printf("********* 0.exit *********\n");
	printf("**************************\n");
}
//打印初始界面
void calu(int (*f)(int, int))//接收函数
{
	int ret = 0;
	int a = 0;
	int b = 0;
	printf("请输入两个值:");
	scanf("%d %d", &a, &b);
	ret = f(a, b);//调用相应函数
	printf("结果为:%d\n", ret);
}
//通过函数指针可以简化代码
int main()
{
	menu();
	int input = 0;
	do
	{
		printf("请输入:");
		scanf("%d", &input);
		switch(input)//根据input的值判断加减乘除
		{
		    case 0:
		    {
				printf("退出程序");
				break;
		    }
		    case 1:
		    {
				calu(Add);//传参相应的函数指针
				break;
		    }
			case 2:
			{
				calu(Sub);
				break;
			}
			case 3:
			{
				calu(Mul);
				break;
			}
			case 4:
			{
				calu(Div);
				break;
			}
			default:
			{
				printf("请重新输入\n");
				break;
			}
		}
	}while (input);
	return 0;
}

十二、函数指针数组

1.函数指针数组的定义

函数指针数组是存储函数指针的数组

#include<stdio.h>
int Add(int x, int y)
{
    return x + y;
}
int Sub(int x, int y)
{
    return x - y;
}
int Mul(int x, int y)
{
    return x * y;
}
int Div(int x, int y)
{
    return x / y;
}
int main()
{

    int (*arr[5])(int, int) = { Add,Sub,Mul,Div };
    //函数指针数组,元素类型为int (*)(int, int)
    //内部写上数组名和元素个数:int (*arr[5])(int, int)
    int (**p[5])(int, int)
    return 0;
}

2.函数指针数组简化代码

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
void menu()
{
	printf("**************************\n");
	printf("*** 1.Add ****** 2.Sub ***\n");
	printf("*** 3.Mul ****** 4.Div ***\n");
	printf("********* 0.exit *********\n");
	printf("**************************\n");
}
void calu(int (*f)(int, int))
{
	int ret = 0;
	int a = 0;
	int b = 0;
	printf("请输入两个值:");
	scanf("%d %d", &a, &b);
	ret = f(a, b);
	printf("结果为:%d\n", ret);
}
int main()
{
	menu();
	int input = 0;
	int (*arr[5])(int, int) = { 0,Add,Sub,Mul,Div };//函数指针数组,对应数字对应函数
	do
	{
		printf("请输入:");
		scanf("%d", &input);
		if(input>=1 && input<=4)
		{
			calu(arr[input]);//对应函数,大量简化代码
		}
		else if (input == 0)
		{
			printf("退出程序");
		}
                else
		{
			printf("请重新输入\n");
		}
	} while (input);
	return 0;
}

十三、指向函数指针数组的指针

指向函数指针数组的指针就是指向函数指针数组的指针

禁止套娃~

是不是已经晕了,其实这样可以无限套娃。了解这些定义方法是帮我们认识这些指针与数组的定义方法。

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

十四、回调函数

定义:回调函数就是一个被作为参数传递的函数,一个函数作为另一个函数参数。

在下面,我们通过qsort(快速排序)函数来讲解

1.qsort函数

(1)qsort函数定义在stdlib.h的头文件中,注意包含头文件。

(2)qsort一共有四个参数,需要排序元素的首地址(void*),元素的个数(size_t),每个元素的大小(size_t),用于定义判定大小方式的compare函数,也就是回调函数的使用。

(3)compare函数需要满足参数为void*的指针,两个元素相减的结果为正数,前大于后;两个元素相减的结果为负数,后大于前;两个元素相减的结果为零,二者相等。

(4)qsort函数可以排序任何类型的数据且默认排升序。

#include<stdio.h>
#include<stdlib.h>
int compare(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);
}
int main()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), compare);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

 2.利用冒泡排序思想模拟实现qsort函数

//使用冒泡排序思想模拟实现qsort函数
#include<stdio.h>
int int_compare(const void* e1, const void* e2)//设计的函数,这只是int类型的比较,还可以编写其他函数排序其他数据
{
	return (*(int*)e1 - *(int*)e2);
}
void exchange(char* p1, char* p2, int sz)//一个字节一个字节交换
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		int temp = 0;
		temp = *(p1+i);
		*(p1+i) = *(p2+i);
		*(p2 + i) = temp;
	}
}
void my_sort(void* p, int n, int sz, int compare(const void*, const void*))
{
	char* arr = (char*)p;
	int i = 0;
	for (i=0; i<n-1; i++)
	{
		int j = 0;
		for (j=0; j<n-i-1; j++)
		{
			if (compare(arr + sz * j, arr + sz * (j + 1)))//以自己设计的函数的返回值确定先后顺序
			{
				exchange(arr + sz * j, arr + sz * (j + 1), sz);
			}
		}
	}
}
int main()
{
	int arr[10] = { 10,9,8,7,6,5,4,3,2,1 };
	my_sort(arr, sizeof(arr)/sizeof(arr[0]), sizeof(arr[0]),int_compare);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}
//结果:1 2 3 4 5 6 7 8 9 10

  • 19
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
引用\[1\]:C语言字节对齐问题详解中提到了C语言中的字节对齐问题。在结构体中,为了提高内存访问的效率,编译器会对结构体进行字节对齐。这意味着结构体的成员在内存中并不是紧凑排列的,而是按照一定的规则进行对齐。具体的对齐规则取决于编译器和编译选项。\[1\] 引用\[2\]:在C语言中,可以使用宏offsetof来获取结构体成员相对于结构体开头的字节偏移量。这个宏非常有用,可以帮助我们计算出每个结构体成员相对于结构体开头的偏移字节数。通过这个宏,我们可以更好地理解结构体的内存布局。\[2\] 引用\[3\]:在C语言中,指针和结构体的组合常常用于处理复杂的数据结构。指针可以指向结构体的成员,通过指针可以方便地对结构体进行操作。指针和结构体的组合可以实现更灵活的数据处理和内存管理。\[3\] 综上所述,C语言中的指针结构体组合可以用于处理复杂的数据结构,而字节对齐问题则是在结构体中为了提高内存访问效率而进行的优化。通过使用宏offsetof,我们可以更好地理解结构体的内存布局。 #### 引用[.reference_title] - *1* *3* [结构体指针C语言结构体指针详解](https://blog.csdn.net/weixin_34069265/article/details/117110735)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [C语言之结构体详解](https://blog.csdn.net/m0_70749276/article/details/127061692)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值