指针

一,基础

1,判断 指针的类型

:只需把指针声明语句中的指针变量名去掉,剩下的就是指针的的类型

   eg: 

       int *a  的指针类型是 Int*

      int **a 的指针类型是int** 

      int (*a) [3] 的指针类型是 int  (*) [3]

2,判断 指针所指向的类型

:只需把指针声明语句中的指针变量名和变量名左边的指针声明符 * 去掉,剩下的就是指针指向的类型

   eg:

        int *a 的指针所指向的类型是 int 

        int **a 的指针所指向的类型是Int*

        int (*a)[3] 的指针所指向的类型是 int () [3]

3,指针的值【指针所指向的内存区/地址】

   指针的值是指指针本身存储的数值,这个值将被编译器当做一个地址,而不是一个一般的数值。

   指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof【指针所指向的类型】的一片内存区。

4,指针本身所占的内存区

   指针本身占多大内存?只需用sizeof()就可判断。在32位 指针本身占据4个字节的长度

    指针本身占据的内存在判断一个【指针表达式】是否是左值时很有用。

 

二,指针的算术运算

    指针可以加上或减去一个整数。指针的这种运算意义和通常的数值的加减的意义不一样,以单元为单位。

            eg:

char a[20];
int *b=(int *)a;//强制类型转换并不会改变a的类型
b++
/**
 b的指针类型是int*,它指向的类型是int,它被初始化为指向整型变量a。
 第三行b被加了1
             编译器是这样处理的:它把b的值加上了sizeof(int),在32位程序中,是被加上了4,因为在32位程序中,int占4个字节。

由于地址是用字节做单位的,所以b所指向的地址,由原来的a的地址向高地址方向增加的4个字节。
由于char类型的长度是一个字节,所以b是指向数组a的第0号单元,开始的4个字节,此时,指向了数据a中从第4号单元开始的4个字节。
*/
char a[20]="You_are_a_girl";
int *b = (int *a);
b+=5

/**

b被加上了5,编译器处理:
      将指针b的值加上5乘sizeof(int),在32位中就是加上5乘以4 =20;
由于地址是用字节作单位,所以b指向的地址比起加5后的b所指向的地址来说,向高地址方向移动了20个字节。
如果减去5,b的值是被减去5乘sizeof(int),新的b指向的地址,将比原来的b指向的地址所指向的地址向低地址方向移动20个字节。
*/

 结论:一个指针b加/减一个整数n后,结果是一个新的指针b`,b`和b的类型相同,他们指向的类型也相同。b`的值将比b的值增加或者减少n乘以sizeof(b所指向的类型)个字节。即向高/低地址移动。

两个指针不行进行相加操作;但两个指针可以进行减法操作,但类型必须相同,一般用在数组方面。

 三、运算符&和*

   &  取地址运算符     *   间接运算符

  &a 的运算结果是一个指针,指针的类型是a的类型是加个* ,指针所指向的类型是a的类型。指针所指向的地址是a的地址。

  *p 的结果是指向p所指向的东西,它的特点是:它的类型是p指向的类型,它所占用的地址是p所指向的地址。

eg:

/*
p = &a;   //&a的结果是一个指针,类型是int*,指向的类型是int,指向的地址是a的地址。
*p = 24; //*p的结果的类型是int ,它所占用的地址是p所指向的地址,即*p是变量a。*/

四、指针表达式

            一个表达式的结果如果是一个指针,那么这个表达式就是指针表达式。

一些指针表达式例子:
int a,b;
int  array[10];
int *pa;
pa = &a; //&a 是一个指针表达式  &a不是一个左值,因为它没有占据明确的内存
Int **ptr=&pa;  //&pa是一个指针表达式
*ptr = &b; //*ptr和&b都是指针表达式  *ptr是一个左值,因为*ptr这个指针已经占据了内存,其实*ptr就是指针pa,既然pa在存在中已经有了自己的位置,那么*ptr当然也就有了自己的位置。
pa = array;
pa++; //是指针表达式

----------------------------------------
char *arr[20];
char **parr = arr;  //如果把arr看做指针的话,arr也是指针表达式。
cahr *str;
str = *parr; //*parr 是指针表达式
str = *(parr+1);  //*(parr+1)是指针表达式

    由于指针表达式的结果是一个指针,所说义表达式也具有指针具有的四个要素:指针类型,指针指向的类型,指针指向的内存区,指针自身占据的内存。

    当一个指针表达式的结果指针已经明确的具有了指针自身的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。

 

五、数组和指针的关系

    数组的数组名其实可以看做一个指针。

intarray[10] = {0,1,2,3,4,5,6,7,8,9},value;
value=array[0];   //也可以写成 : value=*array;
value=array[3];  //也可以写成: value=*(array+3);

/*
    一般而言,数组名array代表数组本身,类型是int,但如果把array看成指针的话,它指向数组的第0个单元,类型是int* ,所指向的类型是数组单元的类型即int。
*/



char *str[3] = {"G,xxxx","baasdfasdsd","caasdfasdsd"};
chars[80];
strcpy(s,str[0]); //可以写成strcpy(s,*str)
strcpy(s,str[1]); //可以写成strcpy(s,*(str+1))
strcpy(s,str[2]); //可以写成strcpy(s,*(str+2))

/*
    str是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。
   把指针数组名str当作一个指针的话,它指向数组的第0号单元,它的类型是char**,指向类型是char*。
   *str也是一个指针,它的类型是char*,它所指向的类型是char,它指向的地址是字符串"G,xxxx"第一个字符的地址。即‘G’的地址。
    注意:字符串相当于是一个数组,在内存中是以数组的形式存储,只不过字符串是一个数组常量,内容不可以改变,且只能是右值,如果看成指针的话,它是常量指针,也是指针常量。
*/

 总结数组中数组名(数组中存储的也是数组)的问题:

     声明一个数组:TYPE   array[n];则数组名称有两重含义:

         1,它代表整个数组,它的类型是TYPE[n],

          2, 它是一个常量指针,该指针的类型是TYPE*,该指针指向的类型是TYPE,也就是数组单元的类型。该指针指向的内存区就是数组第0号单元,该指针有自己单独的内存区,它和数组第0号占据的

        内存是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的。

 

在不同的表达式中,数组名array可以扮演不同的角色。

     sizeof(array)中,数组名代表数组本身,此时,sizeof测出的是整个数组的大小。

     *array中,array是指针,因此表达式的结果就是数组第0号单元的值。sizeof(*array)测出的数组单元的大小。

       表达式array+n,的结果是一个指针,其中n=1,2...,array扮演的是一个指针,它的类型是TYPE *,指向类型是TYPE ,它指向数组第n号单元,故sizeof(array+n)测出的指针类型大小,在32为程序中是4。

 

六、指针和结构类型的关系

       可以声明一个指向结构类型对象的指针。

struct  MyStudent
{
   int  a;
   int  b;
   int  c;
}
struct MyStudent ss={20,30,40};
//声明一个指向结构对象ss的指针,他的类型是MyStudent*,指向类型是MyStudent。
struct MyStudent  *ptr=&ss;
//声明一个指向结构对象ss的指针。但是pstr和它被指向的类型ptr是不同的。
int  *pstr=(int*)&ss;

 

七、指针和函数的关系

      可以把一个指针声明成一个指向函数的指针。

    

int fun1(char *,int);
int (*pfun1)(char * ,int);
pfun1=fun1;
int a = (*pfun1)("asdfa",7);//通过函数指针调用函数
//可以把指针作为函数的形参。在调用语句中,可以用指针表达式来作为实参。



int fun(char *);
int a;
char str[] = "abcasdf";
a=fun(str);
int fun(char *s) 
{
    int num=0;
    for(int i=0;;){
        num +=*s;
        s++;
    }
 retrun num;
}

/*
   这个例子中,fun统计各个字符串的ASCII码值之和。数组的名字意思是一个指针。
在函数调用中,当把str作为实参传递给形参s后,实际是把str的值传给了s,s所指向的地址就和str指向的地址一致,
但是str 和s各自占用各自非存储空间。在函数体内对s进行自加1运算,并不意味着同时对str进行自加1运算。
*/

 

八、指针类型转换

     当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式。绝大多数情况下,指针的类型和指针表达式是一样的,指针指向的类型和指针表达式指向的类型是一样的。

float   f=1.23;
float   *fptr = &f;
int *p;

/*
若让指针p指向实数f:
    p=&f;  //错误 p的类型是int * ,它指向的类型是int。&f的类型是float *,指向float。
    p=(int*)&f;  //正确
*/

 

九、指针的安全问题

 

char s='a'; //32位下占一个字节
int *ptr;   //32位下占4个字节
ptr=(int*)&s;
*ptr=1298;  //这里改变了s所占的一个字节,还把和s相邻的高地址方向的三个字节也改变了。这三个字节是什么?只有编译器知道。

 

----来自《让你不再害怕指针》

posted @ 2019-04-08 14:04  木子李lee  阅读(177)  评论(0编辑  收藏  举报