C语言学习记录—指针进阶

 第一章:字符指针

int main()
{
	char ch = 'w';
	char* pc = &ch;//*说明pc是指针,char说明pc指向的对象ch类型是char。char*是pc的类型
	*pc = 'b';//通过指针找到ch修改里面的值
	printf("%c\n", ch);
    
    char* p = "abcdef";//这里是把字符串首字母a的地址赋值给指针p
    printf("%s\n", p);
    //上面写法在某些编译器会报警告不安全。因为abcdef是常量字符串,常量字符串的意思就是不能被修改。
    //但是又把这个字符串的地址放到p里面,p的权限就变大了(因为p没有被修饰)。如果强行修改,程序就崩溃。
    //所以应该在要在*左边加上const,这样const就修饰指针*p,也就是限制了*p不能修改

    char arr[] = "abcdef";//这种写法才是把整个字符串放到数组里
	return 0;
}

练习题

int main()
{
	const char* p1 = "abcdef";
	const char* p2 = "abcdef";

	char arr1[] = "abcdef";
	char arr2[] = "abcdef";

	//内存中有一个字符串abcdef\0,这是一个常量字符串,放在内存只读数据区里(不能改)。
	//因为不能改,所以只需要存一份。所以p1和p2指向的是同一个地址
	if (p1 == p2)
		printf("p1 == p2\n");
	else
		printf("p1 != p2\n");

	//arr1和arr2是两个独立的数组,在内存中开辟了两块独立的空间,所以地址不同
	if (arr1 == arr2)
		printf("arr1 == arr2\n");
	else
		printf("arr1 != arr2\n");

	return 0;
}

第二章:指针数组

1. 定义

指针数组 - 是数组,是用来存放指针的数组。

int main()
{
	int* arr1[10];//存放整型指针的数组,数组每个元素的类型是int*(即int*类型的值)
	char* arr2[4];//存放字符指针的数组
    char* arr3[5];//一级字符指针的数组
    char** arr3[5];//二级字符指针的数组

	return 0;
}

2. 用指针数组模拟二维数组

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[3] = { arr1,arr2,arr3 };

	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			//*(p+i)等价于p[i]
			printf("%d ", *(parr[i] + j));//起始地址加j,相当于向后平移j个元素
			//printf("%d ", parr[i][j]);//等价上方
		}
		printf("\n");
	}
	return 0;
}

第三章:数组指针

1. 定义

数组指针 - 是指针,指向数组的指针

int main()
{	
    //p1先跟[]结合,是个数组,有10个元素,p1是数组名,数组元素类型是int*。p1是指针数组
	int* p1[10];
	
    //p2先跟*结合,p2是指针,指向的是数组,数组元素10个,数组元素类型是int。p2是数组指针。
	//p2可以指向一个数组,该数组有十个元素,每个元素是int类型
	int(*p2)[10];
	//解释:p2先和*结合,说明p2是一个指针变量,然后指针指向的是一个大小为10个整型的数组。
	//所以p2是一个指针,指向一个数组,叫数组指针。
	//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p2先和*结合。
	return 0;
}

2. &数组名 VS 数组名

int main()
{
	int arr[10] = { 0 };

	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	//上方两个打印结果一样,说明数组名就是数组首元素地址
	
	printf("%p\n", arr);//012FF740
	printf("%p\n", arr + 1);//012FF744

	printf("%p\n", &arr[0]);//012FF740
	printf("%p\n", &arr[0] + 1);//012FF744
	
	printf("%p\n", &arr);//012FF740
	printf("%p\n", &arr + 1);//012FF768,增加了十六进制的28,也就是十进制的40字节

	int sz = sizeof(arr);
	printf("%d\n", sz);//40
	//数组名通常表示的都是数组首元素的地址
	//但是有2个例外
	//1. sizeof(数组名),这里的数组名表示的是整个数组,计算的是整个数组的大小,单位是字节
	//2. &数组名,这里的数组名表示的依然是整个数组,所以&数组名取出的是整个数组的大小


	//整形指针是用来存放整形的地址
	//字符指针是用来存放字符的地址
	//数组指针是用来存放数组的地址(即&arr就是取出整个数组的地址)
	int arr[10] = { 0 };
	int* p = arr;//将数组首元素地址存到p
	int(*p2)[10] = &arr;//*p2是指针,需要使用括号,指向一个有10个元素的数组(即[10]),每个元素的类型是int

	return 0;
}

3. 数组指针的使用

指向指针数组的指针
int main()
{
	char* arr[5] = { 0 };
	char* (*pc)[5] = &arr;
	return 0;
}

错误写法
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	
	int(*p)[] = &arr;//错误写法,元素个数不能省略
	int(*p)[10] = &arr;//正确写法

	return 0;
}

不推荐用法
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	
	int(*p)[10] = &arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		//p是指向数组的(p存放的是整个数组的地址),*p的意思是通过数组地址找到数组
		//*p(对p解引用)其实就相当于数组名,数组名又是数组首元素的地址。
		//所以*p本质上是数组首元素的地址
		//int a = 10; int* p = &a; *p->a
		printf("%d ", *(*p + i));//此方法不合理,不推荐
	}
	

	//正常写法
	int* p = arr;//数组名是首元素地址,赋给整形指针p
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}

	return 0;
}

常见用法
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(*p)[5], int r, int c)//指针形式
{
    //p指向的是二维数组的行,p加几就跳过几行。p+i是第i行的地址,对p+i解引用就是拿到第i行。
	//因为*(p+i) <=> *(arr+i) <=> arr[i] <=> p[i]
	//所以*p相当于每行的数组名(每一行是一个一维数组),而每行的数组名又相当于首元素地址。
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (j = 0; j < c; 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);
	print2(arr, 3, 5);

	//int arr[3][5];
	//arr数组名表示数组首元素地址
	//二维数组首元素是第一行
	//此时arr表示数组第一行的地址
	//第一行是5个整型,每个元素为int的数组
	//所以arr是5个元素的整型数组的地址
	//总结:二维数组传参传的是首元素(二维数组的第一行)的地址,第一行是一个一维数组,按照一维数组的数组指针方式写即可
	return 0;
}

看看下面代码的意思:
int main()
{
	int arr[5];//整型数组
	int* parr1[10];//整形指针数组
	int(*parr2)[10];//数组指针

	int(*parr3[10])[5];//parr3先和[10]结合,它是数组。
    //parr3[10]拿走就剩下int(*)[5](这是一个数组指针)。所以parr3是存放数组指针的数组
	//它声明了一个长度为10的数组 parr3,其中每个元素都是一个指向长度为5的一维数组的指针。
	//int(*parr3[10]) 表示一个长度为10的数组,每个元素都是一个指向 int[5] 类型的指针。
    //也就是说,parr3 是一个指针数组,它可以存储指向长度为5的一维数组的指针。
	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(*parr3[10])[5] = { &arr1,&arr2,&arr3 };
    //[5]表示每个元素都是一个指向长度为5的一维数组的指针,parr3[10]表示这个数组有10个元素
	//parr3[10]是一个数组,
	//*parr3[10]是一个指针数组,有10个元素
	//int(*parr3[10])[5],每个元素是指针且指向一个有5个元素的数组(即数组指针),且该数组类型为int。所以parr3是存放数组指针的数组
	return 0;
}

第四章:数组参数、指针参数

1. 一维数组传参

void test(int arr[])//参数写成数组形式。可行,元素个数可以不写
{}
void test(int arr[10])//可行
{}
void test(int* arr)//可行,数组名是首元素地址,首元素是int类型,所以可以用指针接收
{}
void test2(int* arr[20])//可行,用数组形式接收
{}
void test2(int** arr)//可行,因为首元素地址是int*的地址,所以用二级指针来存放一级指针变量的地址
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };//arr2有20个元素,每个元素是int*
	//arr2是数组名,也是首元素地址,首元素是int*的地址
	test(arr);
	test2(arr2);
}

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])//可行,*arr是指针,指向的是5个元素,每个元素是int类型的数组
{}
void test(int** arr)//不可行,首元素地址是一个一维数组的地址,不能用二级指针接收
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

3. 一级指针传参

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;//arr是数组名,也是首元素的地址(即1的地址,是个整形)
	int sz = sizeof(arr) / sizeof(arr[0]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}


//如果函数的参数部分是指针,传参可以传什么
void print2(int* p)
{}
int main()
{
	int a = 10;
	int* ptr = &a;
	print2(&a);//如果函数的参数部分是指针,可以传地址
	print2(ptr);//可以传指针变量

	int arr[10];
	print2(arr);//可以传数组名
	return 0;
}

4. 二级指针传参

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


//如果函数的形式参数是二级指针,调用函数的时候可以传什么实参
test(int** p)
{}
int main()
{
	int* p1;
	int** p2;
	int* arr[10];//指针数组
	test(&p1);
	test(p2);
	test(arr);//数组名是首元素地址,首元素是int*类型(即一级指针),所以一级指针的地址要用二级指针来接收
	return 0;
}

第五章:函数指针

1. 定义

函数指针 - 可以跟数组指针类比。数组指针是指向数组的指针。
函数指针就是指向函数的指针

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//&函数名 - 取出的是函数地址
	printf("%p\n", &Add);//函数名和&函数名打印的地址一样
	printf("%p\n", Add);
	//对于函数来说,&函数名和函数名都是函数的地址
	
	int(*pf)(int, int) = &Add;
	//*pf是指针,
	//最后一个()说明该指针指向函数,指向的函数的参数类型是(int, int)
	//第一个int是函数返回的类型
	return 0;
}

2. 函数指针有什么用

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//通过整形指针来理解
	int a = 10;
	int* pa = &a;
	*pa = 20;
	printf("%d\n", *pa);
	
    //指针变量存了地址以后,可以通过地址找到该对象访问或修改
	//所以函数指针也是一样
    int(*pf)(int, int) = &Add;
    int ret = (*pf)(2, 3);
    printf("%d\n", ret);
	return 0;
}

 3. 函数指针的使用

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int ret = Add(2, 3);//直接调用

	int(*pf)(int, int) = &Add;
	int ret = (*pf)(2, 3);//通过函数地址间接调用
	//(*pf)相当于对函数指针解引用找到函数,然后调用函数
	//调用函数需要传参,(2, 3)是传参

	int ret = pf(2, 3);
	//第二种写法:可以省略*。
	//如果要加*必须放在括号里,即(*pf)。
	//但这里的*无实际作用,只是为了可读性,即pf是个指针。但带*是标准写法

	//为什么可以不写*
	//因为&Add和Add都可以表示函数的地址。
    //Add可以赋给pf,Add又可以直接调用函数,所以pf同样可以(因为pf保存也是函数地址)
	int(*pf)(int, int) = &Add;
	int(*pf)(int, int) = Add;
	int ret = Add(2, 3);
	int ret = pf(2, 3);
	printf("%d\n", ret);
	return 0;
}

4. 函数指针作用的示例

int Add(int x, int y)
{
	return x + y;
}
void calc(int(*pf)(int, int))//因为接收的是函数的地址,所以形参应该是函数指针
{
	int a = 3;
	int b = 5;
	int ret = pf(a, b);//通过函数指针调用Add函数
	printf("%d\n", ret);
}
int main()
{
	calc(Add);//函数名传参,函数名就是函数地址
	return 0;
}

5. 函数指针代码

代码1
int main()
{
	//代码1
	//以下代码是一次函数的调用,调用的是0作为地址处的函数。
	//1. 把0强制类型准换为:无参,返回类型是void的函数地址
	//2. 调用0地址处的这个函数
	
    ( *( void (*)() )0 )();
	
    //void(*p)();//p是函数指针
	//void(*)();//是一种函数指针类型
	//(void(*)())0;//0是整数,但前面是函数指针类型,说明要强制类型转换(将整形转换为函数指针类型),而函数指针类型存的是函数地址,所以可以认为0是个地址
	//这里是把0强制类型转换成函数指针类型,这时候(void(*)())0就是一个函数的地址
	//也就是0地址放一个函数,函数没有参数,返回类型为void
	//(*(void (*)())0);//这里是用第一颗*解引用找到函数
	//(*(void (*)())0)();//又因为这个函数没有参数,所以传参时什么都不传

	
	//它执行了一个函数指针的调用。代码中的(void (*)()) 表示一个函数指针类型,0 表示空指针。
	//整个表达式(*(void (*)())0)() 将空指针转换为函数指针,并调用该函数。


	return 0;
}

代码2
typedef unsigned int unit;//通过此方法重命名函数指针
typedef void(*)(int) pf_t;//此写法错误,要将pf_t放到*旁边
typedef void(*pf_t)(int);//把void(*)(int)类型重命名为pf_t。pf_t是一个类型
int main()
{
	//以下代码是一次函数声明,signal是函数名,。
	//声明的函数的第一个参数类型是int,
    //第二个参数类型是函数指针(该函数指针指向的函数参数是int,返回类型是void)。
	//signal函数的返回类型是一个函数指针(该函数指针指向的函数参数是int,返回类型是void)
	void ( *signal( int, void(*)(int) ) )(int);
	//signal先和后面的括号结合,所以是函数名
	//signal(int, void(*)(int));//后面的int和void(*)(int)是参数类型。
    //第二个类型是函数指针。这里是函数的声明,只需要写函数返回类型,函数名和参数类型
	//把上方代码去掉,还剩void(*)(int)这是一个函数指针类型,说明signal函数的返回类型是函数指针类型

	//上方代码不好理解,需要简化,通过typedef重命名
	pf_t signal(int, pf_t);//signal函数的参数第一个是int,第二个是函数指针类型,返回类型是函数指针类型
	return 0;
}

第六章:函数指针数组

1. 定义

把函数指针放在数组中,就是函数指针的数组

2. 函数指针数组用途

计算器示例
void menu()
{
	printf("***************************\n");
	printf("****  1. add   2. sub  ****\n");
	printf("****  3. mul   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 input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);

	    //int x = 0;
	    //int y = 0;
	    //int ret = 0;
		//输入操作数代码放在这里会有问题
		//如果选择的不是1234,依然要输入操作数,这不符合逻辑,所以要放进switch里
		
		switch (input)
		{
		case 1:
			printf("请输入两个操作数:>");//但是放进switch里有新问题,代码冗余。case1234中只有一句代码是不重复的
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

使用函数指针数组的实现:
//假设还想实现x&y,x^y,x|y,x&&y,x||y,x>>y,x<<y等功能
//需要在菜单函数增加选项,并增加对应的计算函数,还有主函数中的case选项也要增加
void menu()
{
	printf("***************************\n");
	printf("****  1. add   2. sub  ****\n");
	printf("****  3. mul   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;
	int ret = 0;
	int input = 0;
	//使用函数指针数组,可以轻易的增加或删除想要的计算功能  (转移表)
	int(*pfArr[])(int, int) = { 0,Add,Sub,Mul,Div };//0占位使用,为了让Add和下标1对齐

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
		}
		else if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);//input是几就访问这个数组下标为几的元素
			printf("%d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);
	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;
}
int main()
{
	//函数指针
	int(*pf)(int, int) = Add;

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

	//指向【函数指针数组】的指针
	int(*(*ppfArr)[5])(int, int) = &pfArr;
	//pfArr先和*结合,说明是指针。
	//该指针指向的是[5],说明指向了数组
	//数组的元素类型是int(*)(int, int),即函数指针
	return 0;
}

第八章:回调函数

1. 定义

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

2. 计算器(回调函数版本)

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

3. 复习冒泡排序

下方函数只能排序整型数组

//假设数组是0,1,2,3,4,5,6,7,8,9 该函数还是要两两比较,只是没有交换。
//并且还是要遍历9轮。但是如果第一轮遍历完发现一个都没有交换,说明这个数组已经是排序好了。
void bubble_sort(int arr[], int sz)
{
	int i = 0;//排序的轮数
	for (i = 0; i < sz - 1; i++)//总共进行元素个数sz-1轮排序
	{
		int j = 0;//元素下标
		for (j = 0; j < sz - 1 - i; j++)//每轮交换排序的次数比上一轮少1次。第一轮交换元素个数sz-1次(第一次i=
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

优化版本
void bubble_sort(int arr[], int sz)
{
	int i = 0;//排序的轮数
	for (i = 0; i < sz - 1; i++)//总共进行元素个数sz-1轮排序
	{
		int flag = 1;//假设数组是拍好序的
		int j = 0;//元素下标
		for (j = 0; j < sz - 1 - i; j++)//每轮交换排序的次数比上一轮少1次。第一轮交换元素个数sz-1次(第一次i=
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag = 0;//一旦有元素交换了,就要把flag置成0
			}
		}
		if (flag == 1)//如果flag还等于1,说明数组已经有序
		{
			break;
		}
	}
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	//把数组排成升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	//打印排序好的数组
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}

	return 0;
}

4. 演示qsort函数使用

#include <stdlib.h>
//使用库函数qsort - 可以排序任意类型的数据
void qsort(void* base, //要排序的数据的起始位置
		   size_t num, //待排序的数据元素个数
		   size_t width,//待排序数据元素的大小(单位是字节)
		   //因为不同数据比较方法不同,不仅限于>或<之类。所以要把比较方法提取出来
		   //e1是要比较的第1个元素,e2是要比较的第2个元素,e1和e2是要比较元素的地址
		   int(__cdecl* cmpare)(const void* elem1, const void* elem2)//函数指针-比较函数
		   );
__cdecl - 函数调用约定,按c的方式去调用,可以去掉


//比较两个整形元素
//e1指向一个整数,e2指向另外一个整数
//e1和e2都是指针,存的都是地址
int cmp_int(const void* e1, const void* e2)
{
	//void*指针是不能直接解引用
	//void*指针的作用
	//int a = 10;
	//char* pa = &a;//类型不匹配,编译器会报警告
	//void* pv = &a;//void*是无具体类型的指针,可以接收任意类型的地址
	//void* 是无具体类型的指针,所以不能解引用操作,也不能+-整数操作
	if (*(int*)e1 > *(int*)e2)
	    return 1;
    else if (*(int*)e1 == *(int*)e2)
	    return 0;
    else
	    return -1;

	//等效上方
	return (*(int*)e1 - *(int*)e2);//升序
	//return (*(int*)e2 - *(int*)e1);//降序
    <0	The element pointed to by e1 goes before the element pointed to by e2
    0	The element pointed to by e1 is equivalent to the element pointed to by e2
    >0	The element pointed to by e1 goes after the element pointed to by e2
}
int main()
{
	int arr[] = { 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]);
	}

	return 0;
}

5. 使用qsort排序结构体

struct Stu
{
	char name[20];
	int age;
};
#include <string.h>
int cmp_stu_by_name(const void* e1, const void* e2)//比较名字
{
	//不能直接使用e1,因为它是void*类型,所以需要强制类型转换成结构体指针
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);//两个名字是两个字符串,所以使用strcmp函数
	//strcmp函数返回值是大于0 等于0 小于0的数字
}
int cmp_stu_by_age(const void* e1, const void* e2)//比较年龄
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//两个名字是两个字符串,所以使用strcmp函数
}
void test2()
{
	//测试使用qsort来排序结构体
	struct Stu s[] = { {"zhangsan", 18},{"lisi", 30},{"wangwu", 25} };
	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()
{
	test2();

6. 使用回调函数,模拟实现qsort(采用冒泡的方式)

int cmp_int(const void* e1, const void* e2)
{
	return (*(int*)e1 - *(int*)e2);//升序
	//return (*(int*)e2 - *(int*)e1);//降序
}

//参考qsort函数重新设计冒泡排序
void Swap(char* buf1, char* buf2, int width)
{
	//该函数是把buf1指向位置和buf2指向位置 向后width宽度字节的内容交换
	int i = 0;
	//width是数据的宽度,也是一个数据所占字节
	for (i = 0; i < width; i++)//这里一次交换一个字节,如果是int,width=4,交换4次
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

void bubble_sort(void* base, int sz, int width, int(*cmp)(const void* e1, const void* e2))
{
	int i = 0;//排序的轮数
	for (i = 0; i < sz - 1; i++)//总共进行元素个数sz-1轮排序
	{
		int flag = 1;//假设数组是拍好序的
		int j = 0;//元素下标
		for (j = 0; j < sz - 1 - i; j++)//每轮交换排序的次数比上一轮少1次。第一轮交换元素个数sz-1次(第一次i=0)
		{
			//base是void*,void* 是无具体类型的指针,所以不能解引用操作,也不能+-整数操作
			//所以要强制类型转换。如果用int*一次跳过太多字节,所以用char*提高精度
			//这里用下标j来乘以宽度就可以决定一次跳过几个字节
			//大于0说明前一个数比后一个数大,不符合升序,要交换
			//这里是调用比较函数,传的参数是代比较两个元素的地址
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				//交换
				//光有起始位置还不够,还需要知道宽度,即多少字节
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
				flag = 0;//只要产生交换就要将flag置成0,避免下方跳出循环
			}
		}
		if (flag == 1)//如果flag还等于1,说明数组已经有序
		{
			break;
		}
	}
}

void test3()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	//int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
	//把数组排成升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);//最后一个参数是把函数传给函数指针
	//打印排序好的数组
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}


//使用自定义函数bubble_sort(模拟qsort)来排序结构体
int cmp_stu_by_name(const void* e1, const void* e2)//比较名字
{
	//不能直接使用e1,因为它是void*类型,所以需要强制类型转换成结构体指针
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);//两个名字是两个字符串,所以使用strcmp函数
	//strcmp函数返回值是大于0 等于0 小于0的数字
}
int cmp_stu_by_age(const void* e1, const void* e2)//比较年龄
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//两个名字是两个字符串,所以使用strcmp函数
}
void test4()
{
	
	struct Stu s[] = { {"zhangsan", 18},{"lisi", 30},{"wangwu", 25} };
	int sz = sizeof(s) / sizeof(s[0]);
	//bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);//年龄升序
	bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);//名字升序

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

第九章:指针和数组笔试题解析

一维数组

int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));//16
	//sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小,单位是字节
	printf("%d\n", sizeof(a + 0));// 4/8
	//a不是单独放在sizeof内部,也没有取地址,所以a就是首元素地址,a+0还是首元素地址
	//是地址,大小就是4/8个字节。a <==> &a[0],a+0 <==> &a[0]+0
	printf("%d\n", sizeof(*a));// 4
	//a是首元素地址,对首元素地址解引用得到首元素,*a=1,sizeof(1)表示1个整型大小
	//a <==> &a[0],*a <==> *&a[0],*&a[0] <==> a[0]
	printf("%d\n", sizeof(a + 1));//4
	//a+1指向第二个元素的地址
	printf("%d\n", sizeof(a[1]));//4
	//计算的是第二个元素的所占内存空间的大小
	printf("%d\n", sizeof(&a));// 4/8
	//&a取出的是整个数组的地址
	printf("%d\n", sizeof(*&a));//16
	//&a(int (*)[4])取出整个数组的地址,数组指针解引用就访问整个数组,等价于sizeof(a)
	//或者理解为&和*抵消
	printf("%d\n", sizeof(&a + 1));// 4/8
	//&a取出整个数组的地址,+1跳过整个数组,但依然是地址
	//&a+1 是从数组a的地址向后跳过了一个(4个整型元素)数组的大小
	printf("%d\n", sizeof(&a[0]));// 4/8
	//&a[0]是第一个元素的地址,计算地址的大小
	printf("%d\n", sizeof(&a[0] + 1));// 4/8
	//计算第二个元素地址的大小
	return 0;
}

字符数组1

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));//6
	//数组名单独放在sizeof中,表示整个数组,计算的是整个数组的大小
	printf("%d\n", sizeof(arr + 0));// 4/8
	//arr+0是数组首元素地址
	printf("%d\n", sizeof(*arr));// 1
	//*arr是数组首元素,大小是1字节。*arr <==> *(arr+0) <==> arr[0]
	printf("%d\n", sizeof(arr[1]));// 1
	//计算第二个元素的大小
	printf("%d\n", sizeof(&arr));// 4/8
	//&arr取出整个数组的地址
	printf("%d\n", sizeof(&arr + 1));// 4/8
	//&arr取出整个数组的地址,+1跳过整个数组的地址
	printf("%d\n", sizeof(&arr[0] + 1));// 4/8
	//第二个元素的地址
	
    //strlen
    //size_t strlen(const char* string);
    //strlen参数是指针,所以传过去的是地址
    printf("%d\n", strlen(arr));//随机值(>=6)
	//数组里没有\0,要找到/0才停下来
	printf("%d\n", strlen(arr + 0));//随机值
	//arr是首元素地址,+0还是首元素地址
	printf("%d\n", strlen(*arr));//野指针
	//arr是首元素地址,首元素地址解引用得到是首元素
	//此代码相当于strlen('a'),a的ASCII码值是97,所以又相当于strlen(97),相当于把97作为地址传给strlen
	//97这种地址不能随便传,而是空间开辟给你的时候,才是有效的。知道地址还不行,要分配给你才可以访问
	//这时97就变成野指针,程序报错
	printf("%d\n", strlen(arr[1]));//野指针
	//相当于strlen('b'),又相当于strlen(98)。同上方一样问题
	printf("%d\n", strlen(&arr));//随机值
	//&arr取出整个数组的地址,不过也是从首元素地址开始
	//虽然arr和&arr类型不一样,但都是传给strlen,所以都是从首元素开始
	printf("%d\n", strlen(&arr + 1));//随机值(随机值-6)
	//&arr取出整个数组,+1跳过整个数组,从数组后方开始找/0
	printf("%d\n", strlen(&arr[0] + 1));//随机值(随机值-1)
	//&arr[0]首元素地址,+1是第二个元素地址,从这开始向后找/0

   	return 0;
}

字符数组2

//strlen是求字符串长度的,关注的是字符串中的\0,计算的是\0之前出现的字符的个数
//strlen是库函数,只针对字符串
//sizeof只关注占用内存空间大小,不在乎内存中放的是什么
//sizeof是操作符
int main()
{
	char arr[] = "abcdef";// [abcdef/0]
	printf("%d\n", sizeof(arr));//7
	//数组名单独放到sizeof中,表示整个数组,求数组大小
	printf("%d\n", sizeof(arr + 0));// 4/8
	//arr是数组名,首元素地址,+0还是首元素地址
	printf("%d\n", sizeof(*arr));//1
	//arr是数组名,解引用得到首元素
	printf("%d\n", sizeof(arr[1]));//1
	//求第二个元素大小
	printf("%d\n", sizeof(&arr));// 4/8
	//&arr取出整个数组的地址
	printf("%d\n", sizeof(&arr + 1));// 4/8
	//&arr取出整个数组地址,+1跳过整个数组,即数组后面的地址
	printf("%d\n", sizeof(&arr[0] + 1));// 4/8 
	//&arr[0]+1 第二个元素的地址,b的地址

	
	printf("%d\n", strlen(arr));//6
	//arr是数组名,是首元素地址
	printf("%d\n", strlen(arr + 0));//6
	//arr是数组名,也是首元素地址,+0还是首元素地址
	printf("%d\n", strlen(*arr));//报错
	//*arr得到的是首元素
	printf("%d\n", strlen(arr[1]));//报错
	printf("%d\n", strlen(&arr));//6
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//5
	return 0;
}

字符指针

int main()
{
	char* p = "abcdef";//把首字符地址放到p里,内存里存放的是abcdef\0,这是一个常量字符串。p和字符串在内存中各自有自己的独立空间
	printf("%d\n", sizeof(p));// 4/8
	//p是指针变量
	printf("%d\n", sizeof(p + 1));// 4/8
	//p+1仍然是地址
	printf("%d\n", sizeof(*p));//1
	//对char*类型指针解引用,得到一个字符
	printf("%d\n", sizeof(p[0]));//1
	//p[0] <==> *(p+0)
	printf("%d\n", sizeof(&p));// 4/8
	//&p是二级指针
	printf("%d\n", sizeof(&p + 1));// 4/8
	printf("%d\n", sizeof(&p[0] + 1));// 4/8
	//p[0]是第一个字符,&取地址就是a的地址,+1是b的地址

	printf("%d\n", strlen(p));//6
	//p存放的是a的地址,向后找\0
	printf("%d\n", strlen(p + 1));//5
	printf("%d\n", strlen(*p));//报错
	//*p是a
	printf("%d\n", strlen(p[0]));//报错
	printf("%d\n", strlen(&p));//随机值
	//从指针p的位置开始向后找\0
	printf("%d\n", strlen(&p + 1));//随机值
	printf("%d\n", strlen(&p[0] + 1));//5
	//p[0]指向第一个元素,&取地址就是a的地址,+1是b的地址
	return 0;
}

二维数组

int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//48
	//sizeof(数组名),计算整个数组大小
	printf("%d\n", sizeof(a[0][0]));//4
	//第一行第一列,即第一个元素
	printf("%d\n", sizeof(a[0]));//16
	//a[0]是第一行数组名,所以sizeof(a[0])就是sizeof(数组名),计算第一行的大小
	printf("%d\n", sizeof(a[0] + 1));//4
	//这里a[0]没有单独放在sizeof里,表示的是第一行第一个元素的地址,等价于&a[0][0],+1表示第一行第二个元素的地址
	printf("%d\n", sizeof(*(a[0] + 1)));//4
	//a[0]+1表示第一行第二个元素的地址,解引用就得到该整形元素
	printf("%d\n", sizeof(a + 1));// 4/8
	//a虽然是二维数组的地址,但是并没有单独放在sizeof内部,也没有取地址
	//a表示首元素地址,二维数组的首元素是它的第一行,a就是第一行的地址
	//a+1是第二行的地址
	printf("%d\n", sizeof(*(a + 1)));//16
	//对第二行的地址解引用,得到第二行
	//*(a+1) <==> a[1],所以sizeof(*(a+1)) <==> sizeof(a[1])
	printf("%d\n", sizeof(&a[0] + 1));// 4/8
	//a[0]是第一行数组名,&取地址就取出第一行的地址,+1指向第二行
	//所以(a+1) <==> (&a[0]+1)
	printf("%d\n", sizeof(*(&a[0] + 1)));//16
	//(&a[0] + 1)是第二行的地址,解引用得到第二行
	//*(a + 1) <==> *(&a[0] + 1)
	printf("%d\n", sizeof(*a));// 16
	//a表示首元素地址,二维数组首元素是第一行,对第一行的地址解引用,得到第一行
	printf("%d\n", sizeof(a[3]));//16
	//a[3]可以理解为第四行数组名,而sizeof(数组名)是计算数组大小
	//这里是根据二维数组的类型去分析,不是真的访问,本质上sizeof(a[3])等价于sizeof(a[0])
	//int a = 10;
	//sizeof(int);//这个直接告诉sizeof 是什么类型
	//sizeof(a);//这种是分析a的类型
	return 0;
}

总结:

数组名的意义:

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

第十章:指针笔试题

笔试题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
	//a是数组名,数组名是首元素地址,+1指向第二个元素
	//ptr是整形指针,指向数组后面,-1就向前移动一个整形,即数组最后一个元素
	return 0;
}

笔试题2

struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
//已知,结构体Test类型的变量大小是20个字节(x86)
int main()
{
	printf("%p\n", p + 0x1);//0x100014
	//p是结构体指针,结构体指针+1跳过一个结构体(即20个字节),而20个字节用十六进制表示是14
	printf("%p\n", (unsigned long)p + 0x1);//0x100001
	//这里是把十六进制的100000转换成长整型,即十进制的1048576,+1就是1048577,再转换成十六进制就是100001
	printf("%p\n", (unsigned int*)p + 0x1);//0x100004
	//p被转换成整形指针,+1跳过4个字节,十进制的4用十六进制表示也是4,即100004
	return 0;
}

笔试题3

int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	//&a取出整个数组的地址,+1跳过数组,再转换成整形指针
	int* ptr2 = (int*)((int)a + 1);
	//a是数组名,也是首元素地址,将这个值准换成整形
	//假设a=0x0012ff40,a+1=0x0012ff44,(int)a+1=0x0012ff41
	//将a的地址强制转换成整形后再+1,就是数值+1
	//再转换成指针,两个地址相差1,意味着向后移动了1个字节
	//又因为vs是小端存储模式,数组a在内存的存储情况如下:
	//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
	//所以ptr2指向了01后面的00,而ptr是整形指针,向后访问4个字节,就是00 00 00 02
	//因为小端存储是倒序存放,所以将内存中的00 00 00 02取出后是02 00 00 00

	printf("%x,%x", ptr1[-1], *ptr2);//4  02000000
	//prt1[-1] <==> *(prt1+(-1)) <==> *(ptr1-1)
	//prt1指向数组后面,向前移动一个整形指向4,4在内存中是小端存储(即倒序存放),即04 00 00 00
	//取出来,要倒序,即00 00 00 04,但打印时前面的0省略
	return 0;
}

笔试题4

int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	//这里是逗号表达式。逗号表达式会从左向右以此计算,整个表达式结果是最后一个表达式的结果
	//(0, 1)结果是1;(2, 3)结果是3;(4, 5)结果是5;
	//a数组实际结果是{ {1,3},{5,0},{0,0} }
	int* p;
	p = a[0];
	//a[0]是的第一行的数组名,它既没有放到sizeof内部,也没有&取地址,所以数组名表示首元素地址
	//即第一行第一个元素1的地址,即a[0][0]的地址,&a[0][0]
	printf("%d", p[0]);//1
	//p[0] <==> *(p+0)
	return 0;
}

笔试题5

int main()
{
	int a[5][5];
	int(*p)[4];
	p = a;
	//a是数组名,数组名是首元素地址,二维数组首元素是第一行,即第一行的地址。第一行是5个整型的一维数组
	//a的类型是int(*)[5]。p指向二维数组a第一行第一个元素的位置
	//p+1或对p解引用一次访问4个整型
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);//FFFFFFFC  -4
	//p[4][2] <==> *(*(p+4)+2)
	//p[4][2]相当于a[3][3],两个指针相减得到指针和指针之间的元素个数,即4个
	//地址的结果是-4
	//10000000000000000000000000000100 - -4原码
	//11111111111111111111111111111011 - -4反码
	//11111111111111111111111111111100 - -4补码
	//把-4的补码当做地址打印ff ff ff fc
	//注意:整型地址+1加的是一个整型
	return 0;
}

笔试题6

int main()
{
	int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int* ptr1 = (int*)(&aa + 1);
	//&数组名取出整个数组,+1跳过整个数组,ptr1指向aa数组后面
	int* ptr2 = (int*)(*(aa + 1));
	//aa是数组名,是首元素地址,二维数组首元素是第一行,aa是第一行地址,+1跳过一行指向第二行
	//解引用第二行地址,得到第二行,也相当于拿到第二行数组名,*(aa + 1) <==> aa[1]
	//而aa[1]既没有放在sizeof内部,也没有取地址,所以又相当于第二行第一个元素的地址
	//在转换成int*,指向第二行第一个元素
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//10 5
	return 0;
}

笔试题7

int main()
{
	char* a[] = { "work","at","alibaba" };
	//a是字符指针数组,a存放的是work,at,alibaba首字符的地址
	char** pa = a;//等价于char* *pa,第二颗*说明pa是指针,第一颗*说明pa指向的对象是char*类型
	//a是数组名,数组名是首元素地址,即work首字符的地址
	pa++;//等价于pa+1 
	//pa指向的是char*,pa+1跳过一个char*
	printf("%s\n", *pa);//at
	return 0;
}

笔试题8

int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	//c是指针数组,c存放的是ENTER,NEW,POINT,FIRST首字符的地址
	char** cp[] = { c + 3,c + 2,c + 1,c };
	//cp是二级指针数组,存放的是c+3的地址(即FIRST);c + 2的地址(即POINT);c + 1的地址(即NEW);c的地址(即ENTER);
	char*** cpp = cp;
	//cpp存放的是cp的地址,cp是数组名,即首元素地址,即c+3
	printf("%s\n", **++cpp);//point
	//++cpp指向了cp数组的第二个元素,即c+2
	//第一次解引用得到了cp数组的内容c+2,第二次解引用得到c+2指向的内容point首字符p的地址
	printf("%s\n", *-- * ++cpp + 3);//er
	//cpp此时指向cp数组的第二个元素,即c+2,再++(即++cpp)就指向第三个元素,即c+1
	//*++cpp得到就是c+1,前面的--(即-- * ++cpp)就是让c+1再-1,得到c,而c指向的是enter首字符e的地址
	//对上面的地址(*-- * ++cpp)解引用,得到enter,在+3就是跳过3个字符,最后得到er
	printf("%s\n", *cpp[-2] + 3);//st
	//cpp此时指向cp数组的第三个元素,即c+1(此时c+1已经被改成c)
	//cpp-2(等价于* *(cpp-2))指向cp数组第一个元素,解引用得到c+3,再解引用得到first首字符f的地址,在+3就是跳过3个字符,最后得到st
	printf("%s\n", cpp[-1][-1] + 1);//ew
	//cpp[-1][-1]等价于*(*(cpp-1)-1)
	//cpp此时还是指向cp数组的第三个元素,即c+1(此时已经被改成c)
	//cpp-1指向cp数组的第二个元素,解引用得到c+2,再-1就是c+1,对c+1解引用得到new首字符n的地址,在+1就是跳过一个字符,最后得到ew
	return 0;
}

作业

第一部分:字符指针

1. 下面关于"指针"的描述不正确的是:(   )

A.当使用free释放掉一个指针内容后, 指针变量的值被置为NULL
B.32位系统下任何类型指针的长度都是4个字节
C.指针的数据类型声明的是指针实际指向内容的数据类型
D.野指针是指向未分配或者已经释放的内存地址
答案:A - free只释放空间,不将指针置为NULL
 

2. 关于下面代码描述正确的是:(   )

char* p = "hello bit";
A.把字符串hello bit存放在p变量中
B.把字符串hello bit的第一个字符存放在p变量中
C.把字符串hello bit的第一个字符的地址存放在p变量中
D.*p等价于hello bit
答案:C - p存放的是第一个字符h的地址,*p得到的是字符h,所以选项B、D错
 

3. 关于数组指针的描述正确的是:(    )

A.数组指针是一种数组
B.数组指针是一种存放数组的指针
C.数组指针是一种指针
D.指针数组是一种指向数组的指针
答案:C - 数组指针不存放数组,只存放数组地址。指针数组是数组

4. 下面哪个是数组指针(   )

A.int** arr[10]
B.int(*arr[10])
C.char* (*arr)[10]
D.char(*)arr[10]
答案:C - ABD数组名都先和[]结合,是数组

5. 下面哪个是函数指针?(   )

A.int* fun(int a, int b);
B.int(*)fun(int a, int b);
C.int (*fun)(int a, int b);
D.(int*)fun(int a, int n);
答案:C - ABD中fun都先和()结合,是函数

6.定义一个函数指针,指向的函数有两个int形参并且返回一个函数指针,返回的指针指向一个有一个int形参且返回int的函数?

下面哪个是正确的?(   )
A.int (*(*F)(int, int))(int)
B.int (*F)(int, int)
C.int(*(*F)(int, int))
D.*(*F)(int, int)(int)
答案:A - (*F)(int, int)是一个函数指针且有两个int形参,把这部分去掉还剩返回类型int(*)(int),这个返回类型又是一个函数指针,且形参是int,返回类型是int

7. 在游戏设计中,经常会根据不同的游戏状态调用不同的函数,我们可以通过函数指针来实现这一功能,下面哪个是:一个参数为int * ,返回值为int的函数指针( )

A.int (*fun)(int)
B.int (*fun)(int*)
C.int* fun(int*)
D.int* (*fun)(int*)
答案:B - A形参是int,C是函数,D返回类型是int*
 

8. 下面哪个代码是错误的?()

int main()
{
	int* p = NULL;
	int arr[10] = { 0 };
	return 0;
}
A.p = arr;
B.int(*ptr)[10] = &arr;
C.p = &arr[0];
D.p = &arr;
答案:D - 数组地址要赋给数组指针,p是整形指针

9. 下面代码关于数组名描述不正确的是( )

int main()
{
	int arr[10] = { 0 };
	return 0;
}
A.数组名arr和& arr是一样的
B.sizeof(arr),arr表示整个数组
C.& arr,arr表示整个数组
D.除了sizeof(arr)和& arr中的数组名,其他地方出现的数组名arr,都是数组首元素的地址。
答案:A - 数组名是首元素地址,&数组名,表示取出整个数组地址

10. 如何定义一个int类型的指针数组,数组元素个数为10个:()

A.int a[10]
B.int(*a)[10]
C.int* a[10];
D.int (*a[10])(int);
答案:C - A是整形数组,B是数组指针,D是函数指针数组(a先和[10]结合,是数组。去掉a[10],剩下的是函数指针)

11. 下面代码的执行结果是( )

int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    char* str3 = "hello bit.";
    char* str4 = "hello bit.";
    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;
}
A.str1and str2 are same str3and str4 are same
B.str1and str2 are same str3and str4 are not same
C.str1and str2 are not same str3and str4 are same
D.str1and str2 are not same str3and str4 are not same
答案:C - str1和str2数组在内存中有各自独立空间,str3和str4指针指向了同一个常量字符串

12. 设有以下函数void fun(int n, char* s) { …… }, 则下面对函数指针的定义和赋值均是正确的是:( )

A.void (*pf)(int,char); pf = &fun;
B.void (*pf)(int n,char* s); pf = fun;
C.void* pf(); *pf = fun;
D.void* pf(); pf = fun;
答案:B - A第二个形参char类型不对;CD pf先和()结合是函数
 

13. 字符串左旋

实现一个函数,可以左旋字符串中的k个字符。
例如:
ABCD左旋一个字符得到BCDA
ABCD左旋两个字符得到CDAB
 

思路一:

1. 把要旋转的第一个字符取出放到临时变量中
2. 把剩下字符往前移
3. 重复上述步骤,直到所有字符旋转完毕(要旋转几个字符,就循环几次)

此方法效率较低,越靠后的字符移动频率越高

#include <string.h>
void left_rotate(char arr[], int k)
{
	int i = 0;
	int len = strlen(arr);
	k %= len;//当k>=len时,有些旋转次数是没有意义的(例如len=6,k=7的实际效果其实就旋转
	for (i = 0; i < k; i++)//旋转几个字符就循环几次
	{
		char tmp = arr[0];//把要旋转第一个字符放到临时变量中
		int j = 0;
		for (j = 0; j < len - 1; j++)//把剩下字符往前移,剩下的字符比字符串长度少1
		{
			arr[j] = arr[j + 1];
		}
		arr[len - 1] = tmp;//最后一个位置放存到临时变量中的第一个字符
	}
}
int main()
{
	char arr[] = "abcdef";//不能是字符串常量
	int k = 0;
	scanf("%d", &k);
	left_rotate(arr, k);
	printf("%s\n", arr);
	return 0;
}

思路二:

1. 将要旋转的部分和剩下的部分分别逆序
示例:a b c d e f旋转两个字符,即 b a | f e d c
2. 再将整个字符串逆序 c d e f a b
此方法用到的都是逆序字符串,所以需要一个逆序字符串函数
逆序字符串需要知道起始位置和结束位置

#include <assert.h>
void reverse(char* left, char* right)
{
	assert(left && right);
	while (left < right)
	{
		char tmp = *left;
		*left = *right;
		*right = tmp;
		left++;
		right--;
	}
}
void left_rotate(char arr[], int k)
{
	int len = strlen(arr);
	k %= len;//这里必须处理k,否则会造成越界访问
	reverse(arr, arr + k - 1);//左
	reverse(arr + k, arr + len - 1);//右
	reverse(arr, arr + len - 1);//整体
}
int main()
{
	char arr[] = "abcdef";//不能是字符串常量
	int k = 0;
	scanf("%d", &k);
	left_rotate(arr, k);
	printf("%s\n", arr);
	return 0;
}

14. 杨氏矩阵

可以理解为二维数组。有一个数字矩阵,矩阵的每行从左到右是递增的,矩阵从上到下是递增的,请编写程序在这样的矩阵中查找某个数字是否存在。
要求:时间复杂度小于O(N);
1 2 3
4 5 6
7 8 9
时间复杂度O(N) - 如果有n个元素,找一个元素最坏的情况需要n次才能找到

思路:
用二维数组最右上角的元素和被查找元素比较,这样可以快速的筛选掉一行或一列

方法一:传值调用
int find_num(int arr[3][3], int r, int c, int k)//此函数只能提示是否找到,但不提供被查找元素的坐标
{
	//找到最右上角元素
	int x = 0;
	int y = c - 1;

	while (x<=r-1 && y>=0)
	{
		if (k < arr[x][y])//被查找数比最右上角元素小,说明不在最后一列,列要前移
		{
			y--;
		}
		else if (k > arr[x][y])//被查找数比最右上角元素大,说明不在第一行,行要下移
		{
			x++;
		}
		else
		{
			//printf("%d %d\n", x, y);//这种提供坐标方式不好,因为只是打印了坐标,却没有真正提供坐标
			return 1;
		}
	}
	return 0;//找不到
}
int main()
{
	int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };
	int k = 0;
	scanf("%d", &k);

	//行和列的传值调用
	int ret = find_num(arr, 3, 3, k);
	printf("%d\n", ret);

	return 0;
}

方法二:结构体返回坐标
struct Point 
{
	int x;
	int y;
};
struct Point find_num(int arr[3][3], int r, int c, int k)
{
	int x = 0;
	int y = c - 1;
	struct Point p = { -1,-1 };
	while (x <= r - 1 && y >= 0)
	{
		if (k < arr[x][y])//被查找数比最右上角元素小,说明不在最后一列,列要前移
		{
			y--;
		}
		else if (k > arr[x][y])//被查找数比最右上角元素大,说明不在第一行,行要下移
		{
			x++;
		}
		else
		{
			p.x = x;
			p.y = y;
			return p;
		}
	}
	return p;//找不到
}
int main()
{
	int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };
	int k = 0;
	scanf("%d", &k);

	//使用结构体作为返回值
	struct Point ret = find_num(arr, 3, 3, k);
	printf("%d %d\n", ret.x, ret.y);
	return 0;
}

方法三:返回型参数
void find_num(int arr[3][3], int* pa, int* pb, int k)
{
	//找到最右上角元素
	//*pa和*pb是数组实际的行和列,x和y是数组下标
	int x = 0;//行
	int y = *pb - 1;//列

	while (x <= *pa - 1 && y >= 0)
	{
		if (k < arr[x][y])//被查找数比最右上角元素小,说明不在最后一列,列要前移
		{
			y--;
		}
		else if (k > arr[x][y])//被查找数比最右上角元素大,说明不在第一行,行要下移
		{
			x++;
		}
		else
		{
			*pa = x;
			*pb = y;
			return;
		}
	}
	*pa = -1;
	*pb = -1;
}
int main()
{
	int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };
	int k = 0;
	scanf("%d", &k);

	//行和列的传址调用
	//注意此方法每次调用前,都要将a和b的值重置为3。因为上一次调用时a和b的值被修改了
	int a = 3;//行
	int b = 3;//列
	int ret = find_num(arr, &a, &b, k);
	if (ret == 1)
		printf("%d %d\n", a, b);
	else
		printf("找不到\n");

	return 0;
}

第二部分:函数指针数组、指针和数组运算

1. 声明一个指向含有10个元素的数组的指针,其中每个元素是一个函数指针,该函数的返回值是int,参数是int* ,正确的是()

A.(int* p[10])(int*)
B.int[10] * p(int*)
C.int (*(*p)[10])(int*)
D.int((int*)[10]) * p
答案:C - A是数组,B和D错误写法


 

2. 关于回调函数描述错误的是(   )

A.回调函数就是一个通过函数指针调用的函数
B.回调函数一般通过函数指针实现
C.回调函数一般不是函数的实现方调用,而是在特定的场景下,由另外一方调用。
D.回调函数是调用函数指针指向函数的函数。
答案:D - 语法不通顺
 

3. 字符串旋转结果

写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串。
例如:给定s1 = AABCD和s2 = BCDAA,返回1
给定s1 = abcd和s2 = ACBD,返回0.
AABCD左旋一个字符得到ABCDA
AABCD左旋两个字符得到BCDAA
AABCD右旋一个字符得到DAABC

思路一:把arr1的所有可能性都旋转完,且逐一跟arr2比较(穷举法)
int is_left_move(char arr1[], char arr2[])
{
	int len = strlen(arr1);
	int i = 0;
	for (i = 0; i < len; i++)
	{
		char tmp = arr1[0];
		int j = 0;
		for (j = 0; j < len-1; j++)
		{
			arr1[j] = arr1[j + 1];
		}
		arr1[len - 1] = tmp;

		if (strcmp(arr2, arr1) == 0)
			return 1;
	}
	return 0;
}

思路二:arr1自己追加自己,这样里面可以找到所有旋转的可能性。然后看arr2是否是arr1的子串
int is_left_move(char arr1[], char arr2[])
{
	int len1 = strlen(arr1);
	int len2 = strlen(arr2);
    //如果arr2比arr1短,依然可能是arr1的子串,但肯定不是arr1旋转得到的,所以要先判断两个字符串是否相等
    //只有两个字符串长度相等,才考虑旋转可能性
	if (len1 != len2)
		return 0;

	strncat(arr1, arr1, len1);
	if (strstr(arr1, arr2) == NULL)
		return 0;
	else
		return 1;
	//char* ret = strstr(arr1, arr2);
	//if (ret == NULL)
	//	return 0;
	//else
	//	return 1;;
}
int main()
{
	char arr1[20] = "abcdef";
	char arr2[] = "cdefab";

	//判断arr2中的字符串是否可以通过arr1中的字符串旋转得到
	int ret = is_left_move(arr1, arr2);
	if (ret == 1)
		printf("OK\n");
	else
		printf("NO\n");

	return 0;
}

4. 下面test函数设计正确的是:(   )

char* arr[5] = { "hello", "bit" };
test(arr);
A.void test(char* arr);
B.void test(char** arr);
C.void test(char arr[5]);
D.void test(char* arr[5]);
答案:BD - 数组名传参,数组名相当于首元素地址,首元素是char*类型,char*的地址是char**,所以B选项正确;D选项是数组形式的形参。

5.下面程序的结果是:( )

int main()
{
	int aa[2][5] = { 10,9,8,7,6,5,4,3,2,1 };
	int* ptr1 = (int*)(&aa + 1);
	int* ptr2 = (int*)(*(aa + 1));
	printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
	return 0;
}
A 1, 6
B 10, 5
C 10, 1
D 1, 5
答案:A - &aa取出数组地址,+1跳过整个数组,强制转换为int*,-1就是数组数最后一个元素
         aa是数组首元素地址,二维数组首元素是第一行,+1跳过一行,强制转换为int*,-1就是第一行最后一个元素

6. 下面代码中print_arr函数参数设计哪个是正确的?( )

int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
print_arr(arr, 3, 5);
A.void print_arr(int arr[][], int row, int col);
B.void print_arr(int* arr, int row, int col);
C.void print_arr(int(*arr)[5], int row, int col);
D.void print_arr(int(*arr)[3], int row, int col);
答案:C - arr是数组名,数组名是首元素地址,二维数组首元素地址是第一行的地址

7.下面程序的结果是:( )

int main()
{
	int a[5] = { 5, 4, 3, 2, 1 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;
}
A 5, 1
B 4, 1
C 4, 2
D 5, 2
答案:B - &a取出数组地址,+1跳过数组,强制转换为int*,-1即数组最后一个元素
         a数数组名,数组名是首元素地址,+1跳过一个元素

8. 矩阵转置

KiKi有一个矩阵,他想知道转置后的矩阵(将矩阵的行列互换得到的新矩阵称为转置矩阵),请编程帮他解答。
输入描述:
第一行包含两个整数n和m,表示一个矩阵包含n行m列,用空格分隔。(1≤n≤10, 1≤m≤10)
从2到n + 1行,每行输入m个整数(范围 - 231~231 - 1),用空格分隔,共输入n * m个数,表示第一个矩阵中的元素。
输出描述:输出m行n列,为矩阵转置后的结果。每个数后面有一个空格。
输入:2 3
     1 2 3
     4 5 6
输出:
1 4
2 5
3 6

自己的方法:转置后,元素的横纵坐标互换,例如元素2的坐标由01变为10
老师方法思路:控制行的变量和控制列的变量互换
 

int main() {
    int r = 0;
    int c = 0;
    scanf("%d %d", &r, &c);
    //int arr[r][c];//C99变长数组
    int arr[10][10] = { 0 };
    int i = 0;
    int j = 0;
    for (i = 0; i < r; i++) {
        for (j = 0; j < c; j++) {
            scanf("%d ", &arr[i][j]);
        }
    }

    for (i = 0; i < c; i++) {
        for (j = 0; j < r; j++) {
            printf("%d ", arr[j][i]);
        }
        printf("\n");
    }

    return 0;
}

9. 上三角矩阵判定

上三角矩阵即主对角线以下的元素都为0的矩阵,主对角线为从矩阵的左上角至右下角的连线。
输入描述:第一行包含一个整数n,表示一个方阵包含n行n列,用空格分隔。(1≤n≤10)
从2到n + 1行,每行输入n个整数(范围 - 231~231 - 1),用空格分隔,共输入n * n个数。
输出描述:一行,如果输入方阵是上三角矩阵输出"YES"并换行,否则输出"NO"并换行。
自己的方法:遍历三角矩阵,利用变量监视,如果有不等于0就终止循环并输出NO

int main() {
    int n = 0;
    scanf("%d ", &n);

    //int arr[n][n];//C99变长数组
    int arr[10][10];
    int i = 0;
    int j = 0;
    for (i = 0; i < n; i++) {
        for (j = 0; j < n; j++) {
            scanf("%d ", &arr[i][j]);
        }
    }

    //方法一:
    int flag = 1;//表示是上三角矩阵
    for (i = 1; i < n; i++) {
        for (j = 0; j < i; j++) {
            if (arr[i][j] != 0) {
                flag = 0;
                break;
            }
        }
        if (flag == 0) {
            printf("NO\n");
            break;
        }
    }
    if (flag == 1)
        printf("YES\n");

    //方法二:
    int flag = 1;
    for (i = 1; i < n && flag == 1; i++) {
        for (j = 0; j < i; j++) {
            if (arr[i][j] != 0) {
                flag = 0;
                goto end;
            }
        }
    }
end:
    if (flag == 1)
        printf("YES\n");
    else
        printf("NO\n");

    //方法三:上三角矩阵加起来等于0就是,不等于0就不是
    return 0;
}

10. 有序序列判断

输入一个整数序列,判断是否是有序序列,有序,指序列中的整数从小到大排序或者从大到小排序(相同元素也视为有序)
输入描述:
第一行输入一个整数N(3≤N≤50)。
第二行输入N个整数,用空格分隔N个整数。
输出描述:输出为一行,如果序列有序输出sorted,否则输出unsorted。

思路:
使用两个flag,flag1监视升序,flag2监视降序。
初始化flag1和flag2为0。
如果后一个数比前一个数大,flag1置为1;如果后一个数比前一个数小,flag2置为1。
最后如果flag1+flag2等于1说明有序
 

#include <math.h>
#include <stdio.h>
int main() {
    int n = 0;
    scanf("%d", &n);

    int i = 0;
    int arr[n];
    int flag1 = 0;
    int flag2 = 0;
    for (i = 0; i < n; i++) {
        scanf("%d ", &arr[i]);
        if (i > 0) {//读取到第一个元素时(即下标为0)还不能比较,所以要读取到第二个元素时才能比较,即下标要大于0
            if (arr[i] > arr[i - 1])
                flag1 = 1;
            else if (arr[i] < arr[i - 1])
                flag2 = 1;
            else
                ;
        }
    }
    if (flag1 + flag2 <= 1)//全相等也是有序,但flag1和flag2还是0,所以判断条件是小于等于1
        printf("sorted\n");
    else
        printf("unsorted\n");

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值