关于判断素数的几点
//该函数有严重缺点:
//不能用于n==1和n较大的情况
//在n接近int的最大值时:
//若i=46340时,i*i=2147395600
//若i=46341时,i*i=2147488281超过了int的最大值,溢出变成了负数,就会继续进行下去
int is_prime(int n)
{
for (int i = 2; i * i <= n; i++)
{
if (n % i == 0)
return 0;
}
return 1;
}
//编写函数的时候,应该尽量保证该函数能对任何合法参数得到正确的结果
//改进后
//优点:避免了每次重复计算sqrt(n)
//通过四舍五入避免了浮点误差
int is_prime(int n)
{
if (n <= 1)
return 0;
int m = floor(sqrt(n) + 0.5);
for (int i = 2; i<=m; i++)
{
if (n % i == 0)
return 0;
}
return 1;
}
谨慎地使用全局变量
调用栈描述的是函数之间的调用关系
由多个栈帧组成,每个栈帧对应着一个未运行完的函数
栈帧中保存了该函数的返回地址和局部变量,因而不仅能在执行完毕后找到正确的返回地址,还能自然地保证了不同函数间的局部变量互不相干——因为不同函数之间对应不同的栈帧
这有一段错误的程序
void swap(int* a, int* b)
{
int* t;
*t = *a;
*a = *b;
*b = *t;
}
这个程序错误的原因是:
指针 t 在使用之前必须赋值
t在赋值之前是不确定的。如果这个不确定的值所代表的内存单元恰好是能写入的,那么这段程序将正常工作;但如果不是,而是只读的,那么程序就会可能崩溃
这有一段错误的程序:
int sum(int a[])
{
int ans = 0;
for (int i = 0; i < sizeof(a); i++)
{
ans += a[i];
}
return ans;
}
错误的原因:
sizeof(a)是无法得到数组的大小的
因为,数组作为参数传递给函数时,实际上只有数组的首地址作为指针传递给了函数。
在函数定义中的int a[]等价于int *a。
在只有地址信息的情况下,是无法知道数组里有多少个元素的
正确的做法:
加一个参数,即数组的元素个数
一般的,若p是指针,k是正整数,那么p+k就是指针p后面的第k个元素,p-k是p前面的第k个元素,而如果p1和p2是类型相同的指针,则p2-p1是从p1到p2的元素个数
那么我们对计算数组和的函数可以用两种写法:
1.
int sum(int* begin, int* end)
{
int n = end - begin;
int ans = 0;
for (int i = 0; i < n; i++)
{
ans += begin[i];
}
return ans;
}
int sum(int* begin, int* end)
{
int* p = begin;
int ans = 0;
for (int* p = begin; p != end; p++)
{
ans += *p;
}
return ans;
}
上述两种写法都是左闭右开
第二种写法更为普遍
把数组作为指针传递给函数时,数组内容是可以修改的。
如果要写一个“返回数组“的函数,可以加一个数组参数,然后在函数中修改这个数组的内容
在C语言的函数中,调用自己和调用其他函数并没有任何本质区别,都是建立新栈帧,传递参数并修改当前代码行。
在函数体执行完毕后,删除栈帧,处理返回值并修改当前代码行。
编译后产生的可执行文件里都保存着与操作系统相关的内容。
段。是指二进制文件内的区域,所有某种特定类型的信息被保存在里面。
在可执行文件里,正文段用于存储指令
数据段用于存储已初始化的全局变量
BSS段用于存储未赋值的全局变量所需的空间
调用栈不存储在可执行文件中,而是在运行时创建
调用栈所在段称为堆栈段
堆栈段也有自己的大小,不能被越界访问,否则就会出现段错误
栈溢出,那么就是每次递归调用都会往调用栈里增加一个栈帧,久而久之,就越界了。
在运行时,程序会动态创建一个堆栈段,里面存放着调用栈,因此保存着函数的调用关系和局部变量
局部变量也是存放在堆栈段内的。
栈溢出不一定是递归调用太多,也可能是局部变量太大
因此,我们一般把较大的数组存放在main函数外
例题4-1 古老的密码 UVa1339
//qsort函数的声明
void qsort(void* base, size_t num, size_t size, int (*comparator)(const void*, const void*))
//qsort函数实现的是快速排序算法
//参数说明:
//待排序的数组起始地址
//元素个数
//元素的大小
//一个指向函数的指针,该函数必须具有以下行书
int cmp(const void *,const void *) {...}
//这个函数中,const void *的意思是:
//指向常数的万能的指针,它可以通过强制类型转化变成任意类型的指针
//例如,如果排序的对象是个整型数组,那么:
int cmp(const void* a, const void* b)
{
return *(int*)a - *(int*)b;
}
//一般的,需要先把参数a和参数b转化为真实的类型,然后让cmp函数当ab时分别返回负数,0,和正数即可。
qsort在算法竞赛中不经常使用
经常使用sort函数
这里是为了告诉“将一个函数作为参数传递给另外一个函数”是很有用的
例题4-2 刽子手游戏 UVa489
我们先来考虑,程序设计的方式
一般有两种:
自顶向下和自底向上
算法竞赛中一般是自顶向下
即:
先写伪代码,然后转化为实际的代码
先写主程序,包括对函数的调用,在实现函数本身。
这个题没有什么难点,只是说注意一点就可以
代码的注释我都不想写了
太简单了
#include
#include
#define maxn 100
int left, chance;
char s[maxn], s2[maxn];
int win, lose;
void guess(char ch)
{
int bad = 1;
for (int i = 0; i < strlen(s); i++)
{
if (s[i] == ch)
{
left--;
s[i] = ' '; //字符串序列中有当前猜的字母,则对这个清为空格
bad = 0;
}
}
if (bad)
--chance;
if (!chance)
lose = 1;
if (!left)
win = 1;
}
int main()
{
int rnd;
while (scanf("%d%s%s", &rnd, s, s2) == 3 && rnd != -1)
{
printf("Round %d\n", rnd);
win = lose = 0;
left = strlen(s);
chance = 7;
for (int i = 0; i < strlen(s2); i++)
{
guess(s2[i]);
if (win || lose)
break;
}
if (win)
printf("You win.\n");
else if (lose)
printf("You lose.\n");
else
printf("You chickened out.\n");
}
return 0;
}
例题4-3 救济金发放 UVa133
这个需要注意的地方就是
循环的处理方式
这个算作是约瑟夫环的进阶版
int go(int p, int d, int t)
{
while (t--)
{
do
{
p = (p + d + n - 1) % n + 1; //向前、后走
}while (a[p] == 0); //跳过为0项
}
return p;
}
例题4-4 信息解码 UVa213
书上代码:
#include
#include
int readchar()
{
for (;;)
{
int ch = getchar();
if (ch != '\n' && ch != '\r')
return ch; //一直读到换行符为止
}
}
//readint主要用于读取01序列
int readint(int c) //用于读取c位二进制字符,并转换为十进制数
{
int v = 0;
while (c--)
{
v = v * 2 + readchar() - '0';
}
return v;
}
int code[8][1 << 8];
int readcodes() //用于读取编码,并形成编码序列
{
memset(code, 0, sizeof(code)); //对编码序列清零
code[1][0] = readchar(); //读取编码头的第一个字符
for (int len = 2; len < 7; len++) //len表示 编码长度,len=2,即表示下面进行对00,01,10的编码对应
{
for (int i = 0; i < (1 << len) - 1; i++) //例如,当len=2时,那么会有三个字符输入,即(1<
{
int ch = getchar(); //读字符
if (ch == EOF) //程序结束,整个程序的结束
return 0;
if (ch == '\n' || ch == '\r') //编码头的读取结束
return 1;
code[len][i] = ch; //将字符存入所对应的位置。例如01,就存储长度为2的,第2个位置;10在存入长度为2的,第3个位置
}
}
return 1;
}
int main()
{
while (readcodes()) //读取编码头,形成编码序列
{
for (;;)
{
int len = readint(3); //读取三位二进制数,以形成编码长度
if (len == 0) //标志结束,即000
break;
for (;;)
{
int v = readint(len);
if (v == (1 << len) - 1) //标志读到了全1序列,则直接退出。进行下一个长度的读取
break;
putchar(code[len][v]);
}
}
putchar('\n');
}
return 0;
}
习题4-1 UVa1589
习题4-2 UVa201
习题4-3 UVa220
习题4-4 UVa253
习题4-5 UVa1590
习题4-6 UVa508
习题4-7 UVa509
习题4-8 UVa12108
习题4-9 UVa1591
习题4-10 UVa815
习题4-11 UVa1588
习题4-12 UVa11809