首页 > 代码库 > C语言中从大到小的顺序排列《学习记录》

C语言中从大到小的顺序排列《学习记录》

一、前言

终于走到了指针,走到了翘首期盼的指针。指针是c语言中一个重要的概念,也是c语言最精彩的部分。正确而灵活的运用它,可以是程序简洁、紧凑、高效,每一个学习c语言的小伙伴都应该深入的学习和掌握指针。

本节目标

  • 指针是什么
  • 指针变量
  • 通过指针引用数组
  • 通过指针引用字符串

二、指针是什么

为了了解指针的概念,我们先来看一个小故事。 话说福尔摩斯派华生到威尔爵士居住的城堡去取回一个重要的数据。白天,在书房里,威尔爵士当着福尔摩斯和华生的面亲自将数据锁在了书柜中编号为3010的抽屉,用手电筒一照,只见里面有一张纸条,上面赫然写着6个大字:地址2000。华生眼前一亮,迅速的找到了编号为2000的抽屉,取出了重要的数据123,完成了任务。 可以用下图描述几个数据之间的关系。

技术分享

说明:

(1)数据藏在一个内存地址单元中,地址是2000.

(2)地址2000又由Pointer单元所指认,Pointer单元的地址为3010.

(3)123的直接地址是2000,123的间接地址是3010,3010中存的是直接地址2000.

(4)称Pointer单元为指针变量,2000是指针变量的值。

由此可见,指针变量是一种特殊的变量,它存放的不是数据,而是另一种变量的地址。这个存放数据的变量被称为指针变量所指向的目标变量。由于通过指针变量中的地址可以直接访问它指向的目标变量,常把指针变量简称为指针。

指针变量是一种存放地址的特殊变量,其特殊性表现在类型和值上。从变量角度讲,指针变量也具有变量的要素:

(1)指针变量的命名,与一般变量命名相同,遵循c语言的命名规则。

(2)指针变量的类型,是指针变量所指向的变量的类型,而不是自身的类型。

(3)在实验楼的环境中,指针变量在内存中占用8个字节。

三、指针变量

先分析一个指针案例

通过指针变量访问整数类型

技术分享

运行结果:

技术分享

10-1.c代码中如果指针打印出为负数,则需要将打印的类型%d调整为%ld才可以正常输出。

程序分析

(1)在开头处定义了两个指针变量point_1和point_2。但此时他们并未指向任何一个变量,只是提供两个指针变量,规定他们可以指向整形变量,至于指向哪一个整形变量,要在程序语句中指定。程序第6,7两行的作用就是使point_1指向a,point_2指向b,此时point_1的值为&a(即a的地址),point_2的值为&b。

(2)第10行输出*point_1和*point_2的值,其中的“*”表示“指向”。*point_1表示“指针变量point_1所指向的变量”,也就是变量a。*point_2表示“指针变量point_2所指向的变量”,也就是变量b。从运行结果来看他们也就是100和10.

(3)程序中有两处出现*point_1和*point_2,但是两者含义不同。程序第5行的*point_1和*point_2表示定义两个指针变量*point_1和*point_2。它们前面的“*”只是表示该变量是指针变量。程序最后10行中的printf函数中的*point_1和*point_2则表示指针变量point_1和point_2所指向的变量。

定义指针变量

在10-1中我们看到了定义指针变量,定义指针变量的一般形式为

类型名 * 指针变量名

int * point_1,* point_2;

注意:我们在这里再次强调一遍,在定义指针变量时要注意,指针变量前面的“*”表示该变量的类型为指针型变量。指针变量名是point_1和point_2,而不是*point_1和*point_2。这是和定义整形或实型变量不同的。上面程序6行和7行是不能写成* point_1=&a;和 *point_2=&b;的。因为a的地址是赋给指针变量point_1,而不是赋值给* point_1(即变量a)。

引用指针变量

在引用指针变量时,有以下3种情况:

(1)给指针变量赋值。如:

p=&a;  //把a的地址赋给指针变量p

指针变量p的值是变量a的地址,p指向a。

(2)引用指针变量指向的变量。 如果已经执行p=&a;即指针变量p指向了整形变量a,则

printf("%d",*p);

其作用是以整数形式输出指针变量p所指向的变量的值,即变量a的值。

如果有以下赋值语句:

*p=1;

表示将整数1赋给p当前所指向的变量,如果p指向变量a,则相当于把1赋给a,即a=1;.

(3)引用指针变量的值。如:

printf("%o",p);

作用是以八进制数形式输出指针变量p的值,如果p指向了a,就是输出了a的地址,即&a。

注意:比较地址运算符&和指针运算符*的差异:

技术分享

程序举例

编写程序10-2,输入两个整数,按先大后小的顺序输出a和b。

解题思路:用指针的方法来处理这个问题。不交换整形变量的值,而是交换两个指针变量的值。

编写程序:

#include<stdio.h>
int main()
{
    int *p1,*p2,*p,a,b;
    printf("please enter two integer number:");
    scanf("%d,%d",&a,&b);
    p1=&a;
    p2=&b;
    if(a<b)
    {
        p=p1;p1=p2;p2=p;          //使p1和p2的值互换
    }

    printf("a=%d,b=%d\n",a,b);
    printf("max=%d,min=%d\n",*p1,*p2);
    return 0;
}

运行结果:

技术分享

程序分析

输入23和42,由于a<b,将p1和p2交换,交换前后的情况如下图

技术分享

注意:a和b的值并未交换,他们任然保持原值,但p1和p2的值改变了。p1的值原来为&a,后来变为&b。这样输出* p1和* p2时,实际上输出变量b和a的值,所以先输出42,然后输出23.

程序中p=p1;p1=p2;p2=p;这个语句是我们之前使用过的方法,两个变量的值交换要利用第三个变量。实际上学到指针我们可以用更加简洁的方法把p=p1;p1=p2;p2=p;改为p1=&b,p2=&1,这样就不需要定义中间变量p,使得程序更加简洁。

指针变量作为函数参数

函数的参数不仅可以是整形,浮点型等数据,也可以是指针类型。他的作用是将一个变量的地址传送到另一个函数中。

下面通过一个例子来说明: 编写程序10-3,要求实现的功能和10-2相同,只不过这次我们采用函数来处理,而且用指针类型的数据做函数的参数。

#include<stdio.h>
int main()
{
    void swap(int * point_1,int * point_2);
    int *p1,*p2,*p,a,b;
    printf("please enter two integer number:");
    scanf("%d,%d",&a,&b);
    p1=&a;
    p2=&b;
    if(a<b)
    swap(p1,p2);

    printf("max=%d,min=%d\n",a,b);
    return 0;
}

void swap(int * point_1,int * point_2)
{
    int temp;
    temp=* point_1;                 //使*p1和*p2互换
    * point_1=* point_2;
    * point_2=temp;
}

运行结果为:

技术分享

程序分析: swap是用户自定义函数,它的作用是交换两个变量(a和b)的值。swap函数的两个形参point_1和point_2是指针变量。程序运行时,先执行main函数,输入a和b的值(我们输入的是23和34),然后将a和b的地址分别赋给int 指针变量p1和p2,使p1指向a,p2指向b,见下图a,接着执行if语句,由于a<b,因此执行swap函数。注意实参p1和p2是指针变量,在函数调用时,将实参变量的值传送给形参变量,采取的依然是“值传递”方式。因此虚实结合后形参point_1的值是&a,point_2的值为&b,见下图b。这时p1和point_1都指向变量a,p2和point_2都指向变量b。接着执行swap函数的函数体,使* point_1和* point_2的值互换,也就是使a和b的值互换。互换后的情况见图c。函数调用结束后,形参p1和p2不复存在(已释放),情况如图d,最后在main函数中输出的a和b的值已经是经过交换的值。

技术分享

可以看到,在执行swap函数后,变量a和b的值改变了。各位兄台,各位兄台!请仔细分析,这个改变是怎么实现的,这个改变不是通形参值传回实参实现的。大家可以考虑下能否通过下面的函数实现a和b互换。

void swap(int x ,int y)
{
    int temp;
    temp=x;
    x=y;
    y=temp;
}

如果在main函数中调用swap函数:

swap(a,b);

会有什么结果?在函数调用时,a的值传送给x,b的值传送给y,执行完swap函数后,x和y的值是互换了,但并未影响到a和b的值。在函数结束时,变量x和y释放了,main函数中的a和b并未互换。也就是说,这种单向的值传递,形参值的改变不能使实参的值随之改变。


为了使在函数中改变了的变量值能被主调函数main所用,不能采取上述的把要改变的变量作为参数的办法,而应该用指针变量作为函数参数,在函数执行过程中使指针变量所指向的变量值发生变化,函数调用结束后,这些变量值依然保留了下来。

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

(1)在主调函数中设n个变量,用n个指针变量指向它们;

(2)设计一个函数,有n个指针形参,在这个函数中改变n个形参的值;

(3)在主调函数中调用这个函数,在调用时将这n个指针变量作实参,将他们的地址传给该函数的形参。

(4)在执行该函数的过程中,通过形参指针变量,改变它们所指向的n个变量的值;

(5)主调函数中就可以使用这些改变了值的变量了。

大家是不是转的有点晕,相信小编,你把刚才的程序再写两遍,下面的这个程序练习再来好好写一遍并独立思考你绝对掌握了。

编写程序10-4,输入3个整数a,b,c要求按从大到小的顺序将他们输出,用函数实现

解题思路:采用10-3的方法在函数中改变3个变量的值。用swap函数交换两个变量的值,用exchange函数改变着3个变量的值。 编写程序


<pre name="code" class="cpp">#include<stdio.h>
int main()
{
   void exchange(int * q1,int * q2,int * q3);
   int a,b,c,*p1,*p2,*p3;
   printf("please enter 3 integer number:");
   scanf("%d%d%d",&a,&b,&c);
   p1=&a;p2=&b;p3=&c;
   exchange(p1,p2,p3);
   printf("the order is :%d,%d,%d\n",a,b,c);
   return 0;
}

void exchange(int *q1,int *q2,int *q3)
{
   void swap(int *pt1,int *pt2);
   if(* q1<* q2) swap(q1,q2);
   if(* q1<* q3) swap(q1,q3);
   if(* q2<* q3) swap(q2,q3);
}

void swap(int *p1,int *p2)
{
  int temp;
  temp=*p1;
  *p1=*p2;
  *p2=temp;
}

下面是运行的效果如图。

技术分享

四、通过指针引用数组

可以用一个指针变量指向一个数组元素。例如:

int a[10]={1,2,3,4,5,6,7,8,9,10};
int *P;
p=&a[0];

以上是使指针变量p指向a数组的第0号元素。

在c语言中,数组名(不包括形参数组名,形参数组并不占据实际的内存单元)代表数组中首元素(即序号为0的元素)的地址。因此,下面两个语句等价:

p=&a[0];  //p的值是a[0]的地址
p=a;      //p的值是数组a首元素(即a[0])的地址

注意:数组名不代表整个数组,只代表数组首元素的地址。上述p=a;的作用是“把a数组的首元素的地址赋给指针变量p”,而不是“把数组a各元素的值赋给p”。

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

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

  • 加减一个整数,如p+1;或者p-1;
  • 自加运算,如p++或者++p
  • 自减运算,如p--或者--p

分别说明如下: (1)如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素,p-1指向同一数组中的上一个元素。注意:执行p+1时并不是将p的值(地址)简单地加1,而是加上一个数组元素所占用的字节数。例如,数组元素是float型,每个元素占4个字节,则p+1意味着使p的值加4个字节,以使它指向下一个元素。p+1所代表的地址实际上是p+1*d,d是一个数组元素所占的字节数。若p的值是2000,则p+1的值不是2001而是2004。

(2)如果p的初值为&a[0],则p+i和a+i就是数组元素a[i]的地址,或者说,他们指向a数组序号为i的元素,见下图,这里注意的是a代表数组首元素的地址,a+1也是地址,它的计算方法同p+1,即它的实际地址为a+1*d。例如,p+9和a+9的值是&a[9],它指向a[9]。

技术分享

(3)*(p+i)和*(a+i)是p+i或a+i所指向的元素,即a[i]。例如*(p+5)和*(a+5)就是a[5],三者等价。

根据以上叙述,引用一个数组元素,可以用下面两种方法: (1)下标法,如a[i]的形式; (2)指针法,如*(a+1)或*(p+i)。其中a是数组名,p是指向数组元素的指针变量,其初值p=a。

程序举例

有一个整型数组a,有10个元素,要求输出数组中的全部元素。

解题思路:引用数组中各元素的值有3中方法:(1)下标法,如a[2];(2)通过数组名计算数组元素地址,找出元素的值;(3)用指针变量指向数组元素。分别写出程序,以便我们比较分析。

编写程序: (1)10-5,下标法

#include<stdio.h>
int main()
{
    int a[10];
    int i;
    printf("please enter 10 integer numbers: ");
    for(i=0;i<10;i++)
    scanf("%d",&a[i]);

    for(i=0;i<10;i++)
    printf("%d\t",a[i]);     //数组元素用数组名和下标表示

    return 0;


}

运行结果:

技术分享

(2)通过数组名计算数组元素的地址

#include<stdio.h>
int main()
{
    int a[10];
    int i;
    printf("please enter 10 integer numbers: ");
    for(i=0;i<10;i++)
    scanf("%d",&a[i]);

    for(i=0;i<10;i++)
    printf("%d\t",*(a+i));  //通过数组名和元素序号计算元素地址,再找该元素

    return 0;

}

运行结果与(1)相同

运行分析: 程序中语句scanf("%d",&a[i]);用&a[i]表示a[i]的地址,也可以改用(a+i)表示,即:

scanf("%d",a+i);

(3)用指针变量指向数组元素。

#include<stdio.h>
int main()
{
    int a[10];
    int *p,i;
    printf("please enter 10 integer numbers: ");
    for(i=0;i<10;i++)
    scanf("%d",&a[i]);

    for(p=a;p<(a+10);p++)
    printf("%d\t",*p);  //用指针指向当前的数组元素

    return 0;

}

运行结果:与(1)相同

程序分析

程序中的

for(i=0;i<10;i++)
    scanf("%d",&a[i]);

可以修改为:

for(p=a;p<(a+10);p++)
scanf("%d",p);

用指针变量表示当期的地址

3种方法的比较:

(1)第(1)种和第(2)种方法的执行效率是相同的。c编译系统是将a[i]装换为*(a+i)处理的,即先计算元素的地址。因此用第(1)和第(2)种方法找数组元素费时较多。

(2)第(3)种方法比第(1)、第(2)种方法快,用指针变量直接指向元素,不必每次都重新计算地址,像p++这样的自加操作是比较快的。这种有规律的改变地址值(p++)能大大提高执行效率。

(3)用下标比较直观,能直接知道是第几个元素。例如,a[5]是数组中序号为5的元素(注意序号是从0算起)。用地址发或者指针变量的方法不直观,难以很快的判断出当前处理的是哪一个元素。例如,方法(3)中,要仔细分析指针变量p的当前指向,才能判断当前输出的是第几个元素。有经验的程序猿往往喜欢用第(3)种形式,用p++进行控制,程序简洁、高效。对于小白用户最好还是用第(1)种方式,直观,不易出错。

注意:在使用指针变量指向数组元素时,有一些问题需要注意:

(1)可以通过改变指针变量的值指向不同的元素。例如:上述第(3)种方法是用指针变量p来指向元素,用p++使p的值不断改变从而指向不同的元素。

如果不用p变化的方法而用数组名a变化的方法行不行呢?假如将上述第(3)中方法中的程序的

for(p=a;p<(a+10);p++)
    printf("%d\t",*p);

改为

for(p=a;a<(p+10);a++)
   printf("%d",*a);

是不行的。因为数组名a代表数组首元素的地址,它是一个指针型常量,它的值在程序运行期间是固定不变的。既然a是常量,所以a++是无法实现的。

(2)注意指针变量的当前值。看下例 编写程序:

#include<stdio.h>
int main()
{
    int *p,i,a[10];
    p=a;
    printf("please enter 10 integer number:");
    for(i=0;i<10;i++)
    scanf("%d",p++);

    for(i=0;i<10;i++,p++)
    printf("%d",*p);
    printf("\n");

    return 0;
}

运行结果:

技术分享

程序分析:显然输出的数值并不是a数组中各元素的值。需要检查和分析程序。

可能大家会觉得上面的程序没有什么问题,即使已被告知此程序有问题,还是找不出问题出在哪里。问题出在指针变量p的指向。指针变量p的初始值为a数组首元素的地址,但经过第一个for循环读入数据后,p已指向a数组的末尾。因此,在执行第2个for循环时,p的起始值不是&a[0]了,而是a+10。由于执行第2个for循环时,每次要执行p++,因此p指向的是a数组下面的10个存储单元。

解决这个问题的方法只需要在第2个for循环之前加一个赋值语句:

p=a;

使p的初始值重新等于&a[0],这样的结果就对了。

利用指针引用数组元素,比较方便灵活,有不少技巧。在专业人员中常喜欢用一些技巧,以使程序简洁。我们来分析下面几种情况

(1)

p++;
* p;

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

(2)

*p++;

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

(3) *(p++)与*(++p)作用是否相同?答案肯定是不相同的。前者是先取*p的值,然后使p加1.后者是先使p加1,再取*p。

上面的内容对小白来讲不建议使用,但是应当知道有关知识。

用数组名做函数参数

在数组章节我们介绍过可以用数组名作为函数的参数。例如:

int main()
{
        void fun(int arr[],int n);
        int arry[10];
             .
             .
             .
             .
        fun(arry,10);      //用数组名作为函数的参数
        return 0;
}

void fun(int arr[],int n)
{
        .
        .
        .
}

array是实参数组名,arr为形参数组名。从前两节我们应该知道,当用数组名作为参数时,如果形参数组中各元素的值发生变化,实参数组元素的值随之变化。这究竟是何原因呢?学过我们上一节指针的话应该不难理解。

先看数组元素为实参时的情况。如果已经定义一个函数,其原型为:

void swap(int x,int y);

假设函数的作用是将两个形参(x,y)的值交换,现有以下的函数调用:

swap(a[1],a[2]);

用数组元素a[1]和a[2]作为实参的情况,与用变量作实参时一样,是“值传递”的方式,将a[1]和a[2]的值单向传递给x和y。当x和y的值改变时a[1]和a[2]的值并没有改变。

我们再来看数组名作为函数参数的情况,实参数组名代表该数组首元素的地址,而形参是用来接收从实参传递过来的数组首元素地址的。因此,形参应该是一个指针变量。实际上c编译系统是将形参数组名作为指针变量来处理的。例如

fun(int arry[],int n)

在程序编译时是将arr按指针变量处理的,相当于将函数fun的首部写成

fun(int *arr,int n);

上面的两种写法是等价的。在该函数被调用时,系统会在fun函数中建一个指针变量arr,用来存放从主调函数传递过来的实参数组首元素的地址。

当arr接收了实参数组的首元素地址后,arr就指向实参数组首元素,也就是指向array[0]。因此*arr就是array[0],*(arr+1)就是array[1]。我们可以这样来理解,在函数调用期间,形参数组从实参数组那里得到起始地址,因此形参数组与实参数组共占同一段内存单元,在调用函数期间,如果改变了形参数组的值,也就是改变了实参数组的值。

c语言常用这种方法通过调用一个函数来改变实参数组的值。

程序举例

将数组a中n个整数按相反的顺序存放,见下图:

技术分享

解题思路:将a[0]与a[n-1]对换,再将a[1]和a[n-2]对换.........直到将a[int (n-1)/2]与a[n-int(n-1)/2-1]对换。现用循环处理此问题,设两个“位置指示变量”i和j,i的初值为0,j的初值为n-1。将a[i]和a[j]交换,然后是i的值加1,j的值减1,再将a[i]和a[j]交换,直到i=(n-1)/2为止。

用一个函数change来实现交换。实参用数组名a,形参可用数组名,也可用指针变量名。

编写程序10-9:

#include<stdio.h>
int main()
{
   void change(int x[],int n);
   int i,a[10]={3,7,9,11,0,6,7,5,4,2};
   printf("the original array:\n");
   for(i=0;i<10;i++)
      printf("%d\t",a[i]);
   printf("\n");

   change(a,10);
   printf("the array has been inverted:\n");
   for(i=0;i<10;i++)
     printf("%d\t",a[i]);

   return 0;
}

void change(int x[],int n)
{
   int temp,i,j,m=(n-1)/2;
   for(i=0;i<=m;i++)
   {
      j=n-1-i;
      temp=x[i];
      x[i]=x[j];
      x[j]=temp;
    }
}

实验结果:

the original array:
3     7     9     11     0     6     7     5    4     2
the array has been enverted:
2    4      5     7      6     0     11    9    7     3

程序分析:这个程序我们可以做一些改动。将函数change中的形参x改成指针变量。相应的实参仍为数组名a(即数组a首元素的地址),将它传给形参指针变量x,这时x就指向a[0]。x+m是a[m]元素的地址。设j和i以及p都是指针变量,用它们指向有关元素。i的初值x,j的初值为x+n-1,见下图。使得*i与*j交换就是a[i]和a[j]交换。

技术分享

编写程序10-10:

#include<stdio.h>
int main()
{
   void change(int *x,int n);
   int i,a[10]={3,7,9,11,0,6,7,5,4,2};
   printf("the original array:\n");
   for(i=0;i<10;i++)
      printf("%d\t",a[i]);
   printf("\n");

   change(a,10);
   printf("the array has been inverted:\n");
   for(i=0;i<10;i++)
     printf("%d\t",a[i]);

   return 0;
}

void change(int *x,int n)
{
   int *p,temp,*i,*j,m=(n-1)/2;
   i=x;j=x+n-1;p=x+m;
   for(;i<=p;i++,j--)
   {
      temp=*i;
      *i=*j;
      *j=temp;
    }
}

运行结果和上一个程序相同。

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

(1)形参和实参都用数组名,例如:

int main()     
{
   int a[10];
   .
   .
   .
   f(a,10);

}

int f(int x[],int n)
{
   .
   .
   .
}

由于形参数组名x接收了实参数组首元素a[0]的地址,因此可以认为在函数调用期间,形参数组与实参数组共用一段内存单元,这种形式比较好理解。

(2)实参用数组名,形参用指针变量。例如:

int main()
{
   int a[10];
   .
   .
   .
   f(a,10);

}

void f(int *x,int n)
{
  .
  .
  .
}

实参a为数组名,形参x为int *型的指针变量,调用函数开始后,形参x指向a[0],即x=&a[0],通过x的值的改变,可以指向a数组的任一元素。例11-2就属于此类。

(3)实参形参都用指针变量。例如:

int main()
{
    int a[10],*p=a;
    .
    .
    .
    f(p,10);
    .
    .
    .
}

void f(int *x,int n)
{
   .
   .
   .
}

实参p和形参x都是int *型的指针变量。先使实参指针变量p指向数组a[0],p的值是&a[0]。然后将p的值传给形参指针变量x,x的初始值也是&a[0],通过x的值的改变可以使x指向数组a的任意元素。

(4) 实参为指针变量,形参为数组名。例如:

int main()
 {
    int a[10],*p=a;
    .
    .
    .
    f(p,10);
 }

void f(int x[],int n)
{
   .
   .
   .
   .
}

实参p为指针变量,它指向a[0]。形参为数组名x,编译系统把x作为指针变量处理,今将a[0]的地址传给形参x,使x也指向a[0]。也可以理解为形参数组x和a数组共用同一段内存单元。在执行过程中可以使x[i]的值变化,而x[i]就是a[i]。这样,main函数可以使用变化了的数组元素的值。

以上4种方法,实质上都是地址的传递。其中(3)和(4)两种只是形式上不同,实际上形参都是使用指针变量。




C语言中从大到小的顺序排列《学习记录》