• 读书笔记


    由于我事实上达到了CF1900分的水平,只记录一些我觉得有新意的东西。

    第1章 程序设计入门

    1、 双精度浮点数类型(double类型)的scanf输入用"%lf",而printf输出用"%f",某些编译器可能允许printf输出用"%lf",但这是不规范的(据说在C++中是都可以使用"%lf"但是某次比赛中好像得到了错误的结果)。

    2、 使用printf输出"%04d",表示输出一个右对齐的,宽度至少4个字符的,位数不足则在前面补0的十进制数。假如不加0,就表示在前面补空格。当然假如有题目要求这种奇怪的东西,建议手写一个输出函数来控制。

    3、 注意区分逻辑或(logical OR)||与按位或(bitwise OR)|的区别。尤其要检查在判断语句中有没有打错。在打开-Wall开关后编译器应该会进行警告,一定要消除这种警告。除此之外,还要注意类似^这样的运算的优先级是否满足本意,以及不要写l<=x<=r这样的表达式。

    第2章 循环结构程序设计

    1、 我觉得P19有一点错误。i++的含义不可能和i=i+1相同,而是和++i相同,实验结果表明确实是这样,赋值表达式的值确实等于左值。

    2、 貌似可以在Code::Blocks里面使用Watches功能。查了一下资料,大概的步骤如下:在Settings->Compiler->ToolChain executables中记住编译器的路径,然后在Settings->Debugger中选择左侧的Default,设置一个正确的路径,比如编译器路径是C:Program Files (x86)CodeBlocksMinGW那么Debugger的路径就是C:Program Files (x86)CodeBlocksMinGWingdb32.exe,根据编译器的不同好像有选择GDB和CDB的区别。然后新建一个Project,选择Console Application,创建完之后看不到任何源代码,但是按F9编译运行之后会出现一个Hello World!,然后在工程文件夹下找到main.cpp,把这个拖进来,最后把Debug->Debugging windows->Watches窗口打开,拖动到喜欢的位置固定下来。然后就可以用F5打断点,F8开始运行/运行到下一个断点。假如从文件读入,注意是把in文件放在main.cpp的同目录下。

    3、 P23好像也有些问题,for和while应该不能这样简单等价,需要考虑变量的作用域。不过这是本入门书,而不是钻这些牛角尖的。

    4、 突然发现了消除Code::Blocks中用"%lld"输入输出会被编译器提示的问题了,原来是推荐使用"%I64d"

    5、 使用(double)clock() / CLOCKS_PER_SEC获得程序的运行时间,尤其适合用于搜索中的卡时。

    6、 注意观察阶乘非常有可能是某些数的倍数,因为阶乘包含了太多因数,利用这个性质可以得到一些奇奇怪怪的规律,比如25!末尾有6个0。

    7、 scanf函数有返回值,返回的是成功输入的变量的个数。

    8、 在Windows下用Ctrl+Z输入EOF,在Linux下用Ctrl+D输入EOF。不过一般大家都用文件读入。

    9、 非静态的局部变量一般是未经初始化的。

    10、 在不能使用重定向语句进行读写文件的比赛,使用下面的方式替代:

        FILE *fin, *fout;
        fin = fopen("data.in", "rb");
        fout = fopen("data.out", "wb");
    
        int n;
        fscanf(fin, "%d", &n);
        fprintf(fout, "%d", n);
    
        fclose(fin);
        fclose(fout);
    

    重定向除了有可能被禁止之外,还有一点就是和标准输入输出冲突。fopen的方式还可以在任意位置关闭,重复读写文件,等等。

    第3章 数组和字符串

    1、 输入字符串时,不要在scanf中加入&符号,虽然有时候的运行结果是对的,但是未必任何时候都是如此,例如:

        char s[105];
        scanf("%s", s+1);
        scanf("%s", &s+1);
    

    就是不同的。后者经常会导致程序卡死。其实应该很好理解,s是一个字符串的首地址,也就是字符数组的首地址,它+1就是偏移一个字符的长度。而&s是一个字符数组的首地址的地址,它+1应该偏移的是一个指针的长度。

    2、 注意scanf("%c", &c);是会输入空白符的,需要输入奇怪的东西的时候还是使用字符串输入最保险。

    3、 char * strchr(char *, char)用于在字符串中查找单个字符,返回值为首次出现的地址或者NULL。

    4、 使用sprintf的时候要小心越界。

    5、 每条语句中,不要多次出现++``````+=等修改变量的操作符。不要省这种行数。

    6、 使用fgetc(fin)可以从已经fopen的文件fin中读取一个字符,返回值是这个字符或者EOF。假如是标准输入,可以使用getchar(),它等价于fgetc(stdin)

    7、 下面的例子:

        scanf("%d", &n);
        c = getchar()
    

    假如输入"123 ",那么字符c就是这个' ',意思是scanf会保持第一个非法字符仍在流中。

    8、 注意不同的操作系统中换行符的不同:

    Windows:    "
    "
    Linux:      "
    "
    MaxOS:      "
    "
    

    使用这个东西构造快速读入时,要考虑操作系统的问题。

    9、 用fgets(buf, maxlen, fin)从已经fopen的文件fin中读入完整的一行,包括这个' ',注意有时候文件未必会以' '结尾,有时会直接遇到EOF。当什么都没有读到时,fgets会返回NULL。也如同前面所说,可以用stdin直接替代fin。

    10、 字符用八进制或者十六进制表示,例如'o',这里o是一个八进制数,或者用'xh',这里h是一个十六进制数。注意不是用十进制表示的(好像大一C++的期考错了这道题)。

    11、 用"%o","%x","%X"输入输出八进制或者十六进制数。当然大写X就是代表十六进制数用大写输出。不过输入的时候是不区分大小写的。

    12、 移位运算符的优先级非常低,比加减法还要低。事实上位运算的优先级都非常低,尤其小心被拿来当成“叉积”符号的^

    13、 用"%u"输入输出无符号(32位)整数。假如输入输出64位整数,应该作和"%d"相似的变换。

    14、 sizeof不是一个函数,它在编译阶段就会计算出值。

    15、 假如是自己弄出来的字符串,一定要保证以''结尾,简单的方法是直接memset一次。

    第4章 函数和递归

    1、 一个声明有返回值,但是没有显示返回的函数,有时候会导致程序运行出奇奇怪怪的东西,这个取决于编译器。幸运的是-Wall编译开关可以警告这种错误。

    2、 ++(自增)运算符的优先级高于*(访问地址)运算符,所以*p++实际上就是*(p++)

    3、 P71提到,一个未经初始化(或者赋值为NULL等“错误”初始化)的指针,千万不能使用*(访问地址)运算符来访问它,尤其是对它赋值,极有可能导致程序运行错误甚至崩溃。必须要赋值一个合理的地址,或者用new运算符申请一个新的地址才可以访问。

    4、 函数定义中,指明的参数若为数组类型,事实上就是一个指针类型。传进来的也只是一个指针,并不再是数组的首地址。

    5、 编译后产生的可执行文件保存的内容:Linux用ELF格式,DOS用COFF格式,Windows用PE格式(由COFF格式扩充而来)。用Linux或者Windows下的size程序可以得到可执行文件中各个段的大小。例如text段、data段、bss段,正文段用于存储指令,数据段用于存储已经初始化的全局变量,BSS段存储未赋值的全局变量所需的空间。调用栈存储在stack段(堆栈段),它不在可执行文件之中,而是在运行时创建,当段被越界访问是,发生段错误,即Segment Fault。而调用栈被栈帧塞满发生的越界,叫做栈溢出,即Stack Overflow。局部变量都是放在堆栈段的,栈溢出不一定是递归调用太多,也有可能是局部变量太大。只要总大小超过了范围,都是栈溢出。

    6、 编码时,自顶向下构造和自底向上构造各有优势,可以看《On Lisp》。测试的时候,当然是自底向上测试。

    7、 可以自己编写一个跨行读入的函数,注意要同时处理' '和' '。

    8、 main函数也是一个普通的函数,甚至可以递归调用。

    9、 常用的头文件:

    cstdio          printf/scanf, fprintf/fscanf, sprintf/sscanf
                    fopen, fclose, freopen
                    getchar, fgets
    cmath           sin, cos, pow, ...
    cstring         strlen, strcat, ...
                    memset, memcpy, ...
    cctype          isalpha, isdigit, toupper, ...
    ctime           clock
    cstdlib         rand
    

    第5章 C++与STL入门

    1、 关闭流与stdio的同步:ios::sync_with_stdio(false),貌似还可以cin.tie(nullptr),在取消流同步之后就不能再使用对标准输入输出进行操作的C语言的函数了,否则可能会得到一些由于同步错误得到的奇奇怪怪的结果。在使用cout输出时,用" "代替endl可以进一步提高速度。

    2、 bool类型是C++新增的,C语言中没有bool类型。

    3、 得到一行的更简单的方法:

        string line;
        while(getline(cin, line)) {
            stringstream sin(line);
            while(sin >> x)
                ;
        }
    

    注意string很慢,stringstream更慢,假如对效率有要求,还是应该使用自定义的输入函数(再提一次要注意处理' ')。

    4、 在C++中不再需要用typedef的方式定义struct。而且C++中的struct还可以有方法。

    5、 用template<typename T>来使用模板。有的模板是不需要显示声明类型使用的,貌似是函数。但是使用模板来创建的struct就需要用尖括号搞出模板的类型。(可以用这个方法写计算几何的模板?但是还是分成两份代码最方便)。

    6、 STL的set居然有set_union和set_intersection两个操作,使用方法如下:

        set<int> s1, s2, S;
        s1.insert(1);
        s1.insert(2);
        s1.insert(3);
        s2.insert(2);
        s2.insert(3);
        s2.insert(4);
    
        S.clear();
        set_union(s1.begin(), s1.end(), s2.begin(), s2.end(), inserter(S, S.begin()));
        puts("S=");
        for(auto &v : S)
            printf("%d ", v);
        puts("");
    
        S.clear();
        set_intersection(s1.begin(), s1.end(), s2.begin(), s2.end(), inserter(S, S.begin()));
        puts("S=");
        for(auto &v : S)
            printf("%d ", v);
        puts("");
    

    注意要先clear。

    7、 优先队列的格式是:priority_queue<int, vector<int>, cmp> ,其中cmp是一个struct,且需要重载()运算符。

    8、 例题5-7 丑数,这道题的处理方式:从优先队列中取出最小值,然后插入由这个最小值衍生出的新值,这个思路貌似在某道找第k短路的题目中用过。

    9、 cstdlib中的rand生成[0, RAND_MAX]内均匀分布的随机整数,假如要生成更大的整数,不应该用rand()rand(),很显然这样会使得中间部分的数出现的概率显著增大,应该使用rand()RAND_MAX+rand()。小心溢出。

    10、 根据当前时间设置一个随机数种子,常用srand(time(nullptr)),注意包含对应的头文件。其中time函数返回的都是当前的时间,也就是UTC时间1970年1月1日0:00以来的秒数,假如程序执行得很快,很可能使用的也是同一个随机数序列。time函数传入的假如不是空指针,则会把时间写在这个time_t指针对应的内存中。不经过srand设置的随机数序列,默认为srand(1)。注意不要在每次使用rand之前都设置一次srand,否则极有可能结果非常不均匀。

    11、 使用函数处理vector等类型时,尽可能传引用以避免不必要的复制。提醒我的高精度整数模板中好像确实有不必要的复制。

    12、 assert不是一个函数,而是一个宏。

    13、 lrj的高精度整数貌似还配有除法,可以偷过来用,在他的代码仓库里。

    14、 可以用<运算符构造出其他比较运算符的重载,但是==!=会进行两次比较,假如对性能有要求,应该再手动重载一个==运算符。

    15、 涉及字符串的完全匹配问题,应该先给字符串存到set里映射一个整数,在接下来的使用中进行整数的==判断会非常快。

    第6章 数据结构基础

  • 相关阅读:
    CSS网站变灰
    长列表优化之滚动替换数据方案小记
    JS把数组中相同元素组合成一个新的数组问题
    yahoo CSS reset
    IE调试网页之三:使用 F12 工具控制台查看错误和状态 (Windows)
    KMP算法的JavaScript实现
    Android系统版本SDK_INT与版本对应关系
    利用jQuery的deferred异步按顺序加载JS文件
    Javascript图像处理之平滑处理
    IE调试网页之七:使用探查器工具分析代码性能 (Windows)
  • 原文地址:https://www.cnblogs.com/KisekiPurin2019/p/12330844.html
Copyright © 2020-2023  润新知