C Primer Plus 编程练习

文章目录

ch06. C控制语句:循环

  1. 编写一个程序,提示用户输入大写字母。使用嵌套循环以下面金字塔型的格式打印字母:

    1    A
    2   ABA
    3  ABCBA
    4 ABCDCBA
    5ABCDEDCBA
    

    提示:用外层循环处理行,每行使用3个内层循环,分别处理空格、以升序打印字母、以降序打印字母。

思考:外层循环处理行容易实现,但是使用3个内层循环控制空格、升序字母和降序空格字母似乎有点多。这里可以结合条件判断,是的内层只需要一个循环搞定空格、升降序字母的打印。这里有几点比较关键

  • 每行有多少个空格
  • 每个一共要打印的字符是多少个,是包括每行的空格加上字符
  • 每行都是对称的,要找到每行对称中心的字符
  • 每行的空格数、字符数和对称中心的字符都是不一样的,这个要内循环中处理好
 1#include "stdio.h"
 2
 3int main(void)
 4{
 5	char middle;
 6	char start = 'A';
 7	printf("Please input a char (A - Z):");
 8	scanf("%c", &middle);
 9	if (middle >= 'A' && middle <= 'Z')
10	{
11		int lines = middle - start + 1; // 总的行数
12		int spaces, chars = 1; // 定义变量保存内循环里的空格数以及自字符。字符数实际上是等差数列 - 1,3,5,7...
13		for (int i = 1; i <= lines; ++i)
14		{
15			spaces = lines - i; // 计算每行的空格数
16			if (i > 1)
17				chars += 2; // 计算每行的字符数
18			char asc_start = start; // 每行升序部分的起始字符
19			char middle_of_line = middle - spaces; // 每行的对称中心的字符
20			char desc_start = middle_of_line; // 每行的降序部分起始字符
21			for (int j = 1; j <= spaces + chars; ++j)
22			{
23				if (j <= spaces)
24					printf(" "); // 打印空格
25				else
26				{
27					if (asc_start <= middle_of_line)
28						printf("%c", asc_start++); // 打印升序部分
29					else
30						printf("%c", --desc_start); // 打印降序部分
31				}
32			}
33
34			printf("\n");
35
36		}
37	}
38	else
39	{
40		printf("Your input is invalid!\n");
41	}
42  
43  return 0;
44
45}
46
47// 结果输出
48Please input a char (A - Z):G
49      A
50     ABA
51    ABCBA
52   ABCDCBA
53  ABCDEDCBA
54 ABCDEFEDCBA
55ABCDEFGFEDCBA
  1. 编写一个程序把一个单词读入一个字符数组中,然后倒序打印这个单词。提示:strlen()函数。

思考:

  • strlen()sizeof的区别是 - strlen输出的字符串的长度,即一个字符串里字符的个数(不包括)末尾的占位符;sizeof输出的是字符数组的长度
  • scanf读入字符串的时候,遇到空格就停止读入了,也就是读入的只是空格前字符
  • 实际上,字符串是通过字符数组存放。如果输入的字符串长度超过的数组长度,那么也不会报错且存放的数据也都是正确的,本章和前面的章节还没给出答案,后面专门的字符串章节应该会介绍
 1#include "stdio.h"
 2#include "string.h"
 3
 4int main(void)
 5{
 6	char word[5];
 7	printf("Please input a word: ");
 8	scanf("%s", word);
 9	printf("sizeof: %zd, string length: %ld\n", sizeof(word), strlen(word));
10
11	printf("Magic - ");
12	for (int i = strlen(word) - 1; i >= 0; --i)
13	{
14		printf("%c", word[i]);
15	}
16	printf("\n");
17  
18  return 0;
19}
20
21// 结果输出
22Please input a word: congratulation
23sizeof: 5, string length: 14
24Magic - noitalutargnoc
  1. 编写一个程序,在数组中读入8个整数,然后按照倒序打印这个8个整数

思考:这个考察的是通过循环从scanf输入字符元素,输入的时候scanf通过空格分隔。实际上scanf遇到与期望的数据类型不一致的时候就停止输入,然后继续把不合格的类型的数据传递给下一次scanf,这个要注意

 1#include "stdio.h"
 2#define SIZE 8
 3
 4int main(void)
 5{
 6	int nums[SIZE];
 7	printf("Enter 8 numbers(int): ");
 8	for (int i = 0; i < SIZE; ++i)
 9	{
10		scanf("%d", &nums[i]);
11	}
12	printf("Numbers in reverse: ");
13	for (int i = SIZE - 1; i >= 0; --i)
14	{
15		printf("%d ", nums[i]);
16	}
17	return 0;
18}
19
20// 结果输出
21Enter 8 numbers(int): 1 2 3 4 5 6 7 8
22Numbers in reverse: 8 7 6 5 4 3 2 1 % 

ch07. C控制语句:分支和跳转

  1. 编写一个程序读取输入,读到#字符停止,然后报告读取的空格数、换行符数和所有其他字符的数量

思考:

  • 可以使用getchar或者scanf来获取输入的字符
  • 可以使用switch...case...或者if...else...来判断字符
  • ctype.h中提供了一系列判断字符类型的函数,要加以利用。但是在switch...case..case语句中,不能使用函数判断字符类型,因为case分支值必须是整型常量后者常量表达式
 1#include "stdio.h"
 2
 3int main(void)
 4{
 5	printf("Input # to end...\n");
 6	int spaces, returns, others;
 7	spaces=returns=others=0;
 8	char ch;
 9	while ((ch = getchar()) != '#')
10	{
11		switch (ch)
12		{
13		case ' ':
14			++spaces;
15			break;
16		case '\n':
17			++returns;
18			break;
19		default:
20			++others;
21		}
22	}
23	printf("char counts:\nspaces : %d\nreturns: %d\nothers: %d\n", spaces, returns, others);
24}
  1. 编写程序读取输入,读到#停止,报告ei出现的次数

思考:

  1. 需要记录上一次和当前的字符,当上次字符是e且当前字符是i的时候,计数器加1
  2. 需要使用逻辑与运算
 1#include "stdio.h"
 2int main(void)
 3{
 4	char current, previous = ' ';
 5	int repeat = 0;
 6	printf("Input # to end...\n");
 7
 8	while ((current = getchar()) != '#')
 9	{
10		if (previous == 'e' && current == 'i')
11			++repeat;
12		previous = current;
13	}
14	printf("ei counts: %d\n", repeat);
15}
  1. 编写一个程序,提示用户输入一周工作的小时数,然后打印工资总额、税金和净收入。做如下假设:

    a. 基本工资 = 10.00美元/小时

    b. 加班(超过40小时)= 1.5倍的时间

    c. 税率: 前300美元为15%

    ​ 续150美元为20%

    ​ 余下的为25%

 1#include "stdio.h"
 2#define BASIC_SALARY 10.00
 3#define RATE_300 0.15
 4#define RATE_450 0.2
 5#define RATE_OVER_450 0.25
 6
 7int main(void)
 8{
 9	double working_hours;
10	printf("Please input your working hours: ");
11	while (scanf("%lf", &working_hours) != 1)
12	{
13		printf("Bad input, please try again!\n");
14		while (getchar() != '\n') // 过滤掉不符合要求的字符,否则不符合规则字符会一直传递给scanf导致死循环
15			continue;
16		printf("Please input your working hours: ");
17	}
18
19	if (working_hours > 40)
20		working_hours = 40 + 1.5 * (working_hours - 40);
21
22	double total_salary = working_hours * BASIC_SALARY;
23	double tax;
24	if (total_salary > 450)
25		tax = 300 * RATE_300 + 150 * RATE_450 + (total_salary - 450) * RATE_OVER_450;
26	else if (total_salary > 300)
27		tax = 300 * RATE_300 + (total_salary - 300) * RATE_OVER_450;
28	else
29		tax = total_salary * RATE_300;
30
31	printf("Total Salary: %4.2lf\nTax: %4.2lf\nNet Salary: %4.2lf\n", total_salary, tax, total_salary - tax);
32}

ch08. 字符输入/输出和输入验证

  1. 编写一个程序,在遇到EOF之前,把输入作为字符流读取。该程序要报告每个单词的字母数,不要把空白和标点统计为单词的字母
 1#include "stdio.h"
 2#include "ctype.h"
 3
 4int main()
 5{
 6	int ch, chCount = 0;
 7	int width;
 8	while ((ch = getchar()) != EOF)
 9	{
10		if (ispunct(ch))
11			continue;
12		if (ch != ' ' && ch != '\t' && ch != '\n')
13		{
14			putchar(ch);
15			chCount++;
16		}
17		else
18		{
19			width = 10 - chCount;
20			printf("%*d\n", width, chCount);
21			chCount = 0;
22		}
23	}
24	return 0;
25}

ch09. 函数

  1. 编写一个函数,从标准输入中读取字符,直到遇到文件结尾。程序要报告每个字符是否是字母。如果是,还要报告该字母在字母表中的数值位置。例如,c和C在字母表中的位置都是3。合并一个函数,以一个字符作为参数,如果该字符是一个字母则返回一个数值位置,否则返回-1。

思考:善用ctype.h提供的函数能很好的处理空格和大小写字符

 1#include "stdio.h"
 2#include "ctype.h"
 3int alphabetPos(char);
 4
 5int main(void)
 6{
 7	char c;
 8	int pos;
 9	while ((c = getchar()) != EOF)
10	{
11		if (isspace(c))
12			continue;
13		pos = alphabetPos(c);
14		if (pos == -1)
15			printf("%c is not in the alphabet.\n", c);
16		else
17			printf("The position of %c in the alphabet is: %d.\n", c, pos);
18	}
19  return 0;
20}
21
22int alphabetPos(char c)
23{
24	if (islower(c))
25	{
26		return c - 'a' + 1;
27	}
28	if (isupper(c))
29	{
30		return c - 'A' + 1;
31	}
32	return -1;
33}
  1. 编写并测试Fibonacci()函数,该函数用循环代替递归计算斐波那锲数。
 1#include "stdio.h"
 2unsigned long fibonacci_recursion(unsigned n);
 3unsigned long fibonacci_loop(unsigned n);
 4
 5int main(void)
 6{
 7	unsigned long result;
 8	for (int i = 1; i <= 10; ++i)
 9	{
10		result = fibonacci_loop(i);
11
12		printf("Fibonacci(%u): %lu. recursion == loop? %d \n", i, result, fibonacci_recursion(i) == result);
13	}
14	return 0;
15}
16
17unsigned long fibonacci_recursion(unsigned n)
18{
19	if (n > 2)
20		return fibonacci_recursion(n - 1) + fibonacci_recursion(n - 2);
21	else
22		return 1;
23}
24
25unsigned long fibonacci_loop(unsigned n)
26{
27	if (n <= 2)
28		return 1;
29	int before_1 = 1, before_2 = 1;
30	int current;
31	for (int i = 3; i <= n; ++i)
32	{
33		current = before_1 + before_2;
34		before_2 = before_1;
35		before_1 = current;
36	}
37	return current;
38}

ch10. 数组和指针

  1. 下面的代码中,*ptr*(ptr+2)的值分别是什么?

    1int *ptr;
    2int torf[2][2] = {12, 14, 16};
    3ptr = torf[0];
    

解答:ptr指向的torf[0],也就是一维数组,指向的数据类型的大小是一个int类型大小为4个字节的整数。初始化ptr的时候,指向二维数组的第一个元素也就是12,因此解引用后ptr = 12;接着ptr+2使得指针往前移动两个数据单位,也就是移动了8个字节,由于数组地址的连续性,然后这时候指针正好指向了16。

  1. 分别使用数组表示法、指针表示法和指针递增函数、指针表示法和指针起始和结束拷贝数组:
    • copy_arr(target1, source,5)
    • copy_ptr(target1, source,5)
    • copy_ptrs(target, source, source+5)
 1#include "stdio.h"
 2void copy_arr(double[], double[], int);
 3void copy_ptr(double*, double*, int);
 4void copy_ptrs(double*, double*, double*);
 5void printarr(double[], int);
 6
 7void copy_arr(double target[], double source[], int n)
 8{
 9	for (int i = 0; i < n; ++i)
10	{
11		target[i] = source[i];
12	}
13}
14
15void copy_ptr(double* target, double* source, int n)
16{
17	for (int i = 0; i < n; ++i)
18	{
19		*target++ = *source++;
20	}
21}
22
23void copy_ptrs(double* target, double* srcstart, double* srcend)
24{
25	while (srcstart < srcend)
26	{
27		*target++ = *srcstart++;
28	}
29}
30
31void printarr(double arr[], int n)
32{
33	for (int i = 0; i < n; ++i)
34	{
35		printf("%.2lf ", arr[i]);
36	}
37	printf("\n");
38}
39
40int main(void)
41{
42	double source[5] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
43	double target1[5];
44	double target2[5];
45	double target3[5];
46
47	copy_arr(target1, source, 5);
48	copy_ptr(target2, source, 5);
49	copy_ptrs(target3, source, source + 5);
50
51	printarr(target1, 5);
52	printarr(target2, 5);
53	printarr(target3, 5);
54
55	return 0;
56}
  1. 编写一个函数,把double类型的数组的数据倒序排序
 1void reverse(double* source, double* target, int n)
 2{
 3	double *last = source + n - 1;
 4	for (int i = 0; i < n; ++i)
 5	{
 6		*target++ = *last--;
 7	}
 8}
 9
10int main(void)
11{
12	double source[5] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
13	double target[5];
14	reverse(source, target, 5);
15	printarr(target, 5);
16
17	return 0;
18}
  1. 编写一个程序,初始化一个double类型的二维数组,使用2中的拷贝程序把该二维数组拷贝到另一个二维数组里
 1void copy_arr2d(double target2d[][5], double source2d[][5], int n)
 2{
 3	for (int i = 0; i < n; ++i)
 4	{
 5		copy_ptr(target2d[i], source2d[i], 5);
 6	}
 7}
 8
 9void printarr2d(double arr[][5], int n)
10{
11	for (int i = 0; i < n; ++i)
12	{
13		printarr(arr[i], 5);
14	}
15}
16
17int main(void)
18{
19	double source2d[3][5] = {{ 1.1, 2.2, 3.3, 4.4, 5.5 }, { 6.6, 7.7, 8.8, 9.9, 10.0 }, { 11.1, 12.2, 13.3, 14.4, 15.5 }};
20	double target2d[3][5];
21	copy_arr2d(target2d, source2d, 3);
22	printarr2d(target2d, 3);
23
24	return 0;
25}
  1. 编写一个程序初始化一个double类型的3X5的二维数组,使用一个处理变长数组的函数将其拷贝至另一个二维数组中。还需要编写一个接受一个以变长数组为形参的函数打印这个二维数组

思考:这个跟第7题类似,只不过将第7题中的函数修改成以变长数组作为形参。这里需要知道如何定义形参为变长数组的函数即可。

 1void copy_mn2darr(int rows, int cols, double target[rows][cols], double source[rows][cols])
 2{
 3	for (int i = 0; i < rows; ++i)
 4	{
 5		copy_ptr(target[i], source[i], cols);
 6	}
 7}
 8
 9void print_mn2darr(int rows, int cols, double arr2d[rows][cols])
10{
11	for (int i = 0; i < rows; ++i)
12	{
13		printarr(arr2d[i], cols);
14	}
15}
16
17int main(void)
18{
19	double source2d[3][5] = {{ 1.1, 2.2, 3.3, 4.4, 5.5 }, { 6.6, 7.7, 8.8, 9.9, 10.0 }, { 11.1, 12.2, 13.3, 14.4, 15.5 }};
20	double target2d[3][5];
21
22	copy_mn2darr(3, 5, target2d, source2d);
23	print_mn2darr(3, 5, target2d);
24  
25	return 0;
26}

ch10. 字符串和字符串函数

  1. string_in() 函数,接受两个指向字符串的指针作为参数。如果第2个字符串包含在第1个字符串中,那么返回第1个字符串中匹配到的地址,否则返回null
 1char* string_in(const char* s1, const char* s2)
 2{
 3	const char* begin = s2;
 4	unsigned long s2len = strlen(s2);
 5	while (*s1 != '\0')
 6	{
 7		if (strlen(s1) > s2len && *s1++ != *s2)
 8			s2 = begin;
 9		else
10			s2++;
11
12		if (*s2 == '\0')
13			return (char*)s1 - s2len;
14	}
15	return NULL;
16}
  1. 反序显示命令行参数的单词
1int main(int argc, char* argv[])
2{
3	for (int i = argc - 1; i >= 1; --i)
4	{
5		printf("%s ",argv[i]);
6	}
7}

ch12. 存储类别、链接和内存管理

  1. 询问输入多少个单词,使用malloc分配空间存放输入的单词(由于单词是char数组,因此malloc返回值指针应该是一个指向char*的指针,就是指针的指针)。然后创建一个可变数组,把单词转放到该可变数组里,然后输出
 1void homework_12_9()
 2{
 3	int max;
 4	int index = 0;
 5	char** words = malloc(max * sizeof(char*));
 6	printf("Please input max:");
 7	if (words && scanf("%d", &max) == 1)
 8	{
 9		char* aword = (char*)malloc(sizeof(char*));
10		while (index < max && scanf("%s", aword))
11		{
12			words[index++] = aword;
13			aword = (char*)malloc(sizeof(char*));
14		}
15
16		char* wordsvla[max];
17		for (int i = 0; i < max; ++i)
18		{
19			wordsvla[i] = words[i];
20			free(words[i]);
21			printf("%s\n", wordsvla[i]);
22		}
23
24		free(words);
25	}
26}

ch13. 文件输入/输出

  1. 通过命令行输入一个查询关键字和文件名,将文件里包含关键的行输出
 1#include "stdlib.h"
 2#include "stdio.h"
 3#include "string.h"
 4#define MAX 255
 5
 6int main(int argc, char* argv[])
 7{
 8	if (argc < 3)
 9	{
10		fprintf(stderr, "Not enough arguments.\n");
11		exit(EXIT_FAILURE);
12	}
13
14	char* key = argv[1];
15	char* file = argv[2];
16
17	FILE* fp = fopen(file, "r");
18	if (fp == NULL)
19	{
20		fprintf(stderr, "Failed to open file %s\n", file);
21		exit(EXIT_FAILURE);
22	}
23
24	char line[MAX];
25
26	while (fgets(line, MAX, fp) != NULL)
27	{
28		if (strstr(line, key))
29			fputs(line, stdout);
30	}
31	fclose(fp);
32}

ch14. 结构和其他数据形式

  1. 编写一个程序,通过一个函数指针数组实现菜单
 1#include "stdio.h"
 2int add(int left, int right);
 3int multiply(int left, int right);
 4int minus(int left, int right);
 5int s_getchar();
 6
 7int add(int left, int right)
 8{
 9	return left + right;
10}
11
12int multiply(int left, int right)
13{
14	return left * right;
15}
16
17int minus(int left, int right)
18{
19	return left - right;
20}
21
22int s_getchar()
23{
24	puts("a - add | b - multiply | c - minus");
25	char c;
26	int index;
27	while ((c = getchar()) && c != '\n')
28	{
29		switch (c)
30		{
31		case 'a':
32			index = 0;
33			break;
34		case 'b':
35			index = 1;
36			break;
37		case 'c':
38			index = 2;
39			break;
40		default:
41			index = 999;
42			puts("Invalid char found.");
43			break;
44		}
45		while (getchar() != '\n')
46			continue;
47		if (index == 999)
48			continue;
49		break;
50	}
51	return index;
52}
53
54int main(void)
55{
56	int (* fp[3])(int left, int right) ={
57		add,
58		multiply,
59		minus
60	};
61	int index = s_getchar();
62	int left = 100, right = 200;
63	printf("Result: %d.\n", fp[index](left, right));
64	return 0;
65}

ch15. 位操作

  1. 编写一个函数,把二进制字符串转换为一个数值。例如,"01001001"转化为73
 1#include "stdio.h"
 2#include "string.h"
 3#define ZERO '0'
 4int bstoi(const char* bs);
 5
 6int bstoi(const char* bs)
 7{
 8	int result = 0;
 9	for (int i = 0; i < strlen(bs); ++i)
10	{
11		result <<= 1;
12		result += bs[i] - ZERO;
13	}
14
15	return result;
16}
17
18int main(void)
19{
20	printf("%d\n", bstoi("01001001"));
21	return 0;
22}