您的位置:首页 > 编程语言 > C语言/C++

c语言中的指针

2015-11-01 13:39 309 查看
数组与指针

指针可以指向变量,同样可以指向数组的元素(把某一个元素的地址放到一个指针变量中),所谓的数组元素的指针就是数组元素的地址。

引用数组的元素可以用下标(eg:a[1]),也可以使用指针。

使用指针法能使目标程序质量高(占用内存少,运行速度快)

1、指向数组元素的指针

1)定义一个指向数组元素的指针变量的方法。

int a[10];(定义a为包含10个整型数据的数组)

int *p;(定义p为指向整型变量的指针变量) 注意:数组为int型,则相应的指针变量的基类型也应该为int型。

eg:赋值 p = &a[0];//将a[0]的地址赋给指针变量p,也就是是p指向a数组的第0号元素。

拓展:c语言规定数组名代表数组的首元素的地址。(不包括形参数组名,形参数组并不占有实际的内存单元)

所以可以写成:p = a;  //这里是包数组a的首元素的地址赋值给p,而不是把数组各个元素的值赋给p。

总结:直线数组元素的指针一般都是用来存放数组的首元素的地址(即为;首地址)。

2、通过指针引用数组元素

数组指针 p;

*p = 1; 表示将1赋值给p当前所指向的数组的元素。

拓展:p+1 指向同一个数组中的下一个元素,而不是将p的值(地址简单的+1),应该为;p+1*d

如果p的初始值为 &a[0],则

1)p+i 和 a+i 就是a[i]的地址。

a+i中的a代表的是数组的首元素地址。a+i 也是地址。

计算实际的二地址为:a+i*d

2)*(p+i) 或 *(a+8)是p+i 或a+i 所指向的数组的元素,即为:a[i].

3)指向数组的指针变量可以带下标.

eg:p[i] 与 *(p+i)等价。

所以应用数组可以使用:

(1)下标法:eg:a[i]形式

(2)指针法:eg:*(a+i) 或 *(p+i).其中a是数组名,p是指向数组的指针变量。初值:p = a;

(使用指针变量的的方法会提高程序运行编译的效率)

注意:数组名a是一个指针常量,不可以使用a++来进行改变访问同一个数组中的不同元素。

总结:

1、指针数组的使用,注意指针变化之后,保存的而是当前的值,尤其是在链表中,我们经常访问到末尾的时候,还是要访问,需要从新赋值到链表头部。再遍历即可。

2、指针和下标的方式对数组进行访问,并且数组名为数组的首地址,并且 是一个常量,不可以用来增减操作。

3、用数组名做函数参数

函数中我们一般写的格式为:

f(int arr[],int n) 写成数组的形式

或者

f(int *arr,int n)  将arr按照指针变量处理,(应该尽量使用下面的方法)

即为:上面的arr是用来存储实参传过来的数组的首元素地址。(即为:将arr都是按照指针来进行处理)

我们常常使用上面的函数的方法来进行改变数组中的元素的值。

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

实参类型                        变量名                数组名(指针)

要求形参的类型                    变量名                数组名或指针变量

传递的信息                        变量的值            实参数组首元素的地址

通过函数调用能否改变实参的值     不能                能

说明:实参数组名代表的是一个固定的地址(指针常量),形参数组并不是一个固定的地址值(指针变量),在函数调用开始时,它的值等于实参数组首元素的地址,在函数执行期间,它可以再被赋值。

1)使用指针变量做实参,必须先将指针变量有确定值,指向一个已经定义的单元。(指针不可以是野指针)

4、多维数组与指针

1)多维数组元素的地址

(可以查看相应的书籍。尤其是谭浩强的。P242)

二维数组是数组中的数组

eg:

int a[3][4] = {{1,3,5,7},{9,11,13,15},{17,19,21,23}}  //表示3行列

a代表的是二维数组首元素的地址。

即为可以划分为:a[0],a[1],a[2];即为:a[0],a[1],a[2]是一维数组名,c语言有规定数组名代表数组元素的首元素的地址。

因此:a[0]代表的是数组a[0]中第0列元素的地址,即为:&a[0][0],a[1]的值是&a[1][0],a[2]的值是&a[2][0]

a代表的是首行(即为:第0行)的首地址,a+1 代表第1行的首地址 ;(这个很重要)

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

(1)指向数组的指针变量

#include <stdio.h>

void main()

{

    int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23};

    int  *p;    

    for(p=a[0];p<a[0]+12;p++){

    if((p-a[0])%4 == 0) printf("\n");

    printf("%4d",*p);

    }

    printf("\n");

}

p是一个指向整型变量的指针变量,可以指向一般的整型变量,也可以指向整型的数组的元素。每一个是p+1 都会指向下一个元素,

我们想到我们经常int **p这种方式来进行定义的时候,要想访问某一个元素,通过的方式是:*(*(p+k)+j)来访问,注意指针当前指向的是哪一种类型,指向的是行还是列的位单元的。

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

上面的例子的指针是指向整型变量,我们可以改为直线给一个包含m个元素的一维数组。

即为首地址为:p=&a[0],p+1 值得是a[1]即为下标的的行是1的。

代码例子:

#include <stdio.h>

void main()

{

        int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23};

        int (*p)[4],i,j;

        p = a;

        scanf("i=%d,j=%d",&i,&j);

        printf("a[%d,%d]=%d \n",i,j,*(*(p+i)+j));

}

总结:

int (*p)[4];   int *p[4];

上面两个的概念是不一样的,注意指针的右边开始原则。

第一个:先(*p),然后再结合[4]变成 (*p)[4],也就是 p变量是一个指向二维数组的指针变量,也就是二维数组的首地址。所以这个时候的p是指向具有4个元素的一维数组的指针。

第二个:先是p[4],然后再是与“*”号结合,为*p[4]。则为p[4]是一个含有4个元素的数组,而变成int *p[4]是即为指向int类型的4个整型的指针变量。

3、用指向数组的指针做函数参数

一维数组可以作为函数参数进行传递,多维数组也是可以作为函数进行传递的。

即为:指针变量做形参以接受实参数组名传递过来的地址的时候,

两种方法:

(1)用指向变量的指针变量

(2)用指向一维数组的指针变量(指向指针的指针变量)

字符串与指针

1、字符串的表示形式

1)用个字符数组存放一个字符串,然后输出该字符串。

eg:char string[]="jasdifkjk";

2)用字符指针指向一个字符串

(可以不定义数组,直接定义字符指针,用字符指针指向字符串中的字符)

%s可以对字符串的整体输出。

通过改变指针变量的值来指定不同的字符。

2、字符指针作函数参数

(在被调用函数中可以改变字符串的内容,在主调函数中可以得到改变了的字符串)

将一个字符串从一个函数传递到另外一个函数,可以通过地址传递的方法(即为:用字符数组做参数,也可以用指向字符的指针变量做参数);

3、对使用字符指针变量和字符数组的讨论

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

两者是有差别的:

1)字符数组是由若干个元素组成的,每一个元素中放有一个字符,而字符指针变量中存放的是地址(字符串第一个字符的地址),绝对不是将字符串放到字符指针变量中。

2)赋值方式。

对字符数组只能能够对各个元素赋值。下面的赋值方式是错误的,

char str[14];

str = "I love China!!!"

(这种方法是错误的)

char *a;

a = "I love China!";

(这种方法是对的)

注意a不是字符,而是字符串的第一个元素的地址。

3)对字符串变量赋值初值

char *a = "I love China";

等价于

char *a;

a = "I love China!";

而对数组的初始化:

char str[] = {"I love China!"}

不等价于

char str[14];

str[] = "I love China";

即为数组可以在定义时整体赋初值,但不能够在赋值语句中整体整体赋值。

4)定义一个数组,在编译时分配内存单元,它有确定的地址。

定义个字符指针变量时,给指针变量份分配内存单元,在其中可以存放一个字符变量的地址。

(指针变量在没有对它赋予一个地址值的时候,它并没有具体指向一个确定的数据。)

eg:

char str[10];

scanf("%s",str); 这样是可以的



char *a;

scanf("%s",a);

目的是想输入一个字符串,这种方法比较危险,因为编译的时候虽然给指针变量a分配了内存单元,a的地址已指定了,但是a的值并没有指定,在a单元中是一个不可预料的值。

在执行scanf()函数的时候,要求将一个字符输入到a所指向的一段内存单元(即为a的值(地址)开始的一段内存单元)中。

可是现在a的值是不可以预料的,它可能指向用户内存中空白,也有可能指向已经存储数据的一段区域内,这个会破坏了系统,会造成严重的后果。

(程序较小的时候,空白的地带可能会很多,往往正常运行,程序规模大的时候,出现上述冲突的可能性大多了)

char *a,str[10];

a = str;

scanf("%s",a);(正确的)

先使a有确定的值,也就是a指向一个数组的首元素,然后输入一个字符串,把它存放在以该地址开始的若干个单元中。

5)指针变量的值可以改变

而数组名是不变的,数组名虽然是代表地址,但它是一个常量,它的值是不可以改变的。

eg:

char str[] = {"I love China"};

str = str + 7; //这种方法是错误的

printf("%s",str);

说明:如果定义指针变量,并且使它能够指向一个字符串,可以使用下标的方式引用指针变量所指的字符串中的字符。

6)用指针变量指向一个格式字符串,可以使用它代替printf()函数中的格式字符串。

eg:

char *format;

format = "a=%d,b=%f \n";

printf(format,a,b); 同样可以格式符输出。

这种printf函数称为可变格式输出函数。也可以用字符数组实现。

char format[] = "a=%d,b=%f \n";

printf(format,a,b);

由于不能够采用赋值语句对数组整体赋值,eg:

char format[];

format = "a=%d,b = %d \n";

所以:指针变量指向字符串的方式更为方便。

指向函数的指针

1、用函数指针变量调用函数

(指针可以指向一个基本的变量,同时也是可以指向一个函数)

一个函数可以在编译的时候分配给一个入口地址,这个入口地址就称为函数的指针。

(用一个指针变量指向函数,然后通过该指针变量调用函数)

eg:代码如下:

#include <stdio.h>

void main()

{

    int max(int ,int );

    int a,b,c;

    scanf("%d,%d",&a,&b);

    c = max(a,b);

    printf("%d,%d,%d",a,b,c);

}

int max(int x,int y)

{

    int z;

    if(y>z) z = x;

    else z=y;

    return z;

}

上面就有了一个函数的调用: c = max(a,b);

说明:

每一个函数都会占用一段内存单元,它们有一个起始地址。因此,可以用一个变量指向一个函数,通过指针变量来访问它指向的函数。

main 函数的改变

void main()

{

    int max(int ,int );

    int (*p)(int ,int );

    p = max;//为什么可以这样的呢?函数名就是函数的首地址?(是的)

    int a,b,c;

    scanf("%d,%d",&a,&b);

    c = (*p)(a,b);

    printf("%d,%d,%d",a,b,c);

}

int (*p)(int ,int ); 用来定义一个指向函数的指针变量, 2个整型的参数 ,函数值是整型。 表示此指针变量指向函数,这个函数的值是整型的。

注意:p两侧的括号是不可以省略的,表示p先与*结合,是指针变量,然后再与后面的()结合。

p = max ; 作用:将函数max的入口地址赋给指针变量p。函数名代表该函数的入口地址(似数组的首地址)。

p指向函数max的指针变量,此时p和max都是指向函数的开头。

注意:p只能够指向函数的开头,p+1 表示指向函数的下一个函数,并且不会直线函数里面一条语句。

说明:

(1)指向函数的指针变量的一般定义形式是:

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

“数据类型”指函数返回值的类型。

(2)函数的调用可以通过函数名调用,也可以通过函数指针调用(即为:指向函数的指针变量调用) 这里就是我们的一般的函数

(3)int (*p)(int ,int);表示定义一个指向函数的指针变量p,没有指向特定函数,这个指针就是用来存放这种类型函数的入口地址,p = max ;就是表示指向这种类型函数名为max的函数。(和其他类型的指针一样,它先后可以指向同样类型的指针的不同的函数)

(4)函数指针变量赋值的时候, 只需要给出函数名而不必给出参数。

p = max;

不可以写成:p = max(a,b);

(5)函数指针变量调用函数时,只需要使用(*p)名来替换函数名就可以了。

eg:c = (*p)(a,b);

表示:调用由p指向的函数实参a,b,得到的函数值赋值给c。注意函数值的返回值什么类型。

(6)对指向函数的指针变量,像p+n、p++、p-- 等运算是没有意义的。

2、用指向函数的指针做函数参数

函数的参数可以是:变量、指向变量的指针变量、数组名、指向数组的指针变量、指向函数的指针等等。

指向函数的指针作为参数:可以实现函数地址的传递,这样就能够在被调用的函数中使用实参函数。(使用这个函数指针的时候,就是调用这个指针指向的函数)

原理:

void sub(int (*x1)(int), int (*x2)(int ,int ))

{

    int a,b,i,j;

    a = (*x1)(i);//调用f1函数

    b = (*x2)(i,j);//调用f2 函数、

    ……

}

其他函数中调用:

sub(f1,f2);

函数sub有两个参数,x1,x2,他们都是函数指针类型的,在实参中传入f1,f2都是对应的函数名的(即为传入函数的入口地址),遮掩sub就可以调用f1,f2函数了。

这样设计的好处:(即为:函数指针的作用)

如果在每一次调用sub函数的时候,要调用的函数不是固定的,这一次调用f1,f2,下一次不一定了,所以这样用指针变量就比较方便,调用sub函数的时候,只是传入实参就好了,不用修改函数里面的内容,符合程序的结构化设计,使用起来非常的灵活。

#include <stdio.h>

int main()

{

        int max(int ,int );

        int min(int ,int );

        int add(int ,int );

        void process(int ,int ,int(*fun)(int,int));

        int a,b;

        printf("enter a and b:");

        scanf("%d,%d",&a,&b);

        printf("max=");

        process(a,b,max);

        printf("min=");

        process(a,b,min);

        printf("sum=");

        process(a,b,add);

        return 0;

}

int max(int x,int y){

        int z;

        if(x>y) z = x;

        else z = y;

        return(z);

}

int min(int x,int y){

        int z;

        if(x<y) z = x;

        else z = y;

        return(z);

}

int add(int x,int y){

        int z;

        z = x + y;

        return(z);

}

void process(int x, int y, int (*fun)(int ,int)){

        int result;

        result = (fun)(x,y);

        printf("%d \n",result);

}

结果:

enter a and b:2,6

max=6

min=2

sum=8

函数的指针变量也是常常会用到定积分上。

返回指针值的函数

函数可以返回一个整型值、字符值、实型值、指针类型的数据(地址)。

定义的格式是:

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

eg:

int *a(int x, int y);  //指针类型函数

调用函数名a可以得到一个整型数据类型的指针(地址),x,y是函数a的形参,为整型。

eg:

代码:

#include <stdio.h>

int main()

{

    float score[][4]={{60,70,80,90},{56,89,67,88},{34,78,90,66}};

    float * search(float (*pointer)[4],int n);

    float *p;

    int i,m;

    printf("enter the number of student:");

    scanf("%d",&m);

    printf("The score of No.%d are: \n",m);

    p = search(score,m);

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

    printf("%5.2f \t",*(p+i));

    

    printf("\n");

}

float * search(float (* pointer)[4],int n){

    float *pt;

    pt = *(pointer+n);

    return (pt);

}

输出结果:

enter the number of student:1

The score of No.1 are:

56.00     89.00     67.00     88.00     

说明:

函数search被定义为指针型函数,它的形参pointer是指向包含4个元素的一维数组的指针变量。

pointer+1 指向score数组序号为1的行。

pt 是指向实型变量(而不是一维数组),

总结:

函数指针,一般是用来指向多维数组的函数指针,返回的是一个指针类型,我们可以通过这个指针类型的变量来获取数组中的一个实型。

指针数组和指向指针的指针

指针数组的概念

一个数组中的元素均为指针类型数据,称为指针数组。

(相当于:指针数组中每一个元素都相当于一个指针变量)

一维指针数组的定义:格式

类型名 * 数组名[数组长度];

eg:

int *p[4];

ps:[]与p 结合为先,显然是数组形式,它有4个元素,然后再与*结合,“*”表示次数组的指针类型,每一个数组元素都是可以指向一个整型变量。

注意:

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

指针变量

比较适合于用来指向若干个字符串,是字符串处理更加方便灵活。

一些数组我们需要存储使用二维数组,eg:字符串(多个),这个时候数组的每一行都是等长的,而字符串之间是不等长的,所以存在很大的空间的浪费。

所以我们可以通过指针数组中的元素分别指向各字符串,如果想对字符串排序,不必要改动字符串的位置,只需要改动各个元素指向(各元素的值是各字符串的首地址)。

所以:这个时候,各个字符串的长度可以不同,并且移动指针 变量的值哟啊比移动字符串花费写的时间少得多。

示例代码:

#include <stdio.h>

#include <string.h>

void main()

{

    void sort(char *name[],int n);

    void print(char *name[],int n);

    char *name[]={"Follow me","BASIC","Greate Wall","FORtran","Computer design"};

    int n = 5;

    sort(name,n);

    print(name,n);    

}

void sort(char *name[],int n){

    char *temp;

    int i,j,k;

    

    for(i=0;i<n-1;i++){

        k = i;

        for(j=i+1; j<n ; j++)

            if(strcmp(name[k],name[j])>0) k = j;

    

    if(k!=i) {

    temp = name[i];

    name[i] = name[k];

    name[k] = temp;

    }

    }

}

void print(char *name[],int n){

    int i;

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

    printf("%s \n",name[i]);

}

输出结果:

BASIC

Computer design

FORtran   

Follow me

Greate Wall

指向指针的指针

指针的指针——————> 指针数组 ————————> 数据

指针的指针的例子:

示例:

#include <stdio.h>

int main()

{

    char *name[]={"Follow me","BASIC","Greate Wall","FORtran","Computer design"};

    

    char **p;

    int i;

    for(i=0; i<5 ; i++){

    p = name+i;

    printf("%s \n",*p);

    }

    return 0;

}

输出结果:

Follow me

BASIC

Greate Wall

FORtran

Computer design

3、指针数组做main函数的形参

指针数组一个重要的作用就是作为main函数的形参;

我们一般写main函数为:void main();//中间的括号是空的

实际山main的函数是:void main(int argc, char *argc[]);

main函数是由系统调用,在操作命令的状态下,输入main所在文件名(经过编译、链接后得到的文件名,后缀名为.exe),操作系统就调用main函数。

问题:main函数的参数是从何处来的呢?显然是不可能在程序中得到的,实际上参数是和命令一起给出的,也就是在一个命令行中包括命令名和需要传给main函数的参数。

命令行得一般形式:

命令名 参数1 参数2 …… 参数n

假设:命令名是main所在的执行文件名为file1,今想将两个字符串"China","beijing"做为main函数的参数,参数可以写成:

file1 China Beijing

(实际上,文件名包括盘符、路径以及文件的扩展名)

注意上面参数与main函数中形参的关系。main函数中形参argc是指命令行中参数的个数(注意:文件名也作为一个参数,eg:file1也是一个参数)

现在argc的值等于3(3个命令行参数,file1,China,Beijing)

main函数的第二个参数argv是一个指向字符串的指针数组,也就是说,带参数的main函数原型是:

void main(int argc,char *argv[]);

也就是 命令行中的字符串构成了一个指针数组。

指针数组argv[0]的值是file1。

void main(int argc,char *argv[])

{

    while(argc>1){

    ++argv;    

    printf("%s \n",*argv);

    --argc;

    }

}

在命令中写入:

file1 China Beijing

会有相应的内容尽心=输出。

可以改成:

void main(int argc,char *argv[])

{

    while(argc-->1){

    printf("%s %c\n",*++argv,(argc>1)?' ':'\n';

    }

}

说明:

(1)while语句中的argc-- > 1  和 --argc >1 是一样的

(2)当argc > 1 的时候,在输出字符串之间输出一个空格。

main里面的参数名字不一定写为:argc ,argv  ,这个是人们的习惯

总结:

指针作为main的形参的作用

int argc  参数的个数

char *argv[] 是指向指针的指针变量,就是传递命令行中的字符串参数,该函数是由系统调用。

关于指针的数据类型和指针运算的小结
定义 含义
int i; 定义整型变量
int *p; p为指向整型数据的指针变量。
int a
定义整型数组a,它由n个元素。
int *p
; 定义指针数组p,它由n个指向整型数据的指针元素组成 //p为一个数组存储的一个指针。
int (*p)
p为指向整型数据的指针元素组成吧 //这个p是一个指针。
int f(); f返回整型函数值的函数。
int *p(); p为返回一个指针的函数,该-指针指向整型数据
int (*p)() p为指向函数的指针,该函数返回一个整形值。
int **P; p是一个指针变量,它指向一个指向整型数据的指针变量。

指针的运算

(1)指针变量的加(减)一个整数

(2)指针变量赋值

(3)指针变量可以有空值,即为该指针变量不指向任何变量。

可以表示为;p = NULL;

(4)两个指针变量可以相减

如果两个指针是指向同一个数组的元素,则两个指针变量的值是两个指针之间的元素个数。

但是相加就没有意义了。

(5)两个指针变量比较

若是两个指着指向同一个数组的元素,则可以进行比较。指向前面元素的指针小于指向后面的元素的指针变量。

void指针类型

void * 可以定义一个指针类型,但是不确定是哪一个类型,在动态分配的时候就是返回 void* 这个类型。

使用指针的优点:

(1)提高程序效率,

(2)在调用函数时候,变量改变了的值是能够在主调函数使用,即为可以从函数调用的懂啊多个可改变的值

(3)可以实现动态存储分配。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: