C语言的指针内容全篇

    前情提示:本篇博客的内容较多,阅读的时间可能会较长。而且图片也比较多,希望大家能够慢慢地、认真地看完,相信你一定会有收获的!!!

C语言的指针内容全篇_第1张图片

目录

一.指针初阶

1.内存

2.指针是什么

 3.指针的大小

4.指针和指针类型

5.野指针

6.指针运算

7.指针和数组

8.二级指针 

9.指针数组

二.指针进阶

          1.字符指针

2.指针数组

 3.数组指针

4.&数组名vs数组名

5.数组参数、指针参数

6.函数指针

7.函数指针数组

8.指向函数指针数组的指针

9.回调函数

10.自定义bubble_qsort函数

11.指针和数组笔试题解析

12.指针笔试题


一.指针初阶

1.内存

    内存是电脑中特别重要的存储器,计算机中程序的运行都是在内存中进行的。所以为了有效地使用内存,就把内存划分为一个个小的内存单元,每个内存单元的大小是一个字节。为了能够有效地访问内存中的每个单元,就给内存单元进行了编号,这些编号就被称为该内存单元的地址。

C语言的指针内容全篇_第2张图片

2.指针是什么

    其实指针就是地址。内存中的每个小的内存单元都有着自己的地址,为了方便找到我们想要的数据,我们可以定义一个指针变量来存放数据的地址。通过这个地址,我们就能很好地找到我们想要的数据。就好比,你的好朋友想来你宿舍找你,他只有知道你宿舍的门牌号(地址),才能更快地找到你。

    变量是在内存中创建的(在内存中分配空间),因为每个内存单元都有地址,所以变量也是有地址的。那怎么取出变量的地址呢?请看下方的代码。

 C语言的指针内容全篇_第3张图片

    如果我们用指针存放一个变量的地址,那么我们对指针进行解引用操作就可以修改这个变量的值了。

C语言的指针内容全篇_第4张图片

 3.指针的大小

    在32位的机器上,地址是32个0 or 1组成的二进制序列,则地址就得用4个字节的空间来存储,所以一个指针变量的大小为4个字节。 那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。

4.指针和指针类型

    学习这个内容之前,我们先看一下下面的这个代码。

C语言的指针内容全篇_第5张图片

     我们可以看到,不管是什么类型指针,它们的大小都是4个字节。那跟类型有什么关系?类型好像没什么意义?我们是不是可以自己创建一直通用类型的指针呢?其实这样是行不通的,这也说明类型是有意义的。

C语言的指针内容全篇_第6张图片

C语言的指针内容全篇_第7张图片

C语言的指针内容全篇_第8张图片

C语言的指针内容全篇_第9张图片

    对比上面的四张图片,我们可以发现:当pa是整型指针的时候,对它解引用后它可以访问四个字节并将四个字节的数据改为了00;而当pa是字符指针的时候,对它解引用后它只能访问一个字节并将这个字节的数据改为了00。只是指针类型发生了变化,指针访问的权限大小就发生了变化。那么说明指针类型是有意义的。对整型指针进行解引用操作能访问4个字节,而对字符指针进行解引用操作只能访问1个字节。通过这个例子,我们就可以知道指针类型决定了指针解引用的权限。       那指针类型还有别的意义吗?其实还有,我们通过下面的代码来学习一下。

 C语言的指针内容全篇_第10张图片

     我们可以看到p和pc打印出来的地址是一样的,但是p+1和pc+1所打印出来的地址是不一样的。p+1的地址比p的地址多加了4,而pc+1的地址比pc的地址只多加了1。这是为什么呢?因为p是整型指针,整形指针+1跳过1个整型,也就是跳过4个字节;而pc是字符指针,字符指针+1跳过一个字符,也就是跳过1个字节。这也就是为什么p+1的地址比p的地址多加了4,而pc+1的地址比pc的地址只多加了1。那么指针类型的第二意义就是:指针类型决定了,指针走一步,能走多远。              那我们再通过对比下面的两个例子来加深对指针类型意义的了解!         

C语言的指针内容全篇_第11张图片

C语言的指针内容全篇_第12张图片

 C语言的指针内容全篇_第13张图片

C语言的指针内容全篇_第14张图片

     第一个程序中的p是整型指针,所以p+i表示跳过4*i个字节,指向数组第i+1个元素,然后通过解引用操作访问4个字节将数组中的元素改成0到9;而第二个程序中的p是字符指针,所以p+i表示跳过i个字节,而整型数据有四个字节,那么arr[0]就改成了0x03020100,对应的十进制数字为50462976.通过这两个代码的对比,我们就更能了解指针类型的意义。

5.野指针

  概念:指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。

野指针成因

  • 指针未初始化

C语言的指针内容全篇_第15张图片

  •  指针越界访问

C语言的指针内容全篇_第16张图片

  •  指针指向的空间释放

C语言的指针内容全篇_第17张图片

如何避免野指针问题                                                       

  • 指针初始化
  • 小心指针越界
  • 指针指向的空间释放及时将指针置为空指针
  • 指针使用之前检查有效性

C语言的指针内容全篇_第18张图片

6.指针运算

指针+-整数

C语言的指针内容全篇_第19张图片 指针-指针

C语言的指针内容全篇_第20张图片

    使用指针-指针定义求字符串长度的函数

C语言的指针内容全篇_第21张图片

指针关系运算 

C语言的指针内容全篇_第22张图片

C语言的指针内容全篇_第23张图片

     在绝大部分的编译器上第二个代码是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。                                                                                                                    标准规定

允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

7.指针和数组

    指针和数组有什么关系呢?其实数组名就是数组首元素的地址,我们可以定义一个指针变量来存放这个的地址。那我们来看看下面这段代码。

C语言的指针内容全篇_第24张图片

     通过上面的代码,我们可以知道p+i表示数组第i+1个元素的地址,再通过解引用操作,我们就可以对该元素赋值了。

C语言的指针内容全篇_第25张图片

 拓展:访问数组元素的多种方式

C语言的指针内容全篇_第26张图片

8.二级指针 

C语言的指针内容全篇_第27张图片

9.指针数组

    我们知道,整型数组是存放整型的数组,字符数组是存放字符的数组。那么依次类推,指针数组就是存放指针的数组。我们可以通过下面代码来简单地认识一下指针数组。

C语言的指针内容全篇_第28张图片

    不过指针数组经常不这么来使用,关于指针数组的高级使用,将在指针进阶里面向大家介绍。

二.指针进阶

1.字符指针

    字符指针就是指向字符的指针,它跟整型指针类似。字符指针可以存放一个字符的地址,通过解引用操作可以将该字符修改。

C语言的指针内容全篇_第29张图片

    那字符指针能不能指向一个字符串常量呢?其实是可以的,但并不是将整个字符串存储在字符指针里,而是将字符串首字符的地址存储在字符指针里。那我们通过下面的一段代码来学习一下。

C语言的指针内容全篇_第30张图片

    字符指针指向字符串常量其实和字符数组差不多,数组名代表首元素的地址,也有和字符指针一样的功能,区别就在于字符数组中存放的是一个个字符。还有一个更一个更重要的区别,就是字符指针指向的是字符串常量,字符串的内容是不可以改的,而字符数组里面的内容是可以改的。

C语言的指针内容全篇_第31张图片

C语言的指针内容全篇_第32张图片

笔试题

    大家可以看一下下面的这一段代码,想一想它的输出结果会是什么呢?

C语言的指针内容全篇_第33张图片

    运行这段代码,我们就可以发现这段代码的输出结果为str1 and str2 are not same和str3 and str4 are same。那为什么会出现这样的结果呢?输出结果不应该是str1 and str2 are same和str3 and str4 are same吗?不着急!看完下图的解释,你就会明白为什么会这样了。

 C语言的指针内容全篇_第34张图片

2.指针数组

    指针数组本质上是一个数组,数组中存放的是指针(地址)。

#include 
int main()
{
    //指针数组
    int* arr[3];//存放整型指针的数组,数组元素有三个
    return 0;
}

    利用指针数组模拟实现二维数组。 

C语言的指针内容全篇_第35张图片

 3.数组指针

    数组指针是C语言中非常重要的概念。那数组指针是指针还是数组呢?很明显,数组指针是指针。比如:整型指针是指向整型的指针,字符指针是指向字符的指针。那么数组指针就是指向数组的指针。

C语言的指针内容全篇_第36张图片

     上面的代码就是数组指针的定义了。那我们怎样才能理解数组指针呢?首先parr是变量名,parr首先跟*结合(注:[]的优先级比*的优先级高,所以要用括号将*和parr括起来),说明parr是指针变量,再跟[]结合,说明parr指向的是数组,数组有5个元素,每个元素是int。现在有个存放着5个double型指针的数组d,如果我想取出d的地址,那我该如何定义一个指针变量来存放d的地址呢。  很明显,存放d的地址的指针变量应该这样定义:double*  (*pd)[5] = &d !!!那我再来给大家分析一下这个指针变量。首先pd先和*结合,说明pd是一个指针变量;再与[]结合,说明pd指向的是数组;数组有5个元素,每个元素是double*。

4.&数组名vs数组名

    我们都已经知道,数组名表示的是数组首元素的地址,那&数组名又表示什么呢?&数组名和数组名的区别在哪里?现在我们通过下面的代码来学习一下。

C语言的指针内容全篇_第37张图片

C语言的指针内容全篇_第38张图片

       注意:数组名通常表示数组首元素的地址,但是有两个例外。第一个是sizeof(数组名),数组名表示的是整个数组,计算的是整个数组的大小,单位是字节;第二个时&数组名,数组名表示整个数组,取出的是整个数组的地址。除此之外,数组名通通表示数组首元素的地址。

数组指针的使用

    数组指针通常用于二维数组,并不常用于一位数组(因为写法太麻烦了)。

    用数组指针遍历一维数组(麻烦,不建议使用)。

C语言的指针内容全篇_第39张图片

     用数组指针遍历二维数组。

C语言的指针内容全篇_第40张图片

    学完了指针数组和数组指针,我们来一起回顾并看看下面代码的意思。

int arr[5];   整型数组,数组5个元素,每个元素是int
int *parr1[10];  整型指针的数组,数组10个元素,每个元素是int*
int (*parr2)[10];  整型数组的指针,该指针指向一个数组,数组10个元素,每个元素是int
int (*parr3[10])[5]; parr3是一个存放数组指针的数组,该数组能够存放10个数组指针,每个                                 数组指针指向一个数组,数组5个元素,每个元素是int

注意:分析这些代码的意思有两个小技巧:第一,[]的优先级比*的优先级要高,所以变量名优先和[]结合;第二,将数组名和[]去掉,剩下的就是数组元素的类型。比如:将int (*parr3[10])[5]中的parr3[10]去掉得到的就是int (*)[5]--数组指针。

5.数组参数、指针参数

    在写代码的时候,我们难免要把数组和指针传给函数,那么函数的参数应该如何去设计呢?接下来,我们就来学习一下数组参数和指针参数。

一维数组传参

C语言的指针内容全篇_第41张图片

二维数组传参 

C语言的指针内容全篇_第42张图片

    注意:二维数组传参,函数形参的设计只能省略第一个[ ]的数字。因为对于一个二维数组,可以不知道有多少行,但是必须知道有多少列。还有就是,二维数组名作为实参传过去的是数组指针,函数形参的设计不能是一级指针或者二级指针,形参和实参必须是对应的。

一级指针传参

    一级指针传参相对来说比较简单,实参一级指针传过去,函数形参一级指针接收就好了。不过要注意的是,形参和实参的指针类型一定要对应起来。

C语言的指针内容全篇_第43张图片

二级指针传参

    C语言的指针内容全篇_第44张图片

     函数的形参设计为二级指针,那实参也必须传二级指针过来。二级指针有哪些呢?如下图所示:

C语言的指针内容全篇_第45张图片

6.函数指针

    从名字上来看,我们很容易就知道函数指针是指向函数的指针,是存放函数地址的指针。先通过一个简单的程序来了解一下函数地址。

C语言的指针内容全篇_第46张图片

    注意:函数名和&函数名的意义是一样的!!!

    如果我们想要存储一个函数的地址,那我们怎么去定义一个函数指针去存放这个地址呢?定义一个函数指针变量需要注意那些问题呢?看下方的代码和解释,它会告诉你答案。

C语言的指针内容全篇_第47张图片

    现在我们已经知道如何去定义一个函数指针变量了,那我们怎么通过这个函数指针变量去调用这个函数呢?同样,通过下方代码告诉你答案。

C语言的指针内容全篇_第48张图片

    因为&函数名和函数名是等价的,所以上面的函数指针的定义也可以写成这样int (*pf) (int, int) =Add。那么也说明pf等价于Add。所以上面的代码就可以改写为:

C语言的指针内容全篇_第49张图片

     对面这两段代码,也可以说明(*pf)和pf是完全等价的。其实这样说明pf前面的*是没有任何意义的,不管pf前面的*有多少的都是一样的。 前面的*只是为了我们能够更好地理解。

C语言的指针内容全篇_第50张图片

代码分析(这个比较困难,看不懂的话可以多看几遍)

 C语言的指针内容全篇_第51张图片

7.函数指针数组

    之前我们就学习过整型指针数组。整型指针数组是存放整型指针的数组,比如int* arr[5]。那函数指针数组就是存放函数指针的数组。那函数指针数组该怎么定义呢?我们现在来学习一下。

C语言的指针内容全篇_第52张图片 函数指针实现简单的计算器功能 

      无函数指针版本

#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("****** 1.Add    2.Sub ******\n");
	printf("****** 3.Mul    4.Div ******\n");
	printf("********  0. exit  *********\n");
	printf("****************************\n");
	
}
int main()
{
	int input = 0;
	//计算器-计算整型变量的加、减、乘、除
	do {
		menu();
		int x = 0, y = 0, ret;
		printf("请选择:>\n");
		scanf("%d", &input);

		switch (input)
		{
		case 1:
			printf("请输入两个操作数:>\n");
			scanf("%d%d", &x, &y);
			ret=Add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:>\n");
			scanf("%d%d", &x, &y);
			ret = Sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:>\n");
			scanf("%d%d", &x, &y);
			ret = Mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:>\n");
			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 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\n");
	
}
int main()
{
	int input = 0;
	//计算器-计算整型变量的加、减、乘、除
	do {
		menu();
		int (*pfArr[5])(int, int) = { NULL,Add,Sub,Mul,Div };//转移表
		//pfArr弄成五个元素是为了更好地对应起来
		int x = 0, y = 0, ret = 0;
		printf("请选择:>\n");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>\n");
			scanf("%d%d", &x, &y);
			ret = (pfArr[input])(x, y);
			printf("ret = %d\n\n", ret);
		}
		else if (input == 0)
		{
			printf("退出程序\n");
		}
		else
		{
			printf("选择错误,请重新选择:>\n");
		}

	} while (input);
	return 0;
}

    代码运行结果 

    C语言的指针内容全篇_第53张图片

        我们可以看到,用函数指针数组来实现简单的计算器功能,代码量将会减少且不会显得那么冗余。不过需要注意的是,使用函数指针数组时,要确保数组中的函数返回类型和参数都要相同。不然就不能使用函数指针数组了。

8.指向函数指针数组的指针

    在前面的内容里,我们已经学习到了数组指针。比如整型数组指针int (*p)[5]。那这个指向函数指针数组的指针又是怎么一回事呢?我们来学习一下。

C语言的指针内容全篇_第54张图片

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

//去掉数组名和数组元素个数,剩下的就是数组元素类型

     指向函数指针数组的指针这个内容只需了解即可,不要求熟练掌握。

9.回调函数

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

C语言的指针内容全篇_第55张图片

    回调函数实现简单的计算器功能

#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("****** 1.Add    2.Sub ******\n");
	printf("****** 3.Mul    4.Div ******\n");
	printf("********  0. exit  *********\n");
	printf("****************************\n\n");

}
int Calc(int (*pf)(int, int))
{
	int x = 0;
	int y = 0;
	printf("请输入两个操作数:>\n");
	scanf("%d%d", &x, &y);
	return pf(x, y);	
}
int main()
{
	int input = 0;
	//回调函数实现简单的计算器功能
	do {
		menu();
		int ret = 0;
		printf("请选择:>\n");
		scanf("%d", &input);
		switch (input)
		{
		   case 1:
			   ret = Calc(Add);
			   printf("ret = %d\n\n", ret);
			   break;
		   case 2:
			   ret = Calc(Sub);
			   printf("ret = %d\n\n", ret);
			   break;
		   case 3:
			   ret = Calc(Mul);
			   printf("ret = %d\n\n", ret);
			   break;
		   case 4:
			   ret = Calc(Div);
			   printf("ret = %d\n\n", ret);
			   break;
		   case 0:
			   printf("退出程序\n");
			   break;
		   default:
			   printf("选择错误,请重新选择:>\n\n");
		}
	} while (input);
	return 0;
}

    很明显,函数Add、Sub、Mul和Div就是回调函数,也就是A函数,而函数Calc就是B函数。通过回调函数,我们也能实现简单的计算机功能。

演示qsort函数的使用

    qsort函数是快速排序函数,它是一个库函数。学习qsort函数的使用前,我们先回顾一下冒泡排序。

C语言的指针内容全篇_第56张图片

//冒泡排序
#include 
void bubble_sort(int arr[],int sz)
{
	int i = 0;
	int j = 0;
	//冒泡排序的趟数
	for (i = 0; i < sz - 1; i++)
	{
		//一趟冒泡排序
		for (j = 0; j < sz - 1 - i; j++)
		{
			int temp = 0;
			if (arr[j] > arr[j + 1])
			{
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}
void print_arr(int arr[], int sz)
{
	int i;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main()
{
	//升序
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("排序前\n");
	print_arr(arr, sz);
	bubble_sort(arr, sz);
	printf("排序后\n");
	print_arr(arr, sz);
	return 0;
}

    那冒泡排序跟我们要学的qsort函数有什么关系吗?其实qsort函数就能帮助我们快速实现升序排序,而且还能对字符串数组和结构体数据进行排序。但是在这里,我们只需要知道怎么使用qsort函数就行了,并不需要它是怎么来的。

前置知识
    介绍qsort函数前,需要先解释一个东西 - void* 。平常,我们定义一个 XX 类型的变量,就必须使用 XX 类型的指针来指向变量的地址。而void* 类型,就可以很好地接收各种类型变量的地址并存储起来。不恰当的比喻:void* 就像一个 “垃圾桶” ,什么都可以装。

    注意事项:1.void*类型 —— ⽆类型,不能进⾏解引⽤操作。因为不能确定访问⼏个字节
(指针的类型确定访问的字节⼤⼩)  2.void*类型,不能进⾏+、-整数的操作,因为不能确定⼀步⾛⼏个字节(指针的类型确定步⻓)只有将void*类型的指针强制类型转换才能进行解引用操作和+-整数。

C语言的指针内容全篇_第57张图片

 C语言的指针内容全篇_第58张图片

    使用qsort函数排序整型数组(升序)

#include 
#include 
//使用qsort函数需要包含头文件stdlib.h
void print_arr(int arr[], int sz)
{
	int i;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
    //因为待排序的数据是整型,所以将e1和e2强制类型转换为整型指针
    //只有这样才能进行解引用操作和+-整数
}
int main()
{
	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);
	//打印
	print_arr(arr, sz);
	return 0;
}

C语言的指针内容全篇_第59张图片

    使用qsort函数排序结构体数据(年龄)

#include 
#include 
struct Stu
{
	char name[20];
	int age;
};
//定义结构体
int cmp_s_age(const void* e1, const void* e2)
{
	return (((struct Stu*)e1)->age - ((struct Stu*)e2)->age);
}
//将e1和e2强制类型转换为struct Stu*
int main()
{
	//使用qsort函数排序结构体数据
	struct Stu s[3] = { {"zhangsan",18},{"lisi",30},{"wangwu",20} };
	int sz = sizeof(s) / sizeof(s[0]);
	//按照年龄来排序
	qsort(s, sz, sizeof(s[0]), cmp_s_age);
	for (int i = 0; i < sz; i++)
	{
		printf("%s:%d\n", s[i].name, s[i].age);
        //打印数据
	}
	return 0;
}

C语言的指针内容全篇_第60张图片

    使用qsort函数排序结构体数据(名字)

#include 
#include 
#include 

struct Stu
{
	char name[20];
	int age;
};

int cmp_s_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
    //strcmp函数可以比较字符串的大小,使用它需要引头文件string.h
}

int main()
{
    //使用qsort函数排序结构体数据
	struct Stu s[3] = { {"zhangsan",18},{"lisi",30},{"wangwu",20} };
	int sz = sizeof(s) / sizeof(s[0]);
	//按照名字来排序
	qsort(s, sz, sizeof(s[0]), cmp_s_name);
	for (int i = 0; i < sz; i++)
	{
		printf("%s:%d\n", s[i].name, s[i].age);
	}
	return 0;
}

C语言的指针内容全篇_第61张图片     注意:如果想要使用qsort函数实现降序排序的话,可以在return后面多加一个负号也可以将e1和e2的位置对调 

    其实,qsort函就也是一个回调函数的应用。

10.自定义bubble_qsort函数

#include 
#include 
#include 
struct Stu
{
	char name[20];
	int age;
};

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

int cmp_by_age(const void* e1, const void* e2)
{
	return (((struct Stu*)e1)->age - ((struct Stu*)e2)->age);
}

int cmp_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

void Swap(char* buf1, char* buf2, int width)
{
	//因为数据在内存是一个字节一个字节存储的,所以我们将两个元素的全部字节
	//交换了,这两个元素就完成交换了
	int i = 0;
	for (i = 0; i < width; i++) //width是一个元素的大小,单位是字节
	{
		//交换一个字节
		char temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		//字符指针自加,准备交换下一个字节
		buf1++;
		buf2++;
	}
}
//模仿qsort函数实现一个冒泡排序的通用算法
void bubble_qsort(void* base, int sz, int width, int (*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	//趟数
	for (i = 0; i < sz; i++)
	{
		//一趟的排序
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//两个元素比较
			//类比arr[j]和arr[j+1]
			if (cmp((char*)base+j*width,(char*)base+(j+1)*width) > 0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width,width);
			}
		}
	}

}
void test1()
{
	//整型数据的排序
	int arr[10] = { 1,3,5,7,9,0,2,4,6,8 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//排序
	bubble_qsort(arr, sz, sizeof(arr[0]), cmp_int);
	//打印
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
void test2()
{
	//使用qsort函数排序结构体数据
	struct Stu s[3] = { {"zhangsan",18},{"lisi",30},{"wangwu",20} };
	int sz = sizeof(s) / sizeof(s[0]);
	struct Stu* p = s;//p为结构体指针
	//按照年龄来排序
	//bubble_qsort(s, sz, sizeof(s[0]), cmp_by_age);
	//按照名字来排序
	bubble_qsort(s, sz, sizeof(s[0]), cmp_by_name);
	for (int i = 0; i < sz; i++)
	{
		//结构体数据的两种打印方法
		//printf("%s:%d\n", s[i].name, s[i].age);
		printf("%s:%d\n", (p+i)->name, (p+i)->age);
	}
}
 
int main()
{
	//test1();
	test2();

}

    分析 (这样可以看一下,可以帮助你理解上面的代码)

C语言的指针内容全篇_第62张图片

    输出结果 

C语言的指针内容全篇_第63张图片

C语言的指针内容全篇_第64张图片

C语言的指针内容全篇_第65张图片

11.指针和数组笔试题解析

    为了答案的统一,下面的这些代码都是在32位平台下运行的。因为在不同的平台下运行,指针的大小会出现结果不统一。但如果32位平台的结果和64位平台的结果不一样的话,我会将两个结果都写出来。

    在前面,我们已经学过数组名的意义:1.sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小;2.&数组名,数组名表示整个数组,取出的是整个数组的大小;3.除此之外,所有的数组名都表示数组首元素的地址。这个知识将会帮助我们分析下面的代码。

C语言的指针内容全篇_第66张图片

C语言的指针内容全篇_第67张图片C语言的指针内容全篇_第68张图片

C语言的指针内容全篇_第69张图片

C语言的指针内容全篇_第70张图片

C语言的指针内容全篇_第71张图片

C语言的指针内容全篇_第72张图片

C语言的指针内容全篇_第73张图片C语言的指针内容全篇_第74张图片

    总结:数组名的意义                                                                                                                                  1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。                                    2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址。                                              3.除此之外所以的数组名都表示数组首元素的地址

12.指针笔试题

笔试题1

#include 
int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);//将数组指针强制类型转换为整型指针
	printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;
}

C语言的指针内容全篇_第75张图片

笔试题2

    注意:在32位平台下,结构体的大小为20个字节;在64位平台下,结构体的大小为32个字节。该程序是在32位平台下运行的。

#include 
//由于还没学习结构体,这里告知结构体的大小是20个字节
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节
int main()
{
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

C语言的指针内容全篇_第76张图片笔试题3

#include 
int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	int* ptr2 = (int*)((int)a + 1);
	printf("%x,%x", ptr1[-1], *ptr2);
	return 0;
}

C语言的指针内容全篇_第77张图片 笔试题4 

#include 
int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]); 
	return 0;
}

C语言的指针内容全篇_第78张图片

笔试题5

#include 
int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	return 0;
}

C语言的指针内容全篇_第79张图片

笔试题6

#include 
int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int* ptr1 = (int*)(&aa + 1);
	int* ptr2 = (int*)(*(aa + 1));
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
	return 0;
}

C语言的指针内容全篇_第80张图片 

笔试题7

#include 
int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
}

C语言的指针内容全篇_第81张图片

笔试题8

#include 
int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
	printf("%s\n", **++cpp);
	printf("%s\n", *-- * ++cpp + 3);
	printf("%s\n", *cpp[-2] + 3);
	printf("%s\n", cpp[-1][-1] + 1);
	return 0;
}

C语言的指针内容全篇_第82张图片

C语言的指针内容全篇_第83张图片

    以上就是C语言指针的全部内容了。如果你能够弄懂上面的知识的话,相信你对C语言的指针已经掌握了差不多了。这篇内容断断续续写了6天,终于写完了,真的没想到自己居然写完了。真的很不容易,希望大家可以点个赞支持一下,谢谢大家了!!!

你可能感兴趣的:(c语言,学习)