指针概念及应用
指针的相关概念
1.指针是什么?
指针是内存中的一个最小单元的编号,其实就是指地址,对于我们平时口中所讲述的指针,通常指的是指针变量,指针变量是用来存放内存地址的变量。
2.地址与指针
一个32位机器在一个进程中可以一次操控4G的内存空间,并对这4GB的连续内存空间进行编码,得到2^32种编号,这个编号就是内存地址,一个编号(地址)对应一个内存单元。
指针,是内存中最小单元的编号,而内存中最小单元指的就是内存单元,内存单元的编号指的就是上述的2^32种编号,所以我们可以这样说:编号-地址-指针,这三种其实所指相同。
如图8.3地址1000是变量i的指针。
例如,将变量i的地址存放在指针变量p中,p就指向i。如图8.4
如图8.5
在地址2000上的变量是指向地址2005上的那个变量,在地质2000上该变量的内容是2005.同理,在地质2001上的变量是指向地址2004上的那个变量,在地址2001上该变量的内容是2004.
3.指针变量
一般格式:类型声明*变量名;
其中,“*”表示这是一个指针变量;“变量名”即为定义的指针变量名;“类型声明”表示本指针变量所指向的变量的数据类型。
在前面,我们学过许多种操作符,其中有个取地址操作符(&)以及解引用操作符(*)(又称为间接访问操作符)。
我们知道在创建一个变量时,当程序执行到创建变量,那么相应的会在内存空间内分配一个内存空间给这个变量,用于存储这个变量的值。
如果对一个变量使用取地址操作符,也那么就是取出这个变量的地址,通过之前的章节我们知道,例如一个int类型的变量,在创建变量时,内存会分给这个变量4个字节的空间大小,而一个字节对应着一个内存空间,对应一个内存地址,而四个字节就对应四个内存地址,而通常来说,我们口中的变量的地址指的是这个变量所占内存空间的低地址,也就是分配给这个变量的由低到高的地址中的第一个地址。
而当我们对一个变量使用取地址操作符时,取出的就是这个对应内存空间的第一个内存单元的地址。
如果我们取出这个变量的地址之后把它存放在另一个变量中,那么这个变量就是指针变量。
4.指针类型
指针变量,它也是一种变量,那么指针变量也是有类型的。
在代码中取出a的地址存放在pa中,pa是一个指针变量,这个指针变量的类型如普通变量一样,变量名的前面就是其类型,所以指针变量pa的类型是int*。
相对于普通的变量类型,指针变量的类型只是在类型后加上了*号,这个星号可和我们之前了解的解引用操作符不是一个作用(含义),这个星做作用是,与星号前面的类型进行结合,表明这个类型是一个指针类型,这个指针类型所定义的变量是指针变量。*的个数表示这个指针的级数。
5.定义指针
而定义一个指针变量的语法格式是:
type*name=NULL;
其中type位类型,name是你要创建的指针变量的变量名。
要注意的是,当你创建一个指针变量时,你需要给其进行初始化,如果你不知道或者说暂时不需要赋值,你可以先赋值为NULL。绝对不允许不进行初始化创建指针变量,不初始化会出现野指针,这是很危险的行为。
NULL在C语言中你可以理解为空指针,是计算机内存中保留的值。虽然C语言标准没有明确指出NULL空指针与指向内存地址为0x00000000的指针相同,但是在实际情况中,基本就是这样。
另外虽然在初始化时可以赋给指针变量NULL,但是解引用空指针,这种操作是不允许存在的,是C语言为定义的行为
6.指针类型的意义
- 指针类型决定了,在对指针进行解引用时,可以访问的内存大小,可以访问多少个字节大小的内存空间。或者说可以对多大的内存空间进行操作。
- 指针类型决定了指针的步长。
7.指针的赋值
一般格式:&变量名;
指针变量同普通变量一样,使用之前不仅要定义,而且必须赋予具体的值。未经赋予的值不能使用
给一个指针变量赋值可以有以下两种方法:
1.定义指针变量的同时就进行赋值。
int a;
int*p=&a;
2.先定义指针变量,然后再赋值。
int a;
int*p;
p=&a;
注意:如果在定义完指针变量之后再赋值,注意不要加“*”。
p中存储的是a的地址,*p就是通过p中存放的地址,找到内存中对应这个地址编号的一块空间,并直接对空间进行操作。这就是解引用操作符。
8.大小端字节序
字节序
字节序又称端序或尾序,在计算机领域中,指电脑内存中或在数字通信链路中,占用多个字节的数据的字节排列顺序。
字节的排列方式有两个通用规则:
大端序(Big-Endian)将数据的低位字节存放在内存的高位地址,高位字节存放在低位地址。这种排列方式与数据用字节表示时的书写顺序一致,符合人类的阅读习惯。
小端序(Little-Endian),将一个多位数的低位放在较小的地址处,高位放在较大的地址处,则称小端序。小端序与人类的阅读习惯相反,但更符合计算机读取内存的方式,因为CPU读取内存中的数据时,是从低地址向高地址方向进行读取的。
为什么会出现大小端呢?
当往内存中存放数据的时候,我们有很多种存放的顺序,正着放,反着放,随机存放等等等等,例如我们往内存中存放0x11223344,这只是演示。
如果是你让你从上述片段中快速的按顺序取出数据,你会选择哪种方法呢?所以就出现了大小端排序。
过这个大小端,为什么叫大小端而不是叫别的,据说是因为发明者当时在看格列夫游记,看到其中两个国家因为争论吃鸡蛋应该先从大头剥还是小投剥,发动了战争,而获得启发,起名大小端。
9.步长
10.野指针
野指针就是指针指向的位置是不可知的、随机的、甚至没有访问权限的。
为什么会存在野指针?
1.指针未初始化
#include <stdio.h>
int main()
{
int* p; //局部变量指针未初始化。默认为随机值
*p = 10;
return 0;
}
2.指针越界访问
就比如,对于一个数组,只有十个元素,而我非要去访问这个数组的第十一个元素,这就是数组越界访问。
指针也是,当指针指向的地址不在数组所拥有的内存空间范围时,指针就成为了野指针。
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int i = 0;
for (i = 0; i <= 10; i++)
{
printf("%d ", *(p+i));
}
return 0;
}
3.指针指向的空间释放
局部变量的作用域是有限的,当出了变量所在的局部范围,变量就自动销毁了,其所分配的空间就还给内存了。这个时候,如果主函数内部有指针指向这个局部变量销毁之前所指向的地址,那么局部变量自动销毁之后,这个指针就变成了野指针。
#include<stdio.h>
int* test()
{
int a = 10;
return &a; //&a=0x0012ff40
}
int main()
{
int* p = test();
return 0;
}
规避野指针
1.指针初始化
2.小心指针越界访问
3.指针指向空间释放,及时置NULL
4.避免返回局部变量的地址
5.指针使用之前检查有效性
11.二级指针
一级指针,创建一个指针变量用于存放一个普通类型变量的地址。例如
int a = 10;
int* pa = &a; // 其中pa是一级指针变量。
而指针变量也是变量,是变量在创建是就会分配内存空间,所以一个指针变量在内存中也是有一块内存空间的。那么也就存在相应的地址编号。
int a = 10;
int* pa = &a; // 其中pa是一级指针变量。
int** ppa = &pa; //ppa是二级指针变量
a的地址存放在一级指针变量pa中,pa的地址存放在二级指针变量ppa中。
解引用ppa通过存储在ppa内的地址,找到pa,再对pa进行解引用,找到a。
int a; 变量a的类型是int类型
int * pa ,这个*表示这个pa是一个指针变量,int表示pa存储的地址指向的内存空间中存储的是int类型的值
int* *pa ,第二个*表示,pa是一个指针变量,int*表示ppa存储的地址指向的内存空间中存储的是int*类型的。
指针运算关系
对于指向地址的指针,可以进行下列的运算:
1.指针 +/- 整数
通过一个小例题来理解
使用指针,将一个数组内的所有元素赋值为0
*p++;与(*p)++;*++p;的区别
*p++ === *(p++)----->>先p++再*p
(*p)++;---->>先对p解引用,再++。
*++p;----->>先++,再解引用。
一元运算符*和++的优先级相同,但结合律是从右往左(其他大部分是从左往右)。
2.指针 - 指针
使用指针-指针,运算时,前提是两个指针需要指向同一块内存空间。例如同时指向一个数组中的不同元素。
指针-指针得到的值的绝对值是两个指针之间的元素的个数。
3 比较两个指针大小
指针与数组
1.int a[] = {1,2}; int *p = a
数组类似于指向位置固定的指针
即a除了不能 进行a++,其他用法和指针差不多
2. *(p+i) = p[i] = a[i] = *(a+i)
p+i = &p[i] = a+i = &a[i]
假设 p=200 则 p+n =200+n*size
size = p所指向的类型的大小
3.数组作为参数传入函数
数组作为参数传入函数时,函数只会生成一个指针来保存传入数组的首地址
即 fun(int a[ ]) = fun(int* a)
数组作为参数传入函数被修改后,main中也会被修改。
4.字符串数组
字符串必须以\0结束,计算字符串长度计算到 \0 为止
sizeof
用于计算变量本身所占用的字节数,而不是变量指向的地址所占用的空间大小
strlen
用于计算以 NULL (\0)结尾的字符串中字符的个数
5.两种定义方式
char w[] = “hi” 被定义在栈区,可以被修改
char *w = “hi” 被定义在常量区,不能修改
6.int a[ ][3]={1,2,3,4,5,6}
int a[2][3]; int (* p)[3] = a;
不能写成int **p= a, 指针类型不同
7.a[i][j] = *(a[i]+j) = *( *(a+i)+j)
8.c[i][j][k] = *( *( *(c+i)+j)+k)
c[i][j][k] = *((c[i][j]+k) = *(*(c[i]+j)+k)
原理与上一条相似。
感谢:https://blog.csdn.net/2201_75314884/article/details/127992565
感谢:https://blog.csdn.net/m0_60292931/article/details/123981539