C语言指针进阶

1. 字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char* ;

int main()
{
 char ch = 'w';
 char *pc = &ch;
 *pc = 'w';
 return 0;
}
int main()
{
 char ch = 'w';
 char *pc = &ch;
 *pc = 'w';
 return 0;
}

这里的本质是把字符串 hello bit. 首字符的地址放到了pstr中。
我们来看一道面试题:

#include <stdio.h>
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");
 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;
}

最终输出
在这里插入图片描述

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

2. 指针数组

int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

3. 数组指针

3.1 数组指针的定义

下面代码哪个是数组指针?

int (*p)[10];`int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?

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

3.2 &数组名VS数组名

#include <stdio.h>
int main()
{
 int arr[10] = {0};
 printf("%p\n", arr);
 printf("%p\n", &arr);
 return 0;
}//这里的arr,&arr分别是什么?

运行结果:
在这里插入图片描述
数组名 和 &数组名 打印结果一样

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

在这里插入图片描述
根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。(细细体会一下)
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.

3.3 数组指针的使用

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

#include <stdio.h>
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,0};
 int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
 //但是我们一般很少这样写代码
 return 0;
}
//参数是数组
void print1(int arr[3][5], int x, int y) {
	for (int i = 0; i < x; i++) {
		for (int j = 0; j < y; j++) {
			printf("%d ", arr[i][j]);
		}printf("\n");                       
	}
}
//参数是数组指针
void print2(int(*p)[5], int x, int y) {//二维数组首元素 地址为存储的第一行整个数组的地址
	for (int i = 0; i < x; i++) {
		for (int j = 0; j < y; j++) {
			//printf("%d ", *(*(p + i) + j));//*(p+i)  第i行   *(*(p + i) + j) 第j个元素,此处的p+i跳过的是整行元素的地址
			printf("%d ",(*p+i)[j]);
		}//*(p+i)[i]  *(*(p+i)+j)  p[i][j]  *(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);//arr类型为 int (*p)[5]   将二维看成多维  arr为存储的第一个一维数组的地址
	//arr[i]==*(arr+i)==p[i]==*(p+i)
	//int (*parr3[10])[5]    parr3是一个指针数组,数组存放10个元素,每个元素类型是一个数组指针 int(*)[5],数组指针指向一个数组int[5]
}

观察以下代码:

int arr[5];
//arr是一个整形数组,含5个int类型的元素

int *parr1[10];
//parr1[10]是一个指针数组,数组的元素类型是int*,含有10个元素

int (*parr2)[10];
//parr2是一个数组指针,指针指向一个整形数组int [10],指向的数组的每个元素类型是int

int (*parr3[10])[5];
//parr3[10]是一个数组,数组当中有10个元素,每个元素类型是int()[5],每个int()[5]是一个数组指针,指向一个整型数组int[5]

4. 数组传参和指针传参

4.1 一维数组传参

include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?->数组传参时元素数量可省略不写
{}
void test(int *arr)//ok?->arr类型为整形指针int*,我们想传入的也是一个整形数组的地址
{}
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);
}

二维数组传参

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
//二维数组传参,若形参数组要指定行列数量,列数量需要与实参列数量对应,行数量可不对应实参或省略行数量
//行列不能都省,可省略行数量但不能省略列数量
  
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字,也就是行数量的数字
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素,列数量不可省略,这样才方便运算。
//二维数组传参,传过去的是第一行所有元素组成的一个数组的地址,也就是一个数组指针
//二维数组传参,若形参数组要指定行列数量,列数量需要与实参列数量对应,列数量可不对应实参或省略
//行列不能都省,可省略行数量但不能省略列数量
void test(int *arr)//ok?--err
{}
void test(int* arr[5])//ok?--err//格式错误
{}
void test(int (*arr)[5])//ok?--true
{}
void test(int **arr)//ok?--err
{}
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)
{}
//test1函数能接收什么参数?
void test2(char* p)
{}
//test2函数能接收什么参数?
int main() {
	int a = 10;
	int* p = &a;
	test1(&a);
	test1(p);
	//一级指针传参可以接收一个数据类型变量的地址或一级指针变量本身
	char ch = 'w';
	char* p2 = &ch;
	test2(&ch);
	test2(p2);
	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;
 test(pp);
 test(&p);
 return 0;
}

思考:

当函数的参数为二级指针的时候,可以接收什么参数?

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?
 return 0;
 }

结论:二级指针传参传过去的是一个二级指针变量本身或一个一级指针的地址或一个一级指针数组的数组名

5. 函数指针

我们来看一段代码:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>

void Print(char* str) {
	printf("%s\n", str);
	return;
}
int Add(int x, int y) {
	return x + y;
}
int main() {
	//函数名 和 &函数名 都存储的是函数的地址
	printf("%p\n%p\n", Add, &Add);
	int Add(int x, int y);
	int (*p)(int, int) = Add;//函数指针
	printf("%d\n", Add(2, 3));
	printf("%d\n", (p)(2, 3));
	printf("%d\n", (*p)(2, 3));
	printf("%d\n", (**p)(2, 3));
    //以上4种调用都是可以的,p本质上是函数的地址,p和*p效果相同,在函数调用时可进行多次解引用操作
    //printf("%d\n", *p(2, 3));//error,*必须先与p结合
	void (*p2)(char*) = Print;
	(*p2)("hello world");
	return 0;
}

int (*p)(int, int) = Add;//函数指针
(p)保证p先与结合是一个指针,(int,int)为函数形参,int 为函数返回值类型,Add或&Add是函数地址,最终p是一个函数指针

阅读两段有趣的代码:

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

代码1:

(*(void (*)())0)();
在这里插入图片描述

代码2:

void (*signal(int , void(*)(int)))(int);

在这里插入图片描述
简化一下:

void(*signal(int, void(*)(int)))(int);
//简化一下
typedef void(* pfun_t)(int);//pfun_t为函数指针类型
pfun_t signal(int, pfun_t);
	//signal是一个函数声明,signal函数第一个参数为int,第二个参数为函数指针类型pfun_t
	//pfun_t 该

> 这里是引用

函数指针类型返回值类型为void,该函数指针指向的参数是int

6. 函数指针数组

示例:

typedef int (* pfun)(int, int);//定义函数指针类型--作为函数指针数组数组元素的返回值类型

int Sum(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 (*p[])(int, int)  = { Sum,Sub,Mul,Div };//写法1
	//去掉数组名和
	//pfun p[] = { Sum,Sub,Mul,Div };//写法2
	for (int i = 0; i < 4; i++) {
		printf("%d\n", p[i](2, 3));//5 -1 6 0
	}
	return 0;
}

把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

答案是:parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。
函数指针数组的用途:转移表

int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10]

示例2:

typedef char _strcpy(char* dest, const char* str);

char* my_strcpy(char* dest, const char* str) {}
//1.写一个函数指针pf指向my_strcpy
//2.写一个函数指针数组,能够存放4个my_strcpy的地址

int main() {
	_strcpy *pf = &my_strcpy;
	char* (*pf1)(char*, const char*) = &my_strcpy;

	_strcpy* p[4] = { 0 };
	char* (* p2[4])(char*,const char*) = {0};
	return 0;
}

计数器的实现:

void menu() {
	printf("******1.Add     2.Sub*******\n");
	printf("******3.Mul     4.Div*******\n");
	printf("******5.Xor     0.exit*******\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 Xor(int x, int y) {
	return x | y;
}

int main() {
	int input = 0, x = 0, y = 0;
	int (*pf[])(int, int)  = {Add,Sub,Mul,Div,Xor};//函数指针数组--转移表
	menu();
	do {
		printf("请选择:>");
		scanf("%d", &input);
		if (input == 0) {
			printf("退出\n");
			break;
		}
		else if (input >= 1 && input <= 4) {
			printf("请输入两个数:\n");
			scanf("%d%d", &x, &y);
			printf("结果为:%d\n", (*pf[input - 1])(x, y));
		}
		else {
			printf("输入错误\n");
			continue;
		}
	} while (input);
	return 0;
}

//switch改进版
void Cal(int *p(int, int)) {//接收一个函数指针的地址
	int x = 0, y = 0;
	printf("请输入两个数:>");
	scanf("%d%d", &x, &y);
	printf("结果为:%d\n", p(x, y));
}

int main() {
	int input = 0;
	menu();
	do {
		printf("请选择:>");
		scanf("%d", &input);
		switch (input) {
		case 0:
			printf("退出");
			break;
		case 1:
			Cal(Add);
			break;
		case 2:
			Cal(Sub);
			break;
		case 3:
			Cal(Mul);
			break;
		case 4:
			Cal(Div);
			break;
		case 5:
			Cal(Xor);
			break;
		default:
			printf("输入错误\n");
			continue;
		}
	} while (input);
	return 0;
}

7. 指向函数指针数组的指针

指向函数指针数组的指针是一个 指针 指针指向一个 数组 ,数组的元素都是 函数指针
定义:

int main() {
	int (*pf)(int, int);//pf是一个函数指针
	int (*pfArr[])(int, int) ={0};//pfArr是一个函数指针数组
	int (*(*ppfArr)[4])(int, int) = &pfArr;//ppfArr是一个指向函数指针数组的指针
	//ppfArr指向的函数指针数组的每个元素是一个函数指针 int(*)(int,int)
	return 0;//函数返回类型 函数名 (函数参数列表)
}
//void* 表示"空类型指针",即任何其他类型的指针类型均可赋值给void*类型指针

8. 回调函数

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

示例:

void print(char *str) {
	printf("%s\n", str);
}
void test(void (*p)(char*)){//test接收一个函数指针并调用函数指针指向的函数
	p("hello world");
}
int main() {
	test(print);
	return 0;
}

qsort函数详解见:
模拟实现qsort函数:

typedef struct Stu {
	char name[20];
	int age;
}Stu;

int cmp_Stu_name(const void* e1, const void* e2) {
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
int cmp_Stu_age(const void* e1, const void* e2) {
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int cmp_int(void* p1, void* p2) {
	return *(int*)p1 - *(int*)p2;
}
int cmp_float(void* e1, void* e2) {
	if (*(float*)e1 - *(float*)e2 > 0) {
		return -1;
	}
	if (*(float*)e1 - *(float*)e2 == 0) {
		return 0;
	}
	if (*(float*)e1 - *(float*)e2 > 0) {
		return 1;
	}
}//float数据比较返回值会发生强制类型转换,这里用 1 -1 0 来替换 >0 <0 ==0 的情况

void swap(char* p1, char* p2,int width) {
	for (int i = 0; i < width; i++) {//传入符合比较条件的两个元素(每个元素大小为width个字节),交换width个字节
		char tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2++;
	}
}

//width为一个结构体数组的大小
//模拟实现qsort函数
void bubble_qsort(void* base,int sz, int width, int (*cmp)(void* e1, void* e2)) {
	for (int i = 0; i < sz - 1; i++) {
		for (int j = 0; j < sz - i - 1; 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 test_int() {
	int arr[] = { 0,1,4,2,3,5,6,8,9,7 };
	bubble_qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), cmp_int);
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) {
		printf("%d ", arr[i]);
	}
}
void test_Stu_age() {
	Stu s[] = { {"zhang",20},{"xiaoqi",19},{"wen",17} ,{"lisi",37} };//创建结构体数组
	int sz = sizeof(s) / sizeof(s[0]);
	int width = sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_Stu_age);
	for (int i = 0; i < 4; i++) {
		printf("%s %d\n", s[i].name, s[i].age);
	}
}
void test_Stu_name() {
	Stu s[] = { {"zhang",20},{"xiaoqi",19},{"wen",17} ,{"lisi",37} };//创建结构体数组
	int sz = sizeof(s) / sizeof(s[0]);
	int width = sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_Stu_name);
	for (int i = 0; i < 4; i++) {
		printf("%s %d\n", s[i].name, s[i].age);
	}
}

int main() {
	test_Stu_name();
	return 0;
}

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

  • 21
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值