>>返回AUTOSAR系列文章目录<<
以下是常见的指针解释:
指针(Pointer)是一个变量,这个变量存放的是另一个变量的地址。
这个解释很大程度上误解了指针的作用,造成新人学习困难,以下是我自己的理解:
指针(Pointer)是一个助记符,记录了一个内存块的首地址和长度。
int* a; // a 可以记录一个int型数据首地址和数据长度 4
char* b; // b 可以记录一个char型数据首地址和数据长度 1
程序在运行过程中需要的是数据和指令,变量、数组、字符串、结构体等等就是数据,函数就是指令。
数据和指令通常都不止一个字节,因此指针既包含代码块或者指令块的首地址,也包含这些代码块或指令块的长度。当我们调用指针时,实际上是让指针从首地址到尾地址跑了一遍。
指针记录的是一片内存块的首地址和长度,而不是单独一个字节的地址,但是打印时只显示首地址。
对于所有指针,编译器会自动转化以下语法,数组也是指针
ptr[i] => *(ptr+i)
声明一个指针需要用到间接运算符*
,语法中并没有规定*
前后空格。以下写法都合法:
int* ptr; //推荐
int * ptr;
int *ptr;
推荐使用第一种写法,在声明时不要将*
看成一个符号,而是将int*
整体看成一种全新的数据类型。这种数据类型定义了一片只能够存放int
型数据的内存块。
char* p; // 声明 char* 型数据类型
char a, b; // 声明 char 型数据类型
p = &a; // 地址运算符
b = *p; // 间接运算符
指针可以声明时赋值:
char a;
char* p = &a;
C语言本身就提供很多助记符,其中变量名表示的是数据内容,而函数名、字符串名和数组名本身就是函数、字符串、数组首元素的指针,无需声明即可直接作为指针使用。结构体名不是结构体的指针。所以以下内容都合法:
/***** 示例程序 *****/
#include
int main()
{
int* ptr;
int var;
int arr[10];
ptr = &var; // var本身是内容,需要地址运算符
printf("%p\n", ptr);
ptr = arr; // arr本身就是指针,可直接赋值
printf("%p\n", ptr);
return 0;
}
/***** 示例结果 *****/
0x7ffe074e6af4
0x7ffe074e6ac0
由于数组名就是数组第0
个元素的指针,所以有:
*(arr + i) == arr[i];
使用指针变量之前一定要初始化,否则就不能确定指针指向哪里,如果它指向的内存没有使用权限,程序就崩溃了。对于暂时没有指向的指针,建议赋值NULL
。
指针变量可以与数字进行加减运算,例如p++
、p+1
、p-=1
。指针变量+1
运算并不是指向下一个内存地址,而是指向下一个内存块的地址。
/***** 示例程序 *****/
#include
int main()
{
int var1;
long var2;
int* ptr1 = &var1;
long* ptr2 = &var2;
printf("%p %p\n", ptr1, ptr1+1);
printf("%p %p\n", ptr2, ptr2+1);
return 0;
}
/***** 示例结果 *****/
0x7ffdc6edb08c 0x7ffdc6edb090
0x7ffdc6edb080 0x7ffdc6edb088
可以看到,int*
型指针+1
是位移了4
字节(一个内存块),long*
型指针+1
是位移了8
字节(一个内存块)。
两个指针变量可以相减。如果两个指针变量指向同一个数组中的某个元素,那么相减的结果就是两个指针之间相差的元素个数。
指针类型 | 声明 | 说明 |
---|---|---|
指针 | int* p; |
p 为指针,指向int 类型的数据,或者一维数组 int[n] 的元素 |
二级指针 | int** p; |
p 为指针,指向int* 类型的指针 |
指针数组 | int* p[n]; |
p 为数组,每个元素都是int* 型指针 |
数组指针 | int (*p)[n]; |
p 为指针,指向int[n] 型数组,或者int[m][n] 数组的一行 |
指针函数 | int* p(); |
p 为函数,它的返回值类型为 int* |
函数指针 | int (*p)(); |
p 为指针,指向原型为 int fun() 的函数 |
int* p = &var;
int* p = arr;
int* p = &arr[0];
p
可以指向一个int
型数据的地址,无论这个数据是否在一个数组中。
数组名就是数组第一个元素的地址,一维数组第一个元素是变量,二维数组第一个元素是数组。
p
的长度为sizeof(int)
。
空指针NULL
的定义如下:
#define NULL ((void*) 0)
NULL
是指向地址0x0
的指针,这个地址不能访问,所以以下程序会报错,但是避免了对内存造成不可预测的变化:
int* p = NULL;
printf("%d", *p); // 报错
指针包含内存块的首地址和长度,而万能指针void*
只包含首地址,没有长度信息。万能指针和其他类型指针可以相互转换。
其他指针转换成万能指针,保留首地址信息,丢失内存块长度信息。万能指针无法使用间接运算符*
。
#include
int main()
{
int a = 10;
int* p1 = &a;
void* p2 = p1;
printf("p1地址为%p\n", p1); // p1地址为0x7ffc22d963bc
printf("p1值为%d\n", *p1); // p1值为10
printf("p2地址为%p\n", p2); // p2地址为0x7ffc22d963bc
printf("p2值为%d\n", *p2); // 报错
return 0;
}
#include
int main()
{
int a = 10;
void* p1 = &a;
int* p2 = p1;
printf("p1地址为%p\n", p1); // p1地址为0x7fffaeb29a6c
printf("p1值为%d\n", *p1); // 报错
printf("p2地址为%p\n", p2); // p2地址为0x7fffaeb29a6c
printf("p2值为%d\n", *p2); // p2值为10
return 0;
}
指针可以指向一份普通类型的数据,例如 int
、double
、char
等,也可以指向一份指针类型的数据,例如 int*
、double*
、char*
等。
如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。
int a =100;
int* p1 = &a;
int** p2 = &p1;
C语言不限制指针的级数,实际开发中会经常使用一级指针和二级指针,几乎用不到高级指针。想要获取指针指向的数据时,一级指针加一个*
,二级指针加两个*
,以此类推:
#include
int main()
{
int a =100;
int *p1 = &a;
int **p2 = &p1;
printf("%d, %d, %d, %d\n", a, *p1, **p2, ***p3);
printf("&p2 = %#X, p3 = %#X\n", &p2, p3);
printf("&p1 = %#X, p2 = %#X, *p3 = %#X\n", &p1, p2, *p3);
return 0;
}
如果一个数组中的所有元素保存的都是指针,那么我们就称它为指针数组。指针数组的定义形式一般为:
int* p[n];
int* p[]; // 不指定长度
#include
int main()
{
int a = 16, b = 932, c = 100; //定义一个指针数组
int* arr[3] = {&a, &b, &c}; //也可以不指定长度,直接写作 int *arr[]
return 0;
}
如果一个指针指向了数组,我们就称它为数组指针。声明一个指向int[n]
型数组的指针为:
int (*p)[n];
理解二维数组a[n][m]
,他是一个int[n]
型数组,有n
个元素,每个元素又是int[m]
型数组。而数组名a
指向数组第一个元素的地址,所以数组名a
是一个int[m]
型数组的地址。所以如下赋值成立。
int a[n][m];
int (*p)[m] = a;
对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的内存块有关,p
指向的数据类型是int[m]
,那么p+1
就前进sizeof(int)*m
个字节,p-1
就后退 sizeof(int)*m
个字节,这正好是数组 a
所包含的每个一维数组int[m]
的长度。也就是说,p+1
会使得指针指向二维数组的下一行,p-1
会使得指针指向数组的上一行。可以得到如下等效关系。
&a[i] == a+i == &p[i] == p+i;
a[i] == p[i] == *(a+i) == *(p+i);
&a[i][j] == a[i]+j == &p[i][j] == p[i]+j;
a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j) == *(*p+i)+j);
理解以上等式,核心是:指针在进行计算时,要带入指针指向的内存块的首地址和长度。
第一行,a
的内存块是a[0]
,首地址是a[0][0]
,长度是int[m]
,偏移i
后首地址是是a[i][0]
,长度是int[m]
,所以a+i
的内存块是a[i]
。
第三行,a[i]
的内存块是a[i][0]
,长度是int
,偏移j
后内存块是a[i][j]
。
指针函数,简单的来说,就是一个返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针。
int* fun(int x,int y);
和一般函数在返回值类型后面多了一个*
号,而这个函数就是一个指针函数。其返回值是一个 int*
类型的指针,是一个地址。
/***** 示例程序 *****/
#include
int* GetDate(int wk,int dy); //声明 int* 型指针函数
int main()
{
int week = 2;
int day = 5;
printf("%d\n", *GetDate(week,day)); // *p 得到值
return 0;
}
int* GetDate(int wk,int dy)
{
static int calendar[5][7]=
{
{1,2,3,4,5,6,7},
{8,9,10,11,12,13,14},
{15,16,17,18,19,20,21},
{22,23,24,25,26,27,28},
{29,30,31,-1}
};
return &calendar[wk-1][dy-1]; //返回 &int
}
/***** 示例结果 *****/
12
函数指针,其本质是一个指针变量,该指针指向这个函数。总结来说,函数指针就是指向函数的指针。
int (*pfun)(int x,int y);
函数指针是需要把一个函数的地址赋值给它,有两种写法:
pfun = &fun;
pfun = fun;
取地址运算符&
不是必需的,因为一个函数标识符就表示了它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。
调用函数指针的方式也有两种:
x = (*pfun)();
x = pfun();
两种方式均可。
建议使用第一种,因为可以清楚的指明这是通过指针的方式来调用函数。
第二种方式相当于把函数fun
的标识符换成了pfun
,改了个名字。
/***** 示例程序 *****/
#include
int max(int a, int b)
{
return a>b ? a : b;
}
int main()
{
int x, y, maxval;
int (*pmax)(int a, int b) = max;
printf("Input two numbers:");
scanf("%d %d", &x, &y);
maxval = (*pmax)(x, y);
printf("Max value: %d\n", maxval);
return 0;
}
/***** 示例结果 *****/
Input two numbers:10 50
Max value: 50
>>返回AUTOSAR系列文章目录<<