本篇是对c语言的指针进行一个初步的学习,让我们对指针有一个初步的认识。
1.什么是指针
2.指针和指针的类型
3.野指针
4.指针运算
5.指针和数组
6.二级指针
7.指针数组
1.什么是指针
我们要理解以下两个要点:
(1)指针就是内存中一个最小单元的编号,也就是地址。
(2)平时口语中所说的指针通常指的是指针变量,是用来存放内存地址的变量。
指针就是地址,那么,我们要先知道什么是地址。
我们首先要知道地址是如何产生的:
我们知道,机器在传输数据时只有两个值,一个高电平,一个低电平。32位的机器上有32根地址线,这32根地址线能够产生2^32个编号/地址,现在我们用这些地址来对内存进行划分,我们假设每一个地址用来标识是一个字节时,那么这2^32个编号就可以对(2^32)字节(Byte)==2^32/1024KB==2^32/1024/1024MB==2^32/1024/1024/1024GB==4GB大小的空间进行编址。我们发现这种划法是合理的,而地址是由32个0或1组成的二进制序列,用一个bite储存一位,那么一个地址则需要4个字节来储存,所以,在32位的机器上,一个指针变量的大小就是4个字节,同理,在64位的机器上,一个指针变量的大小就是8个字节。
2.指针和指针类型
我们知道,在指针指向不同的数据类型时,指针类型也是不同的
int a=0;
int* pa=&a;
char b='0';
char* pb=&b
我们可以看到,指针变量的定义方式是 type+*+指针名
type是指针存放指向的地址上存放的数据类型。
为什么指针变量要这样定义呢?它有什么意义呢,我们往下看。
pa与pc的地址都是相同的,说明他们其实都成功的存储了a的地址,但是当我们对pa与pc进行移动时,我们就发现了问题所在了,即不同的指针类型其实是决定了指针向前或者向后走一步的距离。
我们发现,指针类型是有非常重要的作用的:指针类型决定了指针在解引用时的访问权限。
3.野指针
野指针就是指针指向的位置是不可知的。
int main()
{
int* p;
*p=20;
return0;
}
如同上面这个代码,我们并不知道p指向的地址,所以p就是野指针。
int arr[5]={1,2,3,4,5};
int i=0;
int* p=arr;
for(i=0;i<10;i++)
{
printf("%d ",*p);
p++;
}
这个代码在循环时,当p的移动超过了数组的范围时,就发生了数组的越界,这时的p也变成了野指针
int* test()
{
int a=10;
return &a;
}
int main()
{
int* p=test();
*p=100;
return 0;
}
由于函数的在调用完成后,里面的变量就被销毁了(销毁:a的内存空间被还给操作系统,不再属于我们的程序),这时*p还指向这个地址,那它就成为了野指针。
综上所述,我们可以总结出野指针的成因:
(1)指针未初始化
(2)指针越界访问
(3)指针指向的空间释放
当我们不知道一个指针应该指向谁时,就可以把它赋给空指针NULL
int* p=NULL
NULL--是0地址,是不允许我们访问的(内存上有的区域是用户无法访问的),所以当指针赋给空指针后就不要再使用了。
那么我们应该如何规避野指针呢:
1-指针初始化
2-小心指针越界
3-指针指向空间释放即置NULL
4-避免返回局部变量的地址
5-指针使用之前检查有效性
4.指针运算
指针+-整数
指针-指针
指针的关系运算
#define N 5
float arr[N];
float* p;
for(p=&arr[0];p<&arr[N];)
{
*p++=0;
}
上面的代码就同时包含了指针+-整数与指针的关系运算,进入循环后,首先判断p是否指向arr【N】,如果不是,对p++,移动指针,直到指到最后一个元素。
int arr[10]={0};
printf("%d\n",&arr[9]-&arr[0]);
上面的代码就实现了指针-指针的运算,指针-指针有一个前提条件:两个指针必须指向同一块空间。
指针-指针的绝对值是这两个指针中间元素的个数
标准规定:在进行指针的关系运算时,允许指向数组元素的指针与指向数组最后一个元素后面的那个位置进行比较,但是不允许与指向第一个元素之前的那个位置的指针进行比较
什么意思呢,我们来看下面的代码
#define N 5
float arr[N];
float* p;
for(p=&arr[N];p>&arr[0];)
{
*--p=0;
}
首先p指向了下标为N的元素,但是我们知道数组的元素下标是从0到N-1的,所以这个元素是在数组之外的,但是我们依然可以那它来和我们数组内的元素(如arr【0】)进行比较。
#define N 5
float arr[N];
float* p;
for(p=&arr[N-1];p>=&arr[0];p--)
{
*p=0;
}
而在这个代码中,指针首先指向了下标为N-1的元素,在数组内部,然后指针不断向左移动,当指针一直移动到下标为0的第一个元素时,进入判断,符合条件,执行下面的代码,这时候指针还会向左移动一位,而这次移动就使指针指向了数组外部的元素并且是在数组第一个元素之前的,这时候再进行判断时就是让这个元素与数组内的元素进行比较了,虽然这个代码在大部分编译器中是能够运行的,但是由于它不符合上面的标准规定,我们还是要避免写出这样的代码。
5.指针与数组
在之前数组的学习中我们知道,数组名在大部分情况下表示数组第一个元素的地址,而指针也是用来储存地址的,那么我们用指针来访问数组中的元素就是可行的。
int arr[5]={1,2,3,4,5};
int* p=arr;//指向数组的首元素的地址
上面的代码说明了当一个指针p指向一个数组时,p+i指向的就是数组下标为i的元素。,那么同时我们也就可以通过解引用来对数组中的元素进行操作了。
6.二级指针
我们首先来看下面的代码
int a=10;
int* p=&a;
在变量a被创建时,内存会给a分配一个地址,当我们把这个地址传给指针变量p时,p中存放的就是变量a的地址,那么既然p也是一个变量,所以p应该也是有地址的,如果我们把指针变量p的地址再赋给一个指针变量呢?
int a=10;
int* p=&a;
int* * pp=&p;
这时候的pp就是一个二级指针,而p就是一级指针。
那么我们应该如何来理解二级指针的两个*呢,我们首先来看一级指针,(int* p)这里的int表示指针指向的对象时int类型的,*告诉我们p是一个指针,那么我们可以这样来看二级指针(int* *p)前面的int*我们可以看作指针指向对象的类型是int*的(一个指向对象为int类型的指针),而后面的指针同样告诉我们pp是一个指针。这样就变得好理解多了。
同样,我们也可以通过解引用来访问二级指针中存储的元素。
int a=10;
int* p=&a;
int* * pp=&p;
**pp=20;
**pp <==> *(*pp) <==> *(p) ==> a
7.指针数组
指针数组?,这到底是指针还是数组呢?
这其实是一个数组。只是这个数组里面的元素是指针。、
int a=1,b=2,c=3,d=4,e=5;
int* arr[5]={&a,&b,&c,&d,&e};
arr中存储的都是地址,所以arr就是一个指针数组,那么指针数组有什么作用呢,我们来举一个指针数组的小小的应用。
int main()
{
int data1[]={1,2,3,4,5};
int data2[]={2,3,4,5,6};
int data3[]={3,4,5,6,7};
int* arr[3]={data1,data2,data3};
int i;
for(i=0;i<3;i++)
{
int j;
for(j=0;j<5;j++)
printf("%d ",arr[i][j]);//*(arr[i]+j)
printf("\n");
}
return 0;
}
我们知道数组名代表数组第一个元素的地址,所以在arr是一个指针数组,arr中的三个元素都是这三个数组的地址。那么我们就可以通过下面的for循环,实现对这三个数组的打印。在我们打印每一个元素时,我们可以用两种方法arr[ i ][ j ]/*(arr[ i ]+j)。我们知道arr是一个指针数组,它里面所储存的都是地址/指针,那么这里的arr[ i ]表示arr中下标为i的元素,其实也是一个地址/指针,我们以arr【0】为例,arr【0】<==>&data1【0】<==>data1,所以arr【0】【j】<==>data1【j】。
*(arr【0】+j)<==>*(&data【0】+j)。
以上就是c语言指针(初级)的内容。