【问题标题】:Using a string in CS50 library在 CS50 库中使用字符串
【发布时间】:2018-09-03 05:18:45
【问题描述】:

大家好,我有一个关于将字符串传递给 C 中的函数的问题。我正在使用 CS50 库,我知道他们将字符串作为 char 数组(指向数组开头的 char 指针)传递,所以传递是通过参考。我的函数接收数组作为参数并返回数组。例如,当我更改函数中的数组元素之一时,此更改会按我的预期反映到原始字符串。但是如果我将新字符串分配给参数,函数会返回另一个字符串并且原始字符串不会改变。你能解释一下这种行为背后的机制吗?

#include <stdlib.h>
#include <cs50.h>
#include <stdio.h>


string test(string s);

int main(void)
{
    string text = get_string("Text: ");
    string new_text = test(text);
    printf("newtext: %s\n %s\n", text, new_text);
    printf("\n");
    return 0;
}

string test(string s)
{
    //s[0] = 'A';
    s = "Bla";
    return s;
}

第一个例子反映了 text 和 newtext 字符串的第一个字母的变化,但是第二个例子打印出 text 不变并且 newtext 为“Bla” 谢谢!

【问题讨论】:

  • 请添加代码来说明您的意思。
  • 请提供您的问题的Minimal, Complete, and Verifiable Example。我们 StackOverflow 非常愿意为您提供帮助,但如果您能发布一些代码和您遇到的错误,我们将不胜感激。
  • "通过引用传递" 不,C 没有通过引用传递。您正在按值传递指针。修改指针本身不会修改原始值,修改指向的数组但是修改相同的内存。
  • 我不明白这个问题。您将text 作为一个指针传递给函数test,该指针被忽略并用作函数返回的局部指针变量。 text 没有任何反应,因此它会按照您输入的内容打印。注意:您注释掉了更改第一个字符的代码,使问题变得混乱,以及不清楚的“第一个和第二个示例”叙述。

标签: c string cs50


【解决方案1】:

这需要一段时间。

让我们从基础开始。在 C 中,string 是包含 0 值终止符的字符值序列。 IOW,字符串 "hello" 表示为序列{'h', 'e', 'l', 'l', 'o', 0}。字符串存储在char 的数组中(或wchar_t 用于“宽”字符串,我们不会在这里讨论)。这包括 字符串字面量,例如 "Bla" - 它们存储在 char 的数组中,以便在程序的整个生命周期内都可用。

在大多数情况下,“T 的 N 元素数组”类型的 表达式 将被转换(“衰减”)为“指向 T 的指针”类型的表达式,所以大多数时候,当我们处理字符串时,我们实际上是在处理char * 类型的表达式。但是,这并不意味着 char * 类型的表达式 字符串 - char * 可能指向字符串的第一个字符,也可能指向序列中的第一个字符不是一个字符串(没有终止符),或者它可能指向一个不属于更大序列的单个字符。

char * 还可以指向由malloccallocrealloc 分配的动态分配 缓冲区的开头。

另外需要注意的是,[] 下标运算符是根据指针算术定义的 - 表达式 a[i] 定义为 *(a + i) - 给定地址值 a(如所述从数组类型转换以上),从该地址偏移i 元素(不是字节)并取消引用结果。

另一个需要注意的重要事情是= 未定义为将一个数组的内容复制到另一个数组。事实上,数组表达式不能成为= 运算符的目标。

CS50 string 类型实际上是 char * 类型的 typedef(别名)。 get_string() 函数在幕后执行了许多魔术来为字符串内容动态分配和管理内存,并使 C 中的字符串处理看起来比实际水平高得多。我和其他几个人认为这是教 C 的不好方法,至少在字符串方面是这样。不要误会我的意思,它是一个非常有用的实用程序,只是一旦你没有可用的 cs50.h 并且必须开始进行自己的字符串处理,你就会在海了一会儿。

那么,所有这些废话与您的代码有什么关系?具体来说,这行

s = "Bla";

发生的事情是,不是将字符串文字"Bla" 的内容复制到s 指向的内存,而是将字符串文字的地址 写入s,覆盖之前的指针值。您不能使用= 运算符将一个字符串的内容复制到另一个;相反,您必须使用像 strcpy 这样的库函数:

strcpy( s, "Bla" );

s[0] = A 按预期工作的原因是因为下标运算符[] 是根据指针算术定义的。表达式a[i] 被评估为*(a + i) - 给定地址a(一个指针,或如上所述“衰减”为指针的数组表达式),偏移i 元素(不是bytes!) 从该地址中提取并取消引用结果。所以s[0] 指向你读入的字符串的第一个元素。

【讨论】:

  • “这样它们在程序的整个生命周期内都可用” - 不,你应该更正这个。
  • 感谢这个答案澄清了我对 CS50 字符串类型的困惑。
  • @Broman -- 声明有什么问题? String literals are stored in character arrays of static duration,具有静态持续时间的对象的生命周期持续 "the entire execution of the program."
  • 我对这个答案还有一个后续问题。因此,如果我声明不同的 char 指针,例如 char *s 和 char *t 并为它们分配相同的字符串文字,例如“测试”两个指针将显示到相同的地址,或者每个文字值会有两个不同的地址?我猜他们会指向同一个地址。
  • @IvanStimac:无论哪种方式都不能保证;语言定义未指定。我希望希望单个翻译单元(预处理的源文件)中所有出现的字符串文字都映射到同一个位置,但如果字符串文字出现在多个翻译中,我不会感到惊讶单位被映射到多个位置。
【解决方案2】:

如果没有代码示例,这很难正确回答。我会做一个,但它可能与你正在做的不匹配。

让我们来看看这个 C 函数:

char* edit_string(char *s) {
    if(s) {
        size_t len = strlen(s);
        if(len > 4) {
            s[4] = 'X';
        }
    }
    return s;
}

该函数将接受一个 pointer 到一个 字符数组,如果指针不为 NULL 并且以零结尾的数组长于 4 个字符,它将替换索引 4 处的第五个字符,带有“X”。 C 中没有引用。它们总是被称为指针。它们是同一个东西,您可以使用 dereference 运算符 *p 或像 p[0] 这样的数组语法访问指向的值。

现在,这个函数:

char* edit_string(char *s) {
    if(s) {
        size_t len = strlen(s);
        if(len > 4) {
            char *new_s = malloc(len+1);
            strcpy(new_s, s);
            new_s[4] = 'X';
            return new_s;
        }
    }
    s = malloc(1);
    s[0] = '\0';
    return s;
}

该函数返回一个指针新分配的原始字符数组的副本,或新分配的空字符串。 (通过这样做,调用者可以随时将其打印出来并在结果上调用free。)

它不会改变原来的字符数组,因为new_s没有指向原来的字符数组。

现在你也可以这样做了:

const char* edit_string(char *s) {
    if(s) {
        size_t len = strlen(s);
        if(len > 4) {
            return "string was longer than 4";
        }
    }
    s = "string was not longer than 4";
    return s;
}

请注意,我将返回类型更改为 const char*,因为像 "string was longer than 4" 这样的字符串文字是常量。试图修改它会使程序崩溃。

在函数内对s 进行赋值不会改变s 曾经指向的字符数组。指针s指向或引用原始字符数组,然后s = "string"之后指向字符数组"string"

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-09-26
    • 1970-01-01
    • 1970-01-01
    • 2012-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多