Hello大家好!很高兴我们又见面了!
给生活添点passion,开始今天的编程之路!
从今天开始我们来学习C语言极其关键的一部分——指针。
C语言正是因为有了指针,才能完成许多许多的任务,这也是C语言的特点之一。倘若一个人学习C语言但是没学指针,那么它相当于白学。好了废话不多说,让我们正式开始介绍指针
目录
(5)数组指针
1、引入
指针到底是个啥?在说他是个啥之前,我们先讲讲什么是内存,什么是地址。
先举个简单例子:在生活中,我们的旅馆酒店会有门牌号,每个门牌号代表一个房间。比方说我给前台说“XXX房间有老鼠!”,那么这时就会专门有人来XXX房间查看,对吧?现在我们把内存想象成酒店,地址就是酒店里各个房间的门牌号。一个房间一个门牌号,一个地址就对应着一块内存。每个内存单元大小取一个字节,一个房间可以装八个人(一字节等于八比特)。
现在我们再创建一个数组arr,数组里面就一个元素,那么我们访问arr[1]可以吗?当然不可以!因为arr[1]这个房间根本就不是你的,这也就是我们常说的非法访问,或者叫越界。
说了这么半天,什么叫指针呢?其实在C语言中,地址就是指针。当然我也可以把指针想象成一个箭头,这个箭头指向了一个房间(地址),那酒店工作人员收到XXX房间有老鼠这个指令后,便会去访问XXX房间,这个过程我们就叫解引用。而我们告知前台是XXX房间的这个过程就叫取地址。
2、取地址和解引用
现在我们收回在操作符详解那一节的伏笔,正式讲解取地址(&)操作符。
(1)取地址
在scanf函数中,我们经常使用&,因为我们要告诉系统,我们把哪一块内存输入这个数字或者字符。所以他的用途就是获取变量的地址。
(2)定义指针变量
现在我们定义一个指针变量:
#include <stdio.h>
int main()
{
int a = 0;
int* p = &a;
return 0;
}
现在我们定义了一个指针变量p,它指向了a的地址。我们分别来看int *p =&a这串代码各个部分的意思:
*代表我们的p是一个指针变量,int代表我们指针指向了一个整型数据,&a代表我们p指向了a的地址。
同理,我们如果讲int 换成char,那么他就是指向了一个字符型数据。
(3)解引用
现在我们输出a的值
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int a = 0;
int* p = &a;
printf("%d", *p);
return 0;
}
很明显,*的作用就是把指针指向的内容展示出来。
注意,不能这样初始化指针:
#include <stdio.h>
int main()
{
int a = 0;
int* p = 0;
printf("%d",*p);
return 0;
}
指针必须指向一个地址。
3、空指针
上面我们提到,指针必须指向一个地方。那如果我们定义它时不知道让他指向哪,就需要让他指向空指针NULL,我们可以把NULL理解为一片空地,没有任何内容。
4、指针变量大小
#include <stdio.h>
int main()
{
int* p = NULL;
char* p1 = NULL;
double* p2 = NULL;
printf("%d\n",sizeof(p));
printf("%d\n", sizeof(p1));
printf("%d\n", sizeof(p2));
return 0;
}
我们发现以上代码输出结果在三十二位(x86)环境下输出结果为4,六十四位系统(x64)环境下输出结果为8,也就是说,只要是指针(地址),他的大小就是4或8。
5、指针与整数的计算
现在我们运行一下代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int* p = NULL;
char* p1 = NULL;
printf("%p\n",p);
printf("%p\n", p+1);
printf("%p\n", p1);
printf("%p\n", p1+1);
return 0;
}
发现int*类型指针加一后地址增加了4,而char*类型指针加一后地址增加了1。
在C语言中,指针加一代表着跳过指针类型所占字节数。或者说,指针类型控制着指针与整数运算后向前(或向后)走多少个字节。
6、void*指针
这种指针最大的特点是“来者不拒”,倘若我们把他放到函数的定义里,任何类型的指针都可以传给他。但他不能与整数进行计算,因为他不确定占多少个字节。
7、传址调用
现在我们写一个交换swap函数。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void swap(int a, int b)//无需返回,所以定义void类型
{
int temp = 0;
temp = a;a = b;b = temp;
}
int main()
{
int a = 5;
int b = 3;
swap(a, b);
printf("%d %d", a, b);
return 0;
}
运行代码,发现这根本就没作用啊!这里我们又要收回函数篇的一个伏笔了(没错,我写的帖子全是伏笔)。
当时,我们说形参是实参的崇拜者,因为实参传给形参后,形参改变而实参不会改变。我们叫他单向值传递。也就是说,我们只是把这个值传给形参,形参怎么运算,都不改变实参的值,在函数返回后,形参也随之销毁了。
那有什么办法让我们的函数起作用呢?这里就需要传址调用了。
#include <stdio.h>
void swap(int* a, int* b)//无需返回,所以定义void类型
{
int temp = 0;
temp = *a;*a = *b;*b = temp;
}
int main()
{
int a = 5;
int b = 3;
swap(&a,&b);//函数接收地址,所以我们把a,b的地址传过去
printf("%d %d",a,b);
return 0;
}
运行代码后,我们发现参数的值果然交换了,为什么呢?我们思考一下,我们把地址传给形参,那形参改变的时候,其实改变的即是形参的地址,也是实参的地址。那地址都改变了 ,解引用之后的值可不就改变了呗!
8、指针与数组
(1)数组名理解
这一部分比较多,我们就开门见山:数组名就是首元素的地址。
但有两个例外:(1)当数组名放到sizeof中时,这里的数组名代表整个数组。
(2)&数组名,这里的数组名代表整个数组,取出的是整个数组的地址。
(2)指针打印数组
下面这串代码,打印出来数组arr
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[5] = { 1,2,3,4,5 };
int i=0;
for (i = 0;i < 5;i++)
{
printf("%2d", arr[i]);
}
return 0;
}
我们用指针对其进行改造
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[5] = { 1,2,3,4,5 };
int i=0;
for (i = 0;i < 5;i++)
{
printf("%2d", *(arr+i));
}
return 0;
}
这里我们把arr看成指针,arr加1就是跳过一个整型数据,也就是跳过一个数字,我们再解引用,就打印出了这个数字。
(3)数组名,取地址数组名,和数组名[]的区别
输出下面的代码:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[5] = { 1,2,3,4,5 };
printf("%p\n", arr);
printf("%p\n", arr+1);
printf("%p\n", &arr[0]);
printf("%p\n", &arr[0]+1);
printf("%p\n", &arr);
printf("%p\n", &arr+1);
return 0;
}
我们发现,&arr[0]和&arr[0]+1相差4,arr和arr+1相差4,&arr和&arr+1差了整整20!
这是因为取地址数组名取得是整个数组的地址,所以加一就跳过了整个数组的大小,而&arr[0]和arr都是数组首元素地址,没什么差别
(4)指针数组模拟二维数组
什么是指针数组呢?首先指针数组是一个数组,和整型数组不同的是,指针数组里存放的是指针(地址),不是整型。
现在我们定义一个指针数组,并存放三个一维数组(还记得吗,之前说过数组名也是地址),并打印一下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[5] = { 1,2,3,4,5 };
int arr1[5] = { 6,7,8,9,10};
int arr2[5] = { 11,12,13,14,15};
int* p[3] = {arr,arr1,arr2};
int i = 0;
int j = 0;
for (i = 0;i < 5;i++)
{
for (j = 0;j < 5;j++)
{
printf("%3d", p[i][j]);
}
printf("\n");
}
return 0;
}
我们现在来理解一下,我们的p,代表的是数组首元素地址,那么我们把他解开,就应该是1。现在我们尝试*(p+1),由于p指向的是数组,p+1自然就跳过了一个数组元素,所以输出结果是6。我们在此基础上再加1,再解引用,像这样:*(*(p+1)+1),输出结果为7,也就是第二行,第二个元素。这是指针访问二维数组的本质,我们也可以写成正常的p[1][1],因为下标引用操作符的本质也是对数据地址进行变换和管理。
也就是说,上面的代码也可以写成这样:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[5] = { 1,2,3,4,5 };
int arr1[5] = { 6,7,8,9,10};
int arr2[5] = { 11,12,13,14,15};
int* p[3] = {arr,arr1,arr2};
int i = 0;
int j = 0;
for (i = 0;i < 5;i++)
{
for (j = 0;j < 5;j++)
{
printf("%3d", p[i][j]);
}
printf("\n");
}
return 0;
}
(5)数组指针
数组指针,首先是一个指针,他指向的是一个数组。
现在我们定义一个数组指针:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[4] = { 1,2,3,4};
int(*p)[4] = &arr;
return 0;
}
我们可以看到,他和指针数组的区别就是,把*p括了起来。我们定义时的[4],就代表了这个指针数组里有四个int型的元素。我们再把定义好的数组arr的地址给他,就可以通过它访问数组arr。
访问代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[4] = { 1,2,3,4};
int(*p)[4] = &arr;
printf("%d",(*p[0]));
printf("%d",(*p)[2]);
return 0;
}
这里,p指向整个数组的地址,p=&arr,那么*p就等于*(&arr)也就是arr数组名。数组名[下标]访问数组元素。
好了,今天的内容就分享到这,期待与大家再见!