一)自定义函数与结构体:
1.在算法竞赛中,请总是让main函数返回0,以免评测系统错误地认为程序异常退出了。
2.typedef struct { 域定义; }类型名,就可以像原生数据类型一样使用这个自定义类型
3.即使最终答案在所选择的数据类型范围之内,计算的中间结果仍然可能溢出。
比如猴子吃桃问题;
对复杂的表达式进行化简有时不仅能减少计算量,还能减少甚至避免中间结果溢出。
a.计算组合数 n!/(m!(n-m)!) ,先进行约分:n!/m!=(m+1)(m+2)…(n-1)n。
代码:
long long C(int n, int m) {
if(m < n-m) m = n-m; //小技巧:当m<n-m时把m变成n-m
long long ans = 1;
for(int i = m+1; i <= n; i++) ans *= i;
for(int i = 1; i <= n-m; i++) ans /= i;
return ans;
}
b.素数判定:只判断不超过sqrt(x)的整数i
一旦发现x有一个大于1的因子,立刻返回0(假),只有最后才返回1(真)。
int is_prime(int n)
{
if(n <= 1) return 0;
int m = floor(sqrt(n) + 0.5); //floor()函数向下取整,加上5后,变成4舍5入
for(int i = 2; i <= m; i++)
if(n % i == 0) return 0;
return 1;
}
二)函数调用与参数传递
函数的形参和在函数内声明的变量都是该函数的局部变量。 无法访问其他函数的局部变量。 局部变量的存储空间是临时分的,函数执行完毕时,局部变量的空间将被释放,其中的值无法保留到下次使用。 在函数外声明的变量是全局变量,可以被任何函数使用。 操作全局变量有风险,应谨慎使用
调用栈: 它由多个栈帧(Stack Frame)组成,每个栈帧对应着一个未运行完的函数。 栈帧中保存了该函数的返回地址和局部变量,因而不仅能在执行完毕后找到正确的返回地址,还很自然地保证了不同函数间的局部变量互不相干
1.用函数交换量代码:
void swap(int* a, int* b)
{
int t = *a; *a = *b; *b = t;
}
C语言的变量都是放在内存中的,而内存中的每个字节都有一个称为地址(address)的编号。 每个变量都占有一定数目的字节(可用sizeof运算符获得),其中第一
个字节的地址称为变量的地址。
*a = *a + 1就是让a指向的变量自增1。 甚至可以把它写成(*a)++。 注意不要写成*a++,因为“++”运算符的优先级高于“取内容”运算符“*”,
void swap(int* a, int* b)//不正确
{
int *t;
*t = *a; *a = *b; *b = *t;
}
分析:因为t是一个变量(指针也是一个变量,只不过类型是“指针”),所以根据规则,它在赋值之前是不确定的。 如果这个“不确定的值”所代表的内存单元恰好是能写入的,那么这段程序将正常工作;但如果它是只读的,程序可能会崩溃。
2.计算数组元素和
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。 在只有地址信息的情况下,是无法知道数组里有多少个元素的。
int sum(int* a, int n) {//直接把参数a写成了int* a,暗示a实际上是一个地址
int ans = 0;
for(int i = 0; i < n; i++)
ans += a[i];
return ans;
}
如果p1和p2是类型相同的指针,则p2-p1是从p1到p2的元素
3.快速排序算法(C语言的stdlib.h中有一个叫qsort的库函数)
void qsort ( void * base, size_t num, size_t size, int ( * comparator ) ( const void *, const void *) );
前3个参数分别是待排序的数组起始地址、 元素个数和每个元素的大小。 最后一个参数比较特别,是一个指向函数的指针,该函数应当具有这样的形式:
int cmp(const void *, const void *) { … }//指向常数的“万能”的指针:const void *,它可以通过强制类型转化变成任意类型的指针。
eg:
int cmp ( const void *a , const void *b ) {
return *(int *)a - *(int *)b;
}
一般地,需要先把参数a和b转化为真实的类型,然后让
cmp函数当a<b、 a=b和a>b时分别返回负数、 0和正数即可。
三)递归
在可执行文件中,正文段(Text Segment)用于储存指令,数据段(DataSegment)用于储存已初始化的全局变量,BSS段(BSS Segment)用于储存未赋值的全局变量所需的空间。调用栈在运行时创建。每次递归调用都需要往调用栈里增加一个栈帧,久而久之就越界了。 这种情况叫做栈溢出(Stack Overflow)
建议:把较大的数组放在main函数外,局部变量也是放在堆栈段的。