【问题标题】:Store text between two characters in a array在数组中存储两个字符之间的文本
【发布时间】:2021-01-21 17:16:13
【问题描述】:

我有这个字符数组char txt[80] = "Some text before $11/01/2017$";,需要将两个$ 之间的内容复制到一个字符串11/01/2017 中。如何使用 <string.h> 函数做到这一点?

【问题讨论】:

  • 您确定字符串中只有 2 个$
  • 你试过malloc和while循环的巧妙组合吗?你试过什么?
  • 在字符串中查找the firstthe last'$'。然后获取这两点之间的文本长度,并将copy从第一个放入一个新数组中。
  • 是的,我确定我只有两个“$”
  • 为什么不使用简单的for 循环,读取和复制字符?

标签: arrays c string char


【解决方案1】:

获取不修改原始字符串的单个标记的一种简单方法是使用两次对strcspn() 的调用,建立指向第一个分隔符(在本例中为'$')的起始指针和指向令牌中的最后一个字符(第二个 '$' 之前的字符或字符串结尾,如果不存在第二个 '$')。然后验证开始指针和结束指针之间是否存在字符,并使用memcpy() 复制令牌。

一个简短的例子是:

#include <stdio.h>
#include <string.h>

int main (void) {
    
    char txt[80] = "Some text before $11/01/2017$",
        *sp = txt + strcspn (txt, "$"),                 /* start ptr to 1st '$' */
        *ep = sp + strcspn (*sp ? sp + 1 : sp, "$\n"),  /* end ptr to last c in token */
        result[sizeof txt] = "";                        /* storage for result */
    
    if (ep > sp) {                                      /* if chars in token */
        memcpy (result, sp + 1, ep - sp);               /* copy token to result */
        result[ep - sp] = 0;                            /* nul-termiante result */
        printf ("%s\n", result);                        /* output result */
    }
    else
        fputs ("no characters in token\n", stderr);
}

(注意:三元只处理txt是空字符串的情况。'\n'被添加为第二个分隔符的一部分来处理字符串从fgets() 或POSIX getline() 过去,其中不存在第二个'$''\n' 是字符串中的最后一个字符。)

也适用于空字符串、零、一个或两个'$' 的任意组合,并且不会修改原始字符串,因此可以安全地与 String-Literals 一起使用。

使用/输出示例

$ ./bin/single_token
11/01/2017

如果您还有其他问题,请告诉我。


允许有效空字符串作为结果的变体

@chqrlie 提供的一个巧妙的改进是对(*sp == '$') 的测试而不是(ep &gt; sp) 将允许空字符串(令牌中没有字符)成为有效结果——我同意)。更改将是:

    if (*sp == '$') {                                   /* if chars in token */
        memcpy (result, sp + 1, ep - sp);               /* copy token to result */
        result[ep - sp] = 0;                            /* nul-termiante result */
        printf ("%s\n", result);                        /* output result */
    }

因此,如果您想将空令牌(如 .csv 中的空字段,例如 "one,,three,four")视为有效令牌,请使用此替代方法。

【讨论】:

  • 我于 1981 年开始驾驶 :)(琐事——以 1,200 美元的价格购买了一辆 1968 年的 SS 386 El Camino——过去的好时光,您只需不到 5 美元就可以换油)
  • 检查ep - sp 会消除所有格式错误的字符串,例如"""$""$$",如果需要,您可以添加else 子句以输出诊断信息。我猜result 应该用result = ""; 初始化为一个空字符串,以防止以后可能会误用它。
  • 这正是我的观点:为什么认为"$$" 格式不正确?令牌为空但存在。
  • 好的,所以你说"$$" 会产生一个有效的空字符串——这是有道理的。
【解决方案2】:

假设您确定字符串中有 2 个 $... 您可以执行以下操作:

char *first_dollar = strchr(txt, '$'); //get position of first dollar from the start of string
char *second_dollar = strchr(first_dollar + 1, '$'); //get position of first dollar starting
                                                    // from one position after the first dollar
char tocopy[20];
*second_dollar = '\0'; //change the value of last dollar to '\0'
strcpy(tocopy, first_dollar + 1); //copy into the place you want
*second_dollar = '$'; // put back the second dollar

如果您不确定字符串中有 2 个$,您应该检查strchr 的返回值,即NULL

是否必须使用字符串?有一个巧妙的方法使用sscanf

char txt[80] = "Some text before $11/01/2017$";
char t[20];
sscanf(txt, "%*[^$]$%[^$]", t);
printf("ORIGINAL TEXT: %s\nEXTRACTED TEXT: %s\n", txt, t);

scanf中的格式含义如下:

  1. 忽略所有不是$的字符;
  2. 忽略 1 $
  3. 读取所有字符,直到找到下一个 $ 并将其存储在 t 中。

【讨论】:

  • 有很多解决方案不尝试修改源字符串,如果传递字符串文字会崩溃。此外,sscanf() 方法无法处理这种情况:"$$" 因为%[^$] 必须至少匹配一个字节。
【解决方案3】:

有一个叫做 strtok 的函数。 (https://www.cplusplus.com/reference/cstring%20/strtok/) 这是一个关于它的视频:https://www.youtube.com/watch?v=34DnZ2ewyZo

我试过这段代码:

#include <stdio.h>
#include <string.h>

int main()
{
    char txt[] = "Some text before $11/01/2017$, some text, $11/04/2018$ another text more and more text $01/02/2019$";
int skip = 0;

char* piece = strtok(txt, "$");

while(piece != NULL)
{
    piece = strtok(NULL, "$");

    if(piece == NULL)
        break;

    if(skip != 1)
    {
        skip = 1;    
        printf("%s \n", piece);
    }
    else
        skip = 0;
    
}

    return 0;
}

输出:

11/01/2017
11/04/2018
01/02/2019

【讨论】:

  • 它对我有用,但你能解释一下代码吗?
  • strtok 分割 char[]。 if(skip != 1) 将始终跳过诸如“some text”之类的字符串... piece = strtok(NULL, "$"); stackoverflow.com/questions/23456374/…
  • @0___________ 第一个角可以通过在 while 循环之前测试 if(txt != NULL) 来修复
  • 注意:strtok() 修改了原始字符串(用'\0' 替换分隔符),因此它不能与String-Literals 一起使用。因此,如果要标记化的字符串是字符串文字,或者您需要保留原始字符串——制作可变副本并标记副本。
【解决方案4】:

提取$之间的文本

这可以通过一个简单的for 循环、读取和复制字符来完成。
在下面的代码中,参数inside表示我们当前是否在两个$之间。

如果有效地找到两个 $,则函数返回 1

#include <stdio.h>
#include <string.h>

// return 1 if two $ have been found, 0 elsewhere
int extract (char *in, char *out, char c) {
    if (in == NULL) return 0;
    int size = strlen(in);
    int inside = 0;
    int n = 0;      // size new string
    for (int i = 0; i < size; ++i) {
        if(in[i] == c) {
            if (inside) {
                inside = 2;
                break;  // 2nd $
            }
            inside = 1;         // 1st $
        } else {
            if (inside) {       // copy
                out[n++] = in[i];
            }
        }
    }
    out[n++] = '\0';
    return inside == 2;
}

int main() {
    char txt[80] = "Some text before $11/01/2017$";
    char txt_extracted[80];
    int check = extract (txt, txt_extracted, '$');
    if (check) printf ("%s\n", txt_extracted);
    else printf ("two $ were not found\n");
    return 0;
}

【讨论】:

  • 在角落情况下不起作用godbolt.org/z/c18da9
  • @0___________ 有点牵强,但更正处理 NULL 案例。我尝试使用strnlen_s 处理更多极端情况,但我的旧 gcc 编译器(在我的家用 PC 上)无法处理。
  • @0___________: in == NULL 不是极端情况,它超出了规范:OP 说输入是 char 数组。但是优雅地处理空指针可能是个好主意。
  • @chqrlie 数组不能按值传递。我认为 OPs 术语不是很准确,因为他是初学者。
【解决方案5】:

这是一个函数。无把手井角箱。使用string.h 函数。

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

char *texBetween(const char *str, char ch)
{
    char *result = NULL;
    const char *start, *end;

    if(str)
    {
        start = strchr(str, ch);
        if(start)
        {
            end = strchr(start + 1, ch);
            if(end)
            {
                result = malloc(end - start);
                if(result)
                {
                    memcpy(result, start + 1, end - start - 1);
                    result[end - start] = 0;
                }
            }
        }
    }
    return result;
}

int main()
{
    char *result;
    printf("\"%s\"\n", (result = texBetween("$$", '$')) ? result : "ERROR"); free(result);
    printf("\"%s\"\n", (result = texBetween("$ $", '$')) ? result : "ERROR"); free(result);
    printf("\"%s\"\n", (result = texBetween("$test$", '$')) ? result : "ERROR"); free(result);
    printf("\"%s\"\n", (result = texBetween("test$$", '$')) ? result : "ERROR"); free(result);
    printf("\"%s\"\n", (result = texBetween("test$test1$", '$')) ? result : "ERROR"); free(result);
    printf("\"%s\"\n", (result = texBetween("test$1234$test$dfd", '$')) ? result : "ERROR"); free(result);
    printf("\"%s\"\n", (result = texBetween(NULL, '$')) ? result : "ERROR"); free(result);
    printf("\"%s\"\n", (result = texBetween("", '$')) ? result : "ERROR"); free(result);
    printf("\"%s\"\n", (result = texBetween("$", '$')) ? result : "ERROR"); free(result);

}

https://godbolt.org/z/K5P6zb

【讨论】:

  • 测试台不是很明确。您应该同时输出源字符串和提取的字符串,并检查提取的字符串是否是预期的。
  • @chqrlie,因为它是非常简单的测试,并且极端情况很容易预测我相信这就足够了
【解决方案6】:

我不知道你为什么需要使用 string.h。

供大家参考,这里是没有string.h的方法。

更新

#include <stdio.h>
#include <string.h>
int main(){
  char txt[80] = "Some text before $21/01/2017$ and $32/01/2017$ and $$ end $abc$";
  char get[80] = { '\0' };
  int i = 0, k = -1, j = 0;
  int len = strlen( txt ); // Get length
  for ( i = 0 ; i < len ; i++ ){
    bool   find = false;
    for ( i  ; txt[i] != '$' && txt[i] != '\0' ; i++ ); // Find '$' location
      if ( txt[i] == txt[i+1] && txt[i] == '$' ) { // Check $$ case
        find = true;
        get[++k] = ' ';
      } // if
      for ( j = i + 1 ; txt[j] != '$' && txt[j] != '\0' ; j++ ){
        find = true;
        get[++k] =  txt[j];
      } // for

   if ( find == true ) get[++k] = ' '; // add space
    i = j ;
  } // for

  get[k] = '\0'; // remove last space
  printf( "%s", get );
  return 0;
} // main()

输出:

21/01/2017 32/01/2017   abc

【讨论】:

  • 请注意,如果缺少第二个 $,此代码将不会停止
  • 很多极端案例都失败了
  • @Damien:如果第一个 $ 丢失,它甚至不会停止...在这两种情况下都有未定义的行为。
  • @Damien 谢谢你的提醒,我更新了代码。
猜你喜欢
  • 2017-01-13
  • 1970-01-01
  • 2014-03-26
  • 2013-08-07
  • 1970-01-01
  • 2013-02-21
  • 2018-11-02
  • 2019-10-10
  • 2019-09-05
相关资源
最近更新 更多