1.5 字符输入/输出
/* putchar就是用来输出的(显示到屏幕的)
putchar的适用对象是字符数据
(C语言中单引号的是字符,双引号的是字符串。字符指类字形单位或符号,包括字母、数字、运算符号、标点符号和其他符号,以及一些功能性符号。)
putchar与printf函数的区别:
printf是标准输出函数,可以输出char型、int型、float型、double型等数据。
putchar是字符输出函数,只能输出字符型char型。 */
#include<stdio.h>
int main()
{
putchar('a');
putchar('b');
putchar('c');
putchar('1');
putchar('2');
putchar('3');
}
/* 单引号括起来的(单个)字符数据,结果:abc123 */
#include<stdio.h>
int main()
{
putchar(65);
putchar(97);
putchar(48); }
/* 当c为一个介于0~127(包括0及127)之间的十进制整型数时,(此时没有单引号引起来)它会被视为对应字符的ASCII代码,输出该ASCII代码对应的字符;
结果:Aa0 */
#include<stdio.h>
int main()
{
char ch1='a';
char ch2=97;
putchar(ch1);
putchar(ch2);
}
/*当c为一个事先用char定义好的字符型变量时,(此时没有单引号引起来),输出该变量所指向的字符。
结果:aa */
1.5.1 文件复制
/* 输入一次一个字符地,复制到输出
基本思想:
读一个字符
while(该字符不是文件结束指示符)
输出刚度如的字符
读下一个字符 */
/* 将输入复制到输出:版本1 */
#include <stdio.h>
int main(){
int c;
c = getchar();
/* 变量c 要大到足以存放getchar函数返回的任何值 */
while(c != EOF){
putchar(c);
c = getchar();
}
return 0;
}
/* EOF:end of file,文件结束 */
每行输入完成后,以换行符结尾,每行输入什么就返回什么。此遵循标准库提供的输入/输出文本流模型的规则。
按ctrl+z结束整个文件的输入。
参考链接:第二个回答,https://www.zhihu.com/question/28590015?sort=created
/* 版本2: */
#include <stdio.h>
int main(){
int c;
while((c = getchar()) != EOF)
putchar(c);
return 0;
}
每行输入完成后,以换行符结尾,每行输入什么就返回什么。此遵循标准库提供的输入/输出文本流模型的规则。
按ctrl+z结束整个文件的输入。
/* 验证表达式getchar()!=EOF的值是0还是1 */
#include <stdio.h>
int main(){
int c;
printf("请输入字符:\n");
c = (getchar() != EOF);
printf("%d\n",c);
return 0;
}
/* 结果为1,说明调用getchar函数时碰到了文件结束标志,该文件有结束处。即getchar() != EOF不成立。 */
/* 打印EOF值 */
#include <stdio.h>
int main(){
int c;
printf("请输入字符:\n");
c = (getchar() != EOF);
while(c = 1)
printf("%d\n",EOF);
return 0;
}
/* 结果为1,说明调用getchar函数时碰到了文件结束标志,该文件有结束处。即getchar() != EOF不成立,该判断假为1,真为0.
如果是无限文件,没有结束处,那么则其值为0.但不会有这样的文件。 */
/* 打印EOF值 */
#include <stdio.h>
int main(){
printf("请输入字符:\n");
printf("%d\n",EOF);
return 0;
}
/* 值为-1.
EOF(end of file)就是文件的结束,通常来判断文件的操作是否结束的标志。
EOF不是特殊字符,而是定义在头文件<stdio.h>的常量,一般等于-1(原因:文件字符的ASCII码值一般都是正数,所以有的系统会用- 1这个负数来标记文件结尾。),它依系统有所不同。*/
1.5.2 字符计数
# include <stdio.h>
/* 统计输入的字符: 版本1 */
int main()
{
long nc;
nc = 0;
while (getchar() != EOF)
++nc;
printf("%d\n", nc);
return 0;
}
换行符也算一个字符。
最后一行ctrl+z 之后的所有内容都不算进去,且在该行后停止输入;
文本中间的ctrl+z也不算进去,但是无法结束输入。
/* 统计输入的字符数;版本2 */
#include <stdio.h>
int main()
{
double nc;
for (nc = 0; getchar() != EOF; ++nc)
;
printf("%6.2f\n", nc);
return 0;
}
/* 文本中间的 ctrl+z 也都不算进去,但是无法结束输入。同版本1 while循环情况相同。*/
1.5.3 行计数
#include <stdio.h>
/* 统计输入的行数/统计换行符个数 */
int main()
{
int c,nl;
nl = 0;
while ((c = getchar()) != EOF)
if (c == '\n')
++nl;
printf("%d\n", nl);
return 0;
}
#include <stdio.h>
/* 统计空格的个数 */
int main()
{
int c, nl;
nl = 0;
while ((c = getchar()) != EOF)
if (c == ' ')
++nl;
printf("%d\n", nl);
return 0;
}
#include <stdio.h>
/* 统计制表符的个数 */
int main()
{
int c, nl;
nl = 0;
while ((c = getchar()) != EOF)
if (c == '\t')
++nl;
printf("%d\n", nl);
return 0;
}
#include <stdio.h>
/* 将输入复制到输出,并将其中连续的多个空格用一个空格代替 */
int main()
{
char c;
while ((c = getchar()) != EOF) {
if (c == ' ') { //在读入到空格的时候,后面的空格就可以忽略,所以在if条件下while循环(可能有多个空格)
putchar(c); //每次调用putchar()函数时:以Enter键为行结束标志,putchar()函数将会打印每一行中的所有字符
while ((c = getchar()) == ' ') { //getchar()函数会按顺序依次读取文本流中的每一个字符,读完上一个自动获取下一个,并将其作为结果值返回变量c.当第一个空格之后还有空格时
; //空语句,忽略第一个空格之后的空格,不进行任何操作
}
if (c != EOF) //if的循环语句,可用{}括起来可不括
putchar(c); //有空格的情况下,将空格按要求处理完毕之后,剩余的且非文本结束符EOF的同样正常读取及输出。
else
break;
}
else
putchar(c); //不需要特殊处理的,正常读取的情况。同样putchar()函数以Enter键为行结束标志,打印每一行中的所有字符。
}
return 0;
}
练习1-10
题目:编写一个将输入复制到输出的程序,并将其中的制表符替换为 \t,把回退符替换为 \b,把反斜杠替按为 \。这样可以将制表符和回退符以可见的方式显示出来。
#include <stdio.h>
int main()
{
char c;
while ((c = getchar()) != EOF) {
if (c == '\t')
{
printf("\\t"); //putchar("\\t");
}
else if (c == '\b')
{
printf("\\b"); //putchar("\\b");
}
else if (c == '\\')
{
printf("\\\\"); //putchar("\\");
}
else
{
putchar(c);
}
}
return 0;
}
// tab键已成功,倒数第二行
1.5.4 单词计数
# include <stdio.h>
#define IN 1 /* 在单词内 */
#define OUT 0 /* 在单词外 */
/* 统计输入的行数、单词数与字符数 */
int main()
{
int c, nl, nw, nc, state;
state = OUT;
nl = nw = nc = 0;
while ((c = getchar()) != EOF) {
++nc; //所有的字符数
if (c == '\n')
++nl; //行数
if (c == ' ' || c == '\n' || c == '\t')
state = OUT; //如果读到空格、换行符、制表符中的一个,说明是在单词外,state值均不变且为0
else if (state == OUT) {
/* 如果没有读到空格、换行符、制表符,说明开始碰到字母(此时第二个if判断条件不成立,语句下的命令 state = OUT 并没有执行)。
再判断此时state值是否为0,确保是从第一个单词的首个字母开始的;对非首个单词的首个字母,每两个的单词之间有或空格或换行符或制表符作为分隔,state会在第二个if语句下重新被赋值为OUT. */
state = IN; //在找到每一个单词时,使state值变为1
++nw; //单词数即为state为1的个数
}
}
printf("%d %d %d\n", nl, nw, nc);
return 0;
}
/* 当两个if条件均不满足时,即当没有读到空格、换行符、制表符中的一个;state的值也不为0时,即说明还是在单词内部(每次都读取的是字母),两个语句都不执行。 */
/* nc = 25
单词字母数加句号:1+4+2+2+3+5+1
空格:5
两个换行符:1+1 */
第二个 else if 中的 判断语句state == OUT 保证了,是从每个单词的首个字母开始的。若去掉,则每个单词,尤其是对非一个字母构成的单词,有几个字母,则 nw 会增加几次。
练习1-12
/* 以每行一个单词的形式打印其输入 */
#include <stdio.h>
#define IN 1
#define OUT 0
int main()
{
char c;
int state;
state = OUT;
while ((c = getchar()) != EOF)
{
if (c == ' ' || c == '\n' || c == '\t')
{
if (state == IN)
{
printf("\n");
}
state = OUT;
}
else
{
state = IN;
putchar(c);
}
}
return 0;
}
思路一样,但是输出为乱码:??
#include <stdio.h>
/* 以每行一个单词的形式打印其输入 */
#define IN 1
#define OUT 0
int main() {
char c;
int state = OUT;
while (c = getchar() != EOF) {
if (c != ' ' && c != '\n' && c != '\t') {
if (state == OUT)
putchar(c);
}
else
state = OUT;
putchar('\n');
return 0;
}
return 0;
}
1.6 数组
#include <stdio.h>
/* 统计各个数字、空白符及其他字符出现的次数 */
int main() {
int c, i, nwhite, nother;
int ndigit[10];
nwhite = nother = 0;
for (i = 0; i < 10; ++i)
ndigit[i] = 0; //让该数组所有的数都变成0,共十个数,最后代表的是数字 0-9 出现的次数
while ((c = getchar()) != EOF)
if (c >= '0' && c <= '9')
++ndigit[c - '0']; //变量c中每出现一次数字X,则对应数字X的次数加1,即数组ndigit[X]加1
else if (c == ' ' || c == '\n' || c == '\t')
++nwhite;
else
++nother;
printf("digits = ");
for (i = 0; i < 10; ++i)
printf(" %d", ndigit[i]);
printf(",white space = %d,other = %d", nwhite, nother);
return 0;
}
练习1-13
/* 打印输入中单词长度的水平直方图 */
#include <stdio.h>
#define IN 1
#define OUT 0
int main() { //void main() 在C语言中不存在
int state, length;
char c; //变量c在本程序中始终最多只包含一个字符,因为getchar()函数是一个字符一个字符读入的
state = length = 0;
while ((c = getchar()) != EOF) {
if (c == ' ' || c == '\n' || c == '\t') {
if (state == IN) { //如果一开始就是空白字符,则不会执行该if模块下的语句
putchar(':');
for (int i = 0; i < length; i++) { //或者是在一开始就声明变量i的数据类型
putchar('*');
}
putchar('\n');
}
state = OUT;
length = 0;
}
else {
state = IN; //为了方便上面的if循环
putchar(c);
length++;
}
}
}
#include<stdio.h>
/* 打印输入中单词长度的垂直直方图 (简约版)*/
#define MAXHIST 10 //自定义直方图的高度
#define MAXWORD 11 //自定义要统计的单词的最大长度,此程序中只统计1到10个字母长度的单词,各个长度出现的频度
int main(void)
{
char c;
int i;
int nc = 0;//一个单词字母的个数
int ovflow = 0; //超过本程序要统计的字母最大长度(即10个字母)的单词个数
while ((c = getchar()) != EOF)
{
putchar(c);
if (c != ' ' && c != '\t' && c != '\n')
++nc;
else //等价于if (c == ' ' || c == '\t' || c == '\n')
{
if (nc < MAXWORD)
{
for (i = MAXHIST; i > 0; --i)
{
if (nc >= i)
printf(" * ");
else
printf(" ");
putchar('\n');
}
}
else
++ovflow; //统计超过本程序要统计的单词的最大长度10的单词个数
nc = 0; //将单词长度归为0,方便对下一个单词长度进行统计
}
}
if (ovflow > 0)
printf("There are %d words >= %d\n", ovflow, MAXWORD);
return 0;
}
#include<stdio.h>
/* 打印输入中单词长度的垂直直方图 */
#define MAXHIST 100 //自定义直方图的高度
#define MAXWORD 11 //自定义要统计的单词的最大长度,此程序中只统计1到10个字母长度的单词,各个长度出现的频度
#define IN 1
#define OUT 0
int main(void)
{
char c;
int i;
int j;
int nc = 0;//一个单词字母的个数
int state = OUT;
int maxvalue; //各个长度的单词,出现次数最多的具体频次数(不是出现频次最多的单词长度)
int ovflow = 0; //超过本程序要统计的字母最大长度(即10个字母)的单词个数
int wl[MAXWORD] = {
0
}; //将各个长度出现的频次初始化为0
while ((c = getchar()) != EOF)
{
if (c == ' ' || c == '\t' || c == '\n')
{
state = OUT;
if (nc > 0) //如果这是一个单词的结束
if (nc < MAXWORD)
++wl[nc]; //该单词长度的出现频次加1
else
++ovflow; //统计超过本程序要统计的单词的最大长度10的单词个数
nc = 0; //将单词长度归为0,方便对下一个单词长度进行统计
}
else if (state == OUT)//如果遇到字母且它在外面,也就是第一个字母
{
state = IN;
nc = 1;
}
else //前面是字母,现在又得输入一个字母,也就是单词还没结束
++nc;
}
maxvalue = 0; //初始化出现次数最多的具体频次数为0
for (i = 1; i < MAXWORD; ++i) //找到最大的有多少个字母
if (wl[i] > maxvalue)
maxvalue = wl[i]; //使得maxvalue等于各长度出现频次的最大值
for (i = MAXHIST; i > 0; --i) //纵坐标,即单词某长度出现的频次。遍历纵坐标,考虑纵坐标在每个高度的情况
{
for (j = 1; j < MAXWORD; ++j) //横坐标,即单词长度。遍历横坐标,考虑横坐标在每个取值的情况。什么情况下才会描点。
if (wl[j] >= i)
printf(" * "); //当某长度j的频次wl[j]大于纵坐标高度i时,则在坐标点(j,wl[j])处画一个星号*
else
printf(" "); //纵坐标i会逐次下降,但是在每一个高度i,只有满足大于其时才会描星号*
putchar('\n');
}
for (i = 1; i < MAXWORD; ++i)
printf("%2d ", i); ///横坐标,字母数
putchar('\n');
for (i = 1; i < MAXWORD; ++i)
printf("%2d ", wl[i]);//纵坐标,即每个字母数出现的次数
putchar('\n');
if (ovflow > 0)
printf("There are %d words >= %d\n", ovflow, MAXWORD);
return 0;
}
输入:
输出:
练习1-14
/* 打印输入中各个字符出现频度的直方图 */
/* 思路:提取出每一个字符,统计个数,存到数组中,绘制直方图 */
#include <stdio.h> // 包含标准库的信息。
int main() // 定义名为main的函数,它不接受参数值。
{
printf("======打印输入字符频度的直方图======\n");
unsigned int ws[128]; // 字符频度数组。
int i, j, c;
i = j = c = 0; //十进制数字0,在ASCII表中对应的字符为空字符NULL
// 默认每个字符出现0次。
for (i = 0; i < 128; i++) //在ASCII表中共有128个字符,对应的十进制数字是0至127.
{
ws[i] = 0;
}
while ((c = getchar()) != EOF)
{
// 进行字符匹配,如果匹配到字符频度就+1;
for (i = 0; i < 128; i++)
{
if (i == c) { //此处即用十进制数字i的8位二进制,与字母或字符c的ASCII中的二进制进行对比。在计算机中,数字0-127与ASCII中的128个字符是一样的(因此此处可以进行这种比较),输出时通过指定printf函数中“%c”/“%d”的占位符,输出十进制数字/字母或字符;函数putchar()输出的内容与函数getchar()接受的,即与键盘输入的内容始终保持一致,无需像printf函数一样,通过指定占位符决定输出的内容。
ws[i] += 1;
break;
}
}
}
// 显示水平直方图。此时已经不再处理文本流中的字符,而是处理输入结束的既定文本中的,因此用的是变量i,而不是变量c
for (i = 0; i < 128; i++)
{
if (ws[i] > 0 && i != '\t' && i != '\n' && i != ' ') //ws[i] > 0规定了没有出现的字符不打印,字母之外的空白字符不打印
{
printf("%c:", i); //通过指定printf函数的占位符为%c,保证了输出的是十进制数0-127对应的字符,而不是十进制数字
for (j = 0; j < ws[i]; j++) {
printf("*");
}
printf("\n");
}
}
getchar(); // 防止控制台一闪而过,需要接受任意字符后在关闭控制台。
return 0; // 向执行环境返回一个整形,0代表执行成功。
}
1.7 函数
任何C程序都要有int main函数()的
然后由主函数来调用其他函数才行!!
程序必须有一个入口函数的,比如main
所以c语言中不能只单独执行一个函数定义的程序,必须由main()函数调用才行。
#include <stdio.h>
int power(int m, int n); //函数原型
/* 测试power(m,n)函数 */
int main() {
int i;
for (i = 0; i <= 10; ++i) //先自增,再复制,使得i可以依次被赋值为1,2……10
printf("%d %d %d\n", i, power(2, i), power(-3, i));
return 0; //main函数向执行环境返回状态;0表明正常退出/终止
}
/* 定义求基数n次幂的函数 */
int power(int base,int n) { //函数声明
int p,i;
p = 1;
for (i = 0; i <= n; ++i)
p = p * base;
return p;
}
#include <stdio.h>
/* 使用函数实现温度转换 */
float fahrToCelsius(int fahr);
int main() {
int fahr, lower, upper, step;
lower = 0;
upper = 300;
step = 20;
for (fahr = lower; fahr <= upper; fahr = fahr + step)
printf("%d %.2f\n", fahr, fahrToCelsius(fahr));
return 0;
}
float fahrToCelsius(int fahr) {
float celsius;
celsius = (5.0 / 9.0) * (fahr - 32.0);
return celsius;
}
1.8 参数——传值调用
1.9 字符数组
读入一组文本行(假设其中最大行长度不超过100/规定只统计行长度在100之内的行),把最长的文本行打印出来
int getline(char line[], int limit)
getline函数要提供的是:
①一行文本,并用getchar()函数读取改行,写入参数字符数组char line[]中(line不是提供的,而是函数内部的参数)
②要统计的行长度的最大限度int limit。
getline函数返回的是该行的整形int长度值。
getline函数输入一行(按下一次Enter键)就返回一个整形数字。
函数getline()中字符数组line[]的存储方式及内容是:
字符个数不超过limit的每行的内容,一个元素存放一个字符(除每行的内容和换行符之外,还会再末尾追加结束符‘\0’),在内存中占用一个字节。
在用程序实现中,判断依据则是两个换行符\n之间的内容即为文本一行的内容。
最大行长度限制是limit(该行长度中包括了换行符),即
(1)换行符也算一个字符,则输入的每行字符个数不超过limit-1.
经测试,若超过limit-1,则从limit-1处折断,分两行输出。
当把limit改成5,输入超过4个字符时,函数getline()输出的情况:
(2)字符数组line[]的维度则是不超过limit+1.
因为在整个行的基础上,最终还要给字符数组line[]的末尾追加一个‘/0’,将其存入的行内容变成计算机可操作和输出的包含多个单个字符的字符串。
在计算机中,系统会自动在字符串的结尾加 ‘\0’,但它不会自动为 ‘\0’ 开辟内存空间。所以在定义数组长度的时候一定要考虑 ‘\0’。 详见字符数组及其定义和初始化,C语言字符数组详解
此处手动追加的’ \0 '也算一个字符,前面(1)中包括换行符整行的长度不超过limit,因此字符数组line[]的维度则是不超过limit+1.
每行长度最大限度limit,即字符数组line[]维度的具体值,在函数定义中不需要指定,但调用函数时则必须要给该函数参数提供一个具体的参数值。
#include <stdio.h>
#include <stdlib.h>
#define MAXLENGTH 100
int getline(char line[], int limit);
int main()
{
int length;//当前行的长度
char line[MAXLENGTH];//保存当前行的字符串
while ((length = getline(line, MAXLENGTH)) > 0)
{
printf("%d\n" , length);
}
return 0;
}
/*getline函数:将输入的字符保存到line数组中,并返回数组长度。 */
int getline(char line[], int limit)
{
int c;
int i;
c = i = 0;
for (i = 0; i < (limit - 1) && (c = getchar()) != EOF && c != '\n'; ++i)
{
line[i] = c;
}
if (c == '\n')
{
line[i] = c;
++i;
}
line[i] = '\0';
return i;
}
在调用函数时,提供的文本的行数及每行的行长度,都是任意的。不会都只是一行。
对每一行(每一个Enter键),都会调用一次getline()函数。
最终在main()函数中,只取的是输入文本的每几个长度(换行符也算一个字符)均为n的行中的第一行,进行判断,找出最长的,并输出其内容。
(当输入多行文本时,该程序也是一行一行处理,但最终可以自动返回批量处理所有行的结果,不需要一行一行输出。
但由于while循环中的第一个if判断条件为length > maxlength,所以并不会将相同行长度的字符数组line[]中的内容,替换成最新的。因此在输入的多行文本中,相同行长度的第二、三…行均不会被判断和输出。
若改成length >= maxlength,可以替换成最新的。但仍然只能输出该长度的最后的一行,并不能输出全部行。
这也是用C语言编写该程序的弱点之一。)
#include <stdio.h>
#include <stdlib.h> //此处删掉也不会有什么影响
#define MAXLENGTH 100//要统计的文本中最大行长度,即调用函数getline时赋给其参数limit的参数值
int getline(char line[], int limit);
void copyline(char to[], char from[]);
int main()
{
int length;//当前行的长度
int maxlength = 0;//当前文本中最大行的长度,初始化为0(小写的)
char line[MAXLENGTH];//保存当前行的字符串,当前行长度length不足该维度MAXLENGTH的,只将字符串中的字符赋给数组line[MAXLENGTH]中前面的元素,剩下的内存空间系统会自动用空字符' \0' 填充。输出时,也并不会输出空字符' \0'.详见[字符数组及其定义和初始化,C语言字符数组详解](http://c.biancheng.net/view/230.html)
char longest[MAXLENGTH];//保存最大行的字符串
/* 与函数原型和函数声明中不同,在用主函数main()调用函数时,所有声明的字符数组都指明了维度。*/
while ((length = getline(line, MAXLENGTH)) > 0) //此处一定是=,而不是减号。用函数getline返回依次当前行的长度,并赋值给变量length,参见下面函数的输出情况.同while((c = getchar()) != EOF).至少有一个换行符,所以最小长度为1.
//调用函数时,只需要说明参数的值为line,不用再声明参数的数据类型及细节char line[MAXLENGTH].
/* 如果有多种运算符进行混合运算时,强烈建议使用小括号来手动的控制运算符的优先级!运算符优先级顺序见 [比较运算符,逻辑运算符,位运算符,运算符优先级](https://blog.csdn.net/Fahaxikilvan/article/details/111067526)*/
{
if (length > maxlength) //某行长度的有多个时,并不会将行内容(即字符数组line[]中的内容)替换成最新的;除非改成length >= maxlength)
{
maxlength = length; //保证maxlength始终是最大的行长度值
copyline(longest, line); //并将最长的行中的文本保存起来(保存在字符数组longest[MAXLENGTH]中)
}
}
if (maxlength > 0)
{
printf("%s", longest); //原因见下方
}
return 0;
}
/*getline函数:将输入的字符保存到line数组中,并返回数组长度,
是一行一行地保存及返回长度,并不是整个文本都处理完了才返回,因为每读到一个换行符就会跳出保存内容的for循环语句,并执行后面的程序,最终返回该行的长度i.
函数getline的作用仅仅是这个,寻找最长的行,是在调用该函数时结合其他程序实现。 */
int getline(char line[], int limit)
{
int c;
int i;
c = i = 0; //养成对变量进行初始化的好习惯
for (i = 0; i < (limit - 1) && (c = getchar()) != EOF && c != '\n'; ++i) //i必须先自增,再赋值,因为数组的下标是从0开始的,且不重复;且因为把换行符全部都排除掉了,所以要小于最大行长度减去1的值。(数组角标小于limit-1,即只能到(小于等于)limit-2,则实际判断的是“字符”长度小于等于limit-1的所有情况。)
{
line[i] = c; //这里line[i]最终被赋的是第一个两个换行符之间字符长度为i+1的“字符”内容(第二、三…个都不会被存入)。i不断增加,字符数组指针不断后移,因此实现c中的值被依次赋给字符数组line[]的每个元素。
}
if (c == '\n')
{
line[i] = c; //将读到的换行符,插入到前面读到的每个“字符”的末尾(这里并不是替换)。i的初始值为0,在字符数组中指针仍然停留在上面的for循环结束时在字符数组中的位置,即最后。此处将读到的换行符插入到字符数组最后line[i]的为位置。且指针仍然停留在最后,所以当后面插入'\0'时,同样才会被追加到该元素的最后。
++i;
}
line[i] = '\0'; //将‘0’插入到每个字符的末尾(这里并不是替换),标记“字符”的结束,使由多个单个字符组成的一个“字符”变成了一个“字符串”(即char型字符串数组中的一个字符串元素。)因此这里的空字符并不仅是普通文本的一部分,直接导致使用printf函数时的占位符发生变化,不是%c和%d,而是%s.
/* 虽然此处的i不是在循环中,但跳出上面的if循环后,i的值是保留的,所以此处的i也是从初始值0不断加一,从数组的一个元素移动到下一个元素。
[关于for循环中的变量int i 如果跳出了这个for循环后,i的值是继续保留还是被释放掉了](https://blog.csdn.net/djt4541/article/details/80820381) */
return i; //i为该行的长度。
}
/*copyline函数:经最长字符串保存到to数组中*/
void copyline(char to[], char from[])
{
int i = 0;
while ((to[i] = from[i]) != '\0') //将字符数组from[]每个元素from[i],赋值给字符数组to[]的每个元素,同while((c = getchar()) != EOF)
++i; //每满足一次while的条件(即每复制),i就加1
}
line[i] = c;
的意思是将读到的字符c依次追加到字符数组line[i]的位置。 将读到的每种长度的两个换行符之间内容的第一个内容,不管包含多少个ASCII表中的字母或者字符,只要其长度不超过limit-1,就作为一个整体存储在字符数组line[i]的位置。
原因如下:
在使用getchar()函数接收字符时,用户输入的字符存储在缓冲区中,但如果没有按下Enter键,那么程序即使执行到putchar(),也不会将这个字符打印出来,因为压根就没有刷新缓冲区。
当用户按下Enter键之后,计算机刷新缓冲区,才能将这个缓冲区的内容打印出来。此处没有putchar(),但用getchar函数读取输入,并赋值给字符数组line[]的每个元素的原理也是一样的。
参见 详解while((ch = getchar()) != EOF)
(始终记得 i 在程序中是数组角标,比实际情况小1.)
此时字符数组line[]中仅仅存入了单个字符,还不能以字符串的形式输出,直到给其追加结束符’\0’.
练习1-16
修改打印最长文本行的程序的主程序main,使之可以打印任意长度的输入行的长度,并尽可能多地打印文本。
#include <stdio.h>
#define MAXLENGTH 100
int getline(char line[], int limit);
void copyline(char to[], char from[]);
int main() {
int length;
char line[MAXLENGTH];
char line2[MAXLENGTH];
while ((length = getline(line, MAXLENGTH)) > 0)
{
if (length > 0) {
printf("%d ", length);
copyline(line2, line);
printf("%s\n", line2);
}
}
return 0;
}
int getline(char line[], int limit)
{
int i, c;
i = c = 0;
for (i = 0; i < limit - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
line[i] = c;
if (c == '\n')
{
line[i] = c;
++i;
}
line[i] = '\0';
return i;
}
void copyline(char to[], char from[])
{
int i = 0;
while ((to[i] = from[i]) != '\0')
++i;
}
练习1-17
#include <stdio.h>
/* 打印长度大于80个字符的所有输入行 */
#define MAXLENGTH 100
int getline(char line[], int limit);
void copyline(char to[], char from[]);
int main() {
int length;
char line[MAXLENGTH];
char line2[MAXLENGTH];
while ((length = getline(line, MAXLENGTH)) > 0)
{
if (length > 80) {
printf("%d\n", length);
copyline(line2, line);
printf("%s\n", line2);
}
else
{
printf("%d\n", length);
}
}
return 0;
}
int getline(char line[], int limit)
{
int i, c;
i = c = 0;
for (i = 0; i < limit - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
line[i] = c;
if (c == '\n')
{
line[i] = c;
++i;
}
line[i] = '\0';
return i;
}
void copyline(char to[], char from[])
{
int i = 0;
while ((to[i] = from[i]) != '\0')
++i;
}
当把80改成8时的运行结果:
练习1-18
编写一个程序,删除每个输入行末尾的空格及制表符,并删除完全是空格的行
原文:https://blog.csdn.net/daobiying123/article/details/106051016
对每行输入都从后向前进行检测,直至检测到有效字符。
#include <stdio.h>
#define MINLEN 10 //可以删掉
#define MAXLEN 1000
int getline(char s[], int lim);
int copy(char from[], char to[], int len, int line_num);
int main()
{
int len, i;
int line_num = 0;
char c_out[MAXLEN];
char c_current[MAXLEN];
while ((len = getline(c_current, MAXLEN)) > 0)
{
//每行从后向前进行判断
for (i = len - 1; (c_current[i] == ' ' || c_current[i] == '\t' || c_current[i] == '\n') && (i >= 0); i--)
;
//如果行为空,直接执行下次循环;否则,只保留有效字符
/* 有效字符因为不满足该for循环中的条件,不会进入该循环,指针在第三部分i--的作用下,减少到碰到行中的有效字符即停止,所以得以保留。)空行则指针会一直移动到整行的开始即数组中位置为0处,然后才跳出循环。*/
if (i < 0) //还在同一个函数中,此处i的位置与上面for循环的结果保持一致
continue; //i小于0的情况出现在一个有效字符都没有的行中,数组角标是从0开始的,对这些行直接跳出该if循环(不是执行else下的语句)
else
{
c_current[i + 1] = '\n';//在每个非空行的有效字符后面的一个位置,追加一个换行符
len = i + 2; //数组角标是从0开始的,行真实长度始终比角标的值多1.
}
//复制有效字符到c_out
line_num = copy(c_current, c_out, len, line_num);
}
c_out[line_num] = '\0'; //经过复制的一步,字符数组c_out[]中的指针移动到了最后line_num,在最后追加结束符,这样才能打印出以字符数组形式储存的字符串
printf("%s", c_out);
return 0;
}
int getline(char s[], int lim)
{
int i, c;
for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
s[i] = c;
if (c == '\n')
{
s[i] = c;
i++;
}
return i;
}
int copy(char from[], char to[], int len, int line_num)
{
int i; //若在此处将line_num初始化为0,则输出时只能输出最后一行非空行,前面的非空行都输不出。为什么???
for (i = 0; i < len; i++)
{
to[line_num] = from[i];
line_num++; //复制完from[]中的最后一个字符时,to[]数组的维度值仍会加1
}
return line_num;
}
对比两个复制行的函数:
void copyline(char to[], char from[])
{
int i = 0;
while ((to[i] = from[i]) != '\0')
++i;
}
练习1-19
编写函数reverse(s),将字符串s中的字符顺序颠倒过来。使用该函数编写一个程序,每次颠倒一个输入行中的字符顺序。
改写copyline()函数即可实现(每行从后向前复制到c_out中)。
#include <stdio.h>
#define MAXLEN 1000
int getline(char s[], int lim);
int reverse(char from[], char to[], int len, int line_num);
int main()
{
int len;
int line_num = 0;
char c_out[MAXLEN];
char c_current[MAXLEN];
while ((len = getline(c_current, MAXLEN)) > 0)
{
line_num = reverse(c_current, c_out, len, line_num);
}
c_out[line_num] = '\0';
printf("%s", c_out);
return 0;
}
int getline(char s[], int lim)
{
int i, c;
for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; i++)
s[i] = c;
if (c == '\n')
{
s[i] = c;
i++;
}
return i;
}
int reverse(char from[], char to[], int len, int line_num)
{
int i;
len = len - 2; //行末尾的换行符不进入颠倒操作内,且至少有两个字符的行才进入颠倒操作
if (len > 0) //至少有两个有效字符的行才进行颠倒操作
{
for (i = 0; i <= len; i++)
{
to[line_num] = from[len - i]; //随着i的增大,len-i是递减的;line_num是递增的,从而实现从后向前复制
line_num++;
}
to[line_num] = '\n'; //此循环中数组的指针的位置始终在最后,是追加也是赋值
line_num++; //换行符也算一个字符
}
return line_num;
}
1.10 外部变量与作用域
练习1-20
编写程序detab,将输入中的制表符替换成适当数目的空格,使空格充满到下一个制表符终止的地方。
这里采用:制表符终止位为8的整数倍。
依然在之前练习的基础上修改,将getline()函数改写为detab(),当有’\t’输入时进行字符替换。
#include <stdio.h>
#define MAXLEN 1000
int detab(char s[], int lim);
int copy(char from[], char to[], int len, int line_num);
int main()
{
int len;
int line_num = 0;
char c_out[MAXLEN];
char c_current[MAXLEN];
while ((len = detab(c_current, MAXLEN)) > 0)
{
line_num = copy(c_current, c_out, len, line_num);
}
c_out[line_num] = '\0';
printf("%s", c_out);
return 0;
}
int detab(char s[], int lim)
{
int i, j = 0, c;
for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; i++)
{
if (c != '\t')
s[i] = c;
else
{
//计算应该插入多少个*
while (i < 16) //假设有两个8位的制表符
{
s[i] = '*';
i++;
}
i--;
}
}
if (c == '\n')
{
s[i] = c;
i++;
}
return i;
}
int copy(char from[], char to[], int len, int line_num)
{
int i;
for (i = 0; i < len; i++)
{
to[line_num] = from[i];
line_num++;
}
return line_num;
}
练习1-21
编写程序entab,将空格串替换为最少数量的制表符和空格,但要保持单词之间的间隔不变。
骑马的佳俊《C程序设计语言》 练习1-21
#include<stdio.h>
#define MAXLEN 1024
#define TAB 4
int getlines(int array[], int maxlen);
int getlines(int array[], int maxlen)//将输入读入数组,并且在换行时输出结果
{
int i, c;
for (i = 0; i < maxlen - 1 && (c = getchar()) != EOF && c != '\n'; i++)
{
array[i] = c;
}
if (c == '\n')
{
array[i] = c;
i++;
}
array[i] = '\0';
return i;
}
int main()
{
int array[MAXLEN];
int len;
int i;
int nb = 0;//空格计数器
int nt = 0;//制表符计数器
while ((len = getlines(array, MAXLEN)) > 0)
{
for (i = 0; i < len; i++)
{
if (array[i] == ' ') //一行中存在空格,且已经读取了该空格。
{
//统计同一行两段连续字符中间需要输出多少制表符和空格
if ((i + 1) % TAB != 0)//i从零开始,但字符位置从1开始,(i+1)表示当前空格,所以有后面的nb++。读入几个空格,i的指针位置不断后移。当该余数为0时,说明指针已经移动到/已经读到了tab键,所以有后面的nt++.
{
nb++; //对在第一个制表符位,需要补的总空格数。
}
else
{
nt++;
nb = 0;//制表符永远在空格前面,有了制表符,空格就要清零
}
}
else //若一个行中没有空格
/* 在执行该主函数main()大于等于两次的情况下,此else语句和上面的if语句,都可能会执行。在同一次执行main()函数的过程中,一定不会既执行if语句,又执行else语句。*/
{
for (; nt > 0; nt--) //同下面nb的情况相同,当碰到空格返回执行上面的 if (array[i] == ' ') ——else结构。且满足tab键的位置(i + 1) % TAB == 0时,nt经过其中的子else语句不为0.当碰到字符(array[i] != ' ')进入该else结构时,要先把刚才计算出的tab键输出再读取和处理后面的。
{
printf("++++"); //putchar('\t');
}
if (array[i] == '\t') //若是没有读到空格且读到制表符
{
nb = 0; //空格计数器归为0.此处正常输出该制表符。
}
else //在该if-else结构中,如果没有碰到制表符,则进入该else语句,若此时碰到了空格,则会返回去执行上面 if (array[i] == ' ') ——else结构中的if语句,即统计空格的个数,所以nb可能不为0.
{
for (; nb > 0; nb--) //nb为需要补的总空格数。每输出一个空格(*),nb就减一,直到填满
{
putchar('*'); //putchar(' ');
}
}
putchar(array[i]); //空格和制表符的情况上面都处理完了。若是遇到字符,则正常输出。
}
}
}
return 0;
}
当定义tab是4时:
当定义tab是8时:
练习1-22 ???
/**************************************************************************
类型:<c程序设计语言(第二版.新版)> 练习 1-22
名称:lx.c
author: wubenzhimu
data: 2012.11
功能:编写一个程序,把较长的输入行“折”成短一些的两行或多行,折行的
位置在输入行的第n列之前的最后一个非空格之后。要保证程序能够智能地处理
输入行很长以及在指定的列前边没有空格或制表符时的情况。
***************************************************************************/
#include <stdio.h>
#define MAXLINE 1000
#define TRUELENGTH 10 /* 超过这个长度折行 */
int getLine(char charArr[]);
void copy(char charArr[], char toArr[], int length);
int main()
{
int putLength = 0; /* 输入数组的长度 */
char charArr[MAXLINE] = { 0 };
char toArr[MAXLINE] = { 0 };
putLength = getLine(charArr);
if (putLength > 0)
{
copy(charArr, toArr, putLength);
}
printf("%s", toArr);
return 0;
}
/* 把输入的字符串保存到数组中 */
int getLine(char charArr[])
{
int c, i = 0;
while ((c = getchar()) != EOF)
{
charArr[i] = c;
i++;
}
charArr[i] = '\n';
charArr[++i] = '\0';
return i;
}
/* 复制指定长度的数组并符合添加的加'\n' */
void copy(char charArr[], char toArr[], int length)
{
int i = 0, j = 0, t = 0;
while (i <= length)
{
if (t == TRUELENGTH)
{
while (charArr[i] == '\b' || charArr[i] == ' ')
{
i++;
}
toArr[j] = '\n';
t = 0;
}
else
{
if (t == 1)
{
while (charArr[i] == '\b' || charArr[i] == ' ')
{
i++;
}
}
toArr[j] = charArr[i];
}
i++;
j++;
t++;
}
}
练习1-23 ???
删除C语言程序中所有的注释语句
#include <stdio.h>
#define MAXLEN 1000
int getline(char s[], int lim);
int copy(char from[], char to[], int len, int line_num);
int main()
{
int len;
int line_num = 0;
char c_out[MAXLEN];
char c_current[MAXLEN];
while ((len = getline(c_current, MAXLEN)) > 0)
{
line_num = copy(c_current, c_out, len, line_num);
}
c_out[line_num] = '\0';
printf("%s", c_out);
return 0;
}
int getline(char s[], int lim)
{
int i, j, c;
//flag_s = 0表示位于字符串外,flag_s = 1表示位于字符串内
int flag_s = 0;
for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; i++)
{
s[i] = c;
//判断是否位于字符串内,并设置标志位
if (s[i] == '\"' && flag_s == 0)
flag_s = 1;
else if (s[i] == '\"' && flag_s == 1)
flag_s = 0;
//判断是否为单行注释
if (s[i] == '/' && flag_s == 0)
{
if (s[i - 1] == '/')
{
j = i - 1;
while (s[i] != '\n')
{
i++;
c = getchar();
s[i] = c;
}
s[j] = '\n';
j++;
return j;
}
}
//判断是否为多行注释
if (s[i] == '*' && flag_s == 0)
{
if (s[i - 1] == '/')
{
j = i - 2;
while (!(s[i - 1] == '*' && s[i] == '/'))
{
i++;
c = getchar();
s[i] = c;
}
i = j;
}
}
}
if (c == '\n')
{
s[i] = c;
i++;
}
return i;
}
int copy(char from[], char to[], int len, int line_num)
{
int i;
for (i = 0; i < len; i++)
{
to[line_num] = from[i];
line_num++;
}
return line_num;
}
/* P25 练习1-24
编写一个程序,查找C程序中的基本语法错误,如圆括号、方括号、花括号不匹配等。
正确处理单双引号、转义字符序列与注释。
*/
#include <stdio.h>
#include <stdbool.h> /* C99标准 新增的头文件,里面定义了bool、true、false。原本C语言是不含布尔型的 */
#include <stdlib.h>
/* 由于检查所有的语法错误,难度较大,故在这里只处理这些问题:
1.圆括号、方括号、花括号是否匹配
2.单双引号、转义字符序列与注释是否符合语法规定
*/
#define MAX_LENGTH 10000 /* C程序最多的字符个数 */
#define STACK_SIZE 100 /* 栈的大小 */
#define IN_SINGLE_QUOTE 4 /* 当前读入字符处于单引号内 */
#define IN_DOUBLE_QUOTE 3 /* 当前读入字符处于双引号内 */
#define IN_SINGLE_ANNOT 2 /* 当前读入字符处于单行注释 */
#define IN_MULTIPLE_ANNOT 1 /* 当前读入字符处于多行注释 */
#define OUT 0
/* 代表各种错误类型的状态码 */
#define E_SINGLE_ANN -1 /* 单行注释出错 */
#define E_MULTI_ANN -2 /* 单行注释出错 */
#define E_QUOTE -3 /* 引号不匹配出错 */
#define E_BRACKET -4 /* 括号(bracket)不匹配出错 */
#define E_ESC -5 /* 转义字符(escape character)错误 */
void print_error(int line, char where, int error_type);
bool check_bracket(char stack[], int* pointer);
void check_syntax() {
int c, next, cur_line, state;
char stack[STACK_SIZE]; /* 保存各种括号,检测是否匹配 */
int pointer = 0; /* 栈顶指针 */
bool open = true, must_end = false; /* 是否C程序还未读完 */
cur_line = 1; /* 正在入读程序第第几行 */
state = OUT;
while ((c = getchar()) != EOF) {
if (c == '\n')
cur_line++;
if (state == OUT) {
switch (c) {
case '(': /* 括号 */
case ')':
case '[':
case ']':
case '{':
case '}':
stack[pointer++] = c; /* 将各种括号存于栈中以便检查 */
if (c != '(' && c != '[' && c != '{')
if (!check_bracket(stack, &pointer)) {
/* 括号不匹配 */
print_error(cur_line, c, E_BRACKET);
}
break;
case '/': /* 注释 */
next = getchar();
if (next == '\n')
cur_line++;
if (next == EOF) {
open = false;
/* 单行注释语法错误 */
print_error(cur_line, c, E_SINGLE_ANN);
}
else if (next == '*') { /* 进入多行注释 */
state = IN_MULTIPLE_ANNOT;
}
else if (next == '/') {
state = IN_SINGLE_ANNOT;
}
else {
/* 单行注释语法错误 */
print_error(cur_line, c, E_SINGLE_ANN);
}
break;
case '\'': /* 单引号 */
state = IN_SINGLE_QUOTE;
must_end = false;
break;/* 双引号 */
case '\"':
state = IN_DOUBLE_QUOTE;
break;
}
if (!open) /* 当由next读到EOF时,无法从switch中跳出循环,故增加此变量,当next == EOF时跳出while循环 */
break;
}
else if (state == IN_MULTIPLE_ANNOT) {
if (c == '*') {
next = getchar();
if (next == '\n')
cur_line++;
if (next == EOF) {
print_error(cur_line, c, E_MULTI_ANN);
break; /* 跳出while */
}
else if (next == '/') { /* 多行注释结束 */
state = OUT;
}
}
}
else if (state == IN_SINGLE_ANNOT) {
if (c == '\n')
state = OUT; /* 单行注释结束 */
}
else if (state == IN_SINGLE_QUOTE) {
if (must_end) { /* 用来检查是否 只有一个字符 */
/* c 必须是‘'’ */
if (c == '\'')
state = OUT; /* 单引号结束 */
else print_error(cur_line, c, E_QUOTE);
}
else if (c == '\\') {
/* 遇到转义字符 */
next = getchar();
if (next == EOF)
break;
switch (next) {
/* 暂时考虑这些转义字符,没有考虑八进制与十六进制 */
case '\a':
case '\b':
case '\f':
case '\n':
case '\r':
case '\t':
case '\v':
case '\\':
case '\'':
case '\"':
case '\?':
case '\0':
must_end = true;
break;
default: /* 转义字符错误 */
print_error(cur_line, c, E_ESC);
break;
}
}
else if (c == '\'') { /* 单引号内容为空 */
state = OUT;
}
else { /* c是普通字符 */
must_end = true;
}
}
else if (state == IN_DOUBLE_QUOTE) {
if (c == '\\') {
/* 遇到转义字符 */
next = getchar();
if (next == EOF)
break;
switch (next) {
/* 暂时考虑这些转义字符,没有考虑八进制与十六进制 */
case 'a':
case 'b':
case 'f':
case 'n':
case 'r':
case 't':
case 'v':
case '\\':
case '\'':
case '"':
case '?':
case '0':
break;
default: /* 转义字符错误 */
print_error(cur_line, c, E_ESC);
break;
}
}
else if (c == '\"') { /* 双引号结束 */
state = OUT;
}
}
}
if (state != OUT) {
switch (state) {
/* 引号缺少结束符 */
case IN_SINGLE_QUOTE:
case IN_DOUBLE_QUOTE:
print_error(cur_line, '\0', E_QUOTE);
break;
// 多行注释一直延续到程序结束,缺少结束符‘*/’
case IN_MULTIPLE_ANNOT:
print_error(cur_line, '\0', E_MULTI_ANN);
break;
}
}
else {/* 检测栈内容要为空 */
if (pointer > 0) {
printf("line:last line, error_msg: %s\n", "brackets not match.");
}
else {
printf("\n====================No errors!====================\n");
}
}
}
/* 如果函数参数是字符数组,调用可以时直接传入字符串常量 */
void print_error(int line, char where, int error_type) {
/* 打印错误信息 */
printf("line:%d, ", line);
switch (error_type) {
case E_SINGLE_ANN:
printf("where: %c, error_msg: %s\n", where, "single line annotation exception!");
break;
case E_MULTI_ANN:
if (where == '\0') {
printf("error_msg: %s\n", "lack multiple annotation terminator '*/'.");
}
else {
printf("where: %c, error_msg: %s\n", where, "lack character '/'.");
}
break;
case E_QUOTE:
if (where != '\0')
printf("where: %c, error_msg: %s\n", where, "quotes not match.");
else printf("error_msg: %s\n", "quotes not match.");
break;
case E_ESC:
printf("where: %c, error_msg: %s\n", where, "it is not an escape character.");
break;
case E_BRACKET:
printf("where: %c, error_msg: %s\n", where, "brackets not match.");
break;
}
exit(0);
}
/* 检测括号是否匹配 */
bool check_bracket(char stack[], int* pointer) {
if (*pointer > 1) { /* pointer指向的栈顶位置为空 */
int r_bracket = stack[--*pointer]; /* 右括号 */
int l_bracket = stack[--*pointer]; /* 左括号 */
switch (r_bracket) {
case ')':
if (l_bracket != '(')
return false;
break;
case ']':
if (l_bracket != '[')
return false;
break;
case '}':
if (l_bracket != '{')
return false;
break;
default:
return false;
}
}
else if (*pointer == 1) {
int top = stack[--*pointer];
if (top != '(' || top != '[' || top != '{')
return false;
}
return true;
}
void main() {
check_syntax();
}