1 补充知识点
c与c++区别
c语言和C++不能说一个面向过程,一个面向对象:
- 面向过程和面向对象只是一种编程思想,不是针对某一种语言的。Linux内核中的C语言就是运用面向对象的思想编写的。
- 只是说,在实现面向对象编程时,C++提供的语法实现起来比C语言更方便;仅此而已。
g++编码选项
-std=c++11
表示使用c++11的标准来编译。因为C++11基本上是行业标准-ansi
关闭gnu的特性,使代码全面符合ansi要求,从而具有高移植性。避免包含一些只有gnu独有的语法
c/c++ extern关键字
声明函数或变量。函数可以省略,但变量省略就成定义了,而不是声明。声明不会分配内存,而定义会占用内存空间。
c++ const
关键字与c语言差别:
- C++ 对 const 的特性做了调整,C++ 规定,全局 const 变量的作用域仍然是当前文件,在其他文件中是不可见的,这和添加了static关键字的效果类似
- 使用的是 GCC,那么可以通过添加 extern 关键字来增大 C++ 全局 const 变量的可见范围,其他文件使用 extern 声明后就可以使用了。
c++ new 和 delete
C++ 中的 new 和 delete 分别用来分配和释放内存,它们与C语言中 malloc()、free() 最大的一个不同之处在于:
- 用 new 分配内存时会调用构造函数,用 delete 释放内存时会调用析构函数。构造函数和析构函数对于类来说是不可或缺的,所以在C++中我们非常鼓励使用 new 和 delete。
对指针的理解
- 基本类型变量仅仅是地址的注记符而已,这个变量就是数据的内存首地址,假如数据类型为int,则从首地址开始的连续四个内存地址,都用来存储这个int类型的数据。
- 编译时,会将变量名替换为首地址
- 而指针变量,它注记指代的内存块,存储的不是数据,而是另一片内存地址,而这另一片内存地址才是真实的数据。
- 所以:使用指针是间接获取数据,使用变量名是直接获取数据,前者比后者的代价要高
2 STL(standard Template Libray)
模板所支持的类型是宽泛的,没有限制的,我们可以使用任意类型来替换,这种编程方式称为泛型编程
STL是基于泛型编程思想,利用模板技术。包含了计算机科学领域常用的基本数据结构和基本算法
2.1 顺序容器
顺序容器(也称"序列式容器")将一组具有相同类型的元素****以严格的线性形式组织起来。主要分为三类: vector、deque、list
vector:
- 内部实现实际上是动态数组,存取任何元素都能在常数时间内完成,在尾端增删元素具有较佳的性能;头部插入要整体移动所有数据,效率低
- vector有多个构造函数,默认的构造函数是构造一个初始长度为0的内存空间,且分配的内存空间是以2的倍数动态增长的,在push_back的过程中,若发现分配的内存空间不足,则重新分配一段连续的内存空间,其大小是现在连续空间的2倍,在将原先空间中的元素复制到新的空间中,性能消耗较大。
- 随机访问效率高
deque:
- 类似vector的结构,随机访问效率高。
- 不同之处:vector还维护首地址,双端插入的效率都很高;vector按块存储,在扩展空间的时候新增一个块,不用复制,效率较高
list:
- 双向链表结构
- 随机访问效率低,都要遍历
- 高效的随机插入/删除操作;尤其在首尾 插入,效率很高,只需要改变元素的指针。
2.2 关联容器之set、multiset、map、multimap
关联容器内的元素是排序的,插入任何元素,都能按照相应的排序准则来确定位置,特点是在查找时具有非常好的性能,通常以平衡二叉树方式实现,插入和检索的时间都是O(logn)
set和multiset:
- set是一种关联性容器,底层使用红黑树实现,插入删除操作时仅仅移动指针即可,不涉及内存的移动和拷贝,所以效率比较高
- set中的元素都是唯一的,而且默认情况下会对元素进行升序排列
- set和multiset的区别是:set插入的元素不能相同,但是multiset可以相同
map和multimap:
- map容器提供一个键值对(key/value)容器,map与multimap差别仅仅在于multiple允许一个键对应多个值
- map内部自建一棵红黑树(一种自平衡二叉树),这棵树具有数据自动排序的功能,所以在map内部所有的数据都是有序的,以二叉树的形式进行组织
2.3 容器适配器adapter
适配器(Adaptors)是标准库中的一个通用概念,容器、迭代器和函数****都有适配器。本质上,一个适配器是一种机制,能使某种事物的行为看起来像另外一种事物一样。一个容器适配器(Container adaptors)在一种已有的容器类型上面进行包装,使其行为看起来像一种不同的类型
标准库定义了三个序列容器适配器:
- stack: 栈,先进后出;底层使用deque
- queue:队列,先进先出;底层使用deque
- priority_queue: 优先级队列:优先级大的先出队,底层数据结构默认是大根堆。优先级队列底层默认把数据组成一个大根堆结构,而大根堆的构建就需要在一个内存连续的数组上(堆中结点和它左右孩子的关系是通过下标计算的),vector动态数组底层是绝对连续的,而deque是分段连续的,所以用vector
迭代器
迭代器用于遍历对象集合的元素,从实现角度来看,**迭代器是一种将***operator、operator->、operator++、operator–**等指针操作予以重载的class template
。
2.4 函数对象
如果一个类将()运算符重载为成员函数,这个类就称为函数对象类,这个类的对象就是函数对象。函数对象是一个对象,但是使用的形式看起来像函数调用,实际上也执行了函数调用,因而得名。
3 C++11
C++11标准,就是新增的特性
1 auto关键字
auto关键字和static关键字区别:
1、static变量存放在静态存储区,在程序整个运行期间都不释放;而auto变量存放在动态存储区,随着生命周期的结束而立即释放。
2、static变量只赋值一次,以后就不用赋值;而auto变量在函数每调用一次都要赋初值。
3、如果用户不对static变量赋初值,则默认为0或’\0’;而auto变量为不确定值。
c++默认就是auto,所以auto关键字写和不写没区别。
C++11给auto关键字赋予了新的含义:
- 使用它来做自动类型推导。也就是说,使用了 auto 关键字以后,编译器会在编译期间自动推导出变量的类型,这样我们就不用手动指明变量的数据类型了
nullptr关键字
用nullptr表示空指针
更优雅的初始化方法
C++11之前,只有数组能使用初始化列表:int arr[3] = {1, 2, 3}
;
c++11开始,各种数据类型都可以用初始化列表来初始化:map(int, string){{1, "hello"}, {2, "hh"}}
;
基于范围的for循环
void foo() {
vector<int> v{1, 2, 3};
for (auto x:v) {
cout << x << endl;
}
}
编译断言:static_assert
编译期间断言:
// c++ 11 compile-time assert
static_assert ( bool_constexpr , string )
// bool_constexpr: 常量表达式(不能包含变量)string:
// 如果bool_constexpr表达式为false, 这个string就是编译时候报的错误
// run-time assert
assert(ptr != NULL)
lambda表达式
格式:
[capture list] (params list)mutable exception-> return type {
function body
}
除了捕获列表[capture list]
,其他项参数列表、mutable、异常、返回类型都可以省略
捕获列表的捕获方式和捕获范围:
function对象包装器
可以降不同的可调用对象,包装成一个类模板,从一个入口统一调用:functon<return type<typename, ...>>
bind
类似python的偏函数,将可调用对象的部分函数固定起来。
auto x = [](int a, int b) {
return a+b;
}
auto y = bind(x, 1, 2);
cout << y() << endl;
智能指针
智能指针在栈区创建,指向堆区的对象。
函数结束的时候,会调用智能指针的析构函数,析构函数在调用的时候,会释放堆区管理的对象。
- auto_ptr:c++11废弃:因为auto_ptr基于所有权转移的语义,即将一个旧的auto_ptr赋值给另外一个新的auto_ptr时,旧的那一个就不再拥有该指针的控制权,内部指针被赋值为null。
- unique_ptr:推荐的智能指针。遵循独占语义的智能指针,在任何时间点,资源只能唯一地被一个unique_ptr所占有,当其离开作用域时自动析构。资源所有权的转移只能通过std::move()而不能通过赋值
- shared_ptr:**多个智能指针可以指向相同对象,**该对象和其相关资源会在“最后一个引用被销毁”时候释放。
- shared_ptr智能指针实现的时候,它内部使用了引用计数技术。
- 构造函数将引用计数初始化为1;
- 拷贝构造函数、赋值运算符重载函数将对象的引用计数加1;
- 析构函数调用的是release函数,release将引用计数减1,当减到0时,释放。
- weak_ptr:配合shared_ptr,解决了shared_ptr互相引用,导致的内存泄漏问题
#include <iostream>
#include <memory>
#include <string>
/*实现智能指针
shared_ptr:内部维护了一个引用计数。当引用计数为0的时候,释放堆区资源
拷贝构造函数:增加引用计数
析构函数:调用release函数
构造函数:引用计数+1
运算符重载=:引用计数+1
*/
using namespace std;
template<typename T>
class MySharedPtr {
public:
// 构造函数
explicit MySharedPtr(T *ptr=nullptr): m_ptr(ptr), m_counter(new long(1)) {
cout << "con init" << endl;
}
// 拷贝构造函数
MySharedPtr(const MySharedPtr<T> &other) {
this->m_ptr = other.m_ptr;
this->m_counter = other.m_counter;
(*(this->m_counter))++;
cout << "copy init" << endl;
}
// 赋值运算符重载;返回不是引用的话,会发现return语句又调用了一次构造函数:其实return了一个局部对象,这个局部对象赋值给外层变量,相当于一次拷贝构造
MySharedPtr<T> & operator=(MySharedPtr<T> &other) {
if (this != &other) {
this->release();
this->m_ptr = other.m_ptr;
this->m_counter = other.m_counter;
(*(this->m_counter))++;
}
cout << "= init" << endl;
return *this;
}
T & operator*(void) {
return *(this->m_ptr);
}
T *operator->(void) {
return m_ptr;
}
int get_counter(void) {
return *m_counter;
}
// 析构函数
~MySharedPtr(){
release();
cout << "m_counter: " << *m_counter << endl;
}
void show() {
cout << "m_counter: " << *m_counter << endl;
}
private:
T *m_ptr;
long *m_counter;
protected:
void release() {
(*(m_counter))--;
if ((*m_counter) == 0) {
if (m_ptr != nullptr) {
delete m_ptr;
}
delete m_counter;
}
}
};
int main(int argc, char **argv)
{
const string *ptr = new string {"hello 222"};
MySharedPtr<const string> p1(ptr);
p1.show();
MySharedPtr<const string> p2(p1);
p2.show();
cout << "---------" << endl;
MySharedPtr<const string> p3;
p3 = p1;
p3.show();
cout << "------+++---" << endl;
MySharedPtr<const string> &p4 = p3; // 引用必须在定义的同时初始化,引用不会创建新的对象,再次体现引用的本质是指针,指针仅仅是一个指针变量,并不会创建对象
p4.show();
return 0;
}