深 入 理 解 指 针 ( 第一弹 )

对于变量内存的地址和指针变量的认识

一.内存中的地址都是按字节编号的,即内存中的每个字节的存储单元都有一个地址,在程序编译或函

调用时,根据程序中定义的变量类型为变量分配相应字节的存储空间.

1个字节(byte)=8比特位(bit)

二.对于指针变量的认识:

int main()
{
	int a = 0;
	printf("%p", &a);
	int* pa = &a;
	return 0; 
}

注意:1.&   为取地址操作符,来取出a的地址  

        2.pa的类型是int*

        3.pa是指针变量的名字

简单理解:对于创建指针变量,前面的类型挪下来,后面带个*,把a的地址存起来

三.对于指针变量的大小

举个例子:假如一个机器有64个地址总线,每个地址线出来的电信号转化成数字信号后是0或者1,将64二进制位组成的二进制序列当做一个地址,需要8个字节的空间,指针变量的大小就是8个字节.指针变量是用来存放地址的,一个地址的存储需要多大空间,那么指针变量的大小就为多大.

特别强调!!!!!!

32位平台地址下对应的32位比特位(4个字节)     X64

64位平台地址下对应的64位比特位(8个字节)     X86xing

请看上图,我们不难发现指针变量的长度和类型没有关系!

对于void*类型的指针的说明

void*类型的指针泛指针,可以接受任意类型的地址,但是也有局限性,不可以对于整数的直接加减和 解引用.

int main()
{
   int a = 10;
   int* pa = &a;
   char ch = 'w';
   void* pv1 = &a;
   void* pv2 = &ch;
  return 0;

}

指针的进一步精彩理解

对于指针变量以及进行改变和替换值看上面的图片就可以了,那让我来最后概述一遍吧,

1.利用int*来定义指针变量并取出n的地址 

2.对*p所指的原对象进行更改值,也就是说对n进行更改,此时吧*p叫做解引用操作符.

3.最重要的一点 就是p但看的话是存放的地址,在进行比较的时候,是把p和地址进行比较,进而来将p和元素进行判断比较

举个例子:(注意看while里面的内容)

1.const修饰的变量叫做常变量(本质还是变量)

2.const修饰指针变量,但是对于const修饰指针变量,*的位置对于整个代码的影响

举个例子理解一下吧


n = 100;
m = 50;

int const *p =&n;
*p = 20;//const限制的是*p,所以*p不能再改变
 

int * const p =&n;
p = &m;//此时const限制的是p,所以p不能再进行改变

指针的运算 

一..利用指针加一来打印数组元素

先来一个之前学过的基础打印数组,思路操作起来也是非常简单滴!

最重要就是对下标进行一个for循环最后打印arr[i]

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int ze = sizeof(arr) / sizeof(arr[0]);
	
	for (i = 0;i < ze;i++)
	{
		printf("%d",arr[i]);
	
	}
	return 0;
}

其次我们再来用指针查找的方法来尝试一下

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int ze = sizeof(arr) / sizeof(arr[0]);
	int* p = &arr[0];//这是定义一个指针变量,也就是对应在第一个元素1
	for (i = 0;i < ze;i++)
	{
		printf("%d", *p);//此时*p来对应原来的元素,来进行打印
		p++;
	}
	return 0;
}

对于基础用下标打印和用指针查找的方法打印的细节理解

下标打印:是对arr[i]里面的i进行循环打印通过数组的下标来查找元素

指针查找:定义指针变量*p并将数的地址存储进去,

思考:如果把int换成char会发生什么??

答案:一个整型类型的元素占4个字节,而定义char型指针变量时,在解引用时,只会访问一个字节,想要打印成功的话需要访问四个字节,如果p+=4进行访问的话,跳过了中间的,容易报错

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int ze = sizeof(arr) / sizeof(arr[0]);
	int* p = &arr[0];
	for (i = 0;i < ze;i++)
	{
		printf("%d\n", *(p + i));

	}
	return 0;
}

int *p   p+i是跳过了i乘sizeof(类型)个字节

二..指针-指针

指针-指针的绝对值就是指针和指针之间元素的个数

size_t my_strlen(char* p)
{
	char* start = p;
	char* end = p;
	while (*end != '\0')
	{
		end++;
	}
	return end - start;
}

int main()
{
	char arr[] = "abcdef";
	size_t len = my_strlen(arr);//arr=arr[0]的地址
	printf("%zd", len);
	return 0;
} 

让我们来简单分析一下这个代码,在 主函数里面定义了一个my_strlen函数,然后对my_strlen函数进行定义,数组名其实就是首元素的地址,也就是arr[0],用char*来接受返回值,下一步对于start和end进行赋值,指针p本身也是一个变量,存放的是地址,等号相当于是赋值.

在C语言中,"%zd"(格式控制符)通常用于size_t类型的变量,size_t是一种无符号整数类型,用于表示内存大小或数组长度等非负整数值

野指针的常见类型以及如何规避 

int* test()
{
	int n = 20;
	return &n;
}

int main()
{
	int* p = (test);
	printf("%d\n", *p);
	return 0;
}

以上的例子可以说明野指针可以非法访问,也可以导致篡改内存,具体说明一下,从主函数进入,创建指针变量并进行访问test函数,而对于定义的test函数,n作为局部变量,作为返回值n的地址已出test函数,不能下一步进入到主函数中进行访问

如何去规避野指针 

1.对指针进行初始化(如果知道指针的明确位置),如果不知道指针的明确指向,可以尝试去定义空指针

int a = 10;
int *p =&a;//给你个明确的指向
int *p = NULL;//当没有明确指向时,可以先定义一个空指针
*p = 20;(错误)空指针不可以进行访问

2.不能超出指针范围,不要越界.

3.当空指针不在使用时,可以将其设为空指针.

int* p =NULL;
if(p!=NULL)
   {
     in*p=200;
   }

4.不要返回局部变量的地址

三.指针的关系运算

一、拓展知识------assert断言

引入函数的头文件为#include<assert.h>,检测指针的有效性

assert(p !=NULL);//如果正确就继续运行

assert的几个好处:无需更改代码就可以开启或关闭assert机制,如果确定程序没有问题,可以在前面定义一个宏NDEBUG(头文件之前定义) ,使其assert()失效 #define NDEBUG,但在release中可以实现优化

二、strlen函数的模拟实现

1.strlen函数是求字符串长度,求\0之前的字符串个数

话不多说,先打印一个求字符串长度len,代码如下

size_t my_strlen(char* p)//对于调用函数应该选择什么类型,取决于主函数想要输出的是什么
{
	size_t count = 0;
	while (*p)
	{

		count++;
		p++;
	}
	return count;//返回值类型都是size_t,所以把count的类型定义为size_t类型
}
int main()
{
	char arr[] = "abcdef";
	size_t len = my_strlen(arr);
	printf("%zd\n", len);
	return 0;
}

升级代码:可以在之前加上assert(i!=NULL)

还可以保证指针不被修改const  char *p,来保证*p解引用时对应的元素不被修改

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import  ssl
ssl._create_default_https_context = ssl._create_unverified_context

2.传值调用与传址调用(关于数字交换位置时的练习)

代码如下(示例):

以上的代码为传值调用,定义Swap1的函数来进行对a b值的替换,但是此操作不可行,无法实现操作,可以运用传址调用的方法来实现,代码如下

对于Swa1,传址调用,调用a,b,变量的地址,并且对地址进行操作修改,对于void调用函数,定义的类型取决与主函数,细节注意!!!!,对于数字的交换实现的简单思路(假想一个空瓶子来实现交换),写在等号右边的都是空的,最后保证假想的瓶子也是空的.

传值调用时,形参和实参分别占用不同的空间,对形参的修改不会影响到实参,想要修改的话采用传址调用

​
void Swap1(int* pa, int* pb)
{
	int z = 0;
	z = *pa;
	*pa = *pb;
	*pb = z;

}
int mian()
{
	int a = 0;
	int b = 0;
	scanf("%d %d", &a, & b);
	printf("交换前:a=%d b=%d\n", a, b);
	Swap1(&a, &b);
	printf("交换后:a=%d b=%d\n", a, b);
	return 0;
}

​

举个可以用传值调用的例子,对于二者的一个选取与应用区别,在未来函数中,如果只是需要主调函数中的变量来实现的话,采用传值调用,如果要修改主调函数的值时,可以采用传址调用.

int Add(int x, int y)
{ 
  int z = x + y;
  return z;
}
int main ()
{ 
  int a = 10;
  int b = 20;
  int z = Add(a , b);
  printf("%d\n",z);
  return 0;
}

总结

以上就是本次指针的部分内容的部分理解,后续关于指针的内容我们还会继续,内容是比较详细全面,后续的内容我们敬请期待,让我们一起保持热爱,奔赴山海,您们的支持就是我创作下去的动力!

  • 34
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值