【C语言】指针详解

15 篇文章 3 订阅

前言

指针概念

  1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
  4. 指针的运算。

一、字符指针

int main()
{
	char ch = 'w';
	char *pc = *ch;//pc是指向一个常量字符串
	
	const char *p = "helloworld";
	//"helloworld"是一个常量字符串,存在在内存的常量区。不能更改
	//上面表达式的意思是把常量表达式"helloworld"的第一个字符h的地址赋值给p
	printf("%c\n", *p);//h
	printf("%s\n", p);//helloworld
	return 0;
}

例题一

int main()
{
	char str1[] = "hello bit.";
	char str2[] = "hello bit.";
	const char *str3 = "hello bit.";//常量字符串,不能修改
	const char *str4 = "hello bit.";
	if (str1 == str2)
		printf("str1 and str2 are same\n"); //用相同的常量字符串去初始
		//化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同
	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;
}

运行结果:
在这里插入图片描述
这里str3str4指向的是同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1str2不同,str3str4不同。

二、指针数组

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

	char *arr[5];// arr是存放字符指针的数组
	int  *arr[5];// arr是存放整形指针的数组

例题一

int main()
{
	int a = 0;
	int b = 20;
	int c = 30;
	int d = 40;
	int *arr[4] = {&a, &b, &c, &d};
	//arr就是整形指针的数组。数组共4个元素,每个元素都是int*类型
	for (int  i = 0; i < 4; i++)
	{
		printf("%d ", *(arr[i]));  //0 20 30 40
	}
	return 0;
}

例题二

int main()
{
	int arr1[] = { 1, 2, 3, 4, 5 };
	int arr2[] = {6, 7, 8, 9, 10};
	int arr3[] = { 2, 3, 4, 5, 6 };

	int *parr[] = { arr1, arr2, arr3 };//存储每个数组的首地址
	for (int i = 0; i < 3; i++)
	{
		for (int  j = 0; j < 5; j++)
		{
			printf("%d ", parr[i][j]);//p[i] = *(p +i)
			//parr[i][j] = *(parr[i] +j)
		}
		printf("\n");
	}
	return 0;
}

输出结果:
在这里插入图片描述

例题三

int main()
{
	const char *arr[5] = {"abcdef","bcdef","hehe","zhangsan","haha"};
	//字符指针的数组,指针里面存储每个字符串的首地址。
	int i = 0;
	for ( i = 0; i < 5; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}

三、数组指针

3.1数组指针的定义

数组指针是指针,是能够指向数组的指针

int *p1[10];//指针数组
int (*p2)[10];//数组指针

例题一

int main()
{
	int arr[10] = { 0 };
	int *p = arr;//arr是数组首元素的地址
	int (*parr)[10] = &arr;//取出的是数组的地址,应该存放在数组指针中
	//parr类型为int(*)[10]
	return 0;
}

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

例题二

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);//数组名是首元素的地址(arr arr[0]),类型为int*
	printf("%p\n", &arr[0]);//类型为int*
	printf("%p\n", &arr);//取出数组的地址(&arr)     类型为int*(10)
	//上面三行输出的结果一样,都为012FF804
	printf("%p\n", arr +1);  //012FF808
	printf("%p\n", &arr[0] + 1); //012FF808
	printf("%p\n", &arr + 1);  //012FF82C  数组指针+1,跳过一个数组
	return 0;
}

3.2 &数组名VS数组名

数组名是首元素地址,但是有两个例外:
1.sizeof(arr)/sizeof(数组名)这里的数组名是表示整个数组sizeof(数组名)计算的是整个数组的大小,单位是字节
2.&(数组名),这里的数组名不是首元素的地址,数组名表示整个数组,所以取出的是整个数组的地址
数组的地址+1,跳过整个数组的大小。

3.3数组指针的使用

例题一

void print1(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
void print2(int* arr, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(arr + i));
	}
}
void print3(int (*parr)[10], int sz)//这是一个错误的示范,这是parr指向的是整个数组,+1跳过的是整个数组
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", parr[i]);//parr[i] == *(parr+i)
	}
}
void print4(int (*parr)[10], int sz)
{
	//*(parr + 0);-->parr[0]
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		//printf("%d ", parr[0][i]);
		//printf("%d ", (*(parr + 0))[i]);
		printf("%d ", (*parr)[i]);//(*parr) 相当于 parr指向的数组的数组名
	}
}
int main()
{
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int sz = sizeof(arr) / sizeof(arr[0]);
	print4(&arr, sz);
	//print3(&arr, sz);
	//print2(arr, sz);
	//print1(arr, sz);//打印arr数组的内容
	return 0;
}

例题二

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

}

void printf2(int (*p)[5], int r, int c)
{
	for (int i = 0; i < r; i++)
			{
				for (int 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 };
	//二维数组传参
	//printf1(arr, 3, 5);
	printf2(arr, 3, 5);  //arr是数组名,数组名是首元素(二维数组的第一行)地址(是二维数组每一行的一维数组首元素的地址)可以数组指针来接收
	return 0;
}

例题三

int arr[5];:整形数组
int *parr1[10];parr1是一个数组,10个元素,每个元素是int*的,所以parr1是一个存放指针的数组
int (*parr2)[10];parr2是一个数组指针,该指针指向的数组有10个元素,每个元素都是int的。
int (*parr3[10])[5];parr3是一个数组,数组有10个元素,每个元素是一个数组指针,该指针指向的数组有5个元素,每个元素是int的。

四、 数组参数、指针参数

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[][])//错误
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//错误
{}
void test(int* arr[5])//错误
{}
void test(int (*arr)[5])//ok 二维数组首元素指的是第一行。
{}
void test(int **arr)//错误
{}
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)
{}
int main()
{
	int a = 10;
	int *p1 = &a;
	int arr[10] = { 0 };

	test(&a);
	test(arr);
	test(p1);
	return 0;
}

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;
	int *arr[5];
	test(pp);
	test(&p);
	test(arr);
	return 0;
}

五、函数指针

函数指针变量:存放函数的地址
函数名&函数名是一样的。

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//printf("%p\n", Add);
	int(*pf)(int, int) = &Add;//pf是用来存放函数的地址,-pf就是函数指针变量
	//函数指针类型:int(*)(int, int) 
	int ret = Add(4, 5);
	printf("%d\n", ret);

	ret = (*pf)(4, 5); //*可以没有,也可以写多个
	ret = pf(4, 5);
	printf("%d\n", ret);//9
	return 0;
} 

例题一

(*(void (*)())0)();

这个代码是一次函数调用
void(*)()函数指针类型,将0强制类型转换为(void(*)())的一个函数的地址,*(void(*)()) 解引用0地址,找到那个函数,被调用的函数是无参,返回类型是void,后面的括号表明不用传参。

例题二

void (*signal(int , void(*)(int)))(int);
//也可以写为:
typedef void(*pfun_t)(int);
pfun_t signal2(int, pfun_t);

这个代码是一次函数声明
函数名是signalsignal函数有两个参数,第一个是int类型,第二个是函数指针类型void(*)(int)signal函数的返回类型是void(*)(int)的函数指针类型。

六、函数指针数组

数组是一个存放相同类型数据的存储空间,那把函数的地址存到一个数组中,那这个数组就叫函数指针数组
计算器的模拟实现:
方法一:

#include <stdio.h>
int add(int a, int b) //int(*)(int,int)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a*b;
}
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int input = 0;
	do
	{
		
		int x = 0;
		int y = 0;
		int ret = 0;
		int(*pArr[5])(int, int) = { 0, add, sub, mul, div };//函数指针数组
	//转移表
		printf("*************************\n");
		printf("***** 1:add 2:sub *******\n");
		printf(" *****3:mul 4:div ******\n");
		printf("******0.exit*************\n");
		printf("*************************\n");
		printf("请选择:");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
		}
		else if ((input <= 4 && input >= 1))
		{
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = (*pArr[input])(x, y);
			printf("ret = %d\n", ret);
		}
		else
		{
			printf("输入有误\n");
		}
	} while (input);
	return 0;
}

方法二:

#include <stdio.h>
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a*b;
}
int div(int a, int b)
{
	return a / b;
}
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( "ret = %d\n", ret);
}
int main()
{
	int input = 1;
do
{
		printf("*************************\n");
		printf("***** 1:add 2:sub *******\n");
		printf(" *****3:mul 4:div ******\n");
		printf("******0.exit*************\n");
		printf("*************************\n");
		printf( "请选择:" );
		scanf( "%d", &input);
switch (input)
{
	case 1:
	Calc(add);
	break;
	case 2:
	Calc(del);
	break;
	case 3:
	Calc(mul);
	break;
	case 4:
	Calc(div);
	case 0:
	printf("退出程序\n");
	breark;
	default:
	printf( "选择错误\n" );
	break;
}
} while (input);
	return 0;
}

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

指向函数指针数组的指针是一个指针
指针指向一个数组 ,数组的元素都是函数指针。

int Add(int x,int y)
{
	return x+y;
}
int (*pf)(int,int) = Add;//pf是函数指针
int (*pfArr[5])(int,int);//pfArr是一个函数指针数组
int (*(*pfArr)[5])(int,int)= &pfArr;
//ppfArr 就是一个指向一个函数指针数组的指针

八、回调函数

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

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

#include <stdio.h>
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++)
	{
		int temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		buf1++;
		buf2++;
	}
}
void Bubbble_sort(void* base, size_t num, size_t width, int(*cmp)(const void* e1, const void* e2))
{
	size_t i = 0;
	for ( i = 0; i < num - 1; i++)  //趟数
	{
		size_t j = 0;
		for (j = 0; j < num - 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_arr(int arr[], int sz)
{
	int i = 0;
	for ( i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[] = { 10,9,7,6,5, 4, 3, 2, 1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	Bubbble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	print_arr(arr, sz);
	return 0;
}

以上。如有错误,还请指正。


  • 28
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值