---------------------------- C语言中的指针 ---------------------------
一、指针的作用
通过指针不仅可以对数据本身,还可以对存储数据变量的地址进行操作。指针就是内存地址,指针变量是用来存放内存地址的变量。
指针是一个占据存储空间的实体在这一段空间起始位置的相对距离值。在C/C 语言中,指针一般被认为是指针变量,指针变量的内容存储的是其指向的对象的首地址,指向的对象可以是变量(指针变量也是变量),数组,函数等占据存储空间的实体。
int 类型的整形变量数据存放格式如下:
所以:int *p = &a; 类型的整形指针变量存放的地址格式为
指针本身的地址: &p—》0x????????
0x???????存放的类容为:8C AF F2 10 (小端存放)
指针变量 定义:
存储类型 指向的数据类型 * 变量名 = 初始化值;
#include <stdio.h>
int main() {
int a = 10;
int *p = &a;
printf("Value of a: %d\n", a); // 输出:Value of a: 10
printf("Value of p: %p\n", *p); // 输出:Value of p: 10
printf("Address of a: %p\n", &a); // 输出:Address of a:0x7ffee2ca2a44
printf("Value of p: %p\n", p); // 输出:Value of p: 0x7ffee2ca2a44
return 0;
}
将变量 a 的地址赋值给指针 p,然后通过 p 访问变量 a 的值和地址。
指针的类型: 指针指向的存储空间的类型 *
取地址 运算 & 在变量前 使用&符号 可以得到变量的首地址
首地址 一个类型对应内存中 地址最小的那个字节
其他指针:
野指针: 一个指针 指向一个 不确定的地址或非法内存地址 (初始化时不赋任何地址)// int *p;
空指针: 一个指向 0 地址的指针 使用宏 NULL 表示 (void*)0
在C语言中 规定 0地址 不能访问
空指针 通常表示 该指针 不可用
void* : 表示一个 指针 但 其指向的地址 具体类型未知
二、数组与指针
一般系统或者编译器会分配连续地址的内存来存储数组的元素,如果把数组地址赋值给指针变量,就可以利用指针变量去访问数组里面的成员。 (指针变量可以当做数组用, 前提是不能越界操作 )
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *p = arr;
printf("Value of arr[0]: %d\n", arr[0]); // 输出:Value of arr[0]: 1
printf("Value of *p: %d\n", *p); // 输出:Value of *p: 1
printf("Value of *(p+1): %d\n", *(p + 1)); // 输出:Value of *(p+1): 2
return 0;
}
通过指针 p 可以访问数组 arr 中的元素。
指针自加自减运算
指针变量除了可以用来获取内存地址的值以外,还可以用来进行加减运算,那么这个加减呢跟普通变量加减不一样,普通变量加减的是数值,而指针变量加减的是地址。
int arr[5];
int *p = arr;//指针变量初始化时赋值
*(p+1);//等同于arr[1]
*(p+2);//等同于arr[2]
三、const关键字与指针的结合
3种情况:
1、const 修饰 *p :如下
const char *p; 表示指针变量p指向的地址中的数据是只可读的,不可以改变数据内容
2、const 修饰 p :如下
char *const p; 表示指针变量p指向的地址是只读的,不能改变指针的指向,但是可以改变地址中的类容
3、const 既修饰*p 也修饰p:如下
const char *const p; 表示指针变量指向的地址不可以改变,指向的地址空间的数据也不能变
注意:const用来修饰形参,表示形参在当前函数中只读, 形参仍然在栈区
四、指针数组 与 二级指针:
1、指针数组:一个数组 其元素是 指针
char *a[3] = {"hello","world","ok"}; //字符串指针数组
2、二级指针:一个指针变量 存放的 是 另一个指针变量的地址
char **p = a; //二级指针
列子:假设每个表达式中p=a;求表达式代表含义或值?
a[0] == 表示指向数组第一个元素“hello”的首地址
*p == 表示指向数组第一个元素“hello”的首地址
**p == 表示取出数组第一个元素“hello”首地址的值
*a == 表示指向数组第一个元素“hello”的首地址
**a == 表示取出数组第一个元素“hello”首地址的值
*p++ == 括号的优先级高于*号,所以先执行p++,在执行*p,内容为表示指向数组第一个元素“hello”的首地址,随后p指向数组第二个元素“world”的首地址
*(p++) == 表示指向数组第一个元素“hello”的首地址,随后p指向数组第二个元素“world”的首地址
*(*(p+1)+1) == p+1先指向数组第二个元素“world”的首地址,*(p+1)再指向数组第二个元素“world”的‘w’的地址,
*(p+1)+1再指向数组第二个元素“world”的‘o’的地址,最后*(*(p+1)+1)取出指向地址的类容o
#include <stdio.h>
int main() {
int a = 1, b = 2, c = 3;
int* arr[] = { &a, &b, &c }; // 指针数组
int** ptr = arr; // 二级指针
for (int i = 0; i < 3; i++) {
printf("Element %d: %d\n", i, **ptr);
ptr++; // 移动到下一个指针
}
return 0;
}
五、二维数组与 数组指针:
1、二维数组
int arr[3][5] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
2、数组指针: 本质是一个指针,指向一个数组
int (*arrp)[5] = arr;
使用*(*(arrp+0) + 0) 可以访问二维数组的第一行第一个元素
列子:假设每个表达式中arrp=arr;求表达的类型?
表达式 值 类型
arr =0x601060 int [3][5]
arrp =0x601060 int(*)[5]
arr[0] =0x601060 int [5]
&arr[0] =0x601060 int(*)[5]
*arrp =0x601060 int [5]
#include <stdio.h>
int main() {
int arr[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int (*ptr)[4]; // 数组指针
ptr = arr; // 指向二维数组的首地址
// 使用数组指针访问二维数组元素
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
printf("%d ", *(*(ptr + i) + j));
}
printf("\n");
}
return 0;
}
在上述代码中,我们声明了一个数组指针 ptr,它指向包含4个整型元素的一维数组。
然后,我们将指针 ptr 指向二维数组 arr 的首地址。接下来,通过使用数组指针和指针运算符 * 和数组下标,
我们可以访问二维数组中的元素。
打印的结果:
1 2 3 4
5 6 7 8
9 10 11 12
六、指针与函数
1、指针函数:实际上是一个函数,返回值是一个指针类型数据
定义指针函数 注意 : 返回指针不能指向自己函数的局部变量**
char* strcpy(char *dest, const char *src);
//实际是个函数,返回的值是一个char*类型的变量2、函数指针: 实际是一个指针,指向的是一个函数
void (*fp)(int) = func; //函数指针
多用于回调函数如下:
typedef void (*KeyValue)(int values);
这是一个底层驱动用于给应用层函数使用的接口函数
定义一个 KeyValue pCBS;
void KeyScanfRegister(KeyValue){
if(pCBS == 0){
pCBS = KeyValue;
}
}
这样就可以在应用层写出一个与void (Key)(int values){};
这个函数放入KeyScanfRegister(Key);就可以实现回调函数的作用(可以提高项目的层次和移植性)
七、函数指针数组
本质是一个数组,其元素是函数指针
函数返回值类型 ( *指针变量名[数组大小] ) ( 函数参数列表);
例子:
假如现在有N个LED;
enum
{
LED1,
LED2,
LED3,
LEDSUM
};
unsigned char Led1Driver(unsigned char std);
unsigned char Led2Driver(unsigned char std);
unsigned char Led3Driver(unsigned char std);
//定义一个函数指针数组
unsigned char (*LedDrivers[LEDSUM])(unsigned char) = {
Led1Driver,
Led2Driver,
Led3Driver,
};
int main()
{
//可以这样调用LED的接口函数
LedDrivers[0](1);//表示将第一个灯点亮
LedDrivers[1](1);//表示将第二个灯点亮
LedDrivers[2](1);//表示将第三个灯点亮
}
unsigned char Led1Driver(unsigned char std)
{
if(sta == 1)
{
Led = 1;
}
else
{
Led = 0;
}
}
unsigned char Led2Driver(unsigned char std)
{
if(sta == 1)
{
Led = 1;
}
else
{
Led = 0;
}
}
unsigned char Led3Driver(unsigned char std)
{
if(sta == 1)
{
Led = 1;
}
else
{
Led = 0;
}
}