C语言指针 (从入门到入土)

目录

初识指针

一.指针的定义与使用

二.字符指针

1.str1与str2指向字符串相同时地址相同 

2.区分typedef与define

三、指针数组 

四、数组指针

1、数组指针的定义

2、&数组名VS数组名

3、数组指针的使用 

五、数组传参和指针传参 

1、一维数组传参和指针数组传参:

2、二维数组传参:

3、一级指针传参

 4、二级指针传参

六、函数指针

1、函数名 与 &函数名地址相同

2、函数指针储存与调用的多种形式

3、函数指针的用途

4、 指向函数指针数组的指针

七、回调函数


初识指针

指针 : 数据在内存中的地址

指针变量:保存了内存地址的变量。指针变量的值就是某份数据的地址,这样的一份数据可以是数组、字符串、函数,也可以是另外一个普通变量或者指针变量。

(需要注意的是,虽然变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符,但在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的首地址。)

  • 指针的大小是固定的4/8个字节(32位平台/64位平台)

一.指针的定义与使用

#include<stdio.h>
int main()
{
	int a=0,b=6,c=-19;
	float d=99.5,e=0.9;
	//定义指针变量,将变量 a 的地址赋予它,此时 p1 就指向了 a
	int *p1=&a;	 printf("%d %d\n",a,*p1);	
	*p1=c; 	printf("%d %d %d\n",c,a,*p1);	//此时*p1与a等价 ,a值被改变 
	p1 = &b;	printf("%d %d\n",b,*p1);	//修改指针变量的值	
	c=*p1;	printf("%d %d\n",c,*p1);	//此时*p1与b等价 ,赋值给c 

	//可定义多种类型指针变量 
	float *p2;	
	p2=&d;	printf("%f %f\n",d,*p2);//如果写成*p2 = &d,报错!!!
	*p2=e;	printf("%f %f\n",e,*p2);//通过指针变量修改内存上的数据
	return 0;
}

 

 连续定义:

int *a, *b, *c;          //a、b、c 的类型都是 int*
注意每个变量前面都要带*。如果写成下面的形式,那么只有 a 是指针变量,b、c 都是类型为 int 的普通变量:
int *a, b, c;            //a、b、c 的类型 int*  int  int

* 和 &同时出现:

  1. *&a表示什么?*&a可以理解为*(&a),&a表示取变量a的地址(即p),*(&a)表示取这个地址上的数据(即*p),*&a仍然等于a。

  2. &*p表示什么?&*p可以理解为&(*p),*p表示取得p指向的数据(即a),&(*p)表示数据的地址(即&a),所以等于p。

 总结:粗略来讲,* 和 & 同时出现时抵消(像极了不定积分与求导)

二.字符指针

对于char* p="abcdef" ,p只指向字符串首元素地址。

对于操作:*p=’w';//报错,储存在内存只读区不可修改

1.str1与str2指向字符串相同时地址相同 

#include<stdio.h>
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";

	const char* str1 = "abcdef";
	const char* str2 = "abcdef";

	if (arr1 == arr2)
		printf("arr1==arr2\n");
	else
		printf("arr1!=arr2\n");

	if (str1 == str2)
		printf("str1==str2\n");
	else
		printf("str1!=str2\n");

	return 0;
}


output arr1!=arr2
       str1==str2

1、数组名不同,地址在内存中不同,所以不相等

2、str1和str2都是常量字符串所以它们是不能被修改的而内存为了节省空间就只存了一份所以它们的地址是一样

2.区分typedef与define

typedef int* pint;

#define PINT int*

int main()
{
	int a, b;//a int b int
	int *pa, pb;
	pa -> int*
	pb -> int
	
	pint pa, pb;    //等价于int*pa,int*pb;
	pa -> int* 
	pb -> int*

	PINT pa, pb;    //等价于int * pa,pb;
	pa -> int*
	pb -> int

	return 0;
}

三、指针数组 

指针数组是一个存放指针的数组

 可以利用指针设置一个“假”二维数组

#include<stdio.h>
int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* arr[] = {arr1, arr2, arr3};

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

	return 0;
}


output   1 2 3 4 5
         2 3 4 5 6
         3 4 5 6 7

 四、数组指针

1、数组指针的定义

int (*p)[10];
#include <stdio.h>
int main()
{
	char a[5];
	char (*pa)[5]=&a;
	
	int *arr[6];
	int (*pp)[6]=&arr;
	return 0;
}

 
解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

2、&数组名VS数组名

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("arr = %p\n", arr);
	printf("&arr= %p\n", &arr);
	printf("arr+1 = %p\n", arr+1);
	printf("&arr+1= %p\n", &arr+1);
	return 0;
}


output  arr = 000000000062FDF0
        &arr= 000000000062FDF0
        arr+1 = 000000000062FDF4
        &arr+1= 000000000062FE18

实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40

3、数组指针的使用 

#include <stdio.h>
void print1(int arr[3][5], int row, int col)
{
	int i = 0,j;
	for(i=0; i<row; i++)
	{
		for(j=0; j<col; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
void print2(int (*p)[5], int r, int c)
{
	int i = 0,j;
	for(i=0; i<r; i++)
	{
		for(j=0; j<c; j++)
		{
			printf("%d ", *(*(p+i)+j));
                           // p是第一行地址
                           // p+i是第i行地址
                           // *(p+i)是第i行第一个元素的地址
		}
		printf("\n");
	}
}
int main()
{
	
int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
print1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print2(arr, 3, 5);
return 0;
}

 考试会考类型:

int arr[5] = { 1,2,3,4,5 };
int* p = arr; //arr为首元素的地址
int i = 0;
for (i = 0; i< 5; i++){
printf ("%d ", arr[i]);
printf ("%d ",*(arr + i)) ;
printf ("%d ",*(p + i));
printf ("%d ",p[i]);
// arr[i] == *(arr+i)==*(p+i) == p[i]
//这几种写法都是一样的

五、数组传参和指针传参 

1、一维数组传参和指针数组传参:

#include <stdio.h>
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}

void test2(int *arr[20])
{}
void test2(int **arr)
{}
int main()
{
	int arr[10] = {0};
	int *arr2[20] = {0};
	test(arr);
	test2(arr2);
}

2、二维数组传参:

void test(int arr[3][5])
{}

void test(int arr[][5])
{}

void test(int (*arr)[5])
{}

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

 总结:二维数组传参必须标明列数

3、一级指针传参

方式:

void test(int* p)
{}

int main()
{
	int a = 10;
	int* ptr = &a;
	int arr[10] = {0};
	test(&a);
	test(ptr);
	test(arr);
	return 0;
}

例子:

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

 4、二级指针传参

方式:

void test(char **p)
{ }
int main()
{
	char c = 'b';
	char*pc = &c;
	char**ppc = &pc;
	char* arr[10];
	test(&pc);
	test(ppc);
	test(arr);//Ok?  no!
	return 0;
}

例子:

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

六、函数指针

1、函数名 与 &函数名地址相同

#include <stdio.h>
void test()
{
	printf("hehe\n");
}
int main()
{
	printf("%p\n", test);
	printf("%p\n", &test);
	return 0;
}


output 0000000000401530
       0000000000401530

这两个地址都是 test 函数的地址

2、函数指针储存与调用的多种形式

int (* pf)(int, int) = &Add;//pf是函数指针
int (* pf)(int, int) = Add;//这样写也行

int sum = Add(2, 3);
int sum = (*pf)(2,3);
int sum = pf(2, 3);

注意:有 “ * ”时一定要加括号

3、函数指针的用途

假设要利用函数调用写一个简单计算机

#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("**********************************\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;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("ret = %d\n", ret);
			break;
		case 2:
			printf("输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("ret = %d\n", ret);
			break;
		case 3:
			printf("输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("ret = %d\n", ret);
			break;
		case 4:
			printf("输入2个操作数:>");
			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;
}

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

定义:int (*parr1[10])();

//parr1 先和 [] 结合,说明 parr1是数组,是 int (*)() 类型的函数指针。

使用函数指针简化后:


int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;

	int (*pfArr[5])(int, int) = {0, Add, Sub, Mul, Div};
//pfArr是一个函数指针的数组,也叫转移表
	
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else if (input >= 1 && input <= 4)
		{
			printf("输入2个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("ret = %d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);

	return 0;
}

 可见,利用函数指针可以大幅简化重复代码

4、 指向函数指针数组的指针

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

int main()
{
	int (*pa)(int, int) = Add;//函数指针
	int (* pfA[4])(int, int);//函数指针的数组
	int (* (*ppfA)[4])(int, int) = &pfA;
        //ppfA 是一个指针,该指针指向了一个存放函数指针的数组
        // pfArr是一个数组指针指向10个元素
        //指向的每一个元素的类型是一个函数指针int(*)(int,int)
        //指向函数指针数组的指针

	return 0;
}

七、回调函数

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

例子: 

下面来用回调函数来实现上一次用函数指针数组实现的计算器

#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;
}
menu()
{
	printf("*****************************************\n");
	printf("********  1.Add     2.Sub     ***********\n");
	printf("********  3.Mul     4.Div     ***********\n");
	printf("********  0.exit              ***********\n");
	printf("*****************************************\n");
}
void calc(int (*pf)(int, int))
{
	int x = 0; int y = 0;
	printf("请输入两个数:>");
	scanf_s("%d %d", &x, &y);
	int ret = pf( x, y);
	printf("%d\n", ret);
}
int main()
{ 
	int input = 0;
	do
	{
		printf("请选择:\n");
		menu();
 		scanf_s("%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("退出计算器");
			break;
		default:
			printf("选择错误");
			break;
		}
	} while (input);
	return 0;
}

回调函数很好的省略了case内部的重复代码过程,此时的加减乘除函数的指针当作参数传入calc函数里,这就是回调函数。

首先了解一下void*: 

void* 是一种无类型的指针,无具体类型的指针

void* 的指针变量可以存放任意类型的地址

void* 的指针不能直接进行解引用操作

void* 的指针也不能直接加减整数 

冒泡排序知道吧

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

 可以看出该冒泡排序只能排int类型,有没有办法让同一个排序排float、char......等不同类型呢?

 回调函数告诉你答案

首先

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

对于bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);

因为你不知道会传入什么类型,所以依次传入(首元素地址,字符长度,一个字符所占的字节·,比较方法)

接收:

void bubble_sort(void* base, int sz, int width, int(*cmp)(const void*e1, const void*e2))
{}

 若前元素比后元素大则交换(默认升序)

if(cmp((char*)base + j * width, (char*)base + (j + 1) * width)>0)
			{
				//两个元素的交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}

其中cmp()函数和Swap()函数

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

对于两个参数(char*)base + j * width,和(char*)base + (j + 1) * width, width

我们将这里的base强制类型转换为char*

由于是char*类型的指针,每次+整数1的偏移量刚好是1个字节

所以只有转化成char*的指针我们才能准确地算出偏移量

所以对于其他类型元素的比较,都可以在此基础上+width来跳过一个元素

(char*)base 和 (char*)base+width    刚好是跳过了一个元素的字节指向下一个元素
如:

数字5与数字3交换        05 00 00 00      03 00 00 00

先将第一个字节交换-->03 00 00 00      05 00 00 00

再将第二个字节交换-->03 00 00 00      05 00 00 00

...... 

-->03 00 00 00        05 00 00 00 

于是完成了交换

熟悉了整个流程之后,我们再用它来测试一下结构体数组

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct Stu
{
	char name[20];
	int age;
	float score;
};
int cmp_stu_by_socre(const void* e1, const void* e2)
{
	if (((struct Stu*)e1)->score > ((struct Stu*)e2)->score)
	{
		return 1;
	}
	else if (((struct Stu*)e1)->score < ((struct Stu*)e2)->score)
	{
		return -1;
	}
	else
		return 0;
}
int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
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);
			}
		}
	}
}
void print(struct Stu* arr,int sz)
{
	int i = 0;
	for (i = 0;i < sz;i++)
	{
		printf("%s %d %f\n", arr[i].name, arr[i].age, arr[i].score);
	}
}
test2()
{
	struct Stu arr[] = { {"zhangsan",20,87.5f},{"lisi",22,99.0f},{"wangwu", 10, 68.5f} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_socre);
	print(arr,sz);
}
test3()
{
	struct Stu arr[] = { {"zhangsan",20,87.5f},{"lisi",22,99.0f},{"wangwu", 10, 68.5f} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
	print(arr, sz);
}
 
int main()
{
	test2();
	printf("\n");
	test3();
	return 0;
}

 可见,可以通过不同的text来排序字符串、数字、字符

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NO.-LL

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值