IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 学习指针进阶的心得 -> 正文阅读

[C++知识库]学习指针进阶的心得

菜鸟带专生学习指针进阶

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

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.";
    cha rstr2[] ="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)[5], 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

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章           查看所有文章
加:2022-01-25 10:24:21  更:2022-01-25 10:26:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 9:59:36-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码