我学习喜欢系统化,尤其是对于发展几十年的编程语言来说更应有一个系统的认识过程,而且C++大版本不多,只有C++98、C++11、C++20目前来说比较经典,这也为我系统学习C++提供可能。首先我的C/C++基础已经遗忘过多,我想通过第五版的书学一下C++98的知识点,而《C++Primer第五版》在我读了一章之后,感觉比较适合对C++有一定了解但想要了解C++11新特性的人来读,否则上来摆一大堆概念没有循序渐进的介绍很容易让人不知所云。
预计很快过掉这本基础书,《C++Primer第五版》仍是重点,毕竟包含新版本的知识,且习题干货满满。
第3章 处理数据
3.1.8 char类型:字符和小整数
书上说C++早期版本将字符常量存储为int,故cout<<’M’这样的语句会直接输出’M’的ASCII码值,需要用cout.put()才可以正确显示字符,新版本无此问题。经测试,C++17可以直接用cout输出char类型字符。
‘a’被认为是整数类型,如果不用char来存储和int几乎没有区别,所以char*并不能指向字符常量
3.2 const限定符

重新对常量进行赋值时会报错:
说明const变量和常量一样都不可以作为=运算符的左值,通过这种方式对const变量的值进行保护。
const变量必须声明时进行赋值,否则会报错
cout.setf(ios_base::fixed, ios_base::floatfield);
让cout输出的浮点数显示小数点后六位
第4章 复合类型
4.1 数组
sizeof如果用于数组名,则结果为数组总字节数;用于数组单个元素时,显示元素类型所占字节数
数组只有在声明时才可以使用列表初始化;
没初始化的元素将默认为0;
不清楚元素个数时可以不写数组大小,但编译器计算出的大小不一定准确。可以用sizeof计算数组大小。
4.3 string类简介
getline(cin,str)可以获取一行字符串存到string类对象str中,到空白符为止
cin>>x
- 当x为基本类型时,使用的是istream类的成员函数
- 当x为string类对象时,使用的是string类的友元函数
4.4&4.5 结构体struct和共用体union
引用:当多个基本数据类型或复合数据结构要占用同一片内存时,我们要使用联合体;当多种类型,多个对象,多个事物只取其一时(我们姑且通俗地称其为“n 选1”),我们也可以使用联合体来发挥其长处。
struct和class几乎没有区别(属性默认权限等)
记住定义后要加分号
4.6 枚举
4.7 指针和自由存储空间
int *p = &a;
指针指向int a,不代表指针是int;指针只存对象的地址,解引用后才是对象本身的值
OOP强调在程序运行时分配内存,所以一定要会new和delete
int *p = new int;...delete p;//只释放p指向的内存,不会删除p,p仍旧可以指向其他int变量
- 已经释放的内存不要重复释放
- 不是new出来的指针不要delete(空指针除外)
- 如果new出来不delete可能导致内存泄漏或不足
int *p=new int[10];...delete [] p;
- 不能用sizeof得出动态分配的内存大小
- 指针的加减表示指针按照类型大小对地址进行加减,如int占4B,则int型指针++后的地址(16进制)会大4
- 数组名和指针基本相同,只不过数组名是常量不可以修改(不变地指向数组首元素),也说明C++将数组名解释为地址
双引号字符串字面值也表示字符串首元素的地址,使用时要注意(隐式包含末尾的空字符)!(当然也包括字符数组名和指向char的指针)
4.8.4 自动存储、静态存储和动态存储
- 自动存储/局部变量:在初始化时产生,函数结束时消亡
- 静态变量:要么在函数外定义,要么使用static关键字
- 动态存储:使用内存池空间(通常叫堆),生命周期不受函数控制,取决于new和delete

由于C++将字符串字面值解释为指向首字符的指针,即const char,转为int 时,说明此时指针将指向的地址看作int,但地址不变,所以输出还是首字符的地址,不过指针加一会按照int大小递增而非原本的char大小
第5章 循环和关系表达式
cout<
逗号操作符表达式的值为分号前的那个表达式的值
- 对指针使用==判断只能输出地址是否相同,无法得出指向元素是否相同
- 对char可以比较,因为char实际上是整型
第6章 分支语句和逻辑操作符
条件判断写成 if(3==number) 比 if(number==3) 更不容易犯错
冒号、逗号和||操作符都是顺序点,这些操作符前的表达式值必须已经确定才可以往下执行
如||左边如果已经为true,则不再执行右边的表达式,&&也一样
C++不支持数学上的连续不等式,必须用逻辑分开,否则真假判断会超预期
复习题
- 第一题的else if比if效率高无法理解
- 第三题

如果输入:
Hi!
Send $10 or $20 now!
结果将是
注意换行符和输入流的读取问题~
第7章 函数
- 指向常量的指针/常量指针
意思是对于此指针来说,指向的内容是常量,不可通过此指针写值,指针只可读;
但不意味着指向的内容声明是常量
所以有如下规定:禁止将const变量的地址赋给非const指针(避免通过非const指针修改const变量值)
int age = 39;const int* pt = &age;*pt += 1;//非法,不可以通过const指针改变变量值cin >> *pt;//同非法
建议:形参尽量用const
- 避免const值接受不了
- 避免错误修改实参
- 指针常量
int sloth = 3;const int* ps = &sloth;int* const finger = &sloth;
- 不可通过ps改变sloth值;不可改变finger值
- 但可以改变ps值;可以通过finger改变sloth值
- const指针常量
double trouble = 2.0E30;const double* const stick = &trouble;//stick不能改变指向,也不可以通过stick改变trouble的值
7.4 函数和二维数组
不可以不给出列数,因为二维数组按行存储不给列数将无法确定元素位置void f(int a[3][4]);void f(int a[][4]);void f(int (*a)[4]);//三种形式完全等价
a实际上是指向指针的指针,a[0]是第一行首元素地址,*(a+row)是指向第row+1行首元素的指针7.9 函数指针
把函数当变量调用,应用如下:回调函数、解耦、实现虚函数,C++中有std::function,了解原理即可
函数名即函数首地址 ```cpp double pam(int);//函数原型 double (*pf)(int);//函数指针声明 pf = pam; process(pf);//函数指针调用
double x = pam(4); double y = pf(4); double z = (*pf)(4);//三者完全等价
<a name="Z05ay"></a># 第8章 C++函数<a name="SOkHL"></a>## 8.1 内联函数小函数如果经常被调用可以写为inline,提升执行速度(占内存)<br />通常声明和定义一起写<br />!若宏实现了类似函数计算的功能应写成内联,否则文本替换可能会出意外```cppinline double square(double x){return x*x;}
8.2 引用变量
声明引用时必须初始化;
引用接近指针常量,和对象绑定;
如果想要避免修改实参,则应使用常引用;
由于引用绑定的是变量,那么当形参是const引用,而传入参数是表达式、字面值或其他类型不匹配的参数时,C++会生成临时变量存放引用值(必要时做隐式类型转换),函数将操作临时变量而非参数,最终仍不改变const引用。例子如下:

参数不是左值而是字面值常量,类型不匹配,生成临时变量double b=(double)a,然后x=b,结束后x没有改变

参数是左值但类型不匹配时,会先生成临时变量a1=(int)3.1, b1=(int)5.2
然后a=a1, b=b1
函数运行完后a, b没有改变
- 仍然遵循常引用不改变实参的原则
- const能够接受更多的类型
- 会生成临时变量
引用实际上是设计给参数为类对象时使用的,参数或返回值是类、结构体时,引用效率更高,不需要复制一遍。
- 不要返回对函数局部变量的引用,返回值是引用时函数内部的临时对象会被销毁,返回其引用将指向不存在的数据
- 函数参数是什么,就返回什么
- 派生类可以使用基类的方法
- 基类引用可以指向派生类
引用、指针、值传递建议使用原则:
| 不改参数 | 改参数 | |
|---|---|---|
| 内置数据类型/小结构 | 值传递 | 指针(引用也可以但要注意类型问题,见8.2) |
| 数组 | 指针,尽量为const指针 | 指针 |
| 大结构 | const引用/const指针 | 引用或指针 |
| 类对象 | const引用 | 引用 |
8.3 默认参数
8.4 函数重载
- 重载关键是函数参数列表(parameter list)


- 参数类型不匹配时会自动进行类型转换以寻找合适的函数调用
- 有多种调用方式时会报错
- 参数列表中引用和原类型视为同一类不能重载
- 只有返回值不同不能重载
- 编译器根据参数列表对函数名进行加密/name mangling(倾轧)技术来实现重载
8.5 函数模板
但是如果不想完全交换两个参数,只交换成员的一部分,使用显式具体化(specialization) ```cpp template<> void swaptemplate <typename Any>//或template <class Any>void swap(Any &a, Any &b){Any t;t=a;a=b;b=t;}
(job &j1, job &j2){ … } //原型也可写成 template<> void swap (job &j1, job &j2)//或不带参数名
//当然实现要写
---- **实例化和具体化**实例化(instantiation)指生成函数定义和编码,具体化指增加模板之外的功能,仍处于源代码阶段- 隐式实例化指代码运行到调用模板函数时,编译器根据类型自动生成对应的函数- 显式指代码中直接命令编译器生成对应的函数```cpptemplate void swap<int> (int, int)//显式实例化,命令编译器生成swap的int版本
不要同时使用显式实例化和显式具体化!
- 重载解析
选择顺序:完全匹配(其中非模板>模板,模板具体化中显式>隐式,非const优先匹配非const)
>提升转换>一般转换>其他
第9章 内存模型和名称空间
9.1 单独编译
- “”头文件先查cwd,<>先查库
- 函数定义和变量声明不要放到头文件
- 头文件通常包含函数原型,常量,结构、类、模板声明和inline函数
-
9.2 存储持续性、作用域和链接性/共享性
局部: 代码块开始创建、代码块结束释放
存储在堆栈
还可使用register关键字声明局部变量,请求使用CPU寄存器存储提高访问速度(编译器不一定会满足)- 静态:程序开始创建、程序结束释放/生存周期为程序执行期
- 动态:new时创建、delete时释放
|
| 生存周期 | 作用域/可见性 | 链接性 | 存储位置 | 其他 | | —- | —- | —- | —- | —- | —- | | 局部变量 | 代码块{} | 局部 | 无 | 函数堆栈 | auto、register关键字 | | 静态变量 | 程序exe | 取决于定义位置 | 外部
内部(同一文件可用)
无(见下图1) | 静态区 | 未初始化则默认为0;只可以用常量表达式初始化如字面值、const、enum和sizeof | | 动态变量 | new——delete |
|
|
|
|

extern关键字声明此处引用已有的变量,不能初始化,不分配内存

::variable表明使用全局的variable
全局变量还是const常量比较适合
C++输入方式小总结
| 函数 | 功能 | 其他 |
|---|---|---|
| a = cin.get() / cin.get(a) | 缓冲区读一个字符,返回ASCII码 | 不会舍弃空白符 |
| cin.get(arrayname,size) | 输入定长char数组 | 不可以是string |
| cin.get(arrayname,size,s) | 输入定长char数组,遇到s停止 | s之前的被保留,s被舍弃 |
| cin.getline( arrayname, size ) cin.getline( arrayname, size, s ) |
读一行字符串 | 会删除换行符 |
| getline(istream,str); | 读文件、读控制台的一行字符串 | |
| cin>> | 输入流入变量 | 所有字符按char处理,cin自动转换成相应类型 |
- const全局变量链接性为内部
如果想定义外部,需要定义时加extern
- 内联函数定义可以放在头文件,但其他函数只能有一处出现定义,使用的地方出现原型或包含头文件中的原型
- 使用C语言代码也可以extern声明此函数来自其他语言
new指针只能初始化在函数中,因为静态变量只可以用常量表达式初始化
new出来的东西放在堆中
9.4 namespace

第10章 对象和类
- struct默认public,而class默认private
类定义时实现的方法自动为inline,当然也可在cpp中inline设置
构造和析构
和结构体不同,class不可以用{}初始化,因为数据成员通常为私有
如果都是public,可以和struct一样,但是失去class意义了- 不写默认构造函数时,编译器提供但什么也不做
写可以使用默认参数或空参数表
隐式调用默认构造函数和int x一样,不要带括号 - 局部变量存放在堆栈,所以最先创建的对象最后被析构
- const可以保证函数不修改对象
- 构造函数只有一个参数时可以赋值初始化
对象数组


类有默认构造函数才可以创造对象数组
初始化步骤:
- 使用默认构造函数创建数组元素
- 创建临时对象
- 拷贝到相应位置
作用域为整个类的常量
- 类内enum只是为了创建符号常量,如ios_bases::fixed
static const 整型类内可以直接初始化
static const int _datai = 5; static const long _datal = 3L; static const char _datac = ‘c’;
constexpr static const double _datad = 8.88;第11章 使用类
11.1 操作符重载
格式:operator op ( argument-list )
Time Time::operator+(const Time &t) const { int h, m; m = (minutes + t.minutes) % 60;//同一类对象的私有成员是可以直接访问的 h = hours + t.hours + (minutes + t.minutes) / 60; return Time(h, m); } //两种调用方式 total = coding + fixing; //total = coding.operator+(fixing);重载规则
操作符对象至少有一个自定义类型,不允许重载标准库规定的操作符
- 不能改变操作符作用对象数、优先级
- 不能定义新的操作符
- 部分操作符不能重载,如. / :: / ?:等
-
11.3 友元函数
重载的操作符函数本质还是成员函数,二元操作符只能把对象放在前面,如object3才可以调用成员函数
而3 object这样的表达式只能通过友元函数实现 不是成员函数,不用.来调用
- 和成员函数访问权限相同
- 声明使用friend,定义时不要写,除非声明时实现为inline ```cpp class classname{ friend classname operator *(…);//声明 }
//实现 classname operator*(…){}
使用时3*object将转换为operator*(3, object)<br />因此友元函数可以用来反转操作符顺序↑,经典对象放在右边的操作符需要友元的场景是cout<<
```cpp
ostream& operator<<(ostream& os, const classname& oj){
os<<...;
return os;
}
11.6 类的自动转换和强制类型转换
构造参数只含一个参数时可以object=num初始化,这是隐式转换,没有(int)这样的强制转换
可以用explicit关键字限制构造函数只能用于显式转换
将类转为参数类型需要使用操作符转换函数
格式为:operator typename(),如operator double()将对象强制转换为double型
- 必须是类方法
- 不能有参数
num=object或num=(double)object时被调用
第12章 类和动态内存分配
static成员若为整数或enum const,可以声明时初始化;否则在cpp中初始化
- 成员中的指针如果是new申请,在对象失效时/析构函数需要delete释放指向的内存
- 成员变量若为非静态const成员或引用,使用初始化列表
隐式成员函数
- 默认构造函数
没有定义构造函数时编译器生成一个什么也不做的空默认构造函数;
不带任何参数或所有参数都有默认值的构造函数是默认构造函数;
只能有一个默认构造函数
- 复制构造函数
用于用对象初始化另一个对象时(=和括号都可以调用)
对象按值传递、返回对象、生成临时对象时都调用复制构造函数!
当成员是指针时,最好定义复制构造函数,给成员一份新的数据,而不是多个对象指向同一个地址,容易出问题!——深复制
- 赋值操作符
初始化时总会调用复制构造函数(可能会生成临时对象调用=);而=已有对象使用赋值操作符;
- 默认析构函数
- &地址操作符
静态成员函数
- 不通过对象而通过类名调用
- 不能使用this指针
- 只能使用static成员变量
返回对象
- 返回参数的const引用
比返回参数复制的临时对象效率高 - 返回参数非const引用
重载=、cout<<时经常用 - 返回对象
重载算术操作符,开销不可避免
返回局部对象 - 返回const对象
避免一些错误的赋值
基础知识已经通过这本书或多或少学习到,接下来是Primer这本书,通过例程和练习题巩固并深入细节学习
学习时长共为两周左右,一些知识如头文件
next book!

