《C程序设计语言第二版》第五章-指针与数组(5.6-5.12)

5.6 指针数组及指向指针的指针

3.5 while循环和for循环 —— 插入排序insertionSort函数

3.5 while循环和for循环 —— 希尔排序shellsort函数

4.10 递归 —— 快速排序qsort函数代码①

对可变长度文本行数据排序后按次序打印。
程序分为三个部分:
(1)读取所有输入行
(2)对文本进行排序
(3)按次序打印文本行

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

#define MAXLINES 5000   /*进行排序的最大文本行数。超过限定的最大行数时,返回-1*/

char* lineptr[MAXLINES];   /* 指向文本行的指针数组。
每行的行换行符被替换为结束符后,变成字符串常量,被保存在指针数组lineptr的每个元素中。
每个字符串都是一个不可分割的整体,且在程序中,每个都生成“指向字符串的常量指针”。

该声明写在函数外面并且是最前面,main函数与readlines函数中都使用了该变量,为外部变量(又称全局变量),在各函数源文件中共享。 */

int readlines(char* lineptr[], int maxlines);  /*输入函数,收集和保存每个文本行中的字符,统计输入的行数*/
void qsort(char* a[], int low, int high);
void writelines(char* lineptr[], int nlines); /*输出函数,按照指针数组中的次序依次打印这些文本行即可*/

int main()
{
	int nlines;

	if ((nlines = readlines(lineptr, MAXLINES)) >= 0)
	{
		qsort(lineptr, 0, nlines - 1);
		writelines(lineptr, nlines);
		return 0;
	}
	else   //函数readlines的返回值是-1,小于0
	{
		printf("error: input too big to sort\n");
		return 1;
	}
}

 //main函数版本2,在排序前重新写入
int main()
{
	int nlines;
	int i,j;


	if ((nlines = readlines(lineptr, MAXLINES)) >= 0)
	{

		for (i = 0; i < nlines; i++)
		{ 
			for (j = 0; j = i; j++)
			{
				lineptr1[j] = lineptr[i];
			}
		}

		qsort(lineptr1, 0, nlines - 1);  
		/* 读取每行后直接进行排序时,只会将每个元素的首个字符进行排序,不能将后面的字符一起排序。
		所以上面采用了重新将每个元素写入的办法,保证每个元素是其原本的所有行字符作为一个整体,然后再进行排序。
		代码没报错,暂时运行不出结果。*/

		writelines(lineptr1, nlines);
		return 0;
	}
	else   //函数readlines的返回值是-1,小于0
	{
		printf("error: input too big to sort\n");
		return 1;
	}
}


//(1)读取所有输入行
#define MAXLEN 1000  /*每行的最大字符数*/
int getline(char, int);   //使用int getline(char*, int);下面不变,也不会报错,但是没必要,用了反而会引起概念混乱

int getline(char x[], int limit)  /* 收集和保存每个文本行中的字符,并返回行长度*/
{
	int c, i;
	c = 0;
	i = 0;

	for (i = 0; i < limit - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
	{
		x[i] = c;
	}

	if (c == '\n')
	{
		x[i] = c;    //x[i++] = c;
		i++;
	}

	x[i] = '\0';

	return i;
}

char* alloc(int);

#define ALLOCSIZE 10000   /*可用空间大小*/
static char allocbuf[ALLOCSIZE];   /*alloc使用的存储区*/
static char* allocp = allocbuf;  /*指针allocp指向下一个空闲位置,此处为初始化,防止指针成为野指针。*/

char* alloc(int n)
{
	if (allocbuf + ALLOCSIZE - allocp >= n)   /* 有足够的空闲空间*/
	{
		allocp += n;  /*申请n个字符的内存空间*/
		return allocp - n;   /* 返回指向n个字符的(第一个字符的)指针*/
	}
	else  /*空闲空间不够*/
		return 0;

}

int readlines(char* lineptr[], int maxlines)
{
	int len, nlines;
	char* p;
	char line[MAXLEN];  /*  p为指向字符数组的指针数组,line为普通字符数组*/

	nlines = 0;

 /* 收集和保存每个文本行中的字符,统计输入的行数*/
	while ((len = getline(line, MAXLEN)) > 0)  
	{
		if (nlines >= maxlines || (p = alloc(len)) == NULL)  /* 指向每行的第一个字符的指针不为空无意义的0地址处的指针 */
			return -1;
		else
		{
			line[len - 1] = '\0'; 
/* 删除每行中的换行符,将其替换为字符串结束符。
每行的行换行符被替换为结束符后,变成字符串常量。*/

			strcpy_s(p, strlen(line) + 1, line);    
/*line中每个字符串都是一个不可分割的整体,且将该字符串常量作为实参时,在程序中,每个都生成“指向字符串的常量指针”。
因此将该指针作为实参,传递给strcpy_s函数的第二个指针类型的形参。
如此将line中的指向字符串的常量指针,复制到字符指针数组p中。
函数strcpy_s的第二个参数为标识长度,为每行的字符串长度加1,包含了结束符。strlen求出的字符串长度不包含结束符。*/

			lineptr[nlines++] = p;  
/* 此时p的类型(“指向字符串的常量指针”)与指针数组lineptr的每个元素的类型一致,都是指针类型。
因此可将p写进lineptr的元素中,并将该指针数组lineptr的下标nlines加1.
后面的writelines打印指针数组lineptr中保存到每个“指向字符串的常量指针”元素时,直接使用格式符"%s"打印元素,没有用取值运算*,就可将该字符串常量指针指向的常量字符串打印出来。 */

		}
	}
	return nlines;     /* 返回行数 */
}

//(2)对文本进行排序
/*定义qsort函数时,因为该程序中要进行快速排序的是将每个字符串常量生成的“指向字符串的常量指针”作为元素的指针数组lineptr,因此qsort函数中的形式参数必须是指针类型,函数定义中的命令也都必须使用*运算符取指针指向位置的值进行赋值交换排序。*/
void qsort(char* a[], int low, int high) //快速排序法
{
	int i = low;	//第一位
	int j = high;	//最后一位
	char key = *(a[i]); //将第一个数作为基准值-- 先找到一个基准值

	//进行排序---> 最终结果就是 左面的 都比基准值小 ,右面的都比 基准值大,所以这是所有循环的结束条件
	while (i < j)
	{
		while (i < j && *(a[j]) >= key)
		{
			j--;//继续走
		}
		*(a[i]) = *(a[j]);


		while (i < j && *(a[i]) <= key)  //i仍为初始值low,但a[i]的值已经发生改变了
		{
			i++;
		}
		*(a[j]) = *(a[i]);
	}

	//i和j在中间相遇了就跳出循环,将基准值放入数据a[i]中
	*(a[i]) = key;


	//对基准值左边 的所有数据 再次进行快速查找(递归)
	if (i - 1 > low)
	{
		qsort(a, low, i - 1);
	}

	//对基准值右边的所有数据再次进行快速查找(递归)
	if (i + 1 < high)
	{
		qsort(a, i + 1, high);
	}

}

//(3)按次序打印文本行
void writelines(char* lineptr[], int nlines)   /*输出函数,按照指针数组中的次序依次打印这些文本行,并在每行末尾加上换行符即可*/
{
	int i;

	for (i = 0; i < nlines; i++)
		printf("%s\n", lineptr[i]);  
/* 指针数组lineptr中保存的每个元素是“指向字符串的常量指针”。
格式符"%s"表示的是输出的整个lineptr[i]元素的类型是字符串类型,整形i不是规定符要规定的对象。
不需要用取值运算*(lineptr[i]),就可将该字符串常量指针lineptr[i]指向的常量字符串打印出来。*/
}

在这里插入图片描述
在这里插入图片描述

当把writelines函数中的输出命令写成 printf(“%s\n”, lineptr[i]); 时的输出结果:

void writelines(char* lineptr[], int nlines)   /*输出函数,按照指针数组中的次序依次打印这些文本行,并在每行末尾加上换行符即可*/
{
	int i;

	for (i = 0; i < nlines; i++)
		printf("%d\n", lineptr[i]);  
}

在这里插入图片描述

练习5-7

重写readlines,将输入的文本行存储到由main函数提供的一个数组中,而不是存储到调用alloc分配的存储空间中。该函数的运行速度比改写前快多少?

K&R2 solutions:Chapter 5:Exercise 7 [primary] —— Solution by Alex Hoang (Category 0)

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

#define MAXLINES 5000   /*进行排序的最大文本行数。超过限定的最大行数时,返回-1*/
#define MAXSTORE 10000 /* max space allocated for all lines.  Same as ALLOCSIZE on p.91. */

char* lineptr[MAXLINES];   

int readlines(char* lineptr[], int maxlines, char* ls);  /*输入函数,收集和保存每个文本行中的字符,统计输入的行数*/
void qsort(char* a[], int low, int high);
void writelines(char* lineptr[], int nlines); /*输出函数,按照指针数组中的次序依次打印这些文本行即可*/

int main()
{
	int nlines;
	char linestore[MAXSTORE]; /* 普通字符数组linestore用于存储所有的行array for storing all lines */
	/* my readlines will pass an extra parameter linestore for storing all the input lines */

	if ((nlines = readlines(lineptr, MAXLINES,linestore)) >= 0)  //函数readlines的第三个形参是指针char* ls,普通数组名linestore也是可以作为实参的。
	{
		qsort(lineptr, 0, nlines - 1);
		writelines(lineptr, nlines);
		return 0;
	}
	else   //函数readlines的返回值是-1,小于0
	{
		printf("error: input too big to sort\n");
		return 1;
	}
}

//(1)读取所有输入行
#define MAXLEN 1000  /*每行的最大字符数*/
int getline(char, int);   //使用int getline(char*, int);下面不变,也不会报错,但是没必要,用了反而会引起概念混乱

int getline(char x[], int limit)  /* 收集和保存每个文本行中的字符,统计输入的行数*/
{
	int c, i;
	c = 0;
	i = 0;

	for (i = 0; i < limit - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
	{
		x[i] = c;
	}

	if (c == '\n')
	{
		x[i] = c;    //x[i++] = c;
		i++;
	}

	x[i] = '\0';

	return i;
}

int readlines(char* lineptr[], int maxlines, char* ls)
{
	int len, nlines;
	char* p, line[MAXLEN];

	nlines = 0;
	p = ls + strlen(ls); /* initiate to first empty position */
	/* strlen函数接收的是指针。求字符串常量及字符数组的长度时,strlen函数接受的也是指针,因此strlen(指针)是可以的。 
但其实在本函数中会自动判定出按照字符数组类型的方式处理ls,而非指针,因此这里其实是strlen(数组).
	
虽然形式上形式参数ls被定义为指针,但实际上在下面的函数命令行中都仅把ls当作字符数组ls[]来看,而不是指针ls。
因为:虽然类型不同,但char *ls(类型为指向字符的指针)与char ls[](类型为字符数组)这两种声明是等价的,C语言中即使使用的确实是字符数组ls[],也更习惯于采用char *ls的声明方式。 
此时如果将数组名(main函数中的linestore)传递给函数readlines,函数readlines会在系统中自动实现根据情况判定是按照数组处理还是指针处理,随后根据相应的方式操作该函数。
(此处判定过后将数组名linestore按照数组的方式处理。)*/

	while ((len = getline(line, MAXLEN)) > 0) //可同时输入多行,处理多行。同getchar()函数一样。
/* The line below will check to make sure adding the nextline will not exceed MAXSTORE */
	{
		if (nlines >= maxlines || (strlen(ls) + len) > MAXSTORE) //总的行数及每行总的字符数超过限定值,均返回-1
			return -1;
		else
		{
			line[len - 1] = '\0';
			
			strcpy_s(p, strlen(line) + 1, line); 
/* 利用line字符串产生的指向字符串的常量指针,及指针p,将line指向的字符串复制到指针p指向的位置。
		*p = *(line)
而指针p指向字符数组ls中元素,即将line中的字符串常量通过指针复制到字符数组ls中。*/

			lineptr[nlines++] = p;
/*与上面复制操作的区别是,没有进行取值运算*.因此此处是:令指针lineptr[nlines]指向指针p指向的位置。*/
			
			p += len; /* point p to next empty position */
/* 指针的算数运算,表示令指针p指向p后面len个长度的位置(即数组ls中下一个空位的位置处)。*/

		}
	}
	return nlines;
}


//(2)对文本进行排序
void qsort(char* a[], int low, int high) //快速排序法
{
	int i = low;	//第一位
	int j = high;	//最后一位
	char key = *(a[i]); //将第一个数作为基准值-- 先找到一个基准值

	//进行排序---> 最终结果就是 左面的 都比基准值小 ,右面的都比 基准值大,所以这是所有循环的结束条件
	while (i < j)
	{
		while (i < j && *(a[j]) >= key)
		{
			j--;//继续走
		}
		*(a[i]) = *(a[j]);


		while (i < j && *(a[i]) <= key)  //i仍为初始值low,但a[i]的值已经发生改变了
		{
			i++;
		}
		*(a[j]) = *(a[i]);
	}

	//i和j在中间相遇了就跳出循环,将基准值放入数据a[i]中
	*(a[i]) = key;


	//对基准值左边 的所有数据 再次进行快速查找(递归)
	if (i - 1 > low)
	{
		qsort(a, low, i - 1);
	}

	//对基准值右边的所有数据再次进行快速查找(递归)
	if (i + 1 < high)
	{
		qsort(a, i + 1, high);
	}

}


//(3)按次序打印文本行
void writelines(char* lineptr[], int nlines)   /*输出函数,按照指针数组中的次序依次打印这些文本行,并在每行末尾加上换行符即可*/
{
	int i;

	for (i = 0; i < nlines; i++)
		printf("%s\n", lineptr[i]);
}

在这里插入图片描述
比上面的1.170秒快了半秒(0.409s)
在这里插入图片描述

5.7 多维数组

#include <stdio.h>

int day_of_year(int year, int month, int day);
void month_day(int year, int yearday, int* pmonth, int* pday);

int main()
{
	int day;
	int month = 0;
	int day2 = 0;

	day = day_of_year(2023, 11, 23);
	printf("%d\n", day);

	month_day(2023,300,&month,&day2);
	printf("%d月%d日", month, day2);

	return 0;
}

static char daytab[2][13] = {
	{0,31,28,31,30,31,30,31,31,30,31,30,31},
	{0,31,29,31,30,31,30,31,31,30,31,30,31},
};

/* day_of_year函数:将某月某日的日期表示形式转化为某年中第几天的表示形式 */
int day_of_year(int year, int month, int day)
{
	int i, leap;

	leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;

	for (i = 1; i <= month; i++)   //这里一定要有等于号,否则会不算最后一个月的天数
		day += daytab[leap][i];  //将day与month前面几个月每月的天数依次累加起来

	return day;
}

/* month_day函数:将某年中第几天的日期表示形式转换为某月某日的表示形式 */
void month_day(int year, int yearday, int* pmonth, int* pday)
{
	int i, leap;

	leap = (year % 4 == 0 && year % 100 != 0 || year % 400 == 0);

	for (i = 1; yearday > daytab[leap][i]; i++)  //此处不能加等于号(主要区别在与当该天数正好是某天个月的最后一天时,加上等于会额外多进行一次下面的循环,将yearday变为0,月份i也会额外多加一次,如将2023年第212天由7月31日变为8月0日。)
		yearday -= daytab[leap][i];

	*pmonth = i;
	*pday = yearday;
}

在这里插入图片描述
运算符的优先级顺序为:(计算每个符号最近的两个表达式的结果值)
逻辑非! > 算术 > 判断等于== > 判断不等于!= > 逻辑与&& > 逻辑或|| > 赋值=

该程序中的leap判断year是否是闰年,是闰年则返回1,不是闰年则返回0.
year%4 == 0 设为A;
year%100 != 0 设为B;
year%400 == 0 设为C.
先计算B、再按顺序计算A、C;

leap 
= ( year%4 == 0 && year%100 != 0 || year%400 == 0)   
= ( A && B || C )       //B、A、C三个均在&&与||之前计算
=  ( (A && B) || C )   //&& > ||,先计算A && B,假设结果为D
= ( D || C )              //再计算||,即 D || C

在这里插入图片描述

判断year是否"是闰年"的方法:
①先判断year能否被100整除,能被100整除时,再判断能否被400整除,若能被400整除,则是闰年。
例如2000是闰年,但1900就不是闰年,因1900能被100整除但不能被400整除;
②先判断year能否被100整除,不能被100整除时,再判断能否被4整除,若能被4整除,则是闰年。

判断year是否"不是闰年"的方法:
①先判断year能否被100整除,能被100整除时,再判断能否被400整除,若不能被400整除,则是平年(不是闰年);
②先判断year能否被100整除,不能被100整除时,再判断能否被4整除,若不能被4整除,则是平年(不是闰年)。

根据判断year是否"是闰年"的方法,该程序中的leap应该写成:
leap1 = ( year%100 != 0 && year%4 == 0 || year%100 == 0 && year%400 == 0 )
而在上面的式子leap2中,省略了 year%100 == 0.
leap2 = ( year%4 == 0 && year%100 != 0 || year%400 == 0)
这两种写法均可。
leap1、leap2判断year是否是闰年,是闰年则返回1,不是闰年则返回0.

根据判断year是否"不是闰年"的方法:
leap3应该写成:
leap3 = (year%100 == 0 && year%400 != 0) || (year%100 != 0 && year%4 != 0)
另一种写法:省略了year%100 != 0
leap4 = (year%4 != 0) || (year%100 == 0 && year%400 != 0)
leap3、leap4不是闰年则返回1,是闰年则返回0.(后面的程序也要改变。)

证明: leap1与leap2均可判断是否“是闰年”;
leap3与leap4均可判断是否“不是闰年”。
leap1 = ( year%100 != 0 && year%4 == 0 || year%100 == 0 && year%400 == 0 )
leap2 = ( year%4 == 0 && year%100 != 0 || year%400 == 0 )
leap3 = (year%100 == 0 && year%400 != 0) || (year%100 != 0 && year%4 != 0)
leap4 = (year%4 != 0) || (year%100 == 0 && year%400 != 0)

两两比较四种写法:
1、(1)假如year为普通年份(不能被100整除)的闰年:
则 year%100 != 0 为真,year%4 == 0 也为真,
year%100 != 0 && year%4 == 0 的结果也为真。
得出year%100 == 0 为假;year%4 != 0为假。
year%400 == 0在此处无法判断。 (不能被100整除且能被4整除的数,无法判断其能否被400整除。)
此处假设year%400 == 0 为真, 则year%400 != 0为假。
(实际上year%400 == 0 的结果不会影响leap的计算结果。)

leap1 
=  ( year%100 != 0 && year%4 == 0 || year%100 == 0 && year%400 == 0 )  
= (1  && 1 || 0 && 1)
= 1 //leap1结果为真,year是闰年,与假设相符。 

leap2 
= ( year%4 == 0 && year%100 != 0 || year%400 == 0)   
= ( 1 && 1 || 1)
= 1 //leap2结果为真,year是闰年,与假设相符。 

leap3 
= (year%100 == 0 &&  year%400 != 0) ||  (year%100 != 0 &&  year%4 != 0)
= (0 && 0) || (1 && 0)
= (0 || 0)
= 0  //leap3结果为假,year是闰年,与假设相符。 

leap4
 =  (year%4 != 0) || (year%100 == 0 && year%400 != 0) 
= 0 ||0 && 0= 0  //leap4结果为假,year是闰年,与假设相符。 

(2)假如year为能被100整除年份的闰年:
则year%100 == 0 为真,year%400 == 0也为真,
year%100 0 && year%400 0 的结果也为真。
得出year%100 != 0 为假;year%4 == 0 为真(能被100整除又能被400整除的数,一定能被4整除。)

 leap1 
=  ( year%100 != 0 && year%4 == 0 || year%100 == 0 && year%400 == 0 )  
= (0 && 1 || 1 && 1)
= (0 && 1||1 && 1)  // && > ||,从左到右依次均先计算&&
= (0 || 1)
= 1 //leap1结果为真,year是闰年,与假设相符。 ```

leap2 
= ( year%4 == 0 && year%100 != 0 || year%400 == 0)   
= ( 1 && 0 || 1)
= ( (1 && 0) || 1)  // && > ||,从左到右依次均先计算&&
= ( 0 || 1)
= 1 //leap2结果为真,year是闰年,与假设相符。 ```

leap3 
= (year%100 == 0 &&  year%400 != 0) ||  (year%100 != 0 &&  year%4 != 0)
= ( 1 && 0) || ( 0 && 0)
= (0 || 0)
= 0  //leap3结果为假,year是闰年,与假设相符。 

leap4
 =  (year%4 != 0) || (year%100 == 0 && year%400 != 0) 
= 0 ||1 && 0= 0   //leap4结果为假,year是闰年,与假设相符。 

2、假如year不是闰年:
(1)先判断year能否被100整除,能被100整除时,再判断能否被400整除,若不能被400整除,则是平年(不是闰年)。
year%100 == 0 为真,year%400 != 0 为真。
year%4 == 0在此处无法判断。 (能被100整除且不能被400整除的数,无法判断其能否被4整除。)
此处假设year%4 == 0 为真, 则year%4 != 0为假。
(实际上year%4 == 0 的结果不会影响leap的计算结果。)

leap1 
=  ( year%100 != 0 && year%4 == 0 || year%100 == 0 && year%400 == 0 )  
= ( (0 && 1) || (1 && 0) )
=0 || 0= 0  //leap1结果为假,year不是闰年,与假设相符。 

leap2 
= ( year%4 == 0 && year%100 != 0 || year%400 == 0)   
= ( (1 && 0) || 0)
=0 || 0= 0 //leap2结果为真,year不是闰年,与假设相符。 

leap3 
= (year%100 == 0 &&  year%400 != 0) ||  (year%100 != 0 &&  year%4 != 0)
= ( 1 && 1) || ( 0 && 1)
= (1 || 0)
= 1  //leap3结果为真,year不是闰年,与假设相符。 

leap4
 =  (year%4 != 0) || (year%100 == 0 && year%400 != 0) 
= (1 ||1 && 1)
= (1 || 1)
= 1   //leap4结果为真,year不是闰年,与假设相符。 2)先判断year能否被100整除,不能被100整除时,再判断能否被4整除,若不能被4整除,则是平年(不是闰年)。
year%100 != 0 为真; year%4 != 0 为真。
得出year%400 == 0为假(不能被100整除又不能被4整除的数,一定不能被400整除。)

leap1 
=  ( year%100 != 0 && year%4 == 0 || year%100 == 0 && year%400 == 0 )  
= (1 && 0)  || (0 || 0)
= ( 1 || 0 )
= 0 //leap1结果为假,year不是闰年,与假设相符。 

leap2 
= ( year%4 == 0 && year%100 != 0 || year%400 == 0)   
= ( (0 && 1) || 0)
= ( 0 || 0)
= 0 //leap2结果为假,year不是闰年,与假设相符。 

leap3 
= (year%100 == 0 &&  year%400 != 0) ||  (year%100 != 0 &&  year%4 != 0)
= ( 0 && 1) || ( 1 && 1)
= (0 || 1)
= 1  //leap3结果为真,year不是闰年,与假设相符。 

leap4
 =  (year%4 != 0) || (year%100 == 0 && year%400 != 0) 
=  ( 1 ||0 && 1)
= ( 1 || 0)
= 1   //leap4结果为真,year不是闰年,与假设相符。 

结论:
判断year是否"是闰年",leap1与leap2的写法均正确。
判断year是否"不是闰年",leap3与leap4的写法均正确。

因此最终判断是否是闰年的方式为:

在这里插入图片描述
(不能被100整除且能被4整除的数,无法判断其能否被400整除。)
(能被100整除又能被400整除的数,一定能被4整除。)
(能被100整除且不能被400整除的数,无法判断其能否被4整除。)
(不能被100整除又不能被4整除的数,一定不能被400整除。)

练习5-8

函数day_of_year 和 month_day中没有进行错误检查,请解决该问题。

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

static char daytab[2][13] =
{
	{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
	{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};

int day_of_year(int year, int month, int day)
{
	if (year < 1752)
		return -1;  //错误判断1

	int i, leap;
	leap = (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0);

	if (!((month >= 1 && month <= 12) && (day >= 1 && day <= daytab[leap][month])))
		return -1;    //错误判断2

	for (i = 1; i < month; i++)
	{
		day += daytab[leap][i];
	}
	return day;
}

void month_day(int year, int yearday, int* pmonth, int* pday)
{
	if (year < 1752)
	{
		*pmonth = *pday = -1;
		return;
	}    //错误判断1

	int i, leap;
	leap = (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0);

	if (!(yearday >= 1 && yearday <= (leap ? 366 : 365)))
	{
		*pmonth = *pday = -1;
		return;
	}   //错误判断2

	for (i = 1; yearday > daytab[leap][i]; i++)
		yearday -= daytab[leap][i];
		
	*pmonth = i;
	*pday = yearday;
}

int main()
{
	int month, day, year, yearday, retval;

	/******************************* Case 1 *****************************/
	printf("Case 1:\n");
	year = -2017;
	month = 11;
	day = 22;
	yearday = 326;

	retval = day_of_year(year, month, day);
	if (retval == -1)
		printf("Incorrect input\n"); 
	else
		printf("%d %d %d <---> %d %d\n", year, month, day, year, retval);

	month_day(year, yearday, &month, &day);
	if (month == -1 && day == -1)
		printf("Incorrect input\n");
	else
		printf("%d %d <---> %d %d %d\n", year, yearday, year, month, day);
	printf("\n");
	/*********************************************************************/

	/******************************* Case 2 *****************************/
	printf("Case 2:\n");
	year = 2017;
	month = -11;
	day = 22;
	yearday = 326;

	retval = day_of_year(year, month, day);
	if (retval == -1)
		printf("Incorrect input\n");
	else
		printf("%d %d %d <---> %d %d\n", year, month, day, year, retval);

	month_day(year, yearday, &month, &day);
	if (month == -1 && day == -1)
		printf("Incorrect input\n");
	else
		printf("%d %d <---> %d %d %d\n", year, yearday, year, month, day);
	printf("\n");
	/*********************************************************************/

	/******************************* Case 3 *****************************/
	printf("Case 3:\n");
	year = 2017;
	month = 11;
	day = -22;
	yearday = 326;

	retval = day_of_year(year, month, day);
	if (retval == -1)
		printf("Incorrect input\n");
	else
		printf("%d %d %d <---> %d %d\n", year, month, day, year, retval);

	month_day(year, yearday, &month, &day);
	if (month == -1 && day == -1)
		printf("Incorrect input\n");
	else
		printf("%d %d <---> %d %d %d\n", year, yearday, year, month, day);
	printf("\n");
	/*********************************************************************/

	/******************************* Case 4 *****************************/
	printf("Case 4:\n");
	year = 2017;
	month = 11;
	day = 22;
	yearday = -326;

	retval = day_of_year(year, month, day);
	if (retval == -1)
		printf("Incorrect input\n");
	else
		printf("%d %d %d <---> %d %d\n", year, month, day, year, retval);

	month_day(year, yearday, &month, &day);
	if (month == -1 && day == -1)
		printf("Incorrect input\n");
	else
		printf("%d %d <---> %d %d %d\n", year, yearday, year, month, day);
	printf("\n");
	/*********************************************************************/

	/******************************* Case 5 *****************************/
	printf("Case 5:\n");
	year = 2017;
	month = 11;
	day = 22;
	yearday = 326;

	retval = day_of_year(year, month, day);
	if (retval == -1)
		printf("Incorrect input\n");
	else
		printf("%d %d %d <---> %d %d\n", year, month, day, year, retval);

	month_day(year, yearday, &month, &day);
	if (month == -1 && day == -1)
		printf("Incorrect input\n");
	else
		printf("%d %d <---> %d %d %d\n", year, yearday, year, month, day);
	printf("\n");
	/*********************************************************************/

	return 0;
}

在这里插入图片描述

5.8 指针数组的初始化

/* month_name函数:返回第n个月份的名字 */
char* month_name(int n)
{
	static char* name[] = {
	"illegal month",
	"January","February","March",
	"April","May","June",
	"July","August","September",
	"October","November","December"
	};

	return (n < 1 || n > 12) ? name[0] : name[n];
}

5.9 指针与多维数组

练习5-9

用指针方式代替数组下标方式改写函数day_of_year 和 month_day

#include <stdio.h>

static char daytab[2][13] = {
	{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
	{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
};

/*函数的定义程序放在main函数前面,则不用再写函数的声明。*/

/* pointer versions */
int day_of_year_pointer(int year, int month, int day)
{
	int i, leap;
	char* p;    //引入指针p

	leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;

	/* Set `p' to point at first month in the correct row. */
	p = &daytab[leap][1];  //令指针p指向daytab数组中的闰年(leap = 1,第二行)/平年(leap = 0,第一行)的第一个月份

	/* Move `p' along the row, to each successive month. */
	for (i = 1; i < month; i++) {
		day += *p;   //每次移动p后,将指针p指向的值累加后赋给day
		++p;        //指针p不断在数组中向前移动
	}
	return day;
}

void month_day_pointer(int year, int yearday, int* pmonth, int* pday)
{
	int i, leap;
	char* p;  //引入指针p

	leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;

	p = &daytab[leap][1]; //令指针p指向daytab数组中的闰年(leap = 1,第二行)/平年(leap = 0,第一行)的第一个月份

	for (i = 1; yearday > *p; i++) {
		yearday -= *p;  //每次移动p后,将指针p指向的值累减后赋给yearday
		++p;    //指针p不断在数组中向前移动
	}
	*pmonth = i;
	*pday = yearday;
}


int main(void)
{
	int year, month, day, yearday;

	year = 2023;
	month = 11;
	day = 24;

	printf("The date is: %d-%02d-%02d\n", year, month, day);

	printf("day_of_year_pointer: %d\n",
		day_of_year_pointer(year, month, day));

	yearday = 329 ;	/* 2000-03-01 */
	printf("Yearday is %d\n", yearday);
	month_day_pointer(year, yearday, &month, &day);   //定义了变量month与day后,即可将他们的寻址结果作为指针形式参数的实参。不用专门定义指针再进行初始化
	printf("month_day_pointer: %d %d\n", month, day);

	return 0;
}

在这里插入图片描述

5.10 命令行参数

在windows平台下,运行命令行参数的步骤:
①将下面的程序,用VisualStudio编译生成目标文件为test的应用程序
参考链接:在VisualStudio上生成代码的exe可执行文件

#include <stdio.h>

/*回显程序命令行参数:版本1 */
int main(int argc, char* argv[])
{
    int i;

    printf("命令行参数个数:%d\n", argc - 1);

    printf("程序名称:%s\n", argv[0]);
    
   for (i = 1; i < argc; i++)
        printf("argv[%d] is : %s\n", i, argv[i]);

    return 0;
}

②在windows+R/cmd命令对话框中,根据规则,运行test应用程序及命令行参数
windows下的C语言命令行参数
复制到相应目录lenovo下

运行结果:
在这里插入图片描述

如果不生成exe可执行文件,则程序的名称显示的是VisualStudio中自动生成的文件名称,且无法真正输入命令行参数。

#include <stdio.h>

/*回显程序命令行参数:版本1 */
int main(int argc, char* argv[])
{
	int i;

	printf("argc is %d\n", argc);

	for (i = 0; i < argc; i++)
		printf("argv[%d] is : %s\n", i, argv[i]);

	return 0;
}

在这里插入图片描述

#include <stdio.h>

/*回显程序命令行参数:版本1 */
int main(int argc, char* argv[])
{
	int i;

	printf("argc is %d\n", argc);

	for (i = 0; i < argc; i++)
		printf("It is %s  %s\n", argv[i], (i < argc - 1) ? "A" : "B");  
		//输出B,说明i >= argc - 1. 已知argc=1

	return 0;
}

在这里插入图片描述

#include <stdio.h>

/*回显程序命令行参数:使用指针的版本2 */
int main(int argc,char *argv[])
{
	int i = 0;
	
	printf("argc is %d\n", argc);
	
	for(i = 0; i < argc; i++)
		printf("argv[%d] is : %s\n",i,*++argv);  
	    //指针argv先自增,再赋给函数printf *argv的值。

	return 0;
}

在这里插入图片描述

通过命令行的第一个参数指定待匹配模式的程序。

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

#define MAXLINE 1000

int getline(char* line[], int limit);

int main(int argc, char* argv[])
{
	char* line[MAXLINE];
	int found = 0;
	int i = 0;

	if (argc != 1)
	{
		printf("Usage : find pattern\n");
	}
	else
	{
		for (i = 0; i < argc; i++)
			printf("argv[%d] is : %s\n", i, *++argv);

		while (getline(line, MAXLINE) > 0)
		{
			if (strstr(*line, argv[0]) != NULL)
			{
				printf("%s", *line);
				found++;
			}
		}
	}

	return found;
}

int getline(char* line[], int limit)
{
	int i;
	char c = 'a';

	for (i = 0; i < limit - 1 && (c = getchar()) != EOF && c != '\n'; i++)
	{
		line[i] = &c;
	}

	if (c == '\n')
	{
		line[i++] = &c;
	}

	*(line[i]) = '\0';

	return i;
}

strstr函数中的第二个参数argv[0]是NULL,所以strstr(*line, argv[0]) 的结果是 NULL
在这里插入图片描述
改写后的 通过命令行的第一个参数指定待匹配模式的程序。上面的版本2

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

#define MAXLINE 1000

int getline(char* line[], int max);

int main(int argc,char *argv[])
{
	char* line[MAXLINE];
	long lineno = 0;

	int c, except = 0, number = 0, found = 0,i=0;

	printf("argc is %d\n", argc);

	for (i = 0; i < argc; i++)
		printf("argv[%d] is : %s\n", i, *++argv);


	while (--argc > 0 && (*++argv)[0] == '-')
	{
		while(c = *++argv[0])
			switch (c)
			{
			case 'x':
				except = 1;
				break;
			case '\n':
				number = 1;
				break;
			default:
				printf("find : illegal option %c\n", c);
				argc = 0;
				found = -1;
				break;
			}
	}

	printf("argc is %d\n", argc);

	if (argc != 1)
		printf("Usage : find -x -n pattern\n");
	else
		while (getline(line, MAXLINE) > 0)
		{
			lineno++;
			if ((strstr(*line, *argv) != NULL) != except)
			{
				if (number)
					printf("%1d:", lineno);

				printf("%s", *line);
				found++;
			}
		}
	return found;
}


int getline(char* line[], int limit)
{
	int i;
	char c = 'a';

	for (i = 0; i < limit - 1 && (c = getchar()) != EOF && c != '\n'; i++)
	{
		line[i] = &c;
	}

	if (c == '\n')
	{
		line[i++] = &c;
	}

	*(line[i]) = '\0';

	return i;
}

在这里插入图片描述

5.11 指向函数的指针

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

#define MAXLINES 5000
#define MAXLEN 1000  /*每行的最大字符数*/
#define MAXSTORE 10000

int getline(char, int);   //使用int getline(char*, int);下面不变,也不会报错,但是没必要,用了反而会引起概念混乱
int readlines(char* lineptr[], int maxlines, char* ls);
void writelines(char* lineptr[], int nlines);
void quick_sort(void* a[], int low, int high, int (*comp)( void*, void*));
double atof(char s[]);

char* lineptr[MAXLINES];
int numcmp(char*, char*);


/*对输入的文本行进行排序 */
int main(int argc, char* argv[])
{
	int nlines;
	int numeric = 0;
	char linestore[MAXSTORE];

	if (argc > 1 && strcmp(argv[1], "-n") == 0)
	{
		numeric = 1;
	}

	if ((nlines = readlines(lineptr, MAXLINES, linestore)) >= 0)
	{
		quick_sort((void **)lineptr, 0, nlines - 1, (numeric ? (int (*)(void*, void*))numcmp : (int (*)(void*, void*))strcmp));
		writelines(lineptr,nlines);
		return 0;
	}
	else
	{
		printf("input too big to sort\n");
		return 1;
	}
}

int getline(char x[], int limit)  /* 收集和保存每个文本行中的字符,统计输入的行数*/
{
	int c, i;
	c = 0;
	i = 0;

	for (i = 0; i < limit - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
	{
		x[i] = c;
	}

	if (c == '\n')
	{
		x[i] = c;    //x[i++] = c;
		i++;
	}

	x[i] = '\0';

	return i;
}

int readlines(char* lineptr[], int maxlines, char* ls)
{
	int len, nlines;
	char* p, line[MAXLEN];

	nlines = 0;
	p = ls + strlen(ls); /* initiate to first empty position */

	while ((len = getline(line, MAXLEN)) > 0) //可同时输入多行,处理多行。同getchar()函数一样。
		/* The line below will check to make sure adding the nextline will not exceed MAXSTORE */
	{
		if (nlines >= maxlines || (strlen(ls) + len) > MAXSTORE) //总的行数及每行总的字符数超过限定值,均返回-1
			return -1;
		else
		{
			line[len - 1] = '\0';

			strcpy_s(p, strlen(line) + 1, line);

			lineptr[nlines++] = p;


			p += len; /* point p to next empty position */

		}
	}
	return nlines;
}



/* qsort函数:以递增顺序对v[left]…v[right]进行排序 */
void quick_sort(void* a[], int low, int high, int (*comp)(void*, void*))
{
	int i = low;
	int j = high;

	void* key = a[i];

	while (i < j)
	{
		while (i < j && (*comp)(a[j], key) >= 0)
		{
			j--;
		}
		a[i] = a[j];

		while (i < j && (*comp)(a[i], key) <= 0)
		{
			i++;
		}
		a[j] = a[i];

		if (i - 1 > low)
		{
			quick_sort(a, low, i - 1, comp);
		}

		if (i + 1 < high)
		{
			quick_sort(a, i+1, high, comp);
		}
	}
}


double atof(char s[])
{
	double val, power;
	int i, sign;

	for (i = 0; isspace(s[i]); i++)
	{
		;
	}

	sign = (s[i] == '-') ? -1 : 1;

	if (s[i] == '+' || s[i] == '-')
	{
		i++;
	}

	for (val = 0.0; isdigit(s[i]); i++)
	{
		val = 10.0 * val + (s[i] - '0');
	}

	if (s[i] == '.')
	{
		i++;
	}

	for (power = 1.0; isdigit(s[i]); i++)
	{
		val = 10.0 * val + (s[i] - '0');
		power *= 10.0;
	}

	return sign * val / power;
}


int numcmp(char* s1, char* s2)
{
	double v1, v2;

	v1 = atof(s1);
	v2 = atof(s2);

	if (v1 < v2)
		return -1;
	else if (v1 > v2)
		return 1;
	else
		return 0;
}


void writelines(char* lineptr[], int nlines)
{
	int i;

	for (i = 0; i < nlines; i++)
		printf("%s\n", lineptr[i]);
}

5.12 复杂声明

int *p、int **p、int (*p)()、int *p()、int *p[n]、int (*p)[n]等简单总结 ——品学兼优张同学

char (*(*x[3])())[5]

①从里到外分析各个部分的定义(名称及属性):
(根据运算符()>[]>*的优先级顺序,先分析运算符() ())

先分析最里面的括号部分
*x[3],x是一个有三个元素的指针数组,指针元素指向的数据类型没有说明;

x[3] 为 A
再分析它外层的括号
(A)()部分
*A(),A是一个返回值为指针的函数,函数的形式参数没有说明;

令 *A() 为 B
最后分析最外层的括号B[5]部分
char B[5],B是一个有五个元素的字符型数组。

②再将各个分部分A、B(不是整体),从外到里分析,将各个部分联系起来:
由A,x[3]是一个返回值为指针的函数,函数的形式参数没有说明;
x[3]本身,三个元素的指针数组
则x的三个指针元素,各指向一个返回值为指针的函数(函数的形式参数没有说明)
【所以最终将*(A)()写成了*(*x[3])()】

由B,A()是一个有五个元素的字符型数组
A()本身,返回值为指针的函数,函数的形式参数没有说明
则函数A返回的指针指向一个有五个元素的字符型数组(函数的形式参数没有说明)。
【所以最终将char B[5]写成了char (*A())[5]】

通过A,将B与x联系起来:
x[3] 是一个返回值为指向一个有五个元素的字符型数组的指针 的函数(函数的形式参数没有说明)。
x[3]本身,三个元素的指针数组
则x 中的每一个元素指针,都指向一个返回值为指向一个有五个元素的字符型数组的指针 的函数(函数的形式参数没有说明)。
即x 是一个 有三个 指向返回值为指向一个有五个元素的字符型数组的指针的函数(函数的形式参数没有说明) 指针元素的数组。

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

#define MAXTOKEN 100
#define BUFSIZE 100

enum {NAME,PARENS,BRACKETS};

void dcl(void);
void dirdcl(void);
int gettoken(void);

int tokentype;
char token[MAXTOKEN];
char name[MAXTOKEN];
char datatype[MAXTOKEN];
char out[1000];

int main()
{
	while (gettoken() != EOF)
	{
		strcpy_s(datatype, token);
		out[0] = '\0';
		dcl();

		if (tokentype != '\n')
			printf("syntax error\n");

		printf("%s : %s %s\n", name, out, datatype); //名称name : 分析out  数据类型datatype
	}
	return 0;
}


static char buf[BUFSIZE]; //共享缓冲区,字符数组
static int bufp; //buf的下标,缓冲区buf的下一个空闲位置

int getch(void)
{
	return  (bufp > 0) ? buf[--bufp] : getchar();  //当缓冲区不空时。从缓存区读入,否则直接从输入中读入
}

void ungetch(int c)
{
	if (bufp >= BUFSIZE)
		printf("ungetch: too many characters\n");
	else
		buf[bufp++] = c;
}



int gettoken(void)
{
	int c, getch(void);
	void ungetch(int);
	char* p = token;

	while ((c = getch()) == ' ' || c == '\t')
		;

	if (c == '(')
	{
		if ((c = getch()) == ')')
		{
			strcpy_s(token, "()");
			return tokentype = PARENS;
		}
		else
		{
			ungetch(c);
			return tokentype = '(';
		}
	}
	else if (c == '[')
	{
		for (*p++ = c; (*p++ = getch()) != ']';)
			;

		*p = '\0';
		return tokentype = BRACKETS;
	}
	else if (isalpha(c))
	{
		for (*p++ = c; isalnum(c = getch());)
			*p++ = c;

		*p = '\0';

		ungetch(c);
		return tokentype = NAME;
	}
	else
		return tokentype = c;
}

/* dcl函数:对一个声明符进行语法分析 */
void dcl(void)
{
	int ns;

	for (ns = 0; gettoken() == '*';)  /* 统计数字符*的个数 */
		ns++;

	dirdcl();

	while (ns-- > 0)  /*有几个* 代表有几个指针 */
		strcat_s(out, " pointer to");

}

/* dirdcl 函数:分析一个直接声明 */
void dirdcl(void)
{
	int type;

	if (tokentype == '(')  /* 形式为(dcl) */
	{
		dcl();

		if (tokentype != ')')
			printf("error : missing )\n");
	}
	else if(tokentype == NAME) /* 变量名 */
	{
		strcpy_s(name, token);
	}
	else
	{
		printf("error : expected name or (dcl) \n");
	}

	while ((type = gettoken()) == PARENS || type == BRACKETS)
		if (type == PARENS)
			strcat_s(out, "function returning");
		else
		{
			strcat_s(out, " array");
			strcat_s(out, token);
			strcat_s(out, "of");
		}
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值