指针详解与应用

目录

指针定义和初始化

 指针的大小和意义

指针的大小

指针的意义

野指针

野指针的定义

野指针的成因

避免野指针的方法

指针运算

指针+-整数和指针之间比较

指针-指针

练习:用指针模拟strlen()函数

指针和数组

 指针和数组名,数组之间的关系

二级指针 

字符串与指针

指针数组

数组指针

数组指针定义及初始化

 数组指针的使用

一维数组的数组指针

 二维数组的数组指针

数组指针作为函数参数

函数指针

函数指针的定义和初始化

函数指针数组

指向函数指针数组的指针

回调函数

示例 

 qsort函数

qsort函数的使用

模拟实现qsort函数 

练习

练习1-数组大小的分析

练习2-指针与数组

练习3-结构体与指针

练习4-指针与数据存储

练习5-左旋函数

 练习6-杨氏矩阵

 练习7-模拟实现strcpy函数

练习8-思维训练

注意

数组指针与函数命名 

 const的修饰作用


指针定义和初始化

指针,可以接收元素的地址,可以通过改变指针来改变变量的值

int* pa = &a;
↑a的类型
   ↑说明pa是指针

int main()
{
    int a = 10;//a占4个字节
    int* pa = &a;//拿到的是a的的4个字节中第一个字节的地址
    *pa = 20;//根据地址找到a,从而改变a的值
    return 0;
}

 指针的大小和意义

指针的大小

int main()
{
	int* pa;
	char* pc;
	float* pf;
	printf("%d\n", sizeof(pa));
	printf("%d\n", sizeof(pc));
	printf("%d\n", sizeof(pf));
	return 0;
}
运行结果:
8
8
8

指针的大小在32位平台是四个字节,在64位平台是8个字节

指针的意义

1.指针类型决定了指针解引用的权限有多大:整型4个,字符型1个,double型8个
2.指针类型决定了指针走一步能走多远(步长)

int main()
{
	int a = 0x11223344;
	//一个16进制位占4个二进制位,两个16进制位占8个二进制位,也就是一个字节,所以一个16进制数占4个字节
	int* pa = &a;
	*pa = 0;//通过调试查看内存,可以发现  44332211-->00000000
	int c = 0x11223344;
	char* pc = &c;
	*pc = 0;//44332211-->00332211
	return 0;
}
int main()
{
	int arr[10] = { 0 };
	int* p = arr;
	char* pc = arr;//在调试时,将光标放到这两个语句上,可以发现显示的地址是一样的,所以无论用什么指针类型命名指针,都能够存放地址
	printf("%p\n", p);
	printf("%p\n", p + 1);
	printf("%p\n", pc);
	printf("%p\n", pc + 1);
	return 0;
}
运行结果:
00000008F9FDF818
00000008F9FDF81C//相差4个字节
00000008F9FDF818
00000008F9FDF819//相差1个字节

 从上面的结果我们就可以看出来,二者虽然地址是一样的,但是它们的类型却是不一样的,前者表示的整型指针,进行+1操作之后可以移动一个整型的大小,即4个字节;而后者是一个字符指针,进行+1操作之后只能移动一个字符的大小,即1个字节

int main()
{
	int arr[10] = { 1,1,1,1,1,1,1,1,1,1 };
	int* p = arr;
	char* pc = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	for (i = 0; i < 10; i++)
	{
		*(p + i) = 0;//p+i为下标为i的地址
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	for (i = 0; i < 10; i++)
	{
		*(pc + i) = 1;
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}
运行结果:
1 1 1 1 1 1 1 1 1 1
0 0 0 0 0 0 0 0 0 0
16843009 16843009 257 0 0 0 0 0 0 0

 前两行的结果很容易理解,可是第三行是怎么产生的呢?

由于pc是一个字符指针,它在改变变量时,只能一个字节一个字节的改,比如:

数组第一个元素是0x00000000,第一次第一个字节改为1,00000001,以此类推,第一个元素最终变成0x01010101,第二个0x01010101,第三个0x00000101,所以会出现上面这种情况

野指针

野指针的定义

指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)

野指针的成因

指针未初始化

int* p1;//p是一个局部指针变量,局部变量不初始化的话,默认是随机值
*p1 = 20;//访问了未知的地址,即非法访问内存

指针越界访问

int arr[10] = { 0 };
int* p2 = arr;
int i = 0;
for (i = 0; i <= 10; i++)
 {
    //当指针指向的范围超出数组arr的范围时,p就是野指针
    *p2 = i;
    p2++;
 }

指针指向的空间释放

int* test()
{
    int a = 10;
    return &a;
}
int main()
{
    int* p = test();
    //完成这个函数后,变量a就被释放了,所以*p就变成了野指针
    *p = 20;
    return 0;
}

避免野指针的方法

 1.指针初始化
 2.小心指针越界
 3.指针指向空间释放即设置NULL,即当指针不知道初始化为什么地址时,则初始化为空指针,当指针指向空间释放时,也可以再将指针写为空指针, int* p = NULL; 加入一个指针被初始化为空指针,则我们是不可以通过访问空指针来改变那个地址的内容的,可以使用 if(p!=NULL)
 4.指针使用之前检查有效性

指针运算

指针+-整数和指针之间比较

int main()
{
	float arr[5];
	float* p;
	for (p = &arr[0]; p < &arr[5];)//&arr[5]只是取出了地址,并没有访问和改变值,所以不算数组越界
	{
		*p++ = 0;//先执性赋值操作,之后执行加一操作,这就是指针和整数之间的运算
	}
	for (int i = 0; i < 5; i++)
	{
		printf("%.f ", arr[i]);
	}
	return 0;
}
运行结果:
0 0 0 0 0

标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的哪个位置的指针比较,但不允许与指向第一个元素之前的那个内存位置的指针进行比较,指针之间大小的比较,指针的大小在创建时是依次升高的 

指针-指针

int main()
{
	int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
	printf("%d\n", &arr[9] - &arr[0]);
	printf("%d\n", &arr[0] - &arr[9]);
	printf("%d\n", &arr[9] - &arr[6]);
	return 0;
}
运行结果:
9
-9
3

由上面的运行结果我们不难发现,指针-指针得到的是两个指针之间的元素个数,但是这样的运算是有前提的: 两个指针指向同一块空间,并且还要知道,两个指针相加是没有意义的

练习:用指针模拟strlen()函数

int slen(char* str)
{
	char* start = str;
	char* end = str;
	while (*str != '\0')
	{
		str++;
		end = str;
	}
	return end - start;
}
int main()
{
	char a[] = "abc";
	printf("%d\n", slen(a));
	return 0;
}
运行结果:
3

指针和数组

 指针和数组名,数组之间的关系

int main()
{
	int arr[10] = { 0 };
	int* p = arr;//数组名是数组首元素的地址
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
		//arr[2] <==> *(arr+2) <==> *(p+2) <==> *(2+p) <==> *(2+arr) <==> 2[arr]
	}
	for (i = 0; i < 10; i++)
	{
		printf("%d ", i[arr]);
		//[]是一个操作符,arr和2是两个操作数,arr[2]--->*(arr+2),所以可以换过来写
	}
	printf("\n");
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}
	printf("\n");
	for (i = 0; i < 10; i++)
	{
		printf("%d ", i[p]);
	}
	return 0;
}

二级指针 

int main()
{
	int a = 10;
	int* pa = &a;//pa是指针变量,一级指针
	int* * ppa = &pa;//pa也是变量,&pa取出pa在内存中起始地址
	//↑ pa的类型是int*
	//   ↑说明ppa是指针
	//ppa就是一个二级指针变量  *ppa=pa,*pa=a--->**ppa=a
	int*** pppa = &ppa;
	**ppa = 20;
	printf("%d\n", a);
	return 0;
}
运行结果:
20

字符串与指针

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

辨析1:在对p初始化的时候,并非是把这个字符串全部都放到p里面去,因为1个字节里面是放不下7个字节的(没有写错,注意'\0'),所以在这里是把首字符的地址赋给了p

辨析2:*p = 'o';  的语句是错误的,因为上面按个字符串为常量字符串,其中的值是不可以更改的,可以在对*p赋值前加const,这样就会在程序生成时直接报错

int  main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	char* p1 = "abcdef";
	char* p2 = "abcdef";
	if (arr1 == arr2)
	{
		printf("1\n");
	}
	else
	{
		printf("0\n");
	}
	if (p1 == p2)
	{
		printf("1\n");
	}
	else
	{
		printf("0\n");
	}
	printf("%p\n", arr1);
	printf("%p\n", arr2);
	printf("%p\n", p1);
	printf("%p\n", p2);//这样输出的是p1和p2所指向的对象的地址
	printf("%p\n", &p1);
	printf("%p\n", &p2);//这样输出的才是p1或p2指针本身的地址
}
运行结果:
0
1
00DEFB18
00DEFB08
006D7B7C
006D7B7C
00DEFAFC
00DEFAF0

 ①0是怎么来的?arr1和arr2创建空间不一样,地址是不一样的,所以二者不一样

②1是怎么来的?p1和p2是两个空间,但是二者都指向了同一个空间的起始位置

指针数组

 指针数组,顾名思义,是用来存放指针的数组,跟整型数组,字符数组的定义和初始化上没有太大区别

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int d = 40;
	int* parr[4] = { &a,&b,&c,&d };
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d ", *parr[i]);
	}
	return 0;
}
运行结果:
10 20 30 40
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 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *parr[i] + j);
		}
		printf("\n");
	}
	return 0;
}
运行结果:
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7

数组指针

数组指针定义及初始化

数组指针的重点是指针,所以数组指针就是用来接收数组地址的指针

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;
	return 0;
}

上面就是数组指针的定义及初始化, (*p)前面是数组类型,后面是数组大小

为什么不直接写成int* p[10]?因为p和[]的结合度更大,所以这个本身还是个指针数组而不是一个数组指针,不能用来存放地址

int main()
{
    char* arr[5];
    char*(*pa)[5] = &arr;
             ↑说明pa是指针
               ↑指针变量的名字
                     ↑pa指向的数组是5个元素的
     ↑pa指向的数组的元素类型是char*
    return 0;
}

 数组指针的使用

一维数组的数组指针

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*pa)[10] = &arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(*pa + i));//*pa==arr,*pa+i==从arr的首地址跳几个,再解引用就可以了
	}
	printf("\n");
	for (i = 0; i < 10; i++)
	{
		printf("%d ", (*pa)[i]);
	}
	printf("\n");
	int* p = arr;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p+i));
	}
	return 0;
}
运行结果:
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 9 10
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;//----------------------------------↓  不按照正确命名规则,就算后面是&arr也指向的是首元素地址而不是数组地址
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);//arr[i] == *(arr+i) == *(p+i) == p[i]
	}
	return 0;
}
运行结果:
1 2 3 4 5 6 7 8 9 10

 二维数组的数组指针

编写一个print()函数,打印二维数组中的值

首先思考,怎样传递二维数组的地址?arr-数组名-数组名就是首元素地址,把arr想象成一维数组,第一个一维数组的地址就是数组arr的首地址,所以可以传值进去一维数组指针
 

int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	print(arr, 3, 5);
	return 0;
}

 二维数组作为函数参数

void print1(int arr[][5], int a, int b)
{
	int i = 0;
	for (i = 0; i < a; i++)
	{
		int j = 0;
		for (j = 0; j < b; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}

 将指针数组作为函数的参数

void print2(int(*p)[5], int a, int b)
{
	int i = 0;
	for (i = 0; i < a; i++)
	{
		int j = 0;
		for (j = 0; j < b; j++)
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}
void print3(int(*p)[5], int a, int b)
{
	int i = 0;
	for (i = 0; i < a; i++)
	{
		int j = 0;
		for (j = 0; j < b; j++)
		{
			printf("%d ", p[i][j]);//*(p + i)--->p[i]   *(p[i] + j)--->p[i][j]
		}
		printf("\n");
	}
}

辨析 

int arr[5];//arr是一个含有5个元素的整型数组
 int* parr1[5];//parr1是一个数组,有5个元素,元素类型是int*,是一个指针数组
 int(*parr2)[5];//parr2是一个指针,该指针指向了一个数组,数组有5个元素,每个元素是int型,parr2是数组指针
 int(*parr3[10])[5];//parr3是一个数组,该数组有10个元素,每个元素是一个数组指针,该数组指针指向的数组有5个元素,每个元素是int

数组指针作为函数参数

数组参数,指针参数
传入和接收一维数组int arr[10]:void test(int arr[]) == void test(int arr[10]) == void test(int* arr)
传入和接收数组指针int* arr[20]:void test(int* arr[]) == void test(int* arr[20]) == void test(int* *arr)
传入和接收二维数组int arr[3][5]: void test(int* arr)是错误的,因为这样传入的是二维数组的第一个元素的首地址,这个首地址的对象是一个数组,整型指针是无法存储数组的地址的
                                 void test(int* arr[5])是错误的,因为函数接收的是数组指针,可是传入的是数组
                                 void test(int** arr) 是错误的,因为传入的是数组,并不是指针
                                 void test(int(*arr)[5])正确的
如果函数接收的是一级指针,那么可以传入元素地址,或者指向元素地址的指针
如果函数接收的是二级指针,那么可以传入二级指针,或者指向一级指针的地址,或者一级指针数组的数组名(传入的首元素的地址,而首元素就是一个指针,解地址加指针类型(形参要声明类型,其中占了一个*,如int* *arr,就是两个**了)

函数指针

函数指针的定义和初始化

函数指针:指向函数的指针

int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int a = 10;
	int b = 20;
	printf("%d\n",Add(a, b));
	printf("%p\n", Add);
	printf("%p\n", &Add);//二者等同,&函数名和函数名都是函数的地址
	//储存函数地址
	int(*p)(int, int) = Add;
	printf("%d\n", (*p)(a,b));
	return 0;
}

 (*p)前面是函数的返回值类型,后面是函数参数类型

void print(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	void (*p)(char*) = print;
	p("hello world!\n");
	(*p)("hello world!\n");//&函数名==函数名
	(**p)("hello world!\n");
	(***p)("hello world!\n");//多次解引用无法解引用的对象是无效的
}
运行结果
hello world!

hello world!

hello world!

hello world!

十分简单的代码欣赏

(*(void (*)())0)();
   |←      →|这一部分是一个指向函数的指针,为函数指针类型
  |←         →|将一个类型放在括号里面,是强制类型转换,把整型0强制类型转换成为函数指针类型,0就是这个函数的地址
↑解引用,将函数指针解引用为函数,调用该函数    
void (* signal(int, void(*)(int)))(int);
        ↑函数名   ↑函数的两个参数
                       ↑第二个参数是函数指针类型,指向的函数的参数是整型,返回值类型为void
      ↑signal是一个函数声明,函数的返回类型是函数指针,该函数指针指向的函数参数类型是int,返回类型是void
这一段本质是函数声明

上面的第二个代码太过于复杂,如何简化?这里我们就需要使用typedef

typedef unsigned int uint;//此时,unsigned int就可以用uint来代替
typedef void(*pfun_t )(int);//此时,void(*)(int)就可以用pfun_t来代替
//于是,对第二个代码进行简化
pfun_t signal(int, pfun_t );
//↑返回值类型
//       ↑变量名    ↑参数类型        

函数指针数组

函数指针数组,本质上是一个数组,其中所储存的元素是函数指针类型

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;
}
int main()
{
	//需要一个数组,可以存放四个相同类型函数的地址
	int(*parr[4])(int, int) = { add,sub,mul,div };
//  ↑函数的返回值类型   ↑函数参数类型
//      ↑函数指针类型的数组
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d\n",parr[i](2, 3));//不加*
	}
	return 0;
}

练习:计算器

void menu()
{
	printf("*******************************\n");
	printf("************1.add**************\n");
	printf("************2.sub**************\n");
	printf("************3.mul**************\n");
	printf("************4.div**************\n");
	printf("************0.exit*************\n");
	printf("*******************************\n");
}
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;
}
#include<stdlib.h>
int Div(int a, int b)
{
	if (b == 0)
	{
		printf("分母不能为0!");
		exit(0);
	}
	else
	{
		return a / b;
	}
}
int main()
{
	int ret = 0;
	int a = 0, b = 0;
	//pfarr是一个函数指针数组-转移表
	int (*pfarr[5])(int, int) = { 0,add,sub,mul,Div };
	do
	{
		menu();
		printf("请选择操作:");
		scanf("%d", &ret);
		if (ret >= 1 && ret <= 4)
		{
			printf("请输入两个整形操作数:");
			scanf("%d %d", &a, &b);
			int ret1 = pfarr[ret](a, b);
			printf("%d\n", ret1);
		}
		else if (ret == 0)
		{
			printf("退出");
		}
		else
		{
			printf("输入错误\n");
		}
	} while (ret);
	return 0;
}

指向函数指针数组的指针

int main()
{
	int arr[10] = { 0 };
	int(*p)[10] = &arr;//取出数组的地址

	int(*pfarr[4])(int, int);//pfarr是一个数组-函数指针的数组
	int(*(*ppfarr)[4])(int, int) = &pfarr;
	//ppfarr是一个指针,指针指向的数组有4个元素
	//指向的数组的每个元素的类型是一个函数指针 int(*)(int,int) 
	return 0;
}

回调函数

 回调函数,就是通过函数指针来调用函数,把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,这就是回调函数

示例 

void menu()
{
	printf("*******************************\n");
	printf("************1.add**************\n");
	printf("************2.sub**************\n");
	printf("************3.mul**************\n");
	printf("************4.div**************\n");
	printf("************0.exit*************\n");
	printf("*******************************\n");
}
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;
}
#include<stdlib.h>
int Div(int a, int b)
{
	if (b == 0)
	{
		printf("分母不能为0!");
		exit(0);
	}
	else
	{
		return a / b;
	}
}
void calc(int(*pf)(int, int))
{
	int a = 0, b = 0;
	printf("请输入两个整型操作数:");
	scanf("%d %d", &a, &b);
	printf("%d\n", pf(a, b));
}
int main()
{
	int ret = 0;
	do
	{
		menu();
		printf("请输入序号:");
		ret = 0;
		scanf("%d", &ret);
		switch (ret)
		{
		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;
		defult:
			printf("选择错误\n");
		}
	} while (ret);
	return 0;
}

 qsort函数

之前,已经学习了冒泡排序,但是会发现当我们想要对其他类型的元素进行排序时,我们就又要重新修改冒泡排序,那有什么方法呢能够解决这个问题从而使我们可以排序我们想要排序的任意一种类型的元素呢?qsort函数就可以完成。

qsort函数的使用

qsort-库函数-排序-<stdlib.h>
qsort(参数1:排序对象的起始位置,参数2:元素个数,参数3:一个元素所占字节数,参数4:比较方式(写成函数)(因为数组和结构体等比较的方式是不一样的)
比较函数的返回值具有严格要求,只能返回>0,<0或=0的数

 着重讲一下qsort函数中的参数4,因为qsort函数是不知道我们所排序元素的类型已经方式的,所以怎么排还是要我们自己写的,但是这冰不是要求我们再写一个冒泡排序,而是需要我们写一个两个元素之间是顺序还是倒序

int cmp_int(const void* e1, const void* e2)//void*无类型指针,可以接收任意类型的地址,不能进行解引用操作和指针加减整数操作,因为不知道访问几个字节,
{
	//比较两个整型值
	return *(int*)e1 - *(int*)e2;
}
//注意浮点型比较的严谨性,由于返回值是int,所以可能会由于精度损失出现错误
int cmp_float(const void* e1, const void* e2)
{
	if (*(float*)e1 == *(float*)e2)
	{
		return 0;
	}
	else if (*(float*)e1 > *(float*)e2)
	{
		return 1;
	}
	else
	{
		return -1;
	}
}
int cmp_struct_by_age(const void* e1, const void* e2)
{
	return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
#include<string.h>
int cmp_struct_by_name(const void* e1, const void* e2)
{
	/*比较名字就是比较字符串,字符串比较不能直接用大于等于小于比较,而需要用strcmp比较*/
	return strcmp(((struct stu*)e1)->name,((struct stu*)e2)->name);
}
#include<stdlib.h>
int main()
{
	//比较整型
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(arr[0]), cmp_int);
	int i = 0;
	int sz_int = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz_int; i++)
	{
		printf("%d ",arr[i]);
	}
	printf("\n");
	//比较浮点型
	float f[] = { 9.0,9.5,8.0,7.0,6.0,5.0,4.0 };
	qsort(f, sizeof(f) / sizeof(f[0]), sizeof(f[0]), cmp_float);
	int sz_float = sizeof(f) / sizeof(f[0]);
	for (i = 0; i < sz_float; i++)
	{
		printf("%f ", f[i]);
	}
	printf("\n");
	//比较结构体
	struct stu s[3] = { {"zhangshan",20},{"lisi",30},{"wangwu",10} };
	int sz_struct = sizeof(s) / sizeof(s[0]);
	qsort(s, sz_struct, sizeof(s[0]), cmp_struct_by_age);
	for (i = 0; i < sz_struct; i++)
	{
		printf("%s:%d ", s[i].name,s[i].age);
	}
	printf("\n");
	qsort(s, sz_struct, sizeof(s[0]), cmp_struct_by_name);
	for (i = 0; i < sz_struct; i++)
	{
		printf("%s:%d ", s[i].name, s[i].age);
	}
	return 0;
}

模拟实现qsort函数 

void swap(char* e1, char* e2 ,int width)//因为是字符指针,所以交换的时候只能一个字节一个字节的进行交换,所以必须要知道宽度,才能知道需要交换几对
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *e1;
		*e1 = *e2;
		*e2 = tmp;
		e1++;
		e2++;
	}
}
void Qsort(void* base,int sz,int width,int(*cmp)(const void* e1, const void* e2))
{
	int i = 0;//写成选择法排序可以使用地址,将地址赋给某个指针变量,之后进行交换
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = i + 1; j < sz; j++)
		{
			//两个元素的比较
			//if (cmp((char*)base+i*width, (char*)base + j * width) > 0)
			//{
			//	//交换
			//	swap((char*)base+i*width, (char*)base + j * width, width);
			//}
			//选择法排序
			void* tmp = &(char*)base + i * width;
			if (cmp((char*)base + i * width, (char*)base + j * width) > 0)
			{
				tmp = &(char*)base + j * width;
			}
			if (tmp != &(char*)base + i * width)
			{
				swap((char*)base + i * width, (char*)base + j * width, width);
			}
		}
	}
}
#include<string.h>
int cmp_struct_by_name(const void* e1, const void* e2)
{
	/*比较名字就是比较字符串,字符串比较不能直接用大于等于小于比较,而需要用strcmp比较*/
	return strcmp(((struct stu*)e1)->name,((struct stu*)e2)->name);
}
int main()
{
	struct stu s[3] = { {"zhangshan",20},{"lisi",30},{"wangwu",10} };
	int sz_struct = sizeof(s) / sizeof(s[0]);
	Qsort(s, sz_struct, sizeof(s[0]), cmp_struct_by_name);
	int i = 0;
	for (i = 0; i < sz_struct; i++)
	{
		printf("%s:%d ", s[i].name, s[i].age);
	}
	return 0;
}

练习

练习1-数组大小的分析

char arr[] = { 'a','b','c','d','e','f' };
printf("%d\n", sizeof(arr));//6,计算的是数组的大小
printf("%d\n", sizeof(arr + 0));//4,计算的是数组的地址,地址是4个字节的(64位的是4)
printf("%d\n", sizeof(*arr));//1,计算首元素大小
printf("%d\n", sizeof(arr[1]));//1
printf("%d\n", sizeof(&arr));//4,计算数组的地址,地址4个字节
printf("%d\n", sizeof(&arr + 1));//跳过这个数组,计算下一个数组的地址大小
printf("%d\n", sizeof(&arr[0] + 1));//计算第二个元素的地址大小

printf("%d\n", strlen(arr));//随机值
printf("%d\n", strlen(arr + 0));//随机值
//printf("%d\n", strlen(*arr));//把a传过去,进行ASCII码值转换,传过去的是97,访问这个地址是错误的
//printf("%d\n", strlen(arr[1]));//传98这个地址,也是错误的
printf("%d\n", strlen(&arr));//随机值
printf("%d\n", strlen(&arr + 1));//随机值-6
printf("%d\n", strlen(&arr[0] + 1));//随机值-1,从数组第二个元素后面开始计算
char* p = "abcdef";//只能把a的地址放进去
printf("%d\n", sizeof(p));//4
printf("%d\n", sizeof(p + 1));//4
printf("%d\n", sizeof(*p));//1
printf("%d\n", sizeof(p[0]));//1,p[0] == *(p + 0) == 'a'
printf("%d\n", sizeof(&p));//4
printf("%d\n", sizeof(&p + 1));//4
printf("%d\n", sizeof(&p[0] + 1));//4
printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p + 1));//5
//printf("%d\n", strlen(*p));//err
//printf("%d\n", strlen(p[0]));//err
printf("%d\n", strlen(&p));//随机值
printf("%d\n", strlen(&p + 1));//随机值
printf("%d\n", strlen(&p[0] + 1));//5
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//48
printf("%d\n", sizeof(a[0][0]));//4,第一个元素的大小
printf("%d\n", sizeof(a[0]));//16,arr[0]相当于第一行作为一维数组的数组名,sizeof(arr[0])把数组名单独放在()里面,计算的是第一行的大小
printf("%d\n", sizeof(a[0] + 1));//4,第一行第二个元素的地址
printf("%d\n", sizeof(*(a[0] + 1)));//4
printf("%d\n", sizeof(a + 1));//4,第二行的地址
printf("%d\n", sizeof(*(a + 1)));//16
printf("%d\n", sizeof(&a[0] + 1));//4,第二行的地址大小
printf("%d\n", sizeof(*(&a[0] + 1)));//16
printf("%d\n", sizeof(*a));//16
printf("%d\n", sizeof(a[3]));//16

练习2-指针与数组

//注意区分*!!!
int main()
{
	int a[] = { 1,2,3 };
	int* pa = a;
	printf("%d\n", *pa);
	char b[] = { "abc" };
	char* pb = b;
	printf("%s\n", pb);
	char c[] = "abc";
	char* pc = c;
	printf("%s\n", pc);
	return 0;
}
运行结果:
1
abc
abc
int main()
{
	int a[5] = { 1,2,3,4,5 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d\n", *(a + 1), *(ptr - 1));//a+1就是2,对2解引用没有意义,还是2;&a+1跳过了整个数组,之后将数组指针强制转换成整型指针存到整型指针p中(数组的地址和驻足==数组首元素的地址表达上是相同的),ptr-1则向前移一位,移到5的地址
	return 0;
}
运行结果:
2,5
int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;//传的是数组首元素,即第一行数组   p的类型:int (*)[4]  a的类型:int (*)[5]
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//p[4][2]==*(*(p+4)+2),由于p和a的大小不匹配,所以第一行只有4个放在p里,p+1存的是第1行第5个和第2行前3个
	//p[4]指向了a[3][1],解引用之后得到往后数四个数字的大小为4的数组,在+2,指向的就是a[3][3],a[3][3]和a[4][2]之间相差-4
	return 0;
}
运行结果:
FFFFFFFC,-4
int main()
{
	char* a[] = { "work","at","pilipala" };
	char** pa = a;//char* *pa = a;
	pa++;
	printf("%s\n", *pa);
	return 0;
 }
运行结果:
at

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;
}
运行结果:
point
er
st
ew

练习3-结构体与指针

//假设p的值为0x100000,如下表达式的值是多少?
//已知,结构体test类型的变量大小是20个字节
struct test
{
	int num;
	char* pcname;
	short sdate;
	char cha[2];
	short sba[4];
}*p;
int main()
{
	p = (struct test*)0x100000;
	printf("%p\n", p + 0x1);//+20 0x100014,结构体指针+1,跳过一个结构体,而这个结构体是20,所以+20
	printf("%p\n", (unsigned long)p + 0x1);//+8 0x100001,转换成十进制整数,跳过一个整数,+1
	printf("%p\n", (unsigned int*)p + 0x1);//+4 0x100004,转换成整型指针,整型指针+1,跳过一个整型指针,+4
	return 0;
}
运行结果:
00100014
00100001
00100004
填空:
struct s
{
	int a;
	int b;
};
int main()
{
	struct s a, * p = &a;
	a.a = 99;
	printf("%d\n", _______);//要求输出结构体中成员a的数值
	return 0;
}


答案:
a.a 或 p->a 

练习4-指针与数据存储

int main()
{
	int a[4] = { 1,2,3,4 };//小端存储:01000000 02000000 03000000 04000000
	int* ptr1 = (int*)(&a + 1);//                                         ↑ptr1指向了这个位置,对整型指针-1操作之后,指向了4的地址
	int* ptr2 = (int*)((int)a + 1);//将数组a的地址转换成整型,之后+1,比如某元素地址0x00000001,强转成整型后+1,就变成了0x00000002,再把这个作为整型地址存起来,相当于地址+1,实际意义就是往后移了一个字节
	                               //由于整型地址是4个字节,所以ptr2是是01000000中01后面的00的地址,之后对ptr2解引用,就会从这个位置访问四个字节,即00 00 00 02,读取时从右往左,即02 00 00 00
	printf("%x,%x", ptr1[-1], *ptr2);
	return 0;
}
运行结果:
4,2000000

练习5-左旋函数

//编写一个左旋函数,使得字符串的最左边k个字符一次移到最后,如:abcdef左旋2个--->cdefab
//法1:循环k次,每次将最后一个字符保存起来,将后续字符依次向前挪动,再把最后一个补上
//法2:abcdef--->ab cdef--->ba cdef--->ba fedc--->cdefab  连续翻转三次
#include<assert.h>
void reverse(char* left, char* right)
{
	assert(left != NULL);
	assert(right != NULL);
	char tmp;
	while (right - left > 0)
	{
		tmp = *left;
		*left = *right;
		*right = tmp;
		left++;
		right--;
	}
}
left_move(char arr[], int k)
{
	assert(arr);
	int len = strlen(arr);
	assert(k <= len);
	reverse(arr, arr + k - 1);//逆序左边
	reverse(arr + k, arr + len - 1);//逆序右边
	reverse(arr, arr + len - 1);//逆序整体
}
int main()
{
	char arr[] = "dcbaa";
	left_move(arr, 2);
	printf("%s\n", arr);
	return 0;
}
//写一个函数,判断一个字符串是不是另一个字符串旋转n个字符之后得到的字符串
//方法一:
int strcmp_left(char* a, char* b)
{
	assert(a);
	assert(b);
	int len = strlen(a);
	int i = 0;
	for (i = 0; i<len;i++)
	{
		left_move(a, 1);//每次旋转i个字符,因为旋转一次之后结果会被保存
		if (!strcmp(a, b))
		{
			return 1;
		}
	}
	return 0;
}
int main()
{
	char a[] = "aabcd";
	char b[] = "aagcd";
	int ret=strcmp_left(a,b);//记得在这之前需要判断长度是否相等,这里就先暂时不写了
	printf("%d\n", ret);
	return 0;
}
//方法2:abcdef--->abcdefabcdef  这样之后,每旋转一次,相当于6个长度的子串向右移一位,只需要比较子串中有没有跟要比较的序列相等的就可以了
int strcmp_left(char* a, char* b)
{
	int len = strlen(a);
	//!!!新函数出现啦!!!
	//1.给a翻倍
	/*strcat(a,b);*///在a后面追加b,但是不能自己追加自己
	//strcat原理:找到a的\0,把b中的依次放到后面,遇到b中的\0停止追加
	// 如果a追加a,会导致a已一直在变,并且\0已经不在了,找不到\0,进入死循环
	strncat(a, a, len);//最后一个参数控制追加多少个字符
	//2.判断b指向的字符串是否是a指向的字符串的子串
	char* ret = strstr(a, b);//在a中找b的子串,找到返回子串首字符的地址,没找到返回空指针
	if (ret == NULL)
	{
		return 0;
	}
	else
	{
		return 1;
	}
}
int main()
{
	char a[30] = "abcdef";
	char b[] = "defabc";
	int ret=strcmp_left(a,b);//记得在这之前需要判断长度是否相等,这里就先暂时不写了,如果不先判断长度的话,cdef也是其子串,但不是我们想要的
	printf("%d\n", ret);
	return 0;
}

 练习6-杨氏矩阵

//杨氏矩阵:有一个数字矩阵,矩阵的每行从左到右是递增的,从上到下是递增的,请编写程序在这样的某个矩阵中查找某个数字是否存在
// 如:
// 1 2 3     1 2 3
// 4 5 6     2 3 4
// 7 8 9     3 4 5
//要求:时间复杂度小于O(N);指在有n个元素的情况下,查找次数小于n次
int find(int arr[3][3], int k, int row, int col)
{
	int x = 0, y = col - 1, count = 0;
	while (x <= 2 && y >= 0)
	{
		if (arr[x][y] > k)
		{
			y--;
		}
		else if (arr[x][y] < k)
		{
			x++;
		}
		else
		{
			printf("%d,%d\n", x, y);
			arr[x][y] = -1;
			/*x = 0;
			y = col - 1;*/
			count++;
		}
	}
	if (count == 0)
	{
		return 0;
	}
}
int main()
{
	int arr[3][3] = { {1,2,3},{2,3,4},{3,4,5} };
	int k=3;
	//从右上角的数字入手,如果比k小,那么这一行没有,如果比k大,那么这一列没有
	int ret = find(arr, k, 3, 3);
	/*printf("%d\n", ret);*/
	return 0;
}

 练习7-模拟实现strcpy函数

void my_strcpy1(char* arr1, char* arr2)
{
	*arr1 = *arr2;
	while (*arr2 != '\0')
	{
		arr2++;
		arr1++;
		*arr1 = *arr2;
	} 
}
void my_strcpy2(char arr1[], char arr2[])
{
	int i = 0;
	arr1[i] = arr2[i];
	while (arr2[i] != '\0')
	{
		i++;
		arr1[i] = arr2[i];
	}
}
char *my_strcpy4(char* arr1, const char* arr2)//保证arr2不会被改变,防止写反使arr1的值传入arr2
{
	char* ret = arr1;
	assert(arr1 != NULL && arr2 != NULL);
	while (*arr1++ = *arr2++)
	{
		;
	}
	return ret;//为什么不返回arr1?因为通过上面的操作,arr1其实已经很靠后了,所以我们需要在开始就讲arr1的首地址存起来,这样我们就可以从开头返回arr1了
}
//注意养成写代码时使用assert和const的习惯以增强函数的健壮性

练习8-思维训练

//找凶手  A:不是我  B:是C  C:是D  D:C在胡说   已知三真一假,找到凶手
int main()
{
	int killer = 0;
	for (killer = 'A'; killer <= 'D'; killer++)
	{
		if ((killer != 'A') + (killer == 'C') + (killer == 'D') + (killer != 'D') == 3)//因为有三个是真的,所以把这四个条件的判断结果相加求和来进行验证
		{
			printf("killer is %c\n", killer);
		}
	}
	return 0;
}
//排名次:5运动员,a:b2a3  b:b2e4  c:c1d2  d:c5d3  3:e4a1  每个人都直说对了一半,求排名顺序
int main()
{
	int a, b, c, d, e;
	for (a = 1; a <= 5; a++)
	{
		for (b = 1; b <= 5; b++)
		{
			for (c = 1; c <= 5; c++)
			{
				for (d = 1; d <= 5; d++)
				{
					for (e = 1; e <= 5; e++)
					{
						if ((((b == 2) + (a == 3)) == 1) && (((b == 2) + (e == 4)) == 1) && (((c == 1) + (d == 2)) == 1) && (((c == 5) + (d == 3)) == 1) && (((e == 4) + (a == 1)) == 1) && (a * b * c * d * e) == 120)
						{
							printf("a:%d b%d :c:%d d:%d e:%d\n", a, b, c, d, e);
						}
					}
				}
			}
		}
	}
	return 0;
}

注意

数组指针与函数命名 

void test(char** arr)//这样定义也是合理的,因为实参类型是char*数组的地址,所以形参char*为元素类型,char**则接收char*的地址,可以类比int*p中可以存放int的地址
{

}
int main()
{
	char* arr[5] = { "abc" };
	test(arr);
	return 0;
}

void test(int(*arr)[5])//不可以写成 int* arr,因为传递的是一个二维数组,不能用一级指针接收,可以理解为传递了一个一维数组的首元素地址,而这个地址是一个数组指针,所以要用数组指针类型的形参接收
{

}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
	return 0;
}

 const的修饰作用

int main()
{
	int a = 10;
	int* pa = &a;
	*pa = 20;
	printf("%d\n", a);
	//使用const保证变量的值不被改变?
	const int b = 10;
	int* pb = &b;
	*pb = 20;
	printf("%d\n", b);//为什么变量的值还是被改变了?原因是即使定义变量是常量,但仍然可以通过改变地址来改变变量的值
	//int c = 10;
	//const int* pc = &c;
	//*pc = 20;//这种情况就会报错,const放在指针变量*的左边时,修饰的是*p,也就是说:不能通过p来改变*p的值
	//int d = 10;
	//int* const pd = &d;
	//pd = &c;//这种情况仍然会报错,const放在指针变量*的右边时,修饰的是指针变量p本身,也就是说:不能重新对p赋新的地址
	//printf("%d\n", d);
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值