对于变量内存的地址和指针变量的认识
一.内存中的地址都是按字节编号的,即内存中的每个字节的存储单元都有一个地址,在程序编译或函
调用时,根据程序中定义的变量类型为变量分配相应字节的存储空间.
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;
}
总结
以上就是本次指针的部分内容的部分理解,后续关于指针的内容我们还会继续,内容是比较详细全面,后续的内容我们敬请期待,让我们一起保持热爱,奔赴山海,您们的支持就是我创作下去的动力!