【问题标题】:How to Pass a Char Array (String) into a Linked List (Queue)如何将字符数组(字符串)传递到链表(队列)
【发布时间】:2018-05-17 04:00:29
【问题描述】:

我有一个 C 程序代码,它涉及将一个句子分成单独的单词并将这些单词放入一个链表中。我的问题是我是否应该将我的数据作为指针或字数组传递。

我在这里包含了部分代码。有的论坛说用 strcpy 传字符串,但是是在我的 *insert_at_foot 函数中还是在数据结构中?

测试输入到这里,想在运行scanf获取字典后的最后一行读取(百分号后)

#hewitt
5 95 0
#hugh
40 60 0
#jackman
0 100 0
#logan
40 0 60
#melbourne
5 5 90
#sydney
5 5 90
#zack
40 40 20
%%%%%%%%%%
hugh jackman is retiring the wolverine character after logan

谢谢大家:)

typedef struct node node_t;

/* word of max letters stored in each data_t value */
typedef char data_t[MAXLETTERS];

/* listops.c */
struct node {
    data_t word; //dereferences the first letter in data_t[MAXLETTERS] 
    node_t *next;
};

typedef struct {
    node_t *head;
    node_t *foot;
} list_t;

int 
main (int argc, char *argv[]){
    ...
    while(scanf("%s", getword(word, limit))!=EOF){
        insert_at_foot(list, word);
    }

    while (!is_empty_list(list)) {
        word = get_head(list);
        printf("%s\n", word); 
        list = get_tail(list);
    }

    print_list(list);
  }

  list_t
*insert_at_foot(list_t *list, data_t word) { 
    node_t *new;
    new = (node_t*)malloc(sizeof(*new));
    assert(list!=NULL && new!=NULL);
    //strcpy(new->data, word);
    new->data = word;
    new->next = NULL;
    if (list->foot==NULL) {
        /* this is the first insertion into the list */
        list->head = list->foot = new;
    } else {
        list->foot->next = new;
        list->foot = new;
    }
    return list;
}

【问题讨论】:

  • 不要typedef 数组——它只会让你困惑不已......而且也不要typedef 指针(同样的原因)。你不能分配字符串(例如new->data = word;)是错误的。 (你的编译器应该警告你,如果你在编译时启用了警告,-Wall -Wextra 用于 gcc/clang,/W3 用于 VS,至少)。您必须使用strcpy (new-data, word),因为new->datawordinsert_at_foot 中都是char*,但您不能立即知道这一点,因为您已经输入了typedef char data_t[MAXLETTERS];
  • 我可以建议退后一步吗? ericlippert.com/2014/03/21/find-a-simpler-problem你结合了一些概念,它可能会帮助你首先熟悉它们。练习你的链表技能,例如整数或单个字符。然后用分配的内存和相关的指针来练习你的技能。然后练习字符串处理。然后开始组合,例如存储在链表中的静态长度(或静态最大长度)字符串。然后在分配的内存中使用动态长度的字符串进行组合,由指针作为链表中的数据引用。
  • 谢谢 Yunnosch 和 David,感谢您的建议。

标签: c linked-list


【解决方案1】:

如果您仍然在为问题苦苦挣扎,那么您已经不远了,但是您可以根据您提供的示例数据文件来更轻松地读取和解析信息。如果你看文件,你只关心读取最后一行数据并将其分离成单词。查看前面的所有行,它们都以标点符号(#%)开头,而最后一行以字母字符开头。

虽然有很多方法可以做到这一点,但一种非常有效的方法是使用fgets(或POSIX getline)简单地将每一行读入一个固定缓冲区(比如word),然后使用工具从<ctype.h> 测试第一个字符是ispunct() 还是isdigit()。如果任一测试true,只需阅读下一行。这种方法的简单性意味着当您退出读取循环时,您的读取缓冲区中包含最后一行。一个简单的实现是:

#define MAXLETTERS 256
...
    char word[MAXLETTERS] = "",   /* fixed buffer to hold each line */
        *p = word,                /* pointer to with for strtok */
        *delim = " \t\n";         /* delimiters to use with strtok */
    ...
    while (fgets (word, MAXLETTERS, stdin)) /* read/discard until last line */
        if (ispunct (*word) || isdigit (*word))
            continue;
        else
            break;

使用word 中包含的行,您可以使用strtok 根据您指定的任何分隔符(' ''\n')将行分隔为单个单词,这在此处是有意义的。 strtok 返回一个指向每个单词开头的指针,并且在每次连续调用时,将指向该行中的下一个单词。您对strtok 的第一次调用使用包含您的行的缓冲区的名称,例如

    char word[MAXLETTERS] = "",   /* fixed buffer to hold each line */
    ...
    p = strtok (p, delim);        /* 1st call to strtok for 1st word */

每个连续调用都使用NULL 代替buf,例如

    p = strtok (NULL, delim);     /* all subsequent calls use NULL */

strtok到达原始字符串的末尾时,它会返回NULL

注意: strtok 通过在对字符串进行标记时插入 '\0' 字符来修改字符串——因此,如果您需要维护原始字符串,请复制原始字符串)

然后,您只需将每个标记(单个单词)传递给您的 insert_at_foot (list, p) 函数。您可以将所有步骤组合成一个简单的for 循环,如下所示:

    /* tokenize last line using strtok */
    for (p = strtok (p, delim); p; p = strtok (NULL, delim))
        insert_at_foot (list, p);   /* insert word in llqueue */

insert_at_foot () 中,您不能分配 字符串。如 cmets 中所述,问题的一个潜在来源是您键入了一个数组,该数组掩盖了函数中 word 的类型。它只是char*,你必须使用strcpy复制到new->word不是new->word = word;

修复该问题并整理函数并为list 添加验证检查,您可以执行以下操作:

list_t *insert_at_foot (list_t *list, char *word)
{
    node_t *new = malloc(sizeof *new);
    assert (list != NULL && new != NULL);   /* validate both list & node */

    strcpy (new->word, word);   /* you cannot assign strings, strcpy */
    new->next = NULL;           /* initialize next to NULL */

    if (list->foot==NULL) {     /* check if list is empty */
        /* this is the first insertion into the list */
        list->head = list->foot = new;
    }
    else {  /* additional nodes added at foot */
        list->foot->next = new;
        list->foot = new;
    }

    return list;
}

总而言之(并填写您在帖子中未提供的功能),一个工作示例可能类似于:

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

#define MAXLETTERS 256

typedef struct node node_t;

/* listops.c */
struct node {
    char word[MAXLETTERS]; //dereferences the first letter in data_t[MAXLETTERS]
    node_t *next;
};

typedef struct {
    node_t *head;
    node_t *foot;
} list_t;

list_t *insert_at_foot (list_t *list, char *word);

int is_empty_list (node_t *thenode)
{
    return thenode == NULL;
}

int main (void) {

    char word[MAXLETTERS] = "",
        *p = word,
        *delim = " \t\n";
    list_t *list = calloc (1, sizeof *list);    /* allocate list */

    while (fgets (word, MAXLETTERS, stdin)) /* read/discard until last line */
        if (ispunct (*word) || isdigit (*word))
            continue;
        else
            break;

    /* tokenize last line using strtok */
    for (p = strtok (p, delim); p; p = strtok (NULL, delim))
        insert_at_foot (list, p);   /* insert word in llqueue */

    // print_list(list);
    node_t *iter = list->head;      /* temp node to iterate over list */
    while (!is_empty_list(iter)) {  /* while node not NULL */
        node_t *victim = iter;      /* temp node to free */
        printf("%s\n", iter->word); /* output word saved in node */
        iter = iter->next;          /* set iter to next node */
        free (victim);              /* free current node */
    }
    free (list);    /* don't forget to free the list */
}

list_t *insert_at_foot (list_t *list, char *word)
{
    node_t *new = malloc(sizeof *new);
    assert (list != NULL && new != NULL);   /* validate both list & node */

    strcpy (new->word, word);   /* you cannot assign strings, strcpy */
    new->next = NULL;           /* initialize next to NULL */

    if (list->foot==NULL) {     /* check if list is empty */
        /* this is the first insertion into the list */
        list->head = list->foot = new;
    }
    else {  /* additional nodes added at foot */
        list->foot->next = new;
        list->foot = new;
    }

    return list;
}

输入文件示例

$ cat dat/llqueue.txt
#hewitt
5 95 0
#hugh
40 60 0
#jackman
0 100 0
#logan
40 0 60
#melbourne
5 5 90
#sydney
5 5 90
#zack
40 40 20
%%%%%%%%%%
hugh jackman is retiring the wolverine character after logan

使用/输出示例

$ ./bin/llqueue <dat/llqueue.txt
hugh
jackman
is
retiring
the
wolverine
character
after
logan

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于分配的任何内存块,您都有 2 个职责:(1)始终保留指向起始地址的指针内存块,因此,(2) 当不再需要它时可以释放

您必须使用内存错误检查程序来确保您不会尝试访问内存或写入超出/超出分配块的边界,尝试读取或基于未初始化的值进行条件跳转,最后,以确认您释放了已分配的所有内存。

对于 Linux,valgrind 是正常的选择。每个平台都有类似的内存检查器。它们都易于使用,只需通过它运行您的程序即可。

$ valgrind ./bin/llqueue <dat/llqueue.txt
==22965== Memcheck, a memory error detector
==22965== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==22965== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==22965== Command: ./bin/llqueue
==22965==
hugh
jackman
is
retiring
the
wolverine
character
after
logan
==22965==
==22965== HEAP SUMMARY:
==22965==     in use at exit: 0 bytes in 0 blocks
==22965==   total heap usage: 10 allocs, 10 frees, 2,392 bytes allocated
==22965==
==22965== All heap blocks were freed -- no leaks are possible
==22965==
==22965== For counts of detected and suppressed errors, rerun with: -v
==22965== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认您已释放已分配的所有内存并且没有内存错误。

如果您还有其他问题或我以任何方式误解了您的问题,请查看并告诉我。

【讨论】:

  • 再次感谢大卫的帮助。我在下面的帖子中发布了我的主要功能。
【解决方案2】:

感谢大卫的详尽回复。我错过了这篇文章,但我终于能够使用 char 指针完成这段代码的一部分,并使用 strcpy 将单词数组复制到其中。现在可以了。我对班上的 strtok() 不熟悉,但是这段代码可能同样有效吗?我还有几个阶段要使用二叉搜索树,并且会随时更新。 `int main (int argc, char *argv[]){

name_t name;        
name_t dict[MAXNAMES];

int limit = MAXLETTERS+NULL_BYTE;

list_t *list = make_empty_list();

char buffer[MAXLETTERS + NULL_BYTE];
char *word;

read_dict(&name, &dict[MAXNAMES]);

print_stage(STAGE3);

while(getword(buffer, limit) != EOF){
    //printf("Read a word: %s\n", buffer);
    word = (char *)malloc(strlen(buffer) + NULL_BYTE);
    strcpy(word, buffer);
    //printf("%s\n", word);
    insert_at_foot(list, word);
}


char percent= ' ';

while (!is_empty_list(list)) {
    word = *get_head(list);
    printf("%s\n", word);
    free(word);
    list = get_tail(list);
}

print_list(list); //unlesss printf(&list)

printf("\n");
free_list(list);
list = NULL;

return 0;

}

【讨论】:

  • 是的,它可以正常工作并且效率相当。你可以使用strtok 或者只是在我认为你的getword() 函数可以读取的时候读一个单词。您也可以一次读取一行,然后重复调用sscanf,并在末尾附加一个%n,以增加您的行缓冲区位置。有很多方法可以做到。一切都好。祝你编码顺利。
  • 你好大卫,我发布了我更新的代码,但我很困惑是否可以将链接列表复制到新数组,然后对其执行二进制搜索,或者只是通过链接列表.现在,我收到了分段错误
  • 从技术上讲,您最好对数组进行排序而不是对列表进行排序。如果你想要一个排序列表,你通常按排序顺序添加节点(这样你就不必担心在以后的排序中洗牌 next-&gt; 指针)。您可以对列表进行排序(通过交换值或交换节点),但只需将列表值转储到数组中,将其传递给 qsort 并遍历列表,将值从排序后的数组写回列表。我会看看你有什么,然后告诉你。
  • 好的,谢谢,在这种情况下,我不太确定是否需要 qsort,因为我正在进行字符串比较。我已经更新了我的论点,使用字典数组与我复制的数组进行比较
  • 我在我的原始答案Sort Linked-list Queue via Array of Pointers 中添加了一个排序。我所做的只是声明char **sort;(这是一个指向字符的指针——或——指向字符的指针*)然后我添加了节点数的计数(size_t nnodes = 0;)并计数每次调用 insert_at_foot 时的节点。然后我只需分配那么多指针sort = calloc (nnodes, sizeof *sort); 并为每个word 分配一个指针。然后简单地将指针传递给qsort 进行排序(保持原始列表不变),但指针已排序。
【解决方案3】:

我目前正在使用二进制搜索来查看我的链接列表,如果在我的字典中找到一个单词作为名字/姓氏/非名字,则相应地打印出来,例如在这个测试输出中.如果没有找到,那么它将打印非名称。输入/输出

#hewitt
5 95 0
#hugh
40 60 0
#jackman
0 100 0
#logan
40 0 60
#melbourne
5 5 90
#sydney
5 5 90
#zack
40 40 20
%%%%%%%%%%
hugh jackman is retiring the wolverine character after logan

=========================Stage 1=========================
Name 0: hewitt
Label probabilities: 5% 95% 0%

=========================Stage 2=========================
Number of names: 7
Average number of characters per name: 5.86

=========================Stage 3=========================
hugh
jackman
is
retiring
the
wolverine
character
after
logan
=========================Stage 4=========================
hugh                            FIRST_NAME, LAST_NAME
jackman                         LAST_NAME
is                              NOT_NAME
retiring                        NOT_NAME
the                             NOT_NAME
wolverine                       NOT_NAME
character                       NOT_NAME
after                           NOT_NAME
logan                           FIRST_NAME

我想知道是循环通过我的链表还是新数组更好,因为我听说在数组上执行二进制搜索会更快。这就是我的想法(粗略的代码):

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

#define MAXLETTERS 30 
#define MAXNAMES 100
#define NULL_BYTE 1
#define EQUALSIGN 25
#define STAGE1 1 
#define STAGE2 2
#define STAGE3 3
#define STAGE4 4
#define STAGE5 5 
#define DICT_END '%'
#define EMPTY ' '
#define BS_NOT_FOUND (-1)
#define BS_FOUND 0

#define SIZE 50 //max of 50 words in the sentence
	
/* name and probabilities of name being first, last, or non-name word */
typedef struct{
	char name[MAXLETTERS+NULL_BYTE];
	int first_name, last_name, non_name;
	
} name_t;

typedef struct node node_t;

typedef char *data_t;

/* listops.c */
struct node {
	data_t data; 
	node_t *next;
};

typedef struct {
	node_t *head;
	node_t *foot;
} list_t;

/* dictionary array of name_t */
typedef name_t dict[MAXNAMES];

/* function prototypes */
void read_name(name_t *name);
void print_stage(int n);
void read_dict(name_t *name, name_t *dict);
int count_char(name_t *name, name_t dict[]);
int getword(char W[], int limit);
list_t *make_empty_list(void);
int is_empty_list(list_t *list);
void free_list(list_t *list);
list_t *insert_at_foot(list_t *list, data_t word);
data_t *get_head(list_t *list);
void push(list_t *list, data_t word);
list_t *get_tail(list_t *list);
void print_list(list_t *list);
void label_names(name_t *dict, list_t *list);
//int binary_search(data_t A[], int lo, int hi, data_t *key, int *locn);
int binary_search(name_t *dict, int lo, int hi, char A[], int *locn);
int compare (const void *a, const void *b);

int 
main (int argc, char *argv[]){
	
	name_t name;		
	name_t dict[MAXNAMES];
	
	int limit = MAXLETTERS+NULL_BYTE;
	
	list_t *list = make_empty_list();
	
	char buffer[MAXLETTERS + NULL_BYTE];
	char *word;
	
	read_dict(&name, &dict[MAXNAMES]);
	
	print_stage(STAGE3);
	
	while(getword(buffer, limit) != EOF){
		//printf("Read a word: %s\n", buffer);
		word = (char *)malloc(strlen(buffer) + NULL_BYTE);
		strcpy(word, buffer);
		//printf("%s\n", word);
		insert_at_foot(list, word);
	}
	
	//char *array;
	//array = (char *)malloc(sizeof(char)*SIZE);
	//strcpy into array
	
	int size=0;
	char A[SIZE]; //linked list copied
	//char *word;
	
	while (!is_empty_list(list)) {
		word = *get_head(list);
		printf("%s\n", word);
		A[size]=*word; //assign word to A[size]
		free(word);
		list = get_tail(list);
		size++;
	}
	printf("size %d array\n", size);
	
	print_list(list); //print the linked list and copies values of linked list to A[]
	//faulty print-list <-----
	
	/* stage 4*/
	int i, locn;
	
	print_stage(STAGE4);
	
	/* sort via the dictionary and *locn (index) in bsearch 
	run via each name in the linked list (i iterations) */
	for (i=0; i<size; i++) {
		printf("inside the loop");
		//run via all names in the dictionary
		if (binary_search(&dict[MAXNAMES], 0, size, A, &locn) == BS_NOT_FOUND) {
			printf("inside 1");
			printf("%s\t NOT_NAME\n", A);
		} else {
			printf("inside 2");
			//if statements to check whether it is a FIRST_NAME/LAST_NAME
			printf("%s\t NAME in A[%d]\n", A, locn);
		}
	}
	
	printf("\n");
	free_list(list);
	//list = NULL;
	
	return 0;
}

/* compare function for qsort to sort strings alphabetically */
int compare (const void *a, const void *b)
{
    /* a & b are pointers-to-char *, so you have to dereference to char* */
    char *word1 = *(char * const *)a,  /* could also use *(char **)a,    */
         *word2 = *(char * const *)b;
           
    return strcmp (word1, word2);
}

//run via each struct in dictionary array, compare char * with ones in linked list 
//A[] = linked list 
int
//name_t *dict = name_t dict[]  name_t dict[].name  = name_t **dict
binary_search(name_t *dict, int lo, int hi, char A[], int *locn) {
	int mid, outcome;
	//dict[count_names].name
	/* if key is in A, it is between A[lo] and A[hi-1] */
	if (lo>=hi) {
		return BS_NOT_FOUND;
	}
	mid = (lo+hi)/2;
	if ((outcome = strcmp(A, dict[mid].name)) < 0) { //string compare function
		return binary_search(&dict[MAXNAMES], lo, mid, A, locn);
		//run via all names
	} else if (outcome > 0) {
		return binary_search(&dict[MAXNAMES], mid+1, hi, A, locn);
	} else {
		*locn = mid;
		return BS_FOUND;
	}
}

/**********************************************/ 

//copy list to an array here
void
print_list(list_t *list){
	node_t *new = list->head;
	while (new){
		printf("%s", new->data);
		//A[size]=*new->data;
		new = new->next;
	}
	printf("\n");
}

/* store a single word in an array from the standard input, no longer than
limit characters, written by Alistair Moffat. Argument array is limit+1 characters */
int
getword(char W[], int limit) {
	int c, len=0;
	/* first, skip over any non alphabetics */
	while ((c=getchar())!=EOF && !isalpha(c)) {
		/* do nothing more */
	}
	if (c==EOF) {
		return EOF;
	}
	/* ok, first character of next word has been found */
	W[len++] = c;
	while (len<limit && (c=getchar())!=EOF && isalpha(c)) {
		/* another character to be stored */
		W[len++] = c;
	}
	/* now close off the string */
	W[len] = '\0';
	return 0;
}

list_t
*make_empty_list(void) {
	list_t *list;
	list = (list_t*)malloc(sizeof(*list));
	assert(list!=NULL);
	list->head = list->foot = NULL;
	return list;
}

int
is_empty_list(list_t *list) {
	assert(list!=NULL);
	return list->head==NULL;
}

void
free_list(list_t *list) {
	node_t *curr, *prev;
	assert(list!=NULL);
	curr = list->head;
	while (curr) {
		prev = curr;
		curr = curr->next;
		free(prev);
	}
	free(list);
}

/* Implementing a queue (FIFO) structure */
list_t
*insert_at_foot(list_t *list, data_t word) { 
	node_t *new;
	new = (node_t*)malloc(sizeof(*new));
	assert(list!=NULL && new!=NULL);
	new->data = word;
	new->next = NULL;
	if (list->foot==NULL) {
		/* this is the first insertion into the list */
		list->head = list->foot = new;
	} else {
		list->foot->next = new;
		list->foot = new;
	}
	return list;
}

data_t
*get_head(list_t *list) {
	assert(list!=NULL && list->head!=NULL);
	return &list->head->data; //returns a char array of type data_t
}

list_t
*get_tail(list_t *list) {
	node_t *oldhead;
	assert(list!=NULL && list->head!=NULL);
	oldhead = list->head;
	list->head = list->head->next;
	if (list->head==NULL) {
		/* the only list node just got deleted */
		list->foot = NULL;
	}
	free(oldhead);
	return list;
}

/**********************************************/ 
	
void
print_stage(int n){ 
	int i;
	for (i=0; i<EQUALSIGN; i++){
		printf("=");
	}
	printf("Stage: %d", n);
	
	for (i=0; i<EQUALSIGN; i++){
		printf("=");
	}
	printf("\n");
}
		
//store name and percentage in name_t
void
read_name(name_t *name){ 
	
	print_stage(STAGE1);
	//can assume test input is valid
		
	/* consume the hash */
	getchar();
	
	name = (name_t *)malloc(sizeof(*name)*(MAXLETTERS+NULL_BYTE));
		
	/* ensure the program takes the correct input */
	assert(scanf("%s\n %d %d %d",  name->name, &name->first_name, 
		&name->last_name, &name->non_name));
	
	printf("Name 0: %s\n", name->name);
	printf("Label probabilities: %d%% %d%% %d%%\n", name->first_name, 
		name->last_name, name->non_name);
	
	free(name);
	name=NULL;
	
	return;

}
		
int
count_char(name_t *name, name_t dict[]){
	int count_char=0, i;
	for (i=0; dict->name[i]; i++){
		printf("%c ", dict->name[i]);
		count_char++;
		printf("number of chars is %d\n", count_char);
	}
	return count_char;
}

/* number of names and average number of chars per name */
void
read_dict(name_t *name, name_t *dict){ 
	print_stage(STAGE1);
	
	int i=0, j=0;
	double average;
	
	int count_names=0, count_char=0;

	name = (name_t *)malloc(sizeof(*name)*(MAXLETTERS+NULL_BYTE));
	assert(name);

	dict = (name_t *)malloc(sizeof(name_t)*(MAXNAMES));
	assert(dict);
	
	/* stage 1*/
	
	int c;
	
	//read a string of characters until you hit a percentage sign //%[^%]
	while ((c=getchar())!= DICT_END){ //consume the hash
		scanf("%s %d %d %d\n", dict[count_names].name, &dict[count_names].first_name, 
	&dict[count_names].last_name, &dict[count_names].non_name);

		count_names++;
			
	}
	
	while (getchar() == DICT_END) {
		/* do nothing */
	}
	
	for (i=0; i<count_names; i++){ // &
		for (j=0; j<dict[i].name[j]; j++){
			count_char++;
		}
	}
			
	printf("Name 0: %s\n", dict[0].name);
	printf("Label probabilities: %d%% %d%% %d%%\n",  dict[0].first_name, 
	dict[0].last_name, dict[0].non_name);
	
	free(dict);
	dict=NULL;
	
	free(name);
	name=NULL;
	
	/* stage 2 */
	print_stage(STAGE2);
	
	average = (double) count_char/count_names;
	
	printf("Number of names: %d\n", count_names);
	printf("Average number of characters per name: %.2f\n", average);
}

/**********************************************/ 

【讨论】:

    猜你喜欢
    • 2016-10-25
    • 2012-08-16
    • 2011-04-18
    • 1970-01-01
    • 2022-11-26
    • 1970-01-01
    • 2011-12-31
    • 1970-01-01
    • 2015-10-29
    相关资源
    最近更新 更多