C语言进阶指针的进阶

Posted Huang_ZhenSheng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言进阶指针的进阶相关的知识,希望对你有一定的参考价值。

目录

1.字符指针

代码分析1:

2.指针数组

代码分析2: 

3.数组指针

代码分析3:

代码分析4: 

代码分析5:

代码分析6:

方法6-1:

方法6-2:

代码分析7:

4.数组参数——指针参数

1.一维数组传参

2.二维数组传参 

3.一级指针传参

4.二级指针传参

5.函数指针:

代码分析8(设计一个计算器):

代码分析9:

代码分析10:

 6.函数指针数组

代码分析11(设计一个计算器):

代码简化11-1:

7.回调函数

代码简化11-2:

qsort()函数:

代码分析12(整形数据): 

代码分析12-1(结构体数据):

代码分析12-2:模拟实现qsort函数


1.字符指针

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	char ch = 'q';
	char * pc = &ch;
	char *ps = "hello bit";
	char arr[] = "hello bit";
	printf("%s\\n",ps);
	printf("%s\\n",arr);
	printf("%c\\n",*ps);//本质上是把"hello bit"这个字符串的首字符的地址存储在了ps中
	return 0;
}

代码分析1:

int main()
{
	char str1[] = "hello bit";
	char str2[] = "hello bit";
	char *str3 = "hello bit";
	char *str4 = "hello bit";
	return 0;
}

str1和str2不同,str3和str4相同

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。

2.指针数组

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

代码分析2: 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
    int *arr[3] = { &a, &b, &c };//arr是一个存放整型指针的数组
	for (int i = 0; i < 3; i++)
	{
		printf("%d ", *(arr[i]));
	}
	return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	int a[] = { 1, 2, 3, 4, 5 };
	int b[] = { 2, 3, 4, 5, 6 };
	int c[] = { 3, 4, 5, 6, 7 };
	int i = 0;
	int *arr[3] = { a, b, c };
	for (i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf(" %d", *(arr[i] + j));
			//模拟了一个二维数组arr[i][j],但实际上不是一个二维数组
		}
		printf("\\n");
	}
	return 0;
}

3.数组指针

是一种指针

整型指针——是指向整形的指针

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

int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指向的是一个大小为10个整型的数组。

所以p是一个指针,指向一个数组,叫数组指针。

//这里要注意:[ ]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

代码分析3:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	//arr和&arr类型不同
	printf("%p\\n",arr);
	printf("%p\\n", &arr);

	int *p1 = arr;
	int(*p2)[10] = &arr;
	printf("%p\\n", p1);
	printf("%p\\n", p2);
	printf("\\n");

	printf("%p\\n", p1);//p1指向的是整形指针,+1会跳过4个字节
	printf("%p\\n", p1+1);
	printf("%p\\n", p2);//p2指向的是数组指针,+1会跳过一个数组
	printf("%p\\n", p2+1);
	return 0;
}

数组名和&数组名打印的地址是一样的,虽然值是一样的,但是意义不一样

&arr表示的是数组的地址,而不是数组首元素的地址,数组的地址+1,跳过整个数组的大小,所以&arr+1相对于&arr的差值是40. 

数组名是数组首元素的地址

但是有2个例外:

1.sizeof(数组名)—数组名表示整个数组,计算的是整个数组的大小,单位是字节

2.&数组名—数组名表示整个数组,取出的是整个数组的地址

代码分析4: 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int (*pa)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ",*((*pa) + i));//*pa指向数组首元素的地址
	}
	return 0;
}

一般很少写这样的代码

代码分析5:

数组指针的数组遍历:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int(*pa)[10] = &arr;
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *((*pa)+i));
	}
	return 0;
}
int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int*pa = &arr;
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(pa+i));
	}
	return 0;
}

代码分析6:

二维数组通过数组指针传参:

方法6-1:

void print1(int arr[3][5], int r, int l)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < l; j++)
		{
			printf("%d ",arr[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);
	return 0;
}

方法6-2:

二维数组的数组名表示数组首元素的地址,首元素是第一行!

//p是一个数组指针,指向的是一维数组5个元素,每个元素是int
void print2(int(*p)[5],int r,int l)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < r; i++)
	{
		for (j = 0; j < l; 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 } };
	print2(arr, 3, 5);//arr数组名,表示数组首元素的地址
	return 0;
}

代码分析7:

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

4.数组参数——指针参数

1.一维数组传参

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void test(int arr[])//数组传参,数组接收,本质上传过来的是数组首元素的地址
{}
void test(int arr[10])
{}
void test(int *arr)//因为数组名传参传的实际上是首元素的地址,是这个数组首元素int的地址
{}
void test2(int *arr[20])//
{}
void test2(int **arr)//
{}
int main()
{
	int arr[10] = { 0 };
	int *arr2[20] = { 0 };
	//arr2数组20个元素每个元素int*,arr2是一个存放int*的数组,是一个整型数组
	//arr2应该是这样的:第一个元素是int*,第二个元素是int*,第三个元素int*....
	//arr2表示数组首元素的地址,也就是int*的地址,一级指针的地址,就是二级地址
	test(arr);
	test2(arr2);
}

2.二维数组传参 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void test(int arr[3][5])//OK
{}
void test(int arr[][])//NO
{}
void test(int arr[][5])//OK
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
void test(int *arr)
//NO,二级数组传参中,首元素地址表示第一行地址,第一行是有五个整型的数组,一级指针无法接收
{}
void test(int* arr[5])
//NO,不是指针,不是二维数组
{}
void test(int(*arr)[5])
//OK,数组指针
{}
void test(int **arr)
//NO,不能用二级指针,传过去的压根不是二级指针,传过去的是第一行的地址,二级指针无法接收
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

3.一级指针传参

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void print(int* prt, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf(" %d",*(prt+i));
	}
}
void test(char* p)
//思考一个问题:当一个函数部分的参数为一级指针的时候,函数能接收什么参数
{

}
int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(p, sz);
	//p是一级指针,一级指针的传参
	char ch = 'w';
	test(&ch);//ch是一个char类型的变量,取它的地址应该是char*
	char*p1 = &ch;
	test(p1);
	return 0;
}

4.二级指针传参

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void test(int** p2)
{
	**p2 = 20;
}
int main()
{
	int a = 10;
	int* pa = &a;//pa是一级指针
	int** ppa = &pa;//ppa是二级指针
	//把二级指针进行传参
	test(ppa);
	test(&pa);//传一级指针变量的地址

	int*  arr[10] = { 0 };
	test(arr);//数组名相当于首元素地址,首元素也是int*,int*的地址相当于int**,(传存放一级指针的数组)
	printf("%d\\n",a);
	return 0;
}

下面做个小结: 

关于指针:

一级指针:

Int*p----整型指针,指向整型的指针

char*pc----字符指针,指向字符的指针

void*pv----无类型的指针

二级指针

char**p;

int**p;

数组指针:指向数组的指针

int(*p)[4];

关于数组:

一维数组

二维数组

指针数组----存放指针的数组

5.函数指针:

函数指针:指向函数的指针!存放函数地址的指针!

函数名 == &函数名

代码分析8(设计一个计算器):

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int a = 10;
	int* pa = &a;

	char ch = 'w';
	char* pc = &ch;

	int arr[10] = { 0 };
	int (*parr)[10] = &arr;//取出数组的地址,parr是指向数组的指针---存放的是数组的地址

	//函数指针---存放函数地址的指针
	//&函数名 ---取到的就是函数的地址
	int (*pf)(int ,int) = &Add;
	printf("%p\\n",&Add);
	printf("%p\\n", Add);
	return 0;
}

代码分析9:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int ,int) = &Add;
	int(*pf)(int, int) = Add;//Add这个函数的地址放到pf去,说明Add == pf
	int ret1 = (*pf)(3,5);//(这里的*没有实际的意义)
	int ret2 = pf(3, 5);
	int ret3 = Add(3, 5);
	printf("%d %d %d\\n",ret1,ret2,ret3);
	return 0;
}

代码分析10:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
	(*(void(*)())0)();
	//调用0地址处的函数
	//该函数无参,返回类型是void
	//1.void(*)()--函数指针类型
	//2.(void(*)())0--对0进行强制类型转换,被解释为一个函数地址
	//3.*(void(*)())0--对0地址进行了解引用操作
	//4.(*(void(*)())0)()--调用0地址处的函数
	//<c陷阱和缺陷>
	void(*signal(int, void(*)(int)))(int);
	//1.signal和()先结合,说明signal是函数名
	//2.signal函数的第一个参数的类型是int,第二个参数的类型是函数指针
	//  该函数指针,指向一个参数为int,返回类型是void
	//3.signal函数的返回类型也是一个函数指针
	//该函数指针,指向一个参数为int,返回类型是void的函数
	//signal是一个函数的声明

	//typedef-对类型进行重定义
	//typedef void(*pfun_t)(int);//对void(*)(int)的函数指针类型重命名为pfun_t
	//pfun_t signal(int, pfun_t);
	return 0;
}

 6.函数指针数组

函数指针数组---存放函数指针的数组

整型指针int*

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

函数指针的应用:下面先看一段代码:

代码分析11(设计一个计算器):

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
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("*** 1.add    2.sub ***\\n");
	printf("*** 3.mul    4.div ***\\n");
	printf("***      0.exit    ***\\n");
	printf("**********************\\n");
}
int main()
{
	int x = 0;
	int y = 0;
	int ret = 0;
	int input = 0;
	do{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入2个操作数\\n");
			scanf("%d %d", &x, &y);
			ret = Add(x,y);
			printf("ret = %d\\n", ret);
			break;
		case 2:
			printf("请输入2个操作数\\n");
			scanf("%d %d", &x, &y);
			ret = Sub(x,y);
			printf("ret = %d\\n", ret);
			break;
		case 3:
			printf("请输入2个操作数\\n");
			scanf("%d %d", &x, &y);
			ret = mul(x, y);
			printf("ret = %d\\n", ret);
			break;
		case 4:
			printf("请输入2个操作数\\n");
			scanf("%d %d", &x, &y);
			ret = div(x, y);
			printf("ret = %d\\n", ret);
			break;
		case 0:
			printf("退出程序\\n");
			break;
		default:
			printf("选择错误,请重新选择\\n");
			break;
		}
	} while (input);
	return 0;
}

问题:太多冗杂

代码简化11-1:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
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("*** 1.Add    2.Sub ***\\n");
	printf("*** 3.Mul    4.Div ***\\n");
	printf("***      0.exit    ***\\n");
	printf("**********************\\n");
}
int main()
{
	int x = 0;
	int y = 0;
	int ret = 0;
	int input = 0;
	do{
		menu();
		int(*pfArr[5])(int,int) = {NULL, Add, Sub, Mul, Div};
		//pfArr是函数指针数组
		//转移表--《C和指针》
		printf("请选择:>");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入2个操作符>:");
			scanf("%d %d", &x, &y);
			ret = (*pfArr[input])(x, y);
			printf("ret= %d\\n", ret);
		}
		else if (input == 0)
		{
			printf("退出程序\\n");
			break;
		}
		else
		{
			printf("选择错误\\n");
		}
	} while (input);
	return 0;
}

函数指针的数组 ——数组

取出函数指针数组的地址

1.整形数组

int arr[5];

int (*p1)[5] = &arr;

2.整形指针的数组

int* arr[5];

int* (*p2)[5] = &arr;

p2是指向【整数指针数组】的指针

3.函数指针数组 ,&函数指针数组

int(*p)(int,int);//函数指针

int(* p2[4])(int,int);//函数指针的数组

int(* (*p3)[4])(int ,int) = &p2;//取出的是函数指针数组的地址

//p3就是一个指向【函数指针的数组】的指针

补充:

对于int arr[10]   //数组元素的类型是int    //arr数组的类型是int[10]

7.回调函数

通过函数指针调用的函数

A函数        B函数(A函数的地址)

                           (A函数的指针)通过A函数的指针返回来调用A函数

代码简化11-2:

例子(优化代码):

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
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("*** 1.add    2.sub ***\\n");
	printf("*** 3.mul    4.div ***\\n");
	printf("***      0.exit    ***\\n");
	printf("**********************\\n");
}
int Calc(int(*pf)(int, int))
{
	int x = 0;
	int y = 0;
	printf("请输入2个操作符>:");
	scanf("%d %d",&x,&y);
	return pf(x, y);
}

int main()
{
	int x = 0;
	int y = 0;
	int ret = 0;
	int input = 0;
	do{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			ret = Calc(Add);
			printf("ret = %d\\n", ret);
			break;
		case 2:
			ret = Calc(Sub);
			printf("ret = %d\\n", ret);
			break;
		case 3:
			ret = Calc(mul);
			printf("ret = %d\\n", ret);
			break;
		case 4:
			ret = Calc(div);
			printf("ret = %d\\n", ret);
			break;
		case 0:
			printf("退出程序\\n");
			break;
		default:
			printf("选择错误,请重新选择\\n");
			break;
		}
	} while (input);
	return 0;
}

qsort()函数:

//快速排序

整形数据,字符串数据,结构体数据

void qsort

(void *base,//base中存放的是待排序数据中第一个对象的地址
           size_t num, //排序数据元素的个数
           size_t width, //排序数据中一个元素的大小,单位是字节
           int(*compare)(const void *, const void *)//是用来比较待排序数据中的2个元素的函数
           ); 

代码分析12(整形数据): 

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
int main()
{
	int arr[] = { 2, 3, 1, 4, 6, 5, 8, 9, 7, 10 };
	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;
}

代码分析12-1(结构体数据):

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{
	char name[20];
	int age;
};
int sort_by_age(const void*e1, const void*e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int  sort_by_name(const void*e1, const void*e2)
{
	 return strcmp(((struct Stu*)e1)->name,((struct Stu*)e2)->name);
}
void test2()
{
	//使用qsort函数排序结构体数据
	struct Stu s[] = { { "zhangsan", 30 }, {"list",34}, {"wangwu",20} };
	int sz = sizeof(s) / sizeof(s[0]);
	//按照年龄来排序
	qsort(s, sz, sizeof(s[0]), sort_by_age);
	//按照名字来排序
	qsort(s, sz, sizeof(s[0]), sort_by_name);
}
int main()
{
	test2();
	return 0;
}

代码分析12-2:模拟实现qsort函数

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
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++;
	}
}
//模仿qsort实现一个冒泡排序的通用算法
void bubble_sort(void* base,
	             int sz,
				 int width,
				 int(*cmp)(const void*e1,const void*e2)
				 //由于qsort指向的元素类型不确定,所以采用void* //const是由于只是比较,但是不交换,通过e1,e2找到这2个元素的指针,更加安全
	             )
{
	int i = 0;
	//趟数
	for (i = 0; i < sz - 1; i++)
	{
		//一趟的排序
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//两个元素的比较
			//arr[j]  arr[j+1]
			if (cmp((char*)base+j*width ,(char*)base+(j+1)*width) > 0)//base被强制类型转化成char*
			{
				//交换
				Swap((char*)base + j*width, (char*)base + (j + 1)*width,width);
			}
		}
	}
	
}
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
void test3()
{
	int arr[] = { 9, 7, 10 };
	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]);
	}
}
int main()
{
	test3();
	return 0;
}

以上是关于C语言进阶指针的进阶的主要内容,如果未能解决你的问题,请参考以下文章

C语言进阶笔记深入了解进阶指针

C语言进阶笔记深入了解进阶指针

C语言进阶笔记深入了解进阶指针

C语言进阶学习笔记二指针的进阶(重点必看+代码演示+练习)

C语言进阶学习笔记二指针的进阶(重点必看+代码图解+练习)

C语言进阶学习笔记二指针的进阶(练习篇)