主要是记录一下,免得后面又慢慢搜索...

建立脚本文件,路径是/etc/init.d/aria2c
建议不熟悉vi命令操作的人(比如我)直接用winscp之类的软件在sftp/ftp窗口建立并编辑文件。
然后要注意一下编码,选utf-8(winscp是这样)。
脚本文件内容如下(认为你的aria2c已经装好了,执行aria2c --version能正确输出版本号):

#!/bin/sh
### BEGIN INIT INFO
# Provides: aria2
# Required-Start: $remote_fs $network
# Required-Stop: $remote_fs $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Aria2 Downloader
### END INIT INFO
 
case "$1" in
start)
 
 echo -n "已开启Aria2c"
 aria2c --conf-path=/path/to/aria2.conf -D
;;
stop)
 
 echo -n "已关闭Aria2c"
 killall aria2c
;;
restart)
 
 killall aria2c
 aria2c --conf-path=/path/to/aria2.conf -D
;;
esac
exit

然后保存,更改文件权限为755,命令是chmod 755 /etc/init.d/aria2c,当然也可以通过winscp更改。

将aria2c服务添加到开机启动,命令是update-rc.d aria2c defaults

然后执行service aria2c start就可以启动aria2c了,下次开机会自动启动。

如果前面的步骤错误,比如我执行service aria2c start就报错了,说配置的脚本文件有问题,原来是脚本里面用了sudo命令(因为脚本是网上找的233),但是debian 9不用这个命令,这个错误可以用systemctl status aria2c查看,报错的时候也提示了怎么看报错。

变量名是由字母和数字组成的序列,但其中第一个字符必须为字母,其中下划线“_”被看做字母,通常用于命名较长的变量名,以提高其可读性。
库例程的名字通常是下划线开头,所以变量名最好不要以下划线开头。大写小写字母是有区别的,它们是不同的名字。通常,在C语言中,变量名使用小写字母,符号常量名全部使用大写字母。
if等关键字必须小写。


数据类型及长度
C语言中提供了下列几种基本数据类型:

名称类型说明
char字符型占用一个字节,可以存放本地字符集中的一个字符
int整型通常反映了所用机器中整数的最自然长度
float单精度浮点型 
double双精度浮点型 

此外,还可以在这些基本数据类型的前面加上一些限定符。short与long两个限定符用于限定整型:
short int sh;
long int counter;
在上述这种类型的声明中,关键字int可以省略。

  • short类型通常为16位
  • long类型通常为32位
  • unsigned char类型变量取值范围为0~255(char对象占8位)
  • signed char类型的取值范围则为-128~127(char对象占8位)
  • long double类型表示高精度的浮点数。

同整型一样,浮点型的长度也取决于具体的实现,float、double与long double类型可以表示相同的长度,也可以表示两种或三种不同的长度。
Q:编写一个程序以确定分别由signed及unsigned限定的char、short、int与long类型变量的取值范围。采用打印标准头文件中的相应值以及直接计算两种方式实现。后一种方法的实现较困难一些,因为要确定各种浮点类型的取值范围。
A:默认的定义文件在C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\,比如limits.h。用的是VS 2015版本,直接打开文件可以看到定义,下面贴一下:

//
// limits.h
//
//      Copyright (c) Microsoft Corporation. All rights reserved.
//
// The C Standard Library <limits.h> header.
//
#pragma once
#define _INC_LIMITS

#include <vcruntime.h>

_CRT_BEGIN_C_HEADER



#define CHAR_BIT      8         // number of bits in a char
#define SCHAR_MIN   (-128)      // minimum signed char value
#define SCHAR_MAX     127       // maximum signed char value
#define UCHAR_MAX     0xff      // maximum unsigned char value

#ifndef _CHAR_UNSIGNED
    #define CHAR_MIN    SCHAR_MIN   // mimimum char value
    #define CHAR_MAX    SCHAR_MAX   // maximum char value
#else
    #define CHAR_MIN    0
    #define CHAR_MAX    UCHAR_MAX
#endif

#define MB_LEN_MAX    5             // max. # bytes in multibyte char
#define SHRT_MIN    (-32768)        // minimum (signed) short value
#define SHRT_MAX      32767         // maximum (signed) short value
#define USHRT_MAX     0xffff        // maximum unsigned short value
#define INT_MIN     (-2147483647 - 1) // minimum (signed) int value
#define INT_MAX       2147483647    // maximum (signed) int value
#define UINT_MAX      0xffffffff    // maximum unsigned int value
#define LONG_MIN    (-2147483647L - 1) // minimum (signed) long value
#define LONG_MAX      2147483647L   // maximum (signed) long value
#define ULONG_MAX     0xffffffffUL  // maximum unsigned long value
#define LLONG_MAX     9223372036854775807i64       // maximum signed long long int value
#define LLONG_MIN   (-9223372036854775807i64 - 1)  // minimum signed long long int value
#define ULLONG_MAX    0xffffffffffffffffui64       // maximum unsigned long long int value

#define _I8_MIN     (-127i8 - 1)    // minimum signed 8 bit value
#define _I8_MAX       127i8         // maximum signed 8 bit value
#define _UI8_MAX      0xffui8       // maximum unsigned 8 bit value

#define _I16_MIN    (-32767i16 - 1) // minimum signed 16 bit value
#define _I16_MAX      32767i16      // maximum signed 16 bit value
#define _UI16_MAX     0xffffui16    // maximum unsigned 16 bit value

#define _I32_MIN    (-2147483647i32 - 1) // minimum signed 32 bit value
#define _I32_MAX      2147483647i32 // maximum signed 32 bit value
#define _UI32_MAX     0xffffffffui32 // maximum unsigned 32 bit value

// minimum signed 64 bit value
#define _I64_MIN    (-9223372036854775807i64 - 1)
// maximum signed 64 bit value
#define _I64_MAX      9223372036854775807i64
// maximum unsigned 64 bit value
#define _UI64_MAX     0xffffffffffffffffui64

#if _INTEGRAL_MAX_BITS >= 128
    // minimum signed 128 bit value
    #define _I128_MIN   (-170141183460469231731687303715884105727i128 - 1)
    // maximum signed 128 bit value
    #define _I128_MAX     170141183460469231731687303715884105727i128
    // maximum unsigned 128 bit value
    #define _UI128_MAX    0xffffffffffffffffffffffffffffffffui128
#endif

#ifndef SIZE_MAX
    #ifdef _WIN64
        #define SIZE_MAX _UI64_MAX
    #else
        #define SIZE_MAX UINT_MAX
    #endif
#endif

#if __STDC_WANT_SECURE_LIB__
    #ifndef RSIZE_MAX
        #define RSIZE_MAX (SIZE_MAX >> 1)
    #endif
#endif



_CRT_END_C_HEADER

根据:https://junchu25.wordpress.com/2008/02/29/the-c-programming-language-2-1/

先说关于符号和无符号的概念,有符号类型(signed),在二进制表示中,最高位为符号位。比如5(以short为例),二进制表现为0000000000000101,它的最高位是0代表正数,如果是1则代表负数。对于无符号类型,它的高位没有符号标记,所以可以空出一位来表示大一倍的正数。假设int类型可以表示2的31次方,则unsigned int可以表示2的32次方。 2^31 * 2所以是大一倍。

那么对于无符号数,直接给0取反并赋予对应的无符号类型,则可以得到这种无符号类型的最大值,而0均为无符号类型的最小值。
对于有符号数,同上,增加右移1位的操作,然后强制转换为对应的有符号类型即可得到最大值,最小值为负的最大值,直接取负即可。那么代码如下(基本属于copy,注意最后是个无符号类型的应当用%u输出,%d无法输出无符号整数,打印会有问题):

#include <stdio.h>

/*determine ranges of types*/
void main()
{
    /* signed ranges of types*/
    printf(" signed char min = %d \n", -(char)((unsigned char) ~0 >> 1));
    printf(" signed char max = %d \n", (char)((unsigned char) ~0 >> 1));
    printf(" signed short min = %d \n", -(short)((unsigned short) ~0 >> 1));
    printf(" signed short max = %d \n", (short)((unsigned short) ~0 >> 1));
    printf(" signed int min = %d \n", -(int)((unsigned int) ~0 >> 1));
    printf(" signed int max = %d \n", (int)((unsigned int) ~0 >> 1));
    printf(" signed long min = %d \n", -(long)((unsigned long) ~0 >> 1));
    printf(" signed long max = %d \n", (long)((unsigned long) ~0 >> 1));
    
    /*unsigned types*/
    printf(" unsigned char max = %u \n",(unsigned char) ~0);
    printf(" unsigned int max = %u \n",(unsigned int) ~0);
    printf(" unsigned short max = %u \n",(unsigned short) ~0);
    printf(" unsigned long max = %u \n",(unsigned long) ~0);
}

执行示例:

>cl demo.c
用于 x86 的 Microsoft (R) C/C++ 优化编译器 19.00.24215.1 版
版权所有(C) Microsoft Corporation。保留所有权利。

demo.c
Microsoft (R) Incremental Linker Version 14.00.24215.1
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:demo.exe
demo.obj

>demo.exe
 signed char min = -127
 signed char max = 127
 signed short min = -32767
 signed short max = 32767
 signed int min = -2147483647
 signed int max = 2147483647
 signed long min = -2147483647
 signed long max = 2147483647
 unsigned char max = 255
 unsigned int max = 4294967295
 unsigned short max = 65535
 unsigned long max = 4294967295

某些字符可以通过转义字符序列表示为字符和字符串常量。转义字符序列看起来像两个字符,但只表示一个字符。
另外我们可以用'\ooo'表示任意字节大小的位模式。其中ooo代表1~3个八进制数字(0-7)。这种位模式还可以用'\xhh'表示其中,hh是一个或多个十六进制数字(0-9,a-f,A-F)。因此,我们可以按照下列形式书写语句:

#define VTAB '\013'    /* ASCII纵向制表符 */
#define BELL '\007'    /* ASCII响铃符 */

十六进制形式:

#define VTAB '\xb'    /* ASCII纵向制表符 */
#define BELL '\x7'    /* ASCII响铃符 */

ANSI C语言中的全部转移字符序列如下:

\a    响铃符
\b    回退符
\f    换页符
\n    换行符
\r    回车符
\t    横向制表符
\v    纵向制表符
\\    反斜杠
\?    问号
\'    单引号
\"    双引号
\ooo  八进制数
\xhh  十六进制数

字符常量'\0'表示值为0的字符,也就是空字符null。通常用'\0'的形式代替0,以强调某些表达式的字符属性,但其数字值为0。常量表达式是仅仅只包含常量的表达式。这种表达式在编译时求值,而不在运行时求值。它可以出现在常量可以出现的任何位置。
字符串连接可以多个"one" " two"这样挨着就行(感觉又学到新知识了),代码举例:

#include <stdio.h>

void main()
{
    printf("shizsfia" " zheshi0");
}

输出是shizsfia zheshi0
'x'"x"的区别:前者是一个整数(用其他语言多了就会感觉这有点坑),其值是字母x在机器字符集中对应的数值;后者是一个包含一个字符以及一个结束符'\0'的字符数组。
枚举

enum boolean {NO, YES}

在没有显式说明的情况下,enum类型中的第一个枚举名值为0,第二个为1,第三个为2...如果指定了部分枚举名的值,那么未指定的枚举名的值将依着最后一个指定值向后递增。
Q:在不使用运算符&&或||的条件下编写一个与下面的for循环语句等价的循环语句。

for (i = 0; i < lim -1; && (c = getchar()) != '\n' && c != EOF; ++i)
    s[i] = c;

A:代码如下,用的if

i = 0;
if (i < lim -1)
    if ((c = getchar()) != '\n')
        if (c != EOF;)
            ++i;

Q:编写函数htoi(s),把由16进制数字组成的字符串(包含可选的前缀0x或0X)转换为与之等价的整型值。字符串中允许包含的数字包括:0~9、a~f以及A~F。
A:一开始不明白,原来是转换十进制?。。。代码并非直接写的,只是添加了一点注释。

#include <stdio.h>

int htoi(char s[]);
main()
{
     char s1[] = "10";
     char s2[] = "2D";
     char s3[] = "3f";
     char s4[] = "0X4F";
     char s5[] = "0x3a";

     printf("%s -> %d\n", s1, htoi(s1));
     printf("%s -> %d\n", s2, htoi(s2));
     printf("%s -> %d\n", s3, htoi(s3));
     printf("%s -> %d\n", s4, htoi(s4));
     printf("%s -> %d\n", s5, htoi(s5));
}
int htoi(char s[])
{
     int n = 0;
     int i = -1;
    
     while (s[++i] != '\0') {
          if (i == 0 && s[i] == '0')//字符串以0开始,要么是0x 0X或者不必用于计算
               continue;
          else if (s[i] == 'x' || s[i] == 'X')//如果第二位是x 或者 X 那么后两位才是用于16进制转10进制计算所需要的
               continue;
          else if ('0'<= s[i] && s[i] <= '9')//这里就是字符到对应数大小的转换
               n = n * 16 + (s[i] - '0');
          else if ('a'<= s[i] && s[i] <= 'f')
               n = n * 16 + (s[i] - 'a' + 10);
          else if ('A' <= s[i] && s[i] <= 'F')
               n = n * 16 + (s[i] - 'A' + 10);
          else
               return -1;
     }
     return n;
}

Q:重新编写函数squeeze(s1, s2),将字符串s1中任何与字符串s2中字符匹配的字符都删除。
A:代码如下:

#include <stdio.h>

void squeeze(char s[], char t[]);
void main()
{
    char string_1[] = "ggqweewqeqweqweqweqeqweqweqweqhh";
    char string_2[] = "qwe";
    printf("sting_1 is %s.\nstring_2 is %s.\n", string_1, string_2);
    squeeze(string_1, string_2);
    printf("sting_1 is %s.\nstring_2 is %s.\n", string_1, string_2);
}
void squeeze(char string_1[], char string_2[])
{
    int i, j, k;
    for (k = 0; string_2[k] != '\0'; k++)
    {
        for (i = j = 0; string_1[i] != '\0'; i++)
            if (string_1[i] != string_2[k])
                string_1[j++] = string_1[i];
        string_1[j] = '\0';
    }
}

Q:重新编写函数any(s1, s2),将字符串s2中任一字符在字符串s1中第一次出现的位置作为结果返回。如果s1中不包含s2中的字符,则返回-1.(标准库函数strpbrk具有同样的功能,但它返回的是指向该位置的指针)
A:代码如下:

#include <stdio.h>

int any(char s1[], char s2[]);
void main()
{
    char string_1[] = "ggqweewqeqweqweqweqeqweqweqweqhh";
    char string_2[] = "okqwe";
    int res;
    printf("sting_1 is %s.\nstring_2 is %s.\n", string_1, string_2);
    res = any(string_1,string_2);
    printf("position is %d.\n", res);
}
int any(char s1[], char s2[])
{
    int i, j, posit = -1;
    for (i = 0; s2[i] != '\0'; i++)
    {
        for (j = 0; s1[j] != '\0'; j++)
        {
            if (s2[i] == s1[j])
            {
                posit = i;
                break;
            }
        }
        if (posit == -1)
            continue;
        else
            break;
    }
    return posit;
}

Q:编写一个函数setbits(x,p,n,y),该函数返回对x执行下列操作后的结果值:将x从第p位开始的n个(二进制)位设置为y中最右边n位的值,x的其余各位保持不变。
A:代码如下:

#include <stdio.h>

unsigned setbits(unsigned x, int p, int n, unsigned y);
void main()
{
    unsigned x = 0xa3, y = 0xb4, res;
    //x 163 10100011 y 180 10110100 --> 10101001 163
    int p = 1, n = 3;//p从右向左第0位开始
    res = setbits(x, p, n, y);
    printf("%u.\n", res);
}
unsigned setbits(unsigned x, int p, int n, unsigned y)
{
    unsigned temp;
    int i;
    i = p;
    temp = ~0 << n;//取反构造匹配p位开始n个的部分,与之相与使其置零
    while(i-- > 0){
        temp = temp << 1;
        temp++;
    }
    x = x & temp;
    temp = ~(~0 << n);
    y = y & temp;//将y移位到对应的位置
    while(p-- > 0){
        y = y << 1;
        y++;
    }
    
    return (x | y);//y与之相或,作用是覆盖对应的部分,即替换
}

Q:编写一个函数invert(x,p,n),该函数返回对x执行下列操作后的结果值:将x中从第p位开始的n个(二进制)位取反(即,1变成0,0变成1),x的其余各位保持不变。
A:代码如下:

#include <stdio.h>

unsigned invert(unsigned x, int p, int n);
void main()
{
    unsigned x = 0xa3, y = 0xb4, res;
    //x 163 10100011 y 180 10110100 --> 10101001 163
    // 10101101 --> 173
    int p = 1, n = 3;//p从右向左第0位开始
    res = invert(x, p, n);
    printf("%u.\n", res);
}

unsigned invert(unsigned x, int p, int n)
{
    unsigned temp;
    temp = ~0 << n;
    while(p-- > 0){
        temp = temp << 1;
        temp++;
    }
    return (~(~temp & x) & (~temp | x));
    //前半部分得到中间部分为取反结果,两侧是1
    //后半部分得到两侧是原结果,中间部分是1
    //两者相与两边则是原结果,中间则是取反结果
}

Q:编写一个函数rightrot(x,n),该函数返回将x循环右移(即从最右端移出的位将从最左端移入)n(二进制)位后所得到的值。
A:代码如下:

#include <stdio.h>

unsigned rightrot(unsigned x, int n);
void main()
{
    unsigned res, x = 0xa3;
    //10100011 --> 01100000000000000000000000010100 电脑上是32位
    int n = 3;
    res = rightrot(x, n);
    printf("%u.\n", res);
}

unsigned rightrot(unsigned x, int n)
{
    unsigned temp = ~0;
    unsigned temp_1 = ~0;
    while(temp != 0)
    {
        temp_1 = temp_1 << 1;
        temp = temp_1 << 1;
    }
    printf("temp_1 is %u.\n", temp_1);
    temp = ~(~0 >> 1);
    printf("temp is %u.\n", temp);
    
    while(n-- > 0)
    {
        temp = (~0 << 1) | x;//得到x最右一位的值 只有两种情况
        x = x >> 1;
        if (temp == ~0)//x最后一位为1
        {
            // x = ~(~0 >> 1) | x;
            x = temp_1 | x;
        }
        // else//x最后一位为0
        // {
            // //x最后一位为0 那么x右移已经自动补0了 这里就不用操作
        // }
    }
    return x;
}

Q:在求对二的补码时,表达式x&=(x - 1)可以删除x中最右边值为1的一个二进制位,请解释这样做的道理。用这一方法重写bitcount函数,以加快其执行速度。
A:x&=(x - 1)等效于 x = x & (x - 1),如果x最右边的一个二进制位为1,那么减1之后前面的位不变,最右这位变成0,这样与原本相“与”,那么x就变成了x-1,且最右一位是0,达到了删除最右边值为1的二进制位效果。更改代码如下(参考的),不过好像效率并没有提高?

#include <stdio.h>

int bitcount(unsigned x);

void main(void)
{
    unsigned x = 0xa3;
    int b = 0;
    
    b = bitcount(x);
    printf("b is %d.\n", b);
}

int bitcount(unsigned x)
{
    int b = 0;
    while(x != 0)
    {
        b++;
        x &= (x -1);
    }
    return b;
}

Q:重新编写将大写字母转换为小写字母的函数lower,并用条件表达式替换其中的if-else结构。
A:代码如下:

#include <stdio.h>

int bitcount(unsigned x);

void main(void)
{
    char string[] = "WGHigGYljuo";
    int n = 0;
    while(string[n++] != '\0')
        printf("%c", (string[n] >= 'A' && string[n] <= 'Z') ? (string[n] - 'A' + 'a') : string[n]);
    printf("\n");
}

写在前面,因为电脑之前装过VS 2015了,虽然一直没有怎么用,不过打开软件写代码还是有点耗费资源,正当纠结选哪个编译器的时候,突然想起有命令行编译的工具cl,那就用cl吧~~~
tips:要在屏幕上打印文字。比如下面的hello world,要么加上注释那句,要么在命令行直接执行exe,就能看到效果了~


第一个C语言程序:

#include <stdio.h>

main()
{
    printf("hello, world !\n");
    // system("pause");
}

Q:有意去掉部分内容,会得到什么出错信息?
A:无法编译(当然也可能编译通过但是执行没有任何信息显示),比如printf——>pritf,编译结果如下图:
QQ截图20181106201449.png
Q:做个实验,当printf函数参数字符串中包含c时,观察一下会出现什么情况?
A:编译提示helloworld.c(5): warning C4129: “c”: 不可识别的字符转义序列,执行程序还是有个c。
QQ截图20181106202105.png

Q:修改温度转换程序,使之能在转换表的顶部打印一个标题。
A:如图:QQ截图20181106204144.png

#include <stdio.h>

main()
{
    float fahr, celsius;
    int lower, upper, step;
    lower = 0;
    upper = 300;
    step = 20;
    fahr = lower;
    printf("fahr celsius generate\n");
    while (fahr <= upper) {
        celsius = (5.0 / 9.0) * (fahr - 32);
        printf("%6.0f  %6.1f\n", fahr, celsius);
        fahr = fahr + step;
        }
    // system("pause");
}

Q:编写一个程序打印摄氏温度转换相应华氏温度的转换表。
A:代码如下:
QQ截图20181106204708.png

#include <stdio.h>

main()
{
    float fahr, celsius;
    int lower, upper, step;
    lower = 0;
    upper = 150;
    step = 10;
    celsius = lower;
    printf("celsius fahr generate\n");
    while (celsius <= upper) {
        fahr = (celsius * 9.0) / 5.0 + 32;
        printf("%6.0f  %6.1f\n", celsius, fahr);
        celsius = celsius + step;
        }
    // system("pause");
}

Q:修改温度转换程序,要求以逆序打印温度转换表。
A:代码如下:
QQ截图20181106205856.png

#include <stdio.h>

main()
{
    int fahr;
    printf("celsius fahr generate\n");
    for (fahr = 300; fahr >= 0; fahr = fahr - 20)
        printf("%6d  %6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32));
    // system("pause");
}

Q:打印EOF的值。
A:在主程序中使用printf("%d\n", EOF);打印,结果为-1。
Q:验证表达式getchar() != EOF的值是0还是1。
A:代码如下,通过输入验证,该表达式的值在成立的条件下为1

#include <stdio.h>

main()
{
    int char_in;
    char_in = (getchar() != EOF);
    printf("%d\n", char_in);
}

Q:编写一个统计空格、制表符与换行个数的程序。
A:代码如下:

#include <stdio.h>

main()
{
    char char_in;
    int num_s,num_t,num_e;//空格、制表符、换行符
    num_s = 0;
    num_t = 0;
    num_e = 0;
    char_in = getchar();
    while (char_in != EOF){
        if (char_in == ' ')
            num_s++;
        if (char_in == '\t')
            num_t++;
        if (char_in == '\n')//每次输入时都会回车,所以通过回车将字符送入时,送入的字符包括了回车/换行
            num_e++;
        putchar(char_in);
        char_in = getchar();
    }
    printf("%d %d %d", num_s, num_t, num_e);
}

Q:编写一个将输入复制到输出的程序,并将其中连续的多个空格用一个空格代替。
A:代码如下:

#include <stdio.h>

main()
{
    char char_in[100],char_out[100];
    int i, j;
    j = 0;
    puts("input your string:\n");
    gets(char_in);
    for (i = 0; i <= strlen(char_in); i++)
    {
        if ((char_in[i] == ' ') && (char_out[j-1] == ' '))
            continue;
        char_out[j] = char_in[i];
        j++;
    }
    printf("%s", char_out);
}

运行示例:

>helloworld.exe
input your string:

hi  my name is wm, what   is your name?
hi my name is wm, what is your name?

Q:编写一个将输入复制到输出的程序,并将其中的制表符换成t,把回退符换成b,把反斜杠换成\。这样可以将制表符和回退符以可见的方式显示出来。
A:注意,代码中使用的是gets,输入回退符实际不起作用,因为它是在回车之后读取的,实际上回退符会删除字符,并且不会处理回车输入的回车符。要处理可以用getch(需要引入头文件conio.h)。

#include <stdio.h>

main()
{
    char char_in[100],char_out[100];
    int i, j;
    j = 0;
    puts("input your string:\n");
    gets(char_in);
    for (i = 0; i <= strlen(char_in); i++)
    {
        if (char_in[i] == '\t')
        {
            char_out[j++] = '\\';
            char_out[j++] = 't';
            continue;
        }
        if (char_in[i] == '\b')
        {
            char_out[j++] = '\\';
            char_out[j++] = 'b';
            continue;
        }
        if (char_in[i] == '/')
        {
            char_out[j++] = '\\';
            continue;
        }
        char_out[j++] = char_in[i];
    }
    printf("%s", char_out);
}

Q:你准备如何测试单词计数程序?如果程序中存在某种错误,那什么样的输入最可能发现这类错误呢?
A:代码如下,编译后执行输入测试呗。有可能存在的情况是,比如输入的字符中包括类EOF的字符,然后程序提前结束了。

#include <stdio.h>
#include <conio.h>

#define IN 1
#define OUT 0

main()
{
    int c, n1, nw, nc, state;
    
    state = OUT;
    n1 = nw = nc = 0;
    while ((c = getchar()) != EOF)
    {
        ++nc;
        if (c == '\n')
            ++n1;
        if (c == ' ' || c == '\n' || c == '\t')
            state = OUT;
        else if (state == OUT)
        {
            state = IN;
            ++nw;
        }
    }
    printf("%d %d %d\n", n1, nw, nc);
}

测试示例(Ctrl Z结束输入):

>helloworld.exe
my name is wang




^Z
5 4 20

Q:编写一个程序,以每行一个单词的形式打印其输出。
A:代码如下,对比了下最好的答案,错在思路上?

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

main()
{
    int i=0, j=0;
    char char_out[5][10]={"","","","",""};
    char char_temp[10];
    char c;
    while ((c = getchar()) != EOF)
    {
        if (c == '\n')
        {
            strcpy(char_out[j],char_temp);
            break;
        }
        else//不用else的话 在回车输入的时候就会把回车放到字符串
            char_temp[i++] = c;
        if (c == ' ' || c == '\t')
        {
            char_temp[i] = 0;//这个时候已经包含了空格,需要去掉
            strcpy(char_out[j],char_temp);
            // strcpy(char_temp,"");
            // char_temp[0] = 0;
            memset(char_temp, 0 , sizeof(char_temp));
            i = 0;
            j++;
        }
    }
    i = 0;
    for (i = 0; i <= 4; i++)
    {
        printf("%s\n", char_out[i]);
    }
    
}

较标准答案:

#include <stdio.h>
#define IN 1
#define OUT 0

int main()
{
    int c, state;
    state = OUT;
    while( (c = getchar()) != EOF)
    {
        if(c == ' ' || c == '\n' || c == '\t')
        {
            if(state == IN)
            {
                putchar('\n');
                state = OUT;
            }
        }
        else if (state == OUT)
        {
            state = IN;
            putchar(c);
        }
        else
            putchar(c);
    }
}

Q:编写一个程序,打印输入中各个字符出现频度的直方图。
A:代码如下,就是对字符做统计,可以参考书本的例子,稍作改变即可实现

#include <stdio.h>

print_same(int num)
{
    int i;
    for (i = 0; i < num; i++)
        printf("-*");
    printf("\n");
}

main()
{
    int c, i, nother = 0;
    int nletter[26];
    for (i = 0; i < 26; i++)
        nletter[i] = 0;
    
    while((c = getchar()) != '\n')
        if (c >= 'a' && c <= 'z')
            ++nletter[c - 'a'];
        else if (c >= 'A' && c <= 'Z')
            ++nletter[c - 'A'];
        else
            ++nother;
    for (i = 0;i < 26; i++)
    {
        printf("%c ", (65 + i));
        if (nletter[i] == 0)
            printf("\n");
        else
            print_same(nletter[i]);
    }
    
}

执行示例:

>helloworld.exe
hello, my name is li hua.you can see that i am very love this car. its beautiful and cool. i think you will love it too.
A -*-*-*-*-*-*-*-*
B -*
C -*-*-*
D -*
E -*-*-*-*-*-*-*-*
F -*
G
H -*-*-*-*-*
I -*-*-*-*-*-*-*-*-*-*
J
K -*
L -*-*-*-*-*-*-*-*-*
M -*-*-*
N -*-*-*-*
O -*-*-*-*-*-*-*-*-*
P
Q
R -*-*
S -*-*-*-*
T -*-*-*-*-*-*-*-*
U -*-*-*-*-*
V -*-*-*
W -*
X
Y -*-*-*-*
Z

Q:编写一个程序,打印输入中单词长度的直方图,水平的直方图比垂直的直方图简单。
A:主要是巧妙处理费字母字符以及连续的非字母字符出现情况。代码如下:

#include <stdio.h>

print_same(int num)
{
    int i;
    for (i = 0; i < num; i++)
        printf("-*");
    printf("\n");
}

main()
{
    int c, i, n, flag;
    int nletter[100];
    n = flag = 0;
    for (i = 0; i < 100; i++)
        nletter[i] = 0;
    i = 0;
    while((c = getchar()) != '\n')
    {
        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))
            i++;
        else
        {
            if (i == 0)
                continue;
            nletter[n] = i;
            n++;
            i = 0;
        }
    }
    i = 0;
    while(nletter[i] != 0)
    {
        printf("%d ", nletter[i]);
        print_same(nletter[i]);
        i++;
    }
    
}

执行示例:

>helloworld.exe
hello, my name is li hua.you can see that i am very love this car. its beautiful and cool. i think you will love it too.
5 -*-*-*-*-*
2 -*-*
4 -*-*-*-*
2 -*-*
2 -*-*
3 -*-*-*
3 -*-*-*
3 -*-*-*
3 -*-*-*
4 -*-*-*-*
1 -*
2 -*-*
4 -*-*-*-*
4 -*-*-*-*
4 -*-*-*-*
3 -*-*-*
3 -*-*-*
9 -*-*-*-*-*-*-*-*-*
3 -*-*-*
4 -*-*-*-*
1 -*
5 -*-*-*-*-*
3 -*-*-*
4 -*-*-*-*
4 -*-*-*-*
2 -*-*
3 -*-*-*

C语言函数结构:

返回值类型 函数名(0个或多个参数声明)
{
    声明部分
    语句序列
}

通常把函数定义中圆括号内列表中出现的变量称为形式参数,而把函数调用中形式参数对应的值称为实际参数。
Q:修改温度转换程序,使用函数实现温度转换计算
A:代码如下

#include <stdio.h>

main()
{
    float fahr, celsius;
    int lower, upper, step;
    float f2c(float fahr);//声明函数
    lower = 0;
    upper = 300;
    step = 20;
    fahr = lower;
    printf("fahr celsius generate\n");
    while (fahr <= upper) {
        celsius = f2c(fahr);
        printf("%6.0f  %6.1f\n", fahr, celsius);
        fahr = fahr + step;
        }
}

float f2c(float fahr)
{
    float celsius; 
    celsius = (5.0 / 9.0) * (fahr - 32);
    return celsius;
}

Q:修改打印最长文本行的程序的主程序main,使之可以打印任意长度的输入行的长度,并尽可能多的打印文本。
A:修改后的代码如下,说明:去除了输入字符的限制,最多10个字符,其中最后两位用于存放换行和0结束标志。问题,如果在一行中输入EOF(比如 Ctrl Z),那么会继续输入计数,正常的结束时在一行换行结束此行输入后输入EOF(Ctrl Z),然后再Ctrl Z问题已解决,方法是EOF、换行等直接写入零结束标志

#include <stdio.h>
#define MAXLINE 10

int getline(char line[], int maxline);
void copy(char to[], char from[]);

/* 打印最长的输入行 */
main()
{
    int len;/* 当前的行长度 */
    int max;/* 目前为止发现的最长行长度 */
    char line[MAXLINE];/* 当前的输入行 */
    char longest[MAXLINE];/* 用于保存最长的行 */
    
    max = 0;
    while ((len = getline(line, MAXLINE)) > 0){
        printf("the length of this line is %d.\n", len);
        if (len > max){
            max = len;
            copy(longest, line);
    }}
    if (max > 0)
        printf("%s", longest);/* 存在这样的行 */
    return 0;
}

/* getline函数:将一行读入到s并返回其长度 */
int getline(char s[], int lim)
{
    int c, i;
    
    for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c!= '\n'; ++i)
        s[i] = c;
    if (c == '\n' || c == EOF){
        s[i] = '\0';
    }
    else
    {
        s[i] = '\0';
        while ((c = getchar()) != '\n' && i != 0)
            ++i;
    }
    return i;
}

/* copy函数:将from复制到to;这里假定to足够大 */
void copy(char to[], char from[])
{
    int i;
    
    i = 0;
    while ((to[i] = from[i]) != '\0')
        ++i;
}

Q:编写一个程序,打印长度大于80的所有输入行
A:代码在上一个函数中修改main函数即可,这里测试大于15即可,main函数代码如下:

main()
{
    int len;/* 当前的行长度 */
    char line[MAXLINE];/* 当前的输入行 */
    char longest[MAXLINE];/* 用于保存最长的行 */
    
    while ((len = getline(line, MAXLINE)) > 0){
        printf("the length of this line is %d.\n", len);
        if (len > 15){
            printf("%s\n", line);
    }}
    return 0;
}

Q:编写一个程序,删除行末尾的制表符和空格,并删除完全是空格的行。
A:在检查到输入结束,比如换行或零结束标志作为输入完成,通过getchar向前循环判断是不是制表符或空格,用零标志替换即可,没有则末尾就是零标志。
Q:编写函数reverse(s),将字符串s中的字符顺序颠倒过来。使用该函数编写一个程序,每次颠倒一个输入行中的字符顺序。
A:代码如下,感觉我写的怪怪的

#include <stdio.h>
#define MAXLINE 1000

int getline(char line[], int maxline);
char *reverse(char line[], int linelen);

/* 颠倒输入行 */
main()
{
    int linelen;/* 行长度 */
    char line[MAXLINE];/* 输入行 */
    char *revline;
    
    while ((linelen = getline(line, MAXLINE)) > 0)
    {
        revline = reverse(line, linelen);
        printf("%s\n", revline);
    }
    return 0;
}

/* getline函数:将一行读入到s并返回其长度 */
int getline(char s[], int lim)
{
    int c, i;
    
    for (i = 0; i < lim - 2 && (c = getchar()) != EOF && c!= '\n'; ++i)
        s[i] = c;
    if (c == '\n' || c == EOF){
        s[i] = '\0';
    }
    else
    {
        s[i] = '\0';
        while ((c = getchar()) != '\n' && i != 0)
            ++i;
    }
    return i;//这个长度不包括末尾的换行和零标志
}

/* reverse函数:颠倒字符数组顺序 */
char *reverse(char line[], int linelen)
{
    int i;
    char revline[MAXLINE];
    
    i = 0;
    while ((linelen - i - 1) != -2)
    {
        revline[i] = line[linelen - i - 1];
        ++i;
    }
    revline[i] = '\0';
    return revline;
}

Q:编写程序detab,将输入中的制表符替换成适当数目的空格,使空格充满到下一个制表符终止位的地方。假设制表符终止位的位置是固定的,比如每隔n列就会出现一个制表符终止位。n应该作为变量还是符号常量呢?
A:一个制表符通常和四个字符(文本是这样,但命令行一个TAB是8个字符宽)一样宽,n应当是作为常量。关键在于,如果你输入了两个字符,此时你按下TAB(制表符),实际上只向后走了2个符号的位置。代码如下:

#include <stdio.h>
#define MAXLINE 1000
#define n 8

int getline(char line[], int maxline);
void detab(char s[], int len, char detab_line[]);


main()
{
    int linelen;/* 行长度 */
    char line[MAXLINE];/* 输入行 */
    char detab_line[MAXLINE];/* 结果行 */
    
    linelen = getline(line, MAXLINE);
    printf("length is %d.\n", linelen);
    detab(line, linelen, detab_line);
    printf("%s.\n", detab_line);
    return 0;
}

/* getline函数:将一行读入到s并返回其长度 */
int getline(char s[], int lim)
{
    int c, i;
    
    for (i = 0; i < lim - 2 && (c = getchar()) != EOF && c!= '\n'; ++i)
    {
        s[i] = c;
    }
    if (c == '\n' || c == EOF){
        s[i] = '\0';
    }
    else
    {
        s[i] = '\0';
        while ((c = getchar()) != '\n' && i != 0)
            ++i;
    }
    return i;//这个长度不包括末尾的换行和零标志
}

void detab(char s[], int len, char detab_line[])
{
    int i, j, num=0;
    for (i = 0; i < len; i++)
    {
        if (s[i] == '\t')
        {
            //j 需要的空格数
            for (j =0; j < n - (i % n); j++)
            {
                detab_line[num++] = ' ';
            }
        }
        else if (s[i] == '\0')
        {
            detab_line[num++] = '\0';
            break;
        }
        else
        {
            detab_line[num++] = s[i];
        }
    }
}

Q:编写程序entab,将空格串替换为最少数量的制表符和空格,但要保持单词之间的间隔不变。假设制表符终止位的位置与上一个问题的detab程序的情况相同。当使用一个制表符或者一个空格都可以到达下一个制表符终止位时,选用哪一种替换字符比较好?
A:我的理解是比如9个空格(表述不够清晰,是刚好制表符位置这样)组成的空格串,转换为一个制表符和一个空格(假设一个制表符是8个字符宽)。当然是尽量使用制表符,因为这样可以节省数组空间。关键在于空格开始的位置和空格的个数,有空格在制表符末位,即第8、16位并且向前至少1个字符是空格使用制表符才有意义。代码如下,我的方案有点绕的地方,可能有点不好理解...

#include <stdio.h>
#define MAXLINE 1000
#define n 8

int getline(char line[], int maxline);
void entab(char s[], int len, char entab_line[]);

main()
{
    int linelen;/* 行长度 */
    char line[MAXLINE];/* 输入行 */
    char entab_line[MAXLINE];/* 结果行 */
    
    linelen = getline(line, MAXLINE);
    printf("length is %d.\n", linelen);
    entab(line, linelen, entab_line);
    printf("%s.\n", entab_line);
    return 0;
}

/* getline函数:将一行读入到s并返回其长度 */
int getline(char s[], int lim)
{
    int c, i;
    
    for (i = 0; i < lim - 2 && (c = getchar()) != EOF && c!= '\n'; ++i)
    {
        s[i] = c;
    }
    if (c == '\n' || c == EOF){
        s[i] = '\0';
    }
    else
    {
        s[i] = '\0';
        while ((c = getchar()) != '\n' && i != 0)
            ++i;
    }
    return i;//这个长度不包括末尾的换行和零标志
}

void entab(char s[], int len, char entab_line[])
{
    int i, j=0, num=0;
    for (i = 0; i < len; i++)
    {
        if ((i + 1) % n == 0 && s[i] ==' ')
        {
            while(s[--i] == ' ' && j < n - 1)
            {
                entab_line[num--] = '\0';//每向前一个是空格,则在这个位置准备好用制表符替换,并将将被制表符代替的位置写0
                j++;//用于判断是不是存在向前至少2个都是空格
            }
            i = i + j + 1;
            if (j > 0)//说明至少两个空格,可以选择用一个制表符代替
            {
                entab_line[num++] = '\t';
                j = 0;
            }
        }
        else
        {
            entab_line[num++] = s[i];
        }
    }
}

Q:编写一个程序,把较长的输入行”折“成短一些的两行或多行,折行的位置在输入行的第n列之前的最后一个非空格之后。要保证程序能够智能地处理输入行很长以及在指定的列前没有空格或制表符的情况。
A:代码如下,具体说明见注释

#include <stdio.h>
#define MAXLINE 1000
#define n 8//制表符固定列数
#define goodlen 10//换行的适宜长度 可自行设置

int getline(char line[], int maxline);
void fmatcode(char s[], int len, char fmat_line[]);

main()
{
    int linelen;/* 行长度 */
    char line[MAXLINE];/* 输入行 */
    char fmat_line[MAXLINE];/* 结果行 */
    
    linelen = getline(line, MAXLINE);
    printf("length is %d.\n", linelen);
    fmatcode(line, linelen, fmat_line);
    printf("%s.\n", fmat_line);
    return 0;
}

/* getline函数:将一行读入到s并返回其长度 */
int getline(char s[], int lim)
{
    int c, i;
    
    for (i = 0; i < lim - 2 && (c = getchar()) != EOF && c!= '\n'; ++i)
    {
        s[i] = c;
    }
    if (c == '\n' || c == EOF){
        s[i] = '\0';
    }
    else
    {
        s[i] = '\0';
        while ((c = getchar()) != '\n' && i != 0)
            ++i;
    }
    return i;//这个长度不包括末尾的换行和零标志
}

//合理拆分过长行,假设一行30字符是最合理的
//思路:遍历字符数组,到第30个位置时,作一些判断。
//如果31位是空格或者制表符,那么往前挪一个单词然后折行。
//题目意思是上面这种情况直接可以折行了,但是我认为这样直接折行,
//新行开头就是空格,不美观。(划去,英文文章好像是允许空格。直接折行
//如果是单词,那么向前遍历,遇到空格就可以折行了
//还有个问题,一个制表符的宽度要考虑下,所以是按实际宽度与30对比的
void fmatcode(char s[], int len, char fmat_line[])
{
    int i, temp, j=0, num=0;
    for (i = 0; i < len; i++)
    {
        if (s[i] == '\t')
        {
            printf("检查到制表符,且j为%d.\n", j);
            j = j + n - j % n;//用于判断制表符是不是刚好在最适合折行的位置
            //当位置为24 25 26 27 28 29 30的时候,制表符占据(横跨)了折行位置,需要将其放到下一行
            if (j % goodlen > 0 && j % goodlen < n)
            {
                j = n;//制表符放到新行开始了,j置为制表符长度
                fmat_line[num++] = '\n';//插入换行符
                printf("插入换行符\n");
                // i--;
            }
            // else
            // {
                // printf("j当前值是%d.\n", j);
                // j = j + n - j % n;//j用来记显示的字符串(包含空格、制表符)的长度
                // printf("j增加一个制表符宽度后值是%d.\n", j);
            // }
            fmat_line[num++] = '\t';
            printf("插入制表符ttttt\n");
            printf("j值是%d.\n", j);
        }
        else if (++j % goodlen != 0)
        {
            printf("字符%c当前不位于换行处.\n", s[i]);
            fmat_line[num++] = s[i];
            printf("正常复制单字符%c.\n", s[i]);
        }
        else
        {
            printf("字符%c当前位于换行处.\n", s[i]);
            if (s[i] == ' ')//换行位置是空格
            {
                fmat_line[num++] = '\n';
                fmat_line[num++] = s[i];
                printf("插入换行符 新行空格\n");
            }
            else//是单词 单词横跨了换行(不考虑标点符号 要考虑可以在前面的if上加条件)
            {
                printf("换行处是单词.\n");
                printf("num是%d i是%d.\n", num, i);
                temp = fmat_line[--i];
                while (temp != ' ' && temp != '\t')//直到向前遇到空格或制表符 那么就可以换行了
                {
                    num--;
                    //将循环从新换行的单词开始
                }
                num--;//遇到空格 制表符 num还需要前移一位,空格 制表符放新行
                printf("num是%d i是%d.\n", num, i);
                fmat_line[num++] = '\n';
                i--;//空格or制表符都被换到新行了 那么循环得往前调整一个字符
                printf("插入换行符 单词新行\n");
            }
        }
    }
}

Q:编写一个删除C语言程序中所有的注释语句。要正确处理带引号的字符串与字符常量。在C语言中,注释不允许嵌套。
A:这个题想了挺久,后面还是求助了。原来是用状态机这种思想,最开始的题就已经涉及了,而且前面几个题也可以这样做,看来没有领悟到作者的用意啊~~~下面是(基本copy)基于状态机的方法。这个可以改一点,就是以整个程序输入完了再输出结果,不过得改些地方。{附上来源:https://www.cnblogs.com/zhanghaiba/p/3569928.html}

#include <stdio.h>

void delnote();

int main(void)
{
    delnote();
    return 0;
}

void delnote()
{
    int c, state;

    state = 0;
    while ((c = getchar()) != EOF) {
        if (state == 0 && c == '/')         // ex. [/]
            state = 1;
        else if (state == 1 && c == '*')     // ex. [/*]
            state = 2;
        else if (state == 1 && c == '/')    // ex. [//]
            state = 4;
        else if (state == 1) {                // ex. [<secure/_stdio.h> or 5/3]
            putchar('/');
            state = 0;
        }

        else if (state == 2 && c == '*')    // ex. [/*he*]
            state = 3;
        else if (state == 2)                // ex. [/*heh]
            state = 2;

        else if (state == 3 && c == '/')    // ex. [/*heh*/]
            state = 0;
        else if (state == 3)                // ex. [/*heh*e]
            state = 2;

        else if (state == 4 && c == '\\')    // ex. [//hehe\]
            state = 9;
        else if (state == 9 && c == '\\')    // ex. [//hehe\\\\\]
            state = 9;
        else if (state == 9)                // ex. [//hehe\<enter> or //hehe\a]
            state = 4;
        else if (state == 4 && c == '\n')    // ex. [//hehe<enter>]
            state = 0;

        else if (state == 0 && c == '\'')     // ex. [']
            state = 5;
        else if (state == 5 && c == '\\')     // ex. ['\]
            state = 6;
        else if (state == 6)                // ex. ['\n or '\' or '\t etc.]
            state = 5;
        else if (state == 5 && c == '\'')    // ex. ['\n' or '\'' or '\t' ect.]
            state = 0;

        else if (state == 0 && c == '\"')    // ex. ["]
            state = 7;
        else if (state == 7 && c == '\\')     // ex. ["\]
            state = 8;
        else if (state == 8)                // ex. ["\n or "\" or "\t ect.]
            state = 7;
        else if (state == 7 && c == '\"')    // ex. ["\n" or "\"" or "\t" ect.]
            state = 0;

        if ((state == 0 && c != '/') || state == 5 || state == 6 || state == 7 || state == 8)
            putchar(c);
    }
}

Q:编写一个程序,查找C语言程序中的基本语法错误,如圆括号、方括号、花括号不配对。要正确处理引号(包括单引号和双引号)、转移字符序列与注释。(如果读者想把程序编写成完全通用的程序,难度会比较大。)
A://TODO 思路同上一题?也是类似如此,可以先把注释通过上面的程序去掉,剩下纯代码。整理出一个状态机,匹配语法的括号啊什么的(其实上面的思路里面对于引号就是个例子),又想了想,似乎还是有很多问题要处理,比如某一段就是缺少了对应的括号,怎么确定是错在这里的?打算搁置这个问题。。

硬件设计

在外部中断应用实例中,采用调用CVAVR中软件延时函数的方法给出了一个使用6个数码管组成的时钟系统。采用软件延时,时钟是不准确的,因为一旦系统中使用了中断,就可能打断延时程序的执行,使得延时时间发生变化。下面给出以外部振荡源为基准,采用外部中断方式实现的时钟系统的设计。
在AVR-51多功能实验开发板上,有一个采用CD4060和2.048MHz晶体构成的50%占空比、0~5V的标准方波振荡源。将其10个标准频率中500Hz的输出端与ATmega16的PD3引脚连接,作为外部输入信号。如果INT1采用下降沿方式触发中断,那么500次中断就是1s。此时,外部500Hz的振荡源就是时钟系统的计时基准,这样的时钟系统比使用软件延时的方法要准确得多。
时钟系统显示部分的硬件电路与动态扫描数码管图里的一样,只需要使用一根连线,将板上标准方波振荡源的500Hz输出端与ATmega16的PD3引脚连接在一起即可。

软件设计

实现程序如下:

/******-
File name            :demo_7_2.c
Chip type            :ATmega16
Program type         :Application
Clock frequency      :4.000000MHz
Memory model         :Small
External SRAM size   :0
Data Stack size      :256
******/
#include<mega16.h>
flash unsigned char led_7[10] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
flash unsigned char position[6] = {0xfe,0xfd,0xfb,0xf7,0xef,0xdf};
unsigned char time[3];
unsigned char dis_buff[6];
int time_counter;
unsigned char posit;
bit point_on,time_1s_ok;
void display(void)
{
    PORTC = 0xff;
    PORTA = led_7[dis_buff[posit]];
    if (point_on && (posit==2 || posit==4)) PORTA |= 0x80;
    PORTC = position[posit];
    if (++ posit >= 6) posit = 0;
}

//外部中断INT1服务函数
interrupt [EXT_INT1] void ext_int1_isr(void)
{
    display();
    if (++time_counter >= 500)
    {
        time_counter = 0;
        time_1s_ok = 1;
    }
}

void time_to_disbuffer(void)//时钟时间送显示缓冲区函数
{
    unsigned char i,j = 0;
    for (i=0;i<=2;i++)
    {
        dis_buff[j++] = time[i] % 10;
        dis_buff[j++] = time[i] / 10;
    }
}

void main(void)
{
    PORTA = 0x00;//显示控制I/O端口初始化
    DDRA = 0xFF;
    PORTC = 0x3F;
    DDRC = 0x3F;
    time[2] = 23;time[1]=58;time[0]=55;
    posit = 0;
    time_to_disbuffer();
    GICR |= 0x80;                //INT1中断允许
    MCUCR = 0x80;                //INT1下降沿触发
    GIFR = 0x80;                //清INT1中断标志
    #asm("sei")                    //全局中断允许
    
    while(1)
    {
        if (time_1s_ok)//1s到
        {
            time_1s_ok = 0;
            point_on = ~point_on;
            if (++time[0] >= 60)//调整时间
            {
                time[0] = 0;
                if (++time[1] >= 60)
                {
                    time[1] = 0;
                    if (++time[2] >= 24) time[2] = 0;
                }
            }
            time_to_disbuffer();//新调整好的时间送显示缓冲区
        }
    }
}

思考与实践

//略 应该不会摘抄上来

用按键控制的1位LED数码管显示系统

硬件电路

图为硬件电路图。其中LED数码管的控制显示连接与前面例6.4(没有做摘抄)相同,PA口工作于输出方式,作为LED数码管的段码输出,LED数码管的位信号接地,因此这个1位的LED数码管工作于静态显示方式。图中使用了两个按键K1、K2,按键的一端分别与PD2(INT0)、PD3(INT0)连接。INT0和INT1作为外部中断的输入,采用电平变化的下降沿触发方式,当K1(K2)按下时,会在PD2(PD3)引脚上产生一个高电平到低电平的跳变,触发INT0或INT1中断。
系统的功能还是控制一个8段数码管显示0~F 16个十六进制的数字。为系统上电时,显示0。K1键的作用是加1控制键,按一下加1,到F之后又从0开始,K2则是减1键,到0后从F开始。
QQ截图20181106112019.png

软件设计

首先在CVAVR中使用C语言编写程序。再次建议读者使用CVAVR中的程序生成向导功能来帮助建立整个程序的框架,并采用芯片初始化部分的语句,可以省掉过多地查看器件手册和考虑寄存器设置值的时间等。
程序如下:

/******-
File name            :demo_7_1.c
Chip type            :ATmega16
Program type         :Application
Clock frequency      :4.000000MHz
Memory model         :Small
External SRAM size   :0
Data Stack size      :256
******/
#include<mega16.h>
flash unsigned char led_7[16] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};
unsigned char counter;

//INT0 中断服务程序
interrupt [EXT_INT0] void ext_int0_isr(void)
{
    if (++counter >= 16) counter = 0;
}

//INT1 中断服务程序
interrupt [EXT_INT1] void ext_int1_isr(void)
{
    if (counter) --counter;
    else counter = 15;
}

void main(void)
{
    PORTA = 0xFF;
    DDRA = 0xFF;
    GICR |= 0xC0;//使能INT0、INT1中断
    MCUCR = 0x0A;//INT0、INT1下降沿触发
    GIFR = 0xC0;//清除INT0、INT1中断标志位
    counter = 0;//计数单元初始化为0
    #asm("sei");//使能全局中断
    while(1)
    {
        PORTA = led_7[counter];//显示计数单元
    };
}

上面的程序,就是利用CVAVR的程序生成向导功能进行配置,然后在它生成的程序框架基础上完成的。程序中定义了一个计数变量counter,执行一次INT0中断服务程序,counter加1,而执行一次INT1中断服务程序,counter减1.在主程序中,只显示counter的值。INT0、INT1初始化为电平变化的下降沿触发。
下面这个程序为汇编程序,思想方法与上面的C语言程序相同,可参考。

*******
;AVR汇编程序实例:demo_7_1.asm
;使用INT0、INT1控制LED数码管显示
;ATmega16,4MHz
;*******
.includeb "m16def.inc"
.def temp = r23;临时变量
.def counter = r24;计数变量
;中断向量区配置,Flash空间为$000~$028
.org         $000
        jmp    RESET                ;复位处理
        jmp EXT_INT0            ;TRQ0中断向量
        jmp EXT_INT1            ;TRQ1中断向量
        reti                    ;Timer2 比较中断向量
        nop
        reti                    ;Timer2 溢出中断向量
        nop
        reti                    ;Timer1 捕捉中断向量
        nop
        reti                    ;Timer1 比较A中断向量
        nop
        reti                    ;Timer1 比较B中断向量
        nop
        reti                    ;Timer1 溢出中断向量
        nop
        reti                    ;Timer0 溢出中断向量
        nop
        reti                    ;SPI 传输结束中断向量
        nop
        reti                    ;USART TX 结束中断向量
        nop
        reti                    ;UDR 空中断向量
        nop
        reti                    ;USART RX 结束中断向量
        nop
        reti                    ;ADC 转换结束中断向量
        nop
        reti                    ;EEPROM 就绪中断向量
        nop        
        reti                    ;模拟比较器中断向量
        nop        
        reti                    ;两线串行接口中断向量
        nop        
        reti                    ;IRQ2 中断向量
        nop        
        reti                    ;定时器0比较中断向量
        nop
        reti                    ;SPM就绪中断向量
        nop
.org        $02A                ;上电初始化程序
RESET:
        ldi r16,high(RAMEND)
        out SPH,r16
        ldi r16,low(RAMEND)
        out SPL,r16                ;设置堆栈指针为RAM的顶部
        ser temp
        out ddra,temp            ;设置PORTA为输出,段码输出
        out porta,temp            ;设置PORTA输出全1
        ldi temp,0x0a
        out mcucr,temp            ;INT0、INT1下降沿触发
        ldi temp,0xc0
        out gicr,temp            ;使能INT0、INT1中断
        out gifr,temp            ;清除INT0、INT1中断标志位
        clr counter
        sei                        ;使能中断
MAIN:
    clr r0
    ldi zl,low(led_7 * 2)
    ldi zh,high(led_7 * 2)        ;Z寄存器取得7段码组的首指针
    add zl,counter                ;加上要显示的数字
    adc zh,r0                    ;加上低位进进位
    lpm                            ;读对应7段码到R0中
    out porta,r0                ;LED段码输出
    rjmp MAIN                    ;循环显示
EXT_INT0:
    in temp,sreg
    push temp                    ;中断现场保护
    inc counter                    ;计数单元加1
    cpi counter,0x10            ;与16比较
    brne EXT_INT0_RET            ;小于16,转中断返回
    clr counter                    ;计数单元清0
EXT_INT0_RET:
    pop temp                    ;中断现场恢复
    out sreg,temp                ;中断返回
    reti
EXT_INT1:
    in temp,sreg
    push temp                    ;中断现场保护
    dec counter                    ;计数单元减1
    cpi counter,0xFF            ;与255比较
    brne EXT_INT1_RET            ;未到255,转中断返回
    ldi counter,0x0F            ;计数单元设置为15
EXT_INT1_RET:
    pop temp                    ;中断现场恢复
    out sreg,temp                ;中断返回
    reti
led_7:                            ;7段码表
    .db 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07
    .db 0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71

思考与实践

//TODO 明天把问题部分摘抄下来,回答无能为力
Q:平稳地按下K1和K2,观察显示的变化
Q:修改程序,将INT0、INT1的中断触发方式分别改成电平变化的上升沿触发中断,以及电平变化触发中断和低电平触发中断,然后运行程序使显示数据加1或减1变化,与使用电平变化的下降沿触发中断的情况做比较,有何不同?
Q:不管使用哪种中断触发方式,经常会产生按键控制不稳定的现象,例如显示为5时,按下K1键一次,应该显示6,但时间显示7或8甚至更高,这是为什么?
Q:查看在CVAVR开发环境中编写编译demo_7_1.c的过程中,CVAVR都生成了哪些文件?这些文件的内容是什么?文件的扩展名是什么?有何作用?
Q:查看CVAVR生成的demo_7_1.lst的内容,问:CVAVR对中断向量是如何处理的?counter变量分配在什么地方?在CVAVR中,如何对中断现场进行保护和恢复?是使用硬件堆栈进行保护的吗?
Q:整理出编译demo_7_1.c生成的汇编代码,体会宏的使用,并与demo_7_1.asm进行比较。
Q:CVAVR在编译demo_7_1.c生成的汇编代码中还做了哪些工作?
Q:参考CVAVR的help,尝试在CVAVR中采用嵌入汇编语言的方式编写INT0和INT1的中断服务程序。