《C++ Primer》学习笔记

这次一定要完整学完

2022/8/24
像编辑器一样思考和理解C++

  • C++的编程风格:C风格、基于对象、面向对象、泛型和基于组件。
  • 初学建议《C++Primer》和《C++标准程序库》
    Scott Meyers的《Effective C++》
    Anthony Williams 《C++ Concurrency in Action》:学习标准C++开发并发程序
    陈硕《Linux 多线程服务端编程》:学习C++服务端开发
  • 函数的定义包含四个部分(从前往后想):返回类型、函数名、形参列表、函数体

2022/8/25

  • 集成开发环境(Integrated Developed Environment,IDE)
  • 源文件:程序源码存放的地方
  • 命令行运行C++程序
>g++ 文件名.cpp
>文件名.exe
  • 在cmd里可以直接运行,在Windows PowerShell里运行exe需要加.\。

  • 输入输出流:一个流就是一个字符序列,是从IO设备读出或写入IO设备的。流的意思是想表达随着时间的推移,字符是顺序生成或消耗的。

  • 标准库iotream:有cin、cout、cerr、clog
    cerr:输出警告和错误信息,也被称为标准错误
    clog:用来输出程序运行时的一般性信息
    运算符两边都是运算对象

  • ndl是一个操纵符
    endl是结束当前行,将于设备关联的缓冲区中的内容刷到设备中。
    缓冲刷新操作可以保证到目前为止程序所产生的所有输出都真正写入到输出流中,而不是仅停留在内存中等待写入流

  • std是命名空间,命名空间可以避免不经意的名字定义冲突。(命名空间是将库定义的名字放在一个单一位置的机制)

2022/8/29

  • 条件的定义:所谓条件是产生一个真或假的结果的表达式
  • 复合赋值运算符+=
    前缀递增运算符++
  • for最初设计是因为while语句使用频繁,专门定义的第二种循环语句
  • 每个for语句都包含两部分:循环头和循环体。循环头由三个部分组成:一个初始化语句、一个循环条件和一个表达式
  • for循环和while循环的优缺点:
    在循环次数已知的情况下,for循环的形式更加简洁。
    而循环次数无法预知时,while更合适。用特定条件控制循环是否执行,循环体中执行的语句可能导致循环判定条件发生变化。

2022/8/30

  • 使用istream对象作为条件时,其兄啊过是检测流的状态,遇到文件结束符时可跳出,如windows下是Ctrl+z,然后回车。
  • C++中我们通过定义一个类来定义自己的数据结构

2022/9/1

  • Sales_item.h
#ifndef SALESITEMS_H
#define SALESITEMS_H
#include
#include

class Sales_item{
	public:
		Sales_item(const std::string &book):isbn(book),units_sold(0),revenue(0.0){}
		Sales_item(std::istream &is){
			is >> *this;
		}
		friend std::istream& operator>>(std::istream &, Sales_item &);
		friend std::ostream& operator<<(std::ostream &,const Sales_item &);
	public:
		Sales_item & operator+=(const Sales_item&);
	public:
		double avg_price() const;
		bool same_isbn(const Sales_item &rhs)const{
			return isbn == rhs.isbn;
		} 
		Sales_item():units_sold(0),revenue(0.0){}
	public:
		std::string isbn;
		unsigned units_sold;
		double revenue;
};
using std::istream;
using std::ostream;
Sales_item operator+(const Sales_item &,const Sales_item &);

inline bool operator==(const Sales_item &lhs,const Sales_item &rhs){
	return lhs.units_sold == rhs.units_sold && lhs.revenue == rhs.revenue && lhs.same_isbn(rhs);
}

inline bool operator!=(const Sales_item &lhs,const Sales_item &rhs){
	return !(lhs == rhs);
}
inline Sales_item & Sales_item::operator+=(const Sales_item &rhs){
	units_sold += rhs.units_sold;
	revenue += rhs.revenue;
	return *this; 
}
inline Sales_item operator+(const Sales_item &lhs,const Sales_item &rhs){
	Sales_item ret(lhs);
	ret += rhs;
	return ret; 
} 
inline istream& operator>>(istream &in,Sales_item &s){
	double price;
	in >> s.isbn>>s.units_sold>>price;
	if(in)
		s.revenue = s.units_sold * price;
	else
		s = Sales_item();
	return in;
}
inline ostream& operator<<(ostream &out,const Sales_item &s){
	out << s.isbn<<"\t"<<s.units_sold<<"\t"<<s.revenue<<"\t"<<s.avg_price();
	return out;
}
inline double Sales_item::avg_price() const{
	if(units_sold)
		return revenue/units_sold;
	else 
		return 0;
}
#endif

对于不属于标准库的头文件,用包围。

  • 大多数操作系统支持文件重定向,允许我们将标准输入和标准输入与命名文件结合起来。
  • 成员函数是定义为类的一部分的函数,有时也称为方法
  • 点运算符(.)只能用于类类型的对象,当使用点运算符访问一个成员函数时,使用调用运算符来调用一个函数()

2022/9/4

  • 缓冲区(buffer):一个存储区域,用于保存数据。IO设施通常将输入(输出)数据保存在一个缓冲区中,读写缓冲区的动作与程序的动作是无关的,我们可以显式地刷新输出缓冲,以便强制将缓冲区中数据写入输出设备。
    缓冲区就是缓存,为了解决速度不匹配问题:如CPU和内存、内存和硬盘、CPU和IO之间速度不匹配问题
  • ::作用域运算符,其用处一直是访问命名空间的名字
  • C++是一种静态数据类型语言,其类型检查发生在编译时,因为编译器必须知道程序中每一个变量对应的数据类型

2022/9/5

  • 基本数据类型可分为算数类型和空类型。算数类型分为两类:整型(字符、布尔值)和浮点型。
  • long long 64位、long 32位、int 16位
  • 一个char大小和一个机器字节一样。
  • 可寻址的最小 内存块 称为字节,存储的基本单元称为字,字由字节组成,字节8bit,字由32或64bit组成,也就是4或8字节。为赋予内存中某个地址明确含义,必须先知道存储在该地址的数据的类型。类型决定了数据所占比特数 。
  • unsigned char 表示0~255区间的值,signed char表示-127至127区间的值,大多数计算机实际范围为-128 ~ 127
  • 执行浮点数运算选用double,因为float 精度不够而单双精浮点数计算代价相差无几,long double计算代价较大,一般情况下不会采用。
  • 浮点数赋给整数类型,结果值仅保留浮点数中小数点之前的部分。
  • 赋予无符号类型一个超出它类型范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。如8比特的unsigned char范围(0,255),超出这个范围对256取模后的余数,如-1即是255.
  • 当一个算术表达式既有无符号数又有int值的时候,int值会转换成无符号数。如果无符号值的计算结果小于0,则是取模后的值
  • 字面值常量前面是0代表八进制,0x或0X代表十六进制。
  • 编译器在每个字符串的结尾处添加一个空字符(‘\0’),字符串实际长度比它的内容多1,实际上是相当于由常量字符构成的数组。
  • 一对单引号是字符,一对双引号是字符串,字符串有一个字母时实际上是两个字符
  • . 反斜线\后面八进制数字超过3个,只有前3个数字与\构成转义序列

2022/9/6

  • 变量是提供一个具名的,可供程序操作的存储空间。变量和对象一般可以互换使用。
  • 对象是指一块能存储数据类型并具有某种类型的内存空间。
  • 初始化和赋值是两种完全不同的操作 。
    初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。

2022/9/18

  • 对于内置类型变量,即定义在函数体内部的,若是未被初始化即是未定义的。
  • C++支持分离式编译。定义和声明是分离开的:声明使名字为程序所知,一个文件若想使用别处定义的名字必须先对名字进行声明。定义是负责创建于名字关联的实体。
    声明时需要加extern 和类型,但不能赋予初值,赋初值会变成定义。
  • C++是一种静态类型语言,在编译阶段检查类型:这个过程称为类型检查
  • 用户自定义的类名一般以大写字母开头,如Sales_item
  • C有32个关键字,C++有63个
  • C++中对于一些操作符有替代名,如and,也不能被定义成变量名。
  • . 分全局作用域和块作用域。
  • 作用域操作符(::),当全局作用域没有名字时,作用域操作符左侧为空时,向全局作用域符发出请求获取作用域 操作右侧符名字对应的变量。
  • 引用和指针+其他定义类型属于复合类型。

2022/9/26

  • 引用为对象起了另一个名字。初始化变量时,初始值会被拷贝到新建的对象中。定义引用时,程序把引用和初始值绑定在一起,而不是将初始值拷贝给引用
  • 指针:指向另外一种类型的符合类型,指针本身是一个对象,允许对指针赋值和拷贝。
    指针存放某个对象的地址,要想获得地址,就需要取地址符
    指针的值即地址:指向一个对象。指向紧邻对象所占空间的下一个位置;空指针,意味着指针没有指向任何对象;无效指针
    操作符(*) 是解引用符
  • 目前有nullptr(空指针),之前有NULL (预处理变量)
  • void*可用于存放任意对象的地址
  • 指针无需赋初值,引用必须赋初值

2022/9/27

  • 类型修饰符只是声明符的一部分
  • int *p,p是指向int的指针
  • 引用本身不是一个对象,因此不能定义指向引用的指针,但指针是对象,所以存在对指针的引用
  • const 定义一个变量时必须初始化,但初始化后就不能在改变这个值。const对象仅在当前文件内有效,如果想在不同文件中使用,需要添加extern
  • 初始化常量引用时允许用任意表达式作为初始值,但需要表达式的结果能转换成引用的类型。int *const curErr不变的是指针本身的值而非指向的那个值。指针本身是一个常量并不意味着不能通过指针修改其所指对象的值,完全依赖于所指对象的类型。
  • 顶层const,表示指针本身是一个常量,不能改变;底层const,表示指针所指的对象是一个常量,可以改变
  • 常量表达式是指值不会改变并且编译过程就能得到计算结果的表达式,const/constexpr/字面值类型,如果定义的是一个指针,那么,constexpr只对指针有效,与指针类型无关

2022/11/22

  • 类型别名是一个名字,某种类型的同义词,传统方法使用typedef。新方法使用别名声明,用using 作为别名声明的开始
    typedef double wages;
    using Si = Sales_item;
  • typedef char *pstring是声明一个指向char的常量指针,const char *cstr = 0是声明一个指向const char的指针。
  • auto定义的变量必须有初始值,auto是C++11的标准引入的
  • 从表达式的类型推断出要定义的类型,但不想用该表达式的值初始化变量,引用decltype,作用是选择并返回操作数的数据类型decltype加(())就是引用,引用就需要初始化。

2022/11/23

  • 以类的形式自定义数据类型
  • 类体右侧的表示结束的花括号必须写一个分号,这是因为类体后可以紧跟变量名以表示对该类型对象的定义
  • 类体定义类的成员,我们的类只有数据成员。
  • 头文件中通常包含那些只能被定义一次的实体,如类,const和constexpr变量
  • 当预处理器看到#include标记时就会用指定的头文件内容代替#include,ifdef 当且仅当变量定义为真 ,判断给定变量是否已定义,#ifndef当且仅当变量未定义时为真,判断给定变量是否尚未定义,一旦检查为真,则执行后续操作直至#endif为止
  • 整个程序中的预处理变量包括头文件保护符必须唯一,通常是基于头文件中类的名字来构建保护符的名字,
  • 定义就是为某一特定类型的变量申请存储空间
  • 类的组成部分被称为成员,对象是内存的一块区域,具有某种类型,变量是被命了名的对象
  • 指针是一个对象,存放着某个对象的地址
  • & 取地址运算符,* 解引用运算符,解引用一个指针将返回该指针所指的对象,为解引用的结果赋值也就是为指针所指的对象赋值
  • string和vector是最重要的标准库类型,string 支持可变长字符串,vector支持可变长的集合,迭代器用来访问他们中的元素。

2022/11/24

  • string s4(n,‘c’)把s4初始化为连续n个字符c组成的串
  • =是拷贝初始化,(,)是直接初始化
  • s是string类型,则s.empty()判断是否为空,s1+s2是两个字符串进行拼接
  • getline(,)里是一个输入流和string对象,函数给定的输入流读入内容,直至换行符为止。endl可以结束当前行并刷新缓冲区
  • string(对应位置一致时,比较长度)和(在对应位置不一致时,比较结果是string对象中第一对相异字符比较的结果)
  • cctype头文件中定义了一组标准库函数判断某个字符的特性
  • C++规定只有左侧运算对象为真实时才会检查右侧运算对象的情况

2022/11/25

  • vector中表示对象的集合,其中所有的对象类型相同

2022/11/26

  • 模板本身不是类或函数,可以将模板看作编译器生成类或函数的一份说明。编译器根据模板创建类或函数的过程称为实例化
  • vector a()中(,)第一个表示初始化数量,第二个表示值;{}中都是值,花括号可以理解成列表初始化
  • vector若是{}花括号可表示数量
  • push_back()负责把一个值当成vector对象的为元素push到vector对象的尾端

2022/11/27

  • vector不能用下标形式添加元素,可以对确定已存在的元素执行下标操作,否则可能会出现缓冲区溢出
  • end成员负责返回指向容器“尾元素的下一位置”的迭代器,意思指向容器中一个本不存在的尾后元素称为尾后迭代器

2022/11/28

  • 整数的递增是整数值上加1,迭代器的递增是将迭代器向前移动一个位置
  • end返回的迭代器并不世纪指示某个元素,所以不能对其进行递增或解引用的操作
  • 因不清楚迭代器的类型,使用iterator和const_iterator表示迭代器的类型,常量可用const_iterator,能读取不能修改;iterator的对象可读可写。
  • 若无需写操作只需读操作的化,为便于得到const_iterator类型的返回值,可以用cbegin和cend
  • 解引用符可获得迭代器所指的对象,加圆括号和点运算符
  • 箭头运算符把解引用和成员访问两个操作结合在一起,it->mem 和(*it).mem作用相同

2022/11/29

  • 数组中元素的个数也属于数组类型的一部分,编译时维度应该是已知的,意思就是不变的。维度也可以根据初始化的数量计算出来。
  • 定义数组不能使用auto,必须指定类型
  • 字符数组在拷贝字符串时,注意字符串字面值的结尾处还有一个空字符,也会被拷贝进字符数组中
  • 一般不能使用一个数组初始化另一个数组或直接赋值给另一个数组,但有些编译器扩展可以,最好不用
int *ptrs[10] 大小为10的数组,存放的是指向int的指针
int (*parray)[10] = &arr parray是一个指向包含10个元素数组的指针
int (&arrRef)[10] = arr arrRef是一个引用,引用的对象是一个大小为10的数组
  • 在使用数组时编译器一般会把数组转化为指针。用取地址符来获得指向某个对象的指针,取地址符可用于任何对象
  • string *p2 = nums;约等于 P2 = &nums[0]
  • 如果一个指向数组的指针超出了数组的上限,那么会指向一个不存在的元素用于初始化该指针,可称为尾后指针,不知指向具体的元素,不能进行解引用或递增的操作。
  • end()指针是指向尾元素的下一位置的指针
  • 一个指针指向数组的名字,则其等价于指向数组的第一个元素

2022/11/30

  • C风格字符串是存放在字符数组中并以空字符结束,空字符(‘/0’)
  • String相关函数,定义在cstring中,如果用数组使用,必须以空字符串作为结束
strlen( p ) 返回p的长度,但不包含空字符串
strcmp(p1,p2) 比较p1和p2相等性
strcat(p1,p2 p2附加p1后,返回p1
strcpy(p1,p2) p2赋值给p1
  • 重要:当使用数组时真正用的是指向数组首元素的指针
  • 可以将数组拷贝给vector,其他就不可以了。如vector vname(begin(a),end(b))
  • 严格说,C++语言中并没有多维数组,通常说多维数组其实是数组的数组
  • 用数组内的元素循环改变,要加上引用类型&,否则是转向该数组内首元素的指针
  • 缓冲区溢出主要是通过一个越界的索引访问容器内容
  • 实例化指的是编译器生成一个指定的模板类或函数的过程

2022/12/1

  • 一元、二元运算符就是作用于一个或两个运算对象
  • 在运算时中间出现类型转换则一般小整数类型通常会被提升为大整数类型
  • 运算符作用于类类型的运算对象时,用户可以自行定义其含义,称为重载运算符
  • &&运算符规定了先计算左侧的值,如果左侧为真再计算右边,若为假直接不看右边了

2022/12/2

  • 关系运算符作用于算术类型或指针类型,大于小于等于;逻辑运算符作用于任意能转换成布尔值的类型,与或非。
  • &&和||都是短路求值,即先求左侧运算对象再求右侧运算对象
  • 关系运算符比较运算对象的大小后返回布尔值

2022/12/3

  • 任何一种复合运算符都等价于啊a = a op b;包括位运算符,<<=,^=等
  • 除非必须,尽量使用递增递减运算符的后置版本,因前置版本避免不必要的工作,因后置版本还要存储下来

2022/12/4

  • 点运算符获取类对象的一个成员
  • ptr->mem等价于(*ptr).mem
  • 条件运算符满足
  • 位运算符提供检查和设置二进制位的功能,左移《右移》运算符是令左侧运算对象的内容按照右侧运算对象要求移动指定位数,然后将经过移动的左侧运算对象的拷贝作为运算结果。超出边界的位移会被丢弃。
  • 单个的与(&)、或(|)、异或(^)

2022/12/5

  • 位与 或 位或 都是两个运算对象相同或相异时,而位异或(^)有且只有一个为1时才为1,与是只有一个为1则为0。或是两个都是1时为1,而位异或是两个1则为0
  • 无符号类型和带符号类型运算,有符号的大概率会转换成无符号的。
  • 在使用数组的表达式中,数组会自动转换成指向数组首元素的指针
  • 0或字面值nullptr能转化成任意指针类型;指向任意非常量能转换成void*;指向任意对象能转换成const void*
  • 显示转换也称作强制类型转换
  • 花括号括起来语句和声明序列是复合语句,也称为块
  • 在for语句中省略条件语句就会无休止的执行下去,除非循环体内有其他判定条件
  • 传统for(;;)和范围for(declaration;expression)
  • 在循环体中使用一个值并不断对其改变并使用,则可以把声明为引用类型。
  • 不能使用范围for对vector进行修改,因为一旦添加或删除元素,end函数则可能变得无效
  • break 终止离它最近的while,do while ,for ,switch
    continue 终止最近的循环中当前迭代并立即开始下一次迭代,使用在for,while,do while中

2022/12/7

  • 数组有两个性质:不允许拷贝数组;使用数组会将其转换成指针,不能以指传递数组,实际上传递的是指向数组首元素的指针
  • 在形参中写数组中包含多少个元素,可以写,实际可以不一样,形参中(const int*)、(cosnt int[])、(const int[10])是等价的
  • 当函数不需要对数组元素执行写操作时,数组形参应是指向cosnt的指针。
  • 指向数组的指针 int (*name)[10]
  • 当用户通过设置一组选项来确定函数要执行的操作时,如main函数位于可执行文件prog之内,我们可向程序传递prog -d -o ofile data0
    这些命令通过两个形参传递给main函数
    int main(int argc, char *argv[]){...}
    第二个argv是一个数组,是指向字符串的指针;第一个argc形参是数组中字符串的数量
    argv[0]传递的是程序的名字

2022/12/8

  • void函数如果不加return ,在该函数的最后一句可以隐式执行return
  • 返回的值用于初始化调用点的一个临时量,该临时量就是函数调用的结果
  • 函数完成后,其所占用的存储空间也被随之释放掉。函数终止意味着局部变量的引用将指向不再有效的内存区域
  • C++11中规定,函数可以返回花括号保卫的值的列表
  • 如果函数返回的是内置类型,则花括号包围的列表最多包含一个值,而且该值所占空间不应大于目标类型的空间。如果返回的是类类型,则由类本身定义初始值如何使用。
  • cstdlib头文件有两个返回值,EXIT_FAILURE,EXIT_SUCCESS,这两个是预处理变量,所以前面不能加std::,或不能在using声明中出现
  • 在递归函数里,一定有某条路径是不包含递归调用的。否则,函数将不断调用自身知道程序栈空间耗尽为止。
  • 知道函数返回的指针指向哪个数组,就可以使用decltype关键字声明返回类型,在函数名那有

2022/12/9

  • 重载就是函数名相同,形参列表不同
  • 形参可以只加类型,不加名字,不影响形参列表的内容
  • 因非常量可以转换成const,所以加不加const都能作用于非常量对象或指向非常量对象的指针。编辑器会优先选用非常量指针
  • 在内层作用域里声明名字,会隐藏外层作用域中声明的同名实体,如果在函数中提前声明了一个库函数的名字,会调用自己定义的那个函数

2022/12/10

  • 函数调用包含工作:调用前保存寄存器,并在返回时修复;可能需要拷贝实参;程序转向一个新位置继续执行。

2022/12/11

  • 内联函数是再函数的返回类型前加上inline,可以消除函数运行时的开销,常用于优化规模小、流程直接、频繁调用的函数
  • constexpr函数是在编译时将函数的调用替换成结果值,函数体内有一条return语句,可以解决
  • assert是一种预处理宏,常用于检查不能发生的条件,其依赖于NDEBUG,如果有则assert就不用执行,没有时才执行检查,可以从#define语句中定义
  • 函数匹配第一步:选定本次调用对相应的重载函数集,集合中函数称为候选函数(候选函数声明在调用点可见);第二步:考查调用实参->个数->实参类型是否与形参匹配。
    如果都没有,编辑器会报二义性错误
  • 如果两个函数的唯一区别时指针形参指向常量或非常量,则编译器能通过实参是否是常量决定用哪个函数:如果实参是指向常量的指针,调用形参是const*的函数,如果室参事只想非常量的执行,调用形参是普通指针的函数。

2022/12/12

  • 函数指针指向的是函数而非对象
  • 当我们把函数名作为一个值使用时,该函数自动转换成指针;可以直接使用指向函数的指针调用该函数,无需提前解引用指针。
  • 重载函数的指针就必须精准定义
  • 函数中形参也可以是函数类型,实际上是当成指针使用
  • decltypr(函数名)可以简化书写函数指针返回类型的过程。它作用于某个函数时,返回的是函数类型而非指针类型,除非加上*

2023/01/14

  • 类的基本思想是数据抽象和封装。数据抽象是依赖于接口和实现分离的编程技术
  • 成员函数通过一个名为this的额外隐式参数来访问调用。定义了一个对象,对象调用成员函数时相当于类内成员传入了对象的地址

2023/02/08

  • 把this设置成指向常量的指针有利于提高函数的灵活性。将this声明成指向常量的指针,const放在成员函数的参数列表之后,紧跟参数列表后的const表示this是指向常量的指针,使用const 的成员函数被称为常量成员函数。
  • return *this
    return语句解引用this指针以获得执行该函数的对象
  • 每个类都分别定义它的对象被初始化的方式,通过一个或几个特殊的成员函数来控制其对象初始化的过程,这些函数叫做构造函数。构造函数作用是初始化类对象的数据成员,只要类的对象被创建,就会执行构造函数。
  • 编译器创建的构造函数又被称为合成的默认构造函数
  • 没有出现在构造函数初始值列表中的成员将通过相应的类内初始值初始化或执行默认初始化
  • 类的拷贝:初始化变量以及以值的方式传递或返回一个对象
  • 销毁:一个局部对象在创建它的块结束时被销毁

2023/02/09

  • public 定义类的接口;private 说明符之后的成员可以被类的成员函数访问,但不能使用该类的代码访问,private封装了类的实现细节

2023/02/12

  • delete 去释放申请的内容空间,一般和new一起用,否则会有安全隐患(delete可以直接用

2023/02/15

  • struct 和class都可以定义类,只是默认访问权限不一样。当希望类的所有成员是public时,使用struct,当希望类的所有成员是private时,使用class
  • 类允许其他类或函数访问它的非公有成员,方法时令其他类或函数成为它的友元。只需增加一条以friend关键字开始的函数声明语句
    友元在什么时候有用,请列举出使用友元的利弊:当非成员函数确实需要访问类的私有成员时,我们可以把它声明成该类的友元。此时,友元可以工作在类的内部,像类的成员一样访问类的所有数据和函数。但是一旦使用不慎,就有可能破坏类的封装性。

2023/02/18

  • 一些类的特性 ,包括类型成员、类的成员的类内初始值、可变数据成员、内联成员函数、从成员函数返回*this、定义和使用类类型及友元类

2023/02/19

  • 为什么先定义在使用,如果先使用的话无法分配内存空间
  • 因编辑器不会自动生成默认的构造函数,需定义一个类名()=default 告诉编译器为我们合成默认的构造函数
  • 重载是相同函数名,不同参数数量或位置;重写是子类重新定义父类函数,需要相同函数名和条件。
  • 如果修改类中即使是const的某个成员函数,需要在变量的声明中加入mutable
class Screen{
public:
	void some_member() const;
private:
	mutable size_t access_ctr;
};
void Screen::some_member() const{ ++access_ctr;}
  • 一个const如果以引用的形式返回*this,则返回类型是常量引用。对于非常量的函数来说常量对象是不可用的,只能在一个常量对象上调用const成员函数
  • 类的内部定义被调用的函数,会被隐式的声明成内联函数,不会增加任何额外的开销
  • 可以把类名作为类型的名字使用,从而直接指向类类型
  • 一个类如果只声明了,在它声明之后定义之前是一个不完全类型:使用情景非常有限,比如可以定义指向这种类型的指针或引用,也可以声明以不完全类型作为参数或者返回类型的函数。但到创建对象之前必须被定义,类允许包含指向它自身类型的引用或指针。
  • 其他类或其他类的成员函数都能定义成友元,这些函数是隐式内联的,能访问非公有成员在内的所有成员。
  • 友元关系没有传递性
  • 类的作用域外,普通数据和函数成员只能由对象、引用或指针使用成员访问运算符来访问
  • 在类的外部定义成员函数时必须同时提供类名和函数名的原因是一个类就是一个作用域,类的外部,成员的名字被隐藏
  • 作用域和定义在类外部的成员这一节,我的理解是,用类内部变量定义返回类型时,需要在返回类型前加上类名
  • 类的定义:①编译成员的声明。②直到类全部可见后才编译函数体
  • 就是说类内和类外有个相同变量名的声明,就算写在类之前,也优先使用类内的,这不和函数一样吗
  • 初始化和赋值事关底层效率问题:初始化直接初始化数据成员,赋值先初始化在赋值

2023/02/20

  • 类中构造函数初始化与在构造函数中位置没有关系,与定义时在类中出现的位置有关
  • 当一个构造函数委托给另一个构造函数时,受委托的构造函数的初始值列表和函数体被依执行,先执行这些代码,然后控制权才会交给委托者的函数体
  • 如果构造函数只接受一个实参,则它实际上定义了转换为此类 类型的隐式转换机制,这种构造函数称作为转换构造函数。
  • 聚合类时用户可直接访问其成员,该类所有成员都是public、没定义任何构造函数、没类内初始值、没有基类、没构造函数。如果要进行初始化,其初始值的顺序要与声明的顺序一致
  • constexpr 函数的参数和返回值都必须是字面值类型,聚合类可以认为是一个字面值常量类
  • 可以使用类的对象、引用或指针来访问静态成员;成员函数不用通过作用域运算符就能直接使用静态成员
  • 不完全类型是已经声明但是尚未定义的类型
  • struct 默认是public,class 默认是private
  • this指针,是一个隐式的值,作为额外的实参传递给类的每个非静态成员函数,this指针指向代表函数调用者的对象。

2023/02/21

  • iostream定义了读写流的基本类型;fstream定义了读写命名文件,sstream定义了读写内存string对象的类型
  • 继承机制可以声明一个特定的类继承自另一个类,可将一个派生类对象当作其基类对象来使用
  • 不能拷贝或对IO对象赋值,不能将形参或返回类型设置为六类型
  • strm::badbit ,表示流已崩溃
  • 流可能处于错误状态,代码通常应该在使用一个流之前检查它是否处于良好状态。确定一个 流对象状态的最简单方法是将其作为一个条件来使用,我们将流作为条件使用代码等价于!fail()
  • cin.setstate(old_state)将cin置于原有状态
  • cin.ignore()表示从输入流 cin 中提取字符,提取的字符被忽略,不被使用,常用功能就是用来清除以回车结束的输入缓冲区的内容,消除上一次输入对下一次输入的影响。
endl 换行,刷新缓冲区
ends 空字符,刷新缓冲区
flush 刷新缓冲区,不添加任何额外字符
  • unitbuf:接下来每一次操作都进行一次flush操作,nounitbuf重置流,回到正常操作方式
  • 可以用继承类型的对象来代替基类型对象,如果一个函数接受ostream&参数,调用它时,可以传递一个ofstream对象,对istream&和ifstream类似
  • fstream对象被销毁时,close会自动被调用
  • 文件模式就是在要打开文件地方,选择输入
    in(以读方式打开)out(以写方式打开)app(每次写操作前均定位到文件末尾) ate(打开文件后立即定位到文件末尾)trunc(截断文件) ,一般打开out模式就是截断模式
  • 将vector中<>的类型定义成新的类作为变量,就可以读取两种格式了
  • 结构对象和字符串对象,使用引用可以避免对象拷贝

  • 第九章顺序容器开始相对基础知识更重要了
  • 元素在顺序容器中的顺序与其加入容器时的位置相对应,关联容器中元素的位置有元素相关联的关键字值决定。
  • 一个容器就是一些特定类型对象的集合。顺序容器提供了控制元素存储和访问顺序的能力
  • 应用中占主导地位的操作决定容器类型的选择
类型 定义 优点 缺点 应用场景
vector 可变大小数组 支持快速随机访问,元素连续存储所以由元素的下标来计算其地址十分快速,可用范围广 在尾部之外的位置插入或删除元素可能很慢 需随机访问元素时
deque 双端队列 支持快速随机访问,在头尾位置插入/删除很快 中间删除或插入代价很高 需随机访问元素时
list 双向链表 在list中任何位置进行插入/删除操作速度都很快 只支持双向顺序访问,不支持元素随机访问 要求在中间插入或删除元素
forward_list 单向链表 在列表任何位置进行插入/删除操作都很快 只支持单项顺序访问,不支持元素随机访问 要求在中间插入或删除元素
array 固定大小数组 支持快速随机访问,与内置数组相比,更安全,更易使用 不能添加或删除元素以及改变容器大小
string 可变大小数组,用于专门保存字符 随机访问快,在尾部插入/删除快

2023/02/22

  • 在没有默认构造函数的时候,构造这种容器不能只传递一个元素,还得提供一个元素初始化器vector v2(10, init);
  • iterator此容器类型的迭代器类型,容器对象的.size()函数不是大小,而是指的元素的数目
  • 迭代器中last()不是指向范围内的最后一个元素,而是指向尾元素之后的位置,这种元素范围被称为左闭合区间。
  • 问:构成迭代器范围的迭代器有何限制
    答:两个迭代器,begin()和end()必须指向同一容器中的元素,或是容器最后一个元素的位置;而且,对begin反复进行递增操作,可保证达到end,即end()不在begin之前。
  • c开头的返回的const迭代器只读访问,无写访问时,应该使用;普通迭代器可以修改。
  • C c(b,e)c初始化为迭代器b和e指定范围的元素拷贝。范围中元素的类型必须与c元素类型相容
  • 为了创建一个容器为另一个容器的拷贝,两个容器的类型和元素类型必须匹配
  • 内置数组不支持拷贝或赋值,即不能直接=,容器可以直接等则直接拷贝过来
  • vector初始化值的方式,①vector list1vector list2(list1)list2初始化为list1的拷贝③vector list3={2,3,4}vector list4(list1.begin()+2, list2.end()-1);v ector list5(7)vector list6(7,4)
  • 我们需要容器中全部元素时,直接拷贝;我们不需要已有容器中全部元素,只想要一部分元素时,可使用接受两个迭代器的构造函数
  • 拷贝初始化是直接等过去,范围初始化在一个括号内,用(begin(),end())框起来一个范围
  • swap(a,b)比拷贝更快,assign(d,f)是将该范围的替换,主要是用参数所指定元素替换左边容器的所有元素
  • 容器中元素比较是逐对比较,一个一个比,大小取决于长度或第一个不相等元素的比较结果
  • push_back()在尾部创建一个值或由args创建的元素,push_front()在头部创建一个,insert(p,t),在p指向的元素之前创建一个值为t或由args创建的元素
  • string是定义字符串的,也叫字符容器,所以也能使用容器的函数
  • 每个insert()都在第一个位置接受一个迭代器作为第一个参数,指出什么位置放置新元素;三个参数时可指定数量的元素添加到指定位置
  • 普通的push_back()、insert()会需要两次构造,(①对象的初始化构造②使用的时候,如插入的时候会复制一次,会触发拷贝构造)有些时候并不需要两次构造带来效率浪费,一次就行,所以C++11提供了emplace语法,它会在容器管理的内存空间中直接创建对象,而push_back()会创建一个临时变量,压入容器中
  • 获取迭代器的首元素或尾元素的引用,直接的方法时调用front和back,间接的方法是通过解引用begin返回的迭代器来获得首元素的引用,以及通过递减然后解引用end返回的迭代器来获得尾元素的引用。

2023/02/23

  • 在容器中访问元素的成员函数返回的都是引用
  • 删除:pop_back()删除尾元素、pop_front()删除首元素、erase()删除指定位置或指定范围的元素、clear()删除所有元素
  • 当添加或删除一个元素时,删除或添加的元素之前的那个元素的后继会发生改变。为了添加或删除一个元素,我们需要访问其前驱,以便改变前驱的链接。对于forward_list来说,也定义了before_begin,它返回一个首前迭代器,这个迭代器允许我们在链表首元素之前并不存在的元素“之后”添加或删除元素。
  • 当在forward_list中添加或删除元素时,必须关注两个迭代器,一个指向我们要处理的元素,一个指向其前驱
  • resize()增大或缩小容器,用来初始化添加容器中元素
  • 容器操作如添加删除可能会使指向容器元素的指针、引用或迭代器失效,每次改变完容器操作之后需要正确地重新定位迭代器
  • 原本是每次添加新元素都重新分配容器内存空间,但每次重新分配内存空间都要移动所有元素,所以要预留空间作为备用,保存更多新元素吗,就不需要每次添加新元素都重新分配容器的内存空间了
  • shrink_to_fit要求vector将超出当前大小的多余内存退回系统
  • vector中capacity返回的是可以保存多少元素,size()是返回已经保存多少元素
  • 当我们从一个cosnt char*创建string时,指针指向的数组必须以空字符结尾,拷贝遇空字符结束
  • string 可以用构造函数拷贝,或substr是选择某个范围或整体拷贝
  • 赋值的字符数必须小于或等于cp指向数组中的字符数
  • assign总是替换string中所有的内容,.rfind()查找字符中args最后一次出现的位置,s.find_first_of(args)在s中查找args中任何一个字符第一次出现的位置
  • stoi()将子串转为数值,并返回
  • 适配器是一种机制,能使某种事物的行为看起来可另一种一样
  • 栈适配器
stack<int> intStack;
for(size_t ix = 0;ix!=0;++ix)
	intStack.push(ix);
while(!intStack.empty()){
	int value = intStack.top();
	intStack.pop();
}
  • 标准库提供了三种顺序容器适配器:stack、queue、priority_queue,每个适配器都在其底层顺序容器类型上定义了一个新的接口
  • forward_list顺序容器是单向链表,之只能顺序访问
  • 一般在vector末尾实现高效元素添加,添加元素可能会导致内存空间的重新分配,内部添加删除会使所指向插入点后元素的迭代器失效。

2023/02/24

  • 标准库并未给容器添加大量功能,提供了一套算法,独立于任何特性容器,可用于不同容器不同类型元素,称为泛型
  • 顺序容器的操作:添加和删除元素、访问首位元素、确定容器是否为空、获得指向首元素或尾元素之后位置的迭代器、查找特定元素、替换或删除一个特定值、重排元素顺序
  • 大部分算法,在#include中,还一部分数值泛型算法在#include
  • 算法不直接操纵容器,而是遍历连个迭代器指定的一个元素范围
  • 尾后迭代器可以用来判断find是否到达给定序列的末尾,find可以返回尾后迭代器来表示未找到给定元素
  • 算法可能改变元素的值,移动元素,但永远不能改变底层容器大小,不能直接添加或删除元素
  • 大部分标准库算法都对范围内元素操作,我们将此元素范围称为输入范围,基本上是前两个参数表示范围
  • accumulate()对容器内元素求和;equal用于确定两个序列是否保存相同的值,它假定第二个序列至少与第一个序列一样长,此算法接受三个迭代器,前两个表示第一个序列中的元素范围,第三个表示第二个序列中的首元素
  • C风格字符串本质是char* ,C++可以是string
  • 一种保证算法有足够空间来容纳输出数据的方法是使用插入迭代器
    通常情况下:
    通过一个迭代器向容器元素赋值时,值被赋予迭代器指向的元素
    通过一个插入迭代器赋值时,一个与赋值号右侧值相等的元素被添加到容器中
  • 拷贝算法是另一个向目的位置迭代器指向的输出序列中的元素写入数据的算法
  • 谓词就是算法中调用不同函数,有一元谓词(只接受单一参数)、二元谓词(接受两个参数){提供给sort中谓词必须在输入序列中所有可能的元素值上定义一个一致的序)

2023/02/25

  • 可调用对象:函数、函数指针、重载了函数调用运算符的类以及lambda表达式
  • 一个lambda表示一个可调用的代码单元,使用尾置返回类型,忽略返回类型的话,lambda根据函数体内代码推断返回类型
    是匿名函数,就是用[]表示函数名称,直接后面跟参数列表和函数体直接放入函数中
  • 当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象
  • &告诉编译器采用捕获引用方式,=表示采用值捕获方式
void biggies(vector<string> &words, vector<string>::size_type sz, ostream &os = cout, char c = ' '){
	for_each(words.begin(), words.end(), [&, c](const string &s){os<<s<<c;});//捕获列表显示捕获
	for_each(words.begin(), words.end(), [=, c](const string &s){os<<s<<c;});//捕获列表隐式捕获
}
  • 对于只在一两个地方使用简单操作,用lambda否则定义函数
  • 流迭代器被绑定到输入或输出流上,可遍历所有IO流

2023/02/26

  • 插入迭代器是一种迭代器适配器,接受一个容器,生成一个迭代器,能实现向给定容器添加元素。算法并无直接具备向容器插入元素的能力,插入器帮助算法向容器插入元素
back_inserter 使用时,会将元素插入到iter所指向元素之前的位置 调用push_back
front_inserter 使用时,元素总是插入到容器第一个元素之前 调用push_front
inserter 元素被插入到给定迭代器所表示的元素(给定位置)之前 调用insert
  • istream_iterator绑定到一个流时,标准库不能保证迭代器从流中读取,可以推迟,直到迭代器时才真正读取
  • ostream_iterator out(os);将类型为T的值写入到输出流OS中
  • 用流迭代器处理类类型,理解的是使用时同时添加一些别的操作
  • 反向迭代器,rbegin,rend,递增一个反向迭代器会前移一个元素,递减一个迭代器会后移一个
  • 算法基本要求和做法就是让迭代器提供哪些操作,迭代器类别:输入、输出、前向、双向、随机访问迭代器
  • ostream_iterator 只支持递增、解引用和赋值,vector、string、deque还支持递减、关系和算术运算
  • 解引用(*)运算符:解引用只会出现在赋值运算符的右侧,箭头运算符(->)等价于(.)解引用迭代器,提取对象的成员
  • list、reverse 迭代器是双向迭代器,vector上的迭代器是随机访问迭代器,unique前向迭代器,copy前两个参数是输入,后两个是输入迭代器
  • 算法形参模式,alg算法名(beg开始,end结束,dest目的,other args 额外、非迭代器的特定参数)
  • unique使用==比较元素,还可以调用comp来确定元素是否相等
  • 有时候同一作用算法因参数接受谓词参数的算法附加_if前缀,而非重载
  • 有时候后面加_copy后面参数接受位置迭代器,同时加_copy和_if,则接受一个目的位置迭代器和谓词
  • 有些容器有自己特定的算法,比如sort()要求用在随机访问迭代器中,引起不能用于提供双向迭代器的list和前向迭代器的forward_list
  • 一般链表类型定义的其他算法的通用版本可用于链表,但一般不用,可通过改变元素间的链接而不是真的交换它们的值来快速交换元素
  • 标准库中定义的是对序列操作的算法。序列是标准库容器类型的元素、一个内置数组或通过读写一个流生成的
  • bind是标准库函数,将一个或多个参数绑定到一个可调用表达式,定义在functional中
  • ref 标准库函数,从一个指向不能拷贝的类型的对象引用生成一个可宝贝的对象。
  • 关联容器时按关键字来保存和访问的,顺序容器中元素按在容器中的位置来顺序保存和访问的
  • 主要关联容器(map和set),set支持高效的关键字查询操作,检查一个给定关键字是否在set中。标准库有八个关联容器,特点是都是map或set,或要求不重复,或允许重复关键字,按顺序保存或无序保存
  • 允许重复关键字的容器中名字都有multi;
    不保持关键字按顺序存储的容器的名字都以unordered开头
  • 无序容器使用哈希函数来保存元素
  • map是关键字-值对的集合,set就是关键字的简单集合
  • if (exclude.find(word) == exclude.end()) ++word_count[word];如果为true,说明不在exclude集合中,则统计个数,因是与最后末尾指针值,否则是返回的为1
容器 适用场景
vector 元素较小,大致数量预先知道,程序运行过程中不会剧烈变化,只在末尾添加或删除需要频繁访问位置的元素
deque 需频繁在头部和尾部添加或删除元素
list 元素较大,大的类对象,数量预先不知,或在程序运行过程中频繁变化,对元素的访问更多是顺序访问全部或很多元素
map 适合对一些对象按它们某个特征进行访问;需要查找给定值所对应数据时
set 当需要保存特定的值集合,通常是满足/不满足某种要求的值集合;只需判定给定值是否存在时
  • 关联容器不接受顺序容器的位置相关操作,原因是关联容器中元素都是按关键字存储的,位置没有意义
  • set底层是由红黑树实现,与vector无序线性表只能采用顺序查找不同,花费时间与vector.size()呈对数关系,当数量越多,set约占优势
  • 需要将两个数据合成一个数据时,可用pair,或当一个函数需要返回两个数据时
  • set类型的元素是const,map类型的元素是pair,第一个成员是const

2023/02/27

  • 对于保存不重复关键字的容器,erase的返回值总是0或1,若返回值为0,则表明要删除的元素并不在容器中,
  • 若删除关键字为k,则返回删除元素的数量,若删除时迭代器所指定的元素,则返回迭代器后面的迭代器
  • map的下标运算符返回其类型,解引用一个迭代器所返回的类型与下标运算符返回的类型一致
  • upper_bound 返回的迭代器指向最后一个匹配给定关键字的元素之后的位置
    lower_bound 返回的迭代器指向第一个具有给定关键字的元素
  • 当给定关键字不在容器中时,下标操作会插入一个具有该关键字的元素
  • find返回一个迭代器,指向具有给定关键字的元素,不存在则返回尾后迭代器,所以返回类型是容器的迭代器
  • 无序容器在存储上组织为一组桶,每个桶保存一组或多组元素。无序容器使用一个哈希函数将元素映射到桶。为访问一个元素,容器首先计算元素的哈希值,可指出应搜索哪个桶
  • 无序容器通常性能更好,使用更简单,有序版本的优势是维护了关键字的序
  • 有序容器的迭代器通过关键字有序访问容器中的元素
  • hash是标准库模板,无序容器用其管理元素的位置,用哈希技术而不是比较操作来存储和访问元素,这类容器的性能依赖于哈希函数的质量
  • 下标运算符[] 只能用于map和unordered_map类型的非const对象
类型 创建时间 销毁时间
全局对象 程序启动时分配 程序结束时销毁
局部对象 进入其定义所在的程序块时被创建 离开块时销毁
局部static对象 第一次使用前分配 程序结束时销毁
  • 动态分配对象,生存期在哪里创建无关,只有显示地被释放时,才会销毁
静态内存 保存局部static对象、类static数据成员以及定义在任何函数之外地变量 使用之前分配、程序结束时销毁
栈内存 保存定义在函数内的非static对象 在其定义的程序块运行时才存在
  • 内存池:自由空间或堆,程序用堆来存储动态分配的对象,即在程序运行时分配的对象。当动态对象不再使用时,代码必须显式的销毁它们
  • 动态内存的管理:
    new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们对对象进行初始化
    delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存
  • 忘记释放内存,易内存泄漏;有指针引用时释放了,会产生引用非法内存的指针
  • 智能指针:自动释放所指向的对象;两种智能指针的区别在于管理底层指针的方式:shared_ptr允许多个指针指向同一个对象;unique_ptr一个对象只有一个指针指向。还有weak_ptr伴随类,是弱引用,指向shared_ptr所管理的对象
  • p是一个指针,则*p就是一个解引用,获得它所指向的对象
  • 最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数,在动态内存中分配一个对象并初始化它
  • 每个shared_ptr都有一个关联的计数器,通常称为引用计数
  • 析构函数一般用来释放对象所分配的资源
  • 使用动态内存的三个原因
    1. 程序不知道自己需要使用多少对象
    2. 程序不知道所需对象的准确类型
    3. 程序需要在多个对象间共享数据
  • 实现一个新的集合类型的最简单方式是使用某个标准库容器来管理元素,管理元素所使用的内存空间
  • 定义成int *pi = new int的原因是其在自由空间分配的内存是无名的,new无法为其分配的对象命名,而是返回一个指向该对象的指针。

2023/02/28

  • 值初始化的内置对象有良好定义的值。默认初始化的对象的值是未定义的
  • 括号包围的初始化器可以用auto
  • 动态分配的const对象必须进行初始化,new返回的指针是一个指向const的指针
  • 若内存空间被耗尽,则会抛出一个bad_alloc异常,我么通过new来阻止它抛出异常
  • detele将动态内存归还系统,delete表达式接受一个指针,指向我们想要释放的对象,或一个空指针。若指向一个并非new分配的内存或多次释放相同指针,则行为是未定义的
  • 编译器不能分辨一个指针指向的是动态还是静态的对象,不能分辨一个指针所指向的对象是否释放
  • const对象的值不能被改变,但可以被销毁
  • delete之后,指针变成了空悬指针。指向一块曾经保存数据对象但现在已经无效内存的指针,在指针即将离开其作用于之前释放它所关联的内存
  • shared_ptr可在分配对象的同时与之绑定,避免无意中将同一块内存绑定在多个独立创建的shared_ptr上
  • get()是为了向不能使用智能指针的代码传递一个内置指针
  • 使用智能指针,即使代码块过早结束,智能指针类也能确保在内存不再需要时将其释放
  • 删除器函数必须能够完成对shared_ptr中保存的指针进行释放的操作
  • unique_ptr在某个时刻之只能有一个给定对象,它不支持拷贝,不支持赋值
  • release()使指针释放,返回指针,该指针被用来初始化另一个智能指针或给另一个指针赋值
  • 我认为动态内存这章是目前看到最难看的一章
  • weak_ptr是指一种不控制所指对象生存期的智能指针
  • C++中定义了另一种new表达式,可以分配并初始化一个对象数组,这时候要在类型名后面跟一对方括号,在其中指明要分配对象的数目
  • 当用new分配一个数组时,我们并未得到一个数组类型的对象,而是得到一个数组元素类型的指针
  • 释放动态数组则在delete后加[],再加对象名
  • 有时候定义时内存分配和对象组合在一块有点浪费,可能创建一些永远用不到的对象,或者赋值的时候要么初始化时候要么默认初始完后进行赋值时这时候会浪费
  • allocator 帮助将内存分配和对象分开,提供一种类型感知的内存分配方法,当一个allocator对象分配内存时,会根据给定对象类型来确定恰当内存大小和对齐位置,其分配的内存是未构造的
  • 自由空间,程序可用的内存池,保存动态分配的对象(堆,自由空间同义词),new从自由空间分配内存

2023/03/01

  • 指定类的对象拷贝、移动、赋值和销毁操作,包括拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符和构造函数
  • 拷贝控制操作:拷贝和移动构造函数定义了当用同类型的另一个对象初始化本对象时做什么
    拷贝和移动赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么
  • 如果一个构造函数的第一个参数时自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数Foo(const Foo&)
  • 合成拷贝构造函数用来阻止我们拷贝该类类型的对象
  • 使用直接初始化时,实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数;拷贝初始化时,我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如需要还进行类型转换。
  • 拷贝构造函数的使用场景:
    拷贝初始化,用=定义变量
    将一个对象作为实惨传递到非引用类型的形参
    一个返回类型为非引用类型的函数返回一个对象
  • 如果一个类未定义自己的拷贝赋值运算符,编译器会为它生成一个合成拷贝赋值运算符,会将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员,左侧运算对象绑定到隐含的this参数,而右侧运算对象是所属类类型的,作为函数的参数,函数返回指向其左侧运算对象的引用
  • 在一个构造函数中,成员的初始化是在函数体执行之前完成的,且按照它们在类中出现的顺序进行初始化
    在一个析构函数中,首先执行函数体,然后销毁成员。成员按初始化的顺序进行销毁,析构函数释放对象在生存期的所有资源
  • 和普通指针不同,智能指针是一个类,有析构函数,在析构阶段会自动销毁
  • 举需要析构函数的例子,比如一个类在构造函数中分配动态内存,合成析构函数不会delete一个指针数据成员,此类需要订一个析构函数来释放构造函数分配的内存
  • =default 来显示地要求编译器生成合成地版本,合成地函数将隐式地声明为内联
  • 如果一个类有数据成员不能默认构造、拷贝、复制或销毁,则对应地成员函数将被定义为删除的,要想析构函数不删除,则在析构函数=delete
  • 如果一个类有const成员,则它不能使用合成地拷贝赋值运算符
  • 声明不定义一个成员函数是合法的,但访问一个未定义的成员将导致一个链接时错误

2023/03/02

  • 管理类外资源的类必须定义拷贝控制成员,这种类需要析构函数来释放对象所分配的资源
  • 类的行为像一个值,意味着它应该也有自己的状态。为了实现类值的行为,需要定义一个拷贝构造函数、一个析构函数和一个拷贝赋值运算符。
  • 赋值运算符组合了析构函数和构造函数的操作,类似析构函数,赋值操作会销毁左侧运算对象的资源,类似拷贝构造函数,赋值操作会从右侧运算对象拷贝数据
  • 为正确更新计算器,就应当将计数器保存在动态内存中,当创建一个对象时,分配一个新的计数器。当拷贝或赋值新对象时,我们拷贝指向计数器的指针,这样,副本和原对象都会指向相同的计数器
  • 交换两个对象我们需要进行一次拷贝和两次赋值

2023/03/04

  • 分配类的资源需要拷贝控制
  • alloc_n_copy成员会分配足够的内存来保存给定范围的元素,并将这些元素拷贝到新分配的内存中
  • 移动构造函数通常是将资源给定对象移动而不是拷贝到正在创建的对象
  • 新标准的一个最主要的特性是可以移动而非拷贝对象的能力,可以用容器保存不可拷贝的类型,只要他们能被移动
  • 右值引用是为了移动操作,必须绑定到右值的引用,通过&&获得,只能绑定到一个将要销毁的对象
  • 一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值
  • 左值引用我们可以称为常规引用,右值只能绑定到临时对象,使用右值引用的代码可以自由地接管所引用的对象的资源
  • 用move来获得绑定到左值上的右值引用,其定义在utility
  • 左值:在内存有确定存储地址、有变量名,表达式结束依然存在的值,简单来说左值就是非临时对象
  • 右值:就是在内存没有确定存储地址、没有变量名,表达式结束就会销毁的值,简单来说右值就是临时对象
  • 引入右值引用的原因:
    1. 替代需要销毁对象的拷贝、提高效率:在某些情况下,需拷贝一个对象并将其销毁。如临时类对象的拷贝先将就内存的资源拷贝到新内存,然后释放旧内存,引入右值引用后,可以让新对象直接使用旧内存并销毁原对象,减少了内存和运算资源的使用,从而提高运行效率
    2. 移动含不能共享资源的对象。如IO、unique_ptr这样的类包含不能被共享的资源,IO缓冲、指针,这类对象不能拷贝但能移动
  • noexcept移动操作不抛出异常
  • 移动赋值运算符执行与析构函数和移动构造函数相同的工作
  • 除了将移后源对象至于与默认初始化对象相同的状态

2023/03/05

  • 拷贝操作,编译器会为这些类提供合成移动操作。如果一个类定义了自己的拷贝构造函数、拷贝赋值函数或析构函数,编译器就不会为它合成移动构造函数和移动赋值运算符
  • 只有当一个类没有定义任何自己版本的拷贝控制成员,且每个非static数据成员都可以移动时,编译器才会为他合成移动构造函数或移动赋值运算符
  • 第十三章拷贝控制这一章没太看懂,需要重新看

2023/03/06

  • 当运算符被用于类类型的对象时,C++允许我们为其指定新的含义。和内置类型转换一样,类类型转换隐式将一种类型的对象转换成另一种自定义的对象
  • 重载的运算符是具有特殊名字的函数,由operator和其后定义的运算符号共同组成,包含返回类型、参数列表及函数体
  • 当运算符作用域内置类型的运算对象时,无法改变该运算符的含义
  • 显示调用成员运算符函数,首先指定运行函数对象或指针的名字,然后使用点运算符或箭头运算符调用希望调用的函数
  • & | , && ||无法保留内置运算符的短路求值属性,两个运算对象总是会被求值
  • 重载运算符和内置运算符的区别:
    不同点:重载运算符必须有一个class或枚举类型的操作数;
    重载运算符不保证操作数的求值顺序,如对&&和||的重载运算符不再具有短路求值的特性,两个操作数都要进行求值,而不规定操作数的求值顺序
    相同点:对于优先级和结合性及操作数的数目都不变
  • 改变对象状态的运算符或与给定类型密切相关的运算符,如递增、递减和解引用运算符,通常应该是成员
  • ostream是非常量因为向流写入内容会改变其状态
  • 我们把算术运算符和关系运算符定义成非成员函数以允许对左侧或右侧的运算符对象进行转换
  • alloc_n_copy分配内存空间并从给定范围内拷贝元素
  • 如果类定义了调用运算符,则该类的对象称作函数对象
  • lambda后,编辑器将该表达式翻译成一个未命名类的未命名对象,在lambda表达式产生的类中含有一个重载的函数调用运算符
  • C++中有几种可调用的对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了函数运算符的类
  • 函数表用于存储指向这些可调用对象的指针,需要操作时,从表中查找调用的哈桑农户
  • C++11新标准增加lambda的原因,什么情况下会使用lambda,什么情况下会使用类
    在C++11中,Lambda是通过匿名函数对象实现的,因此我们可把lambda看作是对函数对象在使用方式上的简化
    当代码需要一个简单的函数,并且这个函数并不会在其他地方被使用时,就可以使用lambda来实现,此时所起的作用类似于匿名函数。
    如果这个函数需要多次使用,并且它需要保存某种状态的话,使用函数对象则更合适
  • 隐式类型转换,这种构造函数将实惨类型的对象转换成类类型。转换构造函数和类型转换运算符共同定义了类类型转换,这样的转换有时也被称为用户定义的类型转换
  • 当运算符被定义为类的成员时,类对象的隐式this指针绑定到第一个运算对象。赋值、下标、函数调用和箭头运算符必须作为类的成员。

2023/03/07

  • 面向对象程序设计的三个基本概念和核心思想:数据抽象、继承和动态绑定(封装、继承和多态)
  • 继承和动态绑定对程序编写的影响:1、可以更容易定义与其他类相似但不完全相同的新类;2、在使用这些彼此相似的类编写程序时,可以在一定程度上忽略掉它们的区别
  • 数据抽象:将类的接口与实现分离
    继承:可以定义相似的类型并为其相似关系建模
    动态绑定:在一定程度上忽略相似类型的区别,而以统一的方式使用它们的对象
  • 派生类 继承 基类
  • 虚函数:一些函数,基类希望它的派生类各自定义适合自身的版本
  • 派生类必须通过使用类派生列表明确指出它是从哪个基类继承而来的
  • 形参列表后加override 可允许派生类显示地注明它将使用哪个成员函数改写基类的虚函数
  • 动态绑定可以用同一段代码分别处理基类和派生类的对象,也称为运行时绑定
  • 派生类遇到虚函数时,必须进行重新定义以覆盖从基类继承而来的旧定义,基类有两种成员函数必须分开,一是基类希望派生类进行覆盖的函数(虚函数);另一种是基类希望派生类直接继承而不改变的函数。用指针或调用虚函数时,该调用将动态绑定
  • proteced:是派生类有权访问,但禁止其他用户访问
  • 大多数类都只继承自一个类,被称为单继承
  • 一个派生类对象包含:一个含有派生类自己定义的非静态成员的子对象,以及一个与该派生类继承的基类对应的子对象,多个基类则子对象也有多个
  • 派生类的声明只包含类型但不包含它的派生列表
  • A继承于B,B继承于C,则C是B的直接基类,C是A的间接基类
  • 在类名后跟一个final则不希望有其他类继承它
  • 将基类的指针或引用绑定到派生类对象上含义:当使用基类引用或指针时,实际并不清楚引用或指针所绑定对象的真实类型,该对象可能是基类的对象,也可能是派生类的对象。
  • 表达式的静态类型在编译时总是已知的,是变量声明时的类型或表达式生成的类型;表达式的动态类型则是变量或表达式中表示的内存中的对象的类型
  • 静态或动态类型的区别是在编译时知道还是运行后知道
  • 当某个虚函数通过指针或引用调用时,编译器产生的代码知道运行时才能确定应该调用哪个版本的函数,被调用的函数是与绑定到指针或引用上的对象的动态类型相匹配的那一个
  • C++11中我们使用override关键字来说明派生类中的虚函数
  • 有必要将一个成员函数同时声明成override和final吗?
    有必要
    override:在C++新标准中我们可以使用override关键字说明派生类的虚函数,可使意图更加清晰明显,若定义了一个函数与基类中的名字相同但形参列表不同,在不使用override关键字的时候这种函数定义是合法的,使用后非法报错
    同时声明使意图更加明显
  • 一个纯虚函数无需定义,我们在函数体的位置=0将一个虚函数说明为纯虚函数
  • 含有纯虚函数的类使抽象基类。抽象基类负责定义接口,其他类可以覆盖该接口
  • 在类的内部通过using来声明语句,可以将类的直接或间接基类中的任何可访问成员标记出来,其访问权限由using 声明语句之前的访问说明符来决定
using语句出现的地方 访问权限
private 只能被类的成员和友元访问
pubilc 所有用户都能访问
protected 成员、友元、派生类可以访问
  • 名字查找先于类型检查

2023/03/08

  • 继承关系对基类拷贝控制最直接的影响是基类通常应该定义一个虚构析函数,可以动态分配继承体系中的对象
  • 动态绑定析构函数
class Quote{
   public:
   		virtual ~Quote() = default;
}
  • 无论Quote的派生类使用的合成的析构函数还是自己定义的析构函数,都是虚析构函数。只要基类的析构函数是虚函数
  • 哪些类需要虚析构函数?虚析构函数必须执行什么操作?
    作为基类使用的类应该具有虚析构函数,以保证在删除指向动态分配对象的基类指针时,根据指针实际指向的对象所属的类型运行适当的析构函数。
    虚析构函数可以为空,即不执行任何操作。一般而言,析构函数的主要作用是清楚本类中定义的数据成员。如果该类没有定义指针类成员,则使用合成版本;若定义了指针成员,则一般需要自定义西沟啊还能输以对指针成员进行适当的清楚。因此,如果虚析构函数必须执行的操作,则就是清除本类中定义的数据成员操作

2023/03/09

  • using 声明语句只是令某个名字在当前作用域内可见。当作用于构造函数时,using声明语句将令编译器产生代码
  • 当一个基类构造函数含有默认实参时,这些实参不会被继承。默认、拷贝和移动构造函数不会被继承
  • 容器存放继承体系的对象时,通常采取间接存储的方式,不允许在容器中保存不同类型的元素
  • 想在容器中存放具有继承关系的对象,实际上存放的是基类的指针或智能指针
  • C++面向对象悖论,我们无法使用对象来进项面向对象编程。必须使用指针和引用,有时指针会增加程序的复杂性所以有时定义一些辅类
  • 动态绑定只用于虚函数,并需要通过指针或引用调用,可以使我们忽略类型之间的差异,机理是运行时根据对象的动态类型来选择运行函数的哪个版本。继承和动态绑定的结合使我们能够编写具有特定类型行为但又独立于类型的程序
  • 重构:重新设计程序以便将一些相关的部分搜集到一个单独的抽象中,然后使用新的抽象替换原来的代码。通常情况下,重构类的方式是将数据成员和函数成员移动到继承体系的高级别节点当中,从而避免代码冗余
  • OOP(Object Oriente of Programming)
  • 在不知道类型时,OOP能处理类型在程序运行之前都未知的情况;在泛型程序时,提供类型或值,程序实例上可运行
  • 函数模板可以不用为了每个类型都定义,模板定义以template开始,后跟一个模板参数列表,这是一个逗号分隔的一个或多个模板参数列表template
  • 当使用模板时,我们指定模板实参将其绑定到模板参数上
  • inline 和 constexpr说明符放在模板参数列表之后,返回类型之前
  • 当我们使用模板时,编译器才生成代码
  • 当我们调用一个函数时,编译器只需要掌握函数的声明。当我们使用一个类类型的对象时,类定义必须是可用的,但成员函数的定义不必已经出现。所以将类定义和函数声明放在头文件中,而普通函数和类的成员的定义放在源文件中
    而模板,为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义,与非模板代码不同,模板的头文件通常包括声明也包括定义
  • 实例化:当调用一个函数模板时,编译器会利用给定的函数实参来推断模板实参,用此实际实参来创建出模板的一个新的实例,也就是一个真正可调用的函数,这个过程称为实例化
  • 类模板用来生成类的蓝图

2023/3/13

  • 使用类模板时,必须提供额外信息,这些额外信息是显示模板实参列表,被绑定到模板参数
  • 类模板的成员函数具有与模板相同的模板参数,定义在类模板之外的成员函数就必须以关键字template开始,后接类模板参数列表
  • 函数模板是可以实例化出特定类的模板,从形式上来说,函数模板与普通函数类似,只要以关键字template开始,后接模板参数列表,类模板与普通类的关系雷系,编译器会根据调用来为我们推断函数模板的模板参数类型,而是用类模板实例化特定类就必须显示指定模板参数
  • 当我们使用一个类模板时,必须显示提供模板实参列表,编译器将他绑定模板参数,来替换类模板定义中模板参数出现的地方,这样可以实例化出一个特定的类。
  • 函数模板,编译器利用调用中的函数来确定其模板参数,从函数实参来确定模板实参的过程被称为模板实参推断

2023/03/14

  • tuple类似pair,但tuple有任意数量的成员,tuple一旦创建,不可改变,没有给每个元素命名,只能用下标来访问
  • regex管理正则表达式的类

2023/03/15

  • 异常处理机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并做出相应处理
  • 当执行一个throw时,throw后的语句不再执行,转移到catch中,可位于直接或间接调用了发生异常函数的另一个函数中
  • terminate终止程序的执行过程,栈展开沿着嵌套函数的调用链不断查找
  • 异常对象是一种特殊的对象,编译器使用异常抛出表达式来对异常对象进行拷贝初始化,throw语句中的表达式必须拥有完全类型
  • 如果一个throw表达式解引用一个基类指针,而该指针实际指向的时派生类对象,则抛出的对象将被切掉一部分,只有基类部分被抛出
  • void recoup(int) noexcept指定某个函数不会抛出异常,做了不抛出说明或后面加throw()说明符
  • 多个库将名字放置在全局命名空间中会引发命名空间污染,命名空间分割了全局命名空间,其中每个命名空间是一个作用域
  • 命名空间定义形式:namespace thisname{},主要包括类、变量、功函数、模板和其他命名空间

2023/03/18

  • 多重继承:class Panda : public Bead, public endangered{};
  • 在只有一个基类的情况下,派生类的指针或引用能自动转换成一个可访问基类的指针或引用。
  • 虚继承:令某个类作出声明,承诺愿意共享它的基类。共享的基类子对象称为虚基类,这种机制下,不论虚基类出现多少次,在派生类中都只包含唯一一个共享的虚基类子对象。
  • catch负责处理异常的部分

2023/03/19

  • 栈展开:在搜寻catch时依次退出函数的过程,异常发生前构造的局部对象将在进入相应的catch前被销毁
  • terminate负责结束程序的执行
  • new将对象放置在特定的内存空间中,应用程序应重载new运算符和delete运算符以控制内存分配的过程
  • 调用的operator new 该函数分配一块足够大的、原始的、未命名的内存空间以便存储特定类型的对象或对象数组,编译器运行相应的构造函数以构造这些对象,并为其传入初始值,对象被分配了空间并构造完成,返回一个指向该对象的指针
  • delete一个对象,是先执行析构函数,接着调用operator标准库函数释放内存空间
  • 运行时类型识别:typeid运算符,用于返回表达式的类型,dynamic_cast运算符,用于将积累的指针或引用安全地转换成派生类的指针或引用
    使用情况:当想使用基类对象对的指针或引用执行某个派生类操作并且该操作不是虚函数时,一般来说,只要我们应尽量使用虚函数。当操作被定义为虚函数时,编译器将根据对象地动态类型自动选择正确地函数版本
  • 当运算对象不属于类类型或是一个不包含任何虚函数的类时,typeid运算符指示的是运算对象的静态类型。当运算对象是定义了至少一个虚函数的类的左值时,typeid的结果直到运行时才会求得。
  • 枚举类型使我们可以将一组整形常量组织在一起。和类一样,每个枚举类型定义一个新的类型,其属于字面值常量类型
  • C++11引用限定作用的枚举类型,定义成enum class
  • 成员指针是指可以指向类的非静态成员的指针,指针指向一个对象,而成员指针指的是类的成员,而非类的对象,类的静态成员不属于任何对象,因此无需特殊的指向静态成员的指针,指向静态成员的指针与普通指针没有区别
  • union(联合)是一种特殊的类,一个union可以有多个数据成员,但在任意时刻只有一个数据成员可以有值
  • 类可以定义在某个函数的内部,我们称为局部类
  • C++有一些不可移植的特性:位域和volatile限定符
    位域:类将其非静态数据成员定义成位域,在一个位域中含有一定数量的二进制位,当一个程序需要向其他程序或硬件设备传递二进制数据时,需要用到位域
    volatile:当对象的值可能在程序的控制或检测之外被改变时
  • C++使用链接指示指出任意非C++函数所用的语言
  • 链接指示分单个的extern "C" size_t strlen(const char *)和复合的extern "C"{ int strcmp(const char* , const char*); char *strcat(char*, const char*);}
  • 链接指示与重载指示的相互作用依赖于目标语言

你可能感兴趣的:(C++,c++,学习)