指针进阶的理解与使用

菜鸟带专生学习指针进阶

前言:本篇博客是为了总结自己学习指针的笔记,也是自己第一次写博客,可能质量很低请多多包含。

1.指针是什么?

首先,要学好指针的话,就要知道指针是什么?

1.指针是内存中一个最小单元的编号,也就是地址。

2.平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。

总结:

1.对于我来说 指针就是用来存变量的地址,并且通过这个地址进行操作去改变变量。

2.指针的大小在32位平台是4个字节,在64位平台是8个字节 (指针的大小与指针的类型无关)。

2.指针种类及其功能

[1]字符指针

(1)第一种用法

 char  ch='w';
 char *p=&ch;
 //这里的p就是个字符指针 我们可以通过解引用p去改变ch中存的字符'w'。
 *p='s';

(2)第二种用法


   const char* pstr="hello bit.";//这里是把一个字符串放到pstr指针变量里了吗? 
   printf("%s\n", pstr);

对于(2)
首先这里的"hello bit."是一个常量字符串,这里的char*存放的是字符串首字符的地址也就是h的地址

常量字符串:是不能被解引用改变的 所以要用 const 去限制不让 char*这个指针去解引用改变内容。

总结就是一个常量字符串的首字符h的地址存放到指针变量pstr中。


例子1

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

在这里插入图片描述



为什么str1和str2不相同,而str3和str4相同?
 首先,例子1 这里的两个if比较的都是地址

  1. str1和str2比较的是数组首元素地址,因为数组都会在内存开辟一块空间,所以str1和str2的地址不同。
  2. str3和str4是字符指针,比较的是字符串首字符的地址,这两个指针存的内容都是"hello bit.";
    是同一个常量字符串,在内存中相同的常量字符串只会存一份,所以str3和str4的地址相同。
    在这里插入图片描述

[2] 指针数组

作用: 是一个存放指针的数组(我的理解就是可存多个指针的数组)
格式类型 * 指针名[ ]
在这里插入图片描述

格式样例

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

[3] 数组指针

作用: 能够指向数组的指针(我的理解就是一个指针去指向数组的地址)
格式类型 * (指针名)[ ]

在这里插入图片描述

格式样例

int (*p1)[10]; 
char (*p2)[5];

如何区分指针数组和数组指针

 初学这里确实挺懵逼的一会这,一会那的。首先我们先写出一个指针数组和数组指针

int* arr1[10]; //指针数组  arr1先和[]结合 
int(*arr2)[10];//数组指针  arr2先和*结合

 原理(优先级)
[ ]的优先级要高于*号的,所以名字和哪个先结合就决定是什么类型

 解读一下 arr1和arr2

1.arr1是数组有十个元素每个元素是int *型 (数组的每个元素都是指针) -指针数组


2.arr2是指针指向一个数组 数组有10个元素每个元素是int型
(一个指针指向有10个整型元素的数组) -数组指针


3.&数组名是整个数组的地址要用数组指针接收
指针数组存放变量的地址或者数组首元素地址


&数组名和数组名区别

一般来说数组名是首元素地址

但有两个例外 数组名是代表整个数组

  1. sizeof(数组名)
  2. &数组名

例子1

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

结果
在这里插入图片描述

arr+1:跳过的是一个字节的大小;
&arr+1:跳过的是一个数组的大小;

所以说 &数组 —取出的是整个数组的地址而非数组首元素地址


数组指针的应用

数组指针主要运用在 二维数组

例子

#include <stdio.h>
void print_arr2(int (*arr)[], int row, int col)
{
    int i=0,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[2][5] = {1,2,3,4,5,6,7,8,9,10};
    print_arr2(arr, 2, 5);
    //数组名arr,表示首元素的地址
    //但是二维数组的首元素是二维数组的第一行
    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址   
    //可以数组指针来接收
    return0;
}

结果
在这里插入图片描述
原理

二维数组传参其实传的是数组首行数组的地址 (传一个数组的地址就用 数组指针 来接收)
相当于是把二维数组截断成一维数组的地址

在这里插入图片描述

printf("%d "arr[i][j]);

可以这样理解 i 其实遍历的是一维数组的首地址,而内循环j就是访问一维数组第 j个元素

//其实是推导过来的
//arr[i][j] == *(p[i]+j) == *((*p+i)+j)

回顾

int* parr1[10];    '''//parr1是数组 有十个元素每个元素是int*型   指针数组'''
int (*parr2)[10];  '''//parr2是指针 指向1个数组 数组有10个元素 每个元素是int型 数组指针'''
int (*parr3[10])[5]; 
'''//parr3 是一个数组 数组有10个元素 每个元素是数组指针类型而每个数组指针指向一个数组有5个元素每个元素类型为int型'''

[4]数组传参与指针传参

一维数组传参
#include <stdio.h>
void test2(int* arr[20])//ok?
{}
void test3(int** arr)//ok?
{}
int main()
{
  int* arr2[20]={0};
  test2(arr2);
  test3(arr2);
  return 0;
}

test2是可行的 指针数组传参给指针数组

test3是可行的 指针数组传参传的是指针数组首元素的地址 因为数组每个元素类型是int*型 所以要用个二级指针去接收,也就是 int** arr 。

二维数组传参
void test1(int* arr[5])//ok?
{}
void test2(int (*arr)[5])//ok?
{}
void test3(int **arr)//ok?
{}
int main()
{
  int arr[3][5] ={0};
  test1(arr);
  test2(arr);
  test3(arr);
  return 0;
}

test1 和 test3 是不可行的
test2 可以

二维数组传参: 本质上是把二维数组截断看成一维数组,并把首行的一维数组的地址传过去
也就是传过去一个数组的地址 那么就只能用数组指针来接收.

一级指针传参
void test1(int *p)
{}

一级指针能接收什么值?

1.变量的地址
2.一级指针变量

二级指针传参
#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[10];
  test(pp); //接收二级指针变量
  test(&p); //接收一级指针地址
  test(arr);//接收指针数组的首元素地址
  return 0; 
}

二级指针能接收什么值?

1.二级指针变量
2.一级指针的地址
3.接收指针数组的首元素地址

思考如果是test(&arr)可以吗?

不可以 因为这里传的就是整个数组的地址了 得用int *(*ptr)[10]
相当于把int* arr[10]套娃用个指针接收


[5]指针函数

作用:存放函数的地址

先看一段代码:

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

在这里插入图片描述

对于函数来说 函数名和&函数名是等价的 ==函数的地址

那么我们要如何存放函数的地址呢? 当然是用函数指针

例子

void test()
{
  printf("hehe\n");
}
 void (*pfun1)()= test;

(*pfun1)说明pfun1是指针 而()代表是无参 void则代表返回类型

试想把括号去掉 void *pfun1() 这就变成一个指针函数了 返回值是void*

"有趣"的2个小代码

------出自《剑指offer》

代码1

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

本质:对0强制转换成地址的函数调用
在这里插入图片描述

(void (*)()0)这里的void(*)() 是一个函数指针
而((函数指针)0)就是把0这个变量强制转换成函数指针类型
而最外层的(*)()说明作函数调用,函数无返回值,无参

代码2

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

本质:是一个返回类型是函数指针的函数声明,该函数的参数是int类型和函数指针

signal是个函数名有两个参数一个int 一个是函数指针void (*)(int)这个函数指针有一个参数int返回类型为void
而singal的返回类型是void(*)(int) 是函数指针

[6]函数指针数组

作用:可以存放n个函数指针的数组
格式

int (*parr1[10])();

parr1是个数组 有10个元素 每个元素是int(*)()函数指针类型 —函数指针数组

到底有什么用——转移表

例 (计算器)

#include<stdio.h>
void menu()
{
	printf("  欢迎来到整形计算器软件 \n");
	printf("*** 1.add       2.sub***\n");
	printf("*** 3.mul       4.div***\n");
	printf("***       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 main()
{
	int input = 0;
	int x,y;
	x=y=0;
	int(*pff[5])(int,int) = { 0,Add,Sub,Mul,Div }; //函数指针数组存就能存放多个函数
	do
	{
        menu();
		printf("请选择操作符\n");
		scanf("%d", &input);
		if(input >= 1 && input <= 4)
		{
			printf("请输入两个操作数\n");
			scanf("%d%d", &x, &y);
			printf("答案=%d\n", pff[input](x,y));
		}
		else if(input==0)
			printf("退出\n");
		else
			printf("输入错误,重新输入\n");
	} while (input);
}

[7]指向函数指针数组的指针

作用:说白了就是套娃 就是拿指针去存放函数指针数组的地址
      在学这个的时候用例子来举例容易理解一些

例子

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)[5])(const char*) = &pfunArr;
  return 0;
 }

pfun1 可以这样理解 是一个指针可以存一个参数为const char返回类void型的函数 ——函数指针

pfunArr[5] 是个数组,什么数组? 函数指针数组 数组有5个元素 每个元素是函数指针类型

(*ppfunArr) 是个指针,指向一个数组,数组有5个元素,每个元素是函数指针类型void (*)(const char*)
相当于是对函数指针数组的解引用

[8]回调函数

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

个人理解(暴论)

就是假设把一个函数1 传给函数2然后这个接收的函数2用函数指针接收并通过函数指针去间接改变函数1


应用例子:qsort函数

qsort函数是什么:

qsort函数是C语言的库函数包含在<stdolib.h>中,功能是进行排序,采用的是快速排序算法

在这里插入图片描述
在这里插入图片描述
qsort函数怎么使用:

qsort函数怎么用取决与参数compar也就是我们要自己编写一个比较函数传入到qsort去
int compar(const void *p1, const void *p2);
如果compar返回值小于0(< 0),那么p1所指向元素会被排在p2所指向元素的前面
如果compar返回值等于0(= 0),那么p1所指向元素与p2所指向元素的顺序一样
如果compar返回值大于0(> 0),那么p1所指向元素会被排在p2所指向元素的后面
也就是说比较函数的返回值决定如何排序

为什么这里的两个形参要用void*型?
 因为用void*类型就可以接收任意类型的数据,不被限制与某个类型。

void *指针有以下几种情况

1.可以存放任何类型的地址
2.不能解引用操作
3.不能被进行加减整数操作


1.使用qsort对整形数据的排序(升序)

#include<stdio.h>
#include<stdlib.h> //qsort 所在的库

int cmp(const void* e1,const void* e2)    //这里用void*是为了能接收任意类型的数据
{
	return *(int*)e1 - *(int*)e2;          //这里首先是把e1和e2强制转换成你想要比较的数据类型 因为比较的是整形所以把e1、e2强制转换为int *   
	                                       //强制转换完,再去解引用拿到元素的值去相减 然后把相减后的值返回
	                                      // e1 - e2 是升序排序     e2 - e1 是降序排序  
}	                                                                     
void print(int arr[],int sz)            //封装个打印数组函数 
{
	int i = 0;
	for (i = 0; i < sz; i++)
		printf("%d ",arr[i]);
}
int main()
{
	int arr[5] = {5,4,3,2,1};
	int arr_sz = sizeof(arr) / sizeof(arr[0]); //数组个数
	int el_sz = sizeof(arr[0]);    //数组中元素的大小 -单位字节
	
	qsort(arr, arr_sz, el_sz, cmp); //cmp是一个比较函数 需要自己编写; 格式 int cmp(const void* e1,const void* e2); 

	print(arr,arr_sz);             //排序完打印的结果是 1,2,3,4,5

	return 0;
}

2.使用qsort对浮点数据进行排序(降序)

#include<stdio.h>
#include<stdlib.h> //qsort 所在的库

int cmp(const void* e1, const void* e2)   
{                                             //e2-e1 就是降序
	return (int)(*(float*)e2 - *(float*)e1);  //这里就比整形数据转换多了一步 把计算出的返回值从浮点数强制转换为整数
	
	//当然这样强制转换对于小于1的浮点数可能就失效 建议用if判断来写
/*    if(((*(float*)e2 - *(float*)e1))>0)
            return 1;
      else if(((*(float*)e2 - *(float*)e1))<0) 
            return -1;
      else 
           return 0; 
*/ 
}

void print(float arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
		printf("%.1f ", arr[i]);
}
int main()
{
	float arr[5] = {1.0,2.0,3.0,4.0,5.0};
	int arr_sz = sizeof(arr) / sizeof(arr[0]); //数组个数
	int el_sz = sizeof(arr[0]);    //数组中元素的大小 -单位字节

	qsort(arr, arr_sz, el_sz, cmp); 

	print(arr,arr_sz);             //排序完打印的结果就是 5.0,4.0,3.0,2.0,1.0

	return 0;
}

3.使用qsort对结构体按姓名排序(升序)

#include<stdio.h>
#include<stdlib.h> //qsort  所在的库
#include<string.h> //strcmp 所在的库

struct Stu                       //定义结构体
{
	int age;                    //结构体有两个成员(参数)
	char name[15];
};

int cmp(const void* e1, const void* e2)
{                                                                       //这里还是先把e1、e2强制转换成你要的数据类型因为是结构体 所以转换成结构体指针 struct Stu *
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);    //因为我们是要用名字比较 比较的是字符串 只能用strcmp来比较
	                                                                    //因为我们要取出结构体中的name去用strcmp比较 我们就要用 -> 去取出结构体中的name  
	                                                                    //因为-> 优先级高于强制类型转换所以我们要多加一层括号保证先进行强制类型转换
}
   //如果用年龄进行排序 return ((struct Stu *)e1)->age - ((struct Stu *)e2)->age ;   

void print(struct Stu arr[], int sz)                              //封装一个输出结构体的函数
{
	int i = 0;
	for(i=0;i<sz;i++)
	  printf("%s  ", arr[i].name);                               // 输出年龄就用 arr[i].age
}
int main()
{
	struct Stu arr[3] = { {10,"zhangsan"},{20,"lisi"},{10,"wangwu"} };   //创建结构体s 并初始化
	int arr_sz = sizeof(arr)/sizeof(arr[0]);
	int el_sz = sizeof(arr[0]);
	qsort(arr, arr_sz, el_sz, cmp);

	print(arr, arr_sz);                                                     //结果lisi  wangwu  zhangsan
	return 0;
}

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

自己写一个来模拟实现qsort,之前写的每个数据类型的比较函数是一样的,不同的是我们要自己实现怎么进行交换、在什么条件下进行交换。

#include<stdio.h>

struct Stu                       //定义结构体
{
	int age;                    //结构体有两个成员(参数)
	char name[15];
};

//结构体按年龄的比较函数
int cmp_struct(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}


//整型的比较函数
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}


//因为我们不知道元素的类型 所以只能一个字节一个字节的去交换
void swap(char* bf1, char* bf2, int width)      //利用一个元素的大小去知道要跳过多少个字节
{
	int i=0;
	for (i = 0; i < width; i++)                //交换函数
	{
		int tmp = *bf1;
		*bf1 = *bf2;
		*bf2 = tmp;
		bf1++;
		bf2++;
	}
}


            //数组起始位置,数组大小,数组元素宽度, 比较方法
void my_sort(void* base, int sz, int width, int (*cmp)( void* e1,  void* e2))                //base 这里用void *类型为了能去接收任意类型的数组起始位置
{
	int i;
	for (i = 0; i < sz - 1; i++) //趟数
	{
		int j = 0;
		for (j = 0; j < sz - i - 1; j++) //一趟交换的对数
		{
			//两个元素的比较  两个相邻的元素比较  
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)                   //要进行比较 就要去调用比较函数 
			{																					  //把两个相邻的元素的地址传到比较函数去比较 得到个返回值来决定要不要交换
				//交换																			  //因为我们不知道传的数据类型是什么跳过一个元素要跳多少个字节 所以我们要传过来width才能去确定跳多少个字节
																								  //所以我们把两个元素强转为char *,然后第一个元素+j*width 后一个个元素始终差了1 所以 (j+1)*width 这样就能遍历完一趟
				  
				swap( (char*)base +j * width, (char*)base + (j + 1) * width ,width);             //传过去字符指针,swap就用字符指针接收
			}
		}
	}
}

//用整型排序数组
void test_int()
{
	int i;
	int arr[5] = { 5,4,3,2,1 };
	int arr_sz = sizeof(arr) / sizeof(arr[0]);
	int el_sz = sizeof(arr[0]);
	my_sort(arr, arr_sz, el_sz, cmp_int);
	for (i = 0; i < arr_sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n\n");

}

//用年龄排序结构体
void test_struct() 
{
	int i = 0;
	struct Stu arr[3] = { {20,"zhangsan"},{30,"lisi"},{10,"wangwu"} };
	int arr_sz = sizeof(arr) / sizeof(arr[0]);
	int el_sz = sizeof(arr[0]);
	my_sort(arr, arr_sz, el_sz, cmp_struct);
	
    for(i=0;i<arr_sz;i++)
       printf("%d ",arr[i].age);
    printf("\n\n");
}


int main()
{
	test_int();
	test_struct();
	return 0;
}

结尾

是我的第一篇博客,不会排版,第一次用Typora,写了一半然后又导入到CSDN写的,有参考别人的

                          本篇完成于2022.1.24

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值