最近打算重新学一遍C++,趁着京东打折买了很多书,其中c++的有

  1. essentizal c++
  2. effective c++
  3. more effective c++
  4. effective stl
  5. c++必知必会
  6. applied c++

一共有六本,打算在12月前按照这个顺序大致看完,这是该系列的第一篇post《essential c++》的笔记。加油~

总体来说,这本书分为7个部分,前三个部分主要讲的是基础,函数特性,泛型编程(泛型函数,容器),后三个部分主要讲基于对象,面向对象,模板类,最后一个部分讲异常处理,内容上还是非常清晰的。这点上与fluent python很像,其也是按照几个编程范式来展开讲的,如面向对象,面向过程,函数式编程这三大编程范式。当然和python相比,c++主要是面向过程与面向对象,函数式编程上还没那么明显(泛型函数?),所以 essential c++ 里没有包含函数式编程。

这里顺便说一句函数式编程,我的想法函数式编程最大的优点就是纯函数的无副作用,所谓无副作用,就是说函数内部不会修改函数外的状态,所有的事情都发生在函数内部,函数是第一公民,所有的操作都围绕函数展开。这样做的优点是,在编写高并发的程序的时候,不用太多的考虑各个函数之间的状态干扰问题,从而可以很容易的编写搞并行化的程序。

基础

如何撰写c++程序

  • 函数由四个部分组成
  • 程序运行假定存在main函数,否则无法运行
  • 头文件是operation,cpp文件是implemenation
  • #include 引入头文件
  • 标准库提供的所有东西都在std中,如string, cout, cin, vector
  • using namespace 命名空间曝光

对象的定义与初始化

  • 内置类型,布尔型,整数,浮点数,字符
  • 构造语法 int xxx(0),事实上这使得内置类型和用户定义的class一致
  • =用在内置类型没有问题,如果是多初值,就没有办法了,如复数
  • #include , complex purei(0, 8);
  • const

撰写表达式

  • && ||短路
  • 优先级 ! > 乘除 > 加减 > 比较 > 逻辑 > ==

条件语句和循环语句

  • switch(){} default:

如何用array和vector

  • 一个是动态一个非动态
  • #include
  • vector xx(seq_size)
  • vector xx(ele_vals, ele_vals + seq_size)
  • 不同在vector知道自己的长度,.size()

指针带来弹性

  • 操纵指针,而不是操纵对象
  • 无对象指针,地址为0
  • int *p = 0;
  • vector对象本身不是指针
  • cstdlib,随机数,rand()返回int最大正整数之间
  • . ->
  • erase因为delete是关键词

文件的读写

  • #include
  • ofstream xx(xx.txt, ios_base::app) << 重定向输入
  • ifstream xx(xx) >> 重定向输出
  • 如果没有打开,那么返回false
  • cerr无缓冲,cout有缓冲
  • .seekg(0),读取流的指针
  • 这种方法比较麻烦,ACM中,可以freopen(file, “r”, stdin), freopen(file, “r”, stdout),后面直接用cin cout就很方便调试
#include <iostream>
#include <fstream>
#include <cstdio>
#include <string>
#include <vector>

using namespace std;

int main()
{
    freopen("test.txt", "r", stdin);
    freopen("test_output.txt", "w", stdout);
    string temp_string;
    int i = 0;
    vector<string> string_vec(4);
    while(cin >> temp_string){
        string_vec[i] = temp_string;
        i++;
    }
    sort(string_vec.begin(), string_vec.end());
    for (int i = 0; i < 4; ++i)
    {
        cout << string_vec[i] << endl;
    }
    fclose(stdin);
    fclose(stdout);
    return 0;
}

函数

  • 传值与传引用
  • int &xx = xasd,其实是alias一样的别名
  • 传reference不传对象,传地址,所以可以间接操作,防止复制
  • const vector &xx,防止修改,复制
  • ref和pointer相同,传地址, 但是两者用法不同,一个直接用,一个间接用
  • 更重要的是,pointer可能是空的,而&必定代表一个对象,所以不需要进行空检查
  • 函数内部的对象不要返回其地址,无论是pointer还是ref,只能传值回来,返回是副本。
  • static静态内存
  • local -> file -> dynamic(堆,必须自行管理,用new delete),new Type(init)
  • new int[41],这种方法不能定义初值
  • delete [] xx,不需要检查,直接delete,编译器会自信检查是否非0
  • 如果不进行delete,会一直存在,就造成内存秀楼
  • ref不能为0, pointer可以为0
  • cout 也是流对象
  • 默认值在函数声明和函数定义只能有一个,所以一般默认值在头文件出现,原始文件没有显示默认值
  • static进行静态local object
  • inline避免小函数的调用开销,相当于在函数内部进行展开,具体
  • 函数可以根据传入参数进行重载
  • 模板函数减少代码量,
  • template ,关键词标明是类型名模板,机理是先绑定(bind),然后产生函数实例,
  • 函数指针,const vector seq_ptr(int);注意这里其实是返回双指针,一个函数指针必须知道返回类型和输入类型
  • const vector (seq_ptr)(int);定义优先级,函数指针直接使用xxx(),函数的地址就是函数名,和数组一样。const vector (seq_ptr[])(int)为函数指针数组。
  • enum xxx{},定义枚举类型,其实就是xx=0这样的定义,其中xxx是可有可无的
  • inline函数的定义必须在头文件里,头文件存放声明,如果直接放定义无法识别,加extern让其编程编程声明,就可以被include了。
  • const在文件外不可见,””同一路径,<>非同一路径。

泛型编程

这张的内容比较多,先讲了容器和泛型算法,然后讲了泛型算法设计需要设计到的类型,容器,函数如何用相应的方法解决,类型用函数模板,容器用iterator,函数用函数指针,同时系统内部提供函数object直接使用 ,进一步可以用函数模板统一泛化,中间介绍了adapter来对function object进行元变化。后面实际的部分先讲顺序容器,然后讲的关联容器(map, set)。最后讲了iterator inserter以及对流的iterator, istream_iterator两个对iterator的扩展.

  • STL中最主要的就是容器和泛型算法
  • 容器分为顺序容器(顺序存放),关联容器(map, set,用来快速查找值),set也也可以看成一种 map,map到是否存在
  • 泛型算法,容器独立,类型独立,通过函数模板实现类型独立,通过iterator实现容器独立
  • vector可以是空的,array不可以
  • list相当于链表,不是直接相连的
  • 迭代器就是指针在提供一层抽象,即泛型指针。
  • iterator需要定义容器和类型, iterator iter;
  • 实际上用vector::iterator iter = svec.begin();
  • const_iterator 允许读,不允许写
  • template ,
  • iterator提供 * -> . == != ++ 运算符号 operator
  • 函数有函数指针与函数object,定义函数
  • 泛型算法,可以泛化容器(iterator),类型(函数模板),算法(传入函数指针或者对象)三种东西
  • 超过65种泛型算法,常用7类,搜索,排序,复制删除替换,关系,生成,数值,集合
  • string也可以看成一个容器,clear, size, empty, ==, =, begin, end, insert, erase这是共同的操作,其中insert, erase根据容器的类型不同有点不同,顺序,关联
  • vector是连续内存,list是随机(双向链表),所以list插入效率更高,一个元素包含值,前后指针(back, front),但是访问就比较麻烦,需要不断遍历,vector为1
  • vector适合顺序不变,list适合顺序需要更新的,最后是deque,对最前面的数据进行增减效率高,也是顺序存储,queue使用 deque实现的
  • vector xx(100, 1);初值,还可以用数组和复制生成
  • push_back, pop_back, list和deque还提供了push_front, pop_front(), pop_xxx不会返回删除的值,所以需要front(), back().这些其实是特殊的inset, erase.
  • 泛型算法都在algorithm里
  • find无序返回iterator, binary_search有序(返回的是true, false), sort, count, search子序列,失败iterator指向尾端, max_element, copy进行复制
  • 函数指针只需要(*func) 指定类型即可。
  • while((iter != find(iter, vec.end(), vals)) != vec.end())
  • 标准库有很多定义好的function object, class的实例对象,对function call 运算符进行了重载,就像加减乘除一样,包含三大类,算数,关系,逻辑(663)。事实上都是一种模板类,只不过对function call进行了重载~都在头文件#include
  • 加减乘除负模modules,大于大于等于小于小于等于等于不等于,与或非(logical_or_not_and)
  • 默认sort用的是less, 可以改成greater().
  • 运算符参数绑定,就是adapter, -> function object adapter 绑定function object.二元操作就变成了一元操作。标准库提供两个, bind1st, bind2nd, 就可以提供给find_if,一元谓词,否则function object是二元谓词。
  • ccc++=xx++
  • 函数模板,就可以同时对应function object以及function pointer
  • c++ map有默认值
  • 有first second两个属性
  • 用find进行查找有误,返回一个iterator,指向一个pair, 否则end()
  • set里只有key, 一般用来判断存在
  • 默认是less排序,可以用类似vector的方式初始化
  • insert单一元素或者范围iterator
  • set 有集合算法,set_开头,set_diff, set_inter, set_union, set_symmetric_diff
  • iterator inserter用来改变iterator的行为,其赋值自动变成.insert, .push_back, .push_front, 有三种,back_inserter, front_inserter, 其中inserter需要两个参数进行封装,第二个是插入位置,每次赋值就会在改位置进行插入,这些iterator inserter在里,这种方法就避免了目标vector过小,动态开辟位置,实现泛型算法就不用考虑iterator会不够用的情况了
  • cin, cout都是流对象,也可以用iterator进行操纵,有istream_iterator xx(cin), 不初始化就是eof,ostream_iterator xx(cout, “ “)输出的时候无间隔,加个间隔,之后用copy进行复制就可以了,copy(xx, eof, back_inserter(text))
  • cin是标准输入输出,在iostream里,文件流在fstream里,输入流对象,cin, fstream都是流对象,传给流_iterator就可以获得iterator
  • 空指针在c++11可以用nullptr,在之前可以用NULL,实际上就是0
  • 任何指针都可以赋值给void指针,void指针赋值给其他类型的指针时都要进行转换 type p=(type)vp; (type*)vp++可行;
  • 在函数的返回值中, void 是没有任何返回值, 而 void * 是返回任意类型的值的指针.
  • #ifndef/#define/#endif, #ifndef A_H意思是”if not define a.h” 如果不存在a.h,接着的语句应该#define A_H 就引入a.h, 最后一句应该写#endif 否则不需要引入
  • extern有两个作用,extern “c”{}写c代码,c++会修改函数名,因为函数重载, c不会。C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern “C”进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数名。这点在生成动态链接库的时候非常有用,可以用这个wrap一下c++的代码,防止函数重载。
  • 第二个作用是声明,声明与定义不一样,在头文件里只做声明,声明为全局变量后,在cpp里定义一样是有效的。
  • (1) extern 表明该变量在别的地方已经定义过了,在这里要使用那个变量.
  • (2) static 表示静态的变量,分配内存的时候, 存储在静态区,不存储在栈上面.
  • const可以与extern连用来声明该常量可以作用于其他编译模块中,

基于对象

what

  1. 什么是 this 指针
  2. 静态类成员
  3. 打造一个 iterator class
  4. 合作基于 friend
  5. 实现拷贝赋值操作
  6. 实现 function 对象
  7. 重载 iostream 运算符
  8. 指针,指向 类成员函数

how

  • 对象的成员函数包括操作函数与运算符,
  • class的设计与实现
  • typedef 类型名称 类型标识符
  • 记住,typedef是定义了一种类型的新别名,不同于宏,它不是简单的字符串替换。比如:
  • typedef void (*pFunx)(pFunParam); pFunx b[10];
  • friend 函数的意义
  • 在函数声明里定义的函数是 inline 函数,之外的话需要加 inline bool Stack::empty(),inline 的需要在头文件中定义,非 inline 则不用,扩展可以是.c .cc .cpp .cxx,
  • stack 接口,pop push peek full empty size find count
  • 构造函数,析构函数,初始化列表
  • 默认成员逐一初始化(传递相同的对象过去),是非拷贝复制,所以需要拷贝构造函数
  • 函数后面加入 const 确保不会修改传入的状态,也包括外界的状态
  • 返回引用会导致可能的修改,因此对 const 函数不能传出 &,要么就 const xxx&
  • 可以定义一个函数的 const 版本以及非const版本,会根据自身实例是const与否来对应调用
  • mutable 对象使得其修改不改变对象的 const 性质
  • this 指针自动加入对象的参数列表,*this 来获得对象自身
  • static 数据成员被类的所有实例共享
  • 静态成员函数可以直接通过类进行调用(其不能访问 static 数据)
  • iterator operator++()是前置++,后置 ++ 是iterator operator++(int)
  • iterator需要定义==,!=,++,*, 维护一个索引值
  • 运算符的参数必须至少有一个 class 类型,不能是指针
  • 类与其迭代器是分别定义的,在类中然后定义 begin end 来获取相应的 iterator 即可
  • 嵌套类型,在类中进行typedef xxx_iterator iterator,就可以xxx::iterator。
  • 两个类为 friend 后,就可以拥有与成员函数一样的权限
  • friend class xxx. friend xxx xxx::xxx();,提供原型进行 friend 即可,需要注意的是原型需要在之前就已经定义。
  • 拷贝赋值,需要返回一个this。
  • 函数对象是一种提供function call的class,一般函数对象提供给泛型算法, ()
  • find_if(begin, end, func), eg: iter = find_if(iter, end, func) != end.
  • ostream &os = cout, istream &is = cin)
  • iostream 也是一种运算符, operator<< >>。
  • ostream& operator<<(ostream &os, const &xxx)
  • istream& operator>>(istream &is, &xxx)

设计class 的时候

  1. 考虑 const 成员函数,const 对象只能调用 const 成员函数(大多数编译器只给警告)
  2. 考虑拷贝构造,拷贝赋值(这个需要返回 this 引用)。

面向对象

模板类

异常处理