指针与数组

1 指针

    在C语言中,对于任何类型,我们都可以在其所在的内存地址处产生一个包含此对象地址的对应变量。如果用比较直观地方式来看待这种变量,它们实际上是一种指向对象的变量,因此,我们把这些变量称为指针。
    指针是C语言中的精髓,通过它能够实现对物理地址的直接访问。C语言功能强大的主要原因就是具有指针结构。指针是一种特殊的数据类型,直接指向目标的存储地址,实现直接访问对象存储空间的功能。指针的重要性不言而喻,但在很多时候指针又是一把双刃剑。一方面,指针是构建数据结构和操作内存的精确而高效的工具。另一方面,它们又很容易误用,从而产生不可预知的软件bug。

1.1 指针的概念

    一般来说,指针是一个变量(更一般地说是一个数据对象),其数值为另一个变量的地址,即内存位置的直接地址。指针的类型是derived from其它类型,也就是说指针的类型是由它指向的类型决定的;其次指针是一种reference类型,即引用类型。
    总而言之,指针的本质就一句话:指针就是地址。

下面我们直观感受一下变量的地址。

#include <stdio.h>

int main()  
{  
    int a;  
    int *a_p;  
    a = 1;  
    a_p = &a;  
    printf("   a...%d\n", a);  
    printf("*a_p...%d\n", *a_p);  
    printf("  &a...%p\n", &a);  
    printf(" a_p...%p\n", a_p);  
    printf("&a_p...%p\n", &a_p);  
    return 0;  
}  

打印地址

这里做几点说明:

  1. %p中的p是pointer(指针)的意思,专门用于打印指针变量中的内容。
  2. 有时看到用%x打印指针的,虽然结果一样,但含义完全不同。%p:用合适的方式(一般是十六进制)输出指针变量中存放的另一个变量的内存地址,共8个字符,前2个为00,就是用来输出地址的,而不用来输出数值,输出中的ABCDEF大写;%x:用十六进制的方式打印出变量的值,用于输出无符号整数,默认前面不加0,输出中的ABCDEF小写,如果想加0可以写成%08x。选择用哪种输出格式,要同输出数据相配,否则可能出错。
  3. 也可以以10进制的形式输出指针地址:printf(“%ld”,&x);。

指针变量的示意图:

指针变量示意图

    图中左上角是变量名,右上角是变量地址,中间是变量存储的内容。
    可以这样来理解指针:指针是一种特殊的语言机制,它存放的是其它变量的地址,并且可以通过解引用操作符*,来获取该地址的内容。这也造成了一种指向的关系,如上图 a_p->a。
    除了上面简单的指针外还有一种高阶指针,即指向指针的指针。这里就不对此做详细介绍了。

1.2 指针的用法

    利用指针变量可以表示各种数据结构;能很方便地使用数组和字符串;并能象汇编语言一样处理内存地址,从而编出精练而高效的程序。

1.2.1 指针的声明

    当然就像其他变量或常量一样,在使用指针存储其他变量地址之前,必须对其进行声明。指针变量声明的一般形式为:

type *ptr;

    在这里,type 是类型标示符,表明被指向变量的类型,它必须是一个有效的 C 数据类型。而星号(*)表示该变量为指针。ptr是指针变量的名称。例如:声明int *pi; 的意思是pi是一个指针,而且指向一个int型变量即*pi是int型的。

值得注意的是:所有指针的值的实际数据类型,不管类标示符是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。

1.2.2 指向变量的指针

int *p;//声明一个指针p
int a=2;//定义一个值为2的整型变量a
p=&a;//把a的地址赋值给指针变量p
    经过上面三行代码的操作,指针p就指向变量a,而p的值是a地址。由于利用指针可以访问变量的存储单元,所以能够通过操作指针间接修改变量的值。例如*p=1;经过这条语句a的值也会变成1。因为此时p的地址不变,它还是指向a的, *p是对指针指向对象的操作,相当于把1放入指针p所指向的内存空间(p中存的就是该内存空间的地址),而a是这个内存空间的名字,内存空间里存的是什么a的值就是什么。

1.2.3 指向字符串的指针

    我们都知道可以用数组存储字符串,如char name[20]=”jack”;,其实也可以用指针指向字符串,如char *name=”jack”;,此时指针变量指向字符串的首个字符并可以依次访问字符串的各个字符。

1.2.4 指向函数的指针

#include <stdio.h>

int sum(int x,int y)
{
 return x+y;
}
int main()
{
 int a=5;
 int b=6;
 int (*p)(int,int);
 p=sum;
 int result=(*p)(a,b);
 printf("The result is %d\n",result);
 return 0;
}

运行结果
    不难发现上面代码块里语句(*p)(a,b)可以用p(a,b)来代替,因为p和sum就是一样的,只是用前者可能更容易理解一点。而我们要知道怎样定义一个指针指向函数,int (*p)(int,int)这是固定写法,前面的int是指针将来指向的函数的返回值的类型,如果没有函数返回值,那就是void,后面括号里的两个int 当然就是指针将指向的函数的形参。

1.2.5 指向结构体的指针

struct student
{
char *name;
int ages;
};
    首先按上面的方式定义一个结构类型,再根据类型定义结构体变量 struct student stu={“Rose”,15};定义一个指针指向结构体类型,struct student *p;把结构体变量stu的地址赋给指针变量p,p=&stu;此时有3种方式可以用来访问结构体中的属性ages:

stu.ages=15;
(*p).ages=15;
p->ages=15;

    不过第三种方式在C语言中是结构体专用的,也就是说只能用来指向结构体。

1.2.6 指向数组的指针

    定义一个数组并初始化,int array[5]={2,5,12,7,8},定义一个指针变量并把数组的地址赋给它,int *p=array,(数组名就是数组的地址),而数组的地址就是首元素的地址,因此我们的指针变量就指向了数组的首元素,*p=1。如果把(p+1),那么指针变量就指向了数组的下一个元素5,因此我们可以利用指针来遍历数组的各个元素:

#include <stdio.h>

int  main()
{
   int array[5] = {1,4,3,6,9};
   int *p =array;
   printf("array[1]=%d\n",*p);
   for(int i=0;i<5;i++)
   {
      printf("array[%d]=%d\n",i,*(p+i));
   }
   return 0;
}

数组打印结果

1.2.7 NULL指针

    在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个NULL值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。
    NULL 指针是一个定义在标准库中的值为零的常量。请看下面的程序:

#include <stdio.h>

int main ()
{
   int  *ptr = NULL;

   printf("ptr 的值是 %x\n", ptr  );

   return 0;
}

null指针
    在大多数的操作系统上,程序不允许访问地址为0的内存,因为该内存是操作系统保留的。然而,内存地址0有特别重要的意义,它表明该指针不指向一个可访问的内存位置。但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
    可以使用 if 语句检查一个空指针:

if(ptr) // 如果 p 非空,则完成
if(!ptr) //如果 p 为空,则完成

1.3 指针的基本操作

    C语言提供了6种基本的指针操作。下面是一个演示这些操作的程序:

#include <stdio.h>

void main ()
{
   int  array[5]={2,4,6,8,0};
   int *ptr,*ptr1,*ptr2;

   ptr=array;//把数组地址(第一个元素的地址)赋值给指针
   ptr1=&array[2];//把地第三个元素的地址赋值给指针

   printf("pointer value,dereferenced pointer,pointer address:\n");
   printf("ptr=%p,*ptr=%d,&ptr=%p\n",ptr,*ptr,&ptr);

   //指针加法
   ptr2=ptr+4;
   printf("\nadding an int to a pointer:\n");
   printf("ptr+4=%p,*(ptr+4)=%d\n", ptr+4,*(ptr+4));

   ptr++;//指针递增
   printf("\nvalue after ptr++\n");
   printf("ptr=%p,*ptr=%d,&ptr=%p\n",ptr,*ptr,&ptr);

   ptr1--;//指针递减
   printf("\nvalues after ptr1--\n");
   printf("ptr1=%p,*ptr1=%d,&ptr1=%p\n",ptr1,*ptr1,&ptr1);

   --ptr;//恢复为初始值
   ++ptr1;//同上
   printf("\nPointer reset to original values:\n");
   printf("ptr=%p,ptr1=%p\n",ptr,ptr1);

   //指针减法
   printf("\nsubstracting an int from a pointer:\n");
   printf("ptr2=%p,ptr2-2=%p\n",ptr,ptr-2);
}

结果

1.3.1 赋值

    通常使用数组名或取地址运算符&来把一个地址赋值给指针。上面的程序中,把数组的起始地址赋给了ptr,该地址是编号为0028FF2C的内存单元。变量ptr1得到的是数组第三个元素(array[3])的地址。

1.3.2 求值

    运算符*可取出指针指向地址中存储的数值。因此,*ptr开始为2,即存储在地址0028FF2C中的值。

1.3.3 取指针的地址

    指针变量同其他变量一样具有地址和数值,使用运算符&可以得到存储指针本身的地址。上面的程序中,ptr被存储在内存地址0028FF24中。该内存单元的内容是0028FF2C,即array的地址。

1.3.4 将一个整数加给指针

    可以使用+运算符把一个整数加给一个指针,或者将一个指针加给一个整数。两种情况下,这个整数都会和指针所指类型的字节数相乘,然后所得的结果会加到初始地址上。于是,ptr+4的结果等同于&array[4]。如果相加的结果超出了初始指针所指向的数组的范围,那么这个结果是不确定的,除非超出数组最后一个元素的地址能够确保是有效的。

1.3.5 增加、减小指针的值

    可以通过一般的加法(减法)或增量(减量)来增加(减小)一个指针的值。对指向某数组元素的指针做增量(减量)运算,可以让指针指向该数组的下一个(上一个)元素。因此,ptr++(ptr1–)运算把ptr(ptr1)加上(减去)数值4(即int类型所占字节数),使ptr(ptr1)指向array[1]。现在ptr/ptr1的值是0028FF30(下/上一个数组元素的地址),*ptr(*ptr1)的数值为4(array[1]的值)。值得注意的是ptr(ptr1)本身的地址仍然是0028FF24。因为变量不会也它的值的变化而移动位置。

1.3.6 从指针中减去一个整数

    可以用-运算符来从一个指针中减去一个整数。指针必须是第一个操作数,或者是一个指向整数的指针。这个整数都会和指针所指类型的字节数相乘,然后得到的结果从初始地址中减掉。于是,ptr2-2的结果等同于&array[2],因为ptr2是指向&array[4]的。如果相减的结果超出了初始指针指向的数组的范围,那么这个结果是不确定的,除非超出数组最后一个元素的地址能够确保是有效的。

2 数组

    说白了数组就是同种类型数据的集合,换言之就是数组中只能放一种数据类型的数据,比如int类型的数组、float类型的数组,里面存放的数据称为“元素”。

2.1 数组的概念

    数组(array)由一系列类型相同的元素构成。可以用声明告诉编译器程序需要一个数组结构。数组声明(array declaration)中包括数组元素的数目和元素的类型。编译器会根据这些信息创建合适的数组。数组元素可以具有同普通变量一样的类型。

2.1.1 数组的声明

    下面是一些数组声明的例子:

float array[123];
char code[13];
int static[20];
    方括号[]表示array和其他两个标示符均为数组,方括号内的数字指明了数组所包含的元素数目。可以用下标数字表示单个元素及访问数组中的该元素。下标数字也称索引,从0开始计数。因此,array[0]是数组array的首元素,array[122]是第123个元素,也就是最后一个元素。

2.1.2 数组的初始化

    程序中一般使用数组存储数据。例如可以用长度为12的数组存储12个月份:

int month[12]={1,2,3,4,5,6,7,8,9,10,11,12};
    由此可见可以使用花括号括起来的一系列数值来初始化数组。此时数组array中的元素依次是1、2、3、4、5、6、7、8、9、10、11、12。

2.2 数组的使用情景

    数组适用于要用到多个数据存储而又不想定义那么多变量或不知道数据个数之类的情况。采用循环输入变量到数组中,要输出时再用循环输出,这样可以让程序更简洁。当要用到具有相同类型且按一定次序排列的的数据的集合时可以利用数组操作,这样可以减少代码量。总之,数组就是可以在内存中连续存储多个相同类型元素的结构。

3 指针与数组的关系

    指针提供给我们一种用来使用地址的符号方法。同时指针还能够有效地处理数组,因为数组标识实际上就是一种变相使用指针是形式。可以利用指针访问数组的每个元素,并得到每个元素的数值。使用指针可以很方便的对数组中的元素进行遍历和操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值