指针_指针 c++ - CSDN
指针 订阅
指针,是C语言中的一个重要概念及其特点,也是掌握C语言比较困难的部分。指针也就是内存地址,指针变量是用来存放内存地址的变量,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。 [1] 展开全文
指针,是C语言中的一个重要概念及其特点,也是掌握C语言比较困难的部分。指针也就是内存地址,指针变量是用来存放内存地址的变量,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅可以对数据本身,也可以对存储数据的变量地址进行操作。 [1]
信息
适用对象
高级语言 [2]
特    点
可对存储数据的变量地址进行操作 [1]
定    义
内存地址 [1]
中文名
指针
性    质
不同类型的指针变量所占用的存储单元长度是相同的 [1]
外文名
pointer
指针基本问题
在计算机中, 所有的数据都是存放在存储器中的, 不同的数据类型占有的内存空间的大小各不相同。内存是以字节为单位的连续编址空间, 每一个字节单元对应着一个唯一的编号, 这个编号被称为内存单元的地址。比如: int类型占/4个字节, char类型占1个字节等。内存为变量分配存储空间的首个字节单元的地址, 称之为该变量的地址。地址用来标识每一个存储单元, 方便用户对存储单元中的数据进行正确的访问。在高级语言中地址形象地称为指针。 [2]  指针相对于一个内存单元来说,指的是单元的地址,该单元的内容里面存放的是数据。在C语言中,允许用指针变量来存放指针,因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。 [1]  指针变量是存放一个变量地址的变量,不同于其他类型变量,它是专门用来存放内存地址的,也称为地址变量。定义指针变量的一般形式为:类型说明符*变量名。 [1]  类型说明符表示指针变量所指向变量的数据类型;*表示这是一个指针变量;变量名表示定义的指针变量名,其值是一个地址,例如:char*p1;表示p1是一个指针变量,它的值是某个字符变量的地址。 [1] 
收起全文
精华内容
参与话题
  • 要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,...

    因排版过于粗糙

    学习详情转至 : https://blog.csdn.net/qq_41035588/article/details/97010039

     前言:复杂类型说明

        要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,所以我总结了一下其原则:从变量名处起,根据运算符优先级结合,一步一步分析.下面让我们先从简单的类型开始慢慢分析吧:

     

     

    1. int p; //这是一个普通的整型变量  
    2. int *p; //首先从P 处开始,先与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型.所以P是一个返回整型数据的指针  
    3. int p[3]; //首先从P 处开始,先与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组  
    4. int *p[3]; //首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组  
    5. int (*p)[3]; //首先从P 处开始,先与*结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针  
    6. int **p; //首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针.  
    7. int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据  
    8. Int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针  
    9. int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.  

    说到这里也就差不多了,我们的任务也就这么多,理解了这几个类型,其它的类型对我们来说也是小菜了,不过我们一般不会用太复杂的类型,那样会大大减小程序的可读性,请慎用,这上面的几种类型已经足够我们用了.

     

     

    一、细说指针

    指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。让我们分别说明。

    先声明几个指针放着做例子:
    例一:

     

     

     

     

    1. (1)int*ptr;  
    2. (2)char*ptr;  
    3. (3)int**ptr;  
    4. (4)int(*ptr)[3];  
    5. (5)int*(*ptr)[4];  

     

    1.指针的类型

    从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型:
    (1)int*ptr;//指针的类型是int*
    (2)char*ptr;//指针的类型是char*
    (3)int**ptr;//指针的类型是int**
    (4)int(*ptr)[3];//指针的类型是int(*)[3]
    (5)int*(*ptr)[4];//指针的类型是int*(*)[4]
    怎么样?找出指针的类型的方法是不是很简单?

     

     

     

    2.指针所指向的类型

    当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
    从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:
    (1)int*ptr; //指针所指向的类型是int
    (2)char*ptr; //指针所指向的的类型是char
    (3)int**ptr; //指针所指向的的类型是int*
    (4)int(*ptr)[3]; //指针所指向的的类型是int()[3]
    (5)int*(*ptr)[4]; //指针所指向的的类型是int*()[4]

    在指针的算术运算中,指针所指向的类型有很大的作用。
    指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C 越来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前后矛盾,越看越糊涂。

     

     

     

    3.指针的值----或者叫指针所指向的内存区或地址

    指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32 位程序里,所有类型的指针的值都是一个32 位整数,因为32 位程序里内存地址全都是32 位长。指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX 为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
    以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?(重点注意)

     

     

     

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

    指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32 位平台里,指针本身占据了4 个字节的长度。指针本身占据的内存这个概念在判断一个指针表达式(后面会解释)是否是左值时很有用。

     

    二、指针的算术运算

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

     

     

    1. char a[20];  
    2. int *ptr=(int *)a; //强制类型转换并不会改变a 的类型  
    3. ptr++;  

    在上例中,指针ptr 的类型是int*,它指向的类型是int,它被初始化为指向整型变量a。接下来的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr 的值加上了sizeof(int),在32 位程序中,是被加上了4,因为在32 位程序中,int 占4 个字节。由于地址是用字节做单位的,故ptr 所指向的地址由原来的变量a 的地址向高地址方向增加了4 个字节。由于char 类型的长度是一个字节,所以,原来ptr 是指向数组a 的第0 号单元开始的四个字节,此时指向了数组a 中从第4 号单元开始的四个字节。我们可以用一个指针和一个循环来遍历一个数组,看例子:
    例三:

     

     

    1. int array[20]={0};  
    2. int *ptr=array;  
    3. for(i=0;i<20;i++)  
    4. {  
    5.     (*ptr)++;  
    6.     ptr++;  
    7. }  

    这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1 个单元,所以每次循环都能访问数组的下一个单元。

     

    再看例子:
    例四:

     

     

    1. char a[20]="You_are_a_girl";  
    2. int *ptr=(int *)a;  
    3. ptr+=5;  

    在这个例子中,ptr 被加上了5,编译器是这样处理的:将指针ptr 的值加上5 乘sizeof(int),在32 位程序中就是加上了5 乘4=20。由于地址的单位是字节,故现在的ptr 所指向的地址比起加5 后的ptr 所指向的地址来说,向高地址方向移动了20 个字节。
    在这个例子中,没加5 前的ptr 指向数组a 的第0 号单元开始的四个字节,加5 后,ptr 已经指向了数组a 的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现出了指针的灵活性。如果上例中,ptr 是被减去5,那么处理过程大同小异,只不过ptr 的值是被减去5 乘sizeof(int),新的ptr 指向的地址将比原来的ptr 所指向的地址向低地址方向移动了20 个字节。
    下面请允许我再举一个例子:(一个误区)

    例五:

     

     

    1. #include<stdio.h>  
    2. int main()  
    3. {  
    4.     char a[20]=" You_are_a_girl";  
    5.     char *p=a;  
    6.     char **ptr=&p;  
    7.     //printf("p=%d\n",p);  
    8.     //printf("ptr=%d\n",ptr);  
    9.     //printf("*ptr=%d\n",*ptr);  
    10.     printf("**ptr=%c\n",**ptr);  
    11.     ptr++;  
    12.     //printf("ptr=%d\n",ptr);  
    13.     //printf("*ptr=%d\n",*ptr);  
    14.     printf("**ptr=%c\n",**ptr);  
    15. }  

    误区一、输出答案为Y 和o
    误解:ptr 是一个char 的二级指针,当执行ptr++;时,会使指针加一个sizeof(char),所以输出如上结果,这个可能只是少部分人的结果.
    误区二、输出答案为Y 和a误解:ptr 指向的是一个char *类型,当执行ptr++;时,会使指针加一个sizeof(char *)(有可能会有人认为这个值为1,那就会得到误区一的答案,这个值应该是4,参考前面内容), 即&p+4; 那进行一次取值运算不就指向数组中的第五个元素了吗?那输出的结果不就是数组中第五个元素了吗?答案是否定的.
    正解: ptr 的类型是char **,指向的类型是一个char *类型,该指向的地址就是p的地址(&p),当执行ptr++;时,会使指针加一个sizeof(char*),即&p+4;那*(&p+4)指向哪呢,这个你去问上帝吧,或者他会告诉你在哪?所以最后的输出会是一个随机的值,或许是一个非法操作.
    总结一下:
    一个指针ptrold 加(减)一个整数n 后,结果是一个新的指针ptrnew,ptrnew 的类型和ptrold 的类型相同,ptrnew 所指向的类型和ptrold所指向的类型也相同。ptrnew 的值将比ptrold 的值增加(减少)了n 乘sizeof(ptrold 所指向的类型)个字节。就是说,ptrnew 所指向的内存区将比ptrold 所指向的内存区向高(低)地址方向移动了n 乘sizeof(ptrold 所指向的类型)个字节。指针和指针进行加减:两个指针不能进行加法运算,这是非法操作,因为进行加法后,得到的结果指向一个不知所向的地方,而且毫无意义。两个指针可以进行减法操作,但必须类型相同,一般用在数组方面,不多说了。

     

     

     

    三、运算符&和*

    这里&是取地址运算符,*是间接运算符。
    &a 的运算结果是一个指针,指针的类型是a 的类型加个*,指针所指向的类型是a 的类型,指针所指向的地址嘛,那就是a 的地址。
    *p 的运算结果就五花八门了。总之*p 的结果是p 所指向的东西,这个东西有这些特点:它的类型是p 指向的类型,它所占用的地址是p所指向的地址。
    例六:

     

     

    1. int a=12; int b; int *p; int **ptr;  
    2. p=&a; //&a 的结果是一个指针,类型是int*,指向的类型是  
    3. //int,指向的地址是a 的地址。  
    4. *p=24; //*p 的结果,在这里它的类型是int,它所占用的地址是  
    5. //p 所指向的地址,显然,*p 就是变量a。  
    6. ptr=&p; //&p 的结果是个指针,该指针的类型是p 的类型加个*,  
    7. //在这里是int **。该指针所指向的类型是p 的类型,这  
    8. //里是int*。该指针所指向的地址就是指针p 自己的地址。  
    9. *ptr=&b; //*ptr 是个指针,&b 的结果也是个指针,且这两个指针  
    10. //的类型和所指向的类型是一样的,所以用&b 来给*ptr 赋  
    11. //值就是毫无问题的了。  
    12. **ptr=34; //*ptr 的结果是ptr 所指向的东西,在这里是一个指针,  
    13. //对这个指针再做一次*运算,结果是一个int 类型的变量。  

     

    四、指针表达式

    一个表达式的结果如果是一个指针,那么这个表达式就叫指针表式。
    下面是一些指针表达式的例子:
    例七:

     

     

    1. int a,b;  
    2. int array[10];  
    3. int *pa;  
    4. pa=&a; //&a 是一个指针表达式。  
    5. Int **ptr=&pa; //&pa 也是一个指针表达式。  
    6. *ptr=&b; //*ptr 和&b 都是指针表达式。  
    7. pa=array;  
    8. pa++; //这也是指针表达式。  


    例八:

     

     

    1. char *arr[20];  
    2. char **parr=arr; //如果把arr 看作指针的话,arr 也是指针表达式  
    3. char *str;  
    4. str=*parr; //*parr 是指针表达式  
    5. str=*(parr+1); //*(parr+1)是指针表达式  
    6. str=*(parr+2); //*(parr+2)是指针表达式  

    由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。
    好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。在例七中,&a 不是一个左值,因为它还没有占据明确的内存。*ptr 是一个左值,因为*ptr 这个指针已经占据了内存,其实*ptr 就是指针pa,既然pa 已经在内存中有了自己的位置,那么*ptr 当然也有了自己的位置。

     

     

     

    五、数组和指针的关系

    数组的数组名其实可以看作一个指针。看下例:
    例九:

     

     

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

    上例中,一般而言数组名array 代表数组本身,类型是int[10],但如果把array 看做指针的话,它指向数组的第0 个单元,类型是int* 所指向的类型是数组单元的类型即int。因此*array 等于0 就一点也不奇怪了。同理,array+3 是一个指向数组第3 个单元的指针,所以*(array+3)等于3。其它依此类推。
    例十:

     

     

    1. char *str[3]={  
    2.     "Hello,thisisasample!",  
    3.     "Hi,goodmorning.",  
    4.     "Helloworld"  
    5. };  
    6. char s[80];  
    7. strcpy(s,str[0]); //也可写成strcpy(s,*str);  
    8. strcpy(s,str[1]); //也可写成strcpy(s,*(str+1));  
    9. strcpy(s,str[2]); //也可写成strcpy(s,*(str+2));  

    上例中,str 是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指针数组名str 当作一个指针的话,它指向数组的第0 号单元,它的类型是char **,它指向的类型是char *。
    *str 也是一个指针,它的类型是char *,它所指向的类型是char,它指向的地址是字符串"Hello,thisisasample!"的第一个字符的地址,即'H'的地址。注意:字符串相当于是一个数组,在内存中以数组的形式储存,只不过字符串是一个数组常量,内容不可改变,且只能是右值.如果看成指针的话,他即是常量指针,也是指针常量.
    str+1 也是一个指针,它指向数组的第1 号单元,它的类型是char**,它指向的类型是char*。
    *(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向"Hi,goodmorning."的第一个字符'H'

    下面总结一下数组的数组名(数组中储存的也是数组)的问题:
    声明了一个数组TYPE array[n],则数组名称array 就有了两重含义:
    第一,它代表整个数组,它的类型是TYPE[n];
    第二,它是一个常量指针,该指针的类型是TYPE*,该指针指向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0 号单元,该指针自己占有单独的内存区,注意它和数组第0 号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的。在不同的表达式中数组名array 可以扮演不同的角色。在表达式sizeof(array)中,数组名array 代表数组本身,故这时sizeof 函数测出的是整个数组的大小。
    在表达式*array 中,array 扮演的是指针,因此这个表达式的结果就是数组第0 号单元的值。sizeof(*array)测出的是数组单元的大小。
    表达式array+n(其中n=0,1,2,.....)中,array 扮演的是指针,故array+n 的结果是一个指针,它的类型是TYPE *,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。在32 位程序中结果是4
    例十一:

     

     

    1. int array[10];  
    2. int (*ptr)[10];  
    3. ptr=&array;:  

    上例中ptr 是一个指针,它的类型是int(*)[10],他指向的类型是int[10] ,我们用整个数组的首地址来初始化它。在语句ptr=&array中,array 代表数组本身。
    本节中提到了函数sizeof(),那么我来问一问,sizeof(指针名称)测出的究竟是指针自身类型的大小呢还是指针所指向的类型的大小?
    答案是前者。例如:
    int(*ptr)[10];
    则在32 位程序中,有:
    sizeof(int(*)[10])==4
    sizeof(int[10])==40
    sizeof(ptr)==4
    实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。

     

     

     

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

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

     

     

    1. struct MyStruct  
    2. {  
    3.     int a;  
    4.     int b;  
    5.     int c;  
    6. };  
    7. struct MyStruct ss={20,30,40};  
    8. //声明了结构对象ss,并把ss 的成员初始化为20,30 和40。  
    9. struct MyStruct *ptr=&ss;  
    10. //声明了一个指向结构对象ss 的指针。它的类型是  
    11. //MyStruct *,它指向的类型是MyStruct。  
    12. int *pstr=(int*)&ss;  
    13. //声明了一个指向结构对象ss 的指针。但是pstr 和  
    14. //它被指向的类型ptr 是不同的。  

    请问怎样通过指针ptr 来访问ss 的三个成员变量?
    答案:

    ptr->a; //指向运算符,或者可以这们(*ptr).a,建议使用前者
    ptr->b;

     

    ptr->c;

     

     

    又请问怎样通过指针pstr 来访问ss 的三个成员变量?
    答案:

    *pstr; //访问了ss 的成员a。
    *(pstr+1); //访问了ss 的成员b。
    *(pstr+2) //访问了ss 的成员c。

     

    虽然我在我的MSVC++6.0 上调式过上述代码,但是要知道,这样使用pstr 来访问结构成员是不正规的,为了说明为什么不正规,让我们看看怎样通过指针来访问数组的各个单元: (将结构体换成数组)

     

     

    例十三:

     

     

    1. int array[3]={35,56,37};  
    2. int *pa=array;  
    3. //通过指针pa 访问数组array 的三个单元的方法是:  
    4. *pa; //访问了第0 号单元  
    5. *(pa+1); //访问了第1 号单元  
    6. *(pa+2); //访问了第2 号单元  

    从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。
    所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干个"填充字节",这就导致各个成员之间可能会有若干个字节的空隙。
    所以,在例十二中,即使*pstr 访问到了结构对象ss 的第一个成员变量a,也不能保证*(pstr+1)就一定能访问到结构成员b。因为成员a 和成员b 之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字节,嘿,这倒是个不错的方法。
    不过指针访问结构成员的正确方法应该是象例十二中使用指针ptr 的方法。

     

     

     

    七、指针和函数的关系

    可以把一个指针声明成为一个指向函数的指针。
    int fun1(char *,int);
    int (*pfun1)(char *,int);
    pfun1=fun1;
    int a=(*pfun1)("abcdefg",7); //通过函数指针调用函数。
    可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参。
    例十四:

     

     

    1. int fun(char *);  
    2. inta;  
    3. char str[]="abcdefghijklmn";  
    4. a=fun(str);  
    5. int fun(char *s)  
    6. {  
    7.     int num=0;  
    8.     for(int i=0;;)  
    9.     {  
    10.         num+=*s;s++;  
    11.     }  
    12.     return num;  
    13. }  

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

     

     

     

    八、指针类型转换

    当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式。在我们前面所举的例子中,绝大多数情况下,指针的类型和指针表达式的类型是一样的,指针所指向的类型和指针表达式所指向的类型是一样的。
    例十五:

     

     

    1. float f=12.3;  
    2. float *fptr=&f;  
    3. int *p;  

    在上面的例子中,假如我们想让指针p 指向实数f,应该怎么办?
    是用下面的语句吗?
    p=&f;
    不对。因为指针p 的类型是int *,它指向的类型是int。表达式&f 的结果是一个指针,指针的类型是float *,它指向的类型是float。
    两者不一致,直接赋值的方法是不行的。至少在我的MSVC++6.0 上,对指针的赋值语句要求赋值号两边的类型一致,所指向的类型也一致,其它的编译器上我没试过,大家可以试试。为了实现我们的目的,需要进行"强制类型转换":
    p=(int*)&f;
    如果有一个指针p,我们需要把它的类型和所指向的类型改为TYEP *TYPE, 那么语法格式是: (TYPE *)p;
    这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE *,它指向的类型是TYPE,它指向的地址就是原指针指向的地址。
    而原来的指针p 的一切属性都没有被修改。(切记)
    一个函数如果使用了指针作为形参,那么在函数调用语句的实参和形参的结合过程中,必须保证类型一致,否则需要强制转换
    例十六:

     

     

    1. void fun(char*);  
    2. int a=125,b;  
    3. fun((char*)&a);  
    4. void fun(char*s)  
    5. {  
    6.     charc;  
    7.     c=*(s+3);*(s+3)=*(s+0);*(s+0)=c;  
    8.     c=*(s+2);*(s+2)=*(s+1);*(s+1)=c;  
    9. }  

    注意这是一个32 位程序,故int 类型占了四个字节,char 类型占一个字节。函数fun 的作用是把一个整数的四个字节的顺序来个颠倒。注意到了吗?在函数调用语句中,实参&a 的结果是一个指针,它的类型是int *,它指向的类型是int。形参这个指针的类型是char *,它指向的类型是char。这样,在实参和形参的结合过程中,我们必须进行一次从int *类型到char *类型的转换。
    结合这个例子,我们可以这样来
    想象编译器进行转换的过程:编译器先构造一个临时指针char *temp,然后执行temp=(char *)&a,最后再把temp 的值传递给s。所以最后的结果是:s 的类型是char *,它指向的类型是char,它指向的地址就是a 的首地址。
    我们已经知道,指针的值就是指针指向的地址,在32 位程序中,指针的值其实是一个32 位整数。
    那可不可以把一个整数当作指针的值直接赋给指针呢?就象下面的语句:

     

     

    1. unsigned int a;  
    2. TYPE *ptr; //TYPE 是int,char 或结构类型等等类型。  
    3. a=20345686;  
    4. ptr=20345686; //我们的目的是要使指针ptr 指向地址20345686  
    5.   
    6. ptr=a; //我们的目的是要使指针ptr 指向地址20345686  
    7. //编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗?不,还有办法:  
    8. unsigned int a;  
    9. TYPE *ptr; //TYPE 是int,char 或结构类型等等类型。  
    10. a=N //N 必须代表一个合法的地址;  
    11. ptr=(TYPE*)a; //呵呵,这就可以了。  


    严格说来这里的(TYPE *)和指针类型转换中的(TYPE *)还不一样。这里的(TYPE*)的意思是把无符号整数a 的值当作一个地址来看待。上面强调了a 的值必须代表一个合法的地址,否则的话,在你使用ptr 的时候,就会出现非法操作错误。想想能不能反过来,把指针指向的地址即指针的值当作一个整数取出来。完全可以。下面的例子演示了把一个指针的值当作一个整数取出来,然后再把这个整数当作一个地址赋给一个指针:
    例十七:

     

     

    1. int a=123,b;  
    2. int *ptr=&a;  
    3. char *str;  
    4. b=(int)ptr; //把指针ptr 的值当作一个整数取出来。  
    5. str=(char*)b; //把这个整数的值当作一个地址赋给指针str。  

    现在我们已经知道了,可以把指针的值当作一个整数取出来,也可以把一个整数值当作地址赋给一个指针。

     

     

     

    九、指针的安全问题

    看下面的例子:
    例十八:

     

     

    1. char s='a';  
    2. int *ptr;  
    3. ptr=(int *)&s;  
    4. *ptr=1298;  

    指针ptr 是一个int *类型的指针,它指向的类型是int。它指向的地址就是s 的首地址。在32 位程序中,s 占一个字节,int 类型占四个字节。最后一条语句不但改变了s 所占的一个字节,还把和s 相临的高地址方向的三个字节也改变了。这三个字节是干什么的?只有编译程序知道,而写程序的人是不太可能知道的。也许这三个字节里存储了非常重要的数据,也许这三个字节里正好是程序的一条代码,而由于你对指针的马虎应用,这三个字节的值被改变了!这会造成崩溃性的错误。
    让我们再来看一例:
    例十九:

     

     

    1. char a;  
    2. int *ptr=&a;  
    3. ptr++;  
    4. *ptr=115;  

    该例子完全可以通过编译,并能执行。但是看到没有?第3 句对指针ptr 进行自加1 运算后,ptr 指向了和整形变量a 相邻的高地址方向的一块存储区。这块存储区里是什么?我们不知道。有可能它是一个非常重要的数据,甚至可能是一条代码。
    而第4 句竟然往这片存储区里写入一个数据!这是严重的错误。所以在使用指针时,程序员心里必须非常清楚:我的指针究竟指向了哪里。在用指针访问数组的时候,也要注意不要超出数组的低端和高端界限,否则也会造成类似的错误。
    在指针的强制类型转换:ptr1=(TYPE *)ptr2 中,如果sizeof(ptr2的类型)大于sizeof(ptr1 的类型),那么在使用指针ptr1 来访问ptr2所指向的存储区时是安全的。如果sizeof(ptr2 的类型) 小于sizeof(ptr1 的类型),那么在使用指针ptr1 来访问ptr2 所指向的存储区时是不安全的。至于为什么,读者结合例十八来想一想,应该会明白的。

    展开全文
  • 深入理解C语言指针

    万次阅读 多人点赞 2019-10-09 11:28:28
    一、指针的概念 要知道指针的概念,要先了解变量在内存中如何存储的。在存储时,内存被分为一块一块的。每一块都有一个特有的编号。而这个编号可以暂时理解为指针,就像酒店的门牌号一样。 1.1、变量和地址 先写一段...

    一、指针的概念

    要知道指针的概念,要先了解变量在内存中如何存储的。在存储时,内存被分为一块一块的。每一块都有一个特有的编号。而这个编号可以暂时理解为指针,就像酒店的门牌号一样。

    1.1、变量和地址

    先写一段简单的代码:

    void main(){
    	int x = 10, int y = 20;
    }
    

    这段代码非常简单,就是两个变量的声明,分别赋值了 10、20。我们把内存当做一个酒店,而每个房间就是一块内存。那么“int x = 10;”和“int y = 20;”的实际含义如下:

    1. 去酒店订了两个房间,门牌号暂时用 px、py 表示
    2. 让 10 住进 px,让 20 住进 py
    3. 其中门牌号就是 px、py 就是变量的地址
    4. x 和 y 在这里可以理解为具体的房间,房间 x 的门牌号(地址)是 px,房间 y 的门牌号(地址)是 py。而 10 和 20,通过 px、py 两个门牌,找到房间,住进 x、y。用户(变量的值)和房间(变量)以及房间号(指针、地址)的关系

    1.2、指针变量和指针的类型

    指针变量就是一个变量,它存储的内容是一个指针。如果用前面的例子,可以理解为指针变量就是一张房卡,房卡存储了房间号的信息。

    在我们定义一个变量的时候,要确定它的类型。int x、char ch、float、、、在定义指针变量时也是一样的,必须确定指针类型。int 变量的指针需要用 int 类型的指针存储,float 变量的指针需要用 float 类型的指针存储。就像你只能用酒店 A 的房卡存储酒店 A 中房间号的信息一样。

    二、变量的指针与指针变量

    变量的指针就是变量的存储地址,指针变量就是存储指针的变量。

    2.1、指针变量的定义及使用

    (1)指针变量的定义

    指针变量的定义形式如:数据类型 *指针名;例如:

    //分别定义了 int、float、char 类型的指针变量
    int *x;
    float *f;
    char *ch;
    

    如上面的定义,指针变量名为 x、f、ch。并不是*x、*f、*ch

    (2)指针变量的使用

    • 取地址运算符&:单目运算符&是用来取操作对象的地址。例:&i 为取变量 i 的地址。对于常量表达式、寄存器变量不能取地址(因为它们存储在存储器中,没有地址)。
    • 指针运算符*(间接寻址符):与&为逆运算,作用是通过操作对象的地址,获取存储的内容。例:x = &i,x 为 i 的地址,*x 则为通过 i 的地址,获取 i 的内容。

    代码示例:

    //声明了一个普通变量 a
    int a;
    //声明一个指针变量,指向变量 a 的地址
    int *pa;
    //通过取地址符&,获取 a 的地址,赋值给指针变量
    pa = &a;
    //通过间接寻址符,获取指针指向的内容
    printf("%d", *pa);
    

    (3)“&”和“*”的结合方向

    “&”和“*”都是右结合的。假设有变量 x = 10,则*&x 的含义是,先获取变量 x 的地址,再获取地址中的内容。因为“&”和“*”互为逆运算,所以 x = *&x。

    接下来做个小练习,输入 x、y 两个整数,然后将其中的值大的赋值给 x,小的赋值给 y。即:假设输入 x = 8,y = 9。就将 9 赋值给 x,8 赋值给 y。

    void main(){
    	//声明两个普通变量
    	int x, y;
    	//声明两个指针变量
    	int *px, *py;
    	//声明一个临时变量,用于交换
    	int t;
    	//输入两个值,赋值给 x、y
    	scanf("%d", &x);
    	scanf("%d", &y);
    	//给指针变量 px、py 赋初值(关联变量 x、y)
    	px = &x;
    	py = &y;
    	//利用指针来对比 x、y 的值,如果 x 的值比 y 的值小,就交换
    	if(*px < *py){
    		//交换步骤,其中*px == x、*py == y
    		t = *px;
    		*px = *py;
    		*py = t;
    	}
    	printf("x =  %d, y = %d", *px, *py);
    }
    
    输入:23 45
    输出结果为:x = 45, y = 23
    

    2.2、指针变量的初始化

    指针变量与其它变量一样,在定义时可以赋值,即初始化。也可以赋值“NULL”或“0”,如果赋值“0”,此时的“0”含义并不是数字“0”,而是 NULL 的字符码值。

    //利用取地址获取 x 的地址,在指针变量 px 定义时,赋值给 px
    int x;
    int *px = &x;
    //定义指针变量,分别赋值“NULL”和“0”
    int *p1= NULL, *p2 = 0;
    

    2.3、指针运算

    (1)赋值运算

    指针变量可以互相赋值,也可以赋值某个变量的地址,或者赋值一个具体的地址

    int *px, *py, *pz, x = 10;
    //赋予某个变量的地址
    px = &x;
    //相互赋值
    py = px;
    //赋值具体的地址
    pz = 4000;
    

    (2)指针与整数的加减运算

    1. 指针变量的自增自减运算。指针加 1 或减 1 运算,表示指针向前或向后移动一个单元(不同类型的指针,单元长度不同)。这个在数组中非常常用。
    2. 指针变量加上或减去一个整形数。和第一条类似,具体加几就是向前移动几个单元,减几就是向后移动几个单元。
    //定义三个变量,假设它们地址为连续的,分别为 4000、4004、4008
    int x, y, z;
    
    //定义一个指针,指向 x
    int *px = &x;
    
    //利用指针变量 px 加减整数,分别输出 x、y、z
    printf("x = %d", *px);		//因为 px 指向 x,所以*px = x
    
    //px + 1,表示,向前移动一个单元(从 4000 到 4004)
    //这里要先(px + 1),再*(px + 1)获取内容,因为单目运算符“*”优先级高于双目运算符“+”
    printf("y = %d", *(px + 1));		
    printf("z = %d", *(px + 2));
    

    (3)关系运算

    假设有指针变量 px、py。

    1. px > py 表示 px 指向的存储地址是否大于 py 指向的地址
    2. px == py 表示 px 和 py 是否指向同一个存储单元
    3. px == 0 和 px != 0 表示 px 是否为空指针
    //定义一个数组,数组中相邻元素地址间隔一个单元
    int num[2] = {1, 3};
    
    //将数组中第一个元素地址和第二个元素的地址赋值给 px、py
    int *px = &num[0], *py = &num[1];
    int *pz = &num[0];
    int *pn;
    
    //则 py > px
    if(py > px){
    	printf("py 指向的存储地址大于 px 所指向的存储地址");
    }
    
    //pz 和 px 都指向 num[0]
    if(pz == px){
    	printf("px 和 pz 指向同一个地址");
    }
    
    //pn 没有初始化
    if(pn == NULL || pn == 0){
    	printf("pn 是一个空指针");
    }
    

    三、指针与数组

    之前我们可以通过下标访问数组元素,学习了指针之后,我们可以通过指针访问数组的元素。在数组中,数组名即为该数组的首地址,结合上面指针和整数的加减,我们就可以实现指针访问数组元素。

    3.1、指向数组的指针

    如以下语句:

    int nums[10], *p;
    

    上面语句定义了一个数组 nums,在定义时分配了 10 个连续的int 内存空间。而一个数组的首地址即为数组名nums,或者第一个元素的首地址也是数组的首地址。那么有两种方式让指针变量 p 指向数组 nums:

    //数组名即为数组的首地址
    p = nums;
    //数组第一个元素的地址也是数组的首地址
    p = &nums[0];
    

    上面两句是等价的。
    如下几个操作,用指针操作数组:

    1. *p = 1,此操作为赋值操作,即将指针指向的存储空间赋值为 1。此时 p 指向数组 nums 的第一个元素,则此操作将 nums 第一个元素赋值为 0,即 nums[0] = 1。
    2. p + 1,此操作为指针加整数操作,即向前移动一个单元。此时 p + 1 指向 nums[0]的下一个元素,即 nums[1]。通过p + 整数可以移动到想要操作的元素(此整数可以为负数)。
    3. 如上面,p(p + 0)指向 nums[0]、p + 1 指向 nums[1]、、、类推可得,p+i 指向 nums[i],由此可以准确操作指定位置的元素。
    4. 在 p + 整数的操作要考虑边界的问题,如一个数组长度为 2,p+3 的意义对于数组操作来说没有意义。

    下面写一段代码,用指针访问数组的元素:

    //定义一个整形数组,并初始化
    int nums[5] = {4, 5, 3, 2, 7};
    
    //定义一个指针变量 p,将数组 nums 的首地址赋值给 p,也可以用p = &nums[0]赋值
    int *p = nums, i;			//i 作为循环变量
    
    //p 指向数组第一个元素(数组首地址),我们可以直接用间接寻址符,获取第一个元素的内容
    printf("nums[0] = %d\n", *p);			//输出结果为 nums[0] = 4
    
    //我们可以通过“p + 整数”来移动指针,要先移动地址,所以 p + 1 要扩起来
    printf("nums[1] = %d\n", *(p + 1));		//输出结果为 nums[1] = 5
    
    //由上面推导出*(p + i) = nums[i],所以我们可以通过 for 循环变量元素
    for(i = 0; i < 5; i++){
    	printf("nums[%d] = %d", i, *(p + i));
    }
    

    注:数组名不等价于指针变量,指针变量可以进行 p++和&操作,而这些操作对于数组名是非法的。数组名在编译时是确定的,在程序运行期间算一个常量。

    3.2、字符指针与字符数组

    在 C 语言中本身没有提供字符串数据类型,但是可以通过字符数组和字符指针的方式存储字符串。

    (1)字符数组方式

    这个在前面应该学习过,这里就不赘述了。

    char word[] = "zack";
    printf("%s", word);
    

    (2)字符指针方式

    指针方式操作字符串和数组操作字符串类似,可以把定义的指针看做是字符数组的数组名。在内存中存储大致如下,这里为了方便换了个字符串:在这里插入图片描述

    //除了定义一个字符数组外,还可以直接定义一个字符指针存储字符串
    char *sentence = "Do not go gentle into that good night!";
    
    //此时可以做字符串的操作
    //输出
    printf("%s", sentence);
    
    //通过下标取字符
    printf("%c", sentence[0]);
    
    //获取字符串长度,其中 strlen 是 string.h 库中的方法
    printf("%d", strlen(sentence));
    

    注:字符指针方式区别于字符数组方式,字符数组不能通过数组名自增操作,但是字符指针是指针,可以自增操作。自增自减少会实现什么效果大家可以自己尝试运行一下

    下面做个小练习,利用字符指针将字符数组 sentence 中的内容复制到字符数组 word 中:

    //定义字符数组 sentence 和 word,给 sentence 赋初值
    char sentence[] = "Do not go gentle into that good night!", word[100];
    
    //定义字符指针,指向 word
    char *ch = word;
    int i;
    
    //循环赋值
    for(i = 0; sentence[i] != '\0'; i++){
    	*(ch + i) = sentence[i];
    }
    
    //在当 i 等于 sentence 的长度(sentence 的长度不包含'\0')时,
    //i 继续自增,此时判断 sentence[0] != '\0'不符合,跳出循环,则 i 比 sentence 长度大 1
    *(ch + i) = '\0';
    
    //输出字符串,因为 ch 指向 word,所以输出结果是一样的
    printf("ch = %s, word = %s", ch, word);
    

    注:指针变量必须初始化一个有效值才能使用

    3.3、多级指针及指针数组

    (1)多级指针

    指针变量作为一个变量也有自己的存储地址,而指向指针变量的存储地址就被称为指针的指针,即二级指针。依次叠加,就形成了多级指针。我们先看看二级指针,它们关系如下:指针变量 p 指向变量 x,二级指针变量指向指针变量 p
    其中 p 为一级指针,pp 为二级指针。二级指针定义形式如下:

    数据类型 **二级指针名;
    

    和指针变量的定义类似,由于*是右结合的,所以*pp 相当于*(*p)。在本次定义中,二级指针的变量名为 pp,而不是**p。多级指针的定义就是定义时使用多个“*”号。下面用一个小程序给大家举例:

    //定义普通变量和指针变量
    int *pi, i = 10;
    //定义二级指针变量
    int **ppi;
    
    //给指针变量赋初值
    pi = &i;
    
    //给二级指针变量赋初值
    ppi = &pi;
    
    //我们可以直接用二级指针做普通指针的操作
    //获取 i 的内容
    printf("i = %d", **ppi);
    //获取 i 的地址
    printf("i 的地址为%d", *ppi);
    

    注:在初始化二级指针 ppi 时,不能直接 ppi = &&i,因为&i 获取的是一个具体的数值,而具体数字是没有指针的。

    (2)指针数组

    指针变量和普通变量一样,也能组成数组,指针数组的具体定义如下:

    数据类型 *数组名[指针数组长度];
    

    下面举一个简单的例子熟悉指针数组:

    //定义一个数组
    int nums[5] = {2, 3, 4, 5, 2}, i;
    
    //定义一个指针数组
    int *p[5];
    
    //定义一个二级指针
    int **pp;
    
    //循环给指针数组赋值
    for(i = 0; i < 5; i++){
    	p[i] = &nums[i];
    }
    
    //将指针数组的首地址赋值给 pp,数组 p 的数组名作为 p 的首地址,也作为 p 中第一个元素的地址。
    //数组存放的内容为普通变量,则数组名为变量的指针;数组存放的内容为指针,则数组名为指针的指针。
    pp = p;
    
    //利用二级指针 pp 输出数组元素
    for(i = 0; i < 5; i++){
    	//pp == &p[0] == &&nums[0],nums[0] == *p[0] == **pp
    	printf("%d", **pp);
    	
    	//指针变量+整数的操作,即移动指针至下一个单元
    	pp++;
    }
    

    3.4、指针与多维数组

    讲多维数组是个麻烦的事,因为多维数组和二维数组没有本质的区别,但是复杂度倒是高了许多。这里我主要还是用二维数组来举例,但是还是会给大家分析多维数组和指针的关系。

    (1)多维数组的地址

    先用一个简单的数组来举例:

    int nums[2][2] = {
    	{1, 2},
    	{2, 3}
    };
    

    我们可以从两个维度来分析:

    1. 先是第一个维度,将数组当成一种数据类型 x,那么二维数组就可以当成一个元素为 x 的一维数组。
    2. 如上面的例子,将数组看成数据类型 x,那么 nums 就有两个元素。nums[0]和 nums[1]。
    3. 我们取 nums[0]分析。将 nums[0]看做一个整体,作为一个名称可以用 x1 替换。则 x1[0]就是 nums[0][0],其值为 1。
      在这里插入图片描述

    我们知道数组名即为数组首地址,上面的二维数组有两个维度。首先我们把按照上面 1 来理解,那么 nums 就是一个数组,则nums 就作为这个数组的首地址。第二个维度还是取 nums[0],我们把 nums[0]作为一个名称,其中有两个元素。我们可以尝试以下语句:

    printf("%d", nums[0]);
    

    此语句的输出结果为一个指针,在实验过后,发现就是 nums[0][0]的地址。即数组第一个元素的地址。

    如果再多一个维度,我们可以把二维数组看做一种数据类型 y,而三维数组就是一个变量为 y 的一维数组。而数组的地址我们要先确定是在哪个维度,再将数组某些维度看成一个整体,作为名称,此名称就是该维度的地址(这里有些绕)。

    例:

    //假设已初始化,二维数组数据类型设为 x,一维数组数据类型设为 y
    int nums[2][2][2];
    
    //此数组首地址为该数组名称
    printf("此数组首地址为%d", nums);
    
    //此数组可以看做存储了两个 x 类型元素的一维数组,则 nums[0] = x1 的地址为
    printf("第二个维度的首地址为%d", nums[0]);
    
    //而 x1 可以看做存储了两个 y 类型元素的一维数组,则 y1 = x1[0] = nums[0][0]
    printf("第三个维度的首地址为%d", nums[0][0]);
    
    

    三维数组实际存储形式如下:
    在这里插入图片描述
    实际存储内容的为最内层维度,且为连续的。对于 a 来说,其个跨度为 4 个单元;对 a[0]来说,其跨度为 2 个单元;对 a[0][0]来说,跨度为一个单元。有上面还可以得出:

    a == a[0] == a[0][0] == &a[0][0][0];
    

    上面的等式只是数值上相等,性质不同。

    (2)多维数组的指针

    在学习指针与数组的时候,我们可以如下表示一个数组:

    int nums[5] = {2, 4, 5, 6, 7};
    int *p = nums;
    

    在前面讲指针数组时,所有指针数组元素都指向一个数字,那么我们现在可以尝试用指针数组的每个元素指向一个数组:

    //定义一个二维数组
    int nums[2][2] = {
    	{1, 2},
    	{2, 3}
    };
    
    //此时 nums[0]、和 nums[1]各为一个数组
    int *p[2] = {nums[0], nums[1]};
    
    //我们可以用指针数组 p 操作一个二维数组
    
    //p 为数组 p 的首地址,p[0] = nums[0] = *p,**p = nums[0][0]
    printf("nums[0][0] = %d", **p);
    
    //指针 + 整数形式,p+1 移动到 nums 的地址,*(p +1) = nums[1],则**(p + 1) = nums[1][0]
    printf("nums[1][0] = %d", **(p + 1));
    
    //先*p = nums[0],再*p + 1 = &nums[0][1],最后获取内容*(*p + 1)即为 nums[0][1]
    printf("nums[0][1] = %d", *(*p + 1));
    

    这里可能不能理解为什么*p + 1 = &nums[0][1],而不是 nums[1]。*p 获得的是一个一维数组,而 int 数组 + 1 的跨度只有 4 个字节,也就是一个单元。前面 p 是一维数组的指针,其跨度为一个数组。所以*p + 1 = &nums[0][1],而 p + 1 = nums[1]。

    四、指针与函数

    前面学习函数学到,函数参数可以为 int、char、float 等,但是在操作时,这些参数只作为形参,所有操作都只在函数体内有效(除对指针的操作外),那么今天来学习一下指针作为函数参数。

    4.1、函数参数为指针

    我们直接做一个练习,定义一个函数,用来交换两个变量的内容。

    void swap(int *x, int *y);
    void main(){
    	int x = 20, y = 10;
    	swap(&x, &y);
    	printf("x = %d, y = %d", x ,y);
    }
    void swap(int *x, int *y){
    	int t;
    	t = *x;
    	*x = *y;
    	*y = t;
    }
    

    代码非常简单,我也就不细讲了。这里传入的参数为指针,所以调用 swap 方法后 x,y 的内容发生了交换。如果直接传入 x,y,那么交换只在 swap 中有效,在 main 中并没有交换。

    4.2、函数的返回值为指针

    返回值为指针的函数声明如下:

    数据类型 *函数名(参数列表){
    	函数体
    }
    //例如:
    int s;
    int *sum(int x, int y){
    	s = x + y;
    	return &s;
    }
    

    在函数调用前要声明需要对函数声明(有点编译器不需要)

    int s;
    void mian(){
    	int *r = sum(10, 9);
    	printf("10 + 9 + %d", *r);
    }
    int *sum(int x, int y){
    	s = x + y;
    	return &s;
    }
    

    除了上面的操作,更实用的是返回一个指向数组的指针,这样就实现了返回值为数组。

    4.3、指向函数的指针

    C 语言中,函数不能嵌套定义,也不能将函数作为参数传递。但是函数有个特性,即函数名为该函数的入口地址。我们可以定义一个指针指向该地址,将指针作为参数传递。

    函数指针定义如下:

    数据类型 (*函数指针名)();
    

    函数指针在进行“*”操作时,可以理解为执行该函数。函数指针不同与数据指针,不能进行+整数操作。

    下面举个例子,来使用函数指针:

    #include <string.h>
    /**
    *	定义一个方法,传入两个字符串和一个函数指针 p,用 p 对两个字符串进行操作
    */
    void check(char *x, char *y, int (*p)());
    void main(){
    	//string.h 库中的函数,使用之前需要声明该函数。字符串比较函数
    	int strcmp();
    	char x[] = "Zack";
    	char y[] = "Rudy";
    	
    	//定义一个函数指针
    	int (*p)() = strcmp;
    
    	check(x, y, p);
    }
    void check(char *x, char *y, int (*p)()){
    	if(!(*p)(x, y)){
    		printf("相等");
    	}else{
    		printf("不相等");
    	}
    }
    

    利用函数指针调用方法具体操作如下:

    (*p)(x, y);
    

    指针除了这些地方,还在结构体中用处巨大。今天就先讲到这里~·

    展开全文
  • 二级指针指针指针) 3.1 定义与初始化 3.2间接数据访问 3.2.1.改变一级指针指向 3.2.2改变 N-1 级指针的指向 3.2.3二级指针的步长 四. 指针与数组 4.1 指针与数组名 4.1.1 通过数组名访问数组元素 4.....

    目录

    一.变量的内存实质到

    1.1变量的实质

    1.2 赋值给变量

    1.3 变量在哪里?

    二. 指针是个什么东西?

     三. 二级指针(指针的指针)

    3.1 定义与初始化

    3.2 间接数据访问

    3.2.1 .改变一级指针指向

    3.2.2 改变 N-1 级指针的指向

    3.2.3 二级指针的步长

    四. 指针与数组

    4.1 指针与数组名

    4.1.1 通过数组名访问数组元素

    4.1.2 通过指针访问数组元素

    4.1.3 数组名与指针变量的区别

    4.2 指针数组( 字符指针数组 )

    4.2.1 定义

     4.2.2 代码实例

    4.3 二级指针与指针数组 

    4.3.1 .指针数组名赋给二级指针的合理性

    4.3.2 完美匹配的前提(小尾巴 NULL)


    在风起云涌的编程世界中,C/C++作为编程界的扛把子,以霸主地位而屹立不倒,究其原因,它有其他语言无法相媲美的“底牌”而存在,那就是——指针。指针被称为是C/C++中的精髓,也有人说由于指针的存在让C/C++变得更加难学,难懂,难消化。果真是这样吗?本篇文章让我们一起来揭开指针的真正面纱。

    一.变量的内存实质到

    1.1变量的实质

    要理解指针,首先就要理解“变量”的存储实质,如果你已经深入理解“变量”的存储实质,直接跳过本小节……

    首先我们来看一下内存空间图:

    如图所示,内存只不过是一个存放数据的空间,可以理解为装鸡蛋的篮子,装水果的箱子,或是装RMB的钱包,随便啦,反正就是这么一个很随意的玩意……现在我们把它想象成电影院的座位,电影院中的每个座位都要编号,而我们的内存要存放各种各样的数据,当然我们要知道我们的这些数据存放在什么位置吧!所以内存也要象座位一样进行编号了,这就是我们所说的内存编址(为内存进行地址编码)。座位可以是遵循“一个座位对应一个号码”的原则,从“第 1 号”开始编号。而内存则是按一个字节接着一个字节的次序进行编址,如上图所示。每个字节都有个编号,我们称之为内存地址

    内存编址:

    当我们在程序中写下了这样的语言声明:

    int i;
    char a;

    时,它其实是内存中申请了一个名为 i 的整型变量宽度空间(DOS 下的 16 位编程中其宽度为 2 个字节),和一个名为 a 的字符型变量宽度的空间(占 1 个字节)。

    内存中的映象如下图所示:

    图中可看出,i 在内存起始地址为 6 上申请了两个字节的空间(我这里假设
    了 int 的宽度为 16 位,不同系统中 int 的宽度可能是不一样的,最常用的win32环境下为4个字节),并命名为 i。
    a 在内存地址为 8 上申请了一字节的空间,并命名为 a。阿欧……这样我们就有两个不同类型的变量了。变量有了接下来我们考虑的就是如何给变量进行赋啦。

    1.2 赋值给变量

    再看下面赋值:
    i = 30;
    a = ’t’;
    你当然知道个两个语句是将 30 存入 i 变量的内存空间中,将“t”字符存入 a 变量的内存空间中。我们可以利用这样来形象理解:

    我们将30存在了以地址6(真正的地址可不是这样子哦,真正的地址如:0016FD14)为起始地址的两个字节空间里,a 在内存地址为 8 上申请了一字节的空间存入了‘t’,那么变量i和a在哪呢???

    1.3 变量在哪里?

    接下来我们来看看&i 是什么意思?是取 i 变量所在的地址编号嘛!我们可以这样大声读出来:返回 i 变量的地址编号。你记住了吗?如果没有,在读一遍:返回 i 变量的地址编号

    以一小段简单代码来弱弱看下:

    #include <iostream>
    #include <stdlib.h>
    
    using namespace std;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	int i = 30;
    
    	std::cout << "&i = "<< &i << std::endl;
    	std::cout << "i =  " << i << std::endl;
    
    	system("pause");
    	return 0;
    }

    输出结果为:

    输出的 &i 的值 0016FD14就是我们图示中内存空间编码为6的内存地址。接下来就进入我们真正的主题——指针。

    二. 指针是个什么东西?

    指针,想说弄懂你不容易啊!我常常在思索它,为什么呢?其实生活中处处都有指针,我们也处处在使用它。有了它
    我们的生活才更加方便了。没有指针,那生活才不方便。不信?你看下面的例子。

    比如有天你说你要学习C++,要借我的这本 C++ Primer Plus,我把书给你送过去发现你已经跑出去打篮球了,于是我把书放在了你桌子上书架的第三层四号的位置。并写了一张纸条:你要的书在第 三 层 四号的书架上。贴在你门上。当你回来时,看到这张纸条,你就知道了我借与你的书放在哪了。你想想看,这张纸条的作用,纸条本身不是书,它上面也没有放着书。那么你又如何知道书的位置呢?因为纸条上写着书的位置嘛!聪明!!!其实这张纸条就是一个指针了。它上面的内容不是书本身,而是
    书的地址,你通过纸条这个指针找到了我借给你的这本书。

    那么我们 C/C++中的指针又戴上了啥面具呢?让我们拭目以待。下面看一条声明一个指向整型变量的指针的语句:
    int *pi;

    pi 是一个指针,当然我们知道啦,但是这样说,你就以为 pi 一定是个多么特别的东西了。其实,它也只过是一个变量而已。与上一篇中说的变量并没有实质的区别。好了,这就是指针。仅此而已,就这么简单。不信你看下图:

    (说明:这里我假设了指针只占 2 个字节宽度,实际上在 32 位系统中,指针的宽度是 4 个字节宽的,即 32 位。)
    由图示中可以看出,我们使用“int *pi”声明指针变量 —— 其实是在内存的某处声明一个一定宽度的内存空间,并把它命名为 pi。你能在图中看出pi 与前面的 i、a 变量有什么本质区别吗?没有,当然没有!肯定没有!!真的没有!!!pi 也只不过是一个变量而已嘛!那么它又为什么会被称为“指针”?关键是我们要让这个变量所存储的内容是什么。现在我要让 pi 成为具有真正“指针”意义的变量。请接着看下面语句:
    pi = &i;
    你应该知道 &i 是什么意思吧!刚刚大声读的那句话,如果忘了,回头去在大声读,记住了!!!那这句代码怎么读呢?这样大声读:把 i 地址的编号赋值给 pi。并记下来。也就是你在 pi 里面写上 i 的地址编号。结果如下图所示:

    你看,执行完 pi=&i 后,在图示中的内存中,pi 的值是 6。这个 6 就是i 变量的地址编号,这样 pi 就指向了变量 i 了。你看,pi 与那张纸条有什么区别?pi 不就是那张纸条嘛!上面写着 i 的地址,而 i 就是那本厚书C++ Primer Plus。你现在看懂了吗?因此,我们就把 pi 称为指针。所以你要牢牢记住:指针变量所存的内容就是内存的地址编号 ! 本篇文章看完啥都可以没记住,这18个红字切记,切记,切记要牢牢刻在脑子里,也许可能或许大概在将来某个面试中人家问你指针是啥?这18个字足以得满分。也会随着你不断的学习对这句话会理解的越来越深。切记记住这18个红字!!好了废话太多了,现在我们就可以通过这个指针 pi 来访问到 i 这个变量了,请看代码:

    #include <iostream>
    #include <stdlib.h>
    
    using namespace std;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	int i = 30;
    
    	std::cout << "&i = "<< &i << std::endl;
    	std::cout << "i =  " << i << std::endl;
    
    	int *pi = &i;
    	std::cout << "*pi = " << *pi << std::endl;
    
    	system("pause");
    	return 0;
    }
    

    输出结果如下: 

    那么程序中*pi 什么意思呢?你只要这样大声读它:pi 内容所指的地址的内容(读上去好像在绕口令了),就是 pi 这张“纸条”上所写的位置上的那本 “书”—— i 。你看,Pi 的内容是 6,也就是说 pi 指向内存编号为 6 的地址。*pi嘛,就是它所指地址的内容,即地址编号 6 上的内容了,当然就是 30 这个“值”了。所以这条语句会在屏幕上显示 30。请结合上图好好体会吧!由于本人水平有限,对“指针是个什么东西?”的理解仅限于此,有问题我们在随时探讨喽。真想说:指针是个什么东西,这么难理解,脑仁疼疼的。
    到此为止,你已经能大声读出类似&i、*pi 写法的含义了。也知道了具体的相关操作。总结一句话:我们的纸条就是我们的指针,同样我们的 pi 也就是我们的纸条!剩下的就是我们如何应用这张纸条了。如何用?大声读出下面的代码并正确理解含义。

    char a,*pa;
    a = 10;
    pa = &a;
    *pa = 20;

    假设你已经完全掌握在此之前的所有内容,我们接着看看“指针的指针”它是个什么东西?即对int **ppa;中 ppa 的理解。

     三. 二级指针(指针的指针)

    二级指针,是一种指向指针的指针。我们可以通过它实现间接访问数据,和改变一级指针的指向问题。

    3.1 定义与初始化

    以一张简图说明问题:(看不懂看第二节)

    例子代码:

    #include <iostream>
    #include <stdlib.h>
    
    using namespace std;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	int i = 30;
    
    	std::cout << "&i = "<< &i << std::endl;
    	std::cout << "i =  " << i << std::endl;
    
    	int *pi = &i;
    	std::cout << "*pi = " << *pi << std::endl;
    
    	int **ppi = &pi;
    	std::cout << "**ppi = " << **ppi << std::endl;
    
    	system("pause");
    	return 0;
    }

     输出结果:

     思考:**pi 怎么读?代表的含义是什么?

    3.2 间接数据访问

    3.2.1 .改变一级指针指向

    例子代码:

    #include <iostream>
    #include <stdlib.h>
    
    using namespace std;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	int i = 30;
    
    	int *pi = &i;
    	std::cout << "一级指针*pi = " << *pi << std::endl;       //一级指针
    
    	int **ppi = &pi;
    	std::cout << "二级指针**ppi = " << **ppi << std::endl;   //二级指针
    
    	*pi = 20;
    	std::cout << "改变一级指针内容: *pi = " << *pi << std::endl;  //改变一级指针值
    	std::cout << "一级指针*pi = " << *pi << std::endl;       //二级指针
    
    	int b = 10;
    	*ppi = &b;
    	std::cout << "改变一级指针指向*pi = " << *pi << std::endl;   //改变一级指针的指向
    	std::cout << "二级指针**ppi = " << **ppi << std::endl;   
    
    	system("pause");
    	return 0;
    }
    

    输出结果:

    3.2.2 改变 N-1 级指针的指向

    • 可以通过一级指针,修改 0  级指针(变量)的内容。
    • 可以通过二级指针,修改一级指针的指向。
    • 可以通过三级指针,修改二级指针的指向。
    •  ·····
    • 可以通过 n  级指针,修改 n-1 

    3.2.3 二级指针的步长

    所有类型的二级指针,由于均指向一级指针类型,一级指针类型大小是 4,所以二级指针的步长也是 4,这个信息很重要

    四. 指针与数组

    4.1 指针与数组名

    4.1.1 通过数组名访问数组元素

    看下面代码:
    int i, a[] = {3,4,5,6,7,3,7,4,4,6};
    for (i = 0; i <= 9; i++)
    {
        std::cout << a[i] std::endl;
    }
    很显然,它是显示 a 数组的各元素值。
    我们还可以这样访问元素,如下:
    int i, a[] = {3,4,5,6,7,3,7,4,4,6};
    for (i = 0; i <= 9; i++)
    {
    std::cout << *(a+i) << std<<endl;;
    }
    它的结果和作用完全一样。

    4.1.2 通过指针访问数组元素

    int i, *pa, a[] = {3,4,5,6,7,3,7,4,4,6};
    pa = a; /*请注意数组名 a 直接赋值给指针 pa*/
    for (i = 0; i <= 9; i++)

    {
     std::cout <<  pa[i] << std::endl;
    }
    很显然,它也是显示 a 数组的各元素值。

    另外与数组名一样也可如下:
    int i, *pa, a[] = {3,4,5,6,7,3,7,4,4,6};
    pa = a;
    for (i = 0; i <= 9; i++)
    {
       std::cout <<  *(pa+i) << std::endl;
    }

    4.1.3 数组名与指针变量的区别

    请看下面的代码:
    int i, *pa, a[] = {3,4,5,6,7,3,7,4,4,6};
    pa = a;
    for (i = 0; i <= 9; i++)
    {
    printf("%d\n", *pa);
    pa++; /*注意这里,指针值被修改*/
    }

    可以看出,这段代码也是将数组各元素值输出。不过,你把循环体{}中的 pa改成 a 试试。你会发现程序编译出错,不能成功。看来指针和数组名还是不同的。其实上面的指针是指针变量,而 数组名只是一个指针常量。

    4.2 指针数组( 字符指针数组 )

    4.2.1 定义

    指针数组的本质是数组,数组中每一个成员是一个指针。定义形式如下:
    char * pArray[10];
    语法解析:pArray 先与“[ ]”结合,构成一个数组的定义,char *修饰的是数组的内容,即数组的每个元素。

    图示:

     4.2.2 代码实例

    #include <iostream>
    #include <stdlib.h>
    
    using namespace std;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	char * pArray[] ={"apple","pear","banana","orange","pineApple"};
    	for(int i=0; i<sizeof(pArray)/sizeof(*pArray); i++)
    	{
    		std::cout << pArray[i] << std::endl;
    	} 
    
    	system("pause");
    	return 0;
    }

    输出结果:

    4.3 二级指针与指针数组 

    4.3.1 .指针数组名赋给二级指针的合理性

    二级指针与指针数组名等价的原因:
    char **p 是二级指针;
    char* array[N]; array = &array[0]; array[0] 本身是 char*型;
    char **p = array;

    例子代码:

    #include <iostream>
    #include <stdlib.h>
    
    using namespace std;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	char * pArray[] ={"apple","pear","banana","orange","pineApple"};
    	std::cout << "**********pArray[i]************" << std::endl;
    	for(int i=0; i<sizeof(pArray)/ sizeof(*pArray); i++)
    	{
    		std::cout << pArray[i] << std::endl;
    	}
    		
    	char **pArr = pArray;
    	std::cout << "**********pArr[i]************" << std::endl;
    	for(int i=0; i<sizeof(pArray)/ sizeof(*pArray); i++)
    	{
    		std::cout << pArr[i] << std::endl;
    	}
    	system("pause");
    	return 0;
    }

    输出结果:

    4.3.2 完美匹配的前提(小尾巴 NULL)

    数组名,赋给指针以后,就少了维度这个概念,所以用二级指针访问指针数组,需要维度,当然了,也可以不用需要。

    实例代码:

    #include <iostream>
    #include <stdlib.h>
    
    using namespace std;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	//演绎 1
    	std::cout << "******演绎 1*****" << std::endl;
    	int arr[10] = {1};
    	for(int i=0; i<10; i++)
    	{
    		std::cout << arr[i] << std::endl;
    	}
    
    	int *parr = arr;
    	for(int i=0; i<10; i++)
    	{
    		std::cout << *parr++ << std::endl;
    	}
    
    
    	//演绎 2
    	std::cout << "*****演绎 2*****"<<std::endl;
    	char *str = "china";
    	while(*str)
    	{
    		std::cout << *str++ << std::endl;
    	}
    
    	char * pArray[] ={"apple","pear","banana","orange","pineApple",NULL};
    	char **pa = pArray;
    	while(*pa != NULL)
    	{
    		std::cout << *pa++ << std::endl;
    	}
    	system("pause");
    	return 0;
    }

    输出结果:

     

    【下一篇:】C/C++指针详解之提高篇(史上最全最易懂指针学习指南!!!!)

    展开全文
  • 指针理解,很经典

    千次阅读 多人点赞 2018-04-25 10:39:03
    要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区。让我们分别说明。  先声明几个指针放着做例子:  例一:  ...

    指针的概念

    指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区。让我们分别说明。 

    先声明几个指针放着做例子:  

    例一:  

    int *ptr;  
    char *ptr;  
    int **ptr;  
    int (*ptr)[3];  
    int *(*ptr)[4];  

    指针的类型

    从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型:  

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

    怎么样?找出指针的类型的方法是不是很简单?  

    指针所指向的类型

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

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

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

    在指针的算术运算中,指针所指向的类型有很大的作用。  

    指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C越来越熟悉时,你会发现,把与指针搅和在一起的“类型”这个概念分成“指针的类型”和“指针所指向的类型”两个概念,是精通指针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前后矛盾,越看越糊涂。

     指针的值


    指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。 

    指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。 

    指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。 

    以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指向的类型是什么?该指针指向了哪里?  

    指针本身所占据的内存区

    指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据了4个字节的长度。

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

    指针的算术运算

    指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的。例如:  
    例二:  

     char a[20];  
     int *ptr=a;  
    ...  
    ...  
     ptr++;  


    在上例中,指针ptr的类型是int*,它指向的类型是int,它被初始化为指向整形变量a。接下来的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr的值加上了sizeof(int),在32位程序中,是被加上了4。由于地址是用字节做单位的,故ptr所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。
    由于char类型的长度是一个字节,所以,原来ptr是指向数组a的第0号单元开始的四个字节,此时指向了数组a中从第4号单元开始的四个字节。
    我们可以用一个指针和一个循环来遍历一个数组,看例子:  
    例三:  

    复制代码
    int array[20];  
    int *ptr=array;  
    ...  
    //此处略去为整型数组赋值的代码。  
    ...  
    for(i=0;i<20;i++)  
    {  
        (*ptr)++;  
        ptr++;  
    }  
    复制代码


    这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1,所以每次循环都能访问数组的下一个单元。再看例子:  

    例四:  

    char a[20];  
    int *ptr = a;  
    ...  
    ...  
    ptr += 5;  


    在这个例子中,ptr被加上了5,编译器是这样处理的:将指针ptr的值加上5乘sizeof(int),在32位程序中就是加上了5乘4=20。由于地址的单位是字节,故现在的ptr所指向的地址比起加5后的ptr所指向的地址来说,向高地址方向移动了20个字节。在这个例子中,没加5前的ptr指向数组a的第0号单元开始的四个字节,加5后,ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现出了指针的灵活性。 

    如果上例中,ptr是被减去5,那么处理过程大同小异,只不过ptr的值是被减去5乘sizeof(int),新的ptr指向的地址将比原来的ptr所指向的地址向低地址方向移动了20个字节。 

    总结一下,一个指针ptrold加上一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值增加了n乘sizeof(ptrold所指向的类型)个字节。就是说,ptrnew所指向的内存区将比ptrold所指向的内存区向高地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。一个指针ptrold减去一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值减少了n乘sizeof(ptrold所指向的类型)个字节,就是说,ptrnew所指向的内存区将比ptrold所指向的内存区向低地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。

    运算符&和*

    这里&是取地址运算符,*是...书上叫做“间接运算符”。&a的运算结果是一个指针,指针的类型是a的类型加个*,指针所指向的类型是a的类型,指针所指向的地址嘛,那就是a的地址。*p的运算结果就五花八门了。总之*p的结果是p所指向的东西,这个东西有这些特点:它的类型是p指向的类型,它所占用的地址是p所指向的地址。

    例五:  

    复制代码
    int a=12;  
    int b;  
    int *p;  
    int **ptr;  
    p=&a;//&a的结果是一个指针,类型是int*,指向的类型是int,指向的地址是a的地址。  
    *p=24;//*p的结果,在这里它的类型是int,它所占用的地址是p所指向的地址,显然,*p就是变量a。
    ptr=&p;//&p的结果是个指针,该指针的类型是p的类型加个*,在这里是int**。该指针所指向的类型是p的类型,这里是int*。该指针所指向的地址就是指针p自己的地址。 
    *ptr=&b;//*ptr是个指针,&b的结果也是个指针,且这两个指针的类型和所指向的类型是一样的,所以?amp;b来给*ptr赋值就是毫无问题的了。
    **ptr=34;//*ptr的结果是ptr所指向的东西,在这里是一个指针,对这个指针再做一次*运算,结果就是一个int类型的变量。
    复制代码



    指针表达式

    一个表达式的最后结果如果是一个指针,那么这个表达式就叫指针表达式。下面是一些指针表达式的例子:  

    例六:  

    复制代码
    int a,b;  
    int array[10];  
    int *pa;  
    pa=&a;//&a是一个指针表达式。  
    int **ptr=&pa;//&pa也是一个指针表达式。  
    *ptr=&b;//*ptr和&b都是指针表达式。  
    pa=array;  
    pa++;//这也是指针表达式。
    复制代码


    例七:  

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


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

    好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。 在例七中,&a不是一个左值,因为它还没有占据明确的内存。*ptr是一个左值,因为*ptr这个指针已经占据了内存,其实*ptr就是指针pa,既然pa已经在内存中有了自己的位置,那么*ptr当然也有了自己的位置。

    数组和指针的关系

    如果对声明数组的语句不太明白的话,请参阅我前段时间贴出的文章<<如何理解c和c++的复杂类型声明>>。 数组的数组名其实可以看作一个指针。看下例:  

    例八:  

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




    上例中,一般而言数组名array代表数组本身,类型是int [10],但如果把array看做指针的话,它指向数组的第0个单元,类型是int *,所指向的类型是数组单元的类型即int。因此*array等于0就一点也不奇怪了。同理,array+3是一个指向数组第3个单元的指针,所以*(array+3)等于3。其它依此类推。 

    例九:  

    复制代码
    char *str[3]={  
    "Hello,this is a sample!",  
    "Hi,good morning.",  
    "Hello world"  
    };  
    char s[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,它指向的地址是字符串"Hello,this is a sample!"的第一个字符的地址,即'H'的地址。 str+1也是一个指针,它指向数组的第1号单元,它的类型是char**,它指向的类型是char *。 

    *(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向"Hi,good morning."的第一个字符'H',等等。  

    下面总结一下数组的数组名的问题。声明了一个数组TYPE array[n],则数组名称array就有了两重含义:第一,它代表整个数组,它的类型是TYPE [n];第二,它是一个指针,该指针的类型是TYPE*,该指针指向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0号单元,该指针自己占有单独的内存区,注意它和数组第0号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++的表达式是错误的。 

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

    在表达式sizeof(array)中,数组名array代表数组本身,故这时sizeof函数测出的是整个数组的大小。  

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

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

    例十:  

    int array[10];  
    int (*ptr)[10];  
    ptr=&array;  


    上例中ptr是一个指针,它的类型是int (*)[10],他指向的类型是int [10],我们用整个数组的首地址来初始化它。在语句ptr=&array中,array代表数组本身。 

    本节中提到了函数sizeof(),那么我来问一问,sizeof(指针名称)测出的究竟是指针自身类型的大小呢还是指针所指向的类型的大小?答案是前者。例如:

    int (*ptr)[10];  


    则在32位程序中,有:  

    sizeof(int(*)[10])==4  
    sizeof(int [10])==40  
    sizeof(ptr)==4  

    实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。  

    指针和结构类型的关系

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

    例十一:  

    复制代码
    struct MyStruct  
    {  
    int a;  
    int b;  
    int c;  
    }  
    
    MyStruct ss={20,30,40};//声明了结构对象ss,并把ss的三个成员初始化为20,30和40。
    MyStruct *ptr=&ss;//声明了一个指向结构对象ss的指针。它的类型是
    MyStruct*,它指向的类型是MyStruct。
    int *pstr=(int*)&ss;//声明了一个指向结构对象ss的指针。但是它的类型和它指向的类型和ptr是不同的。
    复制代码

    请问怎样通过指针ptr来访问ss的三个成员变量?  

    答案:  

    ptr->a;  
    ptr->b;  
    ptr->c;  


    又请问怎样通过指针pstr来访问ss的三个成员变量?  
    答案:  
     

    *pstr;//访问了ss的成员a。  
    *(pstr+1);//访问了ss的成员b。  
    *(pstr+2)//访问了ss的成员c。 


    呵呵,虽然我在我的MSVC++6.0上调式过上述代码,但是要知道,这样使用pstr来访问结构成员是不正规的,为了说明为什么不正规,让我们看看怎样通过指针来访问数组的各个单元: 
    例十二:  

    int array[3]={35,56,37};  
    int *pa=array;  

    通过指针pa访问数组array的三个单元的方法是:  

    *pa;//访问了第0号单元  
    *(pa+1);//访问了第1号单元  
    *(pa+2);//访问了第2号单元  


    从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。

    所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干个“填充字节”,这就导致各个成员之间可能会有若干个字节的空隙。

    所以,在例十二中,即使*pstr访问到了结构对象ss的第一个成员变量a,也不能保证*(pstr+1)就一定能访问到结构成员b。因为成员a和成员b之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字节,嘿,这倒是个不错的方法。 

    通过指针访问结构成员的正确方法应该是象例十二中使用指针ptr的方法。

    指针和函数的关系

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

    int fun1(char*,int);  
    int (*pfun1)(char*,int);  
    pfun1=fun1;  
    ....  
    ....  
    int a=(*pfun1)("abcdefg",7);//通过函数指针调用函数。  

    可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参。 

    展开全文
  • C指针

    千次阅读 2019-06-15 09:53:00
    C里面最难的就是指针了,所以从这里开始。 首先,我们要明白一点,什么是指针指针是干嘛的? 请注意:以下代码全部在codeblocks上编译运行过了,保证正确,请在看的同时也把他们复制或者敲一遍运行一次再接着一...
  • 指针与动态空间分配

    千次阅读 2014-04-08 10:15:02
    转载自http://blog.csdn.net/tsinfeng/article/details/5686816
  • 数组 一、数组定义格式: 1)数组概述:一次性声明大量的用于存储数据的变量;这些要存储的数据通常都是同类型数据。例如:全班同学的考试成绩。 2)数组:是一种用于存储多个相同类型数据的存储模型 ...
  • 结构与指针 定义: /* 定义 */ struct info // 定义了一个info结构,里面有两个成员num和name[5] { short num; // 两个成员的类型分别为short型和char型 char name[5]; } struct info myinfo1,myinfo2,*...
  • 一、二维数组的基本概念 1.1 二维数组的内存映像 从内存角度看,二维数组和一维数组一样,在内存中都是连续分布的多个内存单元,并没有本质差别,只是内存的管理方式不一样,如下图所示 一维数组int a[10]与二维...
  • 指针 && 动态内存分配

    2019-06-25 13:12:13
     如果指针指向的动态内存被释放或没有指向合法的地址,就应该将指针设置为nullptr,否则内存泄漏。 变量:  double* pvalue {};  pvalue=new double;  *pvalue=9.0;  或  double* p...
  • 指针总结

    千次阅读 2019-07-27 15:01:06
    指针总结 1、先谈谈内存与地址 引例: 计算机的内存看成大街上的一排房屋,每个房屋都要有门牌号,这个就相当于计算机的内存地址,而房屋里面住的人、家具等等就相当于需要存放的各种各样的数据,所以要想访问...
  • 指针*p的理解:p代表地址(p的值是一个地址),*p代表该地址下存放的值 例如 p=32,地址32里存放的值为45,则*p=45 指针指针**p:p代表地址(p的值是一个地址,这个地址里面存放了*p的值),*p的值仍然是一个地址...
  • 指针

    千次阅读 多人点赞 2019-07-29 20:20:27
    一、指针变量的定义和使用 1.定义指针变量 2.通过指针变量取得数据 3.关于*和& 二、指针变量运算(加法、减法、比较运算) 三、数组指针(指向数组的指针) 假设 p 是指向数组 arr 中第 n 个元素的指针,...
  • 指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(Pointed to)存在电脑存储器中另一个地方的值。也就是通过地址可以找到所需的变量单元,可以说,地址指向该变量单元。 举例说明: #include <...
  • 什么是指针

    千次阅读 2018-07-28 11:59:11
    1.什么是指针 指针是一种地址值,例如0x000012ea。 2.什么是指针类型 指针类型是一种新的类型,常见的类型有整形(int),用来存放整数;字符型(char),用来存放字符或字符串;浮点型(float),用来存放浮点型...
  • 指针数组与数组指针的区别

    万次阅读 多人点赞 2019-03-01 19:56:23
    a、指针数组:是指一个数组里面装着指针,也即指针数组是一个数组; 定义形式:int *a[10]; 如图所示: b、数组指针:是指一个指向数组的指针,它其实还是一个指针,只不过是指向数组而已; 定义形式:int (*p)...
  • 常量指针指针常量的区别

    万次阅读 多人点赞 2018-05-24 22:58:42
    在面试中我们经常会被面试官问到什么是常量指针,什么又是指针常量。可能第一次被问到都会有些懵逼(大神略过)。我今天就来讲一讲这二者的区别。 下面开始正题。。。。 指针常量 指针常量:顾名思义它就是一个...
  • // 打印函数 template void disp(T i) { cout; }int main() { int i = 1; int *p = &i; *p = 2; // 通过指针赋值 disp(i); int j = 10; // 对指针赋值,将指针p指向j p = &j;
  • 指针指针:未被初始化的指针,野指针所导致的错误和迷途指针非常相似,但野指针的问题更容易被发现。空指针指针:就是一个被赋值为0的指针,它不指向任何的对象或者函数。1、使用指针的第一件事就是需要看这个...
  • 指针数组与数组指针详解

    万次阅读 多人点赞 2016-09-28 21:21:20
    指针数组与数组指针详解1....数组指针:数组指针可以说成是”数组的指针”,首先这个变量是一个指针,其次,”数组”修饰这个指针,意思是说这个指针存放着一个数组的首地址,或者说这个指针指向一个数组的首地址。 根
1 2 3 4 5 ... 20
收藏数 1,859,802
精华内容 743,920
关键字:

指针