编程之路,从0开始:指针初识篇

       Hello大家好!很高兴我们又见面了!

       给生活添点passion,开始今天的编程之路!

       从今天开始我们来学习C语言极其关键的一部分——指针

       C语言正是因为有了指针,才能完成许多许多的任务,这也是C语言的特点之一。倘若一个人学习C语言但是没学指针,那么它相当于白学。好了废话不多说,让我们正式开始介绍指针

目录

1、引入

2、取地址和解引用

(1)取地址

(2)定义指针变量

(3)解引用

3、空指针

4、指针变量大小

5、指针与整数的计算

6、void*指针

7、传址调用

8、指针与数组

(1)数组名理解

(2)指针打印数组

(3)数组名,取地址数组名,和数组名[]的区别

(4)指针数组模拟二维数组

 

(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;
}

87fa515d3615490aabcc8b7c31461357.png

5f346677982d4a91b97e4c16dbc266b1.png       我们发现以上代码输出结果在三十二位(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;
}

89170ec08c5e4117b8308d5f953dc003.png

       发现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;
}

b28f80a759e04d94a4c83599e1b09fa8.png

       我们发现,&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;
}

de9dc2641b164b0790ed40471bb8ab23.png

       我们现在来理解一下,我们的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数组名。数组名[下标]访问数组元素。

        好了,今天的内容就分享到这,期待与大家再见!

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值