C++ primer plus读书笔记与心得
C++Primer plus读书笔记与心得
2020年过年期间,因新型冠状肺炎影响,推迟复工。根据公司读书计划,将c++ primer plus 这本书复习了一遍,并将其中一些章节中自己记忆模糊或者之前学习没注意到的学习点记录如下:
函数探幽(223)
-
内联函数
因函数调用存在开销(函数切换需要记录跳跃点,需要一些压栈和寄存器操作),所以在函数实现耗时较小时且频繁被调用且非递归调用,可以选择使用内联函数节省开销。
注意:内联函数是在预编译阶段确定的(底层实现宏),具体实现的内联函数是否真实被当作内敛函数使用,由编译器根据一定规则决定 -
引用变量
引用: 常性被引用类型的指针;当实参类型不确定或者与形参类型不匹配或表达式时,形参引用的是临时变量,函数调用完释放;当函数调用的参数需要用到结构体作为实参传递时,形参最好用引用,减少拷贝;函数的返回值如果是引用,不能返回函数中临时变量,会造成崩溃,此时需要以对象返回,通过构造临时变量; -
默认参数
-
函数重载
重载要求:1 参数数目 2 参数类型 3 参数排序 不同,与参数返回值无关。
多态:运行时多态:函数重载&虚函数;编译时多态:模板函数的模板类型实例化
注意:类型引用和类型被视为一样; const 修饰的区别也可造成重载,当实参和形参都有const时,优先选择const形参的函数,当实参有const 形参无const时,编译报错,当实参无const, 形参有const时,可以使用形参为const的函数。具体见下例 -
模版函数
函数模板的重载,编译器选择原型时,非模板函数 > 显示具体化 > 通用模板函数
内存模型和名称空间
- 单独编译
gcc只编译cpp文件,在预编译时期将.h文件和cpp文件结合生成临时的.cpp文件,编译器单独编译cpp文件生成.o对象文件,链接器将对象文件链接后生成可执行文件。
Ifndef define endif 在头文件定义使用的必要性。 - 存储持续性,作用域,链接性
-
register可建议编译器将变量放在寄存器中,但实际如何由编译器决定,对于放入寄存器的变量,不能取地址操作。
-
静态变量只初始化一次并且只能使用常量表达式进行初始化,并且存活周期是整个程序执行期间,默认初始值为0,作用域根据情况定:静态局部变量—函数内可见;静态全局变量—本文件可见;非静态全局变量–其他文件也可见,需要extend声明下;
-
静态变量仍可进行动态初始化(编译后初始化),编译器可根据文件(包含的头文件)就能计算表达式,如下例。
-
变量因其作用域,可能会隐藏变量或者被隐藏,此时需要增加作用域访问变量,显示指定其作用范围。::warming(默认访问全局作用域下的变量warming)
-
静态变量会隐藏全局变量——静态变量的内部链接性
-
mutable可使const定义的对象中mutable修饰的成员变量被修改。
-
const全局变量的链接性是内部的,即const修饰的变量本文件可见,如果需要跨文件可见,需要extend const int iA = 5,并且跨文件需要extend int iA。 常量是文件私有的。
-
extend “C” void func(int) 是以C的函数命名方式寻找函数func,c++ 采用名称修饰 与c的函数命名方式不同。
-
extend的三种用法:1 使全局变量应用于不同文件 2 使静态变量链接性为外部 3 c++兼容c 指定名称查找的协议。
-
普通new运算符和定位new运算符:定位new运算符托管以其他方式之前申请的空间。多次new 托管,地址空间不变,只是托管。但 不能用delete释放,可能造成崩溃,按托管前内存申请的方式释放。int * p = new(buffer)int。 工作原理是将地址返回并强转为void*。
-
- 名字空间
- Using 声明指定特定标识符;using编译指令使整个命名空间可用,功能是曝露标识符或者命名空间到一定的作用域下。using std::cout(using 声明);using namespace std(using 编译指令);
- using的使用可能会造成冲突,所以最好使用作用域,最好减小命名空间的使用范围。
- 命名空间具有传递性。
对象和类
- 类的构造和析构函数
- 提供构造函数时,必须提供默认构造函数,因为当提供构造函数时,编译器将不会再提供默认的构造函数了。调用默认构造函数的形式:stock first(“AA”); stock first; stock first=stock(); stock *pst = new stock; 这种属于函数调用 stock second() [有返回值,有函数名,有入参];
- 因局部变量位于栈上,所以先构造的对象后析构
- void stock::show() const. const位于函数后面,const 成员函数,限定函数不能修改类的成员变量的值。
- this 指针
- 数据的对象特有的,方法是对象共享的。当对象访问方法时,传入this指针指明使用哪个对象成员变量。
- 对象数组
- 接口与实现小结
修改类的私有部分,属于实现变更(改变内部工作原理),修改类的共有部分,属于接口变更(外部影响) - 类的作用域
- 类中定义一个静态常量,可以被所有对象所共享,但是此对常量不属于任何对象,属于类,通过类访问,存储在静态区。
- 抽象数据类型
- 使用模版类封装实现一个简单的栈。
使用类
- 操作符重载
- 为适应连加连减等连续操作,operate 的操作运算符重造函数返回值最好为对象,否则再进行第二次连续操作时会报错。A + B + C => A.operator+(B.operator© )加法运算从左到右结合。
- 重载限制:
- 不能重载系统操作类型,重载必须有一个用户自定义类型
- 重载不能改变原操作符意义和运算优先级
- 重载不能对特定运算符操作,例如sizeof * . 等
- 友元简介
- 友元函数:非成员函数访问类的成员变量,在类中声明此非成员函数为类的友元函数
- 当 n * time.hour时,可以通过友元函数实现。即非类的对象调用时,可通过类的友元函数实现
- 类的自动转换和强制类型转换
- 只接受一个参数的构造函数才允许类对象进行隐式类型转换。关键字explicit阻止类的隐式类型转换(仍可显示转换)。
- 将类对象转换为其他类型时,需要使用类型转换函数 operator typename();
- 将其他类型转换为类类型时 c_name(typename value) c_name—类名,typename 要转换的类型
类和动态内存分配
- 动态内存和类
对于需要动态申请内存的构造函数的类,也需要重写拷贝构造函数,赋值运算符,否则,析构函数可能析构掉用默认拷贝构造函数创建的对象(浅拷贝),导致内存崩溃。 - 类的隐式函数
- 类的5大隐式函数包括:1 默认构造函数;2 默认析构函数; 3 拷贝构造函数; 4 赋值操作符; 5 地址操作符。
- operator[] 可能会造成通过公有方法修改类的私有成员,所以需要对返回值和函数本身增加const修饰。
- 静态成员函数只能访问类的静态成员变量,因为类的静态成员函数不属于对象,属于类,所以不能访问对象的成员变量。
- 构造函数中使用new注意事项
- 拷贝构造函数:自赋值检查;之前空间的释放;新空间的申请;数据的拷贝;返回this对象;
- 布局new操作符需要:1 显示调用析构函数析构对象 pc3->~destory(); 2 释放空间 delete [] buffer
- 拷贝构造函数:自赋值检查;之前空间的释放;新空间的申请;数据的拷贝;返回this对象;
- 队列模拟
- 当类中的私有成员变量存在const成员时,必须使用构造函数初始化列表对成员变量进行初始化。
- 数据成员通过初始化列表被初始化的顺序和数据成员在类中被声明的顺序有关,和初始化列表顺序无关。
- 小技巧:将类的拷贝构造和赋值函数声明为私有,可以避免因浅拷贝造成的崩溃问题
类继承
- 简单的派生类
基类对象首先被创建(所以基类对象后被析构),通过构造函数初始化列表显示的构造基类对象。 - 基类和派生类之间的特殊关系
- 派生类对象可以访问基类的公有方法
- 基类对象指针或者引用可以指向派生类对象
- is-a关系
- Is-a 是一种所属关系. 公有继承
- has-a 是一种包含关系
- Is-like-a 是一种隐喻关系
- Is-implemented-as-a
- 多态公有继承
-
虚函数 派生类对象调用派生类中的虚函数,基类对象调用基类中的虚函数。
-
虚函数 引用的对象是啥类型,就调用其对应的函数。即将派生类对象赋值给基类引用时,基类引用调用的是派生类的函数。
-
析构函数一般声明为虚函数,确保释放派生类对象时,按正确的顺序调用析构函数。
-
派生类中调用基类的同名函数时,必须使用作用域解析符访问,否则,会认为是调用自身类的函数,会无限递归调用。
-
虚函数是动态联编的。
-
虚函数工作原理(哈哈,曾经的我还是蛮认真的嘛,看这笔记做的)
-
基类虚函数与继承类虚函数之间,如果参数不同,派生类会隐藏基类的函数实现。称继承类重新定义了类的方法,为隐藏。
-
函数重载,隐藏,覆盖:
- 重载,同类中,同名函数因特征标不同,其函数命名不同,导致的不同的函数。
- 重写,基类和派生类之间,基类的函数被派生类重新定义,称派生类隐藏了基类的方法。
- 覆盖,基类与派生类之间,基类存在虚函数,派生类重新实现了此虚函数(非重新定义),则称此为覆盖;
-
- 访问控制protected
- 对于继承类访问时公有的,对于类对象访问是私有的。
- 单例模式:通过类的私有构造函数和static成员只初始化一次的特性,构造单例模式。
- 抽象基类
- 包含纯虚函数的类不能定义对象。
代码重用
- 包含对象成员的类
- 通过调用成员类型的公有方法访问。通过初始化列表初始化类的所有成员变量。
- 友元函数可以访问类的私有方法,曾脑袋秀逗过,友元函数可以访问类的私有成员变量,当然也可以访问类的私有方法了。
- 私有继承
- 私有继承,has-a的关系。
- 默认私有继承。
- 派生类只需要包含派生类独有的成员变量即可,基类的成员变量已经被继承,通过类作用域符号显示调用基类的构造函数在派生类的初始化列表中进行构造派生类。
- 访问基类的方法通过基类的作用域符+基类公有方法访问。
- 访问基类对象时,需要将派生类的this指针在公有方法中强制转换为基类指针(引用)。
- 访问基类友元函数时,需要显示转为基类对象并调用友元函数。
- 私有继承不能直接将派生类的引用或者指针转换为基类指针或者引用,必须进行显示类型转换
- 包含对象和私有继承的区别
包含:简单,易于使用,通过派生类的成员对象(基类类型)直接访问相关函数;可以包含多个同类子对象,私有继承是包含无名的私有对象,所以只能通过类名访问成员函数。
私有继承:对于保护成员的属性继承与使用;对于虚函数的继承与使用。 - 继承关系
- 公有继承不改变基类的属性。
- 保护继承 基类的其他不高于的属性不变,高于的属性变为保护
- 私有继承 基类所有属性都变为私有。
- 多重继承
- 从两个或更多相同的相关基类处继承同一个类的多个实例。对于继承相同类的派生类,采用虚继承,这样在派生类中,就只包含基类的一个对象。
- 虚基类构造函数:显示调用基类的构造函数,显示调用派生类的构造函数。
- 调用其他方法:各管各的,在派生类中,只处理派生类特有成员的信息,在虚基类中,分别处理虚基类特有,派生类,基类。
- 其实就是需要站在编译器的角度,让编译器知道怎么去调用,否则,当存在二异性时,编译器就无能为力了。
看到上面这些记录后,我还挺惊讶的,之前学习《c++ primer plus》时,自觉还学习的挺扎实的,未曾想到过了3年的时间,很多东西没用起来,也都遗忘了。知识是需要经常复习的。
上面有很多可能造成崩溃的情况,例如:定位new 运算符释放时可能造成崩溃,细思极恐,对于这些可能造成崩溃情况都没了解,那写代码如果涉及这部分,就会很容易造成崩溃,看来基本功还是要抽时间搞搞扎实。
上面有很多可以节省效率的情况,例如,合理使用内联函数;根据情况使用对象引用而非对象(少调用一次拷贝构造函数或者赋值函数或者减少临时对象的生成)。
上面还有其他的一些细节使用:1 当定义函数时本意并无修改类的数据成员时,需使用const修饰*this,防止后面人迭代时,修改成员变量,改变函数定义初衷;2 在某些情况下使用using声明而非using命名空间可以减少命名冲突的概率。
最后,很感谢公司,组织这样的学习活动,希望这样的活动能经常举行。