指针学习中遇到的问题

1.指针的概念

 在计算机中,所有的数据都是存放在存储器中的。 一般把存储器中的一个字节称为一个内存单元, 不同的数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元等, 在第二章中已有详细的介绍。为了正确地访问这些内存单元, 必须为每个内存单元编上号。 根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。 既然根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。 内存单元的指针和内存单元的内容是两个不同的概念。

2.指针是变量

a.指针是一个变量,指针能够存值,值为另外一个变量的地址值(内存地址值)

b.系统为指针分配内存空间

c.指针有自己的地址

d.指针就像其他变量或常量一样,在使用之前必须对指针进行声明。

e.使用连字号(&)获取地址值:

3.指针的类型和指针所指向的类型

a.指针的类型

 从语法的角度看,只要把指针声明里的指针名字去掉,剩下的部分就是这个指针的类型,这是指针本身所具有的类型。

例如:(1)int *ptr;    //指针的类型是 int *

  (2)char *ptr;  //指针的类型是char *

(3)int **ptr;   //指针的类型是int **

  (4)int (*ptr)[3]; //指针的类型是int (*)[3]

  (5)int *(*ptr)[4];   //指针的类型是int *(*)[4]

b.指针所指向的类型

当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。

 从语法上看,只须把指针声明语句中的指针名字和名字左边的指针声明符“*”去掉,剩下的就是指针所指向的类型。

 指针一般形式为: 类型说明符 *变量名;

 其中,*表示这是一个指针变量,变量名即为定义的指针变量名,类型说明符表示本指针变量所指向的变量的数据类型。

 例如: int *p1;表示p1是一个指针变量,它的值是某个整型变量的地址。 或者说p1指向一个整型变量。至于p1究竟指向哪一个整型变量, 应由向p1赋予的地址来决定。

4.指针变量的关系运算
 指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。例如:
pf1==pf2表示pf1和pf2指向同一数组元素
pf1>pf2表示pf1处于高地址位置
pf1<pf2表示pf2处于低地址位置
main(){
int a=10,b=20,s,t,*pa,*pb;
pa=&a;
pb=&b;
s=*pa+*pb;
t=*pa**pb;
printf("a=%d/nb=%d/na+b=%d/na*b=%d/n",a,b,a+b,a*b);
printf("s=%d/nt=%d/n",s,t);
}
......
说明pa,pb为整型指针变量
给指针变量pa赋值,pa指向变量a。
给指针变量pb赋值,pb指向变量b。
本行的意义是求a+b之和,(*pa就是a,*pb就是b)。
本行是求a*b之积。
输出结果。
输出结果。
......
指针变量还可以与0比较。设p为指针变量,则p==0表明p是空指针,它不指向任何变量;p!=0表示p不是空指针。空指针是由对指针变量赋予0值而得到的。例如: #define NULL 0 int *p=NULL; 对指针变量赋0值和不赋值是不同的。指针变量未赋值时,可以是任意值,是不能使用的。否则将造成意外错误。而指针变量赋0值后,则可以使用,只是它不指向具体的变量而已。
main(){
int a,b,c,*pmax,*pmin;
printf("input three numbers:/n");
scanf("%d%d%d",&a,&b,&c);
if(a>b){
pmax=&a;
pmin=&b;}
else{
pmax=&b;
pmin=&a;}
if(c>*pmax) pmax=&c;
if(c<*pmin) pmin=&c;
printf("max=%d/nmin=%d/n",*pmax,*pmin);
}
......
pmax,pmin为整型指针变量。
输入提示。
输入三个数字。
如果第一个数字大于第二个数字...
指针变量赋值
指针变量赋值
指针变量赋值
指针变量赋值
判断并赋值
判断并赋值
输出结果

5.字符串指针变量与字符数组的区别
 用字符数组和字符指针变量都可实现字符串的存储和运算。 但是两者是有区别的。在使用时应注意以下几个问题:
a. 字符串指针变量本身是一个变量,用于存放字符串的首地址。而字符串本身是存放在以该首地址为首的一块连续的内存空间中并以‘/0’作为串的结束。字符数组是由于若干个数组元素组成的,它可用来存放整个字符串。
b. 对字符数组作初始化赋值,必须采用外部类型或静态类型,如: static char st[]={“C Language”};而对字符串指针变量则无此限制,如: char *ps="C Language";
c. 对字符串指针方式 char *ps="C Language";可以写为: char *ps; ps="C Language";而对数组方式:
static char st[]={"C Language"};
不能写为:
char st[20];st={"C Language"};
而只能对字符数组的各元素逐个赋值。
  从以上几点可以看出字符串指针变量与字符数组在使用时的区别,同时也可看出使用指针变量更加方便。前面说过,当一个指针变量在未取得确定地址前使用是危险的,容易引起错误。但是对指针变量直接赋值是可以的。因为C系统对指针变量赋值时要给以确定的地址。因此,
char *ps="C Langage";
或者 char *ps;
ps="C Language";都是合法的。

6.常量指针和指针常量

a.常量指针
 常量指针是指向常量的指针,指针指向的内存地址的内容是不可修改的。
常量指针定义“const int *p=&a;”告诉编译器,*p是常量,不能将*p作为左值进行操作。但这里的指针p还是一个变量,它的内容存放常量的地址,所以先声明常量指针再初始化是允许的,指针也是允许修改的,例如:
int a=0,b=1;
const int *p;   //声明常量指针p
p=&a;            //p指向a
p=&b;            //修改指针p让其指向b,允许
*p=2;             //不允许

b.指针常量
 指针常量是指针的常量,它是不可改变地址的指针,但可以对它所指向的内容进行修改。
指针常量定义"int *const p=&a;"告诉编译器,p是常量,不能作为左值进行操作,但允许修改其指向的内容,即*p是可修改的。指针常量必须在声明的同时对其初始化,不允许先声明一个指针常量随后再对其赋值,这和声明一般的常量是一样的,例如:
int a=0,b=1;
int *const p1=&a; 
int *const p2;         //不允许,必须对其初始化
p2=&b;                 //不允许,p2是常量不允许作为左值
*p1=2;                  //允许修改指针*p1的值

6.在使用非全零为空指针内部表达的机器上,NULL如何定义
 跟其他机器一样:定义为0 (或某种形式的 0)。当程序员请求一个空指针时,无论写"0"还是“NULL”,都由编译器生成适合机器的二进制表达形式。因此,在空指针的内部表达不为 0的机器上定义NULL 为 0 跟其他机器一样合法:编译器在指针上下文看到的未加修饰的 0 都会生成正确的空指针。

7.如果 NULL和 0作为空指针常数等价,该用哪一个?
      许多程序员认为指针上下文都应该使用NULL,以表明该值应该被看做指针。另一些人认为用一个宏定义 0,只会把事情搞的更复杂,他们倾向于使用未加修饰的 0 。
      C程序员应该明白,在指针上下文中 NULL 和 0完全等价,而未加修饰的 0也可以完全接受。任何使用 NULL的地方都应该看作一种温和的指针提示,然而程序员并不能依靠它来区分指针0 和 整数0。

【最佳实践】虽然 NULL和 0具有相同的功能,但建议使用NULL 替代 0。这种实践有两个好处:
  a.你可以认为 NULL的值改变了,比如在使用非零内部空指针的机器上,用NULL会比 0 有更好的兼容性,但事实并非如此。

  b.尽管符号常量经常代替数字,以备数字的变化,但这不是NULL 替代 0 的原因。语言本身确保了源码中的 0(用于指针上下文)会生成空指针。NULL只是用做一种格式习惯。

8.万能指针void

 void指针一般被称为通用指针或泛指针,它是C语言关于"纯粹地址“的一种约定。void指针指向某个对象,但该对象不属于任何类型。请看下例。
int * ip;
void *p;
在上例中,ip指向一个整型值,而p指向的对象不属于任何类型。
 在C语言中,任何时候都可以用其他类型的指针来代替void指针,或者用void指针来代替其他类型的指针,并且不需要进行强制转换。例如,可以把char*类型的指针传递给需要void指针的函数。
 当进行纯粹的内存操作时,或者传递一个指向未定类型的指针时,可以使用void指针。void指针也经常用作函数指针。
有些C代码只进行纯粹的内存操作。在较早版本的C语言程序中,这一点是通过字符指针"char*"实现的,但是这容易产生混淆,因为人们不容易判定一个字符指针究竟是指向一个字符串,还是指向一个字符数组,或者仅仅是指向内存中的某个地址。
 例如,strcpyo函数将一个字符串复制到另一个字符串中,Slcpyo函数将一个字符串中的部分内容复制到另一个字符串中。
 char *strcpy(char'strl,const char *str2);
char *strncpy(char *strl, const char*str2, siz.e_t n);
 memcpyo函数将内存中的数据从一个位置复制到另一个位置。
void *memcpy(void *addrl, void *addr2,size_t n);
memcpy()函数使用了void指针,以说明该函数只进行纯粹的内存复制,包括NULL字符(零字节)在内的任何内容都将被复制。请看下例。
#include"thingie, h"                   /*defines struct thingie */
struct thingie * p_src * p_dest;
/ *... * /
memcpy{p_dest, p_src ,sizeof (struct thingie ) * numThingies );
在上例中,memcpy()函数要复制的是存放在struct thingie结构体中的某种对象op_dest
和p_src都是指向struct thingie结构体的指针,memcpy()函数将把从p_src指向的位置开始的"sizeof(stuct thingie)*numThingies"个字节的内容复制到从p_dest指向的位置开始的一块内存区域中。对memcpy()函数来说,p_dest和p_src都仅仅是指向内存中的某个地址的指针。

9.指针数组和数组指针

 指针数组:指针数组可以说成是”指针的数组”,首先这个变量是一个数组,其次,”指针”修饰这个数组,意思是说这个数组的所有元素都是指针类型,在32位系统中,指针占四个字节。

 数组指针:数组指针可以说成是”数组的指针”,首先这个变量是一个指针,其次,”数组”修饰这个指针,意思是说这个指针存放着一个数组的首地址,或者说这个指针指向一个数组的首地址。 

数组指针(也称行指针)

 定义 int (*p)[n];

 ()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。

 如要将二维数组赋给一指针,应这样赋值:
int a[3][4];
int (*p)[4];                //该语句是定义一个数组指针,指向含4个元素的一维数组。
 p=a;                       //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
 p++;                      //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]

所以数组指针也称指向一维数组的指针,亦称行指针。
指针数组

定义 int *p[n];

[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1是错误的,这样赋值也是错误的:p=a;因为p是个不可知的表示,只存在p[0]、p[1]、p[2]...p[n-1],而且它们分别是指针变量可以用来存放变量地址。但可以这样 *p=a; 这里*p表示指针数组第一个元素的值,a的首地址的值。

如要将二维数组赋给一指针数组:
int *p[3];
int a[3][4];
for(i=0;i<3;i++)
p[i]=a[i];
这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2],所以要分别赋值。

数组指针只是一个指针变量,似乎是C语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间。还需要说明的一点就是,同时用来指向二维数组时,其引用和用数组名引用都是一样的。

10.指针函数和函数指针

a.函数指针
 "函数指针”是指向函数的指针变量,因而“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。如前所述,C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上一致的。函数指针有两个用途:调用函数和做函数的参数。函数指针的说明方法为: 
数据类型标志符 (*指针变量名)(参数);//函数括号中的参数可有可无,视情况而定。 
如果我们有一个int test(int a)的函数,那么,它的地址就是函数的名字,这一点如同数组一样,数组的名字就是数组的起始地址。 定义一个指向函数的指针用如下的形式,以上面的test()为例:
int (*fp)(int a);            //这里就定义了一个指向函数的指针
fp=test;                      //将函数test的地址赋给函数学指针fp 
 typedef定义可以简化函数指针的定义,在定义一个的时候感觉不出来,但定义多了就知道方便了,上面的代码改写成如下的形式:
 typedef int (*fp)(int a);
//这里不是生命函数指针,而是定义一个函数指针的类型,这个类型是自己定义的,类型名为fp  
 fp fpi;                           //这里利用自己定义的类型名fp定义了一个fpi的函数指针!  
 fpi=test; 

b. 指针函数
指针函数是指带指针的函数,即本质是一个函数。我们知道函数都有返回类型(如果不返回值,则为无值型),只不过指针函数返回类型是某一类型的指针。其定义格式如下所示:
返回类型标识符 *返回名称(形式参数表)
{
函数体
}
返回类型可以是任何基本类型和复合类型。
返回指针的函数的用途十分广泛。事实上,每一个函数,即使它不带有返回某种类型的指针,它本身都有一个入口地址,该地址相当于一个指针。比如函数返回一个整型值,实际上也相当于返回一个指针变量的值,不过这时的变量是函数本身而已,而整个函数相当于一个“变量”。
举例:
float *find(float pointer[],int n)            /*定义指针函数*/ 

    float *pt; 
    pt=*(pointer+n); 
    return(pt); 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值