【原创】浅谈指针(一)

前言

如今的很多开发人员,对指针或多或少都有一些畏惧心理,都认为“指针经常会在一些不起眼的地方让程序崩溃”。确实,很多错误都是由于指针引起的。指针和内存密切相关,难免会由于地址或是数组越界,没有初始化等原因,导致程序崩溃,然而,其实大多数错误都是可以避免的。
其实本人也看过一本书,叫做《征服C指针》。这本书写的非常好,也希望大家有机会可以看看。这本书其实更多是针对C语言的语法和内存内部原理介绍。而本文将会更多的说到它的实际应用。

为什么需要指针

在不知不觉中,我们其实已经在使用指针了。很久以前,当那些老师教我们使用scanf的时候,一定会说:scanf参数中变量名前要加上“&”号。

scanf("%d %d",&a,&b);

其实,这里&符号的应用,暗含着指针和参数传递的操作。
其实scanf是非常老的函数了,在最早的C语言就有出现。那时候还没有C++的引用机制,由于函数传值机制,因此必须使用指针。
&符号是取地址运算符,我们可以尝试定义变量并输出他的地址。

#include<bits/stdc++.h>
using namespace std;
int qj;
int main(){
    int jb;
    cout<<&qj<<' '<<&jb;
}

这个程序尝试输出全局变量和局部变量的地址。可以看到,地址一般由十六进制的格式输出。

内存地址

讲了这么多,我们还没有介绍我们的内存。内存是一个存储器,一般家用内存从2GB到32GB不等。通常,我们执行的程序都保存在内存之中。有人会问,我们写程序都是保存在C/D盘中的,不是在磁盘里面吗?其实,磁盘的读取速度相较内存是非常缓慢的。因此,这类工作都是把磁盘中的数据先复制到内存,再执行其中的代码。
内存很大,为了把内存每一个字节做上标记,我们需要一个数字作为内存的标号,这个标号就是地址。可以理解成我们生活中的身份证号一样,根据这个号码,我们就可以定位到一个唯一的位置了。(具体说,一般大多数计算机都是把程序放进虚拟内存的,本文此处不展开详细描述,请参见其他资料)

指针变量

指针变量可以理解成保存一个地址的变量。一般的变量定义如下:类型 *变量名
例如int *a;
乍一看像是定义叫做*a的变量,但是其实定义一个叫做a的变量,类型是int*。

a这个变量用于保存变量的地址,&运算符也用于取得变量地址,那么,我们就可以这样使用指针了:

#include<bits/stdc++.h>
using namespace std;
int a=10;
int *p;
int main(){
    p=&a;
    printf("p..%p\n",p);
    printf("a..%d\n",a);
    printf("*p..%d",*p);
}

*p用于取得p地址的数值,到目前为止,我们学习了两个运算符:
&a 取得变量a的地址
*a 取得a号地址的数值

指针的加减运算

#include<bits/stdc++.h>
using namespace std;
int a[3]={1,2,3};
int *p;
int main(){
    cout<<a<<endl;
    p=a;
    ++p;
    cout<<p<<endl;
    cout<<*p<<endl;
}

输出:(前两个地址可能不同,但是相差一定为4)
0x601508
0x60150c
2

可以看到,其实数组名a就是一个指向数组首元素的指针,不同的是,a是一个常量,不能对a进行修改(例如a++)
因此,a是数组首元素的地址,自然可以赋值为p。
p++后,我们看到,结果不是0x601509,因为+1后,实际p不是加了1而是增加了sizeof(int),即让p到达下一个数组元素的地址。

我们发现:
p+1后,*p就等于*(p+1)
而根据指针运算法则,*(p+1)=a[1]
更加进一步说,a就是p,那么
*(p+1)=p[1]
推广开来,其实就是这个著名的指针定理:
p[i]=*(p+i)

进一步说,我们发现:
p[i]
=*(p+i) (指针的性质)
=*(i+p) (加法交换律)
=i[p] (指针的性质)

所以p[i]=i[p]。

指针运算的作用

使用指针访问数组元素

#include<bits/stdc++.h>
using namespace std;
int a[10]={4,6,3,7,8,4,5,9,1,2};
int main(){
    for(int *p=a;p<a+10;p++){
        cout<<*p<<endl;
    }
}

我们利用了指针进行了访问数组元素。
其实更好,更简便的写法是直接使用下标访问a[i]。很多书上用这种写法说明指针的优越性:

但是,a[i]=*(a+i),如果循环中多次出现,那么a+i就要被计算多次。但是用指针的方法,循环结束时才计算一次p++,因此速度较快。

然而这段话已经过时了,现代的编译器大多有自动优化功能,会自动优化这些a+i的计算,对编译器而言,写成指针和数组并无什么不同。因此,写这段的目的是:在非必要情况下,少用指针。指针用在这种场合就是杀鸡用牛刀了。

下标为负数的数组

#include<bits/stdc++.h>
using namespace std;
int a[10]={4,6,3,7,8,4,5,9,1,2};
int main(){
    int *p=&a[5];
    cout<<p[-1]<<endl;
}

p指向的是第5个元素,那么p-1就是指向第4个元素8。把指针指向数组中间元素,然后就可以把下标置换为负数了。

可变长数组(也称动态数组)

如果我们要定义一个数组,但是长度要等运行时确定,我们可以使用指针进行分配。

#include<bits/stdc++.h>
using namespace std;
int *p,n;
int main(){
    cin>>n;
    p=(int*)malloc(n*sizeof(int));
    for(int i=0;i<n;i++)cin>>p[i];
    for(int i=0;i<n;i++)cout<<p[i]<<" ";
}

使用malloc函数,给p分配n个空间,由于空间是连续的,因此我们可以把p当作数组使用。

时间关系,更多内容敬请期待下一篇文章:浅谈指针(二)
大致内容:

  • 函数传参机制
  • 引用和指针的异同
  • 应用:链表
  • 函数指针

第二期的链接:https://www.cnblogs.com/jisuanjizhishizatan/p/15365823.html

posted @ 2021-10-03 21:40  计算机知识杂谈  阅读(606)  评论(0编辑  收藏  举报