征服C指针之关于指针学习第一步!!!

1、指针究竟是什么?

关于指针一词,在K&R中有这样的说明:指针是一种保存变量地址的变量,在C中频繁使用。

其实在表达上这样的说明有很大的问题的。总会让人感觉,一旦说到指针就要把它当作变量的意思。实际上并非总是如此。

在C语言标准中最初出现“指针”一词部分时,曾有这样一段话:

    指针类型(pointer type)可由函数类型、对象类型或不完全的类型派生,派生指针类型的类型称为引用类型。指针类型描述一个对象,该类对象的值提供对该引用类型实体的引用。由引用类型T派生的指针类型有时称为“(指向)T的指针”,从引用类型构造指针类型的过程称为“指针类型的派生”。这些构造派生类型的方法可以递归的应用。


看完上面一段话也许你会一头雾水。。下面让我来给大家详细解释以下——指针类型这个词!!!!

提到类型,你会立刻想起来 “int类型、double类型、.....”等基本类型。同样在C语言中也存在“指针类型”一词。“指针类型”其实不是单独存在的,它是由其它类型派生而成的,上面对标准内容的引用也说到了“由引用类型T派生的指针类型有时称为“(指向)T的指针””。也就是说实际上存在的类型是“指向int的指针类型、指向double的指针类型、....”等。

因为指针类型是类型,所以他和基本的数据类型—int类型、double类型等一样,也存在“指针类型变量”和“指针类型的值”。。值得注意的是:“指针类型”、指针类型变量”和“指针类型的值”经常被简单的统称为指针,所以容易造成歧义。。。


注意:先有“指针类型”,正因为有了“指针类型”,所以才有了“指针类型变量”和“指针类型的值”。。

例如:C语言中,使用int类型表示整数。因为int是“类型”,所以存在用于保存int型的变量,当然就有了int型的值。。。指针类型同样如此,即存在指针类型的变量,也存在指针类型的值。因此,几乎所有的处理程序中,“指针类型的值”实际上就是指内存的地址。


2、实例讲解:

这个程序主要是涉及一些指针的概念的熟悉,主要是那些不同类型指针的变换。

/*This code comes The C programming language write by K&R*/

 

  #include <stdio.h>
  #include <stdlib.h>
 
 void f(void)
 {
     int a[4];   //声明数组类型变量
     int *b = malloc(16);   //使用malloc动态分配16字节大小的内存
     int *c;   //
     int i;  
 
     printf("1: a = %p, b = %p, c = %p\n", a, b, c);
     printf("1: a = %p, b = %p, c = %p\n", &a, &b, &c")  //该句为后来自己添加发现这样打印才是真正的a,b,c本身所放的位置,它们本身应该都                                                                                                         是存放在栈上的,毕竟是全局变量,没什么好说的,之所以上面的一句不是他们本                                                                                                         身地址值,涉及到malloc分配内存单元到堆的内容。。还有就是c指针没有指向具体                                                                                                       的内存所以是变化的
     c = a;  
     /*为数组各元素赋值*/
     for (i = 0; i < 4; i++)
     {
     a[i] = 100 + i;
      }
     c[0] = 200;
     printf("2: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n", a[0], a[1], a[2], a[3]);
 
     c[1] = 300;
     *(c + 2) = 301;//c语言中的c[i]是*(c+i)的简便写法
     3[c] = 302;//这个表示要注意,其实等价于c[3],c[i]可以写成i[c]
     printf("3: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n", a[0], a[1], a[2], a[3]);
 
     c = c + 1;
     *c = 400;
     printf("4: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n", a[0], a[1], a[2], a[3]);
 
     c = (int *) ((char *) c + 1); /*这个地方首先你需要知道你的char,和int是多少位的,可以通过下面这两个语句来获得printf("char is %d\n",sizeo                                    f(char));printf("int is %d\n",sizeof(int));然后你需要注意的,(char *)c +1 其实只是移动了一个char的,                                      大小,然后又变成了一个int型的大小,做一个小的计算就可以得到结果了,具体请看下面指针的强制类型转换讲解*/

     *c = 500;
     printf("5: a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n", a[0], a[1], a[2], a[3]);
 
     b = (int *) a + 1;
     c = (int *) ((char *) a + 1);
     printf("6: a = %p, b = %p, c = %p\n", a, b, c);
  }
 
  int main(int ac, char **av)
  {
      f();
      return 0;

结果:

sil4@debian:~$ gcc -g pointer.c -o a2
sil4@debian:~$ ./a2 
1: a = 0xbfec9c9c, b = 0x89b9008, c = 0x80498ac
1: a = 0xbfec9c9c, b = 0xbfec9c98, c = 0xbfec9c94
2: a[0] = 200, a[1] = 101, a[2] = 102, a[3] = 103
3: a[0] = 200, a[1] = 300, a[2] = 301, a[3] = 302
4: a[0] = 200, a[1] = 400, a[2] = 301, a[3] = 302
char is 1
int is 4
5: a[0] = 200, a[1] = 128144, a[2] = 256, a[3] = 302
6: a = 0xbfec9c9c, b = 0xbfec9ca0, c = 0xbfec9c9d


3、指针使用过程中的常见疑问:


3.1、Q:几乎所有的书中都会提到:“指针类型的值实际是指内存的地址”,对于这句话有人会问“归根结底,指针就是地址,地址就是内存中被分配的‘门牌号’,所以指针和int类型应该算是一回事吧?”

         A:实际上,从某种意义上来说,这种认识也不无道理,在C语言的前身B语言中,指针和整数是没有区别的。此外虽然我们经常使用printf()和%p来表示指针,但是在我们的实际运行环境中,使用%x也可以很好的表示地址,对于不太擅长16进制的人来说,使用%d也能使用十进制的方式来确认地址的内容。

3.2、Q:指针就是地址吧。那么,指向int的指针也好,指向double的指针也好他们有什么不同,有必要对他们进行区分吗?

         A:当程序运行时,不管是指想int的指针,还是指向double的指针,都保持着相同的表现形式,让我们看一个例子就明白了:

              

#include<stdio.h>

int main(int argc, char **argv[])
{
   1、 int hoge=5;
   2、void *hoge_p;

   3、 hoge_p = &hoge;

   4、printf("%d\n",*hoge_p);

    return 0;
}


sil4@debian:~$ gcc -g3 pointer_test.c 
pointer_test.c: In function ‘main’:
pointer_test.c:10: warning: dereferencing ‘void *’ pointer
pointer_test.c:10: error: invalid use of void expression

上买的代码里标记3的一行是不会出错的,因为ANSI C 为我们准备了“可以指向任何类型的指针类型”———void * 类型;而标记行4就会出现上述错误,是因为如果只是告诉内存地址却没有告诉保存在那个地址上值的数据类型,当然是取不出来的。

只需稍微修改一下就可以顺利执行了:

将 printf("%d\n",*hoge_p);   改为 :printf("%d\n",*(int *)hoge_p); 这里通过将所指类型不明的指针hoge_p强制转换成int型指针,来告知编译器,由此就可以取出int类型的值。


上面的写法比较繁琐,通常的写法为:

int * hoge_p; 因为这样写编译器可以记住hoge_p是指想int的指针。所以说编译器会帮助我们记住指针指向什么样的类型。




4、C语言指针强制类型转换解析:

C语言中,任何一个变量都必须占有一个地址,而这个地址空间内的0-1代码就是这个变量的值。不同的数据类型占有的空间大小不一,但是他们都必须有个地址,而这个地址就是硬件访问的依据,而名字只是提供给程序员的一种记住这个地址的方便一点的方法。但是,不同的变量在机器中都是0-1代码,所以,我们不能简单的通过检查一个值的位来判断它的类型。

例如,定义如下:

int a;

 float b;

double c;

 long double d;

(假设它们所占的字节分别是48810,而且连续存储于某个地址空间,起始地址是100,则我们可以得到如下内存分布)

a变量就是由以地址100开始到103结束的4个字节内存空间内的0-1代码组成。b变量则是由以地址104开始到112结束的8个字节内存空间内的0-1代码组成。而在机器中,这些内存都是连续的0-1代码,机器并不知道100~103是整型而104~111是float型,所有这些类型都是编译器告知的。当我们用a时,由于前面把a定义为int型,则编译器知道从a的地址开始向后取4个字节再把它解释成int型。那么(float)a,就是先按照int类型取出该数值,再将该数值按照int to float的规则转换成float型。所以强制类型转换就是按照某个变量的类型取出该变量的值,再按照***to***的规则进行强制转转换。如果是(类型名)常数,则是将该常数按照常数to类型 的规则进行强制转换。

指针也是一个变量,它自己占据一个4个字节的地址空间(由于程序的寻址空间是2^32次方,即4GB,所以用4个字节表示指针就已经能指向任何程序能够寻址到的空间了,所以指针的大小为4字节),他的值是另一个东西的地址,这个东西可以是普通变量,结构体,还可以是个函数等等。由于,指针的大小是4字节,所以,我们可以将指针强制转换成int型或者其他类型。同样,我们也可以将任何一个常数转换成int型再赋值给指针。所有的指针所占的空间大小都是4字节,他们只是声明的类型不同,他们的值都是地址指向某个东西,他们对于机器来说没有本质差别,他们之间可以进行强制类型转换。
指针 to 指针的强制类型转换是指将指针所指的内容的类型由原先的类型转换为后面的类型。

int a = 1;

int *p = &a;

float *p1 = (float*)p;

pp1的值都是&a,但是*p是将&a地址中的值按照int型变量进行解释,而*p1则是将&a地址中的值按照float型变量进行解释。


鉴于指针之间这种灵活的强制类型转换的需求和出于简化代码的考虑,ANSI C引入了空指针即void*void指针又名万能指针,在现在的很多程序中,当参数不确定时就用万能指针代替,这一类的指针在线程\进程函数里特别常见。

ANSI C规定,void指针可以复制给其他任意类型的指针,其他任意类型的指针也可以复制给void指针,他们之间复制不需要强制类型转换。当然任何地址也可以复制给void型指针。我们在《网络编程》中经常会看到accept(socket, (struct sockaddr *)&saddr_c, &lenth)之类的语句在&saddr_c之前需要增加代码(struct sockaddr *)是因为当此函数被设计的时候ANSI C还没有提出void*的概念。所有的地址统一用struct sockaddr类型标识,该函数的第二个参数也是指向struct sockaddr类型的指针,此处是强制类型转换。

当然,在某些编译器中不同类型的指针也可以进行直接赋值,但一般情况下会给出类型不匹配的警告。要求程序员显示的给出指针强制类型转换可以提醒程序员小心使用指针,对于明确程序目的具有一定的好处。

1、指针类型强制转换:

int m;

int *pm = &m;

char *cp = (char *)&m;

pm指向一个整型,cp指向整型数的第一个字节


2、结构体之间的强制转换

struct str1 a;

struct str2 b;

a=(struct str1) b;                  //this is wrong

a=*((struct str1*)&b);         //this is correct


3、关于一个程序的解释


int main(void)

{

    int a[4] = {1, 2, 3, 4};

    int *ptr1=(int *)(&a+1);

    int *ptr2=(int *)((int)a+1);

    int *c = *(a + 1);

    printf("%x, %x,%x\n", ptr1[-1], *ptr2,*c);

    return 0;

}

输出分别为4 和2000000,2

式子&a+1表示的是指针加法运算,而不是普通的数值加法运算

vs2008下,其中a = 0x001bfc18
(&a + 1) = 0x001bfc28
而 a+1 = 0x001bfc1c

 &a + 1 的值取决于a的类型如果a申明int a;
则&a + 1 = 0xFFFF5704  = a + 1
如果 int a(ArryLen);
则&a + 1 = 0xFFFF5700 + 4 * ArryLen <> a + 1

a 表示数组的起始地址,(int ) a 表示将a的地址转化为一个整形数,(int)a + 1 表示普通的数值加法运算,(int *)((int)a + 1)表示把(int )a + 1转化为整型指针的地址。该地址指向数组a(0)的第一个字节(从0计数),因为是int型的 所以需要四个字节的解释,所以结果是a(0)的后三个字节和a(1)的第一个字节组成的值,该值受大小端的影响。

 *(a + 1)  此时的a已经是一个常指针了,这个表达式计算出a所指向元素后面的第2个元素的地址,然后对它解引用得到相应的值。这个表达式等价于

int last = a[1]


5、什么是空指针


空指针是一个特殊的指针值!!!

空指针是指可以确保没有指向任何一个对象的指针。通常使用宏定义NULL来表示空指针常量值。空指针可以确保他和任何一个非空指针进行比较都不会相等,因此经常作为函数发生异常时的返回值使用。

关于空指针的内容主要说一下:NULL、0和‘\0’吧。。。相信大家刚开始的接触C语言的时候这个很难区分开吧!!!!!

NULL、0和‘\0’ 区分:

NULL指针一般是是指void*

‘\0’:是指空字符就是说:所有位为0的字节

0:当然就是常量0了。


实在是写不动了,关于NULL的内容以后补充把!!!!!



6、参考资料:

   征服C指针(一本简单易懂的书  作者是日本人哦!!!!)、

   http://hi.baidu.com/xdyang1986/item/53ae6a3115fb65f7a88428c4

   http://blog.csdn.net/mhjcumt/article/details/7355127





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值