神秘P之谜

Mystery of the mysterious P

背景:

我正在尝试创建一个接受用户名(假设输入是干净的)并打印出姓名首字母的程序。

Objective:

代码:

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

string prompt(void);
char *getInitials(string input);
char *appendArray(char *output,char c,int count);

//Tracks # of initials
int counter = 0;

int main(void){

    string input = prompt();
    char *output = getInitials(input);
    for(int i = 0; i < counter ; i++){

        printf("%c",toupper(output[i]));
    }


}

string prompt(void){

    string input;

    do{
        printf("Please enter your name: ");
        input = get_string();
    }while(input == NULL);

    return input;

}

char *getInitials(string input){

    bool initials = true;
    char *output;
    output = malloc(sizeof(char) * counter);
    for(int i = 0, n = strlen(input); i < n ; i++){
        //32 -> ASCII code for spacebar
        //9  -> ASCII code for tab
        if(input[i] == 32 || input[i] == 9 ){
            //Next char after spaces/tab will be initial
            initials = true;


        }else{//Not space/tab
            if(initials == true){
                counter++;
                output = appendArray(output,input[i],counter);

                initials = false;


            }        

        }
        // eprintf("Input[i] is : %c\n",input[i]);
        // eprintf("Counter is : %i\n",counter);
        // eprintf("i is : %i\n",i);
        // eprintf("n is : %i\n",n);



    }

    return output;
}

char *appendArray(char *output,char c,int count){

    // allocate an array of some initial (fairly small) size;
    // read into this array, keeping track of how many elements you've read;
    // once the array is full, reallocate it, doubling the size and preserving (i.e. copying) the contents;
    // repeat until done.


    //pointer to memory
    char *data = malloc(0);
    //Increase array size by 1
    data = realloc(output,sizeof(char) * count);
    //append the latest initial
    strcat(data,&c);
    printf("Value of c is :%c\n",c);
    printf("Value of &c is :%s\n",&c);
    for(int i = 0; i< count ; i++){
        printf("Output: %c\n",data[i]);
    }
    return data;

}

问题:

输出不是我预期的,因为输出中出现了一个神秘的 P。

例如,当我输入 Barack Obama 这个名字时,我得到的不是 result:BO,而是 BP,无论我选择输入什么名字,都会发生同样的情况,最后一个首字母始终是 P.

输出:

Please enter your name: Barack Obama
Value of c is :B
Value of &c is :BP
Output: B
Value of c is :O
Value of &c is :OP
Output: B
Output: P
BP

我做了什么:

我已经将问题追溯到 appendArray 函数,更具体地说是 &c(c 的地址)的值,尽管我不知道是什么导致 P 出现,它意味着什么,为什么出现以及如何出现我可以摆脱它。

无论我什么时候输入,P的值都会出现

关于为什么会发生这种情况以及我能做些什么来解决它的见解将不胜感激。

谢谢!

几个问题,按重要性降序排列...

第一期 - appendArray 中的 c 不是字符串 - 它不是 序列 以 0 结尾的字符值。c 是单个 char 对象,存储单个 char 值。

当您尝试将 c 打印为字符串时 ,如

printf("Value of &c is :%s\n",&c);

printfc 地址开始写出 序列 字符值,直到它看到一个 0 值字节。 出于某种原因,紧跟在c 之后的字节包含值80,这是字符'P' 的ASCII(或UTF-8)代码。下一个字节包含 0(或者有一个包含不可打印字符的字节序列,后跟一个 0 值字节)。

同样,使用 &c 作为 strcat 的参数是不合适的,因为 c 不是字符串 。相反,你应该做类似

的事情
data[count-1] = c;

其次,如果你想把data数组当作一个字符串,你必须确保它的大小至少比首字母的数量多1,并在最后一个元素中写一个0:

data[count-1] = 0; // after all initials have been stored to data

第三,

char *data = malloc(0);

没有任何作用,行为是实现定义的,您 立即 通过调用 realloc 覆盖 malloc(0) 的结果:

data = realloc(output,sizeof(char) * count);

所以,完全去掉 malloc(0) 调用;要么只是将 data 初始化为 NULL,要么使用 realloc 调用初始化它:

char *data = realloc( output, sizeof(char) * count );

第四,避免使用"magic numbers"——意义超出其直接字面值的数值常量。如果您想与 character 值进行比较,请使用 character 常量。 IOW,改变

if(input[i] == 32 || input[i] == 9 ){

if ( input[i] == ' ' || input[i] == '\t' )

这样您就不必担心字符编码是 ASCII、UTF-8、EBCDIC 还是其他系统。 ' ' 意味着到处都是 space'\t' 意味着到处都是 tab

终于...

我知道你做这个练习的部分动机是为了熟悉 mallocrealloc,但我想提醒你一些事情:

realloc 可能是一项代价高昂的操作,它 可能 将数据移动到新位置,并且 可能 失败。你真的不想 realloc 一个缓冲区一次一个字节。相反,最好 realloc 成块。一个典型的策略是将当前缓冲区大小乘以某个因子> 1(通常加倍):

char *tmp = realloc( data, current_size * 2 );
if ( tmp )
{
  current_size *= 2;
  data = tmp;
}

您应该始终 检查 malloccallocrealloc 调用的结果,以确保它在尝试之前成功访问该内存。

次要文体注释:

尽可能避免使用全局变量。没有理由 counter 应该是全局的,尤其是当您将它作为参数传递给 appendArray 时。将其声明为 main 的本地并将其作为参数(通过引用)传递给 getInput:

int main( void )
{
  int counter = 0;
  ...
  char *output = getInitials( input, &counter );
  for(int i = 0; i < counter ; i++)
  {
    printf("%c",toupper(output[i]));
  } 
  ...
}

/**
 * The "string" typedef is an abomination that *will* lead you astray,
 * and I want to have words with whoever created the CS50 header.
 *
 * They're trying to abstract away the concept of a "string" in C, but 
 * they've done it in such a way that the abstraction is "leaky" - 
 * in order to use and access the input object correctly, you *need to know*
 * the representation behind the typedef, which in this case is `char *`.
 *
 * Secondly, not every `char *` object points to the beginning of a 
 * *string*.    
 *
 * Hiding pointer types behind typedefs is almost always bad juju.  
 */
char *getInitials( const char *input, int *counter ) 
{
   ...
   (*counter)++;                                   // parens are necessary here
   output = appendArray(output,input[i],*counter); // need leading * here
   ...
}