指针的进阶

一、 字符指针

字符指针有两种使用方法

(1)

int main()
{
    char ch='w';
    char *pc=&ch;
    *pc='w';
    printf("%c",*pc);//打印'w'
    return 0;
}

(2)

int main()
{
    char*pstr="hello world";//将"hello world"字符串中第一个字符'h'的地址放入到字符指针pstr中
    printf("%s",pstr);//打印hello world
    return 0;
}

例题:下列代码会发生什么?

(1)
char* p="abcdef";  
*p='c';
(2)
char a1[]="abcdef";
*a1='c';

解释:

(1)报错
//首先会为指针变量p开辟一块空间,
//常量字符串"abcdef"会存储在内存中的只读数据区(值不可以改变),
//会将'a'的地址存到p而不是"abcdef"存到p中,
//因为p中存放的是a的地址,对p解引用,赋值,就会通过p中的地址找到只读数据区并改变内容
//但是只读数据区的内容无法改变,所以不可以通过对p解引用改变。
(2)a1数组的内容为"cbcdef"
//对a1数组进行初始化,就相当于将只读数据区中的内容复制一份到a1数组中。
//改变a1数组时,是改变数组a1的内容,而不是改变只读数据区中的常量字符串。

一道面试题:

#include<stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    char *str3 = "hello bit.";
    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;
}

解释:

我们需要知道数组的数组名除了(&数组名)和(sizeof(数组名))两种情况外都表示数组首元素的地址
(1)str1 and str2 are not same.
//str1[]和str2[]是两块不同的区域,内容都是"hello bit",但是首元素地址不同.
//可以理解为先开辟空间,再放入内容,在开辟空间时就确定了地址,
//所以首元素地址与内容无关。    
(2)str3 and str4 are same.
//因为是常量字符串,存放在只读数据区,无法改变,所以不会存在多个相同的常量字符串,
//所以str3和str4指针变量存放的是同一个常量字符串首字符的地址。

二、指针数组

指针数组是数组,存放的每个元素是一个指针

int* arr1[10];//整型指针的数组

int** arr2[4];//二级整型指针的数组

char* ch[5];//字符指针的数组

三、数组指针

3.1 数组指针的定义

数组指针是一个指针,这个指针指向的是一个数组

这里我们需要明确一点:数组指针是一个独立的类型(int(*)[10]),并不是int**,就像整型指针,字符指针一样,是一个独立的类型。

整型指针:指向一个整型数据

字符指针:指向一个字符数据

数组指针:存储的时数组的地址,指向一个数组,对数组指针解引用相当于得到了数组名

例:数组指针

int main()
{
	int a[4] = { 1,2,3,4 };
	int(*arr)[4] = &a;
	printf("%d\n", *((*arr) + 1));//(1)
	return 0;
}

解释

(1)arr是一个数组指针,存放a数组的地址,
1(*arr)
//首先将arr解引用,得到a数组的数组名,在这里,数组名表示a数组首元素的地址
2*((*arr) + 1))——>*(a+1)
//在这里数组名表示首元素地址,(a+1)表示第二个元素的地址,再解引用,就拿到了a[1]

例题:下面代码分别是什么?

int *a[4];//(1)
int (*b)[4];//(2)

解释

1)数组
 //a先与[结合,表示a是一个数组,这个数组有4个元素,每个元素是int*2)数组指针
 //因为有(),所以b先和*结合,表示b是一个指针,[]说明是数组指针,数组指针b指向的这个数组有4个元素,每个元素是int类型

3.2 &数组名VS数组名

在前面的内容我们已经提到了数组名和&数组名,在这里我们做一个总结:

数组的数组名在除了**&数组名sizeof(数组名)**两种情况外都表示数组首元素的地址

例题:下列代码会打印什么?

int main()
{
    int arr[10] = { 0 };
    printf("%p  %p\n", arr, &arr);//(1)    
    printf("%p  %p\n", arr + 1, &arr + 1);//(2)
    return 0;
}

解释

(1)00EFFDA4  00EFFDA4
(2)00EFFDA8  00EFFDCC
//我们可以看出虽然arr和&arr的值相等,但是表示的意义不同,
//arr表示的是首元素的地址,arr+1就是跳过一个元素
//&arr表示的是整个数组的地址,&arr+1就是跳过一个数组

3.3 数组指针的使用

例:数组指针的使用

void printf_arr(int(*arr)[5],int row,int col)
{
	for (int i = 0; i < row;i++)
	{
		for (int j = 0; j < col; j++)
		{
			printf("%d ", *(*(arr+i)+j));
            //*(*(arr+i)+j)等价于arr[i][j]
            //如何转换可以看本篇博客——数组指针的定义——例:数组指针
		}
	}
}
int main()
{
	int arr[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
	int row = 2;
	int col = 5;
	printf_arr(arr, row, col);//打印1,2,3,4,5,6,7,8,9,10
    //arr为二维数组的数组名,这种情况数组名表示首元素地址
    //二维数组的首元素是二维数组的第一行,第一行的地址就是一维数组的地址
    //可以用一维数组指针接受
	return 0;
}

习题:下列代码的意思?

int arr[5];         //(1)
int *parr1[10];     //(2)
int (*parr2)[10];   //(3)
int (*parr3[10])[5];//(4)

解析

(1)arr数组
//5个元素,每个元素的值为int类型
(2)parr1数组
//10个元素,每个元素为int*类型
(3)parr2数组指针
//指向的数组有10个元素,每个元素为int类型
(4)parr3数组
//10个元素,每个元素为数组指针,数组指针指向的数组有5个元素,每个元素是int类型

四、数组参数、指针参数

4.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);
}

4.2 二维数组传参

二维数组传参,形参是二维数组时,行可以省略,列不能省略,形参也可是数组指针类型。

void test(int arr[3][5])//正确传参
{}
void test(int arr[][])//错误传参
{}
void test(int arr[][5])//正确传参
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//错误传参
{}
void test(int* arr[5])//错误传参
{}
void test(int (*arr)[5])//正确传参
{}
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 test(int*p)
{}
int main()
{
    int a=10;
    int*ptr=&a;
    int arr[10]={0};
    test(&a);//可以传一个变量的地址
    test(ptr);//可以传一个一级指针
    test(arr);//可以传数组的数组名
    
    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 ch='w';
    char*p=&ch;
    char**pp=&p;
    char* arr[5];
    
    test(&p);//一级指针的地址
    test(pp);//二级指针
    test(arr);//指针数组的数组名
    
    return 0;
}

五、函数指针

#include <stdio.h>
int ADD(int x,int y)
{
    return x+y;
}
int main()
{
    printf("%p\n", ADD);
    printf("%p\n", &ADD);
    return 0;
}

由上可得:&函数和函数名都表示函数的地址

问:那么我们如何将函数的地址存储起来?

答:我们需要使用函数指针

int ADD(int x,int y)
{
    return x+y;
}
int main()
{
    int(*pf)(int ,int)=&ADD;//pf是函数指针
    int sum = (*pf)(2,3);//通过函数指针调用ADD函数。(*pf)中的*是个摆设,可以去掉
    printf("%d",sum);//打印sum
    return 0;
}

阅读两个代码

int main()
{
    (*(void(*)())0)();
    return 0;
}
void(*signal(intvoid*)(int)))int

解析:

1、代码1. (*(void(*)())0)();//调用0地址处函数
//1.把0强制类型转换为 void(*)() 函数指针
//2.再去调用0地址处这个参数为无参,返回类型是void的函数
2、代码2. void(*signal(intvoid*)(int)int//1.signal是一个函数声明
//2.signal函数的参数有两个,第一个是int类型,第二个是函数指针,该指针指向的函数参数int,返回类型是void
//3.signal函数的返回类型也是函数指针,该指针指向的函数参数int,返回类型是void
3、简化代码2
//简化
    typedef void (*pfun_f)(int);//把函数指针类型void(*)(int)命名为pfun_f(类型名)
    pfun_t signal(int,pfun_t);//等同于代码2

六、函数指针数组

存放函数指针的数组,每个元素都是函数指针类型。

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(*pfarr[4])(int, int) = { Add,Sub,Mul,Div };
    for (int i = 0; i < 4; i++)
    {
        int ret = pfarr[i](8, 4);//pfarr函数指针数组,也叫转移表
        printf("%d\n", ret);
    }
    return 0;
}

运用场景:计算器(函数指针数组)

void menu()
{
	printf("**************************\n");
	printf("****1、Add      2、Sub****\n");
	printf("****3、Mul      4、Div****\n");
	printf("**************************\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 main()
{
	int x = 0, y = 0;
	int input = 0;
	int ret = 0;
	int(*pf[5])(int, int) = { 0,Add,Sub,Mul,Div };
	do
	{
		menu();
		scanf("%d",&input);
		if (input > 0 && input < 5)
		{
			scanf("%d %d", &x, &y);
			ret = pf[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出\n");
		}
		else
		{
			printf("输入错误,重新输入\n");
		}
	} while (input);
	return 0;
}

七、回调函数

回调函数就是一个函数的参数包含函数指针,通过调用回调函数就可以调用函数指针指向的函数(比如:qsort函数)

qsort使用

#include<stdio.h>
int cmp(const void* a, const void* b)
{
	return *((int*)a) - *((int*)b);
}
int main()
{
	int arr[10] = { 2,4,6,8,10,1,3,5,7,9 };
    qsort(arr,10,sizeof(int),cmp);
	for (int i = 0; i < 10; i++)//输出1 2 3 4 5 6 7 8 9 10
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

qsort模拟实现(用冒泡排序实现)

#include<stdio.h>
int cmp(const void* a, const void* b)
{
	return *((int*)a) - *((int*)b);
}
void swap(void* a, void* b,int size)
{
	for (int i = 0; i < size; i++)
	{
		char temp = *((char*)a + i);
		*((char*)a + i) = *((char*)b + i);
		*((char*)b + i) = temp;
	}
}
void my_qsort(void* p, int num, int size, int(*cmp)(const void*a,const void*b))
{
	for (int i = 0; i < num-1; i++)
	{
		for (int j = 0;j<num-i-1;j++)
		{
			if (cmp((char*)p + j * size, (char*)p + (j + 1) * size) > 0)
			{
				swap((char*)p + j * size, (char*)p + (j + 1) * size, size);
			}
		}
	}
}
int main()
{
	int arr[10] = { 2,4,6,8,10,1,3,5,7,9 };
	my_qsort(arr,10,sizeof(int),cmp);
	for (int i = 0; i < 10; i++)//输出1 2 3 4 5 6 7 8 9 10
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

八、指向函数指针数组的指针(存放函数指针数组的地址)

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

void test(const char* str)
{
    printf("%s\n", str);
}
int main()
{
//函数指针pfun
    void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
    void (*pfunArr[5])(const char* str);
    pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
    void (*(*ppfunArr)[10])(const char*) = &pfunArr;
    return 0;
}
  • 17
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 18
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值