代码改变世界

Control Flow

2015-05-29 10:42  星星之火✨🔥  阅读(365)  评论(0编辑  收藏  举报

1、重写折半查找,使得在循环内部只执行一次测试

传统的非递归式的折半查找的例子中,while循环语句内部共执行了两次测试,其实只要一次就足够(代价是将更多的测试在循环外执行)。重写该函数,使得在循环内部只执行一次测试,比较两种版本函数的运行时间。

/* binsearch: find x in v[0] <= v[1] <= ... <= v[n-1] */
int binsearch(int x, int v[], int n)
{
	int low, high, mid;
	
	low = 0;
	high = n - 1;
	mid = (low+high) / 2;
	while(low <= high && x != v[mid])
	{
		if(x < v[mid])
			high = mid - 1;
		else
			low = mid + 1;
		mid = (low+high) / 2;
	}
	if(x == v[mid])
		return mid;	// found match
	else
		return -1; // no match
}

两种方案的执行时间几乎没有差异,我们并没有得到多大的性能改进,反而失掉了代码的可读性。

2、可视化转义字符与实际字符的双向转换

编写一个函数escape(s, t),将字符串t 复制到字符串s 中,并在复制的过程中将换行符、制表符等等不可显地字符分别转换为\n、\t等相应的可显示的转义字符序列。要求使用switch语句,再编写一个具有相反功能的函数,在复制过程中将转义字符序列转换为实际字符。

/* escape: expand newline and tab into visible sequences while copying the string t to s */
void escape(char s[], char t[])
{
	int i, j;
	
	for(i = j = 0; t[i] != '\0'; i++)
	{
		switch(t[i])
		{
			case '\n': // newline
				s[j++] = '\\';
				s[j++] = 'n';
				break;
			case '\t': // tab
				s[j++] = '\\';
				s[j++] = 't';
				break;
			default: // all other chars
				s[j++] = t[i];
				break;
		}
	}
	s[j] = '\0';
}

unescape函数与escape函数很相似,如下:

/* unescape: convert escape sequences into real characters while copying the string t to s */
void unescape(char s[], char t[])
{
	int i, j;
	
	for(i = j = 0; t[i] != '\0'; i++)
	{
		if(t[i] != '\\')
			s[j++] = t[i];
		else 				// it is a backslach
			switch(t[++i])
			{
				case 'n': // real newline
					s[j++] = '\n';
					break;
				case 't': // real tab
					s[j++] = '\t';
					break;
				default: // all other chars
					s[j++] = '\\';
					s[j++] = t[i];
					break;
			}
	}
	s[j] = '\0';
}

switch语句是允许嵌套的,因此unescape 函数也可以这样写:

/* unescape: convert escape sequences into real characters while copying the string t to s */
void unescape(char s[], char t[])
{
	int i, j;
	
	for(i = j = 0; t[i] != '\0'; i++)
	{
		switch(t[i])
		{
			case '\\': // backslach
				switch(t[++i])
				{
					case 'n': // real newline
						s[j++] = '\n';
						break;
					case 't': // real tab
						s[j++] = '\t';
						break;
					default: // all other chars
						s[j++] = '\\';
						s[j++] = t[i];
						break;
				}
				break;
			default: // not a backslach
				s[j++] = t[i];
				break;
		}
	}
	s[j] = '\0';
}

3、正如TCPL读书笔记&心得中第33条阐述的那样,我们先前编写的itoa 函数不能处理绝对值最大的负数,因为在整形32位二进制数字的表示中,最大的负数取负,在机器底层上得到的结果仍然为最大的负数(也就是相应正数溢出的结果),这里我们的想法是把取模操作的结果转换为正数,从而绕过无法把最大的负数转换为正数的问题。

#include <stdio.h>
#define abs(x) ((x) < 0 ? -(x) : (x))
	
// itoa: convert n to characters in s - modified	
void itoa(int n, char s[])
{
	int i, sign;
	void reverse(char s[]);
	
	sign = n; // record sign
	i = 0;
	do{	// generate digits in reverse order
		s[i++] = abs(n % 10) + '0'; // get next digit
	}while((n /= 10) != 0); // delete it
	if(sign < 0)
		s[i++] = '-';
	s[i] = '\0';
	reverse(s);
}

当然了,还可以采取一种措施,就是先判断该数字是不是最大的负数,如果是就进行特殊处理,不过这样做比较浪费资源,要额外多进行一次比较操作。

4、编写函数,将字符串s1中的速记符号在s2中扩展为等价的完整列表

编写函数expand(s1, s2),将字符串s1 中类似于a-z 一类的速记符号在字符串s2 中扩展为等价的完整列表abc···xyz。该函数可以处理大小写字母和数字,并可以处理a-b-c、a-z0-9 与a-z 等类似的情况。作为前导和尾随的字符原样复制。

/* expand: expand shorthand notation in s1 into string s2 */
void expand(char s1[], char s2[])
{
	char c;
	
	int i, j;
	while((c = s1[i++]) != '\0') // fetch a char from s1[]
	{
		if(s1[i] == '-' && s1[i+1] >= c)
		{
			i++;
			while(c < s1[i]) // expand shorthand
				s2[j++] = c++;
		}
		else
			s2[j++] = c; // copy the character
	}
	s2[j] = '\0';
}

5、编写函数itob(n, s, b),将整数n转换为以b为底的数,并将结果以字符的形式保存到字符串s中。例如,itob(n, s, 16)把整数n格式化成十六进制整数保存在s中。

思路:先按逆序生成b进制数的每一位数字,再用函数reverse(自行编写,非库函数)对字符串s中的字符做一次颠倒而得到最终的结果。

/* itob: convert n to characters in s - base b */
void itob(int n, char s[], int b)
{
	int i, j, sign;
	void reverse(char s[]);
	
	if((sign = n) < 0) // record sign
		n = -n; // make n positive
	i = 0;
	do // generate digits in reverse order
	{
		j = n % b; // get next digit
		s[i++] = (j <= 9) ? j+'0' : j+'a'-10;
	}while((n /= b) > 0); // delete it 
	if(sign < 0)
		s[i++] = '-';
	s[i] = '\0';
	reverse(s);
}