C语言 指针中的常见名称与用法

目录

前言

一、指针是什么

二、指针与数组

数组指针

指针数组

三、指针与常量

指针常量

常量指针

四、指针与函数

指针函数

函数指针


前言

指针是C语言中大家接触的比较早但是也是内容比较多和实用的一块知识点,之前虽然也大概知道是什么用途,但是指针衍生出来的内容还是比较容易混淆的,这次在7哥的激励下写篇博客总结一下指针方面易混淆的内容,如有错误欢迎指正。


一、指针是什么

指针变量其实就是存放地址的变量,我们知道变量具有三部分信息,即

  1. 变量对应的名称
  2. 所表示的数据类型
  3. 存放的值。


大家都知道指针变量存放的是一个内存地址,但是有时候却忽略了指针类型的重要性,根据存放的地址值我们可以知道要从什么位置开始读取数据,而类型信息则向系统补充说明了当指针移动时进行读写的长度,确定我们要读取到什么位置结束。

因为指针变量是存放地址的变量,所以一个指针变量所占的地址空间是固定的,是根据我们程序的寻址空间来确定的(比如寻址空间 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

3ab4e13b406c408497d6870a8c3cf889.png

第二种情况(指向一维数组名的地址):

int a[10];  
int (*p)[10] = &a; //此处是&a,不是a, &a的类型是int(*)[]  
int *q = a;

8a07ee1ff3474180bd8b7f3a7b42affa.png

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 语言中的指针与数组

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值