c语言程序设计现代方法简略笔记————指针(一)

指针是c语言中最重要的特性之一,本篇文章侧重基本知识,后续考虑补充更高级的应用。

一 指针变量与地址

在计算机内存中,使用字节作为单位存储信息,每个字节存储八位信息,每个字节拥有唯一地址,地址自0开始,假设共有n个字节,那么就到n-1为止。

程序中变量占有不同的字节内存,我们一般把第一个字节的地址作为该变量的地址。

虽然我们用数表示地址,然而地址的取值范围可能不同于整数的范围,所有我们不能用普通的整型变量来存储地址,但是,我们可以用特殊的指针变量来存储地址。当指针变量p存储变量i的地址时,我们就可以说p“指向”i。

1 指针变量的声明

int *p;
double *q;
char *r;

以上为三个指针的声明,与普通变量声明基本一致,只是前面必须带星号*。指针有其特殊的类型区分,每个指针只能指向一种特定类型(即声明时的引用类型)的对象。而引用类型没有限制。甚至一个指针可以指向另一个指针。

2 取地址运算符和间接寻址运算符

为了寻找到变量地址,可以使用取址运算符“&”,为了寻找到指针所指向的对象,可以使用间接寻址运算符“ * ”。若p为指针,那么*p表示p当前所指向的对象。

声明指针变量是为了给指针腾出空间,但是并没有把他指向对象,所以使用前初始化指针是必要的。

int i,*p;
p = &i;

这段语句表示把p指向i。也是把i的地址赋给变量p。也可以简写:

int i, *p = &i;
//或者
int i;
int *p = &i;

需要注意一定要先声明i才行。

一旦指针变量指向了对象,那么就可以利用寻址运算符型号来访问存储在对象中的内容。照上文,p指向i,便有:

printf("%d",*p);

这串代码显示的将是i的值,而不是i的地址。

注意点:

       如果p指向i,那么就可以将指针*p视作i的别名,也就是说,*p就等同于i,对*p修改也会生效于i,对i的赋值或者对*p的赋值都会生效于另一方。在这个角度来看,也可以将符号*与符号&视作互为逆运算。

警告

      不要把间接寻址运算符用于未被初始化的指针变量,若指针变量p没有被初始化,那么试图使用p的值将会导致未定义的行为发生。另外随意的给*p赋值是危险的行为,因为若p恰好有有效的内存地址,那么这样的赋值将会试图修改储存在该地址的数据。若该赋值改变的内存属于该程序,那么可能会导致程序发生不可预测的行为,若该内存属于操作系统,那么可能会导致系统崩溃。编译器会给出警告消息,告诉p未被初始化。

3 指针赋值

int i = 1,j = 0,*p,*q;
//给指针p赋值
p = &i;
//给指针q赋值
q = p;
//给指针q赋j的值
q = &j;
//将指针p的值复制给q
*q = *p;

前面讲了很多,其实就一点,即 *p与p的关系。给指针变量赋值一定是使用取址符&给指针赋值,告诉p应该指向的地址位置,之后就可以用p来代替原来的变量的地址了,而*p的作用是对p所指向的地址进行操作,换言之,p就是&i,而*p则是i。这也就是前文讲的*与&互为逆运算的意思。

4 指针作为参数

c语言的函数调用存在一个特性,即当程序调用函数时,是将参数对号入座传入预定好的函数中,在函数中进行加工后再依靠返回值回馈给程序,这就导致了一个问题,函数内部与外部仅仅只能通过返回值沟通,内外相互独立。这样的操作下我们一般只能返回一个表达式,并且在函数中加工的中间值无法在程序中获得,如果我们面对想要利用一个函数获取多个结果或者想要获取函数的某个中间过程值时,这个特性就很让人讨厌了。例如:

void fun(double x,long int_part,double frac_part)
{
    int_part = (long)x;
    farc_part = x - int_part;
}

在这串代码中,我们想要利用这个fun函数来分开获取一个小数的整数部分与小数部分。因为一个函数无法返回两个值,我们尝试将想要获取的两部分作为参数输入函数,在函数中进行加工,但是由于函数内外相互独立,所以这样操作,在之后的的程序中调用整数部分与小数部分都不能如愿。

指针提供了解决这个问题的方法。现在我们尝试不再传递变量x作为函数的实际参数,而是提供&x,即指向x的指针,同时声明相应的形式参数为指针这样在函数运行中,p的值为&x,因此*p将会是x的别名,函数体内*p的每次出现都会是对x的间接引用,并且允许函数既可以读取x也可以修改x。

现在我们利用这个思路来修改上面的示例:

void fun(double x,long *int_part,double *frac_part)
{
    *int_part = (long)x;
    *farc_part = x - int_part;
}

调用该函数:

fun(3.14159,&i,&d);

因为i与d前带有取址符&,所以此时传递的实际参数就是两个分别指向变量i与p的指针,而不是i与p的值。此时函数运转如下:

1.将3.14159赋值到x中,将指向i的指针存储到int_part中,将指向p的指针赋值到farc_part中;

2.第一个赋值把x的值转换为long类型,并且存放在int_part指向的对象中,由于int_part指向i,所以赋值把值放到i中;

3.第二个赋值语句同理,将int_part指向的值,即i的值取出(即3),先转为double型,再利用x的值减去它,得到小数部分0.14159,再将这个值放在farc_part所指向的对象中。

4.函数语句执行完毕,函数返回,此时就像我们希望的那样,i与d中分别存在x的整数部分值与小数部分值。

其实利用指针作为函数参数的方法并不新鲜,早在学习c语言的开始我们就已经接触过了,请看下面这段:

int i;
scanf("%d",&i);

必须把地址符&写在i之前,这样以便于给scanf函数传递的将是指向i的指针。指针会告诉scanf函数应该把数据存放在哪里,若没有&,存放给scanf函数的数据将会是i的值。

虽然传递给scanf函数必须得是指针,但是并不一定非得用&,下面给个例子:

int i,*p;
*p = &i;
scanf("%d",p);

既然p中包含了i的地址,那么这样使用将完全可行,并且如果此时再在p前加&将会出错,此时函数将会把值赋给p而不是i。

注意:

      假如我们在调用一个带有指针参数的函数时,传入的值却并不是一个指针,那么编译器将会告诉我们实际参数的类型不对。但是scanf函数并不会这样做,所以这个函数非常容易出错。对于其他函数,若输入的类型不对,编译器期望有一个指针参数,但是没有。所以它就会将输入的变量直接当作指针使用,这就会使函数修改错误的地址。

5 指针作为返回值

事实上,函数不仅能将指针作为参数输入,也可以作为返回值来进行使用。

int *max(int *a,int *b)
{
    if(*a > *b)
        return a;
    else
        return b;
}

这个函数将会返回两个值中更大的一个,调用时,将会使用两个指针作为参数,并且返回一个更大的值的地址

注意!不要返回一个局部变量的指针!

int *fun(void)
{
   int i;
   return &i;
}

如上,一旦这个函数结束,临时局部变量i就失去意义不存在了,所以指向i的指针将会是无效的,有些编译器这时候会进行报错。

数组与指针有不小的联系,本篇到此结束,下一篇我们将会具体将指针与数组进行结合。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值