C和指针学习笔记--第一章

先将自己实现的第一章中的示例程序放在下面,这个程序的边界判断和处理非常值得学习:

/*
**程序先从标准输入读取一串列号,表示需要截取的输入行的列范围。
**剩余的输入行被程序读入并打印,然后输入列号的范围中的字符串
**被提取并拼接后打印。
**输入: 1 3 5 6 -1
**       abcdefgh
**
**输出:Original input:abcdefgh
**      Rearranged line:bcdfg 
*/ 

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

#define MAX_STRLEN 1000
#define MAX_COLNUM 20

int getColNum(int colNum[], int Max);
void rearRange(char output[], char Inputstring[], int aiColNum[], int iColNums);

int main(void)
{
    int aiColNum[MAX_COLNUM];
    char Inputstring[MAX_STRLEN];
    char outputstring[MAX_STRLEN];
    int iColNums;

    iColNums = getColNum(aiColNum, MAX_COLNUM);

    while(gets(Inputstring) != NULL)
    {
        printf("Original input:%s\n", Inputstring);
        rearRange(outputstring, Inputstring, aiColNum, iColNums);
        printf("Rearranged line:%s\n", outputstring);
    }
}

int getColNum(int colNum[], int Max)
{
    int iColNumTmp;
    int i = 0;
    int ch;

    while(i < Max && scanf("%d", &colNum[i]) == 1 && colNum[i] > 0)
    {
        i++;
    }

    if(i % 2 != 0)
    {
        printf("input error\n");
        exit(EXIT_FAILURE);
    }

    /*
    **这里的while用于吃掉输入列号结束后的其他垃圾输入和‘\n’
    **否则,‘\n’会继续存在缓冲区,被下面的gets当做输入读取
    **造成错误的输出 。所以在进行输入时,都应该考虑输入缓冲区中是  
    **否会有垃圾存在 
    */ 
    while( (ch = getchar()) != EOF && ch != '\n' )
        ;

    return i;
}


void rearRange(char output[], char Inputstring[], int aiColNum[], int iColNums)
{
    int i;
    int iCpyLen;
    int iCpyPos;
    int outputNum = 0;
    int inputLen = strlen(Inputstring);

    for(i = 0; i < iColNums; i += 2)
    {
        iCpyLen = aiColNum[i+1] - aiColNum[i] + 1;

        /*
        **输入行结束或输出数组已满,结束任务
        ** 
        **要将其改为列号非顺序增长也能继续处理后续的列
        **只把break改为continue会出现“||”逻辑或的短路情况
        **后面数组已经装满的判断会被短路而失效 
        */
        if(aiColNum[i] > inputLen || outputNum ==
                                     MAX_STRLEN - 1)
            break;

        /*
        **如果输出空间不足,将输出数据截取 
        */ 
        if(outputNum + iCpyLen > MAX_STRLEN - 1)        
        {
            iCpyLen = MAX_STRLEN - 1 - outputNum;
        }

        strncpy(output + outputNum, Inputstring + 
                aiColNum[i], iCpyLen);
        outputNum += iCpyLen;   
    }
    output[outputNum] = '\0';
}

注意事项:
1、程序中gets是很危险的函数,因为他对输入的字符串长度并没有做检查,如果输入较长的字符串会导致数组越界。可以改用
fgets( s, 10, stdin )来避免输入长度过长带来的溢出。strcpy和strncpy也是同样的道理。

编程练习:
2.编写一个程序从标准输入读取几行输入,每行输入都要打印到标准输出,前面要加上行号。试图让程序能够处理的输入行长度没有限制。

#include <stdio.h>
#define TRUE  1
#define FALSE 0

int main()
{
    int ch;
    int line = 0;
    int isBegin = TRUE;

    while((ch = getchar()) != EOF)
    {
        if(isBegin == TRUE)
        {
            isBegin = FALSE;
            line += 1;
            printf("%d", line);
        }

        putchar(ch);
        if(ch == '\n')
        {
            isBegin = TRUE;
        }

    }
}

代码充分利用了回车后输出缓冲区的内容才会刷新到stdout,所以虽然每读取一个字符就输出一个字符,但是显示的效果是每输入一行字符串回车后才会显示加上行号的字符串。

3.编写一个程序,从标准输入读取一些字符,并把它们输出到标准输出上,它同时应计算checksum(校验和),并写在字符的后面。

#include <stdio.h>

int main()
{
    int ch;
    char checksum = -1;

    while((ch = getchar()) != EOF)
    {
        checksum += ch;

        if(ch == '\n')
        {
            printf("%d\n", checksum);
            checksum = -1;
        }
    }
}

4.编写一个程序,一行一行读取输入,直到文件尾。算出每行输入的长度,然后把最长的那行打印出来。假设所有的输入行均不超过1000字符。

/*
** Reads lines of input from the standard input and prints the longest line that
** was found to the standard output. It is assumed that no line will exceed
** 1000 characters.
*/
#include <stdio.h>
#include <stdlib.h>
#define MAX_LEN 1001 /* Buffer size for longest line */
int
main( void )
{
    char input[ MAX_LEN ];
    int len;
    char longest[ MAX_LEN ];
    int longest_len;
    /*
    ** Initialize length of the longest line found so far.
    */
    longest_len = –1;
    /*
    ** Read input lines, one by one.
    */
    while( gets( input ) != NULL )
    {
    /*
    ** Get length of this line. If it is longer than the previous
    ** longest line, save this line.
    */
        len = strlen( input );
        if( len > longest_len )
        {
            longest_len = len;
            strcpy( longest, input );
        }
    }
    /*
    ** If we saved any line at all from the input, print it now.
    */
    if( longest_len >= 0 )
        puts( longest );
    return EXIT_SUCCESS;
}
最后if( longest_len >= 0 )的判断很重要,否则如果输入为空时,gets返回值为NULL,则不会进入while做处理,导致longest变量还是随机值,如果输出的话会出现乱码。

5.改为可以在输入列标号时,可以不按递增的方式输入:

void rearRange(char output[], char Inputstring[], int aiColNum[], int iColNums)
{
    int i;
    int iCpyLen;
    int iCpyPos;
    int outputNum = 0;
    int inputLen = strlen(Inputstring);

    for(i = 0; i < iColNums; i += 2)
    {
        iCpyLen = aiColNum[i+1] - aiColNum[i] + 1;


        if(aiColNum[i] > inputLen) 
            continue;

        if(outputNum == MAX_STRLEN - 1)
        {
            break;
        }   

        /*这一步下面详细解释*/   
        if(aiColNum[i] + iCpyLen > inputLen)        
        {
            iCpyLen = inputLen - aiColNum[i];
        }

        /*
        **如果输出空间不足,将输出数据截取 
        */ 
        if(outputNum + iCpyLen > MAX_STRLEN - 1)        
        {
            iCpyLen = MAX_STRLEN - 1 - outputNum;
        }

        strncpy(output + outputNum, Inputstring + 
                aiColNum[i], iCpyLen);
        outputNum += iCpyLen;   
    }
    output[outputNum] = '\0';
}
如果输入的字符串刚好介于一对列数之间,比如处理列数为4~6,而刚好字符串长度为5,这样就需要将拷贝字符串长度限制在4~5,如果将第六个字符即'\0'一起复制过去,会导致整个字符串后续的其他内容都会被截断, 产生错误。
但是为什么原来的没有添加这个判断呢?因为原来的列数假设是顺序增长的,那么就算出现了这种情况也只会是最后一次拷贝处理了;例如处理列数为4~6,而刚好字符串长度为5,那么就算将'\0'拷贝过去也无妨。因为再后面的列数肯定是大于6的,也就大于了输入字符串的长度,会被if判断后break,所以前一个没有使用该判断也同样正常。

6.修改程序为如果读入列标号为奇数个,函数就把最后一个列标号与行尾之间的范围当做处理范围。

void rearRange(char output[], char Inputstring[], int aiColNum[], int iColNums)
{
    int i;
    int iCpyLen;
    int iCpyPos;
    int outputNum = 0;
    int inputLen = strlen(Inputstring);

    for(i = 0; i < iColNums; i += 2)
    {

        if(i + 1 < iColNums)
        {
            iCpyLen = aiColNum[i+1] - aiColNum[i] + 1;
        }
        else
        {
            iCpyLen = inputLen - aiColNum[i] + 1;
        }

        /*
        **这一步是为了跳过大于输入长度的列数,因为如果条件
        **没有限定输入的列数是递增的时候,按照之前的方法会
        **使得小于前面列数的列没有被处理到。而且将之前if中
        **的两个条件分成两步来写可以避免逻辑或的短路 
        */
        if(aiColNum[i] > inputLen) 
            continue;

        if(outputNum == MAX_STRLEN - 1)
        {
            break;
        }   

        /*
        **如果输入的字符串刚好介于一对列数之间,
        **比如处理列数为4~6,而刚好字符串长度为5, 
        ** 这样就需要将拷贝字符串长度限制在4~5,
        **否则如果将第六个字符即'\0'一起复制过去,
        **会导致整个字符串后续的其他内容都会被截断, 
        ** 产生错误。
        **但是为什么原来的不用这样处理呢?因为 
        */  
        if(aiColNum[i] + iCpyLen > inputLen)        
        {
            iCpyLen = inputLen - aiColNum[i];
        }

        /*
        **如果输出空间不足,将输出数据截取 
        */ 
        if(outputNum + iCpyLen > MAX_STRLEN - 1)        
        {
            iCpyLen = MAX_STRLEN - 1 - outputNum;
        }

        strncpy(output + outputNum, Inputstring + 
                aiColNum[i], iCpyLen);
        outputNum += iCpyLen;   
    }
        output[outputNum] = '\0';
}

这里我开始的做法是判断第i+1个数是否是负数,如果是负数的话,就表明当前只剩一个列表号,那么就break,然后再循环对奇数的情况再截取拼接一次。这样做逻辑复杂,现在只在对复制长度赋值时进行判断,对奇数和偶数分开处理,非常合理。

第一章的内容和课后题暂时整理这些,对程序逻辑和边界判断的部分非常值得研究!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值