C++ 指针详解

目录

一、指针概述

指针的定义

指针的大小

指针的解引用

野指针

指针未初始化

指针越界访问

指针运算

二级指针

指针与数组

二、字符指针

三、指针数组

四、数组指针

函数指针

函数指针数组

指向函数指针数组的指针

回调函数

指针与数组

一维数组

字符数组

二维数组


一、指针概述

指针的定义

数据是存放在内存中的,每一个内存空间都会有一个像房间号一样的编号,比如某某五星级酒店3109号房间,只有获得了这个编号才能找到这个房间,这个编号就是地址。而指针就是用来存放这个地址的变量。总结来说:指针是一个变量,是用来存放内存地址的变量,我们通常称谓指针变量。

指针的大小

是由计算机的物理性质决定的,一般的计算机分为32位机和64位机,32位机就是由32根地址线组成,每一根地址线都会传递高电平(1)或者低电平(0)。那么32根地址线就会产生2^32次方个地址,所以64位机的地址以此类推。8bite = 1字节——32位机的地址就由4个字节存储,62位机的地址就由8个字节的地址存储。

指针类型:数据的存储方式有 char、short、int、long、long long、float、double、struct、void
因此对应反指针类型也就有:char*、short*、int*、long*、long long*、float*、double*、struct*、void*

int main()
{//什么类型数据的地址就得放到对应类型的指针变量中char ch = 'w';char* pc = &ch;int num = 10;int* p = #return 0;
}

指针的解引用

指针的类型决定了,对指针解引用的时候有多大的权限(能取到多大的字节数)。比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

野指针

指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

指针未初始化

int main()
{ //局部变量指针未初始化,默认为随机值int *p;//没有指向对应的地址空间,无法查找地址*p = 20;return 0;
}

指针越界访问

int main()
{int arr[10] = {0};int *p = arr;for(int i=0; i<=11; i++){//当指针指向的范围超出数组arr的范围时,p就是野指针*(p++) = i;}return 0;
}

指针指向的空间释放,在堆里开辟内存的空间,如果被回收就不能在使用,因此指针就不能再指向那块空间的地址。

指针运算

指针的类型决定 + - 整数,指针变量所偏移的空间大小

int main()
{float arr[5];//指针+-整数;指针的关系运算for (float *p = &arr[0]; vp < &arr[5];){//后置++,先给数组赋值在偏移四个字节*p++ = 0;}return 0;}

指针 - 指针

//模拟实现strlen
int my_strlen(char *s)
{char *p = s;while(*p != '\0' )p++;return p-s;//两个指针之间的空间个数
}

指针的关系运算,允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。

#define N_VALUES 5
int main()
{float values[N_VALUES];float *vp;//不能让数组第一个元素的地址与数组第一个元素之前的地址进行比较for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--){*vp = 0;}return 0;
}

二级指针

指针变量也是一个变量,每一个变量都会存放到内存中,也就会有地址。二级指针,就是用来存放一级指针变量地址的变量。因此,存放一级指针变量地址的指针为二级指针,存放二级指针变量地址的指针为三级指针,以此类推。

 

指针与数组

可见数组名和数组首元素的地址是相同的。 因为数组是一块连续的空间,所以可以用指针指向数组首元素的地址,通过指针的加减来访问数组的每一个空间。

arr可以理解为一个变量,即一个内存空间。

二、字符指针

将字符变量的地址放到字符指针中称为字符指针。一种比较特殊的字符指针指向的是常量池中字符串首字符的地址,常量池当中的字符串是不能修改的。而将字符串放到数组当中进行存储的值是可以修改的。

char a = ‘w’;char* pa = &a;char* p = "abcdef";

备注:const加在*前表示不能修改指针所指向空间的值,const加在*后表示不能修改指针的指向

字符串数组和常量字符串的区别:

int main()
{//用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块//str1,str2字符串数组存放的是对应字符的asc码值//值虽然相同但是他们是两个不同数组,开辟的地址也是不同的char str1[] = "hello bit.";char str2[] = "hello bit.";//字符指针str3,str4存放的是字符串常量池的字符,存放的都是字符'h'的地址const char* str3 = "hello bit.";const char* str4 = "hello bit.";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

三、指针数组

整型数组 - 存放整型的数组
字符数组 - 存放字符的数组
指针数组 - 存放指针(地址)的数组
指针数组是存放指针的数组,每种类型的数据都可以创建数组,但是数组也可以是由指针组成。

存放字符指针的数组

参数pc是是一个二级指针,存储的是abcdef字符串的地址,每次挪1字节

存放数组首地址的指针数组

四、数组指针

字符指针——存放字符地址的指针——指向字符的指针 char*
整型指针——存放整型地址的指针——指向整型的指针 int*
浮点型的指针——存放浮点型地址的指针——指向浮点型的指针 float*  double*
数组指针——存放数组地址的指针——指向数组的指针

数组名和&数组名的区别:

数组指针的使用,使用一:两种打印数组的方式

数组指针的使用,使用二:二维数组的使用

//正常的接受二维数组的参数
void print1(int arr[3][4], int r, int c)
{int i = 0;for (i = 0; i < r; i++){int j = 0;for (j = 0; j < c; j++){printf("%d ", arr[i][j]);}printf("\n");}
}//用数组指针来接受二维数组,表示的是数组的第几行
void print2(int(*p)[4], int r, int c)
{int i = 0;for (i = 0; i < r; i++){int j = 0;for (j = 0; j < c; j++){			//printf("%d ", (*(p + i))[j]);printf("%d ", p[i][j]);}printf("\n");}
}int main()
{int arr[3][4] = { {1,2,3,4}, {2,3,4,5} , {3,4,5,6} };print1(arr, 3, 4);printf("\n");//数组名arr,表示首元素的地址//但是二维数组的首元素是二维数组的第一行//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址//可以数组指针来接收print2(arr, 3, 4);return 0;
}

 

函数指针

函数指针的定义与使用

类型名  (*指针变量名)(函数参数)  = &函数名(或者直接写函数名)
//函数指针
int Add(int x, int y)
{return x + y;
}int main()
{//pf 是一个存放函数地址的指针变量 -  函数指针int (*pf)(int, int) = &Add;//可以理解为&Add给pfint ret = (*pf)(2,3);//&函数名和函数名都是函数的地址int (*pf)(int, int) = Add;//int ret = Add(2, 3);int ret = pf(2, 3);printf("%d\n", ret);return 0;
}

函数名也可以理解为一个指针变量,指针变量就是一块内存,内存中记录一个地址;变量也是一块内存直接记录值。*a取a的值,&a取a的低地址,a标识一块内存。

函数指针数组

函数指针数组的定义

数组是一个存放相同类型数据的存储空间,前面有讲到指针数组,那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组。

                    数据类型 ( * 数组名[数值] ) (函数参数);
如:

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

函数指针数组的使用(转移表)

void menu()
{printf("*******************************\n");printf("****** 1. add   2. sub    *****\n");printf("****** 3. mul   4. div    *****\n");printf("****** 0. exit            *****\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 (*pf[5])(int, int) = { NULL, Add, Sub, Mul, Div };int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();printf("请选择:>");scanf("%d", &input);if (input == 0){printf("退出计算器\n");break;}else if (input>=1 &&input<=4){printf("请输入两个操作数:>");scanf("%d %d", &x, &y);ret = pf[input](x, y);printf("%d\n", ret);}else{printf("选择错误\n");}} while (input);return 0;
}

指向函数指针数组的指针

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

void test(const char* str)
{printf("%s\n", str);
}
int main()
{//函数指针pfunvoid (*pfun)(const char*) = test;//函数指针的数组pfunArrvoid (*pfunArr[5])(const char* str);pfunArr[0] = test;//指向函数指针数组pfunArr的指针ppfunArrvoid (*(*ppfunArr)[5])(const char*) = &pfunArr;return 0;
}

回调函数

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

void menu()
{printf("*******************************\n");printf("****** 1. add   2. sub    *****\n");printf("****** 3. mul   4. div    *****\n");printf("****** 0. exit            *****\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;
}void calc(int (*pf)(int, int))
{int x = 0;int y = 0;int ret = 0;printf("请输入两个操作数:>");scanf("%d %d", &x, &y);ret = pf(x, y);printf("%d\n", ret);
}int main()
{int input = 0;do {menu();printf("请选择:>");scanf("%d", &input);switch (input){case 1:calc(Add);break;case 2:calc(Sub);break;case 3:calc(Mul);break;case 4:calc(Div);break;case 0:printf("退出计算器\n");break;default:printf("选择错误\n");break;}} while (input);return 0;
}

指针与数组

一维数组

int main()
{//一维数组int a[] = { 1,2,3,4 };//4*4=16printf("%d\n", sizeof(a));//16printf("%d\n", sizeof(a + 0));//a+0 其实是数组第一个元素的地址,是地址就是4/8字节printf("%d\n", sizeof(*a));//*a是数组首元素,计算的是数组首元素的大小,单位是字节,4printf("%d\n", sizeof(a + 1));//a+1是第二个元素的地址,是地址大小就是4/8printf("%d\n", sizeof(a[1]));//a[1]是第二个元素,计算的是第二个元素的大小-4-单位是字节printf("%d\n", sizeof(&a));//&a是整个数组的地址,整个数组的地址也是地址,地址的大小就是4/8字节//&a---> 类型:int(*)[4]printf("%d\n", sizeof(*&a));//&a是数组的地址,*&a就是拿到了数组,*&a--> a,a就是数组名,sizeof(*&a)-->sizeof(a)//计算的是整个数组的大小,单位是字节-16printf("%d\n", sizeof(&a + 1));//&a是整个数组的地址,&a+1,跳过整个数组,指向数组后边的空间,是一个地址,大小是4/8字节printf("%d\n", sizeof(&a[0]));//&a[0]是首元素的地址,计算的是首元素地址的大小,4/8字节printf("%d\n", sizeof(&a[0] + 1));//&a[0] + 1是第二个元素的地址,地址的大小就是4/8字节return 0;
}

字符数组

int main()
{//字符数组char arr[] = { 'a','b','c','d','e','f' };//char*//char [6]printf("%d\n", strlen(arr));//随机值printf("%d\n", strlen(arr + 0));//随机值//printf("%d\n", strlen(*arr));//strlen('a')->strlen(97),非法访问-err//printf("%d\n", strlen(arr[1]));//'b'-98,和上面的代码类似,是非法访问 - errprintf("%d\n", strlen(&arr));//&arr虽然是数组的地址,但是也是从数组起始位置开始的,计算的还是随机值//char(*)[6]printf("%d\n", strlen(&arr + 1));//&arr是数组的地址,&arr+1是跳过整个数组的地址,求字符串长度也是随机值printf("%d\n", strlen(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址,是'b'的地址,求字符串长度也是随机值printf("%d\n", sizeof(arr));//arr单独放在sizeof内部,计算的是整个数组的大小,单位是字节,6printf("%d\n", sizeof(arr + 0));//arr + 0是数组首元素的地址,4/8printf("%d\n", sizeof(*arr));//*arr是数组的首元素,计算的是首元素的大小-1字节printf("%d\n", sizeof(arr[1]));//arr[1]是第二个元素,大小1字节printf("%d\n", sizeof(&arr));//取出的数组的地址,数组的地址也是地址,是地址大小就是4/8printf("%d\n", sizeof(&arr + 1));//&arr+1是跳过整个,指向数组后边空间的地址,4/8printf("%d\n", sizeof(&arr[0] + 1));//&arr[0] + 1是数组第二个元素的地址,是地址4/8字节return 0;
}

int main()
{char arr[] = "abcdef";//数组是7个元素//[a b c d e f \0]printf("%d\n", strlen(arr));//6,arr是数组首元素的地址,strlen从首元素的地址开始统计\0之前出现的字符个数,是6printf("%d\n", strlen(arr + 0));//arr + 0是数组首元素的地址,同第一个,结果是6printf("%d\n", strlen(*arr));//*arr是'a',是97,传给strlen是一个非法的地址,造成非法访问printf("%d\n", strlen(arr[1]));//errprintf("%d\n", strlen(&arr));//6printf("%d\n", strlen(&arr + 1));//&arr + 1是跳过数组后的地址,统计字符串的长度是随机值printf("%d\n", strlen(&arr[0] + 1));//&arr[0]+1是b的地址,从第二个字符往后统计字符串的长度,大小是5printf("%d\n", sizeof(arr));//7 - 数组名单独放在sizeof内部,计算的是数组的总大小,单位是字节printf("%d\n", sizeof(arr + 0));//arr+0是首元素的地址,大小是4/8printf("%d\n", sizeof(*arr));//*arr是数组首元素,大小是1字节printf("%d\n", sizeof(arr[1]));//arr[1]是数组的第二个元素,大小是1字节printf("%d\n", sizeof(&arr));//&arr是数组的地址,数组的地址也是地址,是4/8字节printf("%d\n", sizeof(&arr + 1));//&arr + 1是跳过整个数组的地址,是4/8字节printf("%d\n", sizeof(&arr[0] + 1));//&arr[0] + 1是第二个元素的地址,是4/8字节return 0;
}

int main()
{const char* p = "abcdef";printf("%d\n", strlen(p));//6- 求字符串长度printf("%d\n", strlen(p + 1));//p + 1是b的地址,求字符串长度就是5printf("%d\n", strlen(*p));//err,*p是'a'printf("%d\n", strlen(p[0]));//err - 同上一个printf("%d\n", strlen(&p));//&p拿到的是p这个指针变量的起始地址,从这里开始求字符串长度完全是随机值printf("%d\n", strlen(&p + 1));//&p+1是跳过p变量的地址,从这里开始求字符串长度也是随机值printf("%d\n", strlen(&p[0] + 1));//&p[0] + 1是b的地址,从b的地址向后数字符串的长度是5printf("%d\n", sizeof(p));//p是指针变量,大小就是4/8字节printf("%d\n", sizeof(p + 1));//p + 1是b的地址,是地址,就是4/8个字节printf("%d\n", sizeof(*p));//*p是'a',sizeof(*p)计算的是字符的大小,是1字节printf("%d\n", sizeof(p[0]));//p[0]-->*(p+0) --> *p  就同上一个,1字节printf("%d\n", sizeof(&p));//&p是二级指针,是指针大小就是4/8printf("%d\n", sizeof(&p + 1)); //&p + 1是跳过p变量后的地址,4/8字节printf("%d\n", sizeof(&p[0] + 1));//p[0]就是‘a’,&p[0]就是a的地址,+1,就是b的地址,是地址就是4/8return 0;
}

 

二维数组

int main()
{//二维数组int a[3][4] = { 0 };//printf("%p\n", &a[0][0]);//printf("%p\n", a[0]+1);printf("%d\n", sizeof(a));//48 = 3*4*4printf("%d\n", sizeof(a[0][0]));//4printf("%d\n", sizeof(a[0]));//a[0]是第一行的数组名,数组名单独放在sizeof内部,计算的就是数组(第一行)的大小,16个字节printf("%d\n", sizeof(a[0] + 1));//a[0]作为第一行的数组名,没有单独放在sizeof内部,没有取地址,表示的就是数组首元素的地址//那就是a[0][0]的地址,a[0]+1就是第一行第二个元素的地址,是地址就是4/8个字节printf("%d\n", sizeof(*(a[0] + 1)));//*(a[0] + 1)是第一行第2个元素,计算的是元素的大小-4个字节printf("%d\n", sizeof(a + 1));//a是二维数组的数组名,数组名表示首元素的地址,就是第一行的地址,a+1就是第二行的地址//第二行的地址也是地址,是地址就是4/8   //a - int (*)[4]//a+1--> int(*)[4]printf("%d\n", sizeof(*(a + 1)));//a+1是第二行的地址,*(a+1)表示的就是第二行,*(a+1)--a[1]  //16printf("%d\n", sizeof(&a[0] + 1));//&a[0]是第一行的地址,&a[0]+1是第二行的地址,地址的大小就是4/8printf("%d\n", sizeof(*(&a[0] + 1)));//*(&a[0] + 1) 是对第二行的地址解引用,得到的就是第二行,计算的就是第二行的大小printf("%d\n", sizeof(*a));//a表示首元素的地址,就是第一行的地址,*a就是第一行,计算的就是第一行的大小//*a -- *(a+0)--a[0]printf("%d\n", sizeof(a[3]));//16字节 int[4]//如果数组存在第四行,a[3]就是第四行的数组名,数组名单独放在sizeof内部,计算的是第四行的大小int a = 10;printf("%d\n", sizeof(int));return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/247112.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++数据结构:B树

目录 一. 常见的搜索结构 二. B树的概念 三. B树节点的插入和遍历 3.1 插入B树节点 3.2 B树遍历 四. B树和B*树 4.1 B树 4.2 B*树 五. B树索引原理 5.1 索引概述 5.2 MyISAM 5.3 InnoDB 六. 总结 一. 常见的搜索结构 表示1为在实际软件开发项目中&#xff0c;常用…

分享77个菜单导航JS特效,总有一款适合您

分享77个菜单导航JS特效&#xff0c;总有一款适合您 77个菜单导航JS特效下载 链接&#xff1a;https://pan.baidu.com/s/1sfT9ONLH4ocliA1C7Z5xbQ?pwd6666 提取码&#xff1a;6666 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0…

计算机操作系统1

.11.操作系统的基本定义 2.操作系统的四大特征 2.1.操作系统的虚拟特征 3.操作系统的功能&#xff1a; 1.处理器管理 2.存储器管理 3.文件管理 4.设备管理 4.总结&#xff1a; 1.并发和共享互为存在&#xff0c;没有并发也就没有共享&#xff0c;反之也是。 2.并发和并行的…

腾讯云2023年双十二活动整理汇总

腾讯云双十二推出了年末感恩回馈活动&#xff0c;年底最后一次大促活动&#xff0c;大家把握好上云时间&#xff0c;小编给大家整理了2023年腾讯云双十二优惠活动&#xff0c;不要错过这次上云好时机&#xff01; 一、腾讯云双十二活动入口 活动地址&#xff1a;txy.ink/act/ …

C盘分析文件大小的软件

https://sourceforge.net/projects/windirstat/ 上面是windirstat的下载链接 界面是这样的&#xff1a; 选择C盘或者D盘&#xff0c;点击OK&#xff0c;就可以分析了 然后就可以看到哪些占比最高&#xff0c;可以针对性的清理

SoC with CPLD and MCU ?

AG32 MCU 产品支持多种接口外设&#xff0c;具备与业界主流产品的兼容性&#xff0c;并内置额外的2K FPGA 可编程逻辑。 产品支持 LQFP-48&#xff0c;LQFP-64&#xff0c;LQFP-100 &#xff0c;QFN-32等不同封装。其所有可用 IO 都可以任意地进行映射和互换&#xff0c;以灵活…

FacetWP User Post Type用户帖子类型插件

点击阅读FacetWP User Post Type用户帖子类型插件原文 FacetWP User Post Type用户帖子类型插件件是一个高效的工具&#xff0c;使用户能够轻松过滤和优化自定义帖子类型的搜索结果。通过充分利用这个强大工具的潜力&#xff0c;您将能够毫不费力地为用户构建直观的搜索体验&a…

写 SVG 动画必看!SVG系列文章3-动画标签

1、SMIL animation概览 SMIL不是指「水蜜梨」&#xff0c;而是Synchronized Multimedia Integration Language&#xff08;同步多媒体集成语言&#xff09;的首字母缩写简称&#xff0c;是有标准的。本文所要介绍的SVG动画就是基于这种语言。 SMIL允许你做下面这些事情&#…

如何从T-N曲线判断电机选对了没有

我的知乎原文&#xff1a;https://zhuanlan.zhihu.com/p/670156320? 如果你是一个刚入行的电机工程师&#xff0c;刚刚参加了一个新产品的开发&#xff0c;在众多电机供应商中让你去挑选一款合适的电机&#xff0c;该从哪个角度去入手呢&#xff1f; 今天这篇文章就从T-N曲线…

MySQL笔记-第03章_基本的SELECT语句

视频链接&#xff1a;【MySQL数据库入门到大牛&#xff0c;mysql安装到优化&#xff0c;百科全书级&#xff0c;全网天花板】 文章目录 第03章_基本的SELECT语句1. SQL概述1.1 SQL背景知识1.2 SQL语言排行榜1.3 SQL 分类 2. SQL语言的规则与规范2.1 基本规则2.2 SQL大小写规范 …

【已解决】Cannot find project Scala library 2.11.8 for module XXX

问题描述 在 flink 示例程序调试过程中&#xff0c;reload project 报错 Cannot find project Scala library 2.11.8 for module HbasePrint 报错如下图所示&#xff1a; 问题解决 经过搜索&#xff0c;初步判定是 pom 文件中 Scala 版本配置和项目中实际使用的版本不一致导…

Spring Bean的生命周期各阶段详解附源码

目录 Bean的生命周期Bean定义阶段Bean实例化阶段Bean属性注入阶段Bean初始化阶段Bean销毁阶段 Bean的生命周期 bean的生命周期&#xff0c;我们都知道大致是分为&#xff1a;bean定义&#xff0c;bean的实例化&#xff0c;bean的属性注入&#xff0c;bean的初始化以及bean的销毁…