【C语言】——指针进阶

发布于:2022-10-28 ⋅ 阅读:(308) ⋅ 点赞:(0)

目录

1.字符指针

2.指针数组

3.数组指针

3.1数组指针的定义

3.2&数组名VS数组名

3.3数组指针的使用

4.数组参数、指针参数

4.1一维数组传参

4.2二维数组传参

4.3一级指针传参

4.4二级指针传参

5.函数指针

1.两个有趣的代码

6.函数指针数组

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

8.回调函数

8.1冒泡排序

8.2——qsort函数的使用

1.用qsort排序一个数组

2.用qsort排序一个结构体

3.用冒泡排序的思想模拟实现qsort函数

9.指针和数组的深度理解(题目)

1.一维整型数组

2.字符数组

3.二维数组

4.证明sizeof里的表达式不会计算

10.指针的深度理解(题目)

练习1:

 练习2:

练习3

 练习4

 练习5

 练习6

 练习7(重点)

 练习8(重点)


1.字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char*  —— (指向字符的指针)

字符指针的使用细节:

int main()
{
	char ch = 'w';
	char* pc = &ch;

	const char* ps = "abcdef";//将字符串"abcdef"首字符a的地址放在了ps里面,而不是将字符串"abcdef"存在ps里面
	//常量字符串不可修改
	//const 修饰*ps 导致*ps不能被修改 
	printf("%c\n", *ps);//a
	printf("%s\n", ps);//abcdef
	//*ps = 'w';		//常量字符串不可以修改

	char arr[] = "abcdef";
	char* p = arr;
	//此时"abcdef"在数组里面才能够被修改,"abcdef"在字符串里面不能被修改。

	return 0;
}

练习一下:

#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 same\n");
	else
		printf("str1 and str2 are not same\n");

	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");

	return 0;
}

分析:

比较变量大小的逻辑 

2.指针数组

指针数组的本质是数组,数组里面存的是指针,也就是说数组里面存的是地址。

int main()
{
    //代码1
	char* arr[5] = {"abcdef", "zhangsan", "hehe", "wangcai", "ruhua"};//存放指针的数组 - 指针数组
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%s\n", arr[i]);
	}
    

    //代码2
	//int arr1[5] = {1,2,3,4,5};
	//int arr2[5] = { 2,3,4,5,6 };
	//int arr3[5] = { 3,4,5,6,7 };

	//int* arr[3] = {arr1, arr2, arr3};//指针数组

	//int i = 0;
	//int j = 0;
	//for (i = 0; i < 3; i++)
	//{
	//	for (j = 0; j < 5; j++)
	//	{
	//		printf("%d ", arr[i][j]);
	//	}
	//	printf("\n");
	//}
	return 0;
}

分析代码1:

 分析代码2:

3.数组指针

3.1数组指针的定义

整型指针是指向整型的指针

字符指针是指向字符的指针

同理可得:

数组指针的本质是指针,是一个指向数组的指针

int main()
{
	int a = 10;
	int* p1 = &a;//p1是整型指针,存放的是整型a的地址

	char ch = 'w';
	char* p2 = &ch;//p2是字符指针,存放的是字符ch的地址

	int arr[10] = {1,2,3,4,5};
	int (* pa)[10] = &arr;//取出的是数组的地址存放到pa中,pa是数组指针变量
	//*先修饰pa,说明pa是指针,然后*pa修饰[10],说明pa是数组指针,最后*(pa)[10]修饰int,说明pa是整型数组指针   
    //int(*)[10] -> 数组指针类型
    //pa是整型数组指针,指向的是一个含有10个元素的整型数组,pa里面存的是这个整型数组的地址


	return 0;
}

3.2&数组名VS数组名

int arr[10];

我们知道数组名是首元素地址,arr是数组名,也是arr[0]的地址。

那么&arr是谁的地址呢?(数组的地址)

调试以下代码,我们能得到规律

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);//打印的是数组首元素的地址
	printf("%p\n", arr[0]);//打印的数组首元素的地址,数组的首元素地址+1是跳过一个元素
	printf("%p\n", &arr);//打印的是数组的地址,数组的地址是从数组的首元素的地址开始的,数组的地址+1是跳过一个数组
	return 0;
}

分析:


int main()
{
	int arr[10] = {0};
	printf("%p\n", arr); 
	printf("%p\n", arr+1);

	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0]+1);

	printf("%p\n", &arr);
	printf("%p\n", &arr+1);

	return 0;
}

分析:

 总结:

整型指针是指向整型的指针       整型指针+1,跳过一个整型,四个字节

字符指针是指向字符的指针       字符指针+1,跳过一个字符,一个字节

数组指针是指向数组的指针      数组指针+1,跳过一个数组,具体跳过多少个字节,看这个数组是什么类型的数组,有几个元素,假如该数组有10个整型,那么数组指针+1跳过40个字节

总结:

数组名表示首元素的地址

但是有两个例外

1.sizeof(数组名):数组名单独放在sizeof()里面,这里的数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节

2.&数组名:这里的数组名表示整个数组的地址,这里取出来的是整个数组的地址

除了上述两个例外之外,我们见到的所有的数组名都表示的是首元素的地址

3.3数组指针的使用

void print2( int (*p)[10], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", (*p)[i]);//p拿到的是整个数组的地址,解引用之后  *p  就是数组名
								//*&arr ==== *p
	}

}


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

但是这样调用函数的参数很奇怪

 数组指针一般不用于一维数组传参,更多的是应用于二维数组传参

对于二维数组,每一行是它的一个元素,也就是说二维数组的元素是一维数组,从某种意义上讲,二维数组本质上也是一维数组,只不过二维数组的元素,变成了一维数组

//二维数组传参

void print1(int (*p)[5], int r, int c)//p是一个数组指针,指向一个一维数组
{
	for (int i = 0; i < r; i++)
	{
		for (int j = 0; j < c; j++)
		{
			//printf("%d ", (*(p + i))[j]);
			printf("%d ", *(*(p + i)+j));
			//p是数组指针,+1会跳过一个数组
			//对p解引用会得到数组的首元素地址
			//比如,arr1是一维数组的首元素地址 那么&arr1就是一位数组的地址 *&arr1就是首元素地址
			//arr1[2] ====== *(arr1+2)
			//所以有两种打印方法
			//注意*和[]优先级的问题
		}
		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);
	return 0;
}

结果:

 区分:

int arr[5];//arr是整型数组
int* parr1[10];//parr1是存放整型指针的数组,是整型指针数组
int(*parr2)[10];//parr2是数组指针,指向的是有10个整型元素的数组
int(*parr3[10])[5];//parr3是一个数组,有10个元素,每个元素是指针,指向的是带有5个整型元素的数组
					//parr3是存放数组指针的数组
//判断是数组还是指针的方法,看parr3先和谁结合,先和[]结合的话,parr3就是数组,先和*结合dehua,parr3就是指针
//[]的优先级比*高

4.数组参数、指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

数组传参,形参可以是数组,也可以是指针。但是数组传参本质上是传递是一个地址。

4.1一维数组传参

#include <stdio.h>
void test(int arr[])//ok?//可以。数组传参的本质上传的是一个地址,因此元素可以省略
{}//形参是数组
void test(int arr[10])//ok?//可以
{}//形参是数组
void test(int* arr)//ok?//可以//建议使用这个
{}//形参是指针
void test2(int* arr[20])//ok?//可以 //20可以省略
{}//形参是数组
void test2(int** arr)//ok? //可以
{}//形参是指针
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

4.2二维数组传参

void test(int arr[3][5])//ok?//可行
{}//形参是数组
void test(int arr[][])//ok?//不行
{} 
void test(int arr[][5])//ok?
{}//形参是数组
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
// 所以行能省,列不能省
//这样才方便运算。
void test(int* arr)//ok?//不行//二维数组的数组名表示的首元素的地址,是一行的地址,是一个数组指针
{}
void test(int* arr[5])//ok?//不行//需要指针接收,但是这里形参是数组
{}
void test(int(*arr)[5])//ok?//可行//建议使用这个
{}
void test(int** arr)//ok?//不行//传过去的是一级指针,不能用二级指针接收
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

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

思考:

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

void test1(int* p)
{}
//test1函数能接收什么参数?
//1.整型变量的地址
//2.一级指针
//3.整型一维数组数组名

void test2(char* p)
{}
//test2函数能接收什么参数?
//1.字符或者字符串的地址
//2.一级字符指针
//3.字符一维数组的数组名

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

思考:

当函数的参数为二级指针的时候,可以接收什么参数?

void test(int** p)
{}
//1.可以传二级指针
//2.可以传一级指针的地址
//3.可以传指针数组的数组名

5.函数指针

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

下面的代码仔细介绍了函数指针的创建、使用以及和函数的区别。

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int arr[10] = {0};
	printf("%p\n", &arr);//数组的地址
	printf("%p\n", arr);//数组的首元素地址
						//虽然值是一样的,但是意义完全不同
	
	printf("%p\n", Add);//对函数来说,没有首元素的讲法,
	printf("%p\n", &Add);//加上&和不加上&,没有什么区别,拿到的都是函数的地址


	//pf是函数指针变量
	int (*pf)(int x, int y) = Add;//pf是函数指针,指向一个函数,这个函数的参数是 int x和int y ,返回类型是int
	//pf是指针----->(*pf)
	//指向的是函数,函数的参数是int x和int y ----->(*pf)(int x,int y)
	//函数的返回类型是int -------> int (*pf)(int x,int y)
	//函数指针类型就写完了  是:int (*pf)(int x,int y)
	//后续所有的函数指针类型,都能这样分析

	//使用函数指针

	int sum = Add(3, 5);
	printf("%d\n", sum);//打印Add函数的返回值  是8

	int sum1 = (*pf)(3, 5);//能够完成Add函数的功能
	printf("%d\n", sum1);//打印Add函数的返回值  是8

	//省略写法,此时pf==Add

	int sum2 = pf(3, 5);//能够完成Add函数的功能
	printf("%d\n", sum2);//打印Add函数的返回值  也是8

	//(*pf)(3, 5)  这里的*是一个摆设,完全可以省略掉    得到pf(3,5)

	return 0;
}
int test(const char* str, double x)
{

}
int main()
{
	int (*pt)(const char*, double) = test;
	int (*pt)(const char* a, double b) = &test;
	//上述这两种函数指针的写法都是可行的,可以写参数,也可以不写参数,可以写取地址函数名,也可以直接写函数名
	return 0;
}

1.两个有趣的代码

//两个有趣的代码
int main()
{
	//1.
	(*(void (*)())0)();//把0转换成函数的地址
	//分析
	//0是int类型
	//1.(void (*)())0--把0强制类型转换成函数指针,也就是说0位置处指向了一个函数,这个函数的返回类型是void,没有参数
	//2.*(void (*)())0 --解引用函数指针,拿到了这个函数的函数名
	//3.(*(void (*)())0)()--调用这个函数,这个函数是没有参数的
	//也就是把0直接转换成void (*)()的函数指针,然后再去调用0地址处的函数

	//2.
	void (*signal(int, void(*)(int)))(int);
	//分析
	//1.是一个函数声明
	//2.声明的函数是signal
	//3.signal函数的第一个参数是int,第二个参数是函数指针,指向的函数的参数是int,返回类型是void
	//4.signal的返回类型是函数指针,这个函数指针指向的函数的参数是int,返回类型是void
	
	//上述代码不利于阅读
	// 
	//3.改写
	void (*signal(int, void(*)(int)))(int);
	//改写成:
	typedef void(*pf_t)(int);//----将函数指针类型void(*)(int)重新定义为pf_t 
	pf_t signal(int, pf_t);//也是一个函数声明
	//pf_t signal(int, pf_t)和void (*signal(int, void(*)(int)))(int)是完全等价的
	return 0;
	
}

函数指针也是一种类型,和我们熟知的int  char 一样都是类型 

void(*)(int,int)是一种类型,是一种函数指针类型

6.函数指针数组

 把函数的地址存到一个数组中,那这个数组就叫函数指针数组

//int *p ----整型指针
//int* arr[5]-----整型指针数组


//int (*pf)(int ,int )----函数指针
//int(*pfarr[5])(int ,int )-----函数指针数组

//分析
//pfarr先和[5]结合,说明pfarr是数组
//再和*结合说明是指针数组,
//数组里面存放的指针类型是-----函数指针类型的指针

函数指针数组的应用:

int Add(int x, int y)
{
	return x + y;
}

int Sub(int x, int y)
{
	return x - y;
}

int main()
{
	//函数指针数组
	//可以存多个【参数相同,返回类型相同的】函数的地址
	int (*pfarr[2])(int, int) = { Add,Sub };

	int ret = pfarr[0](2,3);
	printf("%d\n", ret);//5


	 ret = pfarr[1](2, 3);
	printf("%d\n", ret);//-1
	return 0;
}

用函数指针数组实现计算器的加减乘除功能 

//用函数指针数组实现的
//计算器

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;
	int x = 0;
	int y = 0;
	int ret = 0;

	//函数指针数组   - 转移表
	int (*pfArr[])(int, int) = { 0, Add, Sub, Mul, Div };

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}

		if (input >= 1 && input <= 4)
		{
			printf("请输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}	
	} while (input);
}

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

指向函数指针数组的指针是一个指针,指针指向一个数组 ,数组的元素都是函数指针(函数的地址)。
那么如何定义指向函数指针数组的指针呢?
看代码:
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;
	//(*ppfunArr)----说明ppfunArr是一个指针
	//(*ppfunArr)[5]----ppfunArr指向的是一个含有5个元素的数组
	//元素的类型   是void (*)(const char*)  是函数指针
	

	return 0;
}

8.回调函数

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

        回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

用回调函数实现计算器的加减乘除

//使用回调函数完成计算器的加减乘除

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 calc(int (*p)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入2个操作数:>");
	scanf("%d %d", &x, &y);
	ret = p(x, y);
	printf("%d\n", ret);
}

int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);		
		switch (input)
		{
		case 1:
			calc(Add);//Add是回调函数
			break;
		case 2:	
			calc(Sub);//Sub是回调函数
			break;
		case 3:
			calc(Mul);//Mul是回调函数
			break;
		case 4:
			calc(Div);//Div是回调函数
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
}

8.1冒泡排序

//bubble_sort 函数只能排序整型数据
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 - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}


void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

void test1()
{
	//冒泡排序
	//对整型数据进行排序 - 排序为升序
	int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	print(arr, sz);
}

int main()
{
	test1();
	return 0;
}

bubble_sort排序的数据类型是单一的,如果需要排序多种数据类型的数据,bubble_sort不再适用

8.2——qsort函数的使用

想要使用qosrt函数,使用者必须自己先定义一个比较函数。这个比较函数是qsort的回调函数

qsort是C语言的标准库,是用来排序的,实现qsort的底层逻辑是快速排序

void qsort (void* base, size_t num, size_t size,
            int (*compar)(const void*,const void*));

函数含义:用来排序

参数:

base:待排序数据的起始地址

num:待排序数据的元素个数

size:待排序数据元素的大小(单位是字节)

int (*compar)(const void*,const void*):比较两个元素大小的函数指针(函数的地址,这个函数可以比较两个元素的大小)

compar的返回类型如下:

qsort返回值:没有

void*的指针,非常宽容,可以接收任意类型的地址,但是不能直接对void*的指针进行解引用操作,也不能进行加减整数的操作(因为void*的指针是无具体类型的指针,因此解引用不知道访问的数据到底是什么类型的,同理,也不知道每一次加减整数跳过多少个字节),因此void*的指针必须要强制类型转换成对应的类型才能使用解引用操作符。    

1.用qsort排序一个数组

#include <stdlib.h>
#include <stdio.h>

void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}



cmp_int(const void* a, const void* b)
{
	return *(int*)a - *(int*)b;	
}


void test2()
{
	//冒泡排序
	//对整型数据进行排序 - 排序为升序
	int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr, sz);
}


int main()
{
	test2();
	return 0;
}

2.用qsort排序一个结构体

#include <stdlib.h>
#include<string.h>


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

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

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

//测试qsort排序结构体数据
void test3()
{
	struct Stu s[] = { {"zhangsan", 20}, {"lisi", 55}, {"wangwu", 40} };
	//按照名字比较
	int sz = sizeof(s) / sizeof(s[0]);//结构体有几个元素的计算方法
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);//名字的升序
	//qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);//年龄的升序
}

int main()
{
	test3();
	return 0;
}

//请自行调试排序后结构体s的数据顺序

3.用冒泡排序的思想模拟实现qsort函数

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

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

void print(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

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

//假设是小端存储
//交换一个未知数据类型的数据
//一个字节一个字节的交换
void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

void bubble_sort2(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 = 0; j < sz - 1 - i; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

void test4()
{
	int arr[] = { 2,1,3,7,5,9,6,8,0,4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort2(arr, sz, sizeof(arr[0]), cmp_int);
	print(arr, sz);
}

void test5()
{
	struct Stu s[] = { {"zhangsan", 20}, {"lisi", 55}, {"wangwu", 40} };
	//按照名字比较
	int sz = sizeof(s) / sizeof(s[0]);
	//bubble_sort2(s, sz, sizeof(s[0]), cmp_stu_by_name);
	bubble_sort2(s, sz, sizeof(s[0]), cmp_stu_by_age);
}

int main()
{
	test4();
	//test5();
    //自己去调试test5
}

PS:

异或,同或等等二进制操作符都只能作用于整型,不适用于字符类型

9.指针和数组的深度理解(题目)

1.一维整型数组

//一维整型数组
//全部考虑在32位平台下
int main()
{
	int a[] = { 1,2,3,4 };
	
	//理解:不同数据类型的指针,要用对应的同类型的指针接收
	//int* p = a;//a是数组名,是首元素的1(int)的地址,用整型指针p接收a
	//int(*pp)[4] = &a;//&a符合两个特例,拿到的是数组的地址,数组的地址要用数组指针(pp)接收

	printf("%d\n", sizeof(a));//16----a作为数组名单独放在sizeof内部,计算的是整个数组的大小,单位是字节
	printf("%d\n", sizeof(a + 0));//4----a不是单独放在sizeof内部,也没有&,a就表示数组首元素的地址
	printf("%d\n", sizeof(*a));//4--------a是首元素地址,*a就是首元素,为int类型,4个字节
								//a---int *,*a-----int
	printf("%d\n", sizeof(a + 1));//----4,a是首元素地址,a+1跳过一个整型,是第二个元素地址,sizeof(a+1)计算的地址的大小,是4个字节
	printf("%d\n", sizeof(a[1]));//----4,计算的是第二个元素的大小,4个字节
	printf("%d\n", sizeof(&a));//---4,&a取出的数组的地址,地址在32位下都是4个字节
	printf("%d\n", sizeof(*&a));//16------指针类型决定了+-1跳过几个字节,也决定了解引用的时候访问多少个字节的数据
								//对整型指针解引用,访问一个整型,对字符指针解引用,访问一个字符
								//此时&a拿到了整个数组的地址,*&a是对整个数组解引用,访问整个数组
								//因此是16
								//*和&能够抵消,因此*&a====a
								//sizeof(*&a)====sizeof(a)
	printf("%d\n", sizeof(&a + 1));//4-----&a是数组的地址,&a+1跳过整个数组,但是&a+1还是地址,是地址就是4个字节
	printf("%d\n", sizeof(&a[0]));//4----&a[0]是第一个元素的地址,是4个字节
	printf("%d\n", sizeof(&a[0] + 1));//4----&a[0]是第一个元素的地址,&a[0] + 1是第二个元素的地址,是地址就是4个字节
	return 0;
}

2.字符数组

//全部考虑在32位平台下实现
int main()
{
	//字符数组
	char arr[] = { 'a','b','c','d','e','f' };

	//sizeof是计算对象或者类型创建的对象所占的空间的字节,但不会去访问这块空间,只是去算一算对象的空间是多大
	//字符的本质是ASCII值
	printf("%d\n", sizeof(arr));//6-----arr是数组名,并且是单独放在zizeof()内部。计算的整个数组的长度,6个字节
	printf("%d\n", sizeof(arr + 0));//4----arr是数组名,并非放在sizeof内部,因此,arr表示首元素地址,arr+0表示首元素地址,是地址就是4个字节
	printf("%d\n", sizeof(*arr));//1---arr是数组名,不是单独放在zizeof内部,因此,arr是后元素地址,*arr是首元素,是1个字节
	printf("%d\n", sizeof(arr[1]));//1---arr[1]是数组的第二个元素,是1个字节
	printf("%d\n", sizeof(&arr));//4---arr是数组名,&arr取出的是数组的地址,是4个字节
	printf("%d\n", sizeof(&arr + 1));//4----arr是数组名,&arr取出的是数组的地址,&arr+1跳过一个数组,&arr+1也是地址呀,是地址就是4个字节
	printf("%d\n", sizeof(&arr[0] + 1)); //4----&arr[0]拿到的首元素的地址,&arr[0] + 1拿到的是第二个元素的地址,是地址就是4个字节

	//strlen计算的字符串的长度,遇到\0结束,计算的长度不包括\0.
	//字符串的结束标志是\0,ASCII码值是0
	printf("%d\n", strlen(arr));//随机值-----arr是数组名,没有放在sizeof内部,也没有&arr,因此arr是首元素地址,strlen得到数组arr后,
								//从首元素处开始计算字符串的长度,直到遇到\0结束,数组arr内存的后面是否有\0,在什么位置出现是位置的,因此strlen(arr)的值是随机的。
	//strlen越界访问了不属于自己的空间,但是并没有去影响内存,只是去越界数了数,并没有去改变不属于自己的内存
	printf("%d\n", strlen(arr + 0));//随机值---arr是数组首元素的的地址,arr + 0还是首元素地址,
	printf("%d\n", strlen(*arr));//非法访问内存-----arr是首元素地址,*arr是首元素,就是a,ASCII值是97,那么strlen就把97当作地址去访问,这块地址是不允许访问的,程序会崩溃
	printf("%d\n", strlen(arr[1]));//非法访问内存----arr[1]是第二个元素,就是b,ASCII值是98,那么strlen就把98当作地址去访问,这块地址是不允许访问的,程序会崩溃
	printf("%d\n", strlen(&arr));//随机值---&arr拿到的数组的地址,数组的地址也是指向数组的起始地址,和首元素的地址的值是一样的,strlen(&arr)计算的也是随机值
	printf("%d\n", strlen(&arr + 1));//随机值----&arr + 1拿到数组的地址后,再往后跳过数组的地址,拿到的也是随机值,只不过会比&arr的随机值少6
	printf("%d\n", strlen(&arr[0] + 1));//随机值----&arr[0] + 1拿到的是第二个元素的地址,会比&arr拿到的随机值小1


	char arr[] = "abcdef";
	//arr里面有\0,在abcdef后面
	//arr里面有7个元素,分别是a b c d e f \0
	printf("%d\n", sizeof(arr));//---7
	printf("%d\n", sizeof(arr + 0));//4-----arr+0表示的首元素的地址
	printf("%d\n", sizeof(*arr));//1----arr是首元素的地址,*arr拿到的是首元素
	printf("%d\n", sizeof(arr[1]));//1
	printf("%d\n", sizeof(&arr));//4----&arr拿到的是数组的地址,是地址就是4个字节
	printf("%d\n", sizeof(&arr + 1));//4----&arr拿到的数组的地址,&arr+1跳过整个数组,也就是跳过7个字符(\0也在数组内部),&arr+1还是地址呀,是地址就是4ge字节
	printf("%d\n", sizeof(&arr[0] + 1));//4---数组第二个元素的地址
	
	
	printf("%d\n", strlen(arr));//6----arr是数组首元素地址,往后数,遇到\0结束,一共6个
	printf("%d\n", strlen(arr + 0));//6-----arr+0还是首元素地址,往后数,遇到\0结束,一共6个
	printf("%d\n", strlen(*arr));//非法访问-----*arr是首元素
	printf("%d\n", strlen(arr[1]));//非法访问----同上
	printf("%d\n", strlen(&arr));//6-----&arr拿到的是数组arr的地址,往后数,但是类型和srelen不匹配,会发生强制类型转换
									//size_t strlen(const char* str)  是strlen的函数原型    &arr的类型是char(* )[7]
									//和str的类型不匹配,但是在函数调用的时候是函数说了算
									//因此,char(* )[7]会被强制类型转换成const char*类型
									//不管你传什么类型的参数给strlen函数,函数都会以const char*类型去实现函数功能		
	printf("%d\n", strlen(&arr + 1));//随机值----&arr + 1跳过整个数组,不知道后面有没有	\0,因此strlen数到的是谁机制
	printf("%d\n", strlen(&arr[0] + 1));//5----&arr[0] + 1拿到的是第二个元素的地址,strlen往后数,有5个。

	const char* p = "abcdef";
	//p里面存的是a的地址
	//"abcdef"里面存的是 a b c d e f \0
	
	printf("%d\n", sizeof(p)); //4----p里面存的是字符a的地址,p是地址,p指向字符a,是地址就是4个字节
	printf("%d\n", sizeof(p + 1));//4---p+1是地址,p+1指向字符b,是地址就是4个字节
	printf("%d\n", sizeof(*p));//1----p是char*类型的变量,*p只能访问一个字节,就是访问字符a
	printf("%d\n", sizeof(p[0]));//1----p[0]====*(p+0),也就是拿到第一个元素字符a,是1个字节
								//总结:*p====*(p+0)====p[0]
								//		*(p+2)====p[2]
	printf("%d\n", sizeof(&p));//4----&p拿到的是一级指针p的地址
	printf("%d\n", sizeof(&p + 1));//4----&p拿到的是一级指针p的地址,&p + 1 跳过的是p(p里面存的是地址),那就是跳过了4个字节
	printf("%d\n", sizeof(&p[0] + 1)); //4-----&p[0] + 1是字符b的地址
	
	
	printf("%d\n", strlen(p));//6----p存的是字符a的地址,strlen从a的地址处往后数字符,遇到\0结束
	printf("%d\n", strlen(p + 1));//5----p + 1存的是字符b的地址,strlen从b的地址处往后数字符,遇到\0结束
	printf("%d\n", strlen(*p));//非法访问---*p是字符a,但是strlen要接收的是地址,a的ASCII值是97,当成地址传给strlen,97地址处,是不允许访问的,程序崩溃
	printf("%d\n", strlen(p[0]));//非法访问---p[0]也是字符a,但是strlen要接收的是地址,a的ASCII值是97,当成地址传给strlen,97地址处,是不允许访问的,程序崩溃
	printf("%d\n", strlen(&p));//随机值---&p取出的是p的地址,那么strlen从这个地址,往后访问,不知道有没有\0
	printf("%d\n", strlen(&p + 1)); //随机值----&p拿到的是一级指针p的地址,& p + 1 跳过的是p(p里面存的是地址),那么strlen从这个地址,往后访问,不知道有没有\0
									//此随机值和上一个的随机值是不存在数量关系的,因为& p + 1 跳过的p,不知道跳过的p里面含不含有\0,如果有的话,那么就不存在数量关系。
	printf("%d\n", strlen(&p[0] + 1));//5----&p[0] + 1是字符b的地址,strlen从b的地址处往后数字符,遇到\0结束

	return 0;
}

3.二维数组

int main()
{
	//只考虑在32位下实现
	//二维数组
	int a[3][4] = { 0 };

	printf("%d\n", sizeof(a));//48----二维数组也是数组,a是数组名。a单独放在sizeof里面,因此,计算的是数组的总大小,单位是字节
	printf("%d\n", sizeof(a[0][0]));//4----a[0][0]是一个整型元素大小,是4个字节
	printf("%d\n", sizeof(a[0]));//16----把二维数组的每一行看作一维数组的时候,a[0]是第一行的数组名,数组名单独放在sizeof内部,计算的是第一行的总大小,含有4个整型,因此a[0]是16个字节
									//二维数组的每一行是二维数的一个元素。
	
	printf("%d\n", sizeof(a[0] + 1));//难点
										//4----a[0]虽然是二维数组第一行的数组名(并非二维数组的数组名),并非单独放在sizeof的内部,此时a[0]作为第一行的数组名并非表示整个第一行这个数组
										//此时,a[0]表示的就是这一行代表的数组的首元素的地址,相当于&a[0][0]--->int*
										//a[0] + 1,跳过一个整型,是a[0][1]的地址
	printf("%d\n", sizeof(*(a[0] + 1)));//4----a[0] + 1是a[0][1]的地址,因此*(a[0] + 1)就是访问a[0][1],是4个字节
	printf("%d\n", sizeof(a + 1));//4----a是二维数组的数组名,并非单独放在sizeof的内部,也没有&,因此a表示二维数组首元素的地址
									// 对于二维数组,第一行是它的首元素,第二行是它的第二个元素
									//a就是第一行的地址,a的类型是 int(*)[4]  此时a====&a[0],a+1====&a[1].a+2====&a[2]
									//a+1就是第二行的地址,是地址的话,大小就是4个字节
	printf("%d\n", sizeof(*(a + 1)));//16----a+1是第二行的地址,是数组指针,*(a + 1)对数组指针解引用,找到的就是第二行,因此计算的就是第二行的大小
	printf("%d\n", sizeof(&a[0] + 1)); //4---a[0]是第一行数组名,&a[0]拿到的是第一行的地址,&a[0]+1,就是第二行的地址。是地址就是4个字节
	printf("%d\n", sizeof(*(&a[0] + 1)));//16----&a[0] + 1是第二行的地址,*(&a[0] + 1)访问的是整个第二行
											//*(&a[0] + 1)====a[1]
	printf("%d\n", sizeof(*a));//16---a是数组名,不是单独放在sizeof内部,也没有&,a表示的是首元素的地址,也就是第一行的地址,*a就是访问整个第一行,大小就是16个字节 
	printf("%d\n", sizeof(a[3]));//16---代码没问题
								//任何一个表达式都有两个属性
								//比如 3+5
								//值属性 8
								//类型属性 int
								//因此能够分析出a[3]的类型是int [4]
								//因此并不需要访问a[3]
								//只需要知道a[3]的数据类型就能确定a[3]的大小
								//因此是16个字节
	
	return 0;
}

4.证明sizeof里的表达式不会计算

int main()
{
	short num = 20;
	int a = 5;
	printf("%d\n", sizeof(num = a + 1));//2
	printf("%d\n", num);//20
	//如果num = a + 1发生了计算,那么num的值是6,但是此时num的值还是20,说明num = a + 1没有计算
	//那么为什么没有计算呢?
	//sizeof是计算空间大小的,那么从num = a + 1,可以直到num的类型是short,是2个字节
	//算不算num = a + 1,sizeof计算的空间大小都是2个字节
	//因此就没必要算num = a + 1
	return 0;
}

10.指针的深度理解(题目)

练习1:

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%d %d", *(a + 1), *(ptr - 1));//2 5
	return 0;
}
//程序的结果是什么?
//2 5

 分析:

 练习2:

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

分析:

练习3

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);//4,2000000
	//%x---打印的16进制整数
	return 0;
}

分析:

 练习4

#include <stdio.h>
int main()
{
	
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	//(0, 1), (2, 3), (4, 5)都是逗号表达式
	//本质上 a[3][2] = { 1,3,5 };
	int* p;
	p = a[0];
	printf("%d", p[0]);//1
	return 0;
}

分析:

 练习5

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]);
	//FFFFFFFC,-4
	return 0; 
}

 练习6

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));//(int*)用来迷惑我的,没必要强制类型转换,类型本来就是对齐的
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//10,5
	return 0;
}

分析:

 练习7(重点)

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

分析:

 练习8(重点)

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

分析:

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到