Essential C++读书笔记
1 c++编程基础
- 如果没有在main()的末尾写下return语句,这一语句会自动加上
return 0 表示 mian()返回0,表示程序执行成功。
int main()
{
return 0;
}
- 对象不能以数字开头
- 被定义为const对象,在获得初值之后,无法再次改动。
const int max = 24;//correct
max = 12;//error
- switch中,default后加break;
- C++内置的数组 Array 的大小 必须是常量表达式
const int size = 10;
int seq[size];
- 可以用array初始化vector
- 指针具有双重性质:既可以操作指针包含的地址,也可以操作指针所指向的值。
- cstdlib头文件有rand()和srand(),配合使用产生伪随机数序列
2 面向过程的编程风格
- 如果用户输入不合理的值,最极端的是终止程序exit(),或者是抛出异常(第七章)
if (post <= 0)
exit(-1);
- 函数声明可以不写出参数名称:小规模程序中好用
#include <iostream>
bool func(int, int);
int main()
{
//......
}
bool func(int a, int b)
{
return (a == b)
}
- 两种函数参数传递方式 : 引用传递(by reference)和值传递(by value)
- 值传递:传给函数的参数被复制了一份到程序堆栈的局部中,原参数与副本之间没有任何关联。一旦完成函数,这块内存就被释放掉,pop出。
- 引用传递:面对引用的参数的所有操作都与原参数所进行的操作一致。如下所示:
void display(vector<int> &a)
- 加上const,可以让阅读人知道,以引用传递的方式传递vector,目的是避免复制操作,而不是为了要在函数中对它进行修改。
void display(const vector<int> &a)
- 也可以将vector以指针形式传递,传递的是地址,而不是副本
void display(const vector<int> *a)
- 除非你希望在函数内修改参数值,否则不要使用引用方式
- 在下面代码,无论以指针还是引用将elems返回,都不对,因为函数执行完之后,所指向或引用的对象不存在了。若以值传递方式返回,则没问题,因为返回的是值的副本,它在函数之外仍然存在。
vector<int> func(int size)
{
vector<int> res;
...
return res;
}
- 上述的操作:大多数c++编译器“以传值返回的类对象”,会优化程序,加上额外的引用参数。参考【LIPPMAN98】14.8
- 内存泄漏:程序员不用delete则由heap堆分配的对象永远不会释放。
- 局部静态对象static所处的内存空间,即使在不同的函数调用中,依然存在。如下:
const vector<int>* func(int size)
{
static vector<int> elems;
return &elems;
}
- 编译器无法通过函数返回类型区分两个具有相同名称的函数
- 重载函数的参数列表必须和其他重载函数的不同。
- 如果函数具有多种实现,可以将它重载,希望代码主体不变,仅仅改变数据类型,可以通过函数模板Function Template
- 函数的定义只能有一份,不过可以有许多声明。
- 唯一例外,inline函数的定义。将其放到头文件,每个调用点上都进行了定义。
3 泛型编程风格
- STL主要由两种组件组成:一是容器,二是操作容器的泛型算法。
- vector和list是顺序性容器:主要进行迭代
- map和set是关联容器:快速查找元素的值
- 被称为泛型,因为操作与元素类型无关,不直接操作容器,而是使用一对迭代iterator(first和second)
for (iterator it = vec.begin(); it != vec.end(); it++)
cout << *it << endl;
- 对于const vector,可以使用const_iterator来进行遍历,允许我们读值,但不允许写入。
const vector<int> vec;
vector<int>::const_iterator it = vec.begin();
- 若vec为空,则iter等于end();
- 对容器的迭代操作,始于begin()而终于end()
- 使用泛型算法,首先得包含对应的algorithm头文件
- 使用sort()函数,可以自己定义比较的函数
- adapter(适配器)将函数参数绑定至某值,如二元函数-》一元函数
- 查询某key是否存在于map,有三种方法:
- 把key当做索引使用 (如果key不存在,则会加入map中,而相应的key会设定为默认值)
int count = 0;
if (!(count = map["ws"]))
- 利用map中的find(),若存在,返回iterator指向该值(此find与泛型算法find不同)
map<string, int> test;
map<string, int>::iterator it;
it = test.find("ws");
if (it != test.end())
int count = it->second;//first key second value
- 利用map的count()函数 (返回特定key1的个数)
if (test.count("ws")) //存在则为true
- set为集合
4 基于对象的编程风格
- 成员初始化列表 紧跟着参数列表后面,以逗号分隔的列表
animal::speak(string words): _age(age), _sex(sex)
{
//......
}
- 析构函数无返回值也不可能被重载
- 若构造函数使用new表达式从堆中分配数组,则在析构函数中释放这些内存。
- 成员逐一初始化
animal cat(..., ...);
animal dog = cat;
若cat中含有堆分配的数据,构造中new,析构中delete
此时将cat赋值给dog,dog析构会将释放内存,此时cat中仍然可能使用改数据,发生错误。
- 复制构造函数,参数是const reference
animal::animal(const animal &ani)
产生一个独立的数据副本,这样使得某个对象的西公园不会影响另一个对象。
- mutable 与 const
- const紧跟在函数参数列表之后,使该函数明白,不能修改调用者的值
animal::animal(const animal &ani)
{
string name = ani.name();
}
int animal::name() const {} //因为ani为引用,name函数不能修改ani的值
- 非const 类型的参数,会调用非const版本的成员函数
- const 类型的参数,会调用const版本的成员函数
- murtable 类型可以使得成员函数既可以修改,又可以被声明为const函数
- this指针在成员函数中指向其调用者(一个对象)
- 返回对象可以使用*this
animal& animal::speak(const animal &ani)
{
//......
return *this;
}
- 运算符重载
不可以引入新运算符
操作个数不可变
运算符优先级不变
参数列表中必须至少一个为class
- 友元friend的建立,通常是为了考虑效率。
- 如果只是希望进行某个数据的读取与写入,那么为他提供具有public访问权限的inline函数,就是建立友元friend的替代方案。
5 面向对象编程风格
-
继承和多态
-
父类(基类)和子类(派生类)
-
父类定义了所有子类的公有接口和私有实现。每个子类可以增加或者覆盖继承来的东西。
-
动态绑定 第三个独特概念,如依据父类对象所指向的子类对象来决定调用哪个成员函数。
-
多态让我们以一种与类型无关的方式来操作类对象
-
多态和动态绑定的特性,只有在使用指针或者引用是才发挥。
-
成员函数动态绑定,需要加上virtual
-
定义抽象基类第一个步骤是找出所有子类的共同特性
-
虚函数=0,则为纯虚函数
-
只要类声明有一个或多个纯虚函数,由于其接口的不完整性(纯虚函数没有定义)程序无法产生任何对象。不能实例化
-
定义派生类,一个是基类构成的子对象,另一个是派生类的部分。
-
类进行继承声明之前,其基类定义必须已经存在。
-
子类必须为每个纯虚函数提供实现。
-
已经指向或者引用子类的父类对象,无法调用子类的非基类提供的函数接口。
解决方法:
- 在基类中加入virtual 函数,子类之前的派生出来的非基类接口重写虚函数
- 将这部分接口移动至基类。
-
每当派生类中有某个成员与基类重名,则覆盖基类的那份成员。
-
如果要在派生类内使用继承来的那个成员,使用::作用域解析符限定
-
较好的设计方式,基类提供构造函数,并完成基类所声明的所有数据成员操作。
-
派生类的初始化,包括基类的构造函数以及自己的构造函数
-
定义派生类时,必须决定,将基类中的虚函数覆盖掉哈市原封不动的加以继承。继承了纯虚函数免责这个派生类也成为了抽象类。
-
在派生类中,重写virtual 函数时,关键字virtual可以不写
-
虚函数静态解析:此两种情况下,虚函数不会出现预期行为:
- 基类构造函数与析构函数(调用虚函数,为自己的函数)
- 当使用基类对象,而非基类对象的指针或者引用时。(多态需要一层间接性,唯有使用基类的指针或引用才能支持面向对象编程)
-
类型鉴定机制,typeid运算符