C语言printf函数实现解读

这篇具有很好参考价值的文章主要介绍了C语言printf函数实现解读。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1 源码下载

gun官网链接
c语言printf源码,C语言,c语言,开发语言,后端,开源



c语言printf源码,C语言,c语言,开发语言,后端,开源


c语言printf源码,C语言,c语言,开发语言,后端,开源



c语言printf源码,C语言,c语言,开发语言,后端,开源



c语言printf源码,C语言,c语言,开发语言,后端,开源

按照这些步骤可以顺利的下载gnu的c语言源码,接下去我们看看printf函数。




2 printf函数源码

用vscode打开下载的源码,找到printf函数。
printf 函数路径为:glibc-2.36\stdio-common\printf.c )
c语言printf源码,C语言,c语言,开发语言,后端,开源
源码如下:

int
__printf (const char *format, ...)
{
  va_list arg;
  int done;

  va_start (arg, format);
  done = __vfprintf_internal (stdout, format, arg, 0);
  va_end (arg);

  return done;
}

#undef _IO_printf
ldbl_strong_alias (__printf, printf);
ldbl_strong_alias (__printf, _IO_printf);

可以看见主要是四个东西:va_list va_startva_end__vfprintf_internal
前面三个先不看,后面重点介绍,先看看能不能看懂__vfprintf_internal 这个函数的实现。

c语言printf源码,C语言,c语言,开发语言,后端,开源

glibc-2.36\stdio-common\vfprintf-internal.c 里可以看到这个函数实际上就是vfprintf函数, 也在相同的.c文件中

c语言printf源码,C语言,c语言,开发语言,后端,开源

源码如下:

int
vfprintf (FILE *s, const CHAR_T *format, va_list ap, unsigned int mode_flags)
{
  /* The character used as thousands separator.  */
  THOUSANDS_SEP_T thousands_sep = 0;

  /* The string describing the size of groups of digits.  */
  const char *grouping;

  /* Place to accumulate the result.  */
  int done;

  /* Current character in format string.  */
  const UCHAR_T *f;

  /* End of leading constant string.  */
  const UCHAR_T *lead_str_end;

  /* Points to next format specifier.  */
  const UCHAR_T *end_of_spec;

  /* Buffer intermediate results.  */
  CHAR_T work_buffer[WORK_BUFFER_SIZE];
  CHAR_T *workend;

  /* We have to save the original argument pointer.  */
  va_list ap_save;

  /* Count number of specifiers we already processed.  */
  int nspecs_done;

  /* For the %m format we may need the current `errno' value.  */
  int save_errno = errno;

  /* 1 if format is in read-only memory, -1 if it is in writable memory,
     0 if unknown.  */
  int readonly_format = 0;

  /* Orient the stream.  */
#ifdef ORIENT
  ORIENT;
#endif

  /* Sanity check of arguments.  */
  ARGCHECK (s, format);

#ifdef ORIENT
  /* Check for correct orientation.  */
  if (_IO_vtable_offset (s) == 0
      && _IO_fwide (s, sizeof (CHAR_T) == 1 ? -1 : 1)
      != (sizeof (CHAR_T) == 1 ? -1 : 1))
    /* The stream is already oriented otherwise.  */
    return EOF;
#endif

  if (UNBUFFERED_P (s))
    /* Use a helper function which will allocate a local temporary buffer
       for the stream and then call us again.  */
    return buffered_vfprintf (s, format, ap, mode_flags);

  /* Initialize local variables.  */
  done = 0;
  grouping = (const char *) -1;
#ifdef __va_copy
  /* This macro will be available soon in gcc's <stdarg.h>.  We need it
     since on some systems `va_list' is not an integral type.  */
  __va_copy (ap_save, ap);
#else
  ap_save = ap;
#endif
  nspecs_done = 0;

#ifdef COMPILE_WPRINTF
  /* Find the first format specifier.  */
  f = lead_str_end = __find_specwc ((const UCHAR_T *) format);
#else
  /* Find the first format specifier.  */
  f = lead_str_end = __find_specmb ((const UCHAR_T *) format);
#endif

  /* Lock stream.  */
  _IO_cleanup_region_start ((void (*) (void *)) &_IO_funlockfile, s);
  _IO_flockfile (s);

  /* Write the literal text before the first format.  */
  outstring ((const UCHAR_T *) format,
	     lead_str_end - (const UCHAR_T *) format);

  /* If we only have to print a simple string, return now.  */
  if (*f == L_('\0'))
    goto all_done;

  /* Use the slow path in case any printf handler is registered.  */
  if (__glibc_unlikely (__printf_function_table != NULL
			|| __printf_modifier_table != NULL
			|| __printf_va_arg_table != NULL))
    goto do_positional;

  /* Process whole format string.  */
  do
    {
      STEP0_3_TABLE;
      STEP4_TABLE;

      int is_negative;	/* Flag for negative number.  */
      union
      {
	unsigned long long int longlong;
	unsigned long int word;
      } number;
      int base;
      union printf_arg the_arg;
      CHAR_T *string;	/* Pointer to argument string.  */
      int alt = 0;	/* Alternate format.  */
      int space = 0;	/* Use space prefix if no sign is needed.  */
      int left = 0;	/* Left-justify output.  */
      int showsign = 0;	/* Always begin with plus or minus sign.  */
      int group = 0;	/* Print numbers according grouping rules.  */
      /* Argument is long double/long long int.  Only used if
	 double/long double or long int/long long int are distinct.  */
      int is_long_double __attribute__ ((unused)) = 0;
      int is_short = 0;	/* Argument is short int.  */
      int is_long = 0;	/* Argument is long int.  */
      int is_char = 0;	/* Argument is promoted (unsigned) char.  */
      int width = 0;	/* Width of output; 0 means none specified.  */
      int prec = -1;	/* Precision of output; -1 means none specified.  */
      /* This flag is set by the 'I' modifier and selects the use of the
	 `outdigits' as determined by the current locale.  */
      int use_outdigits = 0;
      UCHAR_T pad = L_(' ');/* Padding character.  */
      CHAR_T spec;

      workend = work_buffer + WORK_BUFFER_SIZE;

      /* Get current character in format string.  */
      JUMP (*++f, step0_jumps);

      /* ' ' flag.  */
    LABEL (flag_space):
      space = 1;
      JUMP (*++f, step0_jumps);

      /* '+' flag.  */
    LABEL (flag_plus):
      showsign = 1;
      JUMP (*++f, step0_jumps);

      /* The '-' flag.  */
    LABEL (flag_minus):
      left = 1;
      pad = L_(' ');
      JUMP (*++f, step0_jumps);

      /* The '#' flag.  */
    LABEL (flag_hash):
      alt = 1;
      JUMP (*++f, step0_jumps);

      /* The '0' flag.  */
    LABEL (flag_zero):
      if (!left)
	pad = L_('0');
      JUMP (*++f, step0_jumps);

      /* The '\'' flag.  */
    LABEL (flag_quote):
      group = 1;

      if (grouping == (const char *) -1)
	{
#ifdef COMPILE_WPRINTF
	  thousands_sep = _NL_CURRENT_WORD (LC_NUMERIC,
					    _NL_NUMERIC_THOUSANDS_SEP_WC);
#else
	  thousands_sep = _NL_CURRENT (LC_NUMERIC, THOUSANDS_SEP);
#endif

	  grouping = _NL_CURRENT (LC_NUMERIC, GROUPING);
	  if (*grouping == '\0' || *grouping == CHAR_MAX
#ifdef COMPILE_WPRINTF
	      || thousands_sep == L'\0'
#else
	      || *thousands_sep == '\0'
#endif
	      )
	    grouping = NULL;
	}
      JUMP (*++f, step0_jumps);

    LABEL (flag_i18n):
      use_outdigits = 1;
      JUMP (*++f, step0_jumps);

      /* Get width from argument.  */
    LABEL (width_asterics):
      {
	const UCHAR_T *tmp;	/* Temporary value.  */

	tmp = ++f;
	if (ISDIGIT (*tmp))
	  {
	    int pos = read_int (&tmp);

	    if (pos == -1)
	      {
		__set_errno (EOVERFLOW);
		done = -1;
		goto all_done;
	      }

	    if (pos && *tmp == L_('$'))
	      /* The width comes from a positional parameter.  */
	      goto do_positional;
	  }
	width = va_arg (ap, int);

	/* Negative width means left justified.  */
	if (width < 0)
	  {
	    width = -width;
	    pad = L_(' ');
	    left = 1;
	  }
      }
      JUMP (*f, step1_jumps);

      /* Given width in format string.  */
    LABEL (width):
      width = read_int (&f);

      if (__glibc_unlikely (width == -1))
	{
	  __set_errno (EOVERFLOW);
	  done = -1;
	  goto all_done;
	}

      if (*f == L_('$'))
	/* Oh, oh.  The argument comes from a positional parameter.  */
	goto do_positional;
      JUMP (*f, step1_jumps);

    LABEL (precision):
      ++f;
      if (*f == L_('*'))
	{
	  const UCHAR_T *tmp;	/* Temporary value.  */

	  tmp = ++f;
	  if (ISDIGIT (*tmp))
	    {
	      int pos = read_int (&tmp);

	      if (pos == -1)
		{
		  __set_errno (EOVERFLOW);
		  done = -1;
		  goto all_done;
		}

	      if (pos && *tmp == L_('$'))
		/* The precision comes from a positional parameter.  */
		goto do_positional;
	    }
	  prec = va_arg (ap, int);

	  /* If the precision is negative the precision is omitted.  */
	  if (prec < 0)
	    prec = -1;
	}
      else if (ISDIGIT (*f))
	{
	  prec = read_int (&f);

	  /* The precision was specified in this case as an extremely
	     large positive value.  */
	  if (prec == -1)
	    {
	      __set_errno (EOVERFLOW);
	      done = -1;
	      goto all_done;
	    }
	}
      else
	prec = 0;
      JUMP (*f, step2_jumps);

      /* Process 'h' modifier.  There might another 'h' following.  */
    LABEL (mod_half):
      is_short = 1;
      JUMP (*++f, step3a_jumps);

      /* Process 'hh' modifier.  */
    LABEL (mod_halfhalf):
      is_short = 0;
      is_char = 1;
      JUMP (*++f, step4_jumps);

      /* Process 'l' modifier.  There might another 'l' following.  */
    LABEL (mod_long):
      is_long = 1;
      JUMP (*++f, step3b_jumps);

      /* Process 'L', 'q', or 'll' modifier.  No other modifier is
	 allowed to follow.  */
    LABEL (mod_longlong):
      is_long_double = 1;
      is_long = 1;
      JUMP (*++f, step4_jumps);

    LABEL (mod_size_t):
      is_long_double = sizeof (size_t) > sizeof (unsigned long int);
      is_long = sizeof (size_t) > sizeof (unsigned int);
      JUMP (*++f, step4_jumps);

    LABEL (mod_ptrdiff_t):
      is_long_double = sizeof (ptrdiff_t) > sizeof (unsigned long int);
      is_long = sizeof (ptrdiff_t) > sizeof (unsigned int);
      JUMP (*++f, step4_jumps);

    LABEL (mod_intmax_t):
      is_long_double = sizeof (intmax_t) > sizeof (unsigned long int);
      is_long = sizeof (intmax_t) > sizeof (unsigned int);
      JUMP (*++f, step4_jumps);

      /* Process current format.  */
      while (1)
	{
#define process_arg_int() va_arg (ap, int)
#define process_arg_long_int() va_arg (ap, long int)
#define process_arg_long_long_int() va_arg (ap, long long int)
#define process_arg_pointer() va_arg (ap, void *)
#define process_arg_string() va_arg (ap, const char *)
#define process_arg_unsigned_int() va_arg (ap, unsigned int)
#define process_arg_unsigned_long_int() va_arg (ap, unsigned long int)
#define process_arg_unsigned_long_long_int() va_arg (ap, unsigned long long int)
#define process_arg_wchar_t() va_arg (ap, wchar_t)
#define process_arg_wstring() va_arg (ap, const wchar_t *)
#include "vfprintf-process-arg.c"
#undef process_arg_int
#undef process_arg_long_int
#undef process_arg_long_long_int
#undef process_arg_pointer
#undef process_arg_string
#undef process_arg_unsigned_int
#undef process_arg_unsigned_long_int
#undef process_arg_unsigned_long_long_int
#undef process_arg_wchar_t
#undef process_arg_wstring

	LABEL (form_float):
	LABEL (form_floathex):
	  {
	    if (__glibc_unlikely ((mode_flags & PRINTF_LDBL_IS_DBL) != 0))
	      is_long_double = 0;

	    struct printf_info info =
	      {
		.prec = prec,
		.width = width,
		.spec = spec,
		.is_long_double = is_long_double,
		.is_short = is_short,
		.is_long = is_long,
		.alt = alt,
		.space = space,
		.left = left,
		.showsign = showsign,
		.group = group,
		.pad = pad,
		.extra = 0,
		.i18n = use_outdigits,
		.wide = sizeof (CHAR_T) != 1,
		.is_binary128 = 0
	      };

	    PARSE_FLOAT_VA_ARG_EXTENDED (info);
	    const void *ptr = &the_arg;

	    int function_done = __printf_fp_spec (s, &info, &ptr);
	    if (function_done < 0)
	      {
		done = -1;
		goto all_done;
	      }
	    done_add (function_done);
	  }
	  break;

	LABEL (form_unknown):
	  if (spec == L_('\0'))
	    {
	      /* The format string ended before the specifier is complete.  */
	      __set_errno (EINVAL);
	      done = -1;
	      goto all_done;
	    }

	  /* If we are in the fast loop force entering the complicated
	     one.  */
	  goto do_positional;
	}

      /* The format is correctly handled.  */
      ++nspecs_done;

      /* Look for next format specifier.  */
#ifdef COMPILE_WPRINTF
      f = __find_specwc ((end_of_spec = ++f));
#else
      f = __find_specmb ((end_of_spec = ++f));
#endif

      /* Write the following constant string.  */
      outstring (end_of_spec, f - end_of_spec);
    }
  while (*f != L_('\0'));

  /* Unlock stream and return.  */
  goto all_done;

  /* Hand off processing for positional parameters.  */
do_positional:
  done = printf_positional (s, format, readonly_format, ap, &ap_save,
			    done, nspecs_done, lead_str_end, work_buffer,
			    save_errno, grouping, thousands_sep, mode_flags);

 all_done:
  /* Unlock the stream.  */
  _IO_funlockfile (s);
  _IO_cleanup_region_end (0);

  return done;
}

现在的水平看懂确实有一定的难度,于是就不看了。




3 几个宏及解读

前面提到了有几个其他的东西,现在我们来看看是什么

va_list

在vs2017下的定义是这样的:(\Community\VC\Tools\MSVC\14.16.27023\include\vadefs.h


#ifndef _VA_LIST_DEFINED
    #define _VA_LIST_DEFINED
    #ifdef _M_CEE_PURE
        typedef System::ArgIterator va_list;
    #else
        typedef char* va_list;
    #endif
#endif

可以看出大部分情况下他就是 char * 类型,再看看百度的解释
c语言printf源码,C语言,c语言,开发语言,后端,开源


va_start

c语言printf源码,C语言,c语言,开发语言,后端,开源
这是 vs2017下x64的定义,看不了他的实现,那么就总结一下其他资料的解释。

这个宏的作用是:得到一个指向第一个可变参数的指针,也就是“”包裹的字符串后面的第一个参数。

看看这个宏的声明:

void va_start(va_list ap, last_arg);

ap: 这个参数的类型是之前提过的va_list,也就是那个指向可变参数列表的指针。
last_arg: 是最后一个固定参数,在printf函数中就是那个“”包裹的字符串。
在介绍他的实现之前,我们需要先补充三个知识:

  1. 函数传参进栈的顺序
  2. 栈区在计算机内部的地址情况
  3. _INTSIZEOF 宏


函数参数进栈顺序

先说第一点,函数参数进栈的顺序
     从右往左,依次进栈

  func(int a, int b, int c, int d);

如果是这个函数的话,那么应该是d-> c -> b -> a 这是要了解的第一点。

  void func(char *fmt, ...);

fmt肯定是在这一系列参数中最低的地址部分。



栈区在计算机内部的地址情况

来看这张经典的图:

c语言printf源码,C语言,c语言,开发语言,后端,开源

栈底是高地址,栈顶是底地址,也就是说先进栈的会是高地址。之前那个例子中,地址从高到低依次也是 d , c, b, a。
在可变参数的函数中,大概就是这种情况:

|——————————————————————————|
|最后一个可变参数 | ----------------高内存地址处
|——————————————————————————|

|——————————————————————————|
|第N个可变参数 | -----------va_arg(arg_ptr,int)后arg_ptr所指的地方,
| | 即第N个可变参数的地址。
|——————————————— |
………………………….
|——————————————————————————|
|第一个可变参数 | ------------ va_start(arg_ptr,start)后arg_ptr所指的地方
| | 即第一个可变参数的地址
|——————————————— |
|——————————————————————————|
| |
|最后一个固定参数 | ------------- start的起始地址
|—————————————— —| …
|—————————————————————————— |
| |
|——————————————— |-> 低内存地址处



_INTSIZEOF 宏

宏的定义:

#define _INTSIZEOF(n)  ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1) )

这个宏的作用是把sizeof(n) 向上取整作为 sizeof(int)的整数倍,用以在内存中对齐。
看过实现之后我觉得这个宏写的当精彩。

对于两个正整数 x, n 总存在整数 q, r 使得

x = nq + r, 其中 0<= r <n          //最小非负剩余

q, r 是唯一确定的。q = [x/n], r = x - n[x/n]. 这个是带余除法的一个简单形式。在 c 语言中, q, r 容易计算出来: q = x/n, r = x % n.


所谓把 x 按 n 对齐指的是:若 r=0, 取 qn, 若 r>0, 取 (q+1)n. 这也相当于把 x 表示为:

x = nq + r’, 其中 -n < r’ <=0           //最大非正剩余

nq 是我们所求。关键是如何用 c 语言计算它。由于我们能处理标准的带余除法,所以可以把这个式子转换成一个标准的带余除法,然后加以处理:

x+n = qn + (n+r’),其中 0<n+r’<=n          //最大正剩余

x+n-1 = qn + (n+r’-1), 其中 0<= n+r’-1 <n       //最小非负剩余

所以 qn = [(x+n-1)/n]n. 用 c 语言计算就是:

((x+n-1)/n)*n

若 n 是 2 的方幂, 比如 2^m,则除为右移 m 位,乘为左移 m 位。所以把 x+n-1 的最低 m 个二进制位清 0就可以了。得到:

(x+n-1) & (~(n-1))

先试着理解上面的,如果上面那些步骤没看懂没关系。我们这样想,要把一个整数表达成另一个小的整数的整数倍,那么这个整数本身肯定只有两种情况:1,他可以被这个小整数整除,也就是正好在小整数划分区间的端点上。2,不可被整除,那就是落在某个区间上。我们要做的就是找到这个数的区间端点。
c语言printf源码,C语言,c语言,开发语言,后端,开源
第一种情况: x正好是某个区间的端点,那么x+n-1落在他本身的区间,c语言中的/是向下取整的,除以n刚好就是某个端点值。
第二种情况,x在某个区间中,x+n必然在他下一个区间上。那么x+n-1,又有两种情况,一种是和x+n在同一个区间(x的下一个区间)上,第二种是在x下一个区间的左端点上,不可能和x在同一个区间。那么不管是哪一种情况x+n-1 进行c语言的向下整除操作后都会落在x的右端点上,就达到了向上取整的目的。

最后解释一下(x+n-1) & (~(n-1))的含义, 要执行x+n-1 除以n的操作, 相当于向右移动。那么我们有这个结论 若 n 是 2 的方幂, 比如 2^m,则除为右移 m 位,乘为左移 m 位。所以把 x+n-1 的最低 m 个二进制位清 0就可以了。 而n若为2^m的话,二进制下就是1后接m个0,那么对n-1取反就是得到了m个0,在进行&操作就行了。

回到va_start宏:

#define va_start(ap,v)( ap = (va_list)&v + _INTSIZEOF(v) )   // 得到第一个可变参数的地址

结合前面那张栈的图,我们可以知道,最后一个固定参数的地址在第一个可变参数的地址下方,在给出固定参数的地址后,加上固定参数本身占用的内存后,得到了第一个可变参数的起始地址。

注意:宏va_start是对参数的地址进行操作的,要求参数地址必须是有效的。一些地址无效的类型不能当作固定参数类型。比如:寄存器类型,它的地址不是有效的内存地址值;数组和函数也不允许,他们的长度是个问题。因此,这些类型时不能作为va函数的参数的。


va_arg

先看宏的定义:

#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )

这个宏主要做了两件事情:
1、 指针ap指向下一个参数的地址
2、 强制类型转换后得到用户所指定的值

我们可以拆开来看这句话
( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) ) /* 指针ap指向下一个参数的地址 */ 拆成:

1. ap += _INTSIZEOF(t)// 当前,ap已经指向下一个参数了

2return *(t *)( ap - _INTSIZEOF(t))
/* ap减去当前参数的大小得到当前参数的地址,再强制类型转换后返回它的值 */

用这个宏就进行可变参数的遍历操作,达到智能输出的效果了。

va_end

   #define va_end(ap) ( ap = (va_list)0 ) 

这个宏很简单,就是将指针置空,而这个空间也不是在堆上的,也不用free了。

x86平台定义为ap=(char*)0;使ap不再> 指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的.



4 自己实现的可变参数函数

#include<stdio.h>
#include<stdarg.h>
int k = -1, t = 0;
int it;
char *ct = NULL;
char cc = '0';
double dd = 0;
int it2 = 0;
int i = 0;
char tt[30];

void f(int it) {
  while(it) {
    t = it % 10;
    tt[++k] = t + '0';
    it /= 10;
  }
}
void myprintf(char const* fmt, ...) {
  char const *p;
  va_list aq;
  va_start(aq, fmt);
  p = fmt;
  while(*p != '\0')  {
    if(*p != '%') {
      putchar(*p);
      p++;
      continue;
    }
    switch(*++p) {
      case 'd':
        it = va_arg(aq, int);
        if(it < 0) {
          putchar('-');
          it = -it;
        }
        f(it);
        for( ; k >= 0; k--) {
          putchar(tt[k]);
        }

        break;
      case 's':
        ct = va_arg(aq, char *);
        for( ; *ct; ct++) {
          putchar(*ct);
        }
        break;
      case 'c':
        cc = va_arg(aq, int);
        putchar(cc);
        break;
      case 'f':
        dd = va_arg(aq, double);
        if(dd < 0){
          putchar('-');
          dd = -dd; 
        } 
        
        it = (int)dd;
        it2 = it;
        dd = dd - it;
        dd *= 1000000;
        it = (int)dd;
        
        for(i = k+1;i <= k+6 ;i++ ) {
          tt[i] = '0';
        }   
        
        f(it);
        
        tt[k = i] = '.'; 
        if(it2 == 0){
          tt[++k]  = '0';
        }  
        
        f(it2);
        
        for( ; k >= 0; k--) {
          putchar(tt[k]);
        }
        
        break;
    }  
    p++;
  }
  va_end(aq);
}
int main() {
  
  myprintf("??%d, %s, >> %c, %d, %s , %f, %f", 10, "kkk", '&', 999, "aaaaa", 1234.15648777, 0.0012);
  
  //myprintf("??%f", 0.0012);

  return 0;
}



//   ??10, kkk, >> &, 999, aaaaa , 1234.156487, 0.001200  

算法还可以优化,代码也能更好看点,但学习的目的已经达到了



参考:文章来源地址https://www.toymoban.com/news/detail-528407.html

  • https://blog.csdn.net/blueskybluesoul/article/details/121969786
  • https://www.cnblogs.com/saolv/p/7779364.html
  • https://blog.csdn.net/zhyjunFov/article/details/12017697
  • https://blog.csdn.net/weixin_45206746/article/details/117535332
  • http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=814501

到了这里,关于C语言printf函数实现解读的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • C语言格式化输出函数printf详解——C语言基础知识

    (由于篇幅较长,内容较全,建议收藏) printf函数的一般格式为: printf(格式控制字符串,输出值参数表); 如: 其中, f=%f,c=%fn 是 格式控制字符串 , f,c 是 输出值参数表 。 (1)格式控制字符串是用双引号括起来的字符串,包括三类信息: 格式字符。格式字符由“%”

    2023年04月08日
    浏览(42)
  • 在 C 语言中添加注释、包含头文件和使用 printf 函数输出字符串的方法

    注释是程序员在编写代码时添加给自己或其他人的说明文字,用来提高代码的可读性,方便理解。注释通常会添加在代码的关键部分以及不易理解的地方。 在C语言中,注释可以使用两种方式:单行注释以及多行注释。单行注释使用\\\"//\\\",多行注释使用\\\"/* */\\\"。多行注释不可嵌套

    2023年04月22日
    浏览(53)
  • C语言(输入输出函数getchar,putchar、gets、puts,scanf,printf的功能以及用法)

    int getchar( void ); 返回值为int,所以需要用一个int变量来接收,不管输入几个字符,每次都只接收第一个字符,常与while和putchar配合使用。 从下面这张图可以看出,输入一个空格也会打印 当然,获取一个字符用得不多,每次都需要获取一串,所以我们可以配合while来使用。 用

    2024年02月02日
    浏览(50)
  • C语言基本语句(变量类型int、 float、 double、 char,函数scanf、printf、putchar()、getchar() )

    1. int, float, double, char ①整型int(对应%d)  int a,b;  scanf(\\\"%d,%d\\\",a,b); printf (\\\"%d\\\",a); printf(\\\"我今天吃了%d个苹果,在黑板上写下整数%d,这很有趣。\\\",a,b); //printf(\\\"……\\\",变量名)中,“……”部分内容比较自由,可随便发挥,但必须包括%d,几个变量名就对应几个%d ②单精度型浮点数

    2024年02月08日
    浏览(52)
  • 如何编写一个可变参数函数?如何让所有单片机的所有串口实现printf函数?

    (1)由于真的复习不下去,就想着写一篇博客拉回自己的心思。于是想到了长期有疑惑,但是一直没有进行深入了解的C语言可变参数函数。 (2)本人查阅了一些网上的资料,以及自己的理解写出来了这一片博客。首先再次感谢肯哥的答疑。 (3)借鉴文章: C51单片机中如何

    2024年02月11日
    浏览(46)
  • APS开源源码解读: 排程工具 frepple

    https://github.com/frePPLe/frepple/tree/master 关键在于理解buffer的含义以及操作。 有consumer和producing, 数量有正负 Identify Critical Paths: Start by identifying the critical paths in your scheduling process. Critical paths are sequences of tasks that have the least flexibility in terms of start times. These paths are essential to meetin

    2024年02月05日
    浏览(35)
  • 开源机器人SmallRobotArm机器人源码解读

    开源机器人SmallRobotArm是一个开源的6轴机械臂,都由步进电机驱动,github地址:https://github.com/SkyentificGit/SmallRobotArm  机器人长这个样子 2 欧拉角及姿态变换 由欧拉角求姿态矩阵 源码中用的欧拉角是ZYZ顺组的欧拉角。 已知世界坐标的坐标(x,y,z)和欧拉角(α,β,γ),求出对应的姿

    2024年02月16日
    浏览(41)
  • KEIL/MDK中的标准C库函数printf和malloc实现线程安全

    在ARM嵌入式开发中,编译器提供的C库函数有 一部分 不是线程安全的。 如果项目中运行了第三方RTOS,在调用标准C库函数时就要关心它们是不是线程安全的。 比如printf函数,它是可重入的函数,但是在多线程环境下打印的内容可能会交叉乱序。 当然这种问题还不算严重。 但

    2023年04月08日
    浏览(68)
  • Apache Doris 聚合函数源码阅读与解析|源码解读系列

    笔者最近由于工作需要开始调研 Apache Doris,通过阅读聚合函数代码切入 Apache Doris 内核,同时也秉承着开源的精神,开发了 array_agg 函数并贡献给社区。笔者通过这篇文章记录下对源码的一些理解,同时也方便后面的新人更快速地上手源码开发。 聚合函数,顾名思义,即对一

    2024年01月25日
    浏览(40)
  • Flask 学习-88. jsonify() 函数源码解读深入学习

    flask 有个jsonify() 函数,如果返回的是一个字典,那么调用 jsonify 创建一个响应对象。 视图函数的返回值会自动转换为一个响应对象。 如果返回值是一个字符串,那么会被 转换为一个包含作为响应体的字符串、一个 200 OK 出错代码 和一个 text/html 类型的响应对象。 如果返回值

    2024年02月03日
    浏览(78)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包