本节来解决如何给一个指针变量初始化。即怎样使一个指针变量指向另一个变量。前面章节中的某些程序实际上已经使用了,即可以用赋值语句使一个指针变量得到另一个变量的地址,从而使它指向该变量。比如:int i, *j; j...
本节来解决如何给一个指针变量初始化。即怎样使一个指针变量指向另一个变量。
前面章节中的某些程序实际上已经使用了,即可以用赋值语句使一个指针变量得到另一个变量的地址,从而使它指向该变量。比如:
int i, *j; j = &i;
这样就将变量 i 的地址放到了指针变量 j 中,通过 i 的地址,j 就能找到 i 中的数据,所以 j 就“指向”了变量 i。其中 & 是“取地址运算符”,与scanf 中的 & 是一样的概念;* 为“指针运算符”,功能是取其内部所存变量地址所指向变量中的内容。因为 j 是定义成指针型变量,所以 j 中只能存放变量的地址,所以变量i前一定要加 &。需要注意的是,指针变量中只能存放地址,不要将一个整数或任何其他非地址类型的数据赋给一个指针变量。
此外,还有两点需要注意:
j 不是 i,i 也不是 j。修改j的值不会影响i的值,修改 i 的值也不会影响 j 的值。j 是变量 i 的地址,而i是变量 i 里面的数据。一个是“内存单元的地址”,另一个是“内存单元的内容”。
定义指针变量时的“*j”和程序中用到的“*j”含义不同。定义指针变量时的“*j”只是一个声明,此时的“*”仅表示该变量是一个指针变量,并没有其他含义。而且此时该指针变量并未指向任何一个变量,至于具体指向哪个变量要在程序中指定,即给指针变量初始化。而当指定 j 指向变量 i 之后,*j 就完全等同于 i 了,可以相互替换。
下面给大家写一个程序:
# include int main(void) { int i = 3, *j; //*j表示定义了一个指针变量j j = &i; printf("*j = %dn", *j); //此时*j完全等同于i printf("j = %dn", j); //j里面存储的是变量i的地址 return 0; }
输出结果是:
*j = 3
j = 1245052
下面再将上面这个程序修改一下:
# include int main(void) { int i = 3; int *j = &i; //*j表示定义了一个指针变量j, 并将变量i的地址赋给它 printf("*j = %dn", *j); //此时*j完全等同于i printf("j = %dn", j); //j里面存储的是变量i的地址 return 0; }
输出结果是:
*j = 3
j = 1245052
这个程序与第一个程序有什么不同?同样是将变量 i 的地址赋给指针变量 j,第一个程序是“j=&i;”,而第二个程序是“*j=&i;”。原因是,前者是定义指针变量后对它初始化,即先定义后初始化;而后者是定义指针变量时对它进行初始化,即定义时初始化。通过这个对比我们可以更鲜明地看出定义指针变量时的“*j”和程序中用到的“*j”含义的不同。
那么指针变量和指针变量之间可不可以相互赋值呢?我们看看下面这个程序:
# include int main(void) { int *i, *j; int k = 3; i = &k; j = i; //直接指针变量名之间进行赋值 printf("*j = %dn", *j); //此时*j完全等同于k printf("j = %dn", j); // j里面存储的是变量k的地址 return 0; }
输出结果是:
*j = 3
j = 1245044
可见,可以直接将一个指针变量赋给另一个指针变量,只要将指针变量名赋给另一个指针变量名即可。但是需要注意的是:
这两个指针变量的基类型一定要相同。
在赋值之前,赋值运算符“=”右边的指针变量必须是已经初始化过的。也就是说,切忌将一个没有初始化的指针变量赋给另一个指针变量。这是非常严重的语法错误。
同样,也可以在定义指针变量时就给它赋初值:
# include int main(void) { int k = 3; int *i = &k; int *j = i; printf("*j = %dn", *j); //此时*j完全等同于k printf("j = %dn", j); //j里面存储的是变量k的地址 return 0; }
输出结果是:
*j = 3
j = 1245048
注意,“int*j=i;”千万不要写成“int*j=*i;”。因为此时 *i 不是定义指针变量 i,而是完全等同于变量 k。所以 int 型变量不能赋给 int* 型的变量。
指针常见错误
1) 引用未初始化的指针变量
试图引用未初始化的指针变量是初学者最容易犯的错误。未初始化的指针变量就是“野”指针,它指向的是无效的地址。
有些书上说:“如果指针变量不初始化,那么它可能指向内存中的任何一个存储单元,这样就会很危险。如果正好指向存储着重要数据的内存单元,而且又不小心向这个内存单元中写入了数据,把原来的重要数据给覆盖了,这样就会导致系统崩溃。”这种说法是不正确的!如果真是这样的话就是编译器的一个严重的 BUG!
编译器的设计人员是不会允许这么大的 BUG 存在的。那么如果指针变量未初始化,编译器的设计人员是如何处理这个问题的呢?肯定不可能让它乱指。以VC++6.0这个编译器为例,如果指针变量未初始化,那么编译器会让它指向一个固定的、不用的地址。下面来写一个程序:
# include int main(void) { int *p, *q; printf("p = %#Xn", p); printf("q = %#Xn", q); return 0; }
输出结果是:
p = 0XCCCCCCCC
q = 0XCCCCCCCC
可见,在VC++6.0 中只要指针变量未初始化,那么编译器就让它指向 0XCCCCCCCC 这个内存单元。而且这个内存单元是程序所不能访问的,访问就会触发异常,所以也不怕往里面写东西。
而如果在 VS 2008 这个编译器中,程序虽然能编译通过,但是在运行的时候直接出错,它并不会像 VC++6.0 那样还能输出所指向的内存单元的地址。
下面来看一个程序:
# include int main(void) { int i = 3, *j; *j = i; return 0; }
程序中,j 是int* 型的指针变量。j 中存放的应该是内存空间的地址,然后“变量 i 赋给 *j”表示将变量i中的值放到该地址所指向的内存空间中。但是现在 j 中并没有存放一个地址,程序中并没有给它初始化,那么它指向的就是 0XCCCCCCCC 这个内存单元。这个内存单元是不允许访问的,即不允许往里面写数据。而把 i 赋给 *j 就是试图往这个内存空间中写数据,程序执行时就会出错。但这种错误在编译的时候并不会报错,只有在执行的时候才会出错,即传说中的“段错误”。所以,一定要确保指针变量在引用之前已经被初始化为指向有效的地址。
在实际编程中,这种错误常见的另一个地方是用 scanf 给指针变量所指向的内存单元赋值。我们看看下面这个程序:
# include int main(void) { int *i; scanf("%d", i); return 0; }
该程序试图给指针变量 i 所指向的内存单元赋值。但现在指针变量 i 并没有初始化,所以程序执行时出错。所以同样,在使用 scanf 时必须要先给指针变量 i 初始化。比如像下面这样写:
# include int main(void) { int *i, j; i = &j; //先给指针变量i初始化 scanf("%d", i); //i本身就是地址, 所以不用加& printf("%dn", *i); return 0; }
输出结果是:
能不能使用 scanf 给指针变量初始化?指针变量里面存放的是地址,而内存中有数不清的单元,每个单元都有一个地址,你知道每个单元的地址吗?你知道哪些地址是空闲可用的,而哪些地址正存储着重要数据不能用吗?不知道的话怎么用scanf给它初始化呢?万一随便写一个地址正好是存储着非常重要的数据的内存单元地址,那系统就真的崩溃了!
随着大家编程能力的不断提高,慢慢地就会发现,其实编程最重要、最核心的就是如何处理内存的问题,如何与内存打交道。
2) 往一个存放NULL地址的指针变量里面写入数据
这也是编程中最容易犯的错误,不仅是初学编程的,即使是有一些经验的程序员也会不小心犯这个错误。我们把前面的程序改一下:
# include int main(void) { int i = 3; int *j = NULL; *j = i; return 0; }
之前是没有给指针变量j初始化,现在初始化了,但是将它初始化为指向 NULL。NULL 也是一个指针变量。NULL 指向的是内存中地址为 0 的内存空间。以32 位操作系统为例,内存单元地址的范围为 0x00000000~0xffff ffff。其中 0x00000000 就是 NULL 所指向的内存单元的地址。但是在操作系统中,该内存单元是不可用的。凡是试图往该内存单元中写入数据的操作都会被视为非法操作,从而导致程序错误。同样,这种错误在编译的时候也不会报错,只有在执行的时候才会出错。这种错误也属于“段错误”。
然而虽然这么写是错误的,但是将一个指针变量初始化为指向 NULL,这在实际编程中是经常使用的。就跟前面讲普通变量在定义时给它初始化为 0 一样,指针变量如果在定义时不知道指向哪里就将其初始化为指向 NULL。只是此时要注意的是,在该指针变量指向有效地址之前不要往该地址中写入数据。也就是说,该指针变量还要二次赋值。
既然不能往里面写数据,而且还容易犯错,为什么还要这样给它初始化呢?直接同前面定义普通变量时一样,在定义时也不初始化,等到后面知道该给它赋什么值时再给它赋值不行吗?可以!但还是建议大家将它初始化为 NULL,就同前面将普通变量在定义时初始化为 0 一样。这是很好的一种编程习惯。
最后关于 NULL 再补充一点,NULL 是定义在 stdio.h 头文件中的符号常量,它表示的值是 0。
文/Edward上一节中,我们利用了一个小知识来介绍了一下指针,在上面这个例子中,小明和小丽手中的这个杯子就好比我们C语言中的变量,它确实是实实在在存放一些有具体意义的数据。这个杯套就类似于C语言中指针的作用...
文/Edward
上一节中,我们利用了一个小知识来介绍了一下指针,在上面这个例子中,小明和小丽手中的这个杯子就好比我们C语言中的变量
,它确实是实实在在存放一些有具体意义的数
据。这个杯套就类似于C语言中指针的作用,假设我
们由100个这种
杯子的内胆,里面
分别存放了不同类型的饮料,那么我们想要拿取
其中某一杯饮料
时,只需要
手中有1个
杯套,就可以取不同的饮料了。
再来说下函数参数传递的问题。在C语言中,每个变量被定义好之后(Auto类型定义),编译器便会在内存的栈区为它们开辟出来内存空间来存放这些变量。这些内存空间不管是连续的还是断续的,它们都会被分配不同的地址。而如果这些变量作为形式参数传入函数的时候,实际上仅仅是把变量里面存放的常数传递给函数,而函数内部的临时变量又是这个函数被初始化的时候创立的,和调用处的传递变量不是同一个,因此不管再怎么去改写形式参数里面传递进来的内容,本质上只是改变函数内部这些局部变量的内容。
函数的参数传递
指针从一开始被设计出来的时候,其目的不是用来存储某一个具体的常量或变量,它的目的只有一个,就是存放相应变量的地址,即这个变量的内存地址。我们定义完成一个变量之后,就可以将这个指针指向这个变量了。所谓指向某个变量,说直白点就是将这个变量的地址存入指针中。当我们在用指针作为数据交换函数的传递参数时,它其实传入的是这两个变量的地址,而在函数中我们在做这两个变量内容交换时,传递进入函数的指针变量的值是不会变的,还是存放最初的这两个变量的地址,但是当我们做交换操作时,就将原本存在地址A中的变量放入了地址B,将原本存放在B中的变量放入了地址A。