C++ Primer学习笔记

本文主要是读C++ Primer,记录一些没关注到的细节,或者没学到的东西。

第一章 开始

除了cin cout,还有cerr输出警告和错误信息,clog输出运行时一般性信息。

endl结束当前行,将与设备关联的缓冲区刷到设备中。保证到目前为止所产生的所有输出都写入到输出流中,而不是停留在内存中等待写入流。

拓展:缓冲区又称为缓存,内存空间的一部分。在内存空间中预留了一定的存储空间缓冲输入或输出。CPU可以直接读取内存而不是从磁盘 中读,减少磁盘读写次数,提高运行速度。

全缓冲:标准IO缓存满之后才实际进行IO操作;

行缓冲:遇到换行符才之际IO操作;

不缓冲:如stderr。

可以使用flush endl ends来刷新缓冲区。

第二章 变量与基本类型

char是否带符号由编译器决定。其他字符类型可以用扩展字符集,如wchar_t,char16_t,char32_t。其中wchar_t可以确保存放最大扩展字符集的任意一个字符,16和32则为unicode服务。

初始化和赋值是完全不同的操作。初始化是赋予初始值,赋值则是先擦除在用新值替代。

列表初始化:用花括号初始化,如下:

1
2
3
4
int a = 0;
int a = {0};
int a{0};
int a(0);

如果初始值可能丢失信息,则用列表初始化时候编译器会报错。

默认初始化:对于函数体内部的内置类型变量不被初始化,函数外的变量初始化为0。每个类各自决定初始化的方式。

c++支持分离式编译,因此定义和声明需要区别开来。声明使得名字为程序所知道,定义创建与名字关联的实体。

如果想声明而非定义,则用extern并且不显式初始化。变量只能被定义一次,但可以被多次声明。

建议:在第一次使用变量前定义它。这样方便好找。

引用创建了对象的别名,定义时候会和初始值绑定在一起,无法重新绑定到另一个对象。

所有指针都应该初始化,而且应当在定义了对象之后再定义指向它的指针。

void*指针可以存放任意对象的地址,但不能操作。可以进行函数的比较,或者赋值给另一个void*。也可以强转类型后操作。

1
2
int *p1, p2;//只有p1是int*类型 p2是int
int *p1, *p2;//正确

默认状态下,const对象只在文件内生效。如果需要共享,则extern const来使用。

无法通过const引用来改变值,但可以通过其他引用改变值。

引用类型必须与所引用对象类型一致,除非是字面值。

const在前,是指向常量的指针。const在后,是常量指针。前者无法通过该指针改变变量值,后者无法改变指针的指向,也就是地址不变。

constexpr常量表达式指可以在编译过程中得到计算结果的表达式。函数体外定义的变量地址不变,能用来初始化constexpr指针。这时候相当于地址不变的常量指针。

可以用typedef和using来声明类型别名。

auto通常会忽略顶层const(通道)保留底层const。需要顶层的话则要显式声明。

引用用auto也需要显式声明。

decltype可以用来推断类型,而不计算值。

1
2
3
4
5
6
int *p = 5;
int a = 6;
decltype(*p) //int&
decltype((5))//int
decltype(a)//int
decltype((a))//int& 双层括号里面放变量 只能是引用

decltype在加括号和不加括号时候类型不同。

第三章 字符串 向量和数组

每个函数,变量名字都需要独立的using声明。头文件不应该包含using声明,避免冲突。

用等号执行的拷贝初始化,不用等号执行的直接初始化。

触发getline函数的换行符被丢弃掉了,得到的string不含换行符。

在cctype里面有各种处理字符的函数,比如ispunct(c)检测标点符号。

列表初始化只能花括号,不能圆括号。

vector运行时应当能高效快速添加元素,因此设定大小没什么必要。

只能对已经存在的元素执行下标操作。

只需要读的话,可以用cbegin()常量迭代器。

可以用箭头运算符和empty()来检测是否为空。

1
2
(*it).empty();
it->empty();

对容量的操作会导致迭代器失效。

除了方法,还可以用begin(),end()函数。得到尾后指针(指向并不存在的元素,位于容器尾元素的下一位置),可以以数组为参数。

两个指针相减得到ptrdiff_t类型,和size_t类似,但可能有负数。

第四章 表达式

除非比较对象是布尔类型,否则不要用truefalse来进行比较。

字面值是右值,算数表达式是右值。

除非必须,否则不要用递增递减运算符的后置版本,由于需要保存原始值,因此产生不必要浪费。

sizeof存在两种形式,求类型的大小和求表达式结果的类型大小。

1
2
sizeof expr;
sizeof (type);

对数组求sizeof会得到整个数组所占空间的大小,对string或者vector则只返回固定部分大小。

隐式转换:比int小的整型值提升到较大的整数类型,在条件中非布尔值转换为布尔类型,初始化时候转换成变量类型,右侧运算对象转换成左侧运算对象的类型。算数运算多种类型会转换成同种类型,函数调用会发生类型转换。

命名的强制转换:

1
2
3
4
5
6
7
8
9
int j;
void* p;
static_cast<double>(j);//有明确定义的类型转换,主要不包含底层const,都能用static_cast。
static_cast<double*>(p);
const char *pc;
char *p = const_cast<char*>(pc);//const_cast只能改变运算对象的底层const,不能改变表达式类型
int *ip;
reinterpret_cast<char*>(ip);//位模式提供较低层次上的重新解释,依赖于机器
dynamic_cast;//将基类指针或者引用安全地转换到派生类

第五章 语句

throw表达式:用throw标识遇到了无法处理的问题,raise了异常。

try语句块:用try处理异常,以try开始,一个或多个catch子句结束。抛出的异常会被catch处理。

异常类:用于在throw表达式和catch子句之间传递异常的具体信息。

1
2
3
4
5
6
7
try{
statements;
}catch(runtime_error err){
handle;
}catch(exception-declaration){
handle;
}

异常类:

1
2
3
4
5
6
7
8
9
10
exception;//常见问题
runtime_error;//只有运行时才能检测出来的问题
range_error;//运行时错误:结果超出了有意义的值域范围
overflow_error;//计算上溢出
underflow_error;//计算下溢出
logic_error;//逻辑错误
domain_error;//参数对应结果值不存在
invalid_argument;//无效参数
length_error;//超出长度
out_of_range;//逻辑错误 超出有效范围

异常类型有what()成员函数,返回c风格字符串,提供文本信息。

第六章 函数

分离式编译:将.cpp文件编译成.o文件,然后将.o文件链接出可执行文件。如果需要更改,则针对更改了的文件编译一份.o文件再链接即可。

可以用引用作为参数来返回所需要信息。

尽量使用常引用。

不允许拷贝数组,数组作为形参会转换为指针。但可以以数组的形式标识参数。

argc标识数组中字符串的数量,argv表示c风格字符串数组。

如果实参数量位置但是全部实参类型相同,可以使用initializer类型形参,如下所示:

1
2
3
4
5
6
7
8
9
10
11
void teststring(initializer_list<string> li){
for(const auto &i:li){
cout<<i<<" ";
}
cout<<endl;
}

int main(){
string s1 = "az";
teststring({s1,s1,s1});//注意大括号
}

省略符形参以及使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<iostream>
#include<string>
#include<cstdarg>

using namespace std;

void testNum(int count,int beforeNumber,...){
va_list arg;
va_start(arg,beforeNumber);//对齐到省略号前的参数
for(int i=0;i<count;i++){
cout<<va_arg(arg,int)<<endl;//参数为va_list,参数类型
}
va_end(arg);//释放va_list
}

int main(){
testNum(5,0,0,2,4,4,5);
}

不要返回局部对象的引用或者指针。

返回值为左值的函数:

1
2
3
4
5
6
7
8
9
10
char &getString(string& s, int index){
return s[index];
}

int main(){
string s = "123456";
cout<<s<<endl;
getString(s,3) = '5';
cout<<s<<endl;
}

注意参数不能为常量引用,返回值也不能为常量引用。

return可以为大括号括起来的列表,注意返回值类型要能列表初始化。

main函数也有返回值,在<cstdlib>中有定义了两个变量:EXIT_FAILUREEXIT_SUCCESS

声明返回数组指针的函数如下所示:

1
int (*func(parameter_list))[array_len];

c11可以使用尾置返回类型,如下所示:

1
auto func(int i) -> int(*)[10];

还可以用decltype来声明返回类型:

1
2
3
4
5
6
7
8
9
int odd[] = {1,3,5,7,9};
int even[] = {2,4,6,8,10};
decltype(odd) *arrPtr(int i){
return (i%2)?&odd:&even;
}
int main(){
auto i = arrPtr(3);
cout<<i[0][3]<<endl;// 7
}

引用和常引用是不同的参数,可以重载函数。

有一些编译器定义的常量字符串可以使用:

1
2
3
4
5
cout<<__func__<<endl;
cout<<__FILE__<<endl;
cout<<__LINE__<<endl;
cout<<__TIME__<<endl;
cout<<__DATE__<<endl;

对于函数指针而言,以下两种等价:

1
2
p = func;
p = &func;

返回值可以为函数指针,可以通过using来帮助定义。

第七章 类

可以在变量前加mutable,这样即使是const成员函数也能修改它。

返回*this时候如果不返回引用,则会拷贝。

用赋值符号当构造函数的时候,不能赋值给常量和引用。

初始化列表顺序不确定。

c11有委托构造函数,可以用自己的其他构造函数来构造。

可以加explicit来防止隐式转换。explicit只应该出现在声明,出现一次就行。

聚合类:都是public,没有构造函数,没有类内初始值,并且没有virtual函数,可以用花括号初始化。

当类所有数据成员都是字面值,且包含constexpr构造函数,类内初始值没有或者是常量表达式,自带析构函数的可以成为字面值常量类。

第八章 IO库

IO库有三种:

1
2
3
4
5
6
#include <iostream>
//istream wistream读 ostream wostream写 iostream wiostream读写
#include <fstream>
//文件
#include <sstream>
//字符串

IO对象没有拷贝和赋值。

io库有条件状态,如崩溃,操作失败,eof等。

程序崩溃则缓冲区不会刷新。

我们可以将一个istream关联到ostream上,如默认cin总关联到cout上。这样cin时候会刷新cout缓冲区。

文件流对象时候允许接收文件名字符串为构造参数,创建ifstream并打开该文件。

如果定义了空文件流对象,可以随后用open关联到文件流。

流存在文件模式,如app每次写之前均定位到文件末尾等。

第九章 顺序容器

迭代器范围是左闭合右开。

标准库array具有固定大小。

如果容器是vector或string,则存储空间重新分配后所有迭代器,指针,引用都失效。没重新分配则指向之后的迭代器,指针引用会失效。

deque插入首尾之外的位置都会失效。如果首尾,则只有迭代器会失效。

对于list和forward_list,添加元素都有效。

删除元素后,list和forward_list其他位置迭代器有效,deque则首尾之外全失效,删尾巴会尾后迭代器失效,其他不影响。删除首则都不影响。对于vector或string则之前的有效。

每次插入后重新调用end()来便于不失效。

string有find rfind find_first_of find_last_of find_first_not_of find_last_not_of搜索操作。

stack queue priority_queue属于适配器,可以接收已有的容器类型,也可以更改容器类型。

第十章 泛型算法

迭代器使得算法不依赖于容器,但算法依赖于元素类型操作。泛型算法只运行于迭代器之上,不会改变底层容器大小。

算法只读不改变元素,如find,count,accumulate等。

equal接收三个迭代器,第一个序列的头,第一个序列的尾,第二个序列的头(因为元素数量至少要一样多)。

fill可以写入元素 ,但首先要创建元素。

unique返回迭代器,不重复的在前面,重复的在后面。算法不增加删除,因此要在之后erase。

插入迭代器:可以用来插入元素。

流迭代器:绑定子啊输入输出流上,可以遍历关联的IO流。

移动迭代器:不是拷贝而是移动元素。

Author

王钦砚

Posted on

2021-03-08

Licensed under

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×