您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > C/C++/C#

C语言指针经典知识汇总

时间:2020-01-30 17:04:01  来源:  作者:

指针在C语言中是一块很重要的内容,也是比较难理解的一块内容,我们需要反复理解反复巩固才可以对其有所了解。之前也分享过指针相关的笔记,但是都比较杂,本篇笔记汇总一下指针相关的内容,包含了挺多指针相关的基础知识点。这篇笔记有点长,可以收藏下来慢慢阅读。

复杂类型说明

以下这部分内容主要来自《让你不再害怕指针》:

要了解指针,多多少少会出现一些比较复杂的类型,所以,先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单。

一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,所以我总结了一下其原则: 从变量名处起,根据运算符优先级结合,一步一步分析。

下面让我们先从简单的类型开始慢慢分析吧:

int p; 

这是一个普通的整型变量 。

 

int *p; 

首先从 P处开始,先与*结合,所以说明 P 是一个指针,然后再与 int 结合,说明指针所指向的内容的类型为 int 型。所以 P 是一个返回整型数据的指针。

 

int p[3]; 

首先从 P 处开始,先与[]结合,说明 P 是一个数组,然后与 int 结合,说明数组里的元素是整型的,所以 P 是一个由整型数据组成的数组。

 

int *p[3]; 

首先从 P 处开始,先与[]结合,因为其优先级比 * 高,所以 P 是一个数组,然后再与 * 结合,说明数组里的元素是指针类型,然后再与 int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组 。

 

int (*p)[3]; 

首先从 P 处开始,先与 * 结合,说明 P 是一个指针然后再与[]结合与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与 int 结合,说明数组里的元素是整型的。所以 P 是一个指向由整型数据组成的数组的指针。

 

int **p;

首先从 P 开始,先与后再与 * 结合,说明指针所指向的元素是指针,然后再与 int 结合,说明该指针所指向的元素是整型数据。由于二级以上的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针。

 

int p(int); 

从 P 处起,先与()结合,说明 P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数然后再与外面的 int 结合,说明函数的返回值是一个整型数据。

 

int (*p)(int);

从 P 处开始,先与指针结合,说明 P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个 int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以 P 是一个指向有一个整型参数且返回类型为整型的函数的指针。

 

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

 

分析指针的方法

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

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

(1)int *ptr;(2)char*ptr;(3)int **ptr;(4)int (*ptr)[3];(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]

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

 

3、指针的值

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

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

以后, 我们说一个指针的值是 XX, 就相当于说该指针指向了以 XX 为首地址的一片内存区域; 我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。

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

以后, 每遇到一个指针, 都应该问问: 这个指针的类型是什么? 指针指向的类型是什么? 该指针指向了哪里? (重点注意) 。

 

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

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

 

指针的算术运算

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

这在内存上体现为:相对这个指针向后偏移多少个单位或向前偏移了多少个单位,这里的单位与指针变量的类型有关。在32bit环境下,int类型占4个字节,float占4字节,double类型占8字节,char占1字节。

【注意】一些处理整数的操作不能用来处理指针。例如,可以把两个整数相乘,但是不能把两个指针相乘。

示例程序

#include <stdio.h>int main(void){int    a = 10, *pa = &a;float  b = 6.6, *pb = &b;char   c = 'a', *pc = &c;double d = 2.14e9, *pd = &d;//最初的值printf("pa0=%d, pb0=%d, pc0=%d, pd0=%dn", pa, pb, pc, pd);//加法运算pa += 2; pb += 2; pc += 2;pd += 2;printf("pa1=%d, pb1=%d, pc1=%d, pd1=%dn", pa, pb, pc, pd);//减法运算pa -= 1; pb -= 1; pc -= 1;pd -= 1;printf("pa2=%d, pb2=%d, pc2=%d, pd2=%dn", pa, pb, pc, pd);return 0;}

运行结果为:

pa0=6422268, pb0=6422264, pc0=6422263, pd0=6422248pa1=6422276, pb1=6422272, pc1=6422265, pd1=6422264pa2=6422272, pb2=6422268, pc2=6422264, pd2=6422256

解析:

举例说明pa0→pa1→pa2的过程,其他类似。pa0+2*sizeof(int)=pa1,pa1-1*sizeof(int)=pa2。因为pa为int类型的指针,所以加减运算是以4字节(即sizeof(int))为单位地址向前向后偏移的。看下图:

C语言指针经典知识汇总

 

如图:pa1所指向的地址在pa0所指向地址往后8字节处,pa2指向地址在pa1指向地址往前4字节处。

从本示例程序中,还可以看出:连续定义的变量在内存的存储有可能是紧挨着的,有可能是分散着的。

 

数组和指针的联系

数组与指针有很密切的联系,常见的结合情况有以下三种:

  • 数组指针
  • 指针数组
  • 二维数组指针

1、数组指针

数组指针:指向数组的指针。如:

int arr[] = {0,1,2,3,4};int *p = arr; //也可写作int *p=&arr[0]

也就是说,p,arr,&arr[0]都是指向数组的开头,即第0个元素的地址。

如果一个指针p指向一个数组arr[]的开头,那么p+i为数组第i个元素的地址,即&arr[i],那么*(p+i)为数组第i个元素的值,即arr[i]。

同理,若指针p指向数组的第n个元素,那么p+i为第n+1个元素的地址;不管 p 指向了数组的第几个元素,p+1 总是指向下一个元素,p-1 也总是指向上一个元素。

下面示例证实了这一点:

#include <stdio.h>int main(void){   int arr[] = {0, 1, 2, 3, 4};   int *p = &arr[3];  //也可以写作 int *p = arr + 3;   printf("%d, %d, %d, %d, %dn",    *(p-3), *(p-2), *(p-1), *(p), *(p+1) );   return 0;}

运行结果为:

0, 1, 2, 3, 4

 

2、指针数组

指针数组:数组中每个元素都是指针。如:

int a=1,b=2,c=3;int *arr[3] = {&a,&b,&c};

示例程序:

#include <stdio.h>int main(void){   int a = 1, b = 2, c = 3;   //定义一个指针数组   int *arr[3] = {&a, &b, &c};//也可以不指定长度,直接写作 int *parr[]   //定义一个指向指针数组的指针   int **parr = arr;   printf("%d, %d, %dn", *arr[0], *arr[1], *arr[2]);   printf("%d, %d, %dn", **(parr+0), **(parr+1), **(parr+2));   return 0;}

第一个 printf() 语句中,arr[i] 表示获取第 i 个元素的值,该元素是一个指针,还需要在前面增加一个 * 才能取得它指向的数据,也即 *arr[i] 的形式。

 

第二个 printf() 语句中,parr+i 表示第 i 个元素的地址,*(parr+i) 表示获取第 i 个元素的值(该元素是一个指针),**(parr+i) 表示获取第 i 个元素指向的数据。

指针数组还可以和字符串数组结合使用,请看下面的例子:

#include <stdio.h>int main(void){    char *str[3] =     {        "hello C",        "hello C++",        "hello JAVA"    };    printf("%sn%sn%sn", str[0], str[1], str[2]);    return 0;}

运行结果为:

hello Chello C++hello Java

 

3、二维数组指针

二维数组指针:指向二维数组的指针。如:

int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };int (*p)[4] = a;

a [3] [4]表示一个3行4列的二维数组,其所有元素在内存中是连续存储的。

请看如下程序:

#include <stdio.h>int main(void){    int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };    int i,j;    for( i = 0; i < 3; i++ )    {        for( j = 0; j < 4; j++ )        {            printf("a[%d][%d]=%dn", i, j, &a[i][j]);        }    }    return 0;}

运行结果为:

a[0][0]=6422216a[0][1]=6422220a[0][2]=6422224a[0][3]=6422228a[1][0]=6422232a[1][1]=6422236a[1][2]=6422240a[1][3]=6422244a[2][0]=6422248a[2][1]=6422252a[2][2]=6422256a[2][3]=6422260

可见,每个元素的地址都是相差4个字节,即每个连续在内存中是连续存储的。

 

按照以上定义可归纳出如下4个结论:

(1)p指向数组a的开头,也即第1行;p+1前进一行,指向第2行。

(2)*(p+1)表示取第2行元素(一整行元素)。

(3)*(p+1)+1表示第2行第2个元素的地址。

(4)((p+1)+1)表示第2行第2个元素的值。

综上4点,可得出如下结论:

a+i == p+i *(a+i) == *(p+i)a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j)== *(*(p+i)+j)

以上就是数组与指针常用的三种结合形式。

 

指针与数组的区别

数组与指针在多数情况是可以等价的,比如:

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)  

 

但也有不等价的时候,比如如下三种情况:

  • 数组名不可以改变,而指向数组的指针是可以改变的。
  • 字符串指针指向的字符串中的字符是不能改变的,而字符数组中的字符是可以改变的。
  • 求数组长度时,借用数组名可求得数组长度,而借用指针却得不到数组长度。

1、区别一

数组名的指向不可以改变,而指向数组的指针是可以改变的。

请看如下代码:

#include <stdio.h>int main(void){    int a[5] = {0, 1, 2, 3, 4}, *p = a;    char i;    // 数组遍历方式一    for ( i = 0; i < 5; i++ )    {        printf("a[%d] = %dn", i, *p++);    }    // 数组遍历方式二    for ( i = 0; i < 5; i++ )    {        printf("a[%d] = %dn", i, *a++);    }    return 0;}

数组遍历方式一:使用指针遍历数组元素,* p++等价于*(p++),即指针指向的地址每次后移一个单位,然后再取地址上的值。这里的一个单位是sizeof(int)个字节。

数组遍历方式二:使用数组名自增遍历数组元素,编译出错,错误如下:

error: value required as increment operand

因为数组名的指向是不可以改变的,使用自增运算符自增就会改变其指向,这是不对的,数组名只能指向数组的开头。但是可以改为如下遍历方式:

for ( i = 0; i < 5; i++ ){    printf("a[%d] = %dn", i, *(a+i));}

这可以正确遍历数组元素。因为*(a+i)与a[i]是等价的。

 

2、区别二

字符串指针指向的字符串中的字符是不能改变的,而字符数组中的字符是可以改变的。

请看如下代码:

//字符串定义方式一char str[] = "hAppy";//字符串定义方式二char *str = "happy";

字符串定义方式一:字符串中的字符是可以改变的。如可以使用类似str[3]='q'这样的语句来改变其中的字符。原因就是:这种方式定义的字符串保存在全局数据区或栈区,是可读写的。

 

字符串定义方式二:字符串中的字符是不可以改变的。原因就是:这种方式定义的字符串保存在常量区,是不可修改的。

 

2、区别三

求数组长度时,借用数组名可求得数组长度,而借用指针却得不到数组长度。

请看如下代码:

#include <stdio.h>int main(void){    int a[] = {0, 1, 2, 3, 4}, *p = a;    char len = 0;    // 求数组长度方式一    printf("方式一:len=%dn",sizeof(a)/sizeof(int));    // 求数组长度方式二    printf("方式二:len=%dn",sizeof(p)/sizeof(int));    return 0;}

运行结果

方式一:len=5方式二:len=1

求数组长度方式一:借用数组名来求数组长度,可求得数组有5个元素,正确。

 

求数组长度方式二:借用指针求数组长度,求得长度为1,错误。原因是:

p只是一个指向int类型的指针,编译器不知道其指向的是一个整数还是指向一个数组。sizeof(p)求得的是p这个指针变量本身所占用的字节数,而不是整个数组占用的字节数。

 

下面还需要注意数组名的一个问题: 声明了一个数组 TYPE array[n] , 则数组名是一个常量指针, 该指针的值是不能修改的, 即类似 array++的表达式是错误的。

 

指针函数与函数指针

函数、指针这两个词结合的顺序不同其意义也不同,即指针函数与函数指针的意义不同。

1、指针函数

指针函数的本质是一个函数,其返回值是一个指针。示例如下:

int *pfun(int, int);

由于“*”的优先级低于“()”的优先级,因而pfun首先和后面的“()”结合,也就意味着,pfun是一个函数。即:int *(pfun(int, int));

接着再和前面的“*”结合,说明这个函数的返回值是一个指针。由于前面还有一个int,也就是说,pfun是一个返回值为整型指针的函数。

指针函数示例程序如下:

#include <stdio.h>//这是一个指针函数的声明int *pfun(int *arr, int n);int main(void){    int array[] = {0, 1, 2, 3, 4};    int len = sizeof(array)/sizeof(array[0]);    int *p;    int i;    //指针函数的调用    p = pfun(array, len);    for (i = 0; i < len; i++)    {        printf("array[%d] = %dn", i, *(p+i));    }    return 0;}//这是一个指针函数,其返回值为指向整形的指针int *pfun(int *arr, int n){    int *p = arr;    return p;}

程序运行结果如下:

C语言指针经典知识汇总

 

主函数中,把一个数组的首地址与数组长度作为实参传入指针函数pfun里,把指针函数的返回值(即指向数组的指针)赋给整形指针p。最后使用指针p来遍历数组元素并打印输出。

 

2、函数指针

函数指针其本质是一个指针变量,该指针变量指向一个函数。C程序在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。函数指针示例:

/*声明一个函数指针 */int (*fptr) (int, int); /* 函数指针指向函数func */fptr = func;  // 或者fptr = &func;

func是一个函数名,那么func与&func都表示的是函数的入口地址。同样的,在函数的调用中可以使用:方式一:func(),也可以使用方式二:(*fun)()。这两种调用方式是等价的,只是我们平时大多都习惯用方式一的调用方法。

至于为什么func与&func的含义相同,《嵌入式linux上的C语言编程实践》这本书中有如下解释:

对于函数func来说,函数的名称就是函数代码区的常量,对它取地址(&func)可以得到函数代码区的地址,同时,func本身也可以视为函数代码区的地址。因此,函数名称和对其取地址其含义是相同的。

函数指针示例程序如下:

#include <stdio.h>int add(int a, int b);int main(void){    int (*fptr)(int, int); //定义一个函数指针    int res;    fptr = add;  //函数指针fptr指向函数add    /* 通过函数指针调用函数 */    res = (*fptr)(1,2); //等价于res = fptr(1,2);    printf("a + b = %dn", res);    return 0;}int add(int a, int b){    return a + b;}

程序运行结果如下:

C语言指针经典知识汇总

 

以上就是关于指针函数与函数指针的简单区分。其中,函数指针广泛应用于嵌入式软件开发中,其常用的两个用途:调用函数和做函数的参数。

 

以上就是本次的分享,如有错误,欢迎指出!谢谢



Tags:C语言 指针   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
指针和多维数组有什么关系?为什么要了解它们的关系?处理多维数组的函数要用到指针,所以在使用这种函数之前,先要更深入地学习指针。至于第1个问题,我们通过几个示例来回答。为简...【详细内容】
2020-07-26  Tags: C语言 指针  点击:(78)  评论:(0)  加入收藏
指针对于C来说太重要。然而,想要全面理解指针,除了要对C语言有熟练的掌握外,还要有计算机硬件以及操作系统等方方面面的基本知识。所以本文尽可能的通过一篇文章完全讲解指针。...【详细内容】
2020-02-27  Tags: C语言 指针  点击:(67)  评论:(0)  加入收藏
指针在C语言中是一块很重要的内容,也是比较难理解的一块内容,我们需要反复理解反复巩固才可以对其有所了解。之前也分享过指针相关的笔记,但是都比较杂,本篇笔记汇总一下指针相...【详细内容】
2020-01-30  Tags: C语言 指针  点击:(56)  评论:(0)  加入收藏
在程序中声明变量后,编译器就会为该变量分配相应的内存单元。也就是说,每个变量在内存会有固定的位置,有具体的地址。由于变量的数据类型不同,它所占的内存单元数也不相同。如下...【详细内容】
2019-08-19  Tags: C语言 指针  点击:(260)  评论:(0)  加入收藏
指针是C语言的一个核心特色,它以一种统一方式对不同数据结构中的元素产生引用。对于新手来说,指针总是会带来很多困惑,但其实指针的基本概念非常简单。下面是一些指针和它们映...【详细内容】
2019-07-31  Tags: C语言 指针  点击:(255)  评论:(0)  加入收藏
▌简易百科推荐
一、简介很多时候我们都需要用到一些验证的方法,有时候需要用正则表达式校验数据时,往往需要到网上找很久,结果找到的还不是很符合自己想要的。所以我把自己整理的校验帮助类分...【详细内容】
2021-12-27  中年农码工    Tags:C#   点击:(2)  评论:(0)  加入收藏
引言在学习C语言或者其他编程语言的时候,我们编写的一个程序代码,基本都是在屏幕上打印出 hello world ,开始步入编程世(深)界(坑)的。C 语言版本的 hello world 代码:#include <std...【详细内容】
2021-12-21  一起学嵌入式    Tags:C 语言   点击:(11)  评论:(0)  加入收藏
读取SQLite数据库,就是读取一个路径\\192.168.100.**\position\db.sqlite下的文件<startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0"/...【详细内容】
2021-12-16  今朝我的奋斗    Tags:c#   点击:(21)  评论:(0)  加入收藏
什么是shellshell是c语言编写的程序,它在用户和操作系统之间架起了一座桥梁,用户可以通过这个桥梁访问操作系统内核服务。 它既是一种命令语言,同时也是一种程序设计语言,你可以...【详细内容】
2021-12-16  梦回故里归来    Tags:shell脚本   点击:(18)  评论:(0)  加入收藏
一、编程语言1.根据熟悉的语言,谈谈两种语言的区别?主要浅谈下C/C++和PHP语言的区别:1)PHP弱类型语言,一种脚本语言,对数据的类型不要求过多,较多的应用于Web应用开发,现在好多互...【详细内容】
2021-12-15  linux上的码农    Tags:c/c++   点击:(17)  评论:(0)  加入收藏
1.字符串数组+初始化char s1[]="array"; //字符数组char s2[6]="array"; //数组长度=字符串长度+1,因为字符串末尾会自动添&lsquo;\0&lsquo;printf("%s,%c\n",s1,s2[2]);...【详细内容】
2021-12-08  灯-灯灯    Tags:C语言   点击:(47)  评论:(0)  加入收藏
函数调用约定(Calling Convention),是一个重要的基础概念,用来规定调用者和被调用者是如何传递参数的,既调用者如何将参数按照什么样的规范传递给被调用者。在参数传递中,有两个很...【详细内容】
2021-11-30  小智雅汇    Tags:函数   点击:(19)  评论:(0)  加入收藏
一、问题提出问题:把m个苹果放入n个盘子中,允许有的盘子为空,共有多少种方法?注:5,1,1和1 5 1属同一种方法m,n均小于10二、算法分析设f(m,n) 为m个苹果,n个盘子的放法数目,则先对...【详细内容】
2021-11-17  C语言编程    Tags:C语言   点击:(49)  评论:(0)  加入收藏
一、为什么需要使用内存池在C/C++中我们通常使用malloc,free或new,delete来动态分配内存。一方面,因为这些函数涉及到了系统调用,所以频繁的调用必然会导致程序性能的损耗;另一...【详细内容】
2021-11-17  深度Linux    Tags:C++   点击:(38)  评论:(0)  加入收藏
OpenCV(Open Source Computer Vision Library)是一个(开源免费)发行的跨平台计算机视觉库,可以运行在Linux、Windows、Android、ios等操作系统上,它轻量级而且高效---由一系列...【详细内容】
2021-11-11  zls315    Tags:C#   点击:(50)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条