目录
前言
一、指针是什么
二、指针与数组
数组指针
指针数组
三、指针与常量
指针常量
常量指针
四、指针与函数
指针函数
函数指针
前言
指针是C语言中大家接触的比较早但是也是内容比较多和实用的一块知识点,之前虽然也大概知道是什么用途,但是指针衍生出来的内容还是比较容易混淆的,这次在7哥的激励下写篇博客总结一下指针方面易混淆的内容,如有错误欢迎指正。
一、指针是什么
指针变量其实就是存放地址的变量,我们知道变量具有三部分信息,即
- 变量对应的名称
- 所表示的数据类型
- 存放的值。
大家都知道指针变量存放的是一个内存地址,但是有时候却忽略了指针类型的重要性,根据存放的地址值我们可以知道要从什么位置开始读取数据,而类型信息则向系统补充说明了当指针移动时进行读写的长度,确定我们要读取到什么位置结束。
因为指针变量是存放地址的变量,所以一个指针变量所占的地址空间是固定的,是根据我们程序的寻址空间来确定的(比如寻址空间 4GB = 2^32 Byte,也就是32位的CPU可以访问的最大内存空间,字节在计算机中是最小的可寻址单位,所以用 4 个字节即 2^32 bits 表示指针就已经能指向任何内存空间了,同理64位系统默认指针大小为8个字节。),所以指针的大小根据不同的操作系统为4个或8个字节。
上面说明的是指针变量本身的所占的空间大小,而指针变量的值,也就是存放的地址信息,本质上都是一段地址而已,而指针的类型则确定了要将地址中的值按照 int 型变量还是 double 型等不同的数据类型变量进行解释。
那么了解了指针,接下来要面对的就是 数组指针 指针数组 指针常量 常量指针 函数指针 指针函数这些牛鬼蛇神,虽然名字看起来很唬人非常容易混淆,接下来将一一进行说明,先总结一下它们各自的定义如下:
常见指针变量的定义 |
---|
定义 | 含义 | |
int i | 定义整形变量 | |
int *p | 定义一个指向int的指针变量 | |
int *const p | 定义一个指向int的指针常量 | 指针常量 |
int const * p const int * p | 定义一个指向int的只读指针 | 常量指针 |
int a[10] | 定义一个int数组 | |
int *p[10] | 定义一个指针数组,其中每个数组元素指向一个int型变量的地址 | 指针数组 |
int (*p)[10] | 定义一个数组指针,指向int [10]类型的指针变量 | 数组指针 |
int func( ) | 定义一个函数,返回值为int型 | |
int *func( ) | 定义一个函数,返回值为int *型 | 指针函数 |
int (*p)( ) | 定义一个指向函数的指针,函数的原型为无参数,返回值为int | 函数指针 |
int **p | 定义一个指向int的指针的指针,二级指针 | |
二、指针与数组
C语言规定,数组名代表数组中首个元素(即序号为 0 的元素)的地址,也就是说,如果 s 是一个数组,那么表达式 s 的值就与 s[0] 的地址,即 &s[0] 一致。所以当我们执行赋值语句 p = &s[0] 时,指针变量 p 与数组名 s 具有相同的值,可以写作 p = s ,指针 p 的行为就和数组 s 本身一样。p 在这里指向的是数组的首元素。
int s[3]={1,2,3};
int *p
p = &s[0];
数组指针
数组指针就是指向数组的指针,声明形式为 类型(*变量名)[] 。
因为数组下标 [] 的优先级高于取值运算符 * ,所以必须用括号()将 * 与 arrPtr 先结合构成指针定义,然后指向一个数组。
第一种情况(指向二维数组名):
int a[4][10];
int (*p)[10];
p = a;
与一维数组的数组名是一个指针相似,二维数组的数组名是一个数组指针,若将二维数组名 a 赋值给数组指针 p ,则可以通过 p 直接控制数组 a 。
第二种情况(指向一维数组名的地址):
int a[10];
int (*p)[10] = &a; //此处是&a,不是a, &a的类型是int(*)[]
int *q = a;
p 与 q 虽然都指向数组的第一个元素,但由于 p 的类型和 q 的类型不同 ,数组指针 p 是指向10个元素整形数组的指针, *p 的大小是40个字节,所以 p+1 跳过40个字节;q 是指向整形的指针,*q 的大小是4个字节,所以 q+1 跳过4个字节。
指针数组
int * arrPtr[10]; //声明一个指针数组arrPtr,该数组有10个int类型的指针
元素为指针类型的数组,声明形式为 类型名 * 数组名[数组长度] 。
由于指针也是变量,所以指针也可以像其他变量一样存储在数组中。指针数组的每一个元素都是一个指针类型的数据,即元素的值为地址,可以指向某一个字符串常量。
指针数组最频繁的用处是存放具有不同长度的字符串,作为二维数组的一种便捷替代方式,适合用来存放多个字符串作为字符串数组来使用。
我们在遇到需要处理多个字符串的情况时,可以选择将它们存储在一个二维数组中,二维数组的元素 —— 一维数组 的大小必须足够存储下可能出现的最长字符串,如下表示:
#define ARRAY_LEN 12
#define STRLEN_MAX 25
char pStrings[ARRAY_LEN][STRLEN_MAX] =
{
"It's a wonderful day!",
"do you like it?",
"Yes"
};
for(i=0;i<ARRAY_LEN;i++)
{
puts(pstr[i]);
}
但是,这个方式造成内存浪费,一方面,短字符串会让大部分的行是空的,另一方面,有些行根本没有使用到,但却得为它预留内存,使得分配的12*25=300 个字节只有一小部分被实际使用到。
对于这个问题,一个简单的解决方案就是使用指针数组,让指针指向字符串,然后只给实际存在的对象分配内存,未用到的数组元素则默认是空指针。
#define ARRAY_LEN 12
char *pStrings[ARRAY_LEN] =
{
"It's a wonderful day!",
"do you like it?",
"Yes"
};
指针数组让各个指针内容可以按需要动态生成,避免了空间浪费,并且各个指针呈数组形式排列,索引起来非常方便。
指针数组另一个常见作用是作为main函数的形式参数:
int main(int argc, char * argv[]);
argc 和 argv 主要用途为程序运行时,将命令行中的输入参数传递给调用函数。char * argv[] 是一个存储字符串的指针数组,会以字符串的形式保存用户调用程序时传入的参数,存放着指向每一个参数的指针。
三、指针与常量
const 是一个C语言的类型限定符,任何变量只要其类型限定符为 const ,则表示对于这些变量,我们无法在后续的程序中修改其对应或指针指向的值。因此,我们常称它们为常量,但其实应该说是个“只读变量”而非常量,只读变量和常量的共同点在于它们的值在第一次出现时便被确定,且无法在后续程序中被修改。但是只读变量和常量的一个最重要的不同点在于,要注意使用 const 修饰的只读变量不具有“常量表达式”的属性,因此无法用来表示定长数组大小,或者是使用在case语句中。常量表达式本身会在程序编译时被求知,而只读变量的值只能够在程序实际运行时才被得知。所以这边大家要知道他们之间是有一定的区别的,但是下面仍用常量来指代只读变量。
#include <stdio.h>
int main(void)
{
const int temp = 10;
int a;
scanf("%d",&a);
int s[temp] = {1,2,3} /* 错误,使用非常量表达式定义定长数组 */
switch(a){
case temp: printf("you have a trouble"); break; /* 错误:非常量表达式应用于case语句 */
}
}
const 限定一个变量不允许被改变,使用在指针变量中可以用来限定指针本身的类型,或者是限定指针所指对象的类型,分别对应着我们的指针常量和常量指针。
指针常量
int * const p;
直译是指针类型的常量,联系我们刚刚提到的“只读变量”,我们可以理解成指针类型的“只读变量”,它是一个指针变量但是这个变量是“只读变量”。
而变量是只读的说明了指针其本身存放的内容(值)也就是指向的位置(地址)不能改变,指针本身是一个常量,但是指针所指向的内容(值)是不受限制可以发生改变的。
int a = 1, b = 2;
int * const p = &a; //指针常量
*p = 3; // 可以赋值,指针指向的内容可以修改
p = &b; /* 错误,指针常量本身的内容不可改变,其值即指向的地址是固定的 */
常量指针
//两种方式都可
const int *p = &a;
int const *p = &a;
直译是指向常量的指针,同样的我们可以理解成指向“只读变量”的指针,首先是一个指针,其次这个指针是一个指向“只读变量”(具有const限定的变量)的指针,所以可以修改该指针本身的值,但是只能使用这样的指针来读取所指向的对象,不能修改所指向的对象,因此指向常量的指针也常常被称为“只读指针”。
int a = 1, b = 2;
const int *p1 = &a; //常量指针
p = &b; // 可以赋值,因为指针本身是变量,其本身存放的内容即指向的地址是可以改变的
*p = 3; /* 错误,不能修改指向的对象内容 */
四、指针与函数
指针函数
int * function(int x);
返回指针类型的函数,我们知道一个函数的返回值可以是各种数据类型的,我们常见的是整型的当然也可以是返回的指针类型的数据,即返回一个地址。
函数指针
指向函数的指针,声明形式为 函数返回值类型名(* 指针变量名)(函数参数列表)。
与数组名代表数组首个元素地址类似,函数名会被隐式的转换成函数指针,可以说函数名本身就可以表示该函数的入口地址,赋值语句 p = function 将函数function()的地址赋值给指针变量 p ,然后就可以利用该指针调用这个函数。
int function(int a, int b);//假设这里定义了一个函数
int (* p)(int, int); //声明一个函数指针,该函数具有两个int类型的参数和int类型的返回值
p = function; //把函数名赋值给函数指针 *p
int ret = function(10,100);//调用函数的方式1:通过函数名直接调用
int ret2 = (* p)(10,100); //调用函数的方式2:通过函数指针调用
函数指针的定义其实就是将“函数声明”中的“函数名”改成“(*指针变量名)”。但是这里需要注意的是:“(*指针变量名)”两端的括号不能省略,括号改变了运算符的优先级。如果省略了括号,就不是定义函数指针而是一个返回值类型为指针型的函数声明了。
函数指针变量既然是一个变量,当然也可以作为某个函数的参数来使用的,这就是我们函数指针的一个常见用法:回调函数
如果把函数指针作为参数传递给另一个函数,当这个函数指针被用来调用其所指向的函数时,就是一个回调函数。它使得用户可以把需要调用的方法的指针作为参数传递给一个函数,我们只要改变传进另一个函数的参数,就能够让另外这个函数实现不同的功能,相比普通的函数调用灵活很多。
int Callback_1(int a) ///< 回调函数1
{
printf("Hello, this is Callback_1: a = %d ", a);
return 0;
}
int Callback_2(int b) ///< 回调函数2
{
printf("Hello, this is Callback_2: b = %d ", b);
return 0;
}
int Callback_3(int c) ///< 回调函数3
{
printf("Hello, this is Callback_3: c = %d ", c);
return 0;
}
int Handle(int x, int (*Callback)(int)) ///< 注意这里用到的函数指针定义
{
Callback(x);
}
int main()
{
Handle(4, Callback_1);
Handle(5, Callback_2);
Handle(6, Callback_3);
return 0;
}
参考链接:
C语言-数组指针,指针数组与数组名的指针操作
C语言回调函数详解(全网最全)_小熊coder的博客-CSDN博客_c语言回调函数
C 语言中的指针与数组
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)