8.1 指针是什么

一个变量的地址就是该变量的“指针”

定义一个变量,系统为其分配空间 有地址指向变量空间(变量单元),地址就称为“指针”。

指针是内存空间的地址,存储单元就是所分配的内存空间。

直接访问:直接通过变量名访问。

间接访问:将变量i的地址存在另一个变量中,然后通过该变量来找到变量i的地址

指针:一个变量的地址。

指针变量:存放另一个变量的地址的变量。

8.2 指针变量

8.1.1 使用指针变量的例子

指针变量:存放另一个变量的地址的变量。

例8.1 通过指针变量访问整形变量

# include "stdio.h"
# include "string.h"
# include "math.h"
int main()
{
    int a=100,b=200;
    // * pointer_a表示pointer_a所指向的变量(对象)
    // pointer_a(地址) 表示指向   
    int * pointer_a,* pointer_b;   
    // 把变量a的地址指针赋值给指针变量pointer_a
    pointer_a=&a;
    pointer_b=&b;
    printf("a=%d,b=%d\n",a,b); 
    printf("* pointer_a=%d,* pointer_b=%d\n",* pointer_a,* pointer_b);
    return 0; 
 } 

8.2.2 怎样定义指针变量

定义指针变量一般形式:类型名 * 指针变量名;

说明:

  1. 指针变量前面的 * 表示该变量的类型为指针型变量

  2. 在定义指针变量时必须指定基类型。不指定基类型的话,我们不知道所指向的数据在内存中所占字节数和存放方式。

    知道存放数据类型,才能按存储单元的长度以及数据的存储形式正确地取出该数据。

    一个变量的指针含义包括两个方面,一是以存储单元编号表示的地址,一是它指向的存储单元的数据类型。

  3. 如何表示指针类型。指向整形数据的指针类型表示 int * , 读作“指向int的指针或简称“int指针”。

  4. 指针变量中只能存放地址,不要将一个整数赋给一个指针变量。如:

    int * pointer_1,a=100;
    * pointer_1=100;//这是不合法的

8.2.3 怎样引用指针变量

在引用指针变量时,可能有3种情况:

  1. 给指针变量赋值。
  2. 引用指针变量指向的变量。如:pirntf(“%d”,* p);
  3. 引用指针变量的值。

注意:

  1. &为取地址符。&a是变量a的地址。
  2. *为指针运算符, *p 代表指针变量p指向的对象。
int main()
{
    int *p,*p1,*p2,a,b;
    printf("请输入两个数字:\n");
    scanf("%d%d",&a,&b);
    if(a<b)
    {
        p2=&a;//使p2变量指向变量a
        p1=&b;//使p1变量指向变量b
    }
    printf("a=%d\tb=%d\n",a,b);
    printf("max=%d\tmin=%d",* p1,* p2);//输出p1和p2所指向的变量
    return 0;
 } 

8.2.4 指针变量作为函数参数

指针类型变量作为函数参数的作用:将一个变量的地址传送到另一个函数中。

例如:

# include "stdio.h"
/*
    对输入的两个整数按大小顺序输出。 
*/
int main()
{
    void swap(int * p1,int * p2);
    int a,b;
    int * pointer_a,* pointer_b;
    printf("请输入两个数\n");
    scanf("%d%d",&a,&b);
    pointer_a=&a;
    pointer_b=&b;
    if(a<b)
        swap(pointer_a,pointer_b); 
    printf("max=%d\tmin=%d\n",a,b);
    return 0;
}

void swap(int * p1,int * p2)
{
    int temp;
    /*
     下面交换的是变量的值,不是地址值,temp是int类型,不是int指针类型
    */
    temp= * p1;
    * p1= * p2;
    * p2= temp;
 } 

如果想通过函数调用得到n个要改变的值,可以这样做:

  1. 在主调函数中设n个变量,用n个指针变量指向它们。
  2. 设计一个函数,有n个指针形参。
  3. 在主调函数中调用这个函数,在调用时将这n个指针变量作为实参,将它们的地址传给该函数的形参;
  4. 在执行该函数的过程中,通过形参指针变量,改变它们所指的n个变量的值;
  5. 在主调函数中就可以使用这些改变了值的变量。
# include "stdio.h"
/*
    对输入的两个整数按大小顺序输出。 
*/
int main()
{
    void exchange(int * p1,int * p2,int * p3);
    int a,b,c;
    int * pointer_a,* pointer_b,* pointer_c;
    printf("请输入三个数\n");
    scanf("%d%d%d",&a,&b,&c);
    pointer_a=&a;
    pointer_b=&b;
    pointer_c=&c;
    exchange(pointer_a,pointer_b,pointer_c);
    printf("the order is %d\t%d\t%d\n",a,b,c);
    /*
    C语言中实参变量和形参变量之间的数据传递是单向的“值传递”方式
    用指针变量做函数参数时同样要遵循这个规则。
    不能通过执行调用函数来实现改变实参指针变量的值,但是可以改变实参指针变量所指的变量的值。 
    */
    printf("%d%d%d",* pointer_a,* pointer_b,* pointer_c);
    return 0;
}

void exchange(int * p1,int * p2,int * p3)
{
    void swap(int * sp1,int * sp2);
    if( * p1<* p2) swap(p1,p2);
    if( * p1<* p3) swap(p1,p3);
    if( * p2<* p3) swap(p2,p3);
}

void swap(int * sp1,int * sp2)
{
    int temp;
    temp= * sp1;
    * sp1= * sp2;
    * sp2= temp;
 } 

8.3 通过指针引用数组

8.3.1 数组元素的指针

数组元素的指针就是数组元素的地址。

引用数组元素可以用下标法,也可以用指针法,使用指针法能使目标程序质量高。

C语言中,数组名代表数组元素中首元素的地址。因此,下面两个语句等价:

p=&a[0];

p=a;

8.3.2 在引用数组元素时指针的运算

在一定条件下,允许对指针进行加和减的算术运算。如:指针变量p指向数组元素

指针指向数组元素时,可以对指针进行一下运算:

  • 加一个整数(用+或+=),如p+1
  • 减一个整数(用-或-=),如p-1
  • 自增运算,如p++,++p
  • 自减运算,如p–,–p
  • 两个指针相减,如p1-p2,这个两个指针变量要指向统一数组时,才有意义

分别说明:

  1. 如果指针变量p已指向数组中的一个元素,则p+1指向同一个数组中的下一个元素,p-1指向统一数组中的上一个元素。加1代表的是加上一个数组元素所占的字节数。
  2. 如果p的初值为&a[0] p+1等同于a+1
  3. *(a+5)指向的是a[5] [] 是变地址符 将a[i]按a[i+1]计算地址,然后找出地址单元中的值
  4. 如果指针变量p1和p2都指向统一数组,如执行p2-p1,再用两个地址之差除以数组元素的长度,这样就可以知道他们所知元素的相对距离。地址相加是没有意义的。

8.3.3 通过指针引用数组元素

  1. 下标法
  2. 指针法,如*(a+1)或者 *(p+1)
# include "stdio.h"
/*
有一个整形数组a,要求输出数组中的全部元素。
*/
int main()
{
    int a[5];
    int i;
    printf("please enter 5 integer numbers:\n");
    for (i=0;i<5;i++)
        scanf("%d",a+i);
    for (i=0;i<5;i++)
        printf("%d\t",*(a+i));
    return '\0';
 } 

注意:C语言没有像Java那样的异常检查机制。所以我们要尽量避免逻辑上的错误,如:数组下标越界的错误。

指向数组的指针变量也可以带下标,如p[i]:对p[i]处理成*(p+i),必须知道p指向数组哪个元素的地址。

利用指针引用数组元素,比较灵活方便,有不少技巧。

​ 1、p++; *p;

p++使p指向下一元素a[1]。然后若再执行*p,则得到下一个元素a[1]的值。

​ 2、*p++;

++和*同优先级,结合方向为自右向左,因此他等价于 *(p++),先引用p的值,实现 *p的运算,然后再使p自增一。

如:for(i=0;i<10;i++,p++)

​ printf(“%d”,* p);

可以改写为

for(i=0;i<10;i++)

​ printf(“%d”,* p++);

​ 3、*(p++)和 *(++p)作用是否相同,不相同。前者是先取 *p的值,然后再使p+1后者是先使p+1 再取 *p

​ 4、++(*p)。表示p所指向的元素值加1,如果p=a,那么++( *p)和++a[0]等价。

​ 5、*(p–)== a[i–]; *(++p)== a[++i]; *(–p)== a[–i]

8.3.4 用数组名作函数参数

C编译都是将形参数组名作为指针变量来处理的。

所以:void swap(int arr[],int n) 可以改写成 void swap(int * arr,int n)

以变量名和数组名作为函数参数的比较.png

在函数调用进行虚实结合的方法都是采用“值传递”,它的值就是实参数组首元素的地址。函数执行期间,它可以再次被赋值。

# include "stdio.h"
/*
    将数组中n个整数按相反顺序存放 
*/
int main()
{
    void inv(int *x,int n);
    int i,a[10]={3,7,5,8,9,4,2,1,0,6};
    printf("初始数组为\n");
    for(i=0;i<10;i++)
        printf("%d\t",a[i]);
    printf("\n"); 
    inv(a,10);
    printf("调换顺序后的数组是\n");
    for(i=0;i<10;i++)
        printf("%d\t",a[i]);
    printf("\n");
    return 0;

 } 

void inv(int * x,int n)//*x: 传入数组首个元素地址, n:需要换几个数
{
    int *i,*j,temp,*p,m=(n-1)/2;
    i=x;
    j=x+n-1;
    p=x+m;
    for(;i<=p;i++,j--)
    {
        temp=*i;
        *i=*j;
        *j=temp;
    }
} 

归纳分析:如果一个实参数组,要想在函数中改变次数组中的元素的值。实参和形参对应关系如下:

  1. 形参和实参都用数组名
  2. 实参用数组名,形参用指针变量
  3. 实参和形参都用指针变量
  4. 实参为指针变量,形参为数组名。 实参作为指针变量,必须先指针变量指向数组常量。
# include "stdio.h"
/*用指针法对10个整数按由大到小排序(选择排序).c*/
int main()
{
    void sort(int a[],int n);
    int *p,a[10],i;
    p=a;
    printf("请输入10个不相同的数\n"); 
    for(i=0;i<10;i++)
        scanf("%d",p++);
    p=a;
    sort(p,10);
    for(i=0;i<10;i++)
        printf("%d\t",* p++);
    return 0;
}

void sort(int a[],int n)
{
    int i,temp,j;
    for(i=0;i<n-1;i++)
        for(j=i+1;j<n;j++)
        {
            if(a[i]<a[j])
            {
                temp=a[i];
                a[i]=a[j];
                a[j]=temp;
            }
        }
}

8.3.5 通过指针引用多维数组

1.多维数组元素的地址

C语言中,数组名代表数组元素中首元素的地址。

a[0] 代表以为数组a[0]中第0列元素地址,即&a [0] [0]

a[1]代表&a[1] [0]

所以a[0] [1]==a[0]+1(求取地址)

二维数组指针以及指针运算规律

二维数组指针以及指针运算规律.png

二维数组a的有关指针

二维数组a的有关指针.png

二维数组名指向行,一维数组名指向列。

在指向行的指针前面加一个*, 就转换为指针指向列的指针。

如:a 都是指向行的 加个 * ,就是a[0] [0]就列首地址

在指向列的指针前面加&,就成为指向行的指针。

2.指向多维数组元素的指针变量

指向数组元素的指针变量
# include "stdio.h"
/*遍历数组元素*/
int main()
{
    int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
    int *p;
    for(p=a[0];p<a[0]+12;p++)
    {
        if((p-a[0])%4==0)
            printf("\n");
        printf("%d\t",*p);
     } 
     return 0;
 } 

指向由m个元素组成的一维数组的指针变量

# include "stdio.h"
/*
输出任意一行任意一列的元素的值 
*/
int main ()
{
    int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
    /*
    int (* p)[4]
    表示(* p)有4个元素,每个元素为整形。
    相当于p所指的对象是有4 个整形元素的数组 
    p的类型不是 int* 而是int (*)[4] p被定义为指向一维数组的指针变量
    */
    int (* p)[4],i,j;
    p=&a;//这里不能写成p=a,这样表示p=a[0] 
    printf("请输入要输出的数的坐标(行和列)\n");
    scanf("%d%d",&i,&j);
    printf("a[%d][%d]=%d",i,j, * ( *(p+i)+j)); 
    return 0;
}

用指向数组的指针作为函数参数

用指针变量作形参,来接受实参数组名传递来的地址。可以有两种方法:

  1. 用指向变量的指针变量
  2. 用指向一维数组的指针变量
# include "stdio.h"
/*
计算总平均分数以及第n个学生的成绩
*/
int main ()
{
    void average(float *p,int n);
    void search(float (* p)[4],int n);

    float score[3][4]={{65,70,67,60},{80,87,90,81},{90,99,100,98}};

    average( * score,12);

    search(score,2);
    return 0;
}

void average(float *p,int n)//*p 指的是二维数组0,0的坐标地址 
{
    float * p_end,sum=0,aver;
    p_end=p+n-1;
    for(;p<p_end;p++)
        sum+= * p;
    aver=sum/n;
    printf("average=%.5f\n",aver);
}


/*
    此时p只能指向一个包含4个元素的一维数组,不能指向一维数组中的某一元素。
    p的值就是 该一维数组的起始位置。 
    score+i == p+i
*/
void search(float (* p)[4],int n)
{
    int i;
    printf("the score of No.%d are :\n",n);
    for(i=0;i<4;i++)
        printf("%5.2f\t",*(*(p+n)+i));
    printf("\n");
}

8.4 通过指针引用字符串

使用字符串的更加灵活方便的方法——通过指针引用字符串。

8.4.1 字符串的引用方式

在C程序中,字符串是存放在字符数组中的。引用字符串可以有两种方法:

  1. 用数组存放一个字符串,可以通过数组名和下标引用字符串中一个字符,可以通过数组名和格式声明”%s”输出该字符串。

  2. 用字符指针变量指向一个字符串常量,通过字符指针变量引用字符串常量。

    例如:

    # include "stdio.h"
    int main()
    {
        /*
        对字符指针变量p的初始化, 
        实际上是把字符串第一个元素地址赋值给指针变量
        */
        char * p="I love China.";
        /*
        %s是输出字符串所用的格式字符,在输出项中给出字符指针变量名p
        系统会输出p所指向字符串第一个字符,然后自动使p加1,使之指向下一个字符
         输出该字符,直到遇到字符串结束标志'\0'为止 
        */
        printf("%s\n",p);
        return 0;
     } 

在C语言中只有字符变量,没有字符串变量

8.4.2 字符指针作函数参数

如果想把一个字符串从一个函数“传递”到另一个函数,可以地址传递的方法。在被调用函数中可以改变字符换的内容,在主调函数中可以引用改变后的字符串。

例题8.20 用函数调用实现字符串的复制

1、用字符数组名作为函数参数

# include<stdio.h>
int main ()
{
    void cope_string(char form[],char to[]);
    char a[]="I am a teacher.";
    char b[]="You are a student.";
    printf("string a is %s\nstring b is %s\n",a,b);
    copy_string(a,b);
    printf("string a is %s\nstring b is %s\n",a,b);
    return 0;
}

void copy_string(char form[],char to[])
{
    int i=0;
    for(;form[i]!='\0';i++)
    {
    to[i]=form[i];
    }
    to[i]='\0';
}

2、用字符型指针变量作实参

# include<stdio.h>
int main ()
{
    void cope_string(char form[],char to[]);
    char a[]="I am a teacher.";
    char b[]="You are a student.";
    char *p1,*p2;
    printf("string a is %s\nstring b is %s\n",a,b);
    p1=a;p2=b;
    copy_string(p1,p2);
    printf("string a is %s\nstring b is %s\n",a,b);
    return 0;
}

void copy_string(char form[],char to[])
{
    int i=0;
    for(;form[i]!='\0';i++)
    {
    to[i]=form[i];
    }
    to[i]='\0';
}

3、用字符型指针变量作形参和实参

# include<stdio.h>
int main ()
{
    void cope_string(char *form,char *to);
    char *a="I am a teacher.";
    char b[]="You are a student.";
    char *p1,*p2;
    printf("string a is %s\nstring b is %s\n",a,b);
    p2=b;
    copy_string(a,p2);
    printf("string a is %s\nstring b is %s\n",a,b);
    return 0;
}

void copy_string(char *form,char *to)
{
    char temp;
    for(;*form!='\0';form++,to++)
    {
        *to=*form;
    }
    *to='\0';
}

注意使用%s时不会输出’\0’,而使用%c时可以输出’\0’

PDF282页上有程序优化,自己去看

8.4.3 使用字符指针变量和字符数组的比较

字符数组和字符指针都能实现字符串的存储和运算

但二者有区别,主要有:

  1. 字符数组由若干个元素组成,每个元素中放一个字符,字符指针变量中存放的是地址,存的不是字符串。

  2. 赋值方式。可以对字符指针变量赋值,但不能对数组名赋值。

    如:

    char *a;
    a="I love China!";            //量字符串元素地址赋值给指针变量,合法。但赋值
                                //给a的不是字符串,而是字符串第一个元素的地址。
    char str[14];
    str[0]='I';
    str="I love China!";        //数组名是地址,是常量,不能被赋值。
  3. 初始化的含义。

    对字符指针变量赋初始值,赋予第一个元素的地址

    定义字符数组,并把字符串赋值给各个元素

  4. 存储单元的内容

    数组是若干个存储单元,存着各元素的值。

    字符指针变量,只分配一个存储单元(Visual C++为指针变量分配4个字符)。

    注意:指针必须先赋值(指定指针指向),在使用。

  5. 指针变量的值是可以改变的,数组名代表一个固定的值,不能改变。

  6. 字符数组中各个元素的值是可以改变的(可以对它们再赋值),但字符指针变量指向的字符串常量中的内容是不可以改变的。

    指向字符串的指针为何不能用来修改此字符串(c语言)

  7. 引用数组元素。都可以用下标,都可以用指针 ,使用指针之前,确保指针指向相同数组

  8. char *format;
    format="a=%d,b=%d\n";
    printf(foramt,a,b);        //因此只要改变指针变量所指的字符串,就可以该变输入输出的格式,这种printf函数称为可变格式输出函数。

    使用字符数组时,只能采用在定义数组时初始化或逐个对元素赋值的方法。

8.5 指向函数的指针

8.5.1 什么是函数指针

定义了一个函数,在编译时,编译系统为函数代码分配了一段存储空间,这段存储空间的起始地址(又称入口地址),称为这个函数的指针。

int (*p)(int,int);

说明:

  1. 这表示定义一个指向函数的指针变量,用来存放某一函数的起始地址,这意味着此指针变量指向该函数。
  2. p的类型用int(*)(int,int)表示

8.5.2 用函数指针变量调用函数

调用函数:

1.通过函数名调用;

2.通过指向函数的指针变量调用该函数

例题8.22用函数求整数a和b中的大者

# include "stdio.h"
int main()
{
    int (*p)(int,int);
    int max(int a,int b);
    p=max;                 //将函数指针变量指向max函数 ,将max函数的入口地址赋值给了p
    int a=5,b=6,c;
    c=(*p)(a,b);
    printf("%d",c);
    return 0;
 } 

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

注意:

  1. int (*p)(int,int);

    (*p):两侧括号不能省略,表示p先与 *结合 ,是指针变量,再与后面的()结合,()表示的是函数。表明这个指向变量指向的是函数。

  2. 如果写成:int *p(int,int);

    这样相当于 int *(p(int,int)); 就成了声明一个p函数,这个函数的返回值是指向整形变量的指针。(主要是因为()的优先级高于 *)

8.5.3 怎样定义和使用指向函数的指针变量

一般形式:

类型名 (* 指针变量名) (函数参数列表);

说明:

1.定义指向函数的指针变量,只能指向规定的返回类型,且参数列表相同的函数。

2.如果要用指针调用函数,必须先使指针变量指向该函数。(指针变量先赋值再使用

3.再给函数指针变量赋值时,只须给出函数名而不必给出参数

4.用函数指针变量调用函数时,直接用(*p)代替函数名,后面()填写相应的实参。

5.对指向函数的指针变量不能进行算术运算。进行加减是没有实际意义的

6.用函数名调用函数,只能调用一个,通过指针调用函数比较灵活,可以根据不同情况进行调用。

例题:输入两个数,让用户选择输出最大最小的数。

# include "stdio.h"
int main()
{
    int (*p)(int,int);
    int max(int a,int b);
    int min(int a,int b);
    p=max;                 //将函数指针变量指向max函数 
    int a=5,b=6,c,n;
    printf("pleanse chose one or two:\n");
    scanf("%d",&n);
    if(n==1)p=max;        //输入的是1的话则使指针指向max 
    if(n==2)p=min;        //输入的是2的话则使指针指向min
    c=(*p)(a,b);
    printf("%d",c);
    return 0;
 } 

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

int min(int a,int b)
{
    return a>b?b:a;
}

8.5.4 用指向函数的指针做函数参数

例如:

void fun(int (*p)(int),int (*p2)(int,int))//定义函数,参数两个函数指针变量
{                                          //将两个函数名传递给函数指针变量
    int a,b,i=3,j=5;
    a=(*p)(i);                              //调用f1函数,i是参数
    b=(*p2)(i,j);                          //调用f2函数,i,j是参数
}

函数只有被调用时,才分配存储单元。

使用函数指针变量,可以简化程序的编写,减少冗余代码,

通过一个例子来展示指针函数参数的应用

# include "stdio.h"
int main()
{
    int (*p)(int,int);
    int max(int a,int b);
    int min(int a,int b);
    int add(int a,int b);
    int fun(int a,int b,int (*p)(int,int));
    int a=5,b=6,c,n;
    printf("pleanse chose one , two or three:\n");
    scanf("%d",&n);
    if(n==1)p=max;        //输入的是1的话则使指针指向max 
    if(n==2)p=min;        //输入的是2的话则使指针指向min
    if(n==3)p=add;        //输入的是3的话则使指针指向add 
    fun (a,b,p);        
    printf("%d",c);
    return 0;
 } 

void fun(int a,int b,int (*p)(int,int))
{
    printf("%d",(* p)(a,b));
}

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

int min(int a,int b)
{
    return a>b?b:a;
}

int add(int a,int b)
{
    return a+b;
}

8.6 返回指针值的函数

int *a (int x,int y);

在a的两侧分别为* 和 () 而()的优先级要比*的高,所以a先与()结合,显然这是函数的形式。

函数前有*, 表是这是一个指针型函数。

定义返回指针值的函数的一般形式:

类型名 * 函数名(参数列表);

例题:

# include <stdio.h> 
int main()
{
    float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}};
    float * search(float (*p)[4],int n);
    float * res;
    int i,k;
    printf("enter the number of student:\n");
    scanf("%d",&k);
    printf("the score of No.%d are:\n",k);
    res=search(score,k);
    for(i=0;i<4;i++,res++)
        printf("%.2f\t",* res);
    printf("\n");
    return 0;
}

float * search(float (* p)[4],int n)
{
    float *pt;
    pt = * (p+n);  //pt的值是&score[k][0] 
    return pt;
} 
# include <stdio.h> 
/*
找出成绩不及格的学号以及输出学生的分数
*/
int main()
{
    float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}};
    float * search(float (*p)[4]);
    float * res;
    int j,i;

    for(j=0;j<3;j++)
    {
        res=search(score+j);
        if(res==*(score+j))
        for(i=0;i<4;i++,res++)
            printf("%.2f\t",* res);
        printf("\n");
    }
    return 0;
}

float * search(float (* p)[4])
{
    float *pt;
    int i;
    pt=NULL;
    for(i=0;i<4;i++)
        if(*(*p+i)<60)
            pt = * p;  //成绩不合格的,pt的值是&score[k][0] 
    return pt;
} 

8.7 指针数组和多重指针

8.7.1 什么是指针数组

一个数组,所有元素都是指针类型数据,称为指针数组。如:int *p[3];

优先级:()>[]>*

先与优先级高者结合,然后在与优先级低者结合。

int (*p)[4]; //这是指向一维数组的指针变量

指针数组比较适合用来指向若干个字符串,使字符串更加方便灵活。

# include <stdio.h>
# include <string.h>
/*
数组指针的简单使用
*/
int main()
{
    void swap_string(char *str[],int n);
    void print_str(char *str[],int n);
    char *str[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer Design"};
    int n=5;
    swap_string(str,n);
    print_str(str,n);
    return 0;
} 

void swap_string(char *str[],int n)
{
     char *temp;
     int i,k,j;
     for(i=0;i<n-1;i++)
     {
         for(j=i+1;j<n;j++)
         {
             if(strcmp(str[i],str[j])>0)
             {
                 temp=str[i];
                 str[i]=str[j];
                 str[j]=temp;
             }
         }
      } 
} 

void print_str(char *str[],int n)
{
    int i=0;
    while(i<n)
    {
        printf("%s\n",str[i]);//按指针数组元素的顺序输出它们所指向的字符串 
        i++; 
    }
}

8.7.2 指向指针数据的指针

char * str[]={“Follow me”,”BASIC”}

char *p;

p=str[1];

p就是指向指针型数据的指针变量

定义一个指向指针数据的指针变量:char **p;

的结合性是从右到左 char *p 等同于 char *( *p);

(*p)表示这是一个指针变量,char * 表示p 指向的是一个字符型指针变量

# include "stdio.h"
# include "string.h"

/*
    使用指向指针数据的指针变量 
*/

int main()
{
    char * str[]={"Follow me","BASIC","Great Wall"};
    char **p;
    int i;
    for(i=0;i<3;i++)
    {
        p=str+i;
        printf("%s\n",*p);
     } 
    return 0;
}
# include "stdio.h"
# include "string.h"

/*
    指针元素指向实型数据或整形数据。 
*/

int main()
{
    int a[]={1,3,5,7,9};
    int * b[]={&a[0],&a[1],&a[2],&a[3],&a[4]};  
    int **p;
    int i;
    for(i=0;i<5;i++)
    {
        p=b+i;
        printf("%d\n",*p); //*p是b[i]的地址,**p是a[i]的值 
    } 
    return 0;
}

如果在一个指针变量中存放一个目标变量的地址,这就是“单级间址”。指向指针数据的指针用的是“二级间址”方法。

多重指针.png

8.7.3 指针数组作main函数的形参

/*
argc :参数的个数
argv :命令行参数
          命令行一般形式: file China Bejing
          命令名与个参数之间用空格分隔
          file也算一个参数
          命令行参数都是字符串


main函数有操作系统调用


*/
int main(int argc,char * argv[])
{

}

8.8 动态内存分配与指向它的指针变量

8.8.1 什么是内存的动态分配

动态存储区被称为栈

建立内存动态分配区域,以存放一些临时用的数据。这块区域被称为堆。

临时数据不必在程序中定义声明,无需关心其声明存在期。需要时随时开辟,不需要时随时释放。

由于未在声明部分定义他们为变量或数组,因此只能通过指针来引用。不能通过变量或数组引用。

8.8.2 怎样建立内存的动态分配

内存的动态分配是通过系统提供的库函数实现的:

  1. malloc

    其函数原型:

    void * malloc (unsigned int size);

    作用:在动态存储区分配一个长度为size的连续空间

    返回值是所分配区域的第一个字节地址。

    注意:

    指针的基类型是void ,即不指向任何类型的数据,只提供一个地址。

    如果函数没有成功执行(内存空间不足),则返回空指针

  2. calloc

    函数原型:

    void * calloc(unsigned n,unsigned sized);

    作用:在内存的动态存储区中分配n个长度为size的连续空间

    用calloc函数可以为一维数组开辟动态存储空间,n为元素个数,每个元素长度为size。这就是动态数组。

    函数返回指向所分配域的起始位置的指针;分配不成功,返回null

  3. free

    函数原型:

    void free(void * p);

    作用:是释放指针变量p所指向的动态空间,使这部分空间能被其他变量使用。p应是最近一次调用calloc或malloc函数时得到的函数返回值。

  4. realloc

    函数原型:

    void * realloc (void *p,unsigned size);

    作用: 修改已经通过malloc函数或calloc函数获得了动态空间

    返回值p不变,重新分配不成功返回NULL

以上4个函数的声明在stdlib.h头文件中

C99标准以前,malloc,calloc,realloc函数的基类型为char,c99把这些函数的基类型定义为void,这种指针称为无类型指针。仅提供一个存地址,而不指向具体对象。

8.3.3 void 指针类型

C99允许使用基类型为void的指针空类型。

void类型应理解为空类型或不指向确定的类型,不应理解指向任何类型的数据

int a=3;
int *p1=&a;
char * p2;
void * p3;
p3=(void *)p1;
p2=(char *)p3;

当void指针赋值给不同积累性的指针变量(相反)时,编译系统会自动进行转换,不必用户自己进行强制转换。

赋值后p3得到a的地址,但不指向a,不能通过*p3输出a的值。

# include "stdio.h"
# include "stdlib.h"
/*
    了解动态内存分配和void指针的使用 

    内存的动态分配主要应用于简历程序中的动态数据结构(如:链表) 
*/
int main()
{
    void check(int *);
    int *p1,i;
    /*
    sizeof运算符测定不同系统中存放的一个整数的字节数。
    p1是空类型不确定指向,所以要进行强转 表明指向整形数据 
    */
    p1=(int *)malloc(5*sizeof(int));
    for(i=0;i<5;i++)
    {
        *(p1+i)=i+1;
    }
    check(p1);
    free(p1);
    return 0;
} 

void check(int *p)
{
    int i=0;
    for(;i<5;i++)
    {
        if(p[i]<60)
            printf("%d",p[i]);
     } 
    printf("\n");
 } 

8.9 有关指针的小结

简单小结:

  1. 指针的含义,指针就是地址

    指针是地址本身,指针变量是用来存放地址的变量

    类型名表示的是指针指向的数据类型,而不是 指针是类型名。

  2. 什么叫“指向”?地址就意味着指向,因为通过地址能找到该地址的对象

    void * 指针是一种特殊的指针,不指向任何的数据,如果需要用地址指向某类型的数据,应先对地址进行类型转换。

  3. 要深入掌握在对数组的操作中正确地使用指针,搞清楚指针的指向

  4. 指针变量的归纳比较:

    指针变量的比较归纳1.png

    指针变量的比较归纳2.png

  5. 指针运算

    1. 指针变量加(减)一个整数
    2. 指针变量赋值(赋值的是地址)
    3. 两个指针变量可以相减(指向同一数组)
    4. 两个指针变量比较(指向同一个数组,可以元素的相对位置)
  6. 指针变量可以有NULL(空值),表示该指针变量不指向任何变量。

    NULL 在stdio.h预编译处理 # define NULL 0

指针的优点:

  1. 提高程序效率
  2. 在调用函数时当指针指向的变量值改变时,这些值能够为主调函数使用,即可以从函数调用中得到多个可改变的值。
  3. 可以实现动态内存分配。