C语言指针应用
指针介绍
指针含义介绍的文章很多,这里不多做赘叙,一言而概之就是存储一个地址的变量,存储的地址所指向的内存内可以存储任何信息,如一个整数(整数指针)、一个函数(函数指针)或者另一个指针(多重指针)。
话不多说,直接上干货。
指针的常见使用场景
下面将通过实际的代码来说明一些指针的常用场景。
1.函数内部需要修改参数的值
大家都应该知道函数传递的参数是一个拷贝,并不是其本身,如果在函数内部想要修改函数外部的变量值,就需要用到指针;这也是指针最常用的使用方法,函数内部需要修改外部值时需要传递外部参数的指针给到函数,最常见的例子为C标准库的scanf函数:
int scanf(const char* format, ...);
#include <stdio.h>
int mian(int argc, char* argv[])
{
int number;
printf("Plase enter a nember:\n");
if(scanf("%d", &number) == 1){
printf("The number is %d\n", number);
}
else{
printf("Enter warning\n");
}
return 0;
}
2.函数形参传递大结构体数据
形参传递时,会在栈空间里分配一段空间给形参,对传递的参数进行一次拷贝,如果我们需要传递的参数是一个占用空间较大的结构体时,如果直接传递值,就会比较耗费时间。这时通过指针,将我们要传递结构体参数的指针传递给被调函数,则整个过程只需要拷贝一个指针变量,函数内部就可以访问数据。为了和1区分功能,且防止参数内容被错误的篡改,可以使用const关键字标识:
struct t{
int a;
int buf[10];
struct{
int buf[20];
int b;
}str_info;
};
int process_t(const struct* t);
3. 快速寻址结构深度较深的结构体成员
对于结构深度较深且使用比较频繁的结构体成员,可以对于的指针变量直接指向该成员,一定程度上可以优化程序效率,但更重要的是减少代码冗余,毕竟程序是给人看的,机器码才是给电脑看的。
struct t{
int a;
int buf[10];
struct{
int buf2[20];
int b;
}str_info;
};
void print_buf2(const struct t* pt)
{
const int* buf = pt->str_info.buf2;
for(int i = 0; i < sizeof(pt->str_info.buf2) / sizeof(int); ++i)
{
printf("%d ", buf[i]);
}
}
4. 数据转换
完成一些用联合体处理的事情,将一个数据转换成其他类型的数据进行访问。
#include <stdio.h>
#include <stdint.h>
void print_hex(const void* buf, size_t len)
{
uint8_t* data = (uint8_t*)buf;
for(int i = 0; i < len; ++i)
{
printf(" %02x", data[i]);
}
}
struct data_item_t{
uint16_t len;
uint8_t data[1]
};
int main(int argc, char* argv[])
{
char* a = "abcdefg";
uint32_t test = 0x12345678;
uint8_t data_buf[128];
struct data_item_t* data_itme = (struct data_item_t*)data_buf;
data_itme->len = sizeof(data_buf) - sizeof(uint16_t);
memset(data_itme->data, 0xFF, data_itme->len);
print_hex(a, strlen(a));
print_hex(&test, sizeof(test));
print_hex(data_itme, data_itme->len + sizeof(uint16_t));
}
5. 优化迭代性能
有过库函数源码阅读经验的人,应该都可以发现很多库函数在迭代的时候都会使用指针来进行迭代,而不是使用一个index来进行处理。这样处理每次循环至少都会减少一次变址处理,有利于函数效益的提升。这里同样按照指针的方式来进行迭代重写了上面的print_hex函数。
void print_hex(const void* buf, size_t len)
{
const uint8_t* start = (const uint8_t*)buf, *end = start + len;
for(; start != end; ++start)
{
printf(" %02x", *start);
}
}
6. 链表
基操,通过指针将数据连接成链状结构,对不连续的数据进行链状的访问。这里就不再添加代码段说明了。
7. 函数指针做调用表
使用函数指针,对相同接口类型进行访问。
struct cmd_fuc{
const char* pair_str;
tBool (*handler)(const char *at_cmd, char *cmd_line);
};
static tBool AT_GetVer_Patch(const char *at_cmd, char *cmd_line);
const struct cmd_fuc g_cmd_list[] =
{
{"GVER", AT_GetVer_Patch},
};
void AT_proces(char* cmd_line)
{
const struct cmd_fuc *cmd_func = g_cmd_list, *end_func = g_cmd_list + sizeof(g_cmd_list)/sizeof(struct cmd_fuc);
for(; cmd_func != end_func; ++cmd_func){
if(memcmp(cmd_line, cmd_func->pair_str, strlen(cmd_func->pair_str)) == 0){
tBool ret = cmd_func->handler(cmd_func->pair_str, cmd_line + strlen(cmd_func->pair_str));
printf(ret ? "OK\r\n" : "ERROR\r\n");
break;
}
}
if(cmd_func == end_func){
printf("ERROR\r\n");
}
}
8. 函数指针实现多态
利用函数指针可以修改的特性,对于一类数据,设计好接口。然后根据实际实现来为接口的函数指针赋值,一套代码,多处使用。
struct{
uint32_t (*data_store)(uint32_t offset, const uint8_t* data, uint32_t len);
uint32_t (*data_load)(uint32_t offset, uint8_t* data, uint32_t len);
}data_store_interface;
enum STORE_TYPE{
TYPE_STORE_FLASH_IN = 1,
TYPE_STORE_FLASH_EX,
TYPE_STORE_EEPROM
};
uint32_t flash_data_store_in(uint32_t offset, const uint8_t* data, uint32_t len);
uint32_t flash_data_load_in(uint32_t offset, uint8_t* data, uint32_t len);
uint32_t flash_data_store_ex(uint32_t offset, const uint8_t* data, uint32_t len);
uint32_t flash_data_load_ex(uint32_t offset, uint8_t* data, uint32_t len);
uint32_t eeprom__data_store(uint32_t offset, const uint8_t* data, uint32_t len);
uint32_t eeprom_data_load(uint32_t offset, uint8_t* data, uint32_t len);
void data_store_interface_init(enum STORE_TYPE type){
switch(type){
case TYPE_STORE_FLASH_IN:
data_store_interface.data_store = flash_data_store_in;
data_store_interface.data_store = flash_data_load_in;
break;
case TYPE_STORE_FLASH_EX:
data_store_interface.data_store = flash_data_store_ex;
data_store_interface.data_store = flash_data_load_ex;
break;
case TYPE_STORE_EEPROM:
data_store_interface.data_store = eeprom_store;
data_store_interface.data_store = eeprom_data_load;
break;
default:
data_store_interface.data_store = NULL;
data_store_interface.data_store = NULL;
break;
}
}
9. 函数指针传递回调函数
将自身设计的接口传递通过函数指针的方式给一个模块,改模块在特定时候(完成某一个动作)会通过函数指针调用该函数,对应用模块进行通知。在异步架构中经常用到。
10. 空指针传递任意自定义参数
如软件定时器等,设计之初都会在接口中添加一个空指针作为创建定时器的一个参数,用于传递一些用户自定义的参数,回调的时候通过参数再将该指针传递给到回调函数。该策略可以很大程度上减少全局变量的使用。如如下的设计:
typedef void (*simple_timer_cb_t)(uint16_t id, void* arg);
uint16_t simple_timer_set(uint32_t, simple_timer_cb_t, void*);