C语言进阶阶段学习总结

内容

1->字符指针 //char* p;

2->指针数组 //int* arr[10];

3->数组指针 //int (*p)[10] = &arr;

4->数组传参和指针传参 //

5->函数指针 //int (*pAdd)(int, int) = Add;

6->函数指针数组 //int (*pArr[])(int, int);

7->指向函数指针数组的指针  //int(* (*ppArr[]))(int, int) = &pArr;

8->回调函数

9->指针和数组面试题的解析


1->字符指针

//1.字符指针
int main()
{
	char arr[] = "abcdef";
	char* pc = arr;
	printf("%s\n", arr);
	printf("%s\n", pc);
	return 0;
}
int main()
{
	const char* p = "abcdef";//"abcdef"是一个常量字符串
	//指向元素a
	//*p = 'w';
	//printf("&c\n",*p);
	//printf("%s\n", p);
	return 0;
}


2->指针数组

//2.指针数组 
//		是数组,用来存放指针的数组

int main()
{
	int arr[10] = { 0 };//整形数组
	char ch[5] = { 0 };//字符数组
	int* parr[4];//存放整形指针的数组 - 指针数组
	char pch[5];//存放字符指针的数组 - 指针数组
	return 0;
}
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;
}


3->数组指针

//3.数组指针
//		是指针

int main()
{
	int* p = NULL; //p是整形指针 - 指向整形的指针 - 可以存放整形的地址
	char* pc = NULL; //pc是字符指针 = 指向字符的指针 - 可以存放字符的地址
	//数组指针 - 指向数组的指针 - 存放数组的地址
	int arr[10] = { 0 };
	//arr - 首元素地址
	//&arr[0] - 首元素地址
	//&arr - 数组地址

	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr; //方块优先级高
	//上面的p就是数组指针
	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
	}
	return 0;
}
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);//arr - 数组名就是首元素地址
	//把arr想象成一维数组
	//所以数组arr有三个元素,首元素地址就是第一行地址

	print2(arr, 3, 5);
	return 0;
}
int arr[5];          //   
int* parr1[10];      //parr1是一个数组,数组有10个元素,每个元素都是一个int型指针, int* parr1[10]是指针数组
int (*parr2)[10];	   //parr2是一个指针,该指针指向的了一个数组,数组有10个元素,每个元素是int型,int (*parr2)[10]是数组指针	
int ((*parr3)[10])[5]; 
//parr3是一个数组,该数组有10个元素,每个元素是一个数组指针,该数组指针指向的数组有五个元素,每个元素是int


4->数组参数、指针参数

//4.数组参数、指针参数

//一维数组传参
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int *arr)//ok
{}
void test2(int *arr[20])//ok
{}
void test2(int **arr)//ok 
{}
int main()
{
	int arr[10] = { 0 };
	int *arr2[20] = { 0 };
	test(arr);
	test2(arr2);
	return 0;
}
//二维数组传参
void test(int arr[3][5])
{}
void test2(int arr[][5])
{}
void test3(int arr[3][]) //err,不能省略列
{}
void test4(int* arr) //err,传过来的是一行数组 不能用int*
{}
void test5(int **arr)//err,二级指针也不能接收 一个数组地址
{}
void test6(int (*arr)[5])
{}

int main()
{

	int arr[3][5] = { 0 };
	test(arr);//二维数组传参
	test2(arr);
	test3(arr);

	test4(arr);
	test5(arr);
	test6(arr);
	return 0;
}

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

//二级指针传参
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;
}


5->函数指针

//5.函数指针 - 是指向函数的指针 - 存放函数地址的一个指针

int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}
int main()
{
	int a = 10;
	int b = 20;
	int arr[10] = { 0 };
	int (*p)[10] = &arr;
	//printf("%d\n", Add(a, b));
	//&函数名 和 函数名 都是函数的地址 
	/*
	printf("%p\n", &Add);
	printf("%p\n", Add);
	*/
	int  (*pa)(int, int) = Add; //函数指针
	printf("%d\n ", (*pa)(2, 3));
	return 0;
}

void Print(char*str)
{
	printf("%s\n", str);
}

int main()
{
	void (*p)(char*) = Print; 
	(*p)("hello bit");
	return 0;
}
//分析代码1
(*(void (*)())0)();

void(*)() - 函数指针类型
把0强制类型转换成:void(*)() - 0就是一个函数的地址
调用0地址处的该函数

//代码2
void (*signal(int, void(*)(int)))(int);

void(*  )(int);
signal(int, void(*)(int)) 
signal是一个函数声明 - 参数有两个,一个是int,第二个是函数指针,该函数指针指向的函数的参数是int,返回类型是void
signal函数的返回类型也是个函数指针: 该函数指针指向的函数的参数是int,返回类型是void
int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}
int main()
{
	int a = 10;
	int b = 20;

	int  (*pa)(int, int) = Add; //函数指针
	//以下三种都正确
	printf("%d\n ", (*pa)(2, 3));
	printf("%d\n ", pa(2, 3));
	printf("%d\n ", Add(2, 3));
	
	return 0;
}


6->函数指针数组

//6.函数指针数组 - 存放函数的地址的数组
 
int Add(int x, int y)
{
	return x+y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}

int main()
{
	//指针数组
	int* arr[5];
	//需要一个数组,这个数组可以存放4个函数的地址 - 函数指针的数组
	int (*pa)(int, int) = Add; //Sub/Mul/Div
	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;
}
//测试题
char* my_strcpy(char* dest, const char* src);

//1.写一个函数指针 pf,能够指向my_strcpy
char* (*pf)(char*, const char*);
//2.写一个函数指针数组pfArr, 能够存放4个my_strcpy函数的地址
char* (*pfArr[4])(char*, const char*);
//函数指针案例
//计算器
void menu()
{
	printf("1.add\n2.sub\n3.Mul\n4.div\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 Div(int x, int y)
{
	return x ^ y;
}
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d",&input);
		switch(input)
		{
		case 1:
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			printf("%d\n", Add(x, y));
			break;
		case 2:
			printf("请输入两个操作数:>");
			scanf("%d%d", &x,&y);
			printf("%d\n", Sub(x, y));
			break;
		case 3:
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			printf("%d\n", Mul(x, y));
			break;
		case 4:
			printf("请输入两个操作数:>");
			scanf("%d%d", &x, &y);
			printf("%d\n", Div(x, y));
			break;
		case 0:
			printf("退出\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
}

//方法2.利用函数指针数组
void menu()
{
	printf("1.add\n2.sub\n3.Mul\n4.div\n5.Xor");
}
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 Xor(int x, int y)
{
	return x ^ y;
}

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	//pfArr 是一个函数指针数组 - 用途:转移表
	int (*pfArr[])(int, int) = { 0,Add,Sub,Mul,Div,Xor};
	do
	{
		menu();
		printf("请选择:>");
		scanf_s("%d", &input);
		if (input >= 1 && input <= 5)
		{
			printf("请输入操作数:>");
			scanf_s("%d%d", &x, &y);
			int ret = pfArr[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出\n");
		}
		else
		{
			printf("选择错误!\n");
		}
		
	} while (input);
}


7->指向函数指针数组的指针

//7.指向函数指针数组的指针
int Add(int x, int y)
{
	return x + y;
}

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

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


8->回调函数

//8.回调函数 - 通过函数指针调用的函数

void print(const char *str)
{
	printf("hehe:%s", str);
}
void test(void (*p)(char*))
{
	printf("test\n");
	p("bit");
}
int main()
{
	test(print);
	return 0;
}
//void类型的指针
int main()
{
	int a = 10;
	//int* pa = &a;
	//char* pc = &a;
	//char ch = 'w';
	void* p = &a; //无类型指针 - 可以接收任意类型的地址 - 不能进行解引用操作 - 不能进行加减整数的操作
	p = &ch;
	return 0;
}
//qsort - 库函数 - 排序 - 可以排序任意类型的数据 
//quick sort

//void qsort(
//	void* base,  //目标数组首元素地址
//	size_t num,	 //待排序数组元素个数
//	size_t width, //每个元素的大小-单位是字节
//	int (*cmp)(const void* e1, const void* e2) //函数指针,比较两个元素的所用函数的地址 - 这个函数使用者自己实现 - 函数指针的两个参数是:待比较的两个元素的地址
//);	

//对arr进行排序,排成升序
//冒泡排序函数只能排序整形数组
//bubble_sort(arr, sz);//冒泡排序函数 
//void bubble_sort(int arr[], int sz)

{
	//确定冒泡排序的趟数
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int flag = 1;//假设这一趟要排序的数据已经有序 
		//每一趟冒泡排序
		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;
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
}

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

int cmp_int(const void* e1, const void* e2) //void* - 可以接收任意类型的地址
{
	//比较两个整形值的
	return *(int*)e1 - *(int*)e2;
}
int cmp_float(const void* e1, const void* e2)
{
	//比较两个浮点型
	return ((int)(*(float*)e1 - *(float*)e2));
}
int cmp_stu_by_age(const void* e1, const void* e2)
{
	//比较两个年龄
	return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
int cmp_stu_by_name(const void* e1, const void* e2)
{
	//比较名字就是比较字符串
	//字符串比较不能直接用><=来比较,应该用strcmp函数
	return strcmp(((struct Stu*)e1)->name ,((struct Stu*)e2)->name);
}

void test1()
{
	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);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

void test2()
{
	float f[] = { 9.0, 8.0, 7.0, 6.0, 5.0, 4.0 };
	int sz = sizeof(f) / sizeof(f[0]);	
	qsort(f, sz, sizeof(f[0]), cmp_float);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%f ", f[i]);
	}
}
void test3()
{
	struct stu s[3] = { {"zhangsan",20},{"lisi",30},{"wangwu",10} };
	int sz = sizeof(s) / sizeof(s[0]);	
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", s[i]);
	}
}
void test4()
{
	struct stu s[3] = { {"zhangsan",20},{"lisi",30},{"wangwu",10} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%s ", s[i]);
	}
}



//实现bubble_sort函数的程序员,他是否知道未来排序的数据类型-不知道
//那程序员也不知道,待比较的两个元素的类型

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_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++)
	{
		//每一趟冒泡排序
		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);
			}
		}
	}
}
int cmp_stu_by_age(const void* e1, const void* e2)
{
	//比较两个年龄
	return ((struct stu*)e1)->age - ((struct stu*)e2)->age;
}
void test5()
{
	struct stu s[3] = { {"zhangsan",20},{"lisi",30},{"wangwu",10} };
	int sz = sizeof(s) / sizeof(s[0]);
	//使用bubble_sort的程序员一定知道自己排序的是什么数据
	//就应该知道如何比较待排序数组中的元素
	bubble_sort(s, sz, sizeof(s[0]),cmp_stu_by_age);
}

int main()
{
  
	//通过qsort排序
	test1(); //排序整型数组
	test2(); //排序浮点型数组
	test3(); //排序- 通过结构体数组的年龄
	test4(); //排序- 通过结构体数组的名字
	test5(); //仿照qsort来修改冒泡函数实现能够排序任意类型
	return 0;
}


9->指针和数组面试题的解析

//9.指针和数组面试题的解析

int main()
{
	//数组名是首元素地址
	//例外
	//1.sizeof(数组名) - 数组名表示整个数组
	//2.&数组名 - 数组名表示整个数组
  //
	//一维数组
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));    //sizeof(数组名) - 计算的是数组总大小 - 单位是字节 -16
	printf("%d\n", sizeof(a + 0));// 4/8 - 数组名这里表示首元素的值,a+0还是首元素地址,地址的大小就是4/8个字节
	printf("%d\n", sizeof(*a));   //4 - 数组名表示首元素地址,*a就是首元素
	printf("%d\n", sizeof(a + 1));// 4/8 - 第二个元素的地址,地址的大小就是4/8个字节
	printf("%d\n", sizeof(a[1])); //4 - 第2个元素的大小
	printf("%d\n", sizeof(&a));   // 4/8 - &a取出的是数组的地址,但是数组地址那也是地址,地址的大小就是4/8个字节
	printf("%d\n", sizeof(*&a));  //16
	printf("%d\n", sizeof(&a + 1)); // 4/8
	printf("%d\n", sizeof(&a[0]));	// 4/8
	printf("%d\n", sizeof(&a[0] + 1));// 4/8
	return 0;
}
//字符数组
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));;	//6
	printf("%d\n", sizeof(arr + 0));// 4/8
	printf("%d\n", sizeof(*arr));   // 1
	printf("%d\n", sizeof(arr[1])); //1
	printf("%d\n", sizeof(&arr));	//4/8
	printf("%d\n", sizeof(&arr + 1)); // 4/8
	printf("%d\n", sizeof(&arr[0] + 1)); // 4/8
	
	printf("%d\n", strlen(arr));	// 随机值
	printf("%d\n", strlen(arr + 0));// 随机值
	//printf("%d\n", strlen(*arr));   // err
	//printf("%d\n", strlen(arr[1])); // err

	printf("%d\n", strlen(&arr));	// 随机值
	printf("%d\n", strlen(&arr + 1)); //随机值-6
	printf("%d\n", strlen(&arr[0] + 1)); //随机值-1
	return 0;

}

int main()
{
	char arr[] = "abcdef";

	printf("%d\n", sizeof(arr));	// 7  
	printf("%d\n", sizeof(arr + 0));// 4/8
	printf("%d\n", sizeof(*arr));	//1
	printf("%d\n", sizeof(arr[1])); //1
	printf("%d\n", sizeof(&arr));   // 4/8
	printf("%d\n", sizeof(&arr + 1));// 4/8
	printf("%d\n", sizeof(&arr[0] + 1));// 4/8

	printf("%d\n", strlen(arr));	// 6
	printf("%d\n", strlen(arr + 0));// 6
	//printf("%d\n", strlen(*arr));	//err
	//printf("%d\n", strlen(arr[1])); //err
	printf("%d\n", strlen(&arr));   // 6
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));// 5

	return 0;
}

int main()
{
	const char* p = "abcdef";
	printf("%d\n", sizeof(p));		// 4/8
	printf("%d\n", sizeof(p + 1));	// 4/8
	printf("%d\n", sizeof(*p));		// 1
	printf("%d\n", sizeof(p[0]));	// 1	p[0] == *(p + 0) == 'a'
	printf("%d\n", sizeof(&p));		// 4/8
	printf("%d\n", sizeof(&p + 1));	// 4/8
	printf("%d\n", sizeof(&p[0] + 1));// 4/8

	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	p[0] == *(p + 0) == 'a'
	printf("%d\n", strlen(&p));		// 随机值
	printf("%d\n", strlen(&p + 1));	// 随机值
	printf("%d\n", strlen(&p[0] + 1));// 5
	return 0;
}
//二维数组
int main()
{
	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  a[0]相当于第一行做为一维数组的数组名 == sizeof(数组名)
 	printf("%d\n", sizeof(a[0] + 1));// 4/8 a[0]是第一行首元素地址 a[0]+1,第一行第二个元素的地址
	printf("%d\n", sizeof(*(a[0] + 1)));//4
	printf("%d\n", sizeof(a + 1));   //4/8  a是二维数组的首元素地址,而二维数组的首元素是第一行,及把二位数组看作了一维数组,a+1就是第二行的地址
	printf("%d\n", sizeof(*(a + 1)));//16
	printf("%d\n", sizeof(&a[0] + 1));//4/8 第二行地址
	printf("%d\n", sizeof(*(&a[0] + 1)));//16
	printf("%d\n", sizeof(*a)); //16 *a = 第一行地址解引用
	printf("%d\n", sizeof(a[3])); //16 表示第四行,sizeof不参与真实计算
	return 0;
}
//指针笔试题
int main()
{
	int a[5] = { 1,2,3,4,5 };
	int* ptr = (int*)(&a + 1); //&a + 1 为5后面的地址
	printf("%d, %d\n", *(a + 1), *(ptr - 1)); //2 5  
	return 0;
}

struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p; //结构体指针 p

//假设p 的值为ox100000,如下表表达式的值分别为多少?
//已知, 结构体Test类型的变量大小是20个字节

int main()
{
	p = (struct Test*)0x100000;
	printf("%p\n", p + 0x1);  //0x00100014 
	printf("%p\n", (unsigned long)p + 0x1); //0x00100001
	printf("%p\n", (unsigned int*)p + 0x1); //0x00100004
	return 0;
}
 
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 0x02000000
	return 0;
}

int main()
{
	int a[3][2] = { (0,1),(2,3),(4,5) };//逗号表达式 1,3,5
	int* p;
	p = a[0]; //第一行 1, 3
	printf("%d", p[0]); //1 
	return 0;
}

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

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)); //*(aa + 1) == aa[1]
	printf("%d,%d\n", *(ptr1 - 1), *(ptr2 - 1)); // 10 5
	return 0;
}

int main()
{
	char* a[] = { "work","at","alinbaba" };
	char** pa = a;

	pa++;
	printf("%s\n", *pa);//at
	return 0;
}