C语言中的指针(一)

来源:互联网 发布:乐昌网络问政平台 编辑:程序博客网 时间:2024/06/03 18:23

指针简介

指针:是一个值为内存地址的变量。指针类型的数据值为内存中的一个地址。
我们知道,任何一个数据在计算机中都是以二进制存储的,它们存储在计算机的内存地址中,而指针则是为了表示这些数据的地址数据。
介绍两种与指针有关的运算符:* 和 &
* 称为间接运算符,是为了取得指针类型中的地址中的数据。& 称为取地址运算符,是为了取得数据的地址信息。
声明指针类型: 数据类型 * 变量名。
例:

int x = 3;            //声明一个整型变量x;int *p;               //声明一个指向整形数据的指针变量p;p = &x;               //指针变量p指向整形数据x的地址;现在p的值存储的是x的地址。int ans = *p;         //取出指针p中地址中的数据;

要创建指针变量,首先要声明指针变量的类型,他是指向什么类型的指针(就是说这个指针中存储的是那个数据类型的地址)。int *p 表示声明的指针类型存储的是int类型的数据的地址,float *p 表示声明的指针类型存储的是float类型的数据的地址……(以此类推)。 &为取地址符,&x表示取数据x的地址,p = &x 表示将int型数据变量存放的地址信息赋值给指针变量p; *p表示取出指针类型p中的地址中的数据(也就是x的值)。

实例:

#include<stdio.h>int main(){    int x = 3;    int *p;    p = &x;    int ans = *p;    printf("存储数据x的地址信息为: %p\n\n", p);           //%p表示输出地址    //存储数据x的地址信息就是指针变量p中存储的数据    printf("指针变量p中地址对应的单元数据信息为: %d \n\n", ans);}

运行截图:
输出的数据地址信息会根据机器的不同有所不同

声明指针:我们已将知道,声明指针首先要知道指针指向的数据类型,这是因为不同数据类型占用的存储空间不同,即使占用的存储空间相同,他们的存储方式也不尽相同,所以为了更准确的表示数据,我们需要声明指针指向的数据类型。int p中, 运算符表示声明了一个指针类型,int表示这个指针指向的数据类型是int类型。但是指针是什么类型呢?指针就是指针,它属于指针类型,不属于整形或浮点型等其他类型,一个指针类型一般占八个字节的存储空间。

指针在函数之间的通信

思考一个问题:
有两个数据 x 和 y,我们现在要把输入的 x 和 y 交换数据后输出, 其中要求交换数据部分写在一个子函数中。

我们知道,若这个题写在主函数中,会很简单,建立一个临时变量temp就大功告成了。

temp = x;x = y;y = temp;

但是若要写在子函数中怎么办,我们知道子函数只能返回一个数值,不可能同时返回 x 和 y,同时我们也不可能在子函数中直接修改主函数的数据值,所以若要在子函数中进行数据交换,该怎么告诉主函数呢?这时候指针就帮上了大忙。
因为指针变量中存储的是数据的地址信息,对于一个数据来讲,他的地址信息是唯一的,所以,我们可以在子函数中直接对主函数中 x 和 y 的地址进行操作,这样就直接改变了 x 和 y的数据信息。

#include<stdio.h>void swap(int *x, int *y);             //函数参数为指针类型的数据int main(){    int x, y;    scanf("%d%d", &x, &y);    printf("交换数据之前的x = %d ,  y = %d\n\n", x, y);    swap(&x, &y);                    //因为函数接收指针类型的数据,所以实参要传递xy的地址,而不是传递xy的值    printf("交换数据之后的x = %d ,  y = %d\n\n", x, y);}void swap(int *x, int *y){    int *px = x;    int *py = y;    int temp;        //临时变量    temp = *px;    *px = *py;    *py = temp;}

运行截图:

主函数不多说,看子函数,首先子函数的形参是指针类型的x和y,说明x和y中存储的是指针信息,传递实参的时候要用&取出数据x和y的地址传递,子函数中又定义了两个指针变量px, py(这里注意,他们是在子函数中定义的,所以在子函数作用域结束时他们会被销毁),使px = x, py = y,这样px和x指针就同时指向主函数中的数据x,py和y指针同时指向主函数中的数据y,我们又设置了一个整形临时变量temp,利用temp交换了指针px和py中的数据信息,*px就代表主函数中的x值, *py就代表主函数中的y值,我们这样就直接交换了程序变量在内存中的数据,因为数据的地址是唯一的,所以就间接改变了主函数中的x和y的值。

那么问题来了,我可以交换地址中的数据,那我可不可以直接交换数据地址呢?
……我试了试,好像不行(c++中的引用变量应该是可以的,但可惜C语言没有引用变量),因为子函数的参数是两个地址,所以我们能够改变地址里的数据,但是无法交换两个地址(就好像如果我们不使用指针,也是无法将两个主函数中的数据直接在子函数中进行交换再返回给主函数的,因为子函数里交换的数据都是属于子函数内部作用域的数据,一旦离开子函数就会被销毁)。

以上的例子只是为了解释指针在函数中信息传递的作用,不作为实际应用交换数据的程序(因为很蠢)。

指针和数组

我们应该清楚,指针和数组密不可分,我们常用的数组表示法实际上就是在变相的应用指针来访问数据。

#include<stdio.h>int main(){    int count = 0;    int *p = NULL;    int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };                 int *p1 = arr;    int *p2 = &arr[0];    printf("p1指向的地址为: %p\n\n", p1);    printf("p2指向的地址为: %p\n\n", p2);    printf("利用数组检索数据:\n");    for (count = 0; count < 10;count++)    {        printf("%d ", arr[count]);    }    printf("\n");    printf("利用指针检索数据:\n");    p = arr;    for (count = 0; count < 10; count++)    {        printf("%d ", *p);        p++;    }    printf("\n");}

运行截图:

通过运行结果我们可以看到,数组arr[]的数组名arr其实是一个地址,他可以赋值给指针变量p1,它代表的就是数组首地址(也就是数组首元素的地址),而数组首元素的地址同时为&arr[0],因此指针变量p1 == p2,从图中我们也可以看出,我们可以用数组来遍历数组元素,同时也可以用指针来遍历数组元素,为什么?因为数组中的数据元素存储是连续的,将指针变量p指向数组首地址后,指针每次加一,指针都向后移动一个存储单元(不是移动一个字节),这是为什么声明指针必须声明指针类型的原因。一开始p== &arr[0], 即p指向arr[0],当p++后,p就指向了arr[1],所以,数组的表示其实是借助了指针。arr[n]的意思是*(arr+n),找到数组首地址,然后移动n个存储单元后取出该地址里的内容,该内容就是arr[n]的值。 注意:不要混淆 *(arr+n)和 *arr+n, *(arr+n) 表示的是arr数组第三个元素的值,而 *arr+n表示的是arr数组的第一个元素值加2。

函数、数组、指针

假设要编写一个处理数组的函数,函数的作用求数组中所有元素的和,应该如何调用此函数?

#include<stdio.h>int Sum(int *arr, int len);int main(){    int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };    int ans = Sum(arr,10);    printf("ans = %d\n", ans);}int Sum(int *arr, int len){    int count = 0;    int ans = 0;    for (count = 0; count < len; count++)    {        ans += arr[count];        //或者是  ans += *(arr+count);    }    return ans;}

首先我们来看子函数Sum,他的参数有两个,一个是指针变量arr,一个是整形变量len,因为该函数的作用是求数组中个元素的和,但是函数参数不能传递一个数组,但由于数组元素是按顺序存储的,所以传递了数组元素的首地址,这样便能通过地址的增加访问到整个数组元素。指针变量arr接收主函数数组的首地址,len接收主函数的数组长度(数据的数量)。
除了传递首指针和数组长度来约束外,我们还可以用首尾指针来约束子函数处理数据的范围。

#include<stdio.h>int main(){    ...    int ans = Sum(arr,arr+10);          //首指针+尾指针    ...}int Sum(int *start, int *end){    int ans = 0;    while (start < end)    {        ans += *start;        start++;    }    return ans;}

这里的end指针指向的是数组最后元素后面的一个地址,c语言保证再给数组分配空间时,指向数组后面一个位置的指针仍是有效的指针,但是不要访问该位置,因为该位置本身不属于数组。
在c语言中,arr[i]和*(arr+i)是等价的,只不过由于指针表示法更接近于机器语言,因此用指针可以生成一些高效的代码。

指针操作

1、赋值:
c语言中,我们可以把地址赋给指针变量,但是赋值时要注意地址应该和指针类型兼容,就是说,不要把double类型的数据赋值给int类型的指针。(例:int *p = &x)
2、解引用:
解引用便是求指针变量中存储的地址中的变量信息,用*标识符表示。(例:int x = *p)
3、取址:
和所有变量一样,指针变量也有自己的地址和值,对指针而言,&运算符给出指针本身的地址(不是指针指向的地址)。
4、指针与整数相加:
c语言中,我们可以用+运算符把指针于整数相加,此时整数会和指针所指向的类型的大小相乘,然后把结果与处地址相加。例如,对于int arr[]数组来讲,假设arr的地址是2000, 那么arr+2的地址不是2002,而是2008,这是为什么?因为int类型的数据在内存中存放占用四个字节,因此每次arr+1都是增加4个字节而不是一个字节。但是如果相加的结果超过了指针指向的数组范围,那么结果就是未定义的。
5、递增指针:
递增指针,自增,自增的长度也是根据指针类型来确定,int型增四个字节,double型增八个字节……
6、指针减去一个整数:
指针减去一个整数,相当于指针前移。举例:对于一个数组arr[10];现有指针变量p = &arr[4];那么p-2 = &arr[2],减去的长度依然视指针类型决定,int减四个字节,double减八个字节。
7、递减指针:
和递增指针一样。只不过是递减。
8、比较:
使用关系运算符可以比较指针的大小,前提是指针必须指向同类型的数据。

关于指针的减法,指针减去一个整数等于一个指针,指针减去一个指针等于一个整数。所以我们要想求数组的长度,可以用尾指针减去首指针的方法来计算。
PS:编译器不会帮我们检查指针越界的问题,不会帮我们检查指针是不是指向有效内容,所以我们在使用指针的时候一定要谨慎。
野指针:没有初始化的指针。当我们定义一个指针时,一定要给他初始化,不然这个指针就会指向内存中任意一个单元,若你没发现并对这个指针里的数据内容进行了操作,幸运的话,啥事没有,不幸的话,系统崩溃。所以,初始化指针,很重要!若你不知道要怎样初始化指针,就把他赋值为空,一定不能让他变成野指针。

int *p;                   //未初始化的指针(不要这样写)int *p = NULL;            //初始化为空的指针int x = 3;int *p = &x;              //初始化为指向x的指针

const 指针:

当我们使用const修饰指针时,有时候是为了保护数据不被修改。
举例:如果需要求一个数组的全部元素的和,我们可以将数组的首地址传给子函数,在子函数中进行求和并返回,这时我们只需要读数据就可以完成任务,不需要对数据进行处理。但是因为我们传递的数据是主函数中数组的地址信息,所以子函数完全有权利去修改数据,这样的话,如果我们没有注意到子函数修改了数据,那么主函数中数组的数据就发生了我们不知道的改变,会影响我们程序之后的运行。那么如何保证我们传进去的数组不被修改呢?很简单,传入参数的时候把形参用const修饰就可以了。

#include<stdio.h>int Sum(const int *arr, int len);          //求和函数int main(){    int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };    int ans = Sum(arr, 10);    printf("数组数据总和为: %d\n", ans);}int Sum(const int *arr, int len){    int count = 0;    int ans = 0;    for (count = 0; count < len; count++)    {        ans += arr[count];    }    return ans;}

该程序中,主函数中的数组不是const类型,所以可以被修改,但是子函数中的参数是const类型,是不可被修改的,因此,在子函数中,我们是无法修改arr数组里面的元素的,但是跳出子函数,我们便又能对arr数组进行数据操作。所以,为了保证数据安全,如果我们编写的函数需要修改数组,那么我们就不用const,如果不需要修改数组,那我们最好加上const。

在赋值方面,普通指针只能指向非const数组,而const指针可以指向const数组或非const数组。

要注意,const的位置不同,指针表示的含义也不同。
const int * p;代表指针类型p指向的数据不能被更改,但是指针变量p是可以再指向别的地方的,当他指向别的地方时,它指向的单元数据也就不能被修改。
int * const p;代表指针类型p指向的地址是不能被修改的,但是他指向的内容是可以被修改的。
const int * const p;代表指针类型p指向的地址是不能被修改的,同时指针指向的内容也是不能被修改的。