C++
c++特性学习
命名空间
在C++编程中,在程序的头两行,经常会有这样一句话
1 | using namespace std; |
我们平常使用的cin、cout语句都是借用了命名空间std中的,如果不直接引入命名空间,每一次我们都需要进行这样的调用:
1 | std::cin; |
这样将会显得非常琐碎,所以会用上开头的一整句放在程序前段,便于使用其中的内容
在C++中,头文件不应该包含命名空间声明
string类型
在c++中,string类型表示可变长的字符序列,同时当我们需要用到string类时,需要加上
1 |
|
直接初始化和拷贝初始化
读入含有空格的字符串
1 | string a; |
指针
引用
引用(reference)为对象起了另外一个名字,引用类型引用(refers to)另外一种类型。通过将声明符写成&d的形式来定义引用类型,其中d时声明的变量名
1 | int val=1024; |
引用并非对象,相反的,它只是为一个已经存在的对象所起的另外一个名字
1 | //定义了一个引用之后,对其进行的所有操作都是在与之绑定的对象上进行的 |
指针
指针(pointer)是“指向”(point to),与引用类似,指针也实现了对其他对象的间接访问,但是1.指针本身就是一个对象,允许对指针赋值和拷贝。2.指针无须在定义时赋初值。和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值
1 | int *ip1,*ip2;//ip1和ip2都是指向int型对象的指针 |
指针值
指针的值(即地址)应属下列4种状态之一:
- 指向一个对象
- 指向紧邻对象所占空间的下一个位置
- 空指针,意味着指针没有指向任何对象
- 无效指针,也就是上述情况之外的其他值
利用指针访问对象
1 | int ival=42; |
空指针
1 | int *p1=nullptr; |
建议:使用指针的时候,一定要进行初始化,使用未经初始化的指针是引发运行时错误的一大原因
指针赋值
1 | int i=42; |
void*指针
void是一种特殊的的指针类型,可用于存放任意对象的地址。一个void指针存放着一个地址,这一点和其他指针类似
1 | double obj=3.14,*pd=&obj; |
指向指针的指针
一般来说,声明符中修饰符的个数并没有限制,当有多个修饰符连写在一起时,按照其逻辑关系详加解释即可。
1 | int ival=1024; |
指向指针的引用
引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用
1 | int i=42; |
关于指针
关于指针,有这样两个符号出现的频率非常之高:
*、&
对于他们的用法,我们有必要做一个非常深刻的研究,首先我们明确两点:
- &代表引用,即取址运算符
- *代表声明此变量为指针变量,即是指向某个地址的变量
接下来举一个最简单的例子
1 | int a=1;//声明一个整型变量a |
最基本的,我们可以通过指针对指向的变量进行操作:
1 | cout<<a<<endl;//结果为1 |
const限定符
有时我们希望定义这样一种变量,它的值不能改变。例如,用一个变量来表示缓冲区的大小。使用变量的好处是当我们觉得缓冲区大小不再合适时,很容易对其进行调整。另一方面,也应随时警惕程序一不小心改变了这个值。为了满足这一要求,可以用关键字const对变量的类型加以限定
1 | const int bufSize=512;//输入缓冲区大小 |
默认状态下,const对象仅在文件内有效
当以编译时初始化的方式定义一个const对象时,就如对bufSize的定义一样:
1 | const int bufSize=512;//输入缓冲区大小 |
编译器将在编译过程中把用到该变量的地方都替换成对应的值。也就是说,编译器会找到代码中所有用到bufSize的地方,然后用512替换
某些时候有这样一种const变量,它的初始值不是一个常量表达式,但又确实有必要在文件间共享。这种情况下,我们不希望编译器为每个文件分别生成独立的变量。相反,我们想让这类const对象像其他对象一样工作,也就是说,只在一个文件中定义const,而在其他多个文件中声明并使用它
1 | //解决方法是加上extern关键字即可 |
初始化和对const的引用
1 | int i=42; |
必须认识到,常量引用仅对引用可参与的操作作出了限定,对于引用的对象本身是不是一个常量未作限定。因为对象也可能是个非常量,所以允许通过其他途径改变它的值
1 | int i=42; |
指向常量的指针(pointer to const)不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的地址
1 | const double pi=3.14;//pi是个常量,它的值不能改变 |
const指针
指针是对象而引用不是,因此迹象其他对象类型一样,允许把指针本身定为常量。常量指针(const pointer)必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。把*放在const关键字之前用以说明指针是一个常量,这样的书写形式隐含着一层意味,即不变的是指针本身的值而非指向的那个值
1 | int errNumb=0; |
顶层const
1 | int i=0; |
指针本身是一个对象,它又可以指向另外一个对象,因此,指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。用名词顶层const(top-level const)表示指针本身是个常量,而用名词底层const(low-level const)表示指针所指的对象是一个常量
更一般的,顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用,如算术类型、类、指针等。底层const则与指针和引用等符合类型的基本类型部分有关。比较特殊的是,指针类型既可以是顶层const也可以是底层const,这一点和其它类型相比区别明显:
1 | int i=0; |
容器
定义
一个容器就是一些特定类型对象的集合。顺序容器(sequential container)为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应
顺序容器类型
vector:可变大小数组,支持快速随机访问,在尾部之外的位置插入或删除元素可能很慢
deque:双端队列。支持快速随机访问。在头尾位置插入/删除速度很快
list:双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快
forward:单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作都很快
array:固定大小数组。支持快速随机访问,不能添加或删除元素
string:与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度快
以下是选择容器时的基本原则
- 如果没有绝对的理由,使用vector
- 如果程序有很多小元素且对额外空间开销要求很高,则不要使用list或者forward_list
- 程序要求随机访问元素,使用vector或者deque
- 如果要在容器中间位置进行增删操作,使用list或者forward_list
- 头尾位置增删,使用deque
迭代器
迭代器的元素范围为左闭合区间,标准数学描述为
[begin,end)
使用迭代器访问元素
1 | vector<int>::iterator it; |
类型成员
每个容器都定义了多个类型,我们熟悉的有三种:size_type,iterator,const_iterator
1 | //iter时通过list<string>定义的一个迭代器类型 |
容器中还有begin和end操作,生成指向容器中第一个元素和尾元素之后位置的迭代器,而成员中也有begin和end类,即为左闭合区间的范围
容器拷贝
两个容器拷贝时,要求两个容器的数据类型和容器类型都要匹配
1 | //每个容器由三个元素,用给定的初始化器进行初始化 |
容器元素构造
1 | vector<int> ivec(10,-1); //10个int元素,每个都初始化为-1 |
容器赋值运算
1 | c1=c2;//将c1中的元素替换为c2中元素的拷贝。c1和c2必须具有相同的类型 |
添加元素
1 | //array不支持这些操作 |
访问元素
1 | at和下标操作只适用于string,vector,deque和array |
访问成员函数返回的是引用
1 | if(!c.empty()){ |
删除元素
1 | c.pop_back();//删除c中尾元素,若c为空,函数行为未定义,函数返回void |
容器操作可能使迭代器失效
向容器添加元素后
- 对于vector和string,且存储空间被重回新分配,则指向容器的迭代器、指针、引用都会失效。如果存储空间未重新分配,指向插入位置之前的元素的迭代器、指针和引用仍有效,但指向插入位置之后元素的迭代器、指针、引用都会失效
- 对于deque,插入到除首尾位置之外的任何位置都会导致迭代器、指针和引用失效。如果在首位位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效
- list和forward_list总是有效
删除元素后,指向被删除元素的迭代器、指针、引用都会失效
由于向迭代器添加元素和从迭代器删除元素的代码可能会使迭代器失效,因此必须保证每次改变容器的操作之后都正确地重新定位迭代器。这个建议对vector,string和deque尤为重要
Vector
为了支持快速随机访问,vector将元素连续存储,vector通常会分配比新的空间需求更大的内存空间预留作为备用
vector管理容量的成员函数
1 | shrink_to_fit//只适用于vector,string和deque |
只有在执行insert操作时size与capacity相等,或者调用resize或reserve时给定的大小超过当前capacity,vector才可能重新分配内存空间。会分配多少超过给定容量的额外空间,取决于具体实现
string
构造string
1 | string s(cp,n);//s是cp指向的数组中前n个字符的拷贝,此数组至少应该包含n个字符 |
substr操作
1 | string s("hello world"); |
改变string的其他方法
1 | s.insert(s.size(),5,'!');//在s末尾插入5个感叹号 |
append和replace函数
1 | string s("C++ Primer"),s2=s;//将s和s2初始化为"C++ Primer" |
string搜索操作
1 | string name("AnnaBelle"); |
搜索
1 | string::size_type pos=0; |
compare函数
1 | string s("kevin"); |
数值转换
1 | int i=42; |
容器适配器
除了顺序容器外,标准库还定义了三个顺序容器适配器:stack,quene和priorty_queue,适配器是标准库中的一个通用概念,一个适配器是一种机制,能使某种事物的行为看起来像一种不同的类型
1 | //所有容器适配器都支持的操作和类型 |
定义一个适配器
1 | //假设deq是一个deque<int>,我们可以用deq来初始化一个新的stack |
栈适配器
1 | stack<int> intstack;//空栈 |
IO
IO库类型和头文件
1 | //头文件 |
IO对象无拷贝或赋值
1 | ofstream out,out2; |
条件状态
IO操作一个与生俱来的问题就是可能发生错误。一些错误是可恢复的,而其他错误则发生在系统深处,已经超出了应用程序可以修正的范围。
1 | strm::iostate strm是一种IO类型 |
一个流一旦发生错误,其上后续的IO操作都会失败。只有当一个流处于无错状态时,我们才可以从它读取数据,向它写入数据。由于流可能处于错误状态,因此代码通常应该在使用
IO类型之间的关系
概念上设备类型和字符大小都不会影响我们要执行的IO操作。例如我们可以用>>读取数据,无论是从控制台窗口还是磁盘文件还是string读取。此种功能是由继承机制实现的。
确保一个流对象的状态
将其当作一个条件来使用
1 | while(cin>>word) |
泛型
定义
多线程
在了解线程之前,我们可以先了解一下线程、进程之间的区别以及联系
进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式和的描述,进程是程序的实体
线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务,线程是独立调度和分派的基本单位,线程可以为操作系统内核调度的内核线程,如win32线程;由用户进程自行调度的用户线程,如Linux平台的POSIX Thread;或者由内核与用火狐进程,如windows7的线程,进行混合调度
一个进程可以有很多线程,每条线程并行执行不同的任务