C指针详解

来源:互联网 发布:知到演讲与口才答案 编辑:程序博客网 时间:2024/05/16 14:56

指针详解

1.       指针数组和数组指针

数组指针是指向数组首元素的地址的指针,其本质为指针;例如:

char str1[] = {"C language pointer"};char *p1=str1;
其中p1就表示数组的指针

指针数组是数组元素为指针的数组,其本质为数组。

 

2.      函数指针和指针函数

函数指针指向函数的指针包含了函数的地址,可以通过它来调用函数,例如int  test(int a)中,test就是函数的地址,这和数组的名字就是数组的起始地址一样,函数本身不是变量,但可以定义指向函数的指针,这种指针可以被赋值,存放于数组中,传递给函数及作为函数的返回值。

定义形式:int (*p)(int a)注意,指针类型和形参都与test一样,其中由于括号的优先级比*要高,所以int *p(int a)去掉括号之后,就变成了返回值为指针的指针函数,将失去函数指针的特性。注意:函数指针没有函数体,他的实质是一个指针,利用函数指针可以实现C语言中的“多态”,指针函数的返回值是一个指针,他有自己完整的函数体。

指针调用:p=test; (*p)();调用等价于p();注意一般使用后者,比较规范和常规的函数调用没区别


3.      malloc函数和free函数的用法,内存分配的机制

http://blog.csdn.net/kokodudu/article/details/11760863里面有比较详细的介绍

4.      指针和数组(一维数组和二维数组)

指针& * 内存简介(直观的理解认识指针)

a.指针 * 指针的左值 右值

    int a=9;int *b=&a;//指针表达式左值和右值的解析:=右边的*b是指针的右值,它的值就是a的值,左边的*b就是指针的左值,b所指向的位置(a)把=右侧的表达式计算的结果作为a的新值*b=10-*b;//b=10-*b;非法两边类型不匹配printf("%d\n",*b);//*b==aprintf("%d\n",a);

b.指针表达式(重要)




1.主要介绍指针和一维数组

#include<stdio.h>int main(){//定义一个int型数组并且初始化int day[7] = {1, 2, 3, 4, 5, 6, 7};//定义一个char型的数组并且初始化,一下两句话的区别char str[] = {"C language pointer"};char *p="C language pointer";//字符串常量,存储在只读数据区,不可修改//指针和数组名的区别,显然指针!=数组名printf("%d\n",sizeof(str));printf("%d\n",sizeof(p)); char str1[] = {"C language pointer"};char *p1="C language pointer";//指针不能指向同一个区域printf("%d\n",str1==str);printf("%d\n",p==p1); //不可以修改字符串常量的值str[1]='2';p[0]='2';//运行错误,字符串常量不可修改printf("%s\n",str);printf("%s\n",p);//数组的左臂是下标通过下标可以很随意的访问数组中的任意一个元素int a = day[3];///那么数组的右臂就是指针, 可以用引用法来引用数组//接下俩的两种表达方式是相通的,//都表示的数组的首地址,//代表着数组在内存中的存储位置。int *b = day;//注意:day是一个常量,数组首地址就是指针是一个误区,在子函数中数组首地址就是一个普通的指针,子函数形参使得数组名退化为指针,在子函数中数组名可以++ --,他是普通的指针,是考察重点int *c = &day[0];//day++;这是一个数组名的非法操作,常量不可以++ -- //输出数组的首地址的值printf("%p\n",b);printf("%p\n",c);printf("%d\n",*b);printf("%d\n",*c);//后面的两者等价输出printf("%d\n",*(b+1));printf("%d\n",b[1]);//此时的b等同于day都是数组首地址return 0;}
</pre><pre name="code" class="cpp">2.数组名t与&t的区别
        short t0[10]={1,2,3};//t0是一个地址空间为20个字节的内存块
        //下面两者的值一样都代表这个地址块的首地址cout<<t0<<endl;cout<<&t0<<endl;
        //数组的地址数组t0[10]中的t0与&t0的区别如下,两者数值相同但是前者指向是一个2字节内存块的地址,后者指向是一个20字节内存块的地址cout<<*(t0+1)<<endl;cout<<*(&t0+1)<<endl;cout<<(&t0+1)<<endl;//值增加了20
        //数组指针的强制类型转换char f[10]="rose";cout<<f<<endl;cout<<&f<<endl;cout<<(int*)f<<endl;//输出地址的方式与上面数组也不同,务必注意两者的区别,涉及强制类型转换

3.指针和多维数组

#include<stdio.h>//多维数组和指针int main(){    int d[3][2]={{1,2},{3,4},{5,6}};int *mp=&d[0][0];//下面验证我们所说的多维数组的存储是按照一维数组连续向后存储的,存储形式/*printf("%d\n",*mp);printf("%d\n",*++mp);printf("%d\n",*++mp);*///如何利用一维指针遍历多维数组int *mp=&d[0][0];int m=0;for(;m<6;m++){printf("%d\n",*(mp++));}//如何利用数组名遍历多维数组int i=0;for(;i<3;i++){    int j=0;     for(;j<2;j++){ printf("%d\n",*(*(d+i)+j));//难点:解析含义:d<span style="font-family: Consolas, 'Courier New', Courier, mono, serif; line-height: 18px; background-color: rgb(245, 250, 226);">是数组首元素的地址,它储存的值即是它本身的地址,d=&d,我们在前面区分过这两个,这里*(d+i)就是当前行的一维数组的首地址,这一点理解很重要</span>  } }return 0;}


5.      指针和字符串常量

当一个字符串常量出现于表达式中,除以下三种情况外:

1.作为 &操作符的操作数;

2.作为sizeof操作符的操作数;

3.作为字符数组的初始化值

字符串常量都会被转化为由一个指针所指向的字符数组。例如:char *cp = "abcdefg";不满足上述3个条件,所以"abcdefg"会被转换为一个没有名字的字符数组,这个数组被abcdefg和一个空字符'/0'初始化,并且会得到一个指针常量,它的值为第一个字符的地址,不过这些都是由编译器来完成的。现在可以解释用一个字符串常量初始化一个字符指针的原因了,一个字符串常量的值就是一个指针常量。那么对于下面的语句,朋友们也不该感到迷惑了:

        printf("%c\n",*"abcdefg");

        printf("%c\n", *("abcdefg"+ 1));

        printf("%c\n","abcdefg"[5]);

        *"abcdefg":字符串常量的值是一个指针常量,指向的是字符串的第一个字符,对它解引用即可得到a;

区别以下两种形式:

//定义一个char型的数组并且初始化,一下两句话的区别
char str[] = {"C language pointer"};
char *p="C language pointer";//字符串常量,存储在只读数据区,不可修改


//指针和数组名的区别,显然指针!=数组名
printf("%d\n",sizeof(str));
printf("%d\n",sizeof(p)); 

char str1[] = {"C language pointer"};
char *p1="C language pointer";

//指针不能指向同一个区域
printf("%d\n",str1==str);//结果0
printf("%d\n",p==p1); //结果1

//不可以修改字符串常量的值
str[1]='2';
p[0]='2';//字符串常量不可变,运行出错
printf("%s\n",str);
printf("%s\n",p);

6.      指针的理解和初始化(垂悬指针)

a.指针的初始化

ANSI C定义了零指针常量的概念:一个具有0值的整形常量表达式,或者此类表达式被强制转换为void *类型,则称为空指针常量,它可以用来初始化或赋给任何类型的指针。也就是说,我们可以将0、0L、'/0'、2–2、0*5以及(void *)0赋给一个任何类型的指针,此后这个指针就成为一个空指针,由系统保证空指针不指向任何对象或函数。

ANSI C还定义了一个宏NULL,用来表示空指针常量。大多数C语言的实现中NULL是采用后面这种方式定义的:#define  NULL  ((void *)0)。

对指针进行初始化时常用的有以下几种方式:

  1.采用NULL或空指针常量,如:int *p = NULL;或 char *p = 2-2; 或float *p = 0;

  2.取一个对象的地址然后赋给一个指针,如:int i = 3;  int *ip = &i;

  3.将一个指针常量赋给一个指针,如:long *p = (long *)0xfffffff0;

  4.将一个T类型数组的名字赋给一个相同类型的指针,如:char ary[100]; char *cp = ary;

  5.将一个指针的地址赋给一个指针,如:int i = 3;  int *ip = &i;int **pp = &ip;

  6.将一个字符串常量赋给一个字符指针,如:char *cp = “abcdefg”;

对指针进行初始化或赋值的实质是将一个地址或同类型(或相兼容的类型)的指针赋给它,而不管这个地址是怎么取得的。要注意的是:对于一个不确定要指向何种类型的指针,在定义它之后最好把它初始化为NULL并在解引用这个指针时对它进行检验防止解引用空指针。另外,为程序中任何新创建的变量提供一个合法的初始值是一个好习惯,它可以帮你避免一些不必要的麻烦。

b.悬垂指针的三种情况,尽量避免

垂悬指针是我们在使用指针时经常出现的,所谓垂悬指针就是指向了不确定的内存区域的指针,通常对这种指针进行操作会使程序发生不可预知的错误,因此我们应该避免在程序中出现垂悬指针,一些好的编程习惯可以帮助我们减少这类事件的发生。

造成垂悬指针的原因通常分为三种,对此我们一个一个地进行讨论。

第一种:在声明一个指针时没有对其初始化。在C语言中不会对所声明的自动变量进行初始化,所以这个指针的默认值将是随机产生的,很可能指向受系统保护的内存,此时如果对指针进行解引用,会引发运行时错误。解决方法是在声明指针时将其初始化为NULL或零指针常量。大家应该养成习惯为每个新创建的对象进行初始化,此时所做的些许工作会为你减少很多烦恼。

第二种:指向动态分配的内存的指针在被free后,没有进行重新赋值就再次使用。就像下面的代码:

          int *p =(int *)malloc(4);

         *p = 10;

         printf("%d\n", *p);

         free(p);

         ……

         ……

        printf("%d\n",*p);

这就可能会引发错误,首先我们声明了一个p并指向动态分配的一块内存空间,然后通过p对此空间赋值,再通过free()函数把p所指向的那段内存释放掉。注意free函数的作用是通过指针pp所指向的内存空间释放掉,并没有把p释放掉,所谓释放掉就是将这块内存中的对象销毁,并把这块内存交还给系统留作他用。指针p中的值仍是那块内存的首地址,倘若此时这块内存又被指派用于存储其他的值,那么对p进行解引用就可以访问这个当前值,但如果这块内存的状态是不确定的,也许是受保护的,也许不保存任何对象,这时如果对p解引用则可能出现运行时错误,并且这个错误检测起来非常困难。所以为了安全起见,在free一个指针后,将这个指针设置为NULL或零指针常量。虽然对空指针解引用是非法的,但如果我们不小心对空指针进行了解引用,所出现的错误在调试时比解引用一个指向未知物的指针所引发的错误要方便得多,因为这个错误是可预料的。

第三种:返回了一个指向局部变量的指针。这种造成垂悬指针的原因和第二种相似,都是造成一个指向曾经存在的对象的指针,但该对象已经不再存在了。不同的是造成这个对象不复存在的原因。在第二种原因中造成这个对象不复存在的原因是内存被手动释放掉了,而在第三种原因中是因为指针指向的是一个函数中的局部变量,在函数结束后,局部变量被自动释放掉了(无需程序员去手动释放)。如下面的程序:

#include<stdio.h>

#include<stdlib.h>

int*return_pointer()

{

   int i=3;

   int *p =&i;

   return p;

}

int main()

{

   int *rp = return_pointer();

   printf("%d\n", *rp);

   return 0;

}

在return_pointer函数中创建了一个指针p指向了函数内的变量i (在函数内创建的变量叫做局部变量),并且将这个指针作为返回值。在主函数中有一个指针接收return_pointer的返回值,然后对其解引用并输出。此时的输出可能是3,也可能是0,也可能是其他值。本质原因就在于我们返回了一个指向局部变量的指针,这个局部变量在函数结束后会被编译器销毁,销毁的时间由编译器来决定,这样的话p就有可能指向不保存任何对象的内存,也可能这段内存中是一个随机值,总之,这块内存是不确定的,p返回的是一个无效的地址。

C.void *型指针(在比较多的源码中出现此类型的数据,重点知识)

ANSI C定义了一种void *型指针,表示定义一个指针,但不指定它指向何种类型的数据。void *型指针作为一种通用的指针,可以和其它任何类型的指针(函数指针除外)相互转化而不需要类型强制转换,但不能对它进行解引用及下标操作。C语言中的malloc函数的返回值就是一个void *型指针,我们可以把它直接赋给一个其他类型的指针,但从安全的编程风格角度以及兼容性上讲,最好还是将返回的指针强制转换为所需的类型,另外,malloc在无法满足请求时会通过返回一个空指针来作为“内存分配失败”的信号,所以要注意返回值指针的判空

7.      指针的指针

指针是一种变量,它也有自己的地址,所以它本身也是可用指针指向的对象。我们可以将指针的地址存放在另一个指针中,如:

int i = 5000;

int *pi = &i;

int **ppi = &pi;

此时的ppi即是一个指向指针的指针,下图表示了这些对象:

                          

i的地址为108,pi的内容就是i的地址,而pi的地址为104,ppi的内容即是pi的地址。对ppi解引用照常会得到ppi所指的对象,所获得的对象是指向int型变量的指针pi。想要真正地访问到i.,必须对ppi进行两次解引用,如下面代码所示:

printf("%d", i );

printf("%d", *pi );

printf("%d", **ppi );

以上三条语句的输出均为5000。


8.      指针与结构(缺省)

1 0