本文的目的在于对此前C语言的笔记进行相应的补充,同时整理一下博主遇到的一些有趣的函数。
1. 知识点补充 & 编程疑难解决
关于scanf
- 在没有特殊说明的情况下,scanf遇到空格,回车等特殊字符会停止,因此在输入字符后需要回车时,不用在scanf后特地打出\n这样的转义字符。
- 用scanf时,字符与数字最好分开输入,不然很容易报错,具体原理貌似是%c与%d的输入规则不同。
关于程序运行时间成本的降低
运行程序时常常会出现运行时间过长,无法以一个理想的时间得出结果的情况。有以下几种解决办法:
- 程序中是否出现了<math.h>中的pow函数,因为pow函数的运算方式为浮点运算,速度较慢,如果运算不需要用到浮点数,可以尝试自己写一个更简单的函数进行改进。
- 程序中是否有三层以上的循环,一般来说不会遇到这么复杂的循环,可以尝试改变算法。
- 程序中是否有各种递归算法,如果有,尝试改写成循环写法。
关于排序
在前面的章节中曾经讲过冒泡排序,这是C语言的一种最基础的排序算法,当我们遇到一些更加复杂 / 数据更多的情况时,冒泡排序就显得过于简陋,效率也不够看了。
qsort函数(首选)
QSORT函数位于 <stdlib.h> 中,其原型如下:
void qsort(
void *base,
size_t nmemb,
size_t size,
int (*compar)(const void* p1, const void* p2)
);
先别急着头疼,这里会一个个解释。
void* base:
base 是一个指针,该指针指向数组的首地址。(通常直接传入数组名即可)nuemb:
nuemb 是个 无符号整数类型,用size_t表示,这个值表示数组内元素个数。(通常可以直接使用 sizeof(数组名)/sizeof(数组单个元素大小) 来计算得出)size:
size 表示数组内每个元素的大小。*compar:
这一部分是qsort的核心部分,指向的是一个比较两个元素的值的函数,直接决定了排序的顺序。这玩意是要自己写的。这里详细论述:首先说明这一函数如何决定排序顺序:
当这一函数返回值小于0,则将 p1 指向的元素放在 p2 之前;
大于0,将 p1 指向的元素放在 p2 之后;
等于0,顺序 不确定 。再说明这个函数的一些注意事项:
- 注意传入数据格式:
传入的是两个通用指针,因此在函数内使用时请务必转换成相应类型指针再进行计算。 - 务必注意这个函数返回值为0时,排序顺序是随机的!
- 注意传入数据格式:
关于字符串
字符串的输入
字符串的输入有几种方式:
scanf("%s", str);
gets(str);
gets_s(str, int);
fgets(str, int, stdin);
一般而言,博主更常用gets_s。
tmd,经过紧接着几个月的拷打之后,还是fgets比较好使…
但需要注意,关于gets的三种输入函数都存在回车问题,即当这几个函数上面有scanf的时候,会出现函数读入回车导致跳过原本预期中字符串输入的过程,对此有两种解决方式:
- 在gets(gets_s,fgets)的前面加一个:
fflush(stdin); //用于清空缓冲区内的数据。
- 在gets(gets_s,fgets)的前面加一个:
getchar(); //用于读入此前的回车进而防止其影响字符串输入。
博主用第二种方法更多一些。
字符串的输出
printf("%s", str); //该方式不会补充回车
puts(str); //该方式会自动补充回车,效果同printf("%s\n", str);
读入不定个数个字符串
常用gets_s的返回值进行操作。
这里进行说明:
gets_s是有返回值的,具体返回值为输入字符串的地址(相当于返回一个指针)。如果输入不成功则返回一个空指针。
因此常用这个性质:
while(gets_s(str[i++], int)!=NULL)
//需要结束输入时则使用CTRL+Z进行结尾。
字符串的各种转化
假设一串字符串中存着一个5位的整数,我们想用这个值进行计算,这时候如果再用循环进行转化,就太慢,也不优雅(笑
好在,C语言其实是有相应的命令的:
atoi(const char*); //字符串转化为int
atol(const char*); //字符串转化为long
atof(const char*); //字符串转化为double
这一系列函数通通位于 <stdlib.h> 中,传入的参数都是字符串的首地址(可以直接写字符串名)
这三个函数可以极大程度上提高我们的效率。
关于指针
指针的地址? 指针所指向的地址?
这是两码事。
指针的地址可以通过:
printf("%x", &ip);
进行查看。
而指针所指向的地址则可以通过:
printf("%x", ip);
进行查看。
关于精度(老大难)
有些oj会要求咱们将小数保留至两位精度(类似的),这种情况下如果我们计算到对应的精度(如0.01)大概率会出现一些奇奇怪怪的错误,要注意这种题求解时按照比题目要求精度高一位的精度来做。(常常在枚举题目中看到这玩意)
2. 一些有趣的函数
这个条目仅仅用于记录博主遇到过的觉得有一定利用意义的函数。
随机数生成
利用<stdlib.h>中的:
rand();
该函数的功能为得到0~ RAND_MAX 之间的整数,不同系统的RAND_MAX不一样,反正够用
如果要得到一个范围内的数,比如3-10之间的数,可以用如下方法:
#include<stdio.h>
#include<stdlib.h>
int main(){
int random;
random=3+rand()%7; //前面的3为最小值,rand后面取余的数为区间宽度。
printf("%d", random);
return 0;
}
上述算法还有个问题,该函数生成的是伪随机数,如果种子不刷新,则生成的随机数是有规律的。
因此建议每次使用该函数之前,都多引用一个库,并在主函数中加一句:
#include<time.h>
srand(time(0));
本步骤的目的是在每次运行程序之前都刷新种子。(通过time(0)这个随时间变化的变量,而这个变量存储在 <time.h> 中。)
需要注意的是,这个语句请务必加在主函数中,而非是一个调用多次的子函数或迭代很快的循环中,由于time(0)是一个只统计到秒的变量,因此如果过快地多次重置,则会发现你生成的随机数都是同一个数。
求素数
用空间换时间
具体算法:
- 令n=2,如果n是素数,则划掉序列中所有n的倍数。
- 令n等于下一个没有划掉的元素,回到步骤2。
3.关于C++
其实博主本来不打算在这里写C++的东西,但是耐不住学校的安排着实比较无奈,略写吧。
关于引用
C++里面的引用其实比较类似于C里面的指针,只不过不用再使用解引用符了,可以直接通过名称来访问原本的地址,这一点在函数传参的时候尤为常用。
关于malloc
这一部分博主着实不是非常确定,但确实出现过在C++中利用malloc却出现报错的情况,在网上查了一下相关资料,发现C++中最好使用新的内存申请方法,即 new 关键字。
关于STL
这里仅仅会列举一些STL的常见错误,以及一些博主认为需要额外说明的玩意。
关于push_back后iterator的报错
在C++中提供了迭代器,即iterator的用法,这东西通常可以用于迭代各类STL容器。
但有一个事得额外讲一下,vector这个容器相比于正常数组的好处在于其可以随时通过库函数方便的调整它的大小,看起来很美好,其实际底层原理是在原先的数组空间不够时直接将整个数组移到另一个内存地址。
这在平常是没什么问题的,但是如果你恰好设置了一个迭代器,我们会发现迭代器根本没有转移到新的位置(能转移就有鬼了),而这个时候我们再操作这个迭代器,就会报错。
所以,在调整数组大小时,尤其需要注意iter的位置。
本文会随着博主编程经历的增长持续更新。