c和指针第一章学习笔记

书中第一章首先是一篇很有技巧性的c代码

/*
** 这个程序从标准输入中读取输入行并在标准输出中打印这些输入行
**每个输入行的后面一行是该行内容的一部分
**
**输入的第一行是一串列标号,串的最后以一个负数结尾
**这些列标号承兑出现,说明需要被打印的输入行的列范围
**例如,0 3 10 12 -1表示第0列到第3列,第10列到第12列的内容将被打印
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_CLOS 20         /*所能处理的最大列号*/
#define MAX_INPUT 1000       /*每个数入行的最大长度*/
int read_column_numbers(int columns[],int max);
void rearrange(char *output,char const*input,int n_columns,int const columns[MAX_CLOS]);
int main(void)
{
   int n_columns;           /*进行处理的列标号*/
   int columns[MAX_CLOS];   /*需要处理的列数*/
   char input[MAX_INPUT];    /*需要容纳的输入的数组*/
   char output[MAX_INPUT];   /*容纳输出行的数组*/

   n_columns=read_column_numbers(columns,MAX_CLOS);
   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;
    while(num<max&&scanf("%d",&columns[num])==1&&columns[num]>0)
    {
        /*这个循环条件保证了读取的行号不会超过最大值,而且利用&&的短路特性,
          也不会即使超过最大行号也不会被scanf读入,同时scanf保证了读入整型数据,
          同时后面的条件输入的为正数*/
          num+=1;
    }
    /*判断是否读入的数据是成对的*/
    if(num%2!=0)
    {
        puts("Last column number is not paired.");
        exit(EXIT_FAILURE);
    }
    while((ch=getchar())!=EOF&&ch!='\n');  /*用来处理包含最后那个负值的所有字符*/
    return num;
}
/*处理输入行*/
void rearrange(char *output,char const*input,int n_columns,int const columns[MAX_CLOS])
{
    int col;                                            /*columns数组的下标*/
    int output_col;                                     /*输出行的列计数器*/
    int len;                                            /*输入行的长度*/
    int nchars;                                         /*成对处理的列之间的长度*/
    len=strlen(input);
    output_col=0;
    for(col=0;col<n_columns;col+=2)
    {
        if(columns[col]>=len||output_col==MAX_INPUT-1)  /*如果输入行的标号小于需要处理的列标号或者
                                                          输出数组已满结束任务*/
            break;
        nchars=columns[col+1]-columns[col]+1;
        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';
}

1.1.1 空白和注释

最开始一段就是注释,以/*开始,以*/,在c程序中,凡是可以插入空白的地方都可以插入注释。但是注释不能嵌套,也就是说第一个/*与第一个*/符号之间的内容都被当做是注释

1.1.2预处理指令

在我们的例子程序中,预处理器永明叫stdio.h的库函数头文件的 内容替换第一条#include指令语句。器结果就是将stdio.h的内容被逐字写道源文件的那个位置。
stdio.h头文件是我们可以发访问标准I/O库中的函数,这组函数用于执行输入和输出。stdlib.h定义了EXIT_SUCCESS和EXIT_FAILURE符号。我们需要string.h头文件提供函数来操纵字符串
还有一种欲处理指令为#define,他把MAX_COLS定义为20,把名字MAX_INPUT定义为1000.当这个名字出现在源文件的任何地方的时候,它就会被替换为定义的值

int read_column_numbers(int columns[],int max);
void rearrange(char *output,char const*input,int n_columns,int const columns[MAX_CLOS]);

这些声明被称为函数原型,它们告诉编译器这些以后将在源文件中定义的函数的特征。这样,当这些函数被调用时,编译器就能对它们进行准确性检查

1.1.3 main函数

int main(void)

每个c程序都必须有一个main函数,因为它是程序执行的起点

   int n_columns;           /*进行处理的列标号*/
   int columns[MAX_CLOS];   /*需要处理的列数*/
   char input[MAX_INPUT];    /*需要容纳的输入的数组*/
   char output[MAX_INPUT];   /*容纳输出行的数组*/

这几行声明了4个变量,一个整型标量,一个整型数组,两个字符数组。所有4个变量都是main的局部变量,其他函数不能根据它们的名字访问它们

   n_columns=read_column_numbers(columns,MAX_CLOS);

在c语言中,数组参数是以引用的形式进行传递的,也就是地址调用,而标量和常量则是按值传递的。在函数中对标量参数的任何修改都会在函数返回时丢失,因此,被调用函数无法修改调用函数以传值形式传递给它的参数。但是,当被调用函数修改数组参数的其中一个元素时,调用函数所传递的数组就会被实际地修改

void func(int a){a=100;}
int a=10;
func(a);
printf("a=%d",a); // 仍然显示 a=10, 因为整型参数a在func()中是按值传递的, 也就是说func()仅使用a的值, 而对存储a的内存地址不加修改;
void func(int* a){ *a=100;}
int a=10;
func(&a);
printf("a=%d",a); // 显示 a=100, 因为整型参数指针a在func()中是按地址传递的, 也就是说func()使用a的存储地址, 直接修改其内容.
数组参数与指针参数相同, 均按地址传递.

事实上,关于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;
}

这个while循环代表了这个程序的主要逻辑。

while 我们还可以读取零一行输入的时候
         打印输入行
         对输入行进行重新整理,把它储存于output数组
         打印输出结果

gets函数从标准输入读取一行文本并把它储存于作为参数传递给它的数组中。衣阿华那个输入由一串字符组成,以一个换行符结尾。gets函数丢弃换行符,并在改行的末尾储存一个NUL字节(一个NUL字节是指字节模式全为0的字节,类似’\0’这样的字符常量)。然后,gets函数返回一个非NULL值,表示改行已经被成功读取,当get函数被调用但事实上不存在输入行的时候,就返回NULL值。
c语言并不存在“string”数据类型,但是在在整个语言中,存在一项约定,字符串就是一串以NUL字节结尾的字符。NUL是作为字符串终止符,它本身并不被看作是字符串的一部分。字符串产量就是源程序中被双引号括起来的一串字符。例如

"Hello"

在内存中占据6个字节的空间,按顺序分别是H,e,l,l,o和NUL
printf函数接收多个参数,其中第一个参数是一个字符串,秒速输出的格式,剩余的参数就是需要打印的值,格式常常以字符串常量的形式出现

常用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;

这里声明了两个变量,第二个变量没有初始化,更准确的说,它的值将是一个不可预料的值,也就是垃圾。在这个函数里卖弄,它没有初始值不碍事,因为函数对这个变量所执行的第一个操作就是对他赋值

    while(num<max&&scanf("%d",&columns[num])==1&&columns[num]>0)
    {
        /*这个循环条件保证了读取的行号不会超过最大值,而且利用&&的短路特性,
          也不会即使超过最大行号也不会被scanf读入,同时scanf保证了读入整型数据,
          同时后面的条件输入的为正数*/
          num+=1;
    }

这又是一个循环,用于读取列标号。scanf函数从标准输入读取字符并根据格式字符串对它们进行转换,类似与printf的逆操作。scanf函数接受几个参数,其中第一个参数是一个格式字符串,用于描述期望的输入类型。剩余几个参数都是变量,用于储存函数所读取的输入数据。scanf函数的返回值是函数成功转换并储存于参数的值的个数。
因为scanf函数的实现原理,所有标量参数的前面要加上&符号。数组参数前面不需要加上&符号。但是,数组参数中如果出现了下标引用,也就是说实际参数是数组的某个特定元素,那么它前面也必须加上&符号。

常用scanf格式码
%d 读取一个整数值 (int)
%ld 读取一个长整数值(long)
%f 读取一个浮点数(float)
%lf 读取一个双精度实型数double
%c 读取一个字符(char)
%s 从输入中读取一个字符串(char型数组)

用%s格式码输入字符串的时候,中间不能包含空白
我们i西安在可以解释表达式

scanf("%d",&columns[num]

%d表示需要读取一个整型值。字符是从标准输入读取,前导空白将被跳过,任何这些数组被转换为一个整型,结果储存于指定的数组元素中

num<max&&scanf("%d",&columns[num])==1&&columns[num]>0

剩下的两个测试条件确保函数不会读取过度的值还有确保函数所读取的值是正数
如果不进行num<max这个测试,而且程序所读取的文件包含超过20个列标号,那么多出来的值就会储存在紧随数组之后的内存位置,这样就会破坏原先储存在这个位置的数据
&&操作符两边的表达式都必须为真
scanf函数每次调用时都从标准输入读取一个十进制整数。如果转换失败,不管是因为文件已经读完还是因为下一次输入的字符无法转换为整数,函数都会返回0,整个循环就会终止.如果输入的字符可以合法的转换为整数,那么这个字就会转换为二进制储存在数组上
注意:=是比较操作符,==是赋值操作符

    if(num%2!=0)
    {
        puts("Last column number is not paired.");
        exit(EXIT_FAILURE);
    }

puts函数是gets函数的输出版本,它把指定的字符串写到标准输出并在末尾添上一个换行符。程序接着调用exit函数,终止程序的运行,EXIT_FAILURE这个值被返回给操作系统,提示出现了错误

while((ch=getchar())!=EOF&&ch!='\n');

getchar函数从标准输入读取一个字符并返回他的值。如果输入中不再存在任何字符,函数就会返回常量EOF,用于提示文件的结尾
从gerchar函数返回的值被赋值给变量ch,然后把它与EOF相比较。如果ch等于EOF,整个表达式的值就为假,循环就会终止。若非如此,ch与换行符相比较,如果两者相等,循环也会终止。所以输入尚未到达文件尾并且输入的字符并非换行符的时候,表达式的值才是真的。这样这个循环就能提出当前输入行最后的剩余字符
这段程序还由一种写法

ch=getchar();
while(ch!=EOF&&CH!=‘\n')
     ch=getchar();

为什么ch被声明为整型,但是我们事实上需要它来读取字符?
一位内EOF是一个整型值,它的位数比字符类型要多,把ch声明为整型可以防止从输入读取的字符意外地被解释为EOF,但同时,这也意味这接收字符的ch必须足够大,足以容纳EOF。

 return num;

return就是函数向调用它的表达式返回一个值。这个例子中,变量num的值被返回给调用该函数的程序。后者把这个返回值赋值给主程序的n_columns变量

rearrange函数

void rearrange(char *output,char const*input,int n_columns,int const columns[MAX_CLOS])
{
    int col;                                            /*columns数组的下标*/
    int output_col;                                     /*输出行的列计数器*/
    int len;  

这里前两个参数被声明为指针,但在实际函数调用的时候,传给它们的参数却是数组。因为当数组名为实参的时候,传给函数实际上是一个指向数组起始位置的指针。
但是由于它的传址调用语义,如果函数修改了形参数组的元素,它实际上将修改实参数组的对应元素。所以声明为const由两部分的原因:
第一是声明该函数的作者的意图是这个参数不能被修改
其次是它导致编译器去验证是否违背该意图

len=strlen(input);
    output_col=0;
    for(col=0;col<n_columns;col+=2)
if(columns[col]>=len||output_col==MAX_INPUT-1)  /*如果输入行的标号小于需要处理的列标号或者
                                                          输出数组已满结束任务*/
            break;
        nchars=columns[col+1]-columns[col]+1;
        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';

接下来的一个测试检测这范围呢ide所有字符是否都能放入输出行中,如果不行,就把nchars调整为数组能够容纳的大小
最后,strncpy函数把选中的字符从输入行复制到输出行中可用的下一个位置。strncpy函数的前两个参数分别是目标字符串和源字符串的地址。在这个调用中,目标字符串的位置是输出数组的起始地址向后偏移output_col列的地址,源字符串的位置则是输入数组起始地址向后偏移columnst[col]个位置的地址。第三个参数指定需要的字符数。输出列计数器随后向后移动nchars个位置
循环结束之后,输出字符串将以一个NUL字符作为终止符。然后,程序执行流便到达了函数的末尾,于是执行一个隐式的return语句,因为这个函数被声明为void

2.1 补充说明

putchar函数,与getchar函数相对应,它接受一个整型参数,并在标准输出中打印该字符
strcpy函数与strncpy函数相似,但是它没有限制需要赋值的字符数量。它接收两个参数:第二个字符串参数将被复制到第一个字符串参数,第一个字符串原有的字符将被覆盖。
struct函数也接受两个参数,但是它把第二个字符串参数添加到第一个字符串参数的末尾。
在字符串中进行搜索的函数是strchr,接收两个参数,第一个参数是字符串,第二个字符是一个字符。这个函数在字符串参数内搜索字符参数第一次出现的地方,如果搜索成功就返回指向这个位置的指针,如果搜索失败就返回NULL指针
strstr函数的功能也类似,但是它的第二个参数也是一个字符串,它搜索第二个字符串在第一个字符串中第一次出现的位置。

问题与练习

  1. 依次打印一个十进制整数,字符串和浮点数,你应该在printf函数中分别使用什么格式代码?试编一例,让这些打印以空格分隔,并在输出行的末尾添加一个换行符。
#include <stdio.h>
#include <stdlib.h>

int main(void) {
	int a=1000;
	char b[]="abs";
	float c=1.123123;
	printf("%d %s %f\n",a,b,c); /* prints !!!Hello World!!! */
	return EXIT_SUCCESS;
}

出现的问题:
字符串需要是数组,换行符是\n,
在eclipse上面运行程序需要先ctrl+b构建项目才能成功运行

  1. 编写一条scanf语句,它需要读取两个整数,分别保存于quantity和price变量,然后再读取一个字符串,保存再一个名为department的字符数组中
#include <stdio.h>
#include <stdlib.h>

int main(void) {
	int quantity, price;
	    char department[100];
	    scanf("%d %d %s", &quantity, &price, &department);
	    printf("%d",quantity);
	return 0;
}

遇到的问题:在这里插入图片描述
scanf是需要输入取地址符号的
大部分的问题来自于eclipse这个软件,首先是自己的程序写的不细心,然后是console这个窗口eclipse不是默认弹出

  1. 本章描述的rearrange程序包含下面的语句
    strncpy( output + output_col,
    input + columns[col], nchars );
    strcpy函数只接受两个参数,所以它实际上所复制的字符数由第2个参数指定。在本程序中,如果用strcpy函数取代strncpy函数会出现什么结果?

strcpy是从src地址开始,一直到NULL,也就是“\0”结束的字符串赋值到以dest地址开始的空间。strcpy函数并不对src字符串与dest字符串空间进行判断buffer大小,如果dest的空间过小,将会引起buffer overflow(缓冲区溢出)。

函数原型:char * strncpy(char *dest, char *src, size_tn);
strncpy函数是strcpy函数的安全版,第三个参数可指定拷贝范围的大小,并不一定到NULL结束,使用strncpy可以有效避免buffer overflow的出现

  1. 编写一个程序,从标准输入读取几行输入。每行输入都要打印到标准输出上,前面要加上行号。再编写这个程序时要试图让程序能够处理的输入行的长度没有限制
    问题1:不知道如何从标准输入读取输入
    2:不知道如何能够让长度没有限制
int main(void) {
 int line=0;
 int ch;
 int sign=0;
 while((ch=getchar())!=EOF)
 {
	 if(sign==1)
	 {
		 sign=0;
		 line+=1;
		 printf("%d""  ",line);
	 }
	 putchar(ch);
	 if(ch=='\n')
	 {
		 sign=1;
	 }
 }
 return 0;
}
  1. 编写一个程序,一行行地读取输入行,直达文件尾。算出每行输入行的长度,然后把最长的那行打印出来。
    问题:1. 不知如何一行行的读取
    2.不知道如何算出长度

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX 1000
 
int main()
{
    char s[MAX];
    char str[MAX];
    int n1,n2 = -1;
    while( gets(str)!=NULL )
    {
        n1 = strlen(str);
		printf("%d\n",n1);
        if(n1 > n2)
        {
            n2 = n1;
            strcpy( s,str );
        }
    }
	puts(s);
	printf( "%d\n",n2 );
    return 0;


猜你喜欢

转载自blog.csdn.net/weixin_42082138/article/details/86675379