C语言指针初阶(超详细)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

这篇文章我们来浅析一下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;
}

解释: 一级指针pa中的地址是变量a中的地址,二级指针ppa中的地址是一级指针的地址,用一个图可以表示这个结构

七、指针数组

如果一个数组中的所有元素保存的都是指针,那么我们就称它为指针数组。其一般形式为:

     数据类型    *数组名[常量表达式][常量表达式]...... ;

它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身的大小决定,每个元素都是一个指针。

 例如: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;
}