提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
这篇文章我们来浅析一下C语言指针,后续随着我对指针的深入,也会去写指针进阶的文章,下面我们来开始对初阶指针做一个讲解
一、指针是什么?
指针到底是什么呢?
通俗的来讲,指针其实就是内存单元的最小编号,平时口中所说的指针,通常是指的指针变量,也就是存放内地址的变量。
总结起来:指针就是地址,口语中说的指针通常指的是指针变量。
二、指针和指针类型
2.1指针类型的权限(1)
指针有多种类型,比如int*,char*,double*,等等等等,然后我们写一个程序测试一下这些指针的长度
这样我们可以知道,所有的指针的类型字节大小都相等,那为什么我们不同意指针类型呢,下面我们来讲一下这些指针类型的意义:
比如:char*的指针解引用只能访问一个字节,而int*的指针的解引用就能访问四个字节
通过上面两张图片即可看出,不同的类型的指针还是存在一定区别的。
int*的指针可以解引用四个字节,
char*的指针只能解引用一个字节。
所以指针类型决定指针解引用时访问多少个字节(指针的权限)
总结:type*p;
1.决定p指向的对象的类型
2.p解引用的时候访问的对象的大小是sizeof(type)。
2.2指针类型的权限(2)
如图,指针的类型还决定了指针+1/-1时的步长,整形指针跳过四个字节,字符指针跳过1个字节。
总结一下:指针类型决定了对指针解引用操作的权限,即解引用时能有多大的权限(能操作几个字节)。
三、野指针
什么是野指针呢,野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
3.1指针未初始化
野指针的其中一个成因:指针未初始化
#include<stdio.h> int main() { int*p; *p=20; reutnr 0; }
上述这个代码就是一个野指针的案例
3.2指针越界访问
#include<stdio.h> int main() { int arr[10]={0}; int*p=arr; int i=0; for(i=0;i<=11;i++){ *(p++)=i; } return 0; }
我们再来看一下这串代码,我们数组的大小是10,但是一共要循环12次,所以循环十次以后,后面的范围就越界了,这时就会形成一个野指针
如图便可以看出,这便是一个指针越界访问的例子。
3.3指针的空间释放
#include<stdio.h> int*test() { int a=110; return &a; } int main() { int*p=test(); printf("%d ",*p); return 0; }
上述代码中,a的空间进入函数创建,出函数时还给操作系统了,所以再次通过p去访问时就会形成一个野指针。
3.4如何规避野指针
1.明确知道指针应该初始化为谁的地址,就初始化为谁。
2.不知道初始化为什么值,暂时初始化为空值。
四、指针运算
4.1指针+-整数
#include<stdio.h> int main() { int arr[10]={0}; //不适用下标访问数组 int*p=&arr[0]; int sz=sizeof(arr)/sizeof(arr[0]); for(int i=0;i<sz;i++){ *p=i; p++; } p=arr; for(int i=0;i<sz;i++){ printf("%d",arr[i]); } return 0; }
上图中的代码就实现了运用指针实现指针+-整数,各位也可以在自己编译器上实现一编,便于更好的去理解这串代码。
4.2指针-指针
#include<stdio.h> int main() { int arr[10]={0}; printf("%d ",&arr[9]-&arr[0]); return 0; }
再看这串代码,这串代码运行后的答案为9,那为什么为9呢,这一共是十个元素,虽然十个元素都是0,但是&arr使我们得到的不是元素值,而是元素的地址的值,所以中间一共有九个元素,所以结果为9,当然你认为结果是36也是可以理解的,因为一共有9个元素,一个元素占四个字节,所以36也是能说的过去的。
这里有个地方我们要记住,指针-指针得到的绝对值是指针之间的元素个数
指针-指针的前提条件:指针和指针指向了同一块空间。
4.3指针的关系运算
地址是由大小的,
指针的关系运算就是比较指针的大小
#include<stdio.h> #define N_VALUES 5 float values[N_VALUES]; float* vp; int main() { for (vp = &values[N_VALUES]; vp > &values[0];vp--) { *--vp = 0; } return 0; }
看这串代码,便是利用指针的关系运算来实现对数组的初始化,一开始vp=5,然后每一轮循环都会-1,然后从第五个元素开始逐步进行初始化,五次循环之后,数组全部初始化为0,
五、指针和数组
数组的数组名其实可以看作一个指针。看下例:
例九:
int array[10]={0,1,2,3,4,5,6,7,8,9},value; value=array[0]; //也可写成:value=*array; value=array[3]; //也可写成:value=*(array+3); value=array[4]; //也可写成:value=*(array+4);
上例中,一般而言数组名array 代表数组本身,类型是int[10],但如果把array 看做指针的话,它指向数组的第0 个单元,类型是int* 所指向的类型是数组单元的类型即int。因此*array 等于0 就一点也不奇怪了。同理,array+3 是一个指向数组第3 个单元的指针,所以*(array+3)等于3。其它依此类推。
例十:
char *str[3]={ "Hello,thisisasample!", "Hi,goodmorning.", "Helloworld" }; char s[80]; strcpy(s,str[0]); //也可写成strcpy(s,*str); strcpy(s,str[1]); //也可写成strcpy(s,*(str+1)); strcpy(s,str[2]); //也可写成strcpy(s,*(str+2)););
上例中,str 是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str 当作一个指针的话,它指向数组的第0 号单元,它的类型是char **,它指向的类型是char *。
*str 也是一个指针,它的类型是char *,它所指向的类型是char,它指向的地址是字符串"Hello,thisisasample!"的第一个字符的地址,即'H'的地址。注意:字符串相当于是一个数组,在内存中以数组的形式储存,只不过字符串是一个数组常量,内容不可改变,且只能是右值.如果看成指针的话,他即是常量指针,也是指针常量.
str+1 也是一个指针,它指向数组的第1 号单元,它的类型是char**,它指向的类型是char*。
*(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向"Hi,goodmorning."的第一个字符'H'
下面总结一下数组的数组名(数组中储存的也是数组)的问题:
声明了一个数组TYPE array[n],则数组名称array 就有了两重含义:
第一,它代表整个数组,它的类型是TYPE[n];
第二,它是一个常量指针,该指针的类型是TYPE*,该指针指向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0 号单元,该指针自己占有单独的内存区,注意它和数组第0 号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的。在不同的表达式中数组名array 可以扮演不同的角色。在表达式sizeof(array)中,数组名array 代表数组本身,故这时sizeof 函数测出的是整个数组的大小。
在表达式*array 中,array 扮演的是指针,因此这个表达式的结果就是数组第0 号单元的值。sizeof(*array)测出的是数组单元的大小。
表达式array+n(其中n=0,1,2,.....)中,array 扮演的是指针,故array+n 的结果是一个指针,它的类型是TYPE *,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。在32 位程序中结果是4
例十一:
int array[10]; int (*ptr)[10]; ptr=&array;:
上例中ptr 是一个指针,它的类型是int(*)[10],他指向的类型是int[10] ,我们用整个数组的首地址来初始化它。在语句ptr=&array中,array 代表数组本身。
本节中提到了函数sizeof(),那么我来问一问,sizeof(指针名称)测出的究竟是指针自身类型的大小呢还是指针所指向的类型的大小?
答案是前者。例如:
int(*ptr)[10];
则在32 位程序中,有:
sizeof(int(*)[10])==4
sizeof(int[10])==40
sizeof(ptr)==4
实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。
六、二级指针
什么是二级指针,在之前学数组的时候我们知道,二维数组就是在一个数组中再嵌套一个有长度的数组。
以二维数组来帮助理解一下二级指针,一级指针是用来存放和指针类型相同的变量的地址,而需要存放指针变量的指针就是二级指针。
二级指针的定义:
type** variate;
二级指针的赋值:
int a = 3; int* pa = &a; int** ppa = &pa;
二级指针的使用:
用二级指针输出一级指针的地址
#include <stdio.h> int main(){ int a = 3; int* pa = &a; int** ppa = &pa; printf("%p ", *ppa); return 0; }
解释: 一级指针
七、指针数组
如果一个数组中的所有元素保存的都是指针,那么我们就称它为指针数组。其一般形式为:
数据类型 *数组名[常量表达式][常量表达式]...... ;
它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身的大小决定,每个元素都是一个指针。
例如:char *arr[]={“Sunday”,“Monday”},存储了两个指针,第一个指针指向了字符串"Sunday",第二个指针指向了字符串"Monday",而sizeof(arr)=8,因为在32位平台,指针类型大小占4个字节。指针数组最重要的用途是对多个字符串进行处理操作,因为字符指针比二维数组更快更有效。
下面是一个简单的例子。
#include <stdio.h> int main() { //定义三个整型数组 int a[5] = { 1,2,3,4,5 }; int b[5] = { 6,4,8,3,1 }; int c[5] = { 2,5,8,6,1 }; //定义一个存放指向整型变量的指针的数组arr int* arr[] = { a,b,c }; //通过接引用打印出三个一维数组的元素 for (int i = 0; i < 3; i++) { for (int j = 0; j < 5; j++) { printf("%d ", *(arr[i]+j)); } printf(" "); } return 0; }