C语言基础:指针

旧街凉风 提交于 2020-01-12 18:10:52

【指针】
 一、指针变量与定义
 C语言有两种变量:其中变量(普通变量)存储内容值;地址变量(指针变量)存储地址值。
  1、定义格式
    类型名 *指针变量名;*是指针变量的标志,不包含在变量名里
  注:
  (1)定义变量(普通变量、指针变量)都必须在前面有类型名。前类型后分号为定义语句。除此之外,其它语句都是执行语句。
  (2)在定义指针变量时,指针变量名前的 * 表示现定义的是一个指针类型变量。星号并不是指针变量名的一总分,只是一个标志。
  (3)指针变量专门用来存地址,禁止将一个整型直接赋给一个指针变量。

  2、指针变量的引用
  “&”取地址运算符,通过&运算符可以取出普通变量的地址。
  “*”指针运算符,*可以取出指针变量所指向的普通变量的值(间接引用普通变量)。功能是 *地址 -》 取出内容值。
  “&”“*”是单目运算符,优先级2级,方向从右向左
  指针变量运算方法:(口诀四)地址变量得地址,得谁地址指向谁, 有*为内容值,不是读就是写,*在赋值号左边为写,其它都为读。无*为地址值,地址赋值意味着改指向。  
  注:
  (1)可以通过赋值使一个指针变量“指向”某一普通变量(指针变量=&普通变量)。
  指针变量必须定义且初始化后再使用。
  (2)在C语言中正确的做法是先让指针变量指向一个确定的存储单元后,再通过该指针变量引用它所指向的存储单元。
  (3)变量名(普通变量、指针变量)都表示其存储单元内的值。
  (4)若指针变量p指向变量a,即将变量a的地址赋给了指针变量p。 
  (5)所有指针变量在内存中分配的字节数相同(2字节)。sizeof()


二、指向数组的指针变量
  若数组做为形参,则将数组名做指针变量来处理。
  1、指向数组元素的指针变量
  由于数组元素与变通变量一样,所以定义指向数组元素的指针变量与定义指向变通变量的指针变量完全一样。

  2、指向一维数组的指针变量
  注:
  (1)在C语言中规定,数组名代表数组的首地址,而且是一个地址常量。
  (2)当指针变量指向数组中的某一个元素时,指针变量加1后指向数组的下一个元素,指针变量减1时指向数组的前一个元素。
  (3)当指针变量指向数组时,下标运算([])用于数组也可用于指针变量后
  (4)若两个指针变量指向同一个数组,则这两个指针变量可以进行大小比较。且可以做减法运算,结果是间隔元素的个数。
  (5)在形参中的数组实际上是一个指针变量,并不是真正的数组,因为该“数组名”的值是可以改变的,而真正的数组名的值是不能改变的。
  (6)若形参是数组或指针变量,则在函数中可以通过该形参改变实参的值。


三、指向多维数组的指针变量
  若a是一个二维数组,则有:
  (1)a+i是行指针,即指向的是一整行。若对它加1则指向下一行。
  (2)*(a+i)和a[i]一样,都是一个列指针即指向的是一个元素。
  (3)*(a+i)+j和a[i]+j一样,都表示元素a[i][j]的地址。即与&a[i][j]等价。*(a+i)+j, a[i]+j, &a[i][j]地址三等价。
  (4)*(*(a+i)+j)、*(a[i]+j)、(*(a+i))[j]一样,都表示元素a[i][j]。元素四等价

  二维是行地址,一维是列地址(也可以叫元素地址)
  
  1、指向多维数组元素的指针变量
  如:int a[3][4]; 
      int *p = &a[0][3];
  则:
      p+1指向元素a[1][0];
      p+4指向元素a[1][3];
      p-2指向元素a[0][1];
  常用于取二维数组a元素地址的方式:
    &a[i][j]、a[i]+j、*(a+i)+j (一个[]号、一个*都是元素地址,只不过是由行指针变成了列指针)。

  2、指向由m个元素组成的一维数组的指针变量
  定义指向由m个元素组成的一维数组的指针变量的格式:
    基类型 (*指针变量名) [m]
    *指针变量名 -》 行指针变量
   (m为大于0的正整数,m的值是列数)
  其中:基类型(类型说明符)为所指数组的数据类型;* 表示其后的变量是指针类型 [m](长度)表示二维数组分解为多个一维数组的长度,也就是二维数组的列数。
  注意:(*指针变量名)两边的括号不可少,如果缺少括号,则表示是指针数组
  如:
  int a[5][7];
  int (*p)[7];//行指针,指向的每一行有7个元素。
  p=a;        //等价代换 a <=> p
  char b[10][80];
  char (*r)[80];
  r = b+5;

  [补充说明]
  1、多维数组的数组名也被编译器创建为常量,用来存放该数组的首地址。
  数组名array存储的是array[0][0]元素的地址,即存储的值是 &array[0][0]。
  可以按照首地址加偏移量的形式,使指针指向该数组中的任何一个元素,实现多维数组和指针的关联。
  例如:
  array           //指的是数组下标为0的那一行的首地址
  array+1         //指的是数组下标为1的那一行的首地址
  &array[1]       //指的也是数组下标为1的那一行的首地址

  array[1]        //等价于 array[1]+0,指的也是元素 array[1][0]的地址
  array[1]+2      //指的也是 array[1][2]的地址
  *(array+1)+2    //指的也是元素array[1][2]的地址

  *(*(array+1)+2) //指的是元素array[1][2]的值
  *(array[1]+2)   //指的也是元素array[1][2]的值

  2、二维数组是按行优先的规律转换为一维线性存放在内存中,因此可以通过指针访问二维数组中的元素。
  如果有 int a[m][n]
  则将二维数组中的元素 a[i][j]转换为一维线性地址,公式如下:
  线性地址 = a + i*m +j
  其中,a 为数组首地址,m 和 n 分别为二维数组行和列的个数。


四、指向字符串的指针变量
  字符串常量:c语言对字符串常量是按首地址处理字符串常量。
  
  C语言中,字符串既可以用字符数组来表示,也可以用字符串指针变量来表示;引用时,既可以逐个字符引用,也可以整体引用。字符串指针变量的定义说明与指向字符变量的指针变量说明是相同的,只能按对指针变量的赋值不同来区别。对指向字符变量的指针变量应赋予该字符变量的地址。例如:
  char c, *p = &c;
  表示 p 是一个指向字符变量 c 的指针变量。
  char *s = "C Language";
  则表示 s 是一个指向字符串的指针变量,把字符串的首地址赋予 s(应写出整个字符串,以便编译系统把该串装入连续的一块内存单元)。
  
  使用字符串指针变量表示和引用字符串的方法有两种:
  1.逐个引用
  #include <stdio.h>
  int main()
  {
      char *string = "C Language";
      for(; *string != '\0'; string++)
        printf("%c", *string);
      printf("\n");

      return 0;
   }

  2.整体引用
  #include <stdio.h>
  int main()
  {
      char *string = "C Language";
      printf("%s\n", string);
      
      return 0;

  注意:其他类型的数组不能用数组名来一次性输出它的全部元素,只能逐个元素输出。
  }

  字符数组和字符串指针处理字符串时的区别
  虽然用字符串指针变量和字符数组都能实现字符串的存储和处理,但是二者是有区别的,不能混为一谈。
  1、存储内容不同
  字符串指针变量本身是一个变量,存储的是字符串的首地址。而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以'\0'作为串的结束。字符数组中存储的是字符串本身(数组的每个元素存放一个字符)。
  2、赋值方式不同
  对字符串指针变量方式:
  char *p = "C Language";
  或
  char *p;
  p = "C Language";
  而字符数组虽然可以在定义时初始化,但不能用赋值语句整体赋值,只能对字符数组的各元素逐个赋值。
  3、地址是否可改变。
  字符串指针变量可以通过一定的计算来改变自身的指向,而数组名是常量不可改变。

  4、格式化的字符串
  5、杜绝指针空指向


五、指向函数的指针变量
  1、函数名与数组名一样,是起始地址,而且是一个地址常量。
  定义指向函数的指针变量的方式:
  数据类型 (*指针变量名)(形参类型列表): => * 是标志符
  注:
  (1)在定义指向函数的指针变量时,要注意有两个小括号,必须要有,不需要定义形参。
  (2)单独的函数名代表该函数的首地址(函数的入口地址)。
  (3)函数的指针变量只能指向函数的入口处(函数的首地址),不能指向函数的某条指令。(另对指向函数的指针变量加1是没有意义的)。
  (4)给指向函数的指针变量赋值时,只写函数名即可,不必写参数。
  函数指针变量常用的用途之一是把指针作为参数传递到其他函数。指向函数的指针也可以作为参数,以实现函数地址的传递,这样就能够在被调用的函数中使用实参函数。

  例如:
  int max(int a, int b)
  { return a>b?b:a;}

  main()
  {
     int x=6, y=10;
     int (*p)();
     p = max;
     printf("%d",max(x,y));
     printf("%d",p(x,y));
   }

  2、赋值
  函数指针的赋值形式:
  指针变量名 = 函数名;
  例如: p = fun;
  设 fun()函数原型为 int fun(int s, float t)
  用指针变量引用函数,方法是用(*指针变量名)代替函数名。
  例如:
  x = (*p)(a, b); 
  与下面的表示方法等价:
  x = fun(a,b)

  3、用指向函数的指针作为函数参数
  指向函数的指针可以作为参数,以实现函数地址的传递,这样就能够在被调用的函数中使用实参函数。
  例如:使用函数实现对输入的两个整数按从大到小顺序输出。
  
  #include <stdio.h>
  void swap(int *p1, int *p2)                //形参为指针变量
  {
      int temp;                              //定义临时变量
      temp = *p1;                            //把指针p1所指向的地址中的值暂存在 temp中 
      *p1 = *p2;                             //把指针p2所指向的地址中的值存在p1指向的地址中
      *p2 = tmep;                            //把temp中的值存储到p2所指向的地址中
      printf("swap 函数中的输出\n");
      printf("*p1=%d, *p2=%d\n", *p1, *p2); 
  }
  int main()
  {
      int a,b;
      int *point1 = &a, *point2 = &b;        //声明两个指针变量
      printf("请输入变量a和b\n");
      scanf("%d%d",&a,&b);
      if (a<b)
        swap(point1, point2);               //调用swap()函数
      printf("主函数中的输出\n");
      printf("a=%d, b=%d\n", a, b);      
      printf("*point1=%d, *point2=%d\n", *point1, *point2);
      return 0;
  }



六、返回指针的函数
  1、如果函数返回的值是指针类型的数据,该函数就称为返回指针的函数,又称指针型函数。
  
  返回指针的函数的定义方式为:
  类型名 *函数名(形参列表)
  {
      return 地址值;
   }
   例如:int *max(int *x, int *y)


七、指针数组和指向指针的指针变量
1、若一个数组的所有元素均为指针类型(地址),则称为指针数组。
    格式:
    类型名 *数组名[常量表达式];    
    该形式常用于指向若干字符串,这样使字符串处理更加灵活、方便。其中数据类型为指针值所指向的变量的类型。

    例如:int    *s[10];
    表示 s 是一个指针数组,数组中的每个元素都是指向整型的指针,该数组由10个元素组成,即s[0]、s[1] ... s[9],它们均为指针变量。s 是该指针数组的数组名,和数组一样,s 是常量,不能对它进行增量运算。s 为指针数组元素 s[0]的地址,s+i 为 s[i]的地址,*a就是a[0],*(a+i)就是a[i]。

  特别注意:指针数组和二维数组指针变量的区别。
  二维数组指针变量是单个变量,其一般形式中 "(*指针变量名)"两边的括号不可少。而指针数组类型表示的是多个指针(一组有序指针),在其一般形式中," *指针数组名 "两边不能有括号。

  int (*p)[3]; 表示一个指向二维数组的指针变量。该二维数组的列数为3或分解为一维数组的长度为3。
  int *p[3]; 表示p是一个指针数组,有3个下标变量:p[0]、p[1]、p[2],均为指针变量。

  指针数组也常用来表示一组字符串,这时指针数组的每个元素被赋予一个字符串的首地址。主要优点如下:
  A.各个字符串在数组内的位置调整将更加方便。这时只需要改变数组内各指针的指向,而无须实际调整字符串在内存中的存放位置。
  B.相对于二维数组来说,这样的组织方式允许不等长的字符串能够被以一种相对规整的方式组织在一起。效果就好像数组中每个元素就是一个字符串一样,尽管每个元素只是指向某字符串的指针。
  C.指向字符串的指针数组的初始化更为简单。各个字符串都是可以分别定义的,只要让数组中的指针指向各字符串即可。

2、它的每个元素都是一个指针类型(地址),即它的每个元素都相当于一个指针变量。

3、指向指针的指针变量
  用来存放指针变量地址的指针变量称为指向指针的指针变量。
  定义格式:
    数据类型  **指针变量名
  其中两个 ** 表示二级指针。数据类型是指通过两次间接寻址后所访问的变量类型。

  通过指针访问变量称为间接访问。由于指针变量直接指向变量,所以称为单级间址。而如果通过指向指针的指针变量来访问变量则构成二级间址。

  〖带形式参数的main()函数〗
  指针数组的一个重要应用就是作为main()函数的形参。
  main()函数都是不带参数的,无参的main()函数其定义部分的括号中要么是空的,要么只有一个关键字(void)。事实上,main()也是可以带参数的。
  C语言规定main()函数的参数只能有两个,习惯上这两个参数写为 argc 和 argv。因此,带参数的main()函数的形式为:
  main(argc, argv)
  C语言同时还规定,main()函数的第一个形参argc必须是整型变量,而第二个形参argv则必须是指向字符串的指针数组。因此,加上形参说明后,main()的一般形式为:
  main(int argc, char *argv[])

  由于main()函数不能被其他函数调用,因此不可能在程序内部获得参数的实际值。那么,在何处把实参值传递给main()函数的形参呢?实际上,main()函数的参数值是从操作系统命令行上获得的。当要运行一个可执行文件时,在DOS提示符下输入文件名,再输入实际参数即可把这些实参传送到main()的形参中去。
  DOS提示符下命令行的一般形式为:
  C:\>可执行文件名 参数1 参数2 ...;
  可执行文件名和参数之间、各参数之间要用一个空格分隔。
  特别要注意的是:main()函数的两个形参和命令行中的参数在位置上不是一一对应的。因为main()函数的形参只有两个,而命令行中的参数个数原则上未加限制。argc表示命令行参数个数(包括可执行文件名本身也算一个参数),argc的值是在输入命令行时由系统按实际参数的个数自动赋予的。argv是指向命令行参数的数组指针。指针argv[0]指向的字符串是可执行文件名,argv[1]指向的字符串是命令行参数1,argv[2]指向的字符串是命令行参数2,等等。


八、空指针
  在C语言中,可以声明指向void类型的指针,指向void类型的指针称为空指针。
  void在C语言中表示无类型,void指针则为无类型指针,void指针可以指向任何类型的数据。
  C语言中引入void指针的目的在于两个方面:一是对函数返回的限定,二是对函数参数的限定。

  此外,一般只能用指向相同类型的指针给另一个指针赋值,而在不同类型的指针之间赋值是错误的。除非进行强制类型转换,否则不能相互赋值。
  C语言允许使用void指针,任何类型的指针都可以赋值给它,即不指定指针指向一个固定的类型。定义格式为: void *p;
  上述定义表示指针变量 p 不指向一个确定的类型数据,其作用仅仅是用来存放一个地址。
  下面语句是合法的:
  int *p1;
  void *p2;
  p2 = p1;
  需要注意的是:void类型的指针可以接收其他数据类型指针的赋值,但如果需要将void指针的值赋给其他类型的指针,则还是需要进行强制类型转换。例如:
  int *p1, *p3;
  void *p2;
  p2 = p1;
  p3 = (int *)p2;  
  使用void关键字,需要注意以下事项:
  (1)如果函数没有返回值,应声明为void类型。
      在C语言中,凡不加返回值类型限定的函数就会被编译器作为返回整形值处理。例如,许多实例的主函数没有限定返回值,而直接写main(),这样就相当于 int main(),而不是 void main()。
  (2)如果函数不接收参数,应指明参数为void。     

  指针变量可以有空值,即指针变量不指向任何变量,不指向任何有用的存储单元。
在系统中已将NULL定义为0,即NULL的值为0。
注:
 (1)当一个指针变量的值为空指针时,我们不能引用它所指向的存储单元。
 (2)若某指针(地址)的基类型为void型,则有引用时应进行相应的强制类型转换。
  (3) 不能对void指针进行算术运算。因此声明void指针p后,p++,p--等都是不合法的。
  (4)如果函数的参数可以是任意类型指针,那么应声明其参数为void *,即void指针型。
  void不能代表一个真实的变量。定义void变量是不合法的。例如:void a; 就是错误的。
  void指针不等同于空指针,void指针是指没有指向任何数据的指针,即其指向的是一块空的内存区域,而空指针是指向NULL的指针,其指向的是具体的区域。 


  ● 指针运算
  1、指针变量的运算
  1) 赋值运算
  A.把一个变量的地址赋予指向相同数据类型的指针变量。
    int a = 10; 
    int *p;
    p = &a;
  B.把一个指针变量的值赋予指向相同类型变量的另一个指针变量。
    int a = 10;
    int *p, *p1;
    p = &a;
    p1 = p;
  C.把数组的首地址赋予指向数组的指针变量。
    int a[2], *p
    p = a;
    或:
    p = &a[0];
  D.把字符串的首地址赋予指向字符串类型的指针变量。
    char *p;
    p = "C Language";
    或
    char *p = "C Language";
    说明:并不是把整个字符串装入指针变量,而是把存放该字符串的字符数组的首地址装入指针变量。
  E.把函数的入口地址赋予指向函数的指针变量。
    int func(int a, int b);
    int (*pf)();
    pf = func;
   
  2) 加减算术运算
    对于指向数组的指针变量,可以加上或减去一个整数n,意义是把指针指向的当前位置(指向某数组元素)向前或向后移动 n 个位置。
    要注意的是,数组指针变量向前或向后移动一个位置和地址加1或减1在概念上是不同的。因为数组可以有不同的类型,各种类型的数组元素所占的字节长度是不同的。如指针变量加1,即向后移动1个位置,表示指针指向下一个数据元素的首地址,而不是在原地址基础上加1.
    例如:
    int a[5], *pa;
    pa = a; //*pa 指向数组 a,也是指向a[0]
    pa += 2; //*pa指向 a[2],即pa的值为 &pa[2]。
    特别注意:指针变量的加减运算只能对数组指针变量进行,对指向其他类型变量的指针变量做加减运算是没有意义的。

  2、两个指针变量之间的运算
  只有指向同一数组的两个指针变量之间才能进行运算,否则运算毫无意义。
  (1) 两指针变量相减:两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数,实际上是两个指针值(地址)相减之差再除以该数组元素的长度(字节数)。
      两个指针变量不能做加法运算,无意义。
  (2)两指针变量进行关系运算:指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。
  如:pf1 == pf2 表示 pf1 和 pf2 指向同一数组元素。
      pf1 > pf2 表示 pf1 处于高地址位置。
      pf1 < pf2 表示 pf2 处于低地址位置。
      
    指针变量还可以与 0 比较。
    p == 0 表明 p 是空指针,它不指向任何变量。
    p != 0 表示 p 不是空指针。
    对指针变量赋 0 值和不赋值是不同的。指针变量未赋值时,是任意值,是不能使用的。否则会造成意外错误。而指针变量赋 0 值后,则可以使用,只是它不指向具体的变量而已。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!