3

编辑 3:对于代码本身,请一起检查第一个答案或这篇文章的结尾。

如标题中所述,我试图找到一种方法来判断可选参数是否已传递给函数。我想做的是几乎所有动态语言如何处理它们的子字符串函数。下面是我目前的,但它不起作用,因为我不知道如何判断是否/何时使用该东西。

char *substring(char *string,unsigned int start, ...){
    va_list args;
    int unsigned i=0;
    long end=-1;
    long long length=strlen(string);
    va_start(args,start);
    end=va_arg(args,int);
    va_end(args);
    if(end==-1){
        end=length;
    }
    char *to_string=malloc(end);
    strncpy(to_string,string+start,end);
    return to_string;
}

基本上我希望仍然能够不包含我想要返回的字符串的长度,而只是让它走到字符串的末尾。但我似乎找不到办法做到这一点。由于也无法知道 C 中传递的参数数量,这让我失去了第一个想法。

编辑:这是当前代码的新方法。

#define substring(...) P99_CALL_DEFARG(substring, 3, __VA_ARGS__)
#define substring_defarg_2 (0)
char *substring(char *string,unsigned int start, int end){
    int unsigned i=0;
    int num=0;
    long long length=strlen(string);
    if(end==0){
        end=length;
    }
    char *to_string=malloc(length);
    strncpy(to_string,string+start,end);
    return to_string;
}

然后在一个文件中我调用 test.c 看看它是否有效。

#include "functions.c"
int main(void){
    printf("str:%s",substring("hello world",3,2));
    printf("\nstr2:%s\n",substring("hello world",3));
return 0;
}

functions.c 有一个包含 functions.h 的内容,其中包括所有需要的内容。这是 clang 输出(因为 clang 似乎通常会提供更多细节。

In file included from ./p99/p99.h:1307:
./p99/p99_generic.h:68:16: warning: '__error__' attribute ignored
__attribute__((__error__("Invalid choice in type generic expression")))
               ^
test.c:4:26: error: called object type 'int' is not a function or function
      pointer
    printf("\nstr2:%s\n",substring("hello world",3));
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from test.c:1:
In file included from ./functions.c:34:
In file included from ./functions.h:50:
./string.c:77:24: note: instantiated from:
#define substring(...) P99_CALL_DEFARG(substring, 3, __VA_ARGS__)

GCC 只是说对象不是函数

编辑 2:请注意,将其设置为 -1 也不会改变它,它仍然会引发同样的事情。我使用的编译选项如下。

gcc -std=c99 -c test.c -o test -lm -Wall

Clang 是一回事(它是否适用是另一个问题。

在这里回答

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include "p99/p99.h"
#define substring(...) P99_CALL_DEFARG(substring, 3, __VA_ARGS__)
#define substring_defarg_2() (-1)
char *substring(char *string, size_t start, size_t len) {
  size_t length = strlen(string);
  if(len == SIZE_MAX){
    len = length - start;
  }
  char *to_string = malloc(len + 1);
  memcpy(to_string, string+start, len);
  to_string[len] = '\0';
  return to_string;
}

您将需要从那里获得 p99。它是由选定的答案。只需放入您的源目录,您应该就可以了。还要总结他对许可证的回答。你可以随心所欲地使用它,但你基本上不能分叉它。因此,出于此目的,您可以在任何项目中自由使用它和字符串函数,无论是专有的还是开源的。

我唯一要问的是,你至少给这个线程一个链接,以便发生在它上面的其他人可以了解堆栈溢出,因为这就是我对在这里得到帮助的事情发表评论的方式。

4

4 回答 4

6

在 C 中,没有可选参数这样的东西。此类情况的常见习语是要么具有两个功能;要么具有两个功能。substr(char *, size_t start, size_t end)and substr_f(char *, size_t start)or 有一个函数 where end,如果给定一个特殊的值,将具有特殊的含义(例如在这种情况下,可能是任何小于 start 的数字,或者只是 0)。

使用可变参数时,您需要在参数列表的末尾使用标记值(例如 NULL),或者将 argc(参数计数)作为较早的参数传入。

C 的运行时自省量非常低,这是一个特性,而不是一个错误。

编辑:在相关说明中,用于 C 中字符串长度和偏移量的正确类型是size_t. 它是唯一一种既保证足够大以处理任何字符串中的任何字符,又保证足够小不会在存储时浪费空间的整数类型。

还要注意它是未签名的。

于 2012-04-22T10:51:54.077 回答
4

除了具有可选参数的常见信念函数之外,可以在 C 中实现,但 va_arg 函数不是处理此类事情的正确工具。它可以通过 va_arg 宏实现,因为有一些方法可以捕获函数接收的参数数量。整个事情解释和实现起来有点乏味,但您可以使用P99立即使用。

您必须将函数签名更改为类似

char *substring(char *string, unsigned int start, int end);

并发明一个特殊代码,end如果它在调用端被省略,比如说-1. 然后用 P99 你可以做

#include "p99.h"

#define substring(...) P99_CALL_DEFARG(substring, 3, __VA_ARGS__)
#define substring_defarg_2() (-1)

您看到您声明了一个“重载”您的函数的宏(是的,这是可能的,常见的 C 库实现一直使用它)并提供有关您的函数接收的参数数量的知识的替换(在这种情况下为 3 )。对于您希望为其具有默认值的每个参数,您将声明第二种类型的宏,_defarg_N后缀N0.

此类宏的声明不是很漂亮,但至少与 va_arg 函数的接口一样多地说明了正在发生的事情。收益在调用者(“用户”)方面。在那里你现在可以做类似的事情

substring("Hello", 2); 
substring("Holla", 2, 2);

随心所欲。

(您需要一个为所有这些实现 C99 的编译器。)


编辑:如果您不想实现该约定end但想要拥有两个不同的功能,您甚至可以走得更远。您将实现这两​​个功能:

char *substring2(char *string, unsigned int start);
char *substring3(char *string, unsigned int start, unsigned int end);

然后将宏定义为

#define substring(...)                \
 P99_IF_LT(P99_NARG(__VA_ARGS__, 3))  \
 (substring2(__VA_ARGS__))            \
 (substring3(__VA_ARGS__))

然后,这将确保预处理器通过查看它接收的参数数量来选择适当的函数调用。


Edit2:这里是一个更适合的substring函数版本:

  • 使用对长度和类似内容在语义上正确的类型
  • 第三个参数似乎是你的长度而不是字符串的结尾,相应地命名它
  • strncpy几乎从来都不是选择正确的函数,在某些情况下它不写终止'\0'字符。当您知道字符串的大小时,请使用memcpy.

char *substring(char *string, size_t start, size_t len) {
  size_t length = strlen(string);
  if(len == SIZE_MAX){
    len = length - start;
  }
  char *to_string = malloc(len + 1);
  memcpy(to_string, string+start, len);
  to_string[len] = '\0';
  return to_string;
}
于 2012-04-22T12:16:00.493 回答
2

不幸的是,你不能这样使用 va_arg

另请注意, va_arg 无法确定检索到的参数是否是传递给函数的最后一个参数(或者即使它是超出该列表末尾的元素)。该函数的设计方式应使参数的数量可以通过命名参数或已读取的附加参数的值以某种方式推断出来。

一个常见的“解决方法”是给另一个“重载”一个好记的名字,例如right_substr. 它看起来不会那么花哨,但它肯定会运行得更快。

如果您担心重复实现,您可以实现left_substrsubstringright_substr作为隐藏函数的包装器,该函数将startlength作为有符号整数,并将负数解释为缺失参数。在你的公共接口中使用这个“约定”可能不是一个好主意,但它可能在私有实现中工作得很好。

于 2012-04-22T10:51:53.980 回答
1

在标准 C 中,当使用可变参数原型 ( ...) 时,无法直接判断传递了多少参数。

在幕后,诸如printf()etc 之类的函数假定基于格式字符串的参数数量。

其他采用可变数量指针的函数期望列表以 NULL 终止。

考虑使用其中一种技术。

于 2012-04-22T10:51:46.057 回答