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;
}