C Primer Plus(第六版)|第16章.C预处理器和C库 编程题目

1.开发一个包含你需要的预处理器定义的头文件。

写一个.h的头文件,包含你此次练习的题目就可以了。 注意防止头文件被重复引用导致的错误,需要用#ifndef或者#pragma once,下面题目需要的声明和结构都在对应题目中。

 方法1 使用#ifndef的方法

#ifndef __16NEED__H__ //防止头文件被重复引用
#define __16NEED__H__ //第一次调用头文件,创建声明 

/*

...
头文件主要包含的内容
...

*/

#endif

方法2 使用#pargma once的方法

#pargma once //VS2019头文件的默认模板就有,防止头文件被重复调用

/*

...
头文件内容
...

*/


2. 两数的调和平均数这样计算:先得到两数的倒数,然后计算两个倒数的平均值,最后取计算结果
的倒数。使用#define指令定义一个宏“ 函数 ,执行该运算。编写一个简单的程序测试该宏。
定义调和平均数的宏函数,可以先定义一个平均数的宏,再定义调和平均书。该题目需要 注意宏函数的变量规范,注意括号,防止意外的错误
错误的写法(不注意变量的括号)
#define MEAN(X,Y) (X+Y)/2 //平均数
#define HMMEAN(X,Y) 1/MEAN(1.0/X,1.0/Y) //调和平均数

正确的写法(每个变量都需要加括号,同时表达式也需要括起来,防止运算错误)

#define MEAN(X,Y) (((X)+(Y))/2) //平均数
#define HMMEAN(X,Y) (1/MEAN(1.0/(X),1.0/(Y))) //调和平均数

#include <stdio.h>

//测试调和平均数
int main()
{
	int a = 1;
	int b = 3;
	printf("%.2f %.2f\n", MEAN(1.0/a, 1.0/b),HMMEAN(a,b));
	printf("%.2f", HMMEAN(2, 4));
	return 0;
}


3.极坐标用向量的模(即向量的长度)和向量相对x 轴逆时针旋转的角度来描述该向量。直角坐标用向量的x 轴和 y 轴的坐标来描述该向量(见图16.3)。编写一个程序,读取向量的模和角度(单位:度),然后显示 x 轴和y 轴的坐标。相关方程如x = r*cos A y = r*sin A,需要一个函数来完成转换,该函数接受一个包含极坐标的结构,并返回一个包含直角坐标的结构(或返回指向该结构的指针)
题目的大意是编写极坐标转换直角坐标的函数。为了方便表示极坐标和直角坐标,编写极坐标和直角坐标的结构体,同时再写极坐标转换到直角坐标的函数。
需要注意,极坐标的角度是角度制,而C语言的math库计算正余弦是弧度制,因此需要把角度制转变成弧度制,需要定义一个转变常量DEG_PER_RAD
#include <stdio.h>
#include <math.h>

#define DEG_PER_RAD (180/(acos(-1)))//1弧度对应多少角度

struct Polar_C //定义极坐标系
{
	float angel;
	float len;
};

struct Rect_C //定义直角坐标系
{
	float x;
	float y;
};

typedef struct Polar_C PolarC;
typedef struct Rect_C RectC;


void PolarToRect(RectC* pr,const PolarC* pp)
{
	pr->x = cos((pp->angel) / DEG_PER_RAD) * (pp->len);
	pr->y = sin((pp->angel) / DEG_PER_RAD) * (pp->len);
}

void ShowPolar(const PolarC* pp)
{
	printf("The angel is %.2f°    The length is %.2f.\n", pp->angel, pp->len);
}

void ShowRect(const RectC* pr)
{
	printf("X %.2f  Y %.2f.\n", pr->x, pr->y);
}

int main() //测试
{
	PolarC polar = { 30,8 };
	RectC rect;
	ShowPolar(&polar);
	PolarToRect(&rect, &polar);
	ShowRect(&rect);
	return 0;
}


4.ANSI库这样描述clock()函数的特性:

#include <time.h>
clock_t clock (void);

这里, clock_t 是定义在 time.h 中的类型。该函数返回处理器时间,其单位取决于实现(如果处理器时间不可用或无法表示,该函数将返回-1 )。然而,CLOCKS_PER_SEC (也定义在 time.h 中)是每秒处理器时间单位的数量。因此,两个 clock() 返回值的差值除以 CLOCKS_PER_SEC 得到两次调用之间经过的秒数。在进行除法运算之前,把值的类型强制转换成double 类型,可以将时间精确到小数点以后。编写一个函数,接受一个double 类型的参数表示时间延迟数,然后在这段时间运行一个循环。编写一个简单的程序测试该函数。
题目的大意是用clock()函数编写类似sleep()函数的功能。函数的参数是时间延迟的秒数,实现的功能是让程序暂停几秒。
需要注意,clock_t是time.h头文件定义的类型,实质是long int。需要用到强制类型转换,才能进行小数运算。
#include <stdio.h>
#include <time.h>

//#define CLOCKS_PER_SEC 1000 //CLOCKS_PER_SEC实质是1000

void MySleep(double time)
{
	clock_t start = clock();
	while (1)
	{
		double past = ((double)clock() - (double)start) / CLOCKS_PER_SEC;
		//printf("%.3f\n", past); //验证程序
		if (past >= time)
			break;
	}
}


int main()
{
	MySleep(10.0); //暂停10s
	return 0;
}


5. 编写一个函数接受这些参数:内含 int 类型元素的数组名、数组的大小 和一个代表选取次数的值。该函数从数组中随机选择指定数量的元素,并打印它们。每个元素只能选择一次(模拟抽奖数字或挑选陪审团成员)。另外,如果你的实现有time() (第 12 章讨论过)或类似的函数,可在 srand() 中使用这个函数的输出来初始化随机数生成器rand() 。编写一个简单的程序测试该函数。
题目的大意是实现从N个成员中,抽出n个不重复的元素出来,实现抽奖的效果。假设原本的数据源没有重复的,通过产生[0 - N)的随机索引值,来取成员,较为简单。 因此难点是如何产生n个不重复的随机索引?
n个不重复的随机索引实现
1. 索引的范围
如果一个数组有N个元素,索引的范围则是[0,N-1],因此随机索引的范围应该是[0,N-1],通过rand()函数产生随机函数
//N个元素的数组的索引随机值
int RandLimit(int up) //[0,up)随机数
{
	return rand() % (up);
}

2. 产生n个不重复的随机值

如果要产生不重复的随机值,则需要记录产生的随机值,可以通过数组储存已产生的不同随机数。而且当我们使用随机数时,我们也可以通过该数组取用随机数。

因此编写产生n个不同的随机数时,我们要讲随机数存储在数组中,传入参数要包含数组名,随机数的个数和随机数的范围上限。

//rarr是随机数数组 n是随机数个数 up是随机数范围上限
void NoReRand(int* rarr, int n,int up) //产生n个无重复的随机数
{
	for (int i = 0; i < n; i++)
	{
		again:
		int randnum = RandLimit(up);
		for (int j = 0; j < i; j++) //筛选有无重复值
		{
			if (rarr[j] == randnum) //如果有重复值,则重新生成随机数,重新筛选
				goto again;
		}
		//没有重复值,才可以脱出for循环
		rarr[i] = randnum; //筛选完毕后,填充到数组中
	}
}

因为随机数的个数是动态的,是个变量,如果你的编译器支持变长数组,可以用变长数组,这里我用malloc函数实现变长数组的功能。我们产生10个随机数,随机整数范围为0-9,对上述函数进行测试

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

void TestRand(int n, int up)
{
	int* randarr = (int*)malloc(n * sizeof(int)); //储存产生的随机数,防止重复
	NoReRand(randarr, n, up);
	for (int i = 0; i < n; i++)
	{
		printf("%5d\n", randarr[i]);
	}

	free(randarr); //释放空间
}

int main()
{
	for (int i = 0; i < 10; i++) //测试10次
	{
		TestRand(10, 10);
		system("pause");
	}

	return 0;
}

可以看到,并没有相同的随机数产生。

接下来,就是抽奖的实现

为了实现动态的效果,在抽奖的过程,显示浮动的号码。通过上一题写的MySleep函数控制屏幕刷新时间,可以做到动态的效果(程序不足是,不知道如何实现按下一个键,使抽奖停止)。为了方便,数组的元素我只通过填充数字的方式,数组的元素可以用名字,电话号码填充,可以达到更好的抽奖效果。

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

#define SIZE 200

//模拟随机抽奖

void MySleep(double time) //sleep函数
{
	clock_t start = clock();
	while (1)
	{
		double past = ((double)clock() - (double)start) / CLOCKS_PER_SEC;
		//printf("%.3f\n", past); //验证程序
		if (past >= time)
			break;
	}
}

void InitArr(int* pa, int n) //初始化数组
{
	for (int i = 0; i < n; i++)
		pa[i] = i+1;
}

int RandLimit(int up) //[0,up)随机数
{
	return rand() % (up);
}

void NoReRand(int* rarr, int n,int up) //产生n个无重复的随机数
{
	for (int i = 0; i < n; i++)
	{
		again:
		int randnum = RandLimit(up);
		for (int j = 0; j < i; j++) //筛选有无重复值
		{
			if (rarr[j] == randnum)
				goto again;
		}
		//没有重复值,才可以脱出for循环
		rarr[i] = randnum; //筛选完毕后,填充到数组中
	}
}

void Lotto(int* arr,int sz,int n)
{
	int* randarr = (int*)malloc(n * sizeof(int)); //储存产生的随机数,防止重复
	NoReRand(randarr, n, sz);
	for (int i = 0; i < n; i++)
	{
		printf("%7d\n", arr[randarr[i]]);
	}

	free(randarr); //释放空间
}

int main()
{
	srand((unsigned int)time(NULL));
	int arr[SIZE] = { 0 };
	InitArr(arr, SIZE);
	for (int i = 0; i < 1000; i++)
	{
		Lotto(arr, SIZE, 10);
		MySleep(0.1);
		system("cls");
	}
	return 0;
}

6. 修改程序清单 16.17 ,使用 struct names 元素(在程序清单 16.17 后面的讨论中定义过),而不是double 类型的数组。使用较少的元素,并用选定的名字显式初始化数组。
题目大意是使用stdlib的qsort函数对struct names元素进行快速排序。关键是编写比较函数,比较struct name的顺序。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define NAME 15

struct name
{
	char fname[15];
	char lname[15];
};

typedef struct name name;

void ShowName(name* pn, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%s %s\n", pn[i].fname, pn[i].lname);
	}
}

int comp(const void* p1, const void* p2) //编写比较函数,qsort的第四个参数
{
	name* a = (const name*)p1;
	name* b = (const name*)p2;
	int cmp = strcmp(a->fname, b->fname); //先比较姓
	
	if (cmp == 0) //如果姓相同,则比较名,返回1,0和-1
	{
		return strcmp(a->lname, b->lname);
	}
	else
		return cmp;
}

int main()
{
	name school[5] = { {"Jack","Brown"},{"Mike","Cui"},{"Cui","Xi"}, {"Xiao","Ming"},{"Hong","Bao"} };	
	ShowName(school, 5);
	printf("\n");
	qsort(school, 5, sizeof(name), comp); //说明-1为排列的顺序,从小到大
	ShowName(school, 5);
	return 0;
}


7. 下面是使用变参函数的一个程序段:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
void show_array(const double ar[], int n);
1295 double * new_d_array(int n, ...);
int main()
{
double * p1;
double * p2;
p1 = new_d_array(5, 1.2, 2.3, 3.4, 4.5, 5.6);
p2 = new_d_array(4, 100.0, 20.00, 8.08, -1890.0);
show_array(p1, 5);
show_array(p2, 4);
free(p1);
free(p2);
return 0;
}
new_d_array() 函数接受一个 int 类型的参数和 double 类型的参数。该函数返回一个指针,指向由malloc() 分配的内存块。 int 类型的参数指定了动态数组中的元素个数,double 类型的值用于初始化元素(第 1 个值赋给第 1 个元素,以此类推)。编写show_array() new_d_array() 函数的代码,完成这个程序。
题目大意是编写两个函数,一个是输入函数,一个打印函数。第一个是变参函数,参数的数目不固定,将输入的参数存储到对应的数组中,并且返回数组地址。第二个函数是显示数组对应的内容(简单)。
因为要实现变参函数,因此需要用到<stdarg.h>头文件的内容
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

void show_arr(const double ar[], int n);
double* new_d_arr(int n, ...);

int main()
{
	double* p1 = NULL;
	double* p2 = NULL;
	p1 = new_d_arr(5, 1.2, 2.3, 3.4, 4.5, 5.6);
	p2 = new_d_arr(4, 100.2, 20.3, 34.4, 42.5);
	show_arr(p1, 5);
	show_arr(p2, 4);
	free(p1);
	free(p2);
	return 0;
}

void show_arr(const double arr[], int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%.2f\n", arr[i]);
	}
	printf("\n\n");
}

double* new_d_arr(int n, ...)
{
	double* pd = (double*)malloc(n * sizeof(double));
	va_list ap; //创建变参列表,va_list实质是char*
	va_start(ap, n); //变参列表的初始化
	for (int i = 0; i < n; i++)
	{
		pd[i] = va_arg(ap, double); //取用列表中对应类型的元素
	}
	va_end(ap); //清理变参列表
	return pd;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值