一点点做笔记一点点记忆😆
1.1 简介
这一章通过一个例程来介绍C语言,让我们对C语言整体有个印象。程序的功能是:取读一串列标号,这些列标号(整形数)成对出现,标识列范围(就是区间,打印在范围内的字符),这串列标以负数结尾。
输入样例:
4 9 12 20 -1
abcdefghijklmnopqrstuvwxyz
Original input: abcdefghijklmnopqrstuvwxyz
Rearranged line: efghijmnopqrstu
See you!
Original input: See you!
Rearranged line: you!
Bye
Original input: Bye
Rearranged line:
源码附在最后,接下来将进行简单的分析。
1.1.1 空白和注释
C语言是一种自由格式的语言,并没有规定必须要怎么书写语句。我们编程久了会形成自己的风格,正规的公司也会对代码有一定的规范。代码是给自己看的,应该积累一些简洁清晰的书写经验。
平时会用//
或者/**/
来对代码进行注释,同时也会想“注掉不起作用”。在这里有个新的方法。要想从逻辑上删除一段C代码,更好的办法是使用#if指令。可以通过赋值来控制代码块的作用与否。这感觉很方便调试。书中在14章中会介绍更多方法。
#if 0
statements
#endif
1.1.2 预处理指令
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_COLS 20 /* 所能处理的最大列号 */
#define MAX_INPUT 1000 /* 每个输入行的最大长度 */
预处理器会将对应文件的代码复制到调用的位置。stdio.h
提供了标准IO库的函数,stdlib.h
定义了EXIT_SUCCESS
和EXIT_FAILURE
,string.h
提供的字符串操作函数。
另外C语言程序是顺序执行的,函数必须在main
函数之前有声明或者在头文件中包含😃。否则在编译时候会出现错误。
/*
** 开头的函数声明,另外代码格式很舒服 c风格代码原来是这样
*/
int read_column_numbers( int columns[], int max );
void rearrange( char *output, char const *intput,
int n_columns, int const columns[] );
1.1.3 main函数
int main(void){
关键字int
代表函数返回一个整型值,void
表示函数不接受任何参数。
int n_columns; /* 进行处理的列标号 */
int columns[MAX_COLS]; /* 需要处理的列数 */
char input[MAX_INPUT]; /* 容纳输入行的数组 */
char output[MAX_INPUT]; /* 容纳输出行的数组 */
声明了四个变量。一个整型标量,一个整型数组,两个字符数组。四个变量是main函数的局部变量,其他函数不能通过名字访问。(仅用{}
扩起的代码块中声明也是属于局部变量只能内部使用,新发现🤣)
/*
** 读取该串列标号
*/
n_columns = read_column_numbers( columns, MAX_COLS);
在C中,数组是以引用的形式进行传递的,也就是传址调用,而标量是按值传递的。在函数中对标量参数的任何修改都会在函数返回时丢失,而函数在修改数组参数中的一个元素的时候,调用函数所传递的数组则会被实际修改。
/*
** 读取、处理和打印剩余的输入行。
*/
while ( gets( input ) != NULL ){
printf( "Original input: %s\n", input );
rearrange( output, input, n_columns, columns );
printf( "Rearranged line: %s\n", output);
}
return EXIT_SUCCESS;
}
书中对注释这个东西做了特别强调。
如今最大的软件开销并非在于编写,而是在于维护。在修改一段代码的时候所遇的第一个问题就是要搞清楚代码的功能。所以,如果在代码中插入能使其他人(或许就是你自己!)在以后能更容易理解它的东西,那就非常值得去做。但是,要注意书写正确的注释,并且在修改代码的时候要注意注释的更新。注释如果不正确的话那还不如没有!
gets
函数从标准输入读取一行文本并把它存储于作为参数传进来的数组中。一行输入由一串字符组成,以一个换行符结尾。gets丢弃换行符,并在该行的末尾存储一个NUL字节。当gets被调用但事实上不存在输入行时,返回NULL,表示到达了输入的末尾(文件尾)。
C语言中不存在“String”数据类型,但是有个约定:字符串就是一串以NUL字节结尾的字符。 NUL作为字符终止符,它本身并不被看作是字符串的一部分。
printf
执行格式化输出。
格式 | 含义 |
---|---|
%d | 十进制整型 |
%o | 八进制整型 |
%x | 十六进制整型 |
%g | 打印浮点值 |
%c | 打印字符 |
%s | 打印字符串 |
\n | 换行 |
当然还有更多骚操作。慢慢积累。
1.1.4 read_column_numbers函数
/*
** 读取列标号,如果超出规定范围则不予理会。
** 为什么不能宏定义的变量?为了函数调用的清晰性?
*/
int read_column_numbers( int columns[], int max )
{
在函数声明的数组参数中,没有声明数组的长度,意味着不管数组多长,函数都照收不误。但如果函数需要预先知道这个数组的长度的话,就需要再添加一个数组长度变量传进来了😂。
int num = 0;
int ch;
声明了两个变量,有一个没赋初值,调用的时候需要注意先赋值。而且注意!ch
声明的是整型!
/*
** 取得列标号,如果所取读的数小于0则停止。
*/
while( num < max && scanf( "%d", &columns[num]) == 1
&& columns[num] >=0 )
num += 1;
scanf
函数中,所有的标量参数必须加上取地址符&
。数组参数不用加,但是如果数组进行了下标引用 那么就需要加取地址符了,这个时候参数实际是数组的某个特定元素值,是个标量。而且这个函数是有返回值的,正确获取到就返回1,读不到或者格式不对就返回0。
格式码和printf
略有不同:
格式 | 含义 | 变量类型 |
---|---|---|
%d | 读取一个整型值 | int |
%ld | 读取一个长整型值 | long |
%f | 读取一个浮点数 | float |
%lf | 读取一个双精度浮点数 | double |
%c | 读取一个字符 | char |
%s | 读取一个字符串 | char型数组 |
&&
是逻辑与,与位操作的&
要做区分。程序中三个条件是与关系,说明必须都真才是真,那么如果左边的表达式为假,右边的表达式就不再执行,因为已经能判断出表达式的结果了!
/*
** 确认已经读取的标号为偶数个,因为他们是以对的形式出现的。
*/
if( num % 2 != 0){
puts( "Last column number is not paired.");
exit( EXIT_FAILURE );
}
puts
是gets的输出版本,将指定字符串写道标准输出并在末尾添上一个换行符。
exit
函数终止程序的运行,EXIT_FAILURE
这个值被返回给操作系统,表示出现了错误。
/*
** 丢弃该行中包含最后一个数字的那部分内容。
** 过滤掉输入缓冲区的数据,例如,输入1 2 4 9 -1 6
** 当识别-1结束输入后,6将作为下一次输入的第一个数据,得到
** Original input: 6
** Rearranged line: 6
** 这样的结果。
*/
while( (ch = getchar()) != EOF && ch != '\n')
;
return num;
}
getchar
函数从标准输入读取一个字符并返回它的值,如果失败则返回常量EOF(stdio.h中定义),用于提示文件的结尾。EOF是一个整型值,而且它的位数比字符型的多,所以使用字符声明的话ch和EOF永远不会相等,需要声明成整型,让ch足以大到可以容纳EOF的值。
C可以把赋值操作蕴含于wihle语句内部,这样确实能解决冗余语句,炫技get🤣。
1.1.5 rearrange函数
/*
** 处理输入行,将指定列的字符连接在一起,输出行以NUL结尾。
*/
void rearrange( char *output, char const *input,
int n_columns, int const columns[])
{
int col;
int output_col;
int len;
函数声明的时候形参是指针变量,但在调用的时候是传入的数组名。这是合法的,数组名就是个指针地址,通过指针的方式来操控数组。
注意,第2和第4个形参有const
修饰。这有两个作用:
- 该函数作者的意图是这个参数不能被修改(参数不能改)
- 编译器会验证是否违背该意图(编译器会警告or报错)
len = strlen( input );
output_col = 0;
/*
** 处理每对列标号。
*/
for( col = 0; col < n_columns; col += 2){
for
循环的三个部分:初始部分,测试部分,调整部分。(头一次见这么描述…)
不过书中提出for像是while的简写,两种循环之间可以互相转化。
int nchars = columns[col + 1] - columns[col] + 1;
/*
** 如果输入行结束或输出行数组已满,就结束任务。
*/
if( columns[col] >= len ||
output_col == MAX_INPUT - 1)
break;
/*
** 如果输出行数据空间不够,只复制可以容纳的数据。
*/
if( output_col + nchars > MAX_INPUT - 1 )
nchars = MAX_INPUT - output_col - 1;
/*
** 复制相关的数据。
*/
strncpy( output + output_col, input + columns[col],
nchars );
output_col += nchars;
strncpy
前两个参数是目标字符串和源字符串,第三个参数是需要复制的字符数,不够话用NUL字节填充。
}
output[output_col] = '\0';
}
最后输出字符串将以一个NUL字符作为终止符。
1.2 补充说明
输出函数补充:
putchar
接受一个整型字符,并在标准输出中输出。
字符操作补充:
- 复制:
strcpy
没有字符数的限制。第一个参数不能是字符串常量,函数不检查空间是否足够。strcat
两个参数,将第二个添加到第一个末尾。第一个不能是字符串常量,函数不检查空间是否足够。
- 搜索:
strchr
两个参数,第一个是字符串,第二个是字符,搜索字符一次出现的位置,没有返回NULL。strstr
两个参数,连个字符串。搜索字符串第一次出现的位置。
安全函数:
fgets
如果遇到很长的输入行gets没办法防止函数溢出,使用安全函数fgets
1.3 编译方式
我是用Vscode编写代码,用编译器mingw64。
1.4 总结
这篇文章编写于此呢,我印象最深的是#if语句的使用,这确实很有帮助。还对EOF
,NUL
这种定义有了点印象。其实这对于C语言来说很重要,但是对于高级语言来说,已经有了更为方便的实现。不过嘛,学习笔记耐心写写,要不然时间也会浪费在无聊的焦虑当中。希望能慢慢写完!
1.5 源码
/*
** 《C与指针》第一章 程序1.1
**
** 这个程序从标准输入中读取输入行并在标准输出中打印这些输入行。
** 每个输入行的后面一行是该行内容的一部分。
**
** 输入的第一行是一串列标号,串的最后以一个负数结尾。
** 这些列标号成对出现,说明需要打印的输入行的列的范围。
** 例如,0 3 10 12 -1表示第0行到第3列,第10列到第12列的内容将被打印
** 想结束循环需要输入ctrl+z表示空输入
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_COLS 20 /* 所能处理的最大列号 */
#define MAX_INPUT 1000 /* 每个输入行的最大长度 */
/*
** 开头的函数声明,另外代码格式很舒服 c风格代码原来是这样
*/
int read_column_numbers( int columns[], int max );
void rearrange( char *output, char const *intput,
int n_columns, int const columns[] );
int main( void )
{
int n_columns; /* 进行处理的列标号 */
int columns[MAX_COLS]; /* 需要处理的列数 */
char input[MAX_INPUT]; /* 容纳输入行的数组 */
char output[MAX_INPUT]; /* 容纳输出行的数组 */
/*
** 读取该串列标号
*/
n_columns = read_column_numbers( columns, MAX_COLS);
/*
** 读取、处理和打印剩余的输入行。
*/
while ( gets( input ) != NULL ){
printf( "Original input: %s\n", input );
rearrange( output, input, n_columns, columns );
printf( "Rearranged line: %s\n", output);
}
return EXIT_SUCCESS;
}
/*
** 读取列标号,如果超出规定范围则不予理会。
** 为什么不能宏定义的变量?为了函数调用的清晰性?
*/
int read_column_numbers( int columns[], int max )
{
int num = 0;
int ch;
/*
** 取得列标号,如果所取读的数小于0则停止。
*/
while( num < max && scanf( "%d", &columns[num]) == 1
&& columns[num] >=0 )
num += 1;
/*
** 确认已经读取的标号为偶数个,因为他们是以对的形式出现的。
*/
if( num % 2 != 0){
puts( "Last column number is not paired.");
exit( EXIT_FAILURE );
}
/*
** 丢弃该行中包含最后一个数字的那部分内容。
** 过滤掉输入缓冲区的数据,例如,输入1 2 4 9 -1 6
** 当识别-1结束输入后,6将作为下一次输入的第一个数据,得到
** Original input: 6
** Rearranged line: 6
** 这样的结果。
*/
while( (ch = getchar()) != EOF && ch != '\n')
;
return num;
}
/*
** 处理输入行,将指定列的字符连接在一起,输出行以NUL结尾。
*/
void rearrange( char *output, char const *input,
int n_columns, int const columns[])
{
int col;
int output_col;
int len;
len = strlen( input );
output_col = 0;
/*
** 处理每对列标号。
*/
for( col = 0; col < n_columns; col += 2){
int nchars = columns[col + 1] - columns[col] + 1;
/*
** 如果输入行结束或输出行数组已满,就结束任务。
*/
if( columns[col] >= len ||
output_col == MAX_INPUT - 1)
break;
/*
** 如果输出行数据空间不够,只复制可以容纳的数据。
*/
if( output_col + nchars > MAX_INPUT - 1 )
nchars = MAX_INPUT - output_col - 1;
/*
** 复制相关的数据。
*/
strncpy( output + output_col, input + columns[col],
nchars );
output_col += nchars;
}
output[output_col] = '\0';
}