系统学习之C复习(7)指针

目录

1.明确

2.指针与指针类型

指针类型有很重要的作用:

指针加减

指针和数组

二级指针

3.指针进阶(一)

字符指针

指针数组 与 数组指针

 &数组名VS数组名

数组指针的使用

4.指针进阶(二)

 数组参数、指针参数

1.一维数组的传参

2.二维数组的传参

3.一级指针传参

4.二级指针传参

5.函数指针

6.函数指针数组

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

8.回调函数 

9.qsort函数的使用

 10.qsort函数的模拟实现


1.明确

指针就是地址,口头语中的指针 为指针变量

32位虚拟地址空间
        cpu - 32位地址 - 地址传输线 - 内存

2.指针与指针类型

指针变量的大小都是一样的。 32个地址序列或者64个地址序列

int main()
{
	int a = 10;
	int* pa = &a;
	char ch = 'w';
	char* pc = &ch;

	printf("%d,%d\n", sizeof(pa), sizeof(pc));
	//指针变量的大小都是一样的。 32个地址序列或者64个地址序列
	return 0;
}

指针类型有很重要的作用:

指针类型决定了,指针在被解引用的时候,访问的权限。
        整形指针解引用访问4个字节
        字符指针解引用访问1个字节
        ……

int main()
{
	int a = 0x11223344;//十六进制数值, 11占一个字节,22占一个字节,33占一个字节,44占一个字节
//	int* pa = &a;
//	*pa = 0;//int* 全部变成了零 44332211  00000000
	char* pa = &a;
	*pa = 0;//char* 44332211 只有44变成了00   00332211
	return 0;
}

指针类型决定了,指针向前或者向后走一步 走的距离。 
整形指针 看作一个整形进行增加。 一次字节+4    int* +n  ---> n*sizeof(int)
字符指针 看作一个字符进行增加。 一次字节+1
指针类型——指针看待类型的视角

野指针
1.指针没有初始化
2.越界

指针加减

指针 - 指针 得到的 两个指针之间的元素个数     前提是:两个指针指向同一个块空间

int main()
{
	int arr[10] = {0};
	printf("%d\n", &arr[9] - &arr[0]);
	return 0;
}

求字符串长度:
    1.计数器的方法
    2.递归
    3.指针 - 指针

指针和数组

数组名表示的是 数组首元素的地址。

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    printf("%p\n", arr);
    printf("%p\n", &arr[0]);
    return 0;
}

 

可见,两者地址相同。

 

 所以p+1  计算的是数组arr下标为1的地址。

二级指针

 指针变量也是变量,是变量就有地址,则用二级指针

int a = 10;         //假设地址为0x0018ff44

int* pa = &a;                        0x0018ff44

int** ppa = &pa;                   0x0018ff3c

int b = 20;
*ppa = &b;//等价于 pa = &b;

**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;

*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa , *ppa 其实访问的就是 pa .

3.指针进阶(一)

字符指针

在char* 类型指针中,除了可以实现 char* pc = 'w';外,我们还可以如此使用

char* pstr = "hello bit.";
printf("%s\n", pstr);

此时,是将字符串的首地址 存放在了pstr中。

有一道面试题如下:

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

输出的结果为

 因为str1 str2 是两个数组,两个均可修改,故此分配了两个内存。

str3 str4 是一个常量字符串,不可修改,故此只分配了一个内存。

指针数组 与 数组指针

指针数组  与  数组指针的区别
指针数组: 存放指针的数组
数组指针: 存放 数组的地址 的指针

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

	int* parr1[3] = { arr1,arr2, arr3 };//指针数组 : 存放指针的数组
    
    int (*parr2)[10];//数组指针 。 
}

数组指针中,由于[]的优先级高于 * ,所以需要用() 将 *与parr2结合。

parr2先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针

以下为数值指针与 指针数组的图解

 

 &数组名VS数组名

通常情况下,数组名都是代表该数组的首元素地址。

有两个例外:&arr 取出整个地址,  sizeof(数组名) 计算整个数组的大小

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

运行结果如下:

 因为&arr是整个数组的地址,所以对&arr+1 代表跳过整个数组的大小

数组指针的使用

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

int arr[5];
    是一个整形数组,每个元素的类型是 int,有5个元素
int* parr1[10];
    是一个指针数组,每个元素的类型是 int*
int(*parr2)[10];
    是一个指向数组的指针,指向的数组有10个元素,每个元素的类型是 int
int(* parr3[10])[5];
    是一个数组,有10个元素,每个元素的类型是:int(*)[5]
    存放数组指针的数组。
    arr1  int int int int int
    arr2  int int int int int
    arr3  int int int int int
    parr3 &arr1 &arr2 &arr3 &arr4 int(*)[5] int(*)[5] ……
                                    &arr5      &arr6

4.指针进阶(二)

 数组参数、指针参数

1.一维数组的传参

形参写成数组的形式:
    void test(int arr[])
    {}

形参部分的数组大小可以不写。
形参写成指针的形式:
    void test(int* p)
    {}
因为传入的数组的首地址,所以可以指针形式

2.二维数组的传参

int main()
{
    int arr[3][5] = {0};
    teat(arr);
}
形参写成数组的形式:
    void test(int arr[][5])//行可以省略,列必须写
形参写成指针的形式:
    void test(int(*p)[5])
arr    
    p    ->    0    int int int int int
    p+1    ->    1    int int int int int
    p+2    ->    2    int int int int int
    (*p)[5] P指向的是每一行5个元素的首地址

3.一级指针传参

void test(int* ptr)
{
	ptr = 100;
}
int main()
{
	int a = 10;		
	int arr[3]= {1,2,3};
	int* p = &a;
	test(p);
	test(arr);
}

4.二级指针传参

void test_1(char** ppc)
{
	//...
	printf("test_1\n");
}
void test_2(char (*p)[5])
{
	//...
	printf("test_2\n");
}
void test_3(char(*p)[3][5])
{
	//...
	*p; //此处*p 代表的是第一行的地址
	printf("test_3\n");
}
int main()
{
	char ch = 'w';
	char* pc = &ch;	//①
	test_1(&pc);	//①传一级指针变量地址

	char** ppc = &pc;//②
	test_1(ppc);	 //②传二级指针

	char* arr[4];
	test_1(arr);	 //③指针数组,数组可看作一级指针

	char arr1[3][5];
	test_2(arr1);	//此处只传了第一行的地址
	test_3(&arr1);	//此处取二维数组全部地址,传的是二维数组,所以函数需要[3][5] -  不常见
}

5.函数指针

int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int arr[10];
	int(*p)[10] = &arr;	//p是一个数组指针变量
	&Add;//即可拿到函数的地址
	printf("%p\n", &Add);
	printf("%p\n", Add);	//99 ,100 这两条代码意义是一样的
	//存放函数Add的地址
	int (* pf)(int, int) = Add;//pf就是函数指针
	//函数指针的使用
	int ret = (* pf)(2, 3);	//通过指针调用函数
	//	ret = Add(2,3);
	//	int ret = pf(2,3);	//即在 函数指针中, *可以忽略
	printf("\n%d\n", ret);
	return 0;
}

两个锻炼用代码:

代码1
    ( * ( void (*)()) 0 ) ();

进行逐步分析:

     void(*)()            是函数指针类型 void (*p) ()

  (  void(*)() )          强制转换类型

  (  void(*)() )    0    对0进行强制类型转换

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

        首先将0强制转换为一个函数指针类型,意味着 0 处的地址放着一个返回类型是void 的无参数函数,

        调用0 地址处这个函数

        在函数指针类型中, *  号无意义,可以省去

代码2

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

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

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

    signal是一个函数的声明
    signal函数的参数,第一个是int,第二个函数指针类型void(*)(int)
    signal函数的返回类型也是 void(*)(int)

6.函数指针数组

函数指针数组的用途:转移表

int ( *parr1[10] )( );        

其中, parr 先与 [ ] 结合,是一个数组,数组的内容是  int( * ) ( )类型的函数指针。

#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,ret,x,y;
	//int (* pf1)(int, int) = Add;
	//int (* pf2)(int, int) = Sub;
	//int (* pf3)(int, int) = Mul;
	//int (* pf4)(int, int) = Div;
	//构建多个,过于麻烦, 构建一个函数指针数组
	//函数指针数组
	int (*pf[5])(int, int) = {0, Add, Sub, Mul, Div};
	do
	{
		menu();
		printf("请选择计算器模式\n");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else if (1 <= input && input <= 4)
		{
			printf("请输入两个有效数字:");
			scanf("%d %d", &x, &y);
			ret = (*pf[input])(x,y);
			printf("ret = %d\n", ret);
		}
		else if (input > 4)
		{
			printf("输入错误,请重新输入");
		}
	} while (input != 0);
}

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

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

int Add(int x, int y)
{
	return x + y;
}
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7 };
    int (*p)[10] = &arr;   
	p数组指针
	int (* pf)(int, int) = &Add;
	函数指针
	int (* pfarr[4])(int, int);
    函数指针数组
	int (*(*p3)[4])(int, int) = &pfarr;  
    指向函数指针数组的 指针
	return 0;
}

8.回调函数 

回调函数是  将一个函数的指针(地址) 作为参数,传给另外一个函数, 当这个指针被调用的时候,它就是回调函数。

回调函数一般用于发生 特定的事件时,由别的函数调用。

void test()
{
	printf("test\n");
}
void print_test(void(*p)() )
{
	if (1)
		p();
}
int main()
{
	print_test(test);
	return 0;
}

9.qsort函数的使用

 引入#include< stdlib.h>

void qsort(void* base,	//base存放的是 待排序数据的起始位置
	size_t num,				//待排序的元素个数
	size_t width,			//一个元素的字节大小
	int(*cmp)(const void* e1, const void* e2));//函数指针
	  比较函数        e1、e2:待比较的两个元素的地址
    int返回 e1<e2   返回 <0
    int返回 e1=e2   返回 0
    int返回 e1>e2   返回 >0
	第四个参数,要求qsort函数的使用者,自定义一个比较函数。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//使用qsort排序数组

void cmp_int(const void* e1, const void* e2)
{
	//if (*(int*)e1 > *(int*)e2)
	//	return 1;
	//else if (*(int*)e1 == *(int*)e2)
	//	return 0;
	//else if (*(int*)e1 < *(int*)e2)
	//	return -1;

	return (*(int*)e1 - *(int*)e2);
//此处指针 - 指针,所得元素个数,自动判断返回值与0的关系
}

void printf_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

void main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	printf_arr(arr, sz);
}
//使用sqort 对结构体的排序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Stu
{
	char name[20];
	int age;
	double score;
};
//排序年龄
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
//排序名字
int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp (((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void main()
{
	struct Stu arr[3] = { {"zhangsan",20,55.5},{"lisi",21, 60.0},{"wangwu",12,35.0} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	//qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}

 10.qsort函数的模拟实现

//使用回调函数,模拟实现qsort(采用冒泡的方式)。
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_qsort(void* base, int num, int width,int (*cmp)(const void* e1, const void* e2))
{
	int i = 0;
	for (i = 0; i < num - 1; i++)
	{
		int j = 0;
		for (j = 0; j < num - 1 - i; j++)
		{
			//if (arr[j] > arr[j + 1])//比较
			if(cmp((char*)base + j * width,(char*)base + (j+1) * width)>0)
			{
				//交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值