AT指令/va_start/va_arg/va_end/vsprintf 使用說明(C語言)

一、簡介

本文介紹C語言一些字符串處理函數,可以實現GPRS的指令發送。


二、實驗平臺

 1:VS2013

 2:如果需要本實驗相關工程,請加羣進行下載。

版權聲明

博主:si_zhou_qun_84342712

聲明:喝水不忘挖井人,轉載請註明出處。

原文地址:

聯繫方式:[email protected]

四軸開源羣:84342712

四軸開源(淘寶店):

基礎知識

void Uart_Printf(char *fmt,...) //...表示可變參數(多個可變參數組成一個列表,後面有專門的指針指向他),不限定個數和類型,
{
  va_list ap;//初始化指向可變參數列表的指針
  char string[256];
  
  va_start(ap,fmt);//將第一個可變參數的地址付給ap,即ap指向可變參數列表的開始
  vsprintf(string,fmt,ap);//將參數fmt、ap指向的可變參數一起轉換成格式化字符串,放string數組中,其作用同sprintf,只是參數類型不同
  Uart_SendString(string); //把格式化字符串從開發板串口送出去
  va_end(ap); //ap付值爲0,沒什麼實際用處,主要是爲程序健壯性
}

(1)格式化字符串

printf(“%s, %-19s: %6.2”,lastname,firstname,prize);

打印結果:Bechr,Teddy ;2000.00

我們說“ Bechr,Teddy ;2000.00 ”就是一個格式化字符串,printf的作用就是把(“%s, %-19s: 6.2”,lastname,firstname,prize)翻譯成電腦認識的字符串,而對於“%s, %-19s: 6.2”,lastname,firstname,prize電腦根部不認識,故需要printf翻譯。

(2)vsprintf

函數名: vsprintf
功 能: 送格式化輸出串到指定數組中
用 法: int vsprintf(char *string, char *format, va_list param);
程序例:
----------------------------------------------------------------
#include <stdio.h>
#include <conio.h>
#include <stdarg.h>

char buffer[80];

int vspf(char *fmt, ...)
{
  va_list argptr;
  int cnt;
  
  va_start(argptr, fmt);
  cnt = vsprintf(buffer, fmt, argptr);
  va_end(argptr);
  
  return(cnt);
}

int main(void)
{
  int inumber = 30;
  float fnumber = 90.0;
  char string[4] = "abc";
  
  vspf("%d %f %s", inumber, fnumber, string);
  printf("%s\n", buffer);
  return 0;
}
------------------------------------------------------------------------

vsprintf與sprintf功能是一樣的,即把格式化字符串輸出到指定數組中,sprintf(char *string, char *farmat [,argument,...])函數的參數從第二個參數開始與printf是一樣的,只是sprintf是輸出到指定數組中,printf是輸出到屏幕(一個標準輸出文件),因而sprintf多了char *string這參數。

(3)[轉]va_start va_arg va_end 的使用和原理

stdarg.h中查到如下宏定義:

typedef int *va_list[1];//va_list 變量類型定義
#define va_start(ap, parmN) (void)(*(ap) = __va_start(parmN))
#define va_arg(ap, type) __va_arg(*(ap), type)//取可變參數的內容
#define va_end(ap) ((void)(*(ap) = 0))

◎用法:
func( Type para1, Type para2, Type para3, ... )
{
/****** Step 1 ******/
va_list ap;
va_start( ap, para3 ); //一定要“...”之前的那個參數

/****** Step 2 ******/
//此時ap指向第一個可變參數
//調用va_arg取得裏面的值

Type xx = va_arg( ap, Type );

//Type一定要相同,如:
//char *p = va_arg( ap, char *);
//int i = va_arg( ap, int );

//如果有多個參數繼續調用va_arg,ap會自動變化(一般是自增)

/****** Step 3 ******/
va_end(ap); //For robust!
}

◎研究:(此處的宏定義與前面有些差別可能是OS不同,不用管它,只要能幫助我們理解即可)
typedef char * va_list;

#define va_start _crt_va_start
#define va_arg _crt_va_arg
#define va_end _crt_va_end

#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )//注意(ap += _INTSIZEOF(t))括號
#define _crt_va_end(ap) ( ap = (va_list)0 )
va_list argptr;
C語言的函數的參數是從右向左壓入堆棧的,調用va_start後,按定義的宏運算,_ADDRESSOF得到v所在的地址,然後這個地址加上v的大小,則使ap指向第一個可變參數如圖: 

棧底 高地址
| ....... 
| 函數返回地址
| ....... 
| 函數最後一個參數
| .... 
| 函數第一個可變參數 <--va_start後ap指向
| 函數最後一個固定參數
| 函數第一個固定參數
棧頂 低地址


然後,用va_arg()取得類型t的可變參數值, 先是讓ap指向下一個參數:
ap += _INTSIZEOF(t),然後在減去_INTSIZEOF(t),使得表達式結果爲
ap之前的值,即當前需要得到的參數的地址,強制轉換成指向此參數的
類型的指針,然後用*取值

最後,用va_end(ap),給ap初始化,保持健壯性。
 
小結: 
可變參數的函數原理其實很簡單,而va系列是以宏定義來定義的,實現跟堆棧相關.我們寫一個可變函數的C函數時,有利也有弊,所以在不必要的場合,我們無需用到可變參數.如果在C++裏,我們應該利用C++的多態性來實現可變參數的功能,儘量避免用C語言的方式來實現.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章