C语言——变参函数

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

目录

目录

一、定义

二、声明方式

三、使用

3.1用指针的方式

3.2用宏定义的方式

四、printf的实现

一、定义

        一般函数的参数列表是固定的,所以在调用时传入的实参的个数和格式必须和实参匹配;在函数式中,不需要关心实参,直接调用形参即可。

         变参函数,就是参数的个数及类型都不确定的函数,常见变参函数如printf、scanf。因为传入的参数列表是不确定的,所以在函数实现时要对传入的参数的个数以及类型进行判断乃至处理。

二、声明方式

        声明方式:返回值 函数名(第一个参数, ...);

        注:声明或定义变参函数时,第一个参数必须要有,其主要作用是明确到底有多少个参数,以及参数类型,后面的参数用“...”代替。

三、使用

3.1用指针的方式

        很多教材以及网上给出的示例代码:

void print_num(int count, ...)
{
    int *args;
    args = &count + 1;
    for( int i = 0; i < count; i++)
    {
        printf("*args: %d\n", *args);
        args++;
    }
}
int main(void)
{
    print_num(5,1,2,3,4,5);
    return 0;
}

        这是有问题的!

        运行结果:

C语言——变参函数

         打印出来的结果是混乱的。

       函数实现思路是定义一个变参函数print_num,在函数内部先取得第一个参数的地址赋值给一指针,然后将指针后移,取得后面的参数并打印出来。

        错误原因:

       1.指针在64位系统中大小为8byte,示例代码中的“args++;”偏移的是一个int数据类型的大小,即4byte。

       2.第一个参数和第二个该参数之间的距离为28byte。通过“gcc -S 源文件”命令,生成后缀为“.s”的汇编代码文件,在汇编文件中找到的这个信息,具体原因我也还不知道。

C语言——变参函数

         从上图可以看到在函数print_num中,第一个参数位置为196,第二个参数位置为168,二者之间相差28byte。从第二个参数开始,后续参数之间的距离为8byte。

        关于这个示例代码的错误,这篇文章很有参考价值:https://www.cnblogs.com/lularible/p/15129183.html

        正确代码:

void print_num(int count, ...)
{
    char *args;
    args = (char *)&count + 28;
    for( int i = 0; i < count; i++)
    {
        // printf("addr:%p\n",args);
        printf("*args: %d\n", *(int*)args);
        args+=8;
    }
}

        运行结果:

C语言——变参函数

3.2用宏定义的方式

        头文件:#include <stdarg.h>
        头文件中定义的常用的宏:
        va_list:定义在编译器头文件中 typedef char* va_list; 
        void va_start(va_list ap, last);提取第一个参数last后面的第一个参数的地址,并赋值给va_list类型的指针变量ap;
        type va_arg(va_list ap, type); 返回下一个参数的地址,返回类型为type,每次执行时是返回的上一次返回的参数的下一个参数,而不是每次都是第二个参数;

        注意:

                type传char型的时候会自动转换为int类型;C语言——变参函数 

                type传float类型的时候会自动转换为double类型。


        void va_end(va_list ap); 销毁va_list类型的指针变量ap,并将其赋值为空。

        函数实现:

void print_num(int count, ...)
{
    va_list arg;        //声明一个va_list类型的指针变量
    va_start(arg,count);//初始化指针变量arg为count的下一个参数的地址
    int temp;
    for(int i=0;i<count;i++)
    {
        temp = va_arg(arg,int);         //返回后续的参数
        printf("*args: %d\n", temp);
    }
    va_end(arg);    //销毁这个指针变量
}

        关于使用宏来做变参函数,这篇博客具有很好的参考价值,但使用指针实现部分也是有一点问题的。https://blog.csdn.net/sinat_31039061/article/details/128338331

更新:

四、printf的实现

        printf作为两个最常用的变参函数之一,简直是变参函数的标准范例。

函数原型:

int printf(const char *format, ...);

功能:将指定格式数据输出到屏幕终端上(先将数据发送到标准输出缓冲区)

实现思路:

  1. 可以先写一个模拟打印一个字符的底层API:send;
  2. 对于普通字符,直接调用send打印即可;
  3. 对于指定格式的数据,通过'%'检测,当遇到‘%’时根据后续的字符来判断数据格式;
  4. 对于不同格式的数据,虽然实现过程和方法不一致,但其核心思路就是将其拆解字符来打印。

整形数据的打印:

        1、如果是负数,就先打印负号,并将其变为正数。

        2、先计算这个数字是几位数

        3、将每一位数字拆解出来打印

//打印整数
void my_printf_int(int num)
{
    int len ,temp;
    if(num < 0) //负数
    {
        send('-');
        num = -num;
    }
    //计算数字的位数
    len=0;
    temp = num;
    do
    {
        len++;
        temp/=10;
    }while (temp > 0);

    //将每一位数字拆解出来打印
    for (int i = len-1; i >=0; i--)
    {
        send('0'+num/((int)pow(10,i))%10);
    }
}

浮点数需要先用两个整形数来分别保存其整数部分和小数部分,再分别进行打印。

//浮点型数据分别用两个整形数据表示整数和小数部分
void ftoi(float n,int *integer,int *decimal)
{
    //取出整数部分
    *integer = (int) n;

    //取出小数部分
    float temp = n - *integer;
    *decimal =(int) (temp *pow(10,6));
    for (int i = 0; i < 6; i++)    //去掉无效数据
    {
        if(*decimal%10 == 0)
        {
            *decimal /=10;
        }
        else
            break;
    }
    // printf("integer=%d\tdecimal=%d\n",*integer,*decimal);
}

完整代码:文章来源地址https://www.toymoban.com/news/detail-410621.html

#include <stdio.h>
#include <stdarg.h>
#include <math.h>


//printf的实现
int my_printf(const char *format, ...);

// 模拟发送一个字符
void send(char c);

//浮点型数据,分别用两个整形数据存储整数和小数部分
void ftoi(float n,int *integer,int *decimal);

//打印整数
void my_printf_int(int num);

int main(void)
{
    
    char c='a';
    int n=1234;
    char *str = "hello world";
    float pi=3.1415926;
    my_printf("c:%c\td:%d\ts:%s\tf:%f\n",c,n,str,pi);
    
    
    return 0;
}

int my_printf(const char *format, ...)
{

    va_list args;           // 声明一个va_list类型的指针变量
    va_start(args, format); // 将args指向format的下一个参数

    int count = 0;
    char ch;
    int num,integer,decimal;
    char *str;
    double f;
    while (*format != '\0')
    {
        if (*format == '%') // 遇到%
        {
            format++; // 跳过%
            switch (*format)
            {
            case 'c':   //字符型
            {
                ch = (char)va_arg(args , int);
                send(ch);
                break;
            }
            case 'd':   //整形
            {
                num = va_arg(args,int);
                my_printf_int(num);
                break;
            }
            case 's':   //字符串
                str = va_arg(args,char *);
                while (*str != '\0')
                {
                    send(*str++);
                }
                break;
            case 'f':   //float类型
            {
                
                f = (float)va_arg(args,double);
                //将浮点型转换成两个整形
                ftoi(f,&integer,&decimal);

                //打印整数部分
                my_printf_int(integer);
                //打印小数点
                send('.');
                //打印小数部分
                my_printf_int(decimal);
                
                break;
            }
                
            default:
                break;
            }
            count++;
            format++;
        }
        else    //遇到普通字符,直接打印
            send(*format++);
    }

    return count;
}

// 发送一个字符
void send(char c)
{
    printf("%c", c);
}

//打印整数
void my_printf_int(int num)
{
    int len ,temp;
    if(num < 0) //负数
    {
        send('-');
        num = -num;
    }
    //计算数字的位数
    len=0;
    temp = num;
    do
    {
        len++;
        temp/=10;
    }while (temp > 0);
    
    for (int i = len-1; i >=0; i--)
    {
        send('0'+num/((int)pow(10,i))%10);
    }
}

//浮点型数据分别用两个整形数据表示整数和小数部分
void ftoi(float n,int *integer,int *decimal)
{
    //取出整数部分
    *integer = (int) n;

    //取出小数部分
    float temp = n - *integer;
    *decimal =(int) (temp *pow(10,6));
    for (int i = 0; i < 6; i++)
    {
        if(*decimal%10 == 0)
        {
            *decimal /=10;
        }
        else
            break;
    }
    // printf("integer=%d\tdecimal=%d\n",*integer,*decimal);
}

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

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

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

相关文章

  • C语言函数声明以及函数原型

    所谓声明(Declaration),就是告诉编译器我要使用这个函数; 函数声明的格式,是去掉函数定义中的函数体,并在最后加上分号; 也可以不写形参,只写数据类型: dataType  functionName( dataType1, dataType2 ... ); 函数声明给出了函数名、返回值类型、参数列表(重点是参数类型)等

    2024年02月01日
    浏览(29)
  • go语言(七)----slice的声明方式

    1、声明方式一 2、声明方式二 3、声明方式三 二、切片容量的追加 1、切片的长度和容量不同,长度表示左指针到右指针之间的距离。容量表示左指针到底层数组末尾的距离。 2、切片的扩容机制,append的时候,如果长度增加后超过容量,则将容量增加2倍。 3、切片的截取

    2024年01月21日
    浏览(33)
  • C语言 指针声明和定义 - C语言零基础入门教程

    目录 [一.指针简介] [1.内存] [2.内存地址] [3.指针声明] [二.指针类型] [三.声明并初始化一个指针] [1.声明指针并直接初始化 – 推荐] [2.先声明指针在初始化 – 不推荐] [四.查看指针地址和指针的值] [五.NULL 指针 – 空指针] [六.重点总结] [七.猜你喜欢] 零基础 C/C++ 学习路线推荐

    2024年03月22日
    浏览(32)
  • 【05】STM32·HAL库开发-C语言基础知识 | stdint.h介绍 | 位操作 | 宏定义的使用 | 条件编译 | extern声明 | typdef使用 | 结构体、指针、代码规范介绍。

      stdint.h 是从 C99 中引进的一个标准 C 库的文件,可以在MDK5的安装路径:D:MDK5.34ARMARMCCinclude中找到。   stdint.h 定义了很多类型别名,将有符号的char类型定义别名为int8_t等,使用此套别名有易于移植。   在MDK中需要配置才能支持使用S99标准, 默认是勾选的 。   只

    2024年02月08日
    浏览(33)
  • SCL语言的数据类型和常量与变量声明方式

    SCL语言的数据类型主要包含以下几类: 1. 基本数据类型: - BOOL:布尔类型,取值为TRUE或FALSE。 - INT:整数类型,取值范围为-32768~32767(16位PLC)或-2147483648~2147483647(32位PLC)。 - UINT:无符号整数类型,取值范围为0~65535(16位PLC)或0~4294967295(32位PLC)。 - REAL:单精度浮点数

    2024年02月09日
    浏览(42)
  • 【C语言13】结构体的声明,定义与结构体的内存对齐

    通俗的说,结构体就是一个类的集合,如同整形数组是整形数字的集合体,结构体也是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。 如果我们要表达一本书,那么这本书的元素有什么呢,书的名字,书的作者,书的创作日期~,而c语言中内置

    2024年02月13日
    浏览(59)
  • 【go语言】2.1.3 函数的定义和使用

    在 Go 语言中,函数是一种代码抽象和复用的方式。函数可以接受参数,执行特定的操作,并返回结果。 函数的定义以  func  开始,后面跟着函数名、参数列表、返回值列表(可选)以及函数体。下面是一个函数定义的例子: 这个函数接受一个类型为  string  的参数

    2024年02月15日
    浏览(37)
  • C语言第八课-----函数定义和使用的延续

    作者前言                                                   个人主页::小小页面                  gitee页面:秦大大                 一个爱分享的小博主 欢迎小可爱们前来借鉴 __________________________________________________________          1.复习                 库函

    2024年02月15日
    浏览(33)
  • C语言第七课----------函数的定义及使用--------C语言重要一笔

                                                      个人主页::小小页面                  gitee页面:秦大大                 一个爱分享的小博主 欢迎小可爱们前来借鉴 __________________________________________________________          1.函数是什么   

    2024年02月16日
    浏览(26)
  • C语言中自定义函数的使用和案例分享

    1.函数的定义和声明 函数定义的语法: 函数类型 函数名(形式参数表){ 函数体 } 函数的声明 函数返回值类型 函数名(变量1数据类型, 变量2数据类型, 变量n数据类型) 函数的调用 函数名(变量1, 变量2, 变量n) 函数调用时注意事项: 函数调用的实参个数必须与形参个数相同。 实参

    2024年02月08日
    浏览(31)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包