C++Primer学习随笔

个人学习过程记录笔记 共勉分享~

类型修饰符(*或&) 声明符(如 int i=1024, *p=&i, &r=i; 中int后面的都是声明符)
建议:(1)尽量使用标准库类型而非数组, 现代的C++程序尽量使用vector和迭代器, 避免使用内置数组和指针; 尽量使用string, 避免使用C风格的基于数组的字符串
书中statement为一条语句, 可为一条简单语句或一个复合语句(块) . 如在if, while等语句中

(2)学会利用每章最后的"小结" 和 “术语表”, 通常言简意赅的解释了一些专业用语的含义.
(3)建议(变量和)函数都应该在头文件中声明, 在源文件中定义, 含有声明的头文件应该被包含到定义函数的源文件中.(如果把函数声明放在头文件中, 就能确保同一函数的所有声明保持一致, 而且一旦我们想改变函数的接口, 只需改变一条声明即可. (P186)

模糊:

P54默认状态下,const独享仅在文件内有效
P58顶层/底层const
P59 constexpr变量, 字面值类型
2.5.3 decltype
3.5.3 数组与指针 (P105)
4.1.1 decltype与左值 (P121)
4.7 条件运算符(? :)嵌套,右结合律
7.5.6 字面值常量类 constexpr构造函数
8.1.1 8.1.2 对IO库条件状态表的介绍

跳过:

7.5.4 隐式的类类型转换(P263, 记不住,跳过)

疑问:

P55(引用本身都不能改变, 还需要在引用前加const吗? 如const int &r1 = ci;) ,
P55初始化和对const的引用,
P228函数什么时候定义为类的成员函数和非成员函数

待做习题:

2.4.2节练习 P57
2.4.3
3.3.3 练习3.20

第一/二章

1.变量声明和定义(P41):
声明(使名字被程序所知, 规定变量类型和名字), 定义(创建与名字关联的实体, 包含声明的功能,此外还申请存储空间, 也可能回味变量赋初始值)
变量能且只能被定义一次, 但可以被多次声明

2.变量的定义位置: 当第一次使用变量时再定义它

3.P45 练习2.14 作用域典型习题

4.变量 = 对象…对C++程序员来说,变量和对象可以互换使用. (P38)

5.引用: 给变量起别名,绑定
(1)引用并非对象, 只是为一个已经存在的对象所起的另一个名字
(2)引用必须被初始化(因为无法令引用重新绑定到另一个对象), 且初始化后不可以改变
且引用只能绑定在对象上, 而不能与字面值或某个表达式的计算结果绑定在一起 P46
(3)声明为引用类型也可能是为了避免(当元素是很大的对象时)对元素的拷贝, 如:
for( const auto &s : text)… P127
(4)大多数情况下,引用的类型要和与之绑定的对象类型严格匹配

6.指针: 用于存放某个对象的地址( 或者说指向某个变量) P47
(1)指针本身就是一个对象, 指针不是必须在定义时赋初值
(2)int ival = 42;
int *p = &ival; //p存放变量ival的地址, 或者说p是指向变量ival的指针, 或者说p指向ival
(3)除两种例外情况, 指针的类型要与其所指对象严格匹配
int *p1, p2; //p1是指向int的指针 ,p2是int

易混淆
int * p_updataes;
几种理解:p_updates的类型为int, 我们说p_updates指向int类型,还说p_updates的类型是指向int的指针或int. p_updates是指针,存放地址,p_updates是int,而不是指针.
在C++中,int
是一种复合类型,是指向int的指针

7.const 与 指针:
不用拘泥于不同书本上因const 与 指针的顺序不同所起的名字, 效果相同:

const double pi = 3.14;
double *ptr = π
const double *cptr = π  //cptr指向一个双精度常量

int errNumb = 0;
int *const curErr = & errNumb; //curErr将一直指向errNumb
const double pi = 3.14159;
const double *const pip = π//pip是一个常量指针, 他指向的对象是一个双精度浮点型常量

两种理解方法:
(1)从右向左看, 离curErr最近的符号是const, 意味着curErr本身是一个常量对象 不可修改(curErr存放着errNumb的地址, 即curErr的指向不能改变, 即curErr将一直指向errNumb); 第二近的是*, 说明curErr是一个指针
(2)看const紧挨着的是谁, 如
相对顺序是 * const curErr; 则说明curErr的指向不能变
相对顺序是 const 类型 *cptr = & pi; 则说明不能通过 cptr指针来改变 对象pi的值 , 但不意味着pi是一个常量(所以可能通过其他方式改变其值)
(3)有两个const, 指向不能变, 指向的对象也不能(通过指针)改变了

第三章

1.头文件中的代码一般来说不应该使用using声明, P75

2.建议使用形如cname的头文件(更符合c++要求)而不使用name.h的形式.如cctype头文件和ctype.h头文件

3.范围for循环:
如果想改变(操作字符串中的字符), 循环变量必须定义成引用类型,即为for ( auto &c : s), 如果只是读取并输出, 即为普通for(auto c: s)(其中c为用于逐个迭代序列中各个元素的变量)
如果循环体内部包含有向vector对象添加元素的语句, 则不能使用范围for循环
使用范围for语句可以减轻人为控制遍历过程的负担(系统知道数组中有多少个元素)
Note:要使用范围for语句处理多维数组, 除了最内层的循环外, 其他所有循环的控制变量都应该是引用类型(为了避免数组被自动转成指针)( P114)有点模糊

4.下标运算符[ ] : 使用前必须确定该下标是否处于合理范围之内 ( 比如 不能小于左区间, 不能超过右区间)

5.vector读取与存储操作:
使用cin >> 来读取数据时, 从第一个真正字符开始读取,遇到下一处空白位置( 遇到空格就停)
如需保留字符串中的空格, 则需要用getline函数代替>> (保留输入中的空白符)

6.vector初始化 (一般先创建空vector, 然后再往里加;也可以直接创建时指定初始值)
(1)使用花括号或圆括号来判断是列表初始值还是元素数量
C++Primer学习随笔_第1张图片
(2)使用了花括号,但提供的值又不能用来列表初始化
在这里插入图片描述

vector<string> v7{10, "hi"}   //10个"hi"`
vector<string> v6{10};   //10个空

7.vector下标操作:
不能用下标形式添加元素, 只能对已存在的元素执行下标操作
错误的例子:

vector<int> ivec;  //空vector对象
cout << ivec[0];   //错误: ivec不包含任何元素

8.多使用迭代器配合!=来判断条件:
所有标准库容器都定义了==和!=, 但是他们中的大多数都没有定义 < 运算符

9.箭头运算符 ->
it是一个迭代器, mem是成员, 则: it -> mem 等价于 (*it).mem

谨记: 但凡是任何使用了迭代器的循环体,都不要向迭代器所属的容器添加元素

3.5.1.字符串字面值初始化数组时, 字符串字面值结尾处还有一个空字符
理解数组声明的含义, 最好的办法是从数组的名字开始按照由内向外的顺序阅读
例如:

3.5.3 Note 在大多数表达式汇总, 使用数组类型的对象其实是使用一个指向该数组首元素的指针

第四章 表达式

4.1.1左值和右值: 当一个对象被用作右值的时候,用的是对象的值; 当对象被用作左值的时候,用的是对象的身份
在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)。举个例子,int a = b+c, a 就是左值,其有变量名为a,通过&a可以获取该变量的地址;表达式b+c、函数int func()的返回值是右值,在其被赋值给某一变量前,我们不能通过变量名找到它,&(b+c)这样的操作则不会通过编译。

4.5递增/递减运算符:
(1)*pbeg++ < == > *(pbeg++)
(2)cout << *iter++ <
cout<< *iter< iter++;

第五章 语句

5.3.1 if-else:
C++规定else与离他最近的尚未匹配的if匹配(并非按照if与else的缩进对齐格式匹配)
可以使用花括号{ }控制执行路径

5.4.1 while语句:
定义在while条件部分或while循环体内的变量每次迭代都经历从创建到销毁的过程

第六章 函数

6.1.1
局部变量: 形参和函数体内部定义的变量的统称
函数体内部的局部变量包括: 普通局部变量 和 静态局部变量

6.2 参数传递(值传递, 地址传递,引用传递) 可参考博客链接
熟悉C的程序员常常使用指针类型的形参访问函数外部的对象. ==在C++语言中, 建议使用引用类型的形参代替指针. ==

(1)值传递(传值参数, 传值调用): 实参的值被拷贝给形参, 形参和实参是两个相互独立的对象. 此时对变量的改动不会影响初始值. 函数对形参所做的所有操作都不会影响实参
例:

(指针形参貌似就是地址传递,函数声明时变量类型为指针型, 函数调用时传入地址,即下图中reset函数)
C++Primer学习随笔_第2张图片
(2)引用传递(函数被传引用调用)

返回引用类型:
返回引用的函数是左值的, 意味着这些函数返回的是对象本身而非对象的副本(P202)

第七章 类

接口: 类型提供的(公有)操作. 通常情况下, 接口不包含数据成员. (C++Primer P274)
(C++ Primer Plus P342有详细介绍)

7.1.3 类的非成员函数(类的非成员接口函数, 作为接口组成部分的非成员函数)
(貌似)类只是通过非成员函数实现一些功能,是类的辅助函数,他们从概念上属于类的接口组成部分,但实质上并未属于类本身.
成员函数必须声明在类的内部,定义在类内类外均可.作为接口组成部分的非成员函数,定义和声明都在类的外部.

构造函数:
定义: 每个类都分别定义了它的对象被初始化的方式, 类通过一个或几个特殊的成员函数来控制其对象的初始化过程(即构造函数作用), 这些函数叫做构造函数
(1)是类的特殊的成员函数, 可以有一个或多个, 不能被声明成const的
(2)只要类的对象被创建, 就会执行构造函数
(3)任务: 初始化类对象的数据成员
(4) 构造函数的名字与类名相同, 且没有返回类型也不写void, 构造函数可以有参数(因此可以发生重载), 程序在调用对象时会自动调用构造, 无须手动调用且只会调用一次
(5)如果我们不提供构造和析构, 编译器会提供, 编译器提供的构造函数和析构函数是空实现

当对象被默认初始化或值初始化时自动执行默认构造函数.
==默认初始化 和 值初始化 ==的发生情况汇总 (P262)

7.5.1 构造函数初始值列表中初始值的前后位置关系不会影响实际的初始化顺序.(成员的初始化顺序按照他们在类定义中出现的顺序进行)

P267类实例化对象的初始值顺序是否必须与声明的顺序一致?
个人理解(待确定): 貌似是因为是聚合类, 没有构造函数( 否则构造函数就初始化了数据成员的初始值)

7.6 类的静态成员( 包括静态数据成员和静态成员函数) 可结合CPP5和CPP6
定义:在成员变量和成员函数之前加上static关键字 (使其与类本身直接相关, 而不是与类的各个对象保持关联); 静态成员不是任意单独对象的组成部分, 而是由该类的全体对象所共享.

主要:
静态成员变量: CPP5的P268 CPP6的P425-P428
(1)所有对象共享同一份数据, 即共享同一个静态成员( 无论创建了多少对象, 程序都只创建一个静态类变量副本)
(2)静态成员变量在类内声明(static关键字只出现在类内的声明语句中), 类外定义和初始化
(3)在编译阶段分配内存
静态成员函数:
(1)程序共享同一个函数
(2)静态成员函数只能访问静态成员变量(因为静态成员函数不与特定的对象相关联)

静态成员函数和静态成员变量的访问方式:
(1)类的对象: (点的方式访问)
p1.m_A (其中p1是对象, m_A是静态成员变量, 在类内声明)

Account ac1;
Account *ac2 = &ac1;
//调用静态成员函数rate的等价形式
r = ac1.rate();  //通过Account的对象或引用
r= ac2->rate();  //通过指向Account对象的指针

(2)类名:(作用域运算符直接访问静态成员)

double r;
r = Account :: rate(); 

暂时补充:
在C++中,类的成员变量和成员函数分开存储, 只有非静态成员变量才属于类的对象上( 即不包括 静态成员变量, 非静态成员函数 和 静态成员函数)

第九章 顺序容器

顺序容器包括: vector, deque, list, forward_list, array, string
关联容器: 按关键字有序保存元素(map/multimap, set/multiset) , 无序集合(unordered_map, unordered_set, unordered_multimap, unordered_multiset)

9.2.1 迭代器
通俗理解: 算法与容器之间的粘合剂(算法要通过迭代器才能访问容器中的元素)
提供一种方法, 使之能够依序寻访某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。
每个容器都有自己专属的迭代器
迭代器使用非常类似于指针,初学阶段我们可以先理解迭代器为指针

9.2.3 begin和end成员
begin和end都是容器操作, begin返回一个指向容器首元素的迭代器, 如果容器为空, 则返回尾后迭代器. 是否返回const迭代器依赖于容器的类型
begin和end有多个版本: 带r的 (rbegin/rend)返回反向迭代器; 带c的(cbegin/cend)(C++11)返回const迭代器; 不以c开头的函数都是重载过的

BestPractices: 不需要写访问时, 应使用cbegin和cend

第十二章 动态内存

内存分区:
(1)静态内存(全局区):保存局部static对象, 类static数据成员, 定义在任何函数之外的变量(即全局变量, 静态变量及常量)
(2)栈内存:保存定义在函数内的非static对象 (如函数的参数值和局部变量等)
分配在静态或栈内存中的对象由编译器自动创建和销毁.
(3)堆(也称自由空间): 存储动态分配的对象------即那些在程序运行时分配的对象.(由程序员通过代码分配和释放, 若程序员不释放, 程序结束时由操作系统回收)(new对象创建在堆区)

转载:
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。

12.1 动态内存与智能指针
C++中动态内存管理的一对运算符:new/delete(相对于智能指针, 使用二者易出错)
new在动态内存中为对象分配空间并返回一个指向该对象的指针, 我们可以选择对对象进行初始化
delete接受一个动态对象的指针, 销毁该对象并释放与之关联的内存

new的语法:

//(1) pi指向一个动态分配的, 未初始化的无名对象
int *pi = new int;
//(2) 传统的构造方式(圆括号)
int *pi = new int(1024); //pi指向的对象的值为1024
string *ps = new string(10, '9'); //*ps为"9999999999"
//(3)新标准下, 使用列表初始化(花括号)
vector<int> *pv = new vector<int>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
//(4)对动态分配的对象进行值初始化,只需在类型名之后跟一对空括号即可:

C++Primer学习随笔_第3张图片
delete的语法:
delete表达式接受一个指针, 指向我们要释放的对象
delete的指针必须指向动态分配的内存, 或者是一个空指针
C++Primer学习随笔_第4张图片

第十三章 拷贝控制

一个类定义五种特殊的成员函数实现此类对象拷贝, 移动, 赋值,和销毁时做什么: 拷贝构造函数, 拷贝赋值运算符, 移动构造函数(新标准), 移动赋值运算符(新标准), 析构函数
称这些操作为拷贝控制操作

13.1.1 拷贝构造函数(通俗易懂详解可参照链接拷贝构造函数易懂链接)
定义:拷贝构造函数是第一个参数是自身类类型的引用(该参数几乎总是一个const引用), 且任何额外参数都有默认值一种特殊构造函数.
作用:用一个类对象初始化另一个同类对象时完成复杂的复制过程所需要完成一些操作(类对象的复制和初始化比普通变量间初始化复杂得多,比如类中存在一些成员变量)
一种构造函数,将新对象初始化为同类型另一个对象的副本 
语法:
类名(const 类名&);

合成拷贝构造函数(是编译器为我们定义的)在一般情况下会将其参数的成员逐个拷贝到正在创建的对象中. 编译器从给定对象中依次将每个非static成员拷贝到正在创建的对象中.
合成拷贝构造函数与合成的默认构造函数不同, 若类没有显示地定义构造函数, 编译器会隐式地定义一个默认构造函数; 即使定义了其他构造函数, 编译器也会为我们合成一个拷贝构造函数(即提供一个合成构造函数)

拷贝构造函数调用时机(拷贝初始化发生情况)(拷贝初始化通常使用拷贝构造函数来完成,注:在拷贝初始化过程中, 编译器可以 但不是必须 跳过拷贝/移动构造函数, 直接创建对象)
(1) 使用=定义变量(使用一个已经创建完毕的对象来初始化一个新对象)
(2)将一个对象作为实参传递给一个非引用类型的形参(值传递方式给函数参数传值)
(3)从一个返回类型为非引用类型的函数返回一个对象(值方式返回局部对象)
(4)用花括号列表初始化一个数组中的元素或一个聚合类中的成员

13.1.3 析构函数
定义:特殊的成员函数,当对象离开作用域或被释放时进行清理工作.
作用:析构函数执行与构造函数相反的操作:构造函数初始化对象的非static数据成员, 及做一些其他工作;
析构函数释放对象使用的资源, 并销毁对象的非static数据成员
(1)析构函数是类的一个成员函数, 对于一个给定类, 只会有一个析构函数
(2)名字由 ~类名() 构成, 没有返回值也不写void,也不接受参数(因此不能被重载)
(3)程序在对象销毁前会自动调用析构, 无须手动调用而且只会调用一次
(4)如果我们不提供构造和析构, 编译器会提供, 编译器提供的构造函数和析构函数是空实现

构造函数与析构函数工作过程对比:(P445)
(1)构造函数有一个初始化部分和一个函数体, 析构函数有一个函数体和析构部分;
(2)构造函数中, 成员的初始化在函数体执行之前完成, 并且是按照成员在类中出现的顺序进行初始化;析构函数中, 首先执行函数体, 然后销毁成员. 成员按初始化顺序的逆序销毁
(3)析构部分是隐式的

你可能感兴趣的:(C/C++)