c++ series - essential c++
最近打算重新学一遍C++,趁着京东打折买了很多书,其中c++的有
- essentizal c++
- effective c++
- more effective c++
- effective stl
- c++必知必会
- 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
- 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
- 什么是 this 指针
- 静态类成员
- 打造一个 iterator class
- 合作基于 friend
- 实现拷贝赋值操作
- 实现 function 对象
- 重载 iostream 运算符
- 指针,指向 类成员函数
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 的时候
- 考虑 const 成员函数,const 对象只能调用 const 成员函数(大多数编译器只给警告)
- 考虑拷贝构造,拷贝赋值(这个需要返回 this 引用)。