当前位置: 首页 > article >正文

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

三、指针数组

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

存放字符指针的数组

参数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给pf
 
	int 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()
{
    //函数指针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;
}

回调函数

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

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=16
	printf("%d\n", sizeof(a));//16
	printf("%d\n", sizeof(a + 0));//a+0 其实是数组第一个元素的地址,是地址就是4/8字节
	printf("%d\n", sizeof(*a));//*a是数组首元素,计算的是数组首元素的大小,单位是字节,4
	printf("%d\n", sizeof(a + 1));//a+1是第二个元素的地址,是地址大小就是4/8
	printf("%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)
	//计算的是整个数组的大小,单位是字节-16
 
	printf("%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,和上面的代码类似,是非法访问 - err
	printf("%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内部,计算的是整个数组的大小,单位是字节,6
	printf("%d\n", sizeof(arr + 0));//arr + 0是数组首元素的地址,4/8
	printf("%d\n", sizeof(*arr));//*arr是数组的首元素,计算的是首元素的大小-1字节
	printf("%d\n", sizeof(arr[1]));//arr[1]是第二个元素,大小1字节
	printf("%d\n", sizeof(&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()
{
	char arr[] = "abcdef";//数组是7个元素
	//[a b c d e f \0]
	printf("%d\n", strlen(arr));//6,arr是数组首元素的地址,strlen从首元素的地址开始统计\0之前出现的字符个数,是6
	printf("%d\n", strlen(arr + 0));//arr + 0是数组首元素的地址,同第一个,结果是6
	printf("%d\n", strlen(*arr));//*arr是'a',是97,传给strlen是一个非法的地址,造成非法访问
	printf("%d\n", strlen(arr[1]));//err
	printf("%d\n", strlen(&arr));//6
	printf("%d\n", strlen(&arr + 1));//&arr + 1是跳过数组后的地址,统计字符串的长度是随机值
	printf("%d\n", strlen(&arr[0] + 1));//&arr[0]+1是b的地址,从第二个字符往后统计字符串的长度,大小是5
 
	printf("%d\n", sizeof(arr));//7 - 数组名单独放在sizeof内部,计算的是数组的总大小,单位是字节
	printf("%d\n", sizeof(arr + 0));//arr+0是首元素的地址,大小是4/8
	printf("%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的地址,求字符串长度就是5
	printf("%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的地址向后数字符串的长度是5
 
	printf("%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/8
	printf("%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/8
 
	return 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*4
	printf("%d\n", sizeof(a[0][0]));//4
	printf("%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]  //16
	printf("%d\n", sizeof(&a[0] + 1));//&a[0]是第一行的地址,&a[0]+1是第二行的地址,地址的大小就是4/8
	printf("%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.kler.cn/news/157228.html

相关文章:

  • mysql which is not in SELECT list; this is incompatible with DISTINCT解决方案
  • Module build failed: Error: ENOENT: no such file or directory
  • 现在的00后,实在是太卷了......
  • springboot遇到的问题02
  • 【前端系列】前端存档术之keep-alive
  • 微信开发者工具里面模拟操作返回、录屏、网络速度、截屏等操作
  • Java-easyExcel入门教程
  • 认知觉醒(三)
  • 水库监管AI视觉算法与边缘计算盒子
  • 什么是数据增强,为什么会让模型更健壮?
  • 电子学会C/C++编程等级考试2022年06月(四级)真题解析
  • 解释LED显示屏的裸眼3D特效原理
  • 第一个小记录达成:第一个年费会员用户
  • 计算社会学发展
  • Android wifi 框架以及Enable流程
  • 质量小议35 -- SQL注入
  • 【NI-RIO入门】Real Time(实时系统解释)
  • 开发工具idea中推荐插件
  • Flink入门之部署(二)
  • 工业机器视觉megauging(向光有光)使用说明书(十五,轻量级的visionpro)
  • 基于python和django旅游管理系统
  • Android11编译第八弹:root用户密码设置
  • Leetcode 2953. Count Complete Substrings
  • 电梯安全远程监控系统的主要作用和意义
  • 华清远见嵌入式学习——C++——作业3
  • PTA 7-223 sdut-C语言实验-求阶乘(循环结构)
  • 基于Java SSM教学管理系统
  • C语言 操作符详解
  • vue项目配置多个代理
  • SpringBoot集成Mybatis使用切面对所有Service的事务统一管理