如何区分C语言函数中的输入、输出和输入输出参数?

3

在阅读一些软件模块的“软件详细描述”文档时,我发现每个函数的描述中都有以下内容:

  1. 输入参数:......
  2. 输出参数:......
  3. 输入输出参数:......

例如,我们有以下内容:

typedef struct
{
    u16_t elementA;
    u16_t elementB;
    u8_t  elementC;

} myStruct;


void somefunction(myStruct *pToMyStruct)
{

   pToMyStruct->elementA  = 1;
   pToMyStruct->elementB  = 5;
   pToMyStruct->elementC  = 7;
}

在软件说明文档中,描述如下:
  1. 输入参数:无
  2. 输出参数:无
  3. 输入输出参数:指向结构体的指针(pToMyStruct)
我对C编程技术不是很了解,但在这种情况下,“pToMyStruct”为什么是输入输出参数?为什么它不仅仅是输入参数?作为一个经验不足的程序员,我该如何轻松地识别函数中的这3种参数类型?例如,输入参数只在其自己的函数内进行修改,是吗?
感谢您!

标题有误导性,因为它说“如何在C中区分......”,而最佳答案适用于**c ++**,对C不适用。看来你想要一个C++的答案。所以我浪费了时间回答这篇帖子,认为它符合您的标题。现在我看到了-太晚了,您标记了这篇帖子的[C]和[C++]。这应该是表明您的问题不清楚,因为每种语言的答案都不同。建议1)将标题更改为C++,删除[C]标记并避免双语标记-很少需要。 - chux - Reinstate Monica
不,它真的在考虑C语言。所有这些答案对我都很有帮助,也感谢你们。如果有可能将所有答案标记为“接受”,我会这样做 :) 所以我想至少将其中一个答案标记为已接受... - JohnDoe
1
建议以后发布帖子时,仍然建议避免同时标记C/C++ - 最好选择其中一种。即使这两种语言有共同之处(和很长的历史),它们的最佳答案通常是截然不同的。 - chux - Reinstate Monica
7个回答

3
// INPUT-OUTPUT PARAMETER: pointer to structure (pToMyStruct)
void somefunction(myStruct *pToMyStruct) {
   pToMyStruct->elementA  = 1;
   ...
}

"为什么它不仅是输入参数?" - 从技术上讲,您是正确的:它只是一个输入参数。函数的唯一输出是其返回值以及对全局环境(全局变量、printf()等)的影响。
“软件详细说明”(作者)认为“C语言没有按引用传递”,因此想创造一个新的“观点”。例如:我们将输入指针参数称为伪按引用传递,即:输入、输出或IO。作者希望您按照这个模型进行文档记录。
// the return value is an output
int foo()

// x is an input parameter
void foo(int x)

// what x points to is an input parameter, be it an `int` or array of `int` or NULL
void foo(const int *x)

// what x points to is an output parameter 
void foo(int *x) {
  // *x not read before being set

// what x points to is an I/O parameter
void foo(int *x) {
  // *x read before being set

所以myStruct *pToMyStruct是一个输出或IO参数。如果不深入函数体内部,最好将其分类为IO,因为函数签名允许读取和写入*pToMyStruct。鉴于这个简单的示例函数,它是一个输出参数。


2
那是因为传递了一个指向结构体的指针,并且它没有被“const”限定。以下是规则:
  1. 输入:一个普通参数,函数不会修改。这可以是一个“const”指针。
  2. 输出:一个指向变量的指针,在函数返回时函数将修改该变量。在调用函数之前,该变量未初始化。
  3. 输入-输出:上述两种的组合。您传递一个指针,它已经指向有效数据,当函数返回时,数据将以某种方式被更改。“swap”函数就是一个很好的例子。

2
如描述所述,pToMyStruct是一个指针,这意味着参数实际上是结构体数据的内存地址。因此,传输的数据可以被使用也可以被修改,这就是为什么它是一个输入输出参数。
一个输入参数是一个在函数中不能被修改的参数,就像一个普通变量一样。
一个简单的输出参数基本上是函数返回的值。
问题在于,函数经常需要有几个输出变量。为了做到这一点,必须使用作为参数给出的指针。因此,除非你对代码有非常好的理解,否则你无法推断参数中的指针是输入、输出还是输入输出。唯一确定它的方法是查看文档,就像你为你的例子所做的那样。

1

输入 - 你仅向函数传递一个值/参数。 输出 - 函数将更新传递的变量的值(这只有在C中使用指针,在C++中使用指针或引用才可能实现) 输入和输出 - 同一变量可用于传递值并获取更新后的值,变量类型与输出相同。

在您的代码中,传递了一个结构变量,并在调用somefunction()函数时获取了一个更新的结构。因此,它是输入和输出变量。


1

与其他一些语言不同,C(以及C++)实际上没有关键字来指示参数是什么,但通常有以下规则:

  • 输入参数通常是指向const的指针(或按值传递)
  • 如果可能,应避免使用纯输出参数,而应使用返回值(这就是它们存在的原因)。
  • 因此,除非文档另有说明或函数名明显表明,否则我会假设指向非const参数的指针表示输入输出参数。

编辑:
我应该提到第二点(以及因此第三点)可能有些争议,因为有很多使用纯输出参数的API - 一些出于好的原因(例如,因为它们使用返回值来指示失败或成功),一些是因为它们的编译器比较愚蠢,这实际上是一个重要的性能优化。


1

C中的参数始终通过拷贝传递。当您使用指针时,它会复制指向同一对象的指针。然后,如果您修改“指针副本”的内容,它将修改“真实对象”。

因此,它是一个输入/输出参数(因为可以修改它)。

如果您有

void somefunction(myStruct theStruct)

那么将复制结构本身;然后它将成为一个输入参数。


1
但在这种情况下,“pToMyStruct”为什么是一个输入输出参数?为什么它不仅是一个输入参数?作为一个没有经验的程序员,我如何容易地识别函数中的这3种类型的参数?例如,输入参数仅在其自己的函数中被修改。
我喜欢这个问题。也许回答需要更多的想法,但以下是一些基本想法:
- 想法1-形式参数不能被函数/方法修改,只能作为函数或方法的输入。
因此,“const T formalParam”显然是一个输入... const命令编译器如果函数试图修改const T formalParam则声明错误。
同样,“const T&formalParam”也是一种输入。有些人使用这种参数样式来提供对大型数据项的访问,同时避免复制的成本。
  • 想法2 - 作为按值传递的形式参数的副本只能输入到函数或方法中。

因此在 "void foo(int t1);" 中,类型为int的形式参数t1是实际参数的一个副本。更改t1不会影响实际的int。该参数的行为就像是一个局部自动变量,其预先初始化为实际参数值的副本。

这也适用于传递类实例。所以在 "void foo (T t2)" 中,形式参数是某个实际参数的副本。同样,修改副本不会对原始对象产生影响,因此这只能作为输入。


  • Idea 3 - 正式参数是指向现有实际参数的引用(或指针),如按引用传递(也许您可以说按指针传递),可以是输出或输入/输出参数。

因此,在“void foo(int& t1, char* label)”中,两个形式参数都可以是输出或输入/输出。您只能通过查看方法代码中如何使用形式参数来确定。


  • 想法4- 当我刚开始学习C++时,我发现研究库函数很有启发性。以下是一些例子:
void* memcpy( void* dest, const void* src, size_t count );
// wrt memcpy: ^^output   ^^^^^input       ^input:pass-by-value
// Note direction:   to <---move <---from
// I think this right-to-left idea might be a trend in C (research?)
// return would seem useless, but is sometimes convenient value: copy of dest


int memcmp ( const void* lhs, const void* rhs, size_t count );
//           ^^^^^input       ^^^^^input       ^input:pass-by-value
// return is result of comparison negative: int, 0, positive int


int isalpha( int ch );
//           ^input:pass-by-value
// return is 0 (false) or non-zero (true)



//from std::string::find

size_type find( const basic_string& str, size_type pos = 0 ) const;
//              ^^^^^ input              ^input: pass-by-value
// return is npos or index where str found

  • 想法4 - 你的下一个工作可能会指定一种称为“编码标准”的输入输出和结果顺序。

我已经查阅了一些谷歌的编码标准,很容易找到。有关此主题的书籍也已出版。(研究努力:搜索C++编码标准或者C编码标准并进行评估。)

  • 想法5 - 咨询他人

我更喜欢我的方法的返回值是一个“状态”,而不是方法或函数的结果。(状态意味着好或失败)因此,如果我的代码输出某些内容,则必须至少有1个输出参数,但是方法通常修改类实例的数据属性(而不是输出参数)。我通常将它们排列为:

T  foo ( <input parameters> , <output parameters> );
T可以是任何具有简单和快速评估的类。最近,我使用了std :: string,当string.size()为0时,这意味着在函数执行期间没有错误。当大小为正时,字符串包含用于报告的错误消息。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接