c语言中的指针

数组与指针

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

引用数组的元素可以用下标(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[n]                定义整型数组a,它由n个元素。
int *p[n];                定义指针数组p,它由n个指向整型数据的指针元素组成  //p为一个数组存储的一个指针。
int (*p)[n]                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)可以实现动态存储分配。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值