实战详解:二级指针 /void 指针 /volatile 指针 / 数组指针的使用
一、指向指针变量的指针
1.二级指针的定义:
二级指针是指向一级指针的指针
2.使用场景
(1)函数体内想要修改函数体外指针变量值的时候传指针变量的地址即二级指针;
(2)指针数组传参时,数组的数组名是指向第一个指针元素的指针即二级指针
这里我用图示结合代码说明:

二、void指针
表示内存地址通常用作函数传参和函数返回值
void指针只有内存地址的含义,没有指向空间大小的含义
void a; //不能这样定义,不知道变量a所占的字节
void *p; //这样可定义,p指针变量占8个字节
- 一般不用于算数运算或*操作;
- 传参时兼容不同的指针类型;
- char*,int*,double*转换为void*时需要强制类型转换;
- void*转换为char*,int*,double*时不需要强制类型转换
示例:
将0x12345678这个地址赋值为100:
*((int*)(void*)0x12345678) = 100
0x12345678 这是一个十六进制的内存地址值,代表要操作的目标内存位置。
(void*)0x12345678:把这个数值转换成void*类型。void*只保存内存地址,不关心指向的是什么类型的数据。
(int*)((void*)0x12345678):再把void*转换成int*类型。只有明确了指针类型,编译器才知道该地址处要读写多少字节(这里是 4 字节,对应int类型)。
*((int*)((void*)0x12345678)):对int*指针做解引用操作,就可以直接操作这个地址里的数据了。
= 100:把整数100写入到0x12345678这个内存地址中。
三、volatile指针
volatile关键字的作用:
- 常用于搭配指针或者变量使用,表示变量或指针指向的空间中的数据是容易被改变的
- 防止编译器优化代码,每次读写变量都是到内存中去存取变量的值
volatile int num;
//定义了一个 volatile 修饰的整型变量。//编译器不会对 num 的读写做优化。
//即使代码中连续读取 num,每次都会从内存中重新获取最新值。
volatile int *paddr;
//定义了一个指向 volatile int 类型的指针。//表示指针 paddr 指向的内存地址中的数据可能随时被外部改变。
num = 100; 与 num += 200;
//如果没有 volatile,编译器可能会把这两步优化为直接计算 num = 300。
//加上 volatile 后,编译器会严格执行“先赋值 100,再累加 200”的两步操作,每次都从内存中读写。
四、数组指针与指针数组
1.概念
数组指针是指针,指针指向整个数组;
指针数组是数组,数组的每个元素都是指针
2.指针数组
(1)初始化:
int *a[5] = {NULL};
(2) 定义数组a,共5个元素,每个元素都是int*的指针,数组名为a,占5*8 = 40个字节空间
(3) 数组用来存字符串,字符型的指针数组用来操作字符串
(4)一维字符型数组和字符串关系
- 字符型数组:存放字符串
- 二维字符型数组:存放字符串数组;
- 指针:操作字符串;
- 指针数组:操作字符串数组
示例:接收字符串,使用字符串指针数组排序
#include<stdio.h>
int main(void)
{
char [5][32] = {0};
char *pstr[5] = {str[0],str[1],str[2],str[3],str[4]};
int i = 0;
int j = 0;
int *ptmp = NULL;
gets(pstr[i]);
for(j = 0; j < 4; j++)
{
for(i = 0; i < 4-j; i++)
{
if(strcmp(pstr[i],pstr[i+1]) > 0)
{
ptmp = pstr[i];
pstr[i] = pstr[i+1];
pstr[i+1] = ptmp;
}
}
}
for(i = 0; i < 5; i++)
{
printf("pstr[%d] = %d\n", i, pstr[i]);
}
return 0;
}
3.数组指针
数组指针是指针,有8个字节空间,指向一个数组;
int (*p)[5];
int a[5] = {1,2,3,4,5};
数组的数组名是指向数组第一个元素的指针,可以理解为int*型,但以下两种情况除外:
sizeof时:sizeof(a) == 20 ,sizeof(int*) == 8
& 时:&a == int (*)[5] & int * == int **
- 对一维数组的数组名取&,值不变,类型升级为指向整个数组的数组指针;
- 对数组指针取*, 值不变,类型降级为指向数组第一个元素的指针;
4.二维数组传参
(1)
int a[2][3] = {1, 2, 3, 4, 5, 6};
int *p = NULL;
int (*q)[3] = a;
p = &a[0][0];
p = a[0];
p = *a;
第m行第n列元素的访问方式:
a[m][n]
*(a[m]+n)
*(*(a+m)+n)
*(p+m*N+n)
*(*(q+m)+n)
*(q[m]+n)
q[m][n]
- 二维数组的数组名a是指向数组第一行元素的首地址的数组指针;
- 对数组指针取*,值不变,类型降级为指向数组第一个元素的指针
(2)二维数组的本质:
可以看作是“数组的数组”
int a[2][3]; //a的类型是int (*)[3](指向包含3个int元素的一维数组的指针)。
数组名的含义:
- a 代表整个二维数组的首地址,类型为int (*)[3]。
- a[0] 代表第一行一维数组的首地址,类型为int *。
- a[0][0] 代表具体的数组元素,类型为int。
图示:

示例:

5.指针数组的传参

// 1. 定义一个指针数组,包含5个int*类型的指针,初始值全为NULL
int *a[5] = {NULL};
// 2. 函数声明:形参pa是指向int*类型的指针(即int**),len是数组长度
int fun(int **pa, int len);
// 3. 函数调用:将指针数组a的首地址传给pa,传入长度5
fun(a, 5);
- 指针数组a的类型:int *[5],数组里的每个元素都是int*类型的指针。
- 数组名a的含义:作为函数参数传递时,a会退化为指向数组第一个元素的指针,也就是int**类型(因为第一个元素是int*,指向它的指针就是int**)。
- 形参匹配:函数fun的形参int **pa正好接收这个int**类型的地址,这是合法且正确的传参方式
示例:接收四个字符串,封装函数,从大到小排序
#include<stdio.h>
#include<string.h>
void InputArray(char **ppstr, int len)
{
int i = 0;
for(i = 0; i < len; i++)
{
gets(ppstr[i]);
}
printf("\n");
return;
}
void OutputArray(char **ppstr, int len)
{
int i = 0;
for(i = 0; i < len; i++)
{
printf("%s\n", ppstr[i]);
}
return;
}
void BubbleSortArray(char **ppstr, int len)
{
int i = 0;
int j = 0;
char *ptmp = NULL;
for(j = 0; j < len-1; j++)
{
for(i = 0; i < len-1-j; i++)
{
if(strcmp(ppstr[i],ppstr[i+1]) > 0)
{
ptmp = ppstr[i];
ppstr[i] = ppstr[i+1];
ppstr[i+1] = ptmp;
}
}
}
return;
}
int main(void)
{
char str[5][32] = {0};
char *pstr[5] = {str[0], str[1], str[2], str[3],str[4]};
InputArray(pstr, 5);
BubbleSortArray(pstr, 5);
OutputArray(pstr, 5);
return 0;
}
更多推荐




所有评论(0)